Ajout des vendor

This commit is contained in:
2022-04-07 13:06:23 +02:00
parent ea47c93aa7
commit 5c116e15b1
1348 changed files with 184572 additions and 1 deletions

View File

@ -0,0 +1,492 @@
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
-----
* Added `translator.default_path` option and parameter
* Session `use_strict_mode` is now enabled by default and the corresponding option has been deprecated
* Made the `cache:clear` command to *not* clear "app" PSR-6 cache pools anymore,
but to still clear "system" ones; use the `cache:pool:clear` command to clear "app" pools instead
* Always register a minimalist logger that writes in `stderr`
* Deprecated `profiler.matcher` option
* Added support for `EventSubscriberInterface` on `MicroKernelTrait`
* Removed `doctrine/cache` from the list of required dependencies in `composer.json`
* Deprecated `validator.mapping.cache.doctrine.apc` service
* The `symfony/stopwatch` dependency has been removed, require it via `composer
require symfony/stopwatch` in your `dev` environment.
* Deprecated using the `KERNEL_DIR` environment variable with `KernelTestCase::getKernelClass()`.
* Deprecated the `KernelTestCase::getPhpUnitXmlDir()` and `KernelTestCase::getPhpUnitCliConfigArgument()` methods.
* Deprecated `AddCacheClearerPass`, use tagged iterator arguments instead.
* Deprecated `AddCacheWarmerPass`, use tagged iterator arguments instead.
* Deprecated `TranslationDumperPass`, use
`Symfony\Component\Translation\DependencyInjection\TranslationDumperPass` instead
* Deprecated `TranslationExtractorPass`, use
`Symfony\Component\Translation\DependencyInjection\TranslationExtractorPass` instead
* Deprecated `TranslatorPass`, use
`Symfony\Component\Translation\DependencyInjection\TranslatorPass` instead
* Added `command` attribute to the `console.command` tag which takes the command
name as value, using it makes the command lazy
* Added `cache:pool:prune` command to allow manual stale cache item pruning of supported PSR-6 and PSR-16 cache pool
implementations
* Deprecated `Symfony\Bundle\FrameworkBundle\Translation\TranslationLoader`, use
`Symfony\Component\Translation\Reader\TranslationReader` instead
* Deprecated `translation.loader` service, use `translation.reader` instead
* `AssetsInstallCommand::__construct()` now takes an instance of
`Symfony\Component\Filesystem\Filesystem` as first argument
* `CacheClearCommand::__construct()` now takes an instance of
`Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface` as
first argument
* `CachePoolClearCommand::__construct()` now takes an instance of
`Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer` as
first argument
* `EventDispatcherDebugCommand::__construct()` now takes an instance of
`Symfony\Component\EventDispatcher\EventDispatcherInterface` as
first argument
* `RouterDebugCommand::__construct()` now takes an instance of
`Symfony\Component\Routing\RouterInterface` as
first argument
* `RouterMatchCommand::__construct()` now takes an instance of
`Symfony\Component\Routing\RouterInterface` as
first argument
* `TranslationDebugCommand::__construct()` now takes an instance of
`Symfony\Component\Translation\TranslatorInterface` as
first argument
* `TranslationUpdateCommand::__construct()` now takes an instance of
`Symfony\Component\Translation\TranslatorInterface` as
first argument
* `AssetsInstallCommand`, `CacheClearCommand`, `CachePoolClearCommand`,
`EventDispatcherDebugCommand`, `RouterDebugCommand`, `RouterMatchCommand`,
`TranslationDebugCommand`, `TranslationUpdateCommand`, `XliffLintCommand`
and `YamlLintCommand` classes have been marked as final
* Added `asset.request_context.base_path` and `asset.request_context.secure` parameters
to provide a default request context in case the stack is empty (similar to `router.request_context.*` parameters)
* Display environment variables managed by `Dotenv` in `AboutCommand`
3.3.0
-----
* Not defining the `type` option of the `framework.workflows.*` configuration entries is deprecated.
The default value will be `state_machine` in Symfony 4.0.
* Deprecated the `CompilerDebugDumpPass` class
* Deprecated the "framework.trusted_proxies" configuration option and the corresponding "kernel.trusted_proxies" parameter
* Added a new version strategy option called "json_manifest_path"
that allows you to use the `JsonManifestVersionStrategy`.
* Added `Symfony\Bundle\FrameworkBundle\Controller\AbstractController`. It provides
the same helpers as the `Controller` class, but does not allow accessing the dependency
injection container, in order to encourage explicit dependency declarations.
* Added support for the `controller.service_arguments` tag, for injecting services into controllers' actions
* Changed default configuration for
assets/forms/validation/translation/serialization/csrf from `canBeEnabled()` to
`canBeDisabled()` when Flex is used
* The server:* commands and their associated router files were moved to WebServerBundle
* Translation related services are not loaded anymore when the `framework.translator` option
is disabled.
* Added `GlobalVariables::getToken()`
* Deprecated `Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConsoleCommandPass`. Use `Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass` instead.
* Added configurable paths for validation files
* Deprecated `SerializerPass`, use `Symfony\Component\Serializer\DependencyInjection\SerializerPass` instead
* Deprecated `FormPass`, use `Symfony\Component\Form\DependencyInjection\FormPass` instead
* Deprecated `SessionListener`
* Deprecated `TestSessionListener`
* Deprecated `Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ConfigCachePass`.
Use tagged iterator arguments instead.
* Deprecated `PropertyInfoPass`, use `Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass` instead
* Deprecated `ControllerArgumentValueResolverPass`. Use
`Symfony\Component\HttpKernel\DependencyInjection\ControllerArgumentValueResolverPass` instead
* Deprecated `RoutingResolverPass`, use `Symfony\Component\Routing\DependencyInjection\RoutingResolverPass` instead
* [BC BREAK] The `server:run`, `server:start`, `server:stop` and
`server:status` console commands have been moved to a dedicated bundle.
Require `symfony/web-server-bundle` in your composer.json and register
`Symfony\Bundle\WebServerBundle\WebServerBundle` in your AppKernel to use them.
* Added `$defaultLocale` as 3rd argument of `Translator::__construct()`
making `Translator` works with any PSR-11 container
* Added `framework.serializer.mapping` config option allowing to define custom
serialization mapping files and directories
* Deprecated `AddValidatorInitializersPass`, use
`Symfony\Component\Validator\DependencyInjection\AddValidatorInitializersPass` instead
* Deprecated `AddConstraintValidatorsPass`, use
`Symfony\Component\Validator\DependencyInjection\AddConstraintValidatorsPass` instead
* Deprecated `ValidateWorkflowsPass`, use
`Symfony\Component\Workflow\DependencyInjection\ValidateWorkflowsPass` instead
* Deprecated `ConstraintValidatorFactory`, use
`Symfony\Component\Validator\ContainerConstraintValidatorFactory` instead.
* Deprecated `PhpStringTokenParser`, use
`Symfony\Component\Translation\Extractor\PhpStringTokenParser` instead.
* Deprecated `PhpExtractor`, use
`Symfony\Component\Translation\Extractor\PhpExtractor` instead.
3.2.0
-----
* Removed `doctrine/annotations` from the list of required dependencies in `composer.json`
* Removed `symfony/security-core` and `symfony/security-csrf` from the list of required dependencies in `composer.json`
* Removed `symfony/templating` from the list of required dependencies in `composer.json`
* Removed `symfony/translation` from the list of required dependencies in `composer.json`
* Removed `symfony/asset` from the list of required dependencies in `composer.json`
* The `Resources/public/images/*` files have been removed.
* The `Resources/public/css/*.css` files have been removed (they are now inlined in TwigBundle).
* Added possibility to prioritize form type extensions with `'priority'` attribute on tags `form.type_extension`
3.1.0
-----
* Added `Controller::json` to simplify creating JSON responses when using the Serializer component
* Deprecated absolute template paths support in the template name parser
* Deprecated using core form types without dependencies as services
* Added `Symfony\Component\HttpHernel\DataCollector\RequestDataCollector::onKernelResponse()`
* Added `Symfony\Bundle\FrameworkBundle\DataCollector\RequestDataCollector`
* The `framework.serializer.cache` option and the service `serializer.mapping.cache.apc` have been
deprecated. APCu should now be automatically used when available.
3.0.0
-----
* removed `validator.api` parameter
* removed `alias` option of the `form.type` tag
2.8.0
-----
* Deprecated the `alias` option of the `form.type_extension` tag in favor of the
`extended_type`/`extended-type` option
* Deprecated the `alias` option of the `form.type` tag
* Deprecated the Shell
2.7.0
-----
* Added possibility to extract translation messages from a file or files besides extracting from a directory
* Added `TranslationsCacheWarmer` to create catalogues at warmup
2.6.0
-----
* Added helper commands (`server:start`, `server:stop` and `server:status`) to control the built-in web
server in the background
* Added `Controller::isCsrfTokenValid` helper
* Added configuration for the PropertyAccess component
* Added `Controller::redirectToRoute` helper
* Added `Controller::addFlash` helper
* Added `Controller::isGranted` helper
* Added `Controller::denyAccessUnlessGranted` helper
* Deprecated `app.security` in twig as `app.user` and `is_granted()` are already available
2.5.0
-----
* Added `translation:debug` command
* Added `--no-backup` option to `translation:update` command
* Added `config:debug` command
* Added `yaml:lint` command
* Deprecated the `RouterApacheDumperCommand` which will be removed in Symfony 3.0.
2.4.0
-----
* allowed multiple IP addresses in profiler matcher settings
* added stopwatch helper to time templates with the WebProfilerBundle
* added service definition for "security.secure_random" service
* added service definitions for the new Security CSRF sub-component
2.3.0
-----
* [BC BREAK] added a way to disable the profiler (when disabling the profiler, it is now completely removed)
To get the same "disabled" behavior as before, set `enabled` to `true` and `collect` to `false`
* [BC BREAK] the `Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RegisterKernelListenersPass` was moved
to `Component\HttpKernel\DependencyInjection\RegisterListenersPass`
* added ControllerNameParser::build() which converts a controller short notation (a:b:c) to a class::method notation
* added possibility to run PHP built-in server in production environment
* added possibility to load the serializer component in the service container
* added route debug information when using the `router:match` command
* added `TimedPhpEngine`
* added `--clean` option to the `translation:update` command
* added `http_method_override` option
* added support for default templates per render tag
* added FormHelper::form(), FormHelper::start() and FormHelper::end()
* deprecated FormHelper::enctype() in favor of FormHelper::start()
* RedirectController actions now receive the Request instance via the method signature.
2.2.0
-----
* added a new `uri_signer` service to help sign URIs
* deprecated `Symfony\Bundle\FrameworkBundle\HttpKernel::render()` and `Symfony\Bundle\FrameworkBundle\HttpKernel::forward()`
* deprecated the `Symfony\Bundle\FrameworkBundle\HttpKernel` class in favor of `Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel`
* added support for adding new HTTP content rendering strategies (like ESI and Hinclude)
in the DIC via the `kernel.fragment_renderer` tag
* [BC BREAK] restricted the `Symfony\Bundle\FrameworkBundle\HttpKernel::render()` method to only accept URIs or ControllerReference instances
* `Symfony\Bundle\FrameworkBundle\HttpKernel::render()` method signature changed and the first argument
must now be a URI or a ControllerReference instance (the `generateInternalUri()` method was removed)
* The internal routes (`Resources/config/routing/internal.xml`) have been removed and replaced with a listener (`Symfony\Component\HttpKernel\EventListener\FragmentListener`)
* The `render` method of the `actions` templating helper signature and arguments changed
* replaced Symfony\Bundle\FrameworkBundle\Controller\TraceableControllerResolver by Symfony\Component\HttpKernel\Controller\TraceableControllerResolver
* replaced Symfony\Component\HttpKernel\Debug\ContainerAwareTraceableEventDispatcher by Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher
* added Client::enableProfiler()
* a new parameter has been added to the DIC: `router.request_context.base_url`
You can customize it for your functional tests or for generating URLs with
the right base URL when your are in the CLI context.
* added support for default templates per render tag
2.1.0
-----
* moved the translation files to the Form and Validator components
* changed the default extension for XLIFF files from .xliff to .xlf
* moved Symfony\Bundle\FrameworkBundle\ContainerAwareEventDispatcher to Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher
* moved Symfony\Bundle\FrameworkBundle\Debug\TraceableEventDispatcher to Symfony\Component\EventDispatcher\ContainerAwareTraceableEventDispatcher
* added a router:match command
* added a config:dump-reference command
* added a server:run command
* added kernel.event_subscriber tag
* added a way to create relative symlinks when running assets:install command (--relative option)
* added Controller::getUser()
* [BC BREAK] assets_base_urls and base_urls merging strategy has changed
* changed the default profiler storage to use the filesystem instead of SQLite
* added support for placeholders in route defaults and requirements (replaced
by the value set in the service container)
* added Filesystem component as a dependency
* added support for hinclude (use ``standalone: 'js'`` in render tag)
* session options: lifetime, path, domain, secure, httponly were deprecated.
Prefixed versions should now be used instead: cookie_lifetime, cookie_path,
cookie_domain, cookie_secure, cookie_httponly
* [BC BREAK] following session options: 'lifetime', 'path', 'domain', 'secure',
'httponly' are now prefixed with cookie_ when dumped to the container
* Added `handler_id` configuration under `session` key to represent `session.handler`
service, defaults to `session.handler.native_file`.
* Added `gc_maxlifetime`, `gc_probability`, and `gc_divisor` to session
configuration. This means session garbage collection has a
`gc_probability`/`gc_divisor` chance of being run. The `gc_maxlifetime` defines
how long a session can idle for. It is different from cookie lifetime which
declares how long a cookie can be stored on the remote client.
* Removed 'auto_start' configuration parameter from session config. The session will
start on demand.
* [BC BREAK] TemplateNameParser::parseFromFilename() has been moved to a dedicated
parser: TemplateFilenameParser::parse().
* [BC BREAK] Kernel parameters are replaced by their value wherever they appear
in Route patterns, requirements and defaults. Use '%%' as the escaped value for '%'.
* [BC BREAK] Switched behavior of flash messages to expire flash messages on retrieval
using Symfony\Component\HttpFoundation\Session\Flash\FlashBag as opposed to on
next pageload regardless of whether they are displayed or not.

View File

@ -0,0 +1,89 @@
<?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\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\NullAdapter;
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
use Symfony\Component\Config\Resource\ClassExistenceResource;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
abstract class AbstractPhpFileCacheWarmer implements CacheWarmerInterface
{
private $phpArrayFile;
/**
* @param string $phpArrayFile The PHP file where metadata are cached
*/
public function __construct(string $phpArrayFile)
{
$this->phpArrayFile = $phpArrayFile;
}
/**
* {@inheritdoc}
*/
public function isOptional()
{
return true;
}
/**
* {@inheritdoc}
*
* @return string[] A list of classes to preload on PHP 7.4+
*/
public function warmUp(string $cacheDir)
{
$arrayAdapter = new ArrayAdapter();
spl_autoload_register([ClassExistenceResource::class, 'throwOnRequiredClass']);
try {
if (!$this->doWarmUp($cacheDir, $arrayAdapter)) {
return [];
}
} finally {
spl_autoload_unregister([ClassExistenceResource::class, 'throwOnRequiredClass']);
}
// the ArrayAdapter stores the values serialized
// to avoid mutation of the data after it was written to the cache
// so here we un-serialize the values first
$values = array_map(function ($val) { return null !== $val ? unserialize($val) : null; }, $arrayAdapter->getValues());
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)
{
return (array) $phpArrayAdapter->warmUp($values);
}
/**
* @internal
*/
final protected function ignoreAutoloadException(string $class, \Exception $exception): void
{
try {
ClassExistenceResource::throwOnRequiredClass($class, $exception);
} catch (\ReflectionException $e) {
}
}
/**
* @return bool false if there is nothing to warm-up
*/
abstract protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter);
}

View File

@ -0,0 +1,113 @@
<?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 Doctrine\Common\Annotations\AnnotationException;
use Doctrine\Common\Annotations\PsrCachedReader;
use Doctrine\Common\Annotations\Reader;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
/**
* Warms up annotation caches for classes found in composer's autoload class map
* and declared in DI bundle extensions using the addAnnotatedClassesToCache method.
*
* @author Titouan Galopin <galopintitouan@gmail.com>
*/
class AnnotationsCacheWarmer extends AbstractPhpFileCacheWarmer
{
private $annotationReader;
private $excludeRegexp;
private $debug;
/**
* @param string $phpArrayFile The PHP file where annotations are cached
*/
public function __construct(Reader $annotationReader, string $phpArrayFile, string $excludeRegexp = null, bool $debug = false)
{
parent::__construct($phpArrayFile);
$this->annotationReader = $annotationReader;
$this->excludeRegexp = $excludeRegexp;
$this->debug = $debug;
}
/**
* {@inheritdoc}
*/
protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter)
{
$annotatedClassPatterns = $cacheDir.'/annotations.map';
if (!is_file($annotatedClassPatterns)) {
return true;
}
$annotatedClasses = include $annotatedClassPatterns;
$reader = new PsrCachedReader($this->annotationReader, $arrayAdapter, $this->debug);
foreach ($annotatedClasses as $class) {
if (null !== $this->excludeRegexp && preg_match($this->excludeRegexp, $class)) {
continue;
}
try {
$this->readAllComponents($reader, $class);
} catch (\Exception $e) {
$this->ignoreAutoloadException($class, $e);
}
}
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
$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);
try {
$reader->getClassAnnotations($reflectionClass);
} catch (AnnotationException $e) {
/*
* Ignore any AnnotationException to not break the cache warming process if an Annotation is badly
* configured or could not be found / read / etc.
*
* In particular cases, an Annotation in your code can be used and defined only for a specific
* environment but is always added to the annotations.map file by some Symfony default behaviors,
* and you always end up with a not found Annotation.
*/
}
foreach ($reflectionClass->getMethods() as $reflectionMethod) {
try {
$reader->getMethodAnnotations($reflectionMethod);
} catch (AnnotationException $e) {
}
}
foreach ($reflectionClass->getProperties() as $reflectionProperty) {
try {
$reader->getPropertyAnnotations($reflectionProperty);
} catch (AnnotationException $e) {
}
}
}
}

View File

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

View File

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

View File

@ -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\CacheWarmer;
use Psr\Container\ContainerInterface;
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
*/
class RouterCacheWarmer implements CacheWarmerInterface, ServiceSubscriberInterface
{
private $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.
$this->container = $container;
}
/**
* {@inheritdoc}
*/
public function warmUp(string $cacheDir): array
{
$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));
}
/**
* {@inheritdoc}
*/
public function isOptional(): bool
{
return true;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedServices(): array
{
return [
'router' => RouterInterface::class,
];
}
}

View File

@ -0,0 +1,87 @@
<?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 Doctrine\Common\Annotations\AnnotationException;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\LoaderChain;
use Symfony\Component\Serializer\Mapping\Loader\LoaderInterface;
use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;
/**
* Warms up XML and YAML serializer metadata.
*
* @author Titouan Galopin <galopintitouan@gmail.com>
*/
class SerializerCacheWarmer extends AbstractPhpFileCacheWarmer
{
private $loaders;
/**
* @param LoaderInterface[] $loaders The serializer metadata loaders
* @param string $phpArrayFile The PHP file where metadata are cached
*/
public function __construct(array $loaders, string $phpArrayFile)
{
parent::__construct($phpArrayFile);
$this->loaders = $loaders;
}
/**
* {@inheritdoc}
*/
protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter)
{
if (!class_exists(CacheClassMetadataFactory::class) || !method_exists(XmlFileLoader::class, 'getMappedClasses') || !method_exists(YamlFileLoader::class, 'getMappedClasses')) {
return false;
}
$metadataFactory = new CacheClassMetadataFactory(new ClassMetadataFactory(new LoaderChain($this->loaders)), $arrayAdapter);
foreach ($this->extractSupportedLoaders($this->loaders) as $loader) {
foreach ($loader->getMappedClasses() as $mappedClass) {
try {
$metadataFactory->getMetadataFor($mappedClass);
} catch (AnnotationException $e) {
// ignore failing annotations
} catch (\Exception $e) {
$this->ignoreAutoloadException($mappedClass, $e);
}
}
}
return true;
}
/**
* @param LoaderInterface[] $loaders
*
* @return XmlFileLoader[]|YamlFileLoader[]
*/
private function extractSupportedLoaders(array $loaders): array
{
$supportedLoaders = [];
foreach ($loaders as $loader) {
if ($loader instanceof XmlFileLoader || $loader instanceof YamlFileLoader) {
$supportedLoaders[] = $loader;
} elseif ($loader instanceof LoaderChain) {
$supportedLoaders = array_merge($supportedLoaders, $this->extractSupportedLoaders($loader->getLoaders()));
}
}
return $supportedLoaders;
}
}

View File

@ -0,0 +1,71 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\CacheWarmer;
use Psr\Container\ContainerInterface;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* Generates the catalogues for translations.
*
* @author Xavier Leune <xavier.leune@gmail.com>
*/
class TranslationsCacheWarmer implements CacheWarmerInterface, ServiceSubscriberInterface
{
private $container;
private $translator;
public function __construct(ContainerInterface $container)
{
// As this cache warmer is optional, dependencies should be lazy-loaded, that's why a container should be injected.
$this->container = $container;
}
/**
* {@inheritdoc}
*
* @return string[]
*/
public function warmUp(string $cacheDir)
{
if (null === $this->translator) {
$this->translator = $this->container->get('translator');
}
if ($this->translator instanceof WarmableInterface) {
return (array) $this->translator->warmUp($cacheDir);
}
return [];
}
/**
* {@inheritdoc}
*/
public function isOptional()
{
return true;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedServices()
{
return [
'translator' => TranslatorInterface::class,
];
}
}

View File

@ -0,0 +1,101 @@
<?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 Doctrine\Common\Annotations\AnnotationException;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
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\ValidatorBuilder;
/**
* Warms up XML and YAML validator metadata.
*
* @author Titouan Galopin <galopintitouan@gmail.com>
*/
class ValidatorCacheWarmer extends AbstractPhpFileCacheWarmer
{
private $validatorBuilder;
/**
* @param string $phpArrayFile The PHP file where metadata are cached
*/
public function __construct(ValidatorBuilder $validatorBuilder, string $phpArrayFile)
{
parent::__construct($phpArrayFile);
$this->validatorBuilder = $validatorBuilder;
}
/**
* {@inheritdoc}
*/
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), $arrayAdapter);
foreach ($this->extractSupportedLoaders($loaders) as $loader) {
foreach ($loader->getMappedClasses() as $mappedClass) {
try {
if ($metadataFactory->hasMetadataFor($mappedClass)) {
$metadataFactory->getMetadataFor($mappedClass);
}
} catch (AnnotationException $e) {
// ignore failing annotations
} catch (\Exception $e) {
$this->ignoreAutoloadException($mappedClass, $e);
}
}
}
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
$values = array_filter($values, function ($val) { return null !== $val; });
return parent::warmUpPhpArrayAdapter($phpArrayAdapter, $values);
}
/**
* @param LoaderInterface[] $loaders
*
* @return XmlFileLoader[]|YamlFileLoader[]
*/
private function extractSupportedLoaders(array $loaders): array
{
$supportedLoaders = [];
foreach ($loaders as $loader) {
if ($loader instanceof XmlFileLoader || $loader instanceof YamlFileLoader) {
$supportedLoaders[] = $loader;
} elseif ($loader instanceof LoaderChain) {
$supportedLoaders = array_merge($supportedLoaders, $this->extractSupportedLoaders($loader->getLoaders()));
}
}
return $supportedLoaders;
}
}

View File

@ -0,0 +1,136 @@
<?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\Helper\Helper;
use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\HttpKernel\KernelInterface;
/**
* A console command to display information about the current installation.
*
* @author Roland Franssen <franssen.roland@gmail.com>
*
* @final
*/
class AboutCommand extends Command
{
protected static $defaultName = 'about';
protected static $defaultDescription = 'Display information about the current project';
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->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.
EOT
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
/** @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],
['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)],
['Environment', $kernel->getEnvironment()],
['Debug', $kernel->isDebug() ? 'true' : 'false'],
['Charset', $kernel->getCharset()],
['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::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'],
];
$io->table([], $rows);
return 0;
}
private static function formatPath(string $path, string $baseDir): string
{
return preg_replace('~^'.preg_quote($baseDir, '~').'~', '.', $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) {
if ($file->isReadable()) {
$size += $file->getSize();
}
}
}
return Helper::formatMemory($size);
}
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 daysBeforeExpiration(string $date): string
{
$date = \DateTime::createFromFormat('d/m/Y', '01/'.$date);
return (new \DateTime())->diff($date->modify('last day of this month 23:59:59'))->format('in %R%a days');
}
}

View File

@ -0,0 +1,159 @@
<?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\Definition\ConfigurationInterface;
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;
/**
* A console command for dumping available configuration reference.
*
* @author Kevin Bond <kevinbond@gmail.com>
* @author Wouter J <waldio.webdesign@gmail.com>
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
abstract class AbstractConfigCommand extends ContainerDebugCommand
{
/**
* @param OutputInterface|StyleInterface $output
*/
protected function listBundles($output)
{
$title = 'Available registered bundles with their extension alias if available';
$headers = ['Bundle name', 'Extension alias'];
$rows = [];
$bundles = $this->getApplication()->getKernel()->getBundles();
usort($bundles, function ($bundleA, $bundleB) {
return strcmp($bundleA->getName(), $bundleB->getName());
});
foreach ($bundles as $bundle) {
$extension = $bundle->getContainerExtension();
$rows[] = [$bundle->getName(), $extension ? $extension->getAlias() : ''];
}
if ($output instanceof StyleInterface) {
$output->title($title);
$output->table($headers, $rows);
} else {
$output->writeln($title);
$table = new Table($output);
$table->setHeaders($headers)->setRows($rows)->render();
}
}
/**
* @return ExtensionInterface
*/
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()) {
throw new \LogicException(sprintf('Bundle "%s" does not have a container extension.', $name));
}
return $bundle->getContainerExtension();
}
$distance = levenshtein($name, $bundle->getName());
if ($distance < $minScore) {
$guess = $bundle->getName();
$minScore = $distance;
}
$extension = $bundle->getContainerExtension();
if ($extension) {
if ($name === $extension->getAlias()) {
return $extension;
}
$distance = levenshtein($name, $extension->getAlias());
if ($distance < $minScore) {
$guess = $extension->getAlias();
$minScore = $distance;
}
}
}
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);
}
if (isset($guess) && $minScore < 3) {
$message .= sprintf("\n\nDid you mean \"%s\"?", $guess);
}
throw new LogicException($message);
}
public function validateConfiguration(ExtensionInterface $extension, $configuration)
{
if (!$configuration) {
throw new \LogicException(sprintf('The extension with alias "%s" does not have its getConfiguration() method setup.', $extension->getAlias()));
}
if (!$configuration instanceof ConfigurationInterface) {
throw new \LogicException(sprintf('Configuration class "%s" should implement ConfigurationInterface in order to be dumpable.', get_debug_type($configuration)));
}
}
private function initializeBundles()
{
// 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.
$kernel = $this->getApplication()->getKernel();
$container = $this->getContainerBuilder($kernel);
$bundles = $kernel->getBundles();
foreach ($bundles as $bundle) {
if ($extension = $bundle->getContainerExtension()) {
$container->registerExtension($extension);
}
}
foreach ($bundles as $bundle) {
$bundle->build($container);
}
return $bundles;
}
}

View File

@ -0,0 +1,279 @@
<?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\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\ContainerInterface;
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.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Gábor Egyed <gabor.egyed@gmail.com>
*
* @final
*/
class AssetsInstallCommand extends Command
{
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;
public function __construct(Filesystem $filesystem, string $projectDir)
{
parent::__construct();
$this->filesystem = $filesystem;
$this->projectDir = $projectDir;
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDefinition([
new InputArgument('target', InputArgument::OPTIONAL, 'The target directory', null),
])
->addOption('symlink', null, InputOption::VALUE_NONE, 'Symlink the assets instead of copying them')
->addOption('relative', null, InputOption::VALUE_NONE, 'Make relative symlinks')
->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).
<info>php %command.full_name% public</info>
A "bundles" directory will be created inside the target directory and the
"Resources/public" directory of each bundle will be copied into it.
To create a symlink to each bundle instead of copying its assets, use the
<info>--symlink</info> option (will fall back to hard copies when symbolic links aren't possible:
<info>php %command.full_name% public --symlink</info>
To make symlink relative, add the <info>--relative</info> option:
<info>php %command.full_name% public --symlink --relative</info>
EOT
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
/** @var KernelInterface $kernel */
$kernel = $this->getApplication()->getKernel();
$targetArg = rtrim($input->getArgument('target') ?? '', '/');
if (!$targetArg) {
$targetArg = $this->getPublicDirectory($kernel->getContainer());
}
if (!is_dir($targetArg)) {
$targetArg = $kernel->getProjectDir().'/'.$targetArg;
if (!is_dir($targetArg)) {
throw new InvalidArgumentException(sprintf('The target directory "%s" does not exist.', $targetArg));
}
}
$bundlesDir = $targetArg.'/bundles/';
$io = new SymfonyStyle($input, $output);
$io->newLine();
if ($input->getOption('relative')) {
$expectedMethod = self::METHOD_RELATIVE_SYMLINK;
$io->text('Trying to install assets as <info>relative symbolic links</info>.');
} elseif ($input->getOption('symlink')) {
$expectedMethod = self::METHOD_ABSOLUTE_SYMLINK;
$io->text('Trying to install assets as <info>absolute symbolic links</info>.');
} else {
$expectedMethod = self::METHOD_COPY;
$io->text('Installing assets as <info>hard copies</info>.');
}
$io->newLine();
$rows = [];
$copyUsed = false;
$exitCode = 0;
$validAssetDirs = [];
/** @var BundleInterface $bundle */
foreach ($kernel->getBundles() as $bundle) {
if (!is_dir($originDir = $bundle->getPath().'/Resources/public') && !is_dir($originDir = $bundle->getPath().'/public')) {
continue;
}
$assetDir = preg_replace('/bundle$/', '', strtolower($bundle->getName()));
$targetDir = $bundlesDir.$assetDir;
$validAssetDirs[] = $assetDir;
if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
$message = sprintf("%s\n-> %s", $bundle->getName(), $targetDir);
} else {
$message = $bundle->getName();
}
try {
$this->filesystem->remove($targetDir);
if (self::METHOD_RELATIVE_SYMLINK === $expectedMethod) {
$method = $this->relativeSymlinkWithFallback($originDir, $targetDir);
} elseif (self::METHOD_ABSOLUTE_SYMLINK === $expectedMethod) {
$method = $this->absoluteSymlinkWithFallback($originDir, $targetDir);
} else {
$method = $this->hardCopy($originDir, $targetDir);
}
if (self::METHOD_COPY === $method) {
$copyUsed = true;
}
if ($method === $expectedMethod) {
$rows[] = [sprintf('<fg=green;options=bold>%s</>', '\\' === \DIRECTORY_SEPARATOR ? 'OK' : "\xE2\x9C\x94" /* HEAVY CHECK MARK (U+2714) */), $message, $method];
} else {
$rows[] = [sprintf('<fg=yellow;options=bold>%s</>', '\\' === \DIRECTORY_SEPARATOR ? 'WARNING' : '!'), $message, $method];
}
} catch (\Exception $e) {
$exitCode = 1;
$rows[] = [sprintf('<fg=red;options=bold>%s</>', '\\' === \DIRECTORY_SEPARATOR ? 'ERROR' : "\xE2\x9C\x98" /* HEAVY BALLOT X (U+2718) */), $message, $e->getMessage()];
}
}
// remove the assets of the bundles that no longer exist
if (!$input->getOption('no-cleanup') && is_dir($bundlesDir)) {
$dirsToRemove = Finder::create()->depth(0)->directories()->exclude($validAssetDirs)->in($bundlesDir);
$this->filesystem->remove($dirsToRemove);
}
if ($rows) {
$io->table(['', 'Bundle', 'Method / Error'], $rows);
}
if (0 !== $exitCode) {
$io->error('Some errors occurred while installing assets.');
} else {
if ($copyUsed) {
$io->note('Some assets were installed via copy. If you make changes to these assets you have to run this command again.');
}
$io->success($rows ? 'All assets were successfully installed.' : 'No assets were provided by any bundle.');
}
return $exitCode;
}
/**
* Try to create relative symlink.
*
* Falling back to absolute symlink and finally hard copy.
*/
private function relativeSymlinkWithFallback(string $originDir, string $targetDir): string
{
try {
$this->symlink($originDir, $targetDir, true);
$method = self::METHOD_RELATIVE_SYMLINK;
} catch (IOException $e) {
$method = $this->absoluteSymlinkWithFallback($originDir, $targetDir);
}
return $method;
}
/**
* Try to create absolute symlink.
*
* Falling back to hard copy.
*/
private function absoluteSymlinkWithFallback(string $originDir, string $targetDir): string
{
try {
$this->symlink($originDir, $targetDir);
$method = self::METHOD_ABSOLUTE_SYMLINK;
} catch (IOException $e) {
// fall back to copy
$method = $this->hardCopy($originDir, $targetDir);
}
return $method;
}
/**
* Creates symbolic link.
*
* @throws IOException if link cannot be created
*/
private function symlink(string $originDir, string $targetDir, bool $relative = false)
{
if ($relative) {
$this->filesystem->mkdir(\dirname($targetDir));
$originDir = $this->filesystem->makePathRelative($originDir, realpath(\dirname($targetDir)));
}
$this->filesystem->symlink($originDir, $targetDir);
if (!file_exists($targetDir)) {
throw new IOException(sprintf('Symbolic link "%s" was created but appears to be broken.', $targetDir), 0, null, $targetDir);
}
}
/**
* Copies origin to target.
*/
private function hardCopy(string $originDir, string $targetDir): string
{
$this->filesystem->mkdir($targetDir, 0777);
// We use a custom iterator to ignore VCS files
$this->filesystem->mirror($originDir, $targetDir, Finder::create()->ignoreDotFiles(false)->in($originDir));
return self::METHOD_COPY;
}
private function getPublicDirectory(ContainerInterface $container): string
{
$defaultPublicDir = 'public';
if (null === $this->projectDir && !$container->hasParameter('kernel.project_dir')) {
return $defaultPublicDir;
}
$composerFilePath = ($this->projectDir ?? $container->getParameter('kernel.project_dir')).'/composer.json';
if (!file_exists($composerFilePath)) {
return $defaultPublicDir;
}
$composerConfig = json_decode(file_get_contents($composerFilePath), true);
return $composerConfig['extra']['public-dir'] ?? $defaultPublicDir;
}
}

View File

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

View File

@ -0,0 +1,261 @@
<?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\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\RebootableInterface;
/**
* Clear and Warmup the cache.
*
* @author Francis Besset <francis.besset@gmail.com>
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class CacheClearCommand extends Command
{
protected static $defaultName = 'cache:clear';
protected static $defaultDescription = 'Clear the cache';
private $cacheClearer;
private $filesystem;
public function __construct(CacheClearerInterface $cacheClearer, Filesystem $filesystem = null)
{
parent::__construct();
$this->cacheClearer = $cacheClearer;
$this->filesystem = $filesystem ?? new Filesystem();
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDefinition([
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(self::$defaultDescription)
->setHelp(<<<'EOF'
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>
<info>php %command.full_name% --env=prod --no-debug</info>
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$fs = $this->filesystem;
$io = new SymfonyStyle($input, $output);
$kernel = $this->getApplication()->getKernel();
$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).(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());
$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($realBuildDir, 0, -1).('_' === substr($realBuildDir, -1) ? '-' : '_');
if ($output->isVerbose() && $fs->exists($warmupDir)) {
$io->comment('Clearing outdated warmup directory...');
}
$fs->remove($warmupDir);
if ($_SERVER['REQUEST_TIME'] <= filemtime($containerFile) && filemtime($containerFile) <= time()) {
if ($output->isVerbose()) {
$io->comment('Cache is fresh.');
}
if (!$input->getOption('no-warmup') && !$input->getOption('no-optional-warmers')) {
if ($output->isVerbose()) {
$io->comment('Warming up optional cache...');
}
$warmer = $kernel->getContainer()->get('cache_warmer');
// non optional warmers already ran during container compilation
$warmer->enableOnlyOptionalWarmers();
$preload = (array) $warmer->warmUp($realCacheDir);
if ($preload && file_exists($preloadFile = $realCacheDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) {
Preloader::append($preloadFile, $preload);
}
}
} else {
$fs->mkdir($warmupDir);
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);
if ($output->isVerbose()) {
$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());
}
}
}
if ($output->isVerbose()) {
$io->comment('Finished');
}
$io->success(sprintf('Cache for the "%s" environment (debug=%s) was successfully cleared.', $kernel->getEnvironment(), var_export($kernel->isDebug(), true)));
return 0;
}
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
$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
if ($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);
}
}
// fix references to cached files with the real cache directory name
$search = [$warmupDir, str_replace('\\', '\\\\', $warmupDir)];
$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);
}
}
}
}

View File

@ -0,0 +1,131 @@
<?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 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;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer;
/**
* Clear cache pools.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
final class CachePoolClearCommand extends Command
{
protected static $defaultName = 'cache:pool:clear';
protected static $defaultDescription = 'Clear cache pools';
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('pools', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'A list of cache pools or cache pool clearers'),
])
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command clears the given cache pools or cache pool clearers.
%command.full_name% <cache pool or clearer 1> [...<cache pool or clearer N>]
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$kernel = $this->getApplication()->getKernel();
$pools = [];
$clearers = [];
foreach ($input->getArgument('pools') as $id) {
if ($this->poolClearer->hasPool($id)) {
$pools[$id] = $id;
} else {
$pool = $kernel->getContainer()->get($id);
if ($pool instanceof CacheItemPoolInterface) {
$pools[$id] = $pool;
} elseif ($pool instanceof Psr6CacheClearer) {
$clearers[$id] = $pool;
} else {
throw new InvalidArgumentException(sprintf('"%s" is not a cache pool nor a cache clearer.', $id));
}
}
}
foreach ($clearers as $id => $clearer) {
$io->comment(sprintf('Calling cache clearer: <info>%s</info>', $id));
$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) {
if (!$pool->clear()) {
$io->warning(sprintf('Cache pool "%s" could not be cleared.', $pool));
$failure = true;
}
} else {
if (false === $this->poolClearer->clearPool($id)) {
$io->warning(sprintf('Cache pool "%s" could not be cleared.', $pool));
$failure = true;
}
}
}
if ($failure) {
return 1;
}
$io->success('Cache was successfully cleared.');
return 0;
}
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if (\is_array($this->poolNames) && $input->mustSuggestArgumentValuesFor('pools')) {
$suggestions->suggestValues($this->poolNames);
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,74 @@
<?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\Cache\PruneableInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Cache pool pruner command.
*
* @author Rob Frawley 2nd <rmf@src.run>
*/
final class CachePoolPruneCommand extends Command
{
protected static $defaultName = 'cache:pool:prune';
protected static $defaultDescription = 'Prune cache pools';
private $pools;
/**
* @param iterable<mixed, PruneableInterface> $pools
*/
public function __construct(iterable $pools)
{
parent::__construct();
$this->pools = $pools;
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command deletes all expired items from all pruneable pools.
%command.full_name%
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
foreach ($this->pools as $name => $pool) {
$io->comment(sprintf('Pruning cache pool: <info>%s</info>', $name));
$pool->prune();
}
$io->success('Successfully pruned cache pool(s).');
return 0;
}
}

View File

@ -0,0 +1,92 @@
<?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\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;
/**
* Warmup the cache.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class CacheWarmupCommand extends Command
{
protected static $defaultName = 'cache:warmup';
protected static $defaultDescription = 'Warm up an empty cache';
private $cacheWarmer;
public function __construct(CacheWarmerAggregate $cacheWarmer)
{
parent::__construct();
$this->cacheWarmer = $cacheWarmer;
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDefinition([
new InputOption('no-optional-warmers', '', InputOption::VALUE_NONE, 'Skip optional cache warmers (faster)'),
])
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command warms up the cache.
Before running this command, the cache must be empty.
This command does not generate the classes cache (as when executing this
command, too many classes that should be part of the cache are already loaded
in memory). Use <comment>curl</comment> or any other similar tool to warm up
the classes cache if you want.
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$kernel = $this->getApplication()->getKernel();
$io->comment(sprintf('Warming up the cache for the <info>%s</info> environment with debug <info>%s</info>', $kernel->getEnvironment(), var_export($kernel->isDebug(), true)));
if (!$input->getOption('no-optional-warmers')) {
$this->cacheWarmer->enableOptionalWarmers();
}
$preload = $this->cacheWarmer->warmUp($cacheDir = $kernel->getContainer()->getParameter('kernel.cache_dir'));
if ($preload && file_exists($preloadFile = $cacheDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) {
Preloader::append($preloadFile, $preload);
}
$io->success(sprintf('Cache for the "%s" environment (debug=%s) was successfully warmed.', $kernel->getEnvironment(), var_export($kernel->isDebug(), true)));
return 0;
}
}

View File

@ -0,0 +1,240 @@
<?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\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;
/**
* A console command for dumping available configuration reference.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*
* @final
*/
class ConfigDebugCommand extends AbstractConfigCommand
{
protected static $defaultName = 'debug:config';
protected static $defaultDescription = 'Dump the current configuration for an extension';
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDefinition([
new InputArgument('name', InputArgument::OPTIONAL, 'The bundle name or the extension alias'),
new InputArgument('path', InputArgument::OPTIONAL, 'The configuration option path'),
])
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command dumps the current configuration for an
extension/bundle.
Either the extension alias or bundle name can be used:
<info>php %command.full_name% framework</info>
<info>php %command.full_name% FrameworkBundle</info>
For dumping a specific option, add its path as second argument:
<info>php %command.full_name% framework serializer.enabled</info>
EOF
)
;
}
/**
* {@inheritdoc}
*/
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 0;
}
$extension = $this->findExtension($name);
$extensionAlias = $extension->getAlias();
$container = $this->compileContainer();
$config = $this->getConfig($extension, $container);
if (null === $path = $input->getArgument('path')) {
$io->title(
sprintf('Current configuration for %s', ($name === $extensionAlias ? sprintf('extension with alias "%s"', $extensionAlias) : sprintf('"%s"', $name)))
);
$io->writeln(Yaml::dump([$extensionAlias => $config], 10));
return 0;
}
try {
$config = $this->getConfigForPath($config, $path, $extensionAlias);
} catch (LogicException $e) {
$errorIo->error($e->getMessage());
return 1;
}
$io->title(sprintf('Current configuration for "%s.%s"', $extensionAlias, $path));
$io->writeln(Yaml::dump($config, 10));
return 0;
}
private function compileContainer(): ContainerBuilder
{
$kernel = clone $this->getApplication()->getKernel();
$kernel->boot();
$method = new \ReflectionMethod($kernel, 'buildContainer');
$method->setAccessible(true);
$container = $method->invoke($kernel);
$container->getCompiler()->compile($container);
return $container;
}
/**
* Iterate over configuration until the last step of the given path.
*
* @throws LogicException If the configuration does not exist
*
* @return mixed
*/
private function getConfigForPath(array $config, string $path, string $alias)
{
$steps = explode('.', $path);
foreach ($steps as $step) {
if (!\array_key_exists($step, $config)) {
throw new LogicException(sprintf('Unable to find configuration for "%s.%s".', $alias, $path));
}
$config = $config[$step];
}
return $config;
}
private function getConfigForExtension(ExtensionInterface $extension, ContainerBuilder $container): array
{
$extensionAlias = $extension->getAlias();
$extensionConfig = [];
foreach ($container->getCompilerPassConfig()->getPasses() as $pass) {
if ($pass instanceof ValidateEnvPlaceholdersPass) {
$extensionConfig = $pass->getExtensionConfig();
break;
}
}
if (isset($extensionConfig[$extensionAlias])) {
return $extensionConfig[$extensionAlias];
}
// Fall back to default config if the extension has one
if (!$extension instanceof ConfigurationExtensionInterface) {
throw new \LogicException(sprintf('The extension with alias "%s" does not have configuration.', $extensionAlias));
}
$configs = $container->getExtensionConfig($extensionAlias);
$configuration = $extension->getConfiguration($configs, $container);
$this->validateConfiguration($extension, $configuration);
return (new Processor())->processConfiguration($configuration, $configs);
}
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('name')) {
$suggestions->suggestValues($this->getAvailableBundles(!preg_match('/^[A-Z]/', $input->getCompletionValue())));
return;
}
if ($input->mustSuggestArgumentValuesFor('path') && null !== $name = $input->getArgument('name')) {
try {
$config = $this->getConfig($this->findExtension($name), $this->compileContainer());
$paths = array_keys(self::buildPathsCompletion($config));
$suggestions->suggestValues($paths);
} catch (LogicException $e) {
}
}
}
private function getAvailableBundles(bool $alias): array
{
$availableBundles = [];
foreach ($this->getApplication()->getKernel()->getBundles() as $bundle) {
$availableBundles[] = $alias ? $bundle->getContainerExtension()->getAlias() : $bundle->getName();
}
return $availableBundles;
}
private function getConfig(ExtensionInterface $extension, ContainerBuilder $container)
{
return $container->resolveEnvPlaceholders(
$container->getParameterBag()->resolveValue(
$this->getConfigForExtension($extension, $container)
)
);
}
private static function buildPathsCompletion(array $paths, string $prefix = ''): array
{
$completionPaths = [];
foreach ($paths as $key => $values) {
if (\is_array($values)) {
$completionPaths = $completionPaths + self::buildPathsCompletion($values, $prefix.$key.'.');
} else {
$completionPaths[$prefix.$key] = null;
}
}
return $completionPaths;
}
}

View File

@ -0,0 +1,190 @@
<?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\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.
*
* @author Kevin Bond <kevinbond@gmail.com>
* @author Wouter J <waldio.webdesign@gmail.com>
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*
* @final
*/
class ConfigDumpReferenceCommand extends AbstractConfigCommand
{
protected static $defaultName = 'config:dump-reference';
protected static $defaultDescription = 'Dump the default configuration for an extension';
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDefinition([
new InputArgument('name', InputArgument::OPTIONAL, 'The Bundle name or the extension alias'),
new InputArgument('path', InputArgument::OPTIONAL, 'The configuration option path'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (yaml or xml)', 'yaml'),
])
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command dumps the default configuration for an
extension/bundle.
Either the extension alias or bundle name can be used:
<info>php %command.full_name% framework</info>
<info>php %command.full_name% FrameworkBundle</info>
With the <info>--format</info> option specifies the format of the configuration,
this is either <comment>yaml</comment> or <comment>xml</comment>.
When the option is not provided, <comment>yaml</comment> is used.
<info>php %command.full_name% FrameworkBundle --format=xml</info>
For dumping a specific option, add its path as second argument (only available for the yaml format):
<info>php %command.full_name% framework profiler.matcher</info>
EOF
)
;
}
/**
* {@inheritdoc}
*
* @throws \LogicException
*/
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 0;
}
$extension = $this->findExtension($name);
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) {
$errorIo->error('The "path" option is only available for the "yaml" format.');
return 1;
}
if ($name === $extension->getAlias()) {
$message = sprintf('Default configuration for extension with alias: "%s"', $name);
} else {
$message = sprintf('Default configuration for "%s"', $name);
}
if (null !== $path) {
$message .= sprintf(' at path "%s"', $path);
}
switch ($format) {
case 'yaml':
$io->writeln(sprintf('# %s', $message));
$dumper = new YamlReferenceDumper();
break;
case 'xml':
$io->writeln(sprintf('<!-- %s -->', $message));
$dumper = new XmlReferenceDumper();
break;
default:
$io->writeln($message);
throw new InvalidArgumentException('Only the yaml and xml formats are supported.');
}
$io->writeln(null === $path ? $dumper->dump($configuration, $extension->getNamespace()) : $dumper->dumpAtPath($configuration, $path));
return 0;
}
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('name')) {
$suggestions->suggestValues($this->getAvailableBundles());
}
if ($input->mustSuggestOptionValuesFor('format')) {
$suggestions->suggestValues($this->getAvailableFormatOptions());
}
}
private function getAvailableBundles(): array
{
$bundles = [];
foreach ($this->getApplication()->getKernel()->getBundles() as $bundle) {
$bundles[] = $bundle->getName();
$bundles[] = $bundle->getContainerExtension()->getAlias();
}
return $bundles;
}
private function getAvailableFormatOptions(): array
{
return ['yaml', 'xml'];
}
}

View File

@ -0,0 +1,313 @@
<?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\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\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\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
/**
* A console command for retrieving information about services.
*
* @author Ryan Weaver <ryan@thatsquality.com>
*
* @internal
*/
class ContainerDebugCommand extends Command
{
use BuildDebugContainerTrait;
protected static $defaultName = 'debug:container';
protected static $defaultDescription = 'Display current services for an application';
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDefinition([
new InputArgument('name', InputArgument::OPTIONAL, 'A service name (foo)'),
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(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>
To see environment variables used by the container, use the <info>--env-vars</info> flag:
<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:
<info>php %command.full_name% --tags</info>
Find all services with a specific tag by specifying the tag name with the <info>--tag</info> option:
<info>php %command.full_name% --tag=form.type</info>
Use the <info>--parameters</info> option to display all parameters:
<info>php %command.full_name% --parameters</info>
Display a specific parameter by specifying its name with the <info>--parameter</info> option:
<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
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$errorIo = $io->getErrorStyle();
$this->validateInput($input);
$kernel = $this->getApplication()->getKernel();
$object = $this->getContainerBuilder($kernel);
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 = [];
foreach ($object->getParameterBag()->all() as $k => $v) {
$parameters[$k] = $object->resolveEnvPlaceholders($v);
}
$object = new ParameterBag($parameters);
$options = [];
} elseif ($parameter = $input->getOption('parameter')) {
$options = ['parameter' => $parameter];
} elseif ($input->getOption('tags')) {
$options = ['group_by' => 'tags'];
} elseif ($tag = $input->getOption('tag')) {
$options = ['tag' => $tag];
} elseif ($name = $input->getArgument('name')) {
$name = $this->findProperServiceName($input, $errorIo, $object, $name, $input->getOption('show-hidden'));
$options = ['id' => $name];
} elseif ($input->getOption('deprecations')) {
$options = ['deprecations' => true];
} else {
$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'] = $kernel->isDebug();
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>)');
} 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()));
}
}
/**
* Validates input arguments and options.
*
* @throws \InvalidArgumentException
*/
protected function validateInput(InputInterface $input)
{
$options = ['tags', 'tag', 'parameters', 'parameter'];
$optionsCount = 0;
foreach ($options as $option) {
if ($input->getOption($option)) {
++$optionsCount;
}
}
$name = $input->getArgument('name');
if ((null !== $name) && ($optionsCount > 0)) {
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 cannot be combined together.');
}
}
private function findProperServiceName(InputInterface $input, SymfonyStyle $io, ContainerBuilder $builder, string $name, bool $showHidden): string
{
$name = ltrim($name, '\\');
if ($builder->has($name) || !$input->isInteractive()) {
return $name;
}
$matchingServices = $this->findServiceIdsContaining($builder, $name, $showHidden);
if (empty($matchingServices)) {
throw new InvalidArgumentException(sprintf('No services found that match "%s".', $name));
}
if (1 === \count($matchingServices)) {
return $matchingServices[0];
}
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(string $serviceId): bool
{
// filter out things that could not be valid class names
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 (str_contains($serviceId, '\\')) {
return true;
}
return class_exists($serviceId) || interface_exists($serviceId, false);
}
}

View File

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

View File

@ -0,0 +1,177 @@
<?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\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.
*
* @author Ryan Weaver <ryan@knpuniversity.com>
*
* @internal
*/
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}
*/
protected function configure()
{
$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(self::$defaultDescription)
->setHelp(<<<'EOF'
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>
You can also pass a search term to filter the list:
<info>php %command.full_name% log</info>
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$errorIo = $io->getErrorStyle();
$builder = $this->getContainerBuilder($this->getApplication()->getKernel());
$serviceIds = $builder->getServiceIds();
$serviceIds = array_filter($serviceIds, [$this, 'filterToServiceTypes']);
if ($search = $input->getArgument('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)) {
$errorIo->error(sprintf('No autowirable classes or interfaces found matching "%s"', $search));
return 1;
}
}
uasort($serviceIds, 'strnatcmp');
$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));
}
$hasAlias = [];
$all = $input->getOption('all');
$previousId = '-';
$serviceIdsNb = 0;
foreach ($serviceIds as $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->newLine();
if (0 < $serviceIdsNb) {
$io->text(sprintf('%s more concrete service%s would be displayed when adding the "--all" option.', $serviceIdsNb, $serviceIdsNb > 1 ? 's' : ''));
}
if ($all) {
$io->text('Pro-tip: use interfaces in your type-hints instead of classes to benefit from the dependency inversion principle.');
}
$io->newLine();
return 0;
}
private function getFileLink(string $class): string
{
if (null === $this->fileLinkFormatter
|| (null === $r = $this->getContainerBuilder($this->getApplication()->getKernel())->getReflectionClass($class, false))) {
return '';
}
return (string) $this->fileLinkFormatter->format($r->getFileName(), $r->getStartLine());
}
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('search')) {
$builder = $this->getContainerBuilder($this->getApplication()->getKernel());
$suggestions->suggestValues(array_filter($builder->getServiceIds(), [$this, 'filterToServiceTypes']));
}
}
}

View File

@ -0,0 +1,164 @@
<?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 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
*/
class EventDispatcherDebugCommand extends Command
{
private const DEFAULT_DISPATCHER = 'event_dispatcher';
protected static $defaultName = 'debug:event-dispatcher';
protected static $defaultDescription = 'Display configured listeners for an application';
private $dispatchers;
public function __construct(ContainerInterface $dispatchers)
{
parent::__construct();
$this->dispatchers = $dispatchers;
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDefinition([
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(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command displays all configured listeners:
<info>php %command.full_name%</info>
To get specific listeners for an event, specify its name:
<info>php %command.full_name% kernel.request</info>
EOF
)
;
}
/**
* {@inheritdoc}
*
* @throws \LogicException
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$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 ($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 0;
} elseif (1 === \count($events)) {
$options = ['event' => $events[array_key_first($events)]];
} else {
$options = ['events' => $events];
}
}
}
$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, $dispatcher, $options);
return 0;
}
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('event')) {
$dispatcherServiceName = $input->getOption('dispatcher');
if ($this->dispatchers->has($dispatcherServiceName)) {
$dispatcher = $this->dispatchers->get($dispatcherServiceName);
$suggestions->suggestValues(array_keys($dispatcher->getListeners()));
}
return;
}
if ($input->mustSuggestOptionValuesFor('dispatcher')) {
if ($this->dispatchers instanceof ServiceProviderInterface) {
$suggestions->suggestValues(array_keys($this->dispatchers->getProvidedServices()));
}
return;
}
if ($input->mustSuggestOptionValuesFor('format')) {
$suggestions->suggestValues((new DescriptorHelper())->getFormats());
}
}
private function searchForEvent(EventDispatcherInterface $dispatcher, string $needle): array
{
$output = [];
$lcNeedle = strtolower($needle);
$allEvents = array_keys($dispatcher->getListeners());
foreach ($allEvents as $event) {
if (str_contains(strtolower($event), $lcNeedle)) {
$output[] = $event;
}
}
return $output;
}
}

View File

@ -0,0 +1,150 @@
<?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\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\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\HttpKernel\Debug\FileLinkFormatter;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RouterInterface;
/**
* A console command for retrieving information about routes.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Tobias Schultze <http://tobion.de>
*
* @final
*/
class RouterDebugCommand extends Command
{
use BuildDebugContainerTrait;
protected static $defaultName = 'debug:router';
protected static $defaultDescription = 'Display current routes for an application';
private $router;
private $fileLinkFormatter;
public function __construct(RouterInterface $router, FileLinkFormatter $fileLinkFormatter = null)
{
parent::__construct();
$this->router = $router;
$this->fileLinkFormatter = $fileLinkFormatter;
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDefinition([
new InputArgument('name', InputArgument::OPTIONAL, 'A route name'),
new InputOption('show-controllers', null, InputOption::VALUE_NONE, 'Show assigned controllers in overview'),
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(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> displays the configured routes:
<info>php %command.full_name%</info>
EOF
)
;
}
/**
* {@inheritdoc}
*
* @throws InvalidArgumentException When route does not exist
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$name = $input->getArgument('name');
$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)) && $matchingRoutes = $this->findRouteNameContaining($name, $routes)) {
$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,
'container' => $container,
]);
} else {
$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 findRouteNameContaining(string $name, RouteCollection $routes): array
{
$foundRoutesNames = [];
foreach ($routes as $routeName => $route) {
if (false !== stripos($routeName, $name)) {
$foundRoutesNames[] = $routeName;
}
}
return $foundRoutesNames;
}
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('name')) {
$suggestions->suggestValues(array_keys($this->router->getRouteCollection()->all()));
return;
}
if ($input->mustSuggestOptionValuesFor('format')) {
$helper = new DescriptorHelper();
$suggestions->suggestValues($helper->getFormats());
}
}
}

View File

@ -0,0 +1,129 @@
<?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\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;
/**
* A console command to test route matching.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
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 iterable<mixed, ExpressionFunctionProviderInterface> $expressionLanguageProviders
*/
public function __construct(RouterInterface $router, iterable $expressionLanguageProviders = [])
{
parent::__construct();
$this->router = $router;
$this->expressionLanguageProviders = $expressionLanguageProviders;
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDefinition([
new InputArgument('path_info', InputArgument::REQUIRED, 'A path info'),
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(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> shows which routes match a given request and which don't and for what reason:
<info>php %command.full_name% /foo</info>
or
<info>php %command.full_name% /foo --method POST --scheme https --host symfony.com --verbose</info>
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$context = $this->router->getContext();
if (null !== $method = $input->getOption('method')) {
$context->setMethod($method);
}
if (null !== $scheme = $input->getOption('scheme')) {
$context->setScheme($scheme);
}
if (null !== $host = $input->getOption('host')) {
$context->setHost($host);
}
$matcher = new TraceableUrlMatcher($this->router->getRouteCollection(), $context);
foreach ($this->expressionLanguageProviders as $provider) {
$matcher->addExpressionLanguageProvider($provider);
}
$traces = $matcher->getTraces($input->getArgument('path_info'));
$io->newLine();
$matches = false;
foreach ($traces as $trace) {
if (TraceableUrlMatcher::ROUTE_ALMOST_MATCHES == $trace['level']) {
$io->text(sprintf('Route <info>"%s"</> almost matches but %s', $trace['name'], lcfirst($trace['log'])));
} elseif (TraceableUrlMatcher::ROUTE_MATCHES == $trace['level']) {
$io->success(sprintf('Route "%s" matches', $trace['name']));
$routerDebugCommand = $this->getApplication()->find('debug:router');
$routerDebugCommand->run(new ArrayInput(['name' => $trace['name']]), $output);
$matches = true;
} elseif ($input->getOption('verbose')) {
$io->text(sprintf('Route "%s" does not match: %s', $trace['name'], $trace['log']));
}
}
if (!$matches) {
$io->error(sprintf('None of the routes match the path "%s"', $input->getArgument('path_info')));
return 1;
}
return 0;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,418 @@
<?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\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\HttpKernel\KernelInterface;
use Symfony\Component\Translation\Catalogue\MergeOperation;
use Symfony\Component\Translation\DataCollectorTranslator;
use Symfony\Component\Translation\Extractor\ExtractorInterface;
use Symfony\Component\Translation\LoggingTranslator;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Reader\TranslationReaderInterface;
use Symfony\Component\Translation\Translator;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* Helps finding unused or missing translation messages in a given locale
* and comparing them with the fallback ones.
*
* @author Florian Voutzinos <florian@voutzinos.com>
*
* @final
*/
class TranslationDebugCommand extends Command
{
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(TranslatorInterface $translator, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultTransPath = null, string $defaultViewsPath = null, array $transPaths = [], array $codePaths = [], array $enabledLocales = [])
{
parent::__construct();
$this->translator = $translator;
$this->reader = $reader;
$this->extractor = $extractor;
$this->defaultTransPath = $defaultTransPath;
$this->defaultViewsPath = $defaultViewsPath;
$this->transPaths = $transPaths;
$this->codePaths = $codePaths;
$this->enabledLocales = $enabledLocales;
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDefinition([
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, '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(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
templates and translation files of a given bundle or the default translations directory.
You can display information about bundle translations in a specific locale:
<info>php %command.full_name% en AcmeDemoBundle</info>
You can also specify a translation domain for the search:
<info>php %command.full_name% --domain=messages en AcmeDemoBundle</info>
You can only display missing messages:
<info>php %command.full_name% --only-missing en AcmeDemoBundle</info>
You can only display unused messages:
<info>php %command.full_name% --only-unused en AcmeDemoBundle</info>
You can display information about application translations in a specific locale:
<info>php %command.full_name% en</info>
You can display information about translations in all registered bundles in a specific locale:
<info>php %command.full_name% --all en</info>
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$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 = $this->getRootTransPaths();
$codePaths = $this->getRootCodePaths($kernel);
// Override with provided Bundle info
if (null !== $input->getArgument('bundle')) {
try {
$bundle = $kernel->getBundle($input->getArgument('bundle'));
$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;
}
if ($this->defaultViewsPath) {
$codePaths[] = $this->defaultViewsPath;
}
} catch (\InvalidArgumentException $e) {
// such a bundle does not exist, so treat the argument as path
$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]));
}
}
} elseif ($input->getOption('all')) {
foreach ($kernel->getBundles() as $bundle) {
$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, $codePaths);
// Load defined messages
$currentCatalogue = $this->loadCurrentMessages($locale, $transPaths);
// Merge defined and extracted messages to get all message ids
$mergeOperation = new MergeOperation($extractedCatalogue, $currentCatalogue);
$allMessages = $mergeOperation->getResult()->all($domain);
if (null !== $domain) {
$allMessages = [$domain => $allMessages];
}
// No defined or extracted messages
if (empty($allMessages) || null !== $domain && empty($allMessages[$domain])) {
$outputMessage = sprintf('No defined or extracted messages for locale "%s"', $locale);
if (null !== $domain) {
$outputMessage .= sprintf(' and domain "%s"', $domain);
}
$io->getErrorStyle()->warning($outputMessage);
return self::EXIT_CODE_GENERAL_ERROR;
}
// Load the fallback catalogues
$fallbackCatalogues = $this->loadFallbackCatalogues($locale, $transPaths);
// Display header line
$headers = ['State', 'Domain', 'Id', sprintf('Message Preview (%s)', $locale)];
foreach ($fallbackCatalogues as $fallbackCatalogue) {
$headers[] = sprintf('Fallback Message Preview (%s)', $fallbackCatalogue->getLocale());
}
$rows = [];
// Iterate all message ids and determine their state
foreach ($allMessages as $domain => $messages) {
foreach (array_keys($messages) as $messageId) {
$value = $currentCatalogue->get($messageId, $domain);
$states = [];
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) && $input->getOption('only-unused')
|| !\in_array(self::MESSAGE_MISSING, $states) && $input->getOption('only-missing')
) {
continue;
}
foreach ($fallbackCatalogues as $fallbackCatalogue) {
if ($fallbackCatalogue->defines($messageId, $domain) && $value === $fallbackCatalogue->get($messageId, $domain)) {
$states[] = self::MESSAGE_EQUALS_FALLBACK;
$exitCode = $exitCode | self::EXIT_CODE_FALLBACK;
break;
}
}
$row = [$this->formatStates($states), $domain, $this->formatId($messageId), $this->sanitizeString($value)];
foreach ($fallbackCatalogues as $fallbackCatalogue) {
$row[] = $this->sanitizeString($fallbackCatalogue->get($messageId, $domain));
}
$rows[] = $row;
}
}
$io->table($headers, $rows);
return $exitCode;
}
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>';
}
if (self::MESSAGE_UNUSED === $state) {
return '<comment> unused </comment>';
}
if (self::MESSAGE_EQUALS_FALLBACK === $state) {
return '<info> fallback </info>';
}
return $state;
}
private function formatStates(array $states): string
{
$result = [];
foreach ($states as $state) {
$result[] = $this->formatState($state);
}
return implode(' ', $result);
}
private function formatId(string $id): string
{
return sprintf('<fg=cyan;options=bold>%s</>', $id);
}
private function sanitizeString(string $string, int $length = 40): string
{
$string = trim(preg_replace('/\s+/', ' ', $string));
if (false !== $encoding = mb_detect_encoding($string, null, true)) {
if (mb_strlen($string, $encoding) > $length) {
return mb_substr($string, 0, $length - 3, $encoding).'...';
}
} elseif (\strlen($string) > $length) {
return substr($string, 0, $length - 3).'...';
}
return $string;
}
private function extractMessages(string $locale, array $transPaths): MessageCatalogue
{
$extractedCatalogue = new MessageCatalogue($locale);
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;
}
/**
* @return MessageCatalogue[]
*/
private function loadFallbackCatalogues(string $locale, array $transPaths): array
{
$fallbackCatalogues = [];
if ($this->translator instanceof Translator || $this->translator instanceof DataCollectorTranslator || $this->translator instanceof LoggingTranslator) {
foreach ($this->translator->getFallbackLocales() as $fallbackLocale) {
if ($fallbackLocale === $locale) {
continue;
}
$fallbackCatalogue = new MessageCatalogue($fallbackLocale);
foreach ($transPaths as $path) {
if (is_dir($path)) {
$this->reader->read($path, $fallbackCatalogue);
}
}
$fallbackCatalogues[] = $fallbackCatalogue;
}
}
return $fallbackCatalogues;
}
private function getRootTransPaths(): array
{
$transPaths = $this->transPaths;
if ($this->defaultTransPath) {
$transPaths[] = $this->defaultTransPath;
}
return $transPaths;
}
private function getRootCodePaths(KernelInterface $kernel): array
{
$codePaths = $this->codePaths;
$codePaths[] = $kernel->getProjectDir().'/src';
if ($this->defaultViewsPath) {
$codePaths[] = $this->defaultViewsPath;
}
return $codePaths;
}
}

View File

@ -0,0 +1,451 @@
<?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\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;
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;
/**
* A command that parses templates to extract translation messages and adds them
* into the translation files.
*
* @author Michel Salib <michelsalib@hotmail.com>
*
* @final
*/
class TranslationUpdateCommand extends Command
{
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;
private $extractor;
private $defaultLocale;
private $defaultTransPath;
private $defaultViewsPath;
private $transPaths;
private $codePaths;
private $enabledLocales;
public function __construct(TranslationWriterInterface $writer, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultLocale, string $defaultTransPath = null, string $defaultViewsPath = null, array $transPaths = [], array $codePaths = [], array $enabledLocales = [])
{
parent::__construct();
$this->writer = $writer;
$this->reader = $reader;
$this->extractor = $extractor;
$this->defaultLocale = $defaultLocale;
$this->defaultTransPath = $defaultTransPath;
$this->defaultViewsPath = $defaultViewsPath;
$this->transPaths = $transPaths;
$this->codePaths = $codePaths;
$this->enabledLocales = $enabledLocales;
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDefinition([
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('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 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 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(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.
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
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$errorIo = $output instanceof ConsoleOutputInterface ? new SymfonyStyle($input, $output->getErrorOutput()) : $io;
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);
$errorIo = $io->getErrorStyle();
// check presence of force or dump-message
if (true !== $input->getOption('force') && true !== $input->getOption('dump-messages')) {
$errorIo->error('You must choose one of --force or --dump-messages');
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($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 = $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'));
$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;
}
if ($this->defaultViewsPath) {
$codePaths[] = $this->defaultViewsPath;
}
$currentName = $foundBundle->getName();
} catch (\InvalidArgumentException $e) {
// such a bundle does not exist, so treat the argument as path
$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]));
}
}
}
$io->title('Translation Messages Extractor and Dumper');
$io->comment(sprintf('Generating "<info>%s</info>" translation files for "<info>%s</info>"', $input->getArgument('locale'), $currentName));
$io->comment('Parsing templates...');
$extractedCatalogue = $this->extractMessages($input->getArgument('locale'), $codePaths, $input->getOption('prefix'));
$io->comment('Loading translation files...');
$currentCatalogue = $this->loadCurrentMessages($input->getArgument('locale'), $transPaths);
if (null !== $domain = $input->getOption('domain')) {
$currentCatalogue = $this->filterCatalogue($currentCatalogue, $domain);
$extractedCatalogue = $this->filterCatalogue($extractedCatalogue, $domain);
}
// process catalogues
$operation = $input->getOption('clean')
? new TargetOperation($currentCatalogue, $extractedCatalogue)
: new MergeOperation($currentCatalogue, $extractedCatalogue);
// Exit if no messages found.
if (!\count($operation->getDomains())) {
$errorIo->warning('No translation messages were found.');
return 0;
}
$resultMessage = 'Translation files were successfully updated';
$operation->moveMessagesToIntlDomainsIfPossible('new');
// show compiled list of messages
if (true === $input->getOption('dump-messages')) {
$extractedMessagesCount = 0;
$io->newLine();
foreach ($operation->getDomains() as $domain) {
$newKeys = array_keys($operation->getNewMessages($domain));
$allKeys = array_keys($operation->getMessages($domain));
$list = array_merge(
array_diff($allKeys, $newKeys),
array_map(function ($id) {
return sprintf('<fg=green>%s</>', $id);
}, $newKeys),
array_map(function ($id) {
return sprintf('<fg=red>%s</>', $id);
}, array_keys($operation->getObsoleteMessages($domain)))
);
$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' === $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');
}
// save the files
if (true === $input->getOption('force')) {
$io->comment('Writing files...');
$bundleTransPath = false;
foreach ($transPaths as $path) {
if (is_dir($path)) {
$bundleTransPath = $path;
}
}
if (!$bundleTransPath) {
$bundleTransPath = end($transPaths);
}
$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';
}
}
$io->success($resultMessage.'.');
return 0;
}
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());
// 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);
}
}
return $filteredCatalogue;
}
private function extractMessages(string $locale, array $transPaths, string $prefix): MessageCatalogue
{
$extractedCatalogue = new MessageCatalogue($locale);
$this->extractor->setPrefix($prefix);
foreach ($transPaths as $path) {
if (is_dir($path) || is_file($path)) {
$this->extractor->extract($path, $extractedCatalogue);
}
}
return $extractedCatalogue;
}
private function loadCurrentMessages(string $locale, array $transPaths): MessageCatalogue
{
$currentCatalogue = new MessageCatalogue($locale);
foreach ($transPaths as $path) {
if (is_dir($path)) {
$this->reader->read($path, $currentCatalogue);
}
}
return $currentCatalogue;
}
private function getRootTransPaths(): array
{
$transPaths = $this->transPaths;
if ($this->defaultTransPath) {
$transPaths[] = $this->defaultTransPath;
}
return $transPaths;
}
private function getRootCodePaths(KernelInterface $kernel): array
{
$codePaths = $this->codePaths;
$codePaths[] = $kernel->getProjectDir().'/src';
if ($this->defaultViewsPath) {
$codePaths[] = $this->defaultViewsPath;
}
return $codePaths;
}
}

View File

@ -0,0 +1,148 @@
<?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\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
*/
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}
*/
protected function configure()
{
$this
->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(self::$defaultDescription)
->setHelp(<<<'EOF'
The <info>%command.name%</info> command dumps the graphical representation of a
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
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$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();
foreach ($input->getArgument('marking') as $place) {
$marking->mark($place);
}
$options = [
'name' => $workflowName,
'nofooter' => true,
'graph' => [
'label' => $input->getOption('label'),
],
];
$output->writeln($dumper->dump($workflow, $marking, $options));
return 0;
}
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('name')) {
$suggestions->suggestValues(array_keys($this->workflows));
}
if ($input->mustSuggestOptionValuesFor('dump-format')) {
$suggestions->suggestValues(self::DUMP_FORMAT_OPTIONS);
}
}
}

View File

@ -0,0 +1,63 @@
<?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\Translation\Command\XliffLintCommand as BaseLintCommand;
/**
* Validates XLIFF files syntax and outputs encountered errors.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
* @author Robin Chalas <robin.chalas@gmail.com>
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
*
* @final
*/
class XliffLintCommand extends BaseLintCommand
{
protected static $defaultName = 'lint:xliff';
protected static $defaultDescription = 'Lints an XLIFF file and outputs encountered errors';
public function __construct()
{
$directoryIteratorProvider = function ($directory, $default) {
if (!is_dir($directory)) {
$directory = $this->getApplication()->getKernel()->locateResource($directory);
}
return $default($directory);
};
$isReadableProvider = function ($fileOrDirectory, $default) {
return str_starts_with($fileOrDirectory, '@') || $default($fileOrDirectory);
};
parent::__construct(null, $directoryIteratorProvider, $isReadableProvider);
}
/**
* {@inheritdoc}
*/
protected function configure()
{
parent::configure();
$this->setHelp($this->getHelp().<<<'EOF'
Or find all files in a bundle:
<info>php %command.full_name% @AcmeDemoBundle</info>
EOF
);
}
}

View File

@ -0,0 +1,62 @@
<?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\Yaml\Command\LintCommand as BaseLintCommand;
/**
* Validates YAML files syntax and outputs encountered errors.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
* @author Robin Chalas <robin.chalas@gmail.com>
*
* @final
*/
class YamlLintCommand extends BaseLintCommand
{
protected static $defaultName = 'lint:yaml';
protected static $defaultDescription = 'Lint a YAML file and outputs encountered errors';
public function __construct()
{
$directoryIteratorProvider = function ($directory, $default) {
if (!is_dir($directory)) {
$directory = $this->getApplication()->getKernel()->locateResource($directory);
}
return $default($directory);
};
$isReadableProvider = function ($fileOrDirectory, $default) {
return str_starts_with($fileOrDirectory, '@') || $default($fileOrDirectory);
};
parent::__construct(null, $directoryIteratorProvider, $isReadableProvider);
}
/**
* {@inheritdoc}
*/
protected function configure()
{
parent::configure();
$this->setHelp($this->getHelp().<<<'EOF'
Or find all files in a bundle:
<info>php %command.full_name% @AcmeDemoBundle</info>
EOF
);
}
}

View File

@ -0,0 +1,212 @@
<?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\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\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\HttpKernel\KernelInterface;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class Application extends BaseApplication
{
private $kernel;
private $commandsRegistered = false;
private $registrationErrors = [];
public function __construct(KernelInterface $kernel)
{
$this->kernel = $kernel;
parent::__construct('Symfony', Kernel::VERSION);
$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, 'Switch off debug mode.'));
}
/**
* Gets the Kernel associated with this Console.
*
* @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.
*
* @return int 0 if everything went fine, or an error code
*/
public function doRun(InputInterface $input, OutputInterface $output)
{
$this->registerCommands();
if ($this->registrationErrors) {
$this->renderRegistrationErrors($input, $output);
}
$this->setDispatcher($this->kernel->getContainer()->get('event_dispatcher'));
return parent::doRun($input, $output);
}
/**
* {@inheritdoc}
*/
protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
{
if (!$command instanceof ListCommand) {
if ($this->registrationErrors) {
$this->renderRegistrationErrors($input, $output);
$this->registrationErrors = [];
}
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(string $name)
{
$this->registerCommands();
return parent::find($name);
}
/**
* {@inheritdoc}
*/
public function get(string $name)
{
$this->registerCommands();
$command = parent::get($name);
if ($command instanceof ContainerAwareInterface) {
$command->setContainer($this->kernel->getContainer());
}
return $command;
}
/**
* {@inheritdoc}
*/
public function all(string $namespace = null)
{
$this->registerCommands();
return parent::all($namespace);
}
/**
* {@inheritdoc}
*/
public function getLongVersion()
{
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)
{
$this->registerCommands();
return parent::add($command);
}
protected function registerCommands()
{
if ($this->commandsRegistered) {
return;
}
$this->commandsRegistered = true;
$this->kernel->boot();
$container = $this->kernel->getContainer();
foreach ($this->kernel->getBundles() as $bundle) {
if ($bundle instanceof Bundle) {
try {
$bundle->registerCommands($this);
} catch (\Throwable $e) {
$this->registrationErrors[] = $e;
}
}
}
if ($container->has('console.command_loader')) {
$this->setCommandLoader($container->get('console.command_loader'));
}
if ($container->hasParameter('console.command.ids')) {
$lazyCommandIds = $container->hasParameter('console.lazy_command.ids') ? $container->getParameter('console.lazy_command.ids') : [];
foreach ($container->getParameter('console.command.ids') as $id) {
if (!isset($lazyCommandIds[$id])) {
try {
$this->add($container->get($id));
} catch (\Throwable $e) {
$this->registrationErrors[] = $e;
}
}
}
}
}
private function renderRegistrationErrors(InputInterface $input, OutputInterface $output)
{
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
(new SymfonyStyle($input, $output))->warning('Some commands could not be registered:');
foreach ($this->registrationErrors as $error) {
$this->doRenderThrowable($error, $output);
}
}
}

View File

@ -0,0 +1,376 @@
<?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\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;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
abstract class Descriptor implements DescriptorInterface
{
/**
* @var OutputInterface
*/
protected $output;
/**
* {@inheritdoc}
*/
public function describe(OutputInterface $output, $object, array $options = [])
{
$this->output = $output;
switch (true) {
case $object instanceof RouteCollection:
$this->describeRouteCollection($object, $options);
break;
case $object instanceof Route:
$this->describeRoute($object, $options);
break;
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;
case $object instanceof ContainerBuilder && isset($options['id']):
$this->describeContainerService($this->resolveServiceDefinition($object, $options['id']), $options, $object);
break;
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;
case $object instanceof Definition:
$this->describeContainerDefinition($object, $options);
break;
case $object instanceof Alias:
$this->describeContainerAlias($object, $options);
break;
case $object instanceof EventDispatcherInterface:
$this->describeEventDispatcherListeners($object, $options);
break;
case \is_callable($object):
$this->describeCallable($object, $options);
break;
default:
throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_debug_type($object)));
}
}
protected function getOutput(): OutputInterface
{
return $this->output;
}
protected function write(string $content, bool $decorated = false)
{
$this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW);
}
abstract protected function describeRouteCollection(RouteCollection $routes, array $options = []);
abstract protected function describeRoute(Route $route, array $options = []);
abstract protected function describeContainerParameters(ParameterBag $parameters, array $options = []);
abstract protected function describeContainerTags(ContainerBuilder $builder, array $options = []);
/**
* Describes a container service by its name.
*
* Common options are:
* * name: name of described service
*
* @param Definition|Alias|object $service
*/
abstract protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null);
/**
* Describes container services.
*
* Common options are:
* * tag: filters described services by given tag
*/
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.
*
* Common options are:
* * name: name of listened event
*/
abstract protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []);
/**
* Describes a callable.
*
* @param mixed $callable
*/
abstract protected function describeCallable($callable, array $options = []);
/**
* Formats a value as string.
*
* @param mixed $value
*/
protected function formatValue($value): string
{
if (\is_object($value)) {
return sprintf('object(%s)', \get_class($value));
}
if (\is_string($value)) {
return $value;
}
return preg_replace("/\n\s*/s", '', var_export($value, true));
}
/**
* Formats a parameter.
*
* @param mixed $value
*/
protected function formatParameter($value): string
{
if ($value instanceof \UnitEnum) {
return 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 = var_export($value, true);
}
});
}
if (\is_bool($value) || \is_array($value) || (null === $value)) {
$jsonString = json_encode($value);
if (preg_match('/^(.{60})./us', $jsonString, $matches)) {
return $matches[1].'...';
}
return $jsonString;
}
return (string) $value;
}
/**
* @return mixed
*/
protected function resolveServiceDefinition(ContainerBuilder $builder, string $serviceId)
{
if ($builder->hasDefinition($serviceId)) {
return $builder->getDefinition($serviceId);
}
// Some service IDs don't have a Definition, they're aliases
if ($builder->hasAlias($serviceId)) {
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);
}
protected function findDefinitionsByTag(ContainerBuilder $builder, bool $showHidden): array
{
$definitions = [];
$tags = $builder->findTags();
asort($tags);
foreach ($tags as $tag) {
foreach ($builder->findTaggedServiceIds($tag) as $serviceId => $attributes) {
$definition = $this->resolveServiceDefinition($builder, $serviceId);
if ($showHidden xor '.' === ($serviceId[0] ?? null)) {
continue;
}
if (!isset($definitions[$tag])) {
$definitions[$tag] = [];
}
$definitions[$tag][$serviceId] = $definition;
}
}
return $definitions;
}
protected function sortParameters(ParameterBag $parameters)
{
$parameters = $parameters->all();
ksort($parameters);
return $parameters;
}
protected function sortServiceIds(array $serviceIds)
{
asort($serviceIds);
return $serviceIds;
}
protected function sortTaggedServicesByPriority(array $services): array
{
$maxPriority = [];
foreach ($services as $service => $tags) {
$maxPriority[$service] = \PHP_INT_MIN;
foreach ($tags as $tag) {
$currentPriority = $tag['priority'] ?? 0;
if ($maxPriority[$service] < $currentPriority) {
$maxPriority[$service] = $currentPriority;
}
}
}
uasort($maxPriority, function ($a, $b) {
return $b <=> $a;
});
return array_keys($maxPriority);
}
protected function sortTagsByPriority(array $tags): array
{
$sortedTags = [];
foreach ($tags as $tagName => $tag) {
$sortedTags[$tagName] = $this->sortByPriority($tag);
}
return $sortedTags;
}
protected function sortByPriority(array $tag): array
{
usort($tag, function ($a, $b) {
return ($b['priority'] ?? 0) <=> ($a['priority'] ?? 0);
});
return $tag;
}
public static function getClassDescription(string $class, string &$resolvedClass = null): string
{
$resolvedClass = $class;
try {
$resource = new ClassExistenceResource($class, false);
// isFresh() will explode ONLY if a parent class/trait does not exist
$resource->isFresh(0);
$r = new \ReflectionClass($class);
$resolvedClass = $r->name;
if ($docComment = $r->getDocComment()) {
$docComment = preg_split('#\n\s*\*\s*[\n@]#', substr($docComment, 3, -2), 2)[0];
return trim(preg_replace('#\s*\n\s*\*\s*#', ' ', $docComment));
}
} catch (\ReflectionException $e) {
}
return '';
}
private function getContainerEnvVars(ContainerBuilder $container): array
{
if (!$container->hasParameter('debug.container.dump')) {
return [];
}
if (!is_file($container->getParameter('debug.container.dump'))) {
return [];
}
$file = file_get_contents($container->getParameter('debug.container.dump'));
preg_match_all('{%env\(((?:\w++:)*+\w++)\)%}', $file, $envVars);
$envVars = array_unique($envVars[1]);
$bag = $container->getParameterBag();
$getDefaultParameter = function (string $name) {
return parent::get($name);
};
$getDefaultParameter = $getDefaultParameter->bindTo($bag, \get_class($bag));
$getEnvReflection = new \ReflectionMethod($container, 'getEnv');
$getEnvReflection->setAccessible(true);
$envs = [];
foreach ($envVars as $env) {
$processor = 'string';
if (false !== $i = strrpos($name = $env, ':')) {
$name = substr($env, $i + 1);
$processor = substr($env, 0, $i);
}
$defaultValue = ($hasDefault = $container->hasParameter("env($name)")) ? $getDefaultParameter("env($name)") : null;
if (false === ($runtimeValue = $_ENV[$name] ?? $_SERVER[$name] ?? getenv($name))) {
$runtimeValue = null;
}
$processedValue = ($hasRuntime = null !== $runtimeValue) || $hasDefault ? $getEnvReflection->invoke($container, $env) : null;
$envs["$name$processor"] = [
'name' => $name,
'processor' => $processor,
'default_available' => $hasDefault,
'default_value' => $defaultValue,
'runtime_available' => $hasRuntime,
'runtime_value' => $runtimeValue,
'processed_value' => $processedValue,
];
}
ksort($envs);
return array_values($envs);
}
}

View File

@ -0,0 +1,420 @@
<?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\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;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class JsonDescriptor extends Descriptor
{
protected function describeRouteCollection(RouteCollection $routes, array $options = [])
{
$data = [];
foreach ($routes->all() as $name => $route) {
$data[$name] = $this->getRouteData($route);
}
$this->writeData($data, $options);
}
protected function describeRoute(Route $route, array $options = [])
{
$this->writeData($this->getRouteData($route), $options);
}
protected function describeContainerParameters(ParameterBag $parameters, array $options = [])
{
$this->writeData($this->sortParameters($parameters), $options);
}
protected function describeContainerTags(ContainerBuilder $builder, array $options = [])
{
$showHidden = isset($options['show_hidden']) && $options['show_hidden'];
$data = [];
foreach ($this->findDefinitionsByTag($builder, $showHidden) as $tag => $definitions) {
$data[$tag] = [];
foreach ($definitions as $definition) {
$data[$tag][] = $this->getContainerDefinitionData($definition, true);
}
}
$this->writeData($data, $options);
}
protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null)
{
if (!isset($options['id'])) {
throw new \InvalidArgumentException('An "id" option must be provided.');
}
if ($service instanceof Alias) {
$this->describeContainerAlias($service, $options, $builder);
} elseif ($service instanceof Definition) {
$this->writeData($this->getContainerDefinitionData($service, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments']), $options);
} else {
$this->writeData(\get_class($service), $options);
}
}
protected function describeContainerServices(ContainerBuilder $builder, array $options = [])
{
$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' => []];
if (isset($options['filter'])) {
$serviceIds = array_filter($serviceIds, $options['filter']);
}
foreach ($serviceIds as $serviceId) {
$service = $this->resolveServiceDefinition($builder, $serviceId);
if ($showHidden xor '.' === ($serviceId[0] ?? null)) {
continue;
}
if ($service instanceof Alias) {
$data['aliases'][$serviceId] = $this->getContainerAliasData($service);
} elseif ($service instanceof Definition) {
$data['definitions'][$serviceId] = $this->getContainerDefinitionData($service, $omitTags, $showArguments);
} else {
$data['services'][$serviceId] = \get_class($service);
}
}
$this->writeData($data, $options);
}
protected function describeContainerDefinition(Definition $definition, array $options = [])
{
$this->writeData($this->getContainerDefinitionData($definition, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments']), $options);
}
protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null)
{
if (!$builder) {
$this->writeData($this->getContainerAliasData($alias), $options);
return;
}
$this->writeData(
[$this->getContainerAliasData($alias), $this->getContainerDefinitionData($builder->getDefinition((string) $alias), isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments'])],
array_merge($options, ['id' => (string) $alias])
);
}
protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = [])
{
$this->writeData($this->getEventDispatcherListenersData($eventDispatcher, $options), $options);
}
protected function describeCallable($callable, array $options = [])
{
$this->writeData($this->getCallableData($callable), $options);
}
protected function describeContainerParameter($parameter, array $options = [])
{
$key = $options['parameter'] ?? '';
$this->writeData([$key => $parameter], $options);
}
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 = $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 = var_export($value, true);
}
});
$this->write(json_encode($data, $flags | \JSON_PRETTY_PRINT)."\n");
}
protected function getRouteData(Route $route): array
{
$data = [
'path' => $route->getPath(),
'pathRegex' => $route->compile()->getRegex(),
'host' => '' !== $route->getHost() ? $route->getHost() : 'ANY',
'hostRegex' => '' !== $route->getHost() ? $route->compile()->getHostRegex() : '',
'scheme' => $route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY',
'method' => $route->getMethods() ? implode('|', $route->getMethods()) : 'ANY',
'class' => \get_class($route),
'defaults' => $route->getDefaults(),
'requirements' => $route->getRequirements() ?: 'NO CUSTOM',
'options' => $route->getOptions(),
];
if ('' !== $route->getCondition()) {
$data['condition'] = $route->getCondition();
}
return $data;
}
private function getContainerDefinitionData(Definition $definition, bool $omitTags = false, bool $showArguments = false): array
{
$data = [
'class' => (string) $definition->getClass(),
'public' => $definition->isPublic() && !$definition->isPrivate(),
'synthetic' => $definition->isSynthetic(),
'lazy' => $definition->isLazy(),
'shared' => $definition->isShared(),
'abstract' => $definition->isAbstract(),
'autowire' => $definition->isAutowired(),
'autoconfigure' => $definition->isAutoconfigured(),
];
if ('' !== $classDescription = $this->getClassDescription((string) $definition->getClass())) {
$data['description'] = $classDescription;
}
if ($showArguments) {
$data['arguments'] = $this->describeValue($definition->getArguments(), $omitTags, $showArguments);
}
$data['file'] = $definition->getFile();
if ($factory = $definition->getFactory()) {
if (\is_array($factory)) {
if ($factory[0] instanceof Reference) {
$data['factory_service'] = (string) $factory[0];
} elseif ($factory[0] instanceof Definition) {
throw new \InvalidArgumentException('Factory is not describable.');
} else {
$data['factory_class'] = $factory[0];
}
$data['factory_method'] = $factory[1];
} else {
$data['factory_function'] = $factory;
}
}
$calls = $definition->getMethodCalls();
if (\count($calls) > 0) {
$data['calls'] = [];
foreach ($calls as $callData) {
$data['calls'][] = $callData[0];
}
}
if (!$omitTags) {
$data['tags'] = [];
foreach ($this->sortTagsByPriority($definition->getTags()) as $tagName => $tagData) {
foreach ($tagData as $parameters) {
$data['tags'][] = ['name' => $tagName, 'parameters' => $parameters];
}
}
}
return $data;
}
private function getContainerAliasData(Alias $alias): array
{
return [
'service' => (string) $alias,
'public' => $alias->isPublic() && !$alias->isPrivate(),
];
}
private function getEventDispatcherListenersData(EventDispatcherInterface $eventDispatcher, array $options): array
{
$data = [];
$event = \array_key_exists('event', $options) ? $options['event'] : null;
if (null !== $event) {
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) {
foreach ($eventListeners as $eventListener) {
$l = $this->getCallableData($eventListener);
$l['priority'] = $eventDispatcher->getListenerPriority($eventListened, $eventListener);
$data[$eventListened][] = $l;
}
}
}
return $data;
}
private function getCallableData($callable): array
{
$data = [];
if (\is_array($callable)) {
$data['type'] = 'function';
if (\is_object($callable[0])) {
$data['name'] = $callable[1];
$data['class'] = \get_class($callable[0]);
} else {
if (!str_starts_with($callable[1], 'parent::')) {
$data['name'] = $callable[1];
$data['class'] = $callable[0];
$data['static'] = true;
} else {
$data['name'] = substr($callable[1], 8);
$data['class'] = $callable[0];
$data['static'] = true;
$data['parent'] = true;
}
}
return $data;
}
if (\is_string($callable)) {
$data['type'] = 'function';
if (!str_contains($callable, '::')) {
$data['name'] = $callable;
} else {
$callableParts = explode('::', $callable);
$data['name'] = $callableParts[1];
$data['class'] = $callableParts[0];
$data['static'] = true;
}
return $data;
}
if ($callable instanceof \Closure) {
$data['type'] = 'closure';
$r = new \ReflectionFunction($callable);
if (str_contains($r->name, '{closure}')) {
return $data;
}
$data['name'] = $r->name;
if ($class = $r->getClosureScopeClass()) {
$data['class'] = $class->name;
if (!$r->getClosureThis()) {
$data['static'] = true;
}
}
return $data;
}
if (method_exists($callable, '__invoke')) {
$data['type'] = 'object';
$data['name'] = \get_class($callable);
return $data;
}
throw new \InvalidArgumentException('Callable is not describable.');
}
private function describeValue($value, bool $omitTags, bool $showArguments)
{
if (\is_array($value)) {
$data = [];
foreach ($value as $k => $v) {
$data[$k] = $this->describeValue($v, $omitTags, $showArguments);
}
return $data;
}
if ($value instanceof ServiceClosureArgument) {
$value = $value->getValues()[0];
}
if ($value instanceof Reference) {
return [
'type' => 'service',
'id' => (string) $value,
];
}
if ($value instanceof AbstractArgument) {
return ['type' => 'abstract', 'text' => $value->getText()];
}
if ($value instanceof ArgumentInterface) {
return $this->describeValue($value->getValues(), $omitTags, $showArguments);
}
if ($value instanceof Definition) {
return $this->getContainerDefinitionData($value, $omitTags, $showArguments);
}
return $value;
}
}

View File

@ -0,0 +1,414 @@
<?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\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;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class MarkdownDescriptor extends Descriptor
{
protected function describeRouteCollection(RouteCollection $routes, array $options = [])
{
$first = true;
foreach ($routes->all() as $name => $route) {
if ($first) {
$first = false;
} else {
$this->write("\n\n");
}
$this->describeRoute($route, ['name' => $name]);
}
$this->write("\n");
}
protected function describeRoute(Route $route, array $options = [])
{
$output = '- Path: '.$route->getPath()
."\n".'- Path Regex: '.$route->compile()->getRegex()
."\n".'- Host: '.('' !== $route->getHost() ? $route->getHost() : 'ANY')
."\n".'- Host Regex: '.('' !== $route->getHost() ? $route->compile()->getHostRegex() : '')
."\n".'- Scheme: '.($route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY')
."\n".'- Method: '.($route->getMethods() ? implode('|', $route->getMethods()) : 'ANY')
."\n".'- Class: '.\get_class($route)
."\n".'- Defaults: '.$this->formatRouterConfig($route->getDefaults())
."\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);
$this->write("\n");
}
protected function describeContainerParameters(ParameterBag $parameters, array $options = [])
{
$this->write("Container parameters\n====================\n");
foreach ($this->sortParameters($parameters) as $key => $value) {
$this->write(sprintf("\n- `%s`: `%s`", $key, $this->formatParameter($value)));
}
}
protected function describeContainerTags(ContainerBuilder $builder, array $options = [])
{
$showHidden = isset($options['show_hidden']) && $options['show_hidden'];
$this->write("Container tags\n==============");
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");
$this->describeContainerDefinition($definition, ['omit_tags' => true, 'id' => $serviceId]);
}
}
}
protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null)
{
if (!isset($options['id'])) {
throw new \InvalidArgumentException('An "id" option must be provided.');
}
$childOptions = array_merge($options, ['id' => $options['id'], 'as_array' => true]);
if ($service instanceof Alias) {
$this->describeContainerAlias($service, $childOptions, $builder);
} elseif ($service instanceof Definition) {
$this->describeContainerDefinition($service, $childOptions);
} else {
$this->write(sprintf('**`%s`:** `%s`', $options['id'], \get_class($service)));
}
}
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 = [])
{
$showHidden = isset($options['show_hidden']) && $options['show_hidden'];
$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']
? $this->sortTaggedServicesByPriority($builder->findTaggedServiceIds($options['tag']))
: $this->sortServiceIds($builder->getServiceIds());
$showArguments = isset($options['show_arguments']) && $options['show_arguments'];
$services = ['definitions' => [], 'aliases' => [], 'services' => []];
if (isset($options['filter'])) {
$serviceIds = array_filter($serviceIds, $options['filter']);
}
foreach ($serviceIds as $serviceId) {
$service = $this->resolveServiceDefinition($builder, $serviceId);
if ($showHidden xor '.' === ($serviceId[0] ?? null)) {
continue;
}
if ($service instanceof Alias) {
$services['aliases'][$serviceId] = $service;
} elseif ($service instanceof Definition) {
$services['definitions'][$serviceId] = $service;
} else {
$services['services'][$serviceId] = $service;
}
}
if (!empty($services['definitions'])) {
$this->write("\n\nDefinitions\n-----------\n");
foreach ($services['definitions'] as $id => $service) {
$this->write("\n");
$this->describeContainerDefinition($service, ['id' => $id, 'show_arguments' => $showArguments]);
}
}
if (!empty($services['aliases'])) {
$this->write("\n\nAliases\n-------\n");
foreach ($services['aliases'] as $id => $service) {
$this->write("\n");
$this->describeContainerAlias($service, ['id' => $id]);
}
}
if (!empty($services['services'])) {
$this->write("\n\nServices\n--------\n");
foreach ($services['services'] as $id => $service) {
$this->write("\n");
$this->write(sprintf('- `%s`: `%s`', $id, \get_class($service)));
}
}
}
protected function describeContainerDefinition(Definition $definition, array $options = [])
{
$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')
."\n".'- Shared: '.($definition->isShared() ? 'yes' : 'no')
."\n".'- Abstract: '.($definition->isAbstract() ? 'yes' : 'no')
."\n".'- Autowired: '.($definition->isAutowired() ? 'yes' : 'no')
."\n".'- Autoconfigured: '.($definition->isAutoconfigured() ? 'yes' : 'no')
;
if (isset($options['show_arguments']) && $options['show_arguments']) {
$output .= "\n".'- Arguments: '.($definition->getArguments() ? 'yes' : 'no');
}
if ($definition->getFile()) {
$output .= "\n".'- File: `'.$definition->getFile().'`';
}
if ($factory = $definition->getFactory()) {
if (\is_array($factory)) {
if ($factory[0] instanceof Reference) {
$output .= "\n".'- Factory Service: `'.$factory[0].'`';
} elseif ($factory[0] instanceof Definition) {
throw new \InvalidArgumentException('Factory is not describable.');
} else {
$output .= "\n".'- Factory Class: `'.$factory[0].'`';
}
$output .= "\n".'- Factory Method: `'.$factory[1].'`';
} else {
$output .= "\n".'- Factory Function: `'.$factory.'`';
}
}
$calls = $definition->getMethodCalls();
foreach ($calls as $callData) {
$output .= "\n".'- Call: `'.$callData[0].'`';
}
if (!(isset($options['omit_tags']) && $options['omit_tags'])) {
foreach ($this->sortTagsByPriority($definition->getTags()) as $tagName => $tagData) {
foreach ($tagData as $parameters) {
$output .= "\n".'- Tag: `'.$tagName.'`';
foreach ($parameters as $name => $value) {
$output .= "\n".' - '.ucfirst($name).': '.$value;
}
}
}
}
$this->write(isset($options['id']) ? sprintf("### %s\n\n%s\n", $options['id'], $output) : $output);
}
protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null)
{
$output = '- Service: `'.$alias.'`'
."\n".'- Public: '.($alias->isPublic() && !$alias->isPrivate() ? 'yes' : 'no');
if (!isset($options['id'])) {
$this->write($output);
return;
}
$this->write(sprintf("### %s\n\n%s\n", $options['id'], $output));
if (!$builder) {
return;
}
$this->write("\n");
$this->describeContainerDefinition($builder->getDefinition((string) $alias), array_merge($options, ['id' => (string) $alias]));
}
protected function describeContainerParameter($parameter, array $options = [])
{
$this->write(isset($options['parameter']) ? sprintf("%s\n%s\n\n%s", $options['parameter'], str_repeat('=', \strlen($options['parameter'])), $this->formatParameter($parameter)) : $parameter);
}
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 = $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");
if (null !== $event) {
foreach ($registeredListeners as $order => $listener) {
$this->write("\n".sprintf('## Listener %d', $order + 1)."\n");
$this->describeCallable($listener);
$this->write(sprintf('- Priority: `%d`', $eventDispatcher->getListenerPriority($event, $listener))."\n");
}
} else {
ksort($registeredListeners);
foreach ($registeredListeners as $eventListened => $eventListeners) {
$this->write("\n".sprintf('## %s', $eventListened)."\n");
foreach ($eventListeners as $order => $eventListener) {
$this->write("\n".sprintf('### Listener %d', $order + 1)."\n");
$this->describeCallable($eventListener);
$this->write(sprintf('- Priority: `%d`', $eventDispatcher->getListenerPriority($eventListened, $eventListener))."\n");
}
}
}
}
protected function describeCallable($callable, array $options = [])
{
$string = '';
if (\is_array($callable)) {
$string .= "\n- Type: `function`";
if (\is_object($callable[0])) {
$string .= "\n".sprintf('- Name: `%s`', $callable[1]);
$string .= "\n".sprintf('- Class: `%s`', \get_class($callable[0]));
} else {
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";
} else {
$string .= "\n".sprintf('- Name: `%s`', substr($callable[1], 8));
$string .= "\n".sprintf('- Class: `%s`', $callable[0]);
$string .= "\n- Static: yes";
$string .= "\n- Parent: yes";
}
}
return $this->write($string."\n");
}
if (\is_string($callable)) {
$string .= "\n- Type: `function`";
if (!str_contains($callable, '::')) {
$string .= "\n".sprintf('- Name: `%s`', $callable);
} else {
$callableParts = explode('::', $callable);
$string .= "\n".sprintf('- Name: `%s`', $callableParts[1]);
$string .= "\n".sprintf('- Class: `%s`', $callableParts[0]);
$string .= "\n- Static: yes";
}
return $this->write($string."\n");
}
if ($callable instanceof \Closure) {
$string .= "\n- Type: `closure`";
$r = new \ReflectionFunction($callable);
if (str_contains($r->name, '{closure}')) {
return $this->write($string."\n");
}
$string .= "\n".sprintf('- Name: `%s`', $r->name);
if ($class = $r->getClosureScopeClass()) {
$string .= "\n".sprintf('- Class: `%s`', $class->name);
if (!$r->getClosureThis()) {
$string .= "\n- Static: yes";
}
}
return $this->write($string."\n");
}
if (method_exists($callable, '__invoke')) {
$string .= "\n- Type: `object`";
$string .= "\n".sprintf('- Name: `%s`', \get_class($callable));
return $this->write($string."\n");
}
throw new \InvalidArgumentException('Callable is not describable.');
}
private function formatRouterConfig(array $array): string
{
if (!$array) {
return 'NONE';
}
$string = '';
ksort($array);
foreach ($array as $name => $value) {
$string .= "\n".' - `'.$name.'`: '.$this->formatValue($value);
}
return $string;
}
}

View File

@ -0,0 +1,638 @@
<?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\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;
/**
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
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'];
$tableHeaders = ['Name', 'Method', 'Scheme', 'Host', 'Path'];
if ($showControllers) {
$tableHeaders[] = 'Controller';
}
$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',
$this->formatControllerLink($controller, $route->getPath(), $options['container'] ?? null),
];
if ($showControllers) {
$row[] = $controller ? $this->formatControllerLink($controller, $this->formatCallable($controller), $options['container'] ?? null) : '';
}
$tableRows[] = $row;
}
if (isset($options['output'])) {
$options['output']->table($tableHeaders, $tableRows);
} else {
$table = new Table($this->getOutput());
$table->setHeaders($tableHeaders)->setRows($tableRows);
$table->render();
}
}
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', $options['name'] ?? ''],
['Path', $route->getPath()],
['Path Regex', $route->compile()->getRegex()],
['Host', ('' !== $route->getHost() ? $route->getHost() : 'ANY')],
['Host Regex', ('' !== $route->getHost() ? $route->compile()->getHostRegex() : '')],
['Scheme', ($route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY')],
['Method', ($route->getMethods() ? implode('|', $route->getMethods()) : 'ANY')],
['Requirements', ($route->getRequirements() ? $this->formatRouterConfig($route->getRequirements()) : 'NO CUSTOM')],
['Class', \get_class($route)],
['Defaults', $this->formatRouterConfig($defaults)],
['Options', $this->formatRouterConfig($route->getOptions())],
];
if ('' !== $route->getCondition()) {
$tableRows[] = ['Condition', $route->getCondition()];
}
$table = new Table($this->getOutput());
$table->setHeaders($tableHeaders)->setRows($tableRows);
$table->render();
}
protected function describeContainerParameters(ParameterBag $parameters, array $options = [])
{
$tableHeaders = ['Parameter', 'Value'];
$tableRows = [];
foreach ($this->sortParameters($parameters) as $parameter => $value) {
$tableRows[] = [$parameter, $this->formatParameter($value)];
}
$options['output']->title('Symfony Container Parameters');
$options['output']->table($tableHeaders, $tableRows);
}
protected function describeContainerTags(ContainerBuilder $builder, array $options = [])
{
$showHidden = isset($options['show_hidden']) && $options['show_hidden'];
if ($showHidden) {
$options['output']->title('Symfony Container Hidden Tags');
} else {
$options['output']->title('Symfony Container Tags');
}
foreach ($this->findDefinitionsByTag($builder, $showHidden) as $tag => $definitions) {
$options['output']->section(sprintf('"%s" tag', $tag));
$options['output']->listing(array_keys($definitions));
}
}
protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null)
{
if (!isset($options['id'])) {
throw new \InvalidArgumentException('An "id" option must be provided.');
}
if ($service instanceof Alias) {
$this->describeContainerAlias($service, $options, $builder);
} elseif ($service instanceof Definition) {
$this->describeContainerDefinition($service, $options);
} else {
$options['output']->title(sprintf('Information for Service "<info>%s</info>"', $options['id']));
$options['output']->table(
['Service ID', 'Class'],
[
[$options['id'] ?? '-', \get_class($service)],
]
);
}
}
protected function describeContainerServices(ContainerBuilder $builder, array $options = [])
{
$showHidden = isset($options['show_hidden']) && $options['show_hidden'];
$showTag = $options['tag'] ?? null;
if ($showHidden) {
$title = 'Symfony Container Hidden Services';
} else {
$title = 'Symfony Container Services';
}
if ($showTag) {
$title .= sprintf(' Tagged with "%s" Tag', $options['tag']);
}
$options['output']->title($title);
$serviceIds = isset($options['tag']) && $options['tag']
? $this->sortTaggedServicesByPriority($builder->findTaggedServiceIds($options['tag']))
: $this->sortServiceIds($builder->getServiceIds());
$maxTags = [];
if (isset($options['filter'])) {
$serviceIds = array_filter($serviceIds, $options['filter']);
}
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) {
if ($showTag) {
$tags = $definition->getTag($showTag);
foreach ($tags as $tag) {
foreach ($tag as $key => $value) {
if (!isset($maxTags[$key])) {
$maxTags[$key] = \strlen($key);
}
if (\strlen($value) > $maxTags[$key]) {
$maxTags[$key] = \strlen($value);
}
}
}
}
}
}
$tagsCount = \count($maxTags);
$tagsNames = array_keys($maxTags);
$tableHeaders = array_merge(['Service ID'], $tagsNames, ['Class name']);
$tableRows = [];
$rawOutput = isset($options['raw_text']) && $options['raw_text'];
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 ($this->sortByPriority($definition->getTag($showTag)) as $key => $tag) {
$tagValues = [];
foreach ($tagsNames as $tagName) {
$tagValues[] = $tag[$tagName] ?? '';
}
if (0 === $key) {
$tableRows[] = array_merge([$serviceId], $tagValues, [$definition->getClass()]);
} else {
$tableRows[] = array_merge([' (same service as previous, another tag)'], $tagValues, ['']);
}
}
} else {
$tableRows[] = [$styledServiceId, $definition->getClass()];
}
} elseif ($definition instanceof Alias) {
$alias = $definition;
$tableRows[] = array_merge([$styledServiceId, sprintf('alias for "%s"', $alias)], $tagsCount ? array_fill(0, $tagsCount, '') : []);
} else {
$tableRows[] = array_merge([$styledServiceId, \get_class($definition)], $tagsCount ? array_fill(0, $tagsCount, '') : []);
}
}
$options['output']->table($tableHeaders, $tableRows);
}
protected function describeContainerDefinition(Definition $definition, array $options = [])
{
if (isset($options['id'])) {
$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', $options['id'] ?? '-'];
$tableRows[] = ['Class', $definition->getClass() ?: '-'];
$omitTags = isset($options['omit_tags']) && $options['omit_tags'];
if (!$omitTags && ($tags = $definition->getTags())) {
$tagInformation = [];
foreach ($tags as $tagName => $tagData) {
foreach ($tagData as $tagParameters) {
$parameters = array_map(function ($key, $value) {
return sprintf('<info>%s</info>: %s', $key, $value);
}, array_keys($tagParameters), array_values($tagParameters));
$parameters = implode(', ', $parameters);
if ('' === $parameters) {
$tagInformation[] = sprintf('%s', $tagName);
} else {
$tagInformation[] = sprintf('%s (%s)', $tagName, $parameters);
}
}
}
$tagInformation = implode("\n", $tagInformation);
} else {
$tagInformation = '-';
}
$tableRows[] = ['Tags', $tagInformation];
$calls = $definition->getMethodCalls();
if (\count($calls) > 0) {
$callInformation = [];
foreach ($calls as $call) {
$callInformation[] = $call[0];
}
$tableRows[] = ['Calls', implode(', ', $callInformation)];
}
$tableRows[] = ['Public', $definition->isPublic() && !$definition->isPrivate() ? 'yes' : 'no'];
$tableRows[] = ['Synthetic', $definition->isSynthetic() ? 'yes' : 'no'];
$tableRows[] = ['Lazy', $definition->isLazy() ? 'yes' : 'no'];
$tableRows[] = ['Shared', $definition->isShared() ? 'yes' : 'no'];
$tableRows[] = ['Abstract', $definition->isAbstract() ? 'yes' : 'no'];
$tableRows[] = ['Autowired', $definition->isAutowired() ? 'yes' : 'no'];
$tableRows[] = ['Autoconfigured', $definition->isAutoconfigured() ? 'yes' : 'no'];
if ($definition->getFile()) {
$tableRows[] = ['Required File', $definition->getFile() ?: '-'];
}
if ($factory = $definition->getFactory()) {
if (\is_array($factory)) {
if ($factory[0] instanceof Reference) {
$tableRows[] = ['Factory Service', $factory[0]];
} elseif ($factory[0] instanceof Definition) {
throw new \InvalidArgumentException('Factory is not describable.');
} else {
$tableRows[] = ['Factory Class', $factory[0]];
}
$tableRows[] = ['Factory Method', $factory[1]];
} else {
$tableRows[] = ['Factory Function', $factory];
}
}
$showArguments = isset($options['show_arguments']) && $options['show_arguments'];
$argumentsInformation = [];
if ($showArguments && ($arguments = $definition->getArguments())) {
foreach ($arguments as $argument) {
if ($argument instanceof ServiceClosureArgument) {
$argument = $argument->getValues()[0];
}
if ($argument instanceof Reference) {
$argumentsInformation[] = sprintf('Service(%s)', (string) $argument);
} elseif ($argument instanceof IteratorArgument) {
if ($argument instanceof TaggedIteratorArgument) {
$argumentsInformation[] = sprintf('Tagged Iterator for "%s"%s', $argument->getTag(), $options['is_debug'] ? '' : sprintf(' (%d element(s))', \count($argument->getValues())));
} 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[] = 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;
}
}
$tableRows[] = ['Arguments', implode("\n", $argumentsInformation)];
}
$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)
{
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;
}
$this->describeContainerDefinition($builder->getDefinition((string) $alias), array_merge($options, ['id' => (string) $alias]));
}
protected function describeContainerParameter($parameter, array $options = [])
{
$options['output']->table(
['Parameter', 'Value'],
[
[$options['parameter'], $this->formatParameter($parameter),
],
]);
}
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 = $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 "%s" Event', $event);
$registeredListeners = $eventDispatcher->getListeners($event);
} else {
$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);
if (null !== $event) {
$this->renderEventListenerTable($eventDispatcher, $event, $registeredListeners, $options['output']);
} else {
ksort($registeredListeners);
foreach ($registeredListeners as $eventListened => $eventListeners) {
$options['output']->section(sprintf('"%s" event', $eventListened));
$this->renderEventListenerTable($eventDispatcher, $eventListened, $eventListeners, $options['output']);
}
}
}
protected function describeCallable($callable, array $options = [])
{
$this->writeText($this->formatCallable($callable), $options);
}
private function renderEventListenerTable(EventDispatcherInterface $eventDispatcher, string $event, array $eventListeners, SymfonyStyle $io)
{
$tableHeaders = ['Order', 'Callable', 'Priority'];
$tableRows = [];
foreach ($eventListeners as $order => $listener) {
$tableRows[] = [sprintf('#%d', $order + 1), $this->formatCallable($listener), $eventDispatcher->getListenerPriority($event, $listener)];
}
$io->table($tableHeaders, $tableRows);
}
private function formatRouterConfig(array $config): string
{
if (empty($config)) {
return 'NONE';
}
ksort($config);
$configAsString = '';
foreach ($config as $key => $value) {
$configAsString .= sprintf("\n%s: %s", $key, $this->formatValue($value));
}
return trim($configAsString);
}
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])) {
return sprintf('%s::%s()', \get_class($callable[0]), $callable[1]);
}
return sprintf('%s::%s()', $callable[0], $callable[1]);
}
if (\is_string($callable)) {
return sprintf('%s()', $callable);
}
if ($callable instanceof \Closure) {
$r = new \ReflectionFunction($callable);
if (str_contains($r->name, '{closure}')) {
return 'Closure()';
}
if ($class = $r->getClosureScopeClass()) {
return sprintf('%s::%s()', $class->name, $r->name);
}
return $r->name.'()';
}
if (method_exists($callable, '__invoke')) {
return sprintf('%s::__invoke()', \get_class($callable));
}
throw new \InvalidArgumentException('Callable is not describable.');
}
private function writeText(string $content, array $options = [])
{
$this->write(
isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content,
isset($options['raw_output']) ? !$options['raw_output'] : true
);
}
}

View File

@ -0,0 +1,570 @@
<?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\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;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class XmlDescriptor extends Descriptor
{
protected function describeRouteCollection(RouteCollection $routes, array $options = [])
{
$this->writeDocument($this->getRouteCollectionDocument($routes));
}
protected function describeRoute(Route $route, array $options = [])
{
$this->writeDocument($this->getRouteDocument($route, $options['name'] ?? null));
}
protected function describeContainerParameters(ParameterBag $parameters, array $options = [])
{
$this->writeDocument($this->getContainerParametersDocument($parameters));
}
protected function describeContainerTags(ContainerBuilder $builder, array $options = [])
{
$this->writeDocument($this->getContainerTagsDocument($builder, isset($options['show_hidden']) && $options['show_hidden']));
}
protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null)
{
if (!isset($options['id'])) {
throw new \InvalidArgumentException('An "id" option must be provided.');
}
$this->writeDocument($this->getContainerServiceDocument($service, $options['id'], $builder, isset($options['show_arguments']) && $options['show_arguments']));
}
protected function describeContainerServices(ContainerBuilder $builder, array $options = [])
{
$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, $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, $options['id'] ?? null)->childNodes->item(0), true));
if (!$builder) {
$this->writeDocument($dom);
return;
}
$dom->appendChild($dom->importNode($this->getContainerDefinitionDocument($builder->getDefinition((string) $alias), (string) $alias)->childNodes->item(0), true));
$this->writeDocument($dom);
}
protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = [])
{
$this->writeDocument($this->getEventDispatcherListenersDocument($eventDispatcher, $options));
}
protected function describeCallable($callable, array $options = [])
{
$this->writeDocument($this->getCallableDocument($callable));
}
protected function describeContainerParameter($parameter, array $options = [])
{
$this->writeDocument($this->getContainerParameterDocument($parameter, $options));
}
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());
}
private function getRouteCollectionDocument(RouteCollection $routes): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($routesXML = $dom->createElement('routes'));
foreach ($routes->all() as $name => $route) {
$routeXML = $this->getRouteDocument($route, $name);
$routesXML->appendChild($routesXML->ownerDocument->importNode($routeXML->childNodes->item(0), true));
}
return $dom;
}
private function getRouteDocument(Route $route, string $name = null): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($routeXML = $dom->createElement('route'));
if ($name) {
$routeXML->setAttribute('name', $name);
}
$routeXML->setAttribute('class', \get_class($route));
$routeXML->appendChild($pathXML = $dom->createElement('path'));
$pathXML->setAttribute('regex', $route->compile()->getRegex());
$pathXML->appendChild(new \DOMText($route->getPath()));
if ('' !== $route->getHost()) {
$routeXML->appendChild($hostXML = $dom->createElement('host'));
$hostXML->setAttribute('regex', $route->compile()->getHostRegex());
$hostXML->appendChild(new \DOMText($route->getHost()));
}
foreach ($route->getSchemes() as $scheme) {
$routeXML->appendChild($schemeXML = $dom->createElement('scheme'));
$schemeXML->appendChild(new \DOMText($scheme));
}
foreach ($route->getMethods() as $method) {
$routeXML->appendChild($methodXML = $dom->createElement('method'));
$methodXML->appendChild(new \DOMText($method));
}
if ($route->getDefaults()) {
$routeXML->appendChild($defaultsXML = $dom->createElement('defaults'));
foreach ($route->getDefaults() as $attribute => $value) {
$defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
$defaultXML->setAttribute('key', $attribute);
$defaultXML->appendChild(new \DOMText($this->formatValue($value)));
}
}
$originRequirements = $requirements = $route->getRequirements();
unset($requirements['_scheme'], $requirements['_method']);
if ($requirements) {
$routeXML->appendChild($requirementsXML = $dom->createElement('requirements'));
foreach ($originRequirements as $attribute => $pattern) {
$requirementsXML->appendChild($requirementXML = $dom->createElement('requirement'));
$requirementXML->setAttribute('key', $attribute);
$requirementXML->appendChild(new \DOMText($pattern));
}
}
if ($route->getOptions()) {
$routeXML->appendChild($optionsXML = $dom->createElement('options'));
foreach ($route->getOptions() as $name => $value) {
$optionsXML->appendChild($optionXML = $dom->createElement('option'));
$optionXML->setAttribute('key', $name);
$optionXML->appendChild(new \DOMText($this->formatValue($value)));
}
}
if ('' !== $route->getCondition()) {
$routeXML->appendChild($conditionXML = $dom->createElement('condition'));
$conditionXML->appendChild(new \DOMText($route->getCondition()));
}
return $dom;
}
private function getContainerParametersDocument(ParameterBag $parameters): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($parametersXML = $dom->createElement('parameters'));
foreach ($this->sortParameters($parameters) as $key => $value) {
$parametersXML->appendChild($parameterXML = $dom->createElement('parameter'));
$parameterXML->setAttribute('key', $key);
$parameterXML->appendChild(new \DOMText($this->formatParameter($value)));
}
return $dom;
}
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, $showHidden) as $tag => $definitions) {
$containerXML->appendChild($tagXML = $dom->createElement('tag'));
$tagXML->setAttribute('name', $tag);
foreach ($definitions as $serviceId => $definition) {
$definitionXML = $this->getContainerDefinitionDocument($definition, $serviceId, true);
$tagXML->appendChild($dom->importNode($definitionXML->childNodes->item(0), true));
}
}
return $dom;
}
private function getContainerServiceDocument(object $service, string $id, ContainerBuilder $builder = null, bool $showArguments = false): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
if ($service instanceof Alias) {
$dom->appendChild($dom->importNode($this->getContainerAliasDocument($service, $id)->childNodes->item(0), true));
if ($builder) {
$dom->appendChild($dom->importNode($this->getContainerDefinitionDocument($builder->getDefinition((string) $service), (string) $service, false, $showArguments)->childNodes->item(0), true));
}
} elseif ($service instanceof Definition) {
$dom->appendChild($dom->importNode($this->getContainerDefinitionDocument($service, $id, false, $showArguments)->childNodes->item(0), true));
} else {
$dom->appendChild($serviceXML = $dom->createElement('service'));
$serviceXML->setAttribute('id', $id);
$serviceXML->setAttribute('class', \get_class($service));
}
return $dom;
}
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
? $this->sortTaggedServicesByPriority($builder->findTaggedServiceIds($tag))
: $this->sortServiceIds($builder->getServiceIds());
if ($filter) {
$serviceIds = array_filter($serviceIds, $filter);
}
foreach ($serviceIds as $serviceId) {
$service = $this->resolveServiceDefinition($builder, $serviceId);
if ($showHidden xor '.' === ($serviceId[0] ?? null)) {
continue;
}
$serviceXML = $this->getContainerServiceDocument($service, $serviceId, null, $showArguments);
$containerXML->appendChild($containerXML->ownerDocument->importNode($serviceXML->childNodes->item(0), true));
}
return $dom;
}
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'));
if ($id) {
$serviceXML->setAttribute('id', $id);
}
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'));
if (\is_array($factory)) {
if ($factory[0] instanceof Reference) {
$factoryXML->setAttribute('service', (string) $factory[0]);
} elseif ($factory[0] instanceof Definition) {
throw new \InvalidArgumentException('Factory is not describable.');
} else {
$factoryXML->setAttribute('class', $factory[0]);
}
$factoryXML->setAttribute('method', $factory[1]);
} else {
$factoryXML->setAttribute('function', $factory);
}
}
$serviceXML->setAttribute('public', $definition->isPublic() && !$definition->isPrivate() ? 'true' : 'false');
$serviceXML->setAttribute('synthetic', $definition->isSynthetic() ? 'true' : 'false');
$serviceXML->setAttribute('lazy', $definition->isLazy() ? 'true' : 'false');
$serviceXML->setAttribute('shared', $definition->isShared() ? 'true' : 'false');
$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() ?? '');
$calls = $definition->getMethodCalls();
if (\count($calls) > 0) {
$serviceXML->appendChild($callsXML = $dom->createElement('calls'));
foreach ($calls as $callData) {
$callsXML->appendChild($callXML = $dom->createElement('call'));
$callXML->setAttribute('method', $callData[0]);
if ($callData[2] ?? false) {
$callXML->setAttribute('returns-clone', 'true');
}
}
}
if ($showArguments) {
foreach ($this->getArgumentNodes($definition->getArguments(), $dom) as $node) {
$serviceXML->appendChild($node);
}
}
if (!$omitTags) {
if ($tags = $this->sortTagsByPriority($definition->getTags())) {
$serviceXML->appendChild($tagsXML = $dom->createElement('tags'));
foreach ($tags as $tagName => $tagData) {
foreach ($tagData as $parameters) {
$tagsXML->appendChild($tagXML = $dom->createElement('tag'));
$tagXML->setAttribute('name', $tagName);
foreach ($parameters as $name => $value) {
$tagXML->appendChild($parameterXML = $dom->createElement('parameter'));
$parameterXML->setAttribute('name', $name);
$parameterXML->appendChild(new \DOMText($this->formatParameter($value)));
}
}
}
}
}
return $dom;
}
/**
* @return \DOMNode[]
*/
private function getArgumentNodes(array $arguments, \DOMDocument $dom): array
{
$nodes = [];
foreach ($arguments as $argumentKey => $argument) {
$argumentXML = $dom->createElement('argument');
if (\is_string($argumentKey)) {
$argumentXML->setAttribute('key', $argumentKey);
}
if ($argument instanceof ServiceClosureArgument) {
$argument = $argument->getValues()[0];
}
if ($argument instanceof Reference) {
$argumentXML->setAttribute('type', 'service');
$argumentXML->setAttribute('id', (string) $argument);
} 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(var_export($argument, true)));
} else {
$argumentXML->appendChild(new \DOMText($argument));
}
$nodes[] = $argumentXML;
}
return $nodes;
}
private function getContainerAliasDocument(Alias $alias, string $id = null): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($aliasXML = $dom->createElement('alias'));
if ($id) {
$aliasXML->setAttribute('id', $id);
}
$aliasXML->setAttribute('service', (string) $alias);
$aliasXML->setAttribute('public', $alias->isPublic() && !$alias->isPrivate() ? 'true' : 'false');
return $dom;
}
private function getContainerParameterDocument($parameter, array $options = []): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($parameterXML = $dom->createElement('parameter'));
if (isset($options['parameter'])) {
$parameterXML->setAttribute('key', $options['parameter']);
}
$parameterXML->appendChild(new \DOMText($this->formatParameter($parameter)));
return $dom;
}
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'));
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) {
$eventDispatcherXML->appendChild($eventXML = $dom->createElement('event'));
$eventXML->setAttribute('name', $eventListened);
$this->appendEventListenerDocument($eventDispatcher, $eventListened, $eventXML, $eventListeners);
}
}
return $dom;
}
private function appendEventListenerDocument(EventDispatcherInterface $eventDispatcher, string $event, \DOMElement $element, array $eventListeners)
{
foreach ($eventListeners as $listener) {
$callableXML = $this->getCallableDocument($listener);
$callableXML->childNodes->item(0)->setAttribute('priority', $eventDispatcher->getListenerPriority($event, $listener));
$element->appendChild($element->ownerDocument->importNode($callableXML->childNodes->item(0), true));
}
}
private function getCallableDocument($callable): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($callableXML = $dom->createElement('callable'));
if (\is_array($callable)) {
$callableXML->setAttribute('type', 'function');
if (\is_object($callable[0])) {
$callableXML->setAttribute('name', $callable[1]);
$callableXML->setAttribute('class', \get_class($callable[0]));
} else {
if (!str_starts_with($callable[1], 'parent::')) {
$callableXML->setAttribute('name', $callable[1]);
$callableXML->setAttribute('class', $callable[0]);
$callableXML->setAttribute('static', 'true');
} else {
$callableXML->setAttribute('name', substr($callable[1], 8));
$callableXML->setAttribute('class', $callable[0]);
$callableXML->setAttribute('static', 'true');
$callableXML->setAttribute('parent', 'true');
}
}
return $dom;
}
if (\is_string($callable)) {
$callableXML->setAttribute('type', 'function');
if (!str_contains($callable, '::')) {
$callableXML->setAttribute('name', $callable);
} else {
$callableParts = explode('::', $callable);
$callableXML->setAttribute('name', $callableParts[1]);
$callableXML->setAttribute('class', $callableParts[0]);
$callableXML->setAttribute('static', 'true');
}
return $dom;
}
if ($callable instanceof \Closure) {
$callableXML->setAttribute('type', 'closure');
$r = new \ReflectionFunction($callable);
if (str_contains($r->name, '{closure}')) {
return $dom;
}
$callableXML->setAttribute('name', $r->name);
if ($class = $r->getClosureScopeClass()) {
$callableXML->setAttribute('class', $class->name);
if (!$r->getClosureThis()) {
$callableXML->setAttribute('static', 'true');
}
}
return $dom;
}
if (method_exists($callable, '__invoke')) {
$callableXML->setAttribute('type', 'object');
$callableXML->setAttribute('name', \get_class($callable));
return $dom;
}
throw new \InvalidArgumentException('Callable is not describable.');
}
}

View File

@ -0,0 +1,37 @@
<?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\Console\Helper;
use Symfony\Bundle\FrameworkBundle\Console\Descriptor\JsonDescriptor;
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>
*
* @internal
*/
class DescriptorHelper extends BaseDescriptorHelper
{
public function __construct(FileLinkFormatter $fileLinkFormatter = null)
{
$this
->register('txt', new TextDescriptor($fileLinkFormatter))
->register('xml', new XmlDescriptor())
->register('json', new JsonDescriptor())
->register('md', new MarkdownDescriptor())
;
}
}

View File

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

View File

@ -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\Controller;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\HttpKernel\Controller\ContainerControllerResolver;
/**
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class ControllerResolver extends ContainerControllerResolver
{
/**
* {@inheritdoc}
*/
protected function instantiateController(string $class): object
{
$controller = parent::instantiateController($class);
if ($controller instanceof ContainerAwareInterface) {
$controller->setContainer($this->container);
}
if ($controller instanceof AbstractController) {
if (null === $previousContainer = $controller->setContainer($this->container)) {
throw new \LogicException(sprintf('"%s" has no container set, did you forget to define it as a service subscriber?', $class));
} else {
$controller->setContainer($previousContainer);
}
}
return $controller;
}
}

View File

@ -0,0 +1,189 @@
<?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\HttpFoundation\HeaderUtils;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
/**
* Redirects a request to another URL.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class RedirectController
{
private $router;
private $httpPort;
private $httpsPort;
public function __construct(UrlGeneratorInterface $router = null, int $httpPort = null, int $httpsPort = null)
{
$this->router = $router;
$this->httpPort = $httpPort;
$this->httpsPort = $httpsPort;
}
/**
* Redirects to another route with the given name.
*
* The response status code is 302 if the permanent parameter is false (default),
* and 301 if the redirection is permanent.
*
* In case the route name is empty, the status code will be 404 when permanent is false
* and 410 otherwise.
*
* @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, string $route, bool $permanent = false, $ignoreAttributes = false, bool $keepRequestMethod = false, bool $keepQueryParams = false): Response
{
if ('' == $route) {
throw new HttpException($permanent ? 410 : 404);
}
$attributes = [];
if (false === $ignoreAttributes || \is_array($ignoreAttributes)) {
$attributes = $request->attributes->get('_route_params');
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));
}
}
if ($keepRequestMethod) {
$statusCode = $permanent ? 308 : 307;
} else {
$statusCode = $permanent ? 301 : 302;
}
return new RedirectResponse($this->router->generate($route, $attributes, UrlGeneratorInterface::ABSOLUTE_URL), $statusCode);
}
/**
* Redirects to a URL.
*
* The response status code is 302 if the permanent parameter is false (default),
* and 301 if the redirection is permanent.
*
* In case the path is empty, the status code will be 404 when permanent is false
* and 410 otherwise.
*
* @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, 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);
}
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)) {
return new RedirectResponse($path, $statusCode);
}
if (null === $scheme) {
$scheme = $request->getScheme();
}
if ($qs = $request->server->get('QUERY_STRING') ?: $request->getQueryString()) {
if (!str_contains($path, '?')) {
$qs = '?'.$qs;
} else {
$qs = '&'.$qs;
}
}
$port = '';
if ('http' === $scheme) {
if (null === $httpPort) {
if ('http' === $request->getScheme()) {
$httpPort = $request->getPort();
} else {
$httpPort = $this->httpPort;
}
}
if (null !== $httpPort && 80 != $httpPort) {
$port = ":$httpPort";
}
} elseif ('https' === $scheme) {
if (null === $httpsPort) {
if ('https' === $request->getScheme()) {
$httpsPort = $request->getPort();
} else {
$httpsPort = $this->httpsPort;
}
}
if (null !== $httpsPort && 443 != $httpsPort) {
$port = ":$httpsPort";
}
}
$url = $scheme.'://'.$request->getHost().$port.$request->getBaseUrl().$path.$qs;
return new RedirectResponse($url, $statusCode);
}
public function __invoke(Request $request): Response
{
$p = $request->attributes->get('_route_params', []);
if (\array_key_exists('route', $p)) {
if (\array_key_exists('path', $p)) {
throw new \RuntimeException(sprintf('Ambiguous redirection settings, use the "path" or "route" parameter, not both: "%s" and "%s" found respectively in "%s" routing configuration.', $p['path'], $p['route'], $request->attributes->get('_route')));
}
return $this->redirectAction($request, $p['route'], $p['permanent'] ?? false, $p['ignoreAttributes'] ?? false, $p['keepRequestMethod'] ?? false, $p['keepQueryParams'] ?? false);
}
if (\array_key_exists('path', $p)) {
return $this->urlRedirectAction($request, $p['path'], $p['permanent'] ?? false, $p['scheme'] ?? null, $p['httpPort'] ?? null, $p['httpsPort'] ?? null, $p['keepRequestMethod'] ?? false);
}
throw new \RuntimeException(sprintf('The parameter "path" or "route" is required to configure the redirect action in "%s" routing configuration.', $request->attributes->get('_route')));
}
}

View File

@ -0,0 +1,72 @@
<?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\HttpFoundation\Response;
use Twig\Environment;
/**
* TemplateController.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class TemplateController
{
private $twig;
public function __construct(Environment $twig = null)
{
$this->twig = $twig;
}
/**
* 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
* @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(string $template, int $maxAge = null, int $sharedAge = null, bool $private = null, array $context = [], int $statusCode = 200): Response
{
if (null === $this->twig) {
throw new \LogicException('You cannot use the TemplateController if the Twig Bundle is not available.');
}
$response = new Response($this->twig->render($template, $context), $statusCode);
if ($maxAge) {
$response->setMaxAge($maxAge);
}
if (null !== $sharedAge) {
$response->setSharedMaxAge($sharedAge);
}
if ($private) {
$response->setPrivate();
} elseif (false === $private || (null === $private && (null !== $maxAge || null !== $sharedAge))) {
$response->setPublic();
}
return $response;
}
public function __invoke(string $template, int $maxAge = null, int $sharedAge = null, bool $private = null, array $context = [], int $statusCode = 200): Response
{
return $this->templateAction($template, $maxAge, $sharedAge, $private, $context, $statusCode);
}
}

View File

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

View File

@ -0,0 +1,37 @@
<?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\Bundle\FrameworkBundle\Controller\RedirectController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\DataCollector\RouterDataCollector as BaseRouterDataCollector;
/**
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class RouterDataCollector extends BaseRouterDataCollector
{
public function guessRoute(Request $request, $controller)
{
if (\is_array($controller)) {
$controller = $controller[0];
}
if ($controller instanceof RedirectController) {
return $request->attributes->get('_route');
}
return parent::guessRoute($request, $controller);
}
}

View File

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

View File

@ -0,0 +1,47 @@
<?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\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* @internal
*/
class AddAnnotationsCachedReaderPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
// "annotations.cached_reader" is wired late so that any passes using
// "annotation_reader" at build time don't get any cache
foreach ($container->findTaggedServiceIds('annotations.cached_reader') as $id => $tags) {
$reader = $container->getDefinition($id);
$reader->setPublic(false);
$properties = $reader->getProperties();
if (isset($properties['cacheProviderBackup'])) {
$provider = $properties['cacheProviderBackup']->getValues()[0];
unset($properties['cacheProviderBackup']);
$reader->setProperties($properties);
$reader->replaceArgument(1, $provider);
} elseif (4 <= \count($arguments = $reader->getArguments()) && $arguments[3] instanceof ServiceClosureArgument) {
$arguments[1] = $arguments[3]->getValues()[0];
unset($arguments[3]);
$reader->setArguments($arguments);
}
}
}
}

View File

@ -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;
class AddDebugLogProcessorPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('profiler')) {
return;
}
if (!$container->hasDefinition('monolog.logger_prototype')) {
return;
}
if (!$container->hasDefinition('debug.log_processor')) {
return;
}
$definition = $container->getDefinition('monolog.logger_prototype');
$definition->setConfigurator([__CLASS__, 'configureLogger']);
$definition->addMethodCall('pushProcessor', [new Reference('debug.log_processor')]);
}
public static function configureLogger($logger)
{
if (\is_object($logger) && method_exists($logger, 'removeDebugLogger') && \in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
$logger->removeDebugLogger();
}
}
}

View File

@ -0,0 +1,38 @@
<?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;
/**
* Registers the expression language providers.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class AddExpressionLanguageProvidersPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
// routing
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)]);
}
}
}
}

View File

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

View File

@ -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\DependencyInjection\Compiler;
use Symfony\Component\Config\ConfigCache;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Dumper\XmlDumper;
/**
* Dumps the ContainerBuilder to a cache file so that it can be used by
* debugging tools such as the debug:container console command.
*
* @author Ryan Weaver <ryan@thatsquality.com>
* @author Fabien Potencier <fabien@symfony.com>
*/
class ContainerBuilderDebugDumpPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$cache = new ConfigCache($container->getParameter('debug.container.dump'), true);
if (!$cache->isFresh()) {
$cache->write((new XmlDumper($container))->dump(), $container->getResources());
}
}
}

View File

@ -0,0 +1,35 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* @author Christian Flothmann <christian.flothmann@sensiolabs.de>
*/
class DataCollectorTranslatorPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->has('translator')) {
return;
}
$translatorClass = $container->getParameterBag()->resolveValue($container->findDefinition('translator')->getClass());
if (!is_subclass_of($translatorClass, 'Symfony\Component\Translation\TranslatorBagInterface')) {
$container->removeDefinition('translator.data_collector');
$container->removeDefinition('data_collector.translation');
}
}
}

View File

@ -0,0 +1,54 @@
<?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\Exception\InvalidArgumentException;
use Symfony\Component\Translation\TranslatorBagInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
*/
class LoggingTranslatorPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasAlias('logger') || !$container->hasAlias('translator')) {
return;
}
if ($container->hasParameter('translator.logging') && $container->getParameter('translator.logging')) {
$translatorAlias = $container->getAlias('translator');
$definition = $container->getDefinition((string) $translatorAlias);
$class = $container->getParameterBag()->resolveValue($definition->getClass());
if (!$r = $container->getReflectionClass($class)) {
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $translatorAlias));
}
if ($r->isSubclassOf(TranslatorInterface::class) && $r->isSubclassOf(TranslatorBagInterface::class)) {
$container->getDefinition('translator.logging')->setDecoratedService('translator');
$warmer = $container->getDefinition('translation.warmer');
$subscriberAttributes = $warmer->getTag('container.service_subscriber');
$warmer->clearTag('container.service_subscriber');
foreach ($subscriberAttributes as $k => $v) {
if ((!isset($v['id']) || 'translator' !== $v['id']) && (!isset($v['key']) || 'translator' !== $v['key'])) {
$warmer->addTag('container.service_subscriber', $v);
}
}
$warmer->addTag('container.service_subscriber', ['key' => 'translator', 'id' => 'translator.logging.inner']);
}
}
}
}

View File

@ -0,0 +1,62 @@
<?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\DataCollector\TemplateAwareDataCollectorInterface;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
/**
* Adds tagged data_collector services to profiler service.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ProfilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (false === $container->hasDefinition('profiler')) {
return;
}
$definition = $container->getDefinition('profiler');
$collectors = new \SplPriorityQueue();
$order = \PHP_INT_MAX;
foreach ($container->findTaggedServiceIds('data_collector', true) as $id => $attributes) {
$priority = $attributes[0]['priority'] ?? 0;
$template = null;
$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 = [$idForTemplate, $attributes[0]['template'] ?? $collectorClass::getTemplate()];
}
$collectors->insert([$id, $template], [$priority, --$order]);
}
$templates = [];
foreach ($collectors as $collector) {
$definition->addMethodCall('add', [new Reference($collector[0])]);
$templates[$collector[0]] = $collector[1];
}
$container->setParameter('data_collector.templates', $templates);
}
}

View File

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

View File

@ -0,0 +1,72 @@
<?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;
/**
* @internal to be removed in 6.0
*/
class SessionPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
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 ($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 || !\in_array((string) $factory[0], ['session', '.session.do-not-use'], true)) {
continue;
}
if ('get'.ucfirst(substr($bag, 8, -4)).'Bag' !== $factory[1]) {
continue;
}
$bags[$bag]->setFactory(null);
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,128 @@
<?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;
/**
* Find all service tags which are defined, but not used and yield a warning log message.
*
* @author Florian Pfitzer <pfitzer@wurzel3.de>
*/
class UnusedTagsPass implements CompilerPassInterface
{
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.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',
'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',
];
public function process(ContainerBuilder $container)
{
$tags = array_unique(array_merge($container->findTags(), self::KNOWN_TAGS));
foreach ($container->findUnusedTags() as $tag) {
// skip known tags
if (\in_array($tag, self::KNOWN_TAGS)) {
continue;
}
// check for typos
$candidates = [];
foreach ($tags as $definedTag) {
if ($definedTag === $tag) {
continue;
}
if (str_contains($definedTag, $tag) || levenshtein($tag, $definedTag) <= \strlen($tag) / 3) {
$candidates[] = $definedTag;
}
}
$services = array_keys($container->findTaggedServiceIds($tag));
$message = sprintf('Tag "%s" was defined on service(s) "%s", but was never used.', $tag, implode('", "', $services));
if (!empty($candidates)) {
$message .= sprintf(' Did you mean "%s"?', implode('", "', $candidates));
}
$container->log($this, $message);
}
}
}

View File

@ -0,0 +1,48 @@
<?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\Exception\LogicException;
/**
* @author Christian Flothmann <christian.flothmann@sensiolabs.de>
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class WorkflowGuardListenerPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasParameter('workflow.has_guard_listeners')) {
return;
}
$container->getParameterBag()->remove('workflow.has_guard_listeners');
$servicesNeeded = [
'security.token_storage',
'security.authorization_checker',
'security.authentication.trust_resolver',
'security.role_hierarchy',
];
foreach ($servicesNeeded as $service) {
if (!$container->has($service)) {
throw new LogicException(sprintf('The "%s" service is needed to be able to use the workflow guard listener.', $service));
}
}
}
}

View File

@ -0,0 +1,2072 @@
<?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;
use Doctrine\Common\Annotations\Annotation;
use Doctrine\Common\Annotations\PsrCachedReader;
use Doctrine\Common\Cache\Cache;
use Doctrine\DBAL\Connection;
use Psr\Log\LogLevel;
use Symfony\Bundle\FullStack;
use Symfony\Component\Asset\Package;
use Symfony\Component\Cache\Adapter\DoctrineAdapter;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\Form\Form;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\Lock\Lock;
use Symfony\Component\Lock\Store\SemaphoreStore;
use Symfony\Component\Mailer\Mailer;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Notifier\Notifier;
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
use Symfony\Component\RateLimiter\Policy\TokenBucketLimiter;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Translation\Translator;
use Symfony\Component\Uid\Factory\UuidFactory;
use Symfony\Component\Validator\Validation;
use Symfony\Component\WebLink\HttpHeaderSerializer;
use Symfony\Component\Workflow\WorkflowEvents;
/**
* FrameworkExtension configuration structure.
*/
class Configuration implements ConfigurationInterface
{
private $debug;
/**
* @param bool $debug Whether debugging is enabled or not
*/
public function __construct(bool $debug)
{
$this->debug = $debug;
}
/**
* Generates the configuration tree builder.
*
* @return TreeBuilder
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('framework');
$rootNode = $treeBuilder->getRootNode();
$rootNode
->beforeNormalization()
->ifTrue(function ($v) { return !isset($v['assets']) && isset($v['templating']) && class_exists(Package::class); })
->then(function ($v) {
$v['assets'] = [];
return $v;
})
->end()
->fixXmlConfig('enabled_locale')
->children()
->scalarNode('secret')->end()
->scalarNode('http_method_override')
->info("Set true to enable support for the '_method' request parameter to determine the intended HTTP method on POST requests. Note: When using the HttpCache, you need to call the method in your front controller instead")
->defaultTrue()
->end()
->scalarNode('ide')->defaultNull()->end()
->booleanNode('test')->end()
->scalarNode('default_locale')->defaultValue('en')->end()
->booleanNode('set_locale_from_accept_language')
->info('Whether to use the Accept-Language HTTP header to set the Request locale (only when the "_locale" request attribute is not passed).')
->defaultFalse()
->end()
->booleanNode('set_content_language_from_locale')
->info('Whether to set the Content-Language HTTP header on the Response using the Request locale.')
->defaultFalse()
->end()
->arrayNode('enabled_locales')
->info('Defines the possible locales for the application. This list is used for generating translations files, but also to restrict which locales are allowed when it is set from Accept-Language header (using "set_locale_from_accept_language").')
->prototype('scalar')->end()
->end()
->arrayNode('trusted_hosts')
->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end()
->prototype('scalar')->end()
->end()
->scalarNode('trusted_proxies')->end()
->arrayNode('trusted_headers')
->fixXmlConfig('trusted_header')
->performNoDeepMerging()
->defaultValue(['x-forwarded-for', 'x-forwarded-port', 'x-forwarded-proto'])
->beforeNormalization()->ifString()->then(function ($v) { return $v ? array_map('trim', explode(',', $v)) : []; })->end()
->enumPrototype()
->values([
'forwarded',
'x-forwarded-for', 'x-forwarded-host', 'x-forwarded-proto', 'x-forwarded-port', 'x-forwarded-prefix',
])
->end()
->end()
->scalarNode('error_controller')
->defaultValue('error_controller')
->end()
->end()
;
$willBeAvailable = static function (string $package, string $class, string $parentPackage = null) {
$parentPackages = (array) $parentPackage;
$parentPackages[] = 'symfony/framework-bundle';
return ContainerBuilder::willBeAvailable($package, $class, $parentPackages, true);
};
$enableIfStandalone = static function (string $package, string $class) use ($willBeAvailable) {
return !class_exists(FullStack::class) && $willBeAvailable($package, $class) ? 'canBeDisabled' : 'canBeEnabled';
};
$this->addCsrfSection($rootNode);
$this->addFormSection($rootNode, $enableIfStandalone);
$this->addHttpCacheSection($rootNode);
$this->addEsiSection($rootNode);
$this->addSsiSection($rootNode);
$this->addFragmentsSection($rootNode);
$this->addProfilerSection($rootNode);
$this->addWorkflowSection($rootNode);
$this->addRouterSection($rootNode);
$this->addSessionSection($rootNode);
$this->addRequestSection($rootNode);
$this->addAssetsSection($rootNode, $enableIfStandalone);
$this->addTranslatorSection($rootNode, $enableIfStandalone);
$this->addValidationSection($rootNode, $enableIfStandalone, $willBeAvailable);
$this->addAnnotationsSection($rootNode, $willBeAvailable);
$this->addSerializerSection($rootNode, $enableIfStandalone, $willBeAvailable);
$this->addPropertyAccessSection($rootNode, $willBeAvailable);
$this->addPropertyInfoSection($rootNode, $enableIfStandalone);
$this->addCacheSection($rootNode, $willBeAvailable);
$this->addPhpErrorsSection($rootNode);
$this->addExceptionsSection($rootNode);
$this->addWebLinkSection($rootNode, $enableIfStandalone);
$this->addLockSection($rootNode, $enableIfStandalone);
$this->addMessengerSection($rootNode, $enableIfStandalone);
$this->addRobotsIndexSection($rootNode);
$this->addHttpClientSection($rootNode, $enableIfStandalone);
$this->addMailerSection($rootNode, $enableIfStandalone);
$this->addSecretsSection($rootNode);
$this->addNotifierSection($rootNode, $enableIfStandalone);
$this->addRateLimiterSection($rootNode, $enableIfStandalone);
$this->addUidSection($rootNode, $enableIfStandalone);
return $treeBuilder;
}
private function addSecretsSection(ArrayNodeDefinition $rootNode)
{
$rootNode
->children()
->arrayNode('secrets')
->canBeDisabled()
->children()
->scalarNode('vault_directory')->defaultValue('%kernel.project_dir%/config/secrets/%kernel.runtime_environment%')->cannotBeEmpty()->end()
->scalarNode('local_dotenv_file')->defaultValue('%kernel.project_dir%/.env.%kernel.environment%.local')->end()
->scalarNode('decryption_env_var')->defaultValue('base64:default::SYMFONY_DECRYPTION_SECRET')->end()
->end()
->end()
->end()
;
}
private function addCsrfSection(ArrayNodeDefinition $rootNode)
{
$rootNode
->children()
->arrayNode('csrf_protection')
->treatFalseLike(['enabled' => false])
->treatTrueLike(['enabled' => true])
->treatNullLike(['enabled' => true])
->addDefaultsIfNotSet()
->children()
// defaults to framework.session.enabled && !class_exists(FullStack::class) && interface_exists(CsrfTokenManagerInterface::class)
->booleanNode('enabled')->defaultNull()->end()
->end()
->end()
->end()
;
}
private function addFormSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone)
{
$rootNode
->children()
->arrayNode('form')
->info('form configuration')
->{$enableIfStandalone('symfony/form', Form::class)}()
->children()
->arrayNode('csrf_protection')
->treatFalseLike(['enabled' => false])
->treatTrueLike(['enabled' => true])
->treatNullLike(['enabled' => true])
->addDefaultsIfNotSet()
->children()
->booleanNode('enabled')->defaultNull()->end() // defaults to framework.csrf_protection.enabled
->scalarNode('field_name')->defaultValue('_token')->end()
->end()
->end()
// to be set to false in Symfony 6.0
->booleanNode('legacy_error_messages')
->defaultTrue()
->validate()
->ifTrue()
->then(function ($v) {
trigger_deprecation('symfony/framework-bundle', '5.2', 'Setting the "framework.form.legacy_error_messages" option to "true" is deprecated. It will have no effect as of Symfony 6.0.');
return $v;
})
->end()
->end()
->end()
->end()
->end()
;
}
private function addHttpCacheSection(ArrayNodeDefinition $rootNode)
{
$rootNode
->children()
->arrayNode('http_cache')
->info('HTTP cache configuration')
->canBeEnabled()
->fixXmlConfig('private_header')
->children()
->booleanNode('debug')->defaultValue('%kernel.debug%')->end()
->enumNode('trace_level')
->values(['none', 'short', 'full'])
->end()
->scalarNode('trace_header')->end()
->integerNode('default_ttl')->end()
->arrayNode('private_headers')
->performNoDeepMerging()
->scalarPrototype()->end()
->end()
->booleanNode('allow_reload')->end()
->booleanNode('allow_revalidate')->end()
->integerNode('stale_while_revalidate')->end()
->integerNode('stale_if_error')->end()
->end()
->end()
->end()
;
}
private function addEsiSection(ArrayNodeDefinition $rootNode)
{
$rootNode
->children()
->arrayNode('esi')
->info('esi configuration')
->canBeEnabled()
->end()
->end()
;
}
private function addSsiSection(ArrayNodeDefinition $rootNode)
{
$rootNode
->children()
->arrayNode('ssi')
->info('ssi configuration')
->canBeEnabled()
->end()
->end();
}
private function addFragmentsSection(ArrayNodeDefinition $rootNode)
{
$rootNode
->children()
->arrayNode('fragments')
->info('fragments configuration')
->canBeEnabled()
->children()
->scalarNode('hinclude_default_template')->defaultNull()->end()
->scalarNode('path')->defaultValue('/_fragment')->end()
->end()
->end()
->end()
;
}
private function addProfilerSection(ArrayNodeDefinition $rootNode)
{
$rootNode
->children()
->arrayNode('profiler')
->info('profiler configuration')
->canBeEnabled()
->children()
->booleanNode('collect')->defaultTrue()->end()
->scalarNode('collect_parameter')->defaultNull()->info('The name of the parameter to use to enable or disable collection on a per request basis')->end()
->booleanNode('only_exceptions')->defaultFalse()->end()
->booleanNode('only_main_requests')->defaultFalse()->end()
->booleanNode('only_master_requests')->setDeprecated('symfony/framework-bundle', '5.3', 'Option "%node%" at "%path%" is deprecated, use "only_main_requests" instead.')->defaultFalse()->end()
->scalarNode('dsn')->defaultValue('file:%kernel.cache_dir%/profiler')->end()
->end()
->end()
->end()
;
}
private function addWorkflowSection(ArrayNodeDefinition $rootNode)
{
$rootNode
->fixXmlConfig('workflow')
->children()
->arrayNode('workflows')
->canBeEnabled()
->beforeNormalization()
->always(function ($v) {
if (\is_array($v) && true === $v['enabled']) {
$workflows = $v;
unset($workflows['enabled']);
if (1 === \count($workflows) && isset($workflows[0]['enabled']) && 1 === \count($workflows[0])) {
$workflows = [];
}
if (1 === \count($workflows) && isset($workflows['workflows']) && !array_is_list($workflows['workflows']) && !empty(array_diff(array_keys($workflows['workflows']), ['audit_trail', 'type', 'marking_store', 'supports', 'support_strategy', 'initial_marking', 'places', 'transitions']))) {
$workflows = $workflows['workflows'];
}
foreach ($workflows as $key => $workflow) {
if (isset($workflow['enabled']) && false === $workflow['enabled']) {
throw new LogicException(sprintf('Cannot disable a single workflow. Remove the configuration for the workflow "%s" instead.', $workflow['name']));
}
unset($workflows[$key]['enabled']);
}
$v = [
'enabled' => true,
'workflows' => $workflows,
];
}
return $v;
})
->end()
->children()
->arrayNode('workflows')
->useAttributeAsKey('name')
->prototype('array')
->fixXmlConfig('support')
->fixXmlConfig('place')
->fixXmlConfig('transition')
->fixXmlConfig('event_to_dispatch', 'events_to_dispatch')
->children()
->arrayNode('audit_trail')
->canBeEnabled()
->end()
->enumNode('type')
->values(['workflow', 'state_machine'])
->defaultValue('state_machine')
->end()
->arrayNode('marking_store')
->children()
->enumNode('type')
->values(['method'])
->end()
->scalarNode('property')
->defaultValue('marking')
->end()
->scalarNode('service')
->cannotBeEmpty()
->end()
->end()
->end()
->arrayNode('supports')
->beforeNormalization()
->ifString()
->then(function ($v) { return [$v]; })
->end()
->prototype('scalar')
->cannotBeEmpty()
->validate()
->ifTrue(function ($v) { return !class_exists($v) && !interface_exists($v, false); })
->thenInvalid('The supported class or interface "%s" does not exist.')
->end()
->end()
->end()
->scalarNode('support_strategy')
->cannotBeEmpty()
->end()
->arrayNode('initial_marking')
->beforeNormalization()->castToArray()->end()
->defaultValue([])
->prototype('scalar')->end()
->end()
->variableNode('events_to_dispatch')
->defaultValue(null)
->validate()
->ifTrue(function ($v) {
if (null === $v) {
return false;
}
if (!\is_array($v)) {
return true;
}
foreach ($v as $value) {
if (!\is_string($value)) {
return true;
}
if (class_exists(WorkflowEvents::class) && !\in_array($value, WorkflowEvents::ALIASES)) {
return true;
}
}
return false;
})
->thenInvalid('The value must be "null" or an array of workflow events (like ["workflow.enter"]).')
->end()
->info('Select which Transition events should be dispatched for this Workflow')
->example(['workflow.enter', 'workflow.transition'])
->end()
->arrayNode('places')
->beforeNormalization()
->always()
->then(function ($places) {
// It's an indexed array of shape ['place1', 'place2']
if (isset($places[0]) && \is_string($places[0])) {
return array_map(function (string $place) {
return ['name' => $place];
}, $places);
}
// It's an indexed array, we let the validation occur
if (isset($places[0]) && \is_array($places[0])) {
return $places;
}
foreach ($places as $name => $place) {
if (\is_array($place) && \array_key_exists('name', $place)) {
continue;
}
$place['name'] = $name;
$places[$name] = $place;
}
return array_values($places);
})
->end()
->isRequired()
->requiresAtLeastOneElement()
->prototype('array')
->children()
->scalarNode('name')
->isRequired()
->cannotBeEmpty()
->end()
->arrayNode('metadata')
->normalizeKeys(false)
->defaultValue([])
->example(['color' => 'blue', 'description' => 'Workflow to manage article.'])
->prototype('variable')
->end()
->end()
->end()
->end()
->end()
->arrayNode('transitions')
->beforeNormalization()
->always()
->then(function ($transitions) {
// It's an indexed array, we let the validation occur
if (isset($transitions[0]) && \is_array($transitions[0])) {
return $transitions;
}
foreach ($transitions as $name => $transition) {
if (\is_array($transition) && \array_key_exists('name', $transition)) {
continue;
}
$transition['name'] = $name;
$transitions[$name] = $transition;
}
return $transitions;
})
->end()
->isRequired()
->requiresAtLeastOneElement()
->prototype('array')
->children()
->scalarNode('name')
->isRequired()
->cannotBeEmpty()
->end()
->scalarNode('guard')
->cannotBeEmpty()
->info('An expression to block the transition')
->example('is_fully_authenticated() and is_granted(\'ROLE_JOURNALIST\') and subject.getTitle() == \'My first article\'')
->end()
->arrayNode('from')
->beforeNormalization()
->ifString()
->then(function ($v) { return [$v]; })
->end()
->requiresAtLeastOneElement()
->prototype('scalar')
->cannotBeEmpty()
->end()
->end()
->arrayNode('to')
->beforeNormalization()
->ifString()
->then(function ($v) { return [$v]; })
->end()
->requiresAtLeastOneElement()
->prototype('scalar')
->cannotBeEmpty()
->end()
->end()
->arrayNode('metadata')
->normalizeKeys(false)
->defaultValue([])
->example(['color' => 'blue', 'description' => 'Workflow to manage article.'])
->prototype('variable')
->end()
->end()
->end()
->end()
->end()
->arrayNode('metadata')
->normalizeKeys(false)
->defaultValue([])
->example(['color' => 'blue', 'description' => 'Workflow to manage article.'])
->prototype('variable')
->end()
->end()
->end()
->validate()
->ifTrue(function ($v) {
return $v['supports'] && isset($v['support_strategy']);
})
->thenInvalid('"supports" and "support_strategy" cannot be used together.')
->end()
->validate()
->ifTrue(function ($v) {
return !$v['supports'] && !isset($v['support_strategy']);
})
->thenInvalid('"supports" or "support_strategy" should be configured.')
->end()
->beforeNormalization()
->always()
->then(function ($values) {
// Special case to deal with XML when the user wants an empty array
if (\array_key_exists('event_to_dispatch', $values) && null === $values['event_to_dispatch']) {
$values['events_to_dispatch'] = [];
unset($values['event_to_dispatch']);
}
return $values;
})
->end()
->end()
->end()
->end()
->end()
->end()
;
}
private function addRouterSection(ArrayNodeDefinition $rootNode)
{
$rootNode
->children()
->arrayNode('router')
->info('router configuration')
->canBeEnabled()
->children()
->scalarNode('resource')->isRequired()->end()
->scalarNode('type')->end()
->scalarNode('default_uri')
->info('The default URI used to generate URLs in a non-HTTP context')
->defaultNull()
->end()
->scalarNode('http_port')->defaultValue(80)->end()
->scalarNode('https_port')->defaultValue(443)->end()
->scalarNode('strict_requirements')
->info(
"set to true to throw an exception when a parameter does not match the requirements\n".
"set to false to disable exceptions when a parameter does not match the requirements (and return null instead)\n".
"set to null to disable parameter checks against requirements\n".
"'true' is the preferred configuration in development mode, while 'false' or 'null' might be preferred in production"
)
->defaultTrue()
->end()
->booleanNode('utf8')->defaultNull()->end()
->end()
->end()
->end()
;
}
private function addSessionSection(ArrayNodeDefinition $rootNode)
{
$rootNode
->children()
->arrayNode('session')
->info('session configuration')
->canBeEnabled()
->beforeNormalization()
->ifTrue(function ($v) {
return \is_array($v) && isset($v['storage_id']) && isset($v['storage_factory_id']);
})
->thenInvalid('You cannot use both "storage_id" and "storage_factory_id" at the same time under "framework.session"')
->end()
->children()
->scalarNode('storage_id')->defaultValue('session.storage.native')->end()
->scalarNode('storage_factory_id')->defaultNull()->end()
->scalarNode('handler_id')->defaultValue('session.handler.native_file')->end()
->scalarNode('name')
->validate()
->ifTrue(function ($v) {
parse_str($v, $parsed);
return implode('&', array_keys($parsed)) !== (string) $v;
})
->thenInvalid('Session name %s contains illegal character(s)')
->end()
->end()
->scalarNode('cookie_lifetime')->end()
->scalarNode('cookie_path')->end()
->scalarNode('cookie_domain')->end()
->enumNode('cookie_secure')->values([true, false, 'auto'])->end()
->booleanNode('cookie_httponly')->defaultTrue()->end()
->enumNode('cookie_samesite')->values([null, Cookie::SAMESITE_LAX, Cookie::SAMESITE_STRICT, Cookie::SAMESITE_NONE])->defaultNull()->end()
->booleanNode('use_cookies')->end()
->scalarNode('gc_divisor')->end()
->scalarNode('gc_probability')->defaultValue(1)->end()
->scalarNode('gc_maxlifetime')->end()
->scalarNode('save_path')->defaultValue('%kernel.cache_dir%/sessions')->end()
->integerNode('metadata_update_threshold')
->defaultValue(0)
->info('seconds to wait between 2 session metadata updates')
->end()
->integerNode('sid_length')
->min(22)
->max(256)
->end()
->integerNode('sid_bits_per_character')
->min(4)
->max(6)
->end()
->end()
->end()
->end()
;
}
private function addRequestSection(ArrayNodeDefinition $rootNode)
{
$rootNode
->children()
->arrayNode('request')
->info('request configuration')
->canBeEnabled()
->fixXmlConfig('format')
->children()
->arrayNode('formats')
->useAttributeAsKey('name')
->prototype('array')
->beforeNormalization()
->ifTrue(function ($v) { return \is_array($v) && isset($v['mime_type']); })
->then(function ($v) { return $v['mime_type']; })
->end()
->beforeNormalization()->castToArray()->end()
->prototype('scalar')->end()
->end()
->end()
->end()
->end()
->end()
;
}
private function addAssetsSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone)
{
$rootNode
->children()
->arrayNode('assets')
->info('assets configuration')
->{$enableIfStandalone('symfony/asset', Package::class)}()
->fixXmlConfig('base_url')
->children()
->booleanNode('strict_mode')
->info('Throw an exception if an entry is missing from the manifest.json')
->defaultFalse()
->end()
->scalarNode('version_strategy')->defaultNull()->end()
->scalarNode('version')->defaultNull()->end()
->scalarNode('version_format')->defaultValue('%%s?%%s')->end()
->scalarNode('json_manifest_path')->defaultNull()->end()
->scalarNode('base_path')->defaultValue('')->end()
->arrayNode('base_urls')
->requiresAtLeastOneElement()
->beforeNormalization()->castToArray()->end()
->prototype('scalar')->end()
->end()
->end()
->validate()
->ifTrue(function ($v) {
return isset($v['version_strategy']) && isset($v['version']);
})
->thenInvalid('You cannot use both "version_strategy" and "version" at the same time under "assets".')
->end()
->validate()
->ifTrue(function ($v) {
return isset($v['version_strategy']) && isset($v['json_manifest_path']);
})
->thenInvalid('You cannot use both "version_strategy" and "json_manifest_path" at the same time under "assets".')
->end()
->validate()
->ifTrue(function ($v) {
return isset($v['version']) && isset($v['json_manifest_path']);
})
->thenInvalid('You cannot use both "version" and "json_manifest_path" at the same time under "assets".')
->end()
->fixXmlConfig('package')
->children()
->arrayNode('packages')
->normalizeKeys(false)
->useAttributeAsKey('name')
->prototype('array')
->fixXmlConfig('base_url')
->children()
->booleanNode('strict_mode')
->info('Throw an exception if an entry is missing from the manifest.json')
->defaultFalse()
->end()
->scalarNode('version_strategy')->defaultNull()->end()
->scalarNode('version')
->beforeNormalization()
->ifTrue(function ($v) { return '' === $v; })
->then(function ($v) { return; })
->end()
->end()
->scalarNode('version_format')->defaultNull()->end()
->scalarNode('json_manifest_path')->defaultNull()->end()
->scalarNode('base_path')->defaultValue('')->end()
->arrayNode('base_urls')
->requiresAtLeastOneElement()
->beforeNormalization()->castToArray()->end()
->prototype('scalar')->end()
->end()
->end()
->validate()
->ifTrue(function ($v) {
return isset($v['version_strategy']) && isset($v['version']);
})
->thenInvalid('You cannot use both "version_strategy" and "version" at the same time under "assets" packages.')
->end()
->validate()
->ifTrue(function ($v) {
return isset($v['version_strategy']) && isset($v['json_manifest_path']);
})
->thenInvalid('You cannot use both "version_strategy" and "json_manifest_path" at the same time under "assets" packages.')
->end()
->validate()
->ifTrue(function ($v) {
return isset($v['version']) && isset($v['json_manifest_path']);
})
->thenInvalid('You cannot use both "version" and "json_manifest_path" at the same time under "assets" packages.')
->end()
->end()
->end()
->end()
->end()
->end()
;
}
private function addTranslatorSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone)
{
$rootNode
->children()
->arrayNode('translator')
->info('translator configuration')
->{$enableIfStandalone('symfony/translation', Translator::class)}()
->fixXmlConfig('fallback')
->fixXmlConfig('path')
->fixXmlConfig('enabled_locale')
->fixXmlConfig('provider')
->children()
->arrayNode('fallbacks')
->info('Defaults to the value of "default_locale".')
->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end()
->prototype('scalar')->end()
->defaultValue([])
->end()
->booleanNode('logging')->defaultValue(false)->end()
->scalarNode('formatter')->defaultValue('translator.formatter.default')->end()
->scalarNode('cache_dir')->defaultValue('%kernel.cache_dir%/translations')->end()
->scalarNode('default_path')
->info('The default path used to load translations')
->defaultValue('%kernel.project_dir%/translations')
->end()
->arrayNode('paths')
->prototype('scalar')->end()
->end()
->arrayNode('enabled_locales')
->setDeprecated('symfony/framework-bundle', '5.3', 'Option "%node%" at "%path%" is deprecated, set the "framework.enabled_locales" option instead.')
->prototype('scalar')->end()
->defaultValue([])
->end()
->arrayNode('pseudo_localization')
->canBeEnabled()
->fixXmlConfig('localizable_html_attribute')
->children()
->booleanNode('accents')->defaultTrue()->end()
->floatNode('expansion_factor')
->min(1.0)
->defaultValue(1.0)
->end()
->booleanNode('brackets')->defaultTrue()->end()
->booleanNode('parse_html')->defaultFalse()->end()
->arrayNode('localizable_html_attributes')
->prototype('scalar')->end()
->end()
->end()
->end()
->arrayNode('providers')
->info('Translation providers you can read/write your translations from')
->useAttributeAsKey('name')
->prototype('array')
->fixXmlConfig('domain')
->fixXmlConfig('locale')
->children()
->scalarNode('dsn')->end()
->arrayNode('domains')
->prototype('scalar')->end()
->defaultValue([])
->end()
->arrayNode('locales')
->prototype('scalar')->end()
->defaultValue([])
->info('If not set, all locales listed under framework.enabled_locales are used.')
->end()
->end()
->end()
->defaultValue([])
->end()
->end()
->end()
->end()
;
}
private function addValidationSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone, callable $willBeAvailable)
{
$rootNode
->children()
->arrayNode('validation')
->info('validation configuration')
->{$enableIfStandalone('symfony/validator', Validation::class)}()
->children()
->scalarNode('cache')->end()
->booleanNode('enable_annotations')->{!class_exists(FullStack::class) && (\PHP_VERSION_ID >= 80000 || $willBeAvailable('doctrine/annotations', Annotation::class, 'symfony/validator')) ? 'defaultTrue' : 'defaultFalse'}()->end()
->arrayNode('static_method')
->defaultValue(['loadValidatorMetadata'])
->prototype('scalar')->end()
->treatFalseLike([])
->validate()->castToArray()->end()
->end()
->scalarNode('translation_domain')->defaultValue('validators')->end()
->enumNode('email_validation_mode')->values(['html5', 'loose', 'strict'])->end()
->arrayNode('mapping')
->addDefaultsIfNotSet()
->fixXmlConfig('path')
->children()
->arrayNode('paths')
->prototype('scalar')->end()
->end()
->end()
->end()
->arrayNode('not_compromised_password')
->canBeDisabled()
->children()
->booleanNode('enabled')
->defaultTrue()
->info('When disabled, compromised passwords will be accepted as valid.')
->end()
->scalarNode('endpoint')
->defaultNull()
->info('API endpoint for the NotCompromisedPassword Validator.')
->end()
->end()
->end()
->arrayNode('auto_mapping')
->info('A collection of namespaces for which auto-mapping will be enabled by default, or null to opt-in with the EnableAutoMapping constraint.')
->example([
'App\\Entity\\' => [],
'App\\WithSpecificLoaders\\' => ['validator.property_info_loader'],
])
->useAttributeAsKey('namespace')
->normalizeKeys(false)
->beforeNormalization()
->ifArray()
->then(function (array $values): array {
foreach ($values as $k => $v) {
if (isset($v['service'])) {
continue;
}
if (isset($v['namespace'])) {
$values[$k]['services'] = [];
continue;
}
if (!\is_array($v)) {
$values[$v]['services'] = [];
unset($values[$k]);
continue;
}
$tmp = $v;
unset($values[$k]);
$values[$k]['services'] = $tmp;
}
return $values;
})
->end()
->arrayPrototype()
->fixXmlConfig('service')
->children()
->arrayNode('services')
->prototype('scalar')->end()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
;
}
private function addAnnotationsSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable)
{
$doctrineCache = $willBeAvailable('doctrine/cache', Cache::class, 'doctrine/annotations');
$psr6Cache = $willBeAvailable('symfony/cache', PsrCachedReader::class, 'doctrine/annotations');
$rootNode
->children()
->arrayNode('annotations')
->info('annotation configuration')
->{$willBeAvailable('doctrine/annotations', Annotation::class) ? 'canBeDisabled' : 'canBeEnabled'}()
->children()
->scalarNode('cache')->defaultValue(($doctrineCache || $psr6Cache) ? 'php_array' : 'none')->end()
->scalarNode('file_cache_dir')->defaultValue('%kernel.cache_dir%/annotations')->end()
->booleanNode('debug')->defaultValue($this->debug)->end()
->end()
->end()
->end()
;
}
private function addSerializerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone, callable $willBeAvailable)
{
$rootNode
->children()
->arrayNode('serializer')
->info('serializer configuration')
->{$enableIfStandalone('symfony/serializer', Serializer::class)}()
->children()
->booleanNode('enable_annotations')->{!class_exists(FullStack::class) && (\PHP_VERSION_ID >= 80000 || $willBeAvailable('doctrine/annotations', Annotation::class, 'symfony/serializer')) ? 'defaultTrue' : 'defaultFalse'}()->end()
->scalarNode('name_converter')->end()
->scalarNode('circular_reference_handler')->end()
->scalarNode('max_depth_handler')->end()
->arrayNode('mapping')
->addDefaultsIfNotSet()
->fixXmlConfig('path')
->children()
->arrayNode('paths')
->prototype('scalar')->end()
->end()
->end()
->end()
->arrayNode('default_context')
->normalizeKeys(false)
->useAttributeAsKey('name')
->defaultValue([])
->prototype('variable')->end()
->end()
->end()
->end()
->end()
;
}
private function addPropertyAccessSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable)
{
$rootNode
->children()
->arrayNode('property_access')
->addDefaultsIfNotSet()
->info('Property access configuration')
->{$willBeAvailable('symfony/property-access', PropertyAccessor::class) ? 'canBeDisabled' : 'canBeEnabled'}()
->children()
->booleanNode('magic_call')->defaultFalse()->end()
->booleanNode('magic_get')->defaultTrue()->end()
->booleanNode('magic_set')->defaultTrue()->end()
->booleanNode('throw_exception_on_invalid_index')->defaultFalse()->end()
->booleanNode('throw_exception_on_invalid_property_path')->defaultTrue()->end()
->end()
->end()
->end()
;
}
private function addPropertyInfoSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone)
{
$rootNode
->children()
->arrayNode('property_info')
->info('Property info configuration')
->{$enableIfStandalone('symfony/property-info', PropertyInfoExtractorInterface::class)}()
->end()
->end()
;
}
private function addCacheSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable)
{
$rootNode
->children()
->arrayNode('cache')
->info('Cache configuration')
->addDefaultsIfNotSet()
->fixXmlConfig('pool')
->children()
->scalarNode('prefix_seed')
->info('Used to namespace cache keys when using several apps with the same shared backend')
->defaultValue('_%kernel.project_dir%.%kernel.container_class%')
->example('my-application-name/%kernel.environment%')
->end()
->scalarNode('app')
->info('App related cache pools configuration')
->defaultValue('cache.adapter.filesystem')
->end()
->scalarNode('system')
->info('System related cache pools configuration')
->defaultValue('cache.adapter.system')
->end()
->scalarNode('directory')->defaultValue('%kernel.cache_dir%/pools/app')->end()
->scalarNode('default_doctrine_provider')->end()
->scalarNode('default_psr6_provider')->end()
->scalarNode('default_redis_provider')->defaultValue('redis://localhost')->end()
->scalarNode('default_memcached_provider')->defaultValue('memcached://localhost')->end()
->scalarNode('default_doctrine_dbal_provider')->defaultValue('database_connection')->end()
->scalarNode('default_pdo_provider')->defaultValue($willBeAvailable('doctrine/dbal', Connection::class) && class_exists(DoctrineAdapter::class) ? 'database_connection' : null)->end()
->arrayNode('pools')
->useAttributeAsKey('name')
->prototype('array')
->fixXmlConfig('adapter')
->beforeNormalization()
->ifTrue(function ($v) { return isset($v['provider']) && \is_array($v['adapters'] ?? $v['adapter'] ?? null) && 1 < \count($v['adapters'] ?? $v['adapter']); })
->thenInvalid('Pool cannot have a "provider" while more than one adapter is defined')
->end()
->children()
->arrayNode('adapters')
->performNoDeepMerging()
->info('One or more adapters to chain for creating the pool, defaults to "cache.app".')
->beforeNormalization()->castToArray()->end()
->beforeNormalization()
->always()->then(function ($values) {
if ([0] === array_keys($values) && \is_array($values[0])) {
return $values[0];
}
$adapters = [];
foreach ($values as $k => $v) {
if (\is_int($k) && \is_string($v)) {
$adapters[] = $v;
} elseif (!\is_array($v)) {
$adapters[$k] = $v;
} elseif (isset($v['provider'])) {
$adapters[$v['provider']] = $v['name'] ?? $v;
} else {
$adapters[] = $v['name'] ?? $v;
}
}
return $adapters;
})
->end()
->prototype('scalar')->end()
->end()
->scalarNode('tags')->defaultNull()->end()
->booleanNode('public')->defaultFalse()->end()
->scalarNode('default_lifetime')
->info('Default lifetime of the pool')
->example('"600" for 5 minutes expressed in seconds, "PT5M" for five minutes expressed as ISO 8601 time interval, or "5 minutes" as a date expression')
->end()
->scalarNode('provider')
->info('Overwrite the setting from the default provider for this adapter.')
->end()
->scalarNode('early_expiration_message_bus')
->example('"messenger.default_bus" to send early expiration events to the default Messenger bus.')
->end()
->scalarNode('clearer')->end()
->end()
->end()
->validate()
->ifTrue(function ($v) { return isset($v['cache.app']) || isset($v['cache.system']); })
->thenInvalid('"cache.app" and "cache.system" are reserved names')
->end()
->end()
->end()
->end()
->end()
;
}
private function addPhpErrorsSection(ArrayNodeDefinition $rootNode)
{
$rootNode
->children()
->arrayNode('php_errors')
->info('PHP errors handling configuration')
->addDefaultsIfNotSet()
->children()
->variableNode('log')
->info('Use the application logger instead of the PHP logger for logging PHP errors.')
->example('"true" to use the default configuration: log all errors. "false" to disable. An integer bit field of E_* constants, or an array mapping E_* constants to log levels.')
->defaultValue($this->debug)
->treatNullLike($this->debug)
->beforeNormalization()
->ifArray()
->then(function (array $v): array {
if (!($v[0]['type'] ?? false)) {
return $v;
}
// Fix XML normalization
$ret = [];
foreach ($v as ['type' => $type, 'logLevel' => $logLevel]) {
$ret[$type] = $logLevel;
}
return $ret;
})
->end()
->validate()
->ifTrue(function ($v) { return !(\is_int($v) || \is_bool($v) || \is_array($v)); })
->thenInvalid('The "php_errors.log" parameter should be either an integer, a boolean, or an array')
->end()
->end()
->booleanNode('throw')
->info('Throw PHP errors as \ErrorException instances.')
->defaultValue($this->debug)
->treatNullLike($this->debug)
->end()
->end()
->end()
->end()
;
}
private function addExceptionsSection(ArrayNodeDefinition $rootNode)
{
$logLevels = (new \ReflectionClass(LogLevel::class))->getConstants();
$rootNode
->children()
->arrayNode('exceptions')
->info('Exception handling configuration')
->beforeNormalization()
->ifArray()
->then(function (array $v): array {
if (!\array_key_exists('exception', $v)) {
return $v;
}
// Fix XML normalization
$data = isset($v['exception'][0]) ? $v['exception'] : [$v['exception']];
$exceptions = [];
foreach ($data as $exception) {
$config = [];
if (\array_key_exists('log-level', $exception)) {
$config['log_level'] = $exception['log-level'];
}
if (\array_key_exists('status-code', $exception)) {
$config['status_code'] = $exception['status-code'];
}
$exceptions[$exception['name']] = $config;
}
return $exceptions;
})
->end()
->prototype('array')
->fixXmlConfig('exception')
->children()
->scalarNode('log_level')
->info('The level of log message. Null to let Symfony decide.')
->validate()
->ifTrue(function ($v) use ($logLevels) { return !\in_array($v, $logLevels); })
->thenInvalid(sprintf('The log level is not valid. Pick one among "%s".', implode('", "', $logLevels)))
->end()
->defaultNull()
->end()
->scalarNode('status_code')
->info('The status code of the response. Null to let Symfony decide.')
->validate()
->ifTrue(function ($v) { return $v < 100 || $v > 599; })
->thenInvalid('The log level is not valid. Pick a value between 100 and 599.')
->end()
->defaultNull()
->end()
->end()
->end()
->end()
->end()
;
}
private function addLockSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone)
{
$rootNode
->children()
->arrayNode('lock')
->info('Lock configuration')
->{$enableIfStandalone('symfony/lock', Lock::class)}()
->beforeNormalization()
->ifString()->then(function ($v) { return ['enabled' => true, 'resources' => $v]; })
->end()
->beforeNormalization()
->ifTrue(function ($v) { return \is_array($v) && !isset($v['enabled']); })
->then(function ($v) { return $v + ['enabled' => true]; })
->end()
->beforeNormalization()
->ifTrue(function ($v) { return \is_array($v) && !isset($v['resources']) && !isset($v['resource']); })
->then(function ($v) {
$e = $v['enabled'];
unset($v['enabled']);
return ['enabled' => $e, 'resources' => $v];
})
->end()
->addDefaultsIfNotSet()
->fixXmlConfig('resource')
->children()
->arrayNode('resources')
->normalizeKeys(false)
->useAttributeAsKey('name')
->requiresAtLeastOneElement()
->defaultValue(['default' => [class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphore' : 'flock']])
->beforeNormalization()
->ifString()->then(function ($v) { return ['default' => $v]; })
->end()
->beforeNormalization()
->ifTrue(function ($v) { return \is_array($v) && array_is_list($v); })
->then(function ($v) {
$resources = [];
foreach ($v as $resource) {
$resources[] = \is_array($resource) && isset($resource['name'])
? [$resource['name'] => $resource['value']]
: ['default' => $resource]
;
}
return array_merge_recursive([], ...$resources);
})
->end()
->prototype('array')
->performNoDeepMerging()
->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end()
->prototype('scalar')->end()
->end()
->end()
->end()
->end()
->end()
;
}
private function addWebLinkSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone)
{
$rootNode
->children()
->arrayNode('web_link')
->info('web links configuration')
->{$enableIfStandalone('symfony/weblink', HttpHeaderSerializer::class)}()
->end()
->end()
;
}
private function addMessengerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone)
{
$rootNode
->children()
->arrayNode('messenger')
->info('Messenger configuration')
->{$enableIfStandalone('symfony/messenger', MessageBusInterface::class)}()
->fixXmlConfig('transport')
->fixXmlConfig('bus', 'buses')
->validate()
->ifTrue(function ($v) { return isset($v['buses']) && \count($v['buses']) > 1 && null === $v['default_bus']; })
->thenInvalid('You must specify the "default_bus" if you define more than one bus.')
->end()
->validate()
->ifTrue(static function ($v): bool { return isset($v['buses']) && null !== $v['default_bus'] && !isset($v['buses'][$v['default_bus']]); })
->then(static function (array $v): void { throw new InvalidConfigurationException(sprintf('The specified default bus "%s" is not configured. Available buses are "%s".', $v['default_bus'], implode('", "', array_keys($v['buses'])))); })
->end()
->children()
->arrayNode('routing')
->normalizeKeys(false)
->useAttributeAsKey('message_class')
->beforeNormalization()
->always()
->then(function ($config) {
if (!\is_array($config)) {
return [];
}
// If XML config with only one routing attribute
if (2 === \count($config) && isset($config['message-class']) && isset($config['sender'])) {
$config = [0 => $config];
}
$newConfig = [];
foreach ($config as $k => $v) {
if (!\is_int($k)) {
$newConfig[$k] = [
'senders' => $v['senders'] ?? (\is_array($v) ? array_values($v) : [$v]),
];
} else {
$newConfig[$v['message-class']]['senders'] = array_map(
function ($a) {
return \is_string($a) ? $a : $a['service'];
},
array_values($v['sender'])
);
}
}
return $newConfig;
})
->end()
->prototype('array')
->performNoDeepMerging()
->children()
->arrayNode('senders')
->requiresAtLeastOneElement()
->prototype('scalar')->end()
->end()
->end()
->end()
->end()
->arrayNode('serializer')
->addDefaultsIfNotSet()
->children()
->scalarNode('default_serializer')
->defaultValue('messenger.transport.native_php_serializer')
->info('Service id to use as the default serializer for the transports.')
->end()
->arrayNode('symfony_serializer')
->addDefaultsIfNotSet()
->children()
->scalarNode('format')->defaultValue('json')->info('Serialization format for the messenger.transport.symfony_serializer service (which is not the serializer used by default).')->end()
->arrayNode('context')
->normalizeKeys(false)
->useAttributeAsKey('name')
->defaultValue([])
->info('Context array for the messenger.transport.symfony_serializer service (which is not the serializer used by default).')
->prototype('variable')->end()
->end()
->end()
->end()
->end()
->end()
->arrayNode('transports')
->normalizeKeys(false)
->useAttributeAsKey('name')
->arrayPrototype()
->beforeNormalization()
->ifString()
->then(function (string $dsn) {
return ['dsn' => $dsn];
})
->end()
->fixXmlConfig('option')
->children()
->scalarNode('dsn')->end()
->scalarNode('serializer')->defaultNull()->info('Service id of a custom serializer to use.')->end()
->arrayNode('options')
->normalizeKeys(false)
->defaultValue([])
->prototype('variable')
->end()
->end()
->scalarNode('failure_transport')
->defaultNull()
->info('Transport name to send failed messages to (after all retries have failed).')
->end()
->arrayNode('retry_strategy')
->addDefaultsIfNotSet()
->beforeNormalization()
->always(function ($v) {
if (isset($v['service']) && (isset($v['max_retries']) || isset($v['delay']) || isset($v['multiplier']) || isset($v['max_delay']))) {
throw new \InvalidArgumentException('The "service" cannot be used along with the other "retry_strategy" options.');
}
return $v;
})
->end()
->children()
->scalarNode('service')->defaultNull()->info('Service id to override the retry strategy entirely')->end()
->integerNode('max_retries')->defaultValue(3)->min(0)->end()
->integerNode('delay')->defaultValue(1000)->min(0)->info('Time in ms to delay (or the initial value when multiplier is used)')->end()
->floatNode('multiplier')->defaultValue(2)->min(1)->info('If greater than 1, delay will grow exponentially for each retry: this delay = (delay * (multiple ^ retries))')->end()
->integerNode('max_delay')->defaultValue(0)->min(0)->info('Max time in ms that a retry should ever be delayed (0 = infinite)')->end()
->end()
->end()
->end()
->end()
->end()
->scalarNode('failure_transport')
->defaultNull()
->info('Transport name to send failed messages to (after all retries have failed).')
->end()
->booleanNode('reset_on_message')
->defaultNull()
->info('Reset container services after each message.')
->end()
->scalarNode('default_bus')->defaultNull()->end()
->arrayNode('buses')
->defaultValue(['messenger.bus.default' => ['default_middleware' => true, 'middleware' => []]])
->normalizeKeys(false)
->useAttributeAsKey('name')
->arrayPrototype()
->addDefaultsIfNotSet()
->children()
->enumNode('default_middleware')
->values([true, false, 'allow_no_handlers'])
->defaultTrue()
->end()
->arrayNode('middleware')
->performNoDeepMerging()
->beforeNormalization()
->ifTrue(function ($v) { return \is_string($v) || (\is_array($v) && !\is_int(key($v))); })
->then(function ($v) { return [$v]; })
->end()
->defaultValue([])
->arrayPrototype()
->beforeNormalization()
->always()
->then(function ($middleware): array {
if (!\is_array($middleware)) {
return ['id' => $middleware];
}
if (isset($middleware['id'])) {
return $middleware;
}
if (1 < \count($middleware)) {
throw new \InvalidArgumentException('Invalid middleware at path "framework.messenger": a map with a single factory id as key and its arguments as value was expected, '.json_encode($middleware).' given.');
}
return [
'id' => key($middleware),
'arguments' => current($middleware),
];
})
->end()
->fixXmlConfig('argument')
->children()
->scalarNode('id')->isRequired()->cannotBeEmpty()->end()
->arrayNode('arguments')
->normalizeKeys(false)
->defaultValue([])
->prototype('variable')
->end()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
;
}
private function addRobotsIndexSection(ArrayNodeDefinition $rootNode)
{
$rootNode
->children()
->booleanNode('disallow_search_engine_index')
->info('Enabled by default when debug is enabled.')
->defaultValue($this->debug)
->treatNullLike($this->debug)
->end()
->end()
;
}
private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone)
{
$rootNode
->children()
->arrayNode('http_client')
->info('HTTP Client configuration')
->{$enableIfStandalone('symfony/http-client', HttpClient::class)}()
->fixXmlConfig('scoped_client')
->beforeNormalization()
->always(function ($config) {
if (empty($config['scoped_clients']) || !\is_array($config['default_options']['retry_failed'] ?? null)) {
return $config;
}
foreach ($config['scoped_clients'] as &$scopedConfig) {
if (!isset($scopedConfig['retry_failed']) || true === $scopedConfig['retry_failed']) {
$scopedConfig['retry_failed'] = $config['default_options']['retry_failed'];
continue;
}
if (\is_array($scopedConfig['retry_failed'])) {
$scopedConfig['retry_failed'] = $scopedConfig['retry_failed'] + $config['default_options']['retry_failed'];
}
}
return $config;
})
->end()
->children()
->integerNode('max_host_connections')
->info('The maximum number of connections to a single host.')
->end()
->arrayNode('default_options')
->fixXmlConfig('header')
->children()
->arrayNode('headers')
->info('Associative array: header => value(s).')
->useAttributeAsKey('name')
->normalizeKeys(false)
->variablePrototype()->end()
->end()
->integerNode('max_redirects')
->info('The maximum number of redirects to follow.')
->end()
->scalarNode('http_version')
->info('The default HTTP version, typically 1.1 or 2.0, leave to null for the best version.')
->end()
->arrayNode('resolve')
->info('Associative array: domain => IP.')
->useAttributeAsKey('host')
->beforeNormalization()
->always(function ($config) {
if (!\is_array($config)) {
return [];
}
if (!isset($config['host'], $config['value']) || \count($config) > 2) {
return $config;
}
return [$config['host'] => $config['value']];
})
->end()
->normalizeKeys(false)
->scalarPrototype()->end()
->end()
->scalarNode('proxy')
->info('The URL of the proxy to pass requests through or null for automatic detection.')
->end()
->scalarNode('no_proxy')
->info('A comma separated list of hosts that do not require a proxy to be reached.')
->end()
->floatNode('timeout')
->info('The idle timeout, defaults to the "default_socket_timeout" ini parameter.')
->end()
->floatNode('max_duration')
->info('The maximum execution time for the request+response as a whole.')
->end()
->scalarNode('bindto')
->info('A network interface name, IP address, a host name or a UNIX socket to bind to.')
->end()
->booleanNode('verify_peer')
->info('Indicates if the peer should be verified in an SSL/TLS context.')
->end()
->booleanNode('verify_host')
->info('Indicates if the host should exist as a certificate common name.')
->end()
->scalarNode('cafile')
->info('A certificate authority file.')
->end()
->scalarNode('capath')
->info('A directory that contains multiple certificate authority files.')
->end()
->scalarNode('local_cert')
->info('A PEM formatted certificate file.')
->end()
->scalarNode('local_pk')
->info('A private key file.')
->end()
->scalarNode('passphrase')
->info('The passphrase used to encrypt the "local_pk" file.')
->end()
->scalarNode('ciphers')
->info('A list of SSL/TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...)')
->end()
->arrayNode('peer_fingerprint')
->info('Associative array: hashing algorithm => hash(es).')
->normalizeKeys(false)
->children()
->variableNode('sha1')->end()
->variableNode('pin-sha256')->end()
->variableNode('md5')->end()
->end()
->end()
->append($this->addHttpClientRetrySection())
->end()
->end()
->scalarNode('mock_response_factory')
->info('The id of the service that should generate mock responses. It should be either an invokable or an iterable.')
->end()
->arrayNode('scoped_clients')
->useAttributeAsKey('name')
->normalizeKeys(false)
->arrayPrototype()
->fixXmlConfig('header')
->beforeNormalization()
->always()
->then(function ($config) {
if (!class_exists(HttpClient::class)) {
throw new LogicException('HttpClient support cannot be enabled as the component is not installed. Try running "composer require symfony/http-client".');
}
return \is_array($config) ? $config : ['base_uri' => $config];
})
->end()
->validate()
->ifTrue(function ($v) { return !isset($v['scope']) && !isset($v['base_uri']); })
->thenInvalid('Either "scope" or "base_uri" should be defined.')
->end()
->validate()
->ifTrue(function ($v) { return !empty($v['query']) && !isset($v['base_uri']); })
->thenInvalid('"query" applies to "base_uri" but no base URI is defined.')
->end()
->children()
->scalarNode('scope')
->info('The regular expression that the request URL must match before adding the other options. When none is provided, the base URI is used instead.')
->cannotBeEmpty()
->end()
->scalarNode('base_uri')
->info('The URI to resolve relative URLs, following rules in RFC 3985, section 2.')
->cannotBeEmpty()
->end()
->scalarNode('auth_basic')
->info('An HTTP Basic authentication "username:password".')
->end()
->scalarNode('auth_bearer')
->info('A token enabling HTTP Bearer authorization.')
->end()
->scalarNode('auth_ntlm')
->info('A "username:password" pair to use Microsoft NTLM authentication (requires the cURL extension).')
->end()
->arrayNode('query')
->info('Associative array of query string values merged with the base URI.')
->useAttributeAsKey('key')
->beforeNormalization()
->always(function ($config) {
if (!\is_array($config)) {
return [];
}
if (!isset($config['key'], $config['value']) || \count($config) > 2) {
return $config;
}
return [$config['key'] => $config['value']];
})
->end()
->normalizeKeys(false)
->scalarPrototype()->end()
->end()
->arrayNode('headers')
->info('Associative array: header => value(s).')
->useAttributeAsKey('name')
->normalizeKeys(false)
->variablePrototype()->end()
->end()
->integerNode('max_redirects')
->info('The maximum number of redirects to follow.')
->end()
->scalarNode('http_version')
->info('The default HTTP version, typically 1.1 or 2.0, leave to null for the best version.')
->end()
->arrayNode('resolve')
->info('Associative array: domain => IP.')
->useAttributeAsKey('host')
->beforeNormalization()
->always(function ($config) {
if (!\is_array($config)) {
return [];
}
if (!isset($config['host'], $config['value']) || \count($config) > 2) {
return $config;
}
return [$config['host'] => $config['value']];
})
->end()
->normalizeKeys(false)
->scalarPrototype()->end()
->end()
->scalarNode('proxy')
->info('The URL of the proxy to pass requests through or null for automatic detection.')
->end()
->scalarNode('no_proxy')
->info('A comma separated list of hosts that do not require a proxy to be reached.')
->end()
->floatNode('timeout')
->info('The idle timeout, defaults to the "default_socket_timeout" ini parameter.')
->end()
->floatNode('max_duration')
->info('The maximum execution time for the request+response as a whole.')
->end()
->scalarNode('bindto')
->info('A network interface name, IP address, a host name or a UNIX socket to bind to.')
->end()
->booleanNode('verify_peer')
->info('Indicates if the peer should be verified in an SSL/TLS context.')
->end()
->booleanNode('verify_host')
->info('Indicates if the host should exist as a certificate common name.')
->end()
->scalarNode('cafile')
->info('A certificate authority file.')
->end()
->scalarNode('capath')
->info('A directory that contains multiple certificate authority files.')
->end()
->scalarNode('local_cert')
->info('A PEM formatted certificate file.')
->end()
->scalarNode('local_pk')
->info('A private key file.')
->end()
->scalarNode('passphrase')
->info('The passphrase used to encrypt the "local_pk" file.')
->end()
->scalarNode('ciphers')
->info('A list of SSL/TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...)')
->end()
->arrayNode('peer_fingerprint')
->info('Associative array: hashing algorithm => hash(es).')
->normalizeKeys(false)
->children()
->variableNode('sha1')->end()
->variableNode('pin-sha256')->end()
->variableNode('md5')->end()
->end()
->end()
->append($this->addHttpClientRetrySection())
->end()
->end()
->end()
->end()
->end()
->end()
;
}
private function addHttpClientRetrySection()
{
$root = new NodeBuilder();
return $root
->arrayNode('retry_failed')
->fixXmlConfig('http_code')
->canBeEnabled()
->addDefaultsIfNotSet()
->beforeNormalization()
->always(function ($v) {
if (isset($v['retry_strategy']) && (isset($v['http_codes']) || isset($v['delay']) || isset($v['multiplier']) || isset($v['max_delay']) || isset($v['jitter']))) {
throw new \InvalidArgumentException('The "retry_strategy" option cannot be used along with the "http_codes", "delay", "multiplier", "max_delay" or "jitter" options.');
}
return $v;
})
->end()
->children()
->scalarNode('retry_strategy')->defaultNull()->info('service id to override the retry strategy')->end()
->arrayNode('http_codes')
->performNoDeepMerging()
->beforeNormalization()
->ifArray()
->then(static function ($v) {
$list = [];
foreach ($v as $key => $val) {
if (is_numeric($val)) {
$list[] = ['code' => $val];
} elseif (\is_array($val)) {
if (isset($val['code']) || isset($val['methods'])) {
$list[] = $val;
} else {
$list[] = ['code' => $key, 'methods' => $val];
}
} elseif (true === $val || null === $val) {
$list[] = ['code' => $key];
}
}
return $list;
})
->end()
->useAttributeAsKey('code')
->arrayPrototype()
->fixXmlConfig('method')
->children()
->integerNode('code')->end()
->arrayNode('methods')
->beforeNormalization()
->ifArray()
->then(function ($v) {
return array_map('strtoupper', $v);
})
->end()
->prototype('scalar')->end()
->info('A list of HTTP methods that triggers a retry for this status code. When empty, all methods are retried')
->end()
->end()
->end()
->info('A list of HTTP status code that triggers a retry')
->end()
->integerNode('max_retries')->defaultValue(3)->min(0)->end()
->integerNode('delay')->defaultValue(1000)->min(0)->info('Time in ms to delay (or the initial value when multiplier is used)')->end()
->floatNode('multiplier')->defaultValue(2)->min(1)->info('If greater than 1, delay will grow exponentially for each retry: delay * (multiple ^ retries)')->end()
->integerNode('max_delay')->defaultValue(0)->min(0)->info('Max time in ms that a retry should ever be delayed (0 = infinite)')->end()
->floatNode('jitter')->defaultValue(0.1)->min(0)->max(1)->info('Randomness in percent (between 0 and 1) to apply to the delay')->end()
->end()
;
}
private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone)
{
$rootNode
->children()
->arrayNode('mailer')
->info('Mailer configuration')
->{$enableIfStandalone('symfony/mailer', Mailer::class)}()
->validate()
->ifTrue(function ($v) { return isset($v['dsn']) && \count($v['transports']); })
->thenInvalid('"dsn" and "transports" cannot be used together.')
->end()
->fixXmlConfig('transport')
->fixXmlConfig('header')
->children()
->scalarNode('message_bus')->defaultNull()->info('The message bus to use. Defaults to the default bus if the Messenger component is installed.')->end()
->scalarNode('dsn')->defaultNull()->end()
->arrayNode('transports')
->useAttributeAsKey('name')
->prototype('scalar')->end()
->end()
->arrayNode('envelope')
->info('Mailer Envelope configuration')
->children()
->scalarNode('sender')->end()
->arrayNode('recipients')
->performNoDeepMerging()
->beforeNormalization()
->ifArray()
->then(function ($v) {
return array_filter(array_values($v));
})
->end()
->prototype('scalar')->end()
->end()
->end()
->end()
->arrayNode('headers')
->normalizeKeys(false)
->useAttributeAsKey('name')
->prototype('array')
->normalizeKeys(false)
->beforeNormalization()
->ifTrue(function ($v) { return !\is_array($v) || array_keys($v) !== ['value']; })
->then(function ($v) { return ['value' => $v]; })
->end()
->children()
->variableNode('value')->end()
->end()
->end()
->end()
->end()
->end()
->end()
;
}
private function addNotifierSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone)
{
$rootNode
->children()
->arrayNode('notifier')
->info('Notifier configuration')
->{$enableIfStandalone('symfony/notifier', Notifier::class)}()
->fixXmlConfig('chatter_transport')
->children()
->arrayNode('chatter_transports')
->useAttributeAsKey('name')
->prototype('scalar')->end()
->end()
->end()
->fixXmlConfig('texter_transport')
->children()
->arrayNode('texter_transports')
->useAttributeAsKey('name')
->prototype('scalar')->end()
->end()
->end()
->children()
->booleanNode('notification_on_failed_messages')->defaultFalse()->end()
->end()
->children()
->arrayNode('channel_policy')
->useAttributeAsKey('name')
->prototype('array')
->beforeNormalization()->ifString()->then(function (string $v) { return [$v]; })->end()
->prototype('scalar')->end()
->end()
->end()
->end()
->fixXmlConfig('admin_recipient')
->children()
->arrayNode('admin_recipients')
->prototype('array')
->children()
->scalarNode('email')->cannotBeEmpty()->end()
->scalarNode('phone')->defaultValue('')->end()
->end()
->end()
->end()
->end()
->end()
->end()
;
}
private function addRateLimiterSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone)
{
$rootNode
->children()
->arrayNode('rate_limiter')
->info('Rate limiter configuration')
->{$enableIfStandalone('symfony/rate-limiter', TokenBucketLimiter::class)}()
->fixXmlConfig('limiter')
->beforeNormalization()
->ifTrue(function ($v) { return \is_array($v) && !isset($v['limiters']) && !isset($v['limiter']); })
->then(function (array $v) {
$newV = [
'enabled' => $v['enabled'] ?? true,
];
unset($v['enabled']);
$newV['limiters'] = $v;
return $newV;
})
->end()
->children()
->arrayNode('limiters')
->useAttributeAsKey('name')
->arrayPrototype()
->children()
->scalarNode('lock_factory')
->info('The service ID of the lock factory used by this limiter (or null to disable locking)')
->defaultValue('lock.factory')
->end()
->scalarNode('cache_pool')
->info('The cache pool to use for storing the current limiter state')
->defaultValue('cache.rate_limiter')
->end()
->scalarNode('storage_service')
->info('The service ID of a custom storage implementation, this precedes any configured "cache_pool"')
->defaultNull()
->end()
->enumNode('policy')
->info('The algorithm to be used by this limiter')
->isRequired()
->values(['fixed_window', 'token_bucket', 'sliding_window', 'no_limit'])
->end()
->integerNode('limit')
->info('The maximum allowed hits in a fixed interval or burst')
->isRequired()
->end()
->scalarNode('interval')
->info('Configures the fixed interval if "policy" is set to "fixed_window" or "sliding_window". The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent).')
->end()
->arrayNode('rate')
->info('Configures the fill rate if "policy" is set to "token_bucket"')
->children()
->scalarNode('interval')
->info('Configures the rate interval. The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent).')
->end()
->integerNode('amount')->info('Amount of tokens to add each interval')->defaultValue(1)->end()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
;
}
private function addUidSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone)
{
$rootNode
->children()
->arrayNode('uid')
->info('Uid configuration')
->{$enableIfStandalone('symfony/uid', UuidFactory::class)}()
->addDefaultsIfNotSet()
->children()
->enumNode('default_uuid_version')
->defaultValue(6)
->values([6, 4, 1])
->end()
->enumNode('name_based_uuid_version')
->defaultValue(5)
->values([5, 3])
->end()
->scalarNode('name_based_uuid_namespace')
->cannotBeEmpty()
->end()
->enumNode('time_based_uuid_version')
->defaultValue(6)
->values([6, 1])
->end()
->scalarNode('time_based_uuid_node')
->cannotBeEmpty()
->end()
->end()
->end()
->end()
;
}
}

View File

@ -0,0 +1,2702 @@
<?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;
use Composer\InstalledVersions;
use Doctrine\Common\Annotations\AnnotationRegistry;
use Doctrine\Common\Annotations\Reader;
use Http\Client\HttpClient;
use phpDocumentor\Reflection\DocBlockFactoryInterface;
use phpDocumentor\Reflection\Types\ContextFactory;
use PHPStan\PhpDocParser\Parser\PhpDocParser;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Container\ContainerInterface as PsrContainerInterface;
use Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcherInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Log\LoggerAwareInterface;
use Symfony\Bridge\Monolog\Processor\DebugProcessor;
use Symfony\Bridge\Twig\Extension\CsrfExtension;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\FrameworkBundle\Routing\AnnotatedRouteControllerLoader;
use Symfony\Bundle\FrameworkBundle\Routing\RouteLoaderInterface;
use Symfony\Bundle\FullStack;
use Symfony\Bundle\MercureBundle\MercureBundle;
use Symfony\Component\Asset\PackageInterface;
use Symfony\Component\BrowserKit\AbstractBrowser;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\ChainAdapter;
use Symfony\Component\Cache\Adapter\DoctrineAdapter;
use Symfony\Component\Cache\Adapter\DoctrineDbalAdapter;
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
use Symfony\Component\Cache\DependencyInjection\CachePoolPass;
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\Config\Resource\DirectoryResource;
use Symfony\Component\Config\ResourceCheckerInterface;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\EnvVarLoaderInterface;
use Symfony\Component\DependencyInjection\EnvVarProcessorInterface;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\Dotenv\Command\DebugCommand;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormTypeExtensionInterface;
use Symfony\Component\Form\FormTypeGuesserInterface;
use Symfony\Component\Form\FormTypeInterface;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Retry\GenericRetryStrategy;
use Symfony\Component\HttpClient\RetryableHttpClient;
use Symfony\Component\HttpClient\ScopingHttpClient;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\Lock\Lock;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\LockInterface;
use Symfony\Component\Lock\PersistingStoreInterface;
use Symfony\Component\Lock\Store\StoreFactory;
use Symfony\Component\Lock\StoreInterface;
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesTransportFactory;
use Symfony\Component\Mailer\Bridge\Google\Transport\GmailTransportFactory;
use Symfony\Component\Mailer\Bridge\Mailchimp\Transport\MandrillTransportFactory;
use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunTransportFactory;
use Symfony\Component\Mailer\Bridge\Mailjet\Transport\MailjetTransportFactory;
use Symfony\Component\Mailer\Bridge\OhMySmtp\Transport\OhMySmtpTransportFactory;
use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory;
use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridTransportFactory;
use Symfony\Component\Mailer\Bridge\Sendinblue\Transport\SendinblueTransportFactory;
use Symfony\Component\Mailer\Mailer;
use Symfony\Component\Mercure\HubRegistry;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Symfony\Component\Messenger\Bridge\AmazonSqs\Transport\AmazonSqsTransportFactory;
use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpTransportFactory;
use Symfony\Component\Messenger\Bridge\Beanstalkd\Transport\BeanstalkdTransportFactory;
use Symfony\Component\Messenger\Bridge\Redis\Transport\RedisTransportFactory;
use Symfony\Component\Messenger\Handler\BatchHandlerInterface;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
use Symfony\Component\Messenger\MessageBus;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Messenger\Middleware\RouterContextMiddleware;
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
use Symfony\Component\Messenger\Transport\TransportInterface;
use Symfony\Component\Mime\Header\Headers;
use Symfony\Component\Mime\MimeTypeGuesserInterface;
use Symfony\Component\Mime\MimeTypes;
use Symfony\Component\Notifier\Bridge\AllMySms\AllMySmsTransportFactory;
use Symfony\Component\Notifier\Bridge\AmazonSns\AmazonSnsTransportFactory;
use Symfony\Component\Notifier\Bridge\Clickatell\ClickatellTransportFactory;
use Symfony\Component\Notifier\Bridge\Discord\DiscordTransportFactory;
use Symfony\Component\Notifier\Bridge\Esendex\EsendexTransportFactory;
use Symfony\Component\Notifier\Bridge\Expo\ExpoTransportFactory;
use Symfony\Component\Notifier\Bridge\FakeChat\FakeChatTransportFactory;
use Symfony\Component\Notifier\Bridge\FakeSms\FakeSmsTransportFactory;
use Symfony\Component\Notifier\Bridge\Firebase\FirebaseTransportFactory;
use Symfony\Component\Notifier\Bridge\FreeMobile\FreeMobileTransportFactory;
use Symfony\Component\Notifier\Bridge\GatewayApi\GatewayApiTransportFactory;
use Symfony\Component\Notifier\Bridge\Gitter\GitterTransportFactory;
use Symfony\Component\Notifier\Bridge\GoogleChat\GoogleChatTransportFactory;
use Symfony\Component\Notifier\Bridge\Infobip\InfobipTransportFactory;
use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory;
use Symfony\Component\Notifier\Bridge\LightSms\LightSmsTransportFactory;
use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory;
use Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransportFactory as MailjetNotifierTransportFactory;
use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory;
use Symfony\Component\Notifier\Bridge\Mercure\MercureTransportFactory;
use Symfony\Component\Notifier\Bridge\MessageBird\MessageBirdTransport;
use Symfony\Component\Notifier\Bridge\MessageMedia\MessageMediaTransportFactory;
use Symfony\Component\Notifier\Bridge\MicrosoftTeams\MicrosoftTeamsTransportFactory;
use Symfony\Component\Notifier\Bridge\Mobyt\MobytTransportFactory;
use Symfony\Component\Notifier\Bridge\Nexmo\NexmoTransportFactory;
use Symfony\Component\Notifier\Bridge\Octopush\OctopushTransportFactory;
use Symfony\Component\Notifier\Bridge\OneSignal\OneSignalTransportFactory;
use Symfony\Component\Notifier\Bridge\OvhCloud\OvhCloudTransportFactory;
use Symfony\Component\Notifier\Bridge\RocketChat\RocketChatTransportFactory;
use Symfony\Component\Notifier\Bridge\Sendinblue\SendinblueTransportFactory as SendinblueNotifierTransportFactory;
use Symfony\Component\Notifier\Bridge\Sinch\SinchTransportFactory;
use Symfony\Component\Notifier\Bridge\Slack\SlackTransportFactory;
use Symfony\Component\Notifier\Bridge\Sms77\Sms77TransportFactory;
use Symfony\Component\Notifier\Bridge\Smsapi\SmsapiTransportFactory;
use Symfony\Component\Notifier\Bridge\SmsBiuras\SmsBiurasTransportFactory;
use Symfony\Component\Notifier\Bridge\Smsc\SmscTransportFactory;
use Symfony\Component\Notifier\Bridge\SpotHit\SpotHitTransportFactory;
use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory;
use Symfony\Component\Notifier\Bridge\Telnyx\TelnyxTransportFactory;
use Symfony\Component\Notifier\Bridge\TurboSms\TurboSmsTransport;
use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory;
use Symfony\Component\Notifier\Bridge\Vonage\VonageTransportFactory;
use Symfony\Component\Notifier\Bridge\Yunpian\YunpianTransportFactory;
use Symfony\Component\Notifier\Bridge\Zulip\ZulipTransportFactory;
use Symfony\Component\Notifier\Notifier;
use Symfony\Component\Notifier\Recipient\Recipient;
use Symfony\Component\Notifier\Transport\TransportFactoryInterface as NotifierTransportFactoryInterface;
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor;
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface;
use Symfony\Component\RateLimiter\LimiterInterface;
use Symfony\Component\RateLimiter\RateLimiterFactory;
use Symfony\Component\RateLimiter\Storage\CacheStorage;
use Symfony\Component\Routing\Loader\AnnotationDirectoryLoader;
use Symfony\Component\Routing\Loader\AnnotationFileLoader;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Serializer\Encoder\DecoderInterface;
use Symfony\Component\Serializer\Encoder\EncoderInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\String\LazyString;
use Symfony\Component\String\Slugger\SluggerInterface;
use Symfony\Component\Translation\Bridge\Crowdin\CrowdinProviderFactory;
use Symfony\Component\Translation\Bridge\Loco\LocoProviderFactory;
use Symfony\Component\Translation\Bridge\Lokalise\LokaliseProviderFactory;
use Symfony\Component\Translation\Command\XliffLintCommand as BaseXliffLintCommand;
use Symfony\Component\Translation\PseudoLocalizationTranslator;
use Symfony\Component\Translation\Translator;
use Symfony\Component\Uid\Factory\UuidFactory;
use Symfony\Component\Uid\UuidV4;
use Symfony\Component\Validator\ConstraintValidatorInterface;
use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader;
use Symfony\Component\Validator\ObjectInitializerInterface;
use Symfony\Component\Validator\Validation;
use Symfony\Component\WebLink\HttpHeaderSerializer;
use Symfony\Component\Workflow;
use Symfony\Component\Workflow\WorkflowInterface;
use Symfony\Component\Yaml\Command\LintCommand as BaseYamlLintCommand;
use Symfony\Component\Yaml\Yaml;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\CallbackInterface;
use Symfony\Contracts\Cache\TagAwareCacheInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\Service\ResetInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
use Symfony\Contracts\Translation\LocaleAwareInterface;
/**
* Process the configuration and prepare the dependency injection container with
* parameters and services.
*/
class FrameworkExtension extends Extension
{
private $formConfigEnabled = false;
private $translationConfigEnabled = false;
private $sessionConfigEnabled = false;
private $annotationsConfigEnabled = false;
private $validatorConfigEnabled = false;
private $messengerConfigEnabled = false;
private $mailerConfigEnabled = false;
private $httpClientConfigEnabled = false;
private $notifierConfigEnabled = false;
private $propertyAccessConfigEnabled = false;
private static $lockConfigEnabled = false;
/**
* Responds to the app.config configuration parameter.
*
* @throws LogicException
*/
public function load(array $configs, ContainerBuilder $container)
{
if (!class_exists(InstalledVersions::class)) {
trigger_deprecation('symfony/framework-bundle', '5.4', 'Configuring Symfony without the Composer Runtime API is deprecated. Consider upgrading to Composer 2.1 or later.');
}
$loader = new PhpFileLoader($container, new FileLocator(\dirname(__DIR__).'/Resources/config'));
$loader->load('web.php');
$loader->load('services.php');
$loader->load('fragment_renderer.php');
$loader->load('error_renderer.php');
if (ContainerBuilder::willBeAvailable('psr/event-dispatcher', PsrEventDispatcherInterface::class, ['symfony/framework-bundle'], true)) {
$container->setAlias(PsrEventDispatcherInterface::class, 'event_dispatcher');
}
$container->registerAliasForArgument('parameter_bag', PsrContainerInterface::class);
if ($this->hasConsole()) {
$loader->load('console.php');
if (!class_exists(BaseXliffLintCommand::class)) {
$container->removeDefinition('console.command.xliff_lint');
}
if (!class_exists(BaseYamlLintCommand::class)) {
$container->removeDefinition('console.command.yaml_lint');
}
if (!class_exists(DebugCommand::class)) {
$container->removeDefinition('console.command.dotenv_debug');
}
}
// Load Cache configuration first as it is used by other components
$loader->load('cache.php');
$configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs);
$this->annotationsConfigEnabled = $this->isConfigEnabled($container, $config['annotations']);
$this->translationConfigEnabled = $this->isConfigEnabled($container, $config['translator']);
// A translator must always be registered (as support is included by
// default in the Form and Validator component). If disabled, an identity
// translator will be used and everything will still work as expected.
if ($this->isConfigEnabled($container, $config['translator']) || $this->isConfigEnabled($container, $config['form']) || $this->isConfigEnabled($container, $config['validation'])) {
if (!class_exists(Translator::class) && $this->isConfigEnabled($container, $config['translator'])) {
throw new LogicException('Translation support cannot be enabled as the Translation component is not installed. Try running "composer require symfony/translation".');
}
if (class_exists(Translator::class)) {
$loader->load('identity_translator.php');
}
}
$container->getDefinition('locale_listener')->replaceArgument(3, $config['set_locale_from_accept_language']);
$container->getDefinition('response_listener')->replaceArgument(1, $config['set_content_language_from_locale']);
// If the slugger is used but the String component is not available, we should throw an error
if (!ContainerBuilder::willBeAvailable('symfony/string', SluggerInterface::class, ['symfony/framework-bundle'], true)) {
$container->register('slugger', 'stdClass')
->addError('You cannot use the "slugger" service since the String component is not installed. Try running "composer require symfony/string".');
} else {
if (!ContainerBuilder::willBeAvailable('symfony/translation', LocaleAwareInterface::class, ['symfony/framework-bundle'], true)) {
$container->register('slugger', 'stdClass')
->addError('You cannot use the "slugger" service since the Translation contracts are not installed. Try running "composer require symfony/translation".');
}
if (!\extension_loaded('intl') && !\defined('PHPUNIT_COMPOSER_INSTALL')) {
trigger_deprecation('', '', 'Please install the "intl" PHP extension for best performance.');
}
}
if (isset($config['secret'])) {
$container->setParameter('kernel.secret', $config['secret']);
}
$container->setParameter('kernel.http_method_override', $config['http_method_override']);
$container->setParameter('kernel.trusted_hosts', $config['trusted_hosts']);
$container->setParameter('kernel.default_locale', $config['default_locale']);
$container->setParameter('kernel.enabled_locales', $config['enabled_locales']);
$container->setParameter('kernel.error_controller', $config['error_controller']);
if (($config['trusted_proxies'] ?? false) && ($config['trusted_headers'] ?? false)) {
$container->setParameter('kernel.trusted_proxies', $config['trusted_proxies']);
$container->setParameter('kernel.trusted_headers', $this->resolveTrustedHeaders($config['trusted_headers']));
}
if (!$container->hasParameter('debug.file_link_format')) {
$container->setParameter('debug.file_link_format', $config['ide']);
}
if (!empty($config['test'])) {
$loader->load('test.php');
if (!class_exists(AbstractBrowser::class)) {
$container->removeDefinition('test.client');
}
}
if ($this->isConfigEnabled($container, $config['request'])) {
$this->registerRequestConfiguration($config['request'], $container, $loader);
}
if ($this->isConfigEnabled($container, $config['assets'])) {
if (!class_exists(\Symfony\Component\Asset\Package::class)) {
throw new LogicException('Asset support cannot be enabled as the Asset component is not installed. Try running "composer require symfony/asset".');
}
$this->registerAssetsConfiguration($config['assets'], $container, $loader);
}
if ($this->httpClientConfigEnabled = $this->isConfigEnabled($container, $config['http_client'])) {
$this->registerHttpClientConfiguration($config['http_client'], $container, $loader, $config['profiler']);
}
if ($this->mailerConfigEnabled = $this->isConfigEnabled($container, $config['mailer'])) {
$this->registerMailerConfiguration($config['mailer'], $container, $loader);
}
$propertyInfoEnabled = $this->isConfigEnabled($container, $config['property_info']);
$this->registerHttpCacheConfiguration($config['http_cache'], $container, $config['http_method_override']);
$this->registerEsiConfiguration($config['esi'], $container, $loader);
$this->registerSsiConfiguration($config['ssi'], $container, $loader);
$this->registerFragmentsConfiguration($config['fragments'], $container, $loader);
$this->registerTranslatorConfiguration($config['translator'], $container, $loader, $config['default_locale'], $config['enabled_locales']);
$this->registerWorkflowConfiguration($config['workflows'], $container, $loader);
$this->registerDebugConfiguration($config['php_errors'], $container, $loader);
// @deprecated since Symfony 5.4, in 6.0 change to:
// $this->registerRouterConfiguration($config['router'], $container, $loader, $config['enabled_locales']);
$this->registerRouterConfiguration($config['router'], $container, $loader, $config['translator']['enabled_locales'] ?: $config['enabled_locales']);
$this->registerAnnotationsConfiguration($config['annotations'], $container, $loader);
$this->registerPropertyAccessConfiguration($config['property_access'], $container, $loader);
$this->registerSecretsConfiguration($config['secrets'], $container, $loader);
$container->getDefinition('exception_listener')->replaceArgument(3, $config['exceptions']);
if ($this->isConfigEnabled($container, $config['serializer'])) {
if (!class_exists(\Symfony\Component\Serializer\Serializer::class)) {
throw new LogicException('Serializer support cannot be enabled as the Serializer component is not installed. Try running "composer require symfony/serializer-pack".');
}
$this->registerSerializerConfiguration($config['serializer'], $container, $loader);
}
if ($propertyInfoEnabled) {
$this->registerPropertyInfoConfiguration($container, $loader);
}
if (self::$lockConfigEnabled = $this->isConfigEnabled($container, $config['lock'])) {
$this->registerLockConfiguration($config['lock'], $container, $loader);
}
if ($this->isConfigEnabled($container, $config['rate_limiter'])) {
if (!interface_exists(LimiterInterface::class)) {
throw new LogicException('Rate limiter support cannot be enabled as the RateLimiter component is not installed. Try running "composer require symfony/rate-limiter".');
}
$this->registerRateLimiterConfiguration($config['rate_limiter'], $container, $loader);
}
if ($this->isConfigEnabled($container, $config['web_link'])) {
if (!class_exists(HttpHeaderSerializer::class)) {
throw new LogicException('WebLink support cannot be enabled as the WebLink component is not installed. Try running "composer require symfony/weblink".');
}
$loader->load('web_link.php');
}
if ($this->isConfigEnabled($container, $config['uid'])) {
if (!class_exists(UuidFactory::class)) {
throw new LogicException('Uid support cannot be enabled as the Uid component is not installed. Try running "composer require symfony/uid".');
}
$this->registerUidConfiguration($config['uid'], $container, $loader);
}
// register cache before session so both can share the connection services
$this->registerCacheConfiguration($config['cache'], $container);
if ($this->isConfigEnabled($container, $config['session'])) {
if (!\extension_loaded('session')) {
throw new LogicException('Session support cannot be enabled as the session extension is not installed. See https://php.net/session.installation for instructions.');
}
$this->sessionConfigEnabled = true;
$this->registerSessionConfiguration($config['session'], $container, $loader);
if (!empty($config['test'])) {
// test listener will replace the existing session listener
// as we are aliasing to avoid duplicated registered events
$container->setAlias('session_listener', 'test.session.listener');
}
} elseif (!empty($config['test'])) {
$container->removeDefinition('test.session.listener');
}
// csrf depends on session being registered
if (null === $config['csrf_protection']['enabled']) {
$config['csrf_protection']['enabled'] = $this->sessionConfigEnabled && !class_exists(FullStack::class) && ContainerBuilder::willBeAvailable('symfony/security-csrf', CsrfTokenManagerInterface::class, ['symfony/framework-bundle'], true);
}
$this->registerSecurityCsrfConfiguration($config['csrf_protection'], $container, $loader);
// form depends on csrf being registered
if ($this->isConfigEnabled($container, $config['form'])) {
if (!class_exists(Form::class)) {
throw new LogicException('Form support cannot be enabled as the Form component is not installed. Try running "composer require symfony/form".');
}
$this->formConfigEnabled = true;
$this->registerFormConfiguration($config, $container, $loader);
if (ContainerBuilder::willBeAvailable('symfony/validator', Validation::class, ['symfony/framework-bundle', 'symfony/form'], true)) {
$config['validation']['enabled'] = true;
} else {
$container->setParameter('validator.translation_domain', 'validators');
$container->removeDefinition('form.type_extension.form.validator');
$container->removeDefinition('form.type_guesser.validator');
}
} else {
$container->removeDefinition('console.command.form_debug');
}
// validation depends on form, annotations being registered
$this->registerValidationConfiguration($config['validation'], $container, $loader, $propertyInfoEnabled);
// messenger depends on validation being registered
if ($this->messengerConfigEnabled = $this->isConfigEnabled($container, $config['messenger'])) {
$this->registerMessengerConfiguration($config['messenger'], $container, $loader, $config['validation']);
} else {
$container->removeDefinition('console.command.messenger_consume_messages');
$container->removeDefinition('console.command.messenger_debug');
$container->removeDefinition('console.command.messenger_stop_workers');
$container->removeDefinition('console.command.messenger_setup_transports');
$container->removeDefinition('console.command.messenger_failed_messages_retry');
$container->removeDefinition('console.command.messenger_failed_messages_show');
$container->removeDefinition('console.command.messenger_failed_messages_remove');
$container->removeDefinition('cache.messenger.restart_workers_signal');
if ($container->hasDefinition('messenger.transport.amqp.factory') && !class_exists(AmqpTransportFactory::class)) {
if (class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory::class)) {
$container->getDefinition('messenger.transport.amqp.factory')
->setClass(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory::class)
->addTag('messenger.transport_factory');
} else {
$container->removeDefinition('messenger.transport.amqp.factory');
}
}
if ($container->hasDefinition('messenger.transport.redis.factory') && !class_exists(RedisTransportFactory::class)) {
if (class_exists(\Symfony\Component\Messenger\Transport\RedisExt\RedisTransportFactory::class)) {
$container->getDefinition('messenger.transport.redis.factory')
->setClass(\Symfony\Component\Messenger\Transport\RedisExt\RedisTransportFactory::class)
->addTag('messenger.transport_factory');
} else {
$container->removeDefinition('messenger.transport.redis.factory');
}
}
}
// notifier depends on messenger, mailer being registered
if ($this->notifierConfigEnabled = $this->isConfigEnabled($container, $config['notifier'])) {
$this->registerNotifierConfiguration($config['notifier'], $container, $loader);
}
// profiler depends on form, validation, translation, messenger, mailer, http-client, notifier being registered
$this->registerProfilerConfiguration($config['profiler'], $container, $loader);
$this->addAnnotatedClassesToCompile([
'**\\Controller\\',
'**\\Entity\\',
// Added explicitly so that we don't rely on the class map being dumped to make it work
'Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController',
]);
if (ContainerBuilder::willBeAvailable('symfony/mime', MimeTypes::class, ['symfony/framework-bundle'], true)) {
$loader->load('mime_type.php');
}
$container->registerForAutoconfiguration(PackageInterface::class)
->addTag('assets.package');
$container->registerForAutoconfiguration(Command::class)
->addTag('console.command');
$container->registerForAutoconfiguration(ResourceCheckerInterface::class)
->addTag('config_cache.resource_checker');
$container->registerForAutoconfiguration(EnvVarLoaderInterface::class)
->addTag('container.env_var_loader');
$container->registerForAutoconfiguration(EnvVarProcessorInterface::class)
->addTag('container.env_var_processor');
$container->registerForAutoconfiguration(CallbackInterface::class)
->addTag('container.reversible');
$container->registerForAutoconfiguration(ServiceLocator::class)
->addTag('container.service_locator');
$container->registerForAutoconfiguration(ServiceSubscriberInterface::class)
->addTag('container.service_subscriber');
$container->registerForAutoconfiguration(ArgumentValueResolverInterface::class)
->addTag('controller.argument_value_resolver');
$container->registerForAutoconfiguration(AbstractController::class)
->addTag('controller.service_arguments');
$container->registerForAutoconfiguration(DataCollectorInterface::class)
->addTag('data_collector');
$container->registerForAutoconfiguration(FormTypeInterface::class)
->addTag('form.type');
$container->registerForAutoconfiguration(FormTypeGuesserInterface::class)
->addTag('form.type_guesser');
$container->registerForAutoconfiguration(FormTypeExtensionInterface::class)
->addTag('form.type_extension');
$container->registerForAutoconfiguration(CacheClearerInterface::class)
->addTag('kernel.cache_clearer');
$container->registerForAutoconfiguration(CacheWarmerInterface::class)
->addTag('kernel.cache_warmer');
$container->registerForAutoconfiguration(EventDispatcherInterface::class)
->addTag('event_dispatcher.dispatcher');
$container->registerForAutoconfiguration(EventSubscriberInterface::class)
->addTag('kernel.event_subscriber');
$container->registerForAutoconfiguration(LocaleAwareInterface::class)
->addTag('kernel.locale_aware');
$container->registerForAutoconfiguration(ResetInterface::class)
->addTag('kernel.reset', ['method' => 'reset']);
if (!interface_exists(MarshallerInterface::class)) {
$container->registerForAutoconfiguration(ResettableInterface::class)
->addTag('kernel.reset', ['method' => 'reset']);
}
$container->registerForAutoconfiguration(PropertyListExtractorInterface::class)
->addTag('property_info.list_extractor');
$container->registerForAutoconfiguration(PropertyTypeExtractorInterface::class)
->addTag('property_info.type_extractor');
$container->registerForAutoconfiguration(PropertyDescriptionExtractorInterface::class)
->addTag('property_info.description_extractor');
$container->registerForAutoconfiguration(PropertyAccessExtractorInterface::class)
->addTag('property_info.access_extractor');
$container->registerForAutoconfiguration(PropertyInitializableExtractorInterface::class)
->addTag('property_info.initializable_extractor');
$container->registerForAutoconfiguration(EncoderInterface::class)
->addTag('serializer.encoder');
$container->registerForAutoconfiguration(DecoderInterface::class)
->addTag('serializer.encoder');
$container->registerForAutoconfiguration(NormalizerInterface::class)
->addTag('serializer.normalizer');
$container->registerForAutoconfiguration(DenormalizerInterface::class)
->addTag('serializer.normalizer');
$container->registerForAutoconfiguration(ConstraintValidatorInterface::class)
->addTag('validator.constraint_validator');
$container->registerForAutoconfiguration(ObjectInitializerInterface::class)
->addTag('validator.initializer');
$container->registerForAutoconfiguration(MessageHandlerInterface::class)
->addTag('messenger.message_handler');
$container->registerForAutoconfiguration(BatchHandlerInterface::class)
->addTag('messenger.message_handler');
$container->registerForAutoconfiguration(TransportFactoryInterface::class)
->addTag('messenger.transport_factory');
$container->registerForAutoconfiguration(MimeTypeGuesserInterface::class)
->addTag('mime.mime_type_guesser');
$container->registerForAutoconfiguration(LoggerAwareInterface::class)
->addMethodCall('setLogger', [new Reference('logger')]);
$container->registerAttributeForAutoconfiguration(AsEventListener::class, static function (ChildDefinition $definition, AsEventListener $attribute, \Reflector $reflector) {
$tagAttributes = get_object_vars($attribute);
if ($reflector instanceof \ReflectionMethod) {
if (isset($tagAttributes['method'])) {
throw new LogicException(sprintf('AsEventListener attribute cannot declare a method on "%s::%s()".', $reflector->class, $reflector->name));
}
$tagAttributes['method'] = $reflector->getName();
}
$definition->addTag('kernel.event_listener', $tagAttributes);
});
$container->registerAttributeForAutoconfiguration(AsController::class, static function (ChildDefinition $definition, AsController $attribute): void {
$definition->addTag('controller.service_arguments');
});
$container->registerAttributeForAutoconfiguration(AsMessageHandler::class, static function (ChildDefinition $definition, AsMessageHandler $attribute): void {
$tagAttributes = get_object_vars($attribute);
$tagAttributes['from_transport'] = $tagAttributes['fromTransport'];
unset($tagAttributes['fromTransport']);
$definition->addTag('messenger.message_handler', $tagAttributes);
});
if (!$container->getParameter('kernel.debug')) {
// remove tagged iterator argument for resource checkers
$container->getDefinition('config_cache_factory')->setArguments([]);
}
if (!$config['disallow_search_engine_index'] ?? false) {
$container->removeDefinition('disallow_search_engine_index_response_listener');
}
$container->registerForAutoconfiguration(RouteLoaderInterface::class)
->addTag('routing.route_loader');
$container->setParameter('container.behavior_describing_tags', [
'container.service_locator',
'container.service_subscriber',
'kernel.event_subscriber',
'kernel.event_listener',
'kernel.locale_aware',
'kernel.reset',
]);
}
/**
* {@inheritdoc}
*/
public function getConfiguration(array $config, ContainerBuilder $container)
{
return new Configuration($container->getParameter('kernel.debug'));
}
protected function hasConsole(): bool
{
return class_exists(Application::class);
}
private function registerFormConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader)
{
$loader->load('form.php');
$container->getDefinition('form.type_extension.form.validator')->setArgument(1, $config['form']['legacy_error_messages']);
if (null === $config['form']['csrf_protection']['enabled']) {
$config['form']['csrf_protection']['enabled'] = $config['csrf_protection']['enabled'];
}
if ($this->isConfigEnabled($container, $config['form']['csrf_protection'])) {
$loader->load('form_csrf.php');
$container->setParameter('form.type_extension.csrf.enabled', true);
$container->setParameter('form.type_extension.csrf.field_name', $config['form']['csrf_protection']['field_name']);
} else {
$container->setParameter('form.type_extension.csrf.enabled', false);
}
if (!ContainerBuilder::willBeAvailable('symfony/translation', Translator::class, ['symfony/framework-bundle', 'symfony/form'], true)) {
$container->removeDefinition('form.type_extension.upload.validator');
}
if (!method_exists(CachingFactoryDecorator::class, 'reset')) {
$container->getDefinition('form.choice_list_factory.cached')
->clearTag('kernel.reset')
;
}
}
private function registerHttpCacheConfiguration(array $config, ContainerBuilder $container, bool $httpMethodOverride)
{
$options = $config;
unset($options['enabled']);
if (!$options['private_headers']) {
unset($options['private_headers']);
}
$container->getDefinition('http_cache')
->setPublic($config['enabled'])
->replaceArgument(3, $options);
if ($httpMethodOverride) {
$container->getDefinition('http_cache')
->addArgument((new Definition('void'))
->setFactory([Request::class, 'enableHttpMethodParameterOverride'])
);
}
}
private function registerEsiConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader)
{
if (!$this->isConfigEnabled($container, $config)) {
$container->removeDefinition('fragment.renderer.esi');
return;
}
$loader->load('esi.php');
}
private function registerSsiConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader)
{
if (!$this->isConfigEnabled($container, $config)) {
$container->removeDefinition('fragment.renderer.ssi');
return;
}
$loader->load('ssi.php');
}
private function registerFragmentsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader)
{
if (!$this->isConfigEnabled($container, $config)) {
$container->removeDefinition('fragment.renderer.hinclude');
return;
}
$container->setParameter('fragment.renderer.hinclude.global_template', $config['hinclude_default_template']);
$loader->load('fragment_listener.php');
$container->setParameter('fragment.path', $config['path']);
}
private function registerProfilerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader)
{
if (!$this->isConfigEnabled($container, $config)) {
// this is needed for the WebProfiler to work even if the profiler is disabled
$container->setParameter('data_collector.templates', []);
return;
}
$loader->load('profiling.php');
$loader->load('collectors.php');
$loader->load('cache_debug.php');
if ($this->formConfigEnabled) {
$loader->load('form_debug.php');
}
if ($this->validatorConfigEnabled) {
$loader->load('validator_debug.php');
}
if ($this->translationConfigEnabled) {
$loader->load('translation_debug.php');
$container->getDefinition('translator.data_collector')->setDecoratedService('translator');
}
if ($this->messengerConfigEnabled) {
$loader->load('messenger_debug.php');
}
if ($this->mailerConfigEnabled) {
$loader->load('mailer_debug.php');
}
if ($this->httpClientConfigEnabled) {
$loader->load('http_client_debug.php');
}
if ($this->notifierConfigEnabled) {
$loader->load('notifier_debug.php');
}
$container->setParameter('profiler_listener.only_exceptions', $config['only_exceptions']);
$container->setParameter('profiler_listener.only_main_requests', $config['only_main_requests'] || $config['only_master_requests']);
// Choose storage class based on the DSN
[$class] = explode(':', $config['dsn'], 2);
if ('file' !== $class) {
throw new \LogicException(sprintf('Driver "%s" is not supported for the profiler.', $class));
}
$container->setParameter('profiler.storage.dsn', $config['dsn']);
$container->getDefinition('profiler')
->addArgument($config['collect'])
->addTag('kernel.reset', ['method' => 'reset']);
$container->getDefinition('profiler_listener')
->addArgument($config['collect_parameter']);
}
private function registerWorkflowConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader)
{
if (!$config['enabled']) {
$container->removeDefinition('console.command.workflow_dump');
return;
}
if (!class_exists(Workflow\Workflow::class)) {
throw new LogicException('Workflow support cannot be enabled as the Workflow component is not installed. Try running "composer require symfony/workflow".');
}
$loader->load('workflow.php');
$registryDefinition = $container->getDefinition('workflow.registry');
$workflows = [];
foreach ($config['workflows'] as $name => $workflow) {
$type = $workflow['type'];
$workflowId = sprintf('%s.%s', $type, $name);
// Process Metadata (workflow + places (transition is done in the "create transition" block))
$metadataStoreDefinition = new Definition(Workflow\Metadata\InMemoryMetadataStore::class, [[], [], null]);
if ($workflow['metadata']) {
$metadataStoreDefinition->replaceArgument(0, $workflow['metadata']);
}
$placesMetadata = [];
foreach ($workflow['places'] as $place) {
if ($place['metadata']) {
$placesMetadata[$place['name']] = $place['metadata'];
}
}
if ($placesMetadata) {
$metadataStoreDefinition->replaceArgument(1, $placesMetadata);
}
// Create transitions
$transitions = [];
$guardsConfiguration = [];
$transitionsMetadataDefinition = new Definition(\SplObjectStorage::class);
// Global transition counter per workflow
$transitionCounter = 0;
foreach ($workflow['transitions'] as $transition) {
if ('workflow' === $type) {
$transitionDefinition = new Definition(Workflow\Transition::class, [$transition['name'], $transition['from'], $transition['to']]);
$transitionDefinition->setPublic(false);
$transitionId = sprintf('.%s.transition.%s', $workflowId, $transitionCounter++);
$container->setDefinition($transitionId, $transitionDefinition);
$transitions[] = new Reference($transitionId);
if (isset($transition['guard'])) {
$configuration = new Definition(Workflow\EventListener\GuardExpression::class);
$configuration->addArgument(new Reference($transitionId));
$configuration->addArgument($transition['guard']);
$configuration->setPublic(false);
$eventName = sprintf('workflow.%s.guard.%s', $name, $transition['name']);
$guardsConfiguration[$eventName][] = $configuration;
}
if ($transition['metadata']) {
$transitionsMetadataDefinition->addMethodCall('attach', [
new Reference($transitionId),
$transition['metadata'],
]);
}
} elseif ('state_machine' === $type) {
foreach ($transition['from'] as $from) {
foreach ($transition['to'] as $to) {
$transitionDefinition = new Definition(Workflow\Transition::class, [$transition['name'], $from, $to]);
$transitionDefinition->setPublic(false);
$transitionId = sprintf('.%s.transition.%s', $workflowId, $transitionCounter++);
$container->setDefinition($transitionId, $transitionDefinition);
$transitions[] = new Reference($transitionId);
if (isset($transition['guard'])) {
$configuration = new Definition(Workflow\EventListener\GuardExpression::class);
$configuration->addArgument(new Reference($transitionId));
$configuration->addArgument($transition['guard']);
$configuration->setPublic(false);
$eventName = sprintf('workflow.%s.guard.%s', $name, $transition['name']);
$guardsConfiguration[$eventName][] = $configuration;
}
if ($transition['metadata']) {
$transitionsMetadataDefinition->addMethodCall('attach', [
new Reference($transitionId),
$transition['metadata'],
]);
}
}
}
}
}
$metadataStoreDefinition->replaceArgument(2, $transitionsMetadataDefinition);
$container->setDefinition(sprintf('%s.metadata_store', $workflowId), $metadataStoreDefinition);
// Create places
$places = array_column($workflow['places'], 'name');
$initialMarking = $workflow['initial_marking'] ?? [];
// Create a Definition
$definitionDefinition = new Definition(Workflow\Definition::class);
$definitionDefinition->setPublic(false);
$definitionDefinition->addArgument($places);
$definitionDefinition->addArgument($transitions);
$definitionDefinition->addArgument($initialMarking);
$definitionDefinition->addArgument(new Reference(sprintf('%s.metadata_store', $workflowId)));
$workflows[$workflowId] = $definitionDefinition;
// Create MarkingStore
if (isset($workflow['marking_store']['type'])) {
$markingStoreDefinition = new ChildDefinition('workflow.marking_store.method');
$markingStoreDefinition->setArguments([
'state_machine' === $type, //single state
$workflow['marking_store']['property'],
]);
} elseif (isset($workflow['marking_store']['service'])) {
$markingStoreDefinition = new Reference($workflow['marking_store']['service']);
}
// Create Workflow
$workflowDefinition = new ChildDefinition(sprintf('%s.abstract', $type));
$workflowDefinition->replaceArgument(0, new Reference(sprintf('%s.definition', $workflowId)));
$workflowDefinition->replaceArgument(1, $markingStoreDefinition ?? null);
$workflowDefinition->replaceArgument(3, $name);
$workflowDefinition->replaceArgument(4, $workflow['events_to_dispatch']);
$workflowDefinition->addTag('container.private', [
'package' => 'symfony/framework-bundle',
'version' => '5.3',
]);
// Store to container
$container->setDefinition($workflowId, $workflowDefinition);
$container->setDefinition(sprintf('%s.definition', $workflowId), $definitionDefinition);
$container->registerAliasForArgument($workflowId, WorkflowInterface::class, $name.'.'.$type);
// Validate Workflow
if ('state_machine' === $workflow['type']) {
$validator = new Workflow\Validator\StateMachineValidator();
} else {
$validator = new Workflow\Validator\WorkflowValidator();
}
$trs = array_map(function (Reference $ref) use ($container): Workflow\Transition {
return $container->get((string) $ref);
}, $transitions);
$realDefinition = new Workflow\Definition($places, $trs, $initialMarking);
$validator->validate($realDefinition, $name);
// Add workflow to Registry
if ($workflow['supports']) {
foreach ($workflow['supports'] as $supportedClassName) {
$strategyDefinition = new Definition(Workflow\SupportStrategy\InstanceOfSupportStrategy::class, [$supportedClassName]);
$strategyDefinition->setPublic(false);
$registryDefinition->addMethodCall('addWorkflow', [new Reference($workflowId), $strategyDefinition]);
}
} elseif (isset($workflow['support_strategy'])) {
$registryDefinition->addMethodCall('addWorkflow', [new Reference($workflowId), new Reference($workflow['support_strategy'])]);
}
// Enable the AuditTrail
if ($workflow['audit_trail']['enabled']) {
$listener = new Definition(Workflow\EventListener\AuditTrailListener::class);
$listener->addTag('monolog.logger', ['channel' => 'workflow']);
$listener->addTag('kernel.event_listener', ['event' => sprintf('workflow.%s.leave', $name), 'method' => 'onLeave']);
$listener->addTag('kernel.event_listener', ['event' => sprintf('workflow.%s.transition', $name), 'method' => 'onTransition']);
$listener->addTag('kernel.event_listener', ['event' => sprintf('workflow.%s.enter', $name), 'method' => 'onEnter']);
$listener->addArgument(new Reference('logger'));
$container->setDefinition(sprintf('.%s.listener.audit_trail', $workflowId), $listener);
}
// Add Guard Listener
if ($guardsConfiguration) {
if (!class_exists(ExpressionLanguage::class)) {
throw new LogicException('Cannot guard workflows as the ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".');
}
if (!class_exists(Security::class)) {
throw new LogicException('Cannot guard workflows as the Security component is not installed. Try running "composer require symfony/security-core".');
}
$guard = new Definition(Workflow\EventListener\GuardListener::class);
$guard->setArguments([
$guardsConfiguration,
new Reference('workflow.security.expression_language'),
new Reference('security.token_storage'),
new Reference('security.authorization_checker'),
new Reference('security.authentication.trust_resolver'),
new Reference('security.role_hierarchy'),
new Reference('validator', ContainerInterface::NULL_ON_INVALID_REFERENCE),
]);
foreach ($guardsConfiguration as $eventName => $config) {
$guard->addTag('kernel.event_listener', ['event' => $eventName, 'method' => 'onTransition']);
}
$container->setDefinition(sprintf('.%s.listener.guard', $workflowId), $guard);
$container->setParameter('workflow.has_guard_listeners', true);
}
}
$commandDumpDefinition = $container->getDefinition('console.command.workflow_dump');
$commandDumpDefinition->setArgument(0, $workflows);
}
private function registerDebugConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader)
{
$loader->load('debug_prod.php');
if (class_exists(Stopwatch::class)) {
$container->register('debug.stopwatch', Stopwatch::class)
->addArgument(true)
->addTag('kernel.reset', ['method' => 'reset']);
$container->setAlias(Stopwatch::class, new Alias('debug.stopwatch', false));
}
$debug = $container->getParameter('kernel.debug');
if ($debug) {
$container->setParameter('debug.container.dump', '%kernel.build_dir%/%kernel.container_class%.xml');
}
if ($debug && class_exists(Stopwatch::class)) {
$loader->load('debug.php');
}
$definition = $container->findDefinition('debug.debug_handlers_listener');
if (false === $config['log']) {
$definition->replaceArgument(1, null);
} elseif (true !== $config['log']) {
$definition->replaceArgument(2, $config['log']);
}
if (!$config['throw']) {
$container->setParameter('debug.error_handler.throw_at', 0);
}
if ($debug && class_exists(DebugProcessor::class)) {
$definition = new Definition(DebugProcessor::class);
$definition->setPublic(false);
$definition->addArgument(new Reference('request_stack'));
$container->setDefinition('debug.log_processor', $definition);
}
}
private function registerRouterConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, array $enabledLocales = [])
{
if (!$this->isConfigEnabled($container, $config)) {
$container->removeDefinition('console.command.router_debug');
$container->removeDefinition('console.command.router_match');
$container->removeDefinition('messenger.middleware.router_context');
return;
}
if (!class_exists(RouterContextMiddleware::class)) {
$container->removeDefinition('messenger.middleware.router_context');
}
$loader->load('routing.php');
if (null === $config['utf8']) {
trigger_deprecation('symfony/framework-bundle', '5.1', 'Not setting the "framework.router.utf8" configuration option is deprecated, it will default to "true" in version 6.0.');
}
if ($config['utf8']) {
$container->getDefinition('routing.loader')->replaceArgument(1, ['utf8' => true]);
}
if ($enabledLocales) {
$enabledLocales = implode('|', array_map('preg_quote', $enabledLocales));
$container->getDefinition('routing.loader')->replaceArgument(2, ['_locale' => $enabledLocales]);
}
if (!ContainerBuilder::willBeAvailable('symfony/expression-language', ExpressionLanguage::class, ['symfony/framework-bundle', 'symfony/routing'], true)) {
$container->removeDefinition('router.expression_language_provider');
}
$container->setParameter('router.resource', $config['resource']);
$router = $container->findDefinition('router.default');
$argument = $router->getArgument(2);
$argument['strict_requirements'] = $config['strict_requirements'];
if (isset($config['type'])) {
$argument['resource_type'] = $config['type'];
}
$router->replaceArgument(2, $argument);
$container->setParameter('request_listener.http_port', $config['http_port']);
$container->setParameter('request_listener.https_port', $config['https_port']);
if (null !== $config['default_uri']) {
$container->getDefinition('router.request_context')
->replaceArgument(0, $config['default_uri']);
}
if (\PHP_VERSION_ID < 80000 && !$this->annotationsConfigEnabled) {
return;
}
$container->register('routing.loader.annotation', AnnotatedRouteControllerLoader::class)
->setPublic(false)
->addTag('routing.loader', ['priority' => -10])
->setArguments([
new Reference('annotation_reader', ContainerInterface::NULL_ON_INVALID_REFERENCE),
'%kernel.environment%',
]);
$container->register('routing.loader.annotation.directory', AnnotationDirectoryLoader::class)
->setPublic(false)
->addTag('routing.loader', ['priority' => -10])
->setArguments([
new Reference('file_locator'),
new Reference('routing.loader.annotation'),
]);
$container->register('routing.loader.annotation.file', AnnotationFileLoader::class)
->setPublic(false)
->addTag('routing.loader', ['priority' => -10])
->setArguments([
new Reference('file_locator'),
new Reference('routing.loader.annotation'),
]);
}
private function registerSessionConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader)
{
$loader->load('session.php');
// session storage
if (null === $config['storage_factory_id']) {
trigger_deprecation('symfony/framework-bundle', '5.3', 'Not setting the "framework.session.storage_factory_id" configuration option is deprecated, it will default to "session.storage.factory.native" and will replace the "framework.session.storage_id" configuration option in version 6.0.');
$container->setAlias('session.storage', $config['storage_id']);
$container->setAlias('session.storage.factory', 'session.storage.factory.service');
} else {
$container->setAlias('session.storage.factory', $config['storage_factory_id']);
$container->removeAlias(SessionStorageInterface::class);
$container->removeDefinition('session.storage.metadata_bag');
$container->removeDefinition('session.storage.native');
$container->removeDefinition('session.storage.php_bridge');
$container->removeDefinition('session.storage.mock_file');
$container->removeAlias('session.storage.filesystem');
}
$options = ['cache_limiter' => '0'];
foreach (['name', 'cookie_lifetime', 'cookie_path', 'cookie_domain', 'cookie_secure', 'cookie_httponly', 'cookie_samesite', 'use_cookies', 'gc_maxlifetime', 'gc_probability', 'gc_divisor', 'sid_length', 'sid_bits_per_character'] as $key) {
if (isset($config[$key])) {
$options[$key] = $config[$key];
}
}
if ('auto' === ($options['cookie_secure'] ?? null)) {
if (null === $config['storage_factory_id']) {
$locator = $container->getDefinition('session_listener')->getArgument(0);
$locator->setValues($locator->getValues() + [
'session_storage' => new Reference('session.storage', ContainerInterface::IGNORE_ON_INVALID_REFERENCE),
'request_stack' => new Reference('request_stack'),
]);
} else {
$container->getDefinition('session.storage.factory.native')->replaceArgument(3, true);
$container->getDefinition('session.storage.factory.php_bridge')->replaceArgument(2, true);
}
}
$container->setParameter('session.storage.options', $options);
// session handler (the internal callback registered with PHP session management)
if (null === $config['handler_id']) {
// Set the handler class to be null
if ($container->hasDefinition('session.storage.native')) {
$container->getDefinition('session.storage.native')->replaceArgument(1, null);
$container->getDefinition('session.storage.php_bridge')->replaceArgument(0, null);
} else {
$container->getDefinition('session.storage.factory.native')->replaceArgument(1, null);
$container->getDefinition('session.storage.factory.php_bridge')->replaceArgument(0, null);
}
$container->setAlias('session.handler', 'session.handler.native_file');
} else {
$container->resolveEnvPlaceholders($config['handler_id'], null, $usedEnvs);
if ($usedEnvs || preg_match('#^[a-z]++://#', $config['handler_id'])) {
$id = '.cache_connection.'.ContainerBuilder::hash($config['handler_id']);
$container->getDefinition('session.abstract_handler')
->replaceArgument(0, $container->hasDefinition($id) ? new Reference($id) : $config['handler_id']);
$container->setAlias('session.handler', 'session.abstract_handler');
} else {
$container->setAlias('session.handler', $config['handler_id']);
}
}
$container->setParameter('session.save_path', $config['save_path']);
$container->setParameter('session.metadata.update_threshold', $config['metadata_update_threshold']);
}
private function registerRequestConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader)
{
if ($config['formats']) {
$loader->load('request.php');
$listener = $container->getDefinition('request.add_request_formats_listener');
$listener->replaceArgument(0, $config['formats']);
}
}
private function registerAssetsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader)
{
$loader->load('assets.php');
if ($config['version_strategy']) {
$defaultVersion = new Reference($config['version_strategy']);
} else {
$defaultVersion = $this->createVersion($container, $config['version'], $config['version_format'], $config['json_manifest_path'], '_default', $config['strict_mode']);
}
$defaultPackage = $this->createPackageDefinition($config['base_path'], $config['base_urls'], $defaultVersion);
$container->setDefinition('assets._default_package', $defaultPackage);
foreach ($config['packages'] as $name => $package) {
if (null !== $package['version_strategy']) {
$version = new Reference($package['version_strategy']);
} elseif (!\array_key_exists('version', $package) && null === $package['json_manifest_path']) {
// if neither version nor json_manifest_path are specified, use the default
$version = $defaultVersion;
} else {
// let format fallback to main version_format
$format = $package['version_format'] ?: $config['version_format'];
$version = $package['version'] ?? null;
$version = $this->createVersion($container, $version, $format, $package['json_manifest_path'], $name, $package['strict_mode']);
}
$packageDefinition = $this->createPackageDefinition($package['base_path'], $package['base_urls'], $version)
->addTag('assets.package', ['package' => $name]);
$container->setDefinition('assets._package_'.$name, $packageDefinition);
$container->registerAliasForArgument('assets._package_'.$name, PackageInterface::class, $name.'.package');
}
}
/**
* Returns a definition for an asset package.
*/
private function createPackageDefinition(?string $basePath, array $baseUrls, Reference $version): Definition
{
if ($basePath && $baseUrls) {
throw new \LogicException('An asset package cannot have base URLs and base paths.');
}
$package = new ChildDefinition($baseUrls ? 'assets.url_package' : 'assets.path_package');
$package
->setPublic(false)
->replaceArgument(0, $baseUrls ?: $basePath)
->replaceArgument(1, $version)
;
return $package;
}
private function createVersion(ContainerBuilder $container, ?string $version, ?string $format, ?string $jsonManifestPath, string $name, bool $strictMode): Reference
{
// Configuration prevents $version and $jsonManifestPath from being set
if (null !== $version) {
$def = new ChildDefinition('assets.static_version_strategy');
$def
->replaceArgument(0, $version)
->replaceArgument(1, $format)
;
$container->setDefinition('assets._version_'.$name, $def);
return new Reference('assets._version_'.$name);
}
if (null !== $jsonManifestPath) {
$def = new ChildDefinition('assets.json_manifest_version_strategy');
$def->replaceArgument(0, $jsonManifestPath);
$def->replaceArgument(2, $strictMode);
$container->setDefinition('assets._version_'.$name, $def);
return new Reference('assets._version_'.$name);
}
return new Reference('assets.empty_version_strategy');
}
private function registerTranslatorConfiguration(array $config, ContainerBuilder $container, LoaderInterface $loader, string $defaultLocale, array $enabledLocales)
{
if (!$this->isConfigEnabled($container, $config)) {
$container->removeDefinition('console.command.translation_debug');
$container->removeDefinition('console.command.translation_extract');
$container->removeDefinition('console.command.translation_pull');
$container->removeDefinition('console.command.translation_push');
return;
}
$loader->load('translation.php');
$loader->load('translation_providers.php');
// Use the "real" translator instead of the identity default
$container->setAlias('translator', 'translator.default')->setPublic(true);
$container->setAlias('translator.formatter', new Alias($config['formatter'], false));
$translator = $container->findDefinition('translator.default');
$translator->addMethodCall('setFallbackLocales', [$config['fallbacks'] ?: [$defaultLocale]]);
$defaultOptions = $translator->getArgument(4);
$defaultOptions['cache_dir'] = $config['cache_dir'];
$translator->setArgument(4, $defaultOptions);
// @deprecated since Symfony 5.4, in 6.0 change to:
// $translator->setArgument(5, $enabledLocales);
$translator->setArgument(5, $config['enabled_locales'] ?: $enabledLocales);
$container->setParameter('translator.logging', $config['logging']);
$container->setParameter('translator.default_path', $config['default_path']);
// Discover translation directories
$dirs = [];
$transPaths = [];
$nonExistingDirs = [];
if (ContainerBuilder::willBeAvailable('symfony/validator', Validation::class, ['symfony/framework-bundle', 'symfony/translation'], true)) {
$r = new \ReflectionClass(Validation::class);
$dirs[] = $transPaths[] = \dirname($r->getFileName()).'/Resources/translations';
}
if (ContainerBuilder::willBeAvailable('symfony/form', Form::class, ['symfony/framework-bundle', 'symfony/translation'], true)) {
$r = new \ReflectionClass(Form::class);
$dirs[] = $transPaths[] = \dirname($r->getFileName()).'/Resources/translations';
}
if (ContainerBuilder::willBeAvailable('symfony/security-core', AuthenticationException::class, ['symfony/framework-bundle', 'symfony/translation'], true)) {
$r = new \ReflectionClass(AuthenticationException::class);
$dirs[] = $transPaths[] = \dirname($r->getFileName(), 2).'/Resources/translations';
}
$defaultDir = $container->getParameterBag()->resolveValue($config['default_path']);
foreach ($container->getParameter('kernel.bundles_metadata') as $name => $bundle) {
if ($container->fileExists($dir = $bundle['path'].'/Resources/translations') || $container->fileExists($dir = $bundle['path'].'/translations')) {
$dirs[] = $dir;
} else {
$nonExistingDirs[] = $dir;
}
}
foreach ($config['paths'] as $dir) {
if ($container->fileExists($dir)) {
$dirs[] = $transPaths[] = $dir;
} else {
throw new \UnexpectedValueException(sprintf('"%s" defined in translator.paths does not exist or is not a directory.', $dir));
}
}
if ($container->hasDefinition('console.command.translation_debug')) {
$container->getDefinition('console.command.translation_debug')->replaceArgument(5, $transPaths);
}
if ($container->hasDefinition('console.command.translation_extract')) {
$container->getDefinition('console.command.translation_extract')->replaceArgument(6, $transPaths);
}
if (null === $defaultDir) {
// allow null
} elseif ($container->fileExists($defaultDir)) {
$dirs[] = $defaultDir;
} else {
$nonExistingDirs[] = $defaultDir;
}
// Register translation resources
if ($dirs) {
$files = [];
foreach ($dirs as $dir) {
$finder = Finder::create()
->followLinks()
->files()
->filter(function (\SplFileInfo $file) {
return 2 <= substr_count($file->getBasename(), '.') && preg_match('/\.\w+$/', $file->getBasename());
})
->in($dir)
->sortByName()
;
foreach ($finder as $file) {
$fileNameParts = explode('.', basename($file));
$locale = $fileNameParts[\count($fileNameParts) - 2];
if (!isset($files[$locale])) {
$files[$locale] = [];
}
$files[$locale][] = (string) $file;
}
}
$projectDir = $container->getParameter('kernel.project_dir');
$options = array_merge(
$translator->getArgument(4),
[
'resource_files' => $files,
'scanned_directories' => $scannedDirectories = array_merge($dirs, $nonExistingDirs),
'cache_vary' => [
'scanned_directories' => array_map(static function (string $dir) use ($projectDir): string {
return str_starts_with($dir, $projectDir.'/') ? substr($dir, 1 + \strlen($projectDir)) : $dir;
}, $scannedDirectories),
],
]
);
$translator->replaceArgument(4, $options);
}
if ($config['pseudo_localization']['enabled']) {
$options = $config['pseudo_localization'];
unset($options['enabled']);
$container
->register('translator.pseudo', PseudoLocalizationTranslator::class)
->setDecoratedService('translator', null, -1) // Lower priority than "translator.data_collector"
->setArguments([
new Reference('translator.pseudo.inner'),
$options,
]);
}
$classToServices = [
CrowdinProviderFactory::class => 'translation.provider_factory.crowdin',
LocoProviderFactory::class => 'translation.provider_factory.loco',
LokaliseProviderFactory::class => 'translation.provider_factory.lokalise',
];
$parentPackages = ['symfony/framework-bundle', 'symfony/translation', 'symfony/http-client'];
foreach ($classToServices as $class => $service) {
$package = substr($service, \strlen('translation.provider_factory.'));
if (!$container->hasDefinition('http_client') || !ContainerBuilder::willBeAvailable(sprintf('symfony/%s-translation-provider', $package), $class, $parentPackages, true)) {
$container->removeDefinition($service);
}
}
if (!$config['providers']) {
return;
}
// @deprecated since Symfony 5.4, in 6.0 change to:
// $locales = $enabledLocales;
$locales = $config['enabled_locales'] ?: $enabledLocales;
foreach ($config['providers'] as $provider) {
if ($provider['locales']) {
$locales += $provider['locales'];
}
}
$locales = array_unique($locales);
$container->getDefinition('console.command.translation_pull')
->replaceArgument(4, array_merge($transPaths, [$config['default_path']]))
->replaceArgument(5, $locales)
;
$container->getDefinition('console.command.translation_push')
->replaceArgument(2, array_merge($transPaths, [$config['default_path']]))
->replaceArgument(3, $locales)
;
$container->getDefinition('translation.provider_collection_factory')
->replaceArgument(1, $locales)
;
$container->getDefinition('translation.provider_collection')->setArgument(0, $config['providers']);
}
private function registerValidationConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $propertyInfoEnabled)
{
if (!$this->validatorConfigEnabled = $this->isConfigEnabled($container, $config)) {
$container->removeDefinition('console.command.validator_debug');
return;
}
if (!class_exists(Validation::class)) {
throw new LogicException('Validation support cannot be enabled as the Validator component is not installed. Try running "composer require symfony/validator".');
}
if (!isset($config['email_validation_mode'])) {
$config['email_validation_mode'] = 'loose';
}
$loader->load('validator.php');
$validatorBuilder = $container->getDefinition('validator.builder');
$container->setParameter('validator.translation_domain', $config['translation_domain']);
$files = ['xml' => [], 'yml' => []];
$this->registerValidatorMapping($container, $config, $files);
if (!empty($files['xml'])) {
$validatorBuilder->addMethodCall('addXmlMappings', [$files['xml']]);
}
if (!empty($files['yml'])) {
$validatorBuilder->addMethodCall('addYamlMappings', [$files['yml']]);
}
$definition = $container->findDefinition('validator.email');
$definition->replaceArgument(0, $config['email_validation_mode']);
if (\array_key_exists('enable_annotations', $config) && $config['enable_annotations']) {
if (!$this->annotationsConfigEnabled && \PHP_VERSION_ID < 80000) {
throw new \LogicException('"enable_annotations" on the validator cannot be set as the PHP version is lower than 8 and Doctrine Annotations support is disabled. Consider upgrading PHP.');
}
$validatorBuilder->addMethodCall('enableAnnotationMapping', [true]);
if ($this->annotationsConfigEnabled) {
$validatorBuilder->addMethodCall('setDoctrineAnnotationReader', [new Reference('annotation_reader')]);
}
}
if (\array_key_exists('static_method', $config) && $config['static_method']) {
foreach ($config['static_method'] as $methodName) {
$validatorBuilder->addMethodCall('addMethodMapping', [$methodName]);
}
}
if (!$container->getParameter('kernel.debug')) {
$validatorBuilder->addMethodCall('setMappingCache', [new Reference('validator.mapping.cache.adapter')]);
}
$container->setParameter('validator.auto_mapping', $config['auto_mapping']);
if (!$propertyInfoEnabled || !class_exists(PropertyInfoLoader::class)) {
$container->removeDefinition('validator.property_info_loader');
}
$container
->getDefinition('validator.not_compromised_password')
->setArgument(2, $config['not_compromised_password']['enabled'])
->setArgument(3, $config['not_compromised_password']['endpoint'])
;
if (!class_exists(ExpressionLanguage::class)) {
$container->removeDefinition('validator.expression_language');
}
}
private function registerValidatorMapping(ContainerBuilder $container, array $config, array &$files)
{
$fileRecorder = function ($extension, $path) use (&$files) {
$files['yaml' === $extension ? 'yml' : $extension][] = $path;
};
if (ContainerBuilder::willBeAvailable('symfony/form', Form::class, ['symfony/framework-bundle', 'symfony/validator'], true)) {
$reflClass = new \ReflectionClass(Form::class);
$fileRecorder('xml', \dirname($reflClass->getFileName()).'/Resources/config/validation.xml');
}
foreach ($container->getParameter('kernel.bundles_metadata') as $bundle) {
$configDir = is_dir($bundle['path'].'/Resources/config') ? $bundle['path'].'/Resources/config' : $bundle['path'].'/config';
if (
$container->fileExists($file = $configDir.'/validation.yaml', false) ||
$container->fileExists($file = $configDir.'/validation.yml', false)
) {
$fileRecorder('yml', $file);
}
if ($container->fileExists($file = $configDir.'/validation.xml', false)) {
$fileRecorder('xml', $file);
}
if ($container->fileExists($dir = $configDir.'/validation', '/^$/')) {
$this->registerMappingFilesFromDir($dir, $fileRecorder);
}
}
$projectDir = $container->getParameter('kernel.project_dir');
if ($container->fileExists($dir = $projectDir.'/config/validator', '/^$/')) {
$this->registerMappingFilesFromDir($dir, $fileRecorder);
}
$this->registerMappingFilesFromConfig($container, $config, $fileRecorder);
}
private function registerMappingFilesFromDir(string $dir, callable $fileRecorder)
{
foreach (Finder::create()->followLinks()->files()->in($dir)->name('/\.(xml|ya?ml)$/')->sortByName() as $file) {
$fileRecorder($file->getExtension(), $file->getRealPath());
}
}
private function registerMappingFilesFromConfig(ContainerBuilder $container, array $config, callable $fileRecorder)
{
foreach ($config['mapping']['paths'] as $path) {
if (is_dir($path)) {
$this->registerMappingFilesFromDir($path, $fileRecorder);
$container->addResource(new DirectoryResource($path, '/^$/'));
} elseif ($container->fileExists($path, false)) {
if (!preg_match('/\.(xml|ya?ml)$/', $path, $matches)) {
throw new \RuntimeException(sprintf('Unsupported mapping type in "%s", supported types are XML & Yaml.', $path));
}
$fileRecorder($matches[1], $path);
} else {
throw new \RuntimeException(sprintf('Could not open file or directory "%s".', $path));
}
}
}
private function registerAnnotationsConfiguration(array $config, ContainerBuilder $container, LoaderInterface $loader)
{
if (!$this->annotationsConfigEnabled) {
return;
}
if (!class_exists(\Doctrine\Common\Annotations\Annotation::class)) {
throw new LogicException('Annotations cannot be enabled as the Doctrine Annotation library is not installed. Try running "composer require doctrine/annotations".');
}
$loader->load('annotations.php');
if (!method_exists(AnnotationRegistry::class, 'registerUniqueLoader')) {
$container->getDefinition('annotations.dummy_registry')
->setMethodCalls([['registerLoader', ['class_exists']]]);
}
if ('none' === $config['cache']) {
$container->removeDefinition('annotations.cached_reader');
return;
}
$cacheService = $config['cache'];
if (\in_array($config['cache'], ['php_array', 'file'])) {
if ('php_array' === $config['cache']) {
$cacheService = 'annotations.cache_adapter';
// Enable warmer only if PHP array is used for cache
$definition = $container->findDefinition('annotations.cache_warmer');
$definition->addTag('kernel.cache_warmer');
} elseif ('file' === $config['cache']) {
$cacheService = 'annotations.filesystem_cache_adapter';
$cacheDir = $container->getParameterBag()->resolveValue($config['file_cache_dir']);
if (!is_dir($cacheDir) && false === @mkdir($cacheDir, 0777, true) && !is_dir($cacheDir)) {
throw new \RuntimeException(sprintf('Could not create cache directory "%s".', $cacheDir));
}
$container
->getDefinition('annotations.filesystem_cache_adapter')
->replaceArgument(2, $cacheDir)
;
}
} else {
trigger_deprecation('symfony/framework-bundle', '5.3', 'Using a custom service for "framework.annotation.cache" is deprecated, only values "none", "php_array" and "file" are valid in version 6.0.');
}
$container
->getDefinition('annotations.cached_reader')
->setPublic(true) // set to false in AddAnnotationsCachedReaderPass
->replaceArgument(2, $config['debug'])
// reference the cache provider without using it until AddAnnotationsCachedReaderPass runs
->addArgument(new ServiceClosureArgument(new Reference($cacheService)))
->addTag('annotations.cached_reader')
;
$container->setAlias('annotation_reader', 'annotations.cached_reader');
$container->setAlias(Reader::class, new Alias('annotations.cached_reader', false));
$container->removeDefinition('annotations.psr_cached_reader');
}
private function registerPropertyAccessConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader)
{
if (!$this->propertyAccessConfigEnabled = $this->isConfigEnabled($container, $config)) {
return;
}
$loader->load('property_access.php');
$magicMethods = PropertyAccessor::DISALLOW_MAGIC_METHODS;
$magicMethods |= $config['magic_call'] ? PropertyAccessor::MAGIC_CALL : 0;
$magicMethods |= $config['magic_get'] ? PropertyAccessor::MAGIC_GET : 0;
$magicMethods |= $config['magic_set'] ? PropertyAccessor::MAGIC_SET : 0;
$throw = PropertyAccessor::DO_NOT_THROW;
$throw |= $config['throw_exception_on_invalid_index'] ? PropertyAccessor::THROW_ON_INVALID_INDEX : 0;
$throw |= $config['throw_exception_on_invalid_property_path'] ? PropertyAccessor::THROW_ON_INVALID_PROPERTY_PATH : 0;
$container
->getDefinition('property_accessor')
->replaceArgument(0, $magicMethods)
->replaceArgument(1, $throw)
->replaceArgument(3, new Reference(PropertyReadInfoExtractorInterface::class, ContainerInterface::NULL_ON_INVALID_REFERENCE))
->replaceArgument(4, new Reference(PropertyWriteInfoExtractorInterface::class, ContainerInterface::NULL_ON_INVALID_REFERENCE))
;
}
private function registerSecretsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader)
{
if (!$this->isConfigEnabled($container, $config)) {
$container->removeDefinition('console.command.secrets_set');
$container->removeDefinition('console.command.secrets_list');
$container->removeDefinition('console.command.secrets_remove');
$container->removeDefinition('console.command.secrets_generate_key');
$container->removeDefinition('console.command.secrets_decrypt_to_local');
$container->removeDefinition('console.command.secrets_encrypt_from_local');
return;
}
$loader->load('secrets.php');
$container->getDefinition('secrets.vault')->replaceArgument(0, $config['vault_directory']);
if ($config['local_dotenv_file']) {
$container->getDefinition('secrets.local_vault')->replaceArgument(0, $config['local_dotenv_file']);
} else {
$container->removeDefinition('secrets.local_vault');
}
if ($config['decryption_env_var']) {
if (!preg_match('/^(?:[-.\w]*+:)*+\w++$/', $config['decryption_env_var'])) {
throw new InvalidArgumentException(sprintf('Invalid value "%s" set as "decryption_env_var": only "word" characters are allowed.', $config['decryption_env_var']));
}
if (ContainerBuilder::willBeAvailable('symfony/string', LazyString::class, ['symfony/framework-bundle'], true)) {
$container->getDefinition('secrets.decryption_key')->replaceArgument(1, $config['decryption_env_var']);
} else {
$container->getDefinition('secrets.vault')->replaceArgument(1, "%env({$config['decryption_env_var']})%");
$container->removeDefinition('secrets.decryption_key');
}
} else {
$container->getDefinition('secrets.vault')->replaceArgument(1, null);
$container->removeDefinition('secrets.decryption_key');
}
}
private function registerSecurityCsrfConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader)
{
if (!$this->isConfigEnabled($container, $config)) {
return;
}
if (!class_exists(\Symfony\Component\Security\Csrf\CsrfToken::class)) {
throw new LogicException('CSRF support cannot be enabled as the Security CSRF component is not installed. Try running "composer require symfony/security-csrf".');
}
if (!$this->sessionConfigEnabled) {
throw new \LogicException('CSRF protection needs sessions to be enabled.');
}
// Enable services for CSRF protection (even without forms)
$loader->load('security_csrf.php');
if (!class_exists(CsrfExtension::class)) {
$container->removeDefinition('twig.extension.security_csrf');
}
}
private function registerSerializerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader)
{
$loader->load('serializer.php');
if ($container->getParameter('kernel.debug')) {
$container->removeDefinition('serializer.mapping.cache_class_metadata_factory');
}
$chainLoader = $container->getDefinition('serializer.mapping.chain_loader');
if (!$this->propertyAccessConfigEnabled) {
$container->removeAlias('serializer.property_accessor');
$container->removeDefinition('serializer.normalizer.object');
}
if (!class_exists(Yaml::class)) {
$container->removeDefinition('serializer.encoder.yaml');
}
if (!class_exists(UnwrappingDenormalizer::class) || !$this->propertyAccessConfigEnabled) {
$container->removeDefinition('serializer.denormalizer.unwrapping');
}
if (!class_exists(Headers::class)) {
$container->removeDefinition('serializer.normalizer.mime_message');
}
$serializerLoaders = [];
if (isset($config['enable_annotations']) && $config['enable_annotations']) {
if (\PHP_VERSION_ID < 80000 && !$this->annotationsConfigEnabled) {
throw new \LogicException('"enable_annotations" on the serializer cannot be set as the PHP version is lower than 8 and Annotations support is disabled. Consider upgrading PHP.');
}
$annotationLoader = new Definition(
'Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader',
[new Reference('annotation_reader', ContainerInterface::NULL_ON_INVALID_REFERENCE)]
);
$annotationLoader->setPublic(false);
$serializerLoaders[] = $annotationLoader;
}
$fileRecorder = function ($extension, $path) use (&$serializerLoaders) {
$definition = new Definition(\in_array($extension, ['yaml', 'yml']) ? 'Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader' : 'Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader', [$path]);
$definition->setPublic(false);
$serializerLoaders[] = $definition;
};
foreach ($container->getParameter('kernel.bundles_metadata') as $bundle) {
$configDir = is_dir($bundle['path'].'/Resources/config') ? $bundle['path'].'/Resources/config' : $bundle['path'].'/config';
if ($container->fileExists($file = $configDir.'/serialization.xml', false)) {
$fileRecorder('xml', $file);
}
if (
$container->fileExists($file = $configDir.'/serialization.yaml', false) ||
$container->fileExists($file = $configDir.'/serialization.yml', false)
) {
$fileRecorder('yml', $file);
}
if ($container->fileExists($dir = $configDir.'/serialization', '/^$/')) {
$this->registerMappingFilesFromDir($dir, $fileRecorder);
}
}
$projectDir = $container->getParameter('kernel.project_dir');
if ($container->fileExists($dir = $projectDir.'/config/serializer', '/^$/')) {
$this->registerMappingFilesFromDir($dir, $fileRecorder);
}
$this->registerMappingFilesFromConfig($container, $config, $fileRecorder);
$chainLoader->replaceArgument(0, $serializerLoaders);
$container->getDefinition('serializer.mapping.cache_warmer')->replaceArgument(0, $serializerLoaders);
if (isset($config['name_converter']) && $config['name_converter']) {
$container->getDefinition('serializer.name_converter.metadata_aware')->setArgument(1, new Reference($config['name_converter']));
}
if (isset($config['circular_reference_handler']) && $config['circular_reference_handler']) {
$arguments = $container->getDefinition('serializer.normalizer.object')->getArguments();
$context = ($arguments[6] ?? []) + ['circular_reference_handler' => new Reference($config['circular_reference_handler'])];
$container->getDefinition('serializer.normalizer.object')->setArgument(5, null);
$container->getDefinition('serializer.normalizer.object')->setArgument(6, $context);
}
if ($config['max_depth_handler'] ?? false) {
$defaultContext = $container->getDefinition('serializer.normalizer.object')->getArgument(6);
$defaultContext += ['max_depth_handler' => new Reference($config['max_depth_handler'])];
$container->getDefinition('serializer.normalizer.object')->replaceArgument(6, $defaultContext);
}
if (isset($config['default_context']) && $config['default_context']) {
$container->setParameter('serializer.default_context', $config['default_context']);
}
}
private function registerPropertyInfoConfiguration(ContainerBuilder $container, PhpFileLoader $loader)
{
if (!interface_exists(PropertyInfoExtractorInterface::class)) {
throw new LogicException('PropertyInfo support cannot be enabled as the PropertyInfo component is not installed. Try running "composer require symfony/property-info".');
}
$loader->load('property_info.php');
if (
ContainerBuilder::willBeAvailable('phpstan/phpdoc-parser', PhpDocParser::class, ['symfony/framework-bundle', 'symfony/property-info'], true)
&& ContainerBuilder::willBeAvailable('phpdocumentor/type-resolver', ContextFactory::class, ['symfony/framework-bundle', 'symfony/property-info'], true)
) {
$definition = $container->register('property_info.phpstan_extractor', PhpStanExtractor::class);
$definition->addTag('property_info.type_extractor', ['priority' => -1000]);
}
if (ContainerBuilder::willBeAvailable('phpdocumentor/reflection-docblock', DocBlockFactoryInterface::class, ['symfony/framework-bundle', 'symfony/property-info'], true)) {
$definition = $container->register('property_info.php_doc_extractor', 'Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor');
$definition->addTag('property_info.description_extractor', ['priority' => -1000]);
$definition->addTag('property_info.type_extractor', ['priority' => -1001]);
}
if ($container->getParameter('kernel.debug')) {
$container->removeDefinition('property_info.cache');
}
}
private function registerLockConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader)
{
$loader->load('lock.php');
foreach ($config['resources'] as $resourceName => $resourceStores) {
if (0 === \count($resourceStores)) {
continue;
}
// Generate stores
$storeDefinitions = [];
foreach ($resourceStores as $storeDsn) {
$storeDsn = $container->resolveEnvPlaceholders($storeDsn, null, $usedEnvs);
$storeDefinition = new Definition(interface_exists(StoreInterface::class) ? StoreInterface::class : PersistingStoreInterface::class);
$storeDefinition->setFactory([StoreFactory::class, 'createStore']);
$storeDefinition->setArguments([$storeDsn]);
$container->setDefinition($storeDefinitionId = '.lock.'.$resourceName.'.store.'.$container->hash($storeDsn), $storeDefinition);
$storeDefinition = new Reference($storeDefinitionId);
$storeDefinitions[] = $storeDefinition;
}
// Wrap array of stores with CombinedStore
if (\count($storeDefinitions) > 1) {
$combinedDefinition = new ChildDefinition('lock.store.combined.abstract');
$combinedDefinition->replaceArgument(0, $storeDefinitions);
$container->setDefinition('lock.'.$resourceName.'.store', $combinedDefinition)->setDeprecated('symfony/framework-bundle', '5.2', 'The "%service_id%" service is deprecated, use "lock.'.$resourceName.'.factory" instead.');
$container->setDefinition($storeDefinitionId = '.lock.'.$resourceName.'.store.'.$container->hash($resourceStores), $combinedDefinition);
} else {
$container->setAlias('lock.'.$resourceName.'.store', (new Alias($storeDefinitionId, false))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "lock.'.$resourceName.'.factory" instead.'));
}
// Generate factories for each resource
$factoryDefinition = new ChildDefinition('lock.factory.abstract');
$factoryDefinition->replaceArgument(0, new Reference($storeDefinitionId));
$container->setDefinition('lock.'.$resourceName.'.factory', $factoryDefinition);
// Generate services for lock instances
$lockDefinition = new Definition(Lock::class);
$lockDefinition->setPublic(false);
$lockDefinition->setFactory([new Reference('lock.'.$resourceName.'.factory'), 'createLock']);
$lockDefinition->setArguments([$resourceName]);
$container->setDefinition('lock.'.$resourceName, $lockDefinition)->setDeprecated('symfony/framework-bundle', '5.2', 'The "%service_id%" service is deprecated, use "lock.'.$resourceName.'.factory" instead.');
// provide alias for default resource
if ('default' === $resourceName) {
$container->setAlias('lock.store', (new Alias($storeDefinitionId, false))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "lock.factory" instead.'));
$container->setAlias('lock.factory', new Alias('lock.'.$resourceName.'.factory', false));
$container->setAlias('lock', (new Alias('lock.'.$resourceName, false))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "lock.factory" instead.'));
$container->setAlias(PersistingStoreInterface::class, (new Alias($storeDefinitionId, false))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "'.LockFactory::class.'" instead.'));
$container->setAlias(LockFactory::class, new Alias('lock.factory', false));
$container->setAlias(LockInterface::class, (new Alias('lock.'.$resourceName, false))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "'.LockFactory::class.'" instead.'));
} else {
$container->registerAliasForArgument($storeDefinitionId, PersistingStoreInterface::class, $resourceName.'.lock.store')->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "'.LockFactory::class.' '.$resourceName.'LockFactory" instead.');
$container->registerAliasForArgument('lock.'.$resourceName.'.factory', LockFactory::class, $resourceName.'.lock.factory');
$container->registerAliasForArgument('lock.'.$resourceName, LockInterface::class, $resourceName.'.lock')->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "'.LockFactory::class.' $'.$resourceName.'LockFactory" instead.');
}
}
}
private function registerMessengerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, array $validationConfig)
{
if (!interface_exists(MessageBusInterface::class)) {
throw new LogicException('Messenger support cannot be enabled as the Messenger component is not installed. Try running "composer require symfony/messenger".');
}
$loader->load('messenger.php');
if (!interface_exists(DenormalizerInterface::class)) {
$container->removeDefinition('serializer.normalizer.flatten_exception');
}
if (ContainerBuilder::willBeAvailable('symfony/amqp-messenger', AmqpTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'], true)) {
$container->getDefinition('messenger.transport.amqp.factory')->addTag('messenger.transport_factory');
}
if (ContainerBuilder::willBeAvailable('symfony/redis-messenger', RedisTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'], true)) {
$container->getDefinition('messenger.transport.redis.factory')->addTag('messenger.transport_factory');
}
if (ContainerBuilder::willBeAvailable('symfony/amazon-sqs-messenger', AmazonSqsTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'], true)) {
$container->getDefinition('messenger.transport.sqs.factory')->addTag('messenger.transport_factory');
}
if (ContainerBuilder::willBeAvailable('symfony/beanstalkd-messenger', BeanstalkdTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'], true)) {
$container->getDefinition('messenger.transport.beanstalkd.factory')->addTag('messenger.transport_factory');
}
if (null === $config['default_bus'] && 1 === \count($config['buses'])) {
$config['default_bus'] = key($config['buses']);
}
$defaultMiddleware = [
'before' => [
['id' => 'add_bus_name_stamp_middleware'],
['id' => 'reject_redelivered_message_middleware'],
['id' => 'dispatch_after_current_bus'],
['id' => 'failed_message_processing_middleware'],
],
'after' => [
['id' => 'send_message'],
['id' => 'handle_message'],
],
];
foreach ($config['buses'] as $busId => $bus) {
$middleware = $bus['middleware'];
if ($bus['default_middleware']) {
if ('allow_no_handlers' === $bus['default_middleware']) {
$defaultMiddleware['after'][1]['arguments'] = [true];
} else {
unset($defaultMiddleware['after'][1]['arguments']);
}
// argument to add_bus_name_stamp_middleware
$defaultMiddleware['before'][0]['arguments'] = [$busId];
$middleware = array_merge($defaultMiddleware['before'], $middleware, $defaultMiddleware['after']);
}
foreach ($middleware as $middlewareItem) {
if (!$validationConfig['enabled'] && \in_array($middlewareItem['id'], ['validation', 'messenger.middleware.validation'], true)) {
throw new LogicException('The Validation middleware is only available when the Validator component is installed and enabled. Try running "composer require symfony/validator".');
}
}
if ($container->getParameter('kernel.debug') && class_exists(Stopwatch::class)) {
array_unshift($middleware, ['id' => 'traceable', 'arguments' => [$busId]]);
}
$container->setParameter($busId.'.middleware', $middleware);
$container->register($busId, MessageBus::class)->addArgument([])->addTag('messenger.bus');
if ($busId === $config['default_bus']) {
$container->setAlias('messenger.default_bus', $busId)->setPublic(true);
$container->setAlias(MessageBusInterface::class, $busId);
} else {
$container->registerAliasForArgument($busId, MessageBusInterface::class);
}
}
if (empty($config['transports'])) {
$container->removeDefinition('messenger.transport.symfony_serializer');
$container->removeDefinition('messenger.transport.amqp.factory');
$container->removeDefinition('messenger.transport.redis.factory');
$container->removeDefinition('messenger.transport.sqs.factory');
$container->removeDefinition('messenger.transport.beanstalkd.factory');
$container->removeAlias(SerializerInterface::class);
} else {
$container->getDefinition('messenger.transport.symfony_serializer')
->replaceArgument(1, $config['serializer']['symfony_serializer']['format'])
->replaceArgument(2, $config['serializer']['symfony_serializer']['context']);
$container->setAlias('messenger.default_serializer', $config['serializer']['default_serializer']);
}
$failureTransports = [];
if ($config['failure_transport']) {
if (!isset($config['transports'][$config['failure_transport']])) {
throw new LogicException(sprintf('Invalid Messenger configuration: the failure transport "%s" is not a valid transport or service id.', $config['failure_transport']));
}
$container->setAlias('messenger.failure_transports.default', 'messenger.transport.'.$config['failure_transport']);
$failureTransports[] = $config['failure_transport'];
}
$failureTransportsByName = [];
foreach ($config['transports'] as $name => $transport) {
if ($transport['failure_transport']) {
$failureTransports[] = $transport['failure_transport'];
$failureTransportsByName[$name] = $transport['failure_transport'];
} elseif ($config['failure_transport']) {
$failureTransportsByName[$name] = $config['failure_transport'];
}
}
$senderAliases = [];
$transportRetryReferences = [];
foreach ($config['transports'] as $name => $transport) {
$serializerId = $transport['serializer'] ?? 'messenger.default_serializer';
$transportDefinition = (new Definition(TransportInterface::class))
->setFactory([new Reference('messenger.transport_factory'), 'createTransport'])
->setArguments([$transport['dsn'], $transport['options'] + ['transport_name' => $name], new Reference($serializerId)])
->addTag('messenger.receiver', [
'alias' => $name,
'is_failure_transport' => \in_array($name, $failureTransports),
]
)
;
$container->setDefinition($transportId = 'messenger.transport.'.$name, $transportDefinition);
$senderAliases[$name] = $transportId;
if (null !== $transport['retry_strategy']['service']) {
$transportRetryReferences[$name] = new Reference($transport['retry_strategy']['service']);
} else {
$retryServiceId = sprintf('messenger.retry.multiplier_retry_strategy.%s', $name);
$retryDefinition = new ChildDefinition('messenger.retry.abstract_multiplier_retry_strategy');
$retryDefinition
->replaceArgument(0, $transport['retry_strategy']['max_retries'])
->replaceArgument(1, $transport['retry_strategy']['delay'])
->replaceArgument(2, $transport['retry_strategy']['multiplier'])
->replaceArgument(3, $transport['retry_strategy']['max_delay']);
$container->setDefinition($retryServiceId, $retryDefinition);
$transportRetryReferences[$name] = new Reference($retryServiceId);
}
}
$senderReferences = [];
// alias => service_id
foreach ($senderAliases as $alias => $serviceId) {
$senderReferences[$alias] = new Reference($serviceId);
}
// service_id => service_id
foreach ($senderAliases as $serviceId) {
$senderReferences[$serviceId] = new Reference($serviceId);
}
foreach ($config['transports'] as $name => $transport) {
if ($transport['failure_transport']) {
if (!isset($senderReferences[$transport['failure_transport']])) {
throw new LogicException(sprintf('Invalid Messenger configuration: the failure transport "%s" is not a valid transport or service id.', $transport['failure_transport']));
}
}
}
$failureTransportReferencesByTransportName = array_map(function ($failureTransportName) use ($senderReferences) {
return $senderReferences[$failureTransportName];
}, $failureTransportsByName);
$messageToSendersMapping = [];
foreach ($config['routing'] as $message => $messageConfiguration) {
if ('*' !== $message && !class_exists($message) && !interface_exists($message, false)) {
throw new LogicException(sprintf('Invalid Messenger routing configuration: class or interface "%s" not found.', $message));
}
// make sure senderAliases contains all senders
foreach ($messageConfiguration['senders'] as $sender) {
if (!isset($senderReferences[$sender])) {
throw new LogicException(sprintf('Invalid Messenger routing configuration: the "%s" class is being routed to a sender called "%s". This is not a valid transport or service id.', $message, $sender));
}
}
$messageToSendersMapping[$message] = $messageConfiguration['senders'];
}
$sendersServiceLocator = ServiceLocatorTagPass::register($container, $senderReferences);
$container->getDefinition('messenger.senders_locator')
->replaceArgument(0, $messageToSendersMapping)
->replaceArgument(1, $sendersServiceLocator)
;
$container->getDefinition('messenger.retry.send_failed_message_for_retry_listener')
->replaceArgument(0, $sendersServiceLocator)
;
$container->getDefinition('messenger.retry_strategy_locator')
->replaceArgument(0, $transportRetryReferences);
if (\count($failureTransports) > 0) {
$container->getDefinition('console.command.messenger_failed_messages_retry')
->replaceArgument(0, $config['failure_transport']);
$container->getDefinition('console.command.messenger_failed_messages_show')
->replaceArgument(0, $config['failure_transport']);
$container->getDefinition('console.command.messenger_failed_messages_remove')
->replaceArgument(0, $config['failure_transport']);
$failureTransportsByTransportNameServiceLocator = ServiceLocatorTagPass::register($container, $failureTransportReferencesByTransportName);
$container->getDefinition('messenger.failure.send_failed_message_to_failure_transport_listener')
->replaceArgument(0, $failureTransportsByTransportNameServiceLocator);
} else {
$container->removeDefinition('messenger.failure.send_failed_message_to_failure_transport_listener');
$container->removeDefinition('console.command.messenger_failed_messages_retry');
$container->removeDefinition('console.command.messenger_failed_messages_show');
$container->removeDefinition('console.command.messenger_failed_messages_remove');
}
if (false === $config['reset_on_message']) {
throw new LogicException('The "framework.messenger.reset_on_message" configuration option can be set to "true" only. To prevent services resetting after each message you can set the "--no-reset" option in "messenger:consume" command.');
}
if (!$container->hasDefinition('console.command.messenger_consume_messages')) {
$container->removeDefinition('messenger.listener.reset_services');
} elseif (null === $config['reset_on_message']) {
trigger_deprecation('symfony/framework-bundle', '5.4', 'Not setting the "framework.messenger.reset_on_message" configuration option is deprecated, it will default to "true" in version 6.0.');
$container->getDefinition('console.command.messenger_consume_messages')->replaceArgument(5, null);
$container->removeDefinition('messenger.listener.reset_services');
}
}
private function registerCacheConfiguration(array $config, ContainerBuilder $container)
{
if (!class_exists(DefaultMarshaller::class)) {
$container->removeDefinition('cache.default_marshaller');
}
if (!class_exists(DoctrineAdapter::class)) {
$container->removeDefinition('cache.adapter.doctrine');
}
if (!class_exists(DoctrineDbalAdapter::class)) {
$container->removeDefinition('cache.adapter.doctrine_dbal');
}
$version = new Parameter('container.build_id');
$container->getDefinition('cache.adapter.apcu')->replaceArgument(2, $version);
$container->getDefinition('cache.adapter.system')->replaceArgument(2, $version);
$container->getDefinition('cache.adapter.filesystem')->replaceArgument(2, $config['directory']);
if (isset($config['prefix_seed'])) {
$container->setParameter('cache.prefix.seed', $config['prefix_seed']);
}
if ($container->hasParameter('cache.prefix.seed')) {
// Inline any env vars referenced in the parameter
$container->setParameter('cache.prefix.seed', $container->resolveEnvPlaceholders($container->getParameter('cache.prefix.seed'), true));
}
foreach (['doctrine', 'psr6', 'redis', 'memcached', 'doctrine_dbal', 'pdo'] as $name) {
if (isset($config[$name = 'default_'.$name.'_provider'])) {
$container->setAlias('cache.'.$name, new Alias(CachePoolPass::getServiceProvider($container, $config[$name]), false));
}
}
foreach (['app', 'system'] as $name) {
$config['pools']['cache.'.$name] = [
'adapters' => [$config[$name]],
'public' => true,
'tags' => false,
];
}
foreach ($config['pools'] as $name => $pool) {
$pool['adapters'] = $pool['adapters'] ?: ['cache.app'];
$isRedisTagAware = ['cache.adapter.redis_tag_aware'] === $pool['adapters'];
foreach ($pool['adapters'] as $provider => $adapter) {
if (($config['pools'][$adapter]['adapters'] ?? null) === ['cache.adapter.redis_tag_aware']) {
$isRedisTagAware = true;
} elseif ($config['pools'][$adapter]['tags'] ?? false) {
$pool['adapters'][$provider] = $adapter = '.'.$adapter.'.inner';
}
}
if (1 === \count($pool['adapters'])) {
if (!isset($pool['provider']) && !\is_int($provider)) {
$pool['provider'] = $provider;
}
$definition = new ChildDefinition($adapter);
} else {
$definition = new Definition(ChainAdapter::class, [$pool['adapters'], 0]);
$pool['reset'] = 'reset';
}
if ($isRedisTagAware && 'cache.app' === $name) {
$container->setAlias('cache.app.taggable', $name);
} elseif ($isRedisTagAware) {
$tagAwareId = $name;
$container->setAlias('.'.$name.'.inner', $name);
} elseif ($pool['tags']) {
if (true !== $pool['tags'] && ($config['pools'][$pool['tags']]['tags'] ?? false)) {
$pool['tags'] = '.'.$pool['tags'].'.inner';
}
$container->register($name, TagAwareAdapter::class)
->addArgument(new Reference('.'.$name.'.inner'))
->addArgument(true !== $pool['tags'] ? new Reference($pool['tags']) : null)
->setPublic($pool['public'])
;
if (method_exists(TagAwareAdapter::class, 'setLogger')) {
$container
->getDefinition($name)
->addMethodCall('setLogger', [new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)])
->addTag('monolog.logger', ['channel' => 'cache']);
}
$pool['name'] = $tagAwareId = $name;
$pool['public'] = false;
$name = '.'.$name.'.inner';
} elseif (!\in_array($name, ['cache.app', 'cache.system'], true)) {
$tagAwareId = '.'.$name.'.taggable';
$container->register($tagAwareId, TagAwareAdapter::class)
->addArgument(new Reference($name))
;
}
if (!\in_array($name, ['cache.app', 'cache.system'], true)) {
$container->registerAliasForArgument($tagAwareId, TagAwareCacheInterface::class, $pool['name'] ?? $name);
$container->registerAliasForArgument($name, CacheInterface::class, $pool['name'] ?? $name);
$container->registerAliasForArgument($name, CacheItemPoolInterface::class, $pool['name'] ?? $name);
}
$definition->setPublic($pool['public']);
unset($pool['adapters'], $pool['public'], $pool['tags']);
$definition->addTag('cache.pool', $pool);
$container->setDefinition($name, $definition);
}
if (method_exists(PropertyAccessor::class, 'createCache')) {
$propertyAccessDefinition = $container->register('cache.property_access', AdapterInterface::class);
$propertyAccessDefinition->setPublic(false);
if (!$container->getParameter('kernel.debug')) {
$propertyAccessDefinition->setFactory([PropertyAccessor::class, 'createCache']);
$propertyAccessDefinition->setArguments(['', 0, $version, new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)]);
$propertyAccessDefinition->addTag('cache.pool', ['clearer' => 'cache.system_clearer']);
$propertyAccessDefinition->addTag('monolog.logger', ['channel' => 'cache']);
} else {
$propertyAccessDefinition->setClass(ArrayAdapter::class);
$propertyAccessDefinition->setArguments([0, false]);
}
}
}
private function registerHttpClientConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, array $profilerConfig)
{
$loader->load('http_client.php');
$options = $config['default_options'] ?? [];
$retryOptions = $options['retry_failed'] ?? ['enabled' => false];
unset($options['retry_failed']);
$container->getDefinition('http_client')->setArguments([$options, $config['max_host_connections'] ?? 6]);
if (!$hasPsr18 = ContainerBuilder::willBeAvailable('psr/http-client', ClientInterface::class, ['symfony/framework-bundle', 'symfony/http-client'], true)) {
$container->removeDefinition('psr18.http_client');
$container->removeAlias(ClientInterface::class);
}
if (!ContainerBuilder::willBeAvailable('php-http/httplug', HttpClient::class, ['symfony/framework-bundle', 'symfony/http-client'], true)) {
$container->removeDefinition(HttpClient::class);
}
if ($this->isConfigEnabled($container, $retryOptions)) {
$this->registerRetryableHttpClient($retryOptions, 'http_client', $container);
}
$httpClientId = ($retryOptions['enabled'] ?? false) ? 'http_client.retryable.inner' : ($this->isConfigEnabled($container, $profilerConfig) ? '.debug.http_client.inner' : 'http_client');
foreach ($config['scoped_clients'] as $name => $scopeConfig) {
if ('http_client' === $name) {
throw new InvalidArgumentException(sprintf('Invalid scope name: "%s" is reserved.', $name));
}
$scope = $scopeConfig['scope'] ?? null;
unset($scopeConfig['scope']);
$retryOptions = $scopeConfig['retry_failed'] ?? ['enabled' => false];
unset($scopeConfig['retry_failed']);
if (null === $scope) {
$baseUri = $scopeConfig['base_uri'];
unset($scopeConfig['base_uri']);
$container->register($name, ScopingHttpClient::class)
->setFactory([ScopingHttpClient::class, 'forBaseUri'])
->setArguments([new Reference($httpClientId), $baseUri, $scopeConfig])
->addTag('http_client.client')
;
} else {
$container->register($name, ScopingHttpClient::class)
->setArguments([new Reference($httpClientId), [$scope => $scopeConfig], $scope])
->addTag('http_client.client')
;
}
if ($this->isConfigEnabled($container, $retryOptions)) {
$this->registerRetryableHttpClient($retryOptions, $name, $container);
}
$container->registerAliasForArgument($name, HttpClientInterface::class);
if ($hasPsr18) {
$container->setDefinition('psr18.'.$name, new ChildDefinition('psr18.http_client'))
->replaceArgument(0, new Reference($name));
$container->registerAliasForArgument('psr18.'.$name, ClientInterface::class, $name);
}
}
if ($responseFactoryId = $config['mock_response_factory'] ?? null) {
$container->register($httpClientId.'.mock_client', MockHttpClient::class)
->setDecoratedService($httpClientId, null, -10) // lower priority than TraceableHttpClient
->setArguments([new Reference($responseFactoryId)]);
}
}
private function registerRetryableHttpClient(array $options, string $name, ContainerBuilder $container)
{
if (!class_exists(RetryableHttpClient::class)) {
throw new LogicException('Support for retrying failed requests requires symfony/http-client 5.2 or higher, try upgrading.');
}
if (null !== $options['retry_strategy']) {
$retryStrategy = new Reference($options['retry_strategy']);
} else {
$retryStrategy = new ChildDefinition('http_client.abstract_retry_strategy');
$codes = [];
foreach ($options['http_codes'] as $code => $codeOptions) {
if ($codeOptions['methods']) {
$codes[$code] = $codeOptions['methods'];
} else {
$codes[] = $code;
}
}
$retryStrategy
->replaceArgument(0, $codes ?: GenericRetryStrategy::DEFAULT_RETRY_STATUS_CODES)
->replaceArgument(1, $options['delay'])
->replaceArgument(2, $options['multiplier'])
->replaceArgument(3, $options['max_delay'])
->replaceArgument(4, $options['jitter']);
$container->setDefinition($name.'.retry_strategy', $retryStrategy);
$retryStrategy = new Reference($name.'.retry_strategy');
}
$container
->register($name.'.retryable', RetryableHttpClient::class)
->setDecoratedService($name, null, 10) // higher priority than TraceableHttpClient
->setArguments([new Reference($name.'.retryable.inner'), $retryStrategy, $options['max_retries'], new Reference('logger')])
->addTag('monolog.logger', ['channel' => 'http_client']);
}
private function registerMailerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader)
{
if (!class_exists(Mailer::class)) {
throw new LogicException('Mailer support cannot be enabled as the component is not installed. Try running "composer require symfony/mailer".');
}
$loader->load('mailer.php');
$loader->load('mailer_transports.php');
if (!\count($config['transports']) && null === $config['dsn']) {
$config['dsn'] = 'smtp://null';
}
$transports = $config['dsn'] ? ['main' => $config['dsn']] : $config['transports'];
$container->getDefinition('mailer.transports')->setArgument(0, $transports);
$container->getDefinition('mailer.default_transport')->setArgument(0, current($transports));
$container->removeDefinition('mailer.logger_message_listener');
$container->setAlias('mailer.logger_message_listener', (new Alias('mailer.message_logger_listener'))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "mailer.message_logger_listener" instead.'));
$mailer = $container->getDefinition('mailer.mailer');
if (false === $messageBus = $config['message_bus']) {
$mailer->replaceArgument(1, null);
} else {
$mailer->replaceArgument(1, $messageBus ? new Reference($messageBus) : new Reference('messenger.default_bus', ContainerInterface::NULL_ON_INVALID_REFERENCE));
}
$classToServices = [
GmailTransportFactory::class => 'mailer.transport_factory.gmail',
MailgunTransportFactory::class => 'mailer.transport_factory.mailgun',
MailjetTransportFactory::class => 'mailer.transport_factory.mailjet',
MandrillTransportFactory::class => 'mailer.transport_factory.mailchimp',
PostmarkTransportFactory::class => 'mailer.transport_factory.postmark',
SendgridTransportFactory::class => 'mailer.transport_factory.sendgrid',
SendinblueTransportFactory::class => 'mailer.transport_factory.sendinblue',
SesTransportFactory::class => 'mailer.transport_factory.amazon',
OhMySmtpTransportFactory::class => 'mailer.transport_factory.ohmysmtp',
];
foreach ($classToServices as $class => $service) {
$package = substr($service, \strlen('mailer.transport_factory.'));
if (!ContainerBuilder::willBeAvailable(sprintf('symfony/%s-mailer', 'gmail' === $package ? 'google' : $package), $class, ['symfony/framework-bundle', 'symfony/mailer'], true)) {
$container->removeDefinition($service);
}
}
$envelopeListener = $container->getDefinition('mailer.envelope_listener');
$envelopeListener->setArgument(0, $config['envelope']['sender'] ?? null);
$envelopeListener->setArgument(1, $config['envelope']['recipients'] ?? null);
if ($config['headers']) {
$headers = new Definition(Headers::class);
foreach ($config['headers'] as $name => $data) {
$value = $data['value'];
if (\in_array(strtolower($name), ['from', 'to', 'cc', 'bcc', 'reply-to'])) {
$value = (array) $value;
}
$headers->addMethodCall('addHeader', [$name, $value]);
}
$messageListener = $container->getDefinition('mailer.message_listener');
$messageListener->setArgument(0, $headers);
} else {
$container->removeDefinition('mailer.message_listener');
}
}
private function registerNotifierConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader)
{
if (!class_exists(Notifier::class)) {
throw new LogicException('Notifier support cannot be enabled as the component is not installed. Try running "composer require symfony/notifier".');
}
$loader->load('notifier.php');
$loader->load('notifier_transports.php');
if ($config['chatter_transports']) {
$container->getDefinition('chatter.transports')->setArgument(0, $config['chatter_transports']);
} else {
$container->removeDefinition('chatter');
}
if ($config['texter_transports']) {
$container->getDefinition('texter.transports')->setArgument(0, $config['texter_transports']);
} else {
$container->removeDefinition('texter');
}
if ($this->mailerConfigEnabled) {
$sender = $container->getDefinition('mailer.envelope_listener')->getArgument(0);
$container->getDefinition('notifier.channel.email')->setArgument(2, $sender);
} else {
$container->removeDefinition('notifier.channel.email');
}
if ($this->messengerConfigEnabled) {
if ($config['notification_on_failed_messages']) {
$container->getDefinition('notifier.failed_message_listener')->addTag('kernel.event_subscriber');
}
// as we have a bus, the channels don't need the transports
$container->getDefinition('notifier.channel.chat')->setArgument(0, null);
if ($container->hasDefinition('notifier.channel.email')) {
$container->getDefinition('notifier.channel.email')->setArgument(0, null);
}
$container->getDefinition('notifier.channel.sms')->setArgument(0, null);
$container->getDefinition('notifier.channel.push')->setArgument(0, null);
}
$container->getDefinition('notifier.channel_policy')->setArgument(0, $config['channel_policy']);
$container->registerForAutoconfiguration(NotifierTransportFactoryInterface::class)
->addTag('chatter.transport_factory');
$container->registerForAutoconfiguration(NotifierTransportFactoryInterface::class)
->addTag('texter.transport_factory');
$classToServices = [
AllMySmsTransportFactory::class => 'notifier.transport_factory.all-my-sms',
AmazonSnsTransportFactory::class => 'notifier.transport_factory.amazon-sns',
ClickatellTransportFactory::class => 'notifier.transport_factory.clickatell',
DiscordTransportFactory::class => 'notifier.transport_factory.discord',
EsendexTransportFactory::class => 'notifier.transport_factory.esendex',
ExpoTransportFactory::class => 'notifier.transport_factory.expo',
FakeChatTransportFactory::class => 'notifier.transport_factory.fake-chat',
FakeSmsTransportFactory::class => 'notifier.transport_factory.fake-sms',
FirebaseTransportFactory::class => 'notifier.transport_factory.firebase',
FreeMobileTransportFactory::class => 'notifier.transport_factory.free-mobile',
GatewayApiTransportFactory::class => 'notifier.transport_factory.gateway-api',
GitterTransportFactory::class => 'notifier.transport_factory.gitter',
GoogleChatTransportFactory::class => 'notifier.transport_factory.google-chat',
InfobipTransportFactory::class => 'notifier.transport_factory.infobip',
IqsmsTransportFactory::class => 'notifier.transport_factory.iqsms',
LightSmsTransportFactory::class => 'notifier.transport_factory.light-sms',
LinkedInTransportFactory::class => 'notifier.transport_factory.linked-in',
MailjetNotifierTransportFactory::class => 'notifier.transport_factory.mailjet',
MattermostTransportFactory::class => 'notifier.transport_factory.mattermost',
MercureTransportFactory::class => 'notifier.transport_factory.mercure',
MessageBirdTransport::class => 'notifier.transport_factory.message-bird',
MessageMediaTransportFactory::class => 'notifier.transport_factory.message-media',
MicrosoftTeamsTransportFactory::class => 'notifier.transport_factory.microsoft-teams',
MobytTransportFactory::class => 'notifier.transport_factory.mobyt',
NexmoTransportFactory::class => 'notifier.transport_factory.nexmo',
OctopushTransportFactory::class => 'notifier.transport_factory.octopush',
OneSignalTransportFactory::class => 'notifier.transport_factory.one-signal',
OvhCloudTransportFactory::class => 'notifier.transport_factory.ovh-cloud',
RocketChatTransportFactory::class => 'notifier.transport_factory.rocket-chat',
SendinblueNotifierTransportFactory::class => 'notifier.transport_factory.sendinblue',
SinchTransportFactory::class => 'notifier.transport_factory.sinch',
SlackTransportFactory::class => 'notifier.transport_factory.slack',
Sms77TransportFactory::class => 'notifier.transport_factory.sms77',
SmsapiTransportFactory::class => 'notifier.transport_factory.smsapi',
SmsBiurasTransportFactory::class => 'notifier.transport_factory.sms-biuras',
SmscTransportFactory::class => 'notifier.transport_factory.smsc',
SpotHitTransportFactory::class => 'notifier.transport_factory.spot-hit',
TelegramTransportFactory::class => 'notifier.transport_factory.telegram',
TelnyxTransportFactory::class => 'notifier.transport_factory.telnyx',
TurboSmsTransport::class => 'notifier.transport_factory.turbo-sms',
TwilioTransportFactory::class => 'notifier.transport_factory.twilio',
VonageTransportFactory::class => 'notifier.transport_factory.vonage',
YunpianTransportFactory::class => 'notifier.transport_factory.yunpian',
ZulipTransportFactory::class => 'notifier.transport_factory.zulip',
];
$parentPackages = ['symfony/framework-bundle', 'symfony/notifier'];
foreach ($classToServices as $class => $service) {
$package = substr($service, \strlen('notifier.transport_factory.'));
if (!ContainerBuilder::willBeAvailable(sprintf('symfony/%s-notifier', $package), $class, $parentPackages, true)) {
$container->removeDefinition($service);
$container->removeAlias(str_replace('-', '', $service)); // @deprecated to be removed in 6.0
}
}
if (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', MercureTransportFactory::class, $parentPackages, true) && ContainerBuilder::willBeAvailable('symfony/mercure-bundle', MercureBundle::class, $parentPackages, true)) {
$container->getDefinition($classToServices[MercureTransportFactory::class])
->replaceArgument('$registry', new Reference(HubRegistry::class));
} elseif (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', MercureTransportFactory::class, $parentPackages, true)) {
$container->removeDefinition($classToServices[MercureTransportFactory::class]);
}
if (ContainerBuilder::willBeAvailable('symfony/fake-chat-notifier', FakeChatTransportFactory::class, ['symfony/framework-bundle', 'symfony/notifier', 'symfony/mailer'], true)) {
$container->getDefinition($classToServices[FakeChatTransportFactory::class])
->replaceArgument('$mailer', new Reference('mailer'))
->replaceArgument('$logger', new Reference('logger'));
}
if (ContainerBuilder::willBeAvailable('symfony/fake-sms-notifier', FakeSmsTransportFactory::class, ['symfony/framework-bundle', 'symfony/notifier', 'symfony/mailer'], true)) {
$container->getDefinition($classToServices[FakeSmsTransportFactory::class])
->replaceArgument('$mailer', new Reference('mailer'))
->replaceArgument('$logger', new Reference('logger'));
}
if (isset($config['admin_recipients'])) {
$notifier = $container->getDefinition('notifier');
foreach ($config['admin_recipients'] as $i => $recipient) {
$id = 'notifier.admin_recipient.'.$i;
$container->setDefinition($id, new Definition(Recipient::class, [$recipient['email'], $recipient['phone']]));
$notifier->addMethodCall('addAdminRecipient', [new Reference($id)]);
}
}
}
private function registerRateLimiterConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader)
{
$loader->load('rate_limiter.php');
foreach ($config['limiters'] as $name => $limiterConfig) {
self::registerRateLimiter($container, $name, $limiterConfig);
}
}
public static function registerRateLimiter(ContainerBuilder $container, string $name, array $limiterConfig)
{
// default configuration (when used by other DI extensions)
$limiterConfig += ['lock_factory' => 'lock.factory', 'cache_pool' => 'cache.rate_limiter'];
$limiter = $container->setDefinition($limiterId = 'limiter.'.$name, new ChildDefinition('limiter'));
if (null !== $limiterConfig['lock_factory']) {
if (!self::$lockConfigEnabled) {
throw new LogicException(sprintf('Rate limiter "%s" requires the Lock component to be installed and configured.', $name));
}
$limiter->replaceArgument(2, new Reference($limiterConfig['lock_factory']));
}
unset($limiterConfig['lock_factory']);
$storageId = $limiterConfig['storage_service'] ?? null;
if (null === $storageId) {
$container->register($storageId = 'limiter.storage.'.$name, CacheStorage::class)->addArgument(new Reference($limiterConfig['cache_pool']));
}
$limiter->replaceArgument(1, new Reference($storageId));
unset($limiterConfig['storage_service']);
unset($limiterConfig['cache_pool']);
$limiterConfig['id'] = $name;
$limiter->replaceArgument(0, $limiterConfig);
$container->registerAliasForArgument($limiterId, RateLimiterFactory::class, $name.'.limiter');
}
private function registerUidConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader)
{
$loader->load('uid.php');
$container->getDefinition('uuid.factory')
->setArguments([
$config['default_uuid_version'],
$config['time_based_uuid_version'],
$config['name_based_uuid_version'],
UuidV4::class,
$config['time_based_uuid_node'] ?? null,
$config['name_based_uuid_namespace'] ?? null,
])
;
if (isset($config['name_based_uuid_namespace'])) {
$container->getDefinition('name_based_uuid.factory')
->setArguments([$config['name_based_uuid_namespace']]);
}
}
private function resolveTrustedHeaders(array $headers): int
{
$trustedHeaders = 0;
foreach ($headers as $h) {
switch ($h) {
case 'forwarded': $trustedHeaders |= Request::HEADER_FORWARDED; break;
case 'x-forwarded-for': $trustedHeaders |= Request::HEADER_X_FORWARDED_FOR; break;
case 'x-forwarded-host': $trustedHeaders |= Request::HEADER_X_FORWARDED_HOST; break;
case 'x-forwarded-proto': $trustedHeaders |= Request::HEADER_X_FORWARDED_PROTO; break;
case 'x-forwarded-port': $trustedHeaders |= Request::HEADER_X_FORWARDED_PORT; break;
case 'x-forwarded-prefix': $trustedHeaders |= Request::HEADER_X_FORWARDED_PREFIX; break;
}
}
return $trustedHeaders;
}
/**
* {@inheritdoc}
*/
public function getXsdValidationBasePath()
{
return \dirname(__DIR__).'/Resources/config/schema';
}
public function getNamespace()
{
return 'http://symfony.com/schema/dic/symfony';
}
}

View File

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

View File

@ -0,0 +1,180 @@
<?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;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddAnnotationsCachedReaderPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddDebugLogProcessorPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddExpressionLanguageProvidersPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AssetsContextPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ContainerBuilderDebugDumpPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\DataCollectorTranslatorPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\LoggingTranslatorPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ProfilerPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RemoveUnusedSessionMarshallingHandlerPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\SessionPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TestServiceContainerRealRefPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TestServiceContainerWeakRefPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\UnusedTagsPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\WorkflowGuardListenerPass;
use Symfony\Component\Cache\Adapter\ApcuAdapter;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\ChainAdapter;
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
use Symfony\Component\Cache\DependencyInjection\CacheCollectorPass;
use Symfony\Component\Cache\DependencyInjection\CachePoolClearerPass;
use Symfony\Component\Cache\DependencyInjection\CachePoolPass;
use Symfony\Component\Cache\DependencyInjection\CachePoolPrunerPass;
use Symfony\Component\Config\Resource\ClassExistenceResource;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\Compiler\RegisterReverseContainerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Dotenv\Dotenv;
use Symfony\Component\ErrorHandler\ErrorHandler;
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
use Symfony\Component\Form\DependencyInjection\FormPass;
use Symfony\Component\HttpClient\DependencyInjection\HttpClientPass;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\HttpKernel\DependencyInjection\ControllerArgumentValueResolverPass;
use Symfony\Component\HttpKernel\DependencyInjection\FragmentRendererPass;
use Symfony\Component\HttpKernel\DependencyInjection\LoggerPass;
use Symfony\Component\HttpKernel\DependencyInjection\RegisterControllerArgumentLocatorsPass;
use Symfony\Component\HttpKernel\DependencyInjection\RegisterLocaleAwareServicesPass;
use Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass;
use Symfony\Component\HttpKernel\DependencyInjection\ResettableServicePass;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Messenger\DependencyInjection\MessengerPass;
use Symfony\Component\Mime\DependencyInjection\AddMimeTypeGuesserPass;
use Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass;
use Symfony\Component\Routing\DependencyInjection\RoutingResolverPass;
use Symfony\Component\Serializer\DependencyInjection\SerializerPass;
use Symfony\Component\Translation\DependencyInjection\TranslationDumperPass;
use Symfony\Component\Translation\DependencyInjection\TranslationExtractorPass;
use Symfony\Component\Translation\DependencyInjection\TranslatorPass;
use Symfony\Component\Translation\DependencyInjection\TranslatorPathsPass;
use Symfony\Component\Validator\DependencyInjection\AddAutoMappingConfigurationPass;
use Symfony\Component\Validator\DependencyInjection\AddConstraintValidatorsPass;
use Symfony\Component\Validator\DependencyInjection\AddValidatorInitializersPass;
use Symfony\Component\VarExporter\Internal\Hydrator;
use Symfony\Component\VarExporter\Internal\Registry;
// Help opcache.preload discover always-needed symbols
class_exists(ApcuAdapter::class);
class_exists(ArrayAdapter::class);
class_exists(ChainAdapter::class);
class_exists(PhpArrayAdapter::class);
class_exists(PhpFilesAdapter::class);
class_exists(Dotenv::class);
class_exists(ErrorHandler::class);
class_exists(Hydrator::class);
class_exists(Registry::class);
/**
* Bundle.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FrameworkBundle extends Bundle
{
public function boot()
{
ErrorHandler::register(null, false)->throwAt($this->container->getParameter('debug.error_handler.throw_at'), true);
if ($this->container->getParameter('kernel.http_method_override')) {
Request::enableHttpMethodParameterOverride();
}
}
public function build(ContainerBuilder $container)
{
parent::build($container);
$registerListenersPass = new RegisterListenersPass();
$registerListenersPass->setHotPathEvents([
KernelEvents::REQUEST,
KernelEvents::CONTROLLER,
KernelEvents::CONTROLLER_ARGUMENTS,
KernelEvents::RESPONSE,
KernelEvents::FINISH_REQUEST,
]);
if (class_exists(ConsoleEvents::class)) {
$registerListenersPass->setNoPreloadEvents([
ConsoleEvents::COMMAND,
ConsoleEvents::TERMINATE,
ConsoleEvents::ERROR,
]);
}
$container->addCompilerPass(new AssetsContextPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION);
$container->addCompilerPass(new LoggerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);
$container->addCompilerPass(new RegisterControllerArgumentLocatorsPass());
$container->addCompilerPass(new RemoveEmptyControllerArgumentLocatorsPass(), PassConfig::TYPE_BEFORE_REMOVING);
$container->addCompilerPass(new RoutingResolverPass());
$container->addCompilerPass(new DataCollectorTranslatorPass());
$container->addCompilerPass(new ProfilerPass());
// must be registered before removing private services as some might be listeners/subscribers
// but as late as possible to get resolved parameters
$container->addCompilerPass($registerListenersPass, PassConfig::TYPE_BEFORE_REMOVING);
$this->addCompilerPassIfExists($container, AddConstraintValidatorsPass::class);
$container->addCompilerPass(new AddAnnotationsCachedReaderPass(), PassConfig::TYPE_AFTER_REMOVING, -255);
$this->addCompilerPassIfExists($container, AddValidatorInitializersPass::class);
$this->addCompilerPassIfExists($container, AddConsoleCommandPass::class, PassConfig::TYPE_BEFORE_REMOVING);
// must be registered as late as possible to get access to all Twig paths registered in
// twig.template_iterator definition
$this->addCompilerPassIfExists($container, TranslatorPass::class, PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);
$this->addCompilerPassIfExists($container, TranslatorPathsPass::class, PassConfig::TYPE_AFTER_REMOVING);
$container->addCompilerPass(new LoggingTranslatorPass());
$container->addCompilerPass(new AddExpressionLanguageProvidersPass(false));
$this->addCompilerPassIfExists($container, TranslationExtractorPass::class);
$this->addCompilerPassIfExists($container, TranslationDumperPass::class);
$container->addCompilerPass(new FragmentRendererPass());
$this->addCompilerPassIfExists($container, SerializerPass::class);
$this->addCompilerPassIfExists($container, PropertyInfoPass::class);
$container->addCompilerPass(new ControllerArgumentValueResolverPass());
$container->addCompilerPass(new CachePoolPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 32);
$container->addCompilerPass(new CachePoolClearerPass(), PassConfig::TYPE_AFTER_REMOVING);
$container->addCompilerPass(new CachePoolPrunerPass(), PassConfig::TYPE_AFTER_REMOVING);
$this->addCompilerPassIfExists($container, FormPass::class);
$container->addCompilerPass(new WorkflowGuardListenerPass());
$container->addCompilerPass(new ResettableServicePass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);
$container->addCompilerPass(new RegisterLocaleAwareServicesPass());
$container->addCompilerPass(new TestServiceContainerWeakRefPass(), PassConfig::TYPE_BEFORE_REMOVING, -32);
$container->addCompilerPass(new TestServiceContainerRealRefPass(), PassConfig::TYPE_AFTER_REMOVING);
$this->addCompilerPassIfExists($container, AddMimeTypeGuesserPass::class);
$this->addCompilerPassIfExists($container, MessengerPass::class);
$this->addCompilerPassIfExists($container, HttpClientPass::class);
$this->addCompilerPassIfExists($container, AddAutoMappingConfigurationPass::class);
$container->addCompilerPass(new RegisterReverseContainerPass(true));
$container->addCompilerPass(new RegisterReverseContainerPass(false), PassConfig::TYPE_AFTER_REMOVING);
$container->addCompilerPass(new RemoveUnusedSessionMarshallingHandlerPass());
$container->addCompilerPass(new SessionPass());
if ($container->getParameter('kernel.debug')) {
$container->addCompilerPass(new AddDebugLogProcessorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 2);
$container->addCompilerPass(new UnusedTagsPass(), PassConfig::TYPE_AFTER_REMOVING);
$container->addCompilerPass(new ContainerBuilderDebugDumpPass(), PassConfig::TYPE_BEFORE_REMOVING, -255);
$container->addCompilerPass(new CacheCollectorPass(), PassConfig::TYPE_BEFORE_REMOVING);
}
}
private function addCompilerPassIfExists(ContainerBuilder $container, string $class, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0)
{
$container->addResource(new ClassExistenceResource($class));
if (class_exists($class)) {
$container->addCompilerPass(new $class(), $type, $priority);
}
}
}

View File

@ -0,0 +1,101 @@
<?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\HttpCache;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpCache\Esi;
use Symfony\Component\HttpKernel\HttpCache\HttpCache as BaseHttpCache;
use Symfony\Component\HttpKernel\HttpCache\Store;
use Symfony\Component\HttpKernel\HttpCache\StoreInterface;
use Symfony\Component\HttpKernel\HttpCache\SurrogateInterface;
use Symfony\Component\HttpKernel\KernelInterface;
/**
* Manages HTTP cache objects in a Container.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class HttpCache extends BaseHttpCache
{
protected $cacheDir;
protected $kernel;
private $store;
private $surrogate;
private $options;
/**
* @param string|StoreInterface $cache The cache directory (default used if null) or the storage instance
*/
public function __construct(KernelInterface $kernel, $cache = null, SurrogateInterface $surrogate = null, array $options = null)
{
$this->kernel = $kernel;
$this->surrogate = $surrogate;
$this->options = $options ?? [];
if ($cache instanceof StoreInterface) {
$this->store = $cache;
} elseif (null !== $cache && !\is_string($cache)) {
throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be a string or a SurrogateInterface, "%s" given.', __METHOD__, get_debug_type($cache)));
} else {
$this->cacheDir = $cache;
}
if (null === $options && $kernel->isDebug()) {
$this->options = ['debug' => true];
}
if ($this->options['debug'] ?? false) {
$this->options += ['stale_if_error' => 0];
}
parent::__construct($kernel, $this->createStore(), $this->createSurrogate(), array_merge($this->options, $this->getOptions()));
}
/**
* {@inheritdoc}
*/
protected function forward(Request $request, bool $catch = false, Response $entry = null)
{
$this->getKernel()->boot();
$this->getKernel()->getContainer()->set('cache', $this);
return parent::forward($request, $catch, $entry);
}
/**
* Returns an array of options to customize the Cache configuration.
*
* @return array
*/
protected function getOptions()
{
return [];
}
/**
* @return SurrogateInterface
*/
protected function createSurrogate()
{
return $this->surrogate ?? new Esi();
}
/**
* @return StoreInterface
*/
protected function createStore()
{
return $this->store ?? new Store($this->cacheDir ?: $this->kernel->getCacheDir().'/http_cache');
}
}

View File

@ -0,0 +1,234 @@
<?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\Kernel;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\Configurator\AbstractConfigurator;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader as ContainerPhpFileLoader;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
use Symfony\Component\Routing\Loader\PhpFileLoader as RoutingPhpFileLoader;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RouteCollectionBuilder;
/**
* A Kernel that provides configuration hooks.
*
* @author Ryan Weaver <ryan@knpuniversity.com>
* @author Fabien Potencier <fabien@symfony.com>
*/
trait MicroKernelTrait
{
/**
* Configures the container.
*
* You can register extensions:
*
* $container->extension('framework', [
* 'secret' => '%secret%'
* ]);
*
* Or services:
*
* $container->services()->set('halloween', 'FooBundle\HalloweenProvider');
*
* Or parameters:
*
* $container->parameters()->set('halloween', 'lot of fun');
*/
private function configureContainer(ContainerConfigurator $container, LoaderInterface $loader, ContainerBuilder $builder): void
{
$configDir = $this->getConfigDir();
$container->import($configDir.'/{packages}/*.yaml');
$container->import($configDir.'/{packages}/'.$this->environment.'/*.yaml');
if (is_file($configDir.'/services.yaml')) {
$container->import($configDir.'/services.yaml');
$container->import($configDir.'/{services}_'.$this->environment.'.yaml');
} else {
$container->import($configDir.'/{services}.php');
}
}
/**
* Adds or imports routes into your application.
*
* $routes->import($this->getConfigDir().'/*.{yaml,php}');
* $routes
* ->add('admin_dashboard', '/admin')
* ->controller('App\Controller\AdminController::dashboard')
* ;
*/
private function configureRoutes(RoutingConfigurator $routes): void
{
$configDir = $this->getConfigDir();
$routes->import($configDir.'/{routes}/'.$this->environment.'/*.yaml');
$routes->import($configDir.'/{routes}/*.yaml');
if (is_file($configDir.'/routes.yaml')) {
$routes->import($configDir.'/routes.yaml');
} else {
$routes->import($configDir.'/{routes}.php');
}
}
/**
* Gets the path to the configuration directory.
*/
private function getConfigDir(): string
{
return $this->getProjectDir().'/config';
}
/**
* Gets the path to the bundles configuration file.
*/
private function getBundlesPath(): string
{
return $this->getConfigDir().'/bundles.php';
}
/**
* {@inheritdoc}
*/
public function getCacheDir(): string
{
if (isset($_SERVER['APP_CACHE_DIR'])) {
return $_SERVER['APP_CACHE_DIR'].'/'.$this->environment;
}
return parent::getCacheDir();
}
/**
* {@inheritdoc}
*/
public function getLogDir(): string
{
return $_SERVER['APP_LOG_DIR'] ?? parent::getLogDir();
}
/**
* {@inheritdoc}
*/
public function registerBundles(): iterable
{
$contents = require $this->getBundlesPath();
foreach ($contents as $class => $envs) {
if ($envs[$this->environment] ?? $envs['all'] ?? false) {
yield new $class();
}
}
}
/**
* {@inheritdoc}
*/
public function registerContainerConfiguration(LoaderInterface $loader)
{
$loader->load(function (ContainerBuilder $container) use ($loader) {
$container->loadFromExtension('framework', [
'router' => [
'resource' => 'kernel::loadRoutes',
'type' => 'service',
],
]);
$kernelClass = false !== strpos(static::class, "@anonymous\0") ? parent::class : static::class;
if (!$container->hasDefinition('kernel')) {
$container->register('kernel', $kernelClass)
->addTag('controller.service_arguments')
->setAutoconfigured(true)
->setSynthetic(true)
->setPublic(true)
;
}
$kernelDefinition = $container->getDefinition('kernel');
$kernelDefinition->addTag('routing.route_loader');
$container->addObjectResource($this);
$container->fileExists($this->getBundlesPath());
$configureContainer = new \ReflectionMethod($this, 'configureContainer');
$configuratorClass = $configureContainer->getNumberOfParameters() > 0 && ($type = $configureContainer->getParameters()[0]->getType()) instanceof \ReflectionNamedType && !$type->isBuiltin() ? $type->getName() : null;
if ($configuratorClass && !is_a(ContainerConfigurator::class, $configuratorClass, true)) {
$configureContainer->getClosure($this)($container, $loader);
return;
}
$file = (new \ReflectionObject($this))->getFileName();
/* @var ContainerPhpFileLoader $kernelLoader */
$kernelLoader = $loader->getResolver()->resolve($file);
$kernelLoader->setCurrentDir(\dirname($file));
$instanceof = &\Closure::bind(function &() { return $this->instanceof; }, $kernelLoader, $kernelLoader)();
$valuePreProcessor = AbstractConfigurator::$valuePreProcessor;
AbstractConfigurator::$valuePreProcessor = function ($value) {
return $this === $value ? new Reference('kernel') : $value;
};
try {
$configureContainer->getClosure($this)(new ContainerConfigurator($container, $kernelLoader, $instanceof, $file, $file, $this->getEnvironment()), $loader, $container);
} finally {
$instanceof = [];
$kernelLoader->registerAliasesForSinglyImplementedInterfaces();
AbstractConfigurator::$valuePreProcessor = $valuePreProcessor;
}
$container->setAlias($kernelClass, 'kernel')->setPublic(true);
});
}
/**
* @internal
*/
public function loadRoutes(LoaderInterface $loader): RouteCollection
{
$file = (new \ReflectionObject($this))->getFileName();
/* @var RoutingPhpFileLoader $kernelLoader */
$kernelLoader = $loader->getResolver()->resolve($file, 'php');
$kernelLoader->setCurrentDir(\dirname($file));
$collection = new RouteCollection();
$configureRoutes = new \ReflectionMethod($this, 'configureRoutes');
$configuratorClass = $configureRoutes->getNumberOfParameters() > 0 && ($type = $configureRoutes->getParameters()[0]->getType()) instanceof \ReflectionNamedType && !$type->isBuiltin() ? $type->getName() : null;
if ($configuratorClass && !is_a(RoutingConfigurator::class, $configuratorClass, true)) {
trigger_deprecation('symfony/framework-bundle', '5.1', 'Using type "%s" for argument 1 of method "%s:configureRoutes()" is deprecated, use "%s" instead.', RouteCollectionBuilder::class, self::class, RoutingConfigurator::class);
$routes = new RouteCollectionBuilder($loader);
$this->configureRoutes($routes);
return $routes->build();
}
$configureRoutes->getClosure($this)(new RoutingConfigurator($collection, $kernelLoader, $file, $file, $this->getEnvironment()));
foreach ($collection as $route) {
$controller = $route->getDefault('_controller');
if (\is_array($controller) && [0, 1] === array_keys($controller) && $this === $controller[0]) {
$route->setDefault('_controller', ['kernel', $controller[1]]);
}
}
return $collection;
}
}

View File

@ -0,0 +1,262 @@
<?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;
use Symfony\Bundle\FrameworkBundle\Test\TestBrowserToken;
use Symfony\Component\BrowserKit\Cookie;
use Symfony\Component\BrowserKit\CookieJar;
use Symfony\Component\BrowserKit\History;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelBrowser;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\HttpKernel\Profiler\Profile as HttpProfile;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* Simulates a browser and makes requests to a Kernel object.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class KernelBrowser extends HttpKernelBrowser
{
private $hasPerformedRequest = false;
private $profiler = false;
private $reboot = true;
/**
* {@inheritdoc}
*/
public function __construct(KernelInterface $kernel, array $server = [], History $history = null, CookieJar $cookieJar = null)
{
parent::__construct($kernel, $server, $history, $cookieJar);
}
/**
* Returns the container.
*
* @return ContainerInterface
*/
public function getContainer()
{
$container = $this->kernel->getContainer();
return $container->has('test.service_container') ? $container->get('test.service_container') : $container;
}
/**
* Returns the kernel.
*
* @return KernelInterface
*/
public function getKernel()
{
return $this->kernel;
}
/**
* Gets the profile associated with the current Response.
*
* @return HttpProfile|false|null
*/
public function getProfile()
{
if (null === $this->response || !$this->getContainer()->has('profiler')) {
return false;
}
return $this->getContainer()->get('profiler')->loadProfileFromResponse($this->response);
}
/**
* Enables the profiler for the very next request.
*
* If the profiler is not enabled, the call to this method does nothing.
*/
public function enableProfiler()
{
if ($this->getContainer()->has('profiler')) {
$this->profiler = true;
}
}
/**
* Disables kernel reboot between requests.
*
* By default, the Client reboots the Kernel for each request. This method
* allows to keep the same kernel across requests.
*/
public function disableReboot()
{
$this->reboot = false;
}
/**
* Enables kernel reboot between requests.
*/
public function enableReboot()
{
$this->reboot = true;
}
/**
* @param UserInterface $user
*
* @return $this
*/
public function loginUser(object $user, string $firewallContext = 'main'): self
{
if (!interface_exists(UserInterface::class)) {
throw new \LogicException(sprintf('"%s" requires symfony/security-core to be installed.', __METHOD__));
}
if (!$user instanceof UserInterface) {
throw new \LogicException(sprintf('The first argument of "%s" must be instance of "%s", "%s" provided.', __METHOD__, UserInterface::class, \is_object($user) ? \get_class($user) : \gettype($user)));
}
$token = new TestBrowserToken($user->getRoles(), $user, $firewallContext);
// @deprecated since Symfony 5.4
if (method_exists($token, 'setAuthenticated')) {
$token->setAuthenticated(true, false);
}
$container = $this->getContainer();
$container->get('security.untracked_token_storage')->setToken($token);
if ($container->has('session.factory')) {
$session = $container->get('session.factory')->createSession();
} elseif ($container->has('session')) {
$session = $container->get('session');
} else {
return $this;
}
$session->set('_security_'.$firewallContext, serialize($token));
$session->save();
$domains = array_unique(array_map(function (Cookie $cookie) use ($session) {
return $cookie->getName() === $session->getName() ? $cookie->getDomain() : '';
}, $this->getCookieJar()->all())) ?: [''];
foreach ($domains as $domain) {
$cookie = new Cookie($session->getName(), $session->getId(), null, null, $domain);
$this->getCookieJar()->set($cookie);
}
return $this;
}
/**
* {@inheritdoc}
*
* @param Request $request
*
* @return Response
*/
protected function doRequest(object $request)
{
// avoid shutting down the Kernel if no request has been performed yet
// WebTestCase::createClient() boots the Kernel but do not handle a request
if ($this->hasPerformedRequest && $this->reboot) {
$this->kernel->boot();
$this->kernel->shutdown();
} else {
$this->hasPerformedRequest = true;
}
if ($this->profiler) {
$this->profiler = false;
$this->kernel->boot();
$this->getContainer()->get('profiler')->enable();
}
return parent::doRequest($request);
}
/**
* {@inheritdoc}
*
* @param Request $request
*
* @return Response
*/
protected function doRequestInProcess(object $request)
{
$response = parent::doRequestInProcess($request);
$this->profiler = false;
return $response;
}
/**
* Returns the script to execute when the request must be insulated.
*
* It assumes that the autoloader is named 'autoload.php' and that it is
* stored in the same directory as the kernel (this is the case for the
* Symfony Standard Edition). If this is not your case, create your own
* client and override this method.
*
* @param Request $request
*
* @return string
*/
protected function getScript(object $request)
{
$kernel = var_export(serialize($this->kernel), true);
$request = var_export(serialize($request), true);
$errorReporting = error_reporting();
$requires = '';
foreach (get_declared_classes() as $class) {
if (str_starts_with($class, 'ComposerAutoloaderInit')) {
$r = new \ReflectionClass($class);
$file = \dirname($r->getFileName(), 2).'/autoload.php';
if (is_file($file)) {
$requires .= 'require_once '.var_export($file, true).";\n";
}
}
}
if (!$requires) {
throw new \RuntimeException('Composer autoloader not found.');
}
$requires .= 'require_once '.var_export((new \ReflectionObject($this->kernel))->getFileName(), true).";\n";
$profilerCode = '';
if ($this->profiler) {
$profilerCode = <<<'EOF'
$container = $kernel->getContainer();
$container = $container->has('test.service_container') ? $container->get('test.service_container') : $container;
$container->get('profiler')->enable();
EOF;
}
$code = <<<EOF
<?php
error_reporting($errorReporting);
$requires
\$kernel = unserialize($kernel);
\$kernel->boot();
$profilerCode
\$request = unserialize($request);
EOF;
return $code.$this->getHandleScript();
}
}

19
vendor/symfony/framework-bundle/LICENSE vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2004-2022 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,13 @@
FrameworkBundle
===============
FrameworkBundle provides a tight integration between Symfony components and the
Symfony full-stack framework.
Resources
---------
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)

View File

@ -0,0 +1,19 @@
<?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.
*/
require dirname(__DIR__, 6).'/vendor/autoload.php';
use Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler\UnusedTagsPassUtils;
$target = dirname(__DIR__, 2).'/DependencyInjection/Compiler/UnusedTagsPass.php';
$contents = file_get_contents($target);
$contents = preg_replace('{private const KNOWN_TAGS = \[(.+?)\];}sm', "private const KNOWN_TAGS = [\n '".implode("',\n '", UnusedTagsPassUtils::getDefinedTags())."',\n ];", $contents);
file_put_contents($target, $contents);

View File

@ -0,0 +1,81 @@
<?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\Component\DependencyInjection\Loader\Configurator;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;
use Doctrine\Common\Annotations\PsrCachedReader;
use Doctrine\Common\Annotations\Reader;
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
use Symfony\Bundle\FrameworkBundle\CacheWarmer\AnnotationsCacheWarmer;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
return static function (ContainerConfigurator $container) {
$container->services()
->set('annotations.reader', AnnotationReader::class)
->call('addGlobalIgnoredName', [
'required',
service('annotations.dummy_registry'), // dummy arg to register class_exists as annotation loader only when required
])
->set('annotations.dummy_registry', AnnotationRegistry::class)
->call('registerUniqueLoader', ['class_exists'])
->set('annotations.cached_reader', PsrCachedReader::class)
->args([
service('annotations.reader'),
inline_service(ArrayAdapter::class),
abstract_arg('Debug-Flag'),
])
->set('annotations.filesystem_cache_adapter', FilesystemAdapter::class)
->args([
'',
0,
abstract_arg('Cache-Directory'),
])
->set('annotations.filesystem_cache', DoctrineProvider::class)
->factory([DoctrineProvider::class, 'wrap'])
->args([
service('annotations.filesystem_cache_adapter'),
])
->deprecate('symfony/framework-bundle', '5.4', '"%service_id% is deprecated"')
->set('annotations.cache_warmer', AnnotationsCacheWarmer::class)
->args([
service('annotations.reader'),
param('kernel.cache_dir').'/annotations.php',
'#^Symfony\\\\(?:Component\\\\HttpKernel\\\\|Bundle\\\\FrameworkBundle\\\\Controller\\\\(?!.*Controller$))#',
param('kernel.debug'),
])
->set('annotations.cache_adapter', PhpArrayAdapter::class)
->factory([PhpArrayAdapter::class, 'create'])
->args([
param('kernel.cache_dir').'/annotations.php',
service('cache.annotations'),
])
->tag('container.hot_path')
->set('annotations.cache', DoctrineProvider::class)
->factory([DoctrineProvider::class, 'wrap'])
->args([
service('annotations.cache_adapter'),
])
->deprecate('symfony/framework-bundle', '5.4', '"%service_id% is deprecated"')
->alias('annotation_reader', 'annotations.reader')
->alias(Reader::class, 'annotation_reader');
};

View File

@ -0,0 +1,94 @@
<?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\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\Asset\Context\RequestStackContext;
use Symfony\Component\Asset\Package;
use Symfony\Component\Asset\Packages;
use Symfony\Component\Asset\PathPackage;
use Symfony\Component\Asset\UrlPackage;
use Symfony\Component\Asset\VersionStrategy\EmptyVersionStrategy;
use Symfony\Component\Asset\VersionStrategy\JsonManifestVersionStrategy;
use Symfony\Component\Asset\VersionStrategy\RemoteJsonManifestVersionStrategy;
use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy;
return static function (ContainerConfigurator $container) {
$container->parameters()
->set('asset.request_context.base_path', null)
->set('asset.request_context.secure', null)
;
$container->services()
->set('assets.packages', Packages::class)
->args([
service('assets._default_package'),
tagged_iterator('assets.package', 'package'),
])
->alias(Packages::class, 'assets.packages')
->set('assets.empty_package', Package::class)
->args([
service('assets.empty_version_strategy'),
])
->alias('assets._default_package', 'assets.empty_package')
->set('assets.context', RequestStackContext::class)
->args([
service('request_stack'),
param('asset.request_context.base_path'),
param('asset.request_context.secure'),
])
->set('assets.path_package', PathPackage::class)
->abstract()
->args([
abstract_arg('base path'),
abstract_arg('version strategy'),
service('assets.context'),
])
->set('assets.url_package', UrlPackage::class)
->abstract()
->args([
abstract_arg('base URLs'),
abstract_arg('version strategy'),
service('assets.context'),
])
->set('assets.static_version_strategy', StaticVersionStrategy::class)
->abstract()
->args([
abstract_arg('version'),
abstract_arg('format'),
])
->set('assets.empty_version_strategy', EmptyVersionStrategy::class)
->set('assets.json_manifest_version_strategy', JsonManifestVersionStrategy::class)
->abstract()
->args([
abstract_arg('manifest path'),
service('http_client')->nullOnInvalid(),
false,
])
->set('assets.remote_json_manifest_version_strategy', RemoteJsonManifestVersionStrategy::class)
->abstract()
->deprecate('symfony/framework-bundle', '5.3', 'The "%service_id%" service is deprecated, use "assets.json_manifest_version_strategy" instead.')
->args([
abstract_arg('manifest url'),
service('http_client'),
])
;
};

View File

@ -0,0 +1,267 @@
<?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\Component\DependencyInjection\Loader\Configurator;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\Adapter\AbstractAdapter;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Adapter\ApcuAdapter;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\DoctrineAdapter;
use Symfony\Component\Cache\Adapter\DoctrineDbalAdapter;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Adapter\MemcachedAdapter;
use Symfony\Component\Cache\Adapter\PdoAdapter;
use Symfony\Component\Cache\Adapter\ProxyAdapter;
use Symfony\Component\Cache\Adapter\RedisAdapter;
use Symfony\Component\Cache\Adapter\RedisTagAwareAdapter;
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
use Symfony\Component\Cache\Messenger\EarlyExpirationHandler;
use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\TagAwareCacheInterface;
return static function (ContainerConfigurator $container) {
$container->services()
->set('cache.app')
->parent('cache.adapter.filesystem')
->public()
->tag('cache.pool', ['clearer' => 'cache.app_clearer'])
->set('cache.app.taggable', TagAwareAdapter::class)
->args([service('cache.app')])
->set('cache.system')
->parent('cache.adapter.system')
->public()
->tag('cache.pool')
->set('cache.validator')
->parent('cache.system')
->private()
->tag('cache.pool')
->set('cache.serializer')
->parent('cache.system')
->private()
->tag('cache.pool')
->set('cache.annotations')
->parent('cache.system')
->private()
->tag('cache.pool')
->set('cache.property_info')
->parent('cache.system')
->private()
->tag('cache.pool')
->set('cache.messenger.restart_workers_signal')
->parent('cache.app')
->private()
->tag('cache.pool')
->set('cache.adapter.system', AdapterInterface::class)
->abstract()
->factory([AbstractAdapter::class, 'createSystemCache'])
->args([
'', // namespace
0, // default lifetime
abstract_arg('version'),
sprintf('%s/pools/system', param('kernel.cache_dir')),
service('logger')->ignoreOnInvalid(),
])
->tag('cache.pool', ['clearer' => 'cache.system_clearer', 'reset' => 'reset'])
->tag('monolog.logger', ['channel' => 'cache'])
->set('cache.adapter.apcu', ApcuAdapter::class)
->abstract()
->args([
'', // namespace
0, // default lifetime
abstract_arg('version'),
])
->call('setLogger', [service('logger')->ignoreOnInvalid()])
->tag('cache.pool', ['clearer' => 'cache.default_clearer', 'reset' => 'reset'])
->tag('monolog.logger', ['channel' => 'cache'])
->set('cache.adapter.doctrine', DoctrineAdapter::class)
->abstract()
->args([
abstract_arg('Doctrine provider service'),
'', // namespace
0, // default lifetime
])
->call('setLogger', [service('logger')->ignoreOnInvalid()])
->tag('cache.pool', [
'provider' => 'cache.default_doctrine_provider',
'clearer' => 'cache.default_clearer',
'reset' => 'reset',
])
->tag('monolog.logger', ['channel' => 'cache'])
->deprecate('symfony/framework-bundle', '5.4', 'The "%service_id%" service inherits from "cache.adapter.doctrine" which is deprecated.')
->set('cache.adapter.filesystem', FilesystemAdapter::class)
->abstract()
->args([
'', // namespace
0, // default lifetime
sprintf('%s/pools/app', param('kernel.cache_dir')),
service('cache.default_marshaller')->ignoreOnInvalid(),
])
->call('setLogger', [service('logger')->ignoreOnInvalid()])
->tag('cache.pool', ['clearer' => 'cache.default_clearer', 'reset' => 'reset'])
->tag('monolog.logger', ['channel' => 'cache'])
->set('cache.adapter.psr6', ProxyAdapter::class)
->abstract()
->args([
abstract_arg('PSR-6 provider service'),
'', // namespace
0, // default lifetime
])
->tag('cache.pool', [
'provider' => 'cache.default_psr6_provider',
'clearer' => 'cache.default_clearer',
'reset' => 'reset',
])
->set('cache.adapter.redis', RedisAdapter::class)
->abstract()
->args([
abstract_arg('Redis connection service'),
'', // namespace
0, // default lifetime
service('cache.default_marshaller')->ignoreOnInvalid(),
])
->call('setLogger', [service('logger')->ignoreOnInvalid()])
->tag('cache.pool', [
'provider' => 'cache.default_redis_provider',
'clearer' => 'cache.default_clearer',
'reset' => 'reset',
])
->tag('monolog.logger', ['channel' => 'cache'])
->set('cache.adapter.redis_tag_aware', RedisTagAwareAdapter::class)
->abstract()
->args([
abstract_arg('Redis connection service'),
'', // namespace
0, // default lifetime
service('cache.default_marshaller')->ignoreOnInvalid(),
])
->call('setLogger', [service('logger')->ignoreOnInvalid()])
->tag('cache.pool', [
'provider' => 'cache.default_redis_provider',
'clearer' => 'cache.default_clearer',
'reset' => 'reset',
])
->tag('monolog.logger', ['channel' => 'cache'])
->set('cache.adapter.memcached', MemcachedAdapter::class)
->abstract()
->args([
abstract_arg('Memcached connection service'),
'', // namespace
0, // default lifetime
service('cache.default_marshaller')->ignoreOnInvalid(),
])
->call('setLogger', [service('logger')->ignoreOnInvalid()])
->tag('cache.pool', [
'provider' => 'cache.default_memcached_provider',
'clearer' => 'cache.default_clearer',
'reset' => 'reset',
])
->tag('monolog.logger', ['channel' => 'cache'])
->set('cache.adapter.doctrine_dbal', DoctrineDbalAdapter::class)
->abstract()
->args([
abstract_arg('DBAL connection service'),
'', // namespace
0, // default lifetime
[], // table options
service('cache.default_marshaller')->ignoreOnInvalid(),
])
->call('setLogger', [service('logger')->ignoreOnInvalid()])
->tag('cache.pool', [
'provider' => 'cache.default_doctrine_dbal_provider',
'clearer' => 'cache.default_clearer',
'reset' => 'reset',
])
->tag('monolog.logger', ['channel' => 'cache'])
->set('cache.adapter.pdo', PdoAdapter::class)
->abstract()
->args([
abstract_arg('PDO connection service'),
'', // namespace
0, // default lifetime
[], // table options
service('cache.default_marshaller')->ignoreOnInvalid(),
])
->call('setLogger', [service('logger')->ignoreOnInvalid()])
->tag('cache.pool', [
'provider' => 'cache.default_pdo_provider',
'clearer' => 'cache.default_clearer',
'reset' => 'reset',
])
->tag('monolog.logger', ['channel' => 'cache'])
->set('cache.adapter.array', ArrayAdapter::class)
->abstract()
->args([
0, // default lifetime
])
->call('setLogger', [service('logger')->ignoreOnInvalid()])
->tag('cache.pool', ['clearer' => 'cache.default_clearer', 'reset' => 'reset'])
->tag('monolog.logger', ['channel' => 'cache'])
->set('cache.default_marshaller', DefaultMarshaller::class)
->args([
null, // use igbinary_serialize() when available
'%kernel.debug%',
])
->set('cache.early_expiration_handler', EarlyExpirationHandler::class)
->args([
service('reverse_container'),
])
->tag('messenger.message_handler')
->set('cache.default_clearer', Psr6CacheClearer::class)
->args([
[],
])
->set('cache.system_clearer')
->parent('cache.default_clearer')
->public()
->set('cache.global_clearer')
->parent('cache.default_clearer')
->public()
->alias('cache.app_clearer', 'cache.default_clearer')
->public()
->alias(CacheItemPoolInterface::class, 'cache.app')
->alias(AdapterInterface::class, 'cache.app')
->deprecate('symfony/framework-bundle', '5.4', sprintf('The "%%alias_id%%" alias is deprecated, use "%s" instead.', CacheItemPoolInterface::class))
->alias(CacheInterface::class, 'cache.app')
->alias(TagAwareCacheInterface::class, 'cache.app.taggable')
;
};

View File

@ -0,0 +1,39 @@
<?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\Component\DependencyInjection\Loader\Configurator;
use Symfony\Bundle\FrameworkBundle\CacheWarmer\CachePoolClearerCacheWarmer;
use Symfony\Component\Cache\DataCollector\CacheDataCollector;
return static function (ContainerConfigurator $container) {
$container->services()
// DataCollector (public to prevent inlining, made private in CacheCollectorPass)
->set('data_collector.cache', CacheDataCollector::class)
->public()
->tag('data_collector', [
'template' => '@WebProfiler/Collector/cache.html.twig',
'id' => 'cache',
'priority' => 275,
])
// CacheWarmer used in dev to clear cache pool
->set('cache_pool_clearer.cache_warmer', CachePoolClearerCacheWarmer::class)
->args([
service('cache.system_clearer'),
[
'cache.validator',
'cache.serializer',
],
])
->tag('kernel.cache_warmer', ['priority' => 64])
;
};

View File

@ -0,0 +1,78 @@
<?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\Component\DependencyInjection\Loader\Configurator;
use Symfony\Bundle\FrameworkBundle\DataCollector\RouterDataCollector;
use Symfony\Component\HttpKernel\DataCollector\AjaxDataCollector;
use Symfony\Component\HttpKernel\DataCollector\ConfigDataCollector;
use Symfony\Component\HttpKernel\DataCollector\EventDataCollector;
use Symfony\Component\HttpKernel\DataCollector\ExceptionDataCollector;
use Symfony\Component\HttpKernel\DataCollector\LoggerDataCollector;
use Symfony\Component\HttpKernel\DataCollector\MemoryDataCollector;
use Symfony\Component\HttpKernel\DataCollector\RequestDataCollector;
use Symfony\Component\HttpKernel\DataCollector\TimeDataCollector;
use Symfony\Component\HttpKernel\KernelEvents;
return static function (ContainerConfigurator $container) {
$container->services()
->set('data_collector.config', ConfigDataCollector::class)
->call('setKernel', [service('kernel')->ignoreOnInvalid()])
->tag('data_collector', ['template' => '@WebProfiler/Collector/config.html.twig', 'id' => 'config', 'priority' => -255])
->set('data_collector.request', RequestDataCollector::class)
->args([
service('request_stack')->ignoreOnInvalid(),
])
->tag('kernel.event_subscriber')
->tag('data_collector', ['template' => '@WebProfiler/Collector/request.html.twig', 'id' => 'request', 'priority' => 335])
->set('data_collector.request.session_collector', \Closure::class)
->factory([\Closure::class, 'fromCallable'])
->args([[service('data_collector.request'), 'collectSessionUsage']])
->set('data_collector.ajax', AjaxDataCollector::class)
->tag('data_collector', ['template' => '@WebProfiler/Collector/ajax.html.twig', 'id' => 'ajax', 'priority' => 315])
->set('data_collector.exception', ExceptionDataCollector::class)
->tag('data_collector', ['template' => '@WebProfiler/Collector/exception.html.twig', 'id' => 'exception', 'priority' => 305])
->set('data_collector.events', EventDataCollector::class)
->args([
service('debug.event_dispatcher')->ignoreOnInvalid(),
service('request_stack')->ignoreOnInvalid(),
])
->tag('data_collector', ['template' => '@WebProfiler/Collector/events.html.twig', 'id' => 'events', 'priority' => 290])
->set('data_collector.logger', LoggerDataCollector::class)
->args([
service('logger')->ignoreOnInvalid(),
sprintf('%s/%s', param('kernel.build_dir'), param('kernel.container_class')),
service('request_stack')->ignoreOnInvalid(),
])
->tag('monolog.logger', ['channel' => 'profiler'])
->tag('data_collector', ['template' => '@WebProfiler/Collector/logger.html.twig', 'id' => 'logger', 'priority' => 300])
->set('data_collector.time', TimeDataCollector::class)
->args([
service('kernel')->ignoreOnInvalid(),
service('debug.stopwatch')->ignoreOnInvalid(),
])
->tag('data_collector', ['template' => '@WebProfiler/Collector/time.html.twig', 'id' => 'time', 'priority' => 330])
->set('data_collector.memory', MemoryDataCollector::class)
->tag('data_collector', ['template' => '@WebProfiler/Collector/memory.html.twig', 'id' => 'memory', 'priority' => 325])
->set('data_collector.router', RouterDataCollector::class)
->tag('kernel.event_listener', ['event' => KernelEvents::CONTROLLER, 'method' => 'onKernelController'])
->tag('data_collector', ['template' => '@WebProfiler/Collector/router.html.twig', 'id' => 'router', 'priority' => 285])
;
};

View File

@ -0,0 +1,331 @@
<?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\Component\DependencyInjection\Loader\Configurator;
use Symfony\Bundle\FrameworkBundle\Command\AboutCommand;
use Symfony\Bundle\FrameworkBundle\Command\AssetsInstallCommand;
use Symfony\Bundle\FrameworkBundle\Command\CacheClearCommand;
use Symfony\Bundle\FrameworkBundle\Command\CachePoolClearCommand;
use Symfony\Bundle\FrameworkBundle\Command\CachePoolDeleteCommand;
use Symfony\Bundle\FrameworkBundle\Command\CachePoolListCommand;
use Symfony\Bundle\FrameworkBundle\Command\CachePoolPruneCommand;
use Symfony\Bundle\FrameworkBundle\Command\CacheWarmupCommand;
use Symfony\Bundle\FrameworkBundle\Command\ConfigDebugCommand;
use Symfony\Bundle\FrameworkBundle\Command\ConfigDumpReferenceCommand;
use Symfony\Bundle\FrameworkBundle\Command\ContainerDebugCommand;
use Symfony\Bundle\FrameworkBundle\Command\ContainerLintCommand;
use Symfony\Bundle\FrameworkBundle\Command\DebugAutowiringCommand;
use Symfony\Bundle\FrameworkBundle\Command\EventDispatcherDebugCommand;
use Symfony\Bundle\FrameworkBundle\Command\RouterDebugCommand;
use Symfony\Bundle\FrameworkBundle\Command\RouterMatchCommand;
use Symfony\Bundle\FrameworkBundle\Command\SecretsDecryptToLocalCommand;
use Symfony\Bundle\FrameworkBundle\Command\SecretsEncryptFromLocalCommand;
use Symfony\Bundle\FrameworkBundle\Command\SecretsGenerateKeysCommand;
use Symfony\Bundle\FrameworkBundle\Command\SecretsListCommand;
use Symfony\Bundle\FrameworkBundle\Command\SecretsRemoveCommand;
use Symfony\Bundle\FrameworkBundle\Command\SecretsSetCommand;
use Symfony\Bundle\FrameworkBundle\Command\TranslationDebugCommand;
use Symfony\Bundle\FrameworkBundle\Command\TranslationUpdateCommand;
use Symfony\Bundle\FrameworkBundle\Command\WorkflowDumpCommand;
use Symfony\Bundle\FrameworkBundle\Command\YamlLintCommand;
use Symfony\Bundle\FrameworkBundle\EventListener\SuggestMissingPackageSubscriber;
use Symfony\Component\Console\EventListener\ErrorListener;
use Symfony\Component\Dotenv\Command\DebugCommand as DotenvDebugCommand;
use Symfony\Component\Messenger\Command\ConsumeMessagesCommand;
use Symfony\Component\Messenger\Command\DebugCommand;
use Symfony\Component\Messenger\Command\FailedMessagesRemoveCommand;
use Symfony\Component\Messenger\Command\FailedMessagesRetryCommand;
use Symfony\Component\Messenger\Command\FailedMessagesShowCommand;
use Symfony\Component\Messenger\Command\SetupTransportsCommand;
use Symfony\Component\Messenger\Command\StopWorkersCommand;
use Symfony\Component\Translation\Command\TranslationPullCommand;
use Symfony\Component\Translation\Command\TranslationPushCommand;
use Symfony\Component\Translation\Command\XliffLintCommand;
use Symfony\Component\Validator\Command\DebugCommand as ValidatorDebugCommand;
return static function (ContainerConfigurator $container) {
$container->services()
->set('console.error_listener', ErrorListener::class)
->args([
service('logger')->nullOnInvalid(),
])
->tag('kernel.event_subscriber')
->tag('monolog.logger', ['channel' => 'console'])
->set('console.suggest_missing_package_subscriber', SuggestMissingPackageSubscriber::class)
->tag('kernel.event_subscriber')
->set('console.command.about', AboutCommand::class)
->tag('console.command')
->set('console.command.assets_install', AssetsInstallCommand::class)
->args([
service('filesystem'),
param('kernel.project_dir'),
])
->tag('console.command')
->set('console.command.cache_clear', CacheClearCommand::class)
->args([
service('cache_clearer'),
service('filesystem'),
])
->tag('console.command')
->set('console.command.cache_pool_clear', CachePoolClearCommand::class)
->args([
service('cache.global_clearer'),
])
->tag('console.command')
->set('console.command.cache_pool_prune', CachePoolPruneCommand::class)
->args([
[],
])
->tag('console.command')
->set('console.command.cache_pool_delete', CachePoolDeleteCommand::class)
->args([
service('cache.global_clearer'),
])
->tag('console.command')
->set('console.command.cache_pool_list', CachePoolListCommand::class)
->args([
null,
])
->tag('console.command')
->set('console.command.cache_warmup', CacheWarmupCommand::class)
->args([
service('cache_warmer'),
])
->tag('console.command')
->set('console.command.config_debug', ConfigDebugCommand::class)
->tag('console.command')
->set('console.command.config_dump_reference', ConfigDumpReferenceCommand::class)
->tag('console.command')
->set('console.command.container_debug', ContainerDebugCommand::class)
->tag('console.command')
->set('console.command.container_lint', ContainerLintCommand::class)
->tag('console.command')
->set('console.command.debug_autowiring', DebugAutowiringCommand::class)
->args([
null,
service('debug.file_link_formatter')->nullOnInvalid(),
])
->tag('console.command')
->set('console.command.dotenv_debug', DotenvDebugCommand::class)
->args([
param('kernel.environment'),
param('kernel.project_dir'),
])
->tag('console.command')
->set('console.command.event_dispatcher_debug', EventDispatcherDebugCommand::class)
->args([
tagged_locator('event_dispatcher.dispatcher', 'name'),
])
->tag('console.command')
->set('console.command.messenger_consume_messages', ConsumeMessagesCommand::class)
->args([
abstract_arg('Routable message bus'),
service('messenger.receiver_locator'),
service('event_dispatcher'),
service('logger')->nullOnInvalid(),
[], // Receiver names
service('messenger.listener.reset_services')->nullOnInvalid(),
[], // Bus names
])
->tag('console.command')
->tag('monolog.logger', ['channel' => 'messenger'])
->set('console.command.messenger_setup_transports', SetupTransportsCommand::class)
->args([
service('messenger.receiver_locator'),
[], // Receiver names
])
->tag('console.command')
->set('console.command.messenger_debug', DebugCommand::class)
->args([
[], // Message to handlers mapping
])
->tag('console.command')
->set('console.command.messenger_stop_workers', StopWorkersCommand::class)
->args([
service('cache.messenger.restart_workers_signal'),
])
->tag('console.command')
->set('console.command.messenger_failed_messages_retry', FailedMessagesRetryCommand::class)
->args([
abstract_arg('Default failure receiver name'),
abstract_arg('Receivers'),
service('messenger.routable_message_bus'),
service('event_dispatcher'),
service('logger'),
])
->tag('console.command')
->set('console.command.messenger_failed_messages_show', FailedMessagesShowCommand::class)
->args([
abstract_arg('Default failure receiver name'),
abstract_arg('Receivers'),
])
->tag('console.command')
->set('console.command.messenger_failed_messages_remove', FailedMessagesRemoveCommand::class)
->args([
abstract_arg('Default failure receiver name'),
abstract_arg('Receivers'),
])
->tag('console.command')
->set('console.command.router_debug', RouterDebugCommand::class)
->args([
service('router'),
service('debug.file_link_formatter')->nullOnInvalid(),
])
->tag('console.command')
->set('console.command.router_match', RouterMatchCommand::class)
->args([
service('router'),
tagged_iterator('routing.expression_language_provider'),
])
->tag('console.command')
->set('console.command.translation_debug', TranslationDebugCommand::class)
->args([
service('translator'),
service('translation.reader'),
service('translation.extractor'),
param('translator.default_path'),
null, // twig.default_path
[], // Translator paths
[], // Twig paths
param('kernel.enabled_locales'),
])
->tag('console.command')
->set('console.command.translation_extract', TranslationUpdateCommand::class)
->args([
service('translation.writer'),
service('translation.reader'),
service('translation.extractor'),
param('kernel.default_locale'),
param('translator.default_path'),
null, // twig.default_path
[], // Translator paths
[], // Twig paths
param('kernel.enabled_locales'),
])
->tag('console.command')
->set('console.command.validator_debug', ValidatorDebugCommand::class)
->args([
service('validator'),
])
->tag('console.command')
->set('console.command.translation_pull', TranslationPullCommand::class)
->args([
service('translation.provider_collection'),
service('translation.writer'),
service('translation.reader'),
param('kernel.default_locale'),
[], // Translator paths
[], // Enabled locales
])
->tag('console.command', ['command' => 'translation:pull'])
->set('console.command.translation_push', TranslationPushCommand::class)
->args([
service('translation.provider_collection'),
service('translation.reader'),
[], // Translator paths
[], // Enabled locales
])
->tag('console.command', ['command' => 'translation:push'])
->set('console.command.workflow_dump', WorkflowDumpCommand::class)
->tag('console.command')
->set('console.command.xliff_lint', XliffLintCommand::class)
->tag('console.command')
->set('console.command.yaml_lint', YamlLintCommand::class)
->tag('console.command')
->set('console.command.form_debug', \Symfony\Component\Form\Command\DebugCommand::class)
->args([
service('form.registry'),
[], // All form types namespaces are stored here by FormPass
[], // All services form types are stored here by FormPass
[], // All type extensions are stored here by FormPass
[], // All type guessers are stored here by FormPass
service('debug.file_link_formatter')->nullOnInvalid(),
])
->tag('console.command')
->set('console.command.secrets_set', SecretsSetCommand::class)
->args([
service('secrets.vault'),
service('secrets.local_vault')->nullOnInvalid(),
])
->tag('console.command')
->set('console.command.secrets_remove', SecretsRemoveCommand::class)
->args([
service('secrets.vault'),
service('secrets.local_vault')->nullOnInvalid(),
])
->tag('console.command')
->set('console.command.secrets_generate_key', SecretsGenerateKeysCommand::class)
->args([
service('secrets.vault'),
service('secrets.local_vault')->ignoreOnInvalid(),
])
->tag('console.command')
->set('console.command.secrets_list', SecretsListCommand::class)
->args([
service('secrets.vault'),
service('secrets.local_vault')->ignoreOnInvalid(),
])
->tag('console.command')
->set('console.command.secrets_decrypt_to_local', SecretsDecryptToLocalCommand::class)
->args([
service('secrets.vault'),
service('secrets.local_vault')->ignoreOnInvalid(),
])
->tag('console.command')
->set('console.command.secrets_encrypt_from_local', SecretsEncryptFromLocalCommand::class)
->args([
service('secrets.vault'),
service('secrets.local_vault')->ignoreOnInvalid(),
])
->tag('console.command')
;
};

View File

@ -0,0 +1,50 @@
<?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\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\NotTaggedControllerValueResolver;
use Symfony\Component\HttpKernel\Controller\TraceableArgumentResolver;
use Symfony\Component\HttpKernel\Controller\TraceableControllerResolver;
use Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher;
return static function (ContainerConfigurator $container) {
$container->services()
->set('debug.event_dispatcher', TraceableEventDispatcher::class)
->decorate('event_dispatcher')
->args([
service('debug.event_dispatcher.inner'),
service('debug.stopwatch'),
service('logger')->nullOnInvalid(),
service('request_stack')->nullOnInvalid(),
])
->tag('monolog.logger', ['channel' => 'event'])
->tag('kernel.reset', ['method' => 'reset'])
->set('debug.controller_resolver', TraceableControllerResolver::class)
->decorate('controller_resolver')
->args([
service('debug.controller_resolver.inner'),
service('debug.stopwatch'),
])
->set('debug.argument_resolver', TraceableArgumentResolver::class)
->decorate('argument_resolver')
->args([
service('debug.argument_resolver.inner'),
service('debug.stopwatch'),
])
->set('argument_resolver.not_tagged_controller', NotTaggedControllerValueResolver::class)
->args([abstract_arg('Controller argument, set in FrameworkExtension')])
->tag('controller.argument_value_resolver', ['priority' => -200])
;
};

View File

@ -0,0 +1,39 @@
<?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\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
use Symfony\Component\HttpKernel\EventListener\DebugHandlersListener;
return static function (ContainerConfigurator $container) {
$container->parameters()->set('debug.error_handler.throw_at', -1);
$container->services()
->set('debug.debug_handlers_listener', DebugHandlersListener::class)
->args([
null, // Exception handler
service('monolog.logger.php')->nullOnInvalid(),
null, // Log levels map for enabled error levels
param('debug.error_handler.throw_at'),
param('kernel.debug'),
param('kernel.debug'),
service('monolog.logger.deprecation')->nullOnInvalid(),
])
->tag('kernel.event_subscriber')
->tag('monolog.logger', ['channel' => 'php'])
->set('debug.file_link_formatter', FileLinkFormatter::class)
->args([param('debug.file_link_format')])
->alias(FileLinkFormatter::class, 'debug.file_link_formatter')
;
};

View File

@ -0,0 +1,38 @@
<?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\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
return static function (ContainerConfigurator $container) {
$container->services()
->set('error_handler.error_renderer.html', HtmlErrorRenderer::class)
->args([
inline_service()
->factory([HtmlErrorRenderer::class, 'isDebug'])
->args([
service('request_stack'),
param('kernel.debug'),
]),
param('kernel.charset'),
service('debug.file_link_formatter')->nullOnInvalid(),
param('kernel.project_dir'),
inline_service()
->factory([HtmlErrorRenderer::class, 'getAndCleanOutputBuffer'])
->args([service('request_stack')]),
service('logger')->nullOnInvalid(),
])
->alias('error_renderer.html', 'error_handler.error_renderer.html')
->alias('error_renderer', 'error_renderer.html')
;
};

View File

@ -0,0 +1,25 @@
<?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\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\HttpKernel\EventListener\SurrogateListener;
use Symfony\Component\HttpKernel\HttpCache\Esi;
return static function (ContainerConfigurator $container) {
$container->services()
->set('esi', Esi::class)
->set('esi_listener', SurrogateListener::class)
->args([service('esi')->ignoreOnInvalid()])
->tag('kernel.event_subscriber')
;
};

View File

@ -0,0 +1,152 @@
<?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\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator;
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
use Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\ColorType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TransformationFailureExtension;
use Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension;
use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationRequestHandler;
use Symfony\Component\Form\Extension\HttpFoundation\Type\FormTypeHttpFoundationExtension;
use Symfony\Component\Form\Extension\Validator\Type\FormTypeValidatorExtension;
use Symfony\Component\Form\Extension\Validator\Type\RepeatedTypeValidatorExtension;
use Symfony\Component\Form\Extension\Validator\Type\SubmitTypeValidatorExtension;
use Symfony\Component\Form\Extension\Validator\Type\UploadValidatorExtension;
use Symfony\Component\Form\Extension\Validator\ValidatorTypeGuesser;
use Symfony\Component\Form\FormFactory;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormRegistry;
use Symfony\Component\Form\FormRegistryInterface;
use Symfony\Component\Form\ResolvedFormTypeFactory;
use Symfony\Component\Form\ResolvedFormTypeFactoryInterface;
use Symfony\Component\Form\Util\ServerParams;
return static function (ContainerConfigurator $container) {
$container->services()
->set('form.resolved_type_factory', ResolvedFormTypeFactory::class)
->alias(ResolvedFormTypeFactoryInterface::class, 'form.resolved_type_factory')
->set('form.registry', FormRegistry::class)
->args([
[
/*
* We don't need to be able to add more extensions.
* more types can be registered with the form.type tag
* more type extensions can be registered with the form.type_extension tag
* more type_guessers can be registered with the form.type_guesser tag
*/
service('form.extension'),
],
service('form.resolved_type_factory'),
])
->alias(FormRegistryInterface::class, 'form.registry')
->set('form.factory', FormFactory::class)
->public()
->args([service('form.registry')])
->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.2'])
->alias(FormFactoryInterface::class, 'form.factory')
->set('form.extension', DependencyInjectionExtension::class)
->args([
abstract_arg('All services with tag "form.type" are stored in a service locator by FormPass'),
abstract_arg('All services with tag "form.type_extension" are stored here by FormPass'),
abstract_arg('All services with tag "form.type_guesser" are stored here by FormPass'),
])
->set('form.type_guesser.validator', ValidatorTypeGuesser::class)
->args([service('validator.mapping.class_metadata_factory')])
->tag('form.type_guesser')
->alias('form.property_accessor', 'property_accessor')
->set('form.choice_list_factory.default', DefaultChoiceListFactory::class)
->set('form.choice_list_factory.property_access', PropertyAccessDecorator::class)
->args([
service('form.choice_list_factory.default'),
service('form.property_accessor'),
])
->set('form.choice_list_factory.cached', CachingFactoryDecorator::class)
->args([service('form.choice_list_factory.property_access')])
->tag('kernel.reset', ['method' => 'reset'])
->alias('form.choice_list_factory', 'form.choice_list_factory.cached')
->set('form.type.form', FormType::class)
->args([service('form.property_accessor')])
->tag('form.type')
->set('form.type.choice', ChoiceType::class)
->args([
service('form.choice_list_factory'),
service('translator')->ignoreOnInvalid(),
])
->tag('form.type')
->set('form.type.file', FileType::class)
->public()
->args([service('translator')->ignoreOnInvalid()])
->tag('form.type')
->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.2'])
->set('form.type.color', ColorType::class)
->args([service('translator')->ignoreOnInvalid()])
->tag('form.type')
->set('form.type_extension.form.transformation_failure_handling', TransformationFailureExtension::class)
->args([service('translator')->ignoreOnInvalid()])
->tag('form.type_extension', ['extended-type' => FormType::class])
->set('form.type_extension.form.http_foundation', FormTypeHttpFoundationExtension::class)
->args([service('form.type_extension.form.request_handler')])
->tag('form.type_extension')
->set('form.type_extension.form.request_handler', HttpFoundationRequestHandler::class)
->args([service('form.server_params')])
->set('form.server_params', ServerParams::class)
->args([service('request_stack')])
->set('form.type_extension.form.validator', FormTypeValidatorExtension::class)
->args([
service('validator'),
true,
service('twig.form.renderer')->ignoreOnInvalid(),
service('translator')->ignoreOnInvalid(),
])
->tag('form.type_extension', ['extended-type' => FormType::class])
->set('form.type_extension.repeated.validator', RepeatedTypeValidatorExtension::class)
->tag('form.type_extension')
->set('form.type_extension.submit.validator', SubmitTypeValidatorExtension::class)
->tag('form.type_extension', ['extended-type' => SubmitType::class])
->set('form.type_extension.upload.validator', UploadValidatorExtension::class)
->args([
service('translator'),
param('validator.translation_domain'),
])
->tag('form.type_extension')
;
};

View File

@ -0,0 +1,29 @@
<?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\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\Form\Extension\Csrf\Type\FormTypeCsrfExtension;
return static function (ContainerConfigurator $container) {
$container->services()
->set('form.type_extension.csrf', FormTypeCsrfExtension::class)
->args([
service('security.csrf.token_manager'),
param('form.type_extension.csrf.enabled'),
param('form.type_extension.csrf.field_name'),
service('translator')->nullOnInvalid(),
param('validator.translation_domain'),
service('form.server_params'),
])
->tag('form.type_extension')
;
};

View File

@ -0,0 +1,38 @@
<?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\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\Form\Extension\DataCollector\FormDataCollector;
use Symfony\Component\Form\Extension\DataCollector\FormDataExtractor;
use Symfony\Component\Form\Extension\DataCollector\Proxy\ResolvedTypeFactoryDataCollectorProxy;
use Symfony\Component\Form\Extension\DataCollector\Type\DataCollectorTypeExtension;
use Symfony\Component\Form\ResolvedFormTypeFactory;
return static function (ContainerConfigurator $container) {
$container->services()
->set('form.resolved_type_factory', ResolvedTypeFactoryDataCollectorProxy::class)
->args([
inline_service(ResolvedFormTypeFactory::class),
service('data_collector.form'),
])
->set('form.type_extension.form.data_collector', DataCollectorTypeExtension::class)
->args([service('data_collector.form')])
->tag('form.type_extension')
->set('data_collector.form.extractor', FormDataExtractor::class)
->set('data_collector.form', FormDataCollector::class)
->args([service('data_collector.form.extractor')])
->tag('data_collector', ['template' => '@WebProfiler/Collector/form.html.twig', 'id' => 'form', 'priority' => 310])
;
};

View File

@ -0,0 +1,22 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\HttpKernel\EventListener\FragmentListener;
return static function (ContainerConfigurator $container) {
$container->services()
->set('fragment.listener', FragmentListener::class)
->args([service('uri_signer'), param('fragment.path')])
->tag('kernel.event_subscriber')
;
};

View File

@ -0,0 +1,71 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\HttpKernel\DependencyInjection\LazyLoadingFragmentHandler;
use Symfony\Component\HttpKernel\Fragment\EsiFragmentRenderer;
use Symfony\Component\HttpKernel\Fragment\FragmentUriGenerator;
use Symfony\Component\HttpKernel\Fragment\FragmentUriGeneratorInterface;
use Symfony\Component\HttpKernel\Fragment\HIncludeFragmentRenderer;
use Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer;
use Symfony\Component\HttpKernel\Fragment\SsiFragmentRenderer;
return static function (ContainerConfigurator $container) {
$container->parameters()
->set('fragment.renderer.hinclude.global_template', null)
->set('fragment.path', '/_fragment')
;
$container->services()
->set('fragment.handler', LazyLoadingFragmentHandler::class)
->args([
abstract_arg('fragment renderer locator'),
service('request_stack'),
param('kernel.debug'),
])
->set('fragment.uri_generator', FragmentUriGenerator::class)
->args([param('fragment.path'), service('uri_signer'), service('request_stack')])
->alias(FragmentUriGeneratorInterface::class, 'fragment.uri_generator')
->set('fragment.renderer.inline', InlineFragmentRenderer::class)
->args([service('http_kernel'), service('event_dispatcher')])
->call('setFragmentPath', [param('fragment.path')])
->tag('kernel.fragment_renderer', ['alias' => 'inline'])
->set('fragment.renderer.hinclude', HIncludeFragmentRenderer::class)
->args([
service('twig')->nullOnInvalid(),
service('uri_signer'),
param('fragment.renderer.hinclude.global_template'),
])
->call('setFragmentPath', [param('fragment.path')])
->set('fragment.renderer.esi', EsiFragmentRenderer::class)
->args([
service('esi')->nullOnInvalid(),
service('fragment.renderer.inline'),
service('uri_signer'),
])
->call('setFragmentPath', [param('fragment.path')])
->tag('kernel.fragment_renderer', ['alias' => 'esi'])
->set('fragment.renderer.ssi', SsiFragmentRenderer::class)
->args([
service('ssi')->nullOnInvalid(),
service('fragment.renderer.inline'),
service('uri_signer'),
])
->call('setFragmentPath', [param('fragment.path')])
->tag('kernel.fragment_renderer', ['alias' => 'ssi'])
;
};

View File

@ -0,0 +1,64 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpClient\HttplugClient;
use Symfony\Component\HttpClient\Psr18Client;
use Symfony\Component\HttpClient\Retry\GenericRetryStrategy;
use Symfony\Contracts\HttpClient\HttpClientInterface;
return static function (ContainerConfigurator $container) {
$container->services()
->set('http_client', HttpClientInterface::class)
->factory([HttpClient::class, 'create'])
->args([
[], // default options
abstract_arg('max host connections'),
])
->call('setLogger', [service('logger')->ignoreOnInvalid()])
->tag('monolog.logger', ['channel' => 'http_client'])
->tag('kernel.reset', ['method' => 'reset', 'on_invalid' => 'ignore'])
->tag('http_client.client')
->alias(HttpClientInterface::class, 'http_client')
->set('psr18.http_client', Psr18Client::class)
->args([
service('http_client'),
service(ResponseFactoryInterface::class)->ignoreOnInvalid(),
service(StreamFactoryInterface::class)->ignoreOnInvalid(),
])
->alias(ClientInterface::class, 'psr18.http_client')
->set(\Http\Client\HttpClient::class, HttplugClient::class)
->args([
service('http_client'),
service(ResponseFactoryInterface::class)->ignoreOnInvalid(),
service(StreamFactoryInterface::class)->ignoreOnInvalid(),
])
->set('http_client.abstract_retry_strategy', GenericRetryStrategy::class)
->abstract()
->args([
abstract_arg('http codes'),
abstract_arg('delay ms'),
abstract_arg('multiplier'),
abstract_arg('max delay ms'),
abstract_arg('jitter'),
])
;
};

View File

@ -0,0 +1,25 @@
<?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\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\HttpClient\DataCollector\HttpClientDataCollector;
return static function (ContainerConfigurator $container) {
$container->services()
->set('data_collector.http_client', HttpClientDataCollector::class)
->tag('data_collector', [
'template' => '@WebProfiler/Collector/http_client.html.twig',
'id' => 'http_client',
'priority' => 250,
])
;
};

View File

@ -0,0 +1,26 @@
<?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\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\Translation\IdentityTranslator;
use Symfony\Contracts\Translation\TranslatorInterface;
return static function (ContainerConfigurator $container) {
$container->services()
->set('translator', IdentityTranslator::class)
->public()
->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.2'])
->alias(TranslatorInterface::class, 'translator')
->set('identity_translator', IdentityTranslator::class)
;
};

View File

@ -0,0 +1,30 @@
<?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\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\Store\CombinedStore;
use Symfony\Component\Lock\Strategy\ConsensusStrategy;
return static function (ContainerConfigurator $container) {
$container->services()
->set('lock.store.combined.abstract', CombinedStore::class)->abstract()
->args([abstract_arg('List of stores'), service('lock.strategy.majority')])
->set('lock.strategy.majority', ConsensusStrategy::class)
->set('lock.factory.abstract', LockFactory::class)->abstract()
->args([abstract_arg('Store')])
->call('setLogger', [service('logger')->ignoreOnInvalid()])
->tag('monolog.logger', ['channel' => 'lock'])
;
};

View File

@ -0,0 +1,81 @@
<?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\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\Mailer\EventListener\EnvelopeListener;
use Symfony\Component\Mailer\EventListener\MessageListener;
use Symfony\Component\Mailer\EventListener\MessageLoggerListener;
use Symfony\Component\Mailer\Mailer;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mailer\Messenger\MessageHandler;
use Symfony\Component\Mailer\Transport;
use Symfony\Component\Mailer\Transport\TransportInterface;
use Symfony\Component\Mailer\Transport\Transports;
return static function (ContainerConfigurator $container) {
$container->services()
->set('mailer.mailer', Mailer::class)
->args([
service('mailer.transports'),
abstract_arg('message bus'),
service('event_dispatcher')->ignoreOnInvalid(),
])
->alias('mailer', 'mailer.mailer')
->alias(MailerInterface::class, 'mailer.mailer')
->set('mailer.transports', Transports::class)
->factory([service('mailer.transport_factory'), 'fromStrings'])
->args([
abstract_arg('transports'),
])
->set('mailer.transport_factory', Transport::class)
->args([
tagged_iterator('mailer.transport_factory'),
])
->set('mailer.default_transport', TransportInterface::class)
->factory([service('mailer.transport_factory'), 'fromString'])
->args([
abstract_arg('env(MAILER_DSN)'),
])
->alias(TransportInterface::class, 'mailer.default_transport')
->set('mailer.messenger.message_handler', MessageHandler::class)
->args([
service('mailer.transports'),
])
->tag('messenger.message_handler')
->set('mailer.envelope_listener', EnvelopeListener::class)
->args([
abstract_arg('sender'),
abstract_arg('recipients'),
])
->tag('kernel.event_subscriber')
->set('mailer.message_listener', MessageListener::class)
->args([
abstract_arg('headers'),
])
->tag('kernel.event_subscriber')
->set('mailer.logger_message_listener', MessageLoggerListener::class)
->tag('kernel.event_subscriber')
->tag('kernel.reset', ['method' => 'reset'])
->deprecate('symfony/framework-bundle', '5.2', 'The "%service_id%" service is deprecated, use "mailer.message_logger_listener" instead.')
->set('mailer.message_logger_listener', MessageLoggerListener::class)
->tag('kernel.event_subscriber')
->tag('kernel.reset', ['method' => 'reset'])
;
};

View File

@ -0,0 +1,27 @@
<?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\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\Mailer\DataCollector\MessageDataCollector;
return static function (ContainerConfigurator $container) {
$container->services()
->set('mailer.data_collector', MessageDataCollector::class)
->args([
service('mailer.message_logger_listener'),
])
->tag('data_collector', [
'template' => '@WebProfiler/Collector/mailer.html.twig',
'id' => 'mailer',
])
;
};

View File

@ -0,0 +1,91 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesTransportFactory;
use Symfony\Component\Mailer\Bridge\Google\Transport\GmailTransportFactory;
use Symfony\Component\Mailer\Bridge\Mailchimp\Transport\MandrillTransportFactory;
use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunTransportFactory;
use Symfony\Component\Mailer\Bridge\Mailjet\Transport\MailjetTransportFactory;
use Symfony\Component\Mailer\Bridge\OhMySmtp\Transport\OhMySmtpTransportFactory;
use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory;
use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridTransportFactory;
use Symfony\Component\Mailer\Bridge\Sendinblue\Transport\SendinblueTransportFactory;
use Symfony\Component\Mailer\Transport\AbstractTransportFactory;
use Symfony\Component\Mailer\Transport\NativeTransportFactory;
use Symfony\Component\Mailer\Transport\NullTransportFactory;
use Symfony\Component\Mailer\Transport\SendmailTransportFactory;
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransportFactory;
return static function (ContainerConfigurator $container) {
$container->services()
->set('mailer.transport_factory.abstract', AbstractTransportFactory::class)
->abstract()
->args([
service('event_dispatcher'),
service('http_client')->ignoreOnInvalid(),
service('logger')->ignoreOnInvalid(),
])
->tag('monolog.logger', ['channel' => 'mailer'])
->set('mailer.transport_factory.amazon', SesTransportFactory::class)
->parent('mailer.transport_factory.abstract')
->tag('mailer.transport_factory')
->set('mailer.transport_factory.gmail', GmailTransportFactory::class)
->parent('mailer.transport_factory.abstract')
->tag('mailer.transport_factory')
->set('mailer.transport_factory.mailchimp', MandrillTransportFactory::class)
->parent('mailer.transport_factory.abstract')
->tag('mailer.transport_factory')
->set('mailer.transport_factory.mailjet', MailjetTransportFactory::class)
->parent('mailer.transport_factory.abstract')
->tag('mailer.transport_factory')
->set('mailer.transport_factory.mailgun', MailgunTransportFactory::class)
->parent('mailer.transport_factory.abstract')
->tag('mailer.transport_factory')
->set('mailer.transport_factory.postmark', PostmarkTransportFactory::class)
->parent('mailer.transport_factory.abstract')
->tag('mailer.transport_factory')
->set('mailer.transport_factory.sendgrid', SendgridTransportFactory::class)
->parent('mailer.transport_factory.abstract')
->tag('mailer.transport_factory')
->set('mailer.transport_factory.null', NullTransportFactory::class)
->parent('mailer.transport_factory.abstract')
->tag('mailer.transport_factory')
->set('mailer.transport_factory.sendmail', SendmailTransportFactory::class)
->parent('mailer.transport_factory.abstract')
->tag('mailer.transport_factory')
->set('mailer.transport_factory.sendinblue', SendinblueTransportFactory::class)
->parent('mailer.transport_factory.abstract')
->tag('mailer.transport_factory')
->set('mailer.transport_factory.ohmysmtp', OhMySmtpTransportFactory::class)
->parent('mailer.transport_factory.abstract')
->tag('mailer.transport_factory')
->set('mailer.transport_factory.smtp', EsmtpTransportFactory::class)
->parent('mailer.transport_factory.abstract')
->tag('mailer.transport_factory', ['priority' => -100])
->set('mailer.transport_factory.native', NativeTransportFactory::class)
->parent('mailer.transport_factory.abstract')
->tag('mailer.transport_factory');
};

View File

@ -0,0 +1,215 @@
<?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\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\Messenger\Bridge\AmazonSqs\Transport\AmazonSqsTransportFactory;
use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpTransportFactory;
use Symfony\Component\Messenger\Bridge\Beanstalkd\Transport\BeanstalkdTransportFactory;
use Symfony\Component\Messenger\Bridge\Redis\Transport\RedisTransportFactory;
use Symfony\Component\Messenger\EventListener\AddErrorDetailsStampListener;
use Symfony\Component\Messenger\EventListener\DispatchPcntlSignalListener;
use Symfony\Component\Messenger\EventListener\ResetServicesListener;
use Symfony\Component\Messenger\EventListener\SendFailedMessageForRetryListener;
use Symfony\Component\Messenger\EventListener\SendFailedMessageToFailureTransportListener;
use Symfony\Component\Messenger\EventListener\StopWorkerOnCustomStopExceptionListener;
use Symfony\Component\Messenger\EventListener\StopWorkerOnRestartSignalListener;
use Symfony\Component\Messenger\EventListener\StopWorkerOnSigtermSignalListener;
use Symfony\Component\Messenger\Middleware\AddBusNameStampMiddleware;
use Symfony\Component\Messenger\Middleware\DispatchAfterCurrentBusMiddleware;
use Symfony\Component\Messenger\Middleware\FailedMessageProcessingMiddleware;
use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware;
use Symfony\Component\Messenger\Middleware\RejectRedeliveredMessageMiddleware;
use Symfony\Component\Messenger\Middleware\RouterContextMiddleware;
use Symfony\Component\Messenger\Middleware\SendMessageMiddleware;
use Symfony\Component\Messenger\Middleware\TraceableMiddleware;
use Symfony\Component\Messenger\Middleware\ValidationMiddleware;
use Symfony\Component\Messenger\Retry\MultiplierRetryStrategy;
use Symfony\Component\Messenger\RoutableMessageBus;
use Symfony\Component\Messenger\Transport\InMemoryTransportFactory;
use Symfony\Component\Messenger\Transport\Sender\SendersLocator;
use Symfony\Component\Messenger\Transport\Serialization\Normalizer\FlattenExceptionNormalizer;
use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer;
use Symfony\Component\Messenger\Transport\Serialization\Serializer;
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
use Symfony\Component\Messenger\Transport\Sync\SyncTransportFactory;
use Symfony\Component\Messenger\Transport\TransportFactory;
return static function (ContainerConfigurator $container) {
$container->services()
->alias(SerializerInterface::class, 'messenger.default_serializer')
// Asynchronous
->set('messenger.senders_locator', SendersLocator::class)
->args([
abstract_arg('per message senders map'),
abstract_arg('senders service locator'),
])
->set('messenger.middleware.send_message', SendMessageMiddleware::class)
->args([
service('messenger.senders_locator'),
service('event_dispatcher'),
])
->call('setLogger', [service('logger')->ignoreOnInvalid()])
->tag('monolog.logger', ['channel' => 'messenger'])
// Message encoding/decoding
->set('messenger.transport.symfony_serializer', Serializer::class)
->args([
service('serializer'),
abstract_arg('format'),
abstract_arg('context'),
])
->set('serializer.normalizer.flatten_exception', FlattenExceptionNormalizer::class)
->tag('serializer.normalizer', ['priority' => -880])
->set('messenger.transport.native_php_serializer', PhpSerializer::class)
// Middleware
->set('messenger.middleware.handle_message', HandleMessageMiddleware::class)
->abstract()
->args([
abstract_arg('bus handler resolver'),
])
->tag('monolog.logger', ['channel' => 'messenger'])
->call('setLogger', [service('logger')->ignoreOnInvalid()])
->set('messenger.middleware.add_bus_name_stamp_middleware', AddBusNameStampMiddleware::class)
->abstract()
->set('messenger.middleware.dispatch_after_current_bus', DispatchAfterCurrentBusMiddleware::class)
->set('messenger.middleware.validation', ValidationMiddleware::class)
->args([
service('validator'),
])
->set('messenger.middleware.reject_redelivered_message_middleware', RejectRedeliveredMessageMiddleware::class)
->set('messenger.middleware.failed_message_processing_middleware', FailedMessageProcessingMiddleware::class)
->set('messenger.middleware.traceable', TraceableMiddleware::class)
->abstract()
->args([
service('debug.stopwatch'),
])
->set('messenger.middleware.router_context', RouterContextMiddleware::class)
->args([
service('router'),
])
// Discovery
->set('messenger.receiver_locator', ServiceLocator::class)
->args([
[],
])
->tag('container.service_locator')
// Transports
->set('messenger.transport_factory', TransportFactory::class)
->args([
tagged_iterator('messenger.transport_factory'),
])
->set('messenger.transport.amqp.factory', AmqpTransportFactory::class)
->set('messenger.transport.redis.factory', RedisTransportFactory::class)
->set('messenger.transport.sync.factory', SyncTransportFactory::class)
->args([
service('messenger.routable_message_bus'),
])
->tag('messenger.transport_factory')
->set('messenger.transport.in_memory.factory', InMemoryTransportFactory::class)
->tag('messenger.transport_factory')
->tag('kernel.reset', ['method' => 'reset'])
->set('messenger.transport.sqs.factory', AmazonSqsTransportFactory::class)
->args([
service('logger')->ignoreOnInvalid(),
])
->set('messenger.transport.beanstalkd.factory', BeanstalkdTransportFactory::class)
// retry
->set('messenger.retry_strategy_locator', ServiceLocator::class)
->args([
[],
])
->tag('container.service_locator')
->set('messenger.retry.abstract_multiplier_retry_strategy', MultiplierRetryStrategy::class)
->abstract()
->args([
abstract_arg('max retries'),
abstract_arg('delay ms'),
abstract_arg('multiplier'),
abstract_arg('max delay ms'),
])
// worker event listener
->set('messenger.retry.send_failed_message_for_retry_listener', SendFailedMessageForRetryListener::class)
->args([
abstract_arg('senders service locator'),
service('messenger.retry_strategy_locator'),
service('logger')->ignoreOnInvalid(),
service('event_dispatcher'),
])
->tag('kernel.event_subscriber')
->tag('monolog.logger', ['channel' => 'messenger'])
->set('messenger.failure.add_error_details_stamp_listener', AddErrorDetailsStampListener::class)
->tag('kernel.event_subscriber')
->set('messenger.failure.send_failed_message_to_failure_transport_listener', SendFailedMessageToFailureTransportListener::class)
->args([
abstract_arg('failure transports'),
service('logger')->ignoreOnInvalid(),
])
->tag('kernel.event_subscriber')
->tag('monolog.logger', ['channel' => 'messenger'])
->set('messenger.listener.dispatch_pcntl_signal_listener', DispatchPcntlSignalListener::class)
->tag('kernel.event_subscriber')
->set('messenger.listener.stop_worker_on_restart_signal_listener', StopWorkerOnRestartSignalListener::class)
->args([
service('cache.messenger.restart_workers_signal'),
service('logger')->ignoreOnInvalid(),
])
->tag('kernel.event_subscriber')
->tag('monolog.logger', ['channel' => 'messenger'])
->set('messenger.listener.stop_worker_on_sigterm_signal_listener', StopWorkerOnSigtermSignalListener::class)
->args([
service('logger')->ignoreOnInvalid(),
])
->tag('kernel.event_subscriber')
->set('messenger.listener.stop_worker_on_stop_exception_listener', StopWorkerOnCustomStopExceptionListener::class)
->tag('kernel.event_subscriber')
->set('messenger.listener.reset_services', ResetServicesListener::class)
->args([
service('services_resetter'),
])
->set('messenger.routable_message_bus', RoutableMessageBus::class)
->args([
abstract_arg('message bus locator'),
service('messenger.default_bus'),
])
;
};

View File

@ -0,0 +1,25 @@
<?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\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\Messenger\DataCollector\MessengerDataCollector;
return static function (ContainerConfigurator $container) {
$container->services()
->set('data_collector.messenger', MessengerDataCollector::class)
->tag('data_collector', [
'template' => '@WebProfiler/Collector/messenger.html.twig',
'id' => 'messenger',
'priority' => 100,
])
;
};

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