mirror of
https://github.com/Combodo/iTop.git
synced 2026-05-01 22:48:45 +02:00
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:
3
lib/symfony/framework-bundle/.gitignore
vendored
3
lib/symfony/framework-bundle/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
vendor/
|
||||
composer.lock
|
||||
phpunit.xml
|
||||
@@ -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
|
||||
-----
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
134
lib/symfony/framework-bundle/Command/ContainerLintCommand.php
Normal file
134
lib/symfony/framework-bundle/Command/ContainerLintCommand.php
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
109
lib/symfony/framework-bundle/Command/SecretsListCommand.php
Normal file
109
lib/symfony/framework-bundle/Command/SecretsListCommand.php
Normal 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;
|
||||
}
|
||||
}
|
||||
102
lib/symfony/framework-bundle/Command/SecretsRemoveCommand.php
Normal file
102
lib/symfony/framework-bundle/Command/SecretsRemoveCommand.php
Normal 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);
|
||||
}
|
||||
}
|
||||
149
lib/symfony/framework-bundle/Command/SecretsSetCommand.php
Normal file
149
lib/symfony/framework-bundle/Command/SecretsSetCommand.php
Normal 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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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')));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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']));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user