diff --git a/.doctor-rst.yaml b/.doctor-rst.yaml index eb18e3f1254..0768119d255 100644 --- a/.doctor-rst.yaml +++ b/.doctor-rst.yaml @@ -1,9 +1,5 @@ rules: american_english: ~ - argument_variable_must_match_type: - arguments: - - { type: 'ContainerBuilder', name: 'container' } - - { type: 'ContainerConfigurator', name: 'container' } avoid_repetetive_words: ~ blank_line_after_anchor: ~ blank_line_after_directive: ~ @@ -15,6 +11,7 @@ rules: ensure_correct_format_for_phpfunction: ~ ensure_exactly_one_space_before_directive_type: ~ ensure_exactly_one_space_between_link_definition_and_link: ~ + ensure_explicit_nullable_types: ~ ensure_github_directive_start_with_prefix: prefix: 'Symfony' ensure_link_bottom: ~ @@ -26,17 +23,21 @@ rules: forbidden_directives: directives: - '.. index::' + - directive: '.. caution::' + replacements: ['.. warning::', '.. danger::'] indention: ~ lowercase_as_in_use_statements: ~ max_blank_lines: max: 2 max_colons: ~ no_app_console: ~ + no_attribute_redundant_parenthesis: ~ no_blank_line_after_filepath_in_php_code_block: ~ no_blank_line_after_filepath_in_twig_code_block: ~ no_blank_line_after_filepath_in_xml_code_block: ~ no_blank_line_after_filepath_in_yaml_code_block: ~ no_brackets_in_method_directive: ~ + no_broken_ref_directive: ~ no_composer_req: ~ no_directive_after_shorthand: ~ no_duplicate_use_statements: ~ @@ -47,6 +48,7 @@ rules: no_namespace_after_use_statements: ~ no_php_open_tag_in_code_block_php_directive: ~ no_space_before_self_xml_closing_tag: ~ + non_static_phpunit_assertions: ~ only_backslashes_in_namespace_in_php_code_block: ~ only_backslashes_in_use_statements_in_php_code_block: ~ ordered_use_statements: ~ @@ -100,7 +102,7 @@ whitelist: - '#. The most important config file is ``app/config/services.yml``, which now is' - 'The bin/console Command' - '.. _`LDAP injection`: http://projects.webappsec.org/w/page/13246947/LDAP%20Injection' - - '.. versionadded:: 2.7.2' # Doctrine + - '.. versionadded:: 2.8.0' # Doctrine - '.. versionadded:: 1.9.0' # Encore - '.. versionadded:: 1.18' # Flex in setup/upgrade_minor.rst - '.. versionadded:: 1.0.0' # Encore @@ -112,5 +114,7 @@ whitelist: - '.. versionadded:: 3.6' # MonologBundle - '.. versionadded:: 3.8' # MonologBundle - '.. versionadded:: 3.5' # Monolog + - '.. versionadded:: 3.0' # Doctrine ORM - '.. _`a feature to test applications using Mercure`: https://github.com/symfony/panther#creating-isolated-browsers-to-test-apps-using-mercure-or-websocket' - 'End to End Tests (E2E)' + - '.. versionadded:: 2.2.0' # Panther diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 091096d74c8..061b0bb85b0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -21,12 +21,12 @@ jobs: steps: - name: "Checkout" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Set-up PHP" uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: 8.2 coverage: none tools: "composer:v2" @@ -57,7 +57,7 @@ jobs: steps: - name: "Checkout" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Create cache dir" run: mkdir .cache @@ -73,71 +73,74 @@ jobs: key: ${{ runner.os }}-doctor-rst-${{ steps.extract_base_branch.outputs.branch }} - name: "Run DOCtor-RST" - uses: docker://oskarstark/doctor-rst:1.59.0 + uses: docker://oskarstark/doctor-rst:1.64.0 with: args: --short --error-format=github --cache-file=/github/workspace/.cache/doctor-rst.cache symfony-code-block-checker: name: Code Blocks + runs-on: ubuntu-latest + continue-on-error: true + steps: - - name: Checkout code - uses: actions/checkout@v3 - with: - path: 'docs' - - - name: Set-up PHP - uses: shivammathur/setup-php@v2 - with: - php-version: 8.2 - coverage: none - - - name: Fetch branch from where the PR started - working-directory: docs - run: git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/* - - - name: Find modified files - id: find-files - working-directory: docs - run: echo "files=$(git diff --name-only origin/${{ github.base_ref }} HEAD | grep ".rst" | tr '\n' ' ')" >> $GITHUB_OUTPUT - - - name: Get composer cache directory - id: composercache - working-directory: docs/_build - run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - - name: Cache dependencies - if: ${{ steps.find-files.outputs.files }} - uses: actions/cache@v3 - with: - path: ${{ steps.composercache.outputs.dir }} - key: ${{ runner.os }}-composer-codeBlocks-${{ hashFiles('_checker/composer.lock', '_sf_app/composer.lock') }} - restore-keys: ${{ runner.os }}-composer-codeBlocks- - - - name: Install dependencies - if: ${{ steps.find-files.outputs.files }} - run: composer create-project symfony-tools/code-block-checker:@dev _checker - - - name: Install test application - if: ${{ steps.find-files.outputs.files }} - run: | - git clone -b ${{ github.base_ref }} --depth 5 --single-branch https://github.com/symfony-tools/symfony-application.git _sf_app - cd _sf_app - composer update - - - name: Generate baseline - if: ${{ steps.find-files.outputs.files }} - working-directory: docs - run: | - CURRENT=$(git rev-parse HEAD) - git checkout -m ${{ github.base_ref }} - ../_checker/code-block-checker.php verify:docs `pwd` ${{ steps.find-files.outputs.files }} --generate-baseline=baseline.json --symfony-application=`realpath ../_sf_app` - git checkout -m $CURRENT - cat baseline.json - - - name: Verify examples - if: ${{ steps.find-files.outputs.files }} - working-directory: docs - run: | - ../_checker/code-block-checker.php verify:docs `pwd` ${{ steps.find-files.outputs.files }} --baseline=baseline.json --output-format=github --symfony-application=`realpath ../_sf_app` + - name: Checkout code + uses: actions/checkout@v4 + with: + path: 'docs' + + - name: Set-up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.2 + coverage: none + + - name: Fetch branch from where the PR started + working-directory: docs + run: git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/* + + - name: Find modified files + id: find-files + working-directory: docs + run: echo "files=$(git diff --name-only origin/${{ github.base_ref }} HEAD | grep ".rst" | tr '\n' ' ')" >> $GITHUB_OUTPUT + + - name: Get composer cache directory + id: composercache + working-directory: docs/_build + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Cache dependencies + if: ${{ steps.find-files.outputs.files }} + uses: actions/cache@v3 + with: + path: ${{ steps.composercache.outputs.dir }} + key: ${{ runner.os }}-composer-codeBlocks-${{ hashFiles('_checker/composer.lock', '_sf_app/composer.lock') }} + restore-keys: ${{ runner.os }}-composer-codeBlocks- + + - name: Install dependencies + if: ${{ steps.find-files.outputs.files }} + run: composer create-project symfony-tools/code-block-checker:@dev _checker + + - name: Install test application + if: ${{ steps.find-files.outputs.files }} + run: | + git clone -b ${{ github.base_ref }} --depth 5 --single-branch https://github.com/symfony-tools/symfony-application.git _sf_app + cd _sf_app + composer update + + - name: Generate baseline + if: ${{ steps.find-files.outputs.files }} + working-directory: docs + run: | + CURRENT=$(git rev-parse HEAD) + git checkout -m ${{ github.base_ref }} + ../_checker/code-block-checker.php verify:docs `pwd` ${{ steps.find-files.outputs.files }} --generate-baseline=baseline.json --symfony-application=`realpath ../_sf_app` + git checkout -m $CURRENT + cat baseline.json + + - name: Verify examples + if: ${{ steps.find-files.outputs.files }} + working-directory: docs + run: | + ../_checker/code-block-checker.php verify:docs `pwd` ${{ steps.find-files.outputs.files }} --baseline=baseline.json --output-format=github --symfony-application=`realpath ../_sf_app` diff --git a/README.md b/README.md index ed323a8ee83..5c063058c02 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,8 @@ We love contributors! For more information on how you can contribute, please rea the [Symfony Docs Contributing Guide](https://symfony.com/doc/current/contributing/documentation/overview.html). > [!IMPORTANT] -> Use `5.4` branch as the base of your pull requests, unless you are documenting a -> feature that was introduced *after* Symfony 5.4 (e.g. in Symfony 7.1). +> Use `6.4` branch as the base of your pull requests, unless you are documenting a +> feature that was introduced *after* Symfony 6.4 (e.g. in Symfony 7.2). Build Documentation Locally --------------------------- diff --git a/_build/redirection_map b/_build/redirection_map index f7c1f65033a..1701f4a8f70 100644 --- a/_build/redirection_map +++ b/_build/redirection_map @@ -525,8 +525,7 @@ /testing/functional_tests_assertions /testing#testing-application-assertions /components https://symfony.com/components /components/index https://symfony.com/components -/serializer/normalizers /components/serializer#normalizers -/components/serializer#component-serializer-attributes-groups-annotations /components/serializer#component-serializer-attributes-groups-attributes +/serializer/normalizers /serializer#serializer-built-in-normalizers /logging/monolog_regex_based_excludes /logging/monolog_exclude_http_codes /security/named_encoders /security/named_hashers /components/inflector /string#inflector @@ -568,3 +567,9 @@ /messenger/multiple_buses /messenger#messenger-multiple-buses /frontend/encore/server-data /frontend/server-data /components/string /string +/testing/http_authentication /testing#testing_logging_in_users +/doctrine/registration_form /security#security-make-registration-form +/form/form_dependencies /form/create_custom_field_type +/doctrine/reverse_engineering /doctrine#doctrine-adding-mapping +/components/serializer /serializer +/serializer/custom_encoder /serializer/encoders#serializer-custom-encoder diff --git a/_build/spelling_word_list.txt b/_build/spelling_word_list.txt deleted file mode 100644 index fa05ce9430e..00000000000 --- a/_build/spelling_word_list.txt +++ /dev/null @@ -1,344 +0,0 @@ -accessor -Akamai -analytics -Ansi -Ansible -async -authenticator -authenticators -autocompleted -autocompletion -autoconfiguration -autoconfigure -autoconfigured -autoconfigures -autoconfiguring -autoload -autoloaded -autoloader -autoloaders -autoloading -autoprefixing -autowire -autowireable -autowired -autowiring -backend -backends -balancer -balancers -bcrypt -benchmarking -Bitbucket -bitmask -bitmasks -bitwise -Blackfire -boolean -booleans -Brasseur -browserslist -buildpack -buildpacks -bundler -cacheable -Caddy -callables -camelCase -casted -changelog -changeset -charset -charsets -checkboxes -classmap -classname -clearers -cloner -cloners -codebase -config -configs -configurator -configurators -contrib -cron -cronjobs -cryptographic -cryptographically -Ctrl -ctype -cURL -customizable -customizations -Cygwin -dataset -datepicker -decrypt -denormalization -denormalize -denormalized -denormalizing -deprecations -deserialization -deserialize -deserialized -deserializing -destructor -dev -dn -DNS -docblock -Dotenv -downloader -Doxygen -DSN -Dunglas -easter -Eberlei -emilie -enctype -entrypoints -enum -env -escaper -escpaer -extensibility -extractable -eZPublish -Fabien -failover -filesystem -filesystems -formatter -formatters -frontend -getter -getters -GitHub -gmail -Gmail -Goutte -grapheme -hardcode -hardcoded -hardcodes -hardcoding -hasser -hassers -headshot -HInclude -hostname -https -iconv -igbinary -incrementing -ini -inlined -inlining -installable -instantiation -interoperable -intl -Intl -invokable -IPv -isser -issers -Jpegoptim -jQuery -js -Karlton -kb -kB -Kévin -Ki -KiB -kibibyte -Kubernetes -Kudu -labelled -latin -Ldap -libketama -licensor -lifecycle -liip -linter -localhost -Loggly -Logplex -lookups -loopback -lorenzo -Luhn -macOS -matcher -matchers -mbstring -mebibyte -memcache -memcached -MiB -michelle -minification -minified -minifier -minifies -minify -minifying -misconfiguration -misconfigured -misgendering -Monolog -mutator -nagle -namespace -namespaced -namespaces -namespacing -natively -nd -netmasks -nginx -normalizer -normalizers -npm -nyholm -OAuth -OPcache -overcomplicate -Packagist -parallelizes -parsers -PHP -PHPUnit -PID -plaintext -polyfill -polyfills -postcss -Potencier -pre -preconfigured -predefines -Predis -preload -preloaded -preloading -prepend -prepended -prepending -prepends -preprocessed -preprocessors -Procfile -profiler -programmatically -prototyped -rebase -reconfiguring -reconnection -redirections -refactorization -regexes -renderer -resolvers -responder -reStructuredText -reusability -runtime -sandboxing -schemas -screencast -semantical -serializable -serializer -sexualized -Silex -sluggable -socio -specificities -SQLite -stacktrace -stacktraces -storages -stringified -stylesheet -stylesheets -subclasses -subdirectories -subdirectory -sublcasses -sublicense -sublincense -subrequests -subtree -superclass -superglobal -superglobals -symfony -Symfony -symlink -symlinks -syntaxes -templating -testability -th -theming -throbber -timestampable -timezones -TLS -tmpfs -tobias -todo -Tomayko -Toolbelt -tooltip -Traversable -triaging -UI -uid -unary -unauthenticate -uncacheable -uncached -uncomment -uncommented -undelete -unhandled -unicode -Unix -unmapped -unminified -unported -unregister -unrendered -unserialize -unserialized -unserializing -unsubmitted -untracked -uploader -URI -validator -validators -variadic -VirtualBox -Vue -webpack -webpacked -webpackJsonp -webserver -whitespace -whitespaces -woh -Wordpress -Xdebug -xkcd -Xliff -XML -XPath -yaml -yay diff --git a/_images/components/serializer/serializer_workflow.svg b/_images/serializer/serializer_workflow.svg similarity index 100% rename from _images/components/serializer/serializer_workflow.svg rename to _images/serializer/serializer_workflow.svg diff --git a/_images/sources/components/serializer/serializer_workflow.dia b/_images/sources/serializer/serializer_workflow.dia similarity index 100% rename from _images/sources/components/serializer/serializer_workflow.dia rename to _images/sources/serializer/serializer_workflow.dia diff --git a/best_practices.rst b/best_practices.rst index afc72774ad9..2c393cae9c6 100644 --- a/best_practices.rst +++ b/best_practices.rst @@ -51,6 +51,7 @@ self-explanatory and not coupled to Symfony: │ └─ console ├─ config/ │ ├─ packages/ + │ ├─ routes/ │ └─ services.yaml ├─ migrations/ ├─ public/ @@ -108,6 +109,10 @@ Define these options as :ref:`parameters ` in the :ref:`environment ` in the ``config/services_dev.yaml`` and ``config/services_prod.yaml`` files. +Unless the application configuration is reused multiple times and needs +rigid validation, do *not* use the :doc:`Config component ` +to define the options. + Use Short and Prefixed Parameter Names ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -155,6 +160,8 @@ values is that it's complicated to redefine their values in your tests. Business Logic -------------- +.. _best-practice-no-application-bundles: + Don't Create any Bundle to Organize your Application Logic ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/bundles.rst b/bundles.rst index c2bc5777d4f..878bee3af4a 100644 --- a/bundles.rst +++ b/bundles.rst @@ -3,10 +3,10 @@ The Bundle System ================= -.. caution:: +.. warning:: In Symfony versions prior to 4.0, it was recommended to organize your own - application code using bundles. This is no longer recommended and bundles + application code using bundles. This is :ref:`no longer recommended ` and bundles should only be used to share code and features between multiple applications. A bundle is similar to a plugin in other software, but even better. The core @@ -58,7 +58,7 @@ Start by creating a new class called ``AcmeBlogBundle``:: { } -.. caution:: +.. warning:: If your bundle must be compatible with previous Symfony versions you have to extend from the :class:`Symfony\\Component\\HttpKernel\\Bundle\\Bundle` instead. @@ -82,6 +82,8 @@ of the bundle. Now that you've created the bundle, enable it:: And while it doesn't do anything yet, AcmeBlogBundle is now ready to be used. +.. _bundles-directory-structure: + Bundle Directory Structure -------------------------- @@ -114,7 +116,9 @@ to be adjusted if needed: ``translations/`` Holds translations organized by domain and locale (e.g. ``AcmeBlogBundle.en.xlf``). -.. caution:: +.. _bundles-legacy-directory-structure: + +.. warning:: The recommended bundle structure was changed in Symfony 5, read the `Symfony 4.4 bundle documentation`_ for information about the old diff --git a/bundles/best_practices.rst b/bundles/best_practices.rst index 0cdf4ecb2b9..376984388db 100644 --- a/bundles/best_practices.rst +++ b/bundles/best_practices.rst @@ -78,16 +78,22 @@ The following is the recommended directory structure of an AcmeBlogBundle: ├── LICENSE └── README.md -This directory structure requires to configure the bundle path to its root -directory as follows:: +.. note:: + + This directory structure is used by default when your bundle class extends + the recommended :class:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle`. + If your bundle extends the :class:`Symfony\\Component\\HttpKernel\\Bundle\\Bundle` + class, you have to override the ``getPath()`` method as follows:: - class AcmeBlogBundle extends Bundle - { - public function getPath(): string + use Symfony\Component\HttpKernel\Bundle\Bundle; + + class AcmeBlogBundle extends Bundle { - return \dirname(__DIR__); + public function getPath(): string + { + return \dirname(__DIR__); + } } - } **The following files are mandatory**, because they ensure a structure convention that automated tools can rely on: @@ -240,7 +246,7 @@ with Symfony Flex to install a specific Symfony version: # recommended to have a better output and faster download time) composer update --prefer-dist --no-progress -.. caution:: +.. warning:: If you want to cache your Composer dependencies, **do not** cache the ``vendor/`` directory as this has side-effects. Instead cache diff --git a/bundles/configuration.rst b/bundles/configuration.rst index 6596512a5ef..dedfada2ea2 100644 --- a/bundles/configuration.rst +++ b/bundles/configuration.rst @@ -46,11 +46,110 @@ as integration of other related components: $framework->form()->enabled(true); }; +There are two different ways of creating friendly configuration for a bundle: + +#. :ref:`Using the main bundle class `: + this is recommended for new bundles and for bundles following the + :ref:`recommended directory structure `; +#. :ref:`Using the Bundle extension class `: + this was the traditional way of doing it, but nowadays it's only recommended for + bundles following the :ref:`legacy directory structure `. + +.. _using-the-bundle-class: +.. _bundle-friendly-config-bundle-class: + +Using the AbstractBundle Class +------------------------------ + +In bundles extending the :class:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle` +class, you can add all the logic related to processing the configuration in that class:: + + // src/AcmeSocialBundle.php + namespace Acme\SocialBundle; + + use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; + use Symfony\Component\DependencyInjection\ContainerBuilder; + use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; + use Symfony\Component\HttpKernel\Bundle\AbstractBundle; + + class AcmeSocialBundle extends AbstractBundle + { + public function configure(DefinitionConfigurator $definition): void + { + $definition->rootNode() + ->children() + ->arrayNode('twitter') + ->children() + ->integerNode('client_id')->end() + ->scalarNode('client_secret')->end() + ->end() + ->end() // twitter + ->end() + ; + } + + public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void + { + // the "$config" variable is already merged and processed so you can + // use it directly to configure the service container (when defining an + // extension class, you also have to do this merging and processing) + $container->services() + ->get('acme_social.twitter_client') + ->arg(0, $config['twitter']['client_id']) + ->arg(1, $config['twitter']['client_secret']) + ; + } + } + +.. note:: + + The ``configure()`` and ``loadExtension()`` methods are called only at compile time. + +.. tip:: + + The ``AbstractBundle::configure()`` method also allows to import the + configuration definition from one or more files:: + + // src/AcmeSocialBundle.php + namespace Acme\SocialBundle; + + // ... + class AcmeSocialBundle extends AbstractBundle + { + public function configure(DefinitionConfigurator $definition): void + { + $definition->import('../config/definition.php'); + // you can also use glob patterns + //$definition->import('../config/definition/*.php'); + } + + // ... + } + + .. code-block:: php + + // config/definition.php + use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; + + return static function (DefinitionConfigurator $definition): void { + $definition->rootNode() + ->children() + ->scalarNode('foo')->defaultValue('bar')->end() + ->end() + ; + }; + +.. _bundle-friendly-config-extension: + Using the Bundle Extension -------------------------- +This is the traditional way of creating friendly configuration for bundles. For new +bundles it's recommended to :ref:`use the main bundle class `, +but the traditional way of creating an extension class still works. + Imagine you are creating a new bundle - AcmeSocialBundle - which provides -integration with Twitter. To make your bundle configurable to the user, you +integration with X/Twitter. To make your bundle configurable to the user, you can add some configuration that looks like this: .. configuration-block:: @@ -110,7 +209,7 @@ load correct services and parameters inside an "Extension" class. If a bundle provides an Extension class, then you should *not* generally override any service container parameters from that bundle. The idea - is that if an Extension class is present, every setting that should be + is that if an extension class is present, every setting that should be configurable should be present in the configuration made available by that class. In other words, the extension class defines all the public configuration settings for which backward compatibility will be maintained. @@ -244,7 +343,7 @@ For example, imagine your bundle has the following example config: https://symfony.com/schema/dic/services/services-1.0.xsd" > - + @@ -267,7 +366,7 @@ In your extension, you can load this and dynamically set its arguments:: $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); - $definition = $container->getDefinition('acme.social.twitter_client'); + $definition = $container->getDefinition('acme_social.twitter_client'); $definition->replaceArgument(0, $config['twitter']['client_id']); $definition->replaceArgument(1, $config['twitter']['client_secret']); } @@ -315,90 +414,6 @@ In your extension, you can load this and dynamically set its arguments:: // ... now use the flat $config array } -.. _using-the-bundle-class: - -Using the AbstractBundle Class ------------------------------- - -As an alternative, instead of creating an extension and configuration class as -shown in the previous section, you can also extend -:class:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle` to add this -logic to the bundle class directly:: - - // src/AcmeSocialBundle.php - namespace Acme\SocialBundle; - - use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; - use Symfony\Component\DependencyInjection\ContainerBuilder; - use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; - use Symfony\Component\HttpKernel\Bundle\AbstractBundle; - - class AcmeSocialBundle extends AbstractBundle - { - public function configure(DefinitionConfigurator $definition): void - { - $definition->rootNode() - ->children() - ->arrayNode('twitter') - ->children() - ->integerNode('client_id')->end() - ->scalarNode('client_secret')->end() - ->end() - ->end() // twitter - ->end() - ; - } - - public function loadExtension(array $config, ContainerConfigurator $containerConfigurator, ContainerBuilder $containerBuilder): void - { - // Contrary to the Extension class, the "$config" variable is already merged - // and processed. You can use it directly to configure the service container. - $containerConfigurator->services() - ->get('acme.social.twitter_client') - ->arg(0, $config['twitter']['client_id']) - ->arg(1, $config['twitter']['client_secret']) - ; - } - } - -.. note:: - - The ``configure()`` and ``loadExtension()`` methods are called only at compile time. - -.. tip:: - - The ``AbstractBundle::configure()`` method also allows to import the - configuration definition from one or more files:: - - // src/AcmeSocialBundle.php - namespace Acme\SocialBundle; - - // ... - class AcmeSocialBundle extends AbstractBundle - { - public function configure(DefinitionConfigurator $definition): void - { - $definition->import('../config/definition.php'); - // you can also use glob patterns - //$definition->import('../config/definition/*.php'); - } - - // ... - } - - .. code-block:: php - - // config/definition.php - use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; - - return static function (DefinitionConfigurator $definition): void { - $definition->rootNode() - ->children() - ->scalarNode('foo')->defaultValue('bar')->end() - ->end() - ; - }; - Modifying the Configuration of Another Bundle --------------------------------------------- diff --git a/bundles/extension.rst b/bundles/extension.rst index 4e2fe233966..0537eb00c3e 100644 --- a/bundles/extension.rst +++ b/bundles/extension.rst @@ -6,12 +6,73 @@ file used by the application but in the bundles themselves. This article explains how to create and load service files using the bundle directory structure. +There are two different ways of doing it: + +#. :ref:`Load your services in the main bundle class `: + this is recommended for new bundles and for bundles following the + :ref:`recommended directory structure `; +#. :ref:`Create an extension class to load the service configuration files `: + this was the traditional way of doing it, but nowadays it's only recommended for + bundles following the :ref:`legacy directory structure `. + +.. _bundle-load-services-bundle-class: + +Loading Services Directly in your Bundle Class +---------------------------------------------- + +In bundles extending the :class:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle` +class, you can define the :method:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle::loadExtension` +method to load service definitions from configuration files:: + + // ... + use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; + use Symfony\Component\HttpKernel\Bundle\AbstractBundle; + + class AcmeHelloBundle extends AbstractBundle + { + public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void + { + // load an XML, PHP or YAML file + $container->import('../config/services.xml'); + + // you can also add or replace parameters and services + $container->parameters() + ->set('acme_hello.phrase', $config['phrase']) + ; + + if ($config['scream']) { + $container->services() + ->get('acme_hello.printer') + ->class(ScreamingPrinter::class) + ; + } + } + } + +This method works similar to the ``Extension::load()`` method explained below, +but it uses a new simpler API to define and import service configuration. + +.. note:: + + Contrary to the ``$configs`` parameter in ``Extension::load()``, the + ``$config`` parameter is already merged and processed by the + ``AbstractBundle``. + +.. note:: + + The ``loadExtension()`` is called only at compile time. + +.. _bundle-load-services-extension: + Creating an Extension Class --------------------------- -In order to load service configuration, you have to create a Dependency -Injection (DI) Extension for your bundle. By default, the Extension class must -follow these conventions (but later you'll learn how to skip them if needed): +This is the traditional way of loading service definitions in bundles. For new +bundles it's recommended to :ref:`load your services in the main bundle class `, +but the traditional way of creating an extension class still works. + +A dependency injection extension is defined as a class that follows these +conventions (later you'll learn how to skip them if needed): * It has to live in the ``DependencyInjection`` namespace of the bundle; @@ -20,7 +81,7 @@ follow these conventions (but later you'll learn how to skip them if needed): :class:`Symfony\\Component\\DependencyInjection\\Extension\\Extension` class; * The name is equal to the bundle name with the ``Bundle`` suffix replaced by - ``Extension`` (e.g. the Extension class of the AcmeBundle would be called + ``Extension`` (e.g. the extension class of the AcmeBundle would be called ``AcmeExtension`` and the one for AcmeHelloBundle would be called ``AcmeHelloExtension``). @@ -70,7 +131,7 @@ class name to underscores (e.g. ``AcmeHelloExtension``'s DI alias is ``acme_hello``). Using the ``load()`` Method ---------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~ In the ``load()`` method, all services and parameters related to this extension will be loaded. This method doesn't get the actual container instance, but a @@ -108,53 +169,6 @@ The Extension is also the class that handles the configuration for that particular bundle (e.g. the configuration in ``config/packages/.yaml``). To read more about it, see the ":doc:`/bundles/configuration`" article. -Loading Services directly in your Bundle class ----------------------------------------------- - -Alternatively, you can define and load services configuration directly in a -bundle class instead of creating a specific ``Extension`` class. You can do -this by extending from :class:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle` -and defining the :method:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle::loadExtension` -method:: - - // ... - use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; - use Symfony\Component\HttpKernel\Bundle\AbstractBundle; - - class AcmeHelloBundle extends AbstractBundle - { - public function loadExtension(array $config, ContainerConfigurator $containerConfigurator, ContainerBuilder $containerBuilder): void - { - // load an XML, PHP or Yaml file - $containerConfigurator->import('../config/services.xml'); - - // you can also add or replace parameters and services - $containerConfigurator->parameters() - ->set('acme_hello.phrase', $config['phrase']) - ; - - if ($config['scream']) { - $containerConfigurator->services() - ->get('acme_hello.printer') - ->class(ScreamingPrinter::class) - ; - } - } - } - -This method works similar to the ``Extension::load()`` method, but it uses -a new API to define and import service configuration. - -.. note:: - - Contrary to the ``$configs`` parameter in ``Extension::load()``, the - ``$config`` parameter is already merged and processed by the - ``AbstractBundle``. - -.. note:: - - The ``loadExtension()`` is called only at compile time. - Adding Classes to Compile ------------------------- @@ -186,7 +200,7 @@ Patterns are transformed into the actual class namespaces using the classmap generated by Composer. Therefore, before using these patterns, you must generate the full classmap executing the ``dump-autoload`` command of Composer. -.. caution:: +.. warning:: This technique can't be used when the classes to compile use the ``__DIR__`` or ``__FILE__`` constants, because their values will change when loading diff --git a/bundles/override.rst b/bundles/override.rst index 36aea69b231..f25bd785373 100644 --- a/bundles/override.rst +++ b/bundles/override.rst @@ -19,7 +19,7 @@ For example, to override the ``templates/registration/confirmed.html.twig`` template from the AcmeUserBundle, create this template: ``/templates/bundles/AcmeUserBundle/registration/confirmed.html.twig`` -.. caution:: +.. warning:: If you add a template in a new location, you *may* need to clear your cache (``php bin/console cache:clear``), even if you are in debug mode. diff --git a/bundles/prepend_extension.rst b/bundles/prepend_extension.rst index 613cda7489f..e4099d9f81a 100644 --- a/bundles/prepend_extension.rst +++ b/bundles/prepend_extension.rst @@ -154,7 +154,7 @@ registered and the ``entity_manager_name`` setting for ``acme_hello`` is set to Prepending Extension in the Bundle Class ---------------------------------------- -You can also append or prepend extension configuration directly in your +You can also prepend extension configuration directly in your Bundle class if you extend from the :class:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle` class and define the :method:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle::prependExtension` method:: @@ -172,12 +172,7 @@ method:: 'cache' => ['prefix_seed' => 'foo/bar'], ]); - // append - $containerConfigurator->extension('framework', [ - 'cache' => ['prefix_seed' => 'foo/bar'], - ]); - - // append from file + // prepend config from a file $containerConfigurator->import('../config/packages/cache.php'); } } @@ -186,6 +181,12 @@ method:: The ``prependExtension()`` method, like ``prepend()``, is called only at compile time. +.. versionadded:: 7.1 + + Starting from Symfony 7.1, calling the :method:`Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ContainerConfigurator::import` + method inside ``prependExtension()`` will prepend the given configuration. + In previous Symfony versions, this method appended the configuration. + Alternatively, you can use the ``prepend`` parameter of the :method:`Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ContainerConfigurator::extension` method:: diff --git a/cache.rst b/cache.rst index 19c8bbb4d01..833e4d77007 100644 --- a/cache.rst +++ b/cache.rst @@ -545,7 +545,7 @@ Using Cache Tags In applications with many cache keys it could be useful to organize the data stored to be able to invalidate the cache more efficiently. One way to achieve that is to use cache tags. One or more tags could be added to the cache item. All items with -the same key could be invalidated with one function call:: +the same tag could be invalidated with one function call:: use Symfony\Contracts\Cache\ItemInterface; use Symfony\Contracts\Cache\TagAwareCacheInterface; @@ -589,8 +589,7 @@ to enable this feature. This could be added by using the following configuration cache: pools: my_cache_pool: - adapter: cache.adapter.redis - tags: true + adapter: cache.adapter.redis_tag_aware .. code-block:: xml @@ -830,7 +829,7 @@ Then, register the ``SodiumMarshaller`` service using this key: //->addArgument(['env(base64:CACHE_DECRYPTION_KEY)', 'env(base64:OLD_CACHE_DECRYPTION_KEY)']) ->addArgument(new Reference('.inner')); -.. caution:: +.. danger:: This will encrypt the values of the cache items, but not the cache keys. Be careful not to leak sensitive data in the keys. diff --git a/components/cache/adapters/apcu_adapter.rst b/components/cache/adapters/apcu_adapter.rst index 99d76ce5d27..f2e92850cd8 100644 --- a/components/cache/adapters/apcu_adapter.rst +++ b/components/cache/adapters/apcu_adapter.rst @@ -5,7 +5,7 @@ This adapter is a high-performance, shared memory cache. It can *significantly* increase an application's performance, as its cache contents are stored in shared memory, a component appreciably faster than many others, such as the filesystem. -.. caution:: +.. warning:: **Requirement:** The `APCu extension`_ must be installed and active to use this adapter. @@ -30,7 +30,7 @@ and cache items version string as constructor arguments:: $version = null ); -.. caution:: +.. warning:: Use of this adapter is discouraged in write/delete heavy workloads, as these operations cause memory fragmentation that results in significantly degraded performance. diff --git a/components/cache/adapters/couchbasebucket_adapter.rst b/components/cache/adapters/couchbasebucket_adapter.rst index aaf400319f4..29c9e26f83c 100644 --- a/components/cache/adapters/couchbasebucket_adapter.rst +++ b/components/cache/adapters/couchbasebucket_adapter.rst @@ -14,7 +14,7 @@ shared memory; you can store contents independent of your PHP environment. The ability to utilize a cluster of servers to provide redundancy and/or fail-over is also available. -.. caution:: +.. warning:: **Requirements:** The `Couchbase PHP extension`_ as well as a `Couchbase server`_ must be installed, active, and running to use this adapter. Version ``2.6`` or diff --git a/components/cache/adapters/couchbasecollection_adapter.rst b/components/cache/adapters/couchbasecollection_adapter.rst index 25640a20b0f..ba78cc46eff 100644 --- a/components/cache/adapters/couchbasecollection_adapter.rst +++ b/components/cache/adapters/couchbasecollection_adapter.rst @@ -8,7 +8,7 @@ shared memory; you can store contents independent of your PHP environment. The ability to utilize a cluster of servers to provide redundancy and/or fail-over is also available. -.. caution:: +.. warning:: **Requirements:** The `Couchbase PHP extension`_ as well as a `Couchbase server`_ must be installed, active, and running to use this adapter. Version ``3.0`` or diff --git a/components/cache/adapters/filesystem_adapter.rst b/components/cache/adapters/filesystem_adapter.rst index 26ef48af27c..db877454859 100644 --- a/components/cache/adapters/filesystem_adapter.rst +++ b/components/cache/adapters/filesystem_adapter.rst @@ -33,7 +33,7 @@ and cache root path as constructor parameters:: $directory = null ); -.. caution:: +.. warning:: The overhead of filesystem IO often makes this adapter one of the *slower* choices. If throughput is paramount, the in-memory adapters diff --git a/components/cache/adapters/memcached_adapter.rst b/components/cache/adapters/memcached_adapter.rst index d68d3e3b9ac..64baf0d4702 100644 --- a/components/cache/adapters/memcached_adapter.rst +++ b/components/cache/adapters/memcached_adapter.rst @@ -8,7 +8,7 @@ shared memory; you can store contents independent of your PHP environment. The ability to utilize a cluster of servers to provide redundancy and/or fail-over is also available. -.. caution:: +.. warning:: **Requirements:** The `Memcached PHP extension`_ as well as a `Memcached server`_ must be installed, active, and running to use this adapter. Version ``2.2`` or @@ -256,7 +256,7 @@ Available Options executed in a "fire-and-forget" manner; no attempt to ensure the operation has been received or acted on will be made once the client has executed it. - .. caution:: + .. warning:: Not all library operations are tested in this mode. Mixed TCP and UDP servers are not allowed. diff --git a/components/cache/adapters/php_files_adapter.rst b/components/cache/adapters/php_files_adapter.rst index efd2cf0e964..6f171f0fede 100644 --- a/components/cache/adapters/php_files_adapter.rst +++ b/components/cache/adapters/php_files_adapter.rst @@ -28,7 +28,7 @@ file similar to the following:: handles file includes, this adapter has the potential to be much faster than other filesystem-based caches. -.. caution:: +.. warning:: While it supports updates and because it is using OPcache as a backend, this adapter is better suited for append-mostly needs. Using it in other scenarios might lead to diff --git a/components/cache/adapters/redis_adapter.rst b/components/cache/adapters/redis_adapter.rst index ef12f09dcd5..3362f4cc2db 100644 --- a/components/cache/adapters/redis_adapter.rst +++ b/components/cache/adapters/redis_adapter.rst @@ -15,7 +15,7 @@ Unlike the :doc:`APCu adapter `, and si shared memory; you can store contents independent of your PHP environment. The ability to utilize a cluster of servers to provide redundancy and/or fail-over is also available. -.. caution:: +.. warning:: **Requirements:** At least one `Redis server`_ must be installed and running to use this adapter. Additionally, this adapter requires a compatible extension or library that implements @@ -38,7 +38,13 @@ as the second and third parameters:: // the default lifetime (in seconds) for cache items that do not define their // own lifetime, with a value 0 causing items to be stored indefinitely (i.e. // until RedisAdapter::clear() is invoked or the server(s) are purged) - $defaultLifetime = 0 + $defaultLifetime = 0, + + // $marshaller (optional) An instance of MarshallerInterface to control the serialization + // and deserialization of cache items. By default, native PHP serialization is used. + // This can be useful for compressing data, applying custom serialization logic, or + // optimizing the size and performance of cached items + ?MarshallerInterface $marshaller = null ); Configure the Connection @@ -195,7 +201,7 @@ Available Options ``redis_cluster`` (type: ``bool``, default: ``false``) Enables or disables redis cluster. The actual value passed is irrelevant as long as it passes loose comparison - checks: `redis_cluster=1` will suffice. + checks: ``redis_cluster=1`` will suffice. ``redis_sentinel`` (type: ``string``, default: ``null``) Specifies the master name connected to the sentinels. @@ -266,6 +272,80 @@ performance when using tag-based invalidation:: Read more about this topic in the official `Redis LRU Cache Documentation`_. +Working with Marshaller +----------------------- + +TagAwareMarshaller for Tag-Based Caching +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Optimizes caching for tag-based retrieval, allowing efficient management of related items:: + + $marshaller = new TagAwareMarshaller(); + + $cache = new RedisAdapter($redis, 'tagged_namespace', 3600, $marshaller); + + $item = $cache->getItem('tagged_key'); + $item->set(['value' => 'some_data', 'tags' => ['tag1', 'tag2']]); + $cache->save($item); + +SodiumMarshaller for Encrypted Caching +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Encrypts cached data using Sodium for enhanced security:: + + $encryptionKeys = [sodium_crypto_box_keypair()]; + $marshaller = new SodiumMarshaller($encryptionKeys); + + $cache = new RedisAdapter($redis, 'secure_namespace', 3600, $marshaller); + + $item = $cache->getItem('secure_key'); + $item->set('confidential_data'); + $cache->save($item); + +DefaultMarshaller with igbinary Serialization +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Uses ``igbinary` for faster and more efficient serialization when available:: + + $marshaller = new DefaultMarshaller(true); + + $cache = new RedisAdapter($redis, 'optimized_namespace', 3600, $marshaller); + + $item = $cache->getItem('optimized_key'); + $item->set(['data' => 'optimized_data']); + $cache->save($item); + +DefaultMarshaller with Exception on Failure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Throws an exception if serialization fails, facilitating error handling:: + + $marshaller = new DefaultMarshaller(false, true); + + $cache = new RedisAdapter($redis, 'error_namespace', 3600, $marshaller); + + try { + $item = $cache->getItem('error_key'); + $item->set('data'); + $cache->save($item); + } catch (\ValueError $e) { + echo 'Serialization failed: '.$e->getMessage(); + } + +SodiumMarshaller with Key Rotation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Supports key rotation, ensuring secure decryption with both old and new keys:: + + $keys = [sodium_crypto_box_keypair(), sodium_crypto_box_keypair()]; + $marshaller = new SodiumMarshaller($keys); + + $cache = new RedisAdapter($redis, 'rotated_namespace', 3600, $marshaller); + + $item = $cache->getItem('rotated_key'); + $item->set('data_to_encrypt'); + $cache->save($item); + .. _`Data Source Name (DSN)`: https://en.wikipedia.org/wiki/Data_source_name .. _`Redis server`: https://redis.io/ .. _`Redis`: https://github.com/phpredis/phpredis diff --git a/components/clock.rst b/components/clock.rst index 45ec484eef5..5b20e6000b9 100644 --- a/components/clock.rst +++ b/components/clock.rst @@ -129,18 +129,18 @@ is expired or not, by modifying the clock's time:: $validUntil = new DateTimeImmutable('2022-11-16 15:25:00'); // $validUntil is in the future, so it is not expired - static::assertFalse($expirationChecker->isExpired($validUntil)); + $this->assertFalse($expirationChecker->isExpired($validUntil)); // Clock sleeps for 10 minutes, so now is '2022-11-16 15:30:00' $clock->sleep(600); // Instantly changes time as if we waited for 10 minutes (600 seconds) // modify the clock, accepts all formats supported by DateTimeImmutable::modify() - static::assertTrue($expirationChecker->isExpired($validUntil)); + $this->assertTrue($expirationChecker->isExpired($validUntil)); $clock->modify('2022-11-16 15:00:00'); // $validUntil is in the future again, so it is no longer expired - static::assertFalse($expirationChecker->isExpired($validUntil)); + $this->assertFalse($expirationChecker->isExpired($validUntil)); } } @@ -229,6 +229,21 @@ The constructor also allows setting a timezone or custom referenced date:: $referenceDate = new \DateTimeImmutable(); $relativeDate = new DatePoint('+1month', reference: $referenceDate); +The ``DatePoint`` class also provides a named constructor to create dates from +timestamps:: + + $dateOfFirstCommitToSymfonyProject = DatePoint::createFromTimestamp(1129645656); + // equivalent to: + // $dateOfFirstCommitToSymfonyProject = (new \DateTimeImmutable())->setTimestamp(1129645656); + + // negative timestamps (for dates before January 1, 1970) and float timestamps + // (for high precision sub-second datetimes) are also supported + $dateOfFirstMoonLanding = DatePoint::createFromTimestamp(-14182940); + +.. versionadded:: 7.1 + + The ``createFromTimestamp()`` method was introduced in Symfony 7.1. + .. note:: In addition ``DatePoint`` offers stricter return types and provides consistent diff --git a/components/config.rst b/components/config.rst index 579d5b3149d..9de03f1f869 100644 --- a/components/config.rst +++ b/components/config.rst @@ -1,9 +1,17 @@ The Config Component ==================== - The Config component provides several classes to help you find, load, - combine, fill and validate configuration values of any kind, whatever - their source may be (YAML, XML, INI files, or for instance a database). +The Config component provides utilities to define and manage the configuration +options of PHP applications. It allows you to: + +* Define a configuration structure, its validation rules, default values and documentation; +* Support different configuration formats (YAML, XML, INI, etc.); +* Merge multiple configurations from different sources into a single configuration. + +.. note:: + + You don't have to use this component to configure Symfony applications. + Instead, read the docs about :doc:`how to configure Symfony applications `. Installation ------------ diff --git a/components/config/definition.rst b/components/config/definition.rst index 63ebcd7cc72..19e4f5fd40c 100644 --- a/components/config/definition.rst +++ b/components/config/definition.rst @@ -81,7 +81,7 @@ reflect the real structure of the configuration values:: ->defaultTrue() ->end() ->scalarNode('default_connection') - ->defaultValue('default') + ->defaultValue('mysql') ->end() ->end() ; @@ -670,7 +670,7 @@ The separator used in keys is typically ``_`` in YAML and ``-`` in XML. For example, ``auto_connect`` in YAML and ``auto-connect`` in XML. The normalization would make both of these ``auto_connect``. -.. caution:: +.. warning:: The target key will not be altered if it's mixed like ``foo-bar_moo`` or if it already exists. @@ -889,7 +889,7 @@ Otherwise the result is a clean array of configuration values:: $configs ); -.. caution:: +.. warning:: When processing the configuration tree, the processor assumes that the top level array key (which matches the extension name) is already stripped off. diff --git a/components/console/changing_default_command.rst b/components/console/changing_default_command.rst index b739e3b39ba..c69995ea395 100644 --- a/components/console/changing_default_command.rst +++ b/components/console/changing_default_command.rst @@ -52,7 +52,7 @@ This will print the following to the command line: Hello World -.. caution:: +.. warning:: This feature has a limitation: you cannot pass any argument or option to the default command because they are ignored. diff --git a/components/console/events.rst b/components/console/events.rst index f0edf2205ac..e550025b7dd 100644 --- a/components/console/events.rst +++ b/components/console/events.rst @@ -14,7 +14,7 @@ the wheel, it uses the Symfony EventDispatcher component to do the work:: $application->setDispatcher($dispatcher); $application->run(); -.. caution:: +.. warning:: Console events are only triggered by the main command being executed. Commands called by the main command will not trigger any event, unless diff --git a/components/console/helpers/formatterhelper.rst b/components/console/helpers/formatterhelper.rst index 5e4ae0d91fb..3cb87c4c307 100644 --- a/components/console/helpers/formatterhelper.rst +++ b/components/console/helpers/formatterhelper.rst @@ -64,8 +64,9 @@ block will be formatted with more padding (one blank line above and below the messages and 2 spaces on the left and right). The exact "style" you use in the block is up to you. In this case, you're using -the pre-defined ``error`` style, but there are other styles, or you can create -your own. See :doc:`/console/coloring`. +the pre-defined ``error`` style, but there are other styles (``info``, +``comment``, ``question``), or you can create your own. +See :doc:`/console/coloring`. Print Truncated Messages ------------------------ @@ -87,7 +88,7 @@ And the output will be: This is... -The message is truncated to the given length, then the suffix is appended to end +The message is truncated to the given length, then the suffix is appended to the end of that string. Negative String Length @@ -109,7 +110,7 @@ Custom Suffix By default, the ``...`` suffix is used. If you wish to use a different suffix, pass it as the third argument to the method. -The suffix is always appended, unless truncate length is longer than a message +The suffix is always appended, unless truncated length is longer than a message and a suffix length. If you don't want to use suffix at all, pass an empty string:: diff --git a/components/console/helpers/progressbar.rst b/components/console/helpers/progressbar.rst index 4d524a2008e..19e2d0daef5 100644 --- a/components/console/helpers/progressbar.rst +++ b/components/console/helpers/progressbar.rst @@ -323,7 +323,7 @@ to display it can be customized:: // the bar width $progressBar->setBarWidth(50); -.. caution:: +.. warning:: For performance reasons, Symfony redraws the screen once every 100ms. If this is too fast or too slow for your application, use the methods diff --git a/components/console/helpers/questionhelper.rst b/components/console/helpers/questionhelper.rst index e33c4ed5fa7..3dc97d5c0d3 100644 --- a/components/console/helpers/questionhelper.rst +++ b/components/console/helpers/questionhelper.rst @@ -145,6 +145,28 @@ The option which should be selected by default is provided with the third argument of the constructor. The default is ``null``, which means that no option is the default one. +Choice questions display both the choice value and a numeric index, which starts +from 0 by default. The user can type either the numeric index or the choice value +to make a selection: + +.. code-block:: terminal + + Please select your favorite color (defaults to red): + [0] red + [1] blue + [2] yellow + > + +.. tip:: + + To use custom indices, pass an array with custom numeric keys as the choice + values:: + + new ChoiceQuestion('Select a room:', [ + 102 => 'Room Foo', + 213 => 'Room Bar', + ]); + If the user enters an invalid string, an error message is shown and the user is asked to provide the answer another time, until they enter a valid string or reach the maximum number of attempts. The default value for the maximum number @@ -329,7 +351,7 @@ convenient for passwords:: return Command::SUCCESS; } -.. caution:: +.. warning:: When you ask for a hidden response, Symfony will use either a binary, change ``stty`` mode or use another trick to hide the response. If none is available, @@ -392,7 +414,7 @@ method:: return Command::SUCCESS; } -.. caution:: +.. warning:: The normalizer is called first and the returned value is used as the input of the validator. If the answer is invalid, don't throw exceptions in the @@ -540,7 +562,7 @@ This way you can test any user interaction (even complex ones) by passing the ap simulates a user hitting ``ENTER`` after each input, no need for passing an additional input. -.. caution:: +.. warning:: On Windows systems Symfony uses a special binary to implement hidden questions. This means that those questions don't use the default ``Input`` diff --git a/components/console/helpers/table.rst b/components/console/helpers/table.rst index 3988859dc76..13bdeb491f0 100644 --- a/components/console/helpers/table.rst +++ b/components/console/helpers/table.rst @@ -220,7 +220,7 @@ You can also set the style to ``box``:: which outputs: -.. code-block:: text +.. code-block:: terminal ┌───────────────┬──────────────────────────┬──────────────────┐ │ ISBN │ Title │ Author │ @@ -238,7 +238,7 @@ You can also set the style to ``box-double``:: which outputs: -.. code-block:: text +.. code-block:: terminal ╔═══════════════╤══════════════════════════╤══════════════════╗ ║ ISBN │ Title │ Author ║ diff --git a/components/dependency_injection/compilation.rst b/components/dependency_injection/compilation.rst index 3787c686982..7f991e85b72 100644 --- a/components/dependency_injection/compilation.rst +++ b/components/dependency_injection/compilation.rst @@ -150,7 +150,7 @@ will look like this:: ], ] -Whilst you can manually manage merging the different files, it is much better +While you can manually manage merging the different files, it is much better to use :doc:`the Config component ` to merge and validate the config values. Using the configuration processing you could access the config value this way:: diff --git a/components/dom_crawler.rst b/components/dom_crawler.rst index ac859efac91..630d301302a 100644 --- a/components/dom_crawler.rst +++ b/components/dom_crawler.rst @@ -267,7 +267,7 @@ The result is an array of values returned by the anonymous function calls. When using nested crawler, beware that ``filterXPath()`` is evaluated in the context of the crawler:: - $crawler->filterXPath('parent')->each(function (Crawler $parentCrawler, $i): avoid { + $crawler->filterXPath('parent')->each(function (Crawler $parentCrawler, $i): void { // DON'T DO THIS: direct child can not be found $subCrawler = $parentCrawler->filterXPath('sub-tag/sub-child-tag'); diff --git a/components/event_dispatcher.rst b/components/event_dispatcher.rst index 83cead3d19c..8cd676dd5fe 100644 --- a/components/event_dispatcher.rst +++ b/components/event_dispatcher.rst @@ -476,11 +476,7 @@ with some other dispatchers: Learn More ---------- -.. toctree:: - :maxdepth: 1 - - /components/event_dispatcher/generic_event - +* :doc:`/components/event_dispatcher/generic_event` * :ref:`The kernel.event_listener tag ` * :ref:`The kernel.event_subscriber tag ` diff --git a/components/expression_language.rst b/components/expression_language.rst index 4e81630c5d3..b0dd10b0f42 100644 --- a/components/expression_language.rst +++ b/components/expression_language.rst @@ -82,7 +82,7 @@ Null Coalescing Operator .. note:: - This content has been moved to the ref:`null coalescing operator `_ + This content has been moved to the :ref:`null coalescing operator ` section of ExpressionLanguage syntax reference page. Parsing and Linting Expressions @@ -93,17 +93,22 @@ The :method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::parse` method returns a :class:`Symfony\\Component\\ExpressionLanguage\\ParsedExpression` instance that can be used to inspect and manipulate the expression. The :method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::lint`, on the -other hand, returns a boolean indicating if the expression is valid or not:: +other hand, throws a :class:`Symfony\\Component\\ExpressionLanguage\\SyntaxError` +if the expression is not valid:: use Symfony\Component\ExpressionLanguage\ExpressionLanguage; $expressionLanguage = new ExpressionLanguage(); - var_dump($expressionLanguage->parse('1 + 2')); + var_dump($expressionLanguage->parse('1 + 2', [])); // displays the AST nodes of the expression which can be // inspected and manipulated - var_dump($expressionLanguage->lint('1 + 2')); // displays true + $expressionLanguage->lint('1 + 2', []); // doesn't throw anything + + $expressionLanguage->lint('1 + a', []); + // throws a SyntaxError exception: + // "Variable "a" is not valid around position 5 for expression `1 + a`." The behavior of these methods can be configured with some flags defined in the :class:`Symfony\\Component\\ExpressionLanguage\\Parser` class: @@ -120,8 +125,8 @@ This is how you can use these flags:: $expressionLanguage = new ExpressionLanguage(); - // this returns true because the unknown variables and functions are ignored - var_dump($expressionLanguage->lint('unknown_var + unknown_function()', Parser::IGNORE_UNKNOWN_VARIABLES | Parser::IGNORE_UNKNOWN_FUNCTIONS)); + // does not throw a SyntaxError because the unknown variables and functions are ignored + $expressionLanguage->lint('unknown_var + unknown_function()', [], Parser::IGNORE_UNKNOWN_VARIABLES | Parser::IGNORE_UNKNOWN_FUNCTIONS); .. versionadded:: 7.1 @@ -161,13 +166,6 @@ expressions (e.g. the request, the current user, etc.): * :doc:`Variables available in service container expressions `; * :ref:`Variables available in routing expressions `. -.. caution:: - - When using variables in expressions, avoid passing untrusted data into the - array of variables. If you can't avoid that, sanitize non-alphanumeric - characters in untrusted data to prevent malicious users from injecting - control characters and altering the expression. - .. _expression-language-caching: Caching @@ -408,7 +406,7 @@ or by using the second argument of the constructor:: class ExpressionLanguage extends BaseExpressionLanguage { - public function __construct(CacheItemPoolInterface $cache = null, array $providers = []) + public function __construct(?CacheItemPoolInterface $cache = null, array $providers = []) { // prepends the default provider to let users override it array_unshift($providers, new StringExpressionLanguageProvider()); diff --git a/components/finder.rst b/components/finder.rst index 516db7cde4e..cecc597ac64 100644 --- a/components/finder.rst +++ b/components/finder.rst @@ -41,7 +41,7 @@ The ``$file`` variable is an instance of :class:`Symfony\\Component\\Finder\\SplFileInfo` which extends PHP's own :phpclass:`SplFileInfo` to provide methods to work with relative paths. -.. caution:: +.. warning:: The ``Finder`` object doesn't reset its internal state automatically. This means that you need to create a new instance if you do not want @@ -127,6 +127,30 @@ If you want to follow `symbolic links`_, use the ``followLinks()`` method:: $finder->files()->followLinks(); +Note that this method follows links but it doesn't resolve them. Consider +the following structure of files of directories: + +.. code-block:: text + + ├── folder1/ + │ ├──file1.txt + │ ├── file2link (symbolic link to folder2/file2.txt file) + │ └── folder3link (symbolic link to folder3/ directory) + ├── folder2/ + │ └── file2.txt + └── folder3/ + └── file3.txt + +If you try to find all files in ``folder1/`` via ``$finder->files()->in('/path/to/folder1/')`` +you'll get the following results: + +* When **not** using the ``followLinks()`` method: ``file1.txt`` and ``file2link`` + (this link is not resolved). The ``folder3link`` doesn't appear in the results + because it's not followed or resolved; +* When using the ``followLinks()`` method: ``file1.txt``, ``file2link`` (this link + is still not resolved) and ``folder3/file3.txt`` (this file appears in the results + because the ``folder1/folder3link`` link was followed). + Version Control Files ~~~~~~~~~~~~~~~~~~~~~ diff --git a/components/form.rst b/components/form.rst index 7584d223032..5e09f38812f 100644 --- a/components/form.rst +++ b/components/form.rst @@ -640,7 +640,7 @@ method: // ... -.. caution:: +.. warning:: The form's ``createView()`` method should be called *after* ``handleRequest()`` is called. Otherwise, when using :doc:`form events `, changes done diff --git a/components/http_foundation.rst b/components/http_foundation.rst index b08aeb8380b..21e9bbfb13e 100644 --- a/components/http_foundation.rst +++ b/components/http_foundation.rst @@ -362,6 +362,24 @@ analysis purposes. Use the ``anonymize()`` method from the $anonymousIpv6 = IpUtils::anonymize($ipv6); // $anonymousIpv6 = '2a01:198:603:10::' +Check If an IP Belongs to a CIDR Subnet +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you need to know if an IP address is included in a CIDR subnet, you can use +the ``checkIp()`` method from :class:`Symfony\\Component\\HttpFoundation\\IpUtils`:: + + use Symfony\Component\HttpFoundation\IpUtils; + + $ipv4 = '192.168.1.56'; + $CIDRv4 = '192.168.1.0/16'; + $isIpInCIDRv4 = IpUtils::checkIp($ipv4, $CIDRv4); + // $isIpInCIDRv4 = true + + $ipv6 = '2001:db8:abcd:1234::1'; + $CIDRv6 = '2001:db8:abcd::/48'; + $isIpInCIDRv6 = IpUtils::checkIp($ipv6, $CIDRv6); + // $isIpInCIDRv6 = true + Check if an IP Belongs to a Private Subnet ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -925,6 +943,16 @@ The ``JsonResponse`` class sets the ``Content-Type`` header to Only methods that respond to GET requests are vulnerable to XSSI 'JSON Hijacking'. Methods responding to POST requests only remain unaffected. +.. warning:: + + The ``JsonResponse`` constructor exhibits non-standard JSON encoding behavior + and will treat ``null`` as an empty object if passed as a constructor argument, + despite null being a `valid JSON top-level value`_. + + This behavior cannot be changed without backwards-compatibility concerns, but + it's possible to call ``setData`` and pass the value there to opt-out of the + behavior. + JSONP Callback ~~~~~~~~~~~~~~ @@ -1014,9 +1042,10 @@ Learn More /session /http_cache/* -.. _nginx: https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile/ +.. _nginx: https://mattbrictson.com/blog/accelerated-rails-downloads .. _Apache: https://tn123.org/mod_xsendfile/ .. _`JSON Hijacking`: https://haacked.com/archive/2009/06/25/json-hijacking.aspx/ +.. _`valid JSON top-level value`: https://www.json.org/json-en.html .. _OWASP guidelines: https://cheatsheetseries.owasp.org/cheatsheets/AJAX_Security_Cheat_Sheet.html#always-return-json-with-an-object-on-the-outside .. _RFC 8674: https://tools.ietf.org/html/rfc8674 .. _Doctrine Batch processing: https://www.doctrine-project.org/projects/doctrine-orm/en/2.14/reference/batch-processing.html#iterating-results diff --git a/components/http_kernel.rst b/components/http_kernel.rst index 19442b5194d..02791b370bc 100644 --- a/components/http_kernel.rst +++ b/components/http_kernel.rst @@ -3,8 +3,8 @@ The HttpKernel Component The HttpKernel component provides a structured process for converting a ``Request`` into a ``Response`` by making use of the EventDispatcher - component. It's flexible enough to create a full-stack framework (Symfony), - a micro-framework (Silex) or an advanced CMS (Drupal). + component. It's flexible enough to create a full-stack framework (Symfony) + or an advanced CMS (Drupal). Installation ------------ @@ -398,7 +398,7 @@ return a ``Response``. There is a default listener inside the Symfony Framework for the ``kernel.view`` event. If your controller action returns an array, and you apply the - :ref:`#[Template()] attribute ` to that + :ref:`#[Template] attribute ` to that controller action, then this listener renders a template, passes the array you returned from your controller to that template, and creates a ``Response`` containing the returned content from that template. @@ -471,7 +471,7 @@ you will trigger the ``kernel.terminate`` event where you can perform certain actions that you may have delayed in order to return the response as quickly as possible to the client (e.g. sending emails). -.. caution:: +.. warning:: Internally, the HttpKernel makes use of the :phpfunction:`fastcgi_finish_request` PHP function. This means that at the moment, only the `PHP FPM`_ server diff --git a/components/intl.rst b/components/intl.rst index d18ac21b10a..ba3cbdcb959 100644 --- a/components/intl.rst +++ b/components/intl.rst @@ -386,10 +386,10 @@ Emoji Transliteration ~~~~~~~~~~~~~~~~~~~~~ Symfony provides utilities to translate emojis into their textual representation -in all languages. Read the documentation on :ref:`working with emojis in strings ` +in all languages. Read the documentation about :ref:`emoji transliteration ` to learn more about this feature. -Disk space +Disk Space ---------- If you need to save disk space (e.g. because you deploy to some service with tight size diff --git a/components/ldap.rst b/components/ldap.rst index 89094fad0b7..f5a142ced9f 100644 --- a/components/ldap.rst +++ b/components/ldap.rst @@ -70,7 +70,7 @@ distinguished name (DN) and the password of a user:: $ldap->bind($dn, $password); -.. caution:: +.. danger:: When the LDAP server allows unauthenticated binds, a blank password will always be valid. diff --git a/components/lock.rst b/components/lock.rst index e600d68ca59..b0240019e8d 100644 --- a/components/lock.rst +++ b/components/lock.rst @@ -105,12 +105,10 @@ to handle the rest of the job:: use App\Lock\RefreshTaxonomy; use Symfony\Component\Lock\Key; - use Symfony\Component\Lock\Lock; $key = new Key('article.'.$article->getId()); - $lock = new Lock( + $lock = $factory->createLockFromKey( $key, - $this->store, 300, // ttl false // autoRelease ); @@ -121,7 +119,7 @@ to handle the rest of the job:: .. note:: Don't forget to set the ``autoRelease`` argument to ``false`` in the - ``Lock`` constructor to avoid releasing the lock when the destructor is + ``Lock`` instantiation to avoid releasing the lock when the destructor is called. Not all stores are compatible with serialization and cross-process locking: for @@ -361,7 +359,7 @@ lose the lock it acquired automatically:: throw new \Exception('Process failed'); } -.. caution:: +.. warning:: A common pitfall might be to use the ``isAcquired()`` method to check if a lock has already been acquired by any process. As you can see in this example @@ -388,20 +386,20 @@ Locks are created and managed in ``Stores``, which are classes that implement The component includes the following built-in store types: -========================================================== ====== ======== ======== ======= -Store Scope Blocking Expiring Sharing -========================================================== ====== ======== ======== ======= -:ref:`FlockStore ` local yes no yes -:ref:`MemcachedStore ` remote no yes no -:ref:`MongoDbStore ` remote no yes no -:ref:`PdoStore ` remote no yes no -:ref:`DoctrineDbalStore ` remote no yes no -:ref:`PostgreSqlStore ` remote yes no yes -:ref:`DoctrineDbalPostgreSqlStore ` remote yes no yes -:ref:`RedisStore ` remote no yes yes -:ref:`SemaphoreStore ` local yes no no -:ref:`ZookeeperStore ` remote no no no -========================================================== ====== ======== ======== ======= +========================================================== ====== ======== ======== ======= ============= +Store Scope Blocking Expiring Sharing Serialization +========================================================== ====== ======== ======== ======= ============= +:ref:`FlockStore ` local yes no yes no +:ref:`MemcachedStore ` remote no yes no yes +:ref:`MongoDbStore ` remote no yes no yes +:ref:`PdoStore ` remote no yes no yes +:ref:`DoctrineDbalStore ` remote no yes no yes +:ref:`PostgreSqlStore ` remote yes no yes no +:ref:`DoctrineDbalPostgreSqlStore ` remote yes no yes no +:ref:`RedisStore ` remote no yes yes yes +:ref:`SemaphoreStore ` local yes no no no +:ref:`ZookeeperStore ` remote no no no no +========================================================== ====== ======== ======== ======= ============= .. tip:: @@ -424,7 +422,7 @@ when the PHP process ends):: // if none is given, sys_get_temp_dir() is used internally. $store = new FlockStore('/var/stores'); -.. caution:: +.. warning:: Beware that some file systems (such as some types of NFS) do not support locking. In those cases, it's better to use a directory on a local disk @@ -665,7 +663,7 @@ the stores:: $store = new CombinedStore($stores, new UnanimousStrategy()); -.. caution:: +.. warning:: In order to get high availability when using the ``ConsensusStrategy``, the minimum cluster size must be three servers. This allows the cluster to keep @@ -717,7 +715,7 @@ the ``Lock``. Every concurrent process must store the ``Lock`` on the same server. Otherwise two different machines may allow two different processes to acquire the same ``Lock``. -.. caution:: +.. warning:: To guarantee that the same server will always be safe, do not use Memcached behind a LoadBalancer, a cluster or round-robin DNS. Even if the main server @@ -756,15 +754,15 @@ Using the above methods, a robust code would be:: $lock->refresh(); } - // Perform the task whose duration MUST be less than 5 minutes + // Perform the task whose duration MUST be less than 5 seconds } -.. caution:: +.. warning:: Choose wisely the lifetime of the ``Lock`` and check whether its remaining time to live is enough to perform the task. -.. caution:: +.. warning:: Storing a ``Lock`` usually takes a few milliseconds, but network conditions may increase that time a lot (up to a few seconds). Take that into account @@ -773,7 +771,7 @@ Using the above methods, a robust code would be:: By design, locks are stored on servers with a defined lifetime. If the date or time of the machine changes, a lock could be released sooner than expected. -.. caution:: +.. warning:: To guarantee that date won't change, the NTP service should be disabled and the date should be updated when the service is stopped. @@ -795,7 +793,7 @@ deployments. Some file systems (such as some types of NFS) do not support locking. -.. caution:: +.. warning:: All concurrent processes must use the same physical file system by running on the same machine and using the same absolute path to the lock directory. @@ -824,7 +822,7 @@ and may disappear by mistake at any time. If the Memcached service or the machine hosting it restarts, every lock would be lost without notifying the running processes. -.. caution:: +.. warning:: To avoid that someone else acquires a lock after a restart, it's recommended to delay service start and wait at least as long as the longest lock TTL. @@ -832,7 +830,7 @@ be lost without notifying the running processes. By default Memcached uses a LRU mechanism to remove old entries when the service needs space to add new items. -.. caution:: +.. warning:: The number of items stored in Memcached must be under control. If it's not possible, LRU should be disabled and Lock should be stored in a dedicated @@ -850,7 +848,7 @@ method uses the Memcached's ``flush()`` method which purges and removes everythi MongoDbStore ~~~~~~~~~~~~ -.. caution:: +.. warning:: The locked resource name is indexed in the ``_id`` field of the lock collection. Beware that an indexed field's value in MongoDB can be @@ -876,7 +874,7 @@ about `Expire Data from Collections by Setting TTL`_ in MongoDB. recommended to set constructor option ``gcProbability`` to ``0.0`` to disable this behavior if you have manually dealt with TTL index creation. -.. caution:: +.. warning:: This store relies on all PHP application and database nodes to have synchronized clocks for lock expiry to occur at the correct time. To ensure @@ -893,12 +891,12 @@ PdoStore The PdoStore relies on the `ACID`_ properties of the SQL engine. -.. caution:: +.. warning:: In a cluster configured with multiple primaries, ensure writes are synchronously propagated to every node, or always use the same node. -.. caution:: +.. warning:: Some SQL engines like MySQL allow to disable the unique constraint check. Ensure that this is not the case ``SET unique_checks=1;``. @@ -907,7 +905,7 @@ In order to purge old locks, this store uses a current datetime to define an expiration date reference. This mechanism relies on all server nodes to have synchronized clocks. -.. caution:: +.. warning:: To ensure locks don't expire prematurely; the TTLs should be set with enough extra time to account for any clock drift between nodes. @@ -936,7 +934,7 @@ and may disappear by mistake at any time. If the Redis service or the machine hosting it restarts, every locks would be lost without notifying the running processes. -.. caution:: +.. warning:: To avoid that someone else acquires a lock after a restart, it's recommended to delay service start and wait at least as long as the longest lock TTL. @@ -964,7 +962,7 @@ The ``CombinedStore`` will be, at best, as reliable as the least reliable of all managed stores. As soon as one managed store returns erroneous information, the ``CombinedStore`` won't be reliable. -.. caution:: +.. warning:: All concurrent processes must use the same configuration, with the same amount of managed stored and the same endpoint. @@ -982,13 +980,13 @@ must run on the same machine, virtual machine or container. Be careful when updating a Kubernetes or Swarm service because for a short period of time, there can be two running containers in parallel. -.. caution:: +.. warning:: All concurrent processes must use the same machine. Before starting a concurrent process on a new machine, check that other processes are stopped on the old one. -.. caution:: +.. warning:: When running on systemd with non-system user and option ``RemoveIPC=yes`` (default value), locks are deleted by systemd when that user logs out. diff --git a/components/messenger.rst b/components/messenger.rst index a1c1e709e00..8d6652fb160 100644 --- a/components/messenger.rst +++ b/components/messenger.rst @@ -162,7 +162,7 @@ Here are some important envelope stamps that are shipped with the Symfony Messen to configure the validation groups used when the validation middleware is enabled. * :class:`Symfony\\Component\\Messenger\\Stamp\\ErrorDetailsStamp`, an internal stamp when a message fails due to an exception in the handler. -* :class:`Symfony\\Component\\Messenger\\Stamp\\ScheduledStamp`, +* :class:`Symfony\\Component\\Scheduler\\Messenger\\ScheduledStamp`, a stamp that marks the message as produced by a scheduler. This helps differentiate it from messages created "manually". You can learn more about it in the :doc:`Scheduler documentation `. diff --git a/components/options_resolver.rst b/components/options_resolver.rst index f70bc20a412..ff25f9e0fc4 100644 --- a/components/options_resolver.rst +++ b/components/options_resolver.rst @@ -485,7 +485,7 @@ these options, you can return the desired default value:: } } -.. caution:: +.. warning:: The argument of the callable must be type hinted as ``Options``. Otherwise, the callable itself is considered as the default value of the option. @@ -699,7 +699,7 @@ to the closure to access to them:: } } -.. caution:: +.. warning:: The arguments of the closure must be type hinted as ``OptionsResolver`` and ``Options`` respectively. Otherwise, the closure itself is considered as the @@ -811,7 +811,7 @@ method:: When using an option deprecated by you in your own library, you can pass ``false`` as the second argument of the - :method:`Symfony\\Component\\OptionsResolver\\Options::offsetGet` method + :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::offsetGet` method to not trigger the deprecation warning. .. note:: diff --git a/components/phpunit_bridge.rst b/components/phpunit_bridge.rst index ba37bc0ecda..5ce4c003a11 100644 --- a/components/phpunit_bridge.rst +++ b/components/phpunit_bridge.rst @@ -253,7 +253,7 @@ deprecations but: * forget to mark appropriate tests with the ``@group legacy`` annotations. By using ``SYMFONY_DEPRECATIONS_HELPER=max[self]=0``, deprecations that are -triggered outside the ``vendors`` directory will be accounted for separately, +triggered outside the ``vendor/`` directory will be accounted for separately, while deprecations triggered from a library inside it will not (unless you reach 999999 of these), giving you the best of both worlds. @@ -621,7 +621,7 @@ test:: And that's all! -.. caution:: +.. warning:: Time-based function mocking follows the `PHP namespace resolutions rules`_ so "fully qualified function calls" (e.g ``\time()``) cannot be mocked. diff --git a/components/process.rst b/components/process.rst index c4db5c18a9c..f6c8837d2c3 100644 --- a/components/process.rst +++ b/components/process.rst @@ -108,7 +108,7 @@ You can configure the options passed to the ``other_options`` argument of // this option allows a subprocess to continue running after the main script exited $process->setOptions(['create_new_console' => true]); -.. caution:: +.. warning:: Most of the options defined by ``proc_open()`` (such as ``create_new_console`` and ``suppress_errors``) are only supported on Windows operating systems. @@ -511,6 +511,20 @@ When running a program asynchronously, you can send it POSIX signals with the // will send a SIGKILL to the process $process->signal(SIGKILL); +You can make the process ignore signals by using the +:method:`Symfony\\Component\\Process\\Process::setIgnoredSignals` +method. The given signals won't be propagated to the child process:: + + use Symfony\Component\Process\Process; + + $process = new Process(['find', '/', '-name', 'rabbit']); + $process->setIgnoredSignals([SIGKILL, SIGUSR1]); + +.. versionadded:: 7.1 + + The :method:`Symfony\\Component\\Process\\Process::setIgnoredSignals` + method was introduced in Symfony 7.1. + Process Pid ----------- @@ -538,7 +552,7 @@ Use :method:`Symfony\\Component\\Process\\Process::disableOutput` and $process->disableOutput(); $process->run(); -.. caution:: +.. warning:: You cannot enable or disable the output while the process is running. diff --git a/components/property_access.rst b/components/property_access.rst index 052ed38e767..f608640fa9b 100644 --- a/components/property_access.rst +++ b/components/property_access.rst @@ -26,6 +26,8 @@ default configuration:: $propertyAccessor = PropertyAccess::createPropertyAccessor(); +.. _property-access-reading-arrays: + Reading from Arrays ------------------- @@ -112,7 +114,7 @@ To read from properties, use the "dot" notation:: var_dump($propertyAccessor->getValue($person, 'children[0].firstName')); // 'Bar' -.. caution:: +.. warning:: Accessing public properties is the last option used by ``PropertyAccessor``. It tries to access the value using the below methods first before using @@ -249,16 +251,21 @@ The ``getValue()`` method can also use the magic ``__get()`` method:: { return $this->children[$id]; } + + public function __isset($id): bool + { + return isset($this->children[$id]); + } } $person = new Person(); var_dump($propertyAccessor->getValue($person, 'Wouter')); // [...] -.. note:: +.. warning:: - The ``__get()`` method support is enabled by default. - See `Enable other Features`_ if you want to disable it. + When implementing the magic ``__get()`` method, you also need to implement + ``__isset()``. .. _components-property-access-magic-call: @@ -296,7 +303,7 @@ enable this feature by using :class:`Symfony\\Component\\PropertyAccess\\Propert var_dump($propertyAccessor->getValue($person, 'wouter')); // [...] -.. caution:: +.. warning:: The ``__call()`` feature is disabled by default, you can enable it by calling :method:`Symfony\\Component\\PropertyAccess\\PropertyAccessorBuilder::enableMagicCall` diff --git a/components/property_info.rst b/components/property_info.rst index 892cd5345a3..2e1ee42dd3f 100644 --- a/components/property_info.rst +++ b/components/property_info.rst @@ -478,9 +478,9 @@ SerializerExtractor This extractor depends on the `symfony/serializer`_ library. -Using :ref:`groups metadata ` -from the :doc:`Serializer component `, -the :class:`Symfony\\Component\\PropertyInfo\\Extractor\\SerializerExtractor` +Using :ref:`groups metadata ` from the +:doc:`Serializer component `, the +:class:`Symfony\\Component\\PropertyInfo\\Extractor\\SerializerExtractor` provides list information. This extractor is *not* registered automatically with the ``property_info`` service in the Symfony Framework:: diff --git a/components/runtime.rst b/components/runtime.rst index 5e6e173240c..4eb75de2a75 100644 --- a/components/runtime.rst +++ b/components/runtime.rst @@ -3,7 +3,7 @@ The Runtime Component The Runtime Component decouples the bootstrapping logic from any global state to make sure the application can run with runtimes like `PHP-PM`_, `ReactPHP`_, - `Swoole`_, etc. without any changes. + `Swoole`_, `FrankenPHP`_ etc. without any changes. Installation ------------ @@ -42,7 +42,7 @@ the component. This file runs the following logic: #. At last, the Runtime is used to run the application (i.e. calling ``$kernel->handle(Request::createFromGlobals())->send()``). -.. caution:: +.. warning:: If you use the Composer ``--no-plugins`` option, the ``autoload_runtime.php`` file won't be created. @@ -97,6 +97,23 @@ Use the ``APP_RUNTIME`` environment variable or by specifying the } } +If modifying the runtime class isn't enough, you can create your own runtime template: + +.. code-block:: json + + { + "require": { + "...": "..." + }, + "extra": { + "runtime": { + "autoload_template": "resources/runtime/autoload_runtime.template" + } + } + } + +Symfony provides a `runtime template file`_ that you can use to create your own. + Using the Runtime ----------------- @@ -470,5 +487,7 @@ The end user will now be able to create front controller like:: .. _PHP-PM: https://github.com/php-pm/php-pm .. _Swoole: https://openswoole.com/ +.. _FrankenPHP: https://frankenphp.dev/ .. _ReactPHP: https://reactphp.org/ .. _`PSR-15`: https://www.php-fig.org/psr/psr-15/ +.. _`runtime template file`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Runtime/Internal/autoload_runtime.template diff --git a/components/serializer.rst b/components/serializer.rst deleted file mode 100644 index a28464ffc04..00000000000 --- a/components/serializer.rst +++ /dev/null @@ -1,1922 +0,0 @@ -The Serializer Component -======================== - - The Serializer component is meant to be used to turn objects into a - specific format (XML, JSON, YAML, ...) and the other way around. - -In order to do so, the Serializer component follows the following schema. - -.. raw:: html - - - -When (de)serializing objects, the Serializer uses an array as the intermediary -between objects and serialized contents. Encoders will only deal with -turning specific **formats** into **arrays** and vice versa. The same way, -normalizers will deal with turning specific **objects** into **arrays** and -vice versa. The Serializer deals with calling the normalizers and encoders -when serializing objects or deserializing formats. - -Serialization is a complex topic. This component may not cover all your use -cases out of the box, but it can be useful for developing tools to -serialize and deserialize your objects. - -Installation ------------- - -.. code-block:: terminal - - $ composer require symfony/serializer - -.. include:: /components/require_autoload.rst.inc - -To use the ``ObjectNormalizer``, the :doc:`PropertyAccess component ` -must also be installed. - -Usage ------ - -.. seealso:: - - This article explains the philosophy of the Serializer and gets you familiar - with the concepts of normalizers and encoders. The code examples assume - that you use the Serializer as an independent component. If you are using - the Serializer in a Symfony application, read :doc:`/serializer` after you - finish this article. - -To use the Serializer component, set up the -:class:`Symfony\\Component\\Serializer\\Serializer` specifying which encoders -and normalizer are going to be available:: - - use Symfony\Component\Serializer\Encoder\JsonEncoder; - use Symfony\Component\Serializer\Encoder\XmlEncoder; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - $encoders = [new XmlEncoder(), new JsonEncoder()]; - $normalizers = [new ObjectNormalizer()]; - - $serializer = new Serializer($normalizers, $encoders); - -The preferred normalizer is the -:class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer`, -but other normalizers are available. All the examples shown below use -the ``ObjectNormalizer``. - -Serializing an Object ---------------------- - -For the sake of this example, assume the following class already -exists in your project:: - - namespace App\Model; - - class Person - { - private int $age; - private string $name; - private bool $sportsperson; - private ?\DateTimeInterface $createdAt; - - // Getters - public function getAge(): int - { - return $this->age; - } - - public function getName(): string - { - return $this->name; - } - - public function getCreatedAt(): ?\DateTimeInterface - { - return $this->createdAt; - } - - // Issers - public function isSportsperson(): bool - { - return $this->sportsperson; - } - - // Setters - public function setAge(int $age): void - { - $this->age = $age; - } - - public function setName(string $name): void - { - $this->name = $name; - } - - public function setSportsperson(bool $sportsperson): void - { - $this->sportsperson = $sportsperson; - } - - public function setCreatedAt(\DateTimeInterface $createdAt = null): void - { - $this->createdAt = $createdAt; - } - } - -Now, if you want to serialize this object into JSON, you only need to -use the Serializer service created before:: - - use App\Model\Person; - - $person = new Person(); - $person->setName('foo'); - $person->setAge(99); - $person->setSportsperson(false); - - $jsonContent = $serializer->serialize($person, 'json'); - - // $jsonContent contains {"name":"foo","age":99,"sportsperson":false,"createdAt":null} - - echo $jsonContent; // or return it in a Response - -The first parameter of the :method:`Symfony\\Component\\Serializer\\Serializer::serialize` -is the object to be serialized and the second is used to choose the proper encoder, -in this case :class:`Symfony\\Component\\Serializer\\Encoder\\JsonEncoder`. - -Deserializing an Object ------------------------ - -You'll now learn how to do the exact opposite. This time, the information -of the ``Person`` class would be encoded in XML format:: - - use App\Model\Person; - - $data = << - foo - 99 - false - - EOF; - - $person = $serializer->deserialize($data, Person::class, 'xml'); - -In this case, :method:`Symfony\\Component\\Serializer\\Serializer::deserialize` -needs three parameters: - -#. The information to be decoded -#. The name of the class this information will be decoded to -#. The encoder used to convert that information into an array - -By default, additional attributes that are not mapped to the denormalized object -will be ignored by the Serializer component. If you prefer to throw an exception -when this happens, set the ``AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES`` context option to -``false`` and provide an object that implements ``ClassMetadataFactoryInterface`` -when constructing the normalizer:: - - use App\Model\Person; - - $data = << - foo - 99 - Paris - - EOF; - - // $loader is any of the valid loaders explained later in this article - $classMetadataFactory = new ClassMetadataFactory($loader); - $normalizer = new ObjectNormalizer($classMetadataFactory); - $serializer = new Serializer([$normalizer]); - - // this will throw a Symfony\Component\Serializer\Exception\ExtraAttributesException - // because "city" is not an attribute of the Person class - $person = $serializer->deserialize($data, Person::class, 'xml', [ - AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false, - ]); - -Deserializing in an Existing Object -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The serializer can also be used to update an existing object:: - - // ... - $person = new Person(); - $person->setName('bar'); - $person->setAge(99); - $person->setSportsperson(true); - - $data = << - foo - 69 - - EOF; - - $serializer->deserialize($data, Person::class, 'xml', [AbstractNormalizer::OBJECT_TO_POPULATE => $person]); - // $person = App\Model\Person(name: 'foo', age: '69', sportsperson: true) - -This is a common need when working with an ORM. - -The ``AbstractNormalizer::OBJECT_TO_POPULATE`` is only used for the top level object. If that object -is the root of a tree structure, all child elements that exist in the -normalized data will be re-created with new instances. - -When the ``AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE`` option is set to -true, existing children of the root ``OBJECT_TO_POPULATE`` are updated from the -normalized data, instead of the denormalizer re-creating them. Note that -``DEEP_OBJECT_TO_POPULATE`` only works for single child objects, but not for -arrays of objects. Those will still be replaced when present in the normalized -data. - -Context -------- - -Many Serializer features can be configured :ref:`using a context `. - -.. _component-serializer-attributes-groups: - -Attributes Groups ------------------ - -Sometimes, you want to serialize different sets of attributes from your -entities. Groups are a handy way to achieve this need. - -Assume you have the following plain-old-PHP object:: - - namespace Acme; - - class MyObj - { - public string $foo; - - private string $bar; - - public function getBar(): string - { - return $this->bar; - } - - public function setBar($bar): string - { - return $this->bar = $bar; - } - } - -The definition of serialization can be specified using annotations, XML -or YAML. The :class:`Symfony\\Component\\Serializer\\Mapping\\Factory\\ClassMetadataFactory` -that will be used by the normalizer must be aware of the format to use. - -The following code shows how to initialize the :class:`Symfony\\Component\\Serializer\\Mapping\\Factory\\ClassMetadataFactory` -for each format: - -* Attributes in PHP files:: - - use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; - use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; - - $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); - -* YAML files:: - - use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; - use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader; - - $classMetadataFactory = new ClassMetadataFactory(new YamlFileLoader('/path/to/your/definition.yaml')); - -* XML files:: - - use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; - use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader; - - $classMetadataFactory = new ClassMetadataFactory(new XmlFileLoader('/path/to/your/definition.xml')); - -.. _component-serializer-attributes-groups-attributes: - -Then, create your groups definition: - -.. configuration-block:: - - .. code-block:: php-attributes - - namespace Acme; - - use Symfony\Component\Serializer\Annotation\Groups; - - class MyObj - { - #[Groups(['group1', 'group2'])] - public string $foo; - - #[Groups(['group4'])] - public string $anotherProperty; - - #[Groups(['group3'])] - public function getBar() // is* methods are also supported - { - return $this->bar; - } - - // ... - } - - .. code-block:: yaml - - Acme\MyObj: - attributes: - foo: - groups: ['group1', 'group2'] - anotherProperty: - groups: ['group4'] - bar: - groups: ['group3'] - - .. code-block:: xml - - - - - - group1 - group2 - - - - group4 - - - - group3 - - - - -You are now able to serialize only attributes in the groups you want:: - - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - $obj = new MyObj(); - $obj->foo = 'foo'; - $obj->anotherProperty = 'anotherProperty'; - $obj->setBar('bar'); - - $normalizer = new ObjectNormalizer($classMetadataFactory); - $serializer = new Serializer([$normalizer]); - - $data = $serializer->normalize($obj, null, ['groups' => 'group1']); - // $data = ['foo' => 'foo']; - - $obj2 = $serializer->denormalize( - ['foo' => 'foo', 'anotherProperty' => 'anotherProperty', 'bar' => 'bar'], - 'MyObj', - null, - ['groups' => ['group1', 'group3']] - ); - // $obj2 = MyObj(foo: 'foo', bar: 'bar') - - // To get all groups, use the special value `*` in `groups` - $obj3 = $serializer->denormalize( - ['foo' => 'foo', 'anotherProperty' => 'anotherProperty', 'bar' => 'bar'], - 'MyObj', - null, - ['groups' => ['*']] - ); - // $obj2 = MyObj(foo: 'foo', anotherProperty: 'anotherProperty', bar: 'bar') - -.. _ignoring-attributes-when-serializing: - -Selecting Specific Attributes ------------------------------ - -It is also possible to serialize only a set of specific attributes:: - - use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - class User - { - public string $familyName; - public string $givenName; - public Company $company; - } - - class Company - { - public string $name; - public string $address; - } - - $company = new Company(); - $company->name = 'Les-Tilleuls.coop'; - $company->address = 'Lille, France'; - - $user = new User(); - $user->familyName = 'Dunglas'; - $user->givenName = 'Kévin'; - $user->company = $company; - - $serializer = new Serializer([new ObjectNormalizer()]); - - $data = $serializer->normalize($user, null, [AbstractNormalizer::ATTRIBUTES => ['familyName', 'company' => ['name']]]); - // $data = ['familyName' => 'Dunglas', 'company' => ['name' => 'Les-Tilleuls.coop']]; - -Only attributes that are not ignored (see below) are available. -If some serialization groups are set, only attributes allowed by those groups can be used. - -As for groups, attributes can be selected during both the serialization and deserialization processes. - -.. _serializer_ignoring-attributes: - -Ignoring Attributes -------------------- - -All accessible attributes are included by default when serializing objects. -There are two options to ignore some of those attributes. - -Option 1: Using ``#[Ignore]`` Attribute -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. configuration-block:: - - .. code-block:: php-attributes - - namespace App\Model; - - use Symfony\Component\Serializer\Annotation\Ignore; - - class MyClass - { - public string $foo; - - #[Ignore] - public string $bar; - } - - .. code-block:: yaml - - App\Model\MyClass: - attributes: - bar: - ignore: true - - .. code-block:: xml - - - - - - - - -You can now ignore specific attributes during serialization:: - - use App\Model\MyClass; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - $obj = new MyClass(); - $obj->foo = 'foo'; - $obj->bar = 'bar'; - - $normalizer = new ObjectNormalizer($classMetadataFactory); - $serializer = new Serializer([$normalizer]); - - $data = $serializer->normalize($obj); - // $data = ['foo' => 'foo']; - -Option 2: Using the Context -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Pass an array with the names of the attributes to ignore using the -``AbstractNormalizer::IGNORED_ATTRIBUTES`` key in the ``context`` of the -serializer method:: - - use Acme\Person; - use Symfony\Component\Serializer\Encoder\JsonEncoder; - use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - $person = new Person(); - $person->setName('foo'); - $person->setAge(99); - - $normalizer = new ObjectNormalizer(); - $encoder = new JsonEncoder(); - - $serializer = new Serializer([$normalizer], [$encoder]); - $serializer->serialize($person, 'json', [AbstractNormalizer::IGNORED_ATTRIBUTES => ['age']]); // Output: {"name":"foo"} - -.. _component-serializer-converting-property-names-when-serializing-and-deserializing: - -Converting Property Names when Serializing and Deserializing ------------------------------------------------------------- - -Sometimes serialized attributes must be named differently than properties -or getter/setter methods of PHP classes. - -The Serializer component provides a handy way to translate or map PHP field -names to serialized names: The Name Converter System. - -Given you have the following object:: - - class Company - { - public string $name; - public string $address; - } - -And in the serialized form, all attributes must be prefixed by ``org_`` like -the following:: - - {"org_name": "Acme Inc.", "org_address": "123 Main Street, Big City"} - -A custom name converter can handle such cases:: - - use Symfony\Component\Serializer\NameConverter\NameConverterInterface; - - class OrgPrefixNameConverter implements NameConverterInterface - { - public function normalize(string $propertyName): string - { - return 'org_'.$propertyName; - } - - public function denormalize(string $propertyName): string - { - // removes 'org_' prefix - return str_starts_with($propertyName, 'org_') ? substr($propertyName, 4) : $propertyName; - } - } - -The custom name converter can be used by passing it as second parameter of any -class extending :class:`Symfony\\Component\\Serializer\\Normalizer\\AbstractNormalizer`, -including :class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer` -and :class:`Symfony\\Component\\Serializer\\Normalizer\\PropertyNormalizer`:: - - use Symfony\Component\Serializer\Encoder\JsonEncoder; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - $nameConverter = new OrgPrefixNameConverter(); - $normalizer = new ObjectNormalizer(null, $nameConverter); - - $serializer = new Serializer([$normalizer], [new JsonEncoder()]); - - $company = new Company(); - $company->name = 'Acme Inc.'; - $company->address = '123 Main Street, Big City'; - - $json = $serializer->serialize($company, 'json'); - // {"org_name": "Acme Inc.", "org_address": "123 Main Street, Big City"} - $companyCopy = $serializer->deserialize($json, Company::class, 'json'); - // Same data as $company - -.. note:: - - You can also implement - :class:`Symfony\\Component\\Serializer\\NameConverter\\AdvancedNameConverterInterface` - to access the current class name, format and context. - -.. _using-camelized-method-names-for-underscored-attributes: - -CamelCase to snake_case -~~~~~~~~~~~~~~~~~~~~~~~ - -In many formats, it's common to use underscores to separate words (also known -as snake_case). However, in Symfony applications is common to use CamelCase to -name properties (even though the `PSR-1 standard`_ doesn't recommend any -specific case for property names). - -Symfony provides a built-in name converter designed to transform between -snake_case and CamelCased styles during serialization and deserialization -processes:: - - use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - - $normalizer = new ObjectNormalizer(null, new CamelCaseToSnakeCaseNameConverter()); - - class Person - { - public function __construct( - private string $firstName, - ) { - } - - public function getFirstName(): string - { - return $this->firstName; - } - } - - $kevin = new Person('Kévin'); - $normalizer->normalize($kevin); - // ['first_name' => 'Kévin']; - - $anne = $normalizer->denormalize(['first_name' => 'Anne'], 'Person'); - // Person object with firstName: 'Anne' - -.. _serializer_name-conversion: - -Configure name conversion using metadata -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When using this component inside a Symfony application and the class metadata -factory is enabled as explained in the :ref:`Attributes Groups section `, -this is already set up and you only need to provide the configuration. Otherwise:: - - // ... - use Symfony\Component\Serializer\Encoder\JsonEncoder; - use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); - - $metadataAwareNameConverter = new MetadataAwareNameConverter($classMetadataFactory); - - $serializer = new Serializer( - [new ObjectNormalizer($classMetadataFactory, $metadataAwareNameConverter)], - ['json' => new JsonEncoder()] - ); - -Now configure your name conversion mapping. Consider an application that -defines a ``Person`` entity with a ``firstName`` property: - -.. configuration-block:: - - .. code-block:: php-attributes - - namespace App\Entity; - - use Symfony\Component\Serializer\Annotation\SerializedName; - - class Person - { - public function __construct( - #[SerializedName('customer_name')] - private string $firstName, - ) { - } - - // ... - } - - .. code-block:: yaml - - App\Entity\Person: - attributes: - firstName: - serialized_name: customer_name - - .. code-block:: xml - - - - - - - - -This custom mapping is used to convert property names when serializing and -deserializing objects:: - - $serialized = $serializer->serialize(new Person('Kévin'), 'json'); - // {"customer_name": "Kévin"} - -.. _serializing-boolean-attributes: - -Handling Boolean Attributes And Values --------------------------------------- - -During Serialization -~~~~~~~~~~~~~~~~~~~~ - -If you are using isser methods (methods prefixed by ``is``, like -``App\Model\Person::isSportsperson()``), the Serializer component will -automatically detect and use it to serialize related attributes. - -The ``ObjectNormalizer`` also takes care of methods starting with ``has``, ``get``, -and ``can``. - -During Deserialization -~~~~~~~~~~~~~~~~~~~~~~ - -PHP considers many different values as true or false. For example, the -strings ``true``, ``1``, and ``yes`` are considered true, while -``false``, ``0``, and ``no`` are considered false. - -When deserializing, the Serializer component can take care of this -automatically. This can be done by using the ``AbstractNormalizer::FILTER_BOOL`` -context option:: - - use Acme\Person; - use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - $normalizer = new ObjectNormalizer(); - $serializer = new Serializer([$normalizer]); - - $data = $serializer->denormalize(['sportsperson' => 'yes'], Person::class, context: [AbstractNormalizer::FILTER_BOOL => true]); - -This context makes the deserialization process behave like the -:phpfunction:`filter_var` function with the ``FILTER_VALIDATE_BOOL`` flag. - -.. versionadded:: 7.1 - - The ``AbstractNormalizer::FILTER_BOOL`` context option was introduced in Symfony 7.1. - -Using Callbacks to Serialize Properties with Object Instances -------------------------------------------------------------- - -When serializing, you can set a callback to format a specific object property:: - - use App\Model\Person; - use Symfony\Component\Serializer\Encoder\JsonEncoder; - use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; - use Symfony\Component\Serializer\Serializer; - - $encoder = new JsonEncoder(); - - // all callback parameters are optional (you can omit the ones you don't use) - $dateCallback = function (object $innerObject, object $outerObject, string $attributeName, string $format = null, array $context = []): string { - return $innerObject instanceof \DateTime ? $innerObject->format(\DateTime::ISO8601) : ''; - }; - - $defaultContext = [ - AbstractNormalizer::CALLBACKS => [ - 'createdAt' => $dateCallback, - ], - ]; - - $normalizer = new GetSetMethodNormalizer(null, null, null, null, null, $defaultContext); - - $serializer = new Serializer([$normalizer], [$encoder]); - - $person = new Person(); - $person->setName('cordoval'); - $person->setAge(34); - $person->setCreatedAt(new \DateTime('now')); - - $serializer->serialize($person, 'json'); - // Output: {"name":"cordoval", "age": 34, "createdAt": "2014-03-22T09:43:12-0500"} - -.. _component-serializer-normalizers: - -Normalizers ------------ - -Normalizers turn **objects** into **arrays** and vice versa. They implement -:class:`Symfony\\Component\\Serializer\\Normalizer\\NormalizerInterface` for -normalizing (object to array) and -:class:`Symfony\\Component\\Serializer\\Normalizer\\DenormalizerInterface` for -denormalizing (array to object). - -Normalizers are enabled in the serializer passing them as its first argument:: - - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - $normalizers = [new ObjectNormalizer()]; - $serializer = new Serializer($normalizers, []); - -Built-in Normalizers -~~~~~~~~~~~~~~~~~~~~ - -The Serializer component provides several built-in normalizers: - -:class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer` - This normalizer leverages the :doc:`PropertyAccess Component ` - to read and write in the object. It means that it can access to properties - directly and through getters, setters, hassers, issers, canners, adders and removers. - It supports calling the constructor during the denormalization process. - - Objects are normalized to a map of property names and values (names are - generated by removing the ``get``, ``set``, ``has``, ``is``, ``can``, ``add`` or ``remove`` - prefix from the method name and transforming the first letter to lowercase; e.g. - ``getFirstName()`` -> ``firstName``). - - The ``ObjectNormalizer`` is the most powerful normalizer. It is configured by - default in Symfony applications with the Serializer component enabled. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer` - This normalizer reads the content of the class by calling the "getters" - (public methods starting with "get"). It will denormalize data by calling - the constructor and the "setters" (public methods starting with "set"). - - Objects are normalized to a map of property names and values (names are - generated by removing the ``get`` prefix from the method name and transforming - the first letter to lowercase; e.g. ``getFirstName()`` -> ``firstName``). - -:class:`Symfony\\Component\\Serializer\\Normalizer\\PropertyNormalizer` - This normalizer directly reads and writes public properties as well as - **private and protected** properties (from both the class and all of its - parent classes) by using `PHP reflection`_. It supports calling the constructor - during the denormalization process. - - Objects are normalized to a map of property names to property values. - - If you prefer to only normalize certain properties (e.g. only public properties) - set the ``PropertyNormalizer::NORMALIZE_VISIBILITY`` context option and - combine the following values: ``PropertyNormalizer::NORMALIZE_PUBLIC``, - ``PropertyNormalizer::NORMALIZE_PROTECTED`` or ``PropertyNormalizer::NORMALIZE_PRIVATE``. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\JsonSerializableNormalizer` - This normalizer works with classes that implement :phpclass:`JsonSerializable`. - - It will call the :phpmethod:`JsonSerializable::jsonSerialize` method and - then further normalize the result. This means that nested - :phpclass:`JsonSerializable` classes will also be normalized. - - This normalizer is particularly helpful when you want to gradually migrate - from an existing codebase using simple :phpfunction:`json_encode` to the Symfony - Serializer by allowing you to mix which normalizers are used for which classes. - - Unlike with :phpfunction:`json_encode` circular references can be handled. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\DateTimeNormalizer` - This normalizer converts :phpclass:`DateTimeInterface` objects (e.g. - :phpclass:`DateTime` and :phpclass:`DateTimeImmutable`) into strings, - integers or floats. By default, it converts them to strings using the `RFC3339`_ format. - To convert the objects to integers or floats, set the serializer context option - ``DateTimeNormalizer::CAST_KEY`` to ``int`` or ``float``. - - .. versionadded:: 7.1 - - The ``DateTimeNormalizer::CAST_KEY`` context option was introduced in Symfony 7.1. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\DateTimeZoneNormalizer` - This normalizer converts :phpclass:`DateTimeZone` objects into strings that - represent the name of the timezone according to the `list of PHP timezones`_. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\DataUriNormalizer` - This normalizer converts :phpclass:`SplFileInfo` objects into a `data URI`_ - string (``data:...``) such that files can be embedded into serialized data. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\DateIntervalNormalizer` - This normalizer converts :phpclass:`DateInterval` objects into strings. - By default, it uses the ``P%yY%mM%dDT%hH%iM%sS`` format. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\BackedEnumNormalizer` - This normalizer converts a \BackedEnum objects into strings or integers. - - By default, an exception is thrown when data is not a valid backed enumeration. If you - want ``null`` instead, you can set the ``BackedEnumNormalizer::ALLOW_INVALID_VALUES`` option. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\FormErrorNormalizer` - This normalizer works with classes that implement - :class:`Symfony\\Component\\Form\\FormInterface`. - - It will get errors from the form and normalize them into a normalized array. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\ConstraintViolationListNormalizer` - This normalizer converts objects that implement - :class:`Symfony\\Component\\Validator\\ConstraintViolationListInterface` - into a list of errors according to the `RFC 7807`_ standard. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\ProblemNormalizer` - Normalizes errors according to the API Problem spec `RFC 7807`_. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\CustomNormalizer` - Normalizes a PHP object using an object that implements :class:`Symfony\\Component\\Serializer\\Normalizer\\NormalizableInterface`. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\UidNormalizer` - This normalizer converts objects that extend - :class:`Symfony\\Component\\Uid\\AbstractUid` into strings. - The default normalization format for objects that implement :class:`Symfony\\Component\\Uid\\Uuid` - is the `RFC 4122`_ format (example: ``d9e7a184-5d5b-11ea-a62a-3499710062d0``). - The default normalization format for objects that implement :class:`Symfony\\Component\\Uid\\Ulid` - is the Base 32 format (example: ``01E439TP9XJZ9RPFH3T1PYBCR8``). - You can change the string format by setting the serializer context option - ``UidNormalizer::NORMALIZATION_FORMAT_KEY`` to ``UidNormalizer::NORMALIZATION_FORMAT_BASE_58``, - ``UidNormalizer::NORMALIZATION_FORMAT_BASE_32`` or ``UidNormalizer::NORMALIZATION_FORMAT_RFC_4122``. - - Also it can denormalize ``uuid`` or ``ulid`` strings to :class:`Symfony\\Component\\Uid\\Uuid` - or :class:`Symfony\\Component\\Uid\\Ulid`. The format does not matter. - -:class:`Symfony\\Component\\Serializer\\Normalizer\\TranslatableNormalizer` - This normalizer converts objects that implement - :class:`Symfony\\Contracts\\Translation\\TranslatableInterface` into - translated strings, using the - :method:`Symfony\\Contracts\\Translation\\TranslatableInterface::trans` - method. You can define the locale to use to translate the object by - setting the ``TranslatableNormalizer::NORMALIZATION_LOCALE_KEY`` serializer - context option. - -.. note:: - - You can also create your own Normalizer to use another structure. Read more at - :doc:`/serializer/custom_normalizer`. - -Certain normalizers are enabled by default when using the Serializer component -in a Symfony application, additional ones can be enabled by tagging them with -:ref:`serializer.normalizer `. - -Here is an example of how to enable the built-in -:class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer`, a -faster alternative to the -:class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer`: - -.. configuration-block:: - - .. code-block:: yaml - - # config/services.yaml - services: - # ... - - get_set_method_normalizer: - class: Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer - tags: [serializer.normalizer] - - .. code-block:: xml - - - - - - - - - - - - - - .. code-block:: php - - // config/services.php - namespace Symfony\Component\DependencyInjection\Loader\Configurator; - - use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; - - return static function (ContainerConfigurator $container): void { - $container->services() - // ... - ->set('get_set_method_normalizer', GetSetMethodNormalizer::class) - ->tag('serializer.normalizer') - ; - }; - -.. _component-serializer-encoders: - -Encoders --------- - -Encoders turn **arrays** into **formats** and vice versa. They implement -:class:`Symfony\\Component\\Serializer\\Encoder\\EncoderInterface` -for encoding (array to format) and -:class:`Symfony\\Component\\Serializer\\Encoder\\DecoderInterface` for decoding -(format to array). - -You can add new encoders to a Serializer instance by using its second constructor argument:: - - use Symfony\Component\Serializer\Encoder\JsonEncoder; - use Symfony\Component\Serializer\Encoder\XmlEncoder; - use Symfony\Component\Serializer\Serializer; - - $encoders = [new XmlEncoder(), new JsonEncoder()]; - $serializer = new Serializer([], $encoders); - -Built-in Encoders -~~~~~~~~~~~~~~~~~ - -The Serializer component provides several built-in encoders: - -:class:`Symfony\\Component\\Serializer\\Encoder\\JsonEncoder` - This class encodes and decodes data in `JSON`_. - -:class:`Symfony\\Component\\Serializer\\Encoder\\XmlEncoder` - This class encodes and decodes data in `XML`_. - -:class:`Symfony\\Component\\Serializer\\Encoder\\YamlEncoder` - This encoder encodes and decodes data in `YAML`_. This encoder requires the - :doc:`Yaml Component `. - -:class:`Symfony\\Component\\Serializer\\Encoder\\CsvEncoder` - This encoder encodes and decodes data in `CSV`_. - -.. note:: - - You can also create your own Encoder to use another structure. Read more at - :doc:`/serializer/custom_encoders`. - -All these encoders are enabled by default when using the Serializer component -in a Symfony application. - -The ``JsonEncoder`` -~~~~~~~~~~~~~~~~~~~ - -The ``JsonEncoder`` encodes to and decodes from JSON strings, based on the PHP -:phpfunction:`json_encode` and :phpfunction:`json_decode` functions. It can be -useful to modify how these functions operate in certain instances by providing -options such as ``JSON_PRESERVE_ZERO_FRACTION``. You can use the serialization -context to pass in these options using the key ``json_encode_options`` or -``json_decode_options`` respectively:: - - $this->serializer->serialize($data, 'json', ['json_encode_options' => \JSON_PRESERVE_ZERO_FRACTION]); - -These are the options available: - -=============================== =========================================================================================================== ================================ -Option Description Default -=============================== ========================================================================================================== ================================ -``json_decode_associative`` If set to true returns the result as an array, returns a nested ``stdClass`` hierarchy otherwise. ``false`` -``json_decode_detailed_errors`` If set to true, exceptions thrown on parsing of JSON are more specific. Requires `seld/jsonlint`_ package. ``false`` -``json_decode_options`` `$flags`_ passed to :phpfunction:`json_decode` function. ``0`` -``json_encode_options`` `$flags`_ passed to :phpfunction:`json_encode` function. ``\JSON_PRESERVE_ZERO_FRACTION`` -``json_decode_recursion_depth`` Sets maximum recursion depth. ``512`` -=============================== ========================================================================================================== ================================ - -The ``CsvEncoder`` -~~~~~~~~~~~~~~~~~~ - -The ``CsvEncoder`` encodes to and decodes from CSV. - -The ``CsvEncoder`` Context Options -.................................. - -The ``encode()`` method defines a third optional parameter called ``context`` -which defines the configuration options for the CsvEncoder an associative array:: - - $csvEncoder->encode($array, 'csv', $context); - -These are the options available: - -======================= ===================================================== ========================== -Option Description Default -======================= ===================================================== ========================== -``csv_delimiter`` Sets the field delimiter separating values (one ``,`` - character only) -``csv_enclosure`` Sets the field enclosure (one character only) ``"`` -``csv_end_of_line`` Sets the character(s) used to mark the end of each ``\n`` - line in the CSV file -``csv_escape_char`` Sets the escape character (at most one character) empty string -``csv_key_separator`` Sets the separator for array's keys during its ``.`` - flattening -``csv_headers`` Sets the order of the header and data columns - E.g.: if ``$data = ['c' => 3, 'a' => 1, 'b' => 2]`` - and ``$options = ['csv_headers' => ['a', 'b', 'c']]`` - then ``serialize($data, 'csv', $options)`` returns - ``a,b,c\n1,2,3`` ``[]``, inferred from input data's keys -``csv_escape_formulas`` Escapes fields containing formulas by prepending them ``false`` - with a ``\t`` character -``as_collection`` Always returns results as a collection, even if only ``true`` - one line is decoded. -``no_headers`` Setting to ``false`` will use first row as headers. ``false`` - ``true`` generate numeric headers. -``output_utf8_bom`` Outputs special `UTF-8 BOM`_ along with encoded data ``false`` -======================= ===================================================== ========================== - -The ``XmlEncoder`` -~~~~~~~~~~~~~~~~~~ - -This encoder transforms arrays into XML and vice versa. - -For example, take an object normalized as following:: - - ['foo' => [1, 2], 'bar' => true]; - -The ``XmlEncoder`` will encode this object like that: - -.. code-block:: xml - - - - 1 - 2 - 1 - - -The special ``#`` key can be used to define the data of a node:: - - ['foo' => ['@bar' => 'value', '#' => 'baz']]; - - // is encoded as follows: - // - // - // - // baz - // - // - -Furthermore, keys beginning with ``@`` will be considered attributes, and -the key ``#comment`` can be used for encoding XML comments:: - - $encoder = new XmlEncoder(); - $encoder->encode([ - 'foo' => ['@bar' => 'value'], - 'qux' => ['#comment' => 'A comment'], - ], 'xml'); - // will return: - // - // - // - // - // - -You can pass the context key ``as_collection`` in order to have the results -always as a collection. - -.. note:: - - You may need to add some attributes on the root node:: - - $encoder = new XmlEncoder(); - $encoder->encode([ - '@attribute1' => 'foo', - '@attribute2' => 'bar', - '#' => ['foo' => ['@bar' => 'value', '#' => 'baz']] - ], 'xml'); - - // will return: - // - // - // baz - // - -.. tip:: - - XML comments are ignored by default when decoding contents, but this - behavior can be changed with the optional context key ``XmlEncoder::DECODER_IGNORED_NODE_TYPES``. - - Data with ``#comment`` keys are encoded to XML comments by default. This can be - changed by adding the ``\XML_COMMENT_NODE`` option to the ``XmlEncoder::ENCODER_IGNORED_NODE_TYPES`` - key of the ``$defaultContext`` of the ``XmlEncoder`` constructor or - directly to the ``$context`` argument of the ``encode()`` method:: - - $xmlEncoder->encode($array, 'xml', [XmlEncoder::ENCODER_IGNORED_NODE_TYPES => [\XML_COMMENT_NODE]]); - -The ``XmlEncoder`` Context Options -.................................. - -The ``encode()`` method defines a third optional parameter called ``context`` -which defines the configuration options for the XmlEncoder an associative array:: - - $xmlEncoder->encode($array, 'xml', $context); - -These are the options available: - -============================== ================================================= ========================== -Option Description Default -============================== ================================================= ========================== -``xml_format_output`` If set to true, formats the generated XML with ``false`` - line breaks and indentation -``xml_version`` Sets the XML version attribute ``1.0`` -``xml_encoding`` Sets the XML encoding attribute ``utf-8`` -``xml_standalone`` Adds standalone attribute in the generated XML ``true`` -``xml_type_cast_attributes`` This provides the ability to forget the attribute ``true`` - type casting -``xml_root_node_name`` Sets the root node name ``response`` -``as_collection`` Always returns results as a collection, even if ``false`` - only one line is decoded -``decoder_ignored_node_types`` Array of node types (`DOM XML_* constants`_) ``[\XML_PI_NODE, \XML_COMMENT_NODE]`` - to be ignored while decoding -``encoder_ignored_node_types`` Array of node types (`DOM XML_* constants`_) ``[]`` - to be ignored while encoding -``load_options`` XML loading `options with libxml`_ ``\LIBXML_NONET | \LIBXML_NOBLANKS`` -``save_options`` XML saving `options with libxml`_ ``0`` -``remove_empty_tags`` If set to true, removes all empty tags in the ``false`` - generated XML -============================== ================================================= ========================== - -Example with custom ``context``:: - - use Symfony\Component\Serializer\Encoder\XmlEncoder; - - // create encoder with specified options as new default settings - $xmlEncoder = new XmlEncoder(['xml_format_output' => true]); - - $data = [ - 'id' => 'IDHNQIItNyQ', - 'date' => '2019-10-24', - ]; - - // encode with default context - $xmlEncoder->encode($data, 'xml'); - // outputs: - // - // - // IDHNQIItNyQ - // 2019-10-24 - // - - // encode with modified context - $xmlEncoder->encode($data, 'xml', [ - 'xml_root_node_name' => 'track', - 'encoder_ignored_node_types' => [ - \XML_PI_NODE, // removes XML declaration (the leading xml tag) - ], - ]); - // outputs: - // - // IDHNQIItNyQ - // 2019-10-24 - // - -The ``YamlEncoder`` -~~~~~~~~~~~~~~~~~~~ - -This encoder requires the :doc:`Yaml Component ` and -transforms from and to Yaml. - -The ``YamlEncoder`` Context Options -................................... - -The ``encode()`` method, like other encoder, uses ``context`` to set -configuration options for the YamlEncoder an associative array:: - - $yamlEncoder->encode($array, 'yaml', $context); - -These are the options available: - -=============== ======================================================== ========================== -Option Description Default -=============== ======================================================== ========================== -``yaml_inline`` The level where you switch to inline YAML ``0`` -``yaml_indent`` The level of indentation (used internally) ``0`` -``yaml_flags`` A bit field of ``Yaml::DUMP_*`` / ``PARSE_*`` constants ``0`` - to customize the encoding / decoding YAML string -=============== ======================================================== ========================== - -.. _component-serializer-context-builders: - -Context Builders ----------------- - -Instead of passing plain PHP arrays to the :ref:`serialization context `, -you can use "context builders" to define the context using a fluent interface:: - - use Symfony\Component\Serializer\Context\Encoder\CsvEncoderContextBuilder; - use Symfony\Component\Serializer\Context\Normalizer\ObjectNormalizerContextBuilder; - - $initialContext = [ - 'custom_key' => 'custom_value', - ]; - - $contextBuilder = (new ObjectNormalizerContextBuilder()) - ->withContext($initialContext) - ->withGroups(['group1', 'group2']); - - $contextBuilder = (new CsvEncoderContextBuilder()) - ->withContext($contextBuilder) - ->withDelimiter(';'); - - $serializer->serialize($something, 'csv', $contextBuilder->toArray()); - -.. note:: - - The Serializer component provides a context builder - for each :ref:`normalizer ` - and :ref:`encoder `. - - You can also :doc:`create custom context builders ` - to deal with your context values. - -Skipping ``null`` Values ------------------------- - -By default, the Serializer will preserve properties containing a ``null`` value. -You can change this behavior by setting the ``AbstractObjectNormalizer::SKIP_NULL_VALUES`` context option -to ``true``:: - - $dummy = new class { - public ?string $foo = null; - public string $bar = 'notNull'; - }; - - $normalizer = new ObjectNormalizer(); - $result = $normalizer->normalize($dummy, 'json', [AbstractObjectNormalizer::SKIP_NULL_VALUES => true]); - // ['bar' => 'notNull'] - -Require all Properties ----------------------- - -By default, the Serializer will add ``null`` to nullable properties when the parameters for those are not provided. -You can change this behavior by setting the ``AbstractNormalizer::REQUIRE_ALL_PROPERTIES`` context option -to ``true``:: - - class Dummy - { - public function __construct( - public string $foo, - public ?string $bar, - ) { - } - } - - $data = ['foo' => 'notNull']; - - $normalizer = new ObjectNormalizer(); - $result = $normalizer->denormalize($data, Dummy::class, 'json', [AbstractNormalizer::REQUIRE_ALL_PROPERTIES => true]); - // throws Symfony\Component\Serializer\Exception\MissingConstructorArgumentException - -Skipping Uninitialized Properties ---------------------------------- - -In PHP, typed properties have an ``uninitialized`` state which is different -from the default ``null`` of untyped properties. When you try to access a typed -property before giving it an explicit value, you get an error. - -To avoid the Serializer throwing an error when serializing or normalizing an -object with uninitialized properties, by default the object normalizer catches -these errors and ignores such properties. - -You can disable this behavior by setting the ``AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES`` -context option to ``false``:: - - class Dummy { - public string $foo = 'initialized'; - public string $bar; // uninitialized - } - - $normalizer = new ObjectNormalizer(); - $result = $normalizer->normalize(new Dummy(), 'json', [AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES => false]); - // throws Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException as normalizer cannot read uninitialized properties - -.. note:: - - Calling ``PropertyNormalizer::normalize`` or ``GetSetMethodNormalizer::normalize`` - with ``AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES`` context option set - to ``false`` will throw an ``\Error`` instance if the given object has uninitialized - properties as the normalizer cannot read them (directly or via getter/isser methods). - -.. _component-serializer-handling-circular-references: - -Collecting Type Errors While Denormalizing ------------------------------------------- - -When denormalizing a payload to an object with typed properties, you'll get an -exception if the payload contains properties that don't have the same type as -the object. - -In those situations, use the ``COLLECT_DENORMALIZATION_ERRORS`` option to -collect all exceptions at once, and to get the object partially denormalized:: - - try { - $dto = $serializer->deserialize($request->getContent(), MyDto::class, 'json', [ - DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true, - ]); - } catch (PartialDenormalizationException $e) { - $violations = new ConstraintViolationList(); - /** @var NotNormalizableValueException $exception */ - foreach ($e->getErrors() as $exception) { - $message = sprintf('The type must be one of "%s" ("%s" given).', implode(', ', $exception->getExpectedTypes()), $exception->getCurrentType()); - $parameters = []; - if ($exception->canUseMessageForUser()) { - $parameters['hint'] = $exception->getMessage(); - } - $violations->add(new ConstraintViolation($message, '', $parameters, null, $exception->getPath(), null)); - } - - return $this->json($violations, 400); - } - -Handling Circular References ----------------------------- - -Circular references are common when dealing with entity relations:: - - class Organization - { - private string $name; - private array $members; - - public function setName($name): void - { - $this->name = $name; - } - - public function getName(): string - { - return $this->name; - } - - public function setMembers(array $members): void - { - $this->members = $members; - } - - public function getMembers(): array - { - return $this->members; - } - } - - class Member - { - private string $name; - private Organization $organization; - - public function setName(string $name): void - { - $this->name = $name; - } - - public function getName(): string - { - return $this->name; - } - - public function setOrganization(Organization $organization): void - { - $this->organization = $organization; - } - - public function getOrganization(): Organization - { - return $this->organization; - } - } - -To avoid infinite loops, :class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer` -or :class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer` -throw a :class:`Symfony\\Component\\Serializer\\Exception\\CircularReferenceException` -when such a case is encountered:: - - $member = new Member(); - $member->setName('Kévin'); - - $organization = new Organization(); - $organization->setName('Les-Tilleuls.coop'); - $organization->setMembers([$member]); - - $member->setOrganization($organization); - - echo $serializer->serialize($organization, 'json'); // Throws a CircularReferenceException - -The key ``circular_reference_limit`` in the default context sets the number of -times it will serialize the same object before considering it a circular -reference. The default value is ``1``. - -Instead of throwing an exception, circular references can also be handled -by custom callables. This is especially useful when serializing entities -having unique identifiers:: - - $encoder = new JsonEncoder(); - $defaultContext = [ - AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function (object $object, string $format, array $context): string { - return $object->getName(); - }, - ]; - $normalizer = new ObjectNormalizer(null, null, null, null, null, null, $defaultContext); - - $serializer = new Serializer([$normalizer], [$encoder]); - var_dump($serializer->serialize($org, 'json')); - // {"name":"Les-Tilleuls.coop","members":[{"name":"K\u00e9vin", organization: "Les-Tilleuls.coop"}]} - -.. _serializer_handling-serialization-depth: - -Handling Serialization Depth ----------------------------- - -The Serializer component is able to detect and limit the serialization depth. -It is especially useful when serializing large trees. Assume the following data -structure:: - - namespace Acme; - - class MyObj - { - public string $foo; - - /** - * @var self - */ - public MyObj $child; - } - - $level1 = new MyObj(); - $level1->foo = 'level1'; - - $level2 = new MyObj(); - $level2->foo = 'level2'; - $level1->child = $level2; - - $level3 = new MyObj(); - $level3->foo = 'level3'; - $level2->child = $level3; - -The serializer can be configured to set a maximum depth for a given property. -Here, we set it to 2 for the ``$child`` property: - -.. configuration-block:: - - .. code-block:: php-attributes - - namespace Acme; - - use Symfony\Component\Serializer\Annotation\MaxDepth; - - class MyObj - { - #[MaxDepth(2)] - public MyObj $child; - - // ... - } - - .. code-block:: yaml - - Acme\MyObj: - attributes: - child: - max_depth: 2 - - .. code-block:: xml - - - - - - - - -The metadata loader corresponding to the chosen format must be configured in -order to use this feature. It is done automatically when using the Serializer component -in a Symfony application. When using the standalone component, refer to -:ref:`the groups documentation ` to -learn how to do that. - -The check is only done if the ``AbstractObjectNormalizer::ENABLE_MAX_DEPTH`` key of the serializer context -is set to ``true``. In the following example, the third level is not serialized -because it is deeper than the configured maximum depth of 2:: - - $result = $serializer->normalize($level1, null, [AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true]); - /* - $result = [ - 'foo' => 'level1', - 'child' => [ - 'foo' => 'level2', - 'child' => [ - 'child' => null, - ], - ], - ]; - */ - -Instead of throwing an exception, a custom callable can be executed when the -maximum depth is reached. This is especially useful when serializing entities -having unique identifiers:: - - use Symfony\Component\Serializer\Annotation\MaxDepth; - use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; - use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; - use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - class Foo - { - public int $id; - - #[MaxDepth(1)] - public MyObj $child; - } - - $level1 = new Foo(); - $level1->id = 1; - - $level2 = new Foo(); - $level2->id = 2; - $level1->child = $level2; - - $level3 = new Foo(); - $level3->id = 3; - $level2->child = $level3; - - $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); - - // all callback parameters are optional (you can omit the ones you don't use) - $maxDepthHandler = function (object $innerObject, object $outerObject, string $attributeName, string $format = null, array $context = []): string { - return '/foos/'.$innerObject->id; - }; - - $defaultContext = [ - AbstractObjectNormalizer::MAX_DEPTH_HANDLER => $maxDepthHandler, - ]; - $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, null, null, null, $defaultContext); - - $serializer = new Serializer([$normalizer]); - - $result = $serializer->normalize($level1, null, [AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true]); - /* - $result = [ - 'id' => 1, - 'child' => [ - 'id' => 2, - 'child' => '/foos/3', - ], - ]; - */ - -Handling Arrays ---------------- - -The Serializer component is capable of handling arrays of objects as well. -Serializing arrays works just like serializing a single object:: - - use Acme\Person; - - $person1 = new Person(); - $person1->setName('foo'); - $person1->setAge(99); - $person1->setSportsman(false); - - $person2 = new Person(); - $person2->setName('bar'); - $person2->setAge(33); - $person2->setSportsman(true); - - $persons = [$person1, $person2]; - $data = $serializer->serialize($persons, 'json'); - - // $data contains [{"name":"foo","age":99,"sportsman":false},{"name":"bar","age":33,"sportsman":true}] - -If you want to deserialize such a structure, you need to add the -:class:`Symfony\\Component\\Serializer\\Normalizer\\ArrayDenormalizer` -to the set of normalizers. By appending ``[]`` to the type parameter of the -:method:`Symfony\\Component\\Serializer\\Serializer::deserialize` method, -you indicate that you're expecting an array instead of a single object:: - - use Symfony\Component\Serializer\Encoder\JsonEncoder; - use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; - use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; - use Symfony\Component\Serializer\Serializer; - - $serializer = new Serializer( - [new GetSetMethodNormalizer(), new ArrayDenormalizer()], - [new JsonEncoder()] - ); - - $data = ...; // The serialized data from the previous example - $persons = $serializer->deserialize($data, 'Acme\Person[]', 'json'); - -Handling Constructor Arguments ------------------------------- - -If the class constructor defines arguments, as usually happens with -`Value Objects`_, the serializer won't be able to create the object if some -arguments are missing. In those cases, use the ``default_constructor_arguments`` -context option:: - - use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - class MyObj - { - public function __construct( - private string $foo, - private string $bar, - ) { - } - } - - $normalizer = new ObjectNormalizer($classMetadataFactory); - $serializer = new Serializer([$normalizer]); - - $data = $serializer->denormalize( - ['foo' => 'Hello'], - 'MyObj', - null, - [AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS => [ - 'MyObj' => ['foo' => '', 'bar' => ''], - ]] - ); - // $data = new MyObj('Hello', ''); - -Recursive Denormalization and Type Safety ------------------------------------------ - -The Serializer component can use the :doc:`PropertyInfo Component ` to denormalize -complex types (objects). The type of the class' property will be guessed using the provided -extractor and used to recursively denormalize the inner data. - -When using this component in a Symfony application, all normalizers are automatically configured to use the registered extractors. -When using the component standalone, an implementation of :class:`Symfony\\Component\\PropertyInfo\\PropertyTypeExtractorInterface`, -(usually an instance of :class:`Symfony\\Component\\PropertyInfo\\PropertyInfoExtractor`) must be passed as the 4th -parameter of the ``ObjectNormalizer``:: - - namespace Acme; - - use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; - use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - class ObjectOuter - { - private ObjectInner $inner; - private \DateTimeInterface $date; - - public function getInner(): ObjectInner - { - return $this->inner; - } - - public function setInner(ObjectInner $inner): void - { - $this->inner = $inner; - } - - public function getDate(): \DateTimeInterface - { - return $this->date; - } - - public function setDate(\DateTimeInterface $date): void - { - $this->date = $date; - } - } - - class ObjectInner - { - public string $foo; - public string $bar; - } - - $normalizer = new ObjectNormalizer(null, null, null, new ReflectionExtractor()); - $serializer = new Serializer([new DateTimeNormalizer(), $normalizer]); - - $obj = $serializer->denormalize( - ['inner' => ['foo' => 'foo', 'bar' => 'bar'], 'date' => '1988/01/21'], - 'Acme\ObjectOuter' - ); - - dump($obj->getInner()->foo); // 'foo' - dump($obj->getInner()->bar); // 'bar' - dump($obj->getDate()->format('Y-m-d')); // '1988-01-21' - -When a ``PropertyTypeExtractor`` is available, the normalizer will also check that the data to denormalize -matches the type of the property (even for primitive types). For instance, if a ``string`` is provided, but -the type of the property is ``int``, an :class:`Symfony\\Component\\Serializer\\Exception\\UnexpectedValueException` -will be thrown. The type enforcement of the properties can be disabled by setting -the serializer context option ``ObjectNormalizer::DISABLE_TYPE_ENFORCEMENT`` -to ``true``. - -.. _serializer_interfaces-and-abstract-classes: - -Serializing Interfaces and Abstract Classes -------------------------------------------- - -When dealing with objects that are fairly similar or share properties, you may -use interfaces or abstract classes. The Serializer component allows you to -serialize and deserialize these objects using a *"discriminator class mapping"*. - -The discriminator is the field (in the serialized string) used to differentiate -between the possible objects. In practice, when using the Serializer component, -pass a :class:`Symfony\\Component\\Serializer\\Mapping\\ClassDiscriminatorResolverInterface` -implementation to the :class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer`. - -The Serializer component provides an implementation of ``ClassDiscriminatorResolverInterface`` -called :class:`Symfony\\Component\\Serializer\\Mapping\\ClassDiscriminatorFromClassMetadata` -which uses the class metadata factory and a mapping configuration to serialize -and deserialize objects of the correct class. - -When using this component inside a Symfony application and the class metadata factory is enabled -as explained in the :ref:`Attributes Groups section `, -this is already set up and you only need to provide the configuration. Otherwise:: - - // ... - use Symfony\Component\Serializer\Encoder\JsonEncoder; - use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; - use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping; - use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; - use Symfony\Component\Serializer\Serializer; - - $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); - - $discriminator = new ClassDiscriminatorFromClassMetadata($classMetadataFactory); - - $serializer = new Serializer( - [new ObjectNormalizer($classMetadataFactory, null, null, null, $discriminator)], - ['json' => new JsonEncoder()] - ); - -Now configure your discriminator class mapping. Consider an application that -defines an abstract ``CodeRepository`` class extended by ``GitHubCodeRepository`` -and ``BitBucketCodeRepository`` classes: - -.. configuration-block:: - - .. code-block:: php-attributes - - namespace App; - - use App\BitBucketCodeRepository; - use App\GitHubCodeRepository; - use Symfony\Component\Serializer\Annotation\DiscriminatorMap; - - #[DiscriminatorMap(typeProperty: 'type', mapping: [ - 'github' => GitHubCodeRepository::class, - 'bitbucket' => BitBucketCodeRepository::class, - ])] - abstract class CodeRepository - { - // ... - } - - .. code-block:: yaml - - App\CodeRepository: - discriminator_map: - type_property: type - mapping: - github: 'App\GitHubCodeRepository' - bitbucket: 'App\BitBucketCodeRepository' - - .. code-block:: xml - - - - - - - - - - - -.. note:: - - The values of the ``mapping`` array option must be strings. - Otherwise, they will be cast into strings automatically. - -Once configured, the serializer uses the mapping to pick the correct class:: - - $serialized = $serializer->serialize(new GitHubCodeRepository(), 'json'); - // {"type": "github"} - - $repository = $serializer->deserialize($serialized, CodeRepository::class, 'json'); - // instanceof GitHubCodeRepository - -Learn more ----------- - -.. toctree:: - :maxdepth: 1 - :glob: - - /serializer - -.. seealso:: - - Normalizers for the Symfony Serializer Component supporting popular web API formats - (JSON-LD, GraphQL, OpenAPI, HAL, JSON:API) are available as part of the `API Platform`_ project. - -.. seealso:: - - A popular alternative to the Symfony Serializer component is the third-party - library, `JMS serializer`_ (versions before ``v1.12.0`` were released under - the Apache license, so incompatible with GPLv2 projects). - -.. _`PSR-1 standard`: https://www.php-fig.org/psr/psr-1/ -.. _`JMS serializer`: https://github.com/schmittjoh/serializer -.. _RFC3339: https://tools.ietf.org/html/rfc3339#section-5.8 -.. _`options with libxml`: https://www.php.net/manual/en/libxml.constants.php -.. _`DOM XML_* constants`: https://www.php.net/manual/en/dom.constants.php -.. _JSON: https://www.json.org/json-en.html -.. _XML: https://www.w3.org/XML/ -.. _YAML: https://yaml.org/ -.. _CSV: https://tools.ietf.org/html/rfc4180 -.. _`RFC 7807`: https://tools.ietf.org/html/rfc7807 -.. _`UTF-8 BOM`: https://en.wikipedia.org/wiki/Byte_order_mark -.. _`Value Objects`: https://en.wikipedia.org/wiki/Value_object -.. _`API Platform`: https://api-platform.com -.. _`list of PHP timezones`: https://www.php.net/manual/en/timezones.php -.. _`RFC 4122`: https://tools.ietf.org/html/rfc4122 -.. _`PHP reflection`: https://php.net/manual/en/book.reflection.php -.. _`data URI`: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs -.. _seld/jsonlint: https://github.com/Seldaek/jsonlint -.. _$flags: https://www.php.net/manual/en/json.constants.php diff --git a/components/type_info.rst b/components/type_info.rst new file mode 100644 index 00000000000..2fd64b9bc5d --- /dev/null +++ b/components/type_info.rst @@ -0,0 +1,86 @@ +The TypeInfo Component +====================== + +The TypeInfo component extracts type information from PHP elements like properties, +arguments and return types. + +This component provides: + +* A powerful ``Type`` definition that can handle unions, intersections, and generics + (and can be extended to support more types in the future); +* A way to get types from PHP elements such as properties, method arguments, + return types, and raw strings. + +.. warning:: + + This component is :doc:`experimental ` and + could be changed at any time without prior notice. + +Installation +------------ + +.. code-block:: terminal + + $ composer require symfony/type-info + +.. include:: /components/require_autoload.rst.inc + +Usage +----- + +This component gives you a :class:`Symfony\\Component\\TypeInfo\\Type` object that +represents the PHP type of anything you built or asked to resolve. + +There are two ways to use this component. First one is to create a type manually thanks +to the :class:`Symfony\\Component\\TypeInfo\\Type` static methods as following:: + + use Symfony\Component\TypeInfo\Type; + + Type::int(); + Type::nullable(Type::string()); + Type::generic(Type::object(Collection::class), Type::int()); + Type::list(Type::bool()); + Type::intersection(Type::object(\Stringable::class), Type::object(\Iterator::class)); + + // Many others are available and can be + // found in Symfony\Component\TypeInfo\TypeFactoryTrait + +The second way of using the component is to use ``TypeInfo`` to resolve a type +based on reflection or a simple string:: + + use Symfony\Component\TypeInfo\Type; + use Symfony\Component\TypeInfo\TypeResolver\TypeResolver; + + // Instantiate a new resolver + $typeResolver = TypeResolver::create(); + + // Then resolve types for any subject + $typeResolver->resolve(new \ReflectionProperty(Dummy::class, 'id')); // returns an "int" Type instance + $typeResolver->resolve('bool'); // returns a "bool" Type instance + + // Types can be instantiated thanks to static factories + $type = Type::list(Type::nullable(Type::bool())); + + // Type instances have several helper methods + + // returns the main type (e.g. in this example it returns an "array" Type instance); + // for nullable types (e.g. string|null) it returns the non-null type (e.g. string) + // and for compound types (e.g. int|string) it throws an exception because both types + // can be considered the main one, so there's no way to pick one + $baseType = $type->getBaseType(); + + // for collections, it returns the type of the item used as the key; + // in this example, the collection is a list, so it returns an "int" Type instance + $keyType = $type->getCollectionKeyType(); + + // you can chain the utility methods (e.g. to introspect the values of the collection) + // the following code will return true + $isValueNullable = $type->getCollectionValueType()->isNullable(); + +Each of these calls will return you a ``Type`` instance that corresponds to the +static method used. You can also resolve types from a string (as shown in the +``bool`` parameter of the previous example) + +.. note:: + + To support raw string resolving, you need to install ``phpstan/phpdoc-parser`` package. diff --git a/components/uid.rst b/components/uid.rst index 2f3b2118c5b..5077fb4f1c8 100644 --- a/components/uid.rst +++ b/components/uid.rst @@ -97,6 +97,8 @@ It's the same as UUIDv3 (explained above) but it uses ``sha1`` instead of ``md5`` to hash the given namespace and name (`read UUIDv5 spec `__). This makes it more secure and less prone to hash collisions. +.. _uid-uuid-v6: + **UUID v6** (reordered time-based) It rearranges the time-based fields of the UUIDv1 to make it lexicographically @@ -113,6 +115,8 @@ sortable (like :ref:`ULIDs `). It's more efficient for database indexing It's recommended to use UUIDv7 instead of UUIDv6 because it provides better entropy. +.. _uid-uuid-v7: + **UUID v7** (UNIX timestamp) Generates time-ordered UUIDs based on a high-resolution Unix Epoch timestamp @@ -357,6 +361,14 @@ entity primary keys:: // ... } +.. warning:: + + Using UUIDs as primary keys is usually not recommended for performance reasons: + indexes are slower and take more space (because UUIDs in binary format take + 128 bits instead of 32/64 bits for auto-incremental integers) and the non-sequential + nature of UUIDs fragments indexes. :ref:`UUID v6 ` and :ref:`UUID v7 ` + are the only variants that solve the fragmentation issue (but the index size issue remains). + When using built-in Doctrine repository methods (e.g. ``findOneBy()``), Doctrine knows how to convert these UUID types to build the SQL query (e.g. ``->findOneBy(['user' => $user->getUuid()])``). However, when using DQL @@ -535,9 +547,15 @@ entity primary keys:: } // ... - } +.. warning:: + + Using ULIDs as primary keys is usually not recommended for performance reasons. + Although ULIDs don't suffer from index fragmentation issues (because the values + are sequential), their indexes are slower and take more space (because ULIDs + in binary format take 128 bits instead of 32/64 bits for auto-incremental integers). + When using built-in Doctrine repository methods (e.g. ``findOneBy()``), Doctrine knows how to convert these ULID types to build the SQL query (e.g. ``->findOneBy(['user' => $user->getUlid()])``). However, when using DQL diff --git a/components/validator/resources.rst b/components/validator/resources.rst index c1474c1710d..5b1448dfba1 100644 --- a/components/validator/resources.rst +++ b/components/validator/resources.rst @@ -171,7 +171,7 @@ You can set this custom implementation using ->setMetadataFactory(new CustomMetadataFactory(...)) ->getValidator(); -.. caution:: +.. warning:: Since you are using a custom metadata factory, you can't configure loaders and caches using the ``add*Mapping()`` methods anymore. You now have to diff --git a/components/var_dumper.rst b/components/var_dumper.rst index d54a789cb96..3f59ff1b796 100644 --- a/components/var_dumper.rst +++ b/components/var_dumper.rst @@ -387,7 +387,7 @@ then its dump representation:: .. note:: - `#14` is the internal object handle. It allows comparing two + ``#14`` is the internal object handle. It allows comparing two consecutive dumps of the same object. .. code-block:: php @@ -623,7 +623,7 @@ For example, to get a dump as a string in a variable, you can do:: $dumper->dump( $cloner->cloneVar($variable), - function (int $line, int $depth) use (&$output): void { + function (string $line, int $depth) use (&$output): void { // A negative depth means "end of dump" if ($depth >= 0) { // Adds a two spaces indentation to the line diff --git a/components/workflow.rst b/components/workflow.rst index 12a9c98ab2f..e3da25b3476 100644 --- a/components/workflow.rst +++ b/components/workflow.rst @@ -75,7 +75,7 @@ Here's an example of using the workflow defined above:: Initialization -------------- -If the property of your object is ``null`` and you want to set it with the +If the marking property of your object is ``null`` and you want to set it with the ``initial_marking`` from the configuration, you can call the ``getMarking()`` method to initialize the object property:: @@ -85,41 +85,6 @@ method to initialize the object property:: // initiate workflow $workflow->getMarking($blogPost); -Using The Workflow Registry ---------------------------- - -When you define multiple workflows you may consider using a ``Registry``, -which is an object that stores and provides access to different workflows. -A registry will also help you to decide if a workflow supports the object you -are trying to use it with:: - - use Acme\Entity\BlogPost; - use Acme\Entity\Newsletter; - use Symfony\Component\Workflow\Registry; - use Symfony\Component\Workflow\SupportStrategy\InstanceOfSupportStrategy; - - $blogPostWorkflow = ...; - $newsletterWorkflow = ...; - - $registry = new Registry(); - $registry->addWorkflow($blogPostWorkflow, new InstanceOfSupportStrategy(BlogPost::class)); - $registry->addWorkflow($newsletterWorkflow, new InstanceOfSupportStrategy(Newsletter::class)); - -You can then use the registry to get the workflow for a specific object:: - - $blogPost = new BlogPost(); - $workflow = $registry->get($blogPost); - - // initiate workflow - $workflow->getMarking($blogPost); - -.. caution:: - - Beware that injecting the ``Registry`` into your services is **not** - recommended. Indeed, it prevents some optimization like lazy-loading - from working and could be a performance hog. Instead, you should always - inject the workflow you need. - Learn more ---------- diff --git a/components/yaml.rst b/components/yaml.rst index 5f724e0572c..ea1c1f4af3a 100644 --- a/components/yaml.rst +++ b/components/yaml.rst @@ -489,7 +489,7 @@ Add the ``--format`` option to get the output in JSON format: .. code-block:: terminal - $ php lint.php path/to/file.yaml --format json + $ php lint.php path/to/file.yaml --format=json .. tip:: diff --git a/configuration.rst b/configuration.rst index 728f0e4e8b5..30d9ade5869 100644 --- a/configuration.rst +++ b/configuration.rst @@ -228,7 +228,7 @@ reusable configuration value. By convention, parameters are defined under the App\Entity\BlogPost::MAX_ITEMS - App\Enum\PostState::Published + App\Enum\PostState::Published @@ -267,7 +267,7 @@ reusable configuration value. By convention, parameters are defined under the // ... -.. caution:: +.. warning:: By default and when using XML configuration, the values between ```` tags are not trimmed. This means that the value of the following parameter will be @@ -379,7 +379,7 @@ a new ``locale`` parameter is added to the ``config/services.yaml`` file). By convention, parameters whose names start with a dot ``.`` (for example, ``.mailer.transport``), are available only during the container compilation. - They are useful when working with :ref:`Compiler Passes ` + They are useful when working with :doc:`Compiler Passes ` to declare some temporary parameters that won't be available later in the application. .. seealso:: @@ -795,7 +795,7 @@ Use environment variables in values by prefixing variables with ``$``: DB_USER=root DB_PASS=${DB_USER}pass # include the user as a password prefix -.. caution:: +.. warning:: The order is important when some env var depends on the value of other env vars. In the above example, ``DB_PASS`` must be defined after ``DB_USER``. @@ -816,7 +816,7 @@ Embed commands via ``$()`` (not supported on Windows): START_TIME=$(date) -.. caution:: +.. warning:: Using ``$()`` might not work depending on your shell. @@ -954,15 +954,7 @@ path is part of the options you can set in your ``composer.json`` file: } } -You can also set the ``SYMFONY_DOTENV_PATH`` environment variable at system -level (e.g. in your web server configuration or in your Dockerfile): - -.. code-block:: bash - - # .env (or .env.local) - SYMFONY_DOTENV_PATH=my/custom/path/to/.env - -Finally, you can directly invoke the ``Dotenv`` class in your +As an alternate option, you can directly invoke the ``Dotenv`` class in your ``bootstrap.php`` file or any other file of your application:: use Symfony\Component\Dotenv\Dotenv; @@ -975,9 +967,13 @@ the local and environment-specific files (e.g. ``.*.local`` and :ref:`how to override environment variables ` to learn more about this. +If you need to know the path to the ``.env`` file that Symfony is using, you can +read the ``SYMFONY_DOTENV_PATH`` environment variable in your application. + .. versionadded:: 7.1 - The ``SYMFONY_DOTENV_PATH`` environment variable was introduced in Symfony 7.1. + The ``SYMFONY_DOTENV_PATH`` environment variable was introduced in Symfony + 7.1. .. _configuration-secrets: diff --git a/configuration/env_var_processors.rst b/configuration/env_var_processors.rst index baf4037d05a..2e82104db66 100644 --- a/configuration/env_var_processors.rst +++ b/configuration/env_var_processors.rst @@ -687,7 +687,7 @@ Symfony provides the following env var processors: ], ]); - .. caution:: + .. warning:: In order to ease extraction of the resource from the URL, the leading ``/`` is trimmed from the ``path`` component. diff --git a/configuration/micro_kernel_trait.rst b/configuration/micro_kernel_trait.rst index cb47a901859..b67335514a1 100644 --- a/configuration/micro_kernel_trait.rst +++ b/configuration/micro_kernel_trait.rst @@ -120,6 +120,12 @@ Next, create an ``index.php`` file that defines the kernel class and runs it: $response->send(); $kernel->terminate($request, $response); +.. note:: + + In addition to the ``index.php`` file, you'll need to create a directory called + ``config/`` in your project (even if it's empty because you define the configuration + options inside the ``configureContainer()`` method). + That's it! To test it, start the :doc:`Symfony Local Web Server `: diff --git a/configuration/multiple_kernels.rst b/configuration/multiple_kernels.rst index 4cef8b0d09e..ec8742213b5 100644 --- a/configuration/multiple_kernels.rst +++ b/configuration/multiple_kernels.rst @@ -117,7 +117,9 @@ resources:: // src/Kernel.php namespace Shared; + use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; + use Symfony\Component\HttpKernel\Kernel as BaseKernel; use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; class Kernel extends BaseKernel @@ -227,7 +229,7 @@ but it should typically be added to your web server configuration. # .env APP_ID=api -.. caution:: +.. warning:: The value of this variable must match the application directory within ``apps/`` as it is used in the Kernel to load the specific application @@ -258,6 +260,7 @@ the application ID to run under CLI context:: // bin/console use Shared\Kernel; + use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; diff --git a/configuration/override_dir_structure.rst b/configuration/override_dir_structure.rst index d17b67aedba..e5dff35b6d0 100644 --- a/configuration/override_dir_structure.rst +++ b/configuration/override_dir_structure.rst @@ -111,7 +111,7 @@ In this case you have changed the location of the cache directory to You can also change the cache directory by defining an environment variable named ``APP_CACHE_DIR`` whose value is the full path of the cache folder. -.. caution:: +.. warning:: You should keep the cache directory different for each environment, otherwise some unexpected behavior may happen. Each environment generates diff --git a/console.rst b/console.rst index 0658ed9a3df..6a3ba70300f 100644 --- a/console.rst +++ b/console.rst @@ -98,6 +98,15 @@ completion (by default, by pressing the Tab key). $ php vendor/bin/phpstan completion --help $ composer completion --help +.. tip:: + + If you are using the :doc:`Symfony local web server + `, it is recommended to use the built-in completion + script that will ensure the right PHP version and configuration are used when + running the Console Completion. Run ``symfony completion --help`` for the + installation instructions for your shell. The Symfony CLI will provide + completion for the ``console`` and ``composer`` commands. + Creating a Command ------------------ @@ -357,7 +366,7 @@ Output sections let you manipulate the Console output in advanced ways, such as are updated independently and :ref:`appending rows to tables ` that have already been rendered. -.. caution:: +.. warning:: Terminals only allow overwriting the visible content, so you must take into account the console height when trying to write/overwrite section contents. @@ -522,13 +531,13 @@ call ``setAutoExit(false)`` on it to get the command result in ``CommandTester`` You can also test a whole console application by using :class:`Symfony\\Component\\Console\\Tester\\ApplicationTester`. -.. caution:: +.. warning:: When testing commands using the ``CommandTester`` class, console events are not dispatched. If you need to test those events, use the :class:`Symfony\\Component\\Console\\Tester\\ApplicationTester` instead. -.. caution:: +.. warning:: When testing commands using the :class:`Symfony\\Component\\Console\\Tester\\ApplicationTester` class, don't forget to disable the auto exit flag:: @@ -538,7 +547,7 @@ call ``setAutoExit(false)`` on it to get the command result in ``CommandTester`` $tester = new ApplicationTester($application); -.. caution:: +.. warning:: When testing ``InputOption::VALUE_NONE`` command options, you must pass ``true`` to them:: @@ -610,7 +619,7 @@ profile is accessible through the web page of the profiler. terminal supports links). If you run it in debug verbosity (``-vvv``) you'll also see the time and memory consumed by the command. -.. caution:: +.. warning:: When profiling the ``messenger:consume`` command from the :doc:`Messenger ` component, add the ``--no-reset`` option to the command or you won't get any diff --git a/console/calling_commands.rst b/console/calling_commands.rst index c5bfc6e5a72..dd1f0b12ff9 100644 --- a/console/calling_commands.rst +++ b/console/calling_commands.rst @@ -1,9 +1,9 @@ How to Call Other Commands ========================== -If a command depends on another one being run before it you can call in the -console command itself. This is useful if a command depends on another command -or if you want to create a "meta" command that runs a bunch of other commands +If a command depends on another one being run before it you can call that in the +console command itself. This can be useful +if you want to create a "meta" command that runs a bunch of other commands (for instance, all commands that need to be run when the project's code has changed on the production servers: clearing the cache, generating Doctrine proxies, dumping web assets, ...). @@ -36,6 +36,9 @@ method):: '--yell' => true, ]); + // disable interactive behavior for the greet command + $greetInput->setInteractive(false); + $returnCode = $this->getApplication()->doRun($greetInput, $output); // ... @@ -57,7 +60,7 @@ method):: ``$this->getApplication()->find('demo:greet')->run()`` will allow proper events to be dispatched for that inner command as well. -.. caution:: +.. warning:: Note that all the commands will run in the same process and some of Symfony's built-in commands may not work well this way. For instance, the ``cache:clear`` diff --git a/console/command_in_controller.rst b/console/command_in_controller.rst index 64475bff103..74af9e17c15 100644 --- a/console/command_in_controller.rst +++ b/console/command_in_controller.rst @@ -11,7 +11,7 @@ service that can be reused in the controller. However, when the command is part of a third-party library, you don't want to modify or duplicate their code. Instead, you can run the command directly from the controller. -.. caution:: +.. warning:: In comparison with a direct call from the console, calling a command from a controller has a slight performance impact because of the request stack diff --git a/console/commands_as_services.rst b/console/commands_as_services.rst index 75aa13d5be8..1393879a1df 100644 --- a/console/commands_as_services.rst +++ b/console/commands_as_services.rst @@ -51,7 +51,7 @@ argument (thanks to autowiring). In other words, you only need to create this class and everything works automatically! You can call the ``app:sunshine`` command and start logging. -.. caution:: +.. warning:: You *do* have access to services in ``configure()``. However, if your command is not :ref:`lazy `, try to avoid doing any @@ -130,7 +130,7 @@ only when the ``app:sunshine`` command is actually called. You don't need to call ``setName()`` for configuring the command when it is lazy. -.. caution:: +.. warning:: Calling the ``list`` command will instantiate all commands, including lazy commands. However, if the command is a ``Symfony\Component\Console\Command\LazyCommand``, then diff --git a/console/input.rst b/console/input.rst index 4d709c18825..98cddd63875 100644 --- a/console/input.rst +++ b/console/input.rst @@ -197,7 +197,7 @@ values after a whitespace or an ``=`` sign (e.g. ``--iterations 5`` or ``--iterations=5``), but short options can only use whitespaces or no separation at all (e.g. ``-i 5`` or ``-i5``). -.. caution:: +.. warning:: While it is possible to separate an option from its value with a whitespace, using this form leads to an ambiguity should the option appear before the @@ -311,6 +311,42 @@ The above code can be simplified as follows because ``false !== null``:: $yell = ($optionValue !== false); $yellLouder = ($optionValue === 'louder'); +Fetching The Raw Command Input +------------------------------ + +Symfony provides a :method:`Symfony\\Component\\Console\\Input\\ArgvInput::getRawTokens` +method to fetch the raw input that was passed to the command. This is useful if +you want to parse the input yourself or when you need to pass the input to another +command without having to worry about the number of arguments or options:: + + // ... + use Symfony\Component\Process\Process; + + protected function execute(InputInterface $input, OutputInterface $output): int + { + // if this command was run as: + // php bin/console app:my-command foo --bar --baz=3 --qux=value1 --qux=value2 + + $tokens = $input->getRawTokens(); + // $tokens = ['app:my-command', 'foo', '--bar', '--baz=3', '--qux=value1', '--qux=value2']; + + // pass true as argument to not include the original command name + $tokens = $input->getRawTokens(true); + // $tokens = ['foo', '--bar', '--baz=3', '--qux=value1', '--qux=value2']; + + // pass the raw input to any other command (from Symfony or the operating system) + $process = new Process(['app:other-command', ...$input->getRawTokens(true)]); + $process->setTty(true); + $process->mustRun(); + + // ... + } + +.. versionadded:: 7.1 + + The :method:`Symfony\\Component\\Console\\Input\\ArgvInput::getRawTokens` + method was introduced in Symfony 7.1. + Adding Argument/Option Value Completion --------------------------------------- diff --git a/contributing/code/bc.rst b/contributing/code/bc.rst index 889a605b422..497c70fb01d 100644 --- a/contributing/code/bc.rst +++ b/contributing/code/bc.rst @@ -30,7 +30,7 @@ The second section, "Working on Symfony Code", is targeted at Symfony contributors. This section lists detailed rules that every contributor needs to follow to ensure smooth upgrades for our users. -.. caution:: +.. warning:: :doc:`Experimental Features ` and code marked with the ``@internal`` tags are excluded from our Backward @@ -53,7 +53,7 @@ All interfaces shipped with Symfony can be used in type hints. You can also call any of the methods that they declare. We guarantee that we won't break code that sticks to these rules. -.. caution:: +.. warning:: The exception to this rule are interfaces tagged with ``@internal``. Such interfaces should not be used or implemented. @@ -89,7 +89,7 @@ Using our Classes All classes provided by Symfony may be instantiated and accessed through their public methods and properties. -.. caution:: +.. warning:: Classes, properties and methods that bear the tag ``@internal`` as well as the classes located in the various ``*\Tests\`` namespaces are an @@ -146,7 +146,7 @@ Using our Traits All traits provided by Symfony may be used in your classes. -.. caution:: +.. warning:: The exception to this rule are traits tagged with ``@internal``. Such traits should not be used. @@ -253,6 +253,14 @@ Make public or protected Yes Remove private property Yes **Constructors** Add constructor without mandatory arguments Yes :ref:`[1] ` +:ref:`Add argument without a default value ` No +Add argument with a default value Yes :ref:`[11] ` +Remove argument No :ref:`[3] ` +Add default value to an argument Yes +Remove default value of an argument No +Add type hint to an argument No +Remove type hint of an argument Yes +Change argument type No Remove constructor No Reduce visibility of a public constructor No Reduce visibility of a protected constructor No :ref:`[7] ` @@ -468,6 +476,10 @@ a return type is only possible with a child type. constructors of Attribute classes. Using PHP named arguments might break your code when upgrading to newer Symfony versions. +.. _note-11: + +**[11]** Only optional argument(s) of a constructor at last position may be added. + Making Code Changes in a Backward Compatible Way ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/contributing/code/bugs.rst b/contributing/code/bugs.rst index fba68617ee3..b0a46766026 100644 --- a/contributing/code/bugs.rst +++ b/contributing/code/bugs.rst @@ -4,7 +4,7 @@ Reporting a Bug Whenever you find a bug in Symfony, we kindly ask you to report it. It helps us make a better Symfony. -.. caution:: +.. warning:: If you think you've found a security issue, please use the special :doc:`procedure ` instead. diff --git a/contributing/code/core_team.rst b/contributing/code/core_team.rst index 6cef3400384..1b1703e4f93 100644 --- a/contributing/code/core_team.rst +++ b/contributing/code/core_team.rst @@ -13,6 +13,15 @@ This document states the rules that govern the Symfony core team. These rules are effective upon publication of this document and all Symfony Core members must adhere to said rules and protocol. +Role of a Core Team Member +-------------------------- + +In addition to being a regular contributor, core team members are expected to: + +* Review, approve, and merge pull requests; +* Help enforce, improve, and implement Symfony :doc:`processes and policies `; +* Participate in the Symfony Core Team discussions (on Slack and GitHub). + Core Organization ----------------- @@ -34,10 +43,8 @@ The Symfony Core groups, in descending order of priority, are as follows: In addition, there are other groups created to manage specific topics: * **Security Team**: manages the whole security process (triaging reported vulnerabilities, - fixing the reported issues, coordinating the release of security fixes, etc.) - -* **Recipes Team**: manages the recipes in the main and contrib recipe repositories. - + fixing the reported issues, coordinating the release of security fixes, etc.); +* **Symfony UX Team**: manages the `UX repositories`_; * **Documentation Team**: manages the whole `symfony-docs repository`_. Active Core Members @@ -52,21 +59,17 @@ Active Core Members * **Nicolas Grekas** (`nicolas-grekas`_); * **Christophe Coevoet** (`stof`_); * **Christian Flothmann** (`xabbuh`_); - * **Tobias Schultze** (`Tobion`_); * **Kévin Dunglas** (`dunglas`_); * **Javier Eguiluz** (`javiereguiluz`_); * **Grégoire Pineau** (`lyrixx`_); * **Ryan Weaver** (`weaverryan`_); * **Robin Chalas** (`chalasr`_); - * **Maxime Steinhausser** (`ogizanagi`_); * **Yonel Ceruto** (`yceruto`_); * **Tobias Nyholm** (`Nyholm`_); * **Wouter De Jong** (`wouterj`_); * **Alexander M. Turek** (`derrabus`_); * **Jérémy Derussé** (`jderusse`_); - * **Titouan Galopin** (`tgalopin`_); * **Oskar Stark** (`OskarStark`_); - * **Thomas Calvet** (`fancyweb`_); * **Mathieu Santostefano** (`welcomattic`_); * **Kevin Bond** (`kbond`_); * **Jérôme Tamarelle** (`gromnan`_). @@ -74,13 +77,15 @@ Active Core Members * **Security Team** (``@symfony/security`` on GitHub): * **Fabien Potencier** (`fabpot`_); - * **Michael Cullum** (`michaelcullum`_); * **Jérémy Derussé** (`jderusse`_). -* **Recipes Team**: +* **Symfony UX Team** (``@symfony/ux`` on GitHub): - * **Fabien Potencier** (`fabpot`_); - * **Tobias Nyholm** (`Nyholm`_). + * **Ryan Weaver** (`weaverryan`_); + * **Kevin Bond** (`kbond`_); + * **Simon André** (`smnandre`_); + * **Hugo Alliaume** (`kocal`_); + * **Matheo Daninos** (`webmamba`_). * **Documentation Team** (``@symfony/team-symfony-docs`` on GitHub): @@ -104,7 +109,12 @@ Symfony contributions: * **Lukas Kahwe Smith** (`lsmith77`_); * **Jules Pietri** (`HeahDude`_); * **Jakub Zalas** (`jakzal`_); -* **Samuel Rozé** (`sroze`_). +* **Samuel Rozé** (`sroze`_); +* **Tobias Schultze** (`Tobion`_); +* **Maxime Steinhausser** (`ogizanagi`_); +* **Titouan Galopin** (`tgalopin`_); +* **Michael Cullum** (`michaelcullum`_); +* **Thomas Calvet** (`fancyweb`_). Core Membership Application ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -138,7 +148,6 @@ Pull Request Voting Policy * Core members can change their votes as many times as they desire during the course of a pull request discussion; - * Core members are not allowed to vote on their own pull requests. Pull Request Merging Policy @@ -147,13 +156,10 @@ Pull Request Merging Policy A pull request **can be merged** if: * It is a :ref:`minor change `; - * Enough time was given for peer reviews; - * It is a bug fix and at least two **Mergers Team** members voted ``+1`` (only one if the submitter is part of the Mergers team) and no Core member voted ``-1`` (via GitHub reviews or as comments). - * It is a new feature and at least two **Mergers Team** members voted ``+1`` (if the submitter is part of the Mergers team, two *other* members) and no Core member voted ``-1`` (via GitHub reviews or as comments). @@ -166,7 +172,24 @@ All code must be committed to the repository through pull requests, except for to the repository. **Mergers** must always use the command-line ``gh`` tool provided by the -**Project Leader** to merge the pull requests. +**Project Leader** to merge pull requests. + +When merging a pull request, the tool asks for a category that should be chosen +following these rules: + +* **Feature**: For new features and deprecations; Pull requests must be merged + in the development branch. +* **Bug**: Only for bug fixes; We are very conservative when it comes to + merging older, but still maintained, branches. Read the :doc:`maintenance` + document for more information. +* **Minor**: For everything that does not change the code or when they don't + need to be listed in the CHANGELOG files: typos, Markdown files, test files, + new or missing translations, etc. +* **Security**: It's the category used for security fixes and should never be + used except by the security team. + +Getting the right category is important as it is used by automated tools to +generate the CHANGELOG files when releasing new versions. Release Policy ~~~~~~~~~~~~~~ @@ -187,6 +210,7 @@ discretion of the **Project Leader**. violations, and minor CSS, JavaScript and HTML modifications. .. _`symfony-docs repository`: https://github.com/symfony/symfony-docs +.. _`UX repositories`: https://github.com/symfony/ux .. _`fabpot`: https://github.com/fabpot/ .. _`webmozart`: https://github.com/webmozart/ .. _`Tobion`: https://github.com/Tobion/ @@ -218,3 +242,6 @@ discretion of the **Project Leader**. .. _`welcomattic`: https://github.com/welcomattic/ .. _`kbond`: https://github.com/kbond/ .. _`gromnan`: https://github.com/gromnan/ +.. _`smnandre`: https://github.com/smnandre/ +.. _`kocal`: https://github.com/kocal/ +.. _`webmamba`: https://github.com/webmamba/ diff --git a/contributing/code/maintenance.rst b/contributing/code/maintenance.rst index 04740ce8c6e..27e4fd73ea0 100644 --- a/contributing/code/maintenance.rst +++ b/contributing/code/maintenance.rst @@ -67,6 +67,9 @@ issue): * **Adding new deprecations**: After a version reaches stability, new deprecations cannot be added anymore. +* **Adding or updating annotations**: Adding or updating annotations (PHPDoc + annotations for instance) is not allowed; fixing them might be accepted. + Anything not explicitly listed above should be done on the next minor or major version instead. For instance, the following changes are never accepted in a patch version: diff --git a/contributing/code/reproducer.rst b/contributing/code/reproducer.rst index 6efae2a8ee8..3392ca87035 100644 --- a/contributing/code/reproducer.rst +++ b/contributing/code/reproducer.rst @@ -2,8 +2,8 @@ Creating a Bug Reproducer ========================= The main Symfony code repository receives thousands of issues reports per year. -Some of those issues are easy to understand and the Symfony Core developers can -fix them without any other information. However, other issues are much harder to +Some of those issues are easy to understand and can +be fixed without any other information. However, other issues are much harder to understand because developers can't reproduce them in their computers. That's when we'll ask you to create a "bug reproducer", which is the minimum amount of code needed to make the bug appear when executed. diff --git a/contributing/code/standards.rst b/contributing/code/standards.rst index 39d96d9e247..ced2568c5f7 100644 --- a/contributing/code/standards.rst +++ b/contributing/code/standards.rst @@ -211,8 +211,8 @@ Naming Conventions * Use `camelCase`_ for PHP variables, function and method names, arguments (e.g. ``$acceptableContentTypes``, ``hasSession()``); -* Use `snake_case`_ for configuration parameters and Twig template variables - (e.g. ``framework.csrf_protection``, ``http_status_code``); +Use `snake_case`_ for configuration parameters, route names and Twig template + variables (e.g. ``framework.csrf_protection``, ``http_status_code``); * Use SCREAMING_SNAKE_CASE for constants (e.g. ``InputArgument::IS_ARRAY``); diff --git a/contributing/community/reviews.rst b/contributing/community/reviews.rst index e3da2dcdb21..06426c03985 100644 --- a/contributing/community/reviews.rst +++ b/contributing/community/reviews.rst @@ -167,7 +167,7 @@ Pick a pull request from the `PRs in need of review`_ and follow these steps: PR by running the following Git commands. Insert the PR ID (that's the number after the ``#`` in the PR title) for the ```` placeholders: - .. code-block:: text + .. code-block:: terminal $ cd vendor/symfony/symfony $ git fetch origin pull//head:pr @@ -175,7 +175,7 @@ Pick a pull request from the `PRs in need of review`_ and follow these steps: For example: - .. code-block:: text + .. code-block:: terminal $ git fetch origin pull/15723/head:pr15723 $ git checkout pr15723 diff --git a/contributing/documentation/format.rst b/contributing/documentation/format.rst index d933f3bcead..e81abe92b79 100644 --- a/contributing/documentation/format.rst +++ b/contributing/documentation/format.rst @@ -16,7 +16,7 @@ source code. If you want to learn more about this format, check out the `reStructuredText Primer`_ tutorial and the `reStructuredText Reference`_. -.. caution:: +.. warning:: If you are familiar with Markdown, be careful as things are sometimes very similar but different: diff --git a/contributing/documentation/overview.rst b/contributing/documentation/overview.rst index aae2c397dec..183910e6ac6 100644 --- a/contributing/documentation/overview.rst +++ b/contributing/documentation/overview.rst @@ -185,6 +185,9 @@ changes and push the new changes: $ git push +It's rare, but you might be asked to rebase your pull request to target another +Symfony branch. Read the :ref:`guide on rebasing pull requests `. + **Step 10.** After your pull request is eventually accepted and merged in the Symfony documentation, you will be included in the `Symfony Documentation Contributors`_ list. Moreover, if you happen to have a `SymfonyConnect`_ diff --git a/contributing/documentation/standards.rst b/contributing/documentation/standards.rst index 420780d25f5..5e195d008fd 100644 --- a/contributing/documentation/standards.rst +++ b/contributing/documentation/standards.rst @@ -122,7 +122,7 @@ Example } } -.. caution:: +.. warning:: In YAML you should put a space after ``{`` and before ``}`` (e.g. ``{ _controller: ... }``), but this should not be done in Twig (e.g. ``{'hello' : 'value'}``). diff --git a/controller.rst b/controller.rst index c44f0dafdac..c11615d93aa 100644 --- a/controller.rst +++ b/controller.rst @@ -176,7 +176,8 @@ These are used for rendering templates, sending emails, querying the database an any other "work" you can think of. If you need a service in a controller, type-hint an argument with its class -(or interface) name. Symfony will automatically pass you the service you need:: +(or interface) name and Symfony will inject it automatically. This requires +your :doc:`controller to be registered as a service `:: use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Response; @@ -392,7 +393,7 @@ optional validation constraints:: use Symfony\Component\Validator\Constraints as Assert; - class UserDTO + class UserDto { public function __construct( #[Assert\NotBlank] @@ -417,7 +418,7 @@ attribute in your controller:: // ... public function dashboard( - #[MapQueryString] UserDTO $userDto + #[MapQueryString] UserDto $userDto ): Response { // ... @@ -434,7 +435,7 @@ HTTP status to return if the validation fails:: #[MapQueryString( validationGroups: ['strict', 'edit'], validationFailedStatusCode: Response::HTTP_UNPROCESSABLE_ENTITY - )] UserDTO $userDto + )] UserDto $userDto ): Response { // ... @@ -452,7 +453,7 @@ default value for your controller arguments:: // ... public function dashboard( - #[MapQueryString] UserDTO $userDto = new UserDTO() + #[MapQueryString] UserDto $userDto = new UserDto() ): Response { // ... @@ -486,7 +487,7 @@ attribute:: // ... public function dashboard( - #[MapRequestPayload] UserDTO $userDto + #[MapRequestPayload] UserDto $userDto ): Response { // ... @@ -501,7 +502,7 @@ your DTO:: serializationContext: ['...'], resolver: App\Resolver\UserDtoResolver )] - UserDTO $userDto + UserDto $userDto ): Response { // ... @@ -519,7 +520,7 @@ the validation fails as well as supported payload formats:: acceptFormat: 'json', validationGroups: ['strict', 'read'], validationFailedStatusCode: Response::HTTP_NOT_FOUND - )] UserDTO $userDto + )] UserDto $userDto ): Response { // ... @@ -539,22 +540,154 @@ Make sure to install `phpstan/phpdoc-parser`_ and `phpdocumentor/type-resolver`_ if you want to map a nested array of specific DTOs:: public function dashboard( - #[MapRequestPayload()] EmployeesDTO $employeesDto + #[MapRequestPayload] EmployeesDto $employeesDto ): Response { // ... } - final class EmployeesDTO + final class EmployeesDto { /** - * @param UserDTO[] $users + * @param UserDto[] $users */ public function __construct( public readonly array $users = [] ) {} } +Instead of returning an array of DTO objects, you can tell Symfony to transform +each DTO object into an array and return something like this: + +.. code-block:: json + + [ + { + "firstName": "John", + "lastName": "Smith", + "age": 28 + }, + { + "firstName": "Jane", + "lastName": "Doe", + "age": 30 + } + ] + +To do so, map the parameter as an array and configure the type of each element +using the ``type`` option of the attribute:: + + public function dashboard( + #[MapRequestPayload(type: UserDto::class)] array $users + ): Response + { + // ... + } + +.. versionadded:: 7.1 + + The ``type`` option of ``#[MapRequestPayload]`` was introduced in Symfony 7.1. + +.. _controller_map-uploaded-file: + +Mapping Uploaded Files +~~~~~~~~~~~~~~~~~~~~~~ + +Symfony provides an attribute called ``#[MapUploadedFile]`` to map one or more +``UploadedFile`` objects to controller arguments:: + + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\HttpFoundation\File\UploadedFile; + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\HttpKernel\Attribute\MapUploadedFile; + use Symfony\Component\Routing\Attribute\Route; + + class UserController extends AbstractController + { + #[Route('/user/picture', methods: ['PUT'])] + public function changePicture( + #[MapUploadedFile] UploadedFile $picture, + ): Response { + // ... + } + } + +In this example, the associated :doc:`argument resolver ` +fetches the ``UploadedFile`` based on the argument name (``$picture``). If no file +is submitted, an ``HttpException`` is thrown. You can change this by making the +controller argument nullable: + +.. code-block:: php-attributes + + #[MapUploadedFile] + ?UploadedFile $document + +The ``#[MapUploadedFile]`` attribute also allows to pass a list of constraints +to apply to the uploaded file:: + + namespace App\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\HttpFoundation\File\UploadedFile; + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\HttpKernel\Attribute\MapUploadedFile; + use Symfony\Component\Routing\Attribute\Route; + use Symfony\Component\Validator\Constraints as Assert; + + class UserController extends AbstractController + { + #[Route('/user/picture', methods: ['PUT'])] + public function changePicture( + #[MapUploadedFile([ + new Assert\File(mimeTypes: ['image/png', 'image/jpeg']), + new Assert\Image(maxWidth: 3840, maxHeight: 2160), + ])] + UploadedFile $picture, + ): Response { + // ... + } + } + +The validation constraints are checked before injecting the ``UploadedFile`` into +the controller argument. If there's a constraint violation, an ``HttpException`` +is thrown and the controller's action is not executed. + +If you need to upload a collection of files, map them to an array or a variadic +argument. The given constraint will be applied to all files and if any of them +fails, an ``HttpException`` is thrown: + +.. code-block:: php-attributes + + #[MapUploadedFile(new Assert\File(mimeTypes: ['application/pdf']))] + array $documents + + #[MapUploadedFile(new Assert\File(mimeTypes: ['application/pdf']))] + UploadedFile ...$documents + +Use the ``name`` option to rename the uploaded file to a custom value: + +.. code-block:: php-attributes + + #[MapUploadedFile(name: 'something-else')] + UploadedFile $document + +In addition, you can change the status code of the HTTP exception thrown when +there are constraint violations: + +.. code-block:: php-attributes + + #[MapUploadedFile( + constraints: new Assert\File(maxSize: '2M'), + validationFailedStatusCode: Response::HTTP_REQUEST_ENTITY_TOO_LARGE + )] + UploadedFile $document + +.. versionadded:: 7.1 + + The ``#[MapUploadedFile]`` attribute was introduced in Symfony 7.1. + Managing the Session -------------------- @@ -655,6 +788,14 @@ response types. Some of these are mentioned below. To learn more about the ``Request`` and ``Response`` (and different ``Response`` classes), see the :ref:`HttpFoundation component documentation `. +.. note:: + + Technically, a controller can return a value other than a ``Response``. + However, your application is responsible for transforming that value into a + ``Response`` object. This is handled using :doc:`events ` + (specifically the :ref:`kernel.view event `), + an advanced feature you'll learn about later. + Accessing Configuration Values ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -757,7 +898,7 @@ method:: { $response = $this->sendEarlyHints([ new Link(rel: 'preconnect', href: 'https://fonts.google.com'), - (new Link(href: '/style.css'))->withAttribute('as', 'stylesheet'), + (new Link(href: '/style.css'))->withAttribute('as', 'style'), (new Link(href: '/script.js'))->withAttribute('as', 'script'), ]); @@ -813,6 +954,6 @@ Learn more about Controllers .. _`Early hints`: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/103 .. _`SAPI`: https://www.php.net/manual/en/function.php-sapi-name.php .. _`FrankenPHP`: https://frankenphp.dev -.. _`Validate Filters`: https://www.php.net/manual/en/filter.filters.validate.php +.. _`Validate Filters`: https://www.php.net/manual/en/filter.constants.php .. _`phpstan/phpdoc-parser`: https://packagist.org/packages/phpstan/phpdoc-parser .. _`phpdocumentor/type-resolver`: https://packagist.org/packages/phpdocumentor/type-resolver diff --git a/controller/error_pages.rst b/controller/error_pages.rst index 52dff49731f..fc36b88779a 100644 --- a/controller/error_pages.rst +++ b/controller/error_pages.rst @@ -216,7 +216,7 @@ contents, create a new Normalizer that supports the ``FlattenException`` input:: class MyCustomProblemNormalizer implements NormalizerInterface { - public function normalize($exception, string $format = null, array $context = []): array + public function normalize($exception, ?string $format = null, array $context = []): array { return [ 'content' => 'This is my custom problem normalizer.', @@ -227,7 +227,7 @@ contents, create a new Normalizer that supports the ``FlattenException`` input:: ]; } - public function supportsNormalization($data, string $format = null, array $context = []): bool + public function supportsNormalization($data, ?string $format = null, array $context = []): bool { return $data instanceof FlattenException; } @@ -319,7 +319,7 @@ error pages. .. note:: - If your listener calls ``setThrowable()`` on the + If your listener calls ``setResponse()`` on the :class:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent` event, propagation will be stopped and the response will be sent to the client. diff --git a/controller/forwarding.rst b/controller/forwarding.rst index a0e0648517a..8d8be859da5 100644 --- a/controller/forwarding.rst +++ b/controller/forwarding.rst @@ -11,7 +11,7 @@ and calls the defined controller. The ``forward()`` method returns the :class:`Symfony\\Component\\HttpFoundation\\Response` object that is returned from *that* controller:: - public function index($name): Response + public function index(string $name): Response { $response = $this->forward('App\Controller\OtherController::fancy', [ 'name' => $name, diff --git a/controller/upload_file.rst b/controller/upload_file.rst index ec477002cb4..b3dc2d6ffd0 100644 --- a/controller/upload_file.rst +++ b/controller/upload_file.rst @@ -120,6 +120,7 @@ Finally, you need to update the code of the controller that handles the form:: use App\Entity\Product; use App\Form\ProductType; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\HttpFoundation\File\Exception\FileException; use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\Request; @@ -130,7 +131,11 @@ Finally, you need to update the code of the controller that handles the form:: class ProductController extends AbstractController { #[Route('/product/new', name: 'app_product_new')] - public function new(Request $request, SluggerInterface $slugger): Response + public function new( + Request $request, + SluggerInterface $slugger, + #[Autowire('%kernel.project_dir%/public/uploads/brochures')] string $brochuresDirectory + ): Response { $product = new Product(); $form = $this->createForm(ProductType::class, $product); @@ -150,10 +155,7 @@ Finally, you need to update the code of the controller that handles the form:: // Move the file to the directory where brochures are stored try { - $brochureFile->move( - $this->getParameter('brochures_directory'), - $newFilename - ); + $brochureFile->move($brochuresDirectory, $newFilename); } catch (FileException $e) { // ... handle exception if something happens during file upload } @@ -174,17 +176,6 @@ Finally, you need to update the code of the controller that handles the form:: } } -Now, create the ``brochures_directory`` parameter that was used in the -controller to specify the directory in which the brochures should be stored: - -.. code-block:: yaml - - # config/services.yaml - - # ... - parameters: - brochures_directory: '%kernel.project_dir%/public/uploads/brochures' - There are some important things to consider in the code of the above controller: #. In Symfony applications, uploaded files are objects of the @@ -230,7 +221,7 @@ You can use the following code to link to the PDF brochure of a product: // ... $product->setBrochureFilename( - new File($this->getParameter('brochures_directory').'/'.$product->getBrochureFilename()) + new File($brochuresDirectory.DIRECTORY_SEPARATOR.$product->getBrochureFilename()) ); Creating an Uploader Service diff --git a/controller/value_resolver.rst b/controller/value_resolver.rst index 5f0f7484e46..dbbea7bcc87 100644 --- a/controller/value_resolver.rst +++ b/controller/value_resolver.rst @@ -98,7 +98,7 @@ Symfony ships with the following value resolvers in the .. tip:: The ``DateTimeInterface`` object is generated with the :doc:`Clock component `. - This. gives your full control over the date and time values the controller + This gives you full control over the date and time values the controller receives when testing your application and using the :class:`Symfony\\Component\\Clock\\MockClock` implementation. diff --git a/create_framework/http_foundation.rst b/create_framework/http_foundation.rst index 66bc1b51d0d..219119164b4 100644 --- a/create_framework/http_foundation.rst +++ b/create_framework/http_foundation.rst @@ -255,7 +255,7 @@ code in production without a proxy, it becomes trivially easy to abuse your system. That's not the case with the ``getClientIp()`` method as you must explicitly trust your reverse proxies by calling ``setTrustedProxies()``:: - Request::setTrustedProxies(['10.0.0.1']); + Request::setTrustedProxies(['10.0.0.1'], Request::HEADER_X_FORWARDED_FOR); if ($myIp === $request->getClientIp()) { // the client is a known one, so give it some more privilege diff --git a/create_framework/introduction.rst b/create_framework/introduction.rst index d3574de4c94..7a1e6b2ad50 100644 --- a/create_framework/introduction.rst +++ b/create_framework/introduction.rst @@ -29,7 +29,7 @@ a few good reasons to start creating your own framework: * To refactor an old/existing application that needs a good dose of recent web development best practices; -* To prove the world that you can actually create a framework on your own (... +* To prove to the world that you can actually create a framework on your own (... but with little effort). This tutorial will gently guide you through the creation of a web framework, diff --git a/create_framework/separation_of_concerns.rst b/create_framework/separation_of_concerns.rst index e0937fbdf45..5238b3aac42 100644 --- a/create_framework/separation_of_concerns.rst +++ b/create_framework/separation_of_concerns.rst @@ -120,7 +120,7 @@ And move the ``is_leap_year()`` function to its own class too:: class LeapYear { - public function isLeapYear(int $year = null): bool + public function isLeapYear(?int $year = null): bool { if (null === $year) { $year = date('Y'); diff --git a/create_framework/templating.rst b/create_framework/templating.rst index 07d7e38417c..282e75cbc94 100644 --- a/create_framework/templating.rst +++ b/create_framework/templating.rst @@ -146,7 +146,7 @@ framework does not need to be modified in any way, create a new use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing; - function is_leap_year(int $year = null): bool + function is_leap_year(?int $year = null): bool { if (null === $year) { $year = (int)date('Y'); diff --git a/create_framework/unit_testing.rst b/create_framework/unit_testing.rst index 5c783c5279a..32c97a03846 100644 --- a/create_framework/unit_testing.rst +++ b/create_framework/unit_testing.rst @@ -12,7 +12,7 @@ using `PHPUnit`_. At first, install PHPUnit as a development dependency: .. code-block:: terminal - $ composer require --dev phpunit/phpunit + $ composer require --dev phpunit/phpunit:^9.6 Then, create a PHPUnit configuration file in ``example.com/phpunit.xml.dist``: @@ -21,7 +21,7 @@ Then, create a PHPUnit configuration file in ``example.com/phpunit.xml.dist``: datetimeFunction('test_datetime', DatetimeFunction::class); }; -.. caution:: +.. warning:: DQL functions are instantiated by Doctrine outside of the Symfony :doc:`service container ` so you can't inject services diff --git a/doctrine/dbal.rst b/doctrine/dbal.rst index a400cee0324..4f47b61eb61 100644 --- a/doctrine/dbal.rst +++ b/doctrine/dbal.rst @@ -32,7 +32,7 @@ Then configure the ``DATABASE_URL`` environment variable in ``.env``: # .env (or override DATABASE_URL in .env.local to avoid committing your changes) # customize this line! - DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7" + DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=8.0.37" Further things can be configured in ``config/packages/doctrine.yaml`` - see :ref:`reference-dbal-configuration`. Remove the ``orm`` key in that file diff --git a/doctrine/events.rst b/doctrine/events.rst index 23373d827f4..dcd97126b7c 100644 --- a/doctrine/events.rst +++ b/doctrine/events.rst @@ -391,9 +391,9 @@ listener in the Symfony application by creating a new service for it and ; }; -.. versionadded:: 2.7.2 +.. versionadded:: 2.8.0 - The `AsDoctrineListener`_ attribute was introduced in DoctrineBundle 2.7.2. + The `AsDoctrineListener`_ attribute was introduced in DoctrineBundle 2.8.0. .. tip:: @@ -404,4 +404,4 @@ listener in the Symfony application by creating a new service for it and .. _`lifecycle events`: https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/events.html#lifecycle-events .. _`official docs about Doctrine events`: https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/events.html .. _`DoctrineMongoDBBundle documentation`: https://symfony.com/doc/current/bundles/DoctrineMongoDBBundle/index.html -.. _`AsDoctrineListener`: https://github.com/doctrine/DoctrineBundle/blob/2.10.x/Attribute/AsDoctrineListener.php +.. _`AsDoctrineListener`: https://github.com/doctrine/DoctrineBundle/blob/2.12.x/src/Attribute/AsDoctrineListener.php diff --git a/doctrine/multiple_entity_managers.rst b/doctrine/multiple_entity_managers.rst index 014d9e4dccb..1a56c55ddad 100644 --- a/doctrine/multiple_entity_managers.rst +++ b/doctrine/multiple_entity_managers.rst @@ -15,7 +15,7 @@ entities, each with their own database connection strings or separate cache conf advanced and not usually required. Be sure you actually need multiple entity managers before adding in this layer of complexity. -.. caution:: +.. warning:: Entities cannot define associations across different entity managers. If you need that, there are `several alternatives`_ that require some custom setup. @@ -142,7 +142,7 @@ and ``customer``. The ``default`` entity manager manages entities in the entities in ``src/Entity/Customer``. You've also defined two connections, one for each entity manager, but you are free to define the same connection for both. -.. caution:: +.. warning:: When working with multiple connections and entity managers, you should be explicit about which configuration you want. If you *do* omit the name of @@ -251,7 +251,7 @@ The same applies to repository calls:: } } -.. caution:: +.. warning:: One entity can be managed by more than one entity manager. This however results in unexpected behavior when extending from ``ServiceEntityRepository`` diff --git a/doctrine/registration_form.rst b/doctrine/registration_form.rst deleted file mode 100644 index 7063b7157a4..00000000000 --- a/doctrine/registration_form.rst +++ /dev/null @@ -1,15 +0,0 @@ -How to Implement a Registration Form -==================================== - -This article has been removed because it only explained things that are -already explained in other articles. Specifically, to implement a registration -form you must: - -#. :ref:`Define a class to represent users `; -#. :doc:`Create a form ` to ask for the registration information (you can - generate this with the ``make:registration-form`` command provided by the `MakerBundle`_); -#. Create :doc:`a controller ` to :ref:`process the form `; -#. :ref:`Protect some parts of your application ` so that - only registered users can access to them. - -.. _`MakerBundle`: https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html diff --git a/doctrine/reverse_engineering.rst b/doctrine/reverse_engineering.rst deleted file mode 100644 index 35c8e729c2d..00000000000 --- a/doctrine/reverse_engineering.rst +++ /dev/null @@ -1,15 +0,0 @@ -How to Generate Entities from an Existing Database -================================================== - -.. caution:: - - The ``doctrine:mapping:import`` command used to generate Doctrine entities - from existing databases was deprecated by Doctrine in 2019 and there's no - replacement for it. - - Instead, you can use the ``make:entity`` command from `Symfony Maker Bundle`_ - to help you generate the code of your Doctrine entities. This command - requires manual supervision because it doesn't generate entities from - existing databases. - -.. _`Symfony Maker Bundle`: https://symfony.com/bundles/SymfonyMakerBundle/current/index.html diff --git a/emoji.rst b/emoji.rst new file mode 100644 index 00000000000..551497f0c76 --- /dev/null +++ b/emoji.rst @@ -0,0 +1,173 @@ +Working with Emojis +=================== + +.. versionadded:: 7.1 + + The emoji component was introduced in Symfony 7.1. + +Symfony provides several utilities to work with emoji characters and sequences +from the `Unicode CLDR dataset`_. They are available via the Emoji component, +which you must first install in your application: + +.. _installation: + +.. code-block:: terminal + + $ composer require symfony/emoji + +.. include:: /components/require_autoload.rst.inc + +The data needed to store the transliteration of all emojis (~5,000) into all +languages take a considerable disk space. + +If you need to save disk space (e.g. because you deploy to some service with tight +size constraints), run this command (e.g. as an automated script after ``composer install``) +to compress the internal Symfony emoji data files using the PHP ``zlib`` extension: + +.. code-block:: terminal + + # adjust the path to the 'compress' binary based on your application installation + $ php ./vendor/symfony/emoji/Resources/bin/compress + +.. _emoji-transliteration: + +Emoji Transliteration +--------------------- + +The ``EmojiTransliterator`` class offers a way to translate emojis into their +textual representation in all languages based on the `Unicode CLDR dataset`_:: + + use Symfony\Component\Emoji\EmojiTransliterator; + + // Describe emojis in English + $transliterator = EmojiTransliterator::create('en'); + $transliterator->transliterate('Menus with 🍕 or 🍝'); + // => 'Menus with pizza or spaghetti' + + // Describe emojis in Ukrainian + $transliterator = EmojiTransliterator::create('uk'); + $transliterator->transliterate('Menus with 🍕 or 🍝'); + // => 'Menus with піца or спагеті' + +.. tip:: + + When using the :ref:`slugger ` from the String component, + you can combine it with the ``EmojiTransliterator`` to :ref:`slugify emojis `. + +Transliterating Emoji Text Short Codes +-------------------------------------- + +Services like GitHub and Slack allows to include emojis in your messages using +text short codes (e.g. you can add the ``:+1:`` code to render the 👍 emoji). + +Symfony also provides a feature to transliterate emojis into short codes and vice +versa. The short codes are slightly different on each service, so you must pass +the name of the service as an argument when creating the transliterator. + +GitHub Emoji Short Codes Transliteration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Convert emojis to GitHub short codes with the ``emoji-github`` locale:: + + $transliterator = EmojiTransliterator::create('emoji-github'); + $transliterator->transliterate('Teenage 🐢 really love 🍕'); + // => 'Teenage :turtle: really love :pizza:' + +Convert GitHub short codes to emojis with the ``github-emoji`` locale:: + + $transliterator = EmojiTransliterator::create('github-emoji'); + $transliterator->transliterate('Teenage :turtle: really love :pizza:'); + // => 'Teenage 🐢 really love 🍕' + +Gitlab Emoji Short Codes Transliteration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Convert emojis to Gitlab short codes with the ``emoji-gitlab`` locale:: + + $transliterator = EmojiTransliterator::create('emoji-gitlab'); + $transliterator->transliterate('Breakfast with 🥝 or 🥛'); + // => 'Breakfast with :kiwi: or :milk:' + +Convert Gitlab short codes to emojis with the ``gitlab-emoji`` locale:: + + $transliterator = EmojiTransliterator::create('gitlab-emoji'); + $transliterator->transliterate('Breakfast with :kiwi: or :milk:'); + // => 'Breakfast with 🥝 or 🥛' + +Slack Emoji Short Codes Transliteration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Convert emojis to Slack short codes with the ``emoji-slack`` locale:: + + $transliterator = EmojiTransliterator::create('emoji-slack'); + $transliterator->transliterate('Menus with 🥗 or 🧆'); + // => 'Menus with :green_salad: or :falafel:' + +Convert Slack short codes to emojis with the ``slack-emoji`` locale:: + + $transliterator = EmojiTransliterator::create('slack-emoji'); + $transliterator->transliterate('Menus with :green_salad: or :falafel:'); + // => 'Menus with 🥗 or 🧆' + +.. _text-emoji: + +Universal Emoji Short Codes Transliteration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you don't know which service was used to generate the short codes, you can use +the ``text-emoji`` locale, which combines all codes from all services:: + + $transliterator = EmojiTransliterator::create('text-emoji'); + + // Github short codes + $transliterator->transliterate('Breakfast with :kiwi-fruit: or :milk-glass:'); + // Gitlab short codes + $transliterator->transliterate('Breakfast with :kiwi: or :milk:'); + // Slack short codes + $transliterator->transliterate('Breakfast with :kiwifruit: or :glass-of-milk:'); + + // all the above examples produce the same result: + // => 'Breakfast with 🥝 or 🥛' + +You can convert emojis to short codes with the ``emoji-text`` locale:: + + $transliterator = EmojiTransliterator::create('emoji-text'); + $transliterator->transliterate('Breakfast with 🥝 or 🥛'); + // => 'Breakfast with :kiwifruit: or :milk-glass: + +Inverse Emoji Transliteration +----------------------------- + +Given the textual representation of an emoji, you can reverse it back to get the +actual emoji thanks to the :ref:`emojify filter `: + +.. code-block:: twig + + {{ 'I like :kiwi-fruit:'|emojify }} {# renders: I like 🥝 #} + {{ 'I like :kiwi:'|emojify }} {# renders: I like 🥝 #} + {{ 'I like :kiwifruit:'|emojify }} {# renders: I like 🥝 #} + +By default, ``emojify`` uses the :ref:`text catalog `, which +merges the emoji text codes of all services. If you prefer, you can select a +specific catalog to use: + +.. code-block:: twig + + {{ 'I :green-heart: this'|emojify }} {# renders: I 💚 this #} + {{ ':green_salad: is nice'|emojify('slack') }} {# renders: 🥗 is nice #} + {{ 'My :turtle: has no name yet'|emojify('github') }} {# renders: My 🐢 has no name yet #} + {{ ':kiwi: is a great fruit'|emojify('gitlab') }} {# renders: 🥝 is a great fruit #} + +Removing Emojis +--------------- + +The ``EmojiTransliterator`` can also be used to remove all emojis from a string, +via the special ``strip`` locale:: + + use Symfony\Component\Emoji\EmojiTransliterator; + + $transliterator = EmojiTransliterator::create('strip'); + $transliterator->transliterate('🎉Hey!🥳 🎁Happy Birthday!🎁'); + // => 'Hey! Happy Birthday!' + +.. _`Unicode CLDR dataset`: https://github.com/unicode-org/cldr diff --git a/event_dispatcher.rst b/event_dispatcher.rst index ef9f74c4ae9..27885af267b 100644 --- a/event_dispatcher.rst +++ b/event_dispatcher.rst @@ -41,6 +41,9 @@ The most common way to listen to an event is to register an **event listener**:: // Customize your response object to display the exception details $response = new Response(); $response->setContent($message); + // the exception message can contain unfiltered user input; + // set the content-type to text to avoid XSS issues + $response->headers->set('Content-Type', 'text/plain; charset=utf-8'); // HttpExceptionInterface is a special type of exception that // holds status code and header details @@ -162,7 +165,10 @@ having to add any configuration in external files:: } } -You can add multiple ``#[AsEventListener()]`` attributes to configure different methods:: +You can add multiple ``#[AsEventListener]`` attributes to configure different methods. +The ``method`` property is optional, and when not defined, it defaults to +``on`` + uppercased event name. In the example below, the ``'foo'`` event listener +doesn't explicitly define its method, so the ``onFoo()`` method will be called:: namespace App\EventListener; @@ -198,7 +204,7 @@ can also be applied to methods directly:: final class MyMultiListener { - #[AsEventListener()] + #[AsEventListener] public function onCustomEvent(CustomEvent $event): void { // ... @@ -795,3 +801,11 @@ could listen to the ``mailer.post_send`` event and change the method's return va That's it! Your subscriber should be called automatically (or read more about :ref:`event subscriber configuration `). + +Learn More +---------- + +- :ref:`The Request-Response Lifecycle ` +- :doc:`/reference/events` +- :ref:`Security-related Events ` +- :doc:`/components/event_dispatcher` diff --git a/form/bootstrap5.rst b/form/bootstrap5.rst index 400747bba12..db098a1ba09 100644 --- a/form/bootstrap5.rst +++ b/form/bootstrap5.rst @@ -171,7 +171,7 @@ class to the label: ], // ... -.. caution:: +.. warning:: Switches only work with **checkbox**. @@ -201,7 +201,7 @@ class to the ``row_attr`` option. } }) }} -.. caution:: +.. warning:: If you fill the ``help`` option of your form, it will also be rendered as part of the group. @@ -239,7 +239,7 @@ of your form type. } }) }} -.. caution:: +.. warning:: You **must** provide a ``label`` and a ``placeholder`` to make floating labels work properly. diff --git a/form/create_custom_field_type.rst b/form/create_custom_field_type.rst index 709f3321544..0d92a967fa0 100644 --- a/form/create_custom_field_type.rst +++ b/form/create_custom_field_type.rst @@ -449,7 +449,7 @@ are some examples of Twig block names for the postal address type: ``postal_address_zipCode_label`` The label block of the ZIP Code field. -.. caution:: +.. warning:: When the name of your form class matches any of the built-in field types, your form might not be rendered correctly. A form type named diff --git a/form/data_mappers.rst b/form/data_mappers.rst index cb5c7936701..38c92ce35ae 100644 --- a/form/data_mappers.rst +++ b/form/data_mappers.rst @@ -126,7 +126,7 @@ in your form type:: } } -.. caution:: +.. warning:: The data passed to the mapper is *not yet validated*. This means that your objects should allow being created in an invalid state in order to produce @@ -215,7 +215,7 @@ If available, these options have priority over the property path accessor and the default data mapper will still use the :doc:`PropertyAccess component ` for the other form fields. -.. caution:: +.. warning:: When a form has the ``inherit_data`` option set to ``true``, it does not use the data mapper and lets its parent map inner values. diff --git a/form/data_transformers.rst b/form/data_transformers.rst index 4e81fc3e930..db051a04bbc 100644 --- a/form/data_transformers.rst +++ b/form/data_transformers.rst @@ -8,7 +8,7 @@ can be rendered as a ``yyyy-MM-dd``-formatted input text box. Internally, a data converts the ``DateTime`` value of the field to a ``yyyy-MM-dd`` formatted string when rendering the form, and then back to a ``DateTime`` object on submit. -.. caution:: +.. warning:: When a form field has the ``inherit_data`` option set to ``true``, data transformers are not applied to that field. @@ -340,7 +340,7 @@ that, after a successful submission, the Form component will pass a real If the issue isn't found, a form error will be created for that field and its error message can be controlled with the ``invalid_message`` field option. -.. caution:: +.. warning:: Be careful when adding your transformers. For example, the following is **wrong**, as the transformer would be applied to the entire form, instead of just this @@ -472,7 +472,7 @@ Which transformer you need depends on your situation. To use the view transformer, call ``addViewTransformer()``. -.. caution:: +.. warning:: Be careful with model transformers and :doc:`Collection ` field types. diff --git a/form/direct_submit.rst b/form/direct_submit.rst index 7b98134af18..7a08fb6978a 100644 --- a/form/direct_submit.rst +++ b/form/direct_submit.rst @@ -65,7 +65,7 @@ the fields defined by the form class. Otherwise, you'll see a form validation er argument to ``submit()``. Passing ``false`` will remove any missing fields within the form object. Otherwise, the missing fields will be set to ``null``. -.. caution:: +.. warning:: When the second parameter ``$clearMissing`` is ``false``, like with the "PATCH" method, the validation will only apply to the submitted fields. If diff --git a/form/dynamic_form_modification.rst b/form/dynamic_form_modification.rst index 72acc7eee0d..09be80ebb5a 100644 --- a/form/dynamic_form_modification.rst +++ b/form/dynamic_form_modification.rst @@ -455,7 +455,7 @@ The type would now look like:: ]) ; - $formModifier = function (FormInterface $form, Sport $sport = null): void { + $formModifier = function (FormInterface $form, ?Sport $sport = null): void { $positions = null === $sport ? [] : $sport->getAvailablePositions(); $form->add('position', EntityType::class, [ @@ -487,7 +487,7 @@ The type would now look like:: $formModifier($event->getForm()->getParent(), $sport); } ); - + // by default, action does not appear in the
tag // you can set this value by passing the controller route $builder->setAction($options['action']); diff --git a/form/events.rst b/form/events.rst index 745df2df453..dad6c242ddd 100644 --- a/form/events.rst +++ b/form/events.rst @@ -192,7 +192,7 @@ Form view data Same as in ``FormEvents::POST_SET_DATA`` See all form events at a glance in the :ref:`Form Events Information Table `. -.. caution:: +.. warning:: At this point, you cannot add or remove fields to the form. @@ -225,7 +225,7 @@ Form view data Normalized data transformed using a view transformer See all form events at a glance in the :ref:`Form Events Information Table `. -.. caution:: +.. warning:: At this point, you cannot add or remove fields to the current form and its children. diff --git a/form/form_collections.rst b/form/form_collections.rst index f0ad76a8a61..2a0ba99657f 100644 --- a/form/form_collections.rst +++ b/form/form_collections.rst @@ -195,7 +195,7 @@ then set on the ``tag`` field of the ``Task`` and can be accessed via ``$task->g So far, this works great, but only to edit *existing* tags. It doesn't allow us yet to add new tags or delete existing ones. -.. caution:: +.. warning:: You can embed nested collections as many levels down as you like. However, if you use Xdebug, you may receive a ``Maximum function nesting level of '100' @@ -427,13 +427,13 @@ That was fine, but forcing the use of the "adder" method makes handling these new ``Tag`` objects easier (especially if you're using Doctrine, which you will learn about next!). -.. caution:: +.. warning:: You have to create **both** ``addTag()`` and ``removeTag()`` methods, otherwise the form will still use ``setTag()`` even if ``by_reference`` is ``false``. You'll learn more about the ``removeTag()`` method later in this article. -.. caution:: +.. warning:: Symfony can only make the plural-to-singular conversion (e.g. from the ``tags`` property to the ``addTag()`` method) for English words. Code diff --git a/form/form_customization.rst b/form/form_customization.rst index 3f3cd0bbc89..1c23601a883 100644 --- a/form/form_customization.rst +++ b/form/form_customization.rst @@ -74,7 +74,7 @@ control over how each form field is rendered, so you can fully customize them: -.. caution:: +.. warning:: If you're rendering each field manually, make sure you don't forget the ``_token`` field that is automatically added for CSRF protection. @@ -305,7 +305,7 @@ Renders any errors for the given field. {# render any "global" errors not associated to any form field #} {{ form_errors(form) }} -.. caution:: +.. warning:: In the Bootstrap 4 form theme, ``form_errors()`` is already included in ``form_label()``. Read more about this in the diff --git a/form/form_dependencies.rst b/form/form_dependencies.rst deleted file mode 100644 index 96b067362ff..00000000000 --- a/form/form_dependencies.rst +++ /dev/null @@ -1,12 +0,0 @@ -How to Access Services or Config from Inside a Form -=================================================== - -The content of this article is no longer relevant because in current Symfony -versions, form classes are services by default and you can inject services in -them using the :doc:`service autowiring ` feature. - -Read the article about :doc:`creating custom form types ` -to see an example of how to inject the database service into a form type. In the -same article you can also read about -:ref:`configuration options for form types `, which is -another way of passing services to forms. diff --git a/form/form_themes.rst b/form/form_themes.rst index eb6f6f2ae22..8b82982edaa 100644 --- a/form/form_themes.rst +++ b/form/form_themes.rst @@ -177,7 +177,7 @@ of form themes: {# ... #} -.. caution:: +.. warning:: When using the ``only`` keyword, none of Symfony's built-in form themes (``form_div_layout.html.twig``, etc.) will be applied. In order to render diff --git a/form/inherit_data_option.rst b/form/inherit_data_option.rst index 19b14b27bcd..2caa0afcdbe 100644 --- a/form/inherit_data_option.rst +++ b/form/inherit_data_option.rst @@ -165,6 +165,6 @@ Finally, make this work by adding the location form to your two original forms:: That's it! You have extracted duplicated field definitions to a separate location form that you can reuse wherever you need it. -.. caution:: +.. warning:: Forms with the ``inherit_data`` option set cannot have ``*_SET_DATA`` event listeners. diff --git a/form/type_guesser.rst b/form/type_guesser.rst index b2137b82578..106eb4e7742 100644 --- a/form/type_guesser.rst +++ b/form/type_guesser.rst @@ -44,14 +44,14 @@ This interface requires four methods: Start by creating the class and these methods. Next, you'll learn how to fill each in:: - // src/Form/TypeGuesser/PHPDocTypeGuesser.php + // src/Form/TypeGuesser/PhpDocTypeGuesser.php namespace App\Form\TypeGuesser; use Symfony\Component\Form\FormTypeGuesserInterface; use Symfony\Component\Form\Guess\TypeGuess; use Symfony\Component\Form\Guess\ValueGuess; - class PHPDocTypeGuesser implements FormTypeGuesserInterface + class PhpDocTypeGuesser implements FormTypeGuesserInterface { public function guessType(string $class, string $property): ?TypeGuess { @@ -90,9 +90,9 @@ The ``TypeGuess`` constructor requires three options: type with the highest confidence is used. With this knowledge, you can implement the ``guessType()`` method of the -``PHPDocTypeGuesser``:: +``PhpDocTypeGuesser``:: - // src/Form/TypeGuesser/PHPDocTypeGuesser.php + // src/Form/TypeGuesser/PhpDocTypeGuesser.php namespace App\Form\TypeGuesser; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; @@ -102,7 +102,7 @@ With this knowledge, you can implement the ``guessType()`` method of the use Symfony\Component\Form\Guess\Guess; use Symfony\Component\Form\Guess\TypeGuess; - class PHPDocTypeGuesser implements FormTypeGuesserInterface + class PhpDocTypeGuesser implements FormTypeGuesserInterface { public function guessType(string $class, string $property): ?TypeGuess { @@ -162,7 +162,7 @@ instance with the value of the option. This constructor has 2 arguments: ``null`` is guessed when you believe the value of the option should not be set. -.. caution:: +.. warning:: You should be very careful using the ``guessMaxLength()`` method. When the type is a float, you cannot determine a length (e.g. you want a float to be @@ -188,7 +188,7 @@ and tag it with ``form.type_guesser``: services: # ... - App\Form\TypeGuesser\PHPDocTypeGuesser: + App\Form\TypeGuesser\PhpDocTypeGuesser: tags: [form.type_guesser] .. code-block:: xml @@ -201,7 +201,7 @@ and tag it with ``form.type_guesser``: https://symfony.com/schema/dic/services/services-1.0.xsd"> - + @@ -210,9 +210,9 @@ and tag it with ``form.type_guesser``: .. code-block:: php // config/services.php - use App\Form\TypeGuesser\PHPDocTypeGuesser; + use App\Form\TypeGuesser\PhpDocTypeGuesser; - $container->register(PHPDocTypeGuesser::class) + $container->register(PhpDocTypeGuesser::class) ->addTag('form.type_guesser') ; @@ -223,12 +223,12 @@ and tag it with ``form.type_guesser``: :method:`Symfony\\Component\\Form\\FormFactoryBuilder::addTypeGuessers` of the ``FormFactoryBuilder`` to register new type guessers:: - use App\Form\TypeGuesser\PHPDocTypeGuesser; + use App\Form\TypeGuesser\PhpDocTypeGuesser; use Symfony\Component\Form\Forms; $formFactory = Forms::createFormFactoryBuilder() // ... - ->addTypeGuesser(new PHPDocTypeGuesser()) + ->addTypeGuesser(new PhpDocTypeGuesser()) ->getFormFactory(); // ... diff --git a/form/unit_testing.rst b/form/unit_testing.rst index bf57e6d1afc..9603c5bc0d2 100644 --- a/form/unit_testing.rst +++ b/form/unit_testing.rst @@ -1,7 +1,7 @@ How to Unit Test your Forms =========================== -.. caution:: +.. warning:: This article is intended for developers who create :doc:`custom form types `. If you are using @@ -121,7 +121,7 @@ variable exists and will be available in your form themes:: Use `PHPUnit data providers`_ to test multiple form conditions using the same test code. -.. caution:: +.. warning:: When your type relies on the ``EntityType``, you should register the :class:`Symfony\\Bridge\\Doctrine\\Form\\DoctrineOrmExtension`, which will @@ -214,7 +214,7 @@ allows you to return a list of extensions to register:: { $validator = Validation::createValidator(); - // or if you also need to read constraints from annotations + // or if you also need to read constraints from attributes $validator = Validation::createValidatorBuilder() ->enableAttributeMapping() ->getValidator(); diff --git a/form/without_class.rst b/form/without_class.rst index 589f8a4739e..8b0af7cf23f 100644 --- a/form/without_class.rst +++ b/form/without_class.rst @@ -121,7 +121,7 @@ but here's a short example:: submitted data is validated using the ``Symfony\Component\Validator\Constraints\Valid`` constraint, unless you :doc:`disable validation `. -.. caution:: +.. warning:: When a form is only partially submitted (for example, in an HTTP PATCH request), only the constraints from the submitted form fields will be @@ -137,6 +137,7 @@ This can be done by setting the ``constraints`` option in the use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; + use Symfony\Component\Validator\Constraints\Collection; use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\NotBlank; @@ -149,17 +150,15 @@ This can be done by setting the ``constraints`` option in the public function configureOptions(OptionsResolver $resolver): void { - $constraints = [ - 'firstName' => new Length(['min' => 3]), - 'lastName' => [ - new NotBlank(), - new Length(['min' => 3]), - ], - ]; - $resolver->setDefaults([ 'data_class' => null, - 'constraints' => $constraints, + 'constraints' => new Collection([ + 'firstName' => new Length(['min' => 3]), + 'lastName' => [ + new NotBlank(), + new Length(['min' => 3]), + ], + ]), ]); } diff --git a/forms.rst b/forms.rst index 8b040358782..008c60a66c6 100644 --- a/forms.rst +++ b/forms.rst @@ -773,7 +773,7 @@ to the ``form()`` or the ``form_start()`` helper functions: that stores this method. The form will be submitted in a normal ``POST`` request, but :doc:`Symfony's routing ` is capable of detecting the ``_method`` parameter and will interpret it as a ``PUT``, ``PATCH`` or - ``DELETE`` request. The :ref:`configuration-framework-http_method_override` + ``DELETE`` request. The :ref:`http_method_override ` option must be enabled for this to work. Changing the Form Name @@ -869,7 +869,7 @@ pass ``null`` to it:: } } -.. caution:: +.. warning:: When using a specific :doc:`form validation group `, the field type guesser will still consider *all* validation constraints when @@ -964,7 +964,6 @@ Advanced Features: /controller/upload_file /security/csrf - /form/form_dependencies /form/create_custom_field_type /form/data_transformers /form/data_mappers diff --git a/frontend.rst b/frontend.rst index 6b7aa6f5048..c28e6fcf222 100644 --- a/frontend.rst +++ b/frontend.rst @@ -34,10 +34,10 @@ Supports `Stimulus/UX`_ yes yes Supports Sass/Tailwind :ref:`yes ` yes Supports React, Vue, Svelte? yes :ref:`[1] ` yes Supports TypeScript :ref:`yes ` yes -Removes comments from JavaScript no yes -Removes comments from CSS no no +Removes comments from JavaScript no :ref:`[2] ` yes +Removes comments from CSS no :ref:`[2] ` no Versioned assets always optional -Can update 3rd party packages yes no :ref:`[2] ` +Can update 3rd party packages yes no :ref:`[3] ` ================================ ================================== ========== .. _ux-note-1: @@ -49,13 +49,22 @@ be executed by a browser. .. _ux-note-2: -**[2]** If you use ``npm``, there are update checkers available (e.g. ``npm-check``). +**[2]** You can install the `SensioLabs Minify Bundle`_ to minify CSS/JS code +(and remove all comments) when compiling assets with AssetMapper. + +.. _ux-note-3: + +**[3]** If you use ``npm``, there are update checkers available (e.g. ``npm-check``). .. _frontend-asset-mapper: AssetMapper (Recommended) ~~~~~~~~~~~~~~~~~~~~~~~~~ +.. screencast:: + + Do you prefer video tutorials? Check out the `AssetMapper screencast series`_. + AssetMapper is the recommended system for handling your assets. It runs entirely in PHP with no complex build step or dependencies. It does this by leveraging the ``importmap`` feature of your browser, which is available in all browsers thanks @@ -78,6 +87,26 @@ pre-processing CSS & JS and compiling and minifying assets. :doc:`Read the Encore Documentation ` +Switch from AssetMapper +^^^^^^^^^^^^^^^^^^^^^^^ + +By default, new Symfony webapp projects (created with ``symfony new --webapp myapp``) +use AssetMapper. If you still need to use Webpack Encore, use the following steps to +switch. This is best done on a new project and provides the same features (Turbo/Stimulus) +as the default webapp. + +.. code-block:: terminal + + # Remove AssetMapper & Turbo/Stimulus temporarily + $ composer remove symfony/ux-turbo symfony/asset-mapper symfony/stimulus-bundle + + # Add Webpack Encore & Turbo/Stimulus back + $ composer require symfony/webpack-encore-bundle symfony/ux-turbo symfony/stimulus-bundle + + # Install & Build Assets + $ npm install + $ npm run dev + Stimulus & Symfony UX Components ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -93,6 +122,10 @@ the `StimulusBundle Documentation`_ Using a Front-end Framework (React, Vue, Svelte, etc) ----------------------------------------------------- +.. screencast:: + + Do you prefer video tutorials? Check out the `API Platform screencast series`_. + If you want to use a front-end framework (Next.js, React, Vue, Svelte, etc), we recommend using their native tools and using Symfony as a pure API. A wonderful tool to do that is `API Platform`_. Their standard distribution comes with a @@ -117,3 +150,6 @@ Other Front-End Articles .. _`Turbo`: https://turbo.hotwired.dev/ .. _`Symfony UX`: https://ux.symfony.com .. _`API Platform`: https://api-platform.com/ +.. _`SensioLabs Minify Bundle`: https://github.com/sensiolabs/minify-bundle +.. _`AssetMapper screencast series`: https://symfonycasts.com/screencast/asset-mapper +.. _`API Platform screencast series`: https://symfonycasts.com/screencast/api-platform diff --git a/frontend/asset_mapper.rst b/frontend/asset_mapper.rst index aa0b4615385..57e0aa53d43 100644 --- a/frontend/asset_mapper.rst +++ b/frontend/asset_mapper.rst @@ -73,6 +73,8 @@ If you look at the HTML in your page, the URL will be something like: ``/assets/images/duck-3c16d9220694c0e56d8648f25e6035e9.png``. If you change the file, the version part of the URL will also change automatically. +.. _asset-mapper-compile-assets: + Serving Assets in dev vs prod ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -89,6 +91,13 @@ This will physically copy all the files from your mapped directories to ``public/assets/`` so that they're served directly by your web server. See :ref:`Deployment ` for more details. +.. warning:: + + If you run the ``asset-map:compile`` command on your development machine, + you won't see any changes made to your assets when reloading the page. + To resolve this, delete the contents of the ``public/assets/`` directory. + This will allow your Symfony application to serve those assets dynamically again. + .. tip:: If you need to copy the compiled assets to a different location (e.g. upload @@ -384,6 +393,8 @@ from inside ``app.js``: // things on "window" become global variables window.$ = $; +.. _asset-mapper-handling-css: + Handling CSS ------------ @@ -615,7 +626,7 @@ To make your AssetMapper-powered site fly, there are a few things you need to do. If you want to take a shortcut, you can use a service like `Cloudflare`_, which will automatically do most of these things for you: -- **Use HTTP/2**: Your web server should be running HTTP/2 (or HTTP/3) so the +- **Use HTTP/2**: Your web server should be running HTTP/2 or HTTP/3 so the browser can download assets in parallel. HTTP/2 is automatically enabled in Caddy and can be activated in Nginx and Apache. Or, proxy your site through a service like Cloudflare, which will automatically enable HTTP/2 for you. @@ -623,9 +634,7 @@ which will automatically do most of these things for you: - **Compress your assets**: Your web server should compress (e.g. using gzip) your assets (JavaScript, CSS, images) before sending them to the browser. This is automatically enabled in Caddy and can be activated in Nginx and Apache. - In Cloudflare, assets are compressed by default and you can also - enable `auto minify`_ to further compress your assets (e.g. removing - whitespace and comments from JavaScript and CSS files). + In Cloudflare, assets are compressed by default. - **Set long-lived cache expiry**: Your web server should set a long-lived ``Cache-Control`` HTTP header on your assets. Because the AssetMapper component includes a version @@ -693,9 +702,16 @@ See :ref:`Optimization ` for more details. Does the AssetMapper Component Minify Assets? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Nope! Minifying or compressing assets *is* important, but can be -done by your web server or a service like Cloudflare. See -:ref:`Optimization ` for more details. +Nope! In most cases, this is perfectly fine. The web asset compression performed +by web servers before sending them is usually sufficient. However, if you think +you could benefit from minifying assets (in addition to later compressing them), +you can use the `SensioLabs Minify Bundle`_. + +This bundle integrates seamlessly with AssetMapper and minifies all web assets +automatically when running the ``asset-map:compile`` command (as explained in +the :ref:`serving assets in production ` section). + +See :ref:`Optimization ` for more details. Is the AssetMapper Component Production Ready? Is it Performant? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -772,6 +788,13 @@ files) with component, as those must be used in a build system. See the `UX Vue.js Documentation`_ for more details about using with the AssetMapper component. +Can I Lint and Format My Code? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Not with AssetMapper, but you can install `kocal/biome-js-bundle`_ in your project +to lint and format your front-end assets. It's much faster than alternatives like +Prettier and requires no configuration to handle your JavaScript, TypeScript and CSS files. + .. _asset-mapper-ts: Using TypeScript @@ -929,7 +952,7 @@ This option is enabled by default. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Configure the polyfill for older browsers. By default, the `ES module shim`_ is loaded -via a CDN (i.e. the default value for this setting is `es-module-shims`): +via a CDN (i.e. the default value for this setting is ``es-module-shims``): .. code-block:: yaml @@ -1029,6 +1052,45 @@ have *one* importmap, so ``importmap()`` must be called exactly once. If, for some reason, you want to execute *only* ``checkout.js`` and *not* ``app.js``, pass only ``checkout`` to ``importmap()``. +Using a Content Security Policy (CSP) +------------------------------------- + +If you're using a `Content Security Policy`_ (CSP) to prevent cross-site +scripting attacks, the inline `` +However, on the client side (i.e. in JavaScript's ``EventSource``), there is no +built-in way to know which topic a certain message originates from. If this (or +any other meta information) is important to you, you need to include it in the +message's data (e.g. by adding a key to the JSON, or a ``data-*`` attribute to +the HTML). + .. tip:: - Google Chrome DevTools natively integrate a `practical UI`_ displaying in live - the received events: + Test if a URI Template matches a URL using `the online debugger`_ - .. image:: /_images/mercure/chrome.png - :alt: The Chrome DevTools showing the EventStream tab containing information about each SSE event. - - To use it: +.. tip:: - * open the DevTools - * select the "Network" tab - * click on the request to the Mercure hub - * click on the "EventStream" sub-tab. + Google Chrome features a practical UI to display the received events: -.. tip:: + .. image:: /_images/mercure/chrome.png + :alt: The Chrome DevTools showing the EventStream tab containing information about each SSE event. - Test if a URI Template match a URL using `the online debugger`_ + In DevTools, select the "Network" tab, then click on the request to the Mercure hub, then on the "EventStream" sub-tab. Discovery --------- @@ -446,7 +448,7 @@ Using cookies is the most secure and preferred way when the client is a web browser. If the client is not a web browser, then using an authorization header is the way to go. -.. caution:: +.. warning:: To use the cookie authentication method, the Symfony app and the Hub must be served from the same domain (can be different sub-domains). @@ -673,10 +675,11 @@ sent: .. code-block:: yaml # config/services_test.yaml - mercure.hub.default: - class: App\Tests\Functional\Stub\HubStub + services: + mercure.hub.default: + class: App\Tests\Functional\Stub\HubStub -As MercureBundle support multiple hubs, you may have to replace +As MercureBundle supports multiple hubs, you may have to replace the other service definitions accordingly. .. tip:: @@ -690,8 +693,6 @@ Debugging The WebProfiler panel was introduced in MercureBundle 0.2. -Enable the panel in your configuration, as follows: - MercureBundle is shipped with a debug panel. Install the Debug pack to enable it:: @@ -766,7 +767,6 @@ Going further .. _`JSON Web Token`: https://tools.ietf.org/html/rfc7519 .. _`example JWT`: https://jwt.io/#debugger-io?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdfX0.iHLdpAEjX4BqCsHJEegxRmO-Y6sMxXwNATrQyRNt3GY .. _`IRI`: https://tools.ietf.org/html/rfc3987 -.. _`practical UI`: https://twitter.com/ChromeDevTools/status/562324683194785792 .. _`the dedicated API Platform documentation`: https://api-platform.com/docs/core/mercure/ .. _`the online debugger`: https://uri-template-tester.mercure.rocks .. _`a feature to test applications using Mercure`: https://github.com/symfony/panther#creating-isolated-browsers-to-test-apps-using-mercure-or-websocket diff --git a/messenger.rst b/messenger.rst index 621a74e55f5..97829327d03 100644 --- a/messenger.rst +++ b/messenger.rst @@ -541,7 +541,7 @@ On production, there are a few important things to think about: **Use the Same Cache Between Deploys** If your deploy strategy involves the creation of new target directories, you - should set a value for the :ref:`cache.prefix.seed ` + should set a value for the :ref:`cache.prefix_seed ` configuration option in order to use the same cache namespace between deployments. Otherwise, the ``cache.app`` pool will use the value of the ``kernel.project_dir`` parameter as base for the namespace, which will lead to different namespaces @@ -728,7 +728,7 @@ times: Change the ``async`` argument to use the name of your transport (or transports) and ``user`` to the Unix user on your server. -.. caution:: +.. warning:: During a deployment, something might be unavailable (e.g. the database) causing the consumer to fail to start. In this situation, @@ -745,7 +745,7 @@ If you use the Redis Transport, note that each worker needs a unique consumer name to avoid the same message being handled by multiple workers. One way to achieve this is to set an environment variable in the Supervisor configuration file, which you can then refer to in ``messenger.yaml`` -(see the ref:`Redis section ` below): +(see the :ref:`Redis section ` below): .. code-block:: ini @@ -903,7 +903,7 @@ Rate Limited Transport ~~~~~~~~~~~~~~~~~~~~~~ Sometimes you might need to rate limit your message worker. You can configure a -rate limiter on a transport (requires the :doc:`RateLimiter component `) +rate limiter on a transport (requires the :doc:`RateLimiter component `) by setting its ``rate_limiter`` option: .. configuration-block:: @@ -950,7 +950,7 @@ by setting its ``rate_limiter`` option: ; }; -.. caution:: +.. warning:: When a rate limiter is configured on a transport, it will block the whole worker when the limit is hit. You should make sure you configure a dedicated @@ -1394,65 +1394,115 @@ the exchange, queues binding keys and more. See the documentation on The transport has a number of options: -============================================ ================================================= =================================== - Option Description Default -============================================ ================================================= =================================== -``auto_setup`` Whether the exchanges and queues should be ``true`` - created automatically during send / get. -``cacert`` Path to the CA cert file in PEM format. -``cert`` Path to the client certificate in PEM format. -``channel_max`` Specifies highest channel number that the server - permits. 0 means standard extension limit -``confirm_timeout`` Timeout in seconds for confirmation; if none - specified, transport will not wait for message - confirmation. Note: 0 or greater seconds. May be - fractional. -``connect_timeout`` Connection timeout. Note: 0 or greater seconds. - May be fractional. -``frame_max`` The largest frame size that the server proposes - for the connection, including frame header and - end-byte. 0 means standard extension limit - (depends on librabbimq default frame size limit) -``heartbeat`` The delay, in seconds, of the connection - heartbeat that the server wants. 0 means the - server does not want a heartbeat. Note, - librabbitmq has limited heartbeat support, which - means heartbeats checked only during blocking - calls. -``host`` Hostname of the AMQP service -``key`` Path to the client key in PEM format. -``login`` Username to use to connect the AMQP service -``password`` Password to use to connect to the AMQP service -``persistent`` ``'false'`` -``port`` Port of the AMQP service -``read_timeout`` Timeout in for income activity. Note: 0 or - greater seconds. May be fractional. +``auto_setup`` (default: ``true``) + Whether the exchanges and queues should be created automatically during + send / get. + +``cacert`` + Path to the CA cert file in PEM format. + +``cert`` + Path to the client certificate in PEM format. + +``channel_max`` + Specifies highest channel number that the server permits. 0 means standard + extension limit + +``confirm_timeout`` + Timeout in seconds for confirmation; if none specified, transport will not + wait for message confirmation. Note: 0 or greater seconds. May be + fractional. + +``connect_timeout`` + Connection timeout. Note: 0 or greater seconds. May be fractional. + +``frame_max`` + The largest frame size that the server proposes for the connection, + including frame header and end-byte. 0 means standard extension limit + (depends on librabbimq default frame size limit) + +``heartbeat`` + The delay, in seconds, of the connection heartbeat that the server wants. 0 + means the server does not want a heartbeat. Note, librabbitmq has limited + heartbeat support, which means heartbeats checked only during blocking + calls. + +``host`` + Hostname of the AMQP service + +``key`` + Path to the client key in PEM format. + +``login`` + Username to use to connect the AMQP service + +``password`` + Password to use to connect to the AMQP service + +``persistent`` (default: ``'false'``) + Whether the connection is persistent + +``port`` + Port of the AMQP service + +``read_timeout`` + Timeout in for income activity. Note: 0 or greater seconds. May be + fractional. + ``retry`` + (no description available) + ``sasl_method`` -``connection_name`` For custom connection names (requires at least - version 1.10 of the PHP AMQP extension) -``verify`` Enable or disable peer verification. If peer - verification is enabled then the common name in - the server certificate must match the server - name. Peer verification is enabled by default. -``vhost`` Virtual Host to use with the AMQP service -``write_timeout`` Timeout in for outcome activity. Note: 0 or - greater seconds. May be fractional. -``delay[queue_name_pattern]`` Pattern to use to create the queues ``delay_%exchange_name%_%routing_key%_%delay%`` -``delay[exchange_name]`` Name of the exchange to be used for the ``delays`` - delayed/retried messages -``queues[name][arguments]`` Extra arguments -``queues[name][binding_arguments]`` Arguments to be used while binding the queue. -``queues[name][binding_keys]`` The binding keys (if any) to bind to this queue -``queues[name][flags]`` Queue flags ``AMQP_DURABLE`` -``exchange[arguments]`` Extra arguments for the exchange (e.g. - ``alternate-exchange``) -``exchange[default_publish_routing_key]`` Routing key to use when publishing, if none is - specified on the message -``exchange[flags]`` Exchange flags ``AMQP_DURABLE`` -``exchange[name]`` Name of the exchange -``exchange[type]`` Type of exchange ``fanout`` -============================================ ================================================= =================================== + (no description available) + +``connection_name`` + For custom connection names (requires at least version 1.10 of the PHP AMQP + extension) + +``verify`` + Enable or disable peer verification. If peer verification is enabled then + the common name in the server certificate must match the server name. Peer + verification is enabled by default. + +``vhost`` + Virtual Host to use with the AMQP service + +``write_timeout`` + Timeout in for outcome activity. Note: 0 or greater seconds. May be + fractional. + +``delay[queue_name_pattern]`` (default: ``delay_%exchange_name%_%routing_key%_%delay%``) + Pattern to use to create the queues + +``delay[exchange_name]`` (default: ``delays``) + Name of the exchange to be used for the delayed/retried messages + +``queues[name][arguments]`` + Extra arguments + +``queues[name][binding_arguments]`` + Arguments to be used while binding the queue. + +``queues[name][binding_keys]`` + The binding keys (if any) to bind to this queue + +``queues[name][flags]`` (default: ``AMQP_DURABLE``) + Queue flags + +``exchange[arguments]`` + Extra arguments for the exchange (e.g. ``alternate-exchange``) + +``exchange[default_publish_routing_key]`` + Routing key to use when publishing, if none is specified on the message + +``exchange[flags]`` (default: ``AMQP_DURABLE``) + Exchange flags + +``exchange[name]`` + Name of the exchange + +``exchange[type]`` (default: ``fanout``) + Type of exchange You can also configure AMQP-specific settings on your message by adding :class:`Symfony\\Component\\Messenger\\Bridge\\Amqp\\Transport\\AmqpStamp` to @@ -1466,7 +1516,7 @@ your Envelope:: new AmqpStamp('custom-routing-key', AMQP_NOPARAM, $attributes), ]); -.. caution:: +.. warning:: The consumers do not show up in an admin panel as this transport does not rely on ``\AmqpQueue::consume()`` which is blocking. Having a blocking receiver makes @@ -1517,36 +1567,28 @@ DSN by using the ``table_name`` option: Or, to create the table yourself, set the ``auto_setup`` option to ``false`` and :ref:`generate a migration `. -.. caution:: +The transport has a number of options: - The datetime property of the messages stored in the database uses the - timezone of the current system. This may cause issues if multiple machines - with different timezone configuration use the same storage. +``table_name`` (default: ``messenger_messages``) + Name of the table -The transport has a number of options: +``queue_name`` (default: ``default``) + Name of the queue (a column in the table, to use one table for multiple + transports) -================== ===================================== ====================== -Option Description Default -================== ===================================== ====================== -table_name Name of the table messenger_messages -queue_name Name of the queue (a column in the default - table, to use one table for - multiple transports) -redeliver_timeout Timeout before retrying a message 3600 - that's in the queue but in the - "handling" state (if a worker stopped - for some reason, this will occur, - eventually you should retry the - message) - in seconds. -auto_setup Whether the table should be created - automatically during send / get. true -================== ===================================== ====================== +``redeliver_timeout`` (default: ``3600``) + Timeout before retrying a message that's in the queue but in the "handling" + state (if a worker stopped for some reason, this will occur, eventually you + should retry the message) - in seconds. -.. note:: + .. note:: + + Set ``redeliver_timeout`` to a greater value than your slowest message + duration. Otherwise, some messages will start a second time while the + first one is still being handled. - Set ``redeliver_timeout`` to a greater value than your slowest message - duration. Otherwise, some messages will start a second time while the - first one is still being handled. +``auto_setup`` + Whether the table should be created automatically during send / get. When using PostgreSQL, you have access to the following options to leverage the `LISTEN/NOTIFY`_ feature. This allow for a more performant approach @@ -1554,17 +1596,16 @@ than the default polling behavior of the Doctrine transport because PostgreSQL will directly notify the workers when a new message is inserted in the table. -======================= ========================================== ====================== -Option Description Default -======================= ========================================== ====================== -use_notify Whether to use LISTEN/NOTIFY. true -check_delayed_interval The interval to check for delayed 60000 - messages, in milliseconds. - Set to 0 to disable checks. -get_notify_timeout The length of time to wait for a 0 - response when calling - ``PDO::pgsqlGetNotify``, in milliseconds. -======================= ========================================== ====================== +``use_notify`` (default: ``true``) + Whether to use LISTEN/NOTIFY. + +``check_delayed_interval`` (default: ``60000``) + The interval to check for delayed messages, in milliseconds. Set to 0 to + disable checks. + +``get_notify_timeout`` (default: ``0``) + The length of time to wait for a response when calling + ``PDO::pgsqlGetNotify``, in milliseconds. Beanstalkd Transport ~~~~~~~~~~~~~~~~~~~~ @@ -1588,20 +1629,16 @@ The Beanstalkd transport DSN may looks like this: The transport has a number of options: -================== =================================== ====================== - Option Description Default -================== =================================== ====================== -tube_name Name of the queue default -timeout Message reservation timeout 0 (will cause the - - in seconds. server to immediately - return either a - response or a - TransportException - will be thrown) -ttr The message time to run before it - is put back in the ready queue - - in seconds. 90 -================== =================================== ====================== +``tube_name`` (default: ``default``) + Name of the queue + +``timeout`` (default: ``0``) + Message reservation timeout - in seconds. 0 will cause the server to + immediately return either a response or a TransportException will be thrown. + +``ttr`` (default: ``90``) + The message time to run before it is put back in the ready queue - in + seconds. .. _messenger-redis-transport: @@ -1636,53 +1673,89 @@ The Redis transport DSN may looks like this: A number of options can be configured via the DSN or via the ``options`` key under the transport in ``messenger.yaml``: -======================= ===================================== ================================= -Option Description Default -======================= ===================================== ================================= -stream The Redis stream name messages -group The Redis consumer group name symfony -consumer Consumer name used in Redis consumer -auto_setup Create the Redis group automatically? true -auth The Redis password -delete_after_ack If ``true``, messages are deleted true - automatically after processing them -delete_after_reject If ``true``, messages are deleted true - automatically if they are rejected -lazy Connect only when a connection is false - really needed -serializer How to serialize the final payload ``Redis::SERIALIZER_PHP`` - in Redis (the - ``Redis::OPT_SERIALIZER`` option) -stream_max_entries The maximum number of entries which ``0`` (which means "no trimming") - the stream will be trimmed to. Set - it to a large enough number to - avoid losing pending messages -redeliver_timeout Timeout before retrying a pending ``3600`` - message which is owned by an - abandoned consumer (if a worker died - for some reason, this will occur, - eventually you should retry the - message) - in seconds. -claim_interval Interval on which pending/abandoned ``60000`` (1 Minute) - messages should be checked for to - claim - in milliseconds -persistent_id String, if null connection is null - non-persistent. -retry_interval Int, value in milliseconds ``0`` -read_timeout Float, value in seconds ``0`` - default indicates unlimited -timeout Connection timeout. Float, value in ``0`` - seconds default indicates unlimited -sentinel_master String, if null or empty Sentinel null -redis_sentinel support is disabled -======================= ===================================== ================================= +``stream`` (default: ``messages``) + The Redis stream name -.. versionadded:: 7.1 +``group`` (default: ``symfony``) + The Redis consumer group name + +``consumer`` (default: ``consumer``) + Consumer name used in Redis + +``auto_setup`` (default: ``true``) + Whether to create the Redis group automatically + +``auth`` + The Redis password + +``delete_after_ack`` (default: ``true``) + If ``true``, messages are deleted automatically after processing them + +``delete_after_reject`` (default: ``true``) + If ``true``, messages are deleted automatically if they are rejected + +``lazy`` (default: ``false``) + Connect only when a connection is really needed + +``serializer`` (default: ``Redis::SERIALIZER_PHP``) + How to serialize the final payload in Redis (the ``Redis::OPT_SERIALIZER`` option) + +``stream_max_entries`` (default: ``0``) + The maximum number of entries which the stream will be trimmed to. Set it to + a large enough number to avoid losing pending messages + +``redeliver_timeout`` (default: ``3600``) + Timeout (in seconds) before retrying a pending message which is owned by an abandoned consumer + (if a worker died for some reason, this will occur, eventually you should retry the message). + +``claim_interval`` (default: ``60000``) + Interval on which pending/abandoned messages should be checked for to claim - in milliseconds + +``persistent_id`` (default: ``null``) + String, if null connection is non-persistent. + +``retry_interval`` (default: ``0``) + Int, value in milliseconds + +``read_timeout`` (default: ``0``) + Float, value in seconds default indicates unlimited + +``timeout`` (default: ``0``) + Connection timeout. Float, value in seconds default indicates unlimited + +``sentinel_master`` (default: ``null``) + String, if null or empty Sentinel support is disabled + +``redis_sentinel`` (default: ``null``) + An alias of the ``sentinel_master`` option + + .. versionadded:: 7.1 - The option `redis_sentinel` as an alias for `sentinel_master` was introduced - in Symfony 7.1. + The ``redis_sentinel`` option was introduced in Symfony 7.1. -.. caution:: +``ssl`` (default: ``null``) + Map of `SSL context options`_ for the TLS channel. This is useful for example + to change the requirements for the TLS channel in tests: + + .. code-block:: yaml + + # config/packages/test/messenger.yaml + framework: + messenger: + transports: + redis: + dsn: "rediss://localhost" + options: + ssl: + allow_self_signed: true + capture_peer_cert: true + capture_peer_cert_chain: true + disable_compression: true + SNI_enabled: true + verify_peer: true + verify_peer_name: true + +.. warning:: There should never be more than one ``messenger:consume`` command running with the same combination of ``stream``, ``group`` and ``consumer``, or messages could end up being @@ -1768,7 +1841,7 @@ during a request:: $this->assertSame(200, $client->getResponse()->getStatusCode()); - /* @var InMemoryTransport $transport */ + /** @var InMemoryTransport $transport */ $transport = $this->getContainer()->get('messenger.transport.async_priority_normal'); $this->assertCount(1, $transport->getSent()); } @@ -1818,27 +1891,44 @@ The SQS transport DSN may looks like this: The transport has a number of options: -====================== ====================================== =================================== - Option Description Default -====================== ====================================== =================================== -``access_key`` AWS access key must be urlencoded -``account`` Identifier of the AWS account The owner of the credentials -``auto_setup`` Whether the queue should be created ``true`` - automatically during send / get. -``buffer_size`` Number of messages to prefetch 9 -``debug`` If ``true`` it logs all HTTP requests ``false`` - and responses (it impacts performance) -``endpoint`` Absolute URL to the SQS service https://sqs.eu-west-1.amazonaws.com -``poll_timeout`` Wait for new message duration in 0.1 - seconds -``queue_name`` Name of the queue messages -``region`` Name of the AWS region eu-west-1 -``secret_key`` AWS secret key must be urlencoded -``session_token`` AWS session token -``visibility_timeout`` Amount of seconds the message will Queue's configuration - not be visible (`Visibility Timeout`_) -``wait_time`` `Long polling`_ duration in seconds 20 -====================== ====================================== =================================== +``access_key`` + AWS access key (must be urlencoded) + +``account`` (default: The owner of the credentials) + Identifier of the AWS account + +``auto_setup`` (default: ``true``) + Whether the queue should be created automatically during send / get. + +``buffer_size`` (default: ``9``) + Number of messages to prefetch + +``debug`` (default: ``false``) + If ``true`` it logs all HTTP requests and responses (it impacts performance) + +``endpoint`` (default: ``https://sqs.eu-west-1.amazonaws.com``) + Absolute URL to the SQS service + +``poll_timeout`` (default: ``0.1``) + Wait for new message duration in seconds + +``queue_name`` (default: ``messages``) + Name of the queue + +``region`` (default: ``eu-west-1``) + Name of the AWS region + +``secret_key`` + AWS secret key (must be urlencoded) + +``session_token`` + AWS session token + +``visibility_timeout`` (default: Queue's configuration) + Amount of seconds the message will not be visible (`Visibility Timeout`_) + +``wait_time`` (default: ``20``) + `Long polling`_ duration in seconds .. note:: @@ -2171,40 +2261,6 @@ wherever you need a query bus behavior instead of the ``MessageBusInterface``:: Customizing Handlers -------------------- -Configuring Handlers Using Attributes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You can configure your handler by passing options to the attribute:: - - // src/MessageHandler/SmsNotificationHandler.php - namespace App\MessageHandler; - - use App\Message\OtherSmsNotification; - use App\Message\SmsNotification; - use Symfony\Component\Messenger\Attribute\AsMessageHandler; - - #[AsMessageHandler(fromTransport: 'async', priority: 10)] - class SmsNotificationHandler - { - public function __invoke(SmsNotification $message): void - { - // ... - } - } - -Possible options to configure with the attribute are: - -============================== ==================================================================================================== -Option Description -============================== ==================================================================================================== -``bus`` Name of the bus from which the handler can receive messages, by default all buses. -``fromTransport`` Name of the transport from which the handler can receive messages, by default all transports. -``handles`` Type of messages (FQCN) that can be processed by the handler, only needed if can't be guessed by - type-hint. -``method`` Name of the method that will process the message, only if the target is a class. -``priority`` Priority of the handler when multiple handlers can process the same message. -============================== ==================================================================================================== - .. _messenger-handler-config: Manually Configuring Handlers @@ -2212,10 +2268,29 @@ Manually Configuring Handlers Symfony will normally :ref:`find and register your handler automatically `. But, you can also configure a handler manually - and pass it some extra config - -by tagging the handler service with ``messenger.message_handler`` +while using ``#AsMessageHandler`` attribute or tagging the handler service +with ``messenger.message_handler``. .. configuration-block:: + .. code-block:: php-attributes + + // src/MessageHandler/SmsNotificationHandler.php + namespace App\MessageHandler; + + use App\Message\OtherSmsNotification; + use App\Message\SmsNotification; + use Symfony\Component\Messenger\Attribute\AsMessageHandler; + + #[AsMessageHandler(fromTransport: 'async', priority: 10)] + class SmsNotificationHandler + { + public function __invoke(SmsNotification $message): void + { + // ... + } + } + .. code-block:: yaml # config/services.yaml @@ -2262,16 +2337,22 @@ by tagging the handler service with ``messenger.message_handler`` Possible options to configure with tags are: -============================ ==================================================================================================== -Option Description -============================ ==================================================================================================== -``bus`` Name of the bus from which the handler can receive messages, by default all buses. -``from_transport`` Name of the transport from which the handler can receive messages, by default all transports. -``handles`` Type of messages (FQCN) that can be processed by the handler, only needed if can't be guessed by - type-hint. -``method`` Name of the method that will process the message. -``priority`` Priority of the handler when multiple handlers can process the same message. -============================ ==================================================================================================== +``bus`` + Name of the bus from which the handler can receive messages, by default all buses. + +``from_transport`` + Name of the transport from which the handler can receive messages, by default + all transports. + +``handles`` + Type of messages (FQCN) that can be processed by the handler, only needed if + can't be guessed by type-hint. + +``method`` + Name of the method that will process the message. + +``priority`` + Priority of the handler when multiple handlers can process the same message. .. _handler-subscriber-options: @@ -2540,7 +2621,7 @@ That's it! You can now consume each transport: $ php bin/console messenger:consume async_priority_normal -vv -.. caution:: +.. warning:: If a handler does *not* have ``from_transport`` config, it will be executed on *every* transport that the message is received from. @@ -2564,7 +2645,7 @@ provided in order to ease the declaration of these special handlers:: { use BatchHandlerTrait; - public function __invoke(MyMessage $message, Acknowledger $ack = null): mixed + public function __invoke(MyMessage $message, ?Acknowledger $ack = null): mixed { return $this->handle($message, $ack); } @@ -2583,15 +2664,8 @@ provided in order to ease the declaration of these special handlers:: } } - // Optionally, you can either redefine the `shouldFlush()` method - // of the trait to define your own batch size... - private function shouldFlush(): bool - { - return 100 <= \count($this->jobs); - } - - // ... or redefine the `getBatchSize()` method if the default - // flush behavior suits your needs + // Optionally, you can override some of the trait methods, such as the + // `getBatchSize()` method, to specify your own batch size... private function getBatchSize(): int { return 100; @@ -2628,8 +2702,8 @@ to your message:: public function index(MessageBusInterface $bus): void { + // wait 5 seconds before processing $bus->dispatch(new SmsNotification('...'), [ - // wait 5 seconds before processing new DelayStamp(5000), ]); @@ -2991,12 +3065,10 @@ Let's say you want to create a message decoder:: { public function decode(array $encodedEnvelope): Envelope { - $envelope = \json_decode($encodedEnvelope, true); - try { // parse the data you received with your custom fields - $data = $envelope['data']; - $data['token'] = $envelope['token']; + $data = $encodedEnvelope['data']; + $data['token'] = $encodedEnvelope['token']; // other operations like getting information from stamps } catch (\Throwable $throwable) { @@ -3348,7 +3420,7 @@ You can also restrict the list to a specific bus by providing its name as an arg Redispatching a Message ----------------------- -It you want to redispatch a message (using the same transport and envelope), create +If you want to redispatch a message (using the same transport and envelope), create a new :class:`Symfony\\Component\\Messenger\\Message\\RedispatchMessage` and dispatch it through your bus. Reusing the same ``SmsNotification`` example shown earlier:: @@ -3396,7 +3468,7 @@ Learn more .. _`streams`: https://redis.io/topics/streams-intro .. _`Supervisor docs`: http://supervisord.org/ .. _`PCNTL`: https://www.php.net/manual/book.pcntl.php -.. _`systemd docs`: https://www.freedesktop.org/wiki/Software/systemd/ +.. _`systemd docs`: https://systemd.io/ .. _`SymfonyCasts' message serializer tutorial`: https://symfonycasts.com/screencast/messenger/transport-serializer .. _`Long polling`: https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-short-and-long-polling.html .. _`Visibility Timeout`: https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html @@ -3405,3 +3477,4 @@ Learn more .. _`AMQProxy`: https://github.com/cloudamqp/amqproxy .. _`high connection churn`: https://www.rabbitmq.com/connections.html#high-connection-churn .. _`article about CQRS`: https://martinfowler.com/bliki/CQRS.html +.. _`SSL context options`: https://php.net/context.ssl diff --git a/messenger/custom-transport.rst b/messenger/custom-transport.rst index d9cc9fa1abf..7d1698126d1 100644 --- a/messenger/custom-transport.rst +++ b/messenger/custom-transport.rst @@ -51,7 +51,7 @@ Here is a simplified example of a database transport:: */ public function __construct( private FakeDatabase $db, - SerializerInterface $serializer = null, + ?SerializerInterface $serializer = null, ) { $this->serializer = $serializer ?? new PhpSerializer(); } diff --git a/notifier.rst b/notifier.rst index 3444ed8ab9b..b74f4eb02e0 100644 --- a/notifier.rst +++ b/notifier.rst @@ -15,13 +15,15 @@ Get the Notifier installed using: $ composer require symfony/notifier .. _channels-chatters-texters-email-and-browser: +.. _channels-chatters-texters-email-browser-and-push: -Channels: Chatters, Texters, Email, Browser and Push ----------------------------------------------------- +Channels +-------- -The notifier component can send notifications to different channels. Each -channel can integrate with different providers (e.g. Slack or Twilio SMS) -by using transports. +Channels refer to the different mediums through which notifications can be delivered. +These channels include email, SMS, chat services, push notifications, etc. Each +channel can integrate with different providers (e.g. Slack or Twilio SMS) by +using transports. The notifier component supports the following channels: @@ -33,79 +35,175 @@ The notifier component supports the following channels: * Browser channel uses :ref:`flash messages `. * :ref:`Push channel ` sends notifications to phones and browsers via push notifications. -.. tip:: - - Use :doc:`secrets ` to securely store your - API tokens. - .. _notifier-sms-channel: SMS Channel ~~~~~~~~~~~ -.. caution:: +The SMS channel uses :class:`Symfony\\Component\\Notifier\\Texter` classes +to send SMS messages to mobile phones. This feature requires subscribing to +a third-party service that sends SMS messages. Symfony provides integration +with a couple popular SMS services: + +.. warning:: If any of the DSN values contains any character considered special in a URI (such as ``: / ? # [ ] @ ! $ & ' ( ) * + , ; =``), you must encode them. See `RFC 3986`_ for the full list of reserved characters or use the :phpfunction:`urlencode` function to encode them. -The SMS channel uses :class:`Symfony\\Component\\Notifier\\Texter` classes -to send SMS messages to mobile phones. This feature requires subscribing to -a third-party service that sends SMS messages. Symfony provides integration -with a couple popular SMS services: +================== ==================================================================================================================================== +Service +================== ==================================================================================================================================== +`46elks`_ **Install**: ``composer require symfony/forty-six-elks-notifier`` \ + **DSN**: ``forty-six-elks://API_USERNAME:API_PASSWORD@default?from=FROM`` \ + **Webhook support**: No +`AllMySms`_ **Install**: ``composer require symfony/all-my-sms-notifier`` \ + **DSN**: ``allmysms://LOGIN:APIKEY@default?from=FROM`` \ + **Webhook support**: No +`AmazonSns`_ **Install**: ``composer require symfony/amazon-sns-notifier`` \ + **DSN**: ``sns://ACCESS_KEY:SECRET_KEY@default?region=REGION`` \ + **Webhook support**: No +`Bandwidth`_ **Install**: ``composer require symfony/bandwidth-notifier`` \ + **DSN**: ``bandwidth://USERNAME:PASSWORD@default?from=FROM&account_id=ACCOUNT_ID&application_id=APPLICATION_ID&priority=PRIORITY`` \ + **Webhook support**: No +`Brevo`_ **Install**: ``composer require symfony/brevo-notifier`` \ + **DSN**: ``brevo://API_KEY@default?sender=SENDER`` \ + **Webhook support**: No +`Clickatell`_ **Install**: ``composer require symfony/clickatell-notifier`` \ + **DSN**: ``clickatell://ACCESS_TOKEN@default?from=FROM`` \ + **Webhook support**: No +`ContactEveryone`_ **Install**: ``composer require symfony/contact-everyone-notifier`` \ + **DSN**: ``contact-everyone://TOKEN@default?&diffusionname=DIFFUSION_NAME&category=CATEGORY`` \ + **Webhook support**: No +`Esendex`_ **Install**: ``composer require symfony/esendex-notifier`` \ + **DSN**: ``esendex://USER_NAME:PASSWORD@default?accountreference=ACCOUNT_REFERENCE&from=FROM`` \ + **Webhook support**: No +`FakeSms`_ **Install**: ``composer require symfony/fake-sms-notifier`` \ + **DSN**: ``fakesms+email://MAILER_SERVICE_ID?to=TO&from=FROM`` or ``fakesms+logger://default`` \ + **Webhook support**: No +`FreeMobile`_ **Install**: ``composer require symfony/free-mobile-notifier`` \ + **DSN**: ``freemobile://LOGIN:API_KEY@default?phone=PHONE`` \ + **Webhook support**: No +`GatewayApi`_ **Install**: ``composer require symfony/gateway-api-notifier`` \ + **DSN**: ``gatewayapi://TOKEN@default?from=FROM`` \ + **Webhook support**: No +`GoIP`_ **Install**: ``composer require symfony/go-ip-notifier`` \ + **DSN**: ``goip://USERNAME:PASSWORD@HOST:80?sim_slot=SIM_SLOT`` \ + **Webhook support**: No +`Infobip`_ **Install**: ``composer require symfony/infobip-notifier`` \ + **DSN**: ``infobip://AUTH_TOKEN@HOST?from=FROM`` \ + **Webhook support**: No +`Iqsms`_ **Install**: ``composer require symfony/iqsms-notifier`` \ + **DSN**: ``iqsms://LOGIN:PASSWORD@default?from=FROM`` \ + **Webhook support**: No +`iSendPro`_ **Install**: ``composer require symfony/isendpro-notifier`` \ + **DSN**: ``isendpro://ACCOUNT_KEY_ID@default?from=FROM&no_stop=NO_STOP&sandbox=SANDBOX`` \ + **Webhook support**: No +`KazInfoTeh`_ **Install**: ``composer require symfony/kaz-info-teh-notifier`` \ + **DSN**: ``kaz-info-teh://USERNAME:PASSWORD@default?sender=FROM`` \ + **Webhook support**: No +`LightSms`_ **Install**: ``composer require symfony/light-sms-notifier`` \ + **DSN**: ``lightsms://LOGIN:TOKEN@default?from=PHONE`` \ + **Webhook support**: No +`LOX24`_ **Install**: ``composer require symfony/lox24-notifier`` \ + **DSN**: ``lox24://USER:TOKEN@default?from=FROM`` \ + **Webhook support**: No +`Mailjet`_ **Install**: ``composer require symfony/mailjet-notifier`` \ + **DSN**: ``mailjet://TOKEN@default?from=FROM`` \ + **Webhook support**: No +`MessageBird`_ **Install**: ``composer require symfony/message-bird-notifier`` \ + **DSN**: ``messagebird://TOKEN@default?from=FROM`` \ + **Webhook support**: No +`MessageMedia`_ **Install**: ``composer require symfony/message-media-notifier`` \ + **DSN**: ``messagemedia://API_KEY:API_SECRET@default?from=FROM`` \ + **Webhook support**: No +`Mobyt`_ **Install**: ``composer require symfony/mobyt-notifier`` \ + **DSN**: ``mobyt://USER_KEY:ACCESS_TOKEN@default?from=FROM`` \ + **Webhook support**: No +`Nexmo`_ **Install**: ``composer require symfony/nexmo-notifier`` \ + Abandoned in favor of Vonage (see below) \ +`Octopush`_ **Install**: ``composer require symfony/octopush-notifier`` \ + **DSN**: ``octopush://USERLOGIN:APIKEY@default?from=FROM&type=TYPE`` \ + **Webhook support**: No +`OrangeSms`_ **Install**: ``composer require symfony/orange-sms-notifier`` \ + **DSN**: ``orange-sms://CLIENT_ID:CLIENT_SECRET@default?from=FROM&sender_name=SENDER_NAME`` \ + **Webhook support**: No +`OvhCloud`_ **Install**: ``composer require symfony/ovh-cloud-notifier`` \ + **DSN**: ``ovhcloud://APPLICATION_KEY:APPLICATION_SECRET@default?consumer_key=CONSUMER_KEY&service_name=SERVICE_NAME`` \ + **Webhook support**: No +`Plivo`_ **Install**: ``composer require symfony/plivo-notifier`` \ + **DSN**: ``plivo://AUTH_ID:AUTH_TOKEN@default?from=FROM`` \ + **Webhook support**: No +`Redlink`_ **Install**: ``composer require symfony/redlink-notifier`` \ + **DSN**: ``redlink://API_KEY:APP_KEY@default?from=SENDER_NAME&version=API_VERSION`` \ + **Webhook support**: No +`RingCentral`_ **Install**: ``composer require symfony/ring-central-notifier`` \ + **DSN**: ``ringcentral://API_TOKEN@default?from=FROM`` \ + **Webhook support**: No +`Sendberry`_ **Install**: ``composer require symfony/sendberry-notifier`` \ + **DSN**: ``sendberry://USERNAME:PASSWORD@default?auth_key=AUTH_KEY&from=FROM`` \ + **Webhook support**: No +`Sendinblue`_ **Install**: ``composer require symfony/sendinblue-notifier`` \ + **DSN**: ``sendinblue://API_KEY@default?sender=PHONE`` \ + **Webhook support**: No +`Sms77`_ **Install**: ``composer require symfony/sms77-notifier`` \ + **DSN**: ``sms77://API_KEY@default?from=FROM`` \ + **Webhook support**: No +`SimpleTextin`_ **Install**: ``composer require symfony/simple-textin-notifier`` \ + **DSN**: ``simpletextin://API_KEY@default?from=FROM`` \ + **Webhook support**: No +`Sinch`_ **Install**: ``composer require symfony/sinch-notifier`` \ + **DSN**: ``sinch://ACCOUNT_ID:AUTH_TOKEN@default?from=FROM`` \ + **Webhook support**: No +`SmsSluzba`_ **Install**: ``composer require symfony/sms-sluzba-notifier`` \ + **DSN**: ``sms-sluzba://USERNAME:PASSWORD@default`` \ + **Webhook support**: No +`Smsapi`_ **Install**: ``composer require symfony/smsapi-notifier`` \ + **DSN**: ``smsapi://TOKEN@default?from=FROM`` \ + **Webhook support**: No +`Smsbox`_ **Install**: ``composer require symfony/smsbox-notifier`` \ + **DSN**: ``smsbox://APIKEY@default?mode=MODE&strategy=STRATEGY&sender=SENDER`` \ + **Webhook support**: No +`SmsBiuras`_ **Install**: ``composer require symfony/sms-biuras-notifier`` \ + **DSN**: ``smsbiuras://UID:API_KEY@default?from=FROM&test_mode=0`` \ + **Webhook support**: No +`Smsc`_ **Install**: ``composer require symfony/smsc-notifier`` \ + **DSN**: ``smsc://LOGIN:PASSWORD@default?from=FROM`` \ + **Webhook support**: No +`SMSense`_ **Install**: ``composer require smsense-notifier`` \ + **DSN**: ``smsense://API_TOKEN@default?from=FROM`` \ + **Webhook support**: No +`SMSFactor`_ **Install**: ``composer require symfony/sms-factor-notifier`` \ + **DSN**: ``sms-factor://TOKEN@default?sender=SENDER&push_type=PUSH_TYPE`` \ + **Webhook support**: No +`SpotHit`_ **Install**: ``composer require symfony/spot-hit-notifier`` \ + **DSN**: ``spothit://TOKEN@default?from=FROM`` \ + **Webhook support**: No +`Telnyx`_ **Install**: ``composer require symfony/telnyx-notifier`` \ + **DSN**: ``telnyx://API_KEY@default?from=FROM&messaging_profile_id=MESSAGING_PROFILE_ID`` \ + **Webhook support**: No +`TurboSms`_ **Install**: ``composer require symfony/turbo-sms-notifier`` \ + **DSN**: ``turbosms://AUTH_TOKEN@default?from=FROM`` \ + **Webhook support**: No +`Twilio`_ **Install**: ``composer require symfony/twilio-notifier`` \ + **DSN**: ``twilio://SID:TOKEN@default?from=FROM`` \ + **Webhook support**: Yes +`Unifonic`_ **Install**: ``composer require symfony/unifonic-notifier`` \ + **DSN**: ``unifonic://APP_SID@default?from=FROM`` \ + **Webhook support**: No +`Vonage`_ **Install**: ``composer require symfony/vonage-notifier`` \ + **DSN**: ``vonage://KEY:SECRET@default?from=FROM`` \ + **Webhook support**: Yes +`Yunpian`_ **Install**: ``composer require symfony/yunpian-notifier`` \ + **DSN**: ``yunpian://APIKEY@default`` \ + **Webhook support**: No +================== ==================================================================================================================================== -================== ===================================== ========================================================================================================================= =============== -Service Package DSN Webhook support -================== ===================================== ========================================================================================================================= =============== -`46elks`_ ``symfony/forty-six-elks-notifier`` ``forty-six-elks://API_USERNAME:API_PASSWORD@default?from=FROM`` -`AllMySms`_ ``symfony/all-my-sms-notifier`` ``allmysms://LOGIN:APIKEY@default?from=FROM`` -`AmazonSns`_ ``symfony/amazon-sns-notifier`` ``sns://ACCESS_KEY:SECRET_KEY@default?region=REGION`` -`Bandwidth`_ ``symfony/bandwidth-notifier`` ``bandwidth://USERNAME:PASSWORD@default?from=FROM&account_id=ACCOUNT_ID&application_id=APPLICATION_ID&priority=PRIORITY`` -`Brevo`_ ``symfony/brevo-notifier`` ``brevo://API_KEY@default?sender=SENDER`` -`Clickatell`_ ``symfony/clickatell-notifier`` ``clickatell://ACCESS_TOKEN@default?from=FROM`` -`ContactEveryone`_ ``symfony/contact-everyone-notifier`` ``contact-everyone://TOKEN@default?&diffusionname=DIFFUSION_NAME&category=CATEGORY`` -`Esendex`_ ``symfony/esendex-notifier`` ``esendex://USER_NAME:PASSWORD@default?accountreference=ACCOUNT_REFERENCE&from=FROM`` -`FakeSms`_ ``symfony/fake-sms-notifier`` ``fakesms+email://MAILER_SERVICE_ID?to=TO&from=FROM`` or ``fakesms+logger://default`` -`FreeMobile`_ ``symfony/free-mobile-notifier`` ``freemobile://LOGIN:API_KEY@default?phone=PHONE`` -`GatewayApi`_ ``symfony/gateway-api-notifier`` ``gatewayapi://TOKEN@default?from=FROM`` -`GoIP`_ ``symfony/goip-notifier`` ``goip://USERNAME:PASSWORD@HOST:80?sim_slot=SIM_SLOT`` -`Infobip`_ ``symfony/infobip-notifier`` ``infobip://AUTH_TOKEN@HOST?from=FROM`` -`Iqsms`_ ``symfony/iqsms-notifier`` ``iqsms://LOGIN:PASSWORD@default?from=FROM`` -`KazInfoTeh`_ ``symfony/kaz-info-teh-notifier`` ``kaz-info-teh://USERNAME:PASSWORD@default?sender=FROM`` -`LightSms`_ ``symfony/light-sms-notifier`` ``lightsms://LOGIN:TOKEN@default?from=PHONE`` -`Mailjet`_ ``symfony/mailjet-notifier`` ``mailjet://TOKEN@default?from=FROM`` -`MessageBird`_ ``symfony/message-bird-notifier`` ``messagebird://TOKEN@default?from=FROM`` -`MessageMedia`_ ``symfony/message-media-notifier`` ``messagemedia://API_KEY:API_SECRET@default?from=FROM`` -`Mobyt`_ ``symfony/mobyt-notifier`` ``mobyt://USER_KEY:ACCESS_TOKEN@default?from=FROM`` -`Nexmo`_ ``symfony/nexmo-notifier`` Abandoned in favor of Vonage (symfony/vonage-notifier). -`Octopush`_ ``symfony/octopush-notifier`` ``octopush://USERLOGIN:APIKEY@default?from=FROM&type=TYPE`` -`OrangeSms`_ ``symfony/orange-sms-notifier`` ``orange-sms://CLIENT_ID:CLIENT_SECRET@default?from=FROM&sender_name=SENDER_NAME`` -`OvhCloud`_ ``symfony/ovh-cloud-notifier`` ``ovhcloud://APPLICATION_KEY:APPLICATION_SECRET@default?consumer_key=CONSUMER_KEY&service_name=SERVICE_NAME`` -`Plivo`_ ``symfony/plivo-notifier`` ``plivo://AUTH_ID:AUTH_TOKEN@default?from=FROM`` -`Redlink`_ ``symfony/redlink-notifier`` ``redlink://API_KEY:APP_KEY@default?from=SENDER_NAME&version=API_VERSION`` -`RingCentral`_ ``symfony/ring-central-notifier`` ``ringcentral://API_TOKEN@default?from=FROM`` -`SMSFactor`_ ``symfony/sms-factor-notifier`` ``sms-factor://TOKEN@default?sender=SENDER&push_type=PUSH_TYPE`` -`Sendberry`_ ``symfony/sendberry-notifier`` ``sendberry://USERNAME:PASSWORD@default?auth_key=AUTH_KEY&from=FROM`` -`Seven.io`_ ``symfony/sevenio-notifier`` ``sevenio://API_KEY@default?from=FROM`` -`SimpleTextin`_ ``symfony/simple-textin-notifier`` ``simpletextin://API_KEY@default?from=FROM`` -`Sinch`_ ``symfony/sinch-notifier`` ``sinch://ACCOUNT_ID:AUTH_TOKEN@default?from=FROM`` -`Sms77`_ ``symfony/sms77-notifier`` ``sms77://API_KEY@default?from=FROM`` -`SmsBiuras`_ ``symfony/sms-biuras-notifier`` ``smsbiuras://UID:API_KEY@default?from=FROM&test_mode=0`` -`SmsSluzba`_ ``symfony/sms-sluzba-notifier`` ``sms-sluzba://USERNAME:PASSWORD@default`` -`Smsapi`_ ``symfony/smsapi-notifier`` ``smsapi://TOKEN@default?from=FROM`` -`Smsbox`_ ``symfony/smsbox-notifier`` ``smsbox://APIKEY@default?mode=MODE&strategy=STRATEGY&sender=SENDER`` -`Smsc`_ ``symfony/smsc-notifier`` ``smsc://LOGIN:PASSWORD@default?from=FROM`` -`SMSense`_ ``symfony/smsense-notifier`` ``smsense://API_TOKEN@default?from=FROM`` -`SpotHit`_ ``symfony/spot-hit-notifier`` ``spothit://TOKEN@default?from=FROM`` -`Telnyx`_ ``symfony/telnyx-notifier`` ``telnyx://API_KEY@default?from=FROM&messaging_profile_id=MESSAGING_PROFILE_ID`` -`TurboSms`_ ``symfony/turbo-sms-notifier`` ``turbosms://AUTH_TOKEN@default?from=FROM`` -`Twilio`_ ``symfony/twilio-notifier`` ``twilio://SID:TOKEN@default?from=FROM`` yes -`Unifonic`_ ``symfony/unifonic-notifier`` ``unifonic://APP_SID@default?from=FROM`` -`Vonage`_ ``symfony/vonage-notifier`` ``vonage://KEY:SECRET@default?from=FROM`` yes -`Yunpian`_ ``symfony/yunpian-notifier`` ``yunpian://APIKEY@default`` -`iSendPro`_ ``symfony/isendpro-notifier`` ``isendpro://ACCOUNT_KEY_ID@default?from=FROM&no_stop=NO_STOP&sandbox=SANDBOX`` -================== ===================================== ========================================================================================================================= =============== +.. tip:: + + Use :doc:`Symfony configuration secrets ` to securely + store your API tokens. .. tip:: @@ -115,7 +213,8 @@ Service Package DSN .. versionadded:: 7.1 - The `SmsSluzba`_ and `SMSense`_ integrations were introduced in Symfony 7.1. + The ``Smsbox``, ``SmsSluzba``, ``SMSense``, ``LOX24`` and ``Unifonic`` + integrations were introduced in Symfony 7.1. .. deprecated:: 7.1 @@ -220,7 +319,7 @@ information such as the message ID and the original message contents. Chat Channel ~~~~~~~~~~~~ -.. caution:: +.. warning:: If any of the DSN values contains any character considered special in a URI (such as ``: / ? # [ ] @ ! $ & ' ( ) * + , ; =``), you must @@ -258,10 +357,9 @@ Service Package D .. versionadded:: 7.1 - The ``Bluesky``, ``Unifonic`` and ``Smsbox`` integrations - were introduced in Symfony 7.1. + The ``Bluesky`` integration was introduced in Symfony 7.1. -.. caution:: +.. warning:: By default, if you have the :doc:`Messenger component ` installed, the notifications will be sent through the MessageBus. If you don't have a @@ -431,7 +529,7 @@ notification emails: Push Channel ~~~~~~~~~~~~ -.. caution:: +.. warning:: If any of the DSN values contains any character considered special in a URI (such as ``: / ? # [ ] @ ! $ & ' ( ) * + , ; =``), you must @@ -795,7 +893,7 @@ and its ``asChatMessage()`` method:: ) { } - public function asChatMessage(RecipientInterface $recipient, string $transport = null): ?ChatMessage + public function asChatMessage(RecipientInterface $recipient, ?string $transport = null): ?ChatMessage { // Add a custom subject and emoji if the message is sent to Slack if ('slack' === $transport) { @@ -826,7 +924,7 @@ The default behavior for browser channel notifications is to add a However, you might prefer to map the importance level of the notification to the type of flash message, so you can tweak their style. -you can do that by overriding the default ``notifier.flash_message_importance_mapper`` +You can do that by overriding the default ``notifier.flash_message_importance_mapper`` service with your own implementation of :class:`Symfony\\Component\\Notifier\\FlashMessage\\FlashMessageImportanceMapperInterface` where you can provide your own "importance" to "alert level" mapping. @@ -966,7 +1064,7 @@ is dispatched. Listeners receive a $dispatcher->addListener(SentMessageEvent::class, function (SentMessageEvent $event): void { // gets the message instance - $message = $event->getOriginalMessage(); + $message = $event->getMessage(); // log something $this->logger(sprintf('The message has been successfully sent and has id: %s', $message->getMessageId())); @@ -996,7 +1094,7 @@ is dispatched. Listeners receive a .. _`FreeMobile`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/FreeMobile/README.md .. _`GatewayApi`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/GatewayApi/README.md .. _`Gitter`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Gitter/README.md -.. _`GoIP`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/GoIP/README.md +.. _`GoIP`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/GoIp/README.md .. _`GoogleChat`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/GoogleChat/README.md .. _`Infobip`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Infobip/README.md .. _`Iqsms`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Iqsms/README.md @@ -1005,6 +1103,7 @@ is dispatched. Listeners receive a .. _`LINE Notify`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/LineNotify/README.md .. _`LightSms`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/LightSms/README.md .. _`LinkedIn`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/LinkedIn/README.md +.. _`LOX24`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Lox24/README.md .. _`Mailjet`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Mailjet/README.md .. _`Mastodon`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Mastodon/README.md .. _`Mattermost`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Mattermost/README.md @@ -1030,6 +1129,7 @@ is dispatched. Listeners receive a .. _`RocketChat`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/RocketChat/README.md .. _`SMSFactor`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/SmsFactor/README.md .. _`Sendberry`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Sendberry/README.md +.. _`Sendinblue`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Sendinblue/README.md .. _`Seven.io`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Sevenio/README.md .. _`SimpleTextin`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/SimpleTextin/README.md .. _`Sinch`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Notifier/Bridge/Sinch/README.md diff --git a/performance.rst b/performance.rst index ca34e95ee37..cd9dacddb1a 100644 --- a/performance.rst +++ b/performance.rst @@ -98,7 +98,7 @@ Use the OPcache Byte Code Cache OPcache stores the compiled PHP files to avoid having to recompile them for every request. There are some `byte code caches`_ available, but as of PHP 5.5, PHP comes with `OPcache`_ built-in. For older versions, the most widely -used byte code cache is `APC`_. +used byte code cache is APC. .. _performance-use-preloading: @@ -394,7 +394,6 @@ Learn more .. _`byte code caches`: https://en.wikipedia.org/wiki/List_of_PHP_accelerators .. _`OPcache`: https://www.php.net/manual/en/book.opcache.php .. _`Composer's autoloader optimization`: https://getcomposer.org/doc/articles/autoloader-optimization.md -.. _`APC`: https://www.php.net/manual/en/book.apc.php .. _`APCu Polyfill component`: https://github.com/symfony/polyfill-apcu .. _`APCu PHP functions`: https://www.php.net/manual/en/ref.apcu.php .. _`cachetool`: https://github.com/gordalina/cachetool diff --git a/profiler.rst b/profiler.rst index df9cefa4f96..57d412472ba 100644 --- a/profiler.rst +++ b/profiler.rst @@ -54,6 +54,12 @@ method to access to its associated profile:: // ... $profiler is the 'profiler' service $profile = $profiler->loadProfileFromResponse($response); +.. note:: + + The ``profiler`` service will be :doc:`autowired ` + automatically when type-hinting any service argument with the + :class:`Symfony\\Component\\HttpKernel\\Profiler\\Profiler` class. + When the profiler stores data about a request, it also associates a token with it; this token is available in the ``X-Debug-Token`` HTTP header of the response. Using this token, you can access the profile of any past response thanks to the @@ -281,7 +287,7 @@ request:: class RequestCollector extends AbstractDataCollector { - public function collect(Request $request, Response $response, \Throwable $exception = null): void + public function collect(Request $request, Response $response, ?\Throwable $exception = null): void { $this->data = [ 'method' => $request->getMethod(), @@ -297,13 +303,13 @@ These are the method that you can define in the data collector class: from ``AbstractDataCollector``). If you need some services to collect the data, inject those services in the data collector constructor. - .. caution:: + .. warning:: The ``collect()`` method is only called once. It is not used to "gather" data but is there to "pick up" the data that has been stored by your service. - .. caution:: + .. warning:: As the profiler serializes data collector instances, you should not store objects that cannot be serialized (like PDO objects) or you need diff --git a/quick_tour/the_big_picture.rst b/quick_tour/the_big_picture.rst index 34bea4f82cc..b069cb4f716 100644 --- a/quick_tour/the_big_picture.rst +++ b/quick_tour/the_big_picture.rst @@ -39,7 +39,7 @@ Symfony application: ├─ var/ └─ vendor/ -Can we already load the project in a browser? Yes! You can setup +Can we already load the project in a browser? Yes! You can set up :doc:`Nginx or Apache ` and configure their document root to be the ``public/`` directory. But, for development, it's better to :doc:`install the Symfony local web server ` and run @@ -63,20 +63,6 @@ web app, or a microservice. Symfony starts small, but scales with you. But before we go too far, let's dig into the fundamentals by building our first page. -Start in ``config/routes.yaml``: this is where *we* can define the URL to our new -page. Uncomment the example that already lives in the file: - -.. code-block:: yaml - - # config/routes.yaml - index: - path: / - controller: 'App\Controller\DefaultController::index' - -This is called a *route*: it defines the URL to your page (``/``) and the "controller": -the *function* that will be called whenever anyone goes to this URL. That function -doesn't exist yet, so let's create it! - In ``src/Controller``, create a new ``DefaultController`` class and an ``index`` method inside:: @@ -84,9 +70,11 @@ method inside:: namespace App\Controller; use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\Routing\Attribute\Route; class DefaultController { + #[Route('/', name: 'index')] public function index(): Response { return new Response('Hello!'); @@ -104,11 +92,21 @@ But the routing system is *much* more powerful. So let's make the route more int .. code-block:: diff - # config/routes.yaml - index: - - path: / - + path: /hello/{name} - controller: 'App\Controller\DefaultController::index' + // src/Controller/DefaultController.php + namespace App\Controller; + + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\Routing\Attribute\Route; + + class DefaultController + { + - #[Route('/', name: 'index')] + + #[Route('/hello/{name}', name: 'index')] + public function index(): Response + { + return new Response('Hello!'); + } + } The URL to this page has changed: it is *now* ``/hello/*``: the ``{name}`` acts like a wildcard that matches anything. And it gets better! Update the controller too: @@ -120,9 +118,11 @@ like a wildcard that matches anything. And it gets better! Update the controller namespace App\Controller; use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\Routing\Attribute\Route; class DefaultController { + #[Route('/hello/{name}', name: 'index')] - public function index() + public function index(string $name): Response { @@ -135,39 +135,8 @@ Try the page out by going to ``http://localhost:8000/hello/Symfony``. You should see: Hello Symfony! The value of the ``{name}`` in the URL is available as a ``$name`` argument in your controller. -But this can be even simpler! Comment-out the YAML route by adding the -``#`` character: - -.. code-block:: yaml - - # config/routes.yaml - # index: - # path: /hello/{name} - # controller: 'App\Controller\DefaultController::index' - -Instead, add the route *right above* the controller method: - -.. code-block:: diff - - ` via the .. _`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 .. _`NGINX rate limiting`: https://www.nginx.com/blog/rate-limiting-nginx/ +.. _`Caddy HTTP rate limit module`: https://github.com/mholt/caddy-ratelimit .. _`token bucket algorithm`: https://en.wikipedia.org/wiki/Token_bucket .. _`PHP date relative formats`: https://www.php.net/manual/en/datetime.formats.php#datetime.formats.relative .. _`Race conditions`: https://en.wikipedia.org/wiki/Race_condition diff --git a/reference/attributes.rst b/reference/attributes.rst index 015b8751834..7975a8aaee4 100644 --- a/reference/attributes.rst +++ b/reference/attributes.rst @@ -33,8 +33,9 @@ Dependency Injection * :ref:`Autowire ` * :ref:`AutowireCallable ` * :doc:`AutowireDecorated ` -* :doc:`AutowireIterator ` +* :ref:`AutowireIterator ` * :ref:`AutowireLocator ` +* :ref:`AutowireMethodOf ` * :ref:`AutowireServiceClosure ` * :ref:`Exclude ` * :ref:`Lazy ` @@ -43,6 +44,12 @@ Dependency Injection * :ref:`Target ` * :ref:`When ` +.. deprecated:: 7.1 + + The :class:`Symfony\\Component\\DependencyInjection\\Attribute\\TaggedIterator` + and :class:`Symfony\\Component\\DependencyInjection\\Attribute\\TaggedLocator` + attributes were deprecated in Symfony 7.1. + EventDispatcher ~~~~~~~~~~~~~~~ @@ -63,6 +70,7 @@ HttpKernel * :ref:`MapQueryParameter ` * :ref:`MapQueryString ` * :ref:`MapRequestPayload ` +* :ref:`MapUploadedFile ` * :ref:`ValueResolver ` * :ref:`WithHttpStatus ` * :ref:`WithLogLevel ` @@ -96,16 +104,18 @@ Security * :ref:`IsCsrfTokenValid ` * :ref:`IsGranted ` +.. _reference-attributes-serializer: + Serializer ~~~~~~~~~~ -* :ref:`Context ` +* :ref:`Context ` * :ref:`DiscriminatorMap ` -* :ref:`Groups ` +* :ref:`Groups ` * :ref:`Ignore ` * :ref:`MaxDepth ` -* :ref:`SerializedName ` -* :ref:`SerializedPath ` +* :ref:`SerializedName ` +* :ref:`SerializedPath ` Twig ~~~~ diff --git a/reference/configuration/debug.rst b/reference/configuration/debug.rst index 62cc40675b2..9b8dc2a6f0c 100644 --- a/reference/configuration/debug.rst +++ b/reference/configuration/debug.rst @@ -91,8 +91,13 @@ Typically, you would set this to ``php://stderr``: .. code-block:: php // config/packages/debug.php - $container->loadFromExtension('debug', [ - 'dump_destination' => 'php://stderr', - ]); + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + return static function (ContainerConfigurator $container): void { + $container->extension('debug', [ + 'dump_destination' => 'php://stderr', + ]); + }; + Configure it to ``"tcp://%env(VAR_DUMPER_SERVER)%"`` in order to use the :ref:`ServerDumper feature `. diff --git a/reference/configuration/doctrine.rst b/reference/configuration/doctrine.rst index f131181d3ac..272ad6b6804 100644 --- a/reference/configuration/doctrine.rst +++ b/reference/configuration/doctrine.rst @@ -57,7 +57,7 @@ The following block shows all possible configuration keys: charset: utf8mb4 logging: '%kernel.debug%' platform_service: App\DBAL\MyDatabasePlatformService - server_version: '5.7' + server_version: '8.0.37' mapping_types: enum: string types: @@ -91,7 +91,7 @@ The following block shows all possible configuration keys: charset="utf8mb4" logging="%kernel.debug%" platform-service="App\DBAL\MyDatabasePlatformService" - server-version="5.7"> + server-version="8.0.37"> bar string @@ -136,13 +136,13 @@ If you want to configure multiple connections in YAML, put them under the user: root password: null host: localhost - server_version: '5.6' + server_version: '8.0.37' customer: dbname: customer user: root password: null host: localhost - server_version: '5.7' + server_version: '8.2.0' The ``database_connection`` service always refers to the *default* connection, which is the first one defined or the one configured via the @@ -271,9 +271,13 @@ you can control. The following configuration options exist for a mapping: ........ One of ``attribute`` (for PHP attributes; it's the default value), -``xml``, ``yml``, ``php`` or ``staticphp``. This specifies which +``xml``, ``php`` or ``staticphp``. This specifies which type of metadata type your mapping uses. +.. versionadded:: 3.0 + + The ``yml`` mapping configuration is deprecated and was removed in Doctrine ORM 3.0. + See `Doctrine Metadata Drivers`_ for more information about this option. ``dir`` @@ -462,5 +466,84 @@ If the ``dir`` configuration is set and the ``is_bundle`` configuration is ``true``, the DoctrineBundle will prefix the ``dir`` configuration with the path of the bundle. +SSL Connection with MySQL +~~~~~~~~~~~~~~~~~~~~~~~~~ + +To securely configure an SSL connection to MySQL in your Symfony application +with Doctrine, you need to specify the SSL certificate options. Here's how to +set up the connection using environment variables for the certificate paths: + +.. configuration-block:: + + .. code-block:: yaml + + doctrine: + dbal: + url: '%env(DATABASE_URL)%' + server_version: '8.0.31' + driver: 'pdo_mysql' + options: + # SSL private key + !php/const 'PDO::MYSQL_ATTR_SSL_KEY': '%env(MYSQL_SSL_KEY)%' + # SSL certificate + !php/const 'PDO::MYSQL_ATTR_SSL_CERT': '%env(MYSQL_SSL_CERT)%' + # SSL CA authority + !php/const 'PDO::MYSQL_ATTR_SSL_CA': '%env(MYSQL_SSL_CA)%' + + .. code-block:: xml + + + + + + + + %env(MYSQL_SSL_KEY)% + %env(MYSQL_SSL_CERT)% + %env(MYSQL_SSL_CA)% + + + + + .. code-block:: php + + // config/packages/doctrine.php + use Symfony\Config\DoctrineConfig; + + return static function (DoctrineConfig $doctrine): void { + $doctrine->dbal() + ->connection('default') + ->url(env('DATABASE_URL')->resolve()) + ->serverVersion('8.0.31') + ->driver('pdo_mysql'); + + $doctrine->dbal()->defaultConnection('default'); + + $doctrine->dbal()->option(\PDO::MYSQL_ATTR_SSL_KEY, '%env(MYSQL_SSL_KEY)%'); + $doctrine->dbal()->option(\PDO::MYSQL_SSL_CERT, '%env(MYSQL_ATTR_SSL_CERT)%'); + $doctrine->dbal()->option(\PDO::MYSQL_SSL_CA, '%env(MYSQL_ATTR_SSL_CA)%'); + }; + +Ensure your environment variables are correctly set in the ``.env.local`` or +``.env.local.php`` file as follows: + +.. code-block:: bash + + MYSQL_SSL_KEY=/path/to/your/server-key.pem + MYSQL_SSL_CERT=/path/to/your/server-cert.pem + MYSQL_SSL_CA=/path/to/your/ca-cert.pem + +This configuration secures your MySQL connection with SSL by specifying the paths to the required certificates. + + .. _DBAL documentation: https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/configuration.html .. _`Doctrine Metadata Drivers`: https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/metadata-drivers.html diff --git a/reference/configuration/framework.rst b/reference/configuration/framework.rst index 02c2be559ee..d4ff35a6381 100644 --- a/reference/configuration/framework.rst +++ b/reference/configuration/framework.rst @@ -175,7 +175,7 @@ named ``kernel.http_method_override``. :ref:`Changing the Action and HTTP Method ` of Symfony forms. -.. caution:: +.. warning:: If you're using the :ref:`HttpCache Reverse Proxy ` with this option, the kernel will ignore the ``_method`` parameter, @@ -193,8 +193,6 @@ named ``kernel.http_method_override``. $request = Request::createFromGlobals(); // ... -.. _configuration-framework-http_method_override: - trust_x_sendfile_type_header ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -409,9 +407,11 @@ performance a bit: $framework->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 -the fallback locale. +An added bonus of defining the enabled locales is that they are automatically +added as a requirement of the :ref:`special _locale parameter `. +For example, if you define this value as ``['ar', 'he', 'ja', 'zh']``, the +``_locale`` routing parameter will have an ``ar|he|ja|zh`` requirement. If some +user makes requests with a locale not included in this option, they'll see a 404 error. set_content_language_from_locale ................................ @@ -860,37 +860,6 @@ If you use for example as the type and name of an argument, autowiring will inject the ``my_api.client`` service into your autowired classes. -.. _reference-http-client-retry-failed: - -By enabling the optional ``retry_failed`` configuration, the HTTP client service -will automatically retry failed HTTP requests. - -.. code-block:: yaml - - # config/packages/framework.yaml - framework: - # ... - http_client: - # ... - default_options: - retry_failed: - # retry_strategy: app.custom_strategy - http_codes: - 0: ['GET', 'HEAD'] # retry network errors if request method is GET or HEAD - 429: true # retry all responses with 429 status code - 500: ['GET', 'HEAD'] - max_retries: 2 - delay: 1000 - multiplier: 3 - max_delay: 5000 - jitter: 0.3 - - scoped_clients: - my_api.client: - # ... - retry_failed: - max_retries: 4 - auth_basic .......... @@ -1001,6 +970,8 @@ crypto_method The minimum version of TLS to accept. The value must be one of the ``STREAM_CRYPTO_METHOD_TLSv*_CLIENT`` constants defined by PHP. +.. _reference-http-client-retry-delay: + delay ..... @@ -1036,6 +1007,8 @@ headers An associative array of the HTTP headers added before making the request. This value must use the format ``['header-name' => 'value0, value1, ...']``. +.. _reference-http-client-retry-http-codes: + http_codes .......... @@ -1051,6 +1024,8 @@ http_version The HTTP version to use, typically ``'1.1'`` or ``'2.0'``. Leave it to ``null`` to let Symfony select the best version automatically. +.. _reference-http-client-retry-jitter: + jitter ...... @@ -1078,6 +1053,8 @@ local_pk The path of a file that contains the `PEM formatted`_ private key of the certificate defined in the ``local_cert`` option. +.. _reference-http-client-retry-max-delay: + max_delay ......... @@ -1112,6 +1089,8 @@ max_redirects The maximum number of redirects to follow. Use ``0`` to not follow any redirection. +.. _reference-http-client-retry-max-retries: + max_retries ........... @@ -1120,6 +1099,8 @@ max_retries The maximum number of retries for failing requests. When the maximum is reached, the client returns the last received response. +.. _reference-http-client-retry-multiplier: + multiplier .......... @@ -1200,6 +1181,50 @@ client and to make your tests easier. The value of this option is an associative array of ``domain => IP address`` (e.g ``['symfony.com' => '46.137.106.254', ...]``). +.. _reference-http-client-retry-failed: + +retry_failed +............ + +**type**: ``array`` + +This option configures the behavior of the HTTP client when some request fails, +including which types of requests to retry and how many times. The behavior is +defined with the following options: + +* :ref:`delay ` +* :ref:`http_codes ` +* :ref:`jitter ` +* :ref:`max_delay ` +* :ref:`max_retries ` +* :ref:`multiplier ` + +.. code-block:: yaml + + # config/packages/framework.yaml + framework: + # ... + http_client: + # ... + default_options: + retry_failed: + # retry_strategy: app.custom_strategy + http_codes: + 0: ['GET', 'HEAD'] # retry network errors if request method is GET or HEAD + 429: true # retry all responses with 429 status code + 500: ['GET', 'HEAD'] + max_retries: 2 + delay: 1000 + multiplier: 3 + max_delay: 5000 + jitter: 0.3 + + scoped_clients: + my_api.client: + # ... + retry_failed: + max_retries: 4 + retry_strategy .............. @@ -1804,7 +1829,7 @@ cookie_httponly This determines whether cookies should only be accessible through the HTTP protocol. This means that the cookie won't be accessible by scripting languages, such as JavaScript. This setting can effectively help to reduce -identity theft through XSS attacks. +identity theft through :ref:`XSS attacks `. gc_divisor .......... @@ -2886,11 +2911,11 @@ enable_attributes **type**: ``boolean`` **default**: ``true`` -If this option is enabled, serialization groups can be defined using `PHP attributes`_. +Enables support for `PHP attributes`_ in the serializer component. .. seealso:: - For more information, see :ref:`serializer-using-serialization-groups-attributes`. + See :ref:`the reference ` for a list of supported annotations. .. _reference-serializer-name_converter: @@ -2906,8 +2931,7 @@ value. .. seealso:: - For more information, see - :ref:`component-serializer-converting-property-names-when-serializing-and-deserializing`. + For more information, see :ref:`serializer-name-conversion`. .. _reference-serializer-circular_reference_handler: @@ -3204,7 +3228,7 @@ settings from the base pool as defaults. .. note:: - Your service MUST implement the ``Psr\Cache\CacheItemPoolInterface`` interface. + Your service needs to implement the ``Psr\Cache\CacheItemPoolInterface`` interface. public """""" diff --git a/reference/configuration/kernel.rst b/reference/configuration/kernel.rst index 813cd41223c..b7596182906 100644 --- a/reference/configuration/kernel.rst +++ b/reference/configuration/kernel.rst @@ -256,7 +256,7 @@ method of the kernel class, which you can override to return a different value. ``kernel.project_dir`` ---------------------- -**type**: ``string`` **default**: the directory of the project ``composer.json`` +**type**: ``string`` **default**: the directory of the project's ``composer.json`` This parameter stores the absolute path of the root directory of your Symfony application, which is used by applications to perform operations with file paths relative to @@ -283,6 +283,8 @@ have deleted it entirely (for example in the production servers), override the public function getProjectDir(): string { + // when defining a hardcoded string, don't add the trailing slash to the path + // e.g. '/home/user/my_project', '/app', '/var/www/example.com' return \dirname(__DIR__); } } diff --git a/reference/configuration/security.rst b/reference/configuration/security.rst index 590f2472425..757dc7313cd 100644 --- a/reference/configuration/security.rst +++ b/reference/configuration/security.rst @@ -330,10 +330,10 @@ form_only **type**: ``boolean`` **default**: ``false`` Set this option to ``true`` to require that the login data is sent using a form -(it checks that the request content-type is ``application/x-www-form-urlencoded``). -This is useful for example to prevent the :ref:`form login authenticator ` -from responding to requests that should be handled by the -:ref:`JSON login authenticator `. +(it checks that the request content-type is ``application/x-www-form-urlencoded`` +or ``multipart/form-data``). This is useful for example to prevent the +:ref:`form login authenticator ` from responding to +requests that should be handled by the :ref:`JSON login authenticator `. use_forward ........... @@ -1063,9 +1063,6 @@ the session must not be used when authenticating users: // ... }; -Routes under this firewall will be :ref:`configured stateless ` -when they are not explicitly configured stateless or not. - User Checkers ~~~~~~~~~~~~~ diff --git a/reference/configuration/twig.rst b/reference/configuration/twig.rst index 883b0b11eb7..596d70d8a2b 100644 --- a/reference/configuration/twig.rst +++ b/reference/configuration/twig.rst @@ -38,8 +38,8 @@ autoescape_service **type**: ``string`` **default**: ``null`` -The escaping strategy applied by default to the template is determined during -compilation time based on the filename of the template. This means for example +The escaping strategy applied by default to the template (to prevent :ref:`XSS attacks `) +is determined during compilation time based on the filename of the template. This means for example that the contents of a ``*.html.twig`` template are escaped for HTML and the contents of ``*.js.twig`` are escaped for JavaScript. @@ -62,6 +62,10 @@ base_template_class **type**: ``string`` **default**: ``Twig\Template`` +.. deprecated:: 7.1 + + The ``base_template_class`` option is deprecated since Symfony 7.1. + Twig templates are compiled into PHP classes before using them to render contents. This option defines the base class from which all the template classes extend. Using a custom base template is discouraged because it will make your diff --git a/reference/configuration/web_profiler.rst b/reference/configuration/web_profiler.rst index f0b11f47064..93c65621999 100644 --- a/reference/configuration/web_profiler.rst +++ b/reference/configuration/web_profiler.rst @@ -20,7 +20,7 @@ under the ``web_profiler`` key in your application configuration. namespace and the related XSD schema is available at: ``https://symfony.com/schema/dic/webprofiler/webprofiler-1.0.xsd`` -.. caution:: +.. warning:: The web debug toolbar is not available for responses of type ``StreamedResponse``. diff --git a/reference/constraints/Callback.rst b/reference/constraints/Callback.rst index 3424d47c9d3..f4c78a9642a 100644 --- a/reference/constraints/Callback.rst +++ b/reference/constraints/Callback.rst @@ -245,7 +245,7 @@ constructor of the Callback constraint:: } } -.. caution:: +.. warning:: Using a ``Closure`` together with attribute configuration will disable the attribute cache for that class/property/method because ``Closure`` cannot @@ -271,14 +271,16 @@ callback method: * A closure. Concrete callbacks receive an :class:`Symfony\\Component\\Validator\\Context\\ExecutionContextInterface` -instance as the first argument and the :ref:`payload option ` +instance as the first argument and the :ref:`payload option ` as the second argument. Static or closure callbacks receive the validated object as the first argument, the :class:`Symfony\\Component\\Validator\\Context\\ExecutionContextInterface` -instance as the second argument and the :ref:`payload option ` +instance as the second argument and the :ref:`payload option ` as the third argument. .. include:: /reference/constraints/_groups-option.rst.inc +.. _reference-constraints-callback-payload: + .. include:: /reference/constraints/_payload-option.rst.inc diff --git a/reference/constraints/Charset.rst b/reference/constraints/Charset.rst index 4f1a260356f..084f24cdf76 100644 --- a/reference/constraints/Charset.rst +++ b/reference/constraints/Charset.rst @@ -88,8 +88,8 @@ Options An encoding or a set of encodings to check against. If you pass an array of encodings, the validator will check if the value is encoded in *any* of the -encodings. This option accepts any value that can be passed to -:phpfunction:`mb_detect_encoding`. +encodings. This option accepts any value that can be passed to the +:phpfunction:`mb_detect_encoding` PHP function. .. include:: /reference/constraints/_groups-option.rst.inc diff --git a/reference/constraints/Choice.rst b/reference/constraints/Choice.rst index 8bafaaede7b..5a9c365be37 100644 --- a/reference/constraints/Choice.rst +++ b/reference/constraints/Choice.rst @@ -389,29 +389,3 @@ Parameter Description =============== ============================================================== .. include:: /reference/constraints/_payload-option.rst.inc - -``separator`` -~~~~~~~~~~~~~ - -**type**: ``string`` **default**: ``-------------------`` - -This option allows you to customize the visual separator shown after the preferred -choices. You can use HTML elements like ``
`` to display a more modern separator, -but you'll also need to set the `separator_html`_ option to ``true``. - -.. versionadded:: 7.1 - - The ``separator`` option was introduced in Symfony 7.1. - -``separator_html`` -~~~~~~~~~~~~~~~~~~ - -**type**: ``boolean`` **default**: ``false`` - -If this option is true, the `separator`_ option will be displayed as HTML instead -of text. This is useful when using HTML elements (e.g. ``
``) as a more modern -visual separator. - -.. versionadded:: 7.1 - - The ``separator_html`` option was introduced in Symfony 7.1. diff --git a/reference/constraints/Cidr.rst b/reference/constraints/Cidr.rst index 24abeb57338..78a5b6c7167 100644 --- a/reference/constraints/Cidr.rst +++ b/reference/constraints/Cidr.rst @@ -95,7 +95,7 @@ It's a constraint for the lowest value a valid netmask may have. ``netmaskMax`` ~~~~~~~~~~~~~~ -**type**: ``string`` **default**: ``32`` for IPv4 or ``128`` for IPv6 +**type**: ``integer`` **default**: ``32`` for IPv4 or ``128`` for IPv6 It's a constraint for the biggest value a valid netmask may have. @@ -126,6 +126,14 @@ Parameter Description This determines exactly *how* the CIDR notation is validated and can take one of :ref:`IP version ranges `. +.. note:: + + The IP range checks (e.g., ``*_private``, ``*_reserved``) validate only the + IP address, not the entire netmask. To improve validation, you can set the + ``{{ min }}`` value for the netmask. For example, the range ``9.0.0.0/6`` is + considered ``*_public``, but it also includes the ``10.0.0.0/8`` range, which + is categorized as ``*_private``. + .. versionadded:: 7.1 The support of all IP version ranges was introduced in Symfony 7.1. diff --git a/reference/constraints/EqualTo.rst b/reference/constraints/EqualTo.rst index d2f151adea8..d5d78f60a0f 100644 --- a/reference/constraints/EqualTo.rst +++ b/reference/constraints/EqualTo.rst @@ -4,7 +4,7 @@ EqualTo Validates that a value is equal to another value, defined in the options. To force that a value is *not* equal, see :doc:`/reference/constraints/NotEqualTo`. -.. caution:: +.. warning:: This constraint compares using ``==``, so ``3`` and ``"3"`` are considered equal. Use :doc:`/reference/constraints/IdenticalTo` to compare with diff --git a/reference/constraints/File.rst b/reference/constraints/File.rst index 13aec13b0d5..6d9b72d17b8 100644 --- a/reference/constraints/File.rst +++ b/reference/constraints/File.rst @@ -40,7 +40,7 @@ type. The ``Author`` class might look as follows:: { protected File $bioFile; - public function setBioFile(File $file = null): void + public function setBioFile(?File $file = null): void { $this->bioFile = $file; } @@ -242,7 +242,7 @@ Parameter Description **type**: ``array`` or ``string`` -.. caution:: +.. warning:: You should always use the ``extensions`` option instead of ``mimeTypes`` except if you explicitly don't want to check that the extension of the file @@ -298,7 +298,12 @@ Parameter Description The message displayed if the extension of the file is not a valid extension per the `extensions`_ option. -.. include:: /reference/constraints/_parameters-mime-types-message-option.rst.inc +==================== ============================================================== +Parameter Description +==================== ============================================================== +``{{ extension }}`` The extension of the given file +``{{ extensions }}`` The list of allowed file extensions +==================== ============================================================== ``mimeTypesMessage`` ~~~~~~~~~~~~~~~~~~~~ diff --git a/reference/constraints/IdenticalTo.rst b/reference/constraints/IdenticalTo.rst index 507493b63d4..5b6d853dc0b 100644 --- a/reference/constraints/IdenticalTo.rst +++ b/reference/constraints/IdenticalTo.rst @@ -5,7 +5,7 @@ Validates that a value is identical to another value, defined in the options. To force that a value is *not* identical, see :doc:`/reference/constraints/NotIdenticalTo`. -.. caution:: +.. warning:: This constraint compares using ``===``, so ``3`` and ``"3"`` are *not* considered equal. Use :doc:`/reference/constraints/EqualTo` to compare diff --git a/reference/constraints/Image.rst b/reference/constraints/Image.rst index 22a7bc1a688..042c6041423 100644 --- a/reference/constraints/Image.rst +++ b/reference/constraints/Image.rst @@ -35,7 +35,7 @@ would be a ``file`` type. The ``Author`` class might look as follows:: { protected File $headshot; - public function setHeadshot(File $file = null): void + public function setHeadshot(?File $file = null): void { $this->headshot = $file; } @@ -210,6 +210,11 @@ add several other options. If this option is false, the image cannot be landscape oriented. +.. note:: + + This option does not apply to SVG files. If you use it with SVG files, + you'll see the error message defined in the ``sizeNotDetectedMessage`` option. + ``allowLandscapeMessage`` ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -234,6 +239,11 @@ Parameter Description If this option is false, the image cannot be portrait oriented. +.. note:: + + This option does not apply to SVG files. If you use it with SVG files, + you'll see the error message defined in the ``sizeNotDetectedMessage`` option. + ``allowPortraitMessage`` ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -260,6 +270,11 @@ If this option is false, the image cannot be a square. If you want to force a square image, then leave this option as its default ``true`` value and set `allowLandscape`_ and `allowPortrait`_ both to ``false``. +.. note:: + + This option does not apply to SVG files. If you use it with SVG files, + you'll see the error message defined in the ``sizeNotDetectedMessage`` option. + ``allowSquareMessage`` ~~~~~~~~~~~~~~~~~~~~~~ @@ -358,6 +373,11 @@ Parameter Description If set, the aspect ratio (``width / height``) of the image file must be less than or equal to this value. +.. note:: + + This option does not apply to SVG files. If you use it with SVG files, + you'll see the error message defined in the ``sizeNotDetectedMessage`` option. + ``maxRatioMessage`` ~~~~~~~~~~~~~~~~~~~ @@ -477,6 +497,11 @@ Parameter Description If set, the aspect ratio (``width / height``) of the image file must be greater than or equal to this value. +.. note:: + + This option does not apply to SVG files. If you use it with SVG files, + you'll see the error message defined in the ``sizeNotDetectedMessage`` option. + ``minRatioMessage`` ~~~~~~~~~~~~~~~~~~~ @@ -530,5 +555,11 @@ options has been set. This message has no parameters. +.. note:: + + Detecting the size of SVG images is not supported. This error message will + be displayed if you use any of the following options: ``allowLandscape``, + ``allowPortrait``, ``allowSquare``, ``maxRatio``, and ``minRatio``. + .. _`IANA website`: https://www.iana.org/assignments/media-types/media-types.xhtml .. _`PHP GD extension`: https://www.php.net/manual/en/book.image.php diff --git a/reference/constraints/MacAddress.rst b/reference/constraints/MacAddress.rst index 8055b53ff4a..9a282ddf118 100644 --- a/reference/constraints/MacAddress.rst +++ b/reference/constraints/MacAddress.rst @@ -19,18 +19,18 @@ Basic Usage ----------- To use the MacAddress validator, apply it to a property on an object that -will contain a host name. +can contain a MAC address: .. configuration-block:: .. code-block:: php-attributes - // src/Entity/Author.php + // src/Entity/Device.php namespace App\Entity; use Symfony\Component\Validator\Constraints as Assert; - class Author + class Device { #[Assert\MacAddress] protected string $mac; @@ -39,7 +39,7 @@ will contain a host name. .. code-block:: yaml # config/validator/validation.yaml - App\Entity\Author: + App\Entity\Device: properties: mac: - MacAddress: ~ @@ -52,7 +52,7 @@ will contain a host name. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd"> - + @@ -61,13 +61,13 @@ will contain a host name. .. code-block:: php - // src/Entity/Author.php + // src/Entity/Device.php namespace App\Entity; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Mapping\ClassMetadata; - class Author + class Device { // ... @@ -103,4 +103,37 @@ Parameter Description .. include:: /reference/constraints/_payload-option.rst.inc +.. _reference-constraint-mac-address-type: + +``type`` +~~~~~~~~ + +**type**: ``string`` **default**: ``all`` + +.. versionadded:: 7.1 + + The ``type`` option was introduced in Symfony 7.1. + +This option defines the kind of MAC addresses that are allowed. There are a lot +of different possible values based on your needs: + +================================ ========================================= +Parameter Allowed MAC addresses +================================ ========================================= +``all`` All +``all_no_broadcast`` All except broadcast +``broadcast`` Only broadcast +``local_all`` Only local +``local_multicast_no_broadcast`` Only local and multicast except broadcast +``local_multicast`` Only local and multicast +``local_no_broadcast`` Only local except broadcast +``local_unicast`` Only local and unicast +``multicast_all`` Only multicast +``multicast_no_broadcast`` Only multicast except broadcast +``unicast_all`` Only unicast +``universal_all`` Only universal +``universal_unicast`` Only universal and unicast +``universal_multicast`` Only universal and multicast +================================ ========================================= + .. _`MAC address`: https://en.wikipedia.org/wiki/MAC_address diff --git a/reference/constraints/NotEqualTo.rst b/reference/constraints/NotEqualTo.rst index 37b03c35907..b8ee4cac32f 100644 --- a/reference/constraints/NotEqualTo.rst +++ b/reference/constraints/NotEqualTo.rst @@ -5,7 +5,7 @@ Validates that a value is **not** equal to another value, defined in the options. To force that a value is equal, see :doc:`/reference/constraints/EqualTo`. -.. caution:: +.. warning:: This constraint compares using ``!=``, so ``3`` and ``"3"`` are considered equal. Use :doc:`/reference/constraints/NotIdenticalTo` to compare with diff --git a/reference/constraints/NotIdenticalTo.rst b/reference/constraints/NotIdenticalTo.rst index ba28fdb7c45..9ea93dc4b86 100644 --- a/reference/constraints/NotIdenticalTo.rst +++ b/reference/constraints/NotIdenticalTo.rst @@ -5,7 +5,7 @@ Validates that a value is **not** identical to another value, defined in the options. To force that a value is identical, see :doc:`/reference/constraints/IdenticalTo`. -.. caution:: +.. warning:: This constraint compares using ``!==``, so ``3`` and ``"3"`` are considered not equal. Use :doc:`/reference/constraints/NotEqualTo` to diff --git a/reference/constraints/Regex.rst b/reference/constraints/Regex.rst index bc042e1eb86..2e11a8d04fc 100644 --- a/reference/constraints/Regex.rst +++ b/reference/constraints/Regex.rst @@ -163,7 +163,7 @@ Options ``htmlPattern`` ~~~~~~~~~~~~~~~ -**type**: ``string|boolean`` **default**: ``null`` +**type**: ``string|null`` **default**: ``null`` This option specifies the pattern to use in the HTML5 ``pattern`` attribute. You usually don't need to specify this option because by default, the constraint @@ -243,7 +243,7 @@ need to specify the HTML5 compatible pattern in the ``htmlPattern`` option: } } -Setting ``htmlPattern`` to false will disable client side validation. +Setting ``htmlPattern`` to the empty string will disable client side validation. ``match`` ~~~~~~~~~ diff --git a/reference/constraints/Type.rst b/reference/constraints/Type.rst index b8f41fbd524..b99e8ce1c54 100644 --- a/reference/constraints/Type.rst +++ b/reference/constraints/Type.rst @@ -14,7 +14,11 @@ Validator :class:`Symfony\\Component\\Validator\\Constraints\\TypeValidator` Basic Usage ----------- -This will check if ``emailAddress`` is an instance of ``Symfony\Component\Mime\Address``, +This constraint should be applied to untyped variables/properties. If a property +or variable is typed and you pass a value of a different type, PHP will throw an +exception before this constraint is checked. + +The following example checks if ``emailAddress`` is an instance of ``Symfony\Component\Mime\Address``, ``firstName`` is of type ``string`` (using :phpfunction:`is_string` PHP function), ``age`` is an ``integer`` (using :phpfunction:`is_int` PHP function) and ``accessCode`` contains either only letters or only digits (using @@ -33,19 +37,19 @@ This will check if ``emailAddress`` is an instance of ``Symfony\Component\Mime\A class Author { #[Assert\Type(Address::class)] - protected Address $emailAddress; + protected $emailAddress; #[Assert\Type('string')] - protected string $firstName; + protected $firstName; #[Assert\Type( type: 'integer', message: 'The value {{ value }} is not a valid {{ type }}.', )] - protected int $age; + protected $age; #[Assert\Type(type: ['alpha', 'digit'])] - protected string $accessCode; + protected $accessCode; } .. code-block:: yaml diff --git a/reference/constraints/UniqueEntity.rst b/reference/constraints/UniqueEntity.rst index 32f46e365fe..d4fbfeb8666 100644 --- a/reference/constraints/UniqueEntity.rst +++ b/reference/constraints/UniqueEntity.rst @@ -126,14 +126,14 @@ between all of the rows in your user table: } } -.. caution:: +.. warning:: This constraint doesn't provide any protection against `race conditions`_. They may occur when another entity is persisted by an external process after this validation has passed and before this entity is actually persisted in the database. -.. caution:: +.. warning:: This constraint cannot deal with duplicates found in a collection of items that haven't been persisted as entities yet. You'll need to create your own @@ -188,8 +188,8 @@ Consider this example: #[ORM\Entity] #[UniqueEntity( fields: ['host', 'port'], - errorPath: 'port', message: 'This port is already in use on that host.', + errorPath: 'port', )] class Service { @@ -207,8 +207,8 @@ Consider this example: constraints: - Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity: fields: [host, port] - errorPath: port message: 'This port is already in use on that host.' + errorPath: port .. code-block:: xml @@ -224,8 +224,8 @@ Consider this example: host port - + @@ -249,8 +249,8 @@ Consider this example: { $metadata->addConstraint(new UniqueEntity([ 'fields' => ['host', 'port'], - 'errorPath' => 'port', 'message' => 'This port is already in use on that host.', + 'errorPath' => 'port', ])); } } @@ -355,7 +355,7 @@ this option to specify one or more fields to only ignore ``null`` values on them } } -.. caution:: +.. warning:: If you ``ignoreNull`` on fields that are part of a unique index in your database, you might see insertion errors when your application attempts to diff --git a/reference/constraints/Url.rst b/reference/constraints/Url.rst index b3a46d5aec4..74f0d750dfd 100644 --- a/reference/constraints/Url.rst +++ b/reference/constraints/Url.rst @@ -315,7 +315,12 @@ also relative URLs that contain no protocol (e.g. ``//example.com``). .. versionadded:: 7.1 - The ``requiredTld`` option was introduced in Symfony 7.1. + The ``requireTld`` option was introduced in Symfony 7.1. + +.. deprecated:: 7.1 + + Not setting the ``requireTld`` option is deprecated since Symfony 7.1 + and will default to ``true`` in Symfony 8.0. By default, URLs like ``https://aaa`` or ``https://foobar`` are considered valid because they are tecnically correct according to the `URL spec`_. If you set this option diff --git a/reference/constraints/_payload-option.rst.inc b/reference/constraints/_payload-option.rst.inc index a76c9a4a29d..5121ba1ae51 100644 --- a/reference/constraints/_payload-option.rst.inc +++ b/reference/constraints/_payload-option.rst.inc @@ -1,5 +1,3 @@ -.. _reference-constraints-payload: - ``payload`` ~~~~~~~~~~~ diff --git a/reference/constraints/map.rst.inc b/reference/constraints/map.rst.inc index 690e98c6bf9..706742dfde5 100644 --- a/reference/constraints/map.rst.inc +++ b/reference/constraints/map.rst.inc @@ -4,57 +4,64 @@ Basic Constraints These are the basic constraints: use them to assert very basic things about the value of properties or the return value of methods on your object. -* :doc:`NotBlank ` +.. class:: ui-list-two-columns + * :doc:`Blank ` -* :doc:`NotNull ` +* :doc:`IsFalse ` * :doc:`IsNull ` * :doc:`IsTrue ` -* :doc:`IsFalse ` +* :doc:`NotBlank ` +* :doc:`NotNull ` * :doc:`Type ` String Constraints ~~~~~~~~~~~~~~~~~~ +.. class:: ui-list-three-columns + +* :doc:`Charset ` +* :doc:`Cidr ` +* :doc:`CssColor ` * :doc:`Email ` * :doc:`ExpressionSyntax ` -* :doc:`Length ` -* :doc:`Url ` -* :doc:`Regex ` * :doc:`Hostname ` * :doc:`Ip ` -* :doc:`Cidr ` * :doc:`Json ` -* :doc:`Uuid ` -* :doc:`Ulid ` -* :doc:`UserPassword ` +* :doc:`Length ` +* :doc:`MacAddress ` +* :doc:`NoSuspiciousCharacters ` * :doc:`NotCompromisedPassword ` * :doc:`PasswordStrength ` -* :doc:`CssColor ` -* :doc:`NoSuspiciousCharacters ` -* :doc:`Charset ` -* :doc:`MacAddress ` +* :doc:`Regex ` +* :doc:`Ulid ` +* :doc:`Url ` +* :doc:`UserPassword ` +* :doc:`Uuid ` Comparison Constraints ~~~~~~~~~~~~~~~~~~~~~~ +.. class:: ui-list-three-columns + +* :doc:`DivisibleBy ` * :doc:`EqualTo ` -* :doc:`NotEqualTo ` +* :doc:`GreaterThan ` +* :doc:`GreaterThanOrEqual ` * :doc:`IdenticalTo ` -* :doc:`NotIdenticalTo ` * :doc:`LessThan ` * :doc:`LessThanOrEqual ` -* :doc:`GreaterThan ` -* :doc:`GreaterThanOrEqual ` +* :doc:`NotEqualTo ` +* :doc:`NotIdenticalTo ` * :doc:`Range ` -* :doc:`DivisibleBy ` * :doc:`Unique ` Number Constraints ~~~~~~~~~~~~~~~~~~ -* :doc:`Positive ` -* :doc:`PositiveOrZero ` + * :doc:`Negative ` * :doc:`NegativeOrZero ` +* :doc:`Positive ` +* :doc:`PositiveOrZero ` Date Constraints ~~~~~~~~~~~~~~~~ @@ -68,9 +75,9 @@ Choice Constraints ~~~~~~~~~~~~~~~~~~ * :doc:`Choice ` +* :doc:`Country ` * :doc:`Language ` * :doc:`Locale ` -* :doc:`Country ` File Constraints ~~~~~~~~~~~~~~~~ @@ -81,30 +88,39 @@ File Constraints Financial and other Number Constraints ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. class:: ui-list-two-columns + * :doc:`Bic ` * :doc:`CardScheme ` * :doc:`Currency ` -* :doc:`Luhn ` * :doc:`Iban ` * :doc:`Isbn ` -* :doc:`Issn ` * :doc:`Isin ` +* :doc:`Issn ` +* :doc:`Luhn ` + +Doctrine Constraints +~~~~~~~~~~~~~~~~~~~~ + +* :doc:`DisableAutoMapping ` +* :doc:`EnableAutoMapping ` +* :doc:`UniqueEntity ` Other Constraints ~~~~~~~~~~~~~~~~~ +.. class:: ui-list-three-columns + +* :doc:`All ` * :doc:`AtLeastOneOf ` -* :doc:`Sequentially ` -* :doc:`Compound ` * :doc:`Callback ` -* :doc:`Expression ` -* :doc:`When ` -* :doc:`All ` -* :doc:`Valid ` * :doc:`Cascade ` -* :doc:`Traverse ` * :doc:`Collection ` +* :doc:`Compound ` * :doc:`Count ` -* :doc:`UniqueEntity ` -* :doc:`EnableAutoMapping ` -* :doc:`DisableAutoMapping ` +* :doc:`Expression ` +* :doc:`GroupSequence ` +* :doc:`Sequentially ` +* :doc:`Traverse ` +* :doc:`Valid ` +* :doc:`When ` diff --git a/reference/dic_tags.rst b/reference/dic_tags.rst index 917bb9bb466..866aac5774f 100644 --- a/reference/dic_tags.rst +++ b/reference/dic_tags.rst @@ -335,7 +335,7 @@ controller.argument_value_resolver Value resolvers implement the :class:`Symfony\\Component\\HttpKernel\\Controller\\ValueResolverInterface` and are used to resolve argument values for controllers as described here: -:doc:`/controller/argument_value_resolver`. +:doc:`/controller/value_resolver`. data_collector -------------- @@ -483,7 +483,7 @@ the :class:`Symfony\\Component\\HttpKernel\\CacheWarmer\\CacheWarmerInterface` i class MyCustomWarmer implements CacheWarmerInterface { - public function warmUp(string $cacheDir, string $buildDir = null): array + public function warmUp(string $cacheDir, ?string $buildDir = null): array { // ... do some sort of operations to "warm" your cache @@ -560,7 +560,7 @@ can also register it manually: that defaults to ``0``. The higher the number, the earlier that warmers are executed. -.. caution:: +.. warning:: If your cache warmer fails its execution because of any exception, Symfony won't try to execute it again for the next requests. Therefore, your diff --git a/reference/events.rst b/reference/events.rst index 92f917f9115..57806ee8f8d 100644 --- a/reference/events.rst +++ b/reference/events.rst @@ -56,8 +56,8 @@ their priorities: This event is dispatched after the controller has been resolved but before executing it. It's useful to initialize things later needed by the -controller, such as `param converters`_, and even to change the controller -entirely:: +controller, such as :ref:`value resolvers `, and +even to change the controller entirely:: use Symfony\Component\HttpKernel\Event\ControllerEvent; @@ -296,5 +296,3 @@ their priorities: .. code-block:: terminal $ php bin/console debug:event-dispatcher kernel.exception - -.. _`param converters`: https://symfony.com/doc/master/bundles/SensioFrameworkExtraBundle/annotations/converters.html diff --git a/reference/formats/expression_language.rst b/reference/formats/expression_language.rst index e0e0e258079..a23440c5715 100644 --- a/reference/formats/expression_language.rst +++ b/reference/formats/expression_language.rst @@ -1,9 +1,9 @@ The Expression Syntax ===================== -The ExpressionLanguage component uses a specific syntax which is based on the -expression syntax of Twig. In this document, you can find all supported -syntaxes. +The :doc:`ExpressionLanguage component ` uses a +specific syntax which is based on the expression syntax of Twig. In this document, +you can find all supported syntaxes. Supported Literals ------------------ @@ -21,7 +21,7 @@ The component supports: * **null** - ``null`` * **exponential** - also known as scientific (e.g. ``1.99E+3`` or ``1e-2``) -.. caution:: +.. warning:: A backslash (``\``) must be escaped by 3 backslashes (``\\\\``) in a string and 7 backslashes (``\\\\\\\\``) in a regex:: @@ -431,6 +431,53 @@ Other Operators * ``?.`` (:ref:`null-safe operator `) * ``??`` (:ref:`null-coalescing operator `) +Operators Precedence +~~~~~~~~~~~~~~~~~~~~ + +Operator precedence determines the order in which operations are processed in an +expression. For example, the result of the expression ``1 + 2 * 4`` is ``9`` +and not ``12`` because the multiplication operator (``*``) takes precedence over +the addition operator (``+``). + +To avoid ambiguities (or to alter the default order of operations) add +parentheses in your expressions (e.g. ``(1 + 2) * 4`` or ``1 + (2 * 4)``. + +The following table summarizes the operators and their associativity from the +**highest to the lowest precedence**: + ++----------------------------------------------------------+---------------+ +| Operators | Associativity | ++==========================================================+===============+ +| ``-`` , ``+`` (unary operators that add the number sign) | none | ++----------------------------------------------------------+---------------+ +| ``**`` | right | ++----------------------------------------------------------+---------------+ +| ``*``, ``/``, ``%`` | left | ++----------------------------------------------------------+---------------+ +| ``not``, ``!`` | none | ++----------------------------------------------------------+---------------+ +| ``~`` | left | ++----------------------------------------------------------+---------------+ +| ``+``, ``-`` | left | ++----------------------------------------------------------+---------------+ +| ``..`` | left | ++----------------------------------------------------------+---------------+ +| ``==``, ``===``, ``!=``, ``!==``, | left | +| ``<``, ``>``, ``>=``, ``<=``, | | +| ``not in``, ``in``, ``contains``, | | +| ``starts with``, ``ends with``, ``matches`` | | ++----------------------------------------------------------+---------------+ +| ``&`` | left | ++----------------------------------------------------------+---------------+ +| ``^`` | left | ++----------------------------------------------------------+---------------+ +| ``|`` | left | ++----------------------------------------------------------+---------------+ +| ``and``, ``&&`` | left | ++----------------------------------------------------------+---------------+ +| ``or``, ``||`` | left | ++----------------------------------------------------------+---------------+ + Built-in Objects and Variables ------------------------------ diff --git a/reference/formats/message_format.rst b/reference/formats/message_format.rst index 5ebd5def049..fb0143228c1 100644 --- a/reference/formats/message_format.rst +++ b/reference/formats/message_format.rst @@ -3,7 +3,8 @@ How to Translate Messages using the ICU MessageFormat Messages (i.e. strings) in applications are almost never completely static. They contain variables or other complex logic like pluralization. To -handle this, the Translator component supports the `ICU MessageFormat`_ syntax. +handle this, the :doc:`Translator component ` supports the +`ICU MessageFormat`_ syntax. .. tip:: @@ -63,7 +64,7 @@ The basic usage of the MessageFormat allows you to use placeholders (called 'say_hello' => "Hello {name}!", ]; -.. caution:: +.. warning:: In the previous translation format, placeholders were often wrapped in ``%`` (e.g. ``%name%``). This ``%`` character is no longer valid with the ICU diff --git a/reference/formats/yaml.rst b/reference/formats/yaml.rst index 44da6971b26..1884735bd82 100644 --- a/reference/formats/yaml.rst +++ b/reference/formats/yaml.rst @@ -1,8 +1,8 @@ The YAML Format --------------- -The Symfony Yaml Component implements a selected subset of features defined in -the `YAML 1.2 version specification`_. +The Symfony :doc:`Yaml Component ` implements a selected subset +of features defined in the `YAML 1.2 version specification`_. Scalars ~~~~~~~ diff --git a/reference/forms/types/birthday.rst b/reference/forms/types/birthday.rst index 2098d3cfb89..383dbf890f2 100644 --- a/reference/forms/types/birthday.rst +++ b/reference/forms/types/birthday.rst @@ -19,8 +19,6 @@ option defaults to 120 years ago to the current year. +---------------------------+-------------------------------------------------------------------------------+ | Default invalid message | Please enter a valid birthdate. | +---------------------------+-------------------------------------------------------------------------------+ -| Legacy invalid message | The value {{ value }} is not valid. | -+---------------------------+-------------------------------------------------------------------------------+ | Parent type | :doc:`DateType ` | +---------------------------+-------------------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\BirthdayType` | diff --git a/reference/forms/types/checkbox.rst b/reference/forms/types/checkbox.rst index 472d6f84024..2299220c5b6 100644 --- a/reference/forms/types/checkbox.rst +++ b/reference/forms/types/checkbox.rst @@ -13,8 +13,6 @@ if you want to handle submitted values like "0" or "false"). +---------------------------+------------------------------------------------------------------------+ | Default invalid message | The checkbox has an invalid value. | +---------------------------+------------------------------------------------------------------------+ -| Legacy invalid message | The value {{ value }} is not valid. | -+---------------------------+------------------------------------------------------------------------+ | Parent type | :doc:`FormType ` | +---------------------------+------------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\CheckboxType` | diff --git a/reference/forms/types/choice.rst b/reference/forms/types/choice.rst index 3637da8bdca..5ec8aac0ff8 100644 --- a/reference/forms/types/choice.rst +++ b/reference/forms/types/choice.rst @@ -11,8 +11,6 @@ To use this field, you must specify *either* ``choices`` or ``choice_loader`` op +---------------------------+----------------------------------------------------------------------+ | Default invalid message | The selected choice is invalid. | +---------------------------+----------------------------------------------------------------------+ -| Legacy invalid message | The value {{ value }} is not valid. | -+---------------------------+----------------------------------------------------------------------+ | Parent type | :doc:`FormType ` | +---------------------------+----------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\ChoiceType` | @@ -95,7 +93,7 @@ method:: You can also customize the `choice_name`_ of each choice. You can learn more about all of these options in the sections below. -.. caution:: +.. warning:: The *placeholder* is a specific field, when the choices are optional the first item in the list must be empty, so the user can unselect. @@ -202,6 +200,32 @@ correct types will be assigned to the model. .. include:: /reference/forms/types/options/preferred_choices.rst.inc +``separator`` +~~~~~~~~~~~~~ + +**type**: ``string`` **default**: ``-------------------`` + +This option allows you to customize the visual separator shown after the preferred +choices. You can use HTML elements like ``
`` to display a more modern separator, +but you'll also need to set the `separator_html`_ option to ``true``. + +.. versionadded:: 7.1 + + The ``separator`` option was introduced in Symfony 7.1. + +``separator_html`` +~~~~~~~~~~~~~~~~~~ + +**type**: ``boolean`` **default**: ``false`` + +If this option is true, the `separator`_ option will be displayed as HTML instead +of text. This is useful when using HTML elements (e.g. ``
``) as a more modern +visual separator. + +.. versionadded:: 7.1 + + The ``separator_html`` option was introduced in Symfony 7.1. + Overridden Options ------------------ diff --git a/reference/forms/types/collection.rst b/reference/forms/types/collection.rst index 2d91bfd06bd..2875ba076d0 100644 --- a/reference/forms/types/collection.rst +++ b/reference/forms/types/collection.rst @@ -16,8 +16,6 @@ that is passed as the collection type field data. +---------------------------+--------------------------------------------------------------------------+ | Default invalid message | The collection is invalid. | +---------------------------+--------------------------------------------------------------------------+ -| Legacy invalid message | The value {{ value }} is not valid. | -+---------------------------+--------------------------------------------------------------------------+ | Parent type | :doc:`FormType ` | +---------------------------+--------------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\CollectionType` | @@ -101,7 +99,7 @@ can be used - with JavaScript - to create new form items dynamically on the client side. For more information, see the above example and :ref:`form-collections-new-prototype`. -.. caution:: +.. warning:: If you're embedding entire other forms to reflect a one-to-many database relationship, you may need to manually ensure that the foreign key of @@ -121,7 +119,7 @@ submitted data will mean that it's removed from the final array. For more information, see :ref:`form-collections-remove`. -.. caution:: +.. warning:: Be careful when using this option when you're embedding a collection of objects. In this case, if any embedded forms are removed, they *will* @@ -141,7 +139,7 @@ form you have to set this option to ``true``. However, existing collection entri will only be deleted if you have the allow_delete_ option enabled. Otherwise the empty values will be kept. -.. caution:: +.. warning:: The ``delete_empty`` option only removes items when the normalized value is ``null``. If the nested `entry_type`_ is a compound form type, you must @@ -160,7 +158,7 @@ the value is removed from the collection. For example:: $builder->add('users', CollectionType::class, [ // ... - 'delete_empty' => function (User $user = null): bool { + 'delete_empty' => function (?User $user = null): bool { return null === $user || empty($user->getFirstName()); }, ]); diff --git a/reference/forms/types/color.rst b/reference/forms/types/color.rst index 1a320b9bdbf..b205127fb91 100644 --- a/reference/forms/types/color.rst +++ b/reference/forms/types/color.rst @@ -16,8 +16,6 @@ element. +---------------------------+---------------------------------------------------------------------+ | Default invalid message | Please select a valid color. | +---------------------------+---------------------------------------------------------------------+ -| Legacy invalid message | The value {{ value }} is not valid. | -+---------------------------+---------------------------------------------------------------------+ | Parent type | :doc:`TextType ` | +---------------------------+---------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\ColorType` | diff --git a/reference/forms/types/country.rst b/reference/forms/types/country.rst index 8913e639f23..6c98897b6ba 100644 --- a/reference/forms/types/country.rst +++ b/reference/forms/types/country.rst @@ -20,8 +20,6 @@ the option manually, but then you should just use the ``ChoiceType`` directly. +---------------------------+-----------------------------------------------------------------------+ | Default invalid message | Please select a valid country. | +---------------------------+-----------------------------------------------------------------------+ -| Legacy invalid message | The value {{ value }} is not valid. | -+---------------------------+-----------------------------------------------------------------------+ | Parent type | :doc:`ChoiceType ` | +---------------------------+-----------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\CountryType` | @@ -54,7 +52,7 @@ Overridden Options The country type defaults the ``choices`` option to the whole list of countries. The locale is used to translate the countries names. -.. caution:: +.. warning:: If you want to override the built-in choices of the country type, you will also have to set the ``choice_loader`` option to ``null``. diff --git a/reference/forms/types/currency.rst b/reference/forms/types/currency.rst index cca441ff930..94c0d2cddc8 100644 --- a/reference/forms/types/currency.rst +++ b/reference/forms/types/currency.rst @@ -13,8 +13,6 @@ manually, but then you should just use the ``ChoiceType`` directly. +---------------------------+------------------------------------------------------------------------+ | Default invalid message | Please select a valid currency. | +---------------------------+------------------------------------------------------------------------+ -| Legacy invalid message | The value {{ value }} is not valid. | -+---------------------------+------------------------------------------------------------------------+ | Parent type | :doc:`ChoiceType ` | +---------------------------+------------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\CurrencyType` | @@ -37,7 +35,7 @@ Overridden Options The choices option defaults to all currencies. -.. caution:: +.. warning:: If you want to override the built-in choices of the currency type, you will also have to set the ``choice_loader`` option to ``null``. diff --git a/reference/forms/types/date.rst b/reference/forms/types/date.rst index 515c12099a1..f28aae474b9 100644 --- a/reference/forms/types/date.rst +++ b/reference/forms/types/date.rst @@ -14,8 +14,6 @@ and can understand a number of different input formats via the `input`_ option. +---------------------------+-----------------------------------------------------------------------------+ | Default invalid message | Please enter a valid date. | +---------------------------+-----------------------------------------------------------------------------+ -| Legacy invalid message | The value {{ value }} is not valid. | -+---------------------------+-----------------------------------------------------------------------------+ | Parent type | :doc:`FormType ` | +---------------------------+-----------------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\DateType` | @@ -103,7 +101,7 @@ This can be tricky: if the date picker is misconfigured, Symfony won't understan the format and will throw a validation error. You can also configure the format that Symfony should expect via the `format`_ option. -.. caution:: +.. warning:: The string used by a JavaScript date picker to describe its format (e.g. ``yyyy-mm-dd``) may not match the string that Symfony uses (e.g. ``yyyy-MM-dd``). This is because diff --git a/reference/forms/types/dateinterval.rst b/reference/forms/types/dateinterval.rst index 627fb78d7ed..838ae2bbdef 100644 --- a/reference/forms/types/dateinterval.rst +++ b/reference/forms/types/dateinterval.rst @@ -16,8 +16,6 @@ or an array (see `input`_). +---------------------------+----------------------------------------------------------------------------------+ | Default invalid message | Please choose a valid date interval. | +---------------------------+----------------------------------------------------------------------------------+ -| Legacy invalid message | The value {{ value }} is not valid. | -+---------------------------+----------------------------------------------------------------------------------+ | Parent type | :doc:`FormType ` | +---------------------------+----------------------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\DateIntervalType` | @@ -223,7 +221,7 @@ following: Whether or not to include days in the input. This will result in an additional input to capture days. -.. caution:: +.. warning:: This can not be used when `with_weeks`_ is enabled. @@ -276,7 +274,7 @@ input to capture seconds. Whether or not to include weeks in the input. This will result in an additional input to capture weeks. -.. caution:: +.. warning:: This can not be used when `with_days`_ is enabled. diff --git a/reference/forms/types/datetime.rst b/reference/forms/types/datetime.rst index 7ffc0ee216f..5fda8e9a14f 100644 --- a/reference/forms/types/datetime.rst +++ b/reference/forms/types/datetime.rst @@ -14,8 +14,6 @@ the data can be a ``DateTime`` object, a string, a timestamp or an array. +---------------------------+-----------------------------------------------------------------------------+ | Default invalid message | Please enter a valid date and time. | +---------------------------+-----------------------------------------------------------------------------+ -| Legacy invalid message | The value {{ value }} is not valid. | -+---------------------------+-----------------------------------------------------------------------------+ | Parent type | :doc:`FormType ` | +---------------------------+-----------------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\DateTimeType` | diff --git a/reference/forms/types/email.rst b/reference/forms/types/email.rst index 9045bba8cc4..ef535050813 100644 --- a/reference/forms/types/email.rst +++ b/reference/forms/types/email.rst @@ -9,8 +9,6 @@ The ``EmailType`` field is a text field that is rendered using the HTML5 +---------------------------+---------------------------------------------------------------------+ | Default invalid message | Please enter a valid email address. | +---------------------------+---------------------------------------------------------------------+ -| Legacy invalid message | The value {{ value }} is not valid. | -+---------------------------+---------------------------------------------------------------------+ | Parent type | :doc:`TextType ` | +---------------------------+---------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\EmailType` | diff --git a/reference/forms/types/entity.rst b/reference/forms/types/entity.rst index f30d5f9a5b2..0d900de377f 100644 --- a/reference/forms/types/entity.rst +++ b/reference/forms/types/entity.rst @@ -183,7 +183,7 @@ passed the ``EntityRepository`` of the entity as the only argument and should return a ``QueryBuilder``. Returning ``null`` in the Closure will result in loading all entities. -.. caution:: +.. warning:: The entity used in the ``FROM`` clause of the ``query_builder`` option will always be validated against the class which you have specified at the diff --git a/reference/forms/types/enum.rst b/reference/forms/types/enum.rst index 47416ee6215..875c0808108 100644 --- a/reference/forms/types/enum.rst +++ b/reference/forms/types/enum.rst @@ -10,8 +10,6 @@ field and defines the same options. +---------------------------+----------------------------------------------------------------------+ | Default invalid message | The selected choice is invalid. | +---------------------------+----------------------------------------------------------------------+ -| Legacy invalid message | The value {{ value }} is not valid. | -+---------------------------+----------------------------------------------------------------------+ | Parent type | :doc:`ChoiceType ` | +---------------------------+----------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\EnumType` | @@ -66,7 +64,7 @@ implement ``TranslatableInterface`` to translate or display custom labels:: case Center = 'Center aligned'; case Right = 'Right aligned'; - public function trans(TranslatorInterface $translator, string $locale = null): string + public function trans(TranslatorInterface $translator, ?string $locale = null): string { // Translate enum from name (Left, Center or Right) return $translator->trans($this->name, locale: $locale); diff --git a/reference/forms/types/file.rst b/reference/forms/types/file.rst index b4982859b98..2e841611eb8 100644 --- a/reference/forms/types/file.rst +++ b/reference/forms/types/file.rst @@ -8,8 +8,6 @@ The ``FileType`` represents a file input in your form. +---------------------------+--------------------------------------------------------------------+ | Default invalid message | Please select a valid file. | +---------------------------+--------------------------------------------------------------------+ -| Legacy invalid message | The value {{ value }} is not valid. | -+---------------------------+--------------------------------------------------------------------+ | Parent type | :doc:`FormType ` | +---------------------------+--------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\FileType` | diff --git a/reference/forms/types/form.rst b/reference/forms/types/form.rst index 0d0089c1fd3..58a6214d379 100644 --- a/reference/forms/types/form.rst +++ b/reference/forms/types/form.rst @@ -7,8 +7,6 @@ on all types for which ``FormType`` is the parent. +---------------------------+--------------------------------------------------------------------+ | Default invalid message | This value is not valid. | +---------------------------+--------------------------------------------------------------------+ -| Legacy invalid message | This value is not valid. | -+---------------------------+--------------------------------------------------------------------+ | Parent | none | +---------------------------+--------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\FormType` | diff --git a/reference/forms/types/hidden.rst b/reference/forms/types/hidden.rst index fba056b88e5..d6aff282edd 100644 --- a/reference/forms/types/hidden.rst +++ b/reference/forms/types/hidden.rst @@ -8,8 +8,6 @@ The hidden type represents a hidden input field. +---------------------------+----------------------------------------------------------------------+ | Default invalid message | The hidden field is invalid. | +---------------------------+----------------------------------------------------------------------+ -| Legacy invalid message | The value {{ value }} is not valid. | -+---------------------------+----------------------------------------------------------------------+ | Parent type | :doc:`FormType ` | +---------------------------+----------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\HiddenType` | diff --git a/reference/forms/types/integer.rst b/reference/forms/types/integer.rst index 619ac996df6..1f94f9e2525 100644 --- a/reference/forms/types/integer.rst +++ b/reference/forms/types/integer.rst @@ -15,8 +15,6 @@ integers. By default, all non-integer values (e.g. 6.78) will round down +---------------------------+-----------------------------------------------------------------------+ | Default invalid message | Please enter an integer. | +---------------------------+-----------------------------------------------------------------------+ -| Legacy invalid message | The value {{ value }} is not valid. | -+---------------------------+-----------------------------------------------------------------------+ | Parent type | :doc:`FormType ` | +---------------------------+-----------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\IntegerType` | diff --git a/reference/forms/types/language.rst b/reference/forms/types/language.rst index 4b1bccd077d..a1e699a0686 100644 --- a/reference/forms/types/language.rst +++ b/reference/forms/types/language.rst @@ -22,8 +22,6 @@ manually, but then you should just use the ``ChoiceType`` directly. +---------------------------+------------------------------------------------------------------------+ | Default invalid message | Please select a valid language. | +---------------------------+------------------------------------------------------------------------+ -| Legacy invalid message | The value {{ value }} is not valid. | -+---------------------------+------------------------------------------------------------------------+ | Parent type | :doc:`ChoiceType ` | +---------------------------+------------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\LanguageType` | @@ -71,7 +69,7 @@ Overridden Options The choices option defaults to all languages. The default locale is used to translate the languages names. -.. caution:: +.. warning:: If you want to override the built-in choices of the language type, you will also have to set the ``choice_loader`` option to ``null``. diff --git a/reference/forms/types/locale.rst b/reference/forms/types/locale.rst index 1868f20eda1..c006beb14fd 100644 --- a/reference/forms/types/locale.rst +++ b/reference/forms/types/locale.rst @@ -23,8 +23,6 @@ manually, but then you should just use the ``ChoiceType`` directly. +---------------------------+----------------------------------------------------------------------+ | Default invalid message | Please select a valid locale. | +---------------------------+----------------------------------------------------------------------+ -| Legacy invalid message | The value {{ value }} is not valid. | -+---------------------------+----------------------------------------------------------------------+ | Parent type | :doc:`ChoiceType ` | +---------------------------+----------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\LocaleType` | @@ -48,7 +46,7 @@ Overridden Options The choices option defaults to all locales. It uses the default locale to specify the language. -.. caution:: +.. warning:: If you want to override the built-in choices of the locale type, you will also have to set the ``choice_loader`` option to ``null``. diff --git a/reference/forms/types/money.rst b/reference/forms/types/money.rst index 8e2130a5909..a02b695abd4 100644 --- a/reference/forms/types/money.rst +++ b/reference/forms/types/money.rst @@ -13,8 +13,6 @@ how the input and output of the data is handled. +---------------------------+---------------------------------------------------------------------+ | Default invalid message | Please enter a valid money amount. | +---------------------------+---------------------------------------------------------------------+ -| Legacy invalid message | The value {{ value }} is not valid. | -+---------------------------+---------------------------------------------------------------------+ | Parent type | :doc:`FormType ` | +---------------------------+---------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\MoneyType` | @@ -72,12 +70,13 @@ html5 If set to ``true``, the HTML input will be rendered as a native HTML5 ```` element. -.. caution:: +.. warning:: - As HTML5 number format is normalized, it is incompatible with ``grouping`` option. + As HTML5 number format is normalized, it is incompatible with the ``grouping`` + option. -model_type -~~~~~~~~~~ +input +~~~~~ **type**: ``string`` **default**: ``float`` @@ -87,7 +86,7 @@ values stored in cents as integers) set this option to ``integer``. .. versionadded:: 7.1 - The ``model_type`` option was introduced in Symfony 7.1. + The ``input`` option was introduced in Symfony 7.1. scale ~~~~~ diff --git a/reference/forms/types/number.rst b/reference/forms/types/number.rst index 86d8eda3116..7e125a5fd05 100644 --- a/reference/forms/types/number.rst +++ b/reference/forms/types/number.rst @@ -10,8 +10,6 @@ that you want to use for your number. +---------------------------+----------------------------------------------------------------------+ | Default invalid message | Please enter a number. | +---------------------------+----------------------------------------------------------------------+ -| Legacy invalid message | The value {{ value }} is not valid. | -+---------------------------+----------------------------------------------------------------------+ | Parent type | :doc:`FormType ` | +---------------------------+----------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\NumberType` | diff --git a/reference/forms/types/options/_date_limitation.rst.inc b/reference/forms/types/options/_date_limitation.rst.inc index 4e5b1be4c87..04106ee7e21 100644 --- a/reference/forms/types/options/_date_limitation.rst.inc +++ b/reference/forms/types/options/_date_limitation.rst.inc @@ -1,4 +1,4 @@ -.. caution:: +.. warning:: If ``timestamp`` is used, ``DateType`` is limited to dates between Fri, 13 Dec 1901 20:45:54 UTC and Tue, 19 Jan 2038 03:14:07 UTC on 32bit diff --git a/reference/forms/types/options/choice_name.rst.inc b/reference/forms/types/options/choice_name.rst.inc index 4ec8abb6ffe..4268c307d17 100644 --- a/reference/forms/types/options/choice_name.rst.inc +++ b/reference/forms/types/options/choice_name.rst.inc @@ -25,7 +25,7 @@ By default, the choice key or an incrementing integer may be used (starting at ` See the :ref:`"choice_loader" option documentation `. -.. caution:: +.. warning:: The configured value must be a valid form name. Make sure to only return valid names when using a callable. Valid form names must be composed of diff --git a/reference/forms/types/options/data.rst.inc b/reference/forms/types/options/data.rst.inc index c3562d0a8b1..34f86e7c4c6 100644 --- a/reference/forms/types/options/data.rst.inc +++ b/reference/forms/types/options/data.rst.inc @@ -16,7 +16,7 @@ an individual field, you can set it in the data option:: 'data' => 'abcdef', ]); -.. caution:: +.. warning:: The ``data`` option *always* overrides the value taken from the domain data (object) when rendering. This means the object value is also overridden when diff --git a/reference/forms/types/options/empty_data_description.rst.inc b/reference/forms/types/options/empty_data_description.rst.inc index e654a7037df..b143b9438fe 100644 --- a/reference/forms/types/options/empty_data_description.rst.inc +++ b/reference/forms/types/options/empty_data_description.rst.inc @@ -22,7 +22,7 @@ initial value in the rendered form. :doc:`/form/use_empty_data` article for more details about these options. -.. caution:: +.. warning:: :doc:`Form data transformers ` will still be applied to the ``empty_data`` value. This means that an empty string will diff --git a/reference/forms/types/options/inherit_data.rst.inc b/reference/forms/types/options/inherit_data.rst.inc index 1b63cc4b56f..f35f6d56b00 100644 --- a/reference/forms/types/options/inherit_data.rst.inc +++ b/reference/forms/types/options/inherit_data.rst.inc @@ -7,7 +7,7 @@ This option determines if the form will inherit data from its parent form. This can be useful if you have a set of fields that are duplicated across multiple forms. See :doc:`/form/inherit_data_option`. -.. caution:: +.. warning:: When a field has the ``inherit_data`` option set, it uses the data of the parent form as is. This means that diff --git a/reference/forms/types/options/sanitize_html.rst.inc b/reference/forms/types/options/sanitize_html.rst.inc index 1f906fd1354..2b5e8a3515b 100644 --- a/reference/forms/types/options/sanitize_html.rst.inc +++ b/reference/forms/types/options/sanitize_html.rst.inc @@ -5,7 +5,7 @@ sanitize_html When ``true``, the text input will be sanitized using the :doc:`Symfony HTML Sanitizer component ` after the form is -submitted. This protects the form input against XSS, clickjacking and CSS +submitted. This protects the form input against :ref:`XSS `, clickjacking and CSS injection. .. note:: diff --git a/reference/forms/types/options/value.rst.inc b/reference/forms/types/options/value.rst.inc index ddbfff6660d..e4669faa7e4 100644 --- a/reference/forms/types/options/value.rst.inc +++ b/reference/forms/types/options/value.rst.inc @@ -6,7 +6,7 @@ The value that's actually used as the value for the checkbox or radio button. This does not affect the value that's set on your object. -.. caution:: +.. warning:: To make a checkbox or radio button checked by default, use the `data`_ option. diff --git a/reference/forms/types/password.rst b/reference/forms/types/password.rst index 7fb760471ef..59e40fb19d1 100644 --- a/reference/forms/types/password.rst +++ b/reference/forms/types/password.rst @@ -8,8 +8,6 @@ The ``PasswordType`` field renders an input password text box. +---------------------------+------------------------------------------------------------------------+ | Default invalid message | The password is invalid. | +---------------------------+------------------------------------------------------------------------+ -| Legacy invalid message | The value {{ value }} is not valid. | -+---------------------------+------------------------------------------------------------------------+ | Parent type | :doc:`TextType ` | +---------------------------+------------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\PasswordType` | @@ -45,7 +43,7 @@ Data passed to the form must be a :class:`Symfony\\Component\\Security\\Core\\User\\PasswordAuthenticatedUserInterface` object. -.. caution:: +.. warning:: To minimize the risk of leaking the plain password, this option can only be used with the :ref:`"mapped" option ` diff --git a/reference/forms/types/percent.rst b/reference/forms/types/percent.rst index ce985488c76..b46ca298c53 100644 --- a/reference/forms/types/percent.rst +++ b/reference/forms/types/percent.rst @@ -14,8 +14,6 @@ the input. +---------------------------+-----------------------------------------------------------------------+ | Default invalid message | Please enter a percentage value. | +---------------------------+-----------------------------------------------------------------------+ -| Legacy invalid message | The value {{ value }} is not valid. | -+---------------------------+-----------------------------------------------------------------------+ | Parent type | :doc:`FormType ` | +---------------------------+-----------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\PercentType` | diff --git a/reference/forms/types/radio.rst b/reference/forms/types/radio.rst index 7702b87cbad..7ab90086803 100644 --- a/reference/forms/types/radio.rst +++ b/reference/forms/types/radio.rst @@ -15,8 +15,6 @@ If you want to have a boolean field, use :doc:`CheckboxType ` | +---------------------------+---------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\RadioType` | diff --git a/reference/forms/types/range.rst b/reference/forms/types/range.rst index 294023ce0c6..06eebfd5473 100644 --- a/reference/forms/types/range.rst +++ b/reference/forms/types/range.rst @@ -9,8 +9,6 @@ The ``RangeType`` field is a slider that is rendered using the HTML5 +---------------------------+---------------------------------------------------------------------+ | Default invalid message | Please choose a valid range. | +---------------------------+---------------------------------------------------------------------+ -| Legacy invalid message | The value {{ value }} is not valid. | -+---------------------------+---------------------------------------------------------------------+ | Parent type | :doc:`TextType ` | +---------------------------+---------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\RangeType` | diff --git a/reference/forms/types/repeated.rst b/reference/forms/types/repeated.rst index e5bd0cd4520..36211237bbd 100644 --- a/reference/forms/types/repeated.rst +++ b/reference/forms/types/repeated.rst @@ -11,8 +11,6 @@ accuracy. +---------------------------+------------------------------------------------------------------------+ | Default invalid message | The values do not match. | +---------------------------+------------------------------------------------------------------------+ -| Legacy invalid message | The value {{ value }} is not valid. | -+---------------------------+------------------------------------------------------------------------+ | Parent type | :doc:`FormType ` | +---------------------------+------------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\RepeatedType` | diff --git a/reference/forms/types/search.rst b/reference/forms/types/search.rst index e38021bc461..ad4a8f7978a 100644 --- a/reference/forms/types/search.rst +++ b/reference/forms/types/search.rst @@ -4,15 +4,11 @@ SearchType Field This renders an ```` field, which is a text box with special functionality supported by some browsers. -Read about the input search field at `DiveIntoHTML5.info`_ - +---------------------------+----------------------------------------------------------------------+ | Rendered as | ``input search`` field | +---------------------------+----------------------------------------------------------------------+ | Default invalid message | Please enter a valid search term. | +---------------------------+----------------------------------------------------------------------+ -| Legacy invalid message | The value {{ value }} is not valid. | -+---------------------------+----------------------------------------------------------------------+ | Parent type | :doc:`TextType ` | +---------------------------+----------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\SearchType` | @@ -65,5 +61,3 @@ The default value is ``''`` (the empty string). .. include:: /reference/forms/types/options/row_attr.rst.inc .. include:: /reference/forms/types/options/trim.rst.inc - -.. _`DiveIntoHTML5.info`: http://diveintohtml5.info/forms.html#type-search diff --git a/reference/forms/types/tel.rst b/reference/forms/types/tel.rst index 675f8e3f5cd..e8ab9c322fe 100644 --- a/reference/forms/types/tel.rst +++ b/reference/forms/types/tel.rst @@ -15,8 +15,6 @@ to input phone numbers. +---------------------------+-------------------------------------------------------------------+ | Default invalid message | Please provide a valid phone number. | +---------------------------+-------------------------------------------------------------------+ -| Legacy invalid message | The value {{ value }} is not valid. | -+---------------------------+-------------------------------------------------------------------+ | Parent type | :doc:`TextType ` | +---------------------------+-------------------------------------------------------------------+ | Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\TelType` | diff --git a/reference/forms/types/textarea.rst b/reference/forms/types/textarea.rst index 0460bca6942..47a32368b99 100644 --- a/reference/forms/types/textarea.rst +++ b/reference/forms/types/textarea.rst @@ -19,10 +19,10 @@ Renders a ``textarea`` HTML element. ``