diff --git a/bundles/configuration.rst b/bundles/configuration.rst index 520e1f04220..198ac07450d 100644 --- a/bundles/configuration.rst +++ b/bundles/configuration.rst @@ -40,9 +40,11 @@ as integration of other related components: .. code-block:: php - $container->loadFromExtension('framework', [ - 'form' => true, - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->form()->enabled(true); + }; Using the Bundle Extension -------------------------- @@ -81,12 +83,13 @@ can add some configuration that looks like this: .. code-block:: php // config/packages/acme_social.php - $container->loadFromExtension('acme_social', [ - 'twitter' => [ - 'client_id' => 123, - 'client_secret' => 'your_secret', - ], - ]); + use Symfony\Config\AcmeSocialConfig; + + return static function (AcmeSocialConfig $acmeSocial) { + $acmeSocial->twitter() + ->clientId(123) + ->clientSecret('your_secret'); + }; The basic idea is that instead of having the user override individual parameters, you let the user configure just a few, specifically created, diff --git a/cache.rst b/cache.rst index 6eb17035e5f..f9a8c42b554 100644 --- a/cache.rst +++ b/cache.rst @@ -85,12 +85,15 @@ adapter (template) they use by using the ``app`` and ``system`` key like: .. code-block:: php // config/packages/cache.php - $container->loadFromExtension('framework', [ - 'cache' => [ - 'app' => 'cache.adapter.filesystem', - 'system' => 'cache.adapter.system', - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->cache() + ->app('cache.adapter.filesystem') + ->system('cache.adapter.system') + ; + }; + The Cache component comes with a series of adapters pre-configured: @@ -165,23 +168,24 @@ will create pools with service IDs that follow the pattern ``cache.[type]``. .. code-block:: php // config/packages/cache.php - $container->loadFromExtension('framework', [ - 'cache' => [ - // Only used with cache.adapter.filesystem - 'directory' => '%kernel.cache_dir%/pools', + use Symfony\Config\FrameworkConfig; + return static function (FrameworkConfig $framework) { + $framework->cache() + // Only used with cache.adapter.filesystem + ->directory('%kernel.cache_dir%/pools') // Service: cache.doctrine - 'default_doctrine_provider' => 'app.doctrine_cache', + ->defaultDoctrineProvider('app.doctrine_cache') // Service: cache.psr6 - 'default_psr6_provider' => 'app.my_psr6_service', + ->defaultPsr6Provider('app.my_psr6_service') // Service: cache.redis - 'default_redis_provider' => 'redis://localhost', + ->defaultRedisProvider('redis://localhost') // Service: cache.memcached - 'default_memcached_provider' => 'memcached://localhost', + ->defaultMemcachedProvider('memcached://localhost') // Service: cache.pdo - 'default_pdo_provider' => 'doctrine.dbal.default_connection', - ], - ]); + ->defaultPdoProvider('doctrine.dbal.default_connection') + ; + }; .. _cache-create-pools: @@ -267,43 +271,36 @@ You can also create more customized pools: .. code-block:: php // config/packages/cache.php - $container->loadFromExtension('framework', [ - 'cache' => [ - 'default_memcached_provider' => 'memcached://localhost', - 'pools' => [ - // creates a "custom_thing.cache" service - // autowireable via "CacheInterface $customThingCache" - // uses the "app" cache configuration - 'custom_thing.cache' => [ - 'adapter' => 'cache.app', - ], - - // creates a "my_cache_pool" service - // autowireable via "CacheInterface $myCachePool" - 'my_cache_pool' => [ - 'adapter' => 'cache.adapter.filesystem', - ], - - // uses the default_memcached_provider from above - 'acme.cache' => [ - 'adapter' => 'cache.adapter.memcached', - ], - - // control adapter's configuration - 'foobar.cache' => [ - 'adapter' => 'cache.adapter.memcached', - 'provider' => 'memcached://user:password@example.com', - ], - - // uses the "foobar.cache" pool as its backend but controls - // the lifetime and (like all pools) has a separate cache namespace - 'short_cache' => [ - 'adapter' => 'foobar.cache', - 'default_lifetime' => 60, - ], - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $cache = $framework->cache(); + $cache->defaultMemcachedProvider('memcached://localhost'); + + // creates a "custom_thing.cache" service + // autowireable via "CacheInterface $customThingCache" + // uses the "app" cache configuration + $cache->pool('custom_thing.cache') + ->adapters(['cache.app']); + + // creates a "my_cache_pool" service + // autowireable via "CacheInterface $myCachePool" + $cache->pool('my_cache_pool') + ->adapters(['cache.adapter.filesystem']); + + // uses the default_memcached_provider from above + $cache->pool('acme.cache') + ->adapters(['cache.adapter.memcached']); + + // control adapter's configuration + $cache->pool('foobar.cache') + ->adapters(['cache.adapter.memcached']) + ->provider('memcached://user:password@example.com'); + + $cache->pool('short_cache') + ->adapters(['foobar.cache']) + ->defaultLifetime(60); + }; Each pool manages a set of independent cache keys: keys from different pools *never* collide, even if they share the same backend. This is achieved by prefixing @@ -442,26 +439,25 @@ and use that when configuring the pool. // config/packages/cache.php use Symfony\Component\Cache\Adapter\RedisAdapter; - - $container->loadFromExtension('framework', [ - 'cache' => [ - 'pools' => [ - 'cache.my_redis' => [ - 'adapter' => 'cache.adapter.redis', - 'provider' => 'app.my_custom_redis_provider', - ], - ], - ], - ]); - - $container->register('app.my_custom_redis_provider', \Redis::class) - ->setFactory([RedisAdapter::class, 'createConnection']) - ->addArgument('redis://localhost') - ->addArgument([ - 'retry_interval' => 2, - 'timeout' => 10 - ]) - ; + use Symfony\Component\DependencyInjection\ContainerBuilder; + use Symfony\Config\FrameworkConfig; + + return static function (ContainerBuilder $container, FrameworkConfig $framework) { + $framework->cache() + ->pool('cache.my_redis') + ->adapters(['cache.adapter.redis']) + ->provider('app.my_custom_redis_provider'); + + + $container->register('app.my_custom_redis_provider', \Redis::class) + ->setFactory([RedisAdapter::class, 'createConnection']) + ->addArgument('redis://localhost') + ->addArgument([ + 'retry_interval' => 2, + 'timeout' => 10 + ]) + ; + }; Creating a Cache Chain ---------------------- @@ -521,20 +517,19 @@ Symfony stores the item automatically in all the missing pools. .. code-block:: php // config/packages/cache.php - $container->loadFromExtension('framework', [ - 'cache' => [ - 'pools' => [ - 'my_cache_pool' => [ - 'default_lifetime' => 31536000, // One year - 'adapters' => [ - 'cache.adapter.array', - 'cache.adapter.apcu', - ['name' => 'cache.adapter.redis', 'provider' => 'redis://user:password@example.com'], - ], - ], - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->cache() + ->pool('my_cache_pool') + ->defaultLifetime(31536000) // One year + ->adapters([ + 'cache.adapter.array', + 'cache.adapter.apcu', + ['name' => 'cache.adapter.redis', 'provider' => 'redis://user:password@example.com'], + ]) + ; + }; Using Cache Tags ---------------- @@ -613,16 +608,15 @@ to enable this feature. This could be added by using the following configuration .. code-block:: php // config/packages/cache.php - $container->loadFromExtension('framework', [ - 'cache' => [ - 'pools' => [ - 'my_cache_pool' => [ - 'adapter' => 'cache.adapter.redis', - 'tags' => true, - ], - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->cache() + ->pool('my_cache_pool') + ->tags(true) + ->adapters(['cache.adapter.redis']) + ; + }; Tags are stored in the same pool by default. This is good in most scenarios. But sometimes it might be better to store the tags in a different pool. That could be @@ -663,19 +657,20 @@ achieved by specifying the adapter. .. code-block:: php // config/packages/cache.php - $container->loadFromExtension('framework', [ - 'cache' => [ - 'pools' => [ - 'my_cache_pool' => [ - 'adapter' => 'cache.adapter.redis', - 'tags' => 'tag_pool', - ], - 'tag_pool' => [ - 'adapter' => 'cache.adapter.apcu', - ], - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->cache() + ->pool('my_cache_pool') + ->tags('tag_pool') + ->adapters(['cache.adapter.redis']) + ; + + $framework->cache() + ->pool('tag_pool') + ->adapters(['cache.adapter.apcu']) + ; + }; .. note:: diff --git a/configuration/env_var_processors.rst b/configuration/env_var_processors.rst index 8187364c152..d1be0338448 100644 --- a/configuration/env_var_processors.rst +++ b/configuration/env_var_processors.rst @@ -44,11 +44,15 @@ processor to turn the value of the ``HTTP_PORT`` env var into an integer: .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ - 'router' => [ - 'http_port' => '%env(int:HTTP_PORT)%', - ], - ]); + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->router() + ->httpPort(env('HTTP_PORT')->int()) + ; + }; Built-In Environment Variable Processors ---------------------------------------- @@ -90,10 +94,15 @@ Symfony provides the following env var processors: .. code-block:: php // config/packages/framework.php - $container->setParameter('env(SECRET)', 'some_secret'); - $container->loadFromExtension('framework', [ - 'secret' => '%env(string:SECRET)%', - ]); + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + use Symfony\Component\DependencyInjection\ContainerBuilder; + use Symfony\Config\FrameworkConfig; + + return static function (ContainerBuilder $container, FrameworkConfig $framework) { + $container->setParameter('env(SECRET)', 'some_secret'); + $framework->secret(env('SECRET')->string()); + }; ``env(bool:FOO)`` Casts ``FOO`` to a bool (``true`` values are ``'true'``, ``'on'``, ``'yes'`` @@ -131,10 +140,15 @@ Symfony provides the following env var processors: .. code-block:: php // config/packages/framework.php - $container->setParameter('env(HTTP_METHOD_OVERRIDE)', 'true'); - $container->loadFromExtension('framework', [ - 'http_method_override' => '%env(bool:HTTP_METHOD_OVERRIDE)%', - ]); + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + use Symfony\Component\DependencyInjection\ContainerBuilder; + use Symfony\Config\FrameworkConfig; + + return static function (ContainerBuilder $container, FrameworkConfig $framework) { + $container->setParameter('env(HTTP_METHOD_OVERRIDE)', 'true'); + $framework->httpMethodOverride(env('HTTP_METHOD_OVERRIDE')->bool()); + }; ``env(not:FOO)`` @@ -269,10 +283,15 @@ Symfony provides the following env var processors: .. code-block:: php // config/packages/framework.php - $container->setParameter('env(TRUSTED_HOSTS)', '["10.0.0.1", "10.0.0.2"]'); - $container->loadFromExtension('framework', [ - 'trusted_hosts' => '%env(json:TRUSTED_HOSTS)%', - ]); + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + use Symfony\Component\DependencyInjection\ContainerBuilder; + use Symfony\Config\FrameworkConfig; + + return static function (ContainerBuilder $container, FrameworkConfig $framework) { + $container->setParameter('env(TRUSTED_HOSTS)', '["10.0.0.1", "10.0.0.2"]'); + $framework->trustedHosts(env('TRUSTED_HOSTS')->json()); + }; ``env(resolve:FOO)`` If the content of ``FOO`` includes container parameters (with the syntax @@ -353,10 +372,15 @@ Symfony provides the following env var processors: .. code-block:: php // config/packages/framework.php - $container->setParameter('env(TRUSTED_HOSTS)', '10.0.0.1,10.0.0.2'); - $container->loadFromExtension('framework', [ - 'trusted_hosts' => '%env(csv:TRUSTED_HOSTS)%', - ]); + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + use Symfony\Component\DependencyInjection\ContainerBuilder; + use Symfony\Config\FrameworkConfig; + + return static function (ContainerBuilder $container, FrameworkConfig $framework) { + $container->setParameter('env(TRUSTED_HOSTS)', '10.0.0.1,10.0.0.2'); + $framework->trustedHosts(env('TRUSTED_HOSTS')->csv()); + }; ``env(file:FOO)`` Returns the contents of a file whose path is the value of the ``FOO`` env var: diff --git a/configuration/micro_kernel_trait.rst b/configuration/micro_kernel_trait.rst index 890f60d1ca8..66e9aae2bbe 100644 --- a/configuration/micro_kernel_trait.rst +++ b/configuration/micro_kernel_trait.rst @@ -234,12 +234,15 @@ because the configuration started to get bigger: .. code-block:: php // config/framework.php - $container->loadFromExtension('framework', [ - 'secret' => 'S0ME_SECRET', - 'profiler' => [ - 'only_exceptions' => false, - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework + ->secret('SOME_SECRET') + ->profiler() + ->onlyExceptions(false) + ; + }; This also loads annotation routes from an ``src/Controller/`` directory, which has one file in it:: diff --git a/configuration/override_dir_structure.rst b/configuration/override_dir_structure.rst index fef0d7756fb..6f56f4b54a4 100644 --- a/configuration/override_dir_structure.rst +++ b/configuration/override_dir_structure.rst @@ -186,11 +186,13 @@ configuration option to define your own translations directory (use :ref:`framew .. code-block:: php // config/packages/translation.php - $container->loadFromExtension('framework', [ - 'translator' => [ - 'default_path' => '%kernel.project_dir%/i18n', - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->translator() + ->defaultPath('%kernel.project_dir%/i18n') + ; + }; .. _override-web-dir: .. _override-the-web-directory: diff --git a/configuration/secrets.rst b/configuration/secrets.rst index 299d36326fc..8db723d63de 100644 --- a/configuration/secrets.rst +++ b/configuration/secrets.rst @@ -308,13 +308,15 @@ The secrets system is enabled by default and some of its behavior can be configu .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ - 'secrets' => [ - // 'vault_directory' => '%kernel.project_dir%/config/secrets/%kernel.environment%', - // 'local_dotenv_file' => '%kernel.project_dir%/.env.%kernel.environment%.local', - // 'decryption_env_var' => 'base64:default::SYMFONY_DECRYPTION_SECRET', - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->secrets() + // ->vaultDirectory('%kernel.project_dir%/config/secrets/%kernel.environment%') + // ->localDotenvFile('%kernel.project_dir%/.env.%kernel.environment%.local') + // ->decryptionEnvVar('base64:default::SYMFONY_DECRYPTION_SECRET') + ; + }; .. _`libsodium`: https://pecl.php.net/package/libsodium diff --git a/controller/error_pages.rst b/controller/error_pages.rst index 337723d8605..fabea2dcd22 100644 --- a/controller/error_pages.rst +++ b/controller/error_pages.rst @@ -273,10 +273,12 @@ configuration option to point to it: .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ - 'error_controller' => 'App\Controller\ErrorController::show', + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { // ... - ]); + $framework->errorController('App\Controller\ErrorController::show'); + }; The :class:`Symfony\\Component\\HttpKernel\\EventListener\\ErrorListener` class used by the FrameworkBundle as a listener of the ``kernel.exception`` event creates diff --git a/deployment/proxies.rst b/deployment/proxies.rst index 49aa04c8361..62d5c182c1e 100644 --- a/deployment/proxies.rst +++ b/deployment/proxies.rst @@ -68,16 +68,18 @@ and what headers your reverse proxy uses to send information: .. code-block:: php // config/packages/framework.php - use Symfony\Component\HttpFoundation\Request; - - $container->loadFromExtension('framework', [ - // the IP address (or range) of your proxy - 'trusted_proxies' => '192.0.0.1,10.0.0.0/8', - // trust *all* "X-Forwarded-*" headers (the ! prefix means to not trust those headers) - 'trusted_headers' => ['x-forwarded-for', 'x-forwarded-host', 'x-forwarded-proto', 'x-forwarded-port'], - // or, if your proxy instead uses the "Forwarded" header - 'trusted_headers' => ['forwarded'], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework + // the IP address (or range) of your proxy + ->trustedProxies('192.0.0.1,10.0.0.0/8') + // trust *all* "X-Forwarded-*" headers (the ! prefix means to not trust those headers) + ->trustedHeaders(['x-forwarded-for', 'x-forwarded-host', 'x-forwarded-proto', 'x-forwarded-port']) + // or, if your proxy instead uses the "Forwarded" header + ->trustedHeaders(['forwarded']) + ; + }; .. deprecated:: 5.2 diff --git a/frontend/custom_version_strategy.rst b/frontend/custom_version_strategy.rst index bf5f82fd9f4..336acfbd295 100644 --- a/frontend/custom_version_strategy.rst +++ b/frontend/custom_version_strategy.rst @@ -190,12 +190,13 @@ the :ref:`version_strategy ` option: // config/packages/framework.php use App\Asset\VersionStrategy\GulpBusterVersionStrategy; + use Symfony\Config\FrameworkConfig; - $container->loadFromExtension('framework', [ + return static function (FrameworkConfig $framework) { // ... - 'assets' => [ - 'version_strategy' => GulpBusterVersionStrategy::class, - ], - ]); + $framework->assets() + ->versionStrategy(GulpBusterVersionStrategy::class) + ; + }; .. _`gulp-buster`: https://www.npmjs.com/package/gulp-buster diff --git a/http_cache/esi.rst b/http_cache/esi.rst index 6108df41f49..a166a01762e 100644 --- a/http_cache/esi.rst +++ b/http_cache/esi.rst @@ -88,10 +88,13 @@ First, to use ESI, be sure to enable it in your application configuration: .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ - // ... - 'esi' => ['enabled' => true], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->esi() + ->enabled(true) + ; + }; Now, suppose you have a page that is relatively static, except for a news ticker at the bottom of the content. With ESI, you can cache the news ticker @@ -225,10 +228,14 @@ that must be enabled in your configuration: .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { // ... - 'fragments' => ['path' => '/_fragment'], - ]); + $framework->fragments() + ->path('/_fragment') + ; + }; One great advantage of the ESI renderer is that you can make your application as dynamic as needed and at the same time, hit the application as little as diff --git a/http_cache/ssi.rst b/http_cache/ssi.rst index 94bab702db4..f7cb9671b54 100644 --- a/http_cache/ssi.rst +++ b/http_cache/ssi.rst @@ -76,9 +76,13 @@ First, to use SSI, be sure to enable it in your application configuration: .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ - 'ssi' => ['enabled' => true], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->ssi() + ->enabled(true) + ; + }; Suppose you have a page with private content like a Profile page and you want to cache a static GDPR content block. With SSI, you can add some expiration @@ -86,7 +90,7 @@ on this block and keep the page private:: // src/Controller/ProfileController.php namespace App\Controller; - + // ... class ProfileController extends AbstractController { diff --git a/http_client.rst b/http_client.rst index e52f3d4940e..dc4018760fd 100644 --- a/http_client.rst +++ b/http_client.rst @@ -123,13 +123,14 @@ You can configure the global options using the ``default_options`` option: .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ - 'http_client' => [ - 'default_options' => [ - 'max_redirects' => 7, - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->httpClient() + ->defaultOptions() + ->maxRedirects(7) + ; + }; .. code-block:: php-standalone @@ -183,12 +184,14 @@ The HTTP client also has one configuration option called .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ - 'http_client' => [ - 'max_host_connections' => 10, + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->httpClient() + ->maxHostConnections(10) // ... - ], - ]); + ; + }; .. code-block:: php-standalone @@ -264,32 +267,26 @@ autoconfigure the HTTP client based on the requested URL: .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ - 'http_client' => [ - 'scoped_clients' => [ - // only requests matching scope will use these options - 'github' => [ - 'scope' => 'https://api\.github\.com', - 'headers' => [ - 'Accept' => 'application/vnd.github.v3+json', - 'Authorization' => 'token %env(GITHUB_API_TOKEN)%', - ], - // ... - ], - - // using base_url, relative URLs (e.g. request("GET", "/repos/symfony/symfony-docs")) - // will default to these options - 'github' => [ - 'base_uri' => 'https://api.github.com', - 'headers' => [ - 'Accept' => 'application/vnd.github.v3+json', - 'Authorization' => 'token %env(GITHUB_API_TOKEN)%', - ], - // ... - ], - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + // only requests matching scope will use these options + $framework->httpClient()->scopedClient('github') + ->scope('https://api\.github\.com') + ->header('Accept', 'application/vnd.github.v3+json') + ->header('Authorization', 'token %env(GITHUB_API_TOKEN)%') + // ... + ; + + // using base_url, relative URLs (e.g. request("GET", "/repos/symfony/symfony-docs")) + // will default to these options + $framework->httpClient()->scopedClient('github') + ->baseUri('https://api.github.com') + ->header('Accept', 'application/vnd.github.v3+json') + ->header('Authorization', 'token %env(GITHUB_API_TOKEN)%') + // ... + ; + }; .. code-block:: php-standalone @@ -432,24 +429,21 @@ each request (which overrides any global authentication): .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ - 'http_client' => [ - 'scoped_clients' => [ - 'example_api' => [ - 'base_uri' => 'https://example.com/', + use Symfony\Config\FrameworkConfig; - // HTTP Basic authentication - 'auth_basic' => 'the-username:the-password', + return static function (FrameworkConfig $framework) { + $framework->httpClient()->scopedClient('example_api') + ->baseUri('https://example.com/') + // HTTP Basic authentication + ->authBasic('the-username:the-password') - // HTTP Bearer authentication (also called token authentication) - 'auth_bearer' => 'the-bearer-token', + // HTTP Bearer authentication (also called token authentication) + ->authBearer('the-bearer-token') - // Microsoft NTLM authentication - 'auth_ntlm' => 'the-username:the-password', - ], - ], - ], - ]); + // Microsoft NTLM authentication + ->authNtlm('the-username:the-password') + ; + }; .. code-block:: php-standalone @@ -537,15 +531,14 @@ Use the ``headers`` option to define the default headers added to all requests: .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ - 'http_client' => [ - 'default_options' => [ - 'headers' => [ - 'User-Agent' => 'My Fancy App', - ], - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->httpClient() + ->defaultOptions() + ->header('User-Agent', 'My Fancy App') + ; + }; .. code-block:: php-standalone @@ -882,13 +875,14 @@ To force HTTP/2 for ``http`` URLs, you need to enable it explicitly via the .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ - 'http_client' => [ - 'default_options' => [ - 'http_version' => '2.0', - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->httpClient() + ->defaultOptions() + ->httpVersion('2.0') + ; + }; .. code-block:: php-standalone @@ -1733,11 +1727,13 @@ Then configure Symfony to use your callback: .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ - 'http_client' => [ - 'mock_response_factory' => MockClientCallback::class, - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->httpClient() + ->mockResponseFactory(MockClientCallback::class) + ; + }; .. _`cURL PHP extension`: https://www.php.net/curl .. _`PSR-17`: https://www.php-fig.org/psr/psr-17/ diff --git a/lock.rst b/lock.rst index db987f1d20e..8e64d42249f 100644 --- a/lock.rst +++ b/lock.rst @@ -129,32 +129,33 @@ this behavior by using the ``lock`` key like: .. code-block:: php // config/packages/lock.php - $container->loadFromExtension('framework', [ - 'lock' => null, - 'lock' => 'flock', - 'lock' => 'flock:///path/to/file', - 'lock' => 'semaphore', - 'lock' => 'memcached://m1.docker', - 'lock' => ['memcached://m1.docker', 'memcached://m2.docker'], - 'lock' => 'redis://r1.docker', - 'lock' => ['redis://r1.docker', 'redis://r2.docker'], - 'lock' => 'zookeeper://z1.docker', - 'lock' => 'zookeeper://z1.docker,z2.docker', - 'lock' => 'sqlite:///%kernel.project_dir%/var/lock.db', - 'lock' => 'mysql:host=127.0.0.1;dbname=app', - 'lock' => 'pgsql:host=127.0.0.1;dbname=app', - 'lock' => 'pgsql+advisory:host=127.0.0.1;dbname=lock', - 'lock' => 'sqlsrv:server=127.0.0.1;Database=app', - 'lock' => 'oci:host=127.0.0.1;dbname=app', - 'lock' => 'mongodb://127.0.0.1/app?collection=lock', - 'lock' => '%env(LOCK_DSN)%', - - // named locks - 'lock' => [ - 'invoice' => ['semaphore', 'redis://r2.docker'], - 'report' => 'semaphore', - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->lock() + ->resource('default', ['flock']) + ->resource('default', ['flock:///path/to/file']) + ->resource('default', ['semaphore']) + ->resource('default', ['memcached://m1.docker']) + ->resource('default', ['memcached://m1.docker', 'memcached://m2.docker']) + ->resource('default', ['redis://r1.docker']) + ->resource('default', ['redis://r1.docker', 'redis://r2.docker']) + ->resource('default', ['zookeeper://z1.docker']) + ->resource('default', ['zookeeper://z1.docker,z2.docker']) + ->resource('default', ['sqlite:///%kernel.project_dir%/var/lock.db']) + ->resource('default', ['mysql:host=127.0.0.1;dbname=app']) + ->resource('default', ['pgsql:host=127.0.0.1;dbname=app']) + ->resource('default', ['pgsql+advisory:host=127.0.0.1;dbname=lock']) + ->resource('default', ['sqlsrv:server=127.0.0.1;Database=app']) + ->resource('default', ['oci:host=127.0.0.1;dbname=app']) + ->resource('default', ['mongodb://127.0.0.1/app?collection=lock']) + ->resource('default', ['%env(LOCK_DSN)%']) + + // named locks + ->resource('invoice', ['semaphore', 'redis://r2.docker']) + ->resource('report', ['semaphore']) + ; + }; Locking a Resource ------------------ @@ -269,12 +270,15 @@ provides :ref:`named lock `: .. code-block:: php // config/packages/lock.php - $container->loadFromExtension('framework', [ - 'lock' => [ - 'invoice' => ['semaphore', 'redis://r2.docker'], - 'report' => 'semaphore', - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->lock() + ->resource('invoice', ['semaphore', 'redis://r2.docker']) + ->resource('report', ['semaphore']); + ; + }; + Each name becomes a service where the service id is part of the name of the lock (e.g. ``lock.invoice.factory``). An autowiring alias is also created for diff --git a/mailer.rst b/mailer.rst index f772dba65af..2a7329caacc 100644 --- a/mailer.rst +++ b/mailer.rst @@ -510,20 +510,20 @@ and headers. .. code-block:: php // config/packages/mailer.php - $container->loadFromExtension('framework', [ - // ... - 'mailer' => [ - 'envelope' => [ - 'sender' => 'fabien@example.com', - 'recipients' => ['foo@example.com', 'bar@example.com'], - ], - 'headers' => [ - 'from' => 'Fabien ', - 'bcc' => 'baz@example.com', - 'X-Custom-Header' => 'foobar', - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $mailer = $framework->mailer(); + $mailer + ->envelope() + ->sender('fabien@example.com') + ->recipients(['foo@example.com', 'bar@example.com']) + ; + + $mailer->header('from')->value('Fabien '); + $mailer->header('bcc')->value('baz@example.com'); + $mailer->header('X-Custom-Header')->value('foobar'); + }; .. versionadded:: 5.2 @@ -1092,15 +1092,14 @@ This can be configured by replacing the ``dsn`` configuration entry with a .. code-block:: php // config/packages/mailer.php - $container->loadFromExtension('framework', [ - // ... - 'mailer' => [ - 'transports' => [ - 'main' => '%env(MAILER_DSN)%', - 'alternative' => '%env(MAILER_DSN_IMPORTANT)%', - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->mailer() + ->transport('main', '%env(MAILER_DSN)%') + ->transport('alternative', '%env(MAILER_DSN_IMPORTANT)%') + ; + }; By default the first transport is used. The other transports can be used by adding a text header ``X-Transport`` to an email:: @@ -1163,16 +1162,17 @@ you have a transport called ``async``, you can route the message there: .. code-block:: php // config/packages/messenger.php - $container->loadFromExtension('framework', [ - 'messenger' => [ - 'transports' => [ - 'async' => '%env(MESSENGER_TRANSPORT_DSN)%', - ], - 'routing' => [ - 'Symfony\Component\Mailer\Messenger\SendEmailMessage' => 'async', - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->messenger() + ->transport('async')->dsn('%env(MESSENGER_TRANSPORT_DSN)%'); + + $framework->messenger() + ->routing('Symfony\Component\Mailer\Messenger\SendEmailMessage') + ->senders(['async']); + }; + Thanks to this, instead of being delivered immediately, messages will be sent to the transport to be handled later (see :ref:`messenger-worker`). @@ -1213,11 +1213,12 @@ disable asynchronous delivery. .. code-block:: php // config/packages/mailer.php - $container->loadFromExtension('framework', [ - 'mailer' => [ - 'message_bus' => 'app.another_bus', - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->mailer() + ->messageBus('app.another_bus'); + }; .. versionadded:: 5.1 @@ -1300,12 +1301,13 @@ the mailer configuration file (e.g. in the ``dev`` or ``test`` environments): .. code-block:: php // config/packages/mailer.php - $container->loadFromExtension('framework', [ + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { // ... - 'mailer' => [ - 'dsn' => 'null://null', - ], - ]); + $framework->mailer() + ->dsn('null://null'); + }; .. note:: @@ -1352,14 +1354,15 @@ a specific address, instead of the *real* address: .. code-block:: php // config/packages/mailer.php - $container->loadFromExtension('framework', [ + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { // ... - 'mailer' => [ - 'envelope' => [ - 'recipients' => ['youremail@example.com'], - ], - ], - ]); + $framework->mailer() + ->envelope() + ->recipients(['youremail@example.com']) + ; + }; .. _`high availability`: https://en.wikipedia.org/wiki/High_availability .. _`load balancing`: https://en.wikipedia.org/wiki/Load_balancing_(computing) diff --git a/messenger.rst b/messenger.rst index c5725a4153a..000b4b622f6 100644 --- a/messenger.rst +++ b/messenger.rst @@ -179,19 +179,20 @@ that uses this configuration: .. code-block:: php // config/packages/messenger.php - $container->loadFromExtension('framework', [ - 'messenger' => [ - 'transports' => [ - 'async' => '%env(MESSENGER_TRANSPORT_DSN)%', - - // or expanded to configure more options - 'async' => [ - 'dsn' => '%env(MESSENGER_TRANSPORT_DSN)%', - 'options' => [], - ], - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->messenger() + ->transport('async') + ->dsn('%env(MESSENGER_TRANSPORT_DSN)%') + ; + + $framework->messenger() + ->transport('async') + ->dsn('%env(MESSENGER_TRANSPORT_DSN)%') + ->options([]) + ; + }; .. _messenger-routing: @@ -240,14 +241,14 @@ you can configure them to be sent to a transport: .. code-block:: php // config/packages/messenger.php - $container->loadFromExtension('framework', [ - 'messenger' => [ - 'routing' => [ - // async is whatever name you gave your transport above - 'App\Message\SmsNotification' => 'async', - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->messenger() + // async is whatever name you gave your transport above + ->routing('App\Message\SmsNotification')->senders(['async']) + ; + }; Thanks to this, the ``App\Message\SmsNotification`` will be sent to the ``async`` transport and its handler(s) will *not* be called immediately. Any messages not @@ -302,16 +303,15 @@ to multiple transports: .. code-block:: php // config/packages/messenger.php - $container->loadFromExtension('framework', [ - 'messenger' => [ - 'routing' => [ - // route all messages that extend this example base class or interface - 'App\Message\AbstractAsyncMessage' => 'async', - 'App\Message\AsyncMessageInterface' => 'async', - 'My\Message\ToBeSentToTwoSenders' => ['async', 'audit'], - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $messenger = $framework->messenger(); + // route all messages that extend this example base class or interface + $messenger->routing('App\Message\AbstractAsyncMessage')->senders(['async']); + $messenger->routing('App\Message\AsyncMessageInterface')->senders(['async']); + $messenger->routing('My\Message\ToBeSentToTwoSenders')->senders(['async', 'audit']); + }; .. note:: @@ -425,18 +425,16 @@ transport and "sending" messages there to be handled immediately: .. code-block:: php // config/packages/messenger.php - $container->loadFromExtension('framework', [ - 'messenger' => [ - 'transports' => [ - // ... other transports - - 'sync' => 'sync://', - ], - 'routing' => [ - 'App\Message\SmsNotification' => 'sync', - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $messenger = $framework->messenger(); + + // ... other transports + + $messenger->transport('sync')->dsn('sync://'); + $messenger->routing('App\Message\SmsNotification')->senders(['sync']); + }; Creating your Own Transport ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -561,28 +559,22 @@ different messages to them. For example: .. code-block:: php // config/packages/messenger.php - $container->loadFromExtension('framework', [ - 'messenger' => [ - 'transports' => [ - 'async_priority_high' => [ - 'dsn' => '%env(MESSENGER_TRANSPORT_DSN)%', - 'options' => [ - 'queue_name' => 'high', - ], - ], - 'async_priority_low' => [ - 'dsn' => '%env(MESSENGER_TRANSPORT_DSN)%', - 'options' => [ - 'queue_name' => 'low', - ], - ], - ], - 'routing' => [ - 'App\Message\SmsNotification' => 'async_priority_low', - 'App\Message\NewUserWelcomeEmail' => 'async_priority_high', - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $messenger = $framework->messenger(); + + $messenger->transport('async_priority_high') + ->dsn('%env(MESSENGER_TRANSPORT_DSN)%') + ->options(['queue_name' => 'high']); + + $messenger->transport('async_priority_low') + ->dsn('%env(MESSENGER_TRANSPORT_DSN)%') + ->options(['queue_name' => 'low']); + + $messenger->routing('App\Message\SmsNotification')->senders(['async_priority_low']); + $messenger->routing('App\Message\NewUserWelcomeEmail')->senders(['async_priority_high']); + }; You can then run individual workers for each transport or instruct one worker to handle messages in a priority order: @@ -732,29 +724,27 @@ this is configurable for each transport: .. code-block:: php // config/packages/messenger.php - $container->loadFromExtension('framework', [ - 'messenger' => [ - 'transports' => [ - 'async_priority_high' => [ - 'dsn' => '%env(MESSENGER_TRANSPORT_DSN)%', - - // default configuration - 'retry_strategy' => [ - 'max_retries' => 3, - // milliseconds delay - 'delay' => 1000, - // causes the delay to be higher before each retry - // e.g. 1 second delay, 2 seconds, 4 seconds - 'multiplier' => 2, - 'max_delay' => 0, - // override all of this with a service that - // implements Symfony\Component\Messenger\Retry\RetryStrategyInterface - // 'service' => null, - ], - ], - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $messenger = $framework->messenger(); + + $messenger->transport('async_priority_high') + ->dsn('%env(MESSENGER_TRANSPORT_DSN)%') + // default configuration + ->retryStrategy() + ->maxRetries(3) + // milliseconds delay + ->delay(1000) + // causes the delay to be higher before each retry + // e.g. 1 second delay, 2 seconds, 4 seconds + ->multiplier(2) + ->maxDelay(0) + // override all of this with a service that + // implements Symfony\Component\Messenger\Retry\RetryStrategyInterface + ->service(null) + ; + }; .. tip:: @@ -833,20 +823,19 @@ be discarded. To avoid this happening, you can instead configure a ``failure_tra .. code-block:: php // config/packages/messenger.php - $container->loadFromExtension('framework', [ - 'messenger' => [ - // after retrying, messages will be sent to the "failed" transport - 'failure_transport' => 'failed', - - 'transports' => [ - // ... other transports - - 'failed' => [ - 'dsn' => 'doctrine://default?queue_name=failed', - ], - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $messenger = $framework->messenger(); + + // after retrying, messages will be sent to the "failed" transport + $messenger->failureTransport('failed'); + + // ... other transports + + $messenger->transport('failed') + ->dsn('doctrine://default?queue_name=failed'); + }; In this example, if handling a message fails 3 times (default ``max_retries``), it will then be sent to the ``failed`` transport. While you *can* use @@ -900,8 +889,8 @@ override the failure transport for only specific transports: # config/packages/messenger.yaml framework: messenger: - # after retrying, messages will be sent to the "failed" transport - # by default if no "failed_transport" is configured inside a transport + # after retrying, messages will be sent to the "failed" transport + # by default if no "failed_transport" is configured inside a transport failure_transport: failed_default transports: @@ -915,7 +904,7 @@ override the failure transport for only specific transports: dsn: 'doctrine://default?queue_name=async_priority_low' failed_default: 'doctrine://default?queue_name=failed_default' - failed_high_priority: 'doctrine://default?queue_name=failed_high_priority' + failed_high_priority: 'doctrine://default?queue_name=failed_high_priority' .. code-block:: xml @@ -930,14 +919,14 @@ override the failure transport for only specific transports: https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> - - + - + @@ -947,31 +936,30 @@ override the failure transport for only specific transports: .. code-block:: php // config/packages/messenger.php - $container->loadFromExtension('framework', [ - 'messenger' => [ - // after retrying, messages will be sent to the "failed" transport - // by default if no "failure_transport" is configured inside a transport - 'failure_transport' => 'failed_default', - - 'transports' => [ - 'async_priority_high' => [ - 'dsn' => '%env(MESSENGER_TRANSPORT_DSN)%', - 'failure_transport' => 'failed_high_priority' - ], - // since no failed transport is configured, the one used will be - // the global failure_transport set - 'async_priority_low' => [ - 'dsn' => 'doctrine://default?queue_name=async_priority_low', - ], - 'failed_default' => [ - 'dsn' => 'doctrine://default?queue_name=failed_default', - ], - 'failed_high_priority' => [ - 'dsn' => 'doctrine://default?queue_name=failed_high_priority', - ], - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $messenger = $framework->messenger(); + + // after retrying, messages will be sent to the "failed" transport + // by default if no "failure_transport" is configured inside a transport + $messenger->failureTransport('failed_default'); + + $messenger->transport('async_priority_high') + ->dsn('%env(MESSENGER_TRANSPORT_DSN)%') + ->failureTransport('failed_high_priority'); + + // since no failed transport is configured, the one used will be + // the global failure_transport set + $messenger->transport('async_priority_low') + ->dsn('doctrine://default?queue_name=async_priority_low'); + + $messenger->transport('failed_default') + ->dsn('doctrine://default?queue_name=failed_default'); + + $messenger->transport('failed_high_priority') + ->dsn('doctrine://default?queue_name=failed_high_priority'); + }; If there is no ``failure_transport`` defined globally or on the transport level, the messages will be discarded after the number of retries. @@ -1040,18 +1028,15 @@ options. Options can be passed to the transport via a DSN string or configuratio .. code-block:: php // config/packages/messenger.php - $container->loadFromExtension('framework', [ - 'messenger' => [ - 'transports' => [ - 'my_transport' => [ - 'dsn' => '%env(MESSENGER_TRANSPORT_DSN)%', - 'options' => [ - 'auto_setup' => false, - ], - ], - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $messenger = $framework->messenger(); + + $messenger->transport('my_transport') + ->dsn('%env(MESSENGER_TRANSPORT_DSN)%') + ->options(['auto_setup' => false]); + }; Options defined under ``options`` take precedence over ones defined in the DSN. @@ -1454,15 +1439,14 @@ override it in the ``test`` environment to use this transport: .. code-block:: php // config/packages/test/messenger.php - $container->loadFromExtension('framework', [ - 'messenger' => [ - 'transports' => [ - 'async_priority_normal' => [ - 'dsn' => 'in-memory://', - ], - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $messenger = $framework->messenger(); + + $messenger->transport('async_priority_normal') + ->dsn('in-memory://'); + }; Then, while testing, messages will *not* be delivered to the real transport. Even better, in a test, you can check that exactly one message was sent @@ -1643,23 +1627,21 @@ this globally (or for each transport) to a service that implements .. code-block:: php // config/packages/messenger.php - $container->loadFromExtension('framework', [ - 'messenger' => [ - 'serializer' => [ - 'default_serializer' => 'messenger.transport.symfony_serializer', - 'symfony_serializer' => [ - 'format' => 'json', - 'context' => [], - ], - ], - 'transports' => [ - 'async_priority_normal' => [ - 'dsn' => ..., - 'serializer' => 'messenger.transport.symfony_serializer', - ], - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $messenger = $framework->messenger(); + + $messenger->serializer() + ->defaultSerializer('messenger.transport.symfony_serializer') + ->symfonySerializer() + ->format('json') + ->context('foo', 'bar'); + + $messenger->transport('async_priority_normal') + ->dsn(...) + ->serializer('messenger.transport.symfony_serializer'); + }; The ``messenger.transport.symfony_serializer`` is a built-in service that uses the :doc:`Serializer component ` and can be configured in a few ways. @@ -1881,17 +1863,17 @@ Then, make sure to "route" your message to *both* transports: .. code-block:: php // config/packages/messenger.php - $container->loadFromExtension('framework', [ - 'messenger' => [ - 'transports' => [ - 'async_priority_normal' => '...', - 'image_transport' => '...', - ], - 'routing' => [ - 'App\Message\UploadedImage' => ['image_transport', 'async_priority_normal'], - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $messenger = $framework->messenger(); + + $messenger->transport('async_priority_normal')->dsn(...); + $messenger->transport('image_transport')->dsn(...); + + $messenger->routing('App\Message\UploadedImage') + ->senders(['image_transport', 'async_priority_normal']); + }; That's it! You can now consume each transport: @@ -2024,22 +2006,16 @@ middleware and *only* include your own: .. code-block:: php // config/packages/messenger.php - $container->loadFromExtension('framework', [ - 'messenger' => [ - 'buses' => [ - 'messenger.bus.default' => [ - // disable the default middleware - 'default_middleware' => false, - - // and/or add your own - 'middleware' => [ - 'App\Middleware\MyMiddleware', - 'App\Middleware\AnotherMiddleware', - ], - ], - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $messenger = $framework->messenger(); + + $bus = $messenger->bus('messenger.bus.default') + ->defaultMiddleware(false); + $bus->middleware()->id('App\Middleware\MyMiddleware'); + $bus->middleware()->id('App\Middleware\AnotherMiddleware'); + }; .. note:: @@ -2118,21 +2094,19 @@ may want to use: .. code-block:: php // config/packages/messenger.php - $container->loadFromExtension('framework', [ - 'messenger' => [ - 'buses' => [ - 'command_bus' => [ - 'middleware' => [ - 'doctrine_transaction', - 'doctrine_ping_connection', - 'doctrine_close_connection', - // Using another entity manager - ['id' => 'doctrine_transaction', 'arguments' => ['custom']], - ], - ], - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $messenger = $framework->messenger(); + + $bus = $messenger->bus('command_bus'); + $bus->middleware()->id('doctrine_transaction'); + $bus->middleware()->id('doctrine_ping_connection'); + $bus->middleware()->id('doctrine_close_connection'); + // Using another entity manager + $bus->middleware()->id('doctrine_transaction') + ->arguments(['custom']); + }; Other Middlewares ~~~~~~~~~~~~~~~~~ @@ -2182,17 +2156,14 @@ when building absolute URLs. .. code-block:: php // config/packages/messenger.php - $container->loadFromExtension('framework', [ - 'messenger' => [ - 'buses' => [ - 'command_bus' => [ - 'middleware' => [ - 'router_context', - ], - ], - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $messenger = $framework->messenger(); + + $bus = $messenger->bus('command_bus'); + $bus->middleware()->id('router_context'); + }; Messenger Events diff --git a/messenger/custom-transport.rst b/messenger/custom-transport.rst index be41d63a41e..f43f347e642 100644 --- a/messenger/custom-transport.rst +++ b/messenger/custom-transport.rst @@ -203,13 +203,14 @@ named transport using your own DSN: .. code-block:: php // config/packages/messenger.php - $container->loadFromExtension('framework', [ - 'messenger' => [ - 'transports' => [ - 'yours' => 'my-transport://...', - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->messenger() + ->transport('yours') + ->dsn('my-transport://...') + ; + }; In addition of being able to route your messages to the ``yours`` sender, this will give you access to the following services: diff --git a/messenger/multiple_buses.rst b/messenger/multiple_buses.rst index fc61a0c9dd2..5a1ffecf94c 100644 --- a/messenger/multiple_buses.rst +++ b/messenger/multiple_buses.rst @@ -74,33 +74,25 @@ an **event bus**. The event bus could have zero or more subscribers. .. code-block:: php // config/packages/messenger.php - $container->loadFromExtension('framework', [ - 'messenger' => [ - // The bus that is going to be injected when injecting MessageBusInterface - 'default_bus' => 'command.bus', - 'buses' => [ - 'command.bus' => [ - 'middleware' => [ - 'validation', - 'doctrine_transaction', - ], - ], - 'query.bus' => [ - 'middleware' => [ - 'validation', - ], - ], - 'event.bus' => [ - // the 'allow_no_handlers' middleware allows to have no handler - // configured for this bus without throwing an exception - 'default_middleware' => 'allow_no_handlers', - 'middleware' => [ - 'validation', - ], - ], - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + // The bus that is going to be injected when injecting MessageBusInterface + $framework->messenger()->defaultBus('command.bus'); + + $commandBus = $framework->messenger()->bus('command.bus'); + $commandBus->middleware()->id('validation'); + $commandBus->middleware()->id('doctrine_transaction'); + + $queryBus = $framework->messenger()->bus('query.bus'); + $queryBus->middleware()->id('validation'); + + $eventBus = $framework->messenger()->bus('event.bus'); + // the 'allow_no_handlers' middleware allows to have no handler + // configured for this bus without throwing an exception + $eventBus->defaultMiddleware('allow_no_handlers'); + $eventBus->middleware()->id('validation'); + }; This will create three new services: diff --git a/notifier.rst b/notifier.rst index 5bbd12252a2..c552d757dff 100644 --- a/notifier.rst +++ b/notifier.rst @@ -132,14 +132,14 @@ configure the ``texter_transports``: .. code-block:: php - # config/packages/notifier.php - $container->loadFromExtension('framework', [ - 'notifier' => [ - 'texter_transports' => [ - 'twilio' => '%env(TWILIO_DSN)%', - ], - ], - ]); + // config/packages/notifier.php + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->notifier() + ->texterTransport('twilio', '%env(TWILIO_DSN)%') + ; + }; .. _notifier-chat-channel: .. _notifier-chatter-dsn: @@ -224,14 +224,14 @@ Chatters are configured using the ``chatter_transports`` setting: .. code-block:: php - # config/packages/notifier.php - $container->loadFromExtension('framework', [ - 'notifier' => [ - 'chatter_transports' => [ - 'slack' => '%env(SLACK_DSN)%', - ], - ], - ]); + // config/packages/notifier.php + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->notifier() + ->chatterTransport('slack', '%env(SLACK_DSN)%') + ; + }; .. _notifier-email-channel: @@ -288,15 +288,16 @@ notification emails: .. code-block:: php - # config/packages/mailer.php - $container->loadFromExtension('framework', [ - 'mailer' => [ - 'dsn' => '%env(MAILER_DSN)%', - 'envelope' => [ - 'sender' => 'notifications@example.com', - ], - ], - ]); + // config/packages/mailer.php + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->mailer() + ->dsn('%env(MAILER_DSN)%') + ->envelope() + ->sender('notifications@example.com') + ; + }; Configure to use Failover or Round-Robin Transports ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -351,19 +352,19 @@ transport: .. code-block:: php - # config/packages/notifier.php - $container->loadFromExtension('framework', [ - 'notifier' => [ - 'chatter_transports' => [ - // Send notifications to Slack and use Telegram if - // Slack errored - 'main' => '%env(SLACK_DSN)% || %env(TELEGRAM_DSN)%', + // config/packages/notifier.php + use Symfony\Config\FrameworkConfig; - // Send notifications to the next scheduled transport calculated by round robin - 'roundrobin' => '%env(SLACK_DSN)% && %env(TELEGRAM_DSN)%', - ], - ], - ]); + return static function (FrameworkConfig $framework) { + $framework->notifier() + // Send notifications to Slack and use Telegram if + // Slack errored + ->chatterTransport('main', '%env(SLACK_DSN)% || %env(TELEGRAM_DSN)%') + + // Send notifications to the next scheduled transport calculated by round robin + ->chatterTransport('roundrobin', '%env(SLACK_DSN)% && %env(TELEGRAM_DSN)%') + ; + }; Creating & Sending Notifications -------------------------------- @@ -495,23 +496,21 @@ specify what channels should be used for specific levels (using .. code-block:: php - # config/packages/notifier.php - $container->loadFromExtension('framework', [ - 'notifier' => [ - // ... - 'channel_policy' => [ - // Use SMS, Slack and email for urgent notifications - 'urgent' => ['sms', 'chat/slack', 'email'], - - // Use Slack for highly important notifications - 'high' => ['chat/slack'], - - // Use browser for medium and low notifications - 'medium' => ['browser'], - 'low' => ['browser'], - ], - ], - ]); + // config/packages/notifier.php + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + // ... + $framework->notifier() + // Use SMS, Slack and email for urgent notifications + ->channelPolicy('urgent', ['sms', 'chat/slack', 'email']) + // Use Slack for highly important notifications + ->channelPolicy('high', ['chat/slack']) + // Use browser for medium and low notifications + ->channelPolicy('medium', ['browser']) + ->channelPolicy('medium', ['browser']) + ; + }; Now, whenever the notification's importance is set to "high", it will be sent using the Slack transport:: diff --git a/rate_limiter.rst b/rate_limiter.rst index 2ab832d7031..96f0cd0e5e0 100644 --- a/rate_limiter.rst +++ b/rate_limiter.rst @@ -177,21 +177,26 @@ enforce different levels of service (free or paid): .. code-block:: php // config/packages/rate_limiter.php - $container->loadFromExtension('framework', [ - 'rate_limiter' => [ - 'anonymous_api' => [ + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->rateLimiter() + ->limiter('anonymous_api') // use 'sliding_window' if you prefer that policy - 'policy' => 'fixed_window', - 'limit' => 100, - 'interval' => '60 minutes', - ], - 'authenticated_api' => [ - 'policy' => 'token_bucket', - 'limit' => 5000, - 'rate' => [ 'interval' => '15 minutes', 'amount' => 500 ], - ], - ], - ]); + ->policy('fixed_window') + ->limit(100) + ->interval('60 minutes') + ; + + $framework->rateLimiter() + ->limiter('authenticated_api') + ->policy('token_bucket') + ->limit(5000) + ->rate() + ->interval('15 minutes') + ->amount(500) + ; + }; .. note:: @@ -409,16 +414,17 @@ Use the ``cache_pool`` option to override the cache used by a specific limiter .. code-block:: php // config/packages/rate_limiter.php - $container->loadFromExtension('framework', [ - 'rate_limiter' => [ - 'anonymous_api' => [ + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->rateLimiter() + ->limiter('anonymous_api') // ... // use the "cache.anonymous_rate_limiter" cache pool - 'cache_pool' => 'cache.anonymous_rate_limiter', - ], - ], - ]); + ->cachePool('cache.anonymous_rate_limiter') + ; + }; .. note:: @@ -483,16 +489,17 @@ you can use a specific :ref:`named lock ` via the .. code-block:: php // config/packages/rate_limiter.php - $container->loadFromExtension('framework', [ - 'rate_limiter' => [ - 'anonymous_api' => [ + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->rateLimiter() + ->limiter('anonymous_api') // ... // use the "lock.rate_limiter.factory" for this limiter - 'lock_factory' => 'lock.rate_limiter.factory', - ], - ], - ]); + ->lockFactory('lock.rate_limiter.factory') + ; + }; .. _`DoS attacks`: https://cheatsheetseries.owasp.org/cheatsheets/Denial_of_Service_Cheat_Sheet.html .. _`Apache mod_ratelimit`: https://httpd.apache.org/docs/current/mod/mod_ratelimit.html diff --git a/reference/configuration/framework.rst b/reference/configuration/framework.rst index 3b2f97a9156..0ca87286119 100644 --- a/reference/configuration/framework.rst +++ b/reference/configuration/framework.rst @@ -454,9 +454,11 @@ doubling them to prevent Symfony from interpreting them as container parameters) .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ - 'ide' => 'myide://open?url=file://%%f&line=%%l', - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->ide('myide://open?url=file://%%f&line=%%l'); + }; Since every developer uses a different IDE, the recommended way to enable this feature is to configure it on a system level. This can be done by setting the @@ -596,9 +598,11 @@ the application won't respond and the user will receive a 400 response. .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ - 'trusted_hosts' => ['^example\.com$', '^example\.org$'], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->trustedHosts(['^example\.com$', '^example\.org$']); + }; Hosts can also be configured to respond to any subdomain, via ``^(.+\.)?example\.com$`` for instance. @@ -724,9 +728,11 @@ You can also set ``esi`` to ``true`` to enable it: .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ - 'esi' => true, - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->esi()->enabled(true); + }; fragments ~~~~~~~~~ @@ -1359,13 +1365,12 @@ To configure a ``jsonp`` format: .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ - 'request' => [ - 'formats' => [ - 'jsonp' => 'application/javascript', - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->request() + ->format('jsonp', 'application/javascript'); + }; router ~~~~~~ @@ -1720,11 +1725,12 @@ setting the value to ``null``: .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ - 'session' => [ - 'save_path' => null, - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->session() + ->savePath(null); + }; .. _reference-session-metadata-update-threshold: @@ -1774,11 +1780,12 @@ Whether to enable the session support in the framework. .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ - 'session' => [ - 'enabled' => true, - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->session() + ->enabled(true); + }; use_cookies ........... @@ -1830,12 +1837,13 @@ This option allows you to define a base path to be used for assets: .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { // ... - 'assets' => [ - 'base_path' => '/images', - ], - ]); + $framework->assets() + ->basePath('/images'); + }; .. _reference-templating-base-urls: .. _reference-assets-base-urls: @@ -1879,12 +1887,13 @@ collection each time it generates an asset's path: .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { // ... - 'assets' => [ - 'base_urls' => ['http://cdn.example.com/'], - ], - ]); + $framework->assets() + ->baseUrls(['http://cdn.example.com/']); + }; .. _reference-framework-assets-packages: @@ -1928,16 +1937,14 @@ You can group assets into packages, to specify different base URLs for them: .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { // ... - 'assets' => [ - 'packages' => [ - 'avatars' => [ - 'base_urls' => 'http://static_cdn.example.com/avatars', - ], - ], - ], - ]); + $framework->assets() + ->package('avatars') + ->baseUrls(['http://static_cdn.example.com/avatars']); + }; Now you can use the ``avatars`` package in your templates: @@ -2005,12 +2012,13 @@ Now, activate the ``version`` option: .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { // ... - 'assets' => [ - 'version' => 'v2', - ], - ]); + $framework->assets() + ->version('v2'); + }; Now, the same asset will be rendered as ``/images/logo.png?v2`` If you use this feature, you **must** manually increment the ``version`` value @@ -2132,25 +2140,25 @@ individually for each asset package: .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ - 'assets' => [ - 'version_strategy' => 'app.asset.my_versioning_strategy', - 'packages' => [ - 'foo_package' => [ - // this package removes any versioning (its assets won't be versioned) - 'version' => null, - ], - 'bar_package' => [ - // this package uses its own strategy (the default strategy is ignored) - 'version_strategy' => 'app.asset.another_version_strategy', - ], - 'baz_package' => [ - // this package inherits the default strategy - 'base_path' => '/images', - ], - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + // ... + $framework->assets() + ->versionStrategy('app.asset.my_versioning_strategy'); + + $framework->assets()->package('foo_package') + // this package removes any versioning (its assets won't be versioned) + ->version(null); + + $framework->assets()->package('bar_package') + // this package uses its own strategy (the default strategy is ignored) + ->versionStrategy('app.asset.another_version_strategy'); + + $framework->assets()->package('baz_package') + // this package inherits the default strategy + ->basePath('/images'); + }; .. note:: @@ -2229,24 +2237,24 @@ package: .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ - 'assets' => [ + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + // ... + $framework->assets() // this manifest is applied to every asset (including packages) - 'json_manifest_path' => '%kernel.project_dir%/public/build/manifest.json', - // you can use absolute URLs too and Symfony will download them automatically - // 'json_manifest_path' => 'https://cdn.example.com/manifest.json', - 'packages' => [ - 'foo_package' => [ - // this package uses its own manifest (the default file is ignored) - 'json_manifest_path' => '%kernel.project_dir%/public/build/a_different_manifest.json', - ], - 'bar_package' => [ - // this package uses the global manifest (the default file is used) - 'base_path' => '/images', - ], - ], - ], - ]); + ->jsonManifestPath('%kernel.project_dir%/public/build/manifest.json'); + + // you can use absolute URLs too and Symfony will download them automatically + // 'json_manifest_path' => 'https://cdn.example.com/manifest.json', + $framework->assets()->package('foo_package') + // this package uses its own manifest (the default file is ignored) + ->jsonManifestPath('%kernel.project_dir%/public/build/a_different_manifest.json'); + + $framework->assets()->package('bar_package') + // this package uses the global manifest (the default file is used) + ->basePath('/images'); + }; .. versionadded:: 5.1 @@ -2335,11 +2343,12 @@ performance a bit: .. code-block:: php // config/packages/translation.php - $container->loadFromExtension('framework', [ - 'translator' => [ - 'enabled_locales' => ['en', 'es'], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->translator() + ->enabledLocales(['en', 'es']); + }; If some user makes requests with a locale not included in this option, the application won't display any error because Symfony will display contents using @@ -2621,15 +2630,13 @@ the component will look for additional validation files: .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ - 'validation' => [ - 'mapping' => [ - 'paths' => [ - '%kernel.project_dir%/config/validation/', - ], - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->validation() + ->mapping() + ->paths(['%kernel.project_dir%/config/validation/']); + }; annotations ~~~~~~~~~~~ @@ -2927,16 +2934,14 @@ To configure a Redis cache pool with a default lifetime of 1 hour, do the follow .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ - 'cache' => [ - 'pools' => [ - 'cache.mycache' => [ - 'adapter' => 'cache.adapter.redis', - 'default_lifetime' => 3600, - ], - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->cache() + ->pool('cache.mycache') + ->adapters(['cache.adapter.redis']) + ->defaultLifetime(3600); + }; .. _reference-cache-pools-name: @@ -3094,9 +3099,12 @@ A list of lock stores to be created by the framework extension. .. code-block:: php // config/packages/lock.php - $container->loadFromExtension('framework', [ - 'lock' => '%env(LOCK_DSN)%', - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->lock() + ->resource('default', ['%env(LOCK_DSN)%']); + }; .. seealso:: @@ -3281,11 +3289,14 @@ A list of workflows to be created by the framework extension: .. code-block:: php // config/packages/workflow.php - $container->loadFromExtension('framework', [ - 'workflows' => [ - 'my_workflow' => // ... - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->workflows() + ->workflows('my_workflow') + // ... + ; + }; .. seealso:: diff --git a/security.rst b/security.rst index 9368c4464ef..57c306fb26d 100644 --- a/security.rst +++ b/security.rst @@ -680,43 +680,41 @@ and set the ``limiter`` option to its service ID: .. code-block:: php // config/packages/security.php + use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\Security\Http\RateLimiter\DefaultLoginRateLimiter; + use Symfony\Config\FrameworkConfig; + use Symfony\Config\SecurityConfig; + + return static function (ContainerBuilder $container, FrameworkConfig $framework, SecurityConfig $security) { + $framework->rateLimiter() + ->limiter('username_ip_login') + ->policy('token_bucket') + ->limit(5) + ->rate() + ->interval('5 minutes') + ; - $container->loadFromExtension('framework', [ - 'rate_limiter' => [ - // define 2 rate limiters (one for username+IP, the other for IP) - 'username_ip_login' => [ - 'policy' => 'token_bucket', - 'limit' => 5, - 'rate' => [ 'interval' => '5 minutes' ], - ], - 'ip_login' => [ - 'policy' => 'sliding_window', - 'limit' => 50, - 'interval' => '15 minutes', - ], - ], - ]); - - $container->register('app.login_rate_limiter', DefaultLoginRateLimiter::class) - ->setArguments([ - // 1st argument is the limiter for IP - new Reference('limiter.ip_login'), - // 2nd argument is the limiter for username+IP - new Reference('limiter.username_ip_login'), - ]); + $framework->rateLimiter() + ->limiter('ip_login') + ->policy('sliding_window') + ->limit(50) + ->interval('15 minutes') + ; - $container->loadFromExtension('security', [ - 'firewalls' => [ - 'main' => [ - // use a custom rate limiter via its service ID - 'login_throttling' => [ - 'limiter' => 'app.login_rate_limiter', - ], - ], - ], - ]); + $container->register('app.login_rate_limiter', DefaultLoginRateLimiter::class) + ->setArguments([ + // 1st argument is the limiter for IP + new Reference('limiter.ip_login'), + // 2nd argument is the limiter for username+IP + new Reference('limiter.username_ip_login'), + ]); + + $security->firewall('main') + ->loginThrottling() + ->limiter('app.login_rate_limiter') + ; + }; .. _`security-authorization`: .. _denying-access-roles-and-other-authorization: diff --git a/security/csrf.rst b/security/csrf.rst index 51b750b1ceb..8416cd4cafc 100644 --- a/security/csrf.rst +++ b/security/csrf.rst @@ -51,9 +51,13 @@ for more information): .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ - 'csrf_protection' => null, - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->csrfProtection() + ->enabled(true) + ; + }; The tokens used for CSRF protection are meant to be different for every user and they are stored in the session. That's why a session is started automatically as diff --git a/security/remember_me.rst b/security/remember_me.rst index 4f0af428361..2efbd8af94e 100644 --- a/security/remember_me.rst +++ b/security/remember_me.rst @@ -288,7 +288,7 @@ so ``DoctrineTokenProvider`` can store the tokens: .. code-block:: php - # config/packages/doctrine.php + // config/packages/doctrine.php use Symfony\Config\DoctrineConfig; return static function (DoctrineConfig $doctrine) { diff --git a/session/database.rst b/session/database.rst index 58d8e48bab4..5e076329640 100644 --- a/session/database.rst +++ b/session/database.rst @@ -149,14 +149,14 @@ configuration option to tell Symfony to use this service as the session handler: // config/packages/framework.php use Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler; + use Symfony\Config\FrameworkConfig; - // ... - $container->loadFromExtension('framework', [ + return static function (FrameworkConfig $framework) { // ... - 'session' => [ - 'handler_id' => RedisSessionHandler::class, - ], - ]); + $framework->session() + ->handlerId(RedisSessionHandler::class) + ; + }; That's all! Symfony will now use your Redis server to read and write the session data. The main drawback of this solution is that Redis does not perform session @@ -273,14 +273,14 @@ configuration option to tell Symfony to use this service as the session handler: // config/packages/framework.php use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; + use Symfony\Config\FrameworkConfig; - // ... - $container->loadFromExtension('framework', [ + return static function (FrameworkConfig $framework) { // ... - 'session' => [ - 'handler_id' => PdoSessionHandler::class, - ], - ]); + $framework->session() + ->handlerId(PdoSessionHandler::class) + ; + }; Configuring the Session Table and Column Names ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -532,14 +532,14 @@ configuration option to tell Symfony to use this service as the session handler: // config/packages/framework.php use Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler; + use Symfony\Config\FrameworkConfig; - // ... - $container->loadFromExtension('framework', [ + return static function (FrameworkConfig $framework) { // ... - 'session' => [ - 'handler_id' => MongoDbSessionHandler::class, - ], - ]); + $framework->session() + ->handlerId(MongoDbSessionHandler::class) + ; + }; .. note:: diff --git a/session/php_bridge.rst b/session/php_bridge.rst index ba7fc53d41a..a0fbfc8e06b 100644 --- a/session/php_bridge.rst +++ b/session/php_bridge.rst @@ -41,12 +41,14 @@ for the ``handler_id``: .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ - 'session' => [ - 'storage_factory_id' => 'session.storage.factory.php_bridge', - 'handler_id' => null, - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->session() + ->storageFactoryId('session.storage.factory.php_bridge') + ->handlerId(null) + ; + }; Otherwise, if the problem is that you cannot avoid the application starting the session with ``session_start()``, you can still make use of @@ -83,12 +85,14 @@ the example below: .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ - 'session' => [ - 'storage_factory_id' => 'session.storage.factory.php_bridge', - 'handler_id' => 'session.storage.native_file', - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->session() + ->storageFactoryId('session.storage.factory.php_bridge') + ->handlerId('session.storage.native_file') + ; + }; .. note:: diff --git a/session/proxy_examples.rst b/session/proxy_examples.rst index c4c3f9423a3..67d46adb27b 100644 --- a/session/proxy_examples.rst +++ b/session/proxy_examples.rst @@ -46,13 +46,14 @@ Symfony to use your session handler instead of the default one: // config/packages/framework.php use App\Session\CustomSessionHandler; - $container->loadFromExtension('framework', [ + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { // ... - 'session' => [ - // ... - 'handler_id' => CustomSessionHandler::class, - ], - ]); + $framework->session() + ->handlerId(CustomSessionHandler::class) + ; + }; Keep reading the next sections to learn how to use the session handlers in practice to solve two common use cases: encrypt session information and define read-only diff --git a/templating/hinclude.rst b/templating/hinclude.rst index 8172ae60652..3a117148983 100644 --- a/templating/hinclude.rst +++ b/templating/hinclude.rst @@ -60,12 +60,14 @@ default content rendering some template: .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { // ... - 'fragments' => [ - 'hinclude_default_template' => 'hinclude.html.twig', - ], - ]); + $framework->fragments() + ->hincludeDefaultTemplate('hinclude.html.twig') + ; + }; You can define default templates per ``render()`` function (which will override any global default template that is defined): diff --git a/testing/profiling.rst b/testing/profiling.rst index d3fa71f8e76..db7714b9d1f 100644 --- a/testing/profiling.rst +++ b/testing/profiling.rst @@ -47,15 +47,15 @@ tests significantly. That's why Symfony disables it by default: .. code-block:: php // config/packages/test/web_profiler.php + use Symfony\Config\FrameworkConfig; - // ... - $container->loadFromExtension('framework', [ + return static function (FrameworkConfig $framework) { // ... - 'profiler' => [ - 'enabled' => true, - 'collect' => false, - ], - ]); + $framework->profiler() + ->enabled(true) + ->collect(false) + ; + }; Setting ``collect`` to ``true`` enables the profiler for all tests. However, if you need the profiler only in a few tests, you can keep it disabled globally and @@ -71,7 +71,7 @@ provided by the collectors obtained through the ``$client->getProfile()`` call:: // tests/Controller/LuckyControllerTest.php namespace App\Tests\Controller; - + use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; class LuckyControllerTest extends WebTestCase diff --git a/translation.rst b/translation.rst index 670142f7b40..382beed92a0 100644 --- a/translation.rst +++ b/translation.rst @@ -92,11 +92,16 @@ are located: .. code-block:: php // config/packages/translation.php - $container->loadFromExtension('framework', [ - 'default_locale' => 'en', - 'translator' => ['default_path' => '%kernel.project_dir%/translations'], + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { // ... - ]); + $framework + ->defaultLocale('en') + ->translator() + ->defaultPath('%kernel.project_dir%/translations') + ; + }; The locale used in translations is the one stored on the request. This is typically set via a ``_locale`` attribute on your routes (see :ref:`translation-locale-url`). @@ -571,13 +576,13 @@ if you're generating translations with specialized programs or teams. .. code-block:: php // config/packages/translation.php - $container->loadFromExtension('framework', [ - 'translator' => [ - 'paths' => [ - '%kernel.project_dir%/custom/path/to/translations', - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->translator() + ->paths(['%kernel.project_dir%/custom/path/to/translations']) + ; + }; .. note:: @@ -647,10 +652,14 @@ checks translation resources for several locales: .. code-block:: php // config/packages/translation.php - $container->loadFromExtension('framework', [ - 'translator' => ['fallbacks' => ['en']], - // ... - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + // ... + $framework->translator() + ->fallbacks(['en']) + ; + }; .. note:: diff --git a/translation/locale.rst b/translation/locale.rst index 87f973a146a..79a9d45ce39 100644 --- a/translation/locale.rst +++ b/translation/locale.rst @@ -65,7 +65,7 @@ A better policy is to include the locale in the URL using the .. configuration-block:: .. code-block:: php-annotations - + // src/Controller/ContactController.php namespace App\Controller; @@ -177,6 +177,8 @@ the framework: .. code-block:: php // config/packages/translation.php - $container->loadFromExtension('framework', [ - 'default_locale' => 'en', - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->defaultLocale('en'); + }; diff --git a/validation.rst b/validation.rst index 0ad5a3108ec..5f9eab48cc6 100644 --- a/validation.rst +++ b/validation.rst @@ -251,11 +251,13 @@ file: .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ - 'validation' => [ - 'enabled' => true, - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->validation() + ->enabled(true) + ; + }; Besides, if you plan to use annotations to configure validation, replace the previous configuration by the following: @@ -287,11 +289,13 @@ previous configuration by the following: .. code-block:: php // config/packages/framework.php - $container->loadFromExtension('framework', [ - 'validation' => [ - 'enable_annotations' => true, - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $framework->validation() + ->enableAnnotations(true) + ; + }; .. tip:: diff --git a/workflow.rst b/workflow.rst index bd36eb49014..6d65ba18305 100644 --- a/workflow.rst +++ b/workflow.rst @@ -120,43 +120,40 @@ like this: // config/packages/workflow.php use App\Entity\BlogPost; - - $container->loadFromExtension('framework', [ - 'workflows' => [ - 'blog_publishing' => [ - 'type' => 'workflow', // or 'state_machine' - 'audit_trail' => [ - 'enabled' => true - ], - 'marking_store' => [ - 'type' => 'method', - 'property' => 'currentPlace', - ], - 'supports' => [BlogPost::class], - 'initial_marking' => 'draft', - 'places' => [ - 'draft', - 'reviewed', - 'rejected', - 'published', - ], - 'transitions' => [ - 'to_review' => [ - 'from' => 'draft', - 'to' => 'reviewed', - ], - 'publish' => [ - 'from' => 'reviewed', - 'to' => 'published', - ], - 'reject' => [ - 'from' => 'reviewed', - 'to' => 'rejected', - ], - ], - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $blogPublishing = $framework->workflows()->workflows('blog_publishing'); + $blogPublishing + ->type('workflow') // or 'state_machine' + ->supports([BlogPost::class]) + ->initialMarking(['draft']); + + $blogPublishing->auditTrail()->enabled(true); + $blogPublishing->markingStore() + ->type('method') + ->property('currentPlace'); + + $blogPublishing->place()->name('draft'); + $blogPublishing->place()->name('reviewed'); + $blogPublishing->place()->name('rejected'); + $blogPublishing->place()->name('published'); + + $blogPublishing->transition() + ->name('to_review') + ->from(['draft']) + ->to(['reviewed']); + + $blogPublishing->transition() + ->name('publish') + ->from(['reviewed']) + ->to(['published']); + + $blogPublishing->transition() + ->name('reject') + ->from(['reviewed']) + ->to(['rejected']); + }; .. tip:: @@ -553,23 +550,25 @@ to :ref:`Guard events `, which are always fired: .. code-block:: php // config/packages/workflow.php - $container->loadFromExtension('framework', [ + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { // ... - 'workflows' => [ - 'blog_publishing' => [ - // you can pass one or more event names - 'events_to_dispatch' => [ - 'workflow.leave', - 'workflow.completed', - ], - - // pass an empty array to not dispatch any event - 'events_to_dispatch' => [], - - // ... - ], - ], - ]); + + $blogPublishing = $framework->workflows()->workflows('blog_publishing'); + + // ... + // you can pass one or more event names + $blogPublishing->eventsToDispatch([ + 'workflow.leave', + 'workflow.completed', + ]); + + // pass an empty array to not dispatch any event + $blogPublishing->eventsToDispatch([]); + + // ... + }; You can also disable a specific event from being fired when applying a transition:: @@ -731,36 +730,33 @@ transition. The value of this option is any valid expression created with the .. code-block:: php // config/packages/workflow.php - use App\Entity\BlogPost; - - $container->loadFromExtension('framework', [ - 'workflows' => [ - 'blog_publishing' => [ - // ... previous configuration - - 'transitions' => [ - 'to_review' => [ - // the transition is allowed only if the current user has the ROLE_REVIEWER role. - 'guard' => 'is_granted("ROLE_REVIEWER")', - 'from' => 'draft', - 'to' => 'reviewed', - ], - 'publish' => [ - // or "is_anonymous", "is_remember_me", "is_fully_authenticated", "is_granted" - 'guard' => 'is_authenticated', - 'from' => 'reviewed', - 'to' => 'published', - ], - 'reject' => [ - // or any valid expression language with "subject" referring to the post - 'guard' => 'is_granted("ROLE_ADMIN") and subject.isStatusReviewed()', - 'from' => 'reviewed', - 'to' => 'rejected', - ], - ], - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $blogPublishing = $framework->workflows()->workflows('blog_publishing'); + // ... previous configuration + + $blogPublishing->transition() + ->name('to_review') + // the transition is allowed only if the current user has the ROLE_REVIEWER role. + ->guard('is_granted("ROLE_REVIEWER")') + ->from(['draft']) + ->to(['reviewed']); + + $blogPublishing->transition() + ->name('publish') + // or "is_anonymous", "is_remember_me", "is_fully_authenticated", "is_granted" + ->guard('is_authenticated') + ->from(['reviewed']) + ->to(['published']); + + $blogPublishing->transition() + ->name('reject') + // or any valid expression language with "subject" referring to the post + ->guard('is_granted("ROLE_ADMIN") and subject.isStatusReviewed()') + ->from(['reviewed']) + ->to(['rejected']); + }; You can also use transition blockers to block and return a user-friendly error message when you stop a transition from happening. @@ -945,42 +941,43 @@ be only the title of the workflow or very complex objects: .. code-block:: php // config/packages/workflow.php - $container->loadFromExtension('framework', [ + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $blogPublishing = $framework->workflows()->workflows('blog_publishing'); + // ... previous configuration + + $blogPublishing->metadata([ + 'title' => 'Blog Publishing Workflow' + ]); + // ... - 'workflows' => [ - 'blog_publishing' => [ - 'metadata' => [ - 'title' => 'Blog Publishing Workflow', - ], - // ... - 'places' => [ - 'draft' => [ - 'metadata' => [ - 'max_num_of_words' => 500, - ], - ], - // ... - ], - 'transitions' => [ - 'to_review' => [ - 'from' => 'draft', - 'to' => 'review', - 'metadata' => [ - 'priority' => 0.5, - ], - ], - 'publish' => [ - 'from' => 'reviewed', - 'to' => 'published', - 'metadata' => [ - 'hour_limit' => 20, - 'explanation' => 'You can not publish after 8 PM.', - ], - ], - ], - ], - ], - ]); + + $blogPublishing->place() + ->name('draft') + ->metadata([ + 'max_num_of_words' => 500, + ]); + + // ... + + $blogPublishing->transition() + ->name('to_review') + ->from(['draft']) + ->to(['reviewed']) + ->metadata([ + 'priority' => 0.5, + ]); + + $blogPublishing->transition() + ->name('publish') + ->from(['reviewed']) + ->to(['published']) + ->metadata([ + 'hour_limit' => 20, + 'explanation' => 'You can not publish after 8 PM.', + ]); + }; Then you can access this metadata in your controller as follows:: diff --git a/workflow/dumping-workflows.rst b/workflow/dumping-workflows.rst index 5dda2ba6c56..98e5911561f 100644 --- a/workflow/dumping-workflows.rst +++ b/workflow/dumping-workflows.rst @@ -251,75 +251,70 @@ Below is the configuration for the pull request state machine with styling added .. code-block:: php // config/packages/workflow.php - $container->loadFromExtension('framework', [ + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { // ... - 'workflows' => [ - 'pull_request' => [ - 'type' => 'state_machine', - 'marking_store' => [ - 'type' => 'method', - 'property' => 'currentPlace', - ], - 'supports' => ['App\Entity\PullRequest'], - 'initial_marking' => 'start', - 'places' => [ - 'start', - 'coding', - 'test', - 'review' => [ - 'metadata' => [ - 'description' => 'Human review', - ], - ], - 'merged', - 'closed' => [ - 'metadata' => [ - 'bg_color' => 'DeepSkyBlue', - ], - ], - ], - 'transitions' => [ - 'submit'=> [ - 'from' => 'start', - 'to' => 'test', - ], - 'update'=> [ - 'from' => ['coding', 'test', 'review'], - 'to' => 'test', - 'metadata' => [ - 'arrow_color' => 'Turquoise', - ], - ], - 'wait_for_review'=> [ - 'from' => 'test', - 'to' => 'review', - 'metadata' => [ - 'color' => 'Orange', - ], - ], - 'request_change'=> [ - 'from' => 'review', - 'to' => 'coding', - ], - 'accept'=> [ - 'from' => 'review', - 'to' => 'merged', - 'metadata' => [ - 'label' => 'Accept PR', - ], - ], - 'reject'=> [ - 'from' => 'review', - 'to' => 'closed', - ], - 'reopen'=> [ - 'from' => 'start', - 'to' => 'review', - ], - ], - ], - ], - ]); + $pullRequest = $framework->workflows()->workflows('pull_request'); + + $pullRequest + ->type('state_machine') + ->supports(['App\Entity\PullRequest']) + ->initialMarking(['start']); + + $pullRequest->markingStore() + ->type('method') + ->property('currentPlace'); + + $pullRequest->place()->name('start'); + $pullRequest->place()->name('coding'); + $pullRequest->place()->name('test'); + $pullRequest->place() + ->name('review') + ->metadata(['description' => 'Human review']); + $pullRequest->place()->name('merged'); + $pullRequest->place() + ->name('closed') + ->metadata(['bg_color' => 'DeepSkyBlue',]); + + $pullRequest->transition() + ->name('submit') + ->from(['start']) + ->to(['test']); + + $pullRequest->transition() + ->name('update') + ->from(['coding', 'test', 'review']) + ->to(['test']) + ->metadata(['arrow_color' => 'Turquoise']); + + $pullRequest->transition() + ->name('wait_for_review') + ->from(['test']) + ->to(['review']) + ->metadata(['color' => 'Orange']); + + $pullRequest->transition() + ->name('request_change') + ->from(['review']) + ->to(['coding']); + + $pullRequest->transition() + ->name('accept') + ->from(['review']) + ->to(['merged']) + ->metadata(['label' => 'Accept PR']); + + $pullRequest->transition() + ->name('reject') + ->from(['review']) + ->to(['closed']); + + $pullRequest->transition() + ->name('accept') + ->from(['closed']) + ->to(['review']); + }; The PlantUML image will look like this: diff --git a/workflow/workflow-and-state-machine.rst b/workflow/workflow-and-state-machine.rst index 730cf66bccc..6ef73aa60cf 100644 --- a/workflow/workflow-and-state-machine.rst +++ b/workflow/workflow-and-state-machine.rst @@ -192,58 +192,62 @@ Below is the configuration for the pull request state machine. .. code-block:: php // config/packages/workflow.php - $container->loadFromExtension('framework', [ - // ... - 'workflows' => [ - 'pull_request' => [ - 'type' => 'state_machine', - 'marking_store' => [ - 'type' => 'method', - 'property' => 'currentPlace', - ], - 'supports' => ['App\Entity\PullRequest'], - 'initial_marking' => 'start', - 'places' => [ - 'start', - 'coding', - 'test', - 'review', - 'merged', - 'closed', - ], - 'transitions' => [ - 'submit'=> [ - 'from' => 'start', - 'to' => 'test', - ], - 'update'=> [ - 'from' => ['coding', 'test', 'review'], - 'to' => 'test', - ], - 'wait_for_review'=> [ - 'from' => 'test', - 'to' => 'review', - ], - 'request_change'=> [ - 'from' => 'review', - 'to' => 'coding', - ], - 'accept'=> [ - 'from' => 'review', - 'to' => 'merged', - ], - 'reject'=> [ - 'from' => 'review', - 'to' => 'closed', - ], - 'reopen'=> [ - 'from' => 'start', - 'to' => 'review', - ], - ], - ], - ], - ]); + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + $pullRequest = $framework->workflows()->workflows('pull_request'); + + $pullRequest + ->type('state_machine') + ->supports(['App\Entity\PullRequest']) + ->initialMarking(['start']); + + $pullRequest->markingStore() + ->type('method') + ->property('currentPlace'); + + $pullRequest->place()->name('start'); + $pullRequest->place()->name('coding'); + $pullRequest->place()->name('test'); + $pullRequest->place()->name('review'); + $pullRequest->place()->name('merged'); + $pullRequest->place()->name('closed'); + + $pullRequest->transition() + ->name('submit') + ->from(['start']) + ->to(['test']); + + $pullRequest->transition() + ->name('update') + ->from(['coding', 'test', 'review']) + ->to(['test']); + + $pullRequest->transition() + ->name('wait_for_review') + ->from(['test']) + ->to(['review']); + + $pullRequest->transition() + ->name('request_change') + ->from(['review']) + ->to(['coding']); + + $pullRequest->transition() + ->name('accept') + ->from(['review']) + ->to(['merged']); + + $pullRequest->transition() + ->name('reject') + ->from(['review']) + ->to(['closed']); + + $pullRequest->transition() + ->name('accept') + ->from(['closed']) + ->to(['review']); + }; In a Symfony application using the :ref:`default services.yaml configuration `,