migration symfony 5 4 (#300)

* symfony 5.4 (diff dev)

* symfony 5.4 (working)

* symfony 5.4 (update autoload)

* symfony 5.4 (remove swiftmailer mailer implementation)

* symfony 5.4 (php doc and split Global accessor class)


### Impacted packages:

composer require php:">=7.2.5 <8.0.0" symfony/console:5.4.* symfony/dotenv:5.4.* symfony/framework-bundle:5.4.* symfony/twig-bundle:5.4.* symfony/yaml:5.4.* --update-with-dependencies

composer require symfony/stopwatch:5.4.* symfony/web-profiler-bundle:5.4.* --dev --update-with-dependencies
This commit is contained in:
bdalsass
2022-06-16 09:13:24 +02:00
committed by GitHub
parent abb13b70b9
commit 79da71ecf8
2178 changed files with 87439 additions and 59451 deletions

View File

@@ -1,3 +0,0 @@
vendor/
composer.lock
phpunit.xml

View File

@@ -1,6 +1,227 @@
CHANGELOG
=========
5.4
---
* Add `set_locale_from_accept_language` config option to automatically set the request locale based on the `Accept-Language`
HTTP request header and the `framework.enabled_locales` config option
* Add `set_content_language_from_locale` config option to automatically set the `Content-Language` HTTP response header based on the Request locale
* Deprecate the `framework.translator.enabled_locales`, use `framework.enabled_locales` instead
* Add autowiring alias for `HttpCache\StoreInterface`
* Add the ability to enable the profiler using a request query parameter, body parameter or attribute
* Deprecate the `AdapterInterface` autowiring alias, use `CacheItemPoolInterface` instead
* Deprecate the public `profiler` service to private
* Deprecate `get()`, `has()`, `getDoctrine()`, and `dispatchMessage()` in `AbstractController`, use method/constructor injection instead
* Deprecate the `cache.adapter.doctrine` service
* Add support for resetting container services after each messenger message
* Add `configureContainer()`, `configureRoutes()`, `getConfigDir()` and `getBundlesPath()` to `MicroKernelTrait`
* Add support for configuring log level, and status code by exception class
* Bind the `default_context` parameter onto serializer's encoders and normalizers
* Add support for `statusCode` default parameter when loading a template directly from route using the `Symfony\Bundle\FrameworkBundle\Controller\TemplateController` controller
* Deprecate `translation:update` command, use `translation:extract` instead
* Add `PhpStanExtractor` support for the PropertyInfo component
* Add `cache.adapter.doctrine_dbal` service to replace `cache.adapter.pdo` when a Doctrine DBAL connection is used.
5.3
---
* Deprecate the `session.storage` alias and `session.storage.*` services, use the `session.storage.factory` alias and `session.storage.factory.*` services instead
* Deprecate the `framework.session.storage_id` configuration option, use the `framework.session.storage_factory_id` configuration option instead
* Deprecate the `session` service and the `SessionInterface` alias, use the `Request::getSession()` or the new `RequestStack::getSession()` methods instead
* Add `AbstractController::renderForm()` to render a form and set the appropriate HTTP status code
* Add support for configuring PHP error level to log levels
* Add the `dispatcher` option to `debug:event-dispatcher`
* Add the `event_dispatcher.dispatcher` tag
* Add `assertResponseFormatSame()` in `BrowserKitAssertionsTrait`
* Add support for configuring UUID factory services
* Add tag `assets.package` to register asset packages
* Add support to use a PSR-6 compatible cache for Doctrine annotations
* Deprecate all other values than "none", "php_array" and "file" for `framework.annotation.cache`
* Add `KernelTestCase::getContainer()` as the best way to get a container in tests
* Rename the container parameter `profiler_listener.only_master_requests` to `profiler_listener.only_main_requests`
* Add service `fragment.uri_generator` to generate the URI of a fragment
* Deprecate registering workflow services as public
* Deprecate option `--xliff-version` of the `translation:update` command, use e.g. `--format=xlf20` instead
* Deprecate option `--output-format` of the `translation:update` command, use e.g. `--format=xlf20` instead
5.2.0
-----
* Added `framework.http_cache` configuration tree
* Added `framework.trusted_proxies` and `framework.trusted_headers` configuration options
* Deprecated the public `form.factory`, `form.type.file`, `translator`, `security.csrf.token_manager`, `serializer`,
`cache_clearer`, `filesystem` and `validator` services to private.
* Added `TemplateAwareDataCollectorInterface` and `AbstractDataCollector` to simplify custom data collector creation and leverage autoconfiguration
* Add `cache.adapter.redis_tag_aware` tag to use `RedisCacheAwareAdapter`
* added `framework.http_client.retry_failing` configuration tree
* added `assertCheckboxChecked()` and `assertCheckboxNotChecked()` in `WebTestCase`
* added `assertFormValue()` and `assertNoFormValue()` in `WebTestCase`
* Added "--as-tree=3" option to `translation:update` command to dump messages as a tree-like structure. The given value defines the level where to switch to inline YAML
* Deprecated the `lock.RESOURCE_NAME` and `lock.RESOURCE_NAME.store` services and the `lock`, `LockInterface`, `lock.store` and `PersistingStoreInterface` aliases, use `lock.RESOURCE_NAME.factory`, `lock.factory` or `LockFactory` instead.
5.1.0
-----
* Removed `--no-backup` option from `translation:update` command (broken since `5.0.0`)
* Added link to source for controllers registered as named services
* Added link to source on controller on `router:match`/`debug:router` (when `framework.ide` is configured)
* Added the `framework.router.default_uri` configuration option to configure the default `RequestContext`
* Made `MicroKernelTrait::configureContainer()` compatible with `ContainerConfigurator`
* Added a new `mailer.message_bus` option to configure or disable the message bus to use to send mails.
* Added flex-compatible default implementation for `MicroKernelTrait::registerBundles()`
* Deprecated passing a `RouteCollectionBuilder` to `MicroKernelTrait::configureRoutes()`, type-hint `RoutingConfigurator` instead
* The `TemplateController` now accepts context argument
* Deprecated *not* setting the "framework.router.utf8" configuration option as it will default to `true` in Symfony 6.0
* Added tag `routing.expression_language_function` to define functions available in route conditions
* Added `debug:container --deprecations` option to see compile-time deprecations.
* Made `BrowserKitAssertionsTrait` report the original error message in case of a failure
* Added ability for `config:dump-reference` and `debug:config` to dump and debug kernel container extension configuration.
* Deprecated `session.attribute_bag` service and `session.flash_bag` service.
5.0.0
-----
* Removed support to load translation resources from the legacy directories `src/Resources/translations/` and `src/Resources/<BundleName>/translations/`
* Removed `ControllerNameParser`.
* Removed `ResolveControllerNameSubscriber`
* Removed support for `bundle:controller:action` to reference controllers. Use `serviceOrFqcn::method` instead
* Removed support for PHP templating, use Twig instead
* Removed `Controller`, use `AbstractController` instead
* Removed `Client`, use `KernelBrowser` instead
* Removed `ContainerAwareCommand`, use dependency injection instead
* Removed the `validation.strict_email` option, use `validation.email_validation_mode` instead
* Removed the `cache.app.simple` service and its corresponding PSR-16 autowiring alias
* Removed cache-related compiler passes and `RequestDataCollector`
* Removed the `translator.selector` and `session.save_listener` services
* Removed `SecurityUserValueResolver`, use `UserValueResolver` instead
* Removed `routing.loader.service`.
* Service route loaders must be tagged with `routing.route_loader`.
* Added `slugger` service and `SluggerInterface` alias
* Removed the `lock.store.flock`, `lock.store.semaphore`, `lock.store.memcached.abstract` and `lock.store.redis.abstract` services.
* Removed the `router.cache_class_prefix` parameter.
4.4.0
-----
* Added `lint:container` command to check that services wiring matches type declarations
* Added `MailerAssertionsTrait`
* Deprecated support for `templating` engine in `TemplateController`, use Twig instead
* Deprecated the `$parser` argument of `ControllerResolver::__construct()` and `DelegatingLoader::__construct()`
* Deprecated the `controller_name_converter` and `resolve_controller_name_subscriber` services
* The `ControllerResolver` and `DelegatingLoader` classes have been marked as `final`
* Added support for configuring chained cache pools
* Deprecated calling `WebTestCase::createClient()` while a kernel has been booted, ensure the kernel is shut down before calling the method
* Deprecated `routing.loader.service`, use `routing.loader.container` instead.
* Not tagging service route loaders with `routing.route_loader` has been deprecated.
* Overriding the methods `KernelTestCase::tearDown()` and `WebTestCase::tearDown()` without the `void` return-type is deprecated.
* Added new `error_controller` configuration to handle system exceptions
* Added sort option for `translation:update` command.
* [BC Break] The `framework.messenger.routing.senders` config key is not deeply merged anymore.
* Added `secrets:*` commands to deal with secrets seamlessly.
* Made `framework.session.handler_id` accept a DSN
* Marked the `RouterDataCollector` class as `@final`.
* [BC Break] The `framework.messenger.buses.<name>.middleware` config key is not deeply merged anymore.
* Moved `MailerAssertionsTrait` in `KernelTestCase`
4.3.0
-----
* Deprecated the `framework.templating` option, configure the Twig bundle instead.
* Added `WebTestAssertionsTrait` (included by default in `WebTestCase`)
* Renamed `Client` to `KernelBrowser`
* Not passing the project directory to the constructor of the `AssetsInstallCommand` is deprecated. This argument will
be mandatory in 5.0.
* Deprecated the "Psr\SimpleCache\CacheInterface" / "cache.app.simple" service, use "Symfony\Contracts\Cache\CacheInterface" / "cache.app" instead
* Added the ability to specify a custom `serializer` option for each
transport under`framework.messenger.transports`.
* Added the `RegisterLocaleAwareServicesPass` and configured the `LocaleAwareListener`
* [BC Break] When using Messenger, the default transport changed from
using Symfony's serializer service to use `PhpSerializer`, which uses
PHP's native `serialize()` and `unserialize()` functions. To use the
original serialization method, set the `framework.messenger.default_serializer`
config option to `messenger.transport.symfony_serializer`. Or set the
`serializer` option under one specific `transport`.
* [BC Break] The `framework.messenger.serializer` config key changed to
`framework.messenger.default_serializer`, which holds the string service
id and `framework.messenger.symfony_serializer`, which configures the
options if you're using Symfony's serializer.
* [BC Break] Removed the `framework.messenger.routing.send_and_handle` configuration.
Instead of setting it to true, configure a `SyncTransport` and route messages to it.
* Added information about deprecated aliases in `debug:autowiring`
* Added php ini session options `sid_length` and `sid_bits_per_character`
to the `session` section of the configuration
* Added support for Translator paths, Twig paths in translation commands.
* Added support for PHP files with translations in translation commands.
* Added support for boolean container parameters within routes.
* Added the `messenger:setup-transports` command to setup messenger transports
* Added a `InMemoryTransport` to Messenger. Use it with a DSN starting with `in-memory://`.
* Added `framework.property_access.throw_exception_on_invalid_property_path` config option.
* Added `cache:pool:list` command to list all available cache pools.
4.2.0
-----
* Added a `AbstractController::addLink()` method to add Link headers to the current response
* Allowed configuring taggable cache pools via a new `framework.cache.pools.tags` option (bool|service-id)
* Allowed configuring PDO-based cache pools via a new `cache.adapter.pdo` abstract service
* Deprecated auto-injection of the container in AbstractController instances, register them as service subscribers instead
* Deprecated processing of services tagged `security.expression_language_provider` in favor of a new `AddExpressionLanguageProvidersPass` in SecurityBundle.
* Deprecated the `Symfony\Bundle\FrameworkBundle\Controller\Controller` class in favor of `Symfony\Bundle\FrameworkBundle\Controller\AbstractController`.
* Enabled autoconfiguration for `Psr\Log\LoggerAwareInterface`
* Added new "auto" mode for `framework.session.cookie_secure` to turn it on when HTTPS is used
* Removed the `framework.messenger.encoder` and `framework.messenger.decoder` options. Use the `framework.messenger.serializer.id` option to replace the Messenger serializer.
* Deprecated the `ContainerAwareCommand` class in favor of `Symfony\Component\Console\Command\Command`
* Made `debug:container` and `debug:autowiring` ignore backslashes in service ids
* Deprecated the `Templating\Helper\TranslatorHelper::transChoice()` method, use the `trans()` one instead with a `%count%` parameter
* Deprecated `CacheCollectorPass`. Use `Symfony\Component\Cache\DependencyInjection\CacheCollectorPass` instead.
* Deprecated `CachePoolClearerPass`. Use `Symfony\Component\Cache\DependencyInjection\CachePoolClearerPass` instead.
* Deprecated `CachePoolPass`. Use `Symfony\Component\Cache\DependencyInjection\CachePoolPass` instead.
* Deprecated `CachePoolPrunerPass`. Use `Symfony\Component\Cache\DependencyInjection\CachePoolPrunerPass` instead.
* Deprecated support for legacy translations directories `src/Resources/translations/` and `src/Resources/<BundleName>/translations/`, use `translations/` instead.
* Deprecated support for the legacy directory structure in `translation:update` and `debug:translation` commands.
4.1.0
-----
* Allowed to pass an optional `LoggerInterface $logger` instance to the `Router`
* Added a new `parameter_bag` service with related autowiring aliases to access parameters as-a-service
* Allowed the `Router` to work with any PSR-11 container
* Added option in workflow dump command to label graph with a custom label
* Using a `RouterInterface` that does not implement the `WarmableInterface` is deprecated.
* Warming up a router in `RouterCacheWarmer` that does not implement the `WarmableInterface` is deprecated and will not
be supported anymore in 5.0.
* The `RequestDataCollector` class has been deprecated. Use the `Symfony\Component\HttpKernel\DataCollector\RequestDataCollector` class instead.
* The `RedirectController` class allows for 307/308 HTTP status codes
* Deprecated `bundle:controller:action` syntax to reference controllers. Use `serviceOrFqcn::method` instead where `serviceOrFqcn`
is either the service ID or the FQCN of the controller.
* Deprecated `Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser`
* The `container.service_locator` tag of `ServiceLocator`s is now autoconfigured.
* Add the ability to search a route in `debug:router`.
* Add the ability to use SameSite cookies for sessions.
4.0.0
-----
* The default `type` option of the `framework.workflows.*` configuration entries is `state_machine`
* removed `AddConsoleCommandPass`, `AddConstraintValidatorsPass`,
`AddValidatorInitializersPass`, `CompilerDebugDumpPass`, `ConfigCachePass`,
`ControllerArgumentValueResolverPass`, `FormPass`, `PropertyInfoPass`,
`RoutingResolverPass`, `SerializerPass`, `ValidateWorkflowsPass`
* made `Translator::__construct()` `$defaultLocale` argument required
* removed `SessionListener`, `TestSessionListener`
* Removed `cache:clear` warmup part along with the `--no-optional-warmers` option
* Removed core form types services registration when unnecessary
* Removed `framework.serializer.cache` option and `serializer.mapping.cache.apc`, `serializer.mapping.cache.doctrine.apc` services
* Removed `ConstraintValidatorFactory`
* Removed class parameters related to routing
* Removed absolute template paths support in the template name parser
* Removed support of the `KERNEL_DIR` environment variable with `KernelTestCase::getKernelClass()`.
* Removed the `KernelTestCase::getPhpUnitXmlDir()` and `KernelTestCase::getPhpUnitCliConfigArgument()` methods.
* Removed the "framework.validation.cache" configuration option. Configure the "cache.validator" service under "framework.cache.pools" instead.
* Removed `PhpStringTokenParser`, use `Symfony\Component\Translation\Extractor\PhpStringTokenParser` instead.
* Removed `PhpExtractor`, use `Symfony\Component\Translation\Extractor\PhpExtractor` instead.
* Removed the `use_strict_mode` session option, it's is now enabled by default
3.4.0
-----

View File

@@ -11,30 +11,22 @@
namespace Symfony\Bundle\FrameworkBundle\CacheWarmer;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\NullAdapter;
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
use Symfony\Component\Cache\Adapter\ProxyAdapter;
use Symfony\Component\Config\Resource\ClassExistenceResource;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
abstract class AbstractPhpFileCacheWarmer implements CacheWarmerInterface
{
private $phpArrayFile;
private $fallbackPool;
/**
* @param string $phpArrayFile The PHP file where metadata are cached
* @param CacheItemPoolInterface $fallbackPool The pool where runtime-discovered metadata are cached
* @param string $phpArrayFile The PHP file where metadata are cached
*/
public function __construct($phpArrayFile, CacheItemPoolInterface $fallbackPool)
public function __construct(string $phpArrayFile)
{
$this->phpArrayFile = $phpArrayFile;
if (!$fallbackPool instanceof AdapterInterface) {
$fallbackPool = new ProxyAdapter($fallbackPool);
}
$this->fallbackPool = $fallbackPool;
}
/**
@@ -47,15 +39,17 @@ abstract class AbstractPhpFileCacheWarmer implements CacheWarmerInterface
/**
* {@inheritdoc}
*
* @return string[] A list of classes to preload on PHP 7.4+
*/
public function warmUp($cacheDir)
public function warmUp(string $cacheDir)
{
$arrayAdapter = new ArrayAdapter();
spl_autoload_register([ClassExistenceResource::class, 'throwOnRequiredClass']);
try {
if (!$this->doWarmUp($cacheDir, $arrayAdapter)) {
return;
return [];
}
} finally {
spl_autoload_unregister([ClassExistenceResource::class, 'throwOnRequiredClass']);
@@ -66,24 +60,21 @@ abstract class AbstractPhpFileCacheWarmer implements CacheWarmerInterface
// so here we un-serialize the values first
$values = array_map(function ($val) { return null !== $val ? unserialize($val) : null; }, $arrayAdapter->getValues());
$this->warmUpPhpArrayAdapter(new PhpArrayAdapter($this->phpArrayFile, $this->fallbackPool), $values);
foreach ($values as $k => $v) {
$item = $this->fallbackPool->getItem($k);
$this->fallbackPool->saveDeferred($item->set($v));
}
$this->fallbackPool->commit();
return $this->warmUpPhpArrayAdapter(new PhpArrayAdapter($this->phpArrayFile, new NullAdapter()), $values);
}
/**
* @return string[] A list of classes to preload on PHP 7.4+
*/
protected function warmUpPhpArrayAdapter(PhpArrayAdapter $phpArrayAdapter, array $values)
{
$phpArrayAdapter->warmUp($values);
return (array) $phpArrayAdapter->warmUp($values);
}
/**
* @internal
*/
final protected function ignoreAutoloadException($class, \Exception $exception)
final protected function ignoreAutoloadException(string $class, \Exception $exception): void
{
try {
ClassExistenceResource::throwOnRequiredClass($class, $exception);
@@ -92,9 +83,7 @@ abstract class AbstractPhpFileCacheWarmer implements CacheWarmerInterface
}
/**
* @param string $cacheDir
*
* @return bool false if there is nothing to warm-up
*/
abstract protected function doWarmUp($cacheDir, ArrayAdapter $arrayAdapter);
abstract protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter);
}

View File

@@ -12,11 +12,10 @@
namespace Symfony\Bundle\FrameworkBundle\CacheWarmer;
use Doctrine\Common\Annotations\AnnotationException;
use Doctrine\Common\Annotations\CachedReader;
use Doctrine\Common\Annotations\PsrCachedReader;
use Doctrine\Common\Annotations\Reader;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\DoctrineProvider;
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
/**
* Warms up annotation caches for classes found in composer's autoload class map
@@ -31,13 +30,11 @@ class AnnotationsCacheWarmer extends AbstractPhpFileCacheWarmer
private $debug;
/**
* @param string $phpArrayFile The PHP file where annotations are cached
* @param CacheItemPoolInterface $fallbackPool The pool where runtime-discovered annotations are cached
* @param bool $debug Run in debug mode
* @param string $phpArrayFile The PHP file where annotations are cached
*/
public function __construct(Reader $annotationReader, $phpArrayFile, CacheItemPoolInterface $fallbackPool, $excludeRegexp = null, $debug = false)
public function __construct(Reader $annotationReader, string $phpArrayFile, string $excludeRegexp = null, bool $debug = false)
{
parent::__construct($phpArrayFile, $fallbackPool);
parent::__construct($phpArrayFile);
$this->annotationReader = $annotationReader;
$this->excludeRegexp = $excludeRegexp;
$this->debug = $debug;
@@ -46,7 +43,7 @@ class AnnotationsCacheWarmer extends AbstractPhpFileCacheWarmer
/**
* {@inheritdoc}
*/
protected function doWarmUp($cacheDir, ArrayAdapter $arrayAdapter)
protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter)
{
$annotatedClassPatterns = $cacheDir.'/annotations.map';
@@ -55,7 +52,7 @@ class AnnotationsCacheWarmer extends AbstractPhpFileCacheWarmer
}
$annotatedClasses = include $annotatedClassPatterns;
$reader = new CachedReader($this->annotationReader, new DoctrineProvider($arrayAdapter), $this->debug);
$reader = new PsrCachedReader($this->annotationReader, $arrayAdapter, $this->debug);
foreach ($annotatedClasses as $class) {
if (null !== $this->excludeRegexp && preg_match($this->excludeRegexp, $class)) {
@@ -71,7 +68,18 @@ class AnnotationsCacheWarmer extends AbstractPhpFileCacheWarmer
return true;
}
private function readAllComponents(Reader $reader, $class)
/**
* @return string[] A list of classes to preload on PHP 7.4+
*/
protected function warmUpPhpArrayAdapter(PhpArrayAdapter $phpArrayAdapter, array $values)
{
// make sure we don't cache null values
$values = array_filter($values, function ($val) { return null !== $val; });
return parent::warmUpPhpArrayAdapter($phpArrayAdapter, $values);
}
private function readAllComponents(Reader $reader, string $class)
{
$reflectionClass = new \ReflectionClass($class);

View File

@@ -0,0 +1,64 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\CacheWarmer;
use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
/**
* Clears the cache pools when warming up the cache.
*
* Do not use in production!
*
* @author Teoh Han Hui <teohhanhui@gmail.com>
*
* @internal
*/
final class CachePoolClearerCacheWarmer implements CacheWarmerInterface
{
private $poolClearer;
private $pools;
/**
* @param string[] $pools
*/
public function __construct(Psr6CacheClearer $poolClearer, array $pools = [])
{
$this->poolClearer = $poolClearer;
$this->pools = $pools;
}
/**
* {@inheritdoc}
*
* @return string[]
*/
public function warmUp(string $cacheDirectory): array
{
foreach ($this->pools as $pool) {
if ($this->poolClearer->hasPool($pool)) {
$this->poolClearer->clearPool($pool);
}
}
return [];
}
/**
* {@inheritdoc}
*/
public function isOptional(): bool
{
// optional cache warmers are not run when handling the request
return false;
}
}

View File

@@ -1,67 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\CacheWarmer;
use Symfony\Component\ClassLoader\ClassCollectionLoader;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
/**
* Generates the Class Cache (classes.php) file.
*
* @author Tugdual Saunier <tucksaun@gmail.com>
*
* @deprecated since version 3.3, to be removed in 4.0.
*/
class ClassCacheCacheWarmer implements CacheWarmerInterface
{
private $declaredClasses;
public function __construct(array $declaredClasses = null)
{
if (\PHP_VERSION_ID >= 70000) {
@trigger_error('The '.__CLASS__.' class is deprecated since Symfony 3.3 and will be removed in 4.0.', \E_USER_DEPRECATED);
}
$this->declaredClasses = $declaredClasses;
}
/**
* Warms up the cache.
*
* @param string $cacheDir The cache directory
*/
public function warmUp($cacheDir)
{
$classmap = $cacheDir.'/classes.map';
if (!is_file($classmap)) {
return;
}
if (file_exists($cacheDir.'/classes.php')) {
return;
}
$declared = null !== $this->declaredClasses ? $this->declaredClasses : array_merge(get_declared_classes(), get_declared_interfaces(), get_declared_traits());
ClassCollectionLoader::inline(include($classmap), $cacheDir.'/classes.php', $declared);
}
/**
* Checks whether this warmer is optional or not.
*
* @return bool always true
*/
public function isOptional()
{
return true;
}
}

View File

@@ -0,0 +1,91 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\CacheWarmer;
use Psr\Log\LoggerInterface;
use Symfony\Component\Config\Builder\ConfigBuilderGenerator;
use Symfony\Component\Config\Builder\ConfigBuilderGeneratorInterface;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
use Symfony\Component\HttpKernel\KernelInterface;
/**
* Generate all config builders.
*
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
class ConfigBuilderCacheWarmer implements CacheWarmerInterface
{
private $kernel;
private $logger;
public function __construct(KernelInterface $kernel, LoggerInterface $logger = null)
{
$this->kernel = $kernel;
$this->logger = $logger;
}
/**
* {@inheritdoc}
*
* @return string[]
*/
public function warmUp(string $cacheDir)
{
$generator = new ConfigBuilderGenerator($cacheDir);
foreach ($this->kernel->getBundles() as $bundle) {
$extension = $bundle->getContainerExtension();
if (null === $extension) {
continue;
}
try {
$this->dumpExtension($extension, $generator);
} catch (\Exception $e) {
if ($this->logger) {
$this->logger->warning('Failed to generate ConfigBuilder for extension {extensionClass}.', ['exception' => $e, 'extensionClass' => \get_class($extension)]);
}
}
}
// No need to preload anything
return [];
}
private function dumpExtension(ExtensionInterface $extension, ConfigBuilderGeneratorInterface $generator): void
{
$configuration = null;
if ($extension instanceof ConfigurationInterface) {
$configuration = $extension;
} elseif ($extension instanceof ConfigurationExtensionInterface) {
$configuration = $extension->getConfiguration([], new ContainerBuilder($this->kernel->getContainer()->getParameterBag()));
}
if (!$configuration) {
return;
}
$generator->build($configuration);
}
/**
* {@inheritdoc}
*/
public function isOptional()
{
return true;
}
}

View File

@@ -12,56 +12,46 @@
namespace Symfony\Bundle\FrameworkBundle\CacheWarmer;
use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
/**
* Generates the router matcher and generator classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final since version 3.4
* @final
*/
class RouterCacheWarmer implements CacheWarmerInterface, ServiceSubscriberInterface
{
protected $router;
private $container;
/**
* @param ContainerInterface $container
*/
public function __construct($container)
public function __construct(ContainerInterface $container)
{
// As this cache warmer is optional, dependencies should be lazy-loaded, that's why a container should be injected.
if ($container instanceof ContainerInterface) {
$this->router = $container->get('router'); // For BC, the $router property must be populated in the constructor
} elseif ($container instanceof RouterInterface) {
$this->router = $container;
@trigger_error(sprintf('Using a "%s" as first argument of %s is deprecated since Symfony 3.4 and will be unsupported in version 4.0. Use a %s instead.', RouterInterface::class, __CLASS__, ContainerInterface::class), \E_USER_DEPRECATED);
} else {
throw new \InvalidArgumentException(sprintf('"%s" only accepts instance of Psr\Container\ContainerInterface as first argument.', __CLASS__));
}
$this->container = $container;
}
/**
* Warms up the cache.
*
* @param string $cacheDir The cache directory
* {@inheritdoc}
*/
public function warmUp($cacheDir)
public function warmUp(string $cacheDir): array
{
if ($this->router instanceof WarmableInterface) {
$this->router->warmUp($cacheDir);
$router = $this->container->get('router');
if ($router instanceof WarmableInterface) {
return (array) $router->warmUp($cacheDir);
}
throw new \LogicException(sprintf('The router "%s" cannot be warmed up because it does not implement "%s".', get_debug_type($router), WarmableInterface::class));
}
/**
* Checks whether this warmer is optional or not.
*
* @return bool always true
* {@inheritdoc}
*/
public function isOptional()
public function isOptional(): bool
{
return true;
}
@@ -69,7 +59,7 @@ class RouterCacheWarmer implements CacheWarmerInterface, ServiceSubscriberInterf
/**
* {@inheritdoc}
*/
public static function getSubscribedServices()
public static function getSubscribedServices(): array
{
return [
'router' => RouterInterface::class,

View File

@@ -12,7 +12,6 @@
namespace Symfony\Bundle\FrameworkBundle\CacheWarmer;
use Doctrine\Common\Annotations\AnnotationException;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
@@ -31,20 +30,19 @@ class SerializerCacheWarmer extends AbstractPhpFileCacheWarmer
private $loaders;
/**
* @param LoaderInterface[] $loaders The serializer metadata loaders
* @param string $phpArrayFile The PHP file where metadata are cached
* @param CacheItemPoolInterface $fallbackPool The pool where runtime-discovered metadata are cached
* @param LoaderInterface[] $loaders The serializer metadata loaders
* @param string $phpArrayFile The PHP file where metadata are cached
*/
public function __construct(array $loaders, $phpArrayFile, CacheItemPoolInterface $fallbackPool)
public function __construct(array $loaders, string $phpArrayFile)
{
parent::__construct($phpArrayFile, $fallbackPool);
parent::__construct($phpArrayFile);
$this->loaders = $loaders;
}
/**
* {@inheritdoc}
*/
protected function doWarmUp($cacheDir, ArrayAdapter $arrayAdapter)
protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter)
{
if (!class_exists(CacheClassMetadataFactory::class) || !method_exists(XmlFileLoader::class, 'getMappedClasses') || !method_exists(YamlFileLoader::class, 'getMappedClasses')) {
return false;
@@ -72,7 +70,7 @@ class SerializerCacheWarmer extends AbstractPhpFileCacheWarmer
*
* @return XmlFileLoader[]|YamlFileLoader[]
*/
private function extractSupportedLoaders(array $loaders)
private function extractSupportedLoaders(array $loaders): array
{
$supportedLoaders = [];

View File

@@ -1,111 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\CacheWarmer;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Templating\TemplateNameParserInterface;
use Symfony\Component\Templating\TemplateReferenceInterface;
/**
* Finds all the templates.
*
* @author Victor Berchet <victor@suumit.com>
*/
class TemplateFinder implements TemplateFinderInterface
{
private $kernel;
private $parser;
private $rootDir;
private $templates;
/**
* @param KernelInterface $kernel A KernelInterface instance
* @param TemplateNameParserInterface $parser A TemplateNameParserInterface instance
* @param string $rootDir The directory where global templates can be stored
*/
public function __construct(KernelInterface $kernel, TemplateNameParserInterface $parser, $rootDir)
{
$this->kernel = $kernel;
$this->parser = $parser;
$this->rootDir = $rootDir;
}
/**
* Find all the templates in the bundle and in the kernel Resources folder.
*
* @return TemplateReferenceInterface[]
*/
public function findAllTemplates()
{
if (null !== $this->templates) {
return $this->templates;
}
$templates = [];
foreach ($this->kernel->getBundles() as $bundle) {
$templates = array_merge($templates, $this->findTemplatesInBundle($bundle));
}
$templates = array_merge($templates, $this->findTemplatesInFolder($this->rootDir.'/views'));
return $this->templates = $templates;
}
/**
* Find templates in the given directory.
*
* @param string $dir The folder where to look for templates
*
* @return TemplateReferenceInterface[]
*/
private function findTemplatesInFolder($dir)
{
$templates = [];
if (is_dir($dir)) {
$finder = new Finder();
foreach ($finder->files()->followLinks()->in($dir) as $file) {
$template = $this->parser->parse($file->getRelativePathname());
if (false !== $template) {
$templates[] = $template;
}
}
}
return $templates;
}
/**
* Find templates in the given bundle.
*
* @param BundleInterface $bundle The bundle where to look for templates
*
* @return TemplateReferenceInterface[]
*/
private function findTemplatesInBundle(BundleInterface $bundle)
{
$name = $bundle->getName();
$templates = array_unique(array_merge(
$this->findTemplatesInFolder($bundle->getPath().'/Resources/views'),
$this->findTemplatesInFolder($this->rootDir.'/'.$name.'/views')
));
foreach ($templates as $i => $template) {
$templates[$i] = $template->set('bundle', $name);
}
return $templates;
}
}

View File

@@ -1,27 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\CacheWarmer;
/**
* Interface for finding all the templates.
*
* @author Victor Berchet <victor@suumit.com>
*/
interface TemplateFinderInterface
{
/**
* Find all the templates.
*
* @return array An array of templates of type TemplateReferenceInterface
*/
public function findAllTemplates();
}

View File

@@ -1,62 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\CacheWarmer;
use Symfony\Bundle\FrameworkBundle\Templating\Loader\TemplateLocator;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmer;
/**
* Computes the association between template names and their paths on the disk.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TemplatePathsCacheWarmer extends CacheWarmer
{
protected $finder;
protected $locator;
public function __construct(TemplateFinderInterface $finder, TemplateLocator $locator)
{
$this->finder = $finder;
$this->locator = $locator;
}
/**
* Warms up the cache.
*
* @param string $cacheDir The cache directory
*/
public function warmUp($cacheDir)
{
$filesystem = new Filesystem();
$templates = [];
foreach ($this->finder->findAllTemplates() as $template) {
$templates[$template->getLogicalName()] = rtrim($filesystem->makePathRelative($this->locator->locate($template), $cacheDir), '/');
}
$templates = str_replace("' => '", "' => __DIR__.'/", var_export($templates, true));
$this->writeCacheFile($cacheDir.'/templates.php', sprintf("<?php return %s;\n", $templates));
}
/**
* Checks whether this warmer is optional or not.
*
* @return bool always true
*/
public function isOptional()
{
return true;
}
}

View File

@@ -12,10 +12,10 @@
namespace Symfony\Bundle\FrameworkBundle\CacheWarmer;
use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* Generates the catalogues for translations.
@@ -27,36 +27,28 @@ class TranslationsCacheWarmer implements CacheWarmerInterface, ServiceSubscriber
private $container;
private $translator;
/**
* TranslationsCacheWarmer constructor.
*
* @param ContainerInterface $container
*/
public function __construct($container)
public function __construct(ContainerInterface $container)
{
// As this cache warmer is optional, dependencies should be lazy-loaded, that's why a container should be injected.
if ($container instanceof ContainerInterface) {
$this->container = $container;
} elseif ($container instanceof TranslatorInterface) {
$this->translator = $container;
@trigger_error(sprintf('Using a "%s" as first argument of %s is deprecated since Symfony 3.4 and will be unsupported in version 4.0. Use a %s instead.', TranslatorInterface::class, __CLASS__, ContainerInterface::class), \E_USER_DEPRECATED);
} else {
throw new \InvalidArgumentException(sprintf('"%s" only accepts instance of Psr\Container\ContainerInterface as first argument.', __CLASS__));
}
$this->container = $container;
}
/**
* {@inheritdoc}
*
* @return string[]
*/
public function warmUp($cacheDir)
public function warmUp(string $cacheDir)
{
if (null === $this->translator) {
$this->translator = $this->container->get('translator');
}
if ($this->translator instanceof WarmableInterface) {
$this->translator->warmUp($cacheDir);
return (array) $this->translator->warmUp($cacheDir);
}
return [];
}
/**

View File

@@ -12,16 +12,14 @@
namespace Symfony\Bundle\FrameworkBundle\CacheWarmer;
use Doctrine\Common\Annotations\AnnotationException;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
use Symfony\Component\Validator\Mapping\Cache\Psr6Cache;
use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory;
use Symfony\Component\Validator\Mapping\Loader\LoaderChain;
use Symfony\Component\Validator\Mapping\Loader\LoaderInterface;
use Symfony\Component\Validator\Mapping\Loader\XmlFileLoader;
use Symfony\Component\Validator\Mapping\Loader\YamlFileLoader;
use Symfony\Component\Validator\ValidatorBuilderInterface;
use Symfony\Component\Validator\ValidatorBuilder;
/**
* Warms up XML and YAML validator metadata.
@@ -33,26 +31,25 @@ class ValidatorCacheWarmer extends AbstractPhpFileCacheWarmer
private $validatorBuilder;
/**
* @param string $phpArrayFile The PHP file where metadata are cached
* @param CacheItemPoolInterface $fallbackPool The pool where runtime-discovered metadata are cached
* @param string $phpArrayFile The PHP file where metadata are cached
*/
public function __construct(ValidatorBuilderInterface $validatorBuilder, $phpArrayFile, CacheItemPoolInterface $fallbackPool)
public function __construct(ValidatorBuilder $validatorBuilder, string $phpArrayFile)
{
parent::__construct($phpArrayFile, $fallbackPool);
parent::__construct($phpArrayFile);
$this->validatorBuilder = $validatorBuilder;
}
/**
* {@inheritdoc}
*/
protected function doWarmUp($cacheDir, ArrayAdapter $arrayAdapter)
protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter)
{
if (!method_exists($this->validatorBuilder, 'getLoaders')) {
return false;
}
$loaders = $this->validatorBuilder->getLoaders();
$metadataFactory = new LazyLoadingMetadataFactory(new LoaderChain($loaders), new Psr6Cache($arrayAdapter));
$metadataFactory = new LazyLoadingMetadataFactory(new LoaderChain($loaders), $arrayAdapter);
foreach ($this->extractSupportedLoaders($loaders) as $loader) {
foreach ($loader->getMappedClasses() as $mappedClass) {
@@ -71,10 +68,15 @@ class ValidatorCacheWarmer extends AbstractPhpFileCacheWarmer
return true;
}
/**
* @return string[] A list of classes to preload on PHP 7.4+
*/
protected function warmUpPhpArrayAdapter(PhpArrayAdapter $phpArrayAdapter, array $values)
{
// make sure we don't cache null values
parent::warmUpPhpArrayAdapter($phpArrayAdapter, array_filter($values));
$values = array_filter($values, function ($val) { return null !== $val; });
return parent::warmUpPhpArrayAdapter($phpArrayAdapter, $values);
}
/**
@@ -82,7 +84,7 @@ class ValidatorCacheWarmer extends AbstractPhpFileCacheWarmer
*
* @return XmlFileLoader[]|YamlFileLoader[]
*/
private function extractSupportedLoaders(array $loaders)
private function extractSupportedLoaders(array $loaders): array
{
$supportedLoaders = [];

View File

@@ -11,6 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Console\Input\InputInterface;
@@ -24,11 +25,12 @@ use Symfony\Component\HttpKernel\KernelInterface;
*
* @author Roland Franssen <franssen.roland@gmail.com>
*
* @final since version 3.4
* @final
*/
class AboutCommand extends ContainerAwareCommand
class AboutCommand extends Command
{
protected static $defaultName = 'about';
protected static $defaultDescription = 'Display information about the current project';
/**
* {@inheritdoc}
@@ -36,15 +38,12 @@ class AboutCommand extends ContainerAwareCommand
protected function configure()
{
$this
->setDescription('Displays information about the current project')
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOT'
The <info>%command.name%</info> command displays information about the current Symfony project.
The <info>PHP</info> section displays important configuration that could affect your application. The values might
be different between web and CLI.
The <info>Environment</info> section displays the current environment variables managed by Symfony Dotenv. It will not
be shown if no variables were found. The values might be different between web and CLI.
EOT
)
;
@@ -53,90 +52,85 @@ EOT
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
/** @var $kernel KernelInterface */
/** @var KernelInterface $kernel */
$kernel = $this->getApplication()->getKernel();
if (method_exists($kernel, 'getBuildDir')) {
$buildDir = $kernel->getBuildDir();
} else {
$buildDir = $kernel->getCacheDir();
}
$rows = [
['<info>Symfony</>'],
new TableSeparator(),
['Version', Kernel::VERSION],
['End of maintenance', Kernel::END_OF_MAINTENANCE.(self::isExpired(Kernel::END_OF_MAINTENANCE) ? ' <error>Expired</>' : '')],
['End of life', Kernel::END_OF_LIFE.(self::isExpired(Kernel::END_OF_LIFE) ? ' <error>Expired</>' : '')],
['Long-Term Support', 4 === Kernel::MINOR_VERSION ? 'Yes' : 'No'],
['End of maintenance', Kernel::END_OF_MAINTENANCE.(self::isExpired(Kernel::END_OF_MAINTENANCE) ? ' <error>Expired</>' : ' (<comment>'.self::daysBeforeExpiration(Kernel::END_OF_MAINTENANCE).'</>)')],
['End of life', Kernel::END_OF_LIFE.(self::isExpired(Kernel::END_OF_LIFE) ? ' <error>Expired</>' : ' (<comment>'.self::daysBeforeExpiration(Kernel::END_OF_LIFE).'</>)')],
new TableSeparator(),
['<info>Kernel</>'],
new TableSeparator(),
['Type', \get_class($kernel)],
['Name', $kernel->getName()],
['Environment', $kernel->getEnvironment()],
['Debug', $kernel->isDebug() ? 'true' : 'false'],
['Charset', $kernel->getCharset()],
['Root directory', self::formatPath($kernel->getRootDir(), $kernel->getProjectDir())],
['Cache directory', self::formatPath($kernel->getCacheDir(), $kernel->getProjectDir()).' (<comment>'.self::formatFileSize($kernel->getCacheDir()).'</>)'],
['Build directory', self::formatPath($buildDir, $kernel->getProjectDir()).' (<comment>'.self::formatFileSize($buildDir).'</>)'],
['Log directory', self::formatPath($kernel->getLogDir(), $kernel->getProjectDir()).' (<comment>'.self::formatFileSize($kernel->getLogDir()).'</>)'],
new TableSeparator(),
['<info>PHP</>'],
new TableSeparator(),
['Version', \PHP_VERSION],
['Architecture', (\PHP_INT_SIZE * 8).' bits'],
['Intl locale', class_exists('Locale', false) && \Locale::getDefault() ? \Locale::getDefault() : 'n/a'],
['Intl locale', class_exists(\Locale::class, false) && \Locale::getDefault() ? \Locale::getDefault() : 'n/a'],
['Timezone', date_default_timezone_get().' (<comment>'.(new \DateTime())->format(\DateTime::W3C).'</>)'],
['OPcache', \extension_loaded('Zend OPcache') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN) ? 'true' : 'false'],
['APCu', \extension_loaded('apcu') && filter_var(ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN) ? 'true' : 'false'],
['Xdebug', \extension_loaded('xdebug') ? 'true' : 'false'],
];
if ($dotenv = self::getDotenvVars()) {
$rows = array_merge($rows, [
new TableSeparator(),
['<info>Environment (.env)</>'],
new TableSeparator(),
], array_map(function ($value, $name) {
return [$name, $value];
}, $dotenv, array_keys($dotenv)));
}
$io->table([], $rows);
return 0;
}
private static function formatPath($path, $baseDir = null)
private static function formatPath(string $path, string $baseDir): string
{
return null !== $baseDir ? preg_replace('~^'.preg_quote($baseDir, '~').'~', '.', $path) : $path;
return preg_replace('~^'.preg_quote($baseDir, '~').'~', '.', $path);
}
private static function formatFileSize($path)
private static function formatFileSize(string $path): string
{
if (is_file($path)) {
$size = filesize($path) ?: 0;
} else {
$size = 0;
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS | \RecursiveDirectoryIterator::FOLLOW_SYMLINKS)) as $file) {
$size += $file->getSize();
if ($file->isReadable()) {
$size += $file->getSize();
}
}
}
return Helper::formatMemory($size);
}
private static function isExpired($date)
private static function isExpired(string $date): bool
{
$date = \DateTime::createFromFormat('d/m/Y', '01/'.$date);
return false !== $date && new \DateTime() > $date->modify('last day of this month 23:59:59');
}
private static function getDotenvVars()
private static function daysBeforeExpiration(string $date): string
{
$vars = [];
foreach (explode(',', getenv('SYMFONY_DOTENV_VARS')) as $name) {
if ('' !== $name && false !== $value = getenv($name)) {
$vars[$name] = $value;
}
}
$date = \DateTime::createFromFormat('d/m/Y', '01/'.$date);
return $vars;
return (new \DateTime())->diff($date->modify('last day of this month 23:59:59'))->format('in %R%a days');
}
}

View File

@@ -16,6 +16,7 @@ use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\StyleInterface;
use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
/**
@@ -59,11 +60,27 @@ abstract class AbstractConfigCommand extends ContainerDebugCommand
/**
* @return ExtensionInterface
*/
protected function findExtension($name)
protected function findExtension(string $name)
{
$bundles = $this->initializeBundles();
$minScore = \INF;
$kernel = $this->getApplication()->getKernel();
if ($kernel instanceof ExtensionInterface && ($kernel instanceof ConfigurationInterface || $kernel instanceof ConfigurationExtensionInterface)) {
if ($name === $kernel->getAlias()) {
return $kernel;
}
if ($kernel->getAlias()) {
$distance = levenshtein($name, $kernel->getAlias());
if ($distance < $minScore) {
$guess = $kernel->getAlias();
$minScore = $distance;
}
}
}
foreach ($bundles as $bundle) {
if ($name === $bundle->getName()) {
if (!$bundle->getContainerExtension()) {
@@ -79,24 +96,24 @@ abstract class AbstractConfigCommand extends ContainerDebugCommand
$guess = $bundle->getName();
$minScore = $distance;
}
}
$extension = $bundle->getContainerExtension();
$container = $this->getContainerBuilder($kernel);
if ($extension) {
if ($name === $extension->getAlias()) {
return $extension;
}
if ($container->hasExtension($name)) {
return $container->getExtension($name);
}
$distance = levenshtein($name, $extension->getAlias());
foreach ($container->getExtensions() as $extension) {
$distance = levenshtein($name, $extension->getAlias());
if ($distance < $minScore) {
$guess = $extension->getAlias();
$minScore = $distance;
}
if ($distance < $minScore) {
$guess = $extension->getAlias();
$minScore = $distance;
}
}
if ('Bundle' !== substr($name, -6)) {
if (!str_ends_with($name, 'Bundle')) {
$message = sprintf('No extensions with configuration available for "%s".', $name);
} else {
$message = sprintf('No extension with alias "%s" is enabled.', $name);
@@ -116,7 +133,7 @@ abstract class AbstractConfigCommand extends ContainerDebugCommand
}
if (!$configuration instanceof ConfigurationInterface) {
throw new \LogicException(sprintf('Configuration class "%s" should implement ConfigurationInterface in order to be dumpable.', \get_class($configuration)));
throw new \LogicException(sprintf('Configuration class "%s" should implement ConfigurationInterface in order to be dumpable.', get_debug_type($configuration)));
}
}
@@ -124,8 +141,9 @@ abstract class AbstractConfigCommand extends ContainerDebugCommand
{
// Re-build bundle manually to initialize DI extensions that can be extended by other bundles in their build() method
// as this method is not called when the container is loaded from the cache.
$container = $this->getContainerBuilder();
$bundles = $this->getApplication()->getKernel()->getBundles();
$kernel = $this->getApplication()->getKernel();
$container = $this->getContainerBuilder($kernel);
$bundles = $kernel->getBundles();
foreach ($bundles as $bundle) {
if ($extension = $bundle->getContainerExtension()) {
$container->registerExtension($extension);

View File

@@ -11,6 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
@@ -22,6 +23,7 @@ use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
use Symfony\Component\HttpKernel\KernelInterface;
/**
* Command that places bundle web assets into a given directory.
@@ -29,34 +31,26 @@ use Symfony\Component\HttpKernel\Bundle\BundleInterface;
* @author Fabien Potencier <fabien@symfony.com>
* @author Gábor Egyed <gabor.egyed@gmail.com>
*
* @final since version 3.4
* @final
*/
class AssetsInstallCommand extends ContainerAwareCommand
class AssetsInstallCommand extends Command
{
const METHOD_COPY = 'copy';
const METHOD_ABSOLUTE_SYMLINK = 'absolute symlink';
const METHOD_RELATIVE_SYMLINK = 'relative symlink';
public const METHOD_COPY = 'copy';
public const METHOD_ABSOLUTE_SYMLINK = 'absolute symlink';
public const METHOD_RELATIVE_SYMLINK = 'relative symlink';
protected static $defaultName = 'assets:install';
protected static $defaultDescription = 'Install bundle\'s web assets under a public directory';
private $filesystem;
private $projectDir;
/**
* @param Filesystem $filesystem
*/
public function __construct($filesystem = null)
public function __construct(Filesystem $filesystem, string $projectDir)
{
if (!$filesystem instanceof Filesystem) {
@trigger_error(sprintf('%s() expects an instance of "%s" as first argument since Symfony 3.4. Not passing it is deprecated and will throw a TypeError in 4.0.', __METHOD__, Filesystem::class), \E_USER_DEPRECATED);
parent::__construct($filesystem);
return;
}
parent::__construct();
$this->filesystem = $filesystem;
$this->projectDir = $projectDir;
}
/**
@@ -68,9 +62,10 @@ class AssetsInstallCommand extends ContainerAwareCommand
->setDefinition([
new InputArgument('target', InputArgument::OPTIONAL, 'The target directory', null),
])
->addOption('symlink', null, InputOption::VALUE_NONE, 'Symlinks the assets instead of copying it')
->addOption('symlink', null, InputOption::VALUE_NONE, 'Symlink the assets instead of copying them')
->addOption('relative', null, InputOption::VALUE_NONE, 'Make relative symlinks')
->setDescription('Installs bundles web assets under a public directory')
->addOption('no-cleanup', null, InputOption::VALUE_NONE, 'Do not remove the assets of the bundles that no longer exist')
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOT'
The <info>%command.name%</info> command installs bundle assets into a given
directory (e.g. the <comment>public</comment> directory).
@@ -97,32 +92,20 @@ EOT
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
// BC to be removed in 4.0
if (null === $this->filesystem) {
$this->filesystem = $this->getContainer()->get('filesystem');
$baseDir = $this->getContainer()->getParameter('kernel.project_dir');
}
/** @var KernelInterface $kernel */
$kernel = $this->getApplication()->getKernel();
$targetArg = rtrim($input->getArgument('target'), '/');
$targetArg = rtrim($input->getArgument('target') ?? '', '/');
if (!$targetArg) {
$targetArg = $this->getPublicDirectory($this->getContainer());
$targetArg = $this->getPublicDirectory($kernel->getContainer());
}
if (!is_dir($targetArg)) {
$targetArg = (isset($baseDir) ? $baseDir : $kernel->getContainer()->getParameter('kernel.project_dir')).'/'.$targetArg;
$targetArg = $kernel->getProjectDir().'/'.$targetArg;
if (!is_dir($targetArg)) {
// deprecated, logic to be removed in 4.0
// this allows the commands to work out of the box with web/ and public/
if (is_dir(\dirname($targetArg).'/web')) {
$targetArg = \dirname($targetArg).'/web';
} else {
throw new InvalidArgumentException(sprintf('The target directory "%s" does not exist.', $targetArg));
}
throw new InvalidArgumentException(sprintf('The target directory "%s" does not exist.', $targetArg));
}
}
@@ -150,7 +133,7 @@ EOT
$validAssetDirs = [];
/** @var BundleInterface $bundle */
foreach ($kernel->getBundles() as $bundle) {
if (!is_dir($originDir = $bundle->getPath().'/Resources/public')) {
if (!is_dir($originDir = $bundle->getPath().'/Resources/public') && !is_dir($originDir = $bundle->getPath().'/public')) {
continue;
}
@@ -190,7 +173,7 @@ EOT
}
}
// remove the assets of the bundles that no longer exist
if (is_dir($bundlesDir)) {
if (!$input->getOption('no-cleanup') && is_dir($bundlesDir)) {
$dirsToRemove = Finder::create()->depth(0)->directories()->exclude($validAssetDirs)->in($bundlesDir);
$this->filesystem->remove($dirsToRemove);
}
@@ -215,13 +198,8 @@ EOT
* Try to create relative symlink.
*
* Falling back to absolute symlink and finally hard copy.
*
* @param string $originDir
* @param string $targetDir
*
* @return string
*/
private function relativeSymlinkWithFallback($originDir, $targetDir)
private function relativeSymlinkWithFallback(string $originDir, string $targetDir): string
{
try {
$this->symlink($originDir, $targetDir, true);
@@ -237,13 +215,8 @@ EOT
* Try to create absolute symlink.
*
* Falling back to hard copy.
*
* @param string $originDir
* @param string $targetDir
*
* @return string
*/
private function absoluteSymlinkWithFallback($originDir, $targetDir)
private function absoluteSymlinkWithFallback(string $originDir, string $targetDir): string
{
try {
$this->symlink($originDir, $targetDir);
@@ -259,13 +232,9 @@ EOT
/**
* Creates symbolic link.
*
* @param string $originDir
* @param string $targetDir
* @param bool $relative
*
* @throws IOException if link can not be created
* @throws IOException if link cannot be created
*/
private function symlink($originDir, $targetDir, $relative = false)
private function symlink(string $originDir, string $targetDir, bool $relative = false)
{
if ($relative) {
$this->filesystem->mkdir(\dirname($targetDir));
@@ -279,13 +248,8 @@ EOT
/**
* Copies origin to target.
*
* @param string $originDir
* @param string $targetDir
*
* @return string
*/
private function hardCopy($originDir, $targetDir)
private function hardCopy(string $originDir, string $targetDir): string
{
$this->filesystem->mkdir($targetDir, 0777);
// We use a custom iterator to ignore VCS files
@@ -294,15 +258,15 @@ EOT
return self::METHOD_COPY;
}
private function getPublicDirectory(ContainerInterface $container)
private function getPublicDirectory(ContainerInterface $container): string
{
$defaultPublicDir = 'public';
if (!$container->hasParameter('kernel.project_dir')) {
if (null === $this->projectDir && !$container->hasParameter('kernel.project_dir')) {
return $defaultPublicDir;
}
$composerFilePath = $container->getParameter('kernel.project_dir').'/composer.json';
$composerFilePath = ($this->projectDir ?? $container->getParameter('kernel.project_dir')).'/composer.json';
if (!file_exists($composerFilePath)) {
return $defaultPublicDir;
@@ -310,10 +274,6 @@ EOT
$composerConfig = json_decode(file_get_contents($composerFilePath), true);
if (isset($composerConfig['extra']['public-dir'])) {
return $composerConfig['extra']['public-dir'];
}
return $defaultPublicDir;
return $composerConfig['extra']['public-dir'] ?? $defaultPublicDir;
}
}

View File

@@ -0,0 +1,64 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Config\ConfigCache;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\HttpKernel\KernelInterface;
/**
* @internal
*
* @author Robin Chalas <robin.chalas@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
trait BuildDebugContainerTrait
{
protected $containerBuilder;
/**
* Loads the ContainerBuilder from the cache.
*
* @throws \LogicException
*/
protected function getContainerBuilder(KernelInterface $kernel): ContainerBuilder
{
if ($this->containerBuilder) {
return $this->containerBuilder;
}
if (!$kernel->isDebug() || !(new ConfigCache($kernel->getContainer()->getParameter('debug.container.dump'), true))->isFresh()) {
$buildContainer = \Closure::bind(function () {
$this->initializeBundles();
return $this->buildContainer();
}, $kernel, \get_class($kernel));
$container = $buildContainer();
$container->getCompilerPassConfig()->setRemovingPasses([]);
$container->getCompilerPassConfig()->setAfterRemovingPasses([]);
$container->compile();
} else {
(new XmlFileLoader($container = new ContainerBuilder(), new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump'));
$locatorPass = new ServiceLocatorTagPass();
$locatorPass->process($container);
$container->getCompilerPassConfig()->setBeforeOptimizationPasses([]);
$container->getCompilerPassConfig()->setOptimizationPasses([]);
$container->getCompilerPassConfig()->setBeforeRemovingPasses([]);
}
return $this->containerBuilder = $container;
}
}

View File

@@ -11,17 +11,18 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Dumper\Preloader;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\HttpKernel\RebootableInterface;
/**
@@ -30,33 +31,22 @@ use Symfony\Component\HttpKernel\RebootableInterface;
* @author Francis Besset <francis.besset@gmail.com>
* @author Fabien Potencier <fabien@symfony.com>
*
* @final since version 3.4
* @final
*/
class CacheClearCommand extends ContainerAwareCommand
class CacheClearCommand extends Command
{
protected static $defaultName = 'cache:clear';
protected static $defaultDescription = 'Clear the cache';
private $cacheClearer;
private $filesystem;
private $warning;
/**
* @param CacheClearerInterface $cacheClearer
*/
public function __construct($cacheClearer = null, Filesystem $filesystem = null)
public function __construct(CacheClearerInterface $cacheClearer, Filesystem $filesystem = null)
{
if (!$cacheClearer instanceof CacheClearerInterface) {
@trigger_error(sprintf('%s() expects an instance of "%s" as first argument since Symfony 3.4. Not passing it is deprecated and will throw a TypeError in 4.0.', __METHOD__, CacheClearerInterface::class), \E_USER_DEPRECATED);
parent::__construct($cacheClearer);
return;
}
parent::__construct();
$this->cacheClearer = $cacheClearer;
$this->filesystem = $filesystem ?: new Filesystem();
$this->filesystem = $filesystem ?? new Filesystem();
}
/**
@@ -69,9 +59,9 @@ class CacheClearCommand extends ContainerAwareCommand
new InputOption('no-warmup', '', InputOption::VALUE_NONE, 'Do not warm up the cache'),
new InputOption('no-optional-warmers', '', InputOption::VALUE_NONE, 'Skip optional cache warmers (faster)'),
])
->setDescription('Clears the cache')
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command clears the application cache for a given environment
The <info>%command.name%</info> command clears and warms up the application cache for a given environment
and debug mode:
<info>php %command.full_name% --env=dev</info>
@@ -84,98 +74,122 @@ EOF
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
// BC to be removed in 4.0
if (null === $this->cacheClearer) {
$this->cacheClearer = $this->getContainer()->get('cache_clearer');
$this->filesystem = $this->getContainer()->get('filesystem');
$realCacheDir = $this->getContainer()->getParameter('kernel.cache_dir');
}
$fs = $this->filesystem;
$io = new SymfonyStyle($input, $output);
$kernel = $this->getApplication()->getKernel();
$realCacheDir = isset($realCacheDir) ? $realCacheDir : $kernel->getContainer()->getParameter('kernel.cache_dir');
$realCacheDir = $kernel->getContainer()->getParameter('kernel.cache_dir');
$realBuildDir = $kernel->getContainer()->hasParameter('kernel.build_dir') ? $kernel->getContainer()->getParameter('kernel.build_dir') : $realCacheDir;
// the old cache dir name must not be longer than the real one to avoid exceeding
// the maximum length of a directory or file path within it (esp. Windows MAX_PATH)
$oldCacheDir = substr($realCacheDir, 0, -1).('~' === substr($realCacheDir, -1) ? '+' : '~');
$oldCacheDir = substr($realCacheDir, 0, -1).(str_ends_with($realCacheDir, '~') ? '+' : '~');
$fs->remove($oldCacheDir);
if (!is_writable($realCacheDir)) {
throw new RuntimeException(sprintf('Unable to write in the "%s" directory.', $realCacheDir));
}
$useBuildDir = $realBuildDir !== $realCacheDir;
$oldBuildDir = substr($realBuildDir, 0, -1).('~' === substr($realBuildDir, -1) ? '+' : '~');
if ($useBuildDir) {
$fs->remove($oldBuildDir);
if (!is_writable($realBuildDir)) {
throw new RuntimeException(sprintf('Unable to write in the "%s" directory.', $realBuildDir));
}
if ($this->isNfs($realCacheDir)) {
$fs->remove($realCacheDir);
} else {
$fs->rename($realCacheDir, $oldCacheDir);
}
$fs->mkdir($realCacheDir);
}
$io->comment(sprintf('Clearing the cache for the <info>%s</info> environment with debug <info>%s</info>', $kernel->getEnvironment(), var_export($kernel->isDebug(), true)));
if ($useBuildDir) {
$this->cacheClearer->clear($realBuildDir);
}
$this->cacheClearer->clear($realCacheDir);
// The current event dispatcher is stale, let's not use it anymore
$this->getApplication()->setDispatcher(new EventDispatcher());
$containerDir = new \ReflectionObject($kernel->getContainer());
$containerDir = basename(\dirname($containerDir->getFileName()));
$containerFile = (new \ReflectionObject($kernel->getContainer()))->getFileName();
$containerDir = basename(\dirname($containerFile));
// the warmup cache dir name must have the same length as the real one
// to avoid the many problems in serialized resources files
$warmupDir = substr($realCacheDir, 0, -1).('_' === substr($realCacheDir, -1) ? '-' : '_');
$warmupDir = substr($realBuildDir, 0, -1).('_' === substr($realBuildDir, -1) ? '-' : '_');
if ($output->isVerbose() && $fs->exists($warmupDir)) {
$io->comment('Clearing outdated warmup directory...');
}
$fs->remove($warmupDir);
$fs->mkdir($warmupDir);
if (!$input->getOption('no-warmup')) {
if ($_SERVER['REQUEST_TIME'] <= filemtime($containerFile) && filemtime($containerFile) <= time()) {
if ($output->isVerbose()) {
$io->comment('Warming up cache...');
$io->comment('Cache is fresh.');
}
$this->warmup($warmupDir, $realCacheDir, !$input->getOption('no-optional-warmers'));
if ($this->warning) {
@trigger_error($this->warning, \E_USER_DEPRECATED);
$io->warning($this->warning);
$this->warning = null;
}
}
if (!$fs->exists($warmupDir.'/'.$containerDir)) {
$fs->rename($realCacheDir.'/'.$containerDir, $warmupDir.'/'.$containerDir);
touch($warmupDir.'/'.$containerDir.'.legacy');
}
if ('/' === \DIRECTORY_SEPARATOR && $mounts = @file('/proc/mounts')) {
foreach ($mounts as $mount) {
$mount = \array_slice(explode(' ', $mount), 1, -3);
if (!\in_array(array_pop($mount), ['vboxsf', 'nfs'])) {
continue;
if (!$input->getOption('no-warmup') && !$input->getOption('no-optional-warmers')) {
if ($output->isVerbose()) {
$io->comment('Warming up optional cache...');
}
$mount = implode(' ', $mount).'/';
$warmer = $kernel->getContainer()->get('cache_warmer');
// non optional warmers already ran during container compilation
$warmer->enableOnlyOptionalWarmers();
$preload = (array) $warmer->warmUp($realCacheDir);
if (0 === strpos($realCacheDir, $mount)) {
$io->note('For better performances, you should move the cache and log directories to a non-shared folder of the VM.');
$oldCacheDir = false;
break;
if ($preload && file_exists($preloadFile = $realCacheDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) {
Preloader::append($preloadFile, $preload);
}
}
}
if ($oldCacheDir) {
$fs->rename($realCacheDir, $oldCacheDir);
} else {
$fs->remove($realCacheDir);
}
$fs->rename($warmupDir, $realCacheDir);
$fs->mkdir($warmupDir);
if ($output->isVerbose()) {
$io->comment('Removing old cache directory...');
}
if (!$input->getOption('no-warmup')) {
if ($output->isVerbose()) {
$io->comment('Warming up cache...');
}
$this->warmup($warmupDir, $realCacheDir, !$input->getOption('no-optional-warmers'));
}
if (!$fs->exists($warmupDir.'/'.$containerDir)) {
$fs->rename($realBuildDir.'/'.$containerDir, $warmupDir.'/'.$containerDir);
touch($warmupDir.'/'.$containerDir.'.legacy');
}
if ($this->isNfs($realBuildDir)) {
$io->note('For better performances, you should move the cache and log directories to a non-shared folder of the VM.');
$fs->remove($realBuildDir);
} else {
$fs->rename($realBuildDir, $oldBuildDir);
}
$fs->rename($warmupDir, $realBuildDir);
try {
$fs->remove($oldCacheDir);
} catch (IOException $e) {
if ($output->isVerbose()) {
$io->warning($e->getMessage());
$io->comment('Removing old build and cache directory...');
}
if ($useBuildDir) {
try {
$fs->remove($oldBuildDir);
} catch (IOException $e) {
if ($output->isVerbose()) {
$io->warning($e->getMessage());
}
}
}
try {
$fs->remove($oldCacheDir);
} catch (IOException $e) {
if ($output->isVerbose()) {
$io->warning($e->getMessage());
}
}
}
@@ -184,177 +198,64 @@ EOF
}
$io->success(sprintf('Cache for the "%s" environment (debug=%s) was successfully cleared.', $kernel->getEnvironment(), var_export($kernel->isDebug(), true)));
return 0;
}
/**
* @param string $warmupDir
* @param string $realCacheDir
* @param bool $enableOptionalWarmers
*/
protected function warmup($warmupDir, $realCacheDir, $enableOptionalWarmers = true)
private function isNfs(string $dir): bool
{
static $mounts = null;
if (null === $mounts) {
$mounts = [];
if ('/' === \DIRECTORY_SEPARATOR && $files = @file('/proc/mounts')) {
foreach ($files as $mount) {
$mount = \array_slice(explode(' ', $mount), 1, -3);
if (!\in_array(array_pop($mount), ['vboxsf', 'nfs'])) {
continue;
}
$mounts[] = implode(' ', $mount).'/';
}
}
}
foreach ($mounts as $mount) {
if (0 === strpos($dir, $mount)) {
return true;
}
}
return false;
}
private function warmup(string $warmupDir, string $realBuildDir, bool $enableOptionalWarmers = true)
{
// create a temporary kernel
$realKernel = $this->getApplication()->getKernel();
if ($realKernel instanceof RebootableInterface) {
$realKernel->reboot($warmupDir);
$tempKernel = $realKernel;
} else {
$this->warning = 'Calling "cache:clear" with a kernel that does not implement "Symfony\Component\HttpKernel\RebootableInterface" is deprecated since Symfony 3.4 and will be unsupported in 4.0.';
$realKernelClass = \get_class($realKernel);
$namespace = '';
if (false !== $pos = strrpos($realKernelClass, '\\')) {
$namespace = substr($realKernelClass, 0, $pos);
$realKernelClass = substr($realKernelClass, $pos + 1);
}
$tempKernel = $this->getTempKernel($realKernel, $namespace, $realKernelClass, $warmupDir);
$tempKernel->boot();
$tempKernelReflection = new \ReflectionObject($tempKernel);
$tempKernelFile = $tempKernelReflection->getFileName();
$kernel = $this->getApplication()->getKernel();
if (!$kernel instanceof RebootableInterface) {
throw new \LogicException('Calling "cache:clear" with a kernel that does not implement "Symfony\Component\HttpKernel\RebootableInterface" is not supported.');
}
$kernel->reboot($warmupDir);
// warmup temporary dir
$warmer = $tempKernel->getContainer()->get('cache_warmer');
if ($enableOptionalWarmers) {
$warmer->enableOptionalWarmers();
$warmer = $kernel->getContainer()->get('cache_warmer');
// non optional warmers already ran during container compilation
$warmer->enableOnlyOptionalWarmers();
$preload = (array) $warmer->warmUp($warmupDir);
if ($preload && file_exists($preloadFile = $warmupDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) {
Preloader::append($preloadFile, $preload);
}
}
$warmer->warmUp($warmupDir);
// fix references to cached files with the real cache directory name
$search = [$warmupDir, str_replace('\\', '\\\\', $warmupDir)];
$replace = str_replace('\\', '/', $realCacheDir);
$replace = str_replace('\\', '/', $realBuildDir);
foreach (Finder::create()->files()->in($warmupDir) as $file) {
$content = str_replace($search, $replace, file_get_contents($file), $count);
if ($count) {
file_put_contents($file, $content);
}
}
if ($realKernel instanceof RebootableInterface) {
return;
}
// fix references to the Kernel in .meta files
$safeTempKernel = str_replace('\\', '\\\\', \get_class($tempKernel));
$realKernelFQN = \get_class($realKernel);
foreach (Finder::create()->files()->depth('<3')->name('*.meta')->in($warmupDir) as $file) {
file_put_contents($file, preg_replace(
'/(C\:\d+\:)"'.$safeTempKernel.'"/',
sprintf('$1"%s"', $realKernelFQN),
file_get_contents($file)
));
}
// fix references to container's class
$tempContainerClass = $tempKernel->getContainerClass();
$realContainerClass = $tempKernel->getRealContainerClass();
foreach (Finder::create()->files()->depth('<2')->name($tempContainerClass.'*')->in($warmupDir) as $file) {
$content = str_replace($tempContainerClass, $realContainerClass, file_get_contents($file));
file_put_contents($file, $content);
rename($file, str_replace(\DIRECTORY_SEPARATOR.$tempContainerClass, \DIRECTORY_SEPARATOR.$realContainerClass, $file));
}
if (is_dir($tempContainerDir = $warmupDir.'/'.\get_class($tempKernel->getContainer()))) {
foreach (Finder::create()->files()->in($tempContainerDir) as $file) {
$content = str_replace($tempContainerClass, $realContainerClass, file_get_contents($file));
file_put_contents($file, $content);
}
}
// remove temp kernel file after cache warmed up
@unlink($tempKernelFile);
}
/**
* @param string $namespace
* @param string $parentClass
* @param string $warmupDir
*
* @return KernelInterface
*/
protected function getTempKernel(KernelInterface $parent, $namespace, $parentClass, $warmupDir)
{
$projectDir = '';
$cacheDir = var_export($warmupDir, true);
$rootDir = var_export(realpath($parent->getRootDir()), true);
$logDir = var_export(realpath($parent->getLogDir()), true);
// the temp kernel class name must have the same length than the real one
// to avoid the many problems in serialized resources files
$class = substr($parentClass, 0, -1).'_';
// the temp container class must be changed too
$container = $parent->getContainer();
$realContainerClass = var_export($container->hasParameter('kernel.container_class') ? $container->getParameter('kernel.container_class') : \get_class($parent->getContainer()), true);
$containerClass = substr_replace($realContainerClass, '_', -2, 1);
if (method_exists($parent, 'getProjectDir')) {
$projectDir = var_export(realpath($parent->getProjectDir()), true);
$projectDir = <<<EOF
public function getProjectDir()
{
return $projectDir;
}
EOF;
}
$code = <<<EOF
<?php
namespace $namespace
{
class $class extends $parentClass
{
public function getCacheDir()
{
return $cacheDir;
}
public function getRootDir()
{
return $rootDir;
}
$projectDir
public function getLogDir()
{
return $logDir;
}
public function getRealContainerClass()
{
return $realContainerClass;
}
public function getContainerClass()
{
return $containerClass;
}
protected function buildContainer()
{
\$container = parent::buildContainer();
// filter container's resources, removing reference to temp kernel file
\$resources = \$container->getResources();
\$filteredResources = [];
foreach (\$resources as \$resource) {
if ((string) \$resource !== __FILE__) {
\$filteredResources[] = \$resource;
}
}
\$container->setResources(\$filteredResources);
return \$container;
}
}
}
EOF;
$this->filesystem->mkdir($warmupDir);
file_put_contents($file = $warmupDir.'/kernel.tmp', $code);
require_once $file;
$class = "$namespace\\$class";
return new $class($parent->getEnvironment(), $parent->isDebug());
}
}

View File

@@ -12,6 +12,9 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
@@ -24,28 +27,23 @@ use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer;
*
* @author Nicolas Grekas <p@tchwork.com>
*/
final class CachePoolClearCommand extends ContainerAwareCommand
final class CachePoolClearCommand extends Command
{
protected static $defaultName = 'cache:pool:clear';
protected static $defaultDescription = 'Clear cache pools';
private $poolClearer;
private $poolNames;
/**
* @param Psr6CacheClearer $poolClearer
* @param string[]|null $poolNames
*/
public function __construct($poolClearer = null)
public function __construct(Psr6CacheClearer $poolClearer, array $poolNames = null)
{
if (!$poolClearer instanceof Psr6CacheClearer) {
@trigger_error(sprintf('%s() expects an instance of "%s" as first argument since Symfony 3.4. Not passing it is deprecated and will throw a TypeError in 4.0.', __METHOD__, Psr6CacheClearer::class), \E_USER_DEPRECATED);
parent::__construct($poolClearer);
return;
}
parent::__construct();
$this->poolClearer = $poolClearer;
$this->poolNames = $poolNames;
}
/**
@@ -57,7 +55,7 @@ final class CachePoolClearCommand extends ContainerAwareCommand
->setDefinition([
new InputArgument('pools', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'A list of cache pools or cache pool clearers'),
])
->setDescription('Clears cache pools')
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command clears the given cache pools or cache pool clearers.
@@ -70,14 +68,8 @@ EOF
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
// BC to be removed in 4.0
if (null === $this->poolClearer) {
$this->poolClearer = $this->getContainer()->get('cache.global_clearer');
$cacheDir = $this->getContainer()->getParameter('kernel.cache_dir');
}
$io = new SymfonyStyle($input, $output);
$kernel = $this->getApplication()->getKernel();
$pools = [];
@@ -101,19 +93,39 @@ EOF
foreach ($clearers as $id => $clearer) {
$io->comment(sprintf('Calling cache clearer: <info>%s</info>', $id));
$clearer->clear(isset($cacheDir) ? $cacheDir : $kernel->getContainer()->getParameter('kernel.cache_dir'));
$clearer->clear($kernel->getContainer()->getParameter('kernel.cache_dir'));
}
$failure = false;
foreach ($pools as $id => $pool) {
$io->comment(sprintf('Clearing cache pool: <info>%s</info>', $id));
if ($pool instanceof CacheItemPoolInterface) {
$pool->clear();
if (!$pool->clear()) {
$io->warning(sprintf('Cache pool "%s" could not be cleared.', $pool));
$failure = true;
}
} else {
$this->poolClearer->clearPool($id);
if (false === $this->poolClearer->clearPool($id)) {
$io->warning(sprintf('Cache pool "%s" could not be cleared.', $pool));
$failure = true;
}
}
}
if ($failure) {
return 1;
}
$io->success('Cache was successfully cleared.');
return 0;
}
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if (\is_array($this->poolNames) && $input->mustSuggestArgumentValuesFor('pools')) {
$suggestions->suggestValues($this->poolNames);
}
}
}

View File

@@ -0,0 +1,98 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer;
/**
* Delete an item from a cache pool.
*
* @author Pierre du Plessis <pdples@gmail.com>
*/
final class CachePoolDeleteCommand extends Command
{
protected static $defaultName = 'cache:pool:delete';
protected static $defaultDescription = 'Delete an item from a cache pool';
private $poolClearer;
private $poolNames;
/**
* @param string[]|null $poolNames
*/
public function __construct(Psr6CacheClearer $poolClearer, array $poolNames = null)
{
parent::__construct();
$this->poolClearer = $poolClearer;
$this->poolNames = $poolNames;
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDefinition([
new InputArgument('pool', InputArgument::REQUIRED, 'The cache pool from which to delete an item'),
new InputArgument('key', InputArgument::REQUIRED, 'The cache key to delete from the pool'),
])
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> deletes an item from a given cache pool.
%command.full_name% <pool> <key>
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$pool = $input->getArgument('pool');
$key = $input->getArgument('key');
$cachePool = $this->poolClearer->getPool($pool);
if (!$cachePool->hasItem($key)) {
$io->note(sprintf('Cache item "%s" does not exist in cache pool "%s".', $key, $pool));
return 0;
}
if (!$cachePool->deleteItem($key)) {
throw new \Exception(sprintf('Cache item "%s" could not be deleted.', $key));
}
$io->success(sprintf('Cache item "%s" was successfully deleted.', $key));
return 0;
}
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if (\is_array($this->poolNames) && $input->mustSuggestArgumentValuesFor('pool')) {
$suggestions->suggestValues($this->poolNames);
}
}
}

View File

@@ -0,0 +1,68 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* List available cache pools.
*
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
final class CachePoolListCommand extends Command
{
protected static $defaultName = 'cache:pool:list';
protected static $defaultDescription = 'List available cache pools';
private $poolNames;
/**
* @param string[] $poolNames
*/
public function __construct(array $poolNames)
{
parent::__construct();
$this->poolNames = $poolNames;
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command lists all available cache pools.
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$io->table(['Pool name'], array_map(function ($pool) {
return [$pool];
}, $this->poolNames));
return 0;
}
}

View File

@@ -25,13 +25,14 @@ use Symfony\Component\Console\Style\SymfonyStyle;
final class CachePoolPruneCommand extends Command
{
protected static $defaultName = 'cache:pool:prune';
protected static $defaultDescription = 'Prune cache pools';
private $pools;
/**
* @param iterable|PruneableInterface[] $pools
* @param iterable<mixed, PruneableInterface> $pools
*/
public function __construct($pools)
public function __construct(iterable $pools)
{
parent::__construct();
@@ -44,7 +45,7 @@ final class CachePoolPruneCommand extends Command
protected function configure()
{
$this
->setDescription('Prunes cache pools')
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command deletes all expired items from all pruneable pools.
@@ -57,7 +58,7 @@ EOF
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
@@ -67,5 +68,7 @@ EOF
}
$io->success('Successfully pruned cache pool(s).');
return 0;
}
}

View File

@@ -11,10 +11,12 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Dumper\Preloader;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate;
/**
@@ -22,27 +24,17 @@ use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate;
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final since version 3.4
* @final
*/
class CacheWarmupCommand extends ContainerAwareCommand
class CacheWarmupCommand extends Command
{
protected static $defaultName = 'cache:warmup';
protected static $defaultDescription = 'Warm up an empty cache';
private $cacheWarmer;
/**
* @param CacheWarmerAggregate $cacheWarmer
*/
public function __construct($cacheWarmer = null)
public function __construct(CacheWarmerAggregate $cacheWarmer)
{
if (!$cacheWarmer instanceof CacheWarmerAggregate) {
@trigger_error(sprintf('Passing a command name as the first argument of "%s()" is deprecated since Symfony 3.4 and support for it will be removed in 4.0. If the command was registered by convention, make it a service instead.', __METHOD__), \E_USER_DEPRECATED);
parent::__construct($cacheWarmer);
return;
}
parent::__construct();
$this->cacheWarmer = $cacheWarmer;
@@ -57,7 +49,7 @@ class CacheWarmupCommand extends ContainerAwareCommand
->setDefinition([
new InputOption('no-optional-warmers', '', InputOption::VALUE_NONE, 'Skip optional cache warmers (faster)'),
])
->setDescription('Warms up an empty cache')
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command warms up the cache.
@@ -76,14 +68,8 @@ EOF
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
// BC to be removed in 4.0
if (null === $this->cacheWarmer) {
$this->cacheWarmer = $this->getContainer()->get('cache_warmer');
$cacheDir = $this->getContainer()->getParameter('kernel.cache_dir');
}
$io = new SymfonyStyle($input, $output);
$kernel = $this->getApplication()->getKernel();
@@ -93,8 +79,14 @@ EOF
$this->cacheWarmer->enableOptionalWarmers();
}
$this->cacheWarmer->warmUp(isset($cacheDir) ? $cacheDir : $kernel->getContainer()->getParameter('kernel.cache_dir'));
$preload = $this->cacheWarmer->warmUp($cacheDir = $kernel->getContainer()->getParameter('kernel.cache_dir'));
if ($preload && file_exists($preloadFile = $cacheDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) {
Preloader::append($preloadFile, $preload);
}
$io->success(sprintf('Cache for the "%s" environment (debug=%s) was successfully warmed.', $kernel->getEnvironment(), var_export($kernel->isDebug(), true)));
return 0;
}
}

View File

@@ -11,12 +11,19 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Compiler\ValidateEnvPlaceholdersPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\Yaml\Yaml;
/**
@@ -24,11 +31,12 @@ use Symfony\Component\Yaml\Yaml;
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*
* @final since version 3.4
* @final
*/
class ConfigDebugCommand extends AbstractConfigCommand
{
protected static $defaultName = 'debug:config';
protected static $defaultDescription = 'Dump the current configuration for an extension';
/**
* {@inheritdoc}
@@ -40,7 +48,7 @@ class ConfigDebugCommand extends AbstractConfigCommand
new InputArgument('name', InputArgument::OPTIONAL, 'The bundle name or the extension alias'),
new InputArgument('path', InputArgument::OPTIONAL, 'The configuration option path'),
])
->setDescription('Dumps the current configuration for an extension')
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command dumps the current configuration for an
extension/bundle.
@@ -62,32 +70,33 @@ EOF
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$errorIo = $io->getErrorStyle();
if (null === $name = $input->getArgument('name')) {
$this->listBundles($errorIo);
$kernel = $this->getApplication()->getKernel();
if ($kernel instanceof ExtensionInterface
&& ($kernel instanceof ConfigurationInterface || $kernel instanceof ConfigurationExtensionInterface)
&& $kernel->getAlias()
) {
$errorIo->table(['Kernel Extension'], [[$kernel->getAlias()]]);
}
$errorIo->comment('Provide the name of a bundle as the first argument of this command to dump its configuration. (e.g. <comment>debug:config FrameworkBundle</comment>)');
$errorIo->comment('For dumping a specific option, add its path as the second argument of this command. (e.g. <comment>debug:config FrameworkBundle serializer</comment> to dump the <comment>framework.serializer</comment> configuration)');
return;
return 0;
}
$extension = $this->findExtension($name);
$extensionAlias = $extension->getAlias();
$container = $this->compileContainer();
$extensionAlias = $extension->getAlias();
$configs = $container->getExtensionConfig($extensionAlias);
$configuration = $extension->getConfiguration($configs, $container);
$this->validateConfiguration($extension, $configuration);
$configs = $container->resolveEnvPlaceholders($container->getParameterBag()->resolveValue($configs));
$processor = new Processor();
$config = $container->resolveEnvPlaceholders($container->getParameterBag()->resolveValue($processor->processConfiguration($configuration, $configs)));
$config = $this->getConfig($extension, $container);
if (null === $path = $input->getArgument('path')) {
$io->title(
@@ -96,7 +105,7 @@ EOF
$io->writeln(Yaml::dump([$extensionAlias => $config], 10));
return;
return 0;
}
try {
@@ -110,9 +119,11 @@ EOF
$io->title(sprintf('Current configuration for "%s.%s"', $extensionAlias, $path));
$io->writeln(Yaml::dump($config, 10));
return 0;
}
private function compileContainer()
private function compileContainer(): ContainerBuilder
{
$kernel = clone $this->getApplication()->getKernel();
$kernel->boot();
@@ -128,13 +139,11 @@ EOF
/**
* Iterate over configuration until the last step of the given path.
*
* @param array $config A bundle configuration
*
* @throws LogicException If the configuration does not exist
*
* @return mixed
*/
private function getConfigForPath(array $config, $path, $alias)
private function getConfigForPath(array $config, string $path, string $alias)
{
$steps = explode('.', $path);
@@ -148,4 +157,84 @@ EOF
return $config;
}
private function getConfigForExtension(ExtensionInterface $extension, ContainerBuilder $container): array
{
$extensionAlias = $extension->getAlias();
$extensionConfig = [];
foreach ($container->getCompilerPassConfig()->getPasses() as $pass) {
if ($pass instanceof ValidateEnvPlaceholdersPass) {
$extensionConfig = $pass->getExtensionConfig();
break;
}
}
if (isset($extensionConfig[$extensionAlias])) {
return $extensionConfig[$extensionAlias];
}
// Fall back to default config if the extension has one
if (!$extension instanceof ConfigurationExtensionInterface) {
throw new \LogicException(sprintf('The extension with alias "%s" does not have configuration.', $extensionAlias));
}
$configs = $container->getExtensionConfig($extensionAlias);
$configuration = $extension->getConfiguration($configs, $container);
$this->validateConfiguration($extension, $configuration);
return (new Processor())->processConfiguration($configuration, $configs);
}
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('name')) {
$suggestions->suggestValues($this->getAvailableBundles(!preg_match('/^[A-Z]/', $input->getCompletionValue())));
return;
}
if ($input->mustSuggestArgumentValuesFor('path') && null !== $name = $input->getArgument('name')) {
try {
$config = $this->getConfig($this->findExtension($name), $this->compileContainer());
$paths = array_keys(self::buildPathsCompletion($config));
$suggestions->suggestValues($paths);
} catch (LogicException $e) {
}
}
}
private function getAvailableBundles(bool $alias): array
{
$availableBundles = [];
foreach ($this->getApplication()->getKernel()->getBundles() as $bundle) {
$availableBundles[] = $alias ? $bundle->getContainerExtension()->getAlias() : $bundle->getName();
}
return $availableBundles;
}
private function getConfig(ExtensionInterface $extension, ContainerBuilder $container)
{
return $container->resolveEnvPlaceholders(
$container->getParameterBag()->resolveValue(
$this->getConfigForExtension($extension, $container)
)
);
}
private static function buildPathsCompletion(array $paths, string $prefix = ''): array
{
$completionPaths = [];
foreach ($paths as $key => $values) {
if (\is_array($values)) {
$completionPaths = $completionPaths + self::buildPathsCompletion($values, $prefix.$key.'.');
} else {
$completionPaths[$prefix.$key] = null;
}
}
return $completionPaths;
}
}

View File

@@ -11,14 +11,20 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Dumper\XmlReferenceDumper;
use Symfony\Component\Config\Definition\Dumper\YamlReferenceDumper;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\Yaml\Yaml;
/**
* A console command for dumping available configuration reference.
@@ -27,11 +33,12 @@ use Symfony\Component\Console\Style\SymfonyStyle;
* @author Wouter J <waldio.webdesign@gmail.com>
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*
* @final since version 3.4
* @final
*/
class ConfigDumpReferenceCommand extends AbstractConfigCommand
{
protected static $defaultName = 'config:dump-reference';
protected static $defaultDescription = 'Dump the default configuration for an extension';
/**
* {@inheritdoc}
@@ -44,7 +51,7 @@ class ConfigDumpReferenceCommand extends AbstractConfigCommand
new InputArgument('path', InputArgument::OPTIONAL, 'The configuration option path'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (yaml or xml)', 'yaml'),
])
->setDescription('Dumps the default configuration for an extension')
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command dumps the default configuration for an
extension/bundle.
@@ -74,28 +81,48 @@ EOF
*
* @throws \LogicException
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$errorIo = $io->getErrorStyle();
if (null === $name = $input->getArgument('name')) {
$this->listBundles($errorIo);
$kernel = $this->getApplication()->getKernel();
if ($kernel instanceof ExtensionInterface
&& ($kernel instanceof ConfigurationInterface || $kernel instanceof ConfigurationExtensionInterface)
&& $kernel->getAlias()
) {
$errorIo->table(['Kernel Extension'], [[$kernel->getAlias()]]);
}
$errorIo->comment([
'Provide the name of a bundle as the first argument of this command to dump its default configuration. (e.g. <comment>config:dump-reference FrameworkBundle</comment>)',
'For dumping a specific option, add its path as the second argument of this command. (e.g. <comment>config:dump-reference FrameworkBundle profiler.matcher</comment> to dump the <comment>framework.profiler.matcher</comment> configuration)',
]);
return null;
return 0;
}
$extension = $this->findExtension($name);
$configuration = $extension->getConfiguration([], $this->getContainerBuilder());
if ($extension instanceof ConfigurationInterface) {
$configuration = $extension;
} else {
$configuration = $extension->getConfiguration([], $this->getContainerBuilder($this->getApplication()->getKernel()));
}
$this->validateConfiguration($extension, $configuration);
$format = $input->getOption('format');
if ('yaml' === $format && !class_exists(Yaml::class)) {
$errorIo->error('Setting the "format" option to "yaml" requires the Symfony Yaml component. Try running "composer install symfony/yaml" or use "--format=xml" instead.');
return 1;
}
$path = $input->getArgument('path');
if (null !== $path && 'yaml' !== $format) {
@@ -130,6 +157,34 @@ EOF
$io->writeln(null === $path ? $dumper->dump($configuration, $extension->getNamespace()) : $dumper->dumpAtPath($configuration, $path));
return null;
return 0;
}
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('name')) {
$suggestions->suggestValues($this->getAvailableBundles());
}
if ($input->mustSuggestOptionValuesFor('format')) {
$suggestions->suggestValues($this->getAvailableFormatOptions());
}
}
private function getAvailableBundles(): array
{
$bundles = [];
foreach ($this->getApplication()->getKernel()->getBundles() as $bundle) {
$bundles[] = $bundle->getName();
$bundles[] = $bundle->getContainerExtension()->getAlias();
}
return $bundles;
}
private function getAvailableFormatOptions(): array
{
return ['yaml', 'xml'];
}
}

View File

@@ -1,56 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Command.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class ContainerAwareCommand extends Command implements ContainerAwareInterface
{
/**
* @var ContainerInterface|null
*/
private $container;
/**
* @return ContainerInterface
*
* @throws \LogicException
*/
protected function getContainer()
{
if (null === $this->container) {
$application = $this->getApplication();
if (null === $application) {
throw new \LogicException('The container cannot be retrieved as the application instance is not yet set.');
}
$this->container = $application->getKernel()->getContainer();
}
return $this->container;
}
/**
* {@inheritdoc}
*/
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
}

View File

@@ -12,17 +12,17 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper;
use Symfony\Component\Config\ConfigCache;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
/**
@@ -30,16 +30,14 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
*
* @author Ryan Weaver <ryan@thatsquality.com>
*
* @internal since version 3.4
* @internal
*/
class ContainerDebugCommand extends ContainerAwareCommand
class ContainerDebugCommand extends Command
{
protected static $defaultName = 'debug:container';
use BuildDebugContainerTrait;
/**
* @var ContainerBuilder|null
*/
protected $containerBuilder;
protected static $defaultName = 'debug:container';
protected static $defaultDescription = 'Display current services for an application';
/**
* {@inheritdoc}
@@ -49,34 +47,48 @@ class ContainerDebugCommand extends ContainerAwareCommand
$this
->setDefinition([
new InputArgument('name', InputArgument::OPTIONAL, 'A service name (foo)'),
new InputOption('show-private', null, InputOption::VALUE_NONE, 'Used to show public *and* private services'),
new InputOption('show-arguments', null, InputOption::VALUE_NONE, 'Used to show arguments in services'),
new InputOption('tag', null, InputOption::VALUE_REQUIRED, 'Shows all services with a specific tag'),
new InputOption('tags', null, InputOption::VALUE_NONE, 'Displays tagged services for an application'),
new InputOption('parameter', null, InputOption::VALUE_REQUIRED, 'Displays a specific parameter for an application'),
new InputOption('parameters', null, InputOption::VALUE_NONE, 'Displays parameters for an application'),
new InputOption('types', null, InputOption::VALUE_NONE, 'Displays types (classes/interfaces) available in the container'),
new InputOption('show-arguments', null, InputOption::VALUE_NONE, 'Show arguments in services'),
new InputOption('show-hidden', null, InputOption::VALUE_NONE, 'Show hidden (internal) services'),
new InputOption('tag', null, InputOption::VALUE_REQUIRED, 'Show all services with a specific tag'),
new InputOption('tags', null, InputOption::VALUE_NONE, 'Display tagged services for an application'),
new InputOption('parameter', null, InputOption::VALUE_REQUIRED, 'Display a specific parameter for an application'),
new InputOption('parameters', null, InputOption::VALUE_NONE, 'Display parameters for an application'),
new InputOption('types', null, InputOption::VALUE_NONE, 'Display types (classes/interfaces) available in the container'),
new InputOption('env-var', null, InputOption::VALUE_REQUIRED, 'Display a specific environment variable used in the container'),
new InputOption('env-vars', null, InputOption::VALUE_NONE, 'Display environment variables used in the container'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw description'),
new InputOption('deprecations', null, InputOption::VALUE_NONE, 'Display deprecations generated when compiling and warming up the container'),
])
->setDescription('Displays current services for an application')
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command displays all configured <comment>public</comment> services:
<info>php %command.full_name%</info>
To see deprecations generated during container compilation and cache warmup, use the <info>--deprecations</info> option:
<info>php %command.full_name% --deprecations</info>
To get specific information about a service, specify its name:
<info>php %command.full_name% validator</info>
To get specific information about a service including all its arguments, use the <info>--show-arguments</info> flag:
<info>php %command.full_name% validator --show-arguments</info>
To see available types that can be used for autowiring, use the <info>--types</info> flag:
<info>php %command.full_name% --types</info>
By default, private services are hidden. You can display all services by
using the <info>--show-private</info> flag:
To see environment variables used by the container, use the <info>--env-vars</info> flag:
<info>php %command.full_name% --show-private</info>
<info>php %command.full_name% --env-vars</info>
Display a specific environment variable by specifying its name with the <info>--env-var</info> option:
<info>php %command.full_name% --env-var=APP_ENV</info>
Use the --tags option to display tagged <comment>public</comment> services grouped by tag:
@@ -94,6 +106,11 @@ Display a specific parameter by specifying its name with the <info>--parameter</
<info>php %command.full_name% --parameter=kernel.debug</info>
By default, internal services are hidden. You can display them
using the <info>--show-hidden</info> flag:
<info>php %command.full_name% --show-hidden</info>
EOF
)
;
@@ -102,16 +119,21 @@ EOF
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$errorIo = $io->getErrorStyle();
$this->validateInput($input);
$object = $this->getContainerBuilder();
$kernel = $this->getApplication()->getKernel();
$object = $this->getContainerBuilder($kernel);
if ($input->getOption('types')) {
$options = ['show_private' => true];
if ($input->getOption('env-vars')) {
$options = ['env-vars' => true];
} elseif ($envVar = $input->getOption('env-var')) {
$options = ['env-vars' => true, 'name' => $envVar];
} elseif ($input->getOption('types')) {
$options = [];
$options['filter'] = [$this, 'filterToServiceTypes'];
} elseif ($input->getOption('parameters')) {
$parameters = [];
@@ -123,33 +145,89 @@ EOF
} elseif ($parameter = $input->getOption('parameter')) {
$options = ['parameter' => $parameter];
} elseif ($input->getOption('tags')) {
$options = ['group_by' => 'tags', 'show_private' => $input->getOption('show-private')];
$options = ['group_by' => 'tags'];
} elseif ($tag = $input->getOption('tag')) {
$options = ['tag' => $tag, 'show_private' => $input->getOption('show-private')];
$options = ['tag' => $tag];
} elseif ($name = $input->getArgument('name')) {
$name = $this->findProperServiceName($input, $errorIo, $object, $name);
$name = $this->findProperServiceName($input, $errorIo, $object, $name, $input->getOption('show-hidden'));
$options = ['id' => $name];
} elseif ($input->getOption('deprecations')) {
$options = ['deprecations' => true];
} else {
$options = ['show_private' => $input->getOption('show-private')];
$options = [];
}
$helper = new DescriptorHelper();
$options['format'] = $input->getOption('format');
$options['show_arguments'] = $input->getOption('show-arguments');
$options['show_hidden'] = $input->getOption('show-hidden');
$options['raw_text'] = $input->getOption('raw');
$options['output'] = $io;
$options['is_debug'] = $this->getApplication()->getKernel()->isDebug();
$helper->describe($io, $object, $options);
$options['is_debug'] = $kernel->isDebug();
if (!$input->getArgument('name') && !$input->getOption('tag') && !$input->getOption('parameter') && $input->isInteractive()) {
try {
$helper->describe($io, $object, $options);
if (isset($options['id']) && isset($kernel->getContainer()->getRemovedIds()[$options['id']])) {
$errorIo->note(sprintf('The "%s" service or alias has been removed or inlined when the container was compiled.', $options['id']));
}
} catch (ServiceNotFoundException $e) {
if ('' !== $e->getId() && '@' === $e->getId()[0]) {
throw new ServiceNotFoundException($e->getId(), $e->getSourceId(), null, [substr($e->getId(), 1)]);
}
throw $e;
}
if (!$input->getArgument('name') && !$input->getOption('tag') && !$input->getOption('parameter') && !$input->getOption('env-vars') && !$input->getOption('env-var') && $input->isInteractive()) {
if ($input->getOption('tags')) {
$errorIo->comment('To search for a specific tag, re-run this command with a search term. (e.g. <comment>debug:container --tag=form.type</comment>)');
} elseif ($input->getOption('parameters')) {
$errorIo->comment('To search for a specific parameter, re-run this command with a search term. (e.g. <comment>debug:container --parameter=kernel.debug</comment>)');
} else {
} elseif (!$input->getOption('deprecations')) {
$errorIo->comment('To search for a specific service, re-run this command with a search term. (e.g. <comment>debug:container log</comment>)');
}
}
return 0;
}
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestOptionValuesFor('format')) {
$helper = new DescriptorHelper();
$suggestions->suggestValues($helper->getFormats());
return;
}
$kernel = $this->getApplication()->getKernel();
$object = $this->getContainerBuilder($kernel);
if ($input->mustSuggestArgumentValuesFor('name')
&& !$input->getOption('tag') && !$input->getOption('tags')
&& !$input->getOption('parameter') && !$input->getOption('parameters')
&& !$input->getOption('env-var') && !$input->getOption('env-vars')
&& !$input->getOption('types') && !$input->getOption('deprecations')
) {
$suggestions->suggestValues($this->findServiceIdsContaining(
$object,
$input->getCompletionValue(),
(bool) $input->getOption('show-hidden')
));
return;
}
if ($input->mustSuggestOptionValuesFor('tag')) {
$suggestions->suggestValues($object->findTags());
return;
}
if ($input->mustSuggestOptionValuesFor('parameter')) {
$suggestions->suggestValues(array_keys($object->getParameterBag()->all()));
}
}
/**
@@ -170,94 +248,66 @@ EOF
$name = $input->getArgument('name');
if ((null !== $name) && ($optionsCount > 0)) {
throw new InvalidArgumentException('The options tags, tag, parameters & parameter can not be combined with the service name argument.');
throw new InvalidArgumentException('The options tags, tag, parameters & parameter cannot be combined with the service name argument.');
} elseif ((null === $name) && $optionsCount > 1) {
throw new InvalidArgumentException('The options tags, tag, parameters & parameter can not be combined together.');
throw new InvalidArgumentException('The options tags, tag, parameters & parameter cannot be combined together.');
}
}
/**
* Loads the ContainerBuilder from the cache.
*
* @return ContainerBuilder
*
* @throws \LogicException
*/
protected function getContainerBuilder()
private function findProperServiceName(InputInterface $input, SymfonyStyle $io, ContainerBuilder $builder, string $name, bool $showHidden): string
{
if ($this->containerBuilder) {
return $this->containerBuilder;
}
$name = ltrim($name, '\\');
$kernel = $this->getApplication()->getKernel();
if (!$kernel->isDebug() || !(new ConfigCache($kernel->getContainer()->getParameter('debug.container.dump'), true))->isFresh()) {
$buildContainer = \Closure::bind(function () { return $this->buildContainer(); }, $kernel, \get_class($kernel));
$container = $buildContainer();
$container->getCompilerPassConfig()->setRemovingPasses([]);
$container->getCompilerPassConfig()->setAfterRemovingPasses([]);
$container->compile();
} else {
(new XmlFileLoader($container = new ContainerBuilder(), new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump'));
$locatorPass = new ServiceLocatorTagPass();
$locatorPass->process($container);
}
return $this->containerBuilder = $container;
}
private function findProperServiceName(InputInterface $input, SymfonyStyle $io, ContainerBuilder $builder, $name)
{
if ($builder->has($name) || !$input->isInteractive()) {
return $name;
}
$matchingServices = $this->findServiceIdsContaining($builder, $name);
$matchingServices = $this->findServiceIdsContaining($builder, $name, $showHidden);
if (empty($matchingServices)) {
throw new InvalidArgumentException(sprintf('No services found that match "%s".', $name));
}
$default = 1 === \count($matchingServices) ? $matchingServices[0] : null;
return $io->choice('Select one of the following services to display its information', $matchingServices, $default);
}
private function findServiceIdsContaining(ContainerBuilder $builder, $name)
{
$serviceIds = $builder->getServiceIds();
$foundServiceIds = [];
foreach ($serviceIds as $serviceId) {
if (false === stripos($serviceId, $name)) {
continue;
}
$foundServiceIds[] = $serviceId;
if (1 === \count($matchingServices)) {
return $matchingServices[0];
}
return $foundServiceIds;
return $io->choice('Select one of the following services to display its information', $matchingServices);
}
private function findServiceIdsContaining(ContainerBuilder $builder, string $name, bool $showHidden): array
{
$serviceIds = $builder->getServiceIds();
$foundServiceIds = $foundServiceIdsIgnoringBackslashes = [];
foreach ($serviceIds as $serviceId) {
if (!$showHidden && str_starts_with($serviceId, '.')) {
continue;
}
if (false !== stripos(str_replace('\\', '', $serviceId), $name)) {
$foundServiceIdsIgnoringBackslashes[] = $serviceId;
}
if ('' === $name || false !== stripos($serviceId, $name)) {
$foundServiceIds[] = $serviceId;
}
}
return $foundServiceIds ?: $foundServiceIdsIgnoringBackslashes;
}
/**
* @internal
*/
public function filterToServiceTypes($serviceId)
public function filterToServiceTypes(string $serviceId): bool
{
// filter out things that could not be valid class names
if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+$/', $serviceId)) {
if (!preg_match('/(?(DEFINE)(?<V>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))^(?&V)(?:\\\\(?&V))*+(?: \$(?&V))?$/', $serviceId)) {
return false;
}
// if the id has a \, assume it is a class
if (false !== strpos($serviceId, '\\')) {
if (str_contains($serviceId, '\\')) {
return true;
}
try {
new \ReflectionClass($serviceId);
return true;
} catch (\ReflectionException $e) {
// the service id is not a valid class/interface
return false;
}
return class_exists($serviceId) || interface_exists($serviceId, false);
}
}

View File

@@ -0,0 +1,134 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Config\ConfigCache;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Compiler\CheckTypeDeclarationsPass;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
use Symfony\Component\HttpKernel\Kernel;
final class ContainerLintCommand extends Command
{
protected static $defaultName = 'lint:container';
protected static $defaultDescription = 'Ensure that arguments injected into services match type declarations';
/**
* @var ContainerBuilder
*/
private $containerBuilder;
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDescription(self::$defaultDescription)
->setHelp('This command parses service definitions and ensures that injected values match the type declarations of each services\' class.')
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$errorIo = $io->getErrorStyle();
try {
$container = $this->getContainerBuilder();
} catch (RuntimeException $e) {
$errorIo->error($e->getMessage());
return 2;
}
$container->setParameter('container.build_time', time());
try {
$container->compile();
} catch (InvalidArgumentException $e) {
$errorIo->error($e->getMessage());
return 1;
}
$io->success('The container was linted successfully: all services are injected with values that are compatible with their type declarations.');
return 0;
}
private function getContainerBuilder(): ContainerBuilder
{
if ($this->containerBuilder) {
return $this->containerBuilder;
}
$kernel = $this->getApplication()->getKernel();
$kernelContainer = $kernel->getContainer();
if (!$kernel->isDebug() || !(new ConfigCache($kernelContainer->getParameter('debug.container.dump'), true))->isFresh()) {
if (!$kernel instanceof Kernel) {
throw new RuntimeException(sprintf('This command does not support the application kernel: "%s" does not extend "%s".', get_debug_type($kernel), Kernel::class));
}
$buildContainer = \Closure::bind(function (): ContainerBuilder {
$this->initializeBundles();
return $this->buildContainer();
}, $kernel, \get_class($kernel));
$container = $buildContainer();
$skippedIds = [];
} else {
if (!$kernelContainer instanceof Container) {
throw new RuntimeException(sprintf('This command does not support the application container: "%s" does not extend "%s".', get_debug_type($kernelContainer), Container::class));
}
(new XmlFileLoader($container = new ContainerBuilder($parameterBag = new EnvPlaceholderParameterBag()), new FileLocator()))->load($kernelContainer->getParameter('debug.container.dump'));
$refl = new \ReflectionProperty($parameterBag, 'resolved');
$refl->setAccessible(true);
$refl->setValue($parameterBag, true);
$skippedIds = [];
foreach ($container->getServiceIds() as $serviceId) {
if (str_starts_with($serviceId, '.errored.')) {
$skippedIds[$serviceId] = true;
}
}
$container->getCompilerPassConfig()->setBeforeOptimizationPasses([]);
$container->getCompilerPassConfig()->setOptimizationPasses([]);
$container->getCompilerPassConfig()->setBeforeRemovingPasses([]);
}
$container->setParameter('container.build_hash', 'lint_container');
$container->setParameter('container.build_id', 'lint_container');
$container->addCompilerPass(new CheckTypeDeclarationsPass(true, $skippedIds), PassConfig::TYPE_AFTER_REMOVING, -100);
return $this->containerBuilder = $container;
}
}

View File

@@ -11,10 +11,16 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Bundle\FrameworkBundle\Console\Descriptor\Descriptor;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
/**
* A console command for autowiring information.
@@ -26,6 +32,17 @@ use Symfony\Component\Console\Style\SymfonyStyle;
class DebugAutowiringCommand extends ContainerDebugCommand
{
protected static $defaultName = 'debug:autowiring';
protected static $defaultDescription = 'List classes/interfaces you can use for autowiring';
private $supportsHref;
private $fileLinkFormatter;
public function __construct(string $name = null, FileLinkFormatter $fileLinkFormatter = null)
{
$this->supportsHref = method_exists(OutputFormatterStyle::class, 'setHref');
$this->fileLinkFormatter = $fileLinkFormatter;
parent::__construct($name);
}
/**
* {@inheritdoc}
@@ -35,10 +52,11 @@ class DebugAutowiringCommand extends ContainerDebugCommand
$this
->setDefinition([
new InputArgument('search', InputArgument::OPTIONAL, 'A search filter'),
new InputOption('all', null, InputOption::VALUE_NONE, 'Show also services that are not aliased'),
])
->setDescription('Lists classes/interfaces you can use for autowiring')
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command displays all classes and interfaces that
The <info>%command.name%</info> command displays the classes and interfaces that
you can use as type-hints for autowiring:
<info>php %command.full_name%</info>
@@ -55,18 +73,20 @@ EOF
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$errorIo = $io->getErrorStyle();
$builder = $this->getContainerBuilder();
$builder = $this->getContainerBuilder($this->getApplication()->getKernel());
$serviceIds = $builder->getServiceIds();
$serviceIds = array_filter($serviceIds, [$this, 'filterToServiceTypes']);
if ($search = $input->getArgument('search')) {
$serviceIds = array_filter($serviceIds, function ($serviceId) use ($search) {
return false !== stripos($serviceId, $search);
$searchNormalized = preg_replace('/[^a-zA-Z0-9\x7f-\xff $]++/', '', $search);
$serviceIds = array_filter($serviceIds, function ($serviceId) use ($searchNormalized) {
return false !== stripos(str_replace('\\', '', $serviceId), $searchNormalized) && !str_starts_with($serviceId, '.');
});
if (empty($serviceIds)) {
@@ -76,24 +96,82 @@ EOF
}
}
asort($serviceIds);
uasort($serviceIds, 'strnatcmp');
$io->title('Autowirable Services');
$io->title('Autowirable Types');
$io->text('The following classes & interfaces can be used as type-hints when autowiring:');
if ($search) {
$io->text(sprintf('(only showing classes/interfaces matching <comment>%s</comment>)', $search));
}
$io->newLine();
$tableRows = [];
$hasAlias = [];
$all = $input->getOption('all');
$previousId = '-';
$serviceIdsNb = 0;
foreach ($serviceIds as $serviceId) {
$tableRows[] = [sprintf('<fg=cyan>%s</fg=cyan>', $serviceId)];
if ($builder->hasAlias($serviceId)) {
$tableRows[] = [sprintf(' alias to %s', $builder->getAlias($serviceId))];
$text = [];
$resolvedServiceId = $serviceId;
if (!str_starts_with($serviceId, $previousId)) {
$text[] = '';
if ('' !== $description = Descriptor::getClassDescription($serviceId, $resolvedServiceId)) {
if (isset($hasAlias[$serviceId])) {
continue;
}
$text[] = $description;
}
$previousId = $serviceId.' $';
}
$serviceLine = sprintf('<fg=yellow>%s</>', $serviceId);
if ($this->supportsHref && '' !== $fileLink = $this->getFileLink($serviceId)) {
$serviceLine = sprintf('<fg=yellow;href=%s>%s</>', $fileLink, $serviceId);
}
if ($builder->hasAlias($serviceId)) {
$hasAlias[$serviceId] = true;
$serviceAlias = $builder->getAlias($serviceId);
$serviceLine .= ' <fg=cyan>('.$serviceAlias.')</>';
if ($serviceAlias->isDeprecated()) {
$serviceLine .= ' - <fg=magenta>deprecated</>';
}
} elseif (!$all) {
++$serviceIdsNb;
continue;
}
$text[] = $serviceLine;
$io->text($text);
}
$io->table([], $tableRows);
$io->newLine();
return null;
if (0 < $serviceIdsNb) {
$io->text(sprintf('%s more concrete service%s would be displayed when adding the "--all" option.', $serviceIdsNb, $serviceIdsNb > 1 ? 's' : ''));
}
if ($all) {
$io->text('Pro-tip: use interfaces in your type-hints instead of classes to benefit from the dependency inversion principle.');
}
$io->newLine();
return 0;
}
private function getFileLink(string $class): string
{
if (null === $this->fileLinkFormatter
|| (null === $r = $this->getContainerBuilder($this->getApplication()->getKernel())->getReflectionClass($class, false))) {
return '';
}
return (string) $this->fileLinkFormatter->format($r->getFileName(), $r->getStartLine());
}
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('search')) {
$builder = $this->getContainerBuilder($this->getApplication()->getKernel());
$suggestions->suggestValues(array_filter($builder->getServiceIds(), [$this, 'filterToServiceTypes']));
}
}
}

View File

@@ -11,42 +11,39 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Psr\Container\ContainerInterface;
use Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\Service\ServiceProviderInterface;
/**
* A console command for retrieving information about event dispatcher.
*
* @author Matthieu Auger <mail@matthieuauger.com>
*
* @final since version 3.4
* @final
*/
class EventDispatcherDebugCommand extends ContainerAwareCommand
class EventDispatcherDebugCommand extends Command
{
private const DEFAULT_DISPATCHER = 'event_dispatcher';
protected static $defaultName = 'debug:event-dispatcher';
private $dispatcher;
protected static $defaultDescription = 'Display configured listeners for an application';
private $dispatchers;
/**
* @param EventDispatcherInterface $dispatcher
*/
public function __construct($dispatcher = null)
public function __construct(ContainerInterface $dispatchers)
{
if (!$dispatcher instanceof EventDispatcherInterface) {
@trigger_error(sprintf('%s() expects an instance of "%s" as first argument since Symfony 3.4. Not passing it is deprecated and will throw a TypeError in 4.0.', __METHOD__, EventDispatcherInterface::class), \E_USER_DEPRECATED);
parent::__construct($dispatcher);
return;
}
parent::__construct();
$this->dispatcher = $dispatcher;
$this->dispatchers = $dispatchers;
}
/**
@@ -56,11 +53,12 @@ class EventDispatcherDebugCommand extends ContainerAwareCommand
{
$this
->setDefinition([
new InputArgument('event', InputArgument::OPTIONAL, 'An event name'),
new InputArgument('event', InputArgument::OPTIONAL, 'An event name or a part of the event name'),
new InputOption('dispatcher', null, InputOption::VALUE_REQUIRED, 'To view events of a specific event dispatcher', self::DEFAULT_DISPATCHER),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw description'),
])
->setDescription('Displays configured listeners for an application')
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command displays all configured listeners:
@@ -79,42 +77,88 @@ EOF
*
* @throws \LogicException
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
// BC to be removed in 4.0
if (null === $this->dispatcher) {
$this->dispatcher = $this->getEventDispatcher();
}
$io = new SymfonyStyle($input, $output);
$options = [];
$dispatcherServiceName = $input->getOption('dispatcher');
if (!$this->dispatchers->has($dispatcherServiceName)) {
$io->getErrorStyle()->error(sprintf('Event dispatcher "%s" is not available.', $dispatcherServiceName));
return 1;
}
$dispatcher = $this->dispatchers->get($dispatcherServiceName);
if ($event = $input->getArgument('event')) {
if (!$this->dispatcher->hasListeners($event)) {
$io->getErrorStyle()->warning(sprintf('The event "%s" does not have any registered listeners.', $event));
if ($dispatcher->hasListeners($event)) {
$options = ['event' => $event];
} else {
// if there is no direct match, try find partial matches
$events = $this->searchForEvent($dispatcher, $event);
if (0 === \count($events)) {
$io->getErrorStyle()->warning(sprintf('The event "%s" does not have any registered listeners.', $event));
return;
return 0;
} elseif (1 === \count($events)) {
$options = ['event' => $events[array_key_first($events)]];
} else {
$options = ['events' => $events];
}
}
$options = ['event' => $event];
}
$helper = new DescriptorHelper();
if (self::DEFAULT_DISPATCHER !== $dispatcherServiceName) {
$options['dispatcher_service_name'] = $dispatcherServiceName;
}
$options['format'] = $input->getOption('format');
$options['raw_text'] = $input->getOption('raw');
$options['output'] = $io;
$helper->describe($io, $this->dispatcher, $options);
$helper->describe($io, $dispatcher, $options);
return 0;
}
/**
* Loads the Event Dispatcher from the container.
*
* BC to removed in 4.0
*
* @return EventDispatcherInterface
*/
protected function getEventDispatcher()
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
return $this->getContainer()->get('event_dispatcher');
if ($input->mustSuggestArgumentValuesFor('event')) {
$dispatcherServiceName = $input->getOption('dispatcher');
if ($this->dispatchers->has($dispatcherServiceName)) {
$dispatcher = $this->dispatchers->get($dispatcherServiceName);
$suggestions->suggestValues(array_keys($dispatcher->getListeners()));
}
return;
}
if ($input->mustSuggestOptionValuesFor('dispatcher')) {
if ($this->dispatchers instanceof ServiceProviderInterface) {
$suggestions->suggestValues(array_keys($this->dispatchers->getProvidedServices()));
}
return;
}
if ($input->mustSuggestOptionValuesFor('format')) {
$suggestions->suggestValues((new DescriptorHelper())->getFormats());
}
}
private function searchForEvent(EventDispatcherInterface $dispatcher, string $needle): array
{
$output = [];
$lcNeedle = strtolower($needle);
$allEvents = array_keys($dispatcher->getListeners());
foreach ($allEvents as $event) {
if (str_contains(strtolower($event), $lcNeedle)) {
$output[] = $event;
}
}
return $output;
}
}

View File

@@ -12,15 +12,17 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper;
use Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\Routing\Route;
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RouterInterface;
/**
@@ -29,50 +31,23 @@ use Symfony\Component\Routing\RouterInterface;
* @author Fabien Potencier <fabien@symfony.com>
* @author Tobias Schultze <http://tobion.de>
*
* @final since version 3.4
* @final
*/
class RouterDebugCommand extends ContainerAwareCommand
class RouterDebugCommand extends Command
{
use BuildDebugContainerTrait;
protected static $defaultName = 'debug:router';
protected static $defaultDescription = 'Display current routes for an application';
private $router;
private $fileLinkFormatter;
/**
* @param RouterInterface $router
*/
public function __construct($router = null)
public function __construct(RouterInterface $router, FileLinkFormatter $fileLinkFormatter = null)
{
if (!$router instanceof RouterInterface) {
@trigger_error(sprintf('%s() expects an instance of "%s" as first argument since Symfony 3.4. Not passing it is deprecated and will throw a TypeError in 4.0.', __METHOD__, RouterInterface::class), \E_USER_DEPRECATED);
parent::__construct($router);
return;
}
parent::__construct();
$this->router = $router;
}
/**
* {@inheritdoc}
*
* BC to be removed in 4.0
*/
public function isEnabled()
{
if (null !== $this->router) {
return parent::isEnabled();
}
if (!$this->getContainer()->has('router')) {
return false;
}
$router = $this->getContainer()->get('router');
if (!$router instanceof RouterInterface) {
return false;
}
return parent::isEnabled();
$this->fileLinkFormatter = $fileLinkFormatter;
}
/**
@@ -87,7 +62,7 @@ class RouterDebugCommand extends ContainerAwareCommand
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw route(s)'),
])
->setDescription('Displays current routes for an application')
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> displays the configured routes:
@@ -101,87 +76,101 @@ EOF
/**
* {@inheritdoc}
*
* @throws \InvalidArgumentException When route does not exist
* @throws InvalidArgumentException When route does not exist
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
// BC to be removed in 4.0
if (null === $this->router) {
$this->router = $this->getContainer()->get('router');
}
$io = new SymfonyStyle($input, $output);
$name = $input->getArgument('name');
$helper = new DescriptorHelper();
$helper = new DescriptorHelper($this->fileLinkFormatter);
$routes = $this->router->getRouteCollection();
$container = null;
if ($this->fileLinkFormatter) {
$container = function () {
return $this->getContainerBuilder($this->getApplication()->getKernel());
};
}
if ($name) {
if (!$route = $routes->get($name)) {
throw new InvalidArgumentException(sprintf('The route "%s" does not exist.', $name));
$route = $routes->get($name);
$matchingRoutes = $this->findRouteNameContaining($name, $routes);
if (!$input->isInteractive() && !$route && \count($matchingRoutes) > 1) {
$helper->describe($io, $this->findRouteContaining($name, $routes), [
'format' => $input->getOption('format'),
'raw_text' => $input->getOption('raw'),
'show_controllers' => $input->getOption('show-controllers'),
'output' => $io,
]);
return 0;
}
$callable = $this->extractCallable($route);
if (!$route && $matchingRoutes) {
$default = 1 === \count($matchingRoutes) ? $matchingRoutes[0] : null;
$name = $io->choice('Select one of the matching routes', $matchingRoutes, $default);
$route = $routes->get($name);
}
if (!$route) {
throw new InvalidArgumentException(sprintf('The route "%s" does not exist.', $name));
}
$helper->describe($io, $route, [
'format' => $input->getOption('format'),
'raw_text' => $input->getOption('raw'),
'name' => $name,
'output' => $io,
'callable' => $callable,
'container' => $container,
]);
} else {
foreach ($routes as $route) {
$this->convertController($route);
}
$helper->describe($io, $routes, [
'format' => $input->getOption('format'),
'raw_text' => $input->getOption('raw'),
'show_controllers' => $input->getOption('show-controllers'),
'output' => $io,
'container' => $container,
]);
}
return 0;
}
private function convertController(Route $route)
private function findRouteNameContaining(string $name, RouteCollection $routes): array
{
if ($route->hasDefault('_controller')) {
$nameParser = new ControllerNameParser($this->getApplication()->getKernel());
try {
$route->setDefault('_controller', $nameParser->build($route->getDefault('_controller')));
} catch (\InvalidArgumentException $e) {
}
}
}
/**
* @return callable|null
*/
private function extractCallable(Route $route)
{
if (!$route->hasDefault('_controller')) {
return null;
}
$controller = $route->getDefault('_controller');
if (1 === substr_count($controller, ':')) {
list($service, $method) = explode(':', $controller);
try {
return sprintf('%s::%s', \get_class($this->getApplication()->getKernel()->getContainer()->get($service)), $method);
} catch (ServiceNotFoundException $e) {
$foundRoutesNames = [];
foreach ($routes as $routeName => $route) {
if (false !== stripos($routeName, $name)) {
$foundRoutesNames[] = $routeName;
}
}
$nameParser = new ControllerNameParser($this->getApplication()->getKernel());
try {
$shortNotation = $nameParser->build($controller);
$route->setDefault('_controller', $shortNotation);
return $foundRoutesNames;
}
return $controller;
} catch (\InvalidArgumentException $e) {
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('name')) {
$suggestions->suggestValues(array_keys($this->router->getRouteCollection()->all()));
return;
}
return null;
if ($input->mustSuggestOptionValuesFor('format')) {
$helper = new DescriptorHelper();
$suggestions->suggestValues($helper->getFormats());
}
}
private function findRouteContaining(string $name, RouteCollection $routes): RouteCollection
{
$foundRoutes = new RouteCollection();
foreach ($routes as $routeName => $route) {
if (false !== stripos($routeName, $name)) {
$foundRoutes->add($routeName, $route);
}
}
return $foundRoutes;
}
}

View File

@@ -11,12 +11,14 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
use Symfony\Component\Routing\Matcher\TraceableUrlMatcher;
use Symfony\Component\Routing\RouterInterface;
@@ -25,51 +27,25 @@ use Symfony\Component\Routing\RouterInterface;
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final since version 3.4
* @final
*/
class RouterMatchCommand extends ContainerAwareCommand
class RouterMatchCommand extends Command
{
protected static $defaultName = 'router:match';
protected static $defaultDescription = 'Help debug routes by simulating a path info match';
private $router;
private $expressionLanguageProviders;
/**
* @param RouterInterface $router
* @param iterable<mixed, ExpressionFunctionProviderInterface> $expressionLanguageProviders
*/
public function __construct($router = null)
public function __construct(RouterInterface $router, iterable $expressionLanguageProviders = [])
{
if (!$router instanceof RouterInterface) {
@trigger_error(sprintf('%s() expects an instance of "%s" as first argument since Symfony 3.4. Not passing it is deprecated and will throw a TypeError in 4.0.', __METHOD__, RouterInterface::class), \E_USER_DEPRECATED);
parent::__construct($router);
return;
}
parent::__construct();
$this->router = $router;
}
/**
* {@inheritdoc}
*
* BC to be removed in 4.0
*/
public function isEnabled()
{
if (null !== $this->router) {
return parent::isEnabled();
}
if (!$this->getContainer()->has('router')) {
return false;
}
$router = $this->getContainer()->get('router');
if (!$router instanceof RouterInterface) {
return false;
}
return parent::isEnabled();
$this->expressionLanguageProviders = $expressionLanguageProviders;
}
/**
@@ -80,11 +56,11 @@ class RouterMatchCommand extends ContainerAwareCommand
$this
->setDefinition([
new InputArgument('path_info', InputArgument::REQUIRED, 'A path info'),
new InputOption('method', null, InputOption::VALUE_REQUIRED, 'Sets the HTTP method'),
new InputOption('scheme', null, InputOption::VALUE_REQUIRED, 'Sets the URI scheme (usually http or https)'),
new InputOption('host', null, InputOption::VALUE_REQUIRED, 'Sets the URI host'),
new InputOption('method', null, InputOption::VALUE_REQUIRED, 'Set the HTTP method'),
new InputOption('scheme', null, InputOption::VALUE_REQUIRED, 'Set the URI scheme (usually http or https)'),
new InputOption('host', null, InputOption::VALUE_REQUIRED, 'Set the URI host'),
])
->setDescription('Helps debug routes by simulating a path info match')
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> shows which routes match a given request and which don't and for what reason:
@@ -102,13 +78,8 @@ EOF
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
// BC to be removed in 4.0
if (null === $this->router) {
$this->router = $this->getContainer()->get('router');
}
$io = new SymfonyStyle($input, $output);
$context = $this->router->getContext();
@@ -123,6 +94,9 @@ EOF
}
$matcher = new TraceableUrlMatcher($this->router->getRouteCollection(), $context);
foreach ($this->expressionLanguageProviders as $provider) {
$matcher->addExpressionLanguageProvider($provider);
}
$traces = $matcher->getTraces($input->getArgument('path_info'));
@@ -150,6 +124,6 @@ EOF
return 1;
}
return null;
return 0;
}
}

View File

@@ -0,0 +1,104 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
final class SecretsDecryptToLocalCommand extends Command
{
protected static $defaultName = 'secrets:decrypt-to-local';
protected static $defaultDescription = 'Decrypt all secrets and stores them in the local vault';
private $vault;
private $localVault;
public function __construct(AbstractVault $vault, AbstractVault $localVault = null)
{
$this->vault = $vault;
$this->localVault = $localVault;
parent::__construct();
}
protected function configure()
{
$this
->setDescription(self::$defaultDescription)
->addOption('force', 'f', InputOption::VALUE_NONE, 'Force overriding of secrets that already exist in the local vault')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command decrypts all secrets and copies them in the local vault.
<info>%command.full_name%</info>
When the option <info>--force</info> is provided, secrets that already exist in the local vault are overriden.
<info>%command.full_name% --force</info>
EOF
)
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output);
if (null === $this->localVault) {
$io->error('The local vault is disabled.');
return 1;
}
$secrets = $this->vault->list(true);
$io->comment(sprintf('%d secret%s found in the vault.', \count($secrets), 1 !== \count($secrets) ? 's' : ''));
$skipped = 0;
if (!$input->getOption('force')) {
foreach ($this->localVault->list() as $k => $v) {
if (isset($secrets[$k])) {
++$skipped;
unset($secrets[$k]);
}
}
}
if ($skipped > 0) {
$io->warning([
sprintf('%d secret%s already overridden in the local vault and will be skipped.', $skipped, 1 !== $skipped ? 's are' : ' is'),
'Use the --force flag to override these.',
]);
}
foreach ($secrets as $k => $v) {
if (null === $v) {
$io->error($this->vault->getLastMessage() ?? sprintf('Secret "%s" has been skipped as there was an error reading it.', $k));
continue;
}
$this->localVault->seal($k, $v);
$io->note($this->localVault->getLastMessage());
}
return 0;
}
}

View File

@@ -0,0 +1,79 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
final class SecretsEncryptFromLocalCommand extends Command
{
protected static $defaultName = 'secrets:encrypt-from-local';
protected static $defaultDescription = 'Encrypt all local secrets to the vault';
private $vault;
private $localVault;
public function __construct(AbstractVault $vault, AbstractVault $localVault = null)
{
$this->vault = $vault;
$this->localVault = $localVault;
parent::__construct();
}
protected function configure()
{
$this
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command encrypts all locally overridden secrets to the vault.
<info>%command.full_name%</info>
EOF
)
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output);
if (null === $this->localVault) {
$io->error('The local vault is disabled.');
return 1;
}
foreach ($this->vault->list(true) as $name => $value) {
$localValue = $this->localVault->reveal($name);
if (null !== $localValue && $value !== $localValue) {
$this->vault->seal($name, $localValue);
} elseif (null !== $message = $this->localVault->getLastMessage()) {
$io->error($message);
return 1;
}
}
return 0;
}
}

View File

@@ -0,0 +1,126 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* @author Tobias Schultze <http://tobion.de>
* @author Jérémy Derussé <jeremy@derusse.com>
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
final class SecretsGenerateKeysCommand extends Command
{
protected static $defaultName = 'secrets:generate-keys';
protected static $defaultDescription = 'Generate new encryption keys';
private $vault;
private $localVault;
public function __construct(AbstractVault $vault, AbstractVault $localVault = null)
{
$this->vault = $vault;
$this->localVault = $localVault;
parent::__construct();
}
protected function configure()
{
$this
->setDescription(self::$defaultDescription)
->addOption('local', 'l', InputOption::VALUE_NONE, 'Update the local vault.')
->addOption('rotate', 'r', InputOption::VALUE_NONE, 'Re-encrypt existing secrets with the newly generated keys.')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command generates a new encryption key.
<info>%command.full_name%</info>
If encryption keys already exist, the command must be called with
the <info>--rotate</info> option in order to override those keys and re-encrypt
existing secrets.
<info>%command.full_name% --rotate</info>
EOF
)
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output);
$vault = $input->getOption('local') ? $this->localVault : $this->vault;
if (null === $vault) {
$io->success('The local vault is disabled.');
return 1;
}
if (!$input->getOption('rotate')) {
if ($vault->generateKeys()) {
$io->success($vault->getLastMessage());
if ($this->vault === $vault) {
$io->caution('DO NOT COMMIT THE DECRYPTION KEY FOR THE PROD ENVIRONMENT⚠');
}
return 0;
}
$io->warning($vault->getLastMessage());
return 1;
}
$secrets = [];
foreach ($vault->list(true) as $name => $value) {
if (null === $value) {
$io->error($vault->getLastMessage());
return 1;
}
$secrets[$name] = $value;
}
if (!$vault->generateKeys(true)) {
$io->warning($vault->getLastMessage());
return 1;
}
$io->success($vault->getLastMessage());
if ($secrets) {
foreach ($secrets as $name => $value) {
$vault->seal($name, $value);
}
$io->comment('Existing secrets have been rotated to the new keys.');
}
if ($this->vault === $vault) {
$io->caution('DO NOT COMMIT THE DECRYPTION KEY FOR THE PROD ENVIRONMENT⚠');
}
return 0;
}
}

View File

@@ -0,0 +1,109 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Dumper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* @author Tobias Schultze <http://tobion.de>
* @author Jérémy Derussé <jeremy@derusse.com>
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
final class SecretsListCommand extends Command
{
protected static $defaultName = 'secrets:list';
protected static $defaultDescription = 'List all secrets';
private $vault;
private $localVault;
public function __construct(AbstractVault $vault, AbstractVault $localVault = null)
{
$this->vault = $vault;
$this->localVault = $localVault;
parent::__construct();
}
protected function configure()
{
$this
->setDescription(self::$defaultDescription)
->addOption('reveal', 'r', InputOption::VALUE_NONE, 'Display decrypted values alongside names')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command list all stored secrets.
<info>%command.full_name%</info>
When the option <info>--reveal</info> is provided, the decrypted secrets are also displayed.
<info>%command.full_name% --reveal</info>
EOF
)
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output);
$io->comment('Use <info>"%env(<name>)%"</info> to reference a secret in a config file.');
if (!$reveal = $input->getOption('reveal')) {
$io->comment(sprintf('To reveal the secrets run <info>php %s %s --reveal</info>', $_SERVER['PHP_SELF'], $this->getName()));
}
$secrets = $this->vault->list($reveal);
$localSecrets = null !== $this->localVault ? $this->localVault->list($reveal) : null;
$rows = [];
$dump = new Dumper($output);
$dump = static function (?string $v) use ($dump) {
return null === $v ? '******' : $dump($v);
};
foreach ($secrets as $name => $value) {
$rows[$name] = [$name, $dump($value)];
}
if (null !== $message = $this->vault->getLastMessage()) {
$io->comment($message);
}
foreach ($localSecrets ?? [] as $name => $value) {
if (isset($rows[$name])) {
$rows[$name][] = $dump($value);
}
}
if (null !== $this->localVault && null !== $message = $this->localVault->getLastMessage()) {
$io->comment($message);
}
(new SymfonyStyle($input, $output))
->table(['Secret', 'Value'] + (null !== $localSecrets ? [2 => 'Local Value'] : []), $rows);
$io->comment("Local values override secret values.\nUse <info>secrets:set --local</info> to define them.");
return 0;
}
}

View File

@@ -0,0 +1,102 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* @author Jérémy Derussé <jeremy@derusse.com>
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
final class SecretsRemoveCommand extends Command
{
protected static $defaultName = 'secrets:remove';
protected static $defaultDescription = 'Remove a secret from the vault';
private $vault;
private $localVault;
public function __construct(AbstractVault $vault, AbstractVault $localVault = null)
{
$this->vault = $vault;
$this->localVault = $localVault;
parent::__construct();
}
protected function configure()
{
$this
->setDescription(self::$defaultDescription)
->addArgument('name', InputArgument::REQUIRED, 'The name of the secret')
->addOption('local', 'l', InputOption::VALUE_NONE, 'Update the local vault.')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command removes a secret from the vault.
<info>%command.full_name% <name></info>
EOF
)
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output);
$vault = $input->getOption('local') ? $this->localVault : $this->vault;
if (null === $vault) {
$io->success('The local vault is disabled.');
return 1;
}
if ($vault->remove($name = $input->getArgument('name'))) {
$io->success($vault->getLastMessage() ?? 'Secret was removed from the vault.');
} else {
$io->comment($vault->getLastMessage() ?? 'Secret was not found in the vault.');
}
if ($this->vault === $vault && null !== $this->localVault->reveal($name)) {
$io->comment('Note that this secret is overridden in the local vault.');
}
return 0;
}
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if (!$input->mustSuggestArgumentValuesFor('name')) {
return;
}
$vaultKeys = array_keys($this->vault->list(false));
if ($input->getOption('local')) {
if (null === $this->localVault) {
return;
}
$vaultKeys = array_intersect($vaultKeys, array_keys($this->localVault->list(false)));
}
$suggestions->suggestValues($vaultKeys);
}
}

View File

@@ -0,0 +1,149 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* @author Tobias Schultze <http://tobion.de>
* @author Jérémy Derussé <jeremy@derusse.com>
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
final class SecretsSetCommand extends Command
{
protected static $defaultName = 'secrets:set';
protected static $defaultDescription = 'Set a secret in the vault';
private $vault;
private $localVault;
public function __construct(AbstractVault $vault, AbstractVault $localVault = null)
{
$this->vault = $vault;
$this->localVault = $localVault;
parent::__construct();
}
protected function configure()
{
$this
->setDescription(self::$defaultDescription)
->addArgument('name', InputArgument::REQUIRED, 'The name of the secret')
->addArgument('file', InputArgument::OPTIONAL, 'A file where to read the secret from or "-" for reading from STDIN')
->addOption('local', 'l', InputOption::VALUE_NONE, 'Update the local vault.')
->addOption('random', 'r', InputOption::VALUE_OPTIONAL, 'Generate a random value.', false)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command stores a secret in the vault.
<info>%command.full_name% <name></info>
To reference secrets in services.yaml or any other config
files, use <info>"%env(<name>)%"</info>.
By default, the secret value should be entered interactively.
Alternatively, provide a file where to read the secret from:
<info>php %command.full_name% <name> filename</info>
Use "-" as a file name to read from STDIN:
<info>cat filename | php %command.full_name% <name> -</info>
Use <info>--local</info> to override secrets for local needs.
EOF
)
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$errOutput = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output;
$io = new SymfonyStyle($input, $errOutput);
$name = $input->getArgument('name');
$vault = $input->getOption('local') ? $this->localVault : $this->vault;
if (null === $vault) {
$io->error('The local vault is disabled.');
return 1;
}
if ($this->localVault === $vault && !\array_key_exists($name, $this->vault->list())) {
$io->error(sprintf('Secret "%s" does not exist in the vault, you cannot override it locally.', $name));
return 1;
}
if (0 < $random = $input->getOption('random') ?? 16) {
$value = strtr(substr(base64_encode(random_bytes($random)), 0, $random), '+/', '-_');
} elseif (!$file = $input->getArgument('file')) {
$value = $io->askHidden('Please type the secret value');
if (null === $value) {
$io->warning('No value provided: using empty string');
$value = '';
}
} elseif ('-' === $file) {
$value = file_get_contents('php://stdin');
} elseif (is_file($file) && is_readable($file)) {
$value = file_get_contents($file);
} elseif (!is_file($file)) {
throw new \InvalidArgumentException(sprintf('File not found: "%s".', $file));
} elseif (!is_readable($file)) {
throw new \InvalidArgumentException(sprintf('File is not readable: "%s".', $file));
}
if ($vault->generateKeys()) {
$io->success($vault->getLastMessage());
if ($this->vault === $vault) {
$io->caution('DO NOT COMMIT THE DECRYPTION KEY FOR THE PROD ENVIRONMENT⚠');
}
}
$vault->seal($name, $value);
$io->success($vault->getLastMessage() ?? 'Secret was successfully stored in the vault.');
if (0 < $random) {
$errOutput->write(' // The generated random value is: <comment>');
$output->write($value);
$errOutput->writeln('</comment>');
$io->newLine();
}
if ($this->vault === $vault && null !== $this->localVault->reveal($name)) {
$io->comment('Note that this secret is overridden in the local vault.');
}
return 0;
}
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('name')) {
$suggestions->suggestValues(array_keys($this->vault->list(false)));
}
}
}

View File

@@ -11,6 +11,9 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
@@ -25,7 +28,7 @@ use Symfony\Component\Translation\LoggingTranslator;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Reader\TranslationReaderInterface;
use Symfony\Component\Translation\Translator;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* Helps finding unused or missing translation messages in a given locale
@@ -33,32 +36,32 @@ use Symfony\Component\Translation\TranslatorInterface;
*
* @author Florian Voutzinos <florian@voutzinos.com>
*
* @final since version 3.4
* @final
*/
class TranslationDebugCommand extends ContainerAwareCommand
class TranslationDebugCommand extends Command
{
const MESSAGE_MISSING = 0;
const MESSAGE_UNUSED = 1;
const MESSAGE_EQUALS_FALLBACK = 2;
public const EXIT_CODE_GENERAL_ERROR = 64;
public const EXIT_CODE_MISSING = 65;
public const EXIT_CODE_UNUSED = 66;
public const EXIT_CODE_FALLBACK = 68;
public const MESSAGE_MISSING = 0;
public const MESSAGE_UNUSED = 1;
public const MESSAGE_EQUALS_FALLBACK = 2;
protected static $defaultName = 'debug:translation';
protected static $defaultDescription = 'Display translation messages information';
private $translator;
private $reader;
private $extractor;
private $defaultTransPath;
private $defaultViewsPath;
private $transPaths;
private $codePaths;
private $enabledLocales;
public function __construct($translator = null, TranslationReaderInterface $reader = null, ExtractorInterface $extractor = null, $defaultTransPath = null, $defaultViewsPath = null)
public function __construct(TranslatorInterface $translator, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultTransPath = null, string $defaultViewsPath = null, array $transPaths = [], array $codePaths = [], array $enabledLocales = [])
{
if (!$translator instanceof TranslatorInterface) {
@trigger_error(sprintf('%s() expects an instance of "%s" as first argument since Symfony 3.4. Not passing it is deprecated and will throw a TypeError in 4.0.', __METHOD__, TranslatorInterface::class), \E_USER_DEPRECATED);
parent::__construct($translator);
return;
}
parent::__construct();
$this->translator = $translator;
@@ -66,6 +69,9 @@ class TranslationDebugCommand extends ContainerAwareCommand
$this->extractor = $extractor;
$this->defaultTransPath = $defaultTransPath;
$this->defaultViewsPath = $defaultViewsPath;
$this->transPaths = $transPaths;
$this->codePaths = $codePaths;
$this->enabledLocales = $enabledLocales;
}
/**
@@ -78,11 +84,11 @@ class TranslationDebugCommand extends ContainerAwareCommand
new InputArgument('locale', InputArgument::REQUIRED, 'The locale'),
new InputArgument('bundle', InputArgument::OPTIONAL, 'The bundle name or directory where to load the messages'),
new InputOption('domain', null, InputOption::VALUE_OPTIONAL, 'The messages domain'),
new InputOption('only-missing', null, InputOption::VALUE_NONE, 'Displays only missing messages'),
new InputOption('only-unused', null, InputOption::VALUE_NONE, 'Displays only unused messages'),
new InputOption('only-missing', null, InputOption::VALUE_NONE, 'Display only missing messages'),
new InputOption('only-unused', null, InputOption::VALUE_NONE, 'Display only unused messages'),
new InputOption('all', null, InputOption::VALUE_NONE, 'Load messages from all registered bundles'),
])
->setDescription('Displays translation messages information')
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command helps finding unused or missing translation
messages and comparing them with the fallback ones by inspecting the
@@ -119,70 +125,42 @@ EOF
/**
* {@inheritdoc}
*
* BC to be removed in 4.0
*/
public function isEnabled()
protected function execute(InputInterface $input, OutputInterface $output): int
{
if (null !== $this->translator) {
return parent::isEnabled();
}
if (!class_exists('Symfony\Component\Translation\Translator')) {
return false;
}
return parent::isEnabled();
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
// BC to be removed in 4.0
if (null === $this->translator) {
$this->translator = $this->getContainer()->get('translator');
$this->reader = $this->getContainer()->get('translation.reader');
$this->extractor = $this->getContainer()->get('translation.extractor');
$this->defaultTransPath = $this->getContainer()->getParameter('translator.default_path');
$this->defaultViewsPath = $this->getContainer()->getParameter('twig.default_path');
}
$io = new SymfonyStyle($input, $output);
$locale = $input->getArgument('locale');
$domain = $input->getOption('domain');
$exitCode = self::SUCCESS;
/** @var KernelInterface $kernel */
$kernel = $this->getApplication()->getKernel();
// Define Root Paths
$transPaths = [$kernel->getRootDir().'/Resources/translations'];
if ($this->defaultTransPath) {
$transPaths[] = $this->defaultTransPath;
}
$viewsPaths = [$kernel->getRootDir().'/Resources/views'];
if ($this->defaultViewsPath) {
$viewsPaths[] = $this->defaultViewsPath;
}
$transPaths = $this->getRootTransPaths();
$codePaths = $this->getRootCodePaths($kernel);
// Override with provided Bundle info
if (null !== $input->getArgument('bundle')) {
try {
$bundle = $kernel->getBundle($input->getArgument('bundle'));
$transPaths = [$bundle->getPath().'/Resources/translations'];
$bundleDir = $bundle->getPath();
$transPaths = [is_dir($bundleDir.'/Resources/translations') ? $bundleDir.'/Resources/translations' : $bundleDir.'/translations'];
$codePaths = [is_dir($bundleDir.'/Resources/views') ? $bundleDir.'/Resources/views' : $bundleDir.'/templates'];
if ($this->defaultTransPath) {
$transPaths[] = $this->defaultTransPath.'/'.$bundle->getName();
$transPaths[] = $this->defaultTransPath;
}
$transPaths[] = sprintf('%s/Resources/%s/translations', $kernel->getRootDir(), $bundle->getName());
$viewsPaths = [$bundle->getPath().'/Resources/views'];
if ($this->defaultViewsPath) {
$viewsPaths[] = $this->defaultViewsPath.'/bundles/'.$bundle->getName();
$codePaths[] = $this->defaultViewsPath;
}
$viewsPaths[] = sprintf('%s/Resources/%s/views', $kernel->getRootDir(), $bundle->getName());
} catch (\InvalidArgumentException $e) {
// such a bundle does not exist, so treat the argument as path
$transPaths = [$input->getArgument('bundle').'/Resources/translations'];
$viewsPaths = [$input->getArgument('bundle').'/Resources/views'];
$path = $input->getArgument('bundle');
$transPaths = [$path.'/translations'];
$codePaths = [$path.'/templates'];
if (!is_dir($transPaths[0])) {
throw new InvalidArgumentException(sprintf('"%s" is neither an enabled bundle nor a directory.', $transPaths[0]));
@@ -190,21 +168,14 @@ EOF
}
} elseif ($input->getOption('all')) {
foreach ($kernel->getBundles() as $bundle) {
$transPaths[] = $bundle->getPath().'/Resources/translations';
if ($this->defaultTransPath) {
$transPaths[] = $this->defaultTransPath.'/'.$bundle->getName();
}
$transPaths[] = sprintf('%s/Resources/%s/translations', $kernel->getRootDir(), $bundle->getName());
$viewsPaths[] = $bundle->getPath().'/Resources/views';
if ($this->defaultViewsPath) {
$viewsPaths[] = $this->defaultViewsPath.'/bundles/'.$bundle->getName();
}
$viewsPaths[] = sprintf('%s/Resources/%s/views', $kernel->getRootDir(), $bundle->getName());
$bundleDir = $bundle->getPath();
$transPaths[] = is_dir($bundleDir.'/Resources/translations') ? $bundleDir.'/Resources/translations' : $bundle->getPath().'/translations';
$codePaths[] = is_dir($bundleDir.'/Resources/views') ? $bundleDir.'/Resources/views' : $bundle->getPath().'/templates';
}
}
// Extract used messages
$extractedCatalogue = $this->extractMessages($locale, $viewsPaths);
$extractedCatalogue = $this->extractMessages($locale, $codePaths);
// Load defined messages
$currentCatalogue = $this->loadCurrentMessages($locale, $transPaths);
@@ -226,7 +197,7 @@ EOF
$io->getErrorStyle()->warning($outputMessage);
return;
return self::EXIT_CODE_GENERAL_ERROR;
}
// Load the fallback catalogues
@@ -247,13 +218,22 @@ EOF
if ($extractedCatalogue->defines($messageId, $domain)) {
if (!$currentCatalogue->defines($messageId, $domain)) {
$states[] = self::MESSAGE_MISSING;
if (!$input->getOption('only-unused')) {
$exitCode = $exitCode | self::EXIT_CODE_MISSING;
}
}
} elseif ($currentCatalogue->defines($messageId, $domain)) {
$states[] = self::MESSAGE_UNUSED;
if (!$input->getOption('only-missing')) {
$exitCode = $exitCode | self::EXIT_CODE_UNUSED;
}
}
if (!\in_array(self::MESSAGE_UNUSED, $states) && true === $input->getOption('only-unused')
|| !\in_array(self::MESSAGE_MISSING, $states) && true === $input->getOption('only-missing')) {
if (!\in_array(self::MESSAGE_UNUSED, $states) && $input->getOption('only-unused')
|| !\in_array(self::MESSAGE_MISSING, $states) && $input->getOption('only-missing')
) {
continue;
}
@@ -261,6 +241,8 @@ EOF
if ($fallbackCatalogue->defines($messageId, $domain) && $value === $fallbackCatalogue->get($messageId, $domain)) {
$states[] = self::MESSAGE_EQUALS_FALLBACK;
$exitCode = $exitCode | self::EXIT_CODE_FALLBACK;
break;
}
}
@@ -275,9 +257,49 @@ EOF
}
$io->table($headers, $rows);
return $exitCode;
}
private function formatState($state)
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('locale')) {
$suggestions->suggestValues($this->enabledLocales);
return;
}
/** @var KernelInterface $kernel */
$kernel = $this->getApplication()->getKernel();
if ($input->mustSuggestArgumentValuesFor('bundle')) {
$availableBundles = [];
foreach ($kernel->getBundles() as $bundle) {
$availableBundles[] = $bundle->getName();
if ($extension = $bundle->getContainerExtension()) {
$availableBundles[] = $extension->getAlias();
}
}
$suggestions->suggestValues($availableBundles);
return;
}
if ($input->mustSuggestOptionValuesFor('domain')) {
$locale = $input->getArgument('locale');
$mergeOperation = new MergeOperation(
$this->extractMessages($locale, $this->getRootCodePaths($kernel)),
$this->loadCurrentMessages($locale, $this->getRootTransPaths())
);
$suggestions->suggestValues($mergeOperation->getDomains());
}
}
private function formatState(int $state): string
{
if (self::MESSAGE_MISSING === $state) {
return '<error> missing </error>';
@@ -294,7 +316,7 @@ EOF
return $state;
}
private function formatStates(array $states)
private function formatStates(array $states): string
{
$result = [];
foreach ($states as $state) {
@@ -304,12 +326,12 @@ EOF
return implode(' ', $result);
}
private function formatId($id)
private function formatId(string $id): string
{
return sprintf('<fg=cyan;options=bold>%s</>', $id);
}
private function sanitizeString($string, $length = 40)
private function sanitizeString(string $string, int $length = 40): string
{
$string = trim(preg_replace('/\s+/', ' ', $string));
@@ -324,17 +346,11 @@ EOF
return $string;
}
/**
* @param string $locale
* @param array $transPaths
*
* @return MessageCatalogue
*/
private function extractMessages($locale, $transPaths)
private function extractMessages(string $locale, array $transPaths): MessageCatalogue
{
$extractedCatalogue = new MessageCatalogue($locale);
foreach ($transPaths as $path) {
if (is_dir($path)) {
if (is_dir($path) || is_file($path)) {
$this->extractor->extract($path, $extractedCatalogue);
}
}
@@ -342,13 +358,7 @@ EOF
return $extractedCatalogue;
}
/**
* @param string $locale
* @param array $transPaths
*
* @return MessageCatalogue
*/
private function loadCurrentMessages($locale, $transPaths)
private function loadCurrentMessages(string $locale, array $transPaths): MessageCatalogue
{
$currentCatalogue = new MessageCatalogue($locale);
foreach ($transPaths as $path) {
@@ -361,12 +371,9 @@ EOF
}
/**
* @param string $locale
* @param array $transPaths
*
* @return MessageCatalogue[]
*/
private function loadFallbackCatalogues($locale, $transPaths)
private function loadFallbackCatalogues(string $locale, array $transPaths): array
{
$fallbackCatalogues = [];
if ($this->translator instanceof Translator || $this->translator instanceof DataCollectorTranslator || $this->translator instanceof LoggingTranslator) {
@@ -387,4 +394,25 @@ EOF
return $fallbackCatalogues;
}
private function getRootTransPaths(): array
{
$transPaths = $this->transPaths;
if ($this->defaultTransPath) {
$transPaths[] = $this->defaultTransPath;
}
return $transPaths;
}
private function getRootCodePaths(KernelInterface $kernel): array
{
$codePaths = $this->codePaths;
$codePaths[] = $kernel->getProjectDir().'/src';
if ($this->defaultViewsPath) {
$codePaths[] = $this->defaultViewsPath;
}
return $codePaths;
}
}

View File

@@ -11,10 +11,14 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\HttpKernel\KernelInterface;
@@ -22,6 +26,7 @@ use Symfony\Component\Translation\Catalogue\MergeOperation;
use Symfony\Component\Translation\Catalogue\TargetOperation;
use Symfony\Component\Translation\Extractor\ExtractorInterface;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\MessageCatalogueInterface;
use Symfony\Component\Translation\Reader\TranslationReaderInterface;
use Symfony\Component\Translation\Writer\TranslationWriterInterface;
@@ -31,11 +36,20 @@ use Symfony\Component\Translation\Writer\TranslationWriterInterface;
*
* @author Michel Salib <michelsalib@hotmail.com>
*
* @final since version 3.4
* @final
*/
class TranslationUpdateCommand extends ContainerAwareCommand
class TranslationUpdateCommand extends Command
{
protected static $defaultName = 'translation:update';
private const ASC = 'asc';
private const DESC = 'desc';
private const SORT_ORDERS = [self::ASC, self::DESC];
private const FORMATS = [
'xlf12' => ['xlf', '1.2'],
'xlf20' => ['xlf', '2.0'],
];
protected static $defaultName = 'translation:extract|translation:update';
protected static $defaultDescription = 'Extract missing translations keys from code to translation files.';
private $writer;
private $reader;
@@ -43,25 +57,12 @@ class TranslationUpdateCommand extends ContainerAwareCommand
private $defaultLocale;
private $defaultTransPath;
private $defaultViewsPath;
private $transPaths;
private $codePaths;
private $enabledLocales;
/**
* @param TranslationWriterInterface $writer
* @param TranslationReaderInterface $reader
* @param ExtractorInterface $extractor
* @param string $defaultLocale
* @param string $defaultTransPath
* @param string $defaultViewsPath
*/
public function __construct($writer = null, TranslationReaderInterface $reader = null, ExtractorInterface $extractor = null, $defaultLocale = null, $defaultTransPath = null, $defaultViewsPath = null)
public function __construct(TranslationWriterInterface $writer, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultLocale, string $defaultTransPath = null, string $defaultViewsPath = null, array $transPaths = [], array $codePaths = [], array $enabledLocales = [])
{
if (!$writer instanceof TranslationWriterInterface) {
@trigger_error(sprintf('%s() expects an instance of "%s" as first argument since Symfony 3.4. Not passing it is deprecated and will throw a TypeError in 4.0.', __METHOD__, TranslationWriterInterface::class), \E_USER_DEPRECATED);
parent::__construct($writer);
return;
}
parent::__construct();
$this->writer = $writer;
@@ -70,6 +71,9 @@ class TranslationUpdateCommand extends ContainerAwareCommand
$this->defaultLocale = $defaultLocale;
$this->defaultTransPath = $defaultTransPath;
$this->defaultViewsPath = $defaultViewsPath;
$this->transPaths = $transPaths;
$this->codePaths = $codePaths;
$this->enabledLocales = $enabledLocales;
}
/**
@@ -82,29 +86,45 @@ class TranslationUpdateCommand extends ContainerAwareCommand
new InputArgument('locale', InputArgument::REQUIRED, 'The locale'),
new InputArgument('bundle', InputArgument::OPTIONAL, 'The bundle name or directory where to load the messages'),
new InputOption('prefix', null, InputOption::VALUE_OPTIONAL, 'Override the default prefix', '__'),
new InputOption('no-prefix', null, InputOption::VALUE_NONE, '[DEPRECATED] If set, no prefix is added to the translations'),
new InputOption('output-format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format', 'yaml'),
new InputOption('output-format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format (deprecated)'),
new InputOption('format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format', 'xlf12'),
new InputOption('dump-messages', null, InputOption::VALUE_NONE, 'Should the messages be dumped in the console'),
new InputOption('force', null, InputOption::VALUE_NONE, 'Should the update be done'),
new InputOption('no-backup', null, InputOption::VALUE_NONE, 'Should backup be disabled'),
new InputOption('force', null, InputOption::VALUE_NONE, 'Should the extract be done'),
new InputOption('clean', null, InputOption::VALUE_NONE, 'Should clean not found messages'),
new InputOption('domain', null, InputOption::VALUE_OPTIONAL, 'Specify the domain to update'),
new InputOption('domain', null, InputOption::VALUE_OPTIONAL, 'Specify the domain to extract'),
new InputOption('xliff-version', null, InputOption::VALUE_OPTIONAL, 'Override the default xliff version (deprecated)'),
new InputOption('sort', null, InputOption::VALUE_OPTIONAL, 'Return list of messages sorted alphabetically', 'asc'),
new InputOption('as-tree', null, InputOption::VALUE_OPTIONAL, 'Dump the messages as a tree-like structure: The given value defines the level where to switch to inline YAML'),
])
->setDescription('Updates the translation file')
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command extracts translation strings from templates
of a given bundle or the default translations directory. It can display them or merge the new ones into the translation files.
of a given bundle or the default translations directory. It can display them or merge
the new ones into the translation files.
When new translation strings are found it can automatically add a prefix to the translation
message.
Example running against a Bundle (AcmeBundle)
<info>php %command.full_name% --dump-messages en AcmeBundle</info>
<info>php %command.full_name% --force --prefix="new_" fr AcmeBundle</info>
Example running against default messages directory
<info>php %command.full_name% --dump-messages en</info>
<info>php %command.full_name% --force --prefix="new_" fr</info>
You can sort the output with the <comment>--sort</> flag:
<info>php %command.full_name% --dump-messages --sort=asc en AcmeBundle</info>
<info>php %command.full_name% --dump-messages --sort=desc fr</info>
You can dump a tree-like structure using the yaml format with <comment>--as-tree</> flag:
<info>php %command.full_name% --force --format=yaml --as-tree=3 en AcmeBundle</info>
<info>php %command.full_name% --force --format=yaml --sort=asc --as-tree=3 fr</info>
EOF
)
;
@@ -112,34 +132,14 @@ EOF
/**
* {@inheritdoc}
*
* BC to be removed in 4.0
*/
public function isEnabled()
protected function execute(InputInterface $input, OutputInterface $output): int
{
if (null !== $this->writer) {
return parent::isEnabled();
}
if (!class_exists('Symfony\Component\Translation\Translator')) {
return false;
}
$io = new SymfonyStyle($input, $output);
$errorIo = $output instanceof ConsoleOutputInterface ? new SymfonyStyle($input, $output->getErrorOutput()) : $io;
return parent::isEnabled();
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
// BC to be removed in 4.0
if (null === $this->writer) {
$this->writer = $this->getContainer()->get('translation.writer');
$this->reader = $this->getContainer()->get('translation.reader');
$this->extractor = $this->getContainer()->get('translation.extractor');
$this->defaultLocale = $this->getContainer()->getParameter('kernel.default_locale');
$this->defaultTransPath = $this->getContainer()->getParameter('translator.default_path');
$this->defaultViewsPath = $this->getContainer()->getParameter('twig.default_path');
if ('translation:update' === $input->getFirstArgument()) {
$errorIo->caution('Command "translation:update" is deprecated since version 5.4 and will be removed in Symfony 6.0. Use "translation:extract" instead.');
}
$io = new SymfonyStyle($input, $output);
@@ -152,47 +152,58 @@ EOF
return 1;
}
$format = $input->getOption('output-format') ?: $input->getOption('format');
$xliffVersion = $input->getOption('xliff-version') ?? '1.2';
if ($input->getOption('xliff-version')) {
$errorIo->warning(sprintf('The "--xliff-version" option is deprecated since version 5.3, use "--format=xlf%d" instead.', 10 * $xliffVersion));
}
if ($input->getOption('output-format')) {
$errorIo->warning(sprintf('The "--output-format" option is deprecated since version 5.3, use "--format=xlf%d" instead.', 10 * $xliffVersion));
}
if (\in_array($format, array_keys(self::FORMATS), true)) {
[$format, $xliffVersion] = self::FORMATS[$format];
}
// check format
$supportedFormats = $this->writer->getFormats();
if (!\in_array($input->getOption('output-format'), $supportedFormats)) {
$errorIo->error(['Wrong output format', 'Supported formats are: '.implode(', ', $supportedFormats).'.']);
if (!\in_array($format, $supportedFormats, true)) {
$errorIo->error(['Wrong output format', 'Supported formats are: '.implode(', ', $supportedFormats).', xlf12 and xlf20.']);
return 1;
}
/** @var KernelInterface $kernel */
$kernel = $this->getApplication()->getKernel();
// Define Root Paths
$transPaths = [$kernel->getRootDir().'/Resources/translations'];
if ($this->defaultTransPath) {
$transPaths[] = $this->defaultTransPath;
}
$viewsPaths = [$kernel->getRootDir().'/Resources/views'];
if ($this->defaultViewsPath) {
$viewsPaths[] = $this->defaultViewsPath;
}
$transPaths = $this->getRootTransPaths();
$codePaths = $this->getRootCodePaths($kernel);
$currentName = 'default directory';
// Override with provided Bundle info
if (null !== $input->getArgument('bundle')) {
try {
$foundBundle = $kernel->getBundle($input->getArgument('bundle'));
$transPaths = [$foundBundle->getPath().'/Resources/translations'];
$bundleDir = $foundBundle->getPath();
$transPaths = [is_dir($bundleDir.'/Resources/translations') ? $bundleDir.'/Resources/translations' : $bundleDir.'/translations'];
$codePaths = [is_dir($bundleDir.'/Resources/views') ? $bundleDir.'/Resources/views' : $bundleDir.'/templates'];
if ($this->defaultTransPath) {
$transPaths[] = $this->defaultTransPath.'/'.$foundBundle->getName();
$transPaths[] = $this->defaultTransPath;
}
$transPaths[] = sprintf('%s/Resources/%s/translations', $kernel->getRootDir(), $foundBundle->getName());
$viewsPaths = [$foundBundle->getPath().'/Resources/views'];
if ($this->defaultViewsPath) {
$viewsPaths[] = $this->defaultViewsPath.'/bundles/'.$foundBundle->getName();
$codePaths[] = $this->defaultViewsPath;
}
$viewsPaths[] = sprintf('%s/Resources/%s/views', $kernel->getRootDir(), $foundBundle->getName());
$currentName = $foundBundle->getName();
} catch (\InvalidArgumentException $e) {
// such a bundle does not exist, so treat the argument as path
$transPaths = [$input->getArgument('bundle').'/Resources/translations'];
$viewsPaths = [$input->getArgument('bundle').'/Resources/views'];
$currentName = $transPaths[0];
$path = $input->getArgument('bundle');
$transPaths = [$path.'/translations'];
$codePaths = [$path.'/templates'];
if (!is_dir($transPaths[0])) {
throw new InvalidArgumentException(sprintf('"%s" is neither an enabled bundle nor a directory.', $transPaths[0]));
@@ -203,30 +214,11 @@ EOF
$io->title('Translation Messages Extractor and Dumper');
$io->comment(sprintf('Generating "<info>%s</info>" translation files for "<info>%s</info>"', $input->getArgument('locale'), $currentName));
// load any messages from templates
$extractedCatalogue = new MessageCatalogue($input->getArgument('locale'));
$io->comment('Parsing templates...');
$prefix = $input->getOption('prefix');
// @deprecated since version 3.4, to be removed in 4.0 along with the --no-prefix option
if ($input->getOption('no-prefix')) {
@trigger_error('The "--no-prefix" option is deprecated since Symfony 3.4 and will be removed in 4.0. Use the "--prefix" option with an empty string as value instead.', \E_USER_DEPRECATED);
$prefix = '';
}
$this->extractor->setPrefix($prefix);
foreach ($viewsPaths as $path) {
if (is_dir($path)) {
$this->extractor->extract($path, $extractedCatalogue);
}
}
$extractedCatalogue = $this->extractMessages($input->getArgument('locale'), $codePaths, $input->getOption('prefix'));
// load any existing messages from the translation files
$currentCatalogue = new MessageCatalogue($input->getArgument('locale'));
$io->comment('Loading translation files...');
foreach ($transPaths as $path) {
if (is_dir($path)) {
$this->reader->read($path, $currentCatalogue);
}
}
$currentCatalogue = $this->loadCurrentMessages($input->getArgument('locale'), $transPaths);
if (null !== $domain = $input->getOption('domain')) {
$currentCatalogue = $this->filterCatalogue($currentCatalogue, $domain);
@@ -242,11 +234,13 @@ EOF
if (!\count($operation->getDomains())) {
$errorIo->warning('No translation messages were found.');
return null;
return 0;
}
$resultMessage = 'Translation files were successfully updated';
$operation->moveMessagesToIntlDomainsIfPossible('new');
// show compiled list of messages
if (true === $input->getOption('dump-messages')) {
$extractedMessagesCount = 0;
@@ -267,23 +261,34 @@ EOF
$domainMessagesCount = \count($list);
if ($sort = $input->getOption('sort')) {
$sort = strtolower($sort);
if (!\in_array($sort, self::SORT_ORDERS, true)) {
$errorIo->error(['Wrong sort order', 'Supported formats are: '.implode(', ', self::SORT_ORDERS).'.']);
return 1;
}
if (self::DESC === $sort) {
rsort($list);
} else {
sort($list);
}
}
$io->section(sprintf('Messages extracted for domain "<info>%s</info>" (%d message%s)', $domain, $domainMessagesCount, $domainMessagesCount > 1 ? 's' : ''));
$io->listing($list);
$extractedMessagesCount += $domainMessagesCount;
}
if ('xlf' == $input->getOption('output-format')) {
$io->comment('Xliff output version is <info>1.2</info>');
if ('xlf' === $format) {
$io->comment(sprintf('Xliff output version is <info>%s</info>', $xliffVersion));
}
$resultMessage = sprintf('%d message%s successfully extracted', $extractedMessagesCount, $extractedMessagesCount > 1 ? 's were' : ' was');
}
if (true === $input->getOption('no-backup')) {
$this->writer->disableBackup();
}
// save the files
if (true === $input->getOption('force')) {
$io->comment('Writing files...');
@@ -299,7 +304,7 @@ EOF
$bundleTransPath = end($transPaths);
}
$this->writer->write($operation->getResult(), $input->getOption('output-format'), ['path' => $bundleTransPath, 'default_locale' => $this->defaultLocale]);
$this->writer->write($operation->getResult(), $format, ['path' => $bundleTransPath, 'default_locale' => $this->defaultLocale, 'xliff_version' => $xliffVersion, 'as_tree' => $input->getOption('as-tree'), 'inline' => $input->getOption('as-tree') ?? 0]);
if (true === $input->getOption('dump-messages')) {
$resultMessage .= ' and translation files were updated';
@@ -308,19 +313,87 @@ EOF
$io->success($resultMessage.'.');
return null;
return 0;
}
private function filterCatalogue(MessageCatalogue $catalogue, $domain)
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('locale')) {
$suggestions->suggestValues($this->enabledLocales);
return;
}
/** @var KernelInterface $kernel */
$kernel = $this->getApplication()->getKernel();
if ($input->mustSuggestArgumentValuesFor('bundle')) {
$bundles = [];
foreach ($kernel->getBundles() as $bundle) {
$bundles[] = $bundle->getName();
if ($bundle->getContainerExtension()) {
$bundles[] = $bundle->getContainerExtension()->getAlias();
}
}
$suggestions->suggestValues($bundles);
return;
}
if ($input->mustSuggestOptionValuesFor('format')) {
$suggestions->suggestValues(array_merge(
$this->writer->getFormats(),
array_keys(self::FORMATS)
));
return;
}
if ($input->mustSuggestOptionValuesFor('domain') && $locale = $input->getArgument('locale')) {
$extractedCatalogue = $this->extractMessages($locale, $this->getRootCodePaths($kernel), $input->getOption('prefix'));
$currentCatalogue = $this->loadCurrentMessages($locale, $this->getRootTransPaths());
// process catalogues
$operation = $input->getOption('clean')
? new TargetOperation($currentCatalogue, $extractedCatalogue)
: new MergeOperation($currentCatalogue, $extractedCatalogue);
$suggestions->suggestValues($operation->getDomains());
return;
}
if ($input->mustSuggestOptionValuesFor('sort')) {
$suggestions->suggestValues(self::SORT_ORDERS);
}
}
private function filterCatalogue(MessageCatalogue $catalogue, string $domain): MessageCatalogue
{
$filteredCatalogue = new MessageCatalogue($catalogue->getLocale());
if ($messages = $catalogue->all($domain)) {
// extract intl-icu messages only
$intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX;
if ($intlMessages = $catalogue->all($intlDomain)) {
$filteredCatalogue->add($intlMessages, $intlDomain);
}
// extract all messages and subtract intl-icu messages
if ($messages = array_diff($catalogue->all($domain), $intlMessages)) {
$filteredCatalogue->add($messages, $domain);
}
foreach ($catalogue->getResources() as $resource) {
$filteredCatalogue->addResource($resource);
}
if ($metadata = $catalogue->getMetadata('', $intlDomain)) {
foreach ($metadata as $k => $v) {
$filteredCatalogue->setMetadata($k, $v, $intlDomain);
}
}
if ($metadata = $catalogue->getMetadata('', $domain)) {
foreach ($metadata as $k => $v) {
$filteredCatalogue->setMetadata($k, $v, $domain);
@@ -329,4 +402,50 @@ EOF
return $filteredCatalogue;
}
private function extractMessages(string $locale, array $transPaths, string $prefix): MessageCatalogue
{
$extractedCatalogue = new MessageCatalogue($locale);
$this->extractor->setPrefix($prefix);
foreach ($transPaths as $path) {
if (is_dir($path) || is_file($path)) {
$this->extractor->extract($path, $extractedCatalogue);
}
}
return $extractedCatalogue;
}
private function loadCurrentMessages(string $locale, array $transPaths): MessageCatalogue
{
$currentCatalogue = new MessageCatalogue($locale);
foreach ($transPaths as $path) {
if (is_dir($path)) {
$this->reader->read($path, $currentCatalogue);
}
}
return $currentCatalogue;
}
private function getRootTransPaths(): array
{
$transPaths = $this->transPaths;
if ($this->defaultTransPath) {
$transPaths[] = $this->defaultTransPath;
}
return $transPaths;
}
private function getRootCodePaths(KernelInterface $kernel): array
{
$codePaths = $this->codePaths;
$codePaths[] = $kernel->getProjectDir().'/src';
if ($this->defaultViewsPath) {
$codePaths[] = $this->defaultViewsPath;
}
return $codePaths;
}
}

View File

@@ -11,22 +11,49 @@
namespace Symfony\Bundle\FrameworkBundle\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Workflow\Definition;
use Symfony\Component\Workflow\Dumper\GraphvizDumper;
use Symfony\Component\Workflow\Dumper\MermaidDumper;
use Symfony\Component\Workflow\Dumper\PlantUmlDumper;
use Symfony\Component\Workflow\Dumper\StateMachineGraphvizDumper;
use Symfony\Component\Workflow\Marking;
/**
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*
* @final since version 3.4
* @final
*/
class WorkflowDumpCommand extends ContainerAwareCommand
class WorkflowDumpCommand extends Command
{
protected static $defaultName = 'workflow:dump';
protected static $defaultDescription = 'Dump a workflow';
/**
* string is the service id.
*
* @var array<string, Definition>
*/
private $workflows = [];
private const DUMP_FORMAT_OPTIONS = [
'puml',
'mermaid',
'dot',
];
public function __construct(array $workflows)
{
parent::__construct();
$this->workflows = $workflows;
}
/**
* {@inheritdoc}
@@ -37,14 +64,17 @@ class WorkflowDumpCommand extends ContainerAwareCommand
->setDefinition([
new InputArgument('name', InputArgument::REQUIRED, 'A workflow name'),
new InputArgument('marking', InputArgument::IS_ARRAY, 'A marking (a list of places)'),
new InputOption('label', 'l', InputOption::VALUE_REQUIRED, 'Label a graph'),
new InputOption('dump-format', null, InputOption::VALUE_REQUIRED, 'The dump format ['.implode('|', self::DUMP_FORMAT_OPTIONS).']', 'dot'),
])
->setDescription('Dump a workflow')
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command dumps the graphical representation of a
workflow in DOT format
%command.full_name% <workflow name> | dot -Tpng > workflow.png
workflow in different formats
<info>DOT</info>: %command.full_name% <workflow name> | dot -Tpng > workflow.png
<info>PUML</info>: %command.full_name% <workflow name> --dump-format=puml | java -jar plantuml.jar -p > workflow.png
<info>MERMAID</info>: %command.full_name% <workflow name> --dump-format=mermaid | mmdc -o workflow.svg
EOF
)
;
@@ -53,18 +83,38 @@ EOF
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$container = $this->getApplication()->getKernel()->getContainer();
$serviceId = $input->getArgument('name');
if ($container->has('workflow.'.$serviceId)) {
$workflow = $container->get('workflow.'.$serviceId);
$dumper = new GraphvizDumper();
} elseif ($container->has('state_machine.'.$serviceId)) {
$workflow = $container->get('state_machine.'.$serviceId);
$dumper = new StateMachineGraphvizDumper();
} else {
throw new InvalidArgumentException(sprintf('No service found for "workflow.%1$s" nor "state_machine.%1$s".', $serviceId));
$workflowName = $input->getArgument('name');
$workflow = null;
if (isset($this->workflows['workflow.'.$workflowName])) {
$workflow = $this->workflows['workflow.'.$workflowName];
$type = 'workflow';
} elseif (isset($this->workflows['state_machine.'.$workflowName])) {
$workflow = $this->workflows['state_machine.'.$workflowName];
$type = 'state_machine';
}
if (null === $workflow) {
throw new InvalidArgumentException(sprintf('No service found for "workflow.%1$s" nor "state_machine.%1$s".', $workflowName));
}
switch ($input->getOption('dump-format')) {
case 'puml':
$transitionType = 'workflow' === $type ? PlantUmlDumper::WORKFLOW_TRANSITION : PlantUmlDumper::STATEMACHINE_TRANSITION;
$dumper = new PlantUmlDumper($transitionType);
break;
case 'mermaid':
$transitionType = 'workflow' === $type ? MermaidDumper::TRANSITION_TYPE_WORKFLOW : MermaidDumper::TRANSITION_TYPE_STATEMACHINE;
$dumper = new MermaidDumper($transitionType);
break;
case 'dot':
default:
$dumper = ('workflow' === $type) ? new GraphvizDumper() : new StateMachineGraphvizDumper();
}
$marking = new Marking();
@@ -73,6 +123,26 @@ EOF
$marking->mark($place);
}
$output->writeln($dumper->dump($workflow->getDefinition(), $marking));
$options = [
'name' => $workflowName,
'nofooter' => true,
'graph' => [
'label' => $input->getOption('label'),
],
];
$output->writeln($dumper->dump($workflow, $marking, $options));
return 0;
}
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('name')) {
$suggestions->suggestValues(array_keys($this->workflows));
}
if ($input->mustSuggestOptionValuesFor('dump-format')) {
$suggestions->suggestValues(self::DUMP_FORMAT_OPTIONS);
}
}
}

View File

@@ -20,35 +20,28 @@ use Symfony\Component\Translation\Command\XliffLintCommand as BaseLintCommand;
* @author Robin Chalas <robin.chalas@gmail.com>
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
*
* @final since version 3.4
* @final
*/
class XliffLintCommand extends BaseLintCommand
{
protected static $defaultName = 'lint:xliff';
protected static $defaultDescription = 'Lints an XLIFF file and outputs encountered errors';
public function __construct($name = null, $directoryIteratorProvider = null, $isReadableProvider = null)
public function __construct()
{
if (\func_num_args()) {
@trigger_error(sprintf('Passing a constructor argument in "%s()" is deprecated since Symfony 3.4 and will be removed in 4.0. If the command was registered by convention, make it a service instead.', __METHOD__), \E_USER_DEPRECATED);
}
$directoryIteratorProvider = function ($directory, $default) {
if (!is_dir($directory)) {
$directory = $this->getApplication()->getKernel()->locateResource($directory);
}
if (null === $directoryIteratorProvider) {
$directoryIteratorProvider = function ($directory, $default) {
if (!is_dir($directory)) {
$directory = $this->getApplication()->getKernel()->locateResource($directory);
}
return $default($directory);
};
return $default($directory);
};
}
$isReadableProvider = function ($fileOrDirectory, $default) {
return str_starts_with($fileOrDirectory, '@') || $default($fileOrDirectory);
};
if (null === $isReadableProvider) {
$isReadableProvider = function ($fileOrDirectory, $default) {
return 0 === strpos($fileOrDirectory, '@') || $default($fileOrDirectory);
};
}
parent::__construct($name, $directoryIteratorProvider, $isReadableProvider);
parent::__construct(null, $directoryIteratorProvider, $isReadableProvider);
}
/**

View File

@@ -19,35 +19,28 @@ use Symfony\Component\Yaml\Command\LintCommand as BaseLintCommand;
* @author Grégoire Pineau <lyrixx@lyrixx.info>
* @author Robin Chalas <robin.chalas@gmail.com>
*
* @final since version 3.4
* @final
*/
class YamlLintCommand extends BaseLintCommand
{
protected static $defaultName = 'lint:yaml';
protected static $defaultDescription = 'Lint a YAML file and outputs encountered errors';
public function __construct($name = null, $directoryIteratorProvider = null, $isReadableProvider = null)
public function __construct()
{
if (\func_num_args()) {
@trigger_error(sprintf('Passing a constructor argument in "%s()" is deprecated since Symfony 3.4 and will be removed in 4.0. If the command was registered by convention, make it a service instead.', __METHOD__), \E_USER_DEPRECATED);
}
$directoryIteratorProvider = function ($directory, $default) {
if (!is_dir($directory)) {
$directory = $this->getApplication()->getKernel()->locateResource($directory);
}
if (null === $directoryIteratorProvider) {
$directoryIteratorProvider = function ($directory, $default) {
if (!is_dir($directory)) {
$directory = $this->getApplication()->getKernel()->locateResource($directory);
}
return $default($directory);
};
return $default($directory);
};
}
$isReadableProvider = function ($fileOrDirectory, $default) {
return str_starts_with($fileOrDirectory, '@') || $default($fileOrDirectory);
};
if (null === $isReadableProvider) {
$isReadableProvider = function ($fileOrDirectory, $default) {
return 0 === strpos($fileOrDirectory, '@') || $default($fileOrDirectory);
};
}
parent::__construct($name, $directoryIteratorProvider, $isReadableProvider);
parent::__construct(null, $directoryIteratorProvider, $isReadableProvider);
}
/**

View File

@@ -13,12 +13,12 @@ namespace Symfony\Bundle\FrameworkBundle\Console;
use Symfony\Component\Console\Application as BaseApplication;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\ListCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Debug\Exception\FatalThrowableError;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\HttpKernel\Kernel;
@@ -41,19 +41,29 @@ class Application extends BaseApplication
$inputDefinition = $this->getDefinition();
$inputDefinition->addOption(new InputOption('--env', '-e', InputOption::VALUE_REQUIRED, 'The Environment name.', $kernel->getEnvironment()));
$inputDefinition->addOption(new InputOption('--no-debug', null, InputOption::VALUE_NONE, 'Switches off debug mode.'));
$inputDefinition->addOption(new InputOption('--no-debug', null, InputOption::VALUE_NONE, 'Switch off debug mode.'));
}
/**
* Gets the Kernel associated with this Console.
*
* @return KernelInterface A KernelInterface instance
* @return KernelInterface
*/
public function getKernel()
{
return $this->kernel;
}
/**
* {@inheritdoc}
*/
public function reset()
{
if ($this->kernel->getContainer()->has('services_resetter')) {
$this->kernel->getContainer()->get('services_resetter')->reset();
}
}
/**
* Runs the current application.
*
@@ -61,16 +71,14 @@ class Application extends BaseApplication
*/
public function doRun(InputInterface $input, OutputInterface $output)
{
$this->kernel->boot();
$this->setDispatcher($this->kernel->getContainer()->get('event_dispatcher'));
$this->registerCommands();
if ($this->registrationErrors) {
$this->renderRegistrationErrors($input, $output);
}
$this->setDispatcher($this->kernel->getContainer()->get('event_dispatcher'));
return parent::doRun($input, $output);
}
@@ -79,17 +87,29 @@ class Application extends BaseApplication
*/
protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
{
if ($this->registrationErrors) {
$this->renderRegistrationErrors($input, $output);
if (!$command instanceof ListCommand) {
if ($this->registrationErrors) {
$this->renderRegistrationErrors($input, $output);
$this->registrationErrors = [];
}
return parent::doRunCommand($command, $input, $output);
}
return parent::doRunCommand($command, $input, $output);
$returnCode = parent::doRunCommand($command, $input, $output);
if ($this->registrationErrors) {
$this->renderRegistrationErrors($input, $output);
$this->registrationErrors = [];
}
return $returnCode;
}
/**
* {@inheritdoc}
*/
public function find($name)
public function find(string $name)
{
$this->registerCommands();
@@ -99,7 +119,7 @@ class Application extends BaseApplication
/**
* {@inheritdoc}
*/
public function get($name)
public function get(string $name)
{
$this->registerCommands();
@@ -115,7 +135,7 @@ class Application extends BaseApplication
/**
* {@inheritdoc}
*/
public function all($namespace = null)
public function all(string $namespace = null)
{
$this->registerCommands();
@@ -127,7 +147,7 @@ class Application extends BaseApplication
*/
public function getLongVersion()
{
return parent::getLongVersion().sprintf(' (kernel: <comment>%s</>, env: <comment>%s</>, debug: <comment>%s</>)', $this->kernel->getName(), $this->kernel->getEnvironment(), $this->kernel->isDebug() ? 'true' : 'false');
return parent::getLongVersion().sprintf(' (env: <comment>%s</>, debug: <comment>%s</>) <bg=#0057B7;fg=#FFDD00>#StandWith</><bg=#FFDD00;fg=#0057B7>Ukraine</> <href=https://sf.to/ukraine>https://sf.to/ukraine</>', $this->kernel->getEnvironment(), $this->kernel->isDebug() ? 'true' : 'false');
}
public function add(Command $command)
@@ -153,10 +173,8 @@ class Application extends BaseApplication
if ($bundle instanceof Bundle) {
try {
$bundle->registerCommands($this);
} catch (\Exception $e) {
$this->registrationErrors[] = $e;
} catch (\Throwable $e) {
$this->registrationErrors[] = new FatalThrowableError($e);
$this->registrationErrors[] = $e;
}
}
}
@@ -171,10 +189,8 @@ class Application extends BaseApplication
if (!isset($lazyCommandIds[$id])) {
try {
$this->add($container->get($id));
} catch (\Exception $e) {
$this->registrationErrors[] = $e;
} catch (\Throwable $e) {
$this->registrationErrors[] = new FatalThrowableError($e);
$this->registrationErrors[] = $e;
}
}
}
@@ -190,9 +206,7 @@ class Application extends BaseApplication
(new SymfonyStyle($input, $output))->warning('Some commands could not be registered:');
foreach ($this->registrationErrors as $error) {
$this->doRenderException($error, $output);
$this->doRenderThrowable($error, $output);
}
$this->registrationErrors = [];
}
}

View File

@@ -11,10 +11,12 @@
namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor;
use Symfony\Component\Config\Resource\ClassExistenceResource;
use Symfony\Component\Console\Descriptor\DescriptorInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@@ -50,6 +52,9 @@ abstract class Descriptor implements DescriptorInterface
case $object instanceof ParameterBag:
$this->describeContainerParameters($object, $options);
break;
case $object instanceof ContainerBuilder && !empty($options['env-vars']):
$this->describeContainerEnvVars($this->getContainerEnvVars($object), $options);
break;
case $object instanceof ContainerBuilder && isset($options['group_by']) && 'tags' === $options['group_by']:
$this->describeContainerTags($object, $options);
break;
@@ -59,6 +64,9 @@ abstract class Descriptor implements DescriptorInterface
case $object instanceof ContainerBuilder && isset($options['parameter']):
$this->describeContainerParameter($object->resolveEnvPlaceholders($object->getParameter($options['parameter'])), $options);
break;
case $object instanceof ContainerBuilder && isset($options['deprecations']):
$this->describeContainerDeprecations($object, $options);
break;
case $object instanceof ContainerBuilder:
$this->describeContainerServices($object, $options);
break;
@@ -75,27 +83,16 @@ abstract class Descriptor implements DescriptorInterface
$this->describeCallable($object, $options);
break;
default:
throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', \get_class($object)));
throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_debug_type($object)));
}
}
/**
* Returns the output.
*
* @return OutputInterface The output
*/
protected function getOutput()
protected function getOutput(): OutputInterface
{
return $this->output;
}
/**
* Writes content to output.
*
* @param string $content
* @param bool $decorated
*/
protected function write($content, $decorated = false)
protected function write(string $content, bool $decorated = false)
{
$this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW);
}
@@ -116,7 +113,7 @@ abstract class Descriptor implements DescriptorInterface
*
* @param Definition|Alias|object $service
*/
abstract protected function describeContainerService($service, array $options = [], ContainerBuilder $builder = null);
abstract protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null);
/**
* Describes container services.
@@ -126,12 +123,16 @@ abstract class Descriptor implements DescriptorInterface
*/
abstract protected function describeContainerServices(ContainerBuilder $builder, array $options = []);
abstract protected function describeContainerDeprecations(ContainerBuilder $builder, array $options = []): void;
abstract protected function describeContainerDefinition(Definition $definition, array $options = []);
abstract protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null);
abstract protected function describeContainerParameter($parameter, array $options = []);
abstract protected function describeContainerEnvVars(array $envs, array $options = []);
/**
* Describes event dispatcher listeners.
*
@@ -151,11 +152,13 @@ abstract class Descriptor implements DescriptorInterface
* Formats a value as string.
*
* @param mixed $value
*
* @return string
*/
protected function formatValue($value)
protected function formatValue($value): string
{
if ($value instanceof \UnitEnum) {
return ltrim(var_export($value, true), '\\');
}
if (\is_object($value)) {
return sprintf('object(%s)', \get_class($value));
}
@@ -171,11 +174,23 @@ abstract class Descriptor implements DescriptorInterface
* Formats a parameter.
*
* @param mixed $value
*
* @return string
*/
protected function formatParameter($value)
protected function formatParameter($value): string
{
if ($value instanceof \UnitEnum) {
return ltrim(var_export($value, true), '\\');
}
// Recursively search for enum values, so we can replace it
// before json_encode (which will not display anything for \UnitEnum otherwise)
if (\is_array($value)) {
array_walk_recursive($value, static function (&$value) {
if ($value instanceof \UnitEnum) {
$value = ltrim(var_export($value, true), '\\');
}
});
}
if (\is_bool($value) || \is_array($value) || (null === $value)) {
$jsonString = json_encode($value);
@@ -190,11 +205,9 @@ abstract class Descriptor implements DescriptorInterface
}
/**
* @param string $serviceId
*
* @return mixed
*/
protected function resolveServiceDefinition(ContainerBuilder $builder, $serviceId)
protected function resolveServiceDefinition(ContainerBuilder $builder, string $serviceId)
{
if ($builder->hasDefinition($serviceId)) {
return $builder->getDefinition($serviceId);
@@ -205,16 +218,15 @@ abstract class Descriptor implements DescriptorInterface
return $builder->getAlias($serviceId);
}
if ('service_container' === $serviceId) {
return (new Definition(ContainerInterface::class))->setPublic(true)->setSynthetic(true);
}
// the service has been injected in some special way, just return the service
return $builder->get($serviceId);
}
/**
* @param bool $showPrivate
*
* @return array
*/
protected function findDefinitionsByTag(ContainerBuilder $builder, $showPrivate)
protected function findDefinitionsByTag(ContainerBuilder $builder, bool $showHidden): array
{
$definitions = [];
$tags = $builder->findTags();
@@ -224,7 +236,7 @@ abstract class Descriptor implements DescriptorInterface
foreach ($builder->findTaggedServiceIds($tag) as $serviceId => $attributes) {
$definition = $this->resolveServiceDefinition($builder, $serviceId);
if (!$definition instanceof Definition || !$showPrivate && !$definition->isPublic()) {
if ($showHidden xor '.' === ($serviceId[0] ?? null)) {
continue;
}
@@ -253,4 +265,116 @@ abstract class Descriptor implements DescriptorInterface
return $serviceIds;
}
protected function sortTaggedServicesByPriority(array $services): array
{
$maxPriority = [];
foreach ($services as $service => $tags) {
$maxPriority[$service] = \PHP_INT_MIN;
foreach ($tags as $tag) {
$currentPriority = $tag['priority'] ?? 0;
if ($maxPriority[$service] < $currentPriority) {
$maxPriority[$service] = $currentPriority;
}
}
}
uasort($maxPriority, function ($a, $b) {
return $b <=> $a;
});
return array_keys($maxPriority);
}
protected function sortTagsByPriority(array $tags): array
{
$sortedTags = [];
foreach ($tags as $tagName => $tag) {
$sortedTags[$tagName] = $this->sortByPriority($tag);
}
return $sortedTags;
}
protected function sortByPriority(array $tag): array
{
usort($tag, function ($a, $b) {
return ($b['priority'] ?? 0) <=> ($a['priority'] ?? 0);
});
return $tag;
}
public static function getClassDescription(string $class, string &$resolvedClass = null): string
{
$resolvedClass = $class;
try {
$resource = new ClassExistenceResource($class, false);
// isFresh() will explode ONLY if a parent class/trait does not exist
$resource->isFresh(0);
$r = new \ReflectionClass($class);
$resolvedClass = $r->name;
if ($docComment = $r->getDocComment()) {
$docComment = preg_split('#\n\s*\*\s*[\n@]#', substr($docComment, 3, -2), 2)[0];
return trim(preg_replace('#\s*\n\s*\*\s*#', ' ', $docComment));
}
} catch (\ReflectionException $e) {
}
return '';
}
private function getContainerEnvVars(ContainerBuilder $container): array
{
if (!$container->hasParameter('debug.container.dump')) {
return [];
}
if (!is_file($container->getParameter('debug.container.dump'))) {
return [];
}
$file = file_get_contents($container->getParameter('debug.container.dump'));
preg_match_all('{%env\(((?:\w++:)*+\w++)\)%}', $file, $envVars);
$envVars = array_unique($envVars[1]);
$bag = $container->getParameterBag();
$getDefaultParameter = function (string $name) {
return parent::get($name);
};
$getDefaultParameter = $getDefaultParameter->bindTo($bag, \get_class($bag));
$getEnvReflection = new \ReflectionMethod($container, 'getEnv');
$getEnvReflection->setAccessible(true);
$envs = [];
foreach ($envVars as $env) {
$processor = 'string';
if (false !== $i = strrpos($name = $env, ':')) {
$name = substr($env, $i + 1);
$processor = substr($env, 0, $i);
}
$defaultValue = ($hasDefault = $container->hasParameter("env($name)")) ? $getDefaultParameter("env($name)") : null;
if (false === ($runtimeValue = $_ENV[$name] ?? $_SERVER[$name] ?? getenv($name))) {
$runtimeValue = null;
}
$processedValue = ($hasRuntime = null !== $runtimeValue) || $hasDefault ? $getEnvReflection->invoke($container, $env) : null;
$envs["$name$processor"] = [
'name' => $name,
'processor' => $processor,
'default_available' => $hasDefault,
'default_value' => $defaultValue,
'runtime_available' => $hasRuntime,
'runtime_value' => $runtimeValue,
'processed_value' => $processedValue,
];
}
ksort($envs);
return array_values($envs);
}
}

View File

@@ -11,7 +11,10 @@
namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -51,10 +54,10 @@ class JsonDescriptor extends Descriptor
protected function describeContainerTags(ContainerBuilder $builder, array $options = [])
{
$showPrivate = isset($options['show_private']) && $options['show_private'];
$showHidden = isset($options['show_hidden']) && $options['show_hidden'];
$data = [];
foreach ($this->findDefinitionsByTag($builder, $showPrivate) as $tag => $definitions) {
foreach ($this->findDefinitionsByTag($builder, $showHidden) as $tag => $definitions) {
$data[$tag] = [];
foreach ($definitions as $definition) {
$data[$tag][] = $this->getContainerDefinitionData($definition, true);
@@ -64,10 +67,7 @@ class JsonDescriptor extends Descriptor
$this->writeData($data, $options);
}
/**
* {@inheritdoc}
*/
protected function describeContainerService($service, array $options = [], ContainerBuilder $builder = null)
protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null)
{
if (!isset($options['id'])) {
throw new \InvalidArgumentException('An "id" option must be provided.');
@@ -82,13 +82,12 @@ class JsonDescriptor extends Descriptor
}
}
/**
* {@inheritdoc}
*/
protected function describeContainerServices(ContainerBuilder $builder, array $options = [])
{
$serviceIds = isset($options['tag']) && $options['tag'] ? array_keys($builder->findTaggedServiceIds($options['tag'])) : $builder->getServiceIds();
$showPrivate = isset($options['show_private']) && $options['show_private'];
$serviceIds = isset($options['tag']) && $options['tag']
? $this->sortTaggedServicesByPriority($builder->findTaggedServiceIds($options['tag']))
: $this->sortServiceIds($builder->getServiceIds());
$showHidden = isset($options['show_hidden']) && $options['show_hidden'];
$omitTags = isset($options['omit_tags']) && $options['omit_tags'];
$showArguments = isset($options['show_arguments']) && $options['show_arguments'];
$data = ['definitions' => [], 'aliases' => [], 'services' => []];
@@ -97,17 +96,17 @@ class JsonDescriptor extends Descriptor
$serviceIds = array_filter($serviceIds, $options['filter']);
}
foreach ($this->sortServiceIds($serviceIds) as $serviceId) {
foreach ($serviceIds as $serviceId) {
$service = $this->resolveServiceDefinition($builder, $serviceId);
if ($showHidden xor '.' === ($serviceId[0] ?? null)) {
continue;
}
if ($service instanceof Alias) {
if ($showPrivate || ($service->isPublic() && !$service->isPrivate())) {
$data['aliases'][$serviceId] = $this->getContainerAliasData($service);
}
$data['aliases'][$serviceId] = $this->getContainerAliasData($service);
} elseif ($service instanceof Definition) {
if (($showPrivate || ($service->isPublic() && !$service->isPrivate()))) {
$data['definitions'][$serviceId] = $this->getContainerDefinitionData($service, $omitTags, $showArguments);
}
$data['definitions'][$serviceId] = $this->getContainerDefinitionData($service, $omitTags, $showArguments);
} else {
$data['services'][$serviceId] = \get_class($service);
}
@@ -135,17 +134,11 @@ class JsonDescriptor extends Descriptor
);
}
/**
* {@inheritdoc}
*/
protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = [])
{
$this->writeData($this->getEventDispatcherListenersData($eventDispatcher, \array_key_exists('event', $options) ? $options['event'] : null), $options);
$this->writeData($this->getEventDispatcherListenersData($eventDispatcher, $options), $options);
}
/**
* {@inheritdoc}
*/
protected function describeCallable($callable, array $options = [])
{
$this->writeData($this->getCallableData($callable), $options);
@@ -153,27 +146,58 @@ class JsonDescriptor extends Descriptor
protected function describeContainerParameter($parameter, array $options = [])
{
$key = isset($options['parameter']) ? $options['parameter'] : '';
$key = $options['parameter'] ?? '';
$this->writeData([$key => $parameter], $options);
}
/**
* Writes data as json.
*/
protected function describeContainerEnvVars(array $envs, array $options = [])
{
throw new LogicException('Using the JSON format to debug environment variables is not supported.');
}
protected function describeContainerDeprecations(ContainerBuilder $builder, array $options = []): void
{
$containerDeprecationFilePath = sprintf('%s/%sDeprecations.log', $builder->getParameter('kernel.build_dir'), $builder->getParameter('kernel.container_class'));
if (!file_exists($containerDeprecationFilePath)) {
throw new RuntimeException('The deprecation file does not exist, please try warming the cache first.');
}
$logs = unserialize(file_get_contents($containerDeprecationFilePath));
$formattedLogs = [];
$remainingCount = 0;
foreach ($logs as $log) {
$formattedLogs[] = [
'message' => $log['message'],
'file' => $log['file'],
'line' => $log['line'],
'count' => $log['count'],
];
$remainingCount += $log['count'];
}
$this->writeData(['remainingCount' => $remainingCount, 'deprecations' => $formattedLogs], $options);
}
private function writeData(array $data, array $options)
{
$flags = isset($options['json_encoding']) ? $options['json_encoding'] : 0;
$flags = $options['json_encoding'] ?? 0;
// Recursively search for enum values, so we can replace it
// before json_encode (which will not display anything for \UnitEnum otherwise)
array_walk_recursive($data, static function (&$value) {
if ($value instanceof \UnitEnum) {
$value = ltrim(var_export($value, true), '\\');
}
});
$this->write(json_encode($data, $flags | \JSON_PRETTY_PRINT)."\n");
}
/**
* @return array
*/
protected function getRouteData(Route $route)
protected function getRouteData(Route $route): array
{
return [
$data = [
'path' => $route->getPath(),
'pathRegex' => $route->compile()->getRegex(),
'host' => '' !== $route->getHost() ? $route->getHost() : 'ANY',
@@ -185,14 +209,15 @@ class JsonDescriptor extends Descriptor
'requirements' => $route->getRequirements() ?: 'NO CUSTOM',
'options' => $route->getOptions(),
];
if ('' !== $route->getCondition()) {
$data['condition'] = $route->getCondition();
}
return $data;
}
/**
* @param bool $omitTags
*
* @return array
*/
private function getContainerDefinitionData(Definition $definition, $omitTags = false, $showArguments = false)
private function getContainerDefinitionData(Definition $definition, bool $omitTags = false, bool $showArguments = false): array
{
$data = [
'class' => (string) $definition->getClass(),
@@ -205,11 +230,8 @@ class JsonDescriptor extends Descriptor
'autoconfigure' => $definition->isAutoconfigured(),
];
// forward compatibility with DependencyInjection component in version 4.0
if (method_exists($definition, 'getAutowiringTypes')) {
foreach ($definition->getAutowiringTypes(false) as $autowiringType) {
$data['autowiring_types'][] = $autowiringType;
}
if ('' !== $classDescription = $this->getClassDescription((string) $definition->getClass())) {
$data['description'] = $classDescription;
}
if ($showArguments) {
@@ -243,7 +265,7 @@ class JsonDescriptor extends Descriptor
if (!$omitTags) {
$data['tags'] = [];
foreach ($definition->getTags() as $tagName => $tagData) {
foreach ($this->sortTagsByPriority($definition->getTags()) as $tagName => $tagData) {
foreach ($tagData as $parameters) {
$data['tags'][] = ['name' => $tagName, 'parameters' => $parameters];
}
@@ -253,10 +275,7 @@ class JsonDescriptor extends Descriptor
return $data;
}
/**
* @return array
*/
private function getContainerAliasData(Alias $alias)
private function getContainerAliasData(Alias $alias): array
{
return [
'service' => (string) $alias,
@@ -264,23 +283,19 @@ class JsonDescriptor extends Descriptor
];
}
/**
* @param string|null $event
*
* @return array
*/
private function getEventDispatcherListenersData(EventDispatcherInterface $eventDispatcher, $event = null)
private function getEventDispatcherListenersData(EventDispatcherInterface $eventDispatcher, array $options): array
{
$data = [];
$event = \array_key_exists('event', $options) ? $options['event'] : null;
$registeredListeners = $eventDispatcher->getListeners($event);
if (null !== $event) {
foreach ($registeredListeners as $listener) {
foreach ($eventDispatcher->getListeners($event) as $listener) {
$l = $this->getCallableData($listener);
$l['priority'] = $eventDispatcher->getListenerPriority($event, $listener);
$data[] = $l;
}
} else {
$registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(function ($event) use ($eventDispatcher) { return $eventDispatcher->getListeners($event); }, $options['events'])) : $eventDispatcher->getListeners();
ksort($registeredListeners);
foreach ($registeredListeners as $eventListened => $eventListeners) {
@@ -295,12 +310,7 @@ class JsonDescriptor extends Descriptor
return $data;
}
/**
* @param callable $callable
*
* @return array
*/
private function getCallableData($callable)
private function getCallableData($callable): array
{
$data = [];
@@ -311,7 +321,7 @@ class JsonDescriptor extends Descriptor
$data['name'] = $callable[1];
$data['class'] = \get_class($callable[0]);
} else {
if (0 !== strpos($callable[1], 'parent::')) {
if (!str_starts_with($callable[1], 'parent::')) {
$data['name'] = $callable[1];
$data['class'] = $callable[0];
$data['static'] = true;
@@ -329,7 +339,7 @@ class JsonDescriptor extends Descriptor
if (\is_string($callable)) {
$data['type'] = 'function';
if (false === strpos($callable, '::')) {
if (!str_contains($callable, '::')) {
$data['name'] = $callable;
} else {
$callableParts = explode('::', $callable);
@@ -346,7 +356,7 @@ class JsonDescriptor extends Descriptor
$data['type'] = 'closure';
$r = new \ReflectionFunction($callable);
if (false !== strpos($r->name, '{closure}')) {
if (str_contains($r->name, '{closure}')) {
return $data;
}
$data['name'] = $r->name;
@@ -371,7 +381,7 @@ class JsonDescriptor extends Descriptor
throw new \InvalidArgumentException('Callable is not describable.');
}
private function describeValue($value, $omitTags, $showArguments)
private function describeValue($value, bool $omitTags, bool $showArguments)
{
if (\is_array($value)) {
$data = [];
@@ -393,6 +403,10 @@ class JsonDescriptor extends Descriptor
];
}
if ($value instanceof AbstractArgument) {
return ['type' => 'abstract', 'text' => $value->getText()];
}
if ($value instanceof ArgumentInterface) {
return $this->describeValue($value->getValues(), $omitTags, $showArguments);
}

View File

@@ -11,6 +11,8 @@
namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
@@ -54,6 +56,10 @@ class MarkdownDescriptor extends Descriptor
."\n".'- Requirements: '.($route->getRequirements() ? $this->formatRouterConfig($route->getRequirements()) : 'NO CUSTOM')
."\n".'- Options: '.$this->formatRouterConfig($route->getOptions());
if ('' !== $route->getCondition()) {
$output .= "\n".'- Condition: '.$route->getCondition();
}
$this->write(isset($options['name'])
? $options['name']."\n".str_repeat('-', \strlen($options['name']))."\n\n".$output
: $output);
@@ -70,10 +76,10 @@ class MarkdownDescriptor extends Descriptor
protected function describeContainerTags(ContainerBuilder $builder, array $options = [])
{
$showPrivate = isset($options['show_private']) && $options['show_private'];
$showHidden = isset($options['show_hidden']) && $options['show_hidden'];
$this->write("Container tags\n==============");
foreach ($this->findDefinitionsByTag($builder, $showPrivate) as $tag => $definitions) {
foreach ($this->findDefinitionsByTag($builder, $showHidden) as $tag => $definitions) {
$this->write("\n\n".$tag."\n".str_repeat('-', \strlen($tag)));
foreach ($definitions as $serviceId => $definition) {
$this->write("\n\n");
@@ -82,10 +88,7 @@ class MarkdownDescriptor extends Descriptor
}
}
/**
* {@inheritdoc}
*/
protected function describeContainerService($service, array $options = [], ContainerBuilder $builder = null)
protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null)
{
if (!isset($options['id'])) {
throw new \InvalidArgumentException('An "id" option must be provided.');
@@ -102,20 +105,46 @@ class MarkdownDescriptor extends Descriptor
}
}
/**
* {@inheritdoc}
*/
protected function describeContainerDeprecations(ContainerBuilder $builder, array $options = []): void
{
$containerDeprecationFilePath = sprintf('%s/%sDeprecations.log', $builder->getParameter('kernel.build_dir'), $builder->getParameter('kernel.container_class'));
if (!file_exists($containerDeprecationFilePath)) {
throw new RuntimeException('The deprecation file does not exist, please try warming the cache first.');
}
$logs = unserialize(file_get_contents($containerDeprecationFilePath));
if (0 === \count($logs)) {
$this->write("## There are no deprecations in the logs!\n");
return;
}
$formattedLogs = [];
$remainingCount = 0;
foreach ($logs as $log) {
$formattedLogs[] = sprintf("- %sx: \"%s\" in %s:%s\n", $log['count'], $log['message'], $log['file'], $log['line']);
$remainingCount += $log['count'];
}
$this->write(sprintf("## Remaining deprecations (%s)\n\n", $remainingCount));
foreach ($formattedLogs as $formattedLog) {
$this->write($formattedLog);
}
}
protected function describeContainerServices(ContainerBuilder $builder, array $options = [])
{
$showPrivate = isset($options['show_private']) && $options['show_private'];
$showHidden = isset($options['show_hidden']) && $options['show_hidden'];
$title = $showPrivate ? 'Public and private services' : 'Public services';
$title = $showHidden ? 'Hidden services' : 'Services';
if (isset($options['tag'])) {
$title .= ' with tag `'.$options['tag'].'`';
}
$this->write($title."\n".str_repeat('=', \strlen($title)));
$serviceIds = isset($options['tag']) && $options['tag'] ? array_keys($builder->findTaggedServiceIds($options['tag'])) : $builder->getServiceIds();
$serviceIds = isset($options['tag']) && $options['tag']
? $this->sortTaggedServicesByPriority($builder->findTaggedServiceIds($options['tag']))
: $this->sortServiceIds($builder->getServiceIds());
$showArguments = isset($options['show_arguments']) && $options['show_arguments'];
$services = ['definitions' => [], 'aliases' => [], 'services' => []];
@@ -123,17 +152,17 @@ class MarkdownDescriptor extends Descriptor
$serviceIds = array_filter($serviceIds, $options['filter']);
}
foreach ($this->sortServiceIds($serviceIds) as $serviceId) {
foreach ($serviceIds as $serviceId) {
$service = $this->resolveServiceDefinition($builder, $serviceId);
if ($showHidden xor '.' === ($serviceId[0] ?? null)) {
continue;
}
if ($service instanceof Alias) {
if ($showPrivate || ($service->isPublic() && !$service->isPrivate())) {
$services['aliases'][$serviceId] = $service;
}
$services['aliases'][$serviceId] = $service;
} elseif ($service instanceof Definition) {
if (($showPrivate || ($service->isPublic() && !$service->isPrivate()))) {
$services['definitions'][$serviceId] = $service;
}
$services['definitions'][$serviceId] = $service;
} else {
$services['services'][$serviceId] = $service;
}
@@ -166,7 +195,13 @@ class MarkdownDescriptor extends Descriptor
protected function describeContainerDefinition(Definition $definition, array $options = [])
{
$output = '- Class: `'.$definition->getClass().'`'
$output = '';
if ('' !== $classDescription = $this->getClassDescription((string) $definition->getClass())) {
$output .= '- Description: `'.$classDescription.'`'."\n";
}
$output .= '- Class: `'.$definition->getClass().'`'
."\n".'- Public: '.($definition->isPublic() && !$definition->isPrivate() ? 'yes' : 'no')
."\n".'- Synthetic: '.($definition->isSynthetic() ? 'yes' : 'no')
."\n".'- Lazy: '.($definition->isLazy() ? 'yes' : 'no')
@@ -176,13 +211,6 @@ class MarkdownDescriptor extends Descriptor
."\n".'- Autoconfigured: '.($definition->isAutoconfigured() ? 'yes' : 'no')
;
// forward compatibility with DependencyInjection component in version 4.0
if (method_exists($definition, 'getAutowiringTypes')) {
foreach ($definition->getAutowiringTypes(false) as $autowiringType) {
$output .= "\n".'- Autowiring Type: `'.$autowiringType.'`';
}
}
if (isset($options['show_arguments']) && $options['show_arguments']) {
$output .= "\n".'- Arguments: '.($definition->getArguments() ? 'yes' : 'no');
}
@@ -212,7 +240,7 @@ class MarkdownDescriptor extends Descriptor
}
if (!(isset($options['omit_tags']) && $options['omit_tags'])) {
foreach ($definition->getTags() as $tagName => $tagData) {
foreach ($this->sortTagsByPriority($definition->getTags()) as $tagName => $tagData) {
foreach ($tagData as $parameters) {
$output .= "\n".'- Tag: `'.$tagName.'`';
foreach ($parameters as $name => $value) {
@@ -251,21 +279,32 @@ class MarkdownDescriptor extends Descriptor
$this->write(isset($options['parameter']) ? sprintf("%s\n%s\n\n%s", $options['parameter'], str_repeat('=', \strlen($options['parameter'])), $this->formatParameter($parameter)) : $parameter);
}
/**
* {@inheritdoc}
*/
protected function describeContainerEnvVars(array $envs, array $options = [])
{
throw new LogicException('Using the markdown format to debug environment variables is not supported.');
}
protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = [])
{
$event = \array_key_exists('event', $options) ? $options['event'] : null;
$event = $options['event'] ?? null;
$dispatcherServiceName = $options['dispatcher_service_name'] ?? null;
$title = 'Registered listeners';
if (null !== $dispatcherServiceName) {
$title .= sprintf(' of event dispatcher "%s"', $dispatcherServiceName);
}
if (null !== $event) {
$title .= sprintf(' for event `%s` ordered by descending priority', $event);
$registeredListeners = $eventDispatcher->getListeners($event);
} else {
// Try to see if "events" exists
$registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(function ($event) use ($eventDispatcher) { return $eventDispatcher->getListeners($event); }, $options['events'])) : $eventDispatcher->getListeners();
}
$this->write(sprintf('# %s', $title)."\n");
$registeredListeners = $eventDispatcher->getListeners($event);
if (null !== $event) {
foreach ($registeredListeners as $order => $listener) {
$this->write("\n".sprintf('## Listener %d', $order + 1)."\n");
@@ -287,9 +326,6 @@ class MarkdownDescriptor extends Descriptor
}
}
/**
* {@inheritdoc}
*/
protected function describeCallable($callable, array $options = [])
{
$string = '';
@@ -301,7 +337,7 @@ class MarkdownDescriptor extends Descriptor
$string .= "\n".sprintf('- Name: `%s`', $callable[1]);
$string .= "\n".sprintf('- Class: `%s`', \get_class($callable[0]));
} else {
if (0 !== strpos($callable[1], 'parent::')) {
if (!str_starts_with($callable[1], 'parent::')) {
$string .= "\n".sprintf('- Name: `%s`', $callable[1]);
$string .= "\n".sprintf('- Class: `%s`', $callable[0]);
$string .= "\n- Static: yes";
@@ -319,7 +355,7 @@ class MarkdownDescriptor extends Descriptor
if (\is_string($callable)) {
$string .= "\n- Type: `function`";
if (false === strpos($callable, '::')) {
if (!str_contains($callable, '::')) {
$string .= "\n".sprintf('- Name: `%s`', $callable);
} else {
$callableParts = explode('::', $callable);
@@ -336,7 +372,7 @@ class MarkdownDescriptor extends Descriptor
$string .= "\n- Type: `closure`";
$r = new \ReflectionFunction($callable);
if (false !== strpos($r->name, '{closure}')) {
if (str_contains($r->name, '{closure}')) {
return $this->write($string."\n");
}
$string .= "\n".sprintf('- Name: `%s`', $r->name);
@@ -361,10 +397,7 @@ class MarkdownDescriptor extends Descriptor
throw new \InvalidArgumentException('Callable is not describable.');
}
/**
* @return string
*/
private function formatRouterConfig(array $array)
private function formatRouterConfig(array $array): string
{
if (!$array) {
return 'NONE';

View File

@@ -12,17 +12,21 @@
namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\Dumper;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
@@ -33,6 +37,13 @@ use Symfony\Component\Routing\RouteCollection;
*/
class TextDescriptor extends Descriptor
{
private $fileLinkFormatter;
public function __construct(FileLinkFormatter $fileLinkFormatter = null)
{
$this->fileLinkFormatter = $fileLinkFormatter;
}
protected function describeRouteCollection(RouteCollection $routes, array $options = [])
{
$showControllers = isset($options['show_controllers']) && $options['show_controllers'];
@@ -44,17 +55,18 @@ class TextDescriptor extends Descriptor
$tableRows = [];
foreach ($routes->all() as $name => $route) {
$controller = $route->getDefault('_controller');
$row = [
$name,
$route->getMethods() ? implode('|', $route->getMethods()) : 'ANY',
$route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY',
'' !== $route->getHost() ? $route->getHost() : 'ANY',
$route->getPath(),
$this->formatControllerLink($controller, $route->getPath(), $options['container'] ?? null),
];
if ($showControllers) {
$controller = $route->getDefault('_controller');
$row[] = $controller ? $this->formatCallable($controller) : '';
$row[] = $controller ? $this->formatControllerLink($controller, $this->formatCallable($controller), $options['container'] ?? null) : '';
}
$tableRows[] = $row;
@@ -71,9 +83,14 @@ class TextDescriptor extends Descriptor
protected function describeRoute(Route $route, array $options = [])
{
$defaults = $route->getDefaults();
if (isset($defaults['_controller'])) {
$defaults['_controller'] = $this->formatControllerLink($defaults['_controller'], $this->formatCallable($defaults['_controller']), $options['container'] ?? null);
}
$tableHeaders = ['Property', 'Value'];
$tableRows = [
['Route Name', isset($options['name']) ? $options['name'] : ''],
['Route Name', $options['name'] ?? ''],
['Path', $route->getPath()],
['Path Regex', $route->compile()->getRegex()],
['Host', ('' !== $route->getHost() ? $route->getHost() : 'ANY')],
@@ -82,11 +99,12 @@ class TextDescriptor extends Descriptor
['Method', ($route->getMethods() ? implode('|', $route->getMethods()) : 'ANY')],
['Requirements', ($route->getRequirements() ? $this->formatRouterConfig($route->getRequirements()) : 'NO CUSTOM')],
['Class', \get_class($route)],
['Defaults', $this->formatRouterConfig($route->getDefaults())],
['Defaults', $this->formatRouterConfig($defaults)],
['Options', $this->formatRouterConfig($route->getOptions())],
];
if (isset($options['callable'])) {
$tableRows[] = ['Callable', $options['callable']];
if ('' !== $route->getCondition()) {
$tableRows[] = ['Condition', $route->getCondition()];
}
$table = new Table($this->getOutput());
@@ -109,24 +127,21 @@ class TextDescriptor extends Descriptor
protected function describeContainerTags(ContainerBuilder $builder, array $options = [])
{
$showPrivate = isset($options['show_private']) && $options['show_private'];
$showHidden = isset($options['show_hidden']) && $options['show_hidden'];
if ($showPrivate) {
$options['output']->title('Symfony Container Public and Private Tags');
if ($showHidden) {
$options['output']->title('Symfony Container Hidden Tags');
} else {
$options['output']->title('Symfony Container Public Tags');
$options['output']->title('Symfony Container Tags');
}
foreach ($this->findDefinitionsByTag($builder, $showPrivate) as $tag => $definitions) {
foreach ($this->findDefinitionsByTag($builder, $showHidden) as $tag => $definitions) {
$options['output']->section(sprintf('"%s" tag', $tag));
$options['output']->listing(array_keys($definitions));
}
}
/**
* {@inheritdoc}
*/
protected function describeContainerService($service, array $options = [], ContainerBuilder $builder = null)
protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null)
{
if (!isset($options['id'])) {
throw new \InvalidArgumentException('An "id" option must be provided.');
@@ -141,24 +156,21 @@ class TextDescriptor extends Descriptor
$options['output']->table(
['Service ID', 'Class'],
[
[isset($options['id']) ? $options['id'] : '-', \get_class($service)],
[$options['id'] ?? '-', \get_class($service)],
]
);
}
}
/**
* {@inheritdoc}
*/
protected function describeContainerServices(ContainerBuilder $builder, array $options = [])
{
$showPrivate = isset($options['show_private']) && $options['show_private'];
$showTag = isset($options['tag']) ? $options['tag'] : null;
$showHidden = isset($options['show_hidden']) && $options['show_hidden'];
$showTag = $options['tag'] ?? null;
if ($showPrivate) {
$title = 'Symfony Container Public and Private Services';
if ($showHidden) {
$title = 'Symfony Container Hidden Services';
} else {
$title = 'Symfony Container Public Services';
$title = 'Symfony Container Services';
}
if ($showTag) {
@@ -167,7 +179,9 @@ class TextDescriptor extends Descriptor
$options['output']->title($title);
$serviceIds = isset($options['tag']) && $options['tag'] ? array_keys($builder->findTaggedServiceIds($options['tag'])) : $builder->getServiceIds();
$serviceIds = isset($options['tag']) && $options['tag']
? $this->sortTaggedServicesByPriority($builder->findTaggedServiceIds($options['tag']))
: $this->sortServiceIds($builder->getServiceIds());
$maxTags = [];
if (isset($options['filter'])) {
@@ -176,12 +190,14 @@ class TextDescriptor extends Descriptor
foreach ($serviceIds as $key => $serviceId) {
$definition = $this->resolveServiceDefinition($builder, $serviceId);
// filter out hidden services unless shown explicitly
if ($showHidden xor '.' === ($serviceId[0] ?? null)) {
unset($serviceIds[$key]);
continue;
}
if ($definition instanceof Definition) {
// filter out private services unless shown explicitly
if (!$showPrivate && (!$definition->isPublic() || $definition->isPrivate())) {
unset($serviceIds[$key]);
continue;
}
if ($showTag) {
$tags = $definition->getTag($showTag);
foreach ($tags as $tag) {
@@ -195,11 +211,6 @@ class TextDescriptor extends Descriptor
}
}
}
} elseif ($definition instanceof Alias) {
if (!$showPrivate && (!$definition->isPublic() || $definition->isPrivate())) {
unset($serviceIds[$key]);
continue;
}
}
}
@@ -209,21 +220,21 @@ class TextDescriptor extends Descriptor
$tableHeaders = array_merge(['Service ID'], $tagsNames, ['Class name']);
$tableRows = [];
$rawOutput = isset($options['raw_text']) && $options['raw_text'];
foreach ($this->sortServiceIds($serviceIds) as $serviceId) {
foreach ($serviceIds as $serviceId) {
$definition = $this->resolveServiceDefinition($builder, $serviceId);
$styledServiceId = $rawOutput ? $serviceId : sprintf('<fg=cyan>%s</fg=cyan>', OutputFormatter::escape($serviceId));
if ($definition instanceof Definition) {
if ($showTag) {
foreach ($definition->getTag($showTag) as $key => $tag) {
foreach ($this->sortByPriority($definition->getTag($showTag)) as $key => $tag) {
$tagValues = [];
foreach ($tagsNames as $tagName) {
$tagValues[] = isset($tag[$tagName]) ? $tag[$tagName] : '';
$tagValues[] = $tag[$tagName] ?? '';
}
if (0 === $key) {
$tableRows[] = array_merge([$serviceId], $tagValues, [$definition->getClass()]);
} else {
$tableRows[] = array_merge([' "'], $tagValues, ['']);
$tableRows[] = array_merge([' (same service as previous, another tag)'], $tagValues, ['']);
}
}
} else {
@@ -246,9 +257,13 @@ class TextDescriptor extends Descriptor
$options['output']->title(sprintf('Information for Service "<info>%s</info>"', $options['id']));
}
if ('' !== $classDescription = $this->getClassDescription((string) $definition->getClass())) {
$options['output']->text($classDescription."\n");
}
$tableHeaders = ['Option', 'Value'];
$tableRows[] = ['Service ID', isset($options['id']) ? $options['id'] : '-'];
$tableRows[] = ['Service ID', $options['id'] ?? '-'];
$tableRows[] = ['Class', $definition->getClass() ?: '-'];
$omitTags = isset($options['omit_tags']) && $options['omit_tags'];
@@ -291,11 +306,6 @@ class TextDescriptor extends Descriptor
$tableRows[] = ['Autowired', $definition->isAutowired() ? 'yes' : 'no'];
$tableRows[] = ['Autoconfigured', $definition->isAutoconfigured() ? 'yes' : 'no'];
// forward compatibility with DependencyInjection component in version 4.0
if (method_exists($definition, 'getAutowiringTypes') && $autowiringTypes = $definition->getAutowiringTypes(false)) {
$tableRows[] = ['Autowiring Types', implode(', ', $autowiringTypes)];
}
if ($definition->getFile()) {
$tableRows[] = ['Required File', $definition->getFile() ?: '-'];
}
@@ -330,8 +340,18 @@ class TextDescriptor extends Descriptor
} else {
$argumentsInformation[] = sprintf('Iterator (%d element(s))', \count($argument->getValues()));
}
foreach ($argument->getValues() as $ref) {
$argumentsInformation[] = sprintf('- Service(%s)', $ref);
}
} elseif ($argument instanceof ServiceLocatorArgument) {
$argumentsInformation[] = sprintf('Service locator (%d element(s))', \count($argument->getValues()));
} elseif ($argument instanceof Definition) {
$argumentsInformation[] = 'Inlined Service';
} elseif ($argument instanceof \UnitEnum) {
$argumentsInformation[] = ltrim(var_export($argument, true), '\\');
} elseif ($argument instanceof AbstractArgument) {
$argumentsInformation[] = sprintf('Abstract argument (%s)', $argument->getText());
} else {
$argumentsInformation[] = \is_array($argument) ? sprintf('Array (%d element(s))', \count($argument)) : $argument;
}
@@ -343,9 +363,39 @@ class TextDescriptor extends Descriptor
$options['output']->table($tableHeaders, $tableRows);
}
protected function describeContainerDeprecations(ContainerBuilder $builder, array $options = []): void
{
$containerDeprecationFilePath = sprintf('%s/%sDeprecations.log', $builder->getParameter('kernel.build_dir'), $builder->getParameter('kernel.container_class'));
if (!file_exists($containerDeprecationFilePath)) {
$options['output']->warning('The deprecation file does not exist, please try warming the cache first.');
return;
}
$logs = unserialize(file_get_contents($containerDeprecationFilePath));
if (0 === \count($logs)) {
$options['output']->success('There are no deprecations in the logs!');
return;
}
$formattedLogs = [];
$remainingCount = 0;
foreach ($logs as $log) {
$formattedLogs[] = sprintf("%sx: %s\n in %s:%s", $log['count'], $log['message'], $log['file'], $log['line']);
$remainingCount += $log['count'];
}
$options['output']->title(sprintf('Remaining deprecations (%s)', $remainingCount));
$options['output']->listing($formattedLogs);
}
protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null)
{
$options['output']->comment(sprintf('This service is an alias for the service <info>%s</info>', (string) $alias));
if ($alias->isPublic() && !$alias->isPrivate()) {
$options['output']->comment(sprintf('This service is a <info>public</info> alias for the service <info>%s</info>', (string) $alias));
} else {
$options['output']->comment(sprintf('This service is a <comment>private</comment> alias for the service <info>%s</info>', (string) $alias));
}
if (!$builder) {
return;
@@ -364,22 +414,89 @@ class TextDescriptor extends Descriptor
]);
}
/**
* {@inheritdoc}
*/
protected function describeContainerEnvVars(array $envs, array $options = [])
{
$dump = new Dumper($this->output);
$options['output']->title('Symfony Container Environment Variables');
if (null !== $name = $options['name'] ?? null) {
$options['output']->comment('Displaying detailed environment variable usage matching '.$name);
$matches = false;
foreach ($envs as $env) {
if ($name === $env['name'] || false !== stripos($env['name'], $name)) {
$matches = true;
$options['output']->section('%env('.$env['processor'].':'.$env['name'].')%');
$options['output']->table([], [
['<info>Default value</>', $env['default_available'] ? $dump($env['default_value']) : 'n/a'],
['<info>Real value</>', $env['runtime_available'] ? $dump($env['runtime_value']) : 'n/a'],
['<info>Processed value</>', $env['default_available'] || $env['runtime_available'] ? $dump($env['processed_value']) : 'n/a'],
]);
}
}
if (!$matches) {
$options['output']->block('None of the environment variables match this name.');
} else {
$options['output']->comment('Note real values might be different between web and CLI.');
}
return;
}
if (!$envs) {
$options['output']->block('No environment variables are being used.');
return;
}
$rows = [];
$missing = [];
foreach ($envs as $env) {
if (isset($rows[$env['name']])) {
continue;
}
$rows[$env['name']] = [
$env['name'],
$env['default_available'] ? $dump($env['default_value']) : 'n/a',
$env['runtime_available'] ? $dump($env['runtime_value']) : 'n/a',
];
if (!$env['default_available'] && !$env['runtime_available']) {
$missing[$env['name']] = true;
}
}
$options['output']->table(['Name', 'Default value', 'Real value'], $rows);
$options['output']->comment('Note real values might be different between web and CLI.');
if ($missing) {
$options['output']->warning('The following variables are missing:');
$options['output']->listing(array_keys($missing));
}
}
protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = [])
{
$event = \array_key_exists('event', $options) ? $options['event'] : null;
$event = $options['event'] ?? null;
$dispatcherServiceName = $options['dispatcher_service_name'] ?? null;
$title = 'Registered Listeners';
if (null !== $dispatcherServiceName) {
$title .= sprintf(' of Event Dispatcher "%s"', $dispatcherServiceName);
}
if (null !== $event) {
$title = sprintf('Registered Listeners for "%s" Event', $event);
$title .= sprintf(' for "%s" Event', $event);
$registeredListeners = $eventDispatcher->getListeners($event);
} else {
$title = 'Registered Listeners Grouped by Event';
$title .= ' Grouped by Event';
// Try to see if "events" exists
$registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(function ($event) use ($eventDispatcher) { return $eventDispatcher->getListeners($event); }, $options['events'])) : $eventDispatcher->getListeners();
}
$options['output']->title($title);
$registeredListeners = $eventDispatcher->getListeners($event);
if (null !== $event) {
$this->renderEventListenerTable($eventDispatcher, $event, $registeredListeners, $options['output']);
} else {
@@ -391,15 +508,12 @@ class TextDescriptor extends Descriptor
}
}
/**
* {@inheritdoc}
*/
protected function describeCallable($callable, array $options = [])
{
$this->writeText($this->formatCallable($callable), $options);
}
private function renderEventListenerTable(EventDispatcherInterface $eventDispatcher, $event, array $eventListeners, SymfonyStyle $io)
private function renderEventListenerTable(EventDispatcherInterface $eventDispatcher, string $event, array $eventListeners, SymfonyStyle $io)
{
$tableHeaders = ['Order', 'Callable', 'Priority'];
$tableRows = [];
@@ -411,10 +525,7 @@ class TextDescriptor extends Descriptor
$io->table($tableHeaders, $tableRows);
}
/**
* @return string
*/
private function formatRouterConfig(array $config)
private function formatRouterConfig(array $config): string
{
if (empty($config)) {
return 'NONE';
@@ -430,12 +541,61 @@ class TextDescriptor extends Descriptor
return trim($configAsString);
}
/**
* @param callable $callable
*
* @return string
*/
private function formatCallable($callable)
private function formatControllerLink($controller, string $anchorText, callable $getContainer = null): string
{
if (null === $this->fileLinkFormatter) {
return $anchorText;
}
try {
if (null === $controller) {
return $anchorText;
} elseif (\is_array($controller)) {
$r = new \ReflectionMethod($controller[0], $controller[1]);
} elseif ($controller instanceof \Closure) {
$r = new \ReflectionFunction($controller);
} elseif (method_exists($controller, '__invoke')) {
$r = new \ReflectionMethod($controller, '__invoke');
} elseif (!\is_string($controller)) {
return $anchorText;
} elseif (str_contains($controller, '::')) {
$r = new \ReflectionMethod($controller);
} else {
$r = new \ReflectionFunction($controller);
}
} catch (\ReflectionException $e) {
if (\is_array($controller)) {
$controller = implode('::', $controller);
}
$id = $controller;
$method = '__invoke';
if ($pos = strpos($controller, '::')) {
$id = substr($controller, 0, $pos);
$method = substr($controller, $pos + 2);
}
if (!$getContainer || !($container = $getContainer()) || !$container->has($id)) {
return $anchorText;
}
try {
$r = new \ReflectionMethod($container->findDefinition($id)->getClass(), $method);
} catch (\ReflectionException $e) {
return $anchorText;
}
}
$fileLink = $this->fileLinkFormatter->format($r->getFileName(), $r->getStartLine());
if ($fileLink) {
return sprintf('<href=%s>%s</>', $fileLink, $anchorText);
}
return $anchorText;
}
private function formatCallable($callable): string
{
if (\is_array($callable)) {
if (\is_object($callable[0])) {
@@ -451,7 +611,7 @@ class TextDescriptor extends Descriptor
if ($callable instanceof \Closure) {
$r = new \ReflectionFunction($callable);
if (false !== strpos($r->name, '{closure}')) {
if (str_contains($r->name, '{closure}')) {
return 'Closure()';
}
if ($class = $r->getClosureScopeClass()) {
@@ -468,10 +628,7 @@ class TextDescriptor extends Descriptor
throw new \InvalidArgumentException('Callable is not describable.');
}
/**
* @param string $content
*/
private function writeText($content, array $options = [])
private function writeText(string $content, array $options = [])
{
$this->write(
isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content,

View File

@@ -11,9 +11,13 @@
namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
@@ -36,7 +40,7 @@ class XmlDescriptor extends Descriptor
protected function describeRoute(Route $route, array $options = [])
{
$this->writeDocument($this->getRouteDocument($route, isset($options['name']) ? $options['name'] : null));
$this->writeDocument($this->getRouteDocument($route, $options['name'] ?? null));
}
protected function describeContainerParameters(ParameterBag $parameters, array $options = [])
@@ -46,13 +50,10 @@ class XmlDescriptor extends Descriptor
protected function describeContainerTags(ContainerBuilder $builder, array $options = [])
{
$this->writeDocument($this->getContainerTagsDocument($builder, isset($options['show_private']) && $options['show_private']));
$this->writeDocument($this->getContainerTagsDocument($builder, isset($options['show_hidden']) && $options['show_hidden']));
}
/**
* {@inheritdoc}
*/
protected function describeContainerService($service, array $options = [], ContainerBuilder $builder = null)
protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null)
{
if (!isset($options['id'])) {
throw new \InvalidArgumentException('An "id" option must be provided.');
@@ -61,23 +62,20 @@ class XmlDescriptor extends Descriptor
$this->writeDocument($this->getContainerServiceDocument($service, $options['id'], $builder, isset($options['show_arguments']) && $options['show_arguments']));
}
/**
* {@inheritdoc}
*/
protected function describeContainerServices(ContainerBuilder $builder, array $options = [])
{
$this->writeDocument($this->getContainerServicesDocument($builder, isset($options['tag']) ? $options['tag'] : null, isset($options['show_private']) && $options['show_private'], isset($options['show_arguments']) && $options['show_arguments'], isset($options['filter']) ? $options['filter'] : null));
$this->writeDocument($this->getContainerServicesDocument($builder, $options['tag'] ?? null, isset($options['show_hidden']) && $options['show_hidden'], isset($options['show_arguments']) && $options['show_arguments'], $options['filter'] ?? null));
}
protected function describeContainerDefinition(Definition $definition, array $options = [])
{
$this->writeDocument($this->getContainerDefinitionDocument($definition, isset($options['id']) ? $options['id'] : null, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments']));
$this->writeDocument($this->getContainerDefinitionDocument($definition, $options['id'] ?? null, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments']));
}
protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null)
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($dom->importNode($this->getContainerAliasDocument($alias, isset($options['id']) ? $options['id'] : null)->childNodes->item(0), true));
$dom->appendChild($dom->importNode($this->getContainerAliasDocument($alias, $options['id'] ?? null)->childNodes->item(0), true));
if (!$builder) {
$this->writeDocument($dom);
@@ -90,17 +88,11 @@ class XmlDescriptor extends Descriptor
$this->writeDocument($dom);
}
/**
* {@inheritdoc}
*/
protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = [])
{
$this->writeDocument($this->getEventDispatcherListenersDocument($eventDispatcher, \array_key_exists('event', $options) ? $options['event'] : null));
$this->writeDocument($this->getEventDispatcherListenersDocument($eventDispatcher, $options));
}
/**
* {@inheritdoc}
*/
protected function describeCallable($callable, array $options = [])
{
$this->writeDocument($this->getCallableDocument($callable));
@@ -111,19 +103,46 @@ class XmlDescriptor extends Descriptor
$this->writeDocument($this->getContainerParameterDocument($parameter, $options));
}
/**
* Writes DOM document.
*/
protected function describeContainerEnvVars(array $envs, array $options = [])
{
throw new LogicException('Using the XML format to debug environment variables is not supported.');
}
protected function describeContainerDeprecations(ContainerBuilder $builder, array $options = []): void
{
$containerDeprecationFilePath = sprintf('%s/%sDeprecations.log', $builder->getParameter('kernel.build_dir'), $builder->getParameter('kernel.container_class'));
if (!file_exists($containerDeprecationFilePath)) {
throw new RuntimeException('The deprecation file does not exist, please try warming the cache first.');
}
$logs = unserialize(file_get_contents($containerDeprecationFilePath));
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($deprecationsXML = $dom->createElement('deprecations'));
$formattedLogs = [];
$remainingCount = 0;
foreach ($logs as $log) {
$deprecationsXML->appendChild($deprecationXML = $dom->createElement('deprecation'));
$deprecationXML->setAttribute('count', $log['count']);
$deprecationXML->appendChild($dom->createElement('message', $log['message']));
$deprecationXML->appendChild($dom->createElement('file', $log['file']));
$deprecationXML->appendChild($dom->createElement('line', $log['line']));
$remainingCount += $log['count'];
}
$deprecationsXML->setAttribute('remainingCount', $remainingCount);
$this->writeDocument($dom);
}
private function writeDocument(\DOMDocument $dom)
{
$dom->formatOutput = true;
$this->write($dom->saveXML());
}
/**
* @return \DOMDocument
*/
private function getRouteCollectionDocument(RouteCollection $routes)
private function getRouteCollectionDocument(RouteCollection $routes): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($routesXML = $dom->createElement('routes'));
@@ -136,12 +155,7 @@ class XmlDescriptor extends Descriptor
return $dom;
}
/**
* @param string|null $name
*
* @return \DOMDocument
*/
private function getRouteDocument(Route $route, $name = null)
private function getRouteDocument(Route $route, string $name = null): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($routeXML = $dom->createElement('route'));
@@ -201,13 +215,15 @@ class XmlDescriptor extends Descriptor
}
}
if ('' !== $route->getCondition()) {
$routeXML->appendChild($conditionXML = $dom->createElement('condition'));
$conditionXML->appendChild(new \DOMText($route->getCondition()));
}
return $dom;
}
/**
* @return \DOMDocument
*/
private function getContainerParametersDocument(ParameterBag $parameters)
private function getContainerParametersDocument(ParameterBag $parameters): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($parametersXML = $dom->createElement('parameters'));
@@ -221,17 +237,12 @@ class XmlDescriptor extends Descriptor
return $dom;
}
/**
* @param bool $showPrivate
*
* @return \DOMDocument
*/
private function getContainerTagsDocument(ContainerBuilder $builder, $showPrivate = false)
private function getContainerTagsDocument(ContainerBuilder $builder, bool $showHidden = false): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($containerXML = $dom->createElement('container'));
foreach ($this->findDefinitionsByTag($builder, $showPrivate) as $tag => $definitions) {
foreach ($this->findDefinitionsByTag($builder, $showHidden) as $tag => $definitions) {
$containerXML->appendChild($tagXML = $dom->createElement('tag'));
$tagXML->setAttribute('name', $tag);
@@ -244,14 +255,7 @@ class XmlDescriptor extends Descriptor
return $dom;
}
/**
* @param mixed $service
* @param string $id
* @param bool $showArguments
*
* @return \DOMDocument
*/
private function getContainerServiceDocument($service, $id, ContainerBuilder $builder = null, $showArguments = false)
private function getContainerServiceDocument(object $service, string $id, ContainerBuilder $builder = null, bool $showArguments = false): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
@@ -271,29 +275,22 @@ class XmlDescriptor extends Descriptor
return $dom;
}
/**
* @param string|null $tag
* @param bool $showPrivate
* @param bool $showArguments
* @param callable $filter
*
* @return \DOMDocument
*/
private function getContainerServicesDocument(ContainerBuilder $builder, $tag = null, $showPrivate = false, $showArguments = false, $filter = null)
private function getContainerServicesDocument(ContainerBuilder $builder, string $tag = null, bool $showHidden = false, bool $showArguments = false, callable $filter = null): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($containerXML = $dom->createElement('container'));
$serviceIds = $tag ? array_keys($builder->findTaggedServiceIds($tag)) : $builder->getServiceIds();
$serviceIds = $tag
? $this->sortTaggedServicesByPriority($builder->findTaggedServiceIds($tag))
: $this->sortServiceIds($builder->getServiceIds());
if ($filter) {
$serviceIds = array_filter($serviceIds, $filter);
}
foreach ($this->sortServiceIds($serviceIds) as $serviceId) {
foreach ($serviceIds as $serviceId) {
$service = $this->resolveServiceDefinition($builder, $serviceId);
if (($service instanceof Definition || $service instanceof Alias) && !($showPrivate || ($service->isPublic() && !$service->isPrivate()))) {
if ($showHidden xor '.' === ($serviceId[0] ?? null)) {
continue;
}
@@ -304,13 +301,7 @@ class XmlDescriptor extends Descriptor
return $dom;
}
/**
* @param string|null $id
* @param bool $omitTags
*
* @return \DOMDocument
*/
private function getContainerDefinitionDocument(Definition $definition, $id = null, $omitTags = false, $showArguments = false)
private function getContainerDefinitionDocument(Definition $definition, string $id = null, bool $omitTags = false, bool $showArguments = false): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($serviceXML = $dom->createElement('definition'));
@@ -319,7 +310,12 @@ class XmlDescriptor extends Descriptor
$serviceXML->setAttribute('id', $id);
}
$serviceXML->setAttribute('class', $definition->getClass());
if ('' !== $classDescription = $this->getClassDescription((string) $definition->getClass())) {
$serviceXML->appendChild($descriptionXML = $dom->createElement('description'));
$descriptionXML->appendChild($dom->createCDATASection($classDescription));
}
$serviceXML->setAttribute('class', $definition->getClass() ?? '');
if ($factory = $definition->getFactory()) {
$serviceXML->appendChild($factoryXML = $dom->createElement('factory'));
@@ -345,7 +341,7 @@ class XmlDescriptor extends Descriptor
$serviceXML->setAttribute('abstract', $definition->isAbstract() ? 'true' : 'false');
$serviceXML->setAttribute('autowired', $definition->isAutowired() ? 'true' : 'false');
$serviceXML->setAttribute('autoconfigured', $definition->isAutoconfigured() ? 'true' : 'false');
$serviceXML->setAttribute('file', $definition->getFile());
$serviceXML->setAttribute('file', $definition->getFile() ?? '');
$calls = $definition->getMethodCalls();
if (\count($calls) > 0) {
@@ -353,6 +349,9 @@ class XmlDescriptor extends Descriptor
foreach ($calls as $callData) {
$callsXML->appendChild($callXML = $dom->createElement('call'));
$callXML->setAttribute('method', $callData[0]);
if ($callData[2] ?? false) {
$callXML->setAttribute('returns-clone', 'true');
}
}
}
@@ -363,7 +362,7 @@ class XmlDescriptor extends Descriptor
}
if (!$omitTags) {
if ($tags = $definition->getTags()) {
if ($tags = $this->sortTagsByPriority($definition->getTags())) {
$serviceXML->appendChild($tagsXML = $dom->createElement('tags'));
foreach ($tags as $tagName => $tagData) {
foreach ($tagData as $parameters) {
@@ -385,7 +384,7 @@ class XmlDescriptor extends Descriptor
/**
* @return \DOMNode[]
*/
private function getArgumentNodes(array $arguments, \DOMDocument $dom)
private function getArgumentNodes(array $arguments, \DOMDocument $dom): array
{
$nodes = [];
@@ -403,20 +402,26 @@ class XmlDescriptor extends Descriptor
if ($argument instanceof Reference) {
$argumentXML->setAttribute('type', 'service');
$argumentXML->setAttribute('id', (string) $argument);
} elseif ($argument instanceof IteratorArgument) {
$argumentXML->setAttribute('type', 'iterator');
} elseif ($argument instanceof IteratorArgument || $argument instanceof ServiceLocatorArgument) {
$argumentXML->setAttribute('type', $argument instanceof IteratorArgument ? 'iterator' : 'service_locator');
foreach ($this->getArgumentNodes($argument->getValues(), $dom) as $childArgumentXML) {
$argumentXML->appendChild($childArgumentXML);
}
} elseif ($argument instanceof Definition) {
$argumentXML->appendChild($dom->importNode($this->getContainerDefinitionDocument($argument, null, false, true)->childNodes->item(0), true));
} elseif ($argument instanceof AbstractArgument) {
$argumentXML->setAttribute('type', 'abstract');
$argumentXML->appendChild(new \DOMText($argument->getText()));
} elseif (\is_array($argument)) {
$argumentXML->setAttribute('type', 'collection');
foreach ($this->getArgumentNodes($argument, $dom) as $childArgumentXML) {
$argumentXML->appendChild($childArgumentXML);
}
} elseif ($argument instanceof \UnitEnum) {
$argumentXML->setAttribute('type', 'constant');
$argumentXML->appendChild(new \DOMText(ltrim(var_export($argument, true), '\\')));
} else {
$argumentXML->appendChild(new \DOMText($argument));
}
@@ -427,12 +432,7 @@ class XmlDescriptor extends Descriptor
return $nodes;
}
/**
* @param string|null $id
*
* @return \DOMDocument
*/
private function getContainerAliasDocument(Alias $alias, $id = null)
private function getContainerAliasDocument(Alias $alias, string $id = null): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($aliasXML = $dom->createElement('alias'));
@@ -447,10 +447,7 @@ class XmlDescriptor extends Descriptor
return $dom;
}
/**
* @return \DOMDocument
*/
private function getContainerParameterDocument($parameter, $options = [])
private function getContainerParameterDocument($parameter, array $options = []): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($parameterXML = $dom->createElement('parameter'));
@@ -464,20 +461,18 @@ class XmlDescriptor extends Descriptor
return $dom;
}
/**
* @param string|null $event
*
* @return \DOMDocument
*/
private function getEventDispatcherListenersDocument(EventDispatcherInterface $eventDispatcher, $event = null)
private function getEventDispatcherListenersDocument(EventDispatcherInterface $eventDispatcher, array $options): \DOMDocument
{
$event = \array_key_exists('event', $options) ? $options['event'] : null;
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($eventDispatcherXML = $dom->createElement('event-dispatcher'));
$registeredListeners = $eventDispatcher->getListeners($event);
if (null !== $event) {
$registeredListeners = $eventDispatcher->getListeners($event);
$this->appendEventListenerDocument($eventDispatcher, $event, $eventDispatcherXML, $registeredListeners);
} else {
// Try to see if "events" exists
$registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(function ($event) use ($eventDispatcher) { return $eventDispatcher->getListeners($event); }, $options['events'])) : $eventDispatcher->getListeners();
ksort($registeredListeners);
foreach ($registeredListeners as $eventListened => $eventListeners) {
@@ -491,7 +486,7 @@ class XmlDescriptor extends Descriptor
return $dom;
}
private function appendEventListenerDocument(EventDispatcherInterface $eventDispatcher, $event, \DOMElement $element, array $eventListeners)
private function appendEventListenerDocument(EventDispatcherInterface $eventDispatcher, string $event, \DOMElement $element, array $eventListeners)
{
foreach ($eventListeners as $listener) {
$callableXML = $this->getCallableDocument($listener);
@@ -501,12 +496,7 @@ class XmlDescriptor extends Descriptor
}
}
/**
* @param callable $callable
*
* @return \DOMDocument
*/
private function getCallableDocument($callable)
private function getCallableDocument($callable): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($callableXML = $dom->createElement('callable'));
@@ -518,7 +508,7 @@ class XmlDescriptor extends Descriptor
$callableXML->setAttribute('name', $callable[1]);
$callableXML->setAttribute('class', \get_class($callable[0]));
} else {
if (0 !== strpos($callable[1], 'parent::')) {
if (!str_starts_with($callable[1], 'parent::')) {
$callableXML->setAttribute('name', $callable[1]);
$callableXML->setAttribute('class', $callable[0]);
$callableXML->setAttribute('static', 'true');
@@ -536,7 +526,7 @@ class XmlDescriptor extends Descriptor
if (\is_string($callable)) {
$callableXML->setAttribute('type', 'function');
if (false === strpos($callable, '::')) {
if (!str_contains($callable, '::')) {
$callableXML->setAttribute('name', $callable);
} else {
$callableParts = explode('::', $callable);
@@ -553,7 +543,7 @@ class XmlDescriptor extends Descriptor
$callableXML->setAttribute('type', 'closure');
$r = new \ReflectionFunction($callable);
if (false !== strpos($r->name, '{closure}')) {
if (str_contains($r->name, '{closure}')) {
return $dom;
}
$callableXML->setAttribute('name', $r->name);

View File

@@ -16,6 +16,7 @@ use Symfony\Bundle\FrameworkBundle\Console\Descriptor\MarkdownDescriptor;
use Symfony\Bundle\FrameworkBundle\Console\Descriptor\TextDescriptor;
use Symfony\Bundle\FrameworkBundle\Console\Descriptor\XmlDescriptor;
use Symfony\Component\Console\Helper\DescriptorHelper as BaseDescriptorHelper;
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
/**
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
@@ -24,10 +25,10 @@ use Symfony\Component\Console\Helper\DescriptorHelper as BaseDescriptorHelper;
*/
class DescriptorHelper extends BaseDescriptorHelper
{
public function __construct()
public function __construct(FileLinkFormatter $fileLinkFormatter = null)
{
$this
->register('txt', new TextDescriptor())
->register('txt', new TextDescriptor($fileLinkFormatter))
->register('xml', new XmlDescriptor())
->register('json', new JsonDescriptor())
->register('md', new MarkdownDescriptor())

View File

@@ -11,41 +11,60 @@
namespace Symfony\Bundle\FrameworkBundle\Controller;
use Doctrine\Common\Persistence\ManagerRegistry as LegacyManagerRegistry;
use Doctrine\Persistence\ManagerRegistry;
use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
use Psr\Link\LinkInterface;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\WebLink\EventListener\AddLinkHeaderListener;
use Symfony\Component\WebLink\GenericLinkProvider;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
use Twig\Environment;
/**
* Provides common features needed in controllers.
* Provides shortcuts for HTTP-related features in controllers.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class AbstractController implements ServiceSubscriberInterface
{
use ControllerTrait;
/**
* @var ContainerInterface
*/
protected $container;
/**
* @internal
* @required
*/
public function setContainer(ContainerInterface $container)
public function setContainer(ContainerInterface $container): ?ContainerInterface
{
$previous = $this->container;
$this->container = $container;
@@ -53,6 +72,20 @@ abstract class AbstractController implements ServiceSubscriberInterface
return $previous;
}
/**
* Gets a container parameter by its name.
*
* @return array|bool|float|int|string|\UnitEnum|null
*/
protected function getParameter(string $name)
{
if (!$this->container->has('parameter_bag')) {
throw new ServiceNotFoundException('parameter_bag.', null, null, [], sprintf('The "%s::getParameter()" method is missing a parameter bag to work properly. Did you forget to register your controller as a service subscriber? This can be fixed either by using autoconfiguration or by manually wiring a "parameter_bag" in the service locator passed to the controller.', static::class));
}
return $this->container->get('parameter_bag')->get($name);
}
public static function getSubscribedServices()
{
return [
@@ -62,12 +95,382 @@ abstract class AbstractController implements ServiceSubscriberInterface
'serializer' => '?'.SerializerInterface::class,
'session' => '?'.SessionInterface::class,
'security.authorization_checker' => '?'.AuthorizationCheckerInterface::class,
'templating' => '?'.EngineInterface::class,
'twig' => '?'.Environment::class,
'doctrine' => '?'.(interface_exists(ManagerRegistry::class) ? ManagerRegistry::class : LegacyManagerRegistry::class),
'doctrine' => '?'.ManagerRegistry::class, // to be removed in 6.0
'form.factory' => '?'.FormFactoryInterface::class,
'security.token_storage' => '?'.TokenStorageInterface::class,
'security.csrf.token_manager' => '?'.CsrfTokenManagerInterface::class,
'parameter_bag' => '?'.ContainerBagInterface::class,
'message_bus' => '?'.MessageBusInterface::class, // to be removed in 6.0
'messenger.default_bus' => '?'.MessageBusInterface::class, // to be removed in 6.0
];
}
/**
* Returns true if the service id is defined.
*
* @deprecated since Symfony 5.4, use method or constructor injection in your controller instead
*/
protected function has(string $id): bool
{
trigger_deprecation('symfony/framework-bundle', '5.4', 'Method "%s()" is deprecated, use method or constructor injection in your controller instead.', __METHOD__);
return $this->container->has($id);
}
/**
* Gets a container service by its id.
*
* @return object The service
*
* @deprecated since Symfony 5.4, use method or constructor injection in your controller instead
*/
protected function get(string $id): object
{
trigger_deprecation('symfony/framework-bundle', '5.4', 'Method "%s()" is deprecated, use method or constructor injection in your controller instead.', __METHOD__);
return $this->container->get($id);
}
/**
* Generates a URL from the given parameters.
*
* @see UrlGeneratorInterface
*/
protected function generateUrl(string $route, array $parameters = [], int $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH): string
{
return $this->container->get('router')->generate($route, $parameters, $referenceType);
}
/**
* Forwards the request to another controller.
*
* @param string $controller The controller name (a string like Bundle\BlogBundle\Controller\PostController::indexAction)
*/
protected function forward(string $controller, array $path = [], array $query = []): Response
{
$request = $this->container->get('request_stack')->getCurrentRequest();
$path['_controller'] = $controller;
$subRequest = $request->duplicate($query, null, $path);
return $this->container->get('http_kernel')->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
}
/**
* Returns a RedirectResponse to the given URL.
*/
protected function redirect(string $url, int $status = 302): RedirectResponse
{
return new RedirectResponse($url, $status);
}
/**
* Returns a RedirectResponse to the given route with the given parameters.
*/
protected function redirectToRoute(string $route, array $parameters = [], int $status = 302): RedirectResponse
{
return $this->redirect($this->generateUrl($route, $parameters), $status);
}
/**
* Returns a JsonResponse that uses the serializer component if enabled, or json_encode.
*/
protected function json($data, int $status = 200, array $headers = [], array $context = []): JsonResponse
{
if ($this->container->has('serializer')) {
$json = $this->container->get('serializer')->serialize($data, 'json', array_merge([
'json_encode_options' => JsonResponse::DEFAULT_ENCODING_OPTIONS,
], $context));
return new JsonResponse($json, $status, $headers, true);
}
return new JsonResponse($data, $status, $headers);
}
/**
* Returns a BinaryFileResponse object with original or customized file name and disposition header.
*
* @param \SplFileInfo|string $file File object or path to file to be sent as response
*/
protected function file($file, string $fileName = null, string $disposition = ResponseHeaderBag::DISPOSITION_ATTACHMENT): BinaryFileResponse
{
$response = new BinaryFileResponse($file);
$response->setContentDisposition($disposition, null === $fileName ? $response->getFile()->getFilename() : $fileName);
return $response;
}
/**
* Adds a flash message to the current session for type.
*
* @throws \LogicException
*/
protected function addFlash(string $type, $message): void
{
try {
$this->container->get('request_stack')->getSession()->getFlashBag()->add($type, $message);
} catch (SessionNotFoundException $e) {
throw new \LogicException('You cannot use the addFlash method if sessions are disabled. Enable them in "config/packages/framework.yaml".', 0, $e);
}
}
/**
* Checks if the attribute is granted against the current authentication token and optionally supplied subject.
*
* @throws \LogicException
*/
protected function isGranted($attribute, $subject = null): bool
{
if (!$this->container->has('security.authorization_checker')) {
throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".');
}
return $this->container->get('security.authorization_checker')->isGranted($attribute, $subject);
}
/**
* Throws an exception unless the attribute is granted against the current authentication token and optionally
* supplied subject.
*
* @throws AccessDeniedException
*/
protected function denyAccessUnlessGranted($attribute, $subject = null, string $message = 'Access Denied.'): void
{
if (!$this->isGranted($attribute, $subject)) {
$exception = $this->createAccessDeniedException($message);
$exception->setAttributes($attribute);
$exception->setSubject($subject);
throw $exception;
}
}
/**
* Returns a rendered view.
*/
protected function renderView(string $view, array $parameters = []): string
{
if (!$this->container->has('twig')) {
throw new \LogicException('You cannot use the "renderView" method if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".');
}
return $this->container->get('twig')->render($view, $parameters);
}
/**
* Renders a view.
*/
protected function render(string $view, array $parameters = [], Response $response = null): Response
{
$content = $this->renderView($view, $parameters);
if (null === $response) {
$response = new Response();
}
$response->setContent($content);
return $response;
}
/**
* Renders a view and sets the appropriate status code when a form is listed in parameters.
*
* If an invalid form is found in the list of parameters, a 422 status code is returned.
*/
protected function renderForm(string $view, array $parameters = [], Response $response = null): Response
{
if (null === $response) {
$response = new Response();
}
foreach ($parameters as $k => $v) {
if ($v instanceof FormView) {
throw new \LogicException(sprintf('Passing a FormView to "%s::renderForm()" is not supported, pass directly the form instead for parameter "%s".', get_debug_type($this), $k));
}
if (!$v instanceof FormInterface) {
continue;
}
$parameters[$k] = $v->createView();
if (200 === $response->getStatusCode() && $v->isSubmitted() && !$v->isValid()) {
$response->setStatusCode(422);
}
}
return $this->render($view, $parameters, $response);
}
/**
* Streams a view.
*/
protected function stream(string $view, array $parameters = [], StreamedResponse $response = null): StreamedResponse
{
if (!$this->container->has('twig')) {
throw new \LogicException('You cannot use the "stream" method if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".');
}
$twig = $this->container->get('twig');
$callback = function () use ($twig, $view, $parameters) {
$twig->display($view, $parameters);
};
if (null === $response) {
return new StreamedResponse($callback);
}
$response->setCallback($callback);
return $response;
}
/**
* Returns a NotFoundHttpException.
*
* This will result in a 404 response code. Usage example:
*
* throw $this->createNotFoundException('Page not found!');
*/
protected function createNotFoundException(string $message = 'Not Found', \Throwable $previous = null): NotFoundHttpException
{
return new NotFoundHttpException($message, $previous);
}
/**
* Returns an AccessDeniedException.
*
* This will result in a 403 response code. Usage example:
*
* throw $this->createAccessDeniedException('Unable to access this page!');
*
* @throws \LogicException If the Security component is not available
*/
protected function createAccessDeniedException(string $message = 'Access Denied.', \Throwable $previous = null): AccessDeniedException
{
if (!class_exists(AccessDeniedException::class)) {
throw new \LogicException('You cannot use the "createAccessDeniedException" method if the Security component is not available. Try running "composer require symfony/security-bundle".');
}
return new AccessDeniedException($message, $previous);
}
/**
* Creates and returns a Form instance from the type of the form.
*/
protected function createForm(string $type, $data = null, array $options = []): FormInterface
{
return $this->container->get('form.factory')->create($type, $data, $options);
}
/**
* Creates and returns a form builder instance.
*/
protected function createFormBuilder($data = null, array $options = []): FormBuilderInterface
{
return $this->container->get('form.factory')->createBuilder(FormType::class, $data, $options);
}
/**
* Shortcut to return the Doctrine Registry service.
*
* @throws \LogicException If DoctrineBundle is not available
*
* @deprecated since Symfony 5.4, inject an instance of ManagerRegistry in your controller instead
*/
protected function getDoctrine(): ManagerRegistry
{
trigger_deprecation('symfony/framework-bundle', '5.4', 'Method "%s()" is deprecated, inject an instance of ManagerRegistry in your controller instead.', __METHOD__);
if (!$this->container->has('doctrine')) {
throw new \LogicException('The DoctrineBundle is not registered in your application. Try running "composer require symfony/orm-pack".');
}
return $this->container->get('doctrine');
}
/**
* Get a user from the Security Token Storage.
*
* @return UserInterface|null
*
* @throws \LogicException If SecurityBundle is not available
*
* @see TokenInterface::getUser()
*/
protected function getUser()
{
if (!$this->container->has('security.token_storage')) {
throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".');
}
if (null === $token = $this->container->get('security.token_storage')->getToken()) {
return null;
}
// @deprecated since 5.4, $user will always be a UserInterface instance
if (!\is_object($user = $token->getUser())) {
// e.g. anonymous authentication
return null;
}
return $user;
}
/**
* Checks the validity of a CSRF token.
*
* @param string $id The id used when generating the token
* @param string|null $token The actual token sent with the request that should be validated
*/
protected function isCsrfTokenValid(string $id, ?string $token): bool
{
if (!$this->container->has('security.csrf.token_manager')) {
throw new \LogicException('CSRF protection is not enabled in your application. Enable it with the "csrf_protection" key in "config/packages/framework.yaml".');
}
return $this->container->get('security.csrf.token_manager')->isTokenValid(new CsrfToken($id, $token));
}
/**
* Dispatches a message to the bus.
*
* @param object|Envelope $message The message or the message pre-wrapped in an envelope
*
* @deprecated since Symfony 5.4, inject an instance of MessageBusInterface in your controller instead
*/
protected function dispatchMessage(object $message, array $stamps = []): Envelope
{
trigger_deprecation('symfony/framework-bundle', '5.4', 'Method "%s()" is deprecated, inject an instance of MessageBusInterface in your controller instead.', __METHOD__);
if (!$this->container->has('messenger.default_bus')) {
$message = class_exists(Envelope::class) ? 'You need to define the "messenger.default_bus" configuration option.' : 'Try running "composer require symfony/messenger".';
throw new \LogicException('The message bus is not enabled in your application. '.$message);
}
return $this->container->get('messenger.default_bus')->dispatch($message, $stamps);
}
/**
* Adds a Link HTTP header to the current response.
*
* @see https://tools.ietf.org/html/rfc5988
*/
protected function addLink(Request $request, LinkInterface $link): void
{
if (!class_exists(AddLinkHeaderListener::class)) {
throw new \LogicException('You cannot use the "addLink" method if the WebLink component is not available. Try running "composer require symfony/web-link".');
}
if (null === $linkProvider = $request->attributes->get('_links')) {
$request->attributes->set('_links', new GenericLinkProvider([$link]));
return;
}
$request->attributes->set('_links', $linkProvider->withLink($link));
}
}

View File

@@ -1,42 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Controller;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
/**
* Controller is a simple implementation of a Controller.
*
* It provides methods to common features needed in controllers.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class Controller implements ContainerAwareInterface
{
use ContainerAwareTrait;
use ControllerTrait;
/**
* Gets a container configuration parameter by its name.
*
* @param string $name The parameter name
*
* @return mixed
*
* @final since version 3.4
*/
protected function getParameter($name)
{
return $this->container->getParameter($name);
}
}

View File

@@ -1,152 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Controller;
use Symfony\Component\HttpKernel\KernelInterface;
/**
* ControllerNameParser converts controller from the short notation a:b:c
* (BlogBundle:Post:index) to a fully-qualified class::method string
* (Bundle\BlogBundle\Controller\PostController::indexAction).
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ControllerNameParser
{
protected $kernel;
public function __construct(KernelInterface $kernel)
{
$this->kernel = $kernel;
}
/**
* Converts a short notation a:b:c to a class::method.
*
* @param string $controller A short notation controller (a:b:c)
*
* @return string A string in the class::method notation
*
* @throws \InvalidArgumentException when the specified bundle is not enabled
* or the controller cannot be found
*/
public function parse($controller)
{
$parts = explode(':', $controller);
if (3 !== \count($parts) || \in_array('', $parts, true)) {
throw new \InvalidArgumentException(sprintf('The "%s" controller is not a valid "a:b:c" controller string.', $controller));
}
$originalController = $controller;
list($bundle, $controller, $action) = $parts;
$controller = str_replace('/', '\\', $controller);
$bundles = [];
try {
// this throws an exception if there is no such bundle
$allBundles = $this->kernel->getBundle($bundle, false, true);
} catch (\InvalidArgumentException $e) {
$message = sprintf(
'The "%s" (from the _controller value "%s") does not exist or is not enabled in your kernel!',
$bundle,
$originalController
);
if ($alternative = $this->findAlternative($bundle)) {
$message .= sprintf(' Did you mean "%s:%s:%s"?', $alternative, $controller, $action);
}
throw new \InvalidArgumentException($message, 0, $e);
}
if (!\is_array($allBundles)) {
// happens when HttpKernel is version 4+
$allBundles = [$allBundles];
}
foreach ($allBundles as $b) {
$try = $b->getNamespace().'\\Controller\\'.$controller.'Controller';
if (class_exists($try)) {
return $try.'::'.$action.'Action';
}
$bundles[] = $b->getName();
$msg = sprintf('The _controller value "%s:%s:%s" maps to a "%s" class, but this class was not found. Create this class or check the spelling of the class and its namespace.', $bundle, $controller, $action, $try);
}
if (\count($bundles) > 1) {
$msg = sprintf('Unable to find controller "%s:%s" in bundles %s.', $bundle, $controller, implode(', ', $bundles));
}
throw new \InvalidArgumentException($msg);
}
/**
* Converts a class::method notation to a short one (a:b:c).
*
* @param string $controller A string in the class::method notation
*
* @return string A short notation controller (a:b:c)
*
* @throws \InvalidArgumentException when the controller is not valid or cannot be found in any bundle
*/
public function build($controller)
{
if (0 === preg_match('#^(.*?\\\\Controller\\\\(.+)Controller)::(.+)Action$#', $controller, $match)) {
throw new \InvalidArgumentException(sprintf('The "%s" controller is not a valid "class::method" string.', $controller));
}
$className = $match[1];
$controllerName = $match[2];
$actionName = $match[3];
foreach ($this->kernel->getBundles() as $name => $bundle) {
if (0 !== strpos($className, $bundle->getNamespace())) {
continue;
}
return sprintf('%s:%s:%s', $name, $controllerName, $actionName);
}
throw new \InvalidArgumentException(sprintf('Unable to find a bundle that defines controller "%s".', $controller));
}
/**
* Attempts to find a bundle that is *similar* to the given bundle name.
*
* @param string $nonExistentBundleName
*
* @return string
*/
private function findAlternative($nonExistentBundleName)
{
$bundleNames = array_map(function ($b) {
return $b->getName();
}, $this->kernel->getBundles());
$alternative = null;
$shortest = null;
foreach ($bundleNames as $bundleName) {
// if there's a partial match, return it immediately
if (false !== strpos($bundleName, $nonExistentBundleName)) {
return $bundleName;
}
$lev = levenshtein($nonExistentBundleName, $bundleName);
if ($lev <= \strlen($nonExistentBundleName) / 3 && (null === $alternative || $lev < $shortest)) {
$alternative = $bundleName;
$shortest = $lev;
}
}
return $alternative;
}
}

View File

@@ -11,66 +11,32 @@
namespace Symfony\Bundle\FrameworkBundle\Controller;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Controller\ContainerControllerResolver;
/**
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class ControllerResolver extends ContainerControllerResolver
{
protected $parser;
public function __construct(ContainerInterface $container, ControllerNameParser $parser, LoggerInterface $logger = null)
{
$this->parser = $parser;
parent::__construct($container, $logger);
}
/**
* {@inheritdoc}
*/
protected function createController($controller)
protected function instantiateController(string $class): object
{
if (false === strpos($controller, '::') && 2 === substr_count($controller, ':')) {
// controller in the a:b:c notation then
$controller = $this->parser->parse($controller);
}
$controller = parent::instantiateController($class);
$resolvedController = parent::createController($controller);
if (1 === substr_count($controller, ':') && \is_array($resolvedController)) {
$resolvedController[0] = $this->configureController($resolvedController[0]);
}
return $resolvedController;
}
/**
* {@inheritdoc}
*/
protected function instantiateController($class)
{
return $this->configureController(parent::instantiateController($class));
}
private function configureController($controller)
{
if ($controller instanceof ContainerAwareInterface) {
// @deprecated switch, to be removed in 4.0 where these classes
// won't implement ContainerAwareInterface anymore
switch (\get_class($controller)) {
case RedirectController::class:
case TemplateController::class:
return $controller;
}
$controller->setContainer($this->container);
}
if ($controller instanceof AbstractController && null !== $previousContainer = $controller->setContainer($this->container)) {
$controller->setContainer($previousContainer);
if ($controller instanceof AbstractController) {
if (null === $previousContainer = $controller->setContainer($this->container)) {
throw new \LogicException(sprintf('"%s" has no container set, did you forget to define it as a service subscriber?', $class));
} else {
$controller->setContainer($previousContainer);
}
}
return $controller;

View File

@@ -1,480 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Controller;
use Doctrine\Common\Persistence\ManagerRegistry as LegacyManagerRegistry;
use Doctrine\Persistence\ManagerRegistry;
use Psr\Container\ContainerInterface;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
/**
* Common features needed in controllers.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @internal
*
* @property ContainerInterface $container
*/
trait ControllerTrait
{
/**
* Returns true if the service id is defined.
*
* @param string $id The service id
*
* @return bool true if the service id is defined, false otherwise
*
* @final since version 3.4
*/
protected function has($id)
{
return $this->container->has($id);
}
/**
* Gets a container service by its id.
*
* @param string $id The service id
*
* @return object The service
*
* @final since version 3.4
*/
protected function get($id)
{
return $this->container->get($id);
}
/**
* Generates a URL from the given parameters.
*
* @param string $route The name of the route
* @param array $parameters An array of parameters
* @param int $referenceType The type of reference (one of the constants in UrlGeneratorInterface)
*
* @return string The generated URL
*
* @see UrlGeneratorInterface
*
* @final since version 3.4
*/
protected function generateUrl($route, $parameters = [], $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH)
{
return $this->container->get('router')->generate($route, $parameters, $referenceType);
}
/**
* Forwards the request to another controller.
*
* @param string $controller The controller name (a string like BlogBundle:Post:index)
* @param array $path An array of path parameters
* @param array $query An array of query parameters
*
* @return Response A Response instance
*
* @final since version 3.4
*/
protected function forward($controller, array $path = [], array $query = [])
{
$request = $this->container->get('request_stack')->getCurrentRequest();
$path['_forwarded'] = $request->attributes;
$path['_controller'] = $controller;
$subRequest = $request->duplicate($query, null, $path);
return $this->container->get('http_kernel')->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
}
/**
* Returns a RedirectResponse to the given URL.
*
* @param string $url The URL to redirect to
* @param int $status The status code to use for the Response
*
* @return RedirectResponse
*
* @final since version 3.4
*/
protected function redirect($url, $status = 302)
{
return new RedirectResponse($url, $status);
}
/**
* Returns a RedirectResponse to the given route with the given parameters.
*
* @param string $route The name of the route
* @param array $parameters An array of parameters
* @param int $status The status code to use for the Response
*
* @return RedirectResponse
*
* @final since version 3.4
*/
protected function redirectToRoute($route, array $parameters = [], $status = 302)
{
return $this->redirect($this->generateUrl($route, $parameters), $status);
}
/**
* Returns a JsonResponse that uses the serializer component if enabled, or json_encode.
*
* @param mixed $data The response data
* @param int $status The status code to use for the Response
* @param array $headers Array of extra headers to add
* @param array $context Context to pass to serializer when using serializer component
*
* @return JsonResponse
*
* @final since version 3.4
*/
protected function json($data, $status = 200, $headers = [], $context = [])
{
if ($this->container->has('serializer')) {
$json = $this->container->get('serializer')->serialize($data, 'json', array_merge([
'json_encode_options' => JsonResponse::DEFAULT_ENCODING_OPTIONS,
], $context));
return new JsonResponse($json, $status, $headers, true);
}
return new JsonResponse($data, $status, $headers);
}
/**
* Returns a BinaryFileResponse object with original or customized file name and disposition header.
*
* @param \SplFileInfo|string $file File object or path to file to be sent as response
* @param string|null $fileName File name to be sent to response or null (will use original file name)
* @param string $disposition Disposition of response ("attachment" is default, other type is "inline")
*
* @return BinaryFileResponse
*
* @final since version 3.4
*/
protected function file($file, $fileName = null, $disposition = ResponseHeaderBag::DISPOSITION_ATTACHMENT)
{
$response = new BinaryFileResponse($file);
$response->setContentDisposition($disposition, null === $fileName ? $response->getFile()->getFilename() : $fileName);
return $response;
}
/**
* Adds a flash message to the current session for type.
*
* @param string $type The type
* @param mixed $message The message
*
* @throws \LogicException
*
* @final since version 3.4
*/
protected function addFlash($type, $message)
{
if (!$this->container->has('session')) {
throw new \LogicException('You can not use the addFlash method if sessions are disabled. Enable them in "config/packages/framework.yaml".');
}
$this->container->get('session')->getFlashBag()->add($type, $message);
}
/**
* Checks if the attributes are granted against the current authentication token and optionally supplied subject.
*
* @param mixed $attributes The attributes
* @param mixed $subject The subject
*
* @return bool
*
* @throws \LogicException
*
* @final since version 3.4
*/
protected function isGranted($attributes, $subject = null)
{
if (!$this->container->has('security.authorization_checker')) {
throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".');
}
return $this->container->get('security.authorization_checker')->isGranted($attributes, $subject);
}
/**
* Throws an exception unless the attributes are granted against the current authentication token and optionally
* supplied subject.
*
* @param mixed $attributes The attributes
* @param mixed $subject The subject
* @param string $message The message passed to the exception
*
* @throws AccessDeniedException
*
* @final since version 3.4
*/
protected function denyAccessUnlessGranted($attributes, $subject = null, $message = 'Access Denied.')
{
if (!$this->isGranted($attributes, $subject)) {
$exception = $this->createAccessDeniedException($message);
$exception->setAttributes($attributes);
$exception->setSubject($subject);
throw $exception;
}
}
/**
* Returns a rendered view.
*
* @param string $view The view name
* @param array $parameters An array of parameters to pass to the view
*
* @return string The rendered view
*
* @final since version 3.4
*/
protected function renderView($view, array $parameters = [])
{
if ($this->container->has('templating')) {
return $this->container->get('templating')->render($view, $parameters);
}
if (!$this->container->has('twig')) {
throw new \LogicException('You can not use the "renderView" method if the Templating Component or the Twig Bundle are not available. Try running "composer require symfony/twig-bundle".');
}
return $this->container->get('twig')->render($view, $parameters);
}
/**
* Renders a view.
*
* @param string $view The view name
* @param array $parameters An array of parameters to pass to the view
* @param Response $response A response instance
*
* @return Response A Response instance
*
* @final since version 3.4
*/
protected function render($view, array $parameters = [], Response $response = null)
{
if ($this->container->has('templating')) {
$content = $this->container->get('templating')->render($view, $parameters);
} elseif ($this->container->has('twig')) {
$content = $this->container->get('twig')->render($view, $parameters);
} else {
throw new \LogicException('You can not use the "render" method if the Templating Component or the Twig Bundle are not available. Try running "composer require symfony/twig-bundle".');
}
if (null === $response) {
$response = new Response();
}
$response->setContent($content);
return $response;
}
/**
* Streams a view.
*
* @param string $view The view name
* @param array $parameters An array of parameters to pass to the view
* @param StreamedResponse $response A response instance
*
* @return StreamedResponse A StreamedResponse instance
*
* @final since version 3.4
*/
protected function stream($view, array $parameters = [], StreamedResponse $response = null)
{
if ($this->container->has('templating')) {
$templating = $this->container->get('templating');
$callback = function () use ($templating, $view, $parameters) {
$templating->stream($view, $parameters);
};
} elseif ($this->container->has('twig')) {
$twig = $this->container->get('twig');
$callback = function () use ($twig, $view, $parameters) {
$twig->display($view, $parameters);
};
} else {
throw new \LogicException('You can not use the "stream" method if the Templating Component or the Twig Bundle are not available. Try running "composer require symfony/twig-bundle".');
}
if (null === $response) {
return new StreamedResponse($callback);
}
$response->setCallback($callback);
return $response;
}
/**
* Returns a NotFoundHttpException.
*
* This will result in a 404 response code. Usage example:
*
* throw $this->createNotFoundException('Page not found!');
*
* @param string $message A message
* @param \Exception|null $previous The previous exception
*
* @return NotFoundHttpException
*
* @final since version 3.4
*/
protected function createNotFoundException($message = 'Not Found', \Exception $previous = null)
{
return new NotFoundHttpException($message, $previous);
}
/**
* Returns an AccessDeniedException.
*
* This will result in a 403 response code. Usage example:
*
* throw $this->createAccessDeniedException('Unable to access this page!');
*
* @param string $message A message
* @param \Exception|null $previous The previous exception
*
* @return AccessDeniedException
*
* @throws \LogicException If the Security component is not available
*
* @final since version 3.4
*/
protected function createAccessDeniedException($message = 'Access Denied.', \Exception $previous = null)
{
if (!class_exists(AccessDeniedException::class)) {
throw new \LogicException('You can not use the "createAccessDeniedException" method if the Security component is not available. Try running "composer require symfony/security-bundle".');
}
return new AccessDeniedException($message, $previous);
}
/**
* Creates and returns a Form instance from the type of the form.
*
* @param string $type The fully qualified class name of the form type
* @param mixed $data The initial data for the form
* @param array $options Options for the form
*
* @return FormInterface
*
* @final since version 3.4
*/
protected function createForm($type, $data = null, array $options = [])
{
return $this->container->get('form.factory')->create($type, $data, $options);
}
/**
* Creates and returns a form builder instance.
*
* @param mixed $data The initial data for the form
* @param array $options Options for the form
*
* @return FormBuilderInterface
*
* @final since version 3.4
*/
protected function createFormBuilder($data = null, array $options = [])
{
return $this->container->get('form.factory')->createBuilder(FormType::class, $data, $options);
}
/**
* Shortcut to return the Doctrine Registry service.
*
* @return ManagerRegistry|LegacyManagerRegistry
*
* @throws \LogicException If DoctrineBundle is not available
*
* @final since version 3.4
*/
protected function getDoctrine()
{
if (!$this->container->has('doctrine')) {
throw new \LogicException('The DoctrineBundle is not registered in your application. Try running "composer require symfony/orm-pack".');
}
return $this->container->get('doctrine');
}
/**
* Get a user from the Security Token Storage.
*
* @return UserInterface|object|null
*
* @throws \LogicException If SecurityBundle is not available
*
* @see TokenInterface::getUser()
*
* @final since version 3.4
*/
protected function getUser()
{
if (!$this->container->has('security.token_storage')) {
throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".');
}
if (null === $token = $this->container->get('security.token_storage')->getToken()) {
return null;
}
if (!\is_object($user = $token->getUser())) {
// e.g. anonymous authentication
return null;
}
return $user;
}
/**
* Checks the validity of a CSRF token.
*
* @param string $id The id used when generating the token
* @param string $token The actual token sent with the request that should be validated
*
* @return bool
*
* @final since version 3.4
*/
protected function isCsrfTokenValid($id, $token)
{
if (!$this->container->has('security.csrf.token_manager')) {
throw new \LogicException('CSRF protection is not enabled in your application. Enable it with the "csrf_protection" key in "config/packages/framework.yaml".');
}
return $this->container->get('security.csrf.token_manager')->isTokenValid(new CsrfToken($id, $token));
}
}

View File

@@ -11,8 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\Controller;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\HeaderUtils;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -24,37 +23,21 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final since version 3.4
* @final
*/
class RedirectController implements ContainerAwareInterface
class RedirectController
{
/**
* @deprecated since version 3.4, to be removed in 4.0
*/
protected $container;
private $router;
private $httpPort;
private $httpsPort;
public function __construct(UrlGeneratorInterface $router = null, $httpPort = null, $httpsPort = null)
public function __construct(UrlGeneratorInterface $router = null, int $httpPort = null, int $httpsPort = null)
{
$this->router = $router;
$this->httpPort = $httpPort;
$this->httpsPort = $httpsPort;
}
/**
* @deprecated since version 3.4, to be removed in 4.0 alongside with the ContainerAwareInterface type.
*/
public function setContainer(ContainerInterface $container = null)
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.4 and will be removed in 4.0. Inject an UrlGeneratorInterface using the constructor instead.', __METHOD__), \E_USER_DEPRECATED);
$this->container = $container;
$this->router = $container->get('router');
}
/**
* Redirects to another route with the given name.
*
@@ -64,16 +47,14 @@ class RedirectController implements ContainerAwareInterface
* In case the route name is empty, the status code will be 404 when permanent is false
* and 410 otherwise.
*
* @param Request $request The request instance
* @param string $route The route name to redirect to
* @param bool $permanent Whether the redirection is permanent
* @param bool|array $ignoreAttributes Whether to ignore attributes or an array of attributes to ignore
*
* @return Response A Response instance
* @param string $route The route name to redirect to
* @param bool $permanent Whether the redirection is permanent
* @param bool|array $ignoreAttributes Whether to ignore attributes or an array of attributes to ignore
* @param bool $keepRequestMethod Whether redirect action should keep HTTP request method
*
* @throws HttpException In case the route name is empty
*/
public function redirectAction(Request $request, $route, $permanent = false, $ignoreAttributes = false)
public function redirectAction(Request $request, string $route, bool $permanent = false, $ignoreAttributes = false, bool $keepRequestMethod = false, bool $keepQueryParams = false): Response
{
if ('' == $route) {
throw new HttpException($permanent ? 410 : 404);
@@ -82,13 +63,30 @@ class RedirectController implements ContainerAwareInterface
$attributes = [];
if (false === $ignoreAttributes || \is_array($ignoreAttributes)) {
$attributes = $request->attributes->get('_route_params');
unset($attributes['route'], $attributes['permanent'], $attributes['ignoreAttributes']);
if ($keepQueryParams) {
if ($query = $request->server->get('QUERY_STRING')) {
$query = HeaderUtils::parseQuery($query);
} else {
$query = $request->query->all();
}
$attributes = array_merge($query, $attributes);
}
unset($attributes['route'], $attributes['permanent'], $attributes['ignoreAttributes'], $attributes['keepRequestMethod'], $attributes['keepQueryParams']);
if ($ignoreAttributes) {
$attributes = array_diff_key($attributes, array_flip($ignoreAttributes));
}
}
return new RedirectResponse($this->router->generate($route, $attributes, UrlGeneratorInterface::ABSOLUTE_URL), $permanent ? 301 : 302);
if ($keepRequestMethod) {
$statusCode = $permanent ? 308 : 307;
} else {
$statusCode = $permanent ? 301 : 302;
}
return new RedirectResponse($this->router->generate($route, $attributes, UrlGeneratorInterface::ABSOLUTE_URL), $statusCode);
}
/**
@@ -100,24 +98,26 @@ class RedirectController implements ContainerAwareInterface
* In case the path is empty, the status code will be 404 when permanent is false
* and 410 otherwise.
*
* @param Request $request The request instance
* @param string $path The absolute path or URL to redirect to
* @param bool $permanent Whether the redirect is permanent or not
* @param string|null $scheme The URL scheme (null to keep the current one)
* @param int|null $httpPort The HTTP port (null to keep the current one for the same scheme or the configured port in the container)
* @param int|null $httpsPort The HTTPS port (null to keep the current one for the same scheme or the configured port in the container)
*
* @return Response A Response instance
* @param string $path The absolute path or URL to redirect to
* @param bool $permanent Whether the redirect is permanent or not
* @param string|null $scheme The URL scheme (null to keep the current one)
* @param int|null $httpPort The HTTP port (null to keep the current one for the same scheme or the default configured port)
* @param int|null $httpsPort The HTTPS port (null to keep the current one for the same scheme or the default configured port)
* @param bool $keepRequestMethod Whether redirect action should keep HTTP request method
*
* @throws HttpException In case the path is empty
*/
public function urlRedirectAction(Request $request, $path, $permanent = false, $scheme = null, $httpPort = null, $httpsPort = null)
public function urlRedirectAction(Request $request, string $path, bool $permanent = false, string $scheme = null, int $httpPort = null, int $httpsPort = null, bool $keepRequestMethod = false): Response
{
if ('' == $path) {
throw new HttpException($permanent ? 410 : 404);
}
$statusCode = $permanent ? 301 : 302;
if ($keepRequestMethod) {
$statusCode = $permanent ? 308 : 307;
} else {
$statusCode = $permanent ? 301 : 302;
}
// redirect if the path is a full URL
if (parse_url($path, \PHP_URL_SCHEME)) {
@@ -129,7 +129,7 @@ class RedirectController implements ContainerAwareInterface
}
if ($qs = $request->server->get('QUERY_STRING') ?: $request->getQueryString()) {
if (false === strpos($path, '?')) {
if (!str_contains($path, '?')) {
$qs = '?'.$qs;
} else {
$qs = '&'.$qs;
@@ -141,9 +141,6 @@ class RedirectController implements ContainerAwareInterface
if (null === $httpPort) {
if ('http' === $request->getScheme()) {
$httpPort = $request->getPort();
} elseif ($this->container && $this->container->hasParameter('request_listener.http_port')) {
@trigger_error(sprintf('Passing the http port as a container parameter is deprecated since Symfony 3.4 and won\'t be possible in 4.0. Pass it to the constructor of the "%s" class instead.', __CLASS__), \E_USER_DEPRECATED);
$httpPort = $this->container->getParameter('request_listener.http_port');
} else {
$httpPort = $this->httpPort;
}
@@ -156,9 +153,6 @@ class RedirectController implements ContainerAwareInterface
if (null === $httpsPort) {
if ('https' === $request->getScheme()) {
$httpsPort = $request->getPort();
} elseif ($this->container && $this->container->hasParameter('request_listener.https_port')) {
@trigger_error(sprintf('Passing the https port as a container parameter is deprecated since Symfony 3.4 and won\'t be possible in 4.0. Pass it to the constructor of the "%s" class instead.', __CLASS__), \E_USER_DEPRECATED);
$httpsPort = $this->container->getParameter('request_listener.https_port');
} else {
$httpsPort = $this->httpsPort;
}
@@ -173,4 +167,23 @@ class RedirectController implements ContainerAwareInterface
return new RedirectResponse($url, $statusCode);
}
public function __invoke(Request $request): Response
{
$p = $request->attributes->get('_route_params', []);
if (\array_key_exists('route', $p)) {
if (\array_key_exists('path', $p)) {
throw new \RuntimeException(sprintf('Ambiguous redirection settings, use the "path" or "route" parameter, not both: "%s" and "%s" found respectively in "%s" routing configuration.', $p['path'], $p['route'], $request->attributes->get('_route')));
}
return $this->redirectAction($request, $p['route'], $p['permanent'] ?? false, $p['ignoreAttributes'] ?? false, $p['keepRequestMethod'] ?? false, $p['keepQueryParams'] ?? false);
}
if (\array_key_exists('path', $p)) {
return $this->urlRedirectAction($request, $p['path'], $p['permanent'] ?? false, $p['scheme'] ?? null, $p['httpPort'] ?? null, $p['httpsPort'] ?? null, $p['keepRequestMethod'] ?? false);
}
throw new \RuntimeException(sprintf('The parameter "path" or "route" is required to configure the redirect action in "%s" routing configuration.', $request->attributes->get('_route')));
}
}

View File

@@ -11,10 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\Controller;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Templating\EngineInterface;
use Twig\Environment;
/**
@@ -22,60 +19,36 @@ use Twig\Environment;
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final since version 3.4
* @final
*/
class TemplateController implements ContainerAwareInterface
class TemplateController
{
/**
* @deprecated since version 3.4, to be removed in 4.0
*/
protected $container;
private $twig;
private $templating;
public function __construct(Environment $twig = null, EngineInterface $templating = null)
public function __construct(Environment $twig = null)
{
$this->twig = $twig;
$this->templating = $templating;
}
/**
* @deprecated since version 3.4, to be removed in 4.0 alongside with the ContainerAwareInterface type.
*/
public function setContainer(ContainerInterface $container = null)
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.4 and will be removed in 4.0. Inject a Twig Environment or an EngineInterface using the constructor instead.', __METHOD__), \E_USER_DEPRECATED);
if ($container->has('templating')) {
$this->templating = $container->get('templating');
} elseif ($container->has('twig')) {
$this->twig = $container->get('twig');
}
$this->container = $container;
}
/**
* Renders a template.
*
* @param string $template The template name
* @param int|null $maxAge Max age for client caching
* @param int|null $sharedAge Max age for shared (proxy) caching
* @param bool|null $private Whether or not caching should apply for client caches only
*
* @return Response A Response instance
* @param string $template The template name
* @param int|null $maxAge Max age for client caching
* @param int|null $sharedAge Max age for shared (proxy) caching
* @param bool|null $private Whether or not caching should apply for client caches only
* @param array $context The context (arguments) of the template
* @param int $statusCode The HTTP status code to return with the response. Defaults to 200
*/
public function templateAction($template, $maxAge = null, $sharedAge = null, $private = null)
public function templateAction(string $template, int $maxAge = null, int $sharedAge = null, bool $private = null, array $context = [], int $statusCode = 200): Response
{
if ($this->templating) {
$response = new Response($this->templating->render($template));
} elseif ($this->twig) {
$response = new Response($this->twig->render($template));
} else {
throw new \LogicException('You can not use the TemplateController if the Templating Component or the Twig Bundle are not available.');
if (null === $this->twig) {
throw new \LogicException('You cannot use the TemplateController if the Twig Bundle is not available.');
}
if (null !== $maxAge) {
$response = new Response($this->twig->render($template, $context), $statusCode);
if ($maxAge) {
$response->setMaxAge($maxAge);
}
@@ -91,4 +64,9 @@ class TemplateController implements ContainerAwareInterface
return $response;
}
public function __invoke(string $template, int $maxAge = null, int $sharedAge = null, bool $private = null, array $context = [], int $statusCode = 200): Response
{
return $this->templateAction($template, $maxAge, $sharedAge, $private, $context, $statusCode);
}
}

View File

@@ -0,0 +1,35 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DataCollector;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
/**
* @author Laurent VOULLEMIER <laurent.voullemier@gmail.com>
*/
abstract class AbstractDataCollector extends DataCollector implements TemplateAwareDataCollectorInterface
{
public function getName(): string
{
return static::class;
}
public function reset(): void
{
$this->data = [];
}
public static function getTemplate(): ?string
{
return null;
}
}

View File

@@ -1,77 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DataCollector;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\RequestDataCollector as BaseRequestCollector;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
/**
* RequestDataCollector.
*
* @author Jules Pietri <jusles@heahprod.com>
*/
class RequestDataCollector extends BaseRequestCollector implements EventSubscriberInterface
{
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
parent::collect($request, $response, $exception);
if ($parentRequestAttributes = $request->attributes->get('_forwarded')) {
if ($parentRequestAttributes instanceof ParameterBag) {
$parentRequestAttributes->set('_forward_token', $response->headers->get('x-debug-token'));
}
}
if ($request->attributes->has('_forward_controller')) {
$this->data['forward'] = [
'token' => $request->attributes->get('_forward_token'),
'controller' => $this->parseController($request->attributes->get('_forward_controller')),
];
}
}
/**
* Gets the parsed forward controller.
*
* @return array|bool An array with keys 'token' the forward profile token, and
* 'controller' the parsed forward controller, false otherwise
*/
public function getForward()
{
return isset($this->data['forward']) ? $this->data['forward'] : false;
}
public function onKernelController(FilterControllerEvent $event)
{
$this->controllers[$event->getRequest()] = $event->getController();
if ($parentRequestAttributes = $event->getRequest()->attributes->get('_forwarded')) {
if ($parentRequestAttributes instanceof ParameterBag) {
$parentRequestAttributes->set('_forward_controller', $event->getController());
}
}
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'request';
}
}

View File

@@ -16,9 +16,9 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\DataCollector\RouterDataCollector as BaseRouterDataCollector;
/**
* RouterDataCollector.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class RouterDataCollector extends BaseRouterDataCollector
{

View File

@@ -0,0 +1,22 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DataCollector;
use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface;
/**
* @author Laurent VOULLEMIER <laurent.voullemier@gmail.com>
*/
interface TemplateAwareDataCollectorInterface extends DataCollectorInterface
{
public static function getTemplate(): ?string;
}

View File

@@ -11,6 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -34,8 +35,11 @@ class AddAnnotationsCachedReaderPass implements CompilerPassInterface
$provider = $properties['cacheProviderBackup']->getValues()[0];
unset($properties['cacheProviderBackup']);
$reader->setProperties($properties);
$container->set($id, null);
$container->setDefinition($id, $reader->replaceArgument(1, $provider));
$reader->replaceArgument(1, $provider);
} elseif (4 <= \count($arguments = $reader->getArguments()) && $arguments[3] instanceof ServiceClosureArgument) {
$arguments[1] = $arguments[3]->getValues()[0];
unset($arguments[3]);
$reader->setArguments($arguments);
}
}
}

View File

@@ -1,45 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
@trigger_error(sprintf('The %s class is deprecated since Symfony 3.4 and will be removed in 4.0. Use tagged iterator arguments instead.', AddCacheClearerPass::class), \E_USER_DEPRECATED);
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* Registers the cache clearers.
*
* @deprecated since version 3.4, to be removed in 4.0. Use tagged iterator arguments.
*
* @author Dustin Dobervich <ddobervich@gmail.com>
*/
class AddCacheClearerPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('cache_clearer')) {
return;
}
$clearers = [];
foreach ($container->findTaggedServiceIds('kernel.cache_clearer', true) as $id => $attributes) {
$clearers[] = new Reference($id);
}
$container->getDefinition('cache_clearer')->replaceArgument(0, $clearers);
}
}

View File

@@ -1,48 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
@trigger_error(sprintf('The %s class is deprecated since Symfony 3.4 and will be removed in 4.0. Use tagged iterator arguments instead.', AddCacheWarmerPass::class), \E_USER_DEPRECATED);
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Registers the cache warmers.
*
* @deprecated since version 3.4, to be removed in 4.0. Use tagged iterator arguments instead.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class AddCacheWarmerPass implements CompilerPassInterface
{
use PriorityTaggedServiceTrait;
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('cache_warmer')) {
return;
}
$warmers = $this->findAndSortTaggedServices('kernel.cache_warmer', $container);
if (empty($warmers)) {
return;
}
$container->getDefinition('cache_warmer')->replaceArgument(0, $warmers);
}
}

View File

@@ -1,27 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
@trigger_error(sprintf('%s is deprecated since Symfony 3.3 and will be removed in 4.0. Use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass instead.', AddConsoleCommandPass::class), \E_USER_DEPRECATED);
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass as BaseAddConsoleCommandPass;
/**
* Registers console commands.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*
* @deprecated since version 3.3, to be removed in 4.0. Use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass instead.
*/
class AddConsoleCommandPass extends BaseAddConsoleCommandPass
{
}

View File

@@ -1,23 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Component\Validator\DependencyInjection\AddConstraintValidatorsPass as BaseAddConstraintValidatorsPass;
@trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use %s instead.', AddConstraintValidatorsPass::class, BaseAddConstraintValidatorsPass::class), \E_USER_DEPRECATED);
/**
* @deprecated since version 3.3, to be removed in 4.0. Use {@link BaseAddConstraintValidatorsPass} instead
*/
class AddConstraintValidatorsPass extends BaseAddConstraintValidatorsPass
{
}

View File

@@ -30,6 +30,14 @@ class AddDebugLogProcessorPass implements CompilerPassInterface
}
$definition = $container->getDefinition('monolog.logger_prototype');
$definition->setConfigurator([__CLASS__, 'configureLogger']);
$definition->addMethodCall('pushProcessor', [new Reference('debug.log_processor')]);
}
public static function configureLogger($logger)
{
if (\is_object($logger) && method_exists($logger, 'removeDebugLogger') && \in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
$logger->removeDebugLogger();
}
}
}

View File

@@ -28,19 +28,11 @@ class AddExpressionLanguageProvidersPass implements CompilerPassInterface
public function process(ContainerBuilder $container)
{
// routing
if ($container->has('router')) {
$definition = $container->findDefinition('router');
if ($container->has('router.default')) {
$definition = $container->findDefinition('router.default');
foreach ($container->findTaggedServiceIds('routing.expression_language_provider', true) as $id => $attributes) {
$definition->addMethodCall('addExpressionLanguageProvider', [new Reference($id)]);
}
}
// security
if ($container->has('security.expression_language')) {
$definition = $container->findDefinition('security.expression_language');
foreach ($container->findTaggedServiceIds('security.expression_language_provider', true) as $id => $attributes) {
$definition->addMethodCall('registerProvider', [new Reference($id)]);
}
}
}
}

View File

@@ -1,23 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Component\Validator\DependencyInjection\AddValidatorInitializersPass as BaseAddValidatorsInitializerPass;
@trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use %s instead.', AddValidatorInitializersPass::class, BaseAddValidatorsInitializerPass::class), \E_USER_DEPRECATED);
/**
* @deprecated since version 3.3, to be removed in 4.0. Use {@link BaseAddValidatorInitializersPass} instead
*/
class AddValidatorInitializersPass extends BaseAddValidatorsInitializerPass
{
}

View File

@@ -0,0 +1,44 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
class AssetsContextPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('assets.context')) {
return;
}
if (!$container->hasDefinition('router.request_context')) {
$container->setParameter('asset.request_context.base_path', $container->getParameter('asset.request_context.base_path') ?? '');
$container->setParameter('asset.request_context.secure', $container->getParameter('asset.request_context.secure') ?? false);
return;
}
$context = $container->getDefinition('assets.context');
if (null === $container->getParameter('asset.request_context.base_path')) {
$context->replaceArgument(1, (new Definition('string'))->setFactory([new Reference('router.request_context'), 'getBaseUrl']));
}
if (null === $container->getParameter('asset.request_context.secure')) {
$context->replaceArgument(2, (new Definition('bool'))->setFactory([new Reference('router.request_context'), 'isSecure']));
}
}
}

View File

@@ -1,68 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
use Symfony\Component\Cache\Adapter\TraceableAdapter;
use Symfony\Component\Cache\Adapter\TraceableTagAwareAdapter;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
/**
* Inject a data collector to all the cache services to be able to get detailed statistics.
*
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
class CacheCollectorPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('data_collector.cache')) {
return;
}
$collectorDefinition = $container->getDefinition('data_collector.cache');
foreach ($container->findTaggedServiceIds('cache.pool') as $id => $attributes) {
$definition = $container->getDefinition($id);
if ($definition->isAbstract()) {
continue;
}
$recorder = new Definition(is_subclass_of($definition->getClass(), TagAwareAdapterInterface::class) ? TraceableTagAwareAdapter::class : TraceableAdapter::class);
$recorder->setTags($definition->getTags());
if (!$definition->isPublic() || !$definition->isPrivate()) {
$recorder->setPublic($definition->isPublic());
}
$recorder->setArguments([new Reference($innerId = $id.'.recorder_inner')]);
$definition->setTags([]);
$definition->setPublic(false);
if (method_exists($definition, 'getAutowiringTypes') && $types = $definition->getAutowiringTypes(false)) {
$recorder->setAutowiringTypes($types);
$definition->setAutowiringTypes([]);
}
$container->setDefinition($innerId, $definition);
$container->setDefinition($id, $recorder);
// Tell the collector to add the new instance
$collectorDefinition->addMethodCall('addInstance', [$id, new Reference($id)]);
$collectorDefinition->setPublic(false);
}
}
}

View File

@@ -1,41 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
final class CachePoolClearerPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$container->getParameterBag()->remove('cache.prefix.seed');
foreach ($container->findTaggedServiceIds('cache.pool.clearer') as $id => $attr) {
$clearer = $container->getDefinition($id);
$pools = [];
foreach ($clearer->getArgument(0) as $id => $ref) {
if ($container->hasDefinition($id)) {
$pools[$id] = new Reference($id);
}
}
$clearer->replaceArgument(0, $pools);
}
}
}

View File

@@ -1,148 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Component\Cache\Adapter\AbstractAdapter;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class CachePoolPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if ($container->hasParameter('cache.prefix.seed')) {
$seed = '.'.$container->getParameterBag()->resolveValue($container->getParameter('cache.prefix.seed'));
} else {
$seed = '_'.$container->getParameter('kernel.root_dir');
}
$seed .= '.'.$container->getParameter('kernel.name').'.'.$container->getParameter('kernel.environment');
$pools = [];
$clearers = [];
$attributes = [
'provider',
'namespace',
'default_lifetime',
'reset',
];
foreach ($container->findTaggedServiceIds('cache.pool') as $id => $tags) {
$adapter = $pool = $container->getDefinition($id);
if ($pool->isAbstract()) {
continue;
}
while ($adapter instanceof ChildDefinition) {
$adapter = $container->findDefinition($adapter->getParent());
if ($t = $adapter->getTag('cache.pool')) {
$tags[0] += $t[0];
}
}
if (!isset($tags[0]['namespace'])) {
$tags[0]['namespace'] = $this->getNamespace($seed, $id);
}
if (isset($tags[0]['clearer'])) {
$clearer = $tags[0]['clearer'];
while ($container->hasAlias($clearer)) {
$clearer = (string) $container->getAlias($clearer);
}
} else {
$clearer = null;
}
unset($tags[0]['clearer']);
if (isset($tags[0]['provider'])) {
$tags[0]['provider'] = new Reference(static::getServiceProvider($container, $tags[0]['provider']));
}
$i = 0;
foreach ($attributes as $attr) {
if (!isset($tags[0][$attr])) {
// no-op
} elseif ('reset' === $attr) {
if ($tags[0][$attr]) {
$pool->addTag('kernel.reset', ['method' => $tags[0][$attr]]);
}
} elseif ('namespace' !== $attr || ArrayAdapter::class !== $adapter->getClass()) {
$pool->replaceArgument($i++, $tags[0][$attr]);
}
unset($tags[0][$attr]);
}
if (!empty($tags[0])) {
throw new InvalidArgumentException(sprintf('Invalid "cache.pool" tag for service "%s": accepted attributes are "clearer", "provider", "namespace", "default_lifetime" and "reset", found "%s".', $id, implode('", "', array_keys($tags[0]))));
}
if (null !== $clearer) {
$clearers[$clearer][$id] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE);
}
$pools[$id] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE);
}
$clearer = 'cache.global_clearer';
while ($container->hasAlias($clearer)) {
$clearer = (string) $container->getAlias($clearer);
}
if ($container->hasDefinition($clearer)) {
$clearers['cache.global_clearer'] = $pools;
}
foreach ($clearers as $id => $pools) {
$clearer = $container->getDefinition($id);
if ($clearer instanceof ChildDefinition) {
$clearer->replaceArgument(0, $pools);
} else {
$clearer->setArgument(0, $pools);
}
$clearer->addTag('cache.pool.clearer');
if ('cache.system_clearer' === $id) {
$clearer->addTag('kernel.cache_clearer');
}
}
}
private function getNamespace($seed, $id)
{
return substr(str_replace('/', '-', base64_encode(hash('sha256', $id.$seed, true))), 0, 10);
}
/**
* @internal
*/
public static function getServiceProvider(ContainerBuilder $container, $name)
{
$container->resolveEnvPlaceholders($name, null, $usedEnvs);
if ($usedEnvs || preg_match('#^[a-z]++://#', $name)) {
$dsn = $name;
if (!$container->hasDefinition($name = 'cache_connection.'.ContainerBuilder::hash($dsn))) {
$definition = new Definition(AbstractAdapter::class);
$definition->setPublic(false);
$definition->setFactory([AbstractAdapter::class, 'createConnection']);
$definition->setArguments([$dsn, ['lazy' => true]]);
$container->setDefinition($name, $definition);
}
}
return $name;
}
}

View File

@@ -1,60 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Rob Frawley 2nd <rmf@src.run>
*/
class CachePoolPrunerPass implements CompilerPassInterface
{
private $cacheCommandServiceId;
private $cachePoolTag;
public function __construct($cacheCommandServiceId = 'console.command.cache_pool_prune', $cachePoolTag = 'cache.pool')
{
$this->cacheCommandServiceId = $cacheCommandServiceId;
$this->cachePoolTag = $cachePoolTag;
}
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->cacheCommandServiceId)) {
return;
}
$services = [];
foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $tags) {
$class = $container->getParameterBag()->resolveValue($container->getDefinition($id)->getClass());
if (!$reflection = $container->getReflectionClass($class)) {
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
}
if ($reflection->implementsInterface(PruneableInterface::class)) {
$services[$id] = new Reference($id);
}
}
$container->getDefinition($this->cacheCommandServiceId)->replaceArgument(0, new IteratorArgument($services));
}
}

View File

@@ -1,46 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
@trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0.', CompilerDebugDumpPass::class), \E_USER_DEPRECATED);
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem;
/**
* @deprecated since version 3.3, to be removed in 4.0.
*/
class CompilerDebugDumpPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$filename = self::getCompilerLogFilename($container);
$filesystem = new Filesystem();
$filesystem->dumpFile($filename, implode("\n", $container->getCompiler()->getLog()), null);
try {
$filesystem->chmod($filename, 0666, umask());
} catch (IOException $e) {
// discard chmod failure (some filesystem may not support it)
}
}
public static function getCompilerLogFilename(ContainerInterface $container)
{
$class = $container->getParameter('kernel.container_class');
return $container->getParameter('kernel.cache_dir').'/'.$class.'Compiler.log';
}
}

View File

@@ -1,28 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Component\Config\DependencyInjection\ConfigCachePass as BaseConfigCachePass;
@trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use tagged iterator arguments instead.', ConfigCachePass::class), \E_USER_DEPRECATED);
/**
* Adds services tagged config_cache.resource_checker to the config_cache_factory service, ordering them by priority.
*
* @deprecated since version 3.3, to be removed in 4.0. Use tagged iterator arguments instead.
*
* @author Matthias Pigulla <mp@webfactory.de>
* @author Benjamin Klotz <bk@webfactory.de>
*/
class ConfigCachePass extends BaseConfigCachePass
{
}

View File

@@ -1,27 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Component\HttpKernel\DependencyInjection\ControllerArgumentValueResolverPass as BaseControllerArgumentValueResolverPass;
@trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use %s instead.', ControllerArgumentValueResolverPass::class, BaseControllerArgumentValueResolverPass::class), \E_USER_DEPRECATED);
/**
* Gathers and configures the argument value resolvers.
*
* @author Iltar van der Berg <kjarli@gmail.com>
*
* @deprecated since version 3.3, to be removed in 4.0. Use {@link BaseControllerArgumentValueResolverPass}
*/
class ControllerArgumentValueResolverPass extends BaseControllerArgumentValueResolverPass
{
}

View File

@@ -1,88 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
@trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use Symfony\Component\Form\DependencyInjection\FormPass instead.', FormPass::class), \E_USER_DEPRECATED);
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
/**
* Adds all services with the tags "form.type" and "form.type_guesser" as
* arguments of the "form.extension" service.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @deprecated since version 3.3, to be removed in 4.0. Use FormPass in the Form component instead.
*/
class FormPass implements CompilerPassInterface
{
use PriorityTaggedServiceTrait;
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('form.extension')) {
return;
}
$definition = $container->getDefinition('form.extension');
// Builds an array with fully-qualified type class names as keys and service IDs as values
$types = [];
foreach ($container->findTaggedServiceIds('form.type') as $serviceId => $tag) {
$serviceDefinition = $container->getDefinition($serviceId);
if (!$serviceDefinition->isPublic()) {
$serviceDefinition->setPublic(true);
}
// Support type access by FQCN
$types[$serviceDefinition->getClass()] = $serviceId;
}
$definition->replaceArgument(1, $types);
$typeExtensions = [];
foreach ($this->findAndSortTaggedServices('form.type_extension', $container) as $reference) {
$serviceId = (string) $reference;
$serviceDefinition = $container->getDefinition($serviceId);
if (!$serviceDefinition->isPublic()) {
$serviceDefinition->setPublic(true);
}
$tag = $serviceDefinition->getTag('form.type_extension');
if (isset($tag[0]['extended_type'])) {
$extendedType = $tag[0]['extended_type'];
} else {
throw new InvalidArgumentException(sprintf('Tagged form type extension must have the extended type configured using the extended_type/extended-type attribute, none was configured for the "%s" service.', $serviceId));
}
$typeExtensions[$extendedType][] = $serviceId;
}
$definition->replaceArgument(2, $typeExtensions);
// Find all services annotated with "form.type_guesser"
$guessers = array_keys($container->findTaggedServiceIds('form.type_guesser'));
foreach ($guessers as $serviceId) {
$serviceDefinition = $container->getDefinition($serviceId);
if (!$serviceDefinition->isPublic()) {
$serviceDefinition->setPublic(true);
}
}
$definition->replaceArgument(3, $guessers);
}
}

View File

@@ -15,7 +15,7 @@ use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\Translation\TranslatorBagInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>

View File

@@ -11,6 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Bundle\FrameworkBundle\DataCollector\TemplateAwareDataCollectorInterface;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
@@ -34,14 +35,17 @@ class ProfilerPass implements CompilerPassInterface
$collectors = new \SplPriorityQueue();
$order = \PHP_INT_MAX;
foreach ($container->findTaggedServiceIds('data_collector', true) as $id => $attributes) {
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
$priority = $attributes[0]['priority'] ?? 0;
$template = null;
if (isset($attributes[0]['template'])) {
if (!isset($attributes[0]['id'])) {
$collectorClass = $container->findDefinition($id)->getClass();
$isTemplateAware = is_subclass_of($collectorClass, TemplateAwareDataCollectorInterface::class);
if (isset($attributes[0]['template']) || $isTemplateAware) {
$idForTemplate = $attributes[0]['id'] ?? $collectorClass;
if (!$idForTemplate) {
throw new InvalidArgumentException(sprintf('Data collector service "%s" must have an id attribute in order to specify a template.', $id));
}
$template = [$attributes[0]['id'], $attributes[0]['template']];
$template = [$idForTemplate, $attributes[0]['template'] ?? $collectorClass::getTemplate()];
}
$collectors->insert([$id, $template], [$priority, --$order]);

View File

@@ -1,27 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
@trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass instead.', PropertyInfoPass::class), \E_USER_DEPRECATED);
use Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass as BasePropertyInfoPass;
/**
* Adds extractors to the property_info service.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*
* @deprecated since version 3.3, to be removed in 4.0. Use {@link BasePropertyInfoPass instead}.
*/
class PropertyInfoPass extends BasePropertyInfoPass
{
}

View File

@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* @author Ahmed TAILOULOUTE <ahmed.tailouloute@gmail.com>
*/
class RemoveUnusedSessionMarshallingHandlerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('session.marshalling_handler')) {
return;
}
$isMarshallerDecorated = false;
foreach ($container->getDefinitions() as $definition) {
$decorated = $definition->getDecoratedService();
if (null !== $decorated && 'session.marshaller' === $decorated[0]) {
$isMarshallerDecorated = true;
break;
}
}
if (!$isMarshallerDecorated) {
$container->removeDefinition('session.marshalling_handler');
}
}
}

View File

@@ -1,27 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Component\Routing\DependencyInjection\RoutingResolverPass as BaseRoutingResolverPass;
@trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use %s instead.', RoutingResolverPass::class, BaseRoutingResolverPass::class), \E_USER_DEPRECATED);
/**
* Adds tagged routing.loader services to routing.resolver service.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since version 3.3, to be removed in 4.0. Use {@link BaseRoutingResolverPass}
*/
class RoutingResolverPass extends BaseRoutingResolverPass
{
}

View File

@@ -1,28 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
@trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use Symfony\Component\Serializer\DependencyInjection\SerializerPass instead.', SerializerPass::class), \E_USER_DEPRECATED);
use Symfony\Component\Serializer\DependencyInjection\SerializerPass as BaseSerializerPass;
/**
* Adds all services with the tags "serializer.encoder" and "serializer.normalizer" as
* encoders and normalizers to the Serializer service.
*
* @deprecated since version 3.3, to be removed in 4.0. Use {@link BaseSerializerPass} instead.
*
* @author Javier Lopez <f12loalf@gmail.com>
*/
class SerializerPass extends BaseSerializerPass
{
}

View File

@@ -22,21 +22,43 @@ class SessionPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('session')) {
if (!$container->has('session.factory')) {
return;
}
// BC layer: Make "session" an alias of ".session.do-not-use" when not overridden by the user
if (!$container->has('session')) {
$alias = $container->setAlias('session', '.session.do-not-use');
$alias->setDeprecated('symfony/framework-bundle', '5.3', 'The "%alias_id%" service and "SessionInterface" alias are deprecated, use "$requestStack->getSession()" instead.');
// restore previous behavior
$alias->setPublic(true);
return;
}
if ($container->hasDefinition('session')) {
$definition = $container->getDefinition('session');
$definition->setDeprecated('symfony/framework-bundle', '5.3', 'The "%service_id%" service and "SessionInterface" alias are deprecated, use "$requestStack->getSession()" instead.');
} else {
$alias = $container->getAlias('session');
$alias->setDeprecated('symfony/framework-bundle', '5.3', 'The "%alias_id%" and "SessionInterface" aliases are deprecated, use "$requestStack->getSession()" instead.');
$definition = $container->findDefinition('session');
}
// Convert internal service `.session.do-not-use` into alias of `session`.
$container->setAlias('.session.do-not-use', 'session');
$bags = [
'session.flash_bag' => $container->hasDefinition('session.flash_bag') ? $container->getDefinition('session.flash_bag') : null,
'session.attribute_bag' => $container->hasDefinition('session.attribute_bag') ? $container->getDefinition('session.attribute_bag') : null,
];
foreach ($container->getDefinition('session')->getArguments() as $v) {
foreach ($definition->getArguments() as $v) {
if (!$v instanceof Reference || !isset($bags[$bag = (string) $v]) || !\is_array($factory = $bags[$bag]->getFactory())) {
continue;
}
if ([0, 1] !== array_keys($factory) || !$factory[0] instanceof Reference || 'session' !== (string) $factory[0]) {
if ([0, 1] !== array_keys($factory) || !$factory[0] instanceof Reference || !\in_array((string) $factory[0], ['session', '.session.do-not-use'], true)) {
continue;
}

View File

@@ -1,54 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface as FrameworkBundleEngineInterface;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Templating\EngineInterface as ComponentEngineInterface;
class TemplatingPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if ($container->hasDefinition('templating')) {
return;
}
if ($container->hasAlias('templating')) {
$container->setAlias(ComponentEngineInterface::class, new Alias('templating', false));
$container->setAlias(FrameworkBundleEngineInterface::class, new Alias('templating', false));
}
if ($container->hasDefinition('templating.engine.php')) {
$refs = [];
$helpers = [];
foreach ($container->findTaggedServiceIds('templating.helper', true) as $id => $attributes) {
if (isset($attributes[0]['alias'])) {
$helpers[$attributes[0]['alias']] = $id;
$refs[$id] = new Reference($id);
}
}
if (\count($helpers) > 0) {
$definition = $container->getDefinition('templating.engine.php');
$definition->addMethodCall('setHelpers', [$helpers]);
if ($container->hasDefinition('templating.engine.php.helpers_locator')) {
$container->getDefinition('templating.engine.php.helpers_locator')->replaceArgument(0, $refs);
}
}
}
}
}

View File

@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class TestServiceContainerRealRefPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('test.private_services_locator')) {
return;
}
$privateContainer = $container->getDefinition('test.private_services_locator');
$definitions = $container->getDefinitions();
$privateServices = $privateContainer->getArgument(0);
foreach ($privateServices as $id => $argument) {
if (isset($definitions[$target = (string) $argument->getValues()[0]])) {
$argument->setValues([new Reference($target)]);
} else {
unset($privateServices[$id]);
}
}
$privateContainer->replaceArgument(0, $privateServices);
}
}

View File

@@ -0,0 +1,71 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class TestServiceContainerWeakRefPass implements CompilerPassInterface
{
private $privateTagName;
public function __construct(string $privateTagName = 'container.private')
{
if (0 < \func_num_args()) {
trigger_deprecation('symfony/framework-bundle', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
}
$this->privateTagName = $privateTagName;
}
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('test.private_services_locator')) {
return;
}
$privateServices = [];
$definitions = $container->getDefinitions();
$hasErrors = method_exists(Definition::class, 'hasErrors') ? 'hasErrors' : 'getErrors';
foreach ($definitions as $id => $definition) {
if ($id && '.' !== $id[0] && (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag($this->privateTagName)) && !$definition->$hasErrors() && !$definition->isAbstract()) {
$privateServices[$id] = new Reference($id, ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE);
}
}
$aliases = $container->getAliases();
foreach ($aliases as $id => $alias) {
if ($id && '.' !== $id[0] && (!$alias->isPublic() || $alias->isPrivate())) {
while (isset($aliases[$target = (string) $alias])) {
$alias = $aliases[$target];
}
if (isset($definitions[$target]) && !$definitions[$target]->$hasErrors() && !$definitions[$target]->isAbstract()) {
$privateServices[$id] = new Reference($target, ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE);
}
}
}
if ($privateServices) {
$id = (string) ServiceLocatorTagPass::register($container, $privateServices);
$container->setDefinition('test.private_services_locator', $container->getDefinition($id))->setPublic(true);
$container->removeDefinition($id);
}
}
}

View File

@@ -1,25 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
@trigger_error(sprintf('The %s class is deprecated since Symfony 3.4 and will be removed in 4.0. Use Symfony\Component\Translation\DependencyInjection\TranslationDumperPass instead.', TranslationDumperPass::class), \E_USER_DEPRECATED);
use Symfony\Component\Translation\DependencyInjection\TranslationDumperPass as BaseTranslationDumperPass;
/**
* Adds tagged translation.formatter services to translation writer.
*
* @deprecated since version 3.4, to be removed in 4.0. Use {@link BaseTranslationDumperPass instead}.
*/
class TranslationDumperPass extends BaseTranslationDumperPass
{
}

View File

@@ -1,25 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
@trigger_error(sprintf('The %s class is deprecated since Symfony 3.4 and will be removed in 4.0. Use Symfony\Component\Translation\DependencyInjection\TranslationExtractorPass instead.', TranslationExtractorPass::class), \E_USER_DEPRECATED);
use Symfony\Component\Translation\DependencyInjection\TranslationExtractorPass as BaseTranslationExtractorPass;
/**
* Adds tagged translation.formatter services to translation writer.
*
* @deprecated since version 3.4, to be removed in 4.0. Use {@link BaseTranslationDumperPass instead}.
*/
class TranslationExtractorPass extends BaseTranslationExtractorPass
{
}

View File

@@ -1,25 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
@trigger_error(sprintf('The %s class is deprecated since Symfony 3.4 and will be removed in 4.0. Use Symfony\Component\Translation\DependencyInjection\TranslatorPass instead.', TranslatorPass::class), \E_USER_DEPRECATED);
use Symfony\Component\Translation\DependencyInjection\TranslatorPass as BaseTranslatorPass;
/**
* Adds tagged translation.formatter services to translation writer.
*
* @deprecated since version 3.4, to be removed in 4.0. Use {@link BaseTranslatorPass instead}.
*/
class TranslatorPass extends BaseTranslatorPass
{
}

View File

@@ -21,60 +21,87 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
*/
class UnusedTagsPass implements CompilerPassInterface
{
private $knownTags = [
private const KNOWN_TAGS = [
'annotations.cached_reader',
'assets.package',
'auto_alias',
'cache.pool',
'cache.pool.clearer',
'chatter.transport_factory',
'config_cache.resource_checker',
'console.command',
'container.do_not_inline',
'container.env_var_loader',
'container.env_var_processor',
'container.hot_path',
'container.no_preload',
'container.preload',
'container.private',
'container.reversible',
'container.service_locator',
'container.service_locator_context',
'container.service_subscriber',
'container.stack',
'controller.argument_value_resolver',
'controller.service_arguments',
'data_collector',
'event_dispatcher.dispatcher',
'form.type',
'form.type_extension',
'form.type_guesser',
'http_client.client',
'kernel.cache_clearer',
'kernel.cache_warmer',
'kernel.event_listener',
'kernel.event_subscriber',
'kernel.fragment_renderer',
'kernel.locale_aware',
'kernel.reset',
'ldap',
'mailer.transport_factory',
'messenger.bus',
'messenger.message_handler',
'messenger.receiver',
'messenger.transport_factory',
'mime.mime_type_guesser',
'monolog.logger',
'notifier.channel',
'property_info.access_extractor',
'property_info.initializable_extractor',
'property_info.list_extractor',
'property_info.type_extractor',
'proxy',
'routing.expression_language_function',
'routing.expression_language_provider',
'routing.loader',
'routing.route_loader',
'security.authenticator.login_linker',
'security.expression_language_provider',
'security.remember_me_aware',
'security.remember_me_handler',
'security.voter',
'serializer.encoder',
'serializer.normalizer',
'templating.helper',
'texter.transport_factory',
'translation.dumper',
'translation.extractor',
'translation.loader',
'translation.provider_factory',
'twig.extension',
'twig.loader',
'twig.runtime',
'validator.auto_mapper',
'validator.constraint_validator',
'validator.initializer',
'workflow.definition',
];
public function process(ContainerBuilder $container)
{
$tags = array_unique(array_merge($container->findTags(), $this->knownTags));
$tags = array_unique(array_merge($container->findTags(), self::KNOWN_TAGS));
foreach ($container->findUnusedTags() as $tag) {
// skip known tags
if (\in_array($tag, $this->knownTags)) {
if (\in_array($tag, self::KNOWN_TAGS)) {
continue;
}
@@ -85,7 +112,7 @@ class UnusedTagsPass implements CompilerPassInterface
continue;
}
if (false !== strpos($definedTag, $tag) || levenshtein($tag, $definedTag) <= \strlen($tag) / 3) {
if (str_contains($definedTag, $tag) || levenshtein($tag, $definedTag) <= \strlen($tag) / 3) {
$candidates[] = $definedTag;
}
}

View File

@@ -1,25 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Component\Workflow\DependencyInjection\ValidateWorkflowsPass as BaseValidateWorkflowsPass;
@trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use %s instead.', ValidateWorkflowsPass::class, BaseValidateWorkflowsPass::class), \E_USER_DEPRECATED);
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*
* @deprecated since version 3.3, to be removed in 4.0. Use {@link BaseValidateWorkflowsPass} instead
*/
class ValidateWorkflowsPass extends BaseValidateWorkflowsPass
{
}

View File

@@ -1,48 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\EventListener;
use Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Guarantees that the _controller key is parsed into its final format.
*
* @author Ryan Weaver <ryan@knpuniversity.com>
*/
class ResolveControllerNameSubscriber implements EventSubscriberInterface
{
private $parser;
public function __construct(ControllerNameParser $parser)
{
$this->parser = $parser;
}
public function onKernelRequest(GetResponseEvent $event)
{
$controller = $event->getRequest()->attributes->get('_controller');
if (\is_string($controller) && false === strpos($controller, '::') && 2 === substr_count($controller, ':')) {
// controller in the a:b:c notation then
$event->getRequest()->attributes->set('_controller', $this->parser->parse($controller));
}
}
public static function getSubscribedEvents()
{
return [
KernelEvents::REQUEST => ['onKernelRequest', 24],
];
}
}

View File

@@ -1,39 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\EventListener;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\EventListener\AbstractSessionListener;
@trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use Symfony\Component\HttpKernel\EventListener\SessionListener instead.', SessionListener::class), \E_USER_DEPRECATED);
/**
* Sets the session in the request.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since version 3.3, to be removed in 4.0. Use Symfony\Component\HttpKernel\EventListener\SessionListener instead
*/
class SessionListener extends AbstractSessionListener
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
protected function getSession()
{
return $this->container->get('session', ContainerInterface::NULL_ON_INVALID_REFERENCE);
}
}

View File

@@ -0,0 +1,82 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\EventListener;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Suggests a package, that should be installed (via composer),
* if the package is missing, and the input command namespace can be mapped to a Symfony bundle.
*
* @author Przemysław Bogusz <przemyslaw.bogusz@tubotax.pl>
*
* @internal
*/
final class SuggestMissingPackageSubscriber implements EventSubscriberInterface
{
private const PACKAGES = [
'doctrine' => [
'fixtures' => ['DoctrineFixturesBundle', 'doctrine/doctrine-fixtures-bundle --dev'],
'mongodb' => ['DoctrineMongoDBBundle', 'doctrine/mongodb-odm-bundle'],
'_default' => ['Doctrine ORM', 'symfony/orm-pack'],
],
'generate' => [
'_default' => ['SensioGeneratorBundle', 'sensio/generator-bundle'],
],
'make' => [
'_default' => ['MakerBundle', 'symfony/maker-bundle --dev'],
],
'server' => [
'_default' => ['Debug Bundle', 'symfony/debug-bundle --dev'],
],
];
public function onConsoleError(ConsoleErrorEvent $event): void
{
if (!$event->getError() instanceof CommandNotFoundException) {
return;
}
[$namespace, $command] = explode(':', $event->getInput()->getFirstArgument()) + [1 => ''];
if (!isset(self::PACKAGES[$namespace])) {
return;
}
if (isset(self::PACKAGES[$namespace][$command])) {
$suggestion = self::PACKAGES[$namespace][$command];
$exact = true;
} else {
$suggestion = self::PACKAGES[$namespace]['_default'];
$exact = false;
}
$error = $event->getError();
if ($error->getAlternatives() && !$exact) {
return;
}
$message = sprintf("%s\n\nYou may be looking for a command provided by the \"%s\" which is currently not installed. Try running \"composer require %s\".", $error->getMessage(), $suggestion[0], $suggestion[1]);
$event->setError(new CommandNotFoundException($message));
}
public static function getSubscribedEvents(): array
{
return [
ConsoleEvents::ERROR => ['onConsoleError', 0],
];
}
}

View File

@@ -1,39 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\EventListener;
@trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use Symfony\Component\HttpKernel\EventListener\TestSessionListener instead.', TestSessionListener::class), \E_USER_DEPRECATED);
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\EventListener\AbstractTestSessionListener;
/**
* TestSessionListener.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since version 3.3, to be removed in 4.0.
*/
class TestSessionListener extends AbstractTestSessionListener
{
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
protected function getSession()
{
return $this->container->get('session', ContainerInterface::NULL_ON_INVALID_REFERENCE);
}
}

Some files were not shown because too many files have changed in this diff Show More