diff --git a/.alexrc b/.alexrc
new file mode 100644
index 00000000000..168d412c177
--- /dev/null
+++ b/.alexrc
@@ -0,0 +1,16 @@
+{
+ "allow": [
+ "attack",
+ "attacks",
+ "bigger",
+ "color",
+ "colors",
+ "failure",
+ "hook",
+ "hooks",
+ "host-hostess",
+ "invalid",
+ "remain",
+ "special"
+ ]
+}
diff --git a/.doctor-rst.yaml b/.doctor-rst.yaml
index fc13097eccc..2ecd4fc7d2d 100644
--- a/.doctor-rst.yaml
+++ b/.doctor-rst.yaml
@@ -1,91 +1,122 @@
rules:
- no_inheritdoc: ~
+ american_english: ~
avoid_repetetive_words: ~
+ blank_line_after_anchor: ~
blank_line_after_directive: ~
- short_array_syntax: ~
- no_app_console: ~
- typo: ~
- replacement: ~
+ blank_line_before_directive: ~
composer_dev_option_not_at_the_end: ~
- yarn_dev_option_at_the_end: ~
- versionadded_directive_should_have_version: ~
+ correct_code_block_directive_based_on_the_content: ~
deprecated_directive_should_have_version: ~
- no_composer_req: ~
- no_php_open_tag_in_code_block_php_directive: ~
- no_blank_line_after_filepath_in_php_code_block: ~
- no_blank_line_after_filepath_in_yaml_code_block: ~
- no_blank_line_after_filepath_in_xml_code_block: ~
- no_blank_line_after_filepath_in_twig_code_block: ~
- php_prefix_before_bin_console: ~
- use_deprecated_directive_instead_of_versionadded: ~
- no_space_before_self_xml_closing_tag: ~
- no_explicit_use_of_code_block_php: ~
+ ensure_bash_prompt_before_composer_command: ~
+ 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: ~
+ ensure_link_definition_contains_valid_url: ~
ensure_order_of_code_blocks_in_configuration_block: ~
- american_english: ~
- valid_use_statements: ~
+ ensure_php_reference_syntax: ~
+ extend_abstract_controller: ~
+ # extension_xlf_instead_of_xliff: ~
+ forbidden_directives:
+ directives:
+ - '.. index::'
+ - directive: '.. caution::'
+ replacements: ['.. warning::', '.. danger::']
+ indention: ~
lowercase_as_in_use_statements: ~
- ordered_use_statements: ~
- no_namespace_after_use_statements: ~
- correct_code_block_directive_based_on_the_content: ~
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: ~
+ no_empty_literals: ~
+ no_explicit_use_of_code_block_php: ~
+ no_footnotes: ~
+ no_inheritdoc: ~
+ no_merge_conflict: ~
+ 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: ~
+ php_prefix_before_bin_console: ~
+ remove_trailing_whitespace: ~
replace_code_block_types: ~
+ replacement: ~
+ short_array_syntax: ~
+ space_between_label_and_link_in_doc: ~
+ space_between_label_and_link_in_ref: ~
+ string_replacement: ~
+ title_underline_length_must_match_title_length: ~
+ typo: ~
+ unused_links: ~
+ use_deprecated_directive_instead_of_versionadded: ~
+ use_named_constructor_without_new_keyword_rule: ~
use_https_xsd_urls: ~
- blank_line_before_directive: ~
- extension_xlf_instead_of_xliff: ~
valid_inline_highlighted_namespaces: ~
- indention: ~
- unused_links: ~
+ valid_use_statements: ~
+ versionadded_directive_should_have_version: ~
yaml_instead_of_yml_suffix: ~
- extend_abstract_controller: ~
-# no_app_bundle: ~
# master
versionadded_directive_major_version:
- major_version: 5
+ major_version: 7
versionadded_directive_min_version:
- min_version: '5.0'
+ min_version: '7.0'
deprecated_directive_major_version:
- major_version: 5
+ major_version: 7
deprecated_directive_min_version:
- min_version: '5.0'
+ min_version: '7.0'
+
+exclude_rule_for_file:
+ - path: configuration/multiple_kernels.rst
+ rule_name: replacement
+ - path: page_creation.rst
+ rule_name: no_php_open_tag_in_code_block_php_directive
+ - path: frontend/create_ux_bundle.rst
+ rule_name: argument_variable_must_match_type
# do not report as violation
whitelist:
regex:
- - '/FOSUserBundle(.*)\.yml/'
- '/``.yml``/'
- '/(.*)\.orm\.yml/' # currently DoctrineBundle only supports .yml
- - '/rst-class/'
lines:
- 'in config files, so the old ``app/config/config_dev.yml`` goes to'
- '#. The most important config file is ``app/config/services.yml``, which now is'
- - 'code in production without a proxy, it becomes trivially easy to abuse your'
- - '.. _`EasyDeployBundle`: https://github.com/EasyCorp/easy-deploy-bundle'
- 'The bin/console Command'
- - '# username is your full Gmail or Google Apps email address'
- '.. _`LDAP injection`: http://projects.webappsec.org/w/page/13246947/LDAP%20Injection'
- - '.. versionadded:: 0.21.0' # Encore
- - '.. versionadded:: 0.28.4' # Encore
- - '.. versionadded:: 2.4.0' # SwiftMailer
- - '.. versionadded:: 1.30' # Twig
- - '.. versionadded:: 1.35' # Twig
- - '.. versionadded:: 1.2' # MakerBundle
- - '.. versionadded:: 1.11' # MakerBundle
- - '.. versionadded:: 1.3' # MakerBundle
- - '.. versionadded:: 1.8' # MakerBundle
- - '.. versionadded:: 1.6' # Flex in setup/upgrade_minor.rst
- - '0 => 123' # assertion for var_dumper - components/var_dumper.rst
- - '1 => "foo"' # assertion for var_dumper - components/var_dumper.rst
+ - '.. versionadded:: 2.8.0' # Doctrine
+ - '.. versionadded:: 1.9.0' # Encore
+ - '.. versionadded:: 1.18' # Flex in setup/upgrade_minor.rst
+ - '.. versionadded:: 1.0.0' # Encore
+ - '.. versionadded:: 2.7.1' # Doctrine
- '123,' # assertion for var_dumper - components/var_dumper.rst
- '"foo",' # assertion for var_dumper - components/var_dumper.rst
- '$var .= "Because of this `\xE9` octet (\\xE9),\n";'
- - "`Deploying Symfony 4 Apps on Heroku`_."
- - ".. _`Deploying Symfony 4 Apps on Heroku`: https://devcenter.heroku.com/articles/deploying-symfony4"
- - "// 224, 165, 141, 224, 164, 164, 224, 165, 135])"
- '.. versionadded:: 0.2' # MercureBundle
- - 'provides a ``loginUser()`` method to simulate logging in in your functional'
- - '.. code-block:: twig'
+ - '.. 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
+ - '* Inline code blocks use double-ticks (````like this````).'
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 51ce53a1a89..9eb5d91783b 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,3 +1,6 @@
+# GithubActions workflows
+/.github/workflows* @OskarStark
+
# Console
/console* @chalasr
/components/console* @chalasr
diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md
deleted file mode 100644
index 9a4e5a2cedc..00000000000
--- a/.github/CODE_OF_CONDUCT.md
+++ /dev/null
@@ -1,12 +0,0 @@
-Code of Conduct
-===============
-
-This project follows a [Code of Conduct][code_of_conduct] in order to ensure an
-open and welcoming environment. Please read the full text for understanding the
-accepted and unaccepted behavior.
-
-Please read also the [reporting guidelines][guidelines], in case you encountered
-or witnessed any misbehavior.
-
-[code_of_conduct]: https://symfony.com/doc/current/contributing/code_of_conduct/code_of_conduct.html
-[guidelines]: https://symfony.com/doc/current/contributing/code_of_conduct/reporting_guidelines.html
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index af709c9c60f..f32043e4523 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -4,6 +4,6 @@ If your pull request fixes a BUG, use the oldest maintained branch that contains
the bug (see https://symfony.com/releases for the list of maintained branches).
If your pull request documents a NEW FEATURE, use the same Symfony branch where
-the feature was introduced (and `master` for features of unreleased versions).
+the feature was introduced (and `7.x` for features of unreleased versions).
-->
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 402e0dc572b..497dfd9b430 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -1,3 +1,5 @@
+name: CI
+
on:
push:
branches-ignore:
@@ -6,66 +8,138 @@ on:
branches-ignore:
- 'github-comments'
-name: CI
+permissions:
+ contents: read
jobs:
- build:
- name: Build
+ symfony-docs-builder-build:
+ name: Build (symfony-tools/docs-builder)
runs-on: ubuntu-latest
+ continue-on-error: true
+
steps:
- name: "Checkout"
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- - name: "Set up Python 3.7"
- uses: actions/setup-python@v1
+ - name: "Set-up PHP"
+ uses: shivammathur/setup-php@v2
with:
- python-version: '3.7' # Semantic version range syntax or exact version of a Python version
+ php-version: 8.4
+ coverage: none
- - name: "Display Python version"
- run: python -c "import sys; print(sys.version)"
+ - name: Get composer cache directory
+ id: composercache
+ working-directory: _build
+ run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- - name: "Install Sphinx dependencies"
- run: sudo apt-get install python-dev build-essential
-
- - name: "Cache pip"
- uses: actions/cache@v2
+ - name: Cache dependencies
+ uses: actions/cache@v3
with:
- path: ~/.cache/pip
- key: ${{ runner.os }}-pip-${{ hashFiles('_build/.requirements.txt') }}
- restore-keys: |
- ${{ runner.os }}-pip-
+ path: ${{ steps.composercache.outputs.dir }}
+ key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
+ restore-keys: ${{ runner.os }}-composer-
- - name: "Install Sphinx + requirements via pip"
- run: pip install -r _build/.requirements.txt
+ - name: "Install dependencies"
+ working-directory: _build
+ run: composer install --prefer-dist --no-progress
- - name: "Build documentation"
- run: make -C _build SPHINXOPTS="-nqW -j auto" html
+ - name: "Build the docs"
+ working-directory: _build
+ run: php build.php --disable-cache
doctor-rst:
- name: DOCtor-RST
+ name: Lint (DOCtor-RST)
runs-on: ubuntu-latest
steps:
- name: "Checkout"
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: "Create cache dir"
run: mkdir .cache
- name: "Extract base branch name"
- run: echo "##[set-output name=branch;]$(echo ${GITHUB_BASE_REF:=${GITHUB_REF##*/}})"
+ run: echo "branch=$(echo ${GITHUB_BASE_REF:=${GITHUB_REF##*/}})" >> $GITHUB_OUTPUT
id: extract_base_branch
- name: "Cache DOCtor-RST"
- uses: actions/cache@v1
+ uses: actions/cache@v3
with:
path: .cache
key: ${{ runner.os }}-doctor-rst-${{ steps.extract_base_branch.outputs.branch }}
- name: "Run DOCtor-RST"
- uses: docker://oskarstark/doctor-rst
+ uses: docker://oskarstark/doctor-rst:1.67.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@v4
+ with:
+ path: 'docs'
+
+ - name: Set-up PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: 8.4
+ 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:
- args: --short --cache-file=/github/workspace/.cache/doctor-rst.cache
+ 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/.gitignore b/.gitignore
index 6a20088680a..b69047f69a1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,2 @@
-/_build/doctrees
-/_build/spelling
-/_build/html
-*.pyc
+/_build/vendor
+/_build/output
diff --git a/.symfony.cloud.yaml b/.symfony.cloud.yaml
deleted file mode 100644
index faa3c24780e..00000000000
--- a/.symfony.cloud.yaml
+++ /dev/null
@@ -1,58 +0,0 @@
-# This file describes an application. You can have multiple applications
-# in the same project.
-
-# The name of this app. Must be unique within a project.
-name: symfonydocs
-
-# The toolstack used to build the application.
-type: "python:3.7"
-
-# The configuration of app when it is exposed to the web.
-web:
- # The public directory of the app, relative to its root.
- document_root: "/_build/html"
- index_files:
- - index.html
- whitelist:
- - \.html$
- - \.txt$
-
- # CSS and Javascript.
- - \.css$
- - \.js$
- - \.hbs$
-
- # image/* types.
- - \.gif$
- - \.png$
- - \.ico$
- - \.svgz?$
-
- # fonts types.
- - \.ttf$
- - \.eot$
- - \.woff$
- - \.otf$
-
- # robots.txt.
- - /robots\.txt$
-
-# The size of the persistent disk of the application (in MB).
-disk: 512
-
-# Build time dependencies.
-dependencies:
- python:
- virtualenv: 15.1.0
-
-# The hooks that will be performed when the package is deployed.
-hooks:
- build: |
- virtualenv .virtualenv
- . .virtualenv/bin/activate
- # SymfonyCloud currently sets PIP_USER=1.
- export PIP_USER=
- pip install pip==9.0.1 wheel==0.29.0
- pip install -r _build/.requirements.txt
- find .virtualenv -type f -name "*.rst" -delete
- make -C _build html
diff --git a/.symfony/routes.yaml b/.symfony/routes.yaml
deleted file mode 100644
index caf4875f732..00000000000
--- a/.symfony/routes.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-https://{default}/:
- cache:
- cookies:
- - '*'
- default_ttl: 0
- enabled: true
- headers:
- - Accept
- - Accept-Language
- type: upstream
- upstream: symfonydocs:http
diff --git a/.symfony/services.yaml b/.symfony/services.yaml
deleted file mode 100644
index ec9369f2b00..00000000000
--- a/.symfony/services.yaml
+++ /dev/null
@@ -1 +0,0 @@
-# Keeping this file empty to not deploy unused services.
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
deleted file mode 100644
index d211dd419d0..00000000000
--- a/CODE_OF_CONDUCT.md
+++ /dev/null
@@ -1,83 +0,0 @@
-Code of Conduct
-===============
-
-Our Pledge
-----------
-
-In the interest of fostering an open and welcoming environment, we as
-contributors and maintainers pledge to making participation in our project and
-our community a harassment-free experience for everyone, regardless of age, body
-size, disability, ethnic origin, gender identity and expression, level of
-experience, education, socio-economic status, nationality, personal appearance,
-religion, or sexual identity and orientation.
-
-Our Standards
--------------
-
-Examples of behavior that contributes to creating a positive environment
-include:
-
-* Using welcoming and inclusive language
-* Being respectful of differing viewpoints and experiences
-* Gracefully accepting constructive criticism
-* Focusing on what is best for the community
-* Showing empathy towards other community members
-
-Examples of unacceptable behavior by participants include:
-
-* The use of sexualized language or imagery and unwelcome sexual attention or
- advances
-* Trolling, insulting/derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or electronic
- address, without explicit permission
-* Other conduct which could reasonably be considered inappropriate in a
- professional setting
-
-Our Responsibilities
---------------------
-
-[CoC Active Response Ensurers, or CARE][1], are responsible for clarifying the
-standards of acceptable behavior and are expected to take appropriate and fair
-corrective action in response to any instances of unacceptable behavior.
-
-CARE team members have the right and responsibility to remove, edit, or reject
-comments, commits, code, wiki edits, issues, and other contributions that are
-not aligned to this Code of Conduct, or to ban temporarily or permanently any
-contributor for other behaviors that they deem inappropriate, threatening,
-offensive, or harmful.
-
-Scope
------
-
-This Code of Conduct applies both within project spaces and in public spaces
-when an individual is representing the project or its community. Examples of
-representing a project or community include using an official project e-mail
-address, posting via an official social media account, or acting as an appointed
-representative at an online or offline event. Representation of a project may be
-further defined and clarified by CARE team members.
-
-Enforcement
------------
-
-Instances of abusive, harassing, or otherwise unacceptable behavior
-[may be reported][2] by contacting the [CARE team members][1].
-All complaints will be reviewed and investigated and will result in a response
-that is deemed necessary and appropriate to the circumstances. The CARE team is
-obligated to maintain confidentiality with regard to the reporter of an
-incident. Further details of specific enforcement policies may be posted
-separately.
-
-CARE team members who do not follow or enforce the Code of Conduct in good
-faith may face temporary or permanent repercussions as determined by the
-[core team][3].
-
-Attribution
------------
-
-This Code of Conduct is adapted from the [Contributor Covenant version 1.4][4].
-
-[1]: https://symfony.com/doc/current/contributing/code_of_conduct/care_team.html
-[2]: https://symfony.com/doc/current/contributing/code_of_conduct/reporting_guidelines.html
-[3]: https://symfony.com/doc/current/contributing/code/core_team.html
-[4]: https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index c1e63debe91..00000000000
--- a/Dockerfile
+++ /dev/null
@@ -1,16 +0,0 @@
-FROM python:2-stretch as builder
-
-WORKDIR /www
-
-COPY ./_build/.requirements.txt _build/
-
-RUN pip install pip==9.0.1 wheel==0.29.0 \
- && pip install -r _build/.requirements.txt
-
-COPY . /www
-
-RUN make -C _build html
-
-FROM nginx:latest
-
-COPY --from=builder /www/_build/html /usr/share/nginx/html
diff --git a/LICENSE.md b/LICENSE.md
index 01524e6ec84..547ac103984 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -195,7 +195,7 @@ b. You may Distribute or Publicly Perform an Adaptation only under the terms of:
(i) this License; (ii) a later version of this License with the same License
Elements as this License; (iii) a Creative Commons jurisdiction license (either
this or a later license version) that contains the same License Elements as this
-License (e.g., Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons
+License (e.g. Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons
Compatible License. If you license the Adaptation under one of the licenses
mentioned in (iv), you must comply with the terms of that license. If you
license the Adaptation under the terms of any of the licenses mentioned in (i),
@@ -221,7 +221,7 @@ Collections, You must, unless a request has been made pursuant to Section 4(a),
keep intact all copyright notices for the Work and provide, reasonable to the
medium or means You are utilizing: (i) the name of the Original Author (or
pseudonym, if applicable) if supplied, and/or if the Original Author and/or
-Licensor designate another party or parties (e.g., a sponsor institute,
+Licensor designate another party or parties (e.g. a sponsor institute,
publishing entity, journal) for attribution ("Attribution Parties") in
Licensor's copyright notice, terms of service or by other reasonable means, the
name of such party or parties; (ii) the title of the Work if supplied; (iii) to
@@ -229,7 +229,7 @@ the extent reasonably practicable, the URI, if any, that Licensor specifies to
be associated with the Work, unless such URI does not refer to the copyright
notice or licensing information for the Work; and (iv) , consistent with Section
3(b), in the case of an Adaptation, a credit identifying the use of the Work in
-the Adaptation (e.g., "French translation of the Work by Original Author," or
+the Adaptation (e.g. "French translation of the Work by Original Author," or
"Screenplay based on original Work by Original Author"). The credit required by
this Section 4(c) may be implemented in any reasonable manner; provided,
however, that in the case of a Adaptation or Collection, at a minimum such
diff --git a/README.markdown b/README.markdown
deleted file mode 100644
index 1d94f6f1ff0..00000000000
--- a/README.markdown
+++ /dev/null
@@ -1,39 +0,0 @@
-Symfony Documentation
-=====================
-
-This documentation is rendered online at https://symfony.com/doc/current/
-
-Contributing
-------------
-
-We love contributors! For more information on how you can contribute to the
-Symfony documentation, please read
-[Contributing to the Documentation](https://symfony.com/doc/current/contributing/documentation/overview.html)
-
-> **Note**
-> Unless you're documenting a feature that was introduced *after* Symfony 3.4
-> (e.g. in Symfony 4.4), all pull requests must be based off of the **3.4** branch,
-> **not** the master or older branches.
-
-SymfonyCloud
-------------
-
-Thanks to [SymfonyCloud](https://symfony.com/cloud) for providing an integration
-server where Pull Requests are built and can be reviewed by contributors.
-
-Docker
-------
-
-You can build the doc locally with these commands:
-
-```bash
-# build the image...
-$ docker build . -t symfony-docs
-
-# ...and start the local web server
-# (if it's already in use, change the '8080' port by any other port)
-$ docker run --rm -p 8080:80 symfony-docs
-```
-
-You can now read the docs at http://127.0.0.1:8080 (if you use a virtual
-machine, browse its IP instead of localhost; e.g. `http://192.168.99.100:8080`).
diff --git a/README.md b/README.md
new file mode 100644
index 00000000000..5c063058c02
--- /dev/null
+++ b/README.md
@@ -0,0 +1,56 @@
+
+
+
+
+
+ The official Symfony Documentation
+
+
+
+
+ Online version
+
+ |
+
+ Components
+
+ |
+
+ Screencasts
+
+
+
+Contributing
+------------
+
+We love contributors! For more information on how you can contribute, please read
+the [Symfony Docs Contributing Guide](https://symfony.com/doc/current/contributing/documentation/overview.html).
+
+> [!IMPORTANT]
+> 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
+---------------------------
+
+This is not needed for contributing, but it's useful if you would like to debug some
+issue in the docs or if you want to read Symfony Documentation offline.
+
+```bash
+$ git clone git@github.com:symfony/symfony-docs.git
+
+$ cd symfony-docs/
+$ cd _build/
+
+$ composer install
+
+$ php build.php
+```
+
+After generating docs, serve them with the internal PHP server:
+
+```bash
+$ php -S localhost:8000 -t output/
+```
+
+Browse `http://localhost:8000` to read the docs.
diff --git a/_build/.requirements.txt b/_build/.requirements.txt
deleted file mode 100644
index 47f076e9403..00000000000
--- a/_build/.requirements.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-docutils==0.13.1
-Pygments==2.2.0
-sphinx==1.8.5
-git+https://github.com/fabpot/sphinx-php.git@v2.0.0#egg_name=sphinx-php
-jsx-lexer===0.0.8
-sphinx_rtd_theme==0.5.0
diff --git a/_build/Makefile b/_build/Makefile
deleted file mode 100644
index 25b660056fe..00000000000
--- a/_build/Makefile
+++ /dev/null
@@ -1,153 +0,0 @@
-# Makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line.
-SPHINXOPTS =
-SPHINXBUILD = sphinx-build
-PAPER =
-BUILDDIR = .
-
-# Internal variables.
-PAPEROPT_a4 = -D latex_paper_size=a4
-PAPEROPT_letter = -D latex_paper_size=letter
-ALLSPHINXOPTS = -c $(BUILDDIR) -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) ../
-# the i18n builder cannot share the environment and doctrees with the others
-I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-
-.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
-
-help:
- @echo "Please use \`make ' where is one of"
- @echo " html to make standalone HTML files"
- @echo " dirhtml to make HTML files named index.html in directories"
- @echo " singlehtml to make a single large HTML file"
- @echo " pickle to make pickle files"
- @echo " json to make JSON files"
- @echo " htmlhelp to make HTML files and a HTML help project"
- @echo " qthelp to make HTML files and a qthelp project"
- @echo " devhelp to make HTML files and a Devhelp project"
- @echo " epub to make an epub"
- @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
- @echo " latexpdf to make LaTeX files and run them through pdflatex"
- @echo " text to make text files"
- @echo " man to make manual pages"
- @echo " texinfo to make Texinfo files"
- @echo " info to make Texinfo files and run them through makeinfo"
- @echo " gettext to make PO message catalogs"
- @echo " changes to make an overview of all changed/added/deprecated items"
- @echo " linkcheck to check all external links for integrity"
- @echo " doctest to run all doctests embedded in the documentation (if enabled)"
-
-clean:
- -rm -rf $(BUILDDIR)/*
-
-html:
- $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
- @echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
-
-dirhtml:
- $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
- @echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
-
-singlehtml:
- $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
- @echo
- @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
-
-pickle:
- $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
- @echo
- @echo "Build finished; now you can process the pickle files."
-
-json:
- $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
- @echo
- @echo "Build finished; now you can process the JSON files."
-
-htmlhelp:
- $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
- @echo
- @echo "Build finished; now you can run HTML Help Workshop with the" \
- ".hhp project file in $(BUILDDIR)/htmlhelp."
-
-qthelp:
- $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
- @echo
- @echo "Build finished; now you can run "qcollectiongenerator" with the" \
- ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
- @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Symfony.qhcp"
- @echo "To view the help file:"
- @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Symfony.qhc"
-
-devhelp:
- $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
- @echo
- @echo "Build finished."
- @echo "To view the help file:"
- @echo "# mkdir -p $$HOME/.local/share/devhelp/Symfony"
- @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Symfony"
- @echo "# devhelp"
-
-epub:
- $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
- @echo
- @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
-
-latex:
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
- @echo
- @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
- @echo "Run \`make' in that directory to run these through (pdf)latex" \
- "(use \`make latexpdf' here to do that automatically)."
-
-latexpdf:
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
- @echo "Running LaTeX files through pdflatex..."
- $(MAKE) -C $(BUILDDIR)/latex all-pdf
- @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
-
-text:
- $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
- @echo
- @echo "Build finished. The text files are in $(BUILDDIR)/text."
-
-man:
- $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
- @echo
- @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
-
-texinfo:
- $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
- @echo
- @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
- @echo "Run \`make' in that directory to run these through makeinfo" \
- "(use \`make info' here to do that automatically)."
-
-info:
- $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
- @echo "Running Texinfo files through makeinfo..."
- make -C $(BUILDDIR)/texinfo info
- @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
-
-gettext:
- $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
- @echo
- @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
-
-changes:
- $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
- @echo
- @echo "The overview file is in $(BUILDDIR)/changes."
-
-linkcheck:
- $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
- @echo
- @echo "Link check complete; look for any errors in the above output " \
- "or in $(BUILDDIR)/linkcheck/output.txt."
-
-doctest:
- $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
- @echo "Testing of doctests in the sources finished, look at the " \
- "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/_build/_exts/symfonycom/__init__.py b/_build/_exts/symfonycom/__init__.py
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/_build/_exts/symfonycom/sphinx/__init__.py b/_build/_exts/symfonycom/sphinx/__init__.py
deleted file mode 100644
index 4a61e711809..00000000000
--- a/_build/_exts/symfonycom/sphinx/__init__.py
+++ /dev/null
@@ -1,86 +0,0 @@
-from pygments.style import Style
-from pygments.token import Keyword, Name, Comment, String, Error, \
- Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
-
-class SensioStyle(Style):
- background_color = "#000000"
- default_style = ""
-
- styles = {
- # No corresponding class for the following:
- #Text: "", # class: ''
- Whitespace: "underline #f8f8f8", # class: 'w'
- Error: "#a40000 border:#ef2929", # class: 'err'
- Other: "#ffffff", # class 'x'
-
- Comment: "italic #B729D9", # class: 'c'
- Comment.Single: "italic #B729D9", # class: 'c1'
- Comment.Multiline: "italic #B729D9", # class: 'cm'
- Comment.Preproc: "noitalic #aaa", # class: 'cp'
-
- Keyword: "#FF8400", # class: 'k'
- Keyword.Constant: "#FF8400", # class: 'kc'
- Keyword.Declaration: "#FF8400", # class: 'kd'
- Keyword.Namespace: "#FF8400", # class: 'kn'
- Keyword.Pseudo: "#FF8400", # class: 'kp'
- Keyword.Reserved: "#FF8400", # class: 'kr'
- Keyword.Type: "#FF8400", # class: 'kt'
-
- Operator: "#E0882F", # class: 'o'
- Operator.Word: "#E0882F", # class: 'ow' - like keywords
-
- Punctuation: "#999999", # class: 'p'
-
- # because special names such as Name.Class, Name.Function, etc.
- # are not recognized as such later in the parsing, we choose them
- # to look the same as ordinary variables.
- Name: "#ffffff", # class: 'n'
- Name.Attribute: "#ffffff", # class: 'na' - to be revised
- Name.Builtin: "#ffffff", # class: 'nb'
- Name.Builtin.Pseudo: "#3465a4", # class: 'bp'
- Name.Class: "#ffffff", # class: 'nc' - to be revised
- Name.Constant: "#ffffff", # class: 'no' - to be revised
- Name.Decorator: "#888", # class: 'nd' - to be revised
- Name.Entity: "#ce5c00", # class: 'ni'
- Name.Exception: "#cc0000", # class: 'ne'
- Name.Function: "#ffffff", # class: 'nf'
- Name.Property: "#ffffff", # class: 'py'
- Name.Label: "#f57900", # class: 'nl'
- Name.Namespace: "#ffffff", # class: 'nn' - to be revised
- Name.Other: "#ffffff", # class: 'nx'
- Name.Tag: "#cccccc", # class: 'nt' - like a keyword
- Name.Variable: "#ffffff", # class: 'nv' - to be revised
- Name.Variable.Class: "#ffffff", # class: 'vc' - to be revised
- Name.Variable.Global: "#ffffff", # class: 'vg' - to be revised
- Name.Variable.Instance: "#ffffff", # class: 'vi' - to be revised
-
- Number: "#1299DA", # class: 'm'
-
- Literal: "#ffffff", # class: 'l'
- Literal.Date: "#ffffff", # class: 'ld'
-
- String: "#56DB3A", # class: 's'
- String.Backtick: "#56DB3A", # class: 'sb'
- String.Char: "#56DB3A", # class: 'sc'
- String.Doc: "italic #B729D9", # class: 'sd' - like a comment
- String.Double: "#56DB3A", # class: 's2'
- String.Escape: "#56DB3A", # class: 'se'
- String.Heredoc: "#56DB3A", # class: 'sh'
- String.Interpol: "#56DB3A", # class: 'si'
- String.Other: "#56DB3A", # class: 'sx'
- String.Regex: "#56DB3A", # class: 'sr'
- String.Single: "#56DB3A", # class: 's1'
- String.Symbol: "#56DB3A", # class: 'ss'
-
- Generic: "#ffffff", # class: 'g'
- Generic.Deleted: "#a40000", # class: 'gd'
- Generic.Emph: "italic #ffffff", # class: 'ge'
- Generic.Error: "#ef2929", # class: 'gr'
- Generic.Heading: "#000080", # class: 'gh'
- Generic.Inserted: "#00A000", # class: 'gi'
- Generic.Output: "#888", # class: 'go'
- Generic.Prompt: "#745334", # class: 'gp'
- Generic.Strong: "bold #ffffff", # class: 'gs'
- Generic.Subheading: "bold #800080", # class: 'gu'
- Generic.Traceback: "bold #a40000", # class: 'gt'
- }
diff --git a/_build/_exts/symfonycom/sphinx/lexer.py b/_build/_exts/symfonycom/sphinx/lexer.py
deleted file mode 100644
index f1e87066236..00000000000
--- a/_build/_exts/symfonycom/sphinx/lexer.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from pygments.lexer import RegexLexer, bygroups, using
-from pygments.token import *
-from pygments.lexers.shell import BashLexer, BatchLexer
-
-class TerminalLexer(RegexLexer):
- name = 'Terminal'
- aliases = ['terminal']
- filenames = []
-
- tokens = {
- 'root': [
- ('^\$', Generic.Prompt, 'bash-prompt'),
- ('^>', Generic.Prompt, 'dos-prompt'),
- ('^#.+$', Comment.Single),
- ('^.+$', Generic.Output),
- ],
- 'bash-prompt': [
- ('(.+)$', bygroups(using(BashLexer)), '#pop')
- ],
- 'dos-prompt': [
- ('(.+)$', bygroups(using(BatchLexer)), '#pop')
- ],
- }
diff --git a/_build/_static/rtd_custom.css b/_build/_static/rtd_custom.css
deleted file mode 100644
index 01298437755..00000000000
--- a/_build/_static/rtd_custom.css
+++ /dev/null
@@ -1,23 +0,0 @@
-body {
- font-family:Lucida Grande,Lucida Sans Unicode,Lucida Sans,Geneva,Verdana,sans-serif !important;
-}
-
-h1, h2, h3, h4, h5, h6 {
- font-family:Georgia,Times New Roman,Times,serif !important;
- line-height:1.2 !important;
- margin-top:0 !important;
- margin-bottom:.5em !important;
-}
-p, .rst-content li{
- font-size:14px !important;
- line-height:1.45 !important;
-}
-.wy-menu-vertical a {
- font-size:14px !important;
- padding-right:0 !important;
-}
-
-.highlight {
- background:#1e2125 !important;
- color:#fafafa !important;
-}
diff --git a/_build/_static/symfony-logo.svg b/_build/_static/symfony-logo.svg
deleted file mode 100644
index 828c2b297b0..00000000000
--- a/_build/_static/symfony-logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/_build/build.php b/_build/build.php
new file mode 100755
index 00000000000..b684700a848
--- /dev/null
+++ b/_build/build.php
@@ -0,0 +1,91 @@
+#!/usr/bin/env php
+register('build-docs')
+ ->addOption('generate-fjson-files', null, InputOption::VALUE_NONE, 'Use this option to generate docs both in HTML and JSON formats')
+ ->addOption('disable-cache', null, InputOption::VALUE_NONE, 'Use this option to force a full regeneration of all doc contents')
+ ->setCode(function(InputInterface $input, OutputInterface $output) {
+ // the doc building app doesn't work on Windows
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $output->writeln('ERROR: The application that builds Symfony Docs does not support Windows. You can try using a Linux distribution via WSL (Windows Subsystem for Linux). ');
+
+ return 1;
+ }
+
+ $io = new SymfonyStyle($input, $output);
+ $io->text('Building all Symfony Docs...');
+
+ $outputDir = __DIR__.'/output';
+ $buildConfig = (new BuildConfig())
+ ->setSymfonyVersion('7.1')
+ ->setContentDir(__DIR__.'/..')
+ ->setOutputDir($outputDir)
+ ->setImagesDir(__DIR__.'/output/_images')
+ ->setImagesPublicPrefix('_images')
+ ->setTheme('rtd')
+ ;
+
+ $buildConfig->setExcludedPaths(['.github/', '_build/']);
+
+ if (!$generateJsonFiles = $input->getOption('generate-fjson-files')) {
+ $buildConfig->disableJsonFileGeneration();
+ }
+
+ if ($isCacheDisabled = $input->getOption('disable-cache')) {
+ $buildConfig->disableBuildCache();
+ }
+
+ $io->comment(sprintf('cache: %s / output file type(s): %s', $isCacheDisabled ? 'disabled' : 'enabled', $generateJsonFiles ? 'HTML and JSON' : 'HTML'));
+ if (!$isCacheDisabled) {
+ $io->comment('Tip: add the --disable-cache option to this command to force the re-build of all docs.');
+ }
+
+ $result = (new DocBuilder())->build($buildConfig);
+
+ if ($result->isSuccessful()) {
+ // fix assets URLs to make them absolute (otherwise, they don't work in subdirectories)
+ $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($outputDir));
+
+ foreach (new RegexIterator($iterator, '/^.+\.html$/i', RegexIterator::GET_MATCH) as $match) {
+ $htmlFilePath = array_shift($match);
+ $htmlContents = file_get_contents($htmlFilePath);
+
+ $htmlRelativeFilePath = str_replace($outputDir.'/', '', $htmlFilePath);
+ $subdirLevel = substr_count($htmlRelativeFilePath, '/');
+ $baseHref = str_repeat('../', $subdirLevel);
+
+ $htmlContents = str_replace('', ' ', $htmlContents);
+ $htmlContents = str_replace(' success(sprintf("The Symfony Docs were successfully built at %s", realpath($outputDir)));
+ } else {
+ $io->error(sprintf("There were some errors while building the docs:\n\n%s\n", $result->getErrorTrace()));
+ $io->newLine();
+ $io->comment('Tip: you can add the -v, -vv or -vvv flags to this command to get debug information.');
+
+ return 1;
+ }
+
+ return 0;
+ })
+ ->getApplication()
+ ->setDefaultCommand('build-docs', true)
+ ->run();
diff --git a/_build/composer.json b/_build/composer.json
new file mode 100644
index 00000000000..f77976b10f4
--- /dev/null
+++ b/_build/composer.json
@@ -0,0 +1,22 @@
+{
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "config": {
+ "platform": {
+ "php": "8.3"
+ },
+ "preferred-install": {
+ "*": "dist"
+ },
+ "sort-packages": true,
+ "allow-plugins": {
+ "symfony/flex": true
+ }
+ },
+ "require": {
+ "php": ">=8.3",
+ "symfony/console": "^6.2",
+ "symfony/process": "^6.2",
+ "symfony-tools/docs-builder": "^0.27"
+ }
+}
diff --git a/_build/composer.lock b/_build/composer.lock
new file mode 100644
index 00000000000..b9a4646f8ae
--- /dev/null
+++ b/_build/composer.lock
@@ -0,0 +1,1792 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "e38eca557458275428db96db370d2c74",
+ "packages": [
+ {
+ "name": "doctrine/event-manager",
+ "version": "2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/event-manager.git",
+ "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/event-manager/zipball/b680156fa328f1dfd874fd48c7026c41570b9c6e",
+ "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.1"
+ },
+ "conflict": {
+ "doctrine/common": "<2.9"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^12",
+ "phpstan/phpstan": "^1.8.8",
+ "phpunit/phpunit": "^10.5",
+ "vimeo/psalm": "^5.24"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Common\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ },
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com"
+ }
+ ],
+ "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.",
+ "homepage": "https://www.doctrine-project.org/projects/event-manager.html",
+ "keywords": [
+ "event",
+ "event dispatcher",
+ "event manager",
+ "event system",
+ "events"
+ ],
+ "support": {
+ "issues": "https://github.com/doctrine/event-manager/issues",
+ "source": "https://github.com/doctrine/event-manager/tree/2.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://www.doctrine-project.org/sponsorship.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://www.patreon.com/phpdoctrine",
+ "type": "patreon"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-05-22T20:47:39+00:00"
+ },
+ {
+ "name": "doctrine/rst-parser",
+ "version": "0.5.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/rst-parser.git",
+ "reference": "ca7f5f31f9ea58fde5aeffe0f7b8eb569e71a104"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/rst-parser/zipball/ca7f5f31f9ea58fde5aeffe0f7b8eb569e71a104",
+ "reference": "ca7f5f31f9ea58fde5aeffe0f7b8eb569e71a104",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/event-manager": "^1.0 || ^2.0",
+ "php": "^7.2 || ^8.0",
+ "symfony/filesystem": "^4.1 || ^5.0 || ^6.0 || ^7.0",
+ "symfony/finder": "^4.1 || ^5.0 || ^6.0 || ^7.0",
+ "symfony/polyfill-mbstring": "^1.0",
+ "symfony/string": "^5.3 || ^6.0 || ^7.0",
+ "symfony/translation-contracts": "^1.1 || ^2.0 || ^3.0",
+ "twig/twig": "^2.9 || ^3.3"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^11.0",
+ "gajus/dindent": "^2.0.2",
+ "phpstan/phpstan": "^1.9",
+ "phpstan/phpstan-deprecation-rules": "^1.0",
+ "phpstan/phpstan-phpunit": "^1.2",
+ "phpstan/phpstan-strict-rules": "^1.4",
+ "phpunit/phpunit": "^7.5 || ^8.0 || ^9.0",
+ "symfony/css-selector": "4.4 || ^5.2 || ^6.0 || ^7.0",
+ "symfony/dom-crawler": "4.4 || ^5.2 || ^6.0 || ^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\RST\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Grégoire Passault",
+ "email": "g.passault@gmail.com",
+ "homepage": "http://www.gregwar.com/"
+ },
+ {
+ "name": "Jonathan H. Wage",
+ "email": "jonwage@gmail.com",
+ "homepage": "https://jwage.com"
+ }
+ ],
+ "description": "PHP library to parse reStructuredText documents and generate HTML or LaTeX documents.",
+ "homepage": "https://github.com/doctrine/rst-parser",
+ "keywords": [
+ "html",
+ "latex",
+ "markup",
+ "parser",
+ "reStructuredText",
+ "rst"
+ ],
+ "support": {
+ "issues": "https://github.com/doctrine/rst-parser/issues",
+ "source": "https://github.com/doctrine/rst-parser/tree/0.5.6"
+ },
+ "time": "2024-01-14T11:02:23+00:00"
+ },
+ {
+ "name": "masterminds/html5",
+ "version": "2.9.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Masterminds/html5-php.git",
+ "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6",
+ "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.7-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Masterminds\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Matt Butcher",
+ "email": "technosophos@gmail.com"
+ },
+ {
+ "name": "Matt Farina",
+ "email": "matt@mattfarina.com"
+ },
+ {
+ "name": "Asmir Mustafic",
+ "email": "goetas@gmail.com"
+ }
+ ],
+ "description": "An HTML5 parser and serializer.",
+ "homepage": "http://masterminds.github.io/html5-php",
+ "keywords": [
+ "HTML5",
+ "dom",
+ "html",
+ "parser",
+ "querypath",
+ "serializer",
+ "xml"
+ ],
+ "support": {
+ "issues": "https://github.com/Masterminds/html5-php/issues",
+ "source": "https://github.com/Masterminds/html5-php/tree/2.9.0"
+ },
+ "time": "2024-03-31T07:05:07+00:00"
+ },
+ {
+ "name": "psr/container",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "homepage": "https://github.com/php-fig/container",
+ "keywords": [
+ "PSR-11",
+ "container",
+ "container-interface",
+ "container-interop",
+ "psr"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/container/issues",
+ "source": "https://github.com/php-fig/container/tree/2.0.2"
+ },
+ "time": "2021-11-05T16:47:00+00:00"
+ },
+ {
+ "name": "psr/log",
+ "version": "3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
+ "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/log/tree/3.0.2"
+ },
+ "time": "2024-09-11T13:17:53+00:00"
+ },
+ {
+ "name": "scrivo/highlight.php",
+ "version": "v9.18.1.10",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/scrivo/highlight.php.git",
+ "reference": "850f4b44697a2552e892ffe71490ba2733c2fc6e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/scrivo/highlight.php/zipball/850f4b44697a2552e892ffe71490ba2733c2fc6e",
+ "reference": "850f4b44697a2552e892ffe71490ba2733c2fc6e",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "php": ">=5.4"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8|^5.7",
+ "sabberworm/php-css-parser": "^8.3",
+ "symfony/finder": "^2.8|^3.4|^5.4",
+ "symfony/var-dumper": "^2.8|^3.4|^5.4"
+ },
+ "suggest": {
+ "ext-mbstring": "Allows highlighting code with unicode characters and supports language with unicode keywords"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "HighlightUtilities/functions.php"
+ ],
+ "psr-0": {
+ "Highlight\\": "",
+ "HighlightUtilities\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Geert Bergman",
+ "homepage": "http://www.scrivo.org/",
+ "role": "Project Author"
+ },
+ {
+ "name": "Vladimir Jimenez",
+ "homepage": "https://allejo.io",
+ "role": "Maintainer"
+ },
+ {
+ "name": "Martin Folkers",
+ "homepage": "https://twobrain.io",
+ "role": "Contributor"
+ }
+ ],
+ "description": "Server side syntax highlighter that supports 185 languages. It's a PHP port of highlight.js",
+ "keywords": [
+ "code",
+ "highlight",
+ "highlight.js",
+ "highlight.php",
+ "syntax"
+ ],
+ "support": {
+ "issues": "https://github.com/scrivo/highlight.php/issues",
+ "source": "https://github.com/scrivo/highlight.php"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/allejo",
+ "type": "github"
+ }
+ ],
+ "time": "2022-12-17T21:53:22+00:00"
+ },
+ {
+ "name": "symfony-tools/docs-builder",
+ "version": "0.27.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony-tools/docs-builder.git",
+ "reference": "720b52b2805122a4c08376496bd9661944c2624a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony-tools/docs-builder/zipball/720b52b2805122a4c08376496bd9661944c2624a",
+ "reference": "720b52b2805122a4c08376496bd9661944c2624a",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/rst-parser": "^0.5",
+ "ext-curl": "*",
+ "ext-json": "*",
+ "php": ">=8.3",
+ "scrivo/highlight.php": "^9.18.1",
+ "symfony/console": "^5.2 || ^6.0 || ^7.0",
+ "symfony/css-selector": "^5.2 || ^6.0 || ^7.0",
+ "symfony/dom-crawler": "^5.2 || ^6.0 || ^7.0",
+ "symfony/filesystem": "^5.2 || ^6.0 || ^7.0",
+ "symfony/finder": "^5.2 || ^6.0 || ^7.0",
+ "symfony/http-client": "^5.2 || ^6.0 || ^7.0",
+ "twig/twig": "^2.14 || ^3.3"
+ },
+ "require-dev": {
+ "gajus/dindent": "^2.0",
+ "masterminds/html5": "^2.7",
+ "symfony/phpunit-bridge": "^5.2 || ^6.0 || ^7.0",
+ "symfony/process": "^5.2 || ^6.0 || ^7.0"
+ },
+ "bin": [
+ "bin/docs-builder"
+ ],
+ "type": "project",
+ "autoload": {
+ "psr-4": {
+ "SymfonyDocsBuilder\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "The build system for Symfony's documentation",
+ "support": {
+ "issues": "https://github.com/symfony-tools/docs-builder/issues",
+ "source": "https://github.com/symfony-tools/docs-builder/tree/0.27.0"
+ },
+ "time": "2025-03-21T09:48:45+00:00"
+ },
+ {
+ "name": "symfony/console",
+ "version": "v6.4.17",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/console.git",
+ "reference": "799445db3f15768ecc382ac5699e6da0520a0a04"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/console/zipball/799445db3f15768ecc382ac5699e6da0520a0a04",
+ "reference": "799445db3f15768ecc382ac5699e6da0520a0a04",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/service-contracts": "^2.5|^3",
+ "symfony/string": "^5.4|^6.0|^7.0"
+ },
+ "conflict": {
+ "symfony/dependency-injection": "<5.4",
+ "symfony/dotenv": "<5.4",
+ "symfony/event-dispatcher": "<5.4",
+ "symfony/lock": "<5.4",
+ "symfony/process": "<5.4"
+ },
+ "provide": {
+ "psr/log-implementation": "1.0|2.0|3.0"
+ },
+ "require-dev": {
+ "psr/log": "^1|^2|^3",
+ "symfony/config": "^5.4|^6.0|^7.0",
+ "symfony/dependency-injection": "^5.4|^6.0|^7.0",
+ "symfony/event-dispatcher": "^5.4|^6.0|^7.0",
+ "symfony/http-foundation": "^6.4|^7.0",
+ "symfony/http-kernel": "^6.4|^7.0",
+ "symfony/lock": "^5.4|^6.0|^7.0",
+ "symfony/messenger": "^5.4|^6.0|^7.0",
+ "symfony/process": "^5.4|^6.0|^7.0",
+ "symfony/stopwatch": "^5.4|^6.0|^7.0",
+ "symfony/var-dumper": "^5.4|^6.0|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Console\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Eases the creation of beautiful and testable command line interfaces",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "cli",
+ "command-line",
+ "console",
+ "terminal"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/console/tree/v6.4.17"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-12-07T12:07:30+00:00"
+ },
+ {
+ "name": "symfony/css-selector",
+ "version": "v7.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/css-selector.git",
+ "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/css-selector/zipball/601a5ce9aaad7bf10797e3663faefce9e26c24e2",
+ "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\CssSelector\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Jean-François Simon",
+ "email": "jeanfrancois.simon@sensiolabs.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Converts CSS selectors to XPath expressions",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/css-selector/tree/v7.2.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-25T14:21:43+00:00"
+ },
+ {
+ "name": "symfony/deprecation-contracts",
+ "version": "v3.5.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/deprecation-contracts.git",
+ "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
+ "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.5-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "function.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "A generic function and convention to trigger deprecation notices",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-25T14:20:29+00:00"
+ },
+ {
+ "name": "symfony/dom-crawler",
+ "version": "v7.2.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/dom-crawler.git",
+ "reference": "19cc7b08efe9ad1ab1b56e0948e8d02e15ed3ef7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/19cc7b08efe9ad1ab1b56e0948e8d02e15ed3ef7",
+ "reference": "19cc7b08efe9ad1ab1b56e0948e8d02e15ed3ef7",
+ "shasum": ""
+ },
+ "require": {
+ "masterminds/html5": "^2.6",
+ "php": ">=8.2",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-mbstring": "~1.0"
+ },
+ "require-dev": {
+ "symfony/css-selector": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\DomCrawler\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Eases DOM navigation for HTML and XML documents",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/dom-crawler/tree/v7.2.4"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-02-17T15:53:07+00:00"
+ },
+ {
+ "name": "symfony/filesystem",
+ "version": "v7.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/filesystem.git",
+ "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb",
+ "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-mbstring": "~1.8"
+ },
+ "require-dev": {
+ "symfony/process": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Filesystem\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides basic utilities for the filesystem",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/filesystem/tree/v7.2.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-10-25T15:15:23+00:00"
+ },
+ {
+ "name": "symfony/finder",
+ "version": "v7.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/finder.git",
+ "reference": "87a71856f2f56e4100373e92529eed3171695cfb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/87a71856f2f56e4100373e92529eed3171695cfb",
+ "reference": "87a71856f2f56e4100373e92529eed3171695cfb",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "symfony/filesystem": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Finder\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Finds files and directories via an intuitive fluent interface",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/finder/tree/v7.2.2"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-12-30T19:00:17+00:00"
+ },
+ {
+ "name": "symfony/http-client",
+ "version": "v7.2.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/http-client.git",
+ "reference": "78981a2ffef6437ed92d4d7e2a86a82f256c6dc6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/http-client/zipball/78981a2ffef6437ed92d4d7e2a86a82f256c6dc6",
+ "reference": "78981a2ffef6437ed92d4d7e2a86a82f256c6dc6",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "psr/log": "^1|^2|^3",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/http-client-contracts": "~3.4.4|^3.5.2",
+ "symfony/service-contracts": "^2.5|^3"
+ },
+ "conflict": {
+ "amphp/amp": "<2.5",
+ "php-http/discovery": "<1.15",
+ "symfony/http-foundation": "<6.4"
+ },
+ "provide": {
+ "php-http/async-client-implementation": "*",
+ "php-http/client-implementation": "*",
+ "psr/http-client-implementation": "1.0",
+ "symfony/http-client-implementation": "3.0"
+ },
+ "require-dev": {
+ "amphp/http-client": "^4.2.1|^5.0",
+ "amphp/http-tunnel": "^1.0|^2.0",
+ "amphp/socket": "^1.1",
+ "guzzlehttp/promises": "^1.4|^2.0",
+ "nyholm/psr7": "^1.0",
+ "php-http/httplug": "^1.0|^2.0",
+ "psr/http-client": "^1.0",
+ "symfony/amphp-http-client-meta": "^1.0|^2.0",
+ "symfony/dependency-injection": "^6.4|^7.0",
+ "symfony/http-kernel": "^6.4|^7.0",
+ "symfony/messenger": "^6.4|^7.0",
+ "symfony/process": "^6.4|^7.0",
+ "symfony/rate-limiter": "^6.4|^7.0",
+ "symfony/stopwatch": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\HttpClient\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "http"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/http-client/tree/v7.2.4"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-02-13T10:27:23+00:00"
+ },
+ {
+ "name": "symfony/http-client-contracts",
+ "version": "v3.5.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/http-client-contracts.git",
+ "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ee8d807ab20fcb51267fdace50fbe3494c31e645",
+ "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.5-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\HttpClient\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Test/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to HTTP clients",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.2"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-12-07T08:49:48+00:00"
+ },
+ {
+ "name": "symfony/polyfill-ctype",
+ "version": "v1.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-ctype.git",
+ "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
+ "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "provide": {
+ "ext-ctype": "*"
+ },
+ "suggest": {
+ "ext-ctype": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Ctype\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Gert de Pagter",
+ "email": "BackEndTea@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for ctype functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "ctype",
+ "polyfill",
+ "portable"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-grapheme",
+ "version": "v1.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
+ "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe",
+ "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Grapheme\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's grapheme_* functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "grapheme",
+ "intl",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-normalizer",
+ "version": "v1.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
+ "reference": "3833d7255cc303546435cb650316bff708a1c75c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c",
+ "reference": "3833d7255cc303546435cb650316bff708a1c75c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Normalizer\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's Normalizer class and related functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "intl",
+ "normalizer",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
+ "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "provide": {
+ "ext-mbstring": "*"
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Mbstring\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for the Mbstring extension",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "mbstring",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/process",
+ "version": "v6.4.19",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "7a1c12e87b08ec9c97abdd188c9b3f5a40e37fc3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/7a1c12e87b08ec9c97abdd188c9b3f5a40e37fc3",
+ "reference": "7a1c12e87b08ec9c97abdd188c9b3f5a40e37fc3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Process\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Executes commands in sub-processes",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/process/tree/v6.4.19"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-02-04T13:35:48+00:00"
+ },
+ {
+ "name": "symfony/service-contracts",
+ "version": "v3.5.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/service-contracts.git",
+ "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0",
+ "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "psr/container": "^1.1|^2.0",
+ "symfony/deprecation-contracts": "^2.5|^3"
+ },
+ "conflict": {
+ "ext-psr": "<1.1|>=2"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.5-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Service\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Test/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to writing services",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/service-contracts/tree/v3.5.1"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-25T14:20:29+00:00"
+ },
+ {
+ "name": "symfony/string",
+ "version": "v7.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/string.git",
+ "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82",
+ "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-intl-grapheme": "~1.0",
+ "symfony/polyfill-intl-normalizer": "~1.0",
+ "symfony/polyfill-mbstring": "~1.0"
+ },
+ "conflict": {
+ "symfony/translation-contracts": "<2.5"
+ },
+ "require-dev": {
+ "symfony/emoji": "^7.1",
+ "symfony/error-handler": "^6.4|^7.0",
+ "symfony/http-client": "^6.4|^7.0",
+ "symfony/intl": "^6.4|^7.0",
+ "symfony/translation-contracts": "^2.5|^3.0",
+ "symfony/var-exporter": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "Resources/functions.php"
+ ],
+ "psr-4": {
+ "Symfony\\Component\\String\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "grapheme",
+ "i18n",
+ "string",
+ "unicode",
+ "utf-8",
+ "utf8"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/string/tree/v7.2.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-11-13T13:31:26+00:00"
+ },
+ {
+ "name": "symfony/translation-contracts",
+ "version": "v3.5.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/translation-contracts.git",
+ "reference": "4667ff3bd513750603a09c8dedbea942487fb07c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/4667ff3bd513750603a09c8dedbea942487fb07c",
+ "reference": "4667ff3bd513750603a09c8dedbea942487fb07c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.5-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Translation\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Test/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to translation",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/translation-contracts/tree/v3.5.1"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-25T14:20:29+00:00"
+ },
+ {
+ "name": "twig/twig",
+ "version": "v3.20.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/twigphp/Twig.git",
+ "reference": "3468920399451a384bef53cf7996965f7cd40183"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/twigphp/Twig/zipball/3468920399451a384bef53cf7996965f7cd40183",
+ "reference": "3468920399451a384bef53cf7996965f7cd40183",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1.0",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/polyfill-ctype": "^1.8",
+ "symfony/polyfill-mbstring": "^1.3"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^2.0",
+ "psr/container": "^1.0|^2.0",
+ "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/Resources/core.php",
+ "src/Resources/debug.php",
+ "src/Resources/escaper.php",
+ "src/Resources/string_loader.php"
+ ],
+ "psr-4": {
+ "Twig\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com",
+ "homepage": "http://fabien.potencier.org",
+ "role": "Lead Developer"
+ },
+ {
+ "name": "Twig Team",
+ "role": "Contributors"
+ },
+ {
+ "name": "Armin Ronacher",
+ "email": "armin.ronacher@active-4.com",
+ "role": "Project Founder"
+ }
+ ],
+ "description": "Twig, the flexible, fast, and secure template language for PHP",
+ "homepage": "https://twig.symfony.com",
+ "keywords": [
+ "templating"
+ ],
+ "support": {
+ "issues": "https://github.com/twigphp/Twig/issues",
+ "source": "https://github.com/twigphp/Twig/tree/v3.20.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/twig/twig",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-02-13T08:34:43+00:00"
+ }
+ ],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "dev",
+ "stability-flags": {},
+ "prefer-stable": true,
+ "prefer-lowest": false,
+ "platform": {
+ "php": ">=8.3"
+ },
+ "platform-dev": {},
+ "platform-overrides": {
+ "php": "8.3"
+ },
+ "plugin-api-version": "2.6.0"
+}
diff --git a/_build/conf.py b/_build/conf.py
deleted file mode 100644
index 49cc12581ad..00000000000
--- a/_build/conf.py
+++ /dev/null
@@ -1,301 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Symfony documentation build configuration file, created by
-# sphinx-quickstart on Sat Jul 28 21:58:57 2012.
-#
-# This file is execfile()d with the current directory set to its containing dir.
-#
-# Note that not all possible configuration values are present in this
-# autogenerated file.
-#
-# All configuration values have a default; values that are commented out
-# serve to show the default.
-
-import sys, os
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-sys.path.append(os.path.abspath('_exts'))
-
-# adding PhpLexer
-from sphinx.highlighting import lexers
-from pygments.lexers.compiled import CLexer
-from pygments.lexers.shell import BashLexer
-from pygments.lexers.special import TextLexer
-from pygments.lexers.text import RstLexer
-from pygments.lexers.web import PhpLexer
-from symfonycom.sphinx.lexer import TerminalLexer
-
-# -- General configuration -----------------------------------------------------
-
-# If your documentation needs a minimal Sphinx version, state it here.
-needs_sphinx = '1.8.5'
-
-# Add any Sphinx extension module names here, as strings. They can be extensions
-# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = [
- 'sphinx.ext.autodoc', 'sphinx.ext.doctest',
- 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.ifconfig',
- 'sphinx.ext.viewcode', 'sphinx.ext.extlinks',
- 'sensio.sphinx.codeblock', 'sensio.sphinx.configurationblock', 'sensio.sphinx.phpcode', 'sensio.sphinx.bestpractice'
- #,'sphinxcontrib.spelling'
-]
-
-#spelling_show_sugestions=True
-#spelling_lang='en_US'
-#spelling_word_list_filename='_build/spelling_word_list.txt'
-
-# Add any paths that contain templates here, relative to this directory.
-# templates_path = ['_theme/_templates']
-
-# The suffix of source filenames.
-source_suffix = '.rst'
-
-# The encoding of source files.
-#source_encoding = 'utf-8-sig'
-
-# The master toctree document.
-master_doc = 'index'
-
-# General information about the project.
-project = 'Symfony Framework Documentation'
-copyright = ''
-
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The short X.Y version.
-# version = '2.2'
-# The full version, including alpha/beta/rc tags.
-# release = '2.2.13'
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-#language = None
-
-# There are two options for replacing |today|: either, you set today to some
-# non-false value, then it is used:
-#today = ''
-# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-exclude_patterns = ['_build']
-
-# The reST default role (used for this markup: `text`) to use for all documents.
-#default_role = None
-
-# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
-
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
-#add_module_names = True
-
-# If true, sectionauthor and moduleauthor directives will be shown in the
-# output. They are ignored by default.
-#show_authors = False
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'symfonycom.sphinx.SensioStyle'
-
-# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
-
-# -- Settings for symfony doc extension ---------------------------------------------------
-
-# enable highlighting for PHP code not between ```` by default
-lexers['markdown'] = TextLexer()
-lexers['php'] = PhpLexer(startinline=True)
-lexers['php-annotations'] = PhpLexer(startinline=True)
-lexers['php-standalone'] = PhpLexer(startinline=True)
-lexers['php-symfony'] = PhpLexer(startinline=True)
-lexers['rst'] = RstLexer()
-lexers['varnish2'] = CLexer()
-lexers['varnish3'] = CLexer()
-lexers['varnish4'] = CLexer()
-lexers['terminal'] = TerminalLexer()
-lexers['env'] = BashLexer()
-
-config_block = {
- 'apache': 'Apache',
- 'markdown': 'Markdown',
- 'nginx': 'Nginx',
- 'rst': 'reStructuredText',
- 'varnish2': 'Varnish 2',
- 'varnish3': 'Varnish 3',
- 'varnish4': 'Varnish 4',
- 'env': '.env'
-}
-
-# don't enable Sphinx Domains
-primary_domain = None
-
-# set url for API links
-api_url = 'https://github.com/symfony/symfony/blob/master/src/%s.php'
-
-
-# -- Options for HTML output ---------------------------------------------------
-
-# The theme to use for HTML and HTML Help pages. See the documentation for
-# a list of builtin themes.
-html_theme = "sphinx_rtd_theme"
-
-# Theme options are theme-specific and customize the look and feel of a theme
-# further. For a list of options available for each theme, see the
-# documentation.
-html_theme_options = {
- 'logo_only': True,
- 'prev_next_buttons_location': None,
- 'style_nav_header_background': '#f0f0f0'
-}
-
-# Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
-
-# The name for this set of Sphinx documents. If None, it defaults to
-# " v documentation".
-#html_title = None
-
-# A shorter title for the navigation bar. Default is the same as html_title.
-#html_short_title = None
-
-# The name of an image file (relative to this directory) to place at the top
-# of the sidebar.
-html_logo = '_static/symfony-logo.svg'
-
-# The name of an image file (within the static path) to use as favicon of the
-# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
-# pixels large.
-#html_favicon = None
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
-html_css_files = ['rtd_custom.css']
-
-# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
-# using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
-
-# If true, SmartyPants will be used to convert quotes and dashes to
-# typographically correct entities.
-#html_use_smartypants = True
-
-# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
-
-# Additional templates that should be rendered to pages, maps page names to
-# template names.
-#html_additional_pages = {}
-
-# If false, no module index is generated.
-#html_domain_indices = True
-
-# If false, no index is generated.
-#html_use_index = True
-
-# If true, the index is split into individual pages for each letter.
-#html_split_index = False
-
-# If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
-
-# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
-
-# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
-
-# If true, an OpenSearch description file will be output, and all pages will
-# contain a tag referring to it. The value of this option must be the
-# base URL from which the finished HTML is served.
-#html_use_opensearch = ''
-
-# This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = 'SymfonyDoc'
-
-
-# -- Options for LaTeX output --------------------------------------------------
-
-latex_elements = {
-# The paper size ('letterpaper' or 'a4paper').
-#'papersize': 'letterpaper',
-
-# The font size ('10pt', '11pt' or '12pt').
-#'pointsize': '10pt',
-
-# Additional stuff for the LaTeX preamble.
-#'preamble': '',
-}
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title, author, documentclass [howto/manual]).
-latex_documents = [
- ('index', 'Symfony.tex', u'Symfony Documentation',
- u'Symfony community', 'manual'),
-]
-
-# The name of an image file (relative to this directory) to place at the top of
-# the title page.
-#latex_logo = None
-
-# For "manual" documents, if this is true, then toplevel headings are parts,
-# not chapters.
-#latex_use_parts = False
-
-# If true, show page references after internal links.
-#latex_show_pagerefs = False
-
-# If true, show URL addresses after external links.
-#latex_show_urls = False
-
-# Documents to append as an appendix to all manuals.
-#latex_appendices = []
-
-# If false, no module index is generated.
-#latex_domain_indices = True
-
-
-# -- Options for manual page output --------------------------------------------
-
-# One entry per manual page. List of tuples
-# (source start file, name, description, authors, manual section).
-man_pages = [
- ('index', 'symfony', u'Symfony Documentation',
- [u'Symfony community'], 1)
-]
-
-# If true, show URL addresses after external links.
-#man_show_urls = False
-
-
-# -- Options for Texinfo output ------------------------------------------------
-
-# Grouping the document tree into Texinfo files. List of tuples
-# (source start file, target name, title, author,
-# dir menu entry, description, category)
-texinfo_documents = [
- ('index', 'Symfony', u'Symfony Documentation',
- u'Symfony community', 'Symfony', 'One line description of project.',
- 'Miscellaneous'),
-]
-
-# Documents to append as an appendix to all manuals.
-#texinfo_appendices = []
-
-# If false, no module index is generated.
-#texinfo_domain_indices = True
-
-# How to display URL addresses: 'footnote', 'no', or 'inline'.
-#texinfo_show_urls = 'footnote'
-
-# Use PHP syntax highlighting in code examples by default
-highlight_language='php'
diff --git a/_build/maintainer_guide.rst b/_build/maintainer_guide.rst
index 7eff3143941..9758b4e7397 100644
--- a/_build/maintainer_guide.rst
+++ b/_build/maintainer_guide.rst
@@ -39,14 +39,14 @@ contributes again, it's OK to mention some of the minor issues to educate them.
$ gh merge 11059
- Working on symfony/symfony-docs (branch master)
+ Working on symfony/symfony-docs (branch 6.2)
Merging Pull Request 11059: dmaicher/patch-3
...
# This is important!! Say NO to push the changes now
Push the changes now? (Y/n) n
- Now, push with: git push gh "master" refs/notes/github-comments
+ Now, push with: git push gh "6.2" refs/notes/github-comments
# Now, open your editor and make the needed changes ...
@@ -54,7 +54,7 @@ contributes again, it's OK to mention some of the minor issues to educate them.
# Use "Minor reword", "Minor tweak", etc. as the commit message
# now run the 'push' command shown above by 'gh' (it's different each time)
- $ git push gh "master" refs/notes/github-comments
+ $ git push gh "6.2" refs/notes/github-comments
Merging Pull Requests
---------------------
@@ -335,6 +335,43 @@ in the tree as follows:
$ git push origin
$ git push upstream
+Merging in the wrong branch
+...........................
+
+A Pull Request was made against ``5.x`` but it should be merged in ``5.1`` and you
+forgot to merge as ``gh merge NNNNN -s 5.1`` to change the merge branch. Solution:
+
+.. code-block:: terminal
+
+ $ git checkout 5.1
+ $ git cherry-pick -m 1
+ $ git checkout 5.x
+ $ git revert -m 1
+ # now continue with the normal "upmerging"
+ $ git checkout 5.2
+ $ git merge 5.1
+ $ ...
+
+Merging while the target branch changed
+.......................................
+
+Sometimes, someone else merges a PR in ``5.x`` at the same time as you are
+doing it. In these cases, ``gh merge ...`` fails to push. Solve this by
+resetting your local branch and restarting the merge:
+
+.. code-block:: terminal
+
+ $ gh merge ...
+ # this failed
+
+ # fetch the updated 5.x branch from GitHub
+ $ git fetch upstream
+ $ git checkout 5.x
+ $ git reset --hard upstream/5.x
+
+ # restart the merge
+ $ gh merge ...
+
.. _`symfony/symfony-docs`: https://github.com/symfony/symfony-docs
.. _`Symfony Docs team`: https://github.com/orgs/symfony/teams/team-symfony-docs
.. _`Symfony's respectful review comments`: https://symfony.com/doc/current/contributing/community/review-comments.html
diff --git a/_build/make.bat b/_build/make.bat
deleted file mode 100644
index 6d3f205272f..00000000000
--- a/_build/make.bat
+++ /dev/null
@@ -1,263 +0,0 @@
-@ECHO OFF
-
-REM Command file for Sphinx documentation
-
-if "%SPHINXBUILD%" == "" (
- set SPHINXBUILD=sphinx-build
-)
-set BUILDDIR=.
-set ALLSPHINXOPTS=-c %BUILDDIR% -d %BUILDDIR%/doctrees %SPHINXOPTS% ..
-set I18NSPHINXOPTS=%SPHINXOPTS% .
-if NOT "%PAPER%" == "" (
- set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
- set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
-)
-
-if "%1" == "" goto help
-
-if "%1" == "help" (
- :help
- echo.Please use `make ^` where ^ is one of
- echo. html to make standalone HTML files
- echo. dirhtml to make HTML files named index.html in directories
- echo. singlehtml to make a single large HTML file
- echo. pickle to make pickle files
- echo. json to make JSON files
- echo. htmlhelp to make HTML files and a HTML help project
- echo. qthelp to make HTML files and a qthelp project
- echo. devhelp to make HTML files and a Devhelp project
- echo. epub to make an epub
- echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
- echo. text to make text files
- echo. man to make manual pages
- echo. texinfo to make Texinfo files
- echo. gettext to make PO message catalogs
- echo. changes to make an overview over all changed/added/deprecated items
- echo. xml to make Docutils-native XML files
- echo. pseudoxml to make pseudoxml-XML files for display purposes
- echo. linkcheck to check all external links for integrity
- echo. doctest to run all doctests embedded in the documentation if enabled
- echo. coverage to run coverage check of the documentation if enabled
- goto end
-)
-
-if "%1" == "clean" (
- for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
- del /q /s %BUILDDIR%\*
- goto end
-)
-
-
-REM Check if sphinx-build is available and fallback to Python version if any
-%SPHINXBUILD% 2> nul
-if errorlevel 9009 goto sphinx_python
-goto sphinx_ok
-
-:sphinx_python
-
-set SPHINXBUILD=python -m sphinx.__init__
-%SPHINXBUILD% 2> nul
-if errorlevel 9009 (
- echo.
- echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
- echo.installed, then set the SPHINXBUILD environment variable to point
- echo.to the full path of the 'sphinx-build' executable. Alternatively you
- echo.may add the Sphinx directory to PATH.
- echo.
- echo.If you don't have Sphinx installed, grab it from
- echo.http://sphinx-doc.org/
- exit /b 1
-)
-
-:sphinx_ok
-
-
-if "%1" == "html" (
- %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The HTML pages are in %BUILDDIR%/html.
- goto end
-)
-
-if "%1" == "dirhtml" (
- %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
- goto end
-)
-
-if "%1" == "singlehtml" (
- %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
- goto end
-)
-
-if "%1" == "pickle" (
- %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can process the pickle files.
- goto end
-)
-
-if "%1" == "json" (
- %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can process the JSON files.
- goto end
-)
-
-if "%1" == "htmlhelp" (
- %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can run HTML Help Workshop with the ^
-.hhp project file in %BUILDDIR%/htmlhelp.
- goto end
-)
-
-if "%1" == "qthelp" (
- %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can run "qcollectiongenerator" with the ^
-.qhcp project file in %BUILDDIR%/qthelp, like this:
- echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Symfony.qhcp
- echo.To view the help file:
- echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Symfony.ghc
- goto end
-)
-
-if "%1" == "devhelp" (
- %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished.
- goto end
-)
-
-if "%1" == "epub" (
- %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The epub file is in %BUILDDIR%/epub.
- goto end
-)
-
-if "%1" == "latex" (
- %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
- goto end
-)
-
-if "%1" == "latexpdf" (
- %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
- cd %BUILDDIR%/latex
- make all-pdf
- cd %~dp0
- echo.
- echo.Build finished; the PDF files are in %BUILDDIR%/latex.
- goto end
-)
-
-if "%1" == "latexpdfja" (
- %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
- cd %BUILDDIR%/latex
- make all-pdf-ja
- cd %~dp0
- echo.
- echo.Build finished; the PDF files are in %BUILDDIR%/latex.
- goto end
-)
-
-if "%1" == "text" (
- %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The text files are in %BUILDDIR%/text.
- goto end
-)
-
-if "%1" == "man" (
- %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The manual pages are in %BUILDDIR%/man.
- goto end
-)
-
-if "%1" == "texinfo" (
- %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
- goto end
-)
-
-if "%1" == "gettext" (
- %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
- goto end
-)
-
-if "%1" == "changes" (
- %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
- if errorlevel 1 exit /b 1
- echo.
- echo.The overview file is in %BUILDDIR%/changes.
- goto end
-)
-
-if "%1" == "linkcheck" (
- %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
- if errorlevel 1 exit /b 1
- echo.
- echo.Link check complete; look for any errors in the above output ^
-or in %BUILDDIR%/linkcheck/output.txt.
- goto end
-)
-
-if "%1" == "doctest" (
- %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
- if errorlevel 1 exit /b 1
- echo.
- echo.Testing of doctests in the sources finished, look at the ^
-results in %BUILDDIR%/doctest/output.txt.
- goto end
-)
-
-if "%1" == "coverage" (
- %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
- if errorlevel 1 exit /b 1
- echo.
- echo.Testing of coverage in the sources finished, look at the ^
-results in %BUILDDIR%/coverage/python.txt.
- goto end
-)
-
-if "%1" == "xml" (
- %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The XML files are in %BUILDDIR%/xml.
- goto end
-)
-
-if "%1" == "pseudoxml" (
- %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
- goto end
-)
-
-:end
diff --git a/_build/redirection_map b/_build/redirection_map
index 4bbf0738ba5..ee14c191025 100644
--- a/_build/redirection_map
+++ b/_build/redirection_map
@@ -132,11 +132,6 @@
/cookbook/controller/upload_file /controller/upload_file
/cookbook/debugging /
/debug/debugging /
-/cookbook/deployment/azure-website /cookbook/azure-website
-/cookbook/deployment/fortrabbit /deployment/fortrabbit
-/cookbook/deployment/heroku /deployment/heroku
-/cookbook/deployment/index /deployment
-/cookbook/deployment/platformsh /deployment/platformsh
/cookbook/deployment/tools /deployment/tools
/cookbook/doctrine/common_extensions /doctrine/common_extensions
/cookbook/doctrine/console /doctrine
@@ -161,11 +156,13 @@
/cookbook/email/index /email
/cookbook/email/spool /email/spool
/cookbook/email/testing /email/testing
-/cookbook/event_dispatcher/before_after_filters /event_dispatcher/before_after_filters
+/cookbook/event_dispatcher/before_after_filters /event_dispatcher#event-dispatcher-before-after-filters
+/event_dispatcher/before_after_filters /event_dispatcher#event-dispatcher-before-after-filters
/cookbook/event_dispatcher/class_extension /event_dispatcher/class_extension
/cookbook/event_dispatcher/event_listener /event_dispatcher
/cookbook/event_dispatcher/index /event_dispatcher
/cookbook/event_dispatcher/method_behavior /event_dispatcher/method_behavior
+/event_dispatcher/method_behavior /event_dispatcher#event-dispatcher-method-behavior
/cookbook/expressions /security/expressions
/expressions /security/expressions
/cookbook/form/create_custom_field_type /form/create_custom_field_type
@@ -193,7 +190,8 @@
/cookbook/logging/monolog_console /logging/monolog_console
/cookbook/logging/monolog_email /logging/monolog_email
/cookbook/logging/monolog_regex_based_excludes /logging/monolog_regex_based_excludes
-/cookbook/profiler/data_collector /profiler/data_collector
+/cookbook/profiler/data_collector /profiler#profiler-data-collector
+/profiler/data_collector /profiler#profiler-data-collector
/cookbook/profiler/index /profiler
/cookbook/profiler/matchers /profiler/matchers
/cookbook/profiler/profiling_data /profiler/profiling_data
@@ -253,12 +251,14 @@
/cookbook/session/index /session
/cookbook/session/limit_metadata_writes /reference/configuration/framework
/session/limit_metadata_writes /reference/configuration/framework
-/cookbook/session/locale_sticky_session /session/locale_sticky_session
+/cookbook/session/locale_sticky_session /session#locale-sticky-session
+/cookbook/locale_sticky_session /session#locale-sticky-session
/cookbook/session/php_bridge /session/php_bridge
/cookbook/session/proxy_examples /session/proxy_examples
/cookbook/session/sessions_directory /session/sessions_directory
/cookbook/symfony1 /introduction/symfony1
-/cookbook/templating/global_variables /templating/global_variables
+/cookbook/templating/global_variables /templating#templating-global-variables
+/templating/global_variables /templating#templating-global-variables
/cookbook/templating/index /templating
/cookbook/templating/namespaced_paths /templating/namespaced_paths
/cookbook/templating/PHP /templating/PHP
@@ -390,6 +390,9 @@
/quick_tour/the_view /quick_tour/flex_recipes
/service_container/service_locators /service_container/service_subscribers_locators
/templating/overriding /bundles/override
+/templating/twig_extension /templates#templates-twig-extension
+/templating/hinclude /templates#templates-hinclude
+/templating/PHP /templates
/security/custom_provider /security/user_provider
/security/multiple_user_providers /security/user_provider
/security/custom_password_authenticator /security/guard_authentication
@@ -411,6 +414,7 @@
/security/entity_provider /security/user_provider
/session/avoid_session_start /session
/session/sessions_directory /session
+/session/configuring_ttl /session#session-configure-ttl
/frontend/encore/legacy-apps /frontend/encore/legacy-applications
/configuration/external_parameters /configuration/environment_variables
/contributing/code/patches /contributing/code/pull_requests
@@ -426,6 +430,7 @@
/email/spool /mailer
/email/testing /mailer
/contributing/community/other /contributing/community
+/contributing/code/core_team /contributing/core_team
/profiler/storage /profiler
/setup/composer /setup
/security/security_checker /setup
@@ -449,16 +454,20 @@
/reference/requirements /setup
/bundles/inheritance /bundles/override
/templating /templates
-/templating/escaping /templates
-/templating/syntax /templates
-/templating/debug /templates
-/templating/render_without_controller /templates
-/templating/app_variable /templates
+/templating/escaping /templates#output-escaping
+/templating/syntax /templates#linting-twig-templates
+/templating/debug /templates#the-dump-twig-utilities
+/templating/render_without_controller /templates#rendering-a-template-directly-from-a-route
+/templating/app_variable /templates#the-app-global-variable
/templating/formats /templates
-/templating/namespaced_paths /templates
-/templating/embedding_controllers /templates
-/templating/inheritance /templates
+/templating/namespaced_paths /templates#template-namespaces
+/templating/embedding_controllers /templates#embedding-controllers
+/templating/inheritance /templates#template-inheritance-and-layouts
/testing/doctrine /testing/database
+/translation/templates /translation#translation-in-templates
+/translation/debug /translation#translation-debug
+/translation/lint /translation#translation-lint
+/translation/locale /translation#translation-locale
/doctrine/lifecycle_callbacks /doctrine/events
/doctrine/event_listeners_subscribers /doctrine/events
/doctrine/common_extensions /doctrine
@@ -481,8 +490,9 @@
/components/translation/custom_message_formatter https://github.com/symfony/translation
/components/notifier https://github.com/symfony/notifier
/components/routing https://github.com/symfony/routing
-/doctrine/pdo_session_storage /session/database
-/doctrine/mongodb_session_storage /session/database
+/session/database /session#session-database
+/doctrine/pdo_session_storage /session#session-database-pdo
+/doctrine/mongodb_session_storage /session#session-database-mongodb
/components/dotenv https://github.com/symfony/dotenv
/components/mercure /mercure
/components/polyfill_apcu https://github.com/symfony/polyfill-apcu
@@ -508,4 +518,59 @@
/frontend/encore/versus-assetic /frontend
/components/http_client /http_client
/components/mailer /mailer
-/messenger/message-recorder messenger/dispatch_after_current_bus
+/messenger/message-recorder /messenger/dispatch_after_current_bus
+/components/stopwatch https://github.com/symfony/stopwatch
+/service_container/3.3-di-changes https://symfony.com/doc/3.4/service_container/3.3-di-changes.html
+/frontend/encore/shared-entry /frontend/encore/split-chunks
+/frontend/encore/page-specific-assets /frontend/encore/simple-example#page-specific-javascript-or-css
+/testing/functional_tests_assertions /testing#testing-application-assertions
+/components https://symfony.com/components
+/components/index https://symfony.com/components
+/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
+/security/experimental_authenticators /security
+/security/user_provider /security/user_providers
+/security/reset_password /security/passwords#reset-password
+/security/auth_providers /security#security-authenticators
+/security/form_login /security#form-login
+/security/form_login_setup /security#form-login
+/security/json_login_setup /security#json-login
+/security/named_hashers /security/passwords#named-password-hashers
+/security/password_migration /security/passwords#security-password-migration
+/security/acl https://github.com/symfony/acl-bundle/blob/main/src/Resources/doc/index.rst
+/security/securing_services /security#securing-other-services
+/security/authenticator_manager /security
+/security/multiple_guard_authenticators /security/entry_point
+/security/guard_authentication /security/custom_authenticator
+/components/security/authentication /security#authenticating-users
+/components/security/authorization /security#access-control-authorization
+/components/security/firewall /security#the-firewall
+/components/security/secure_tools /security/passwords
+/components/security /security
+/components/var_dumper/advanced /components/var_dumper#advanced-usage
+/components/yaml/yaml_format /reference/formats/yaml
+/components/expression_language/syntax /reference/formats/expression_language
+/components/expression_language/ast /components/expression_language#expression-language-ast
+/components/expression_language/caching /components/expression_language#expression-language-caching
+/components/expression_language/extending /components/expression_language#expression-language-extending
+/notifier/chatters /notifier#sending-chat-messages
+/notifier/texters /notifier#sending-sms
+/notifier/events /notifier#notifier-events
+/email /mailer
+/frontend/assetic /frontend
+/frontend/assetic/index /frontend
+/controller/argument_value_resolver /controller/value_resolver
+/frontend/ux https://symfony.com/bundles/StimulusBundle/current/index.html
+/messenger/handler_results /messenger#messenger-getting-handler-results
+/messenger/dispatch_after_current_bus /messenger#messenger-transactional-messages
+/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 3b1d630fa11..00000000000
--- a/_build/spelling_word_list.txt
+++ /dev/null
@@ -1,346 +0,0 @@
-accessor
-Akamai
-analytics
-Ansi
-Ansible
-Assetic
-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
-fortrabbit
-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/console/completion.gif b/_images/components/console/completion.gif
new file mode 100644
index 00000000000..18b3f5475c8
Binary files /dev/null and b/_images/components/console/completion.gif differ
diff --git a/_images/components/console/cursor.gif b/_images/components/console/cursor.gif
new file mode 100644
index 00000000000..71a74dd8637
Binary files /dev/null and b/_images/components/console/cursor.gif differ
diff --git a/_images/components/console/debug_formatter.png b/_images/components/console/debug_formatter.png
index 7482f39851f..4ba2c0c2b57 100644
Binary files a/_images/components/console/debug_formatter.png and b/_images/components/console/debug_formatter.png differ
diff --git a/_images/components/console/process-helper-debug.png b/_images/components/console/process-helper-debug.png
index 282e1336389..96c5c316739 100644
Binary files a/_images/components/console/process-helper-debug.png and b/_images/components/console/process-helper-debug.png differ
diff --git a/_images/components/console/process-helper-error-debug.png b/_images/components/console/process-helper-error-debug.png
index 8d1145478f2..48f6c7258d4 100644
Binary files a/_images/components/console/process-helper-error-debug.png and b/_images/components/console/process-helper-error-debug.png differ
diff --git a/_images/components/console/process-helper-verbose.png b/_images/components/console/process-helper-verbose.png
index c4c912e1433..abdff9812b0 100644
Binary files a/_images/components/console/process-helper-verbose.png and b/_images/components/console/process-helper-verbose.png differ
diff --git a/_images/components/console/progress.png b/_images/components/console/progress.png
deleted file mode 100644
index c126bff5252..00000000000
Binary files a/_images/components/console/progress.png and /dev/null differ
diff --git a/_images/components/console/progressbar.gif b/_images/components/console/progressbar.gif
index 6c80e6e897f..0746e399354 100644
Binary files a/_images/components/console/progressbar.gif and b/_images/components/console/progressbar.gif differ
diff --git a/_images/components/form/general_flow.png b/_images/components/form/general_flow.png
deleted file mode 100644
index 31650e52af6..00000000000
Binary files a/_images/components/form/general_flow.png and /dev/null differ
diff --git a/_images/components/form/set_data_flow.png b/_images/components/form/set_data_flow.png
deleted file mode 100644
index 3cd4b1e2f7b..00000000000
Binary files a/_images/components/form/set_data_flow.png and /dev/null differ
diff --git a/_images/components/form/submission_flow.png b/_images/components/form/submission_flow.png
deleted file mode 100644
index a3c6e9cfb90..00000000000
Binary files a/_images/components/form/submission_flow.png and /dev/null differ
diff --git a/_images/components/messenger/basic_cycle.png b/_images/components/messenger/basic_cycle.png
new file mode 100644
index 00000000000..a0558968cbb
Binary files /dev/null and b/_images/components/messenger/basic_cycle.png differ
diff --git a/_images/components/messenger/overview.svg b/_images/components/messenger/overview.svg
index 94737e7a6da..4b82c203756 100644
--- a/_images/components/messenger/overview.svg
+++ b/_images/components/messenger/overview.svg
@@ -1 +1 @@
-
+
diff --git a/_images/components/scheduler/generate_consume.png b/_images/components/scheduler/generate_consume.png
new file mode 100644
index 00000000000..269281266a5
Binary files /dev/null and b/_images/components/scheduler/generate_consume.png differ
diff --git a/_images/components/scheduler/scheduler_cycle.png b/_images/components/scheduler/scheduler_cycle.png
new file mode 100644
index 00000000000..18addb37d91
Binary files /dev/null and b/_images/components/scheduler/scheduler_cycle.png differ
diff --git a/_images/components/serializer/serializer_workflow.svg b/_images/components/serializer/serializer_workflow.svg
deleted file mode 100644
index f3906506878..00000000000
--- a/_images/components/serializer/serializer_workflow.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/_images/components/var_dumper/10-uninitialized.png b/_images/components/var_dumper/10-uninitialized.png
new file mode 100644
index 00000000000..735731b83b5
Binary files /dev/null and b/_images/components/var_dumper/10-uninitialized.png differ
diff --git a/_images/components/workflow/blogpost.png b/_images/components/workflow/blogpost.png
index 38e29250eb1..b7f51eabb43 100644
Binary files a/_images/components/workflow/blogpost.png and b/_images/components/workflow/blogpost.png differ
diff --git a/_images/components/workflow/blogpost_mermaid.png b/_images/components/workflow/blogpost_mermaid.png
new file mode 100644
index 00000000000..7a4d3a57cfe
Binary files /dev/null and b/_images/components/workflow/blogpost_mermaid.png differ
diff --git a/_images/components/workflow/blogpost_metadata.png b/_images/components/workflow/blogpost_metadata.png
new file mode 100644
index 00000000000..783f51c6ccf
Binary files /dev/null and b/_images/components/workflow/blogpost_metadata.png differ
diff --git a/_images/components/workflow/blogpost_puml.png b/_images/components/workflow/blogpost_puml.png
index 14d45c8b40f..efe543a6f8e 100644
Binary files a/_images/components/workflow/blogpost_puml.png and b/_images/components/workflow/blogpost_puml.png differ
diff --git a/_images/components/workflow/states_transitions.png b/_images/components/workflow/states_transitions.png
index 1e68f9ca597..d1f54391afd 100644
Binary files a/_images/components/workflow/states_transitions.png and b/_images/components/workflow/states_transitions.png differ
diff --git a/_images/contributing/code/stack-trace.gif b/_images/contributing/code/stack-trace.gif
new file mode 100644
index 00000000000..97a2043448d
Binary files /dev/null and b/_images/contributing/code/stack-trace.gif differ
diff --git a/_images/contributing/docs-github-create-pr.png b/_images/contributing/docs-github-create-pr.png
index 29fe22f5dbd..43b6842ffc2 100644
Binary files a/_images/contributing/docs-github-create-pr.png and b/_images/contributing/docs-github-create-pr.png differ
diff --git a/_images/contributing/docs-github-edit-page.png b/_images/contributing/docs-github-edit-page.png
index c34f13f0889..b739497f70f 100644
Binary files a/_images/contributing/docs-github-edit-page.png and b/_images/contributing/docs-github-edit-page.png differ
diff --git a/_images/contributing/docs-pull-request-change-base.png b/_images/contributing/docs-pull-request-change-base.png
index d824e8ef1bc..791901b8ec6 100644
Binary files a/_images/contributing/docs-pull-request-change-base.png and b/_images/contributing/docs-pull-request-change-base.png differ
diff --git a/_images/contributing/docs-pull-request-symfonycloud.png b/_images/contributing/docs-pull-request-symfonycloud.png
deleted file mode 100644
index 0c485c1491c..00000000000
Binary files a/_images/contributing/docs-pull-request-symfonycloud.png and /dev/null differ
diff --git a/_images/controller/error_pages/exceptions-in-dev-environment.png b/_images/controller/error_pages/exceptions-in-dev-environment.png
index 74128990e57..e1fba2bebf9 100644
Binary files a/_images/controller/error_pages/exceptions-in-dev-environment.png and b/_images/controller/error_pages/exceptions-in-dev-environment.png differ
diff --git a/_images/docs-pull-request-change-base.png b/_images/docs-pull-request-change-base.png
deleted file mode 100644
index d824e8ef1bc..00000000000
Binary files a/_images/docs-pull-request-change-base.png and /dev/null differ
diff --git a/_images/doctrine/mapping_relations.png b/_images/doctrine/mapping_relations.png
deleted file mode 100644
index a679f9cb317..00000000000
Binary files a/_images/doctrine/mapping_relations.png and /dev/null differ
diff --git a/_images/doctrine/mapping_relations.svg b/_images/doctrine/mapping_relations.svg
new file mode 100644
index 00000000000..7dc8979cb1a
--- /dev/null
+++ b/_images/doctrine/mapping_relations.svg
@@ -0,0 +1,602 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/_images/doctrine/mapping_relations_proxy.png b/_images/doctrine/mapping_relations_proxy.png
deleted file mode 100644
index 935153291d4..00000000000
Binary files a/_images/doctrine/mapping_relations_proxy.png and /dev/null differ
diff --git a/_images/doctrine/mapping_relations_proxy.svg b/_images/doctrine/mapping_relations_proxy.svg
new file mode 100644
index 00000000000..634d1b0add2
--- /dev/null
+++ b/_images/doctrine/mapping_relations_proxy.svg
@@ -0,0 +1,926 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/_images/doctrine/mapping_single_entity.png b/_images/doctrine/mapping_single_entity.png
deleted file mode 100644
index 6f88c6cacfa..00000000000
Binary files a/_images/doctrine/mapping_single_entity.png and /dev/null differ
diff --git a/_images/doctrine/mapping_single_entity.svg b/_images/doctrine/mapping_single_entity.svg
new file mode 100644
index 00000000000..5d517c85fb1
--- /dev/null
+++ b/_images/doctrine/mapping_single_entity.svg
@@ -0,0 +1,469 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/_images/form/data-transformer-types.png b/_images/form/data-transformer-types.png
deleted file mode 100644
index 950acd39ea7..00000000000
Binary files a/_images/form/data-transformer-types.png and /dev/null differ
diff --git a/_images/form/data-transformer-types.svg b/_images/form/data-transformer-types.svg
new file mode 100644
index 00000000000..9393b224f89
--- /dev/null
+++ b/_images/form/data-transformer-types.svg
@@ -0,0 +1,178 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/_images/form/form-custom-type-postal-address-fragment-names.svg b/_images/form/form-custom-type-postal-address-fragment-names.svg
index 9b6092c9808..db9463b8327 100644
--- a/_images/form/form-custom-type-postal-address-fragment-names.svg
+++ b/_images/form/form-custom-type-postal-address-fragment-names.svg
@@ -1 +1 @@
-
+
diff --git a/_images/form/form-custom-type-postal-address.svg b/_images/form/form-custom-type-postal-address.svg
index ab0fde8af3a..42ffce4067f 100644
--- a/_images/form/form-custom-type-postal-address.svg
+++ b/_images/form/form-custom-type-postal-address.svg
@@ -1 +1 @@
-
+
diff --git a/_images/form/form_prepopulation_workflow.svg b/_images/form/form_prepopulation_workflow.svg
new file mode 100644
index 00000000000..c908f5c5a76
--- /dev/null
+++ b/_images/form/form_prepopulation_workflow.svg
@@ -0,0 +1,253 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/_images/form/form_submission_workflow.svg b/_images/form/form_submission_workflow.svg
new file mode 100644
index 00000000000..d6d138ee61a
--- /dev/null
+++ b/_images/form/form_submission_workflow.svg
@@ -0,0 +1,334 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/_images/form/form_workflow.svg b/_images/form/form_workflow.svg
new file mode 100644
index 00000000000..2dbacbbf096
--- /dev/null
+++ b/_images/form/form_workflow.svg
@@ -0,0 +1,263 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/_images/form/tailwindcss-form.png b/_images/form/tailwindcss-form.png
new file mode 100644
index 00000000000..8a290749149
Binary files /dev/null and b/_images/form/tailwindcss-form.png differ
diff --git a/_images/http/xkcd-full.png b/_images/http/xkcd-full.png
deleted file mode 100644
index d5b01ea32b9..00000000000
Binary files a/_images/http/xkcd-full.png and /dev/null differ
diff --git a/_images/http/xkcd-full.svg b/_images/http/xkcd-full.svg
new file mode 100644
index 00000000000..da590c2b97e
--- /dev/null
+++ b/_images/http/xkcd-full.svg
@@ -0,0 +1,324 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/_images/http/xkcd-request.png b/_images/http/xkcd-request.png
deleted file mode 100644
index 310713d304c..00000000000
Binary files a/_images/http/xkcd-request.png and /dev/null differ
diff --git a/_images/http/xkcd-request.svg b/_images/http/xkcd-request.svg
new file mode 100644
index 00000000000..6a21280ca34
--- /dev/null
+++ b/_images/http/xkcd-request.svg
@@ -0,0 +1,191 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/_images/install/deprecations-in-profiler.png b/_images/install/deprecations-in-profiler.png
index a8abcae32b7..3d3f9a98a4a 100644
Binary files a/_images/install/deprecations-in-profiler.png and b/_images/install/deprecations-in-profiler.png differ
diff --git a/_images/mercure/discovery.png b/_images/mercure/discovery.png
deleted file mode 100644
index 0ef38271de6..00000000000
Binary files a/_images/mercure/discovery.png and /dev/null differ
diff --git a/_images/mercure/discovery.svg b/_images/mercure/discovery.svg
new file mode 100644
index 00000000000..ed18381068a
--- /dev/null
+++ b/_images/mercure/discovery.svg
@@ -0,0 +1,294 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/_images/mercure/hub.svg b/_images/mercure/hub.svg
new file mode 100644
index 00000000000..6b5e496e3c6
--- /dev/null
+++ b/_images/mercure/hub.svg
@@ -0,0 +1,196 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/_images/mercure/schema.png b/_images/mercure/schema.png
deleted file mode 100644
index 4616046e5cc..00000000000
Binary files a/_images/mercure/schema.png and /dev/null differ
diff --git a/_images/notifier/microsoft_teams/message-card.png b/_images/notifier/microsoft_teams/message-card.png
new file mode 100644
index 00000000000..05f505fb3e0
Binary files /dev/null and b/_images/notifier/microsoft_teams/message-card.png differ
diff --git a/_images/notifier/microsoft_teams/message.png b/_images/notifier/microsoft_teams/message.png
new file mode 100644
index 00000000000..5c4c7f11ed1
Binary files /dev/null and b/_images/notifier/microsoft_teams/message.png differ
diff --git a/_images/notifier/slack/field-method.png b/_images/notifier/slack/field-method.png
new file mode 100644
index 00000000000..d77a60e6a2e
Binary files /dev/null and b/_images/notifier/slack/field-method.png differ
diff --git a/_images/notifier/slack/message-reply.png b/_images/notifier/slack/message-reply.png
new file mode 100644
index 00000000000..9a60e4573ab
Binary files /dev/null and b/_images/notifier/slack/message-reply.png differ
diff --git a/_images/notifier/slack/slack-footer.png b/_images/notifier/slack/slack-footer.png
new file mode 100644
index 00000000000..a53952c78f6
Binary files /dev/null and b/_images/notifier/slack/slack-footer.png differ
diff --git a/_images/notifier/slack/slack-header.png b/_images/notifier/slack/slack-header.png
new file mode 100644
index 00000000000..a7caf915d8f
Binary files /dev/null and b/_images/notifier/slack/slack-header.png differ
diff --git a/_images/profiler/web-interface.png b/_images/profiler/web-interface.png
index 2e6c6061892..b107f6427d7 100644
Binary files a/_images/profiler/web-interface.png and b/_images/profiler/web-interface.png differ
diff --git a/_images/quick_tour/no_routes_page.png b/_images/quick_tour/no_routes_page.png
index 382950b6ef5..030953a17b1 100644
Binary files a/_images/quick_tour/no_routes_page.png and b/_images/quick_tour/no_routes_page.png differ
diff --git a/_images/quick_tour/web_debug_toolbar.png b/_images/quick_tour/web_debug_toolbar.png
deleted file mode 100644
index 465020380cb..00000000000
Binary files a/_images/quick_tour/web_debug_toolbar.png and /dev/null differ
diff --git a/_images/rate_limiter/fixed_window.svg b/_images/rate_limiter/fixed_window.svg
new file mode 100644
index 00000000000..83d5f6e79ac
--- /dev/null
+++ b/_images/rate_limiter/fixed_window.svg
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10:00
+
+
+ 10:30
+
+
+ 11:00
+
+
+ 11:30
+
+
+ 12:00
+
+
+
+
+
+
+
+ 12:30
+
+
+ 13:00
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1 hour window
+
+
+ 1 hour window
+
+
+
+
+
+ 1 hour window
+
+
+
+
+ 13:15
+
+
+
diff --git a/_images/rate_limiter/sliding_window.svg b/_images/rate_limiter/sliding_window.svg
new file mode 100644
index 00000000000..2c565615441
--- /dev/null
+++ b/_images/rate_limiter/sliding_window.svg
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+ 10:00
+
+
+ 10:30
+
+
+ 11:00
+
+
+ 11:30
+
+
+ 12:00
+
+
+
+
+
+ 12:30
+
+
+ 13:00
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1 hour window
+
+
+
+
+
+ 13:15
+
+
+
+
+
+
diff --git a/_images/rate_limiter/token_bucket.svg b/_images/rate_limiter/token_bucket.svg
new file mode 100644
index 00000000000..29d6fc8f103
--- /dev/null
+++ b/_images/rate_limiter/token_bucket.svg
@@ -0,0 +1,83 @@
+
+
+
+ 10:00
+
+
+ 10:30
+
+
+ 11:00
+
+
+ 11:30
+
+
+ 12:00
+
+
+
+
+
+
+
+ 12:30
+
+
+ 13:00
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 13:15
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/_images/release-process.jpg b/_images/release-process.jpg
deleted file mode 100644
index 9868404b07f..00000000000
Binary files a/_images/release-process.jpg and /dev/null differ
diff --git a/_images/security/anonymous_wdt.png b/_images/security/anonymous_wdt.png
index 8dbf1cd8298..80736afce39 100644
Binary files a/_images/security/anonymous_wdt.png and b/_images/security/anonymous_wdt.png differ
diff --git a/_images/security/login_link_email.png b/_images/security/login_link_email.png
new file mode 100644
index 00000000000..8331b878f68
Binary files /dev/null and b/_images/security/login_link_email.png differ
diff --git a/_images/security/profiler-badges.png b/_images/security/profiler-badges.png
new file mode 100644
index 00000000000..a19f8539581
Binary files /dev/null and b/_images/security/profiler-badges.png differ
diff --git a/_images/security/security_events.svg b/_images/security/security_events.svg
new file mode 100644
index 00000000000..f1b93923da6
--- /dev/null
+++ b/_images/security/security_events.svg
@@ -0,0 +1,338 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/_images/serializer/serializer_workflow.svg b/_images/serializer/serializer_workflow.svg
new file mode 100644
index 00000000000..b6e9c254778
--- /dev/null
+++ b/_images/serializer/serializer_workflow.svg
@@ -0,0 +1,283 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/_images/sources/README.md b/_images/sources/README.md
index 9e40e0ac884..84810a9783d 100644
--- a/_images/sources/README.md
+++ b/_images/sources/README.md
@@ -1,8 +1,8 @@
-How to Create Symfony Diagrams
-==============================
+How to Create Symfony Images
+============================
-Creating the Diagram
---------------------
+Creating Diagrams
+-----------------
* Use [Dia][1] as the diagramming application;
* Use [PT Sans Narrow][2] as the only font in all diagrams (if possible, use
@@ -21,38 +21,82 @@ Creating the Diagram
In case of doubt, check the existing diagrams or ask to the
[Symfony Documentation Team][3].
-Saving and Exporting the Diagram
---------------------------------
+### Saving and Exporting the Diagram
* Save the original diagram in `*.dia` format in `_images/sources/`;
* Export the diagram to SVG format and save it in `_images/`.
-Including the Diagram in the Symfony Docs
------------------------------------------
+Important: choose "Cairo Scalable Vector Graphics (.svg)" format instead of
+plain " Scalable Vector Graphics (.svg)" because the former is the only format
+that transforms text into vector shapes (resulting file is larger in size, but
+it's truly portable because text is displayed the same even if you don't have
+some fonts installed).
+
+### Including the Diagram in the Symfony Docs
Use the following snippet to embed the diagram in the docs:
```
.. raw:: html
-
+
```
-Reasoning
----------
+### Reasoning
* Dia was chosen because it's one of the few applications which are free, open
source and compatible with Linux, macOS and Windows.
* Font, colors and line widths were chosen to be similar to the diagrams used
in the best tech books.
-Troubleshooting
----------------
+### Troubleshooting
* On some macOS systems, Dia cannot be executed as a regular application and
you must run the following console command instead:
`export DISPLAY=:0 && /Applications/Dia.app/Contents/Resources/bin/dia`
+Creating Console Screenshots
+----------------------------
+
+* Use [Asciinema][4] to record the console session locally:
+
+ ```
+ $ asciinema rec -c bash recording.cast
+ ```
+* Use `$ ` as the prompt in recordings. E.g. if you're using Bash, add the
+ following lines to your ``.bashrc``:
+
+ ```
+ if [ "$ASCIINEMA_REC" = "1" ]; then
+ PS1="\e[37m$ \e[0m"
+ fi
+ ```
+* Save the generated asciicast in `_images/sources/`.
+
+### Rendering the Recording
+
+Rendering the recording can be a difficult task. The [documentation team][3]
+is always ready to help you with this task (e.g. you can open a PR with
+only the asciicast file).
+
+* Use [agg][5] to generated a GIF file from the recording;
+* Install the [JetBrains Mono][6] font;
+* Use the ``_images/sources/ascii-render.sh`` file to call agg:
+
+ ```
+ AGG_PATH=/path/to/agg ./_images/sources/ascii-render.sh recording.cast --cols 45 --rows 20
+ ```
+
+ This utility configures a predefined theme;
+* Always configure `--cols`` (width) and ``--rows`` (height), try to use as
+ low as possible numbers. Do not exceed 70 columns;
+* Save the generated GIF file in `_images/`.
+
[1]: http://dia-installer.de/
[2]: https://fonts.google.com/specimen/PT+Sans+Narrow
-[3]: https://symfony.com/doc/current/contributing/code/core_team.html
+[3]: https://symfony.com/doc/current/contributing/core_team.html
+[4]: https://github.com/asciinema/asciinema
+[5]: https://github.com/asciinema/agg
+[6]: https://www.jetbrains.com/lp/mono/
diff --git a/_images/sources/ascii-render.sh b/_images/sources/ascii-render.sh
new file mode 100755
index 00000000000..e72be572390
--- /dev/null
+++ b/_images/sources/ascii-render.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env sh
+case "$1" in
+ ''|help|-h)
+ echo "ansi-render.sh RECORDING [options]"
+ echo ""
+ echo " RECORDING: path to the .cast file generated by asciinema"
+ echo " [options]: optional options to be passed to agg"
+ ;;
+ *)
+ recording=$1
+ extra_options=
+ if [ $# -gt 1 ]; then
+ shift
+ extra_options=$@
+ fi
+
+ # optionally, use this green color: 1f4631
+ ${AGG_PATH:-agg} \
+ --theme 18202a,f9fafb,f9fafb,ff7b72,7ee787,ffa657,79c0ff,d2a8ff,a5d6ff,f9fafb,8b949e,ff7b72,00c300,ffa657,79c0ff,d2a8ff,a5d6ff,f9fafb --line-height 1.6 \
+ --font-family 'JetBrains Mono' \
+ $extra_options \
+ $recording $(echo $recording | sed "s/cast/gif/")
+ ;;
+esac
diff --git a/_images/sources/components/console/completion.cast b/_images/sources/components/console/completion.cast
new file mode 100644
index 00000000000..c268863e9b0
--- /dev/null
+++ b/_images/sources/components/console/completion.cast
@@ -0,0 +1,37 @@
+{"version": 2, "width": 76, "height": 30, "timestamp": 1663253713, "env": {"SHELL": "/usr/bin/fish", "TERM": "st-256color"}}
+[0.00798, "o", "\u001b[?2004h\u001b[90m$ \u001b[0m"]
+[0.614685, "o", "b"]
+[0.776549, "o", "i"]
+[0.86682, "o", "n"]
+[1.092426, "o", "/"]
+[1.332671, "o", "c"]
+[1.55068, "o", "o"]
+[1.630651, "o", "n"]
+[1.784584, "o", "s"]
+[1.873108, "o", "o"]
+[2.074652, "o", "l"]
+[2.180433, "o", "e"]
+[2.260475, "o", " "]
+[2.696628, "o", "\u0007"]
+[2.947263, "o", "\r\nabout debug:event-dispatcher\r\nassets:install debug:router\r\ncache:clear help\r\ncache:pool:clear lint:container\r\ncache:pool:delete lint:yaml\r\ncache:pool:list list\r\ncache:pool:prune router:match\r\ncache:warmup secrets:decrypt-to-local\r\ncompletion secrets:encrypt-from-local\r\nconfig:dump-reference secrets:generate-keys\r\ndebug:autowiring secrets:list\r\ndebug:config secrets:remove\r\ndebug:container secrets:set\r\ndebug:dotenv \r\n\u001b[37m$ \u001b[0mbin/console "]
+[3.614479, "o", "s"]
+[3.802449, "o", "e"]
+[4.205631, "o", "\u0007crets:"]
+[4.520435, "o", "r"]
+[4.598031, "o", "e"]
+[5.026287, "o", "move "]
+[5.47041, "o", "\u0007SOME_"]
+[5.673941, "o", "\u0007"]
+[6.024086, "o", "\r\nSOME_OTHER_SECRET SOME_SECRET \r\n\u001b[37m$ \u001b[0mbin/console secrets:remove SOME_"]
+[6.770627, "o", "O"]
+[7.14335, "o", "THER_SECRET "]
+[7.724482, "o", "\r\n\u001b[?2004l\r"]
+[7.776657, "o", "\r\n"]
+[7.779108, "o", "\u001b[30;42m \u001b[39;49m\r\n\u001b[30;42m [OK] Secret \"SOME_OTHER_SECRET\" removed from \"config/secrets/dev/\". \u001b[39;49m\r\n\u001b[30;42m \u001b[39;49m\r\n\r\n"]
+[7.782993, "o", "\u001b[?2004h\u001b[37m$ \u001b[0m"]
+[9.214537, "o", "e"]
+[9.522429, "o", "x"]
+[9.690371, "o", "i"]
+[9.85446, "o", "t"]
+[10.292412, "o", "\r\n\u001b[?2004l\r"]
+[10.292526, "o", "exit\r\n"]
diff --git a/_images/sources/components/console/cursor.cast b/_images/sources/components/console/cursor.cast
new file mode 100644
index 00000000000..be2f2f6c351
--- /dev/null
+++ b/_images/sources/components/console/cursor.cast
@@ -0,0 +1,49 @@
+{"version": 2, "width": 191, "height": 30, "timestamp": 1663251833, "env": {"SHELL": "/usr/bin/fish", "TERM": "st-256color"}}
+[0.007941, "o", "\u001b[?2004h\u001b[90m$ \u001b[0m"]
+[0.566363, "o", "c"]
+[0.643353, "o", "l"]
+[0.762325, "o", "e"]
+[0.952363, "o", "a"]
+[0.995878, "o", "r"]
+[1.107784, "o", "\r\n\u001b[?2004l\r"]
+[1.109766, "o", "\u001b[H\u001b[2J"]
+[1.109946, "o", "\u001b[?2004h\u001b[30m$ \u001b[0m"]
+[1.653461, "o", "p"]
+[1.772323, "o", "h"]
+[1.856444, "o", "p"]
+[1.980339, "o", " "]
+[2.15827, "o", "c"]
+[2.273242, "o", "u"]
+[2.402231, "o", "r"]
+[2.563066, "o", "s"]
+[2.760266, "o", "o"]
+[2.900252, "o", "r"]
+[3.020537, "o", "."]
+[3.316404, "o", "p"]
+[3.403213, "o", "h"]
+[3.483391, "o", "p"]
+[3.820273, "o", "\r\n\u001b[?2004l\r"]
+[3.845697, "o", "\u001b[6;9H#"]
+[4.045942, "o", "\u001b[8;9H#"]
+[4.246327, "o", "\u001b[8;2H#####"]
+[4.446737, "o", "\u001b[2;9H#######"]
+[4.647128, "o", "\u001b[7;7H#"]
+[4.84749, "o", "\u001b[3;9H#"]
+[5.047857, "o", "\u001b[7;9H#"]
+[5.248246, "o", "\u001b[4;9H#"]
+[5.448622, "o", "\u001b[2;2H#####"]
+[5.648999, "o", "\u001b[3;7H#"]
+[5.849378, "o", "\u001b[5;9H#####"]
+[6.049711, "o", "\u001b[3;1H#"]
+[6.250118, "o", "\u001b[7;1H#"]
+[6.45056, "o", "\u001b[5;2H#####"]
+[6.650897, "o", "\u001b[4;1H#"]
+[6.851281, "o", "\u001b[6;7H#"]
+[7.051644, "o", "\u001b[9;1H"]
+[7.058802, "o", "\u001b[?2004h\u001b[30m$ \u001b[0m"]
+[7.657612, "o", "e"]
+[7.846956, "o", "x"]
+[7.949451, "o", "i"]
+[8.0893, "o", "t"]
+[8.201144, "o", "\r\n\u001b[?2004l\r"]
+[8.201227, "o", "exit\r\n"]
diff --git a/_images/sources/components/console/progress.cast b/_images/sources/components/console/progress.cast
new file mode 100644
index 00000000000..9c5244b37e2
--- /dev/null
+++ b/_images/sources/components/console/progress.cast
@@ -0,0 +1,57 @@
+{"version": 2, "width": 191, "height": 17, "timestamp": 1663423221, "env": {"SHELL": "/usr/bin/fish", "TERM": "st-256color"}}
+[0.008171, "o", "\u001b[?2004h\u001b[90m$ \u001b[0m"]
+[0.385858, "o", "p"]
+[0.577979, "o", "h"]
+[0.768282, "o", "p"]
+[0.96433, "o", " "]
+[1.133645, "o", "p"]
+[1.262693, "o", "r"]
+[1.385832, "o", "o"]
+[1.476876, "o", "g"]
+[1.652322, "o", "r"]
+[1.722357, "o", "e"]
+[1.935395, "o", "s"]
+[2.083915, "o", "s"]
+[2.200109, "o", "."]
+[2.403686, "o", "p"]
+[2.510201, "o", "h"]
+[2.602756, "o", "p"]
+[2.909974, "o", "\r\n\u001b[?2004l\r"]
+[2.935647, "o", "\u001b[34m Starting the demo... fingers crossed \u001b[39m\r\n 0/15 \u001b[32;41m\u001b[39;49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m 0%\r\n < 1 sec 4.0 MiB"]
+[3.418022, "o", "\u001b[1G\u001b[2K\u001b[1A\u001b[1G\u001b[2K\u001b[1A\u001b[1G\u001b[2K"]
+[3.419196, "o", "\u001b[34m Starting the demo... fingers crossed \u001b[39m\r\n 2/15 \u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[32;41m\u001b[39;49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m 13%\r\n < 1 sec 6.0 MiB"]
+[3.66102, "o", "\u001b[1G\u001b[2K\u001b[1A\u001b[1G\u001b[2K\u001b[1A\u001b[1G"]
+[3.661071, "o", "\u001b[2K"]
+[3.661731, "o", "\u001b[34m Starting the demo... fingers crossed \u001b[39m\r\n 3/15 \u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[32;41m\u001b[39;49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m 20%\r\n 5 secs 6.0 MiB"]
+[4.143554, "o", "\u001b[1G\u001b[2K\u001b[1A\u001b[1G\u001b[2K\u001b[1A\u001b[1G\u001b[2K"]
+[4.14385, "o", "\u001b[34m Starting the demo... fingers crossed \u001b[39m\r\n 5/15 \u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[32;41m\u001b[39;49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m 33%\r\n 3 secs 6.5 MiB"]
+[4.385367, "o", "\u001b[1G\u001b[2K\u001b[1A\u001b[1G\u001b[2K\u001b[1A\u001b[1G\u001b[2K"]
+[4.38612, "o", "\u001b[34m Looks good to me... \u001b[39m\r\n 6/15 \u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[32;41m\u001b[39;49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m 40%\r\n 3 secs 7.1 MiB"]
+[4.868053, "o", "\u001b[1G\u001b[2K\u001b[1A\u001b[1G\u001b[2K\u001b[1A\u001b[1G\u001b[2K"]
+[4.86852, "o", "\u001b[34m Looks good to me... \u001b[39m\r\n 8/15 \u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[32;41m\u001b[39;49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m 53%\r\n 4 secs 8.1 MiB"]
+[5.110341, "o", "\u001b[1G\u001b[2K\u001b[1A\u001b[1G\u001b[2K\u001b[1A\u001b[1G\u001b[2K"]
+[5.11133, "o", "\u001b[34m Looks good to me... \u001b[39m\r\n 9/15 \u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[32;41m\u001b[39;49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m 60%\r\n 3 secs 8.6 MiB"]
+[5.593851, "o", "\u001b[1G\u001b[2K\u001b[1A\u001b[1G\u001b[2K\u001b[1A\u001b[1G"]
+[5.593924, "o", "\u001b[2K"]
+[5.594818, "o", "\u001b[34m Looks good to me... \u001b[39m\r\n11/15 \u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[32;41m\u001b[39;49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m 73%\r\n 4 secs 9.6 MiB"]
+[5.836301, "o", "\u001b[1G\u001b[2K\u001b[1A\u001b[1G\u001b[2K\u001b[1A\u001b[1G\u001b[2K"]
+[5.836831, "o", "\u001b[34m Looks good to me... \u001b[39m\r\n12/15 \u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[32;41m\u001b[39;49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m\u001b[41m \u001b[49m 80%\r\n 4 secs 10.1 MiB"]
+[6.31877, "o", "\u001b[1G\u001b[2K\u001b[1A\u001b[1G\u001b[2K\u001b[1A"]
+[6.318814, "o", "\u001b[1G\u001b[2K"]
+[6.319403, "o", "\u001b[34m Looks good to me... \u001b[39m\r\n14/15 \u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[32;41m\u001b[39;49m\u001b[41m \u001b[49m 93%\r\n 3 secs 11.1 MiB"]
+[6.561359, "o", "\u001b[1G\u001b[2K\u001b[1A"]
+[6.561561, "o", "\u001b[1G\u001b[2K\u001b[1A\u001b[1G\u001b[2K"]
+[6.562504, "o", "\u001b[34m Looks good to me... \u001b[39m\r\n15/15 \u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m 100%\r\n 4 secs 11.6 MiB"]
+[6.563772, "o", "\u001b[1G"]
+[6.563824, "o", "\u001b[2K\u001b[1A"]
+[6.563875, "o", "\u001b[1G\u001b[2K"]
+[6.563926, "o", "\u001b[1A\u001b[1G\u001b[2K"]
+[6.564766, "o", "\u001b[34m Thanks bye! \u001b[39m\r\n15/15 \u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m\u001b[42m \u001b[49m 100%\r\n 4 secs 11.6 MiB"]
+[6.564805, "o", "\r\n\r\n"]
+[6.570516, "o", "\u001b[?2004h"]
+[6.570537, "o", "\u001b[90m$ \u001b[0m"]
+[8.441927, "o", "e"]
+[8.646449, "o", "x"]
+[8.76668, "o", "i"]
+[8.897799, "o", "t"]
+[9.091614, "o", "\r\n\u001b[?2004l\rexit\r\n"]
diff --git a/_images/sources/components/messenger/overview.dia b/_images/sources/components/messenger/overview.dia
index 55ee153439e..b0e2edaeab2 100644
Binary files a/_images/sources/components/messenger/overview.dia and b/_images/sources/components/messenger/overview.dia differ
diff --git a/_images/sources/components/serializer/serializer_workflow.dia b/_images/sources/components/serializer/serializer_workflow.dia
deleted file mode 100644
index 6cb44280d0d..00000000000
Binary files a/_images/sources/components/serializer/serializer_workflow.dia and /dev/null differ
diff --git a/_images/sources/doctrine/mapping_relations.dia b/_images/sources/doctrine/mapping_relations.dia
new file mode 100644
index 00000000000..5703e1b781c
Binary files /dev/null and b/_images/sources/doctrine/mapping_relations.dia differ
diff --git a/_images/sources/doctrine/mapping_relations_proxy.dia b/_images/sources/doctrine/mapping_relations_proxy.dia
new file mode 100644
index 00000000000..1f491e9e2ef
Binary files /dev/null and b/_images/sources/doctrine/mapping_relations_proxy.dia differ
diff --git a/_images/sources/doctrine/mapping_single_entity.dia b/_images/sources/doctrine/mapping_single_entity.dia
new file mode 100644
index 00000000000..5a9dc21889c
Binary files /dev/null and b/_images/sources/doctrine/mapping_single_entity.dia differ
diff --git a/_images/sources/form/data-transformer-types.dia b/_images/sources/form/data-transformer-types.dia
new file mode 100644
index 00000000000..972b973a36d
Binary files /dev/null and b/_images/sources/form/data-transformer-types.dia differ
diff --git a/_images/sources/form/form-custom-type-postal-address-fragment-names.dia b/_images/sources/form/form-custom-type-postal-address-fragment-names.dia
index aebdadb4170..ca12fcdeadc 100644
Binary files a/_images/sources/form/form-custom-type-postal-address-fragment-names.dia and b/_images/sources/form/form-custom-type-postal-address-fragment-names.dia differ
diff --git a/_images/sources/form/form-custom-type-postal-address.dia b/_images/sources/form/form-custom-type-postal-address.dia
index 35a1eaebfd6..1b7c6226315 100644
Binary files a/_images/sources/form/form-custom-type-postal-address.dia and b/_images/sources/form/form-custom-type-postal-address.dia differ
diff --git a/_images/sources/form/form_events.dia b/_images/sources/form/form_events.dia
new file mode 100644
index 00000000000..8e7afb1cb83
Binary files /dev/null and b/_images/sources/form/form_events.dia differ
diff --git a/_images/sources/form/form_prepopulation_workflow.dia b/_images/sources/form/form_prepopulation_workflow.dia
new file mode 100644
index 00000000000..1d6d450fed1
Binary files /dev/null and b/_images/sources/form/form_prepopulation_workflow.dia differ
diff --git a/_images/sources/form/form_submission_workflow.dia b/_images/sources/form/form_submission_workflow.dia
new file mode 100644
index 00000000000..cc08f117878
Binary files /dev/null and b/_images/sources/form/form_submission_workflow.dia differ
diff --git a/_images/sources/form/form_workflow.dia b/_images/sources/form/form_workflow.dia
new file mode 100644
index 00000000000..30f9acabe2b
Binary files /dev/null and b/_images/sources/form/form_workflow.dia differ
diff --git a/_images/sources/http/xkcd-full.dia b/_images/sources/http/xkcd-full.dia
new file mode 100644
index 00000000000..a730d01c3ef
Binary files /dev/null and b/_images/sources/http/xkcd-full.dia differ
diff --git a/_images/sources/http/xkcd-request.dia b/_images/sources/http/xkcd-request.dia
new file mode 100644
index 00000000000..3796228bf1d
Binary files /dev/null and b/_images/sources/http/xkcd-request.dia differ
diff --git a/_images/sources/mercure/discovery.dia b/_images/sources/mercure/discovery.dia
new file mode 100644
index 00000000000..3db5c86f020
Binary files /dev/null and b/_images/sources/mercure/discovery.dia differ
diff --git a/_images/sources/mercure/hub.dia b/_images/sources/mercure/hub.dia
new file mode 100644
index 00000000000..b0dfb9d88d2
Binary files /dev/null and b/_images/sources/mercure/hub.dia differ
diff --git a/_images/sources/rate_limiter/fixed_window.dia b/_images/sources/rate_limiter/fixed_window.dia
new file mode 100644
index 00000000000..16282a2dcce
Binary files /dev/null and b/_images/sources/rate_limiter/fixed_window.dia differ
diff --git a/_images/sources/rate_limiter/sliding_window.dia b/_images/sources/rate_limiter/sliding_window.dia
new file mode 100644
index 00000000000..e16275d8995
Binary files /dev/null and b/_images/sources/rate_limiter/sliding_window.dia differ
diff --git a/_images/sources/rate_limiter/token_bucket.dia b/_images/sources/rate_limiter/token_bucket.dia
new file mode 100644
index 00000000000..16761971337
Binary files /dev/null and b/_images/sources/rate_limiter/token_bucket.dia differ
diff --git a/_images/sources/security/security_events.dia b/_images/sources/security/security_events.dia
new file mode 100644
index 00000000000..0a8afa73179
Binary files /dev/null and b/_images/sources/security/security_events.dia differ
diff --git a/_images/sources/serializer/serializer_workflow.dia b/_images/sources/serializer/serializer_workflow.dia
new file mode 100644
index 00000000000..3e2ea62558f
Binary files /dev/null and b/_images/sources/serializer/serializer_workflow.dia differ
diff --git a/_images/translation/pseudolocalization-interface-original.png b/_images/translation/pseudolocalization-interface-original.png
new file mode 100644
index 00000000000..d89f4e63a24
Binary files /dev/null and b/_images/translation/pseudolocalization-interface-original.png differ
diff --git a/_images/translation/pseudolocalization-interface-translated.png b/_images/translation/pseudolocalization-interface-translated.png
new file mode 100644
index 00000000000..496d5a0f86f
Binary files /dev/null and b/_images/translation/pseudolocalization-interface-translated.png differ
diff --git a/_images/translation/pseudolocalization-symfony-demo-disabled.png b/_images/translation/pseudolocalization-symfony-demo-disabled.png
new file mode 100644
index 00000000000..1a7472bd41f
Binary files /dev/null and b/_images/translation/pseudolocalization-symfony-demo-disabled.png differ
diff --git a/_images/translation/pseudolocalization-symfony-demo-enabled.png b/_images/translation/pseudolocalization-symfony-demo-enabled.png
new file mode 100644
index 00000000000..a23300a7271
Binary files /dev/null and b/_images/translation/pseudolocalization-symfony-demo-enabled.png differ
diff --git a/_includes/_annotation_loader_tip.rst.inc b/_includes/_annotation_loader_tip.rst.inc
deleted file mode 100644
index 0f4267b07f5..00000000000
--- a/_includes/_annotation_loader_tip.rst.inc
+++ /dev/null
@@ -1,19 +0,0 @@
-.. note::
-
- In order to use the annotation loader, you should have installed the
- ``doctrine/annotations`` and ``doctrine/cache`` packages with Composer.
-
-.. tip::
-
- Annotation classes aren't loaded automatically, so you must load them
- using a class loader like this::
-
- use Composer\Autoload\ClassLoader;
- use Doctrine\Common\Annotations\AnnotationRegistry;
-
- /** @var ClassLoader $loader */
- $loader = require __DIR__.'/../vendor/autoload.php';
-
- AnnotationRegistry::registerLoader([$loader, 'loadClass']);
-
- return $loader;
diff --git a/_includes/service_container/_my_mailer.rst.inc b/_includes/service_container/_my_mailer.rst.inc
deleted file mode 100644
index 01eafdfe87a..00000000000
--- a/_includes/service_container/_my_mailer.rst.inc
+++ /dev/null
@@ -1,33 +0,0 @@
-.. configuration-block::
-
- .. code-block:: yaml
-
- # config/services.yaml
- services:
- app.mailer:
- class: App\Mailer
- arguments: [sendmail]
-
- .. code-block:: xml
-
-
-
-
-
-
-
- sendmail
-
-
-
-
- .. code-block:: php
-
- // config/services.php
- use App\Mailer;
-
- $container->register('app.mailer', Mailer::class)
- ->addArgument('sendmail');
diff --git a/best_practices.rst b/best_practices.rst
index 9afabcb310e..2c393cae9c6 100644
--- a/best_practices.rst
+++ b/best_practices.rst
@@ -30,7 +30,7 @@ to create new Symfony applications:
.. code-block:: terminal
- $ symfony new my_project_name
+ $ symfony new my_project_directory
Under the hood, this Symfony binary command executes the needed `Composer`_
command to :ref:`create a new Symfony application `
@@ -51,6 +51,7 @@ self-explanatory and not coupled to Symfony:
│ └─ console
├─ config/
│ ├─ packages/
+ │ ├─ routes/
│ └─ services.yaml
├─ migrations/
├─ public/
@@ -81,18 +82,20 @@ Configuration
Use Environment Variables for Infrastructure Configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-These are the options that change from one machine to another (e.g. from your
-development machine to the production server) but which don't change the
+The values of these options change from one machine to another (e.g. from your
+development machine to the production server), but they don't modify the
application behavior.
:ref:`Use env vars in your project ` to define these options
and create multiple ``.env`` files to :ref:`configure env vars per environment `.
-Use Secret for Sensitive Information
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.. _use-secret-for-sensitive-information:
+
+Use Secrets for Sensitive Information
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-When your application has sensitive configuration - like an API key - you should
-store those securely via :doc:`secrets `.
+When your application has sensitive configuration, like an API key, you should
+store those securely via :doc:`Symfony’s secrets management system `.
Use Parameters for Application Configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -106,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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -117,7 +124,7 @@ Then, use just one or two words to describe the purpose of the parameter:
# config/services.yaml
parameters:
- # don't do this: 'dir' is too generic and it doesn't convey any meaning
+ # don't do this: 'dir' is too generic, and it doesn't convey any meaning
app.dir: '...'
# do this: short but easy to understand names
app.contents_dir: '...'
@@ -130,7 +137,7 @@ Use Constants to Define Options that Rarely Change
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Configuration options like the number of items to display in some listing rarely
-change. Instead of defining them as :ref:`service container parameters `,
+change. Instead of defining them as :ref:`configuration parameters `,
define them as PHP constants in the related classes. Example::
// src/Entity/Post.php
@@ -153,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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -162,7 +171,7 @@ InvoiceBundle, etc. However, a bundle is meant to be something that can be
reused as a stand-alone piece of software.
If you need to reuse some feature in your projects, create a bundle for it (in a
-private repository, to not make it publicly available). For the rest of your
+private repository, do not make it publicly available). For the rest of your
application code, use PHP namespaces to organize code instead of bundles.
Use Autowiring to Automate the Configuration of Application Services
@@ -170,7 +179,7 @@ Use Autowiring to Automate the Configuration of Application Services
:doc:`Service autowiring ` is a feature that
reads the type-hints on your constructor (or other methods) and automatically
-passes the correct services to each method, making unnecessary to configure
+passes the correct services to each method, making it unnecessary to configure
services explicitly and simplifying the application maintenance.
Use it in combination with :ref:`service autoconfiguration `
@@ -184,25 +193,25 @@ Services Should be Private Whenever Possible
those services via ``$container->get()``. Instead, you will need to use proper
dependency injection.
-Use the YAML Format to Configure your Own Services
+Use the YAML Format to Configure your own Services
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you use the :ref:`default services.yaml configuration `,
most services will be configured automatically. However, in some edge cases
you'll need to configure services (or parts of them) manually.
-YAML is the format recommended to configure services because it's friendly to
+YAML is the format recommended configuring services because it's friendly to
newcomers and concise, but Symfony also supports XML and PHP configuration.
-Use Annotations to Define the Doctrine Entity Mapping
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Use Attributes to Define the Doctrine Entity Mapping
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Doctrine entities are plain PHP objects that you store in some "database".
Doctrine only knows about your entities through the mapping metadata configured
for your model classes.
-Doctrine supports several metadata formats, but it's recommended to use
-annotations because they are by far the most convenient and agile way of setting
+Doctrine supports several metadata formats, but it's recommended to use PHP
+attributes because they are by far the most convenient and agile way of setting
up and looking for mapping information.
Controllers
@@ -222,42 +231,37 @@ nothing more than a few lines of *glue-code*, so you are not coupling the
important parts of your application.
.. _best-practice-controller-annotations:
+.. _best-practice-controller-attributes:
-Use Annotations to Configure Routing, Caching and Security
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Using annotations for routing, caching and security simplifies configuration.
-You don't need to browse several files created with different formats (YAML, XML,
-PHP): all the configuration is just where you need it and it only uses one format.
-
-Don't Use Annotations to Configure the Controller Template
+Use Attributes to Configure Routing, Caching, and Security
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-The ``@Template`` annotation is useful, but also involves some *magic*.
-Moreover, most of the time ``@Template`` is used without any parameters, which
-makes it more difficult to know which template is being rendered. It also hides
-the fact that a controller should always return a ``Response`` object.
+Using attributes for routing, caching, and security simplifies
+configuration. You don't need to browse several files created with different
+formats (YAML, XML, PHP): all the configuration is just where you require it,
+and it only uses one format.
Use Dependency Injection to Get Services
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-If you extend the base ``AbstractController``, you can only access to the most
+If you extend the base ``AbstractController``, you can only get access to the most
common services (e.g ``twig``, ``router``, ``doctrine``, etc.), directly from the
-container via ``$this->container->get()`` or ``$this->get()``.
+container via ``$this->container->get()``.
Instead, you must use dependency injection to fetch services by
:ref:`type-hinting action method arguments ` or
constructor arguments.
-Use ParamConverters If They Are Convenient
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Use Entity Value Resolvers If They Are Convenient
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-If you're using :doc:`Doctrine `, then you can *optionally* use the
-`ParamConverter`_ to automatically query for an entity and pass it as an argument
-to your controller. It will also show a 404 page if no entity can be found.
+If you're using :doc:`Doctrine `, then you can *optionally* use
+the :ref:`EntityValueResolver ` to
+automatically query for an entity and pass it as an argument to your
+controller. It will also show a 404 page if no entity can be found.
If the logic to get an entity from a route variable is more complex, instead of
-configuring the ParamConverter, it's better to make the Doctrine query inside
-the controller (e.g. by calling to a :doc:`Doctrine repository method `).
+configuring the EntityValueResolver, it's better to make the Doctrine query
+inside the controller (e.g. by calling to a :doc:`Doctrine repository method `).
Templates
---------
@@ -265,7 +269,7 @@ Templates
Use Snake Case for Template Names and Variables
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Use lowercased snake_case for template names, directories and variables (e.g.
+Use lowercase snake_case for template names, directories, and variables (e.g.
``user_profile`` instead of ``userProfile`` and ``product/edit_form.html.twig``
instead of ``Product/EditForm.html.twig``).
@@ -283,9 +287,9 @@ Forms
Define your Forms as PHP Classes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Creating :ref:`forms in classes ` allows to reuse
+Creating :ref:`forms in classes ` allows reusing
them in different parts of the application. Besides, not creating forms in
-controllers simplify the code and maintenance of the controllers.
+controllers simplifies the code and maintenance of the controllers.
Add Form Buttons in Templates
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -295,7 +299,7 @@ button of a form used to both create and edit items should change from "Add new"
to "Save changes" depending on where it's used.
Instead of adding buttons in form classes or the controllers, it's recommended
-to add buttons in the templates. This also improves the separation of concerns,
+to add buttons in the templates. This also improves the separation of concerns
because the button styling (CSS class and other attributes) is defined in the
template instead of in a PHP class.
@@ -317,8 +321,10 @@ Use a Single Action to Render and Process the Form
:ref:`Rendering forms ` and :ref:`processing forms `
are two of the main tasks when handling forms. Both are too similar (most of the
-times, almost identical), so it's much simpler to let a single controller action
-handle everything.
+time, almost identical), so it's much simpler to let a single controller action
+handle both.
+
+.. _best-practice-internationalization:
Internationalization
--------------------
@@ -327,8 +333,8 @@ Use the XLIFF Format for Your Translation Files
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Of all the translation formats supported by Symfony (PHP, Qt, ``.po``, ``.mo``,
-JSON, CSV, INI, etc.) XLIFF and gettext have the best support in the tools used
-by professional translators. And since it's based on XML, you can validate XLIFF
+JSON, CSV, INI, etc.), ``XLIFF`` and ``gettext`` have the best support in the tools used
+by professional translators. And since it's based on XML, you can validate ``XLIFF``
file contents as you write them.
Symfony also supports notes in XLIFF files, making them more user-friendly for
@@ -339,8 +345,8 @@ Use Keys for Translations Instead of Content Strings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Using keys simplifies the management of the translation files because you can
-change the original contents in templates, controllers and services without
-having to update all of the translation files.
+change the original contents in templates, controllers, and services without
+having to update all the translation files.
Keys should always describe their *purpose* and *not* their location. For
example, if a form has a field with the label "Username", then a nice key
@@ -365,29 +371,27 @@ Use the ``auto`` Password Hasher
The :ref:`auto password hasher ` automatically
selects the best possible encoder/hasher depending on your PHP installation.
-Currently, it tries to use ``sodium`` by default and falls back to ``bcrypt``.
+Currently, the default auto hasher is ``bcrypt``.
Use Voters to Implement Fine-grained Security Restrictions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If your security logic is complex, you should create custom
:doc:`security voters ` instead of defining long expressions
-inside the ``@Security`` annotation.
+inside the ``#[Security]`` attribute.
Web Assets
----------
-Use Webpack Encore to Process Web Assets
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.. _use-webpack-encore-to-process-web-assets:
-Web assets are things like CSS, JavaScript and image files that make the
-frontend of your site look and work great. `Webpack`_ is the leading JavaScript
-module bundler that compiles, transforms and packages assets for usage in a browser.
+Use AssetMapper to Manage Web Assets
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-:doc:`Webpack Encore ` is a JavaScript library that gets rid of most
-of Webpack complexity without hiding any of its features or distorting its usage
-and philosophy. It was originally created for Symfony applications, but it works
-for any application using any technology.
+Web assets are the CSS, JavaScript, and image files that make the frontend of
+your site look and work great. :doc:`AssetMapper ` lets
+you write modern JavaScript and CSS without the complexity of using a bundler
+such as `Webpack`_ (directly or via :doc:`Webpack Encore `).
Tests
-----
@@ -397,8 +401,8 @@ Smoke Test your URLs
In software engineering, `smoke testing`_ consists of *"preliminary testing to
reveal simple failures severe enough to reject a prospective software release"*.
-Using :ref:`PHPUnit data providers ` you can define a
-functional test that checks that all application URLs load successfully::
+Using `PHPUnit data providers`_ you can define a functional test that
+checks that all application URLs load successfully::
// tests/ApplicationAvailabilityFunctionalTest.php
namespace App\Tests;
@@ -410,7 +414,7 @@ functional test that checks that all application URLs load successfully::
/**
* @dataProvider urlProvider
*/
- public function testPageIsSuccessful($url)
+ public function testPageIsSuccessful($url): void
{
$client = self::createClient();
$client->request('GET', $url);
@@ -418,7 +422,7 @@ functional test that checks that all application URLs load successfully::
$this->assertResponseIsSuccessful();
}
- public function urlProvider()
+ public function urlProvider(): \Generator
{
yield ['/'];
yield ['/posts'];
@@ -430,25 +434,27 @@ functional test that checks that all application URLs load successfully::
}
Add this test while creating your application because it requires little effort
-and checks that none of your pages returns an error. Later you'll add more
+and checks that none of your pages returns an error. Later, you'll add more
specific tests for each page.
-Hardcode URLs in a Functional Test
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.. _hardcode-urls-in-a-functional-test:
+
+Hard-code URLs in a Functional Test
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-In Symfony applications it's recommended to :ref:`generate URLs `
+In Symfony applications, it's recommended to :ref:`generate URLs `
using routes to automatically update all links when a URL changes. However, if a
public URL changes, users won't be able to browse it unless you set up a
redirection to the new URL.
That's why it's recommended to use raw URLs in tests instead of generating them
-from routes. Whenever a route changes, tests will break and you'll know that
+from routes. Whenever a route changes, tests will fail, and you'll know that
you must set up a redirection.
.. _`Symfony Demo`: https://github.com/symfony/demo
.. _`download Symfony`: https://symfony.com/download
.. _`Composer`: https://getcomposer.org/
-.. _`ParamConverter`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
.. _`feature toggles`: https://en.wikipedia.org/wiki/Feature_toggle
.. _`smoke testing`: https://en.wikipedia.org/wiki/Smoke_testing_(software)
.. _`Webpack`: https://webpack.js.org/
+.. _`PHPUnit data providers`: https://docs.phpunit.de/en/9.6/writing-tests-for-phpunit.html#data-providers
diff --git a/bundles.rst b/bundles.rst
index 130171e0120..878bee3af4a 100644
--- a/bundles.rst
+++ b/bundles.rst
@@ -1,15 +1,12 @@
-.. index::
- single: Bundles
-
.. _page-creation-bundles:
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
@@ -25,14 +22,15 @@ file::
return [
// 'all' means that the bundle is enabled for any Symfony environment
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
- Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
- Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
- Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
- Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle::class => ['all' => true],
- Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
- Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
+ // ...
+
+ // this bundle is enabled only in 'dev'
+ Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true],
+ // ...
+
// this bundle is enabled only in 'dev' and 'test', so you can't use it in 'prod'
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
+ // ...
];
.. tip::
@@ -45,28 +43,32 @@ Creating a Bundle
-----------------
This section creates and enables a new bundle to show there are only a few steps required.
-The new bundle is called AcmeTestBundle, where the ``Acme`` portion is just a
-dummy name that should be replaced by some "vendor" name that represents you or
-your organization (e.g. ABCTestBundle for some company named ``ABC``).
+The new bundle is called AcmeBlogBundle, where the ``Acme`` portion is an example
+name that should be replaced by some "vendor" name that represents you or your
+organization (e.g. AbcBlogBundle for some company named ``Abc``).
-Start by creating a ``src/Acme/TestBundle/`` directory and adding a new file
-called ``AcmeTestBundle.php``::
+Start by creating a new class called ``AcmeBlogBundle``::
- // src/Acme/TestBundle/AcmeTestBundle.php
- namespace App\Acme\TestBundle;
+ // src/AcmeBlogBundle.php
+ namespace Acme\BlogBundle;
- use Symfony\Component\HttpKernel\Bundle\Bundle;
+ use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
- class AcmeTestBundle extends Bundle
+ class AcmeBlogBundle extends AbstractBundle
{
}
+.. warning::
+
+ If your bundle must be compatible with previous Symfony versions you have to
+ extend from the :class:`Symfony\\Component\\HttpKernel\\Bundle\\Bundle` instead.
+
.. tip::
- The name AcmeTestBundle follows the standard
+ The name AcmeBlogBundle follows the standard
:ref:`Bundle naming conventions `. You could
- also choose to shorten the name of the bundle to simply TestBundle by naming
- this class TestBundle (and naming the file ``TestBundle.php``).
+ also choose to shorten the name of the bundle to simply BlogBundle by naming
+ this class BlogBundle (and naming the file ``BlogBundle.php``).
This empty class is the only piece you need to create the new bundle. Though
commonly empty, this class is powerful and can be used to customize the behavior
@@ -75,10 +77,12 @@ of the bundle. Now that you've created the bundle, enable it::
// config/bundles.php
return [
// ...
- App\Acme\TestBundle\AcmeTestBundle::class => ['all' => true],
+ Acme\BlogBundle\AcmeBlogBundle::class => ['all' => true],
];
-And while it doesn't do anything yet, AcmeTestBundle is now ready to be used.
+And while it doesn't do anything yet, AcmeBlogBundle is now ready to be used.
+
+.. _bundles-directory-structure:
Bundle Directory Structure
--------------------------
@@ -87,35 +91,71 @@ The directory structure of a bundle is meant to help to keep code consistent
between all Symfony bundles. It follows a set of conventions, but is flexible
to be adjusted if needed:
-``Controller/``
- Contains the controllers of the bundle (e.g. ``RandomController.php``).
+``assets/``
+ Contains the web asset sources like JavaScript and TypeScript files, CSS and
+ Sass files, but also images and other assets related to the bundle that are
+ not in ``public/`` (e.g. Stimulus controllers).
-``DependencyInjection/``
- Holds certain Dependency Injection Extension classes, which may import service
- configuration, register compiler passes or more (this directory is not
- necessary).
+``config/``
+ Houses configuration, including routing configuration (e.g. ``routes.php``).
-``Resources/config/``
- Houses configuration, including routing configuration (e.g. ``routing.yaml``).
+``public/``
+ Contains web assets (images, compiled CSS and JavaScript files, etc.) and is
+ copied or symbolically linked into the project ``public/`` directory via the
+ ``assets:install`` console command.
-``Resources/views/``
- Holds templates organized by controller name (e.g. ``Random/index.html.twig``).
+``src/``
+ Contains all PHP classes related to the bundle logic (e.g. ``Controller/CategoryController.php``).
-``Resources/public/``
- Contains web assets (images, stylesheets, etc) and is copied or symbolically
- linked into the project ``public/`` directory via the ``assets:install`` console
- command.
+``templates/``
+ Holds templates organized by controller name (e.g. ``category/show.html.twig``).
-``Tests/``
+``tests/``
Holds all tests for the bundle.
-A bundle can be as small or large as the feature it implements. It contains
-only the files you need and nothing else.
+``translations/``
+ Holds translations organized by domain and locale (e.g. ``AcmeBlogBundle.en.xlf``).
+
+.. _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
+ structure.
+
+ When using the new ``AbstractBundle`` class, the bundle defaults to the
+ new structure. Override the ``Bundle::getPath()`` method to change to
+ the old structure::
+
+ class AcmeBlogBundle extends AbstractBundle
+ {
+ public function getPath(): string
+ {
+ return __DIR__;
+ }
+ }
+
+.. tip::
-As you move through the guides, you'll learn how to persist objects to a
-database, create and validate forms, create translations for your application,
-write tests and much more. Each of these has their own place and role within
-the bundle.
+ It's recommended to use the `PSR-4`_ autoload standard: use the namespace as key,
+ and the location of the bundle's main class (relative to ``composer.json``)
+ as value. As the main class is located in the ``src/`` directory of the bundle:
+
+ .. code-block:: json
+
+ {
+ "autoload": {
+ "psr-4": {
+ "Acme\\BlogBundle\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Acme\\BlogBundle\\Tests\\": "tests/"
+ }
+ }
+ }
Learn more
----------
@@ -127,3 +167,5 @@ Learn more
* :doc:`/bundles/prepend_extension`
.. _`third-party bundles`: https://github.com/search?q=topic%3Asymfony-bundle&type=Repositories
+.. _`Symfony 4.4 bundle documentation`: https://symfony.com/doc/4.4/bundles.html#bundle-directory-structure
+.. _`PSR-4`: https://www.php-fig.org/psr/psr-4/
diff --git a/bundles/best_practices.rst b/bundles/best_practices.rst
index e80050e2fce..023b58af162 100644
--- a/bundles/best_practices.rst
+++ b/bundles/best_practices.rst
@@ -1,6 +1,3 @@
-.. index::
- single: Bundle; Best practices
-
Best Practices for Reusable Bundles
===================================
@@ -9,9 +6,6 @@ configurable and extendable. Reusable bundles are those meant to be shared
privately across many company projects or publicly so any Symfony project can
install them.
-.. index::
- pair: Bundle; Naming conventions
-
.. _bundles-naming-conventions:
Bundle Name
@@ -22,8 +16,9 @@ interoperability standard for PHP namespaces and class names: it starts with a
vendor segment, followed by zero or more category segments, and it ends with the
namespace short name, which must end with ``Bundle``.
-A namespace becomes a bundle as soon as you add a bundle class to it. The
-bundle class name must follow these rules:
+A namespace becomes a bundle as soon as you add "a bundle class" to it (which is
+a class that extends :class:`Symfony\\Component\\HttpKernel\\Bundle\\Bundle`).
+The bundle class name must follow these rules:
* Use only alphanumeric characters and underscores;
* Use a StudlyCaps name (i.e. camelCase with an uppercase first letter);
@@ -63,35 +58,54 @@ configuration options (see below for some usage examples).
Directory Structure
-------------------
-The basic directory structure of an AcmeBlogBundle must read as follows:
+The following is the recommended directory structure of an AcmeBlogBundle:
.. code-block:: text
/
- ├─ AcmeBlogBundle.php
- ├─ Controller/
- ├─ README.md
- ├─ LICENSE
- ├─ Resources/
- │ ├─ config/
- │ ├─ doc/
- │ │ └─ index.rst
- │ ├─ translations/
- │ ├─ views/
- │ └─ public/
- └─ Tests/
+ ├── assets/
+ ├── config/
+ ├── docs/
+ │ └─ index.md
+ ├── public/
+ ├── src/
+ │ ├── Controller/
+ │ ├── DependencyInjection/
+ │ └── AcmeBlogBundle.php
+ ├── templates/
+ ├── tests/
+ ├── translations/
+ ├── LICENSE
+ └── README.md
+
+.. 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::
+
+ use Symfony\Component\HttpKernel\Bundle\Bundle;
+
+ class AcmeBlogBundle extends Bundle
+ {
+ public function getPath(): string
+ {
+ return \dirname(__DIR__);
+ }
+ }
**The following files are mandatory**, because they ensure a structure convention
that automated tools can rely on:
-* ``AcmeBlogBundle.php``: This is the class that transforms a plain directory
+* ``src/AcmeBlogBundle.php``: This is the class that transforms a plain directory
into a Symfony bundle (change this to your bundle's name);
* ``README.md``: This file contains the basic description of the bundle and it
usually shows some basic examples and links to its full documentation (it
can use any of the markup formats supported by GitHub, such as ``README.rst``);
* ``LICENSE``: The full contents of the license used by the code. Most third-party
bundles are published under the MIT license, but you can `choose any license`_;
-* ``Resources/doc/index.rst``: The root file for the Bundle documentation.
+* ``docs/index.md``: The root file for the Bundle documentation.
The depth of subdirectories should be kept to a minimum for the most used
classes and files. Two levels is the maximum.
@@ -107,19 +121,20 @@ and others are just conventions followed by most developers):
=================================================== ========================================
Type Directory
=================================================== ========================================
-Commands ``Command/``
-Controllers ``Controller/``
-Service Container Extensions ``DependencyInjection/``
-Doctrine ORM entities (when not using annotations) ``Entity/``
-Doctrine ODM documents (when not using annotations) ``Document/``
-Event Listeners ``EventListener/``
-Configuration (routes, services, etc.) ``Resources/config/``
-Web Assets (CSS, JS, images) ``Resources/public/``
-Translation files ``Resources/translations/``
-Validation (when not using annotations) ``Resources/config/validation/``
-Serialization (when not using annotations) ``Resources/config/serialization/``
-Templates ``Resources/views/``
-Unit and Functional Tests ``Tests/``
+Commands ``src/Command/``
+Controllers ``src/Controller/``
+Service Container Extensions ``src/DependencyInjection/``
+Doctrine ORM entities ``src/Entity/``
+Doctrine ODM documents ``src/Document/``
+Event Listeners ``src/EventListener/``
+Configuration (routes, services, etc.) ``config/``
+Web Assets (compiled CSS and JS, images) ``public/``
+Web Asset sources (``.scss``, ``.ts``, Stimulus) ``assets/``
+Translation files ``translations/``
+Validation (when not using attributes) ``config/validation/``
+Serialization (when not using attributes) ``config/serialization/``
+Templates ``templates/``
+Unit and Functional Tests ``tests/``
=================================================== ========================================
Classes
@@ -127,7 +142,7 @@ Classes
The bundle directory structure is used as the namespace hierarchy. For
instance, a ``ContentController`` controller which is stored in
-``Acme/BlogBundle/Controller/ContentController.php`` would have the fully
+``src/Controller/ContentController.php`` would have the fully
qualified class name of ``Acme\BlogBundle\Controller\ContentController``.
All classes and files must follow the :doc:`Symfony coding standards `.
@@ -149,11 +164,20 @@ standard Symfony autoloading instead.
A bundle should also not embed third-party libraries written in JavaScript,
CSS or any other language.
+Doctrine Entities/Documents
+---------------------------
+
+If the bundle includes Doctrine ORM entities and/or ODM documents, it's
+recommended to define their mapping using XML files stored in
+``config/doctrine/``. This allows to override that mapping using the
+:doc:`standard Symfony mechanism to override bundle parts `.
+This is not possible when using attributes to define the mapping.
+
Tests
-----
A bundle should come with a test suite written with PHPUnit and stored under
-the ``Tests/`` directory. Tests should follow the following principles:
+the ``tests/`` directory. Tests should follow the following principles:
* The test suite must be executable with a simple ``phpunit`` command run from
a sample application;
@@ -171,73 +195,61 @@ Continuous Integration
Testing bundle code continuously, including all its commits and pull requests,
is a good practice called Continuous Integration. There are several services
-providing this feature for free for open source projects. The most popular
-service for Symfony bundles is called `Travis CI`_.
-
-Here is the recommended configuration file (``.travis.yml``) for Symfony bundles,
-which test the two latest :doc:`LTS versions `
-of Symfony and the latest beta release:
-
-.. code-block:: yaml
-
- language: php
-
- cache:
- directories:
- - $HOME/.composer/cache/files
- - $HOME/symfony-bridge/.phpunit
-
- env:
- global:
- - PHPUNIT_FLAGS="-v"
- - SYMFONY_PHPUNIT_DIR="$HOME/symfony-bridge/.phpunit"
-
- matrix:
- fast_finish: true
- include:
- # Minimum supported dependencies with the latest and oldest PHP version
- - php: 7.2
- env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" SYMFONY_DEPRECATIONS_HELPER="max[self]=0"
- - php: 7.1
- env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" SYMFONY_DEPRECATIONS_HELPER="max[self]=0"
-
- # Test the latest stable release
- - php: 7.1
- - php: 7.2
- env: COVERAGE=true PHPUNIT_FLAGS="-v --coverage-text"
-
- # Test LTS versions. This makes sure we do not use Symfony packages with version greater
- # than 2 or 3 respectively. Read more at https://github.com/symfony/lts
- - php: 7.2
- env: DEPENDENCIES="symfony/lts:^2"
- - php: 7.2
- env: DEPENDENCIES="symfony/lts:^3"
-
- # Latest commit to master
- - php: 7.2
- env: STABILITY="dev"
-
- allow_failures:
- # Dev-master is allowed to fail.
- - env: STABILITY="dev"
-
- before_install:
- - if [[ $COVERAGE != true ]]; then phpenv config-rm xdebug.ini || true; fi
- - if ! [ -z "$STABILITY" ]; then composer config minimum-stability ${STABILITY}; fi;
- - if ! [ -v "$DEPENDENCIES" ]; then composer require --no-update ${DEPENDENCIES}; fi;
-
- install:
- - composer update ${COMPOSER_FLAGS} --prefer-dist --no-interaction
- - ./vendor/bin/simple-phpunit install
-
- script:
- - composer validate --strict --no-check-lock
- # simple-phpunit is the PHPUnit wrapper provided by the PHPUnit Bridge component and
- # it helps with testing legacy code and deprecations (composer require symfony/phpunit-bridge)
- - ./vendor/bin/simple-phpunit $PHPUNIT_FLAGS
-
-Consider using the `Travis cron`_ tool to make sure your project is built even if
-there are no new pull requests or commits.
+providing this feature for free for open source projects, like `GitHub Actions`_.
+
+A bundle should at least test:
+
+* The lower bound of their dependencies (by running ``composer update --prefer-lowest``);
+* The supported PHP versions;
+* All supported major Symfony versions (e.g. both ``6.4`` and ``7.x`` if
+ support is claimed for both).
+
+Thus, a bundle supporting PHP 7.4, 8.3 and 8.4, and Symfony 6.4 and 7.x should
+have at least this test matrix:
+
+=========== =============== ===================
+PHP version Symfony version Composer flags
+=========== =============== ===================
+7.4 ``6.4`` ``--prefer-lowest``
+8.3 ``7.*``
+8.4 ``7.*``
+=========== =============== ===================
+
+.. tip::
+
+ The tests should be run with the ``SYMFONY_DEPRECATIONS_HELPER``
+ env variable set to ``max[direct]=0``. This ensures no code in the
+ bundle uses deprecated features directly.
+
+ The lowest dependency tests can be run with this variable set to
+ ``disabled=1``.
+
+Require a Specific Symfony Version
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can use the special ``SYMFONY_REQUIRE`` environment variable together
+with Symfony Flex to install a specific Symfony version:
+
+.. code-block:: bash
+
+ # this requires Symfony 7.x for all Symfony packages
+ export SYMFONY_REQUIRE=7.*
+ # alternatively you can run this command to update composer.json config
+ # composer config extra.symfony.require "7.*"
+
+ # install Symfony Flex in the CI environment
+ composer global config --no-plugins allow-plugins.symfony/flex true
+ composer global require --no-progress --no-scripts --no-plugins symfony/flex
+
+ # install the dependencies (using --prefer-dist and --no-progress is
+ # recommended to have a better output and faster download time)
+ composer update --prefer-dist --no-progress
+
+.. warning::
+
+ If you want to cache your Composer dependencies, **do not** cache the
+ ``vendor/`` directory as this has side-effects. Instead cache
+ ``$HOME/.composer/cache/files``.
Installation
------------
@@ -254,10 +266,10 @@ Documentation
All classes and functions must come with full PHPDoc.
-Extensive documentation should also be provided in the ``Resources/doc/``
+Extensive documentation should also be provided in the ``docs/``
directory.
-The index file (for example ``Resources/doc/index.rst`` or
-``Resources/doc/index.md``) is the only mandatory file and must be the entry
+The index file (for example ``docs/index.rst`` or
+``docs/index.md``) is the only mandatory file and must be the entry
point for the documentation. The
:doc:`reStructuredText (rST) ` is the format
used to render the documentation on the Symfony website.
@@ -285,7 +297,7 @@ following standardized instructions in your ``README.md`` file.
Open a command console, enter your project directory and execute:
```console
- $ composer require
+ composer require
```
Applications that don't use Symfony Flex
@@ -297,7 +309,7 @@ following standardized instructions in your ``README.md`` file.
following command to download the latest stable version of this bundle:
```console
- $ composer require
+ composer require
```
### Step 2: Enable the Bundle
@@ -326,9 +338,9 @@ following standardized instructions in your ``README.md`` file.
Open a command console, enter your project directory and execute:
- .. code-block:: bash
+ .. code-block:: terminal
- $ composer require
+ composer require
Applications that don't use Symfony Flex
----------------------------------------
@@ -341,7 +353,7 @@ following standardized instructions in your ``README.md`` file.
.. code-block:: terminal
- $ composer require
+ composer require
Step 2: Enable the Bundle
~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -384,10 +396,14 @@ Translation Files
-----------------
If a bundle provides message translations, they must be defined in the XLIFF
-format; the domain should be named after the bundle name (``acme_blog``).
+format; the domain should be named after the bundle name (``AcmeBlog``).
A bundle must not override existing messages from another bundle.
+The translation domain must match the translation file names. For example,
+if the translation domain is ``AcmeBlog``, the English translation file name
+should be ``AcmeBlog.en.xlf``.
+
Configuration
-------------
@@ -418,8 +434,8 @@ The end user can provide values in any configuration file:
-
+ https://symfony.com/schema/dic/services/services-1.0.xsd"
+ >
fabien@example.com
@@ -429,7 +445,13 @@ The end user can provide values in any configuration file:
.. code-block:: php
// config/services.php
- $container->setParameter('acme_blog.author.email', 'fabien@example.com');
+ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+ return static function (ContainerConfigurator $container): void {
+ $container->parameters()
+ ->set('acme_blog.author.email', 'fabien@example.com')
+ ;
+ };
Retrieve the configuration parameters in your code from the container::
@@ -463,6 +485,13 @@ can be used for autowiring.
Services should not use autowiring or autoconfiguration. Instead, all services should
be defined explicitly.
+.. tip::
+
+ If there is no intention for the service id to be used by the end user, you can
+ mark it as *hidden* by prefixing it with a dot (e.g. ``.acme_blog.logger``).
+ This prevents the service from being listed in the default ``debug:container``
+ command output.
+
.. seealso::
You can learn much more about service loading in bundles reading this article:
@@ -477,7 +506,7 @@ The ``composer.json`` file should include at least the following metadata:
Consists of the vendor and the short bundle name. If you are releasing the
bundle on your own instead of on behalf of a company, use your personal name
(e.g. ``johnsmith/blog-bundle``). Exclude the vendor name from the bundle
- short name and separate each word with an hyphen. For example: AcmeBlogBundle
+ short name and separate each word with a hyphen. For example: AcmeBlogBundle
is transformed into ``blog-bundle`` and AcmeSocialConnectBundle is
transformed into ``social-connect-bundle``.
@@ -494,10 +523,22 @@ The ``composer.json`` file should include at least the following metadata:
This information is used by Symfony to load the classes of the bundle. It's
recommended to use the `PSR-4`_ autoload standard: use the namespace as key,
and the location of the bundle's main class (relative to ``composer.json``)
- as value. For example, if the main class is located in the bundle root
- directory: ``"autoload": { "psr-4": { "SomeVendor\\BlogBundle\\": "" } }``.
- If the main class is located in the ``src/`` directory of the bundle:
- ``"autoload": { "psr-4": { "SomeVendor\\BlogBundle\\": "src/" } }``.
+ as value. As the main class is located in the ``src/`` directory of the bundle:
+
+ .. code-block:: json
+
+ {
+ "autoload": {
+ "psr-4": {
+ "Acme\\BlogBundle\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Acme\\BlogBundle\\Tests\\": "tests/"
+ }
+ }
+ }
In order to make it easier for developers to find your bundle, register it on
`Packagist`_, the official repository for Composer packages.
@@ -506,16 +547,12 @@ Resources
---------
If the bundle references any resources (config files, translation files, etc.),
-don't use physical paths (e.g. ``__DIR__/config/services.xml``) but logical
-paths (e.g. ``@FooBundle/Resources/config/services.xml``).
-
-The logical paths are required because of the bundle overriding mechanism that
-lets you override any resource/file of any bundle. See :ref:`http-kernel-resource-locator`
-for more details about transforming physical paths into logical paths.
+you can use physical paths (e.g. ``__DIR__/config/services.xml``).
-Beware that templates use a simplified version of the logical path shown above.
-For example, an ``index.html.twig`` template located in the ``Resources/views/Default/``
-directory of the FooBundle, is referenced as ``@Foo/Default/index.html.twig``.
+In the past, we recommended to only use logical paths (e.g.
+``@AcmeBlogBundle/config/services.xml``) and resolve them with the
+:ref:`resource locator ` provided by the Symfony
+kernel, but this is no longer a recommended practice.
Learn more
----------
@@ -529,5 +566,4 @@ Learn more
.. _`Packagist`: https://packagist.org/
.. _`choose any license`: https://choosealicense.com/
.. _`valid license identifier`: https://spdx.org/licenses/
-.. _`Travis CI`: https://travis-ci.org/
-.. _`Travis cron`: https://docs.travis-ci.com/user/cron-jobs/
+.. _`GitHub Actions`: https://docs.github.com/en/free-pro-team@latest/actions
diff --git a/bundles/configuration.rst b/bundles/configuration.rst
index 4066c36772e..dedfada2ea2 100644
--- a/bundles/configuration.rst
+++ b/bundles/configuration.rst
@@ -1,7 +1,3 @@
-.. index::
- single: Configuration; Semantic
- single: Bundle; Extension configuration
-
How to Create Friendly Configuration for a Bundle
=================================================
@@ -20,19 +16,22 @@ as integration of other related components:
.. code-block:: yaml
+ # config/packages/framework.yaml
framework:
form: true
.. code-block:: xml
+
-
+ https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"
+ >
@@ -40,15 +39,117 @@ as integration of other related components:
.. code-block:: php
- $container->loadFromExtension('framework', [
- 'form' => true,
- ]);
+ // config/packages/framework.php
+ use Symfony\Config\FrameworkConfig;
+
+ return static function (FrameworkConfig $framework): void {
+ $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::
@@ -64,29 +165,30 @@ can add some configuration that looks like this:
.. code-block:: xml
-
+
-
+ https://symfony.com/schema/dic/services/services-1.0.xsd"
+ >
-
+
-
-
.. code-block:: php
// config/packages/acme_social.php
- $container->loadFromExtension('acme_social', [
- 'twitter' => [
- 'client_id' => 123,
- 'client_secret' => 'your_secret',
- ],
- ]);
+ use Symfony\Config\AcmeSocialConfig;
+
+ return static function (AcmeSocialConfig $acmeSocial): void {
+ $acmeSocial->twitter()
+ ->clientId(123)
+ ->clientSecret('your_secret');
+ };
The basic idea is that instead of having the user override individual
parameters, you let the user configure just a few, specifically created,
@@ -97,7 +199,7 @@ load correct services and parameters inside an "Extension" class.
The root key of your bundle configuration (``acme_social`` in the previous
example) is automatically determined from your bundle name (it's the
- `snake case`_ of the bundle name without the ``Bundle`` suffix ).
+ `snake case`_ of the bundle name without the ``Bundle`` suffix).
.. seealso::
@@ -107,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.
@@ -172,7 +274,7 @@ of your bundle's configuration.
The ``Configuration`` class to handle the sample configuration looks like::
- // src/Acme/SocialBundle/DependencyInjection/Configuration.php
+ // src/DependencyInjection/Configuration.php
namespace Acme\SocialBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
@@ -180,7 +282,7 @@ The ``Configuration`` class to handle the sample configuration looks like::
class Configuration implements ConfigurationInterface
{
- public function getConfigTreeBuilder()
+ public function getConfigTreeBuilder(): TreeBuilder
{
$treeBuilder = new TreeBuilder('acme_social');
@@ -213,8 +315,8 @@ This class can now be used in your ``load()`` method to merge configurations and
force validation (e.g. if an additional option was passed, an exception will be
thrown)::
- // src/Acme/SocialBundle/DependencyInjection/AcmeSocialExtension.php
- public function load(array $configs, ContainerBuilder $container)
+ // src/DependencyInjection/AcmeSocialExtension.php
+ public function load(array $configs, ContainerBuilder $container): void
{
$configuration = new Configuration();
@@ -233,15 +335,15 @@ For example, imagine your bundle has the following example config:
.. code-block:: xml
-
+
-
+ https://symfony.com/schema/dic/services/services-1.0.xsd"
+ >
-
@@ -165,23 +162,26 @@ will create pools with service IDs that follow the pattern ``cache.[type]``.
.. code-block:: php
// config/packages/cache.php
- $container->loadFromExtension('framework', [
- 'cache' => [
+ use Symfony\Config\FrameworkConfig;
+
+ return static function (FrameworkConfig $framework): void {
+ $framework->cache()
// Only used with cache.adapter.filesystem
- 'directory' => '%kernel.cache_dir%/pools',
-
- // Service: cache.doctrine
- 'default_doctrine_provider' => 'app.doctrine_cache',
- // Service: cache.psr6
- 'default_psr6_provider' => 'app.my_psr6_service',
- // Service: cache.redis
- 'default_redis_provider' => 'redis://localhost',
- // Service: cache.memcached
- 'default_memcached_provider' => 'memcached://localhost',
- // Service: cache.pdo
- 'default_pdo_provider' => 'doctrine.dbal.default_connection',
- ],
- ]);
+ ->directory('%kernel.cache_dir%/pools')
+
+ ->defaultDoctrineDbalProvider('doctrine.dbal.default_connection')
+ ->defaultPsr6Provider('app.my_psr6_service')
+ ->defaultRedisProvider('redis://localhost')
+ ->defaultMemcachedProvider('memcached://localhost')
+ ->defaultPdoProvider('pgsql:host=localhost')
+ ;
+ };
+
+.. versionadded:: 7.1
+
+ Using a DSN as the provider for the PDO adapter was introduced in Symfony 7.1.
+
+.. _cache-create-pools:
Creating Custom (Namespaced) Pools
----------------------------------
@@ -234,8 +234,8 @@ You can also create more customized pools:
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony
- https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
-
+ https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"
+ >
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/services.php
+ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+ return function(ContainerConfigurator $container): void {
+ $container->services()
+ // ...
+
+ ->set('app.cache.adapter.redis')
+ ->parent('cache.adapter.redis')
+ ->tag('cache.pool', ['namespace' => 'my_custom_namespace'])
+ ;
+ };
+
Custom Provider Options
-----------------------
@@ -369,11 +416,14 @@ and use that when configuring the pool.
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony
- https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
-
+ https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"
+ >
-
+
@@ -392,27 +442,27 @@ and use that when configuring the pool.
.. code-block:: php
// config/packages/cache.php
- use Symfony\Component\Cache\Adapter\RedisAdapter;
+ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
- $container->loadFromExtension('framework', [
- 'cache' => [
- 'pools' => [
- 'cache.my_redis' => [
- 'adapter' => 'cache.adapter.redis',
- 'provider' => 'app.my_custom_redis_provider',
- ],
- ],
- ],
- ]);
-
- $container->register('app.my_custom_redis_provider', \Redis::class)
- ->setFactory([RedisAdapter::class, 'createConnection'])
- ->addArgument('redis://localhost')
- ->addArgument([
- 'retry_interval' => 2,
- 'timeout' => 10
- ])
- ;
+ use Symfony\Component\Cache\Adapter\RedisAdapter;
+ use Symfony\Component\DependencyInjection\ContainerBuilder;
+ use Symfony\Config\FrameworkConfig;
+
+ return static function (ContainerBuilder $container, FrameworkConfig $framework): void {
+ $framework->cache()
+ ->pool('cache.my_redis')
+ ->adapters(['cache.adapter.redis'])
+ ->provider('app.my_custom_redis_provider');
+
+ $container->register('app.my_custom_redis_provider', \Redis::class)
+ ->setFactory([RedisAdapter::class, 'createConnection'])
+ ->addArgument('redis://localhost')
+ ->addArgument([
+ 'retry_interval' => 2,
+ 'timeout' => 10
+ ])
+ ;
+ };
Creating a Cache Chain
----------------------
@@ -456,11 +506,14 @@ Symfony stores the item automatically in all the missing pools.
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:framework="http://symfony.com/schema/dic/symfony"
xsi:schemaLocation="http://symfony.com/schema/dic/services
- https://symfony.com/schema/dic/services/services-1.0.xsd">
-
+ https://symfony.com/schema/dic/services/services-1.0.xsd
+ http://symfony.com/schema/dic/symfony
+ https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"
+ >
-
+
@@ -472,20 +525,19 @@ Symfony stores the item automatically in all the missing pools.
.. code-block:: php
// config/packages/cache.php
- $container->loadFromExtension('framework', [
- 'cache' => [
- 'pools' => [
- 'my_cache_pool' => [
- 'default_lifetime' => 31536000, // One year
- 'adapters' => [
- 'cache.adapter.array',
- 'cache.adapter.apcu',
- ['name' => 'cache.adapter.redis', 'provider' => 'redis://user:password@example.com'],
- ],
- ],
- ],
- ],
- ]);
+ use Symfony\Config\FrameworkConfig;
+
+ return static function (FrameworkConfig $framework): void {
+ $framework->cache()
+ ->pool('my_cache_pool')
+ ->defaultLifetime(31536000) // One year
+ ->adapters([
+ 'cache.adapter.array',
+ 'cache.adapter.apcu',
+ ['name' => 'cache.adapter.redis', 'provider' => 'redis://user:password@example.com'],
+ ])
+ ;
+ };
Using Cache Tags
----------------
@@ -493,30 +545,28 @@ 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;
class SomeClass
{
- private $myCachePool;
-
// using autowiring to inject the cache pool
- public function __construct(TagAwareCacheInterface $myCachePool)
- {
- $this->myCachePool = $myCachePool;
+ public function __construct(
+ private TagAwareCacheInterface $myCachePool,
+ ) {
}
- public function someMethod()
+ public function someMethod(): void
{
- $value0 = $this->myCachePool->get('item_0', function (ItemInterface $item) {
+ $value0 = $this->myCachePool->get('item_0', function (ItemInterface $item): string {
$item->tag(['foo', 'bar']);
return 'debug';
});
- $value1 = $this->myCachePool->get('item_1', function (ItemInterface $item) {
+ $value1 = $this->myCachePool->get('item_1', function (ItemInterface $item): string {
$item->tag('foo');
return 'debug';
@@ -539,7 +589,7 @@ to enable this feature. This could be added by using the following configuration
cache:
pools:
my_cache_pool:
- adapter: cache.adapter.redis
+ adapter: cache.adapter.redis_tag_aware
tags: true
.. code-block:: xml
@@ -552,11 +602,14 @@ to enable this feature. This could be added by using the following configuration
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony
- https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
-
+ https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"
+ >
-
+
@@ -564,19 +617,15 @@ to enable this feature. This could be added by using the following configuration
.. code-block:: php
// config/packages/cache.php
- use Symfony\Component\Cache\Adapter\ChainAdapter;
- use Symfony\Component\DependencyInjection\Reference;
+ use Symfony\Config\FrameworkConfig;
- $container->loadFromExtension('framework', [
- 'cache' => [
- 'pools' => [
- 'my_cache_pool' => [
- 'adapter' => 'cache.adapter.redis',
- 'tags' => true,
- ],
- ],
- ],
- ]);
+ return static function (FrameworkConfig $framework): void {
+ $framework->cache()
+ ->pool('my_cache_pool')
+ ->tags(true)
+ ->adapters(['cache.adapter.redis_tag_aware'])
+ ;
+ };
Tags are stored in the same pool by default. This is good in most scenarios. But
sometimes it might be better to store the tags in a different pool. That could be
@@ -604,12 +653,17 @@ achieved by specifying the adapter.
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:framework="http://symfony.com/schema/dic/symfony"
xsi:schemaLocation="http://symfony.com/schema/dic/services
- https://symfony.com/schema/dic/services/services-1.0.xsd">
-
+ https://symfony.com/schema/dic/services/services-1.0.xsd
+ http://symfony.com/schema/dic/symfony
+ https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"
+ >
-
-
+
+
@@ -617,19 +671,20 @@ achieved by specifying the adapter.
.. code-block:: php
// config/packages/cache.php
- $container->loadFromExtension('framework', [
- 'cache' => [
- 'pools' => [
- 'my_cache_pool' => [
- 'adapter' => 'cache.adapter.redis',
- 'tags' => 'tag_pool',
- ],
- 'tag_pool' => [
- 'adapter' => 'cache.adapter.apcu',
- ],
- ],
- ],
- ]);
+ use Symfony\Config\FrameworkConfig;
+
+ return static function (FrameworkConfig $framework): void {
+ $framework->cache()
+ ->pool('my_cache_pool')
+ ->tags('tag_pool')
+ ->adapters(['cache.adapter.redis'])
+ ;
+
+ $framework->cache()
+ ->pool('tag_pool')
+ ->adapters(['cache.adapter.apcu'])
+ ;
+ };
.. note::
@@ -670,8 +725,256 @@ Clear all custom pools:
$ php bin/console cache:pool:clear cache.app_clearer
+Clear all cache pools:
+
+.. code-block:: terminal
+
+ $ php bin/console cache:pool:clear --all
+
+Clear all cache pools except some:
+
+.. code-block:: terminal
+
+ $ php bin/console cache:pool:clear --all --exclude=my_cache_pool --exclude=another_cache_pool
+
Clear all caches everywhere:
.. code-block:: terminal
$ php bin/console cache:pool:clear cache.global_clearer
+
+Clear cache by tag(s):
+
+.. code-block:: terminal
+
+ # invalidate tag1 from all taggable pools
+ $ php bin/console cache:pool:invalidate-tags tag1
+
+ # invalidate tag1 & tag2 from all taggable pools
+ $ php bin/console cache:pool:invalidate-tags tag1 tag2
+
+ # invalidate tag1 & tag2 from cache.app pool
+ $ php bin/console cache:pool:invalidate-tags tag1 tag2 --pool=cache.app
+
+ # invalidate tag1 & tag2 from cache1 & cache2 pools
+ $ php bin/console cache:pool:invalidate-tags tag1 tag2 -p cache1 -p cache2
+
+Encrypting the Cache
+--------------------
+
+To encrypt the cache using ``libsodium``, you can use the
+:class:`Symfony\\Component\\Cache\\Marshaller\\SodiumMarshaller`.
+
+First, you need to generate a secure key and add it to your :doc:`secret
+store ` as ``CACHE_DECRYPTION_KEY``:
+
+.. code-block:: terminal
+
+ $ php -r 'echo base64_encode(sodium_crypto_box_keypair());'
+
+Then, register the ``SodiumMarshaller`` service using this key:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/cache.yaml
+
+ # ...
+ services:
+ Symfony\Component\Cache\Marshaller\SodiumMarshaller:
+ decorates: cache.default_marshaller
+ arguments:
+ - ['%env(base64:CACHE_DECRYPTION_KEY)%']
+ # use multiple keys in order to rotate them
+ #- ['%env(base64:CACHE_DECRYPTION_KEY)%', '%env(base64:OLD_CACHE_DECRYPTION_KEY)%']
+ - '@.inner'
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+ env(base64:CACHE_DECRYPTION_KEY)
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/cache.php
+ use Symfony\Component\Cache\Marshaller\SodiumMarshaller;
+ use Symfony\Component\DependencyInjection\ChildDefinition;
+ use Symfony\Component\DependencyInjection\Reference;
+
+ // ...
+ $container->setDefinition(SodiumMarshaller::class, new ChildDefinition('cache.default_marshaller'))
+ ->addArgument(['env(base64:CACHE_DECRYPTION_KEY)'])
+ // use multiple keys in order to rotate them
+ //->addArgument(['env(base64:CACHE_DECRYPTION_KEY)', 'env(base64:OLD_CACHE_DECRYPTION_KEY)'])
+ ->addArgument(new Reference('.inner'));
+
+.. 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.
+
+When configuring multiple keys, the first key will be used for reading and
+writing, and the additional key(s) will only be used for reading. Once all
+cache items encrypted with the old key have expired, you can completely remove
+``OLD_CACHE_DECRYPTION_KEY``.
+
+Computing Cache Values Asynchronously
+-------------------------------------
+
+The Cache component uses the `probabilistic early expiration`_ algorithm to
+protect against the :ref:`cache stampede ` problem.
+This means that some cache items are elected for early-expiration while they are
+still fresh.
+
+By default, expired cache items are computed synchronously. However, you can
+compute them asynchronously by delegating the value computation to a background
+worker using the :doc:`Messenger component `. In this case,
+when an item is queried, its cached value is immediately returned and a
+:class:`Symfony\\Component\\Cache\\Messenger\\EarlyExpirationMessage` is
+dispatched through a Messenger bus.
+
+When this message is handled by a message consumer, the refreshed cache value is
+computed asynchronously. The next time the item is queried, the refreshed value
+will be fresh and returned.
+
+First, create a service that will compute the item's value::
+
+ // src/Cache/CacheComputation.php
+ namespace App\Cache;
+
+ use Symfony\Contracts\Cache\ItemInterface;
+
+ class CacheComputation
+ {
+ public function compute(ItemInterface $item): string
+ {
+ $item->expiresAfter(5);
+
+ // this is just a random example; here you must do your own calculation
+ return sprintf('#%06X', mt_rand(0, 0xFFFFFF));
+ }
+ }
+
+This cache value will be requested from a controller, another service, etc.
+In the following example, the value is requested from a controller::
+
+ // src/Controller/CacheController.php
+ namespace App\Controller;
+
+ use App\Cache\CacheComputation;
+ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+ use Symfony\Component\Routing\Attribute\Route;
+ use Symfony\Contracts\Cache\CacheInterface;
+ use Symfony\Contracts\Cache\ItemInterface;
+
+ class CacheController extends AbstractController
+ {
+ #[Route('/cache', name: 'cache')]
+ public function index(CacheInterface $asyncCache): Response
+ {
+ // pass to the cache the service method that refreshes the item
+ $cachedValue = $asyncCache->get('my_value', [CacheComputation::class, 'compute'])
+
+ // ...
+ }
+ }
+
+Finally, configure a new cache pool (e.g. called ``async.cache``) that will use
+a message bus to compute values in a worker:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/framework.yaml
+ framework:
+ cache:
+ pools:
+ async.cache:
+ early_expiration_message_bus: messenger.default_bus
+
+ messenger:
+ transports:
+ async_bus: '%env(MESSENGER_TRANSPORT_DSN)%'
+ routing:
+ 'Symfony\Component\Cache\Messenger\EarlyExpirationMessage': async_bus
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+ %env(MESSENGER_TRANSPORT_DSN)%
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/framework/framework.php
+ use function Symfony\Component\DependencyInjection\Loader\Configurator\env;
+ use Symfony\Component\Cache\Messenger\EarlyExpirationMessage;
+ use Symfony\Config\FrameworkConfig;
+
+ return static function (FrameworkConfig $framework): void {
+ $framework->cache()
+ ->pool('async.cache')
+ ->earlyExpirationMessageBus('messenger.default_bus');
+
+ $framework->messenger()
+ ->transport('async_bus')
+ ->dsn(env('MESSENGER_TRANSPORT_DSN'))
+ ->routing(EarlyExpirationMessage::class)
+ ->senders(['async_bus']);
+ };
+
+You can now start the consumer:
+
+.. code-block:: terminal
+
+ $ php bin/console messenger:consume async_bus
+
+That's it! Now, whenever an item is queried from this cache pool, its cached
+value will be returned immediately. If it is elected for early-expiration, a
+message will be sent through to bus to schedule a background computation to refresh
+the value.
+
+.. _`probabilistic early expiration`: https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration
diff --git a/components/asset.rst b/components/asset.rst
index 20b5ce20b2d..d6d3f485859 100644
--- a/components/asset.rst
+++ b/components/asset.rst
@@ -1,14 +1,10 @@
-.. index::
- single: Asset
- single: Components; Asset
-
The Asset Component
===================
The Asset component manages URL generation and versioning of web assets such
as CSS stylesheets, JavaScript files and image files.
-In the past, it was common for web applications to hardcode URLs of web assets.
+In the past, it was common for web applications to hard-code the URLs of web assets.
For example:
.. code-block:: html
@@ -51,6 +47,8 @@ Installation
Usage
-----
+.. _asset-packages:
+
Asset Packages
~~~~~~~~~~~~~~
@@ -165,21 +163,33 @@ In those cases, use the
echo $package->getUrl('css/app.css');
// result: build/css/app.b916426ea1d10021f3f17ce8031f93c2.css
+If you request an asset that is *not found* in the ``rev-manifest.json`` file,
+the original - *unmodified* - asset path will be returned. The ``$strictMode``
+argument helps debug issues because it throws an exception when the asset is not
+listed in the manifest::
+
+ use Symfony\Component\Asset\Package;
+ use Symfony\Component\Asset\VersionStrategy\JsonManifestVersionStrategy;
+
+ // The value of $strictMode can be specific per environment "true" for debugging and "false" for stability.
+ $strictMode = true;
+ // assumes the JSON file above is called "rev-manifest.json"
+ $package = new Package(new JsonManifestVersionStrategy(__DIR__.'/rev-manifest.json', null, $strictMode));
+
+ echo $package->getUrl('not-found.css');
+ // error:
+
If your JSON file is not on your local filesystem but is accessible over HTTP,
-use the :class:`Symfony\\Component\\Asset\\VersionStrategy\\RemoteJsonManifestVersionStrategy`
+use the :class:`Symfony\\Component\\Asset\\VersionStrategy\\JsonManifestVersionStrategy`
with the :doc:`HttpClient component `::
use Symfony\Component\Asset\Package;
- use Symfony\Component\Asset\VersionStrategy\RemoteJsonManifestVersionStrategy;
+ use Symfony\Component\Asset\VersionStrategy\JsonManifestVersionStrategy;
use Symfony\Component\HttpClient\HttpClient;
$httpClient = HttpClient::create();
$manifestUrl = 'https://cdn.example.com/rev-manifest.json';
- $package = new Package(new RemoteJsonManifestVersionStrategy($manifestUrl, $httpClient));
-
-.. versionadded:: 5.1
-
- The ``RemoteJsonManifestVersionStrategy`` was introduced in Symfony 5.1.
+ $package = new Package(new JsonManifestVersionStrategy($manifestUrl, $httpClient));
Custom Version Strategies
.........................
@@ -193,19 +203,19 @@ every day::
class DateVersionStrategy implements VersionStrategyInterface
{
- private $version;
+ private string $version;
public function __construct()
{
$this->version = date('Ymd');
}
- public function getVersion($path)
+ public function getVersion(string $path): string
{
return $this->version;
}
- public function applyVersion($path)
+ public function applyVersion(string $path): string
{
return sprintf('%s?v=%s', $path, $this->getVersion($path));
}
@@ -276,12 +286,12 @@ class to generate absolute URLs for their assets::
// ...
$urlPackage = new UrlPackage(
- 'http://static.example.com/images/',
+ 'https://static.example.com/images/',
new StaticVersionStrategy('v1')
);
echo $urlPackage->getUrl('/logo.png');
- // result: http://static.example.com/images/logo.png?v1
+ // result: https://static.example.com/images/logo.png?v1
You can also pass a schema-agnostic URL::
@@ -308,15 +318,15 @@ constructor::
// ...
$urls = [
- '//static1.example.com/images/',
- '//static2.example.com/images/',
+ 'https://static1.example.com/images/',
+ 'https://static2.example.com/images/',
];
$urlPackage = new UrlPackage($urls, new StaticVersionStrategy('v1'));
echo $urlPackage->getUrl('/logo.png');
- // result: http://static1.example.com/images/logo.png?v1
+ // result: https://static1.example.com/images/logo.png?v1
echo $urlPackage->getUrl('/icon.png');
- // result: http://static2.example.com/images/icon.png?v1
+ // result: https://static2.example.com/images/icon.png?v1
For each asset, one of the URLs will be randomly used. But, the selection
is deterministic, meaning that each asset will always be served by the same
@@ -366,14 +376,14 @@ they all have different base paths::
$defaultPackage = new Package($versionStrategy);
$namedPackages = [
- 'img' => new UrlPackage('http://img.example.com/', $versionStrategy),
+ 'img' => new UrlPackage('https://img.example.com/', $versionStrategy),
'doc' => new PathPackage('/somewhere/deep/for/documents', $versionStrategy),
];
$packages = new Packages($defaultPackage, $namedPackages);
The ``Packages`` class allows to define a default package, which will be applied
-to assets that don't define the name of package to use. In addition, this
+to assets that don't define the name of the package to use. In addition, this
application defines a package named ``img`` to serve images from an external
domain and a ``doc`` package to avoid repeating long paths when linking to a
document inside a template::
@@ -382,7 +392,7 @@ document inside a template::
// result: /main.css?v1
echo $packages->getUrl('/logo.png', 'img');
- // result: http://img.example.com/logo.png?v1
+ // result: https://img.example.com/logo.png?v1
echo $packages->getUrl('resume.pdf', 'doc');
// result: /somewhere/deep/for/documents/resume.pdf?v1
diff --git a/components/browser_kit.rst b/components/browser_kit.rst
index b73783f95e0..8cf0772298c 100644
--- a/components/browser_kit.rst
+++ b/components/browser_kit.rst
@@ -1,20 +1,9 @@
-.. index::
- single: BrowserKit
- single: Components; BrowserKit
-
The BrowserKit Component
========================
The BrowserKit component simulates the behavior of a web browser, allowing
you to make requests, click on links and submit forms programmatically.
-.. note::
-
- In Symfony versions prior to 4.3, the BrowserKit component could only make
- internal requests to your application. Starting from Symfony 4.3, this
- component can also :ref:`make HTTP requests to any public site `
- when using it in combination with the :doc:`HttpClient component `.
-
Installation
------------
@@ -49,7 +38,7 @@ This method accepts a request and should return a response::
class Client extends AbstractBrowser
{
- protected function doRequest($request)
+ protected function doRequest($request): Response
{
// ... convert request into a response
@@ -60,7 +49,7 @@ This method accepts a request and should return a response::
For a simple implementation of a browser based on the HTTP layer, have a look
at the :class:`Symfony\\Component\\BrowserKit\\HttpBrowser` provided by
:ref:`this component `. For an implementation based
-on ``HttpKernelInterface``, have a look at the :class:`Symfony\\Component\\HttpKernel\\Client`
+on ``HttpKernelInterface``, have a look at the :class:`Symfony\\Component\\HttpKernel\\HttpClientKernel`
provided by the :doc:`HttpKernel component `.
Making Requests
@@ -80,6 +69,16 @@ The value returned by the ``request()`` method is an instance of the
:doc:`DomCrawler component `, which allows accessing
and traversing HTML elements programmatically.
+The :method:`Symfony\\Component\\BrowserKit\\AbstractBrowser::jsonRequest` method,
+which defines the same arguments as the ``request()`` method, is a shortcut to
+convert the request parameters into a JSON string and set the needed HTTP headers::
+
+ use Acme\Client;
+
+ $client = new Client();
+ // this encodes parameters as JSON and sets the required CONTENT_TYPE and HTTP_ACCEPT headers
+ $crawler = $client->jsonRequest('GET', '/', ['some_parameter' => 'some_value']);
+
The :method:`Symfony\\Component\\BrowserKit\\AbstractBrowser::xmlHttpRequest` method,
which defines the same arguments as the ``request()`` method, is a shortcut to
make AJAX requests::
@@ -113,6 +112,24 @@ provides access to the link properties (e.g. ``$link->getMethod()``,
$link = $crawler->selectLink('Go elsewhere...')->link();
$client->click($link);
+The :method:`Symfony\\Component\\BrowserKit\\AbstractBrowser::click` and
+:method:`Symfony\\Component\\BrowserKit\\AbstractBrowser::clickLink` methods
+can take an optional ``serverParameters`` argument. This
+parameter allows to send additional information like headers when clicking
+on a link::
+
+ use Acme\Client;
+
+ $client = new Client();
+ $client->request('GET', '/product/123');
+
+ // works both with `click()`...
+ $link = $crawler->selectLink('Go elsewhere...')->link();
+ $client->click($link, ['X-Custom-Header' => 'Some data']);
+
+ // ... and `clickLink()`
+ $crawler = $client->clickLink('Go elsewhere...', ['X-Custom-Header' => 'Some data']);
+
Submitting Forms
~~~~~~~~~~~~~~~~
@@ -126,7 +143,7 @@ field values, etc.) before submitting it::
$crawler = $client->request('GET', 'https://github.com/login');
// find the form with the 'Log in' button and submit it
- // 'Log in' can be the text content, id, value or name of a or
+ // 'Log in' can be the text content, id or name of a or
$client->submitForm('Log in');
// the second optional argument lets you override the default form field values
@@ -164,14 +181,10 @@ provides access to the form properties (e.g. ``$form->getUri()``,
Custom Header Handling
~~~~~~~~~~~~~~~~~~~~~~
-.. versionadded:: 5.2
-
- The ``getHeaders()`` method was introduced in Symfony 5.2.
-
-The optional HTTP headers passed to the ``request()`` method follows the FastCGI
+The optional HTTP headers passed to the ``request()`` method follow the FastCGI
request format (uppercase, underscores instead of dashes and prefixed with ``HTTP_``).
Before saving those headers to the request, they are lower-cased, with ``HTTP_``
-stripped, and underscores turned to dashes.
+stripped, and underscores converted into dashes.
If you're making a request to an application that has special rules about header
capitalization or punctuation, override the ``getHeaders()`` method, which must
@@ -274,6 +287,27 @@ into the client constructor::
$client = new Client([], null, $cookieJar);
// ...
+.. _component-browserkit-sending-cookies:
+
+Sending Cookies
+~~~~~~~~~~~~~~~
+
+Requests can include cookies. To do so, use the ``serverParameters`` argument of
+the :method:`Symfony\\Component\\BrowserKit\\AbstractBrowser::request` method
+to set the ``Cookie`` header value::
+
+ $client->request('GET', '/', [], [], [
+ 'HTTP_COOKIE' => new Cookie('flavor', 'chocolate', strtotime('+1 day')),
+
+ // you can also pass the cookie contents as a string
+ 'HTTP_COOKIE' => 'flavor=chocolate; expires=Sat, 11 Feb 2023 12:18:13 GMT; Max-Age=86400; path=/'
+ ]);
+
+.. note::
+
+ All HTTP headers set with the ``serverParameters`` argument must be
+ prefixed by ``HTTP_``.
+
History
-------
@@ -337,6 +371,34 @@ dedicated web crawler or scraper such as `Goutte`_::
'.table-list-header-toggle a:nth-child(1)'
)->text());
+.. tip::
+
+ You can also use HTTP client options like ``ciphers``, ``auth_basic`` and
+ ``query``. They have to be passed as the default options argument to the
+ client which is used by the HTTP browser.
+
+Dealing with HTTP responses
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When using the BrowserKit component, you may need to deal with responses of
+the requests you made. To do so, call the ``getResponse()`` method of the
+``HttpBrowser`` object. This method returns the last response the browser received::
+
+ $browser = new HttpBrowser(HttpClient::create());
+
+ $browser->request('GET', 'https://foo.com');
+ $response = $browser->getResponse();
+
+If you're making requests that result in a JSON response, you may use the
+``toArray()`` method to turn the JSON document into a PHP array without having
+to call ``json_decode()`` explicitly::
+
+ $browser = new HttpBrowser(HttpClient::create());
+
+ $browser->request('GET', 'https://api.foo.com');
+ $response = $browser->getResponse()->toArray();
+ // $response is a PHP array of the decoded JSON contents
+
Learn more
----------
diff --git a/components/cache.rst b/components/cache.rst
index a620206682f..4873fb7abc7 100644
--- a/components/cache.rst
+++ b/components/cache.rst
@@ -1,8 +1,3 @@
-.. index::
- single: Cache
- single: Performance
- single: Components; Cache
-
.. _`cache-component`:
The Cache Component
@@ -16,9 +11,8 @@ The Cache Component
.. tip::
- The component also contains adapters to convert between PSR-6, PSR-16 and
- Doctrine caches. See :doc:`/components/cache/psr6_psr16_adapters` and
- :doc:`/components/cache/adapters/doctrine_adapter`.
+ The component also contains adapters to convert between PSR-6 and PSR-16.
+ See :doc:`/components/cache/psr6_psr16_adapters`.
Installation
------------
@@ -71,7 +65,7 @@ generate and return the value::
use Symfony\Contracts\Cache\ItemInterface;
// The callable will only be executed on a cache miss.
- $value = $cache->get('my_cache_key', function (ItemInterface $item) {
+ $value = $cache->get('my_cache_key', function (ItemInterface $item): string {
$item->expiresAfter(3600);
// ... do some HTTP request or heavy computations
@@ -90,7 +84,12 @@ generate and return the value::
Use cache tags to delete more than one key at the time. Read more at
:doc:`/components/cache/cache_invalidation`.
-The Cache Contracts also comes with built in `Stampede prevention`_. This will
+.. _cache_stampede-prevention:
+
+Stampede Prevention
+~~~~~~~~~~~~~~~~~~~
+
+The Cache Contracts also come with built in `Stampede prevention`_. This will
remove CPU spikes at the moments when the cache is cold. If an example application
spends 5 seconds to compute data that is cached for 1 hour and this data is accessed
10 times every second, this means that you mostly have cache hits and everything
@@ -118,7 +117,7 @@ recompute::
use Symfony\Contracts\Cache\ItemInterface;
$beta = 1.0;
- $value = $cache->get('my_cache_key', function (ItemInterface $item) {
+ $value = $cache->get('my_cache_key', function (ItemInterface $item): string {
$item->expiresAfter(3600);
$item->tag(['tag_0', 'tag_1']);
@@ -136,7 +135,6 @@ The following cache adapters are available:
cache/adapters/*
-
.. _cache-component-psr6-caching:
Generic Caching (PSR-6)
@@ -156,7 +154,7 @@ concepts:
**Adapter**
It implements the actual caching mechanism to store the information in the
filesystem, in a database, etc. The component provides several ready to use
- adapters for common caching backends (Redis, APCu, Doctrine, PDO, etc.)
+ adapters for common caching backends (Redis, APCu, PDO, etc.)
Basic Usage (PSR-6)
-------------------
@@ -192,6 +190,45 @@ Now you can create, retrieve, update and delete items using this cache pool::
For a list of all of the supported adapters, see :doc:`/components/cache/cache_pools`.
+Marshalling (Serializing) Data
+------------------------------
+
+.. note::
+
+ `Marshalling`_ and `serializing`_ are similar concepts. Serializing is the
+ process of translating an object state into a format that can be stored
+ (e.g. in a file). Marshalling is the process of translating both the object
+ state and its codebase into a format that can be stored or transmitted.
+
+ Unmarshalling an object produces a copy of the original object, possibly by
+ automatically loading the class definitions of the object.
+
+Symfony uses *marshallers* (classes which implement
+:class:`Symfony\\Component\\Cache\\Marshaller\\MarshallerInterface`) to process
+the cache items before storing them.
+
+The :class:`Symfony\\Component\\Cache\\Marshaller\\DefaultMarshaller` uses PHP's
+``serialize()`` function by default, but you can optionally use the ``igbinary_serialize()``
+function from the `Igbinary extension`_::
+
+ use Symfony\Component\Cache\Adapter\RedisAdapter;
+ use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
+ use Symfony\Component\Cache\Marshaller\DeflateMarshaller;
+
+ $marshaller = new DeflateMarshaller(new DefaultMarshaller());
+ // you can optionally use the Igbinary extension if you have it installed
+ // $marshaller = new DeflateMarshaller(new DefaultMarshaller(useIgbinarySerialize: true));
+
+ $cache = new RedisAdapter(new \Redis(), 'namespace', 0, $marshaller);
+
+There are other *marshallers* that can encrypt or compress the data before storing it.
+
+.. versionadded:: 7.2
+
+ In Symfony versions prior to 7.2, the ``igbinary_serialize()`` function was
+ used by default when the Igbinary extension was installed. Starting from
+ Symfony 7.2, you have to enable Igbinary support explicitly.
+
Advanced Usage
--------------
@@ -205,3 +242,6 @@ Advanced Usage
.. _`Cache Contracts`: https://github.com/symfony/contracts/blob/master/Cache/CacheInterface.php
.. _`Stampede prevention`: https://en.wikipedia.org/wiki/Cache_stampede
.. _Probabilistic early expiration: https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration
+.. _`Marshalling`: https://en.wikipedia.org/wiki/Marshalling_(computer_science)
+.. _`serializing`: https://en.wikipedia.org/wiki/Serialization
+.. _`Igbinary extension`: https://github.com/igbinary/igbinary
diff --git a/components/cache/adapters/apcu_adapter.rst b/components/cache/adapters/apcu_adapter.rst
index 17ecd4058e6..f2e92850cd8 100644
--- a/components/cache/adapters/apcu_adapter.rst
+++ b/components/cache/adapters/apcu_adapter.rst
@@ -1,9 +1,3 @@
-.. index::
- single: Cache Pool
- single: APCu Cache
-
-.. _apcu-adapter:
-
APCu Cache Adapter
==================
@@ -11,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.
@@ -36,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/array_cache_adapter.rst b/components/cache/adapters/array_cache_adapter.rst
index c7b06f40753..08f8276db3d 100644
--- a/components/cache/adapters/array_cache_adapter.rst
+++ b/components/cache/adapters/array_cache_adapter.rst
@@ -1,7 +1,3 @@
-.. index::
- single: Cache Pool
- single: Array Cache
-
Array Cache Adapter
===================
@@ -19,7 +15,7 @@ method::
// until the current PHP process finishes)
$defaultLifetime = 0,
- // if ``true``, the values saved in the cache are serialized before storing them
+ // if true, the values saved in the cache are serialized before storing them
$storeSerialized = true,
// the maximum lifetime (in seconds) of the entire cache (after this time, the
@@ -28,9 +24,14 @@ method::
// the maximum number of items that can be stored in the cache. When the limit
// is reached, cache follows the LRU model (least recently used items are deleted)
- $maxItems = 0
+ $maxItems = 0,
+
+ // optional implementation of the Psr\Clock\ClockInterface that will be used
+ // to calculate the lifetime of cache items (for example to get predictable
+ // lifetimes in tests)
+ $clock = null,
);
-.. versionadded:: 5.1
+.. versionadded:: 7.2
- The ``maxLifetime`` and ``maxItems`` options were introduced in Symfony 5.1.
+ The optional ``$clock`` argument was introduced in Symfony 7.2.
diff --git a/components/cache/adapters/chain_adapter.rst b/components/cache/adapters/chain_adapter.rst
index acb4cccaa43..586857d2e4d 100644
--- a/components/cache/adapters/chain_adapter.rst
+++ b/components/cache/adapters/chain_adapter.rst
@@ -1,9 +1,3 @@
-.. index::
- single: Cache Pool
- single: Chain Cache
-
-.. _component-cache-chain-adapter:
-
Chain Cache Adapter
===================
@@ -12,7 +6,7 @@ This adapter allows combining any number of the other
fetched from the first adapter containing them and cache items are saved to all the
given adapters. This exposes a simple and efficient method for creating a layered cache.
-The ChainAdapter must be provided an array of adapters and optionally a maximum cache
+The ChainAdapter must be provided an array of adapters and optionally a default cache
lifetime as its constructor arguments::
use Symfony\Component\Cache\Adapter\ChainAdapter;
@@ -21,8 +15,8 @@ lifetime as its constructor arguments::
// The ordered list of adapters used to fetch cached items
array $adapters,
- // The max lifetime of items propagated from lower adapters to upper ones
- $maxLifetime = 0
+ // The default lifetime of items propagated from lower adapters to upper ones
+ $defaultLifetime = 0
);
.. note::
diff --git a/components/cache/adapters/couchbasebucket_adapter.rst b/components/cache/adapters/couchbasebucket_adapter.rst
index 7043a7c3e95..29c9e26f83c 100644
--- a/components/cache/adapters/couchbasebucket_adapter.rst
+++ b/components/cache/adapters/couchbasebucket_adapter.rst
@@ -1,28 +1,24 @@
-.. index::
- single: Cache Pool
- single: Couchabase Cache
+Couchbase Bucket Cache Adapter
+==============================
-.. _couchbase-adapter:
+.. deprecated:: 7.1
-Couchbase Cache Adapter
-=======================
-
-.. versionadded:: 5.1
-
- The CouchbaseBucketAdapter was introduced in Symfony 5.1.
+ The ``CouchbaseBucketAdapter`` is deprecated since Symfony 7.1, use the
+ :doc:`CouchbaseCollectionAdapter `
+ instead.
This adapter stores the values in-memory using one (or more) `Couchbase server`_
-instances. Unlike the :ref:`APCu adapter `, and similarly to the
-:ref:`Memcached adapter `, it is not limited to the current server's
+instances. Unlike the :doc:`APCu adapter `, and similarly to the
+:doc:`Memcached adapter `, it is not limited to the current server's
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
- greater of the `Couchbase PHP extension`_ is required for this adapter.
+ less than 3.0 of the `Couchbase PHP extension`_ is required for this adapter.
This adapter expects a `Couchbase Bucket`_ instance to be passed as the first
parameter. A namespace and default cache lifetime can optionally be passed as
@@ -32,20 +28,19 @@ the second and third parameters::
$cache = new CouchbaseBucketAdapter(
// the client object that sets options and adds the server instance(s)
- \CouchbaseBucket $client,
+ $client,
// the name of bucket
- string $bucket,
+ $bucket,
// a string prefixed to the keys of the items stored in this cache
- $namespace = '',
+ $namespace,
// 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
- $defaultLifetime = 0,
+ $defaultLifetime
);
-
Configure the Connection
------------------------
@@ -60,7 +55,7 @@ helper method allows creating and configuring a `Couchbase Bucket`_ class instan
'couchbase://localhost'
// the DSN can include config options (pass them as a query string):
// 'couchbase://localhost:11210?operationTimeout=10'
- // 'couchbase://localhost:11210?operationTimeout=10&configTimout=20'
+ // 'couchbase://localhost:11210?operationTimeout=10&configTimeout=20'
);
// pass an array of DSN strings to register multiple servers with the client
@@ -77,7 +72,6 @@ helper method allows creating and configuring a `Couchbase Bucket`_ class instan
'couchbase:?host[localhost]&host[localhost:12345]'
);
-
Configure the Options
---------------------
diff --git a/components/cache/adapters/couchbasecollection_adapter.rst b/components/cache/adapters/couchbasecollection_adapter.rst
new file mode 100644
index 00000000000..ba78cc46eff
--- /dev/null
+++ b/components/cache/adapters/couchbasecollection_adapter.rst
@@ -0,0 +1,135 @@
+Couchbase Collection Cache Adapter
+==================================
+
+This adapter stores the values in-memory using one (or more) `Couchbase server`_
+instances. Unlike the :doc:`APCu adapter `, and similarly to the
+:doc:`Memcached adapter `, it is not limited to the current server's
+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.
+
+.. 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
+ greater of the `Couchbase PHP extension`_ is required for this adapter.
+
+This adapter expects a `Couchbase Collection`_ instance to be passed as the first
+parameter. A namespace and default cache lifetime can optionally be passed as
+the second and third parameters::
+
+ use Symfony\Component\Cache\Adapter\CouchbaseCollectionAdapter;
+
+ $cache = new CouchbaseCollectionAdapter(
+ // the client object that sets options and adds the server instance(s)
+ $client,
+
+ // a string prefixed to the keys of the items stored in this cache
+ $namespace,
+
+ // 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
+ $defaultLifetime
+ );
+
+Configure the Connection
+------------------------
+
+The :method:`Symfony\\Component\\Cache\\Adapter\\CouchbaseCollectionAdapter::createConnection`
+helper method allows creating and configuring a `Couchbase Collection`_ class instance using a
+`Data Source Name (DSN)`_ or an array of DSNs::
+
+ use Symfony\Component\Cache\Adapter\CouchbaseCollectionAdapter;
+
+ // pass a single DSN string to register a single server with the client
+ $client = CouchbaseCollectionAdapter::createConnection(
+ 'couchbase://localhost'
+ // the DSN can include config options (pass them as a query string):
+ // 'couchbase://localhost:11210?operationTimeout=10'
+ // 'couchbase://localhost:11210?operationTimeout=10&configTimout=20'
+ );
+
+ // pass an array of DSN strings to register multiple servers with the client
+ $client = CouchbaseCollectionAdapter::createConnection([
+ 'couchbase://10.0.0.100',
+ 'couchbase://10.0.0.101',
+ 'couchbase://10.0.0.102',
+ // etc...
+ ]);
+
+ // a single DSN can define multiple servers using the following syntax:
+ // host[hostname-or-IP:port] (where port is optional). Sockets must include a trailing ':'
+ $client = CouchbaseCollectionAdapter::createConnection(
+ 'couchbase:?host[localhost]&host[localhost:12345]'
+ );
+
+Configure the Options
+---------------------
+
+The :method:`Symfony\\Component\\Cache\\Adapter\\CouchbaseCollectionAdapter::createConnection`
+helper method also accepts an array of options as its second argument. The
+expected format is an associative array of ``key => value`` pairs representing
+option names and their respective values::
+
+ use Symfony\Component\Cache\Adapter\CouchbaseCollectionAdapter;
+
+ $client = CouchbaseCollectionAdapter::createConnection(
+ // a DSN string or an array of DSN strings
+ [],
+
+ // associative array of configuration options
+ [
+ 'username' => 'xxxxxx',
+ 'password' => 'yyyyyy',
+ 'configTimeout' => '100',
+ ]
+ );
+
+Available Options
+~~~~~~~~~~~~~~~~~
+
+``username`` (type: ``string``)
+ Username for connection ``CouchbaseCluster``.
+
+``password`` (type: ``string``)
+ Password of connection ``CouchbaseCluster``.
+
+``operationTimeout`` (type: ``int``, default: ``2500000``)
+ The operation timeout (in microseconds) is the maximum amount of time the library will
+ wait for an operation to receive a response before invoking its callback with a failure status.
+
+``configTimeout`` (type: ``int``, default: ``5000000``)
+ How long (in microseconds) the client will wait to obtain the initial configuration.
+
+``configNodeTimeout`` (type: ``int``, default: ``2000000``)
+ Per-node configuration timeout (in microseconds).
+
+``viewTimeout`` (type: ``int``, default: ``75000000``)
+ The I/O timeout (in microseconds) for HTTP requests to Couchbase Views API.
+
+``httpTimeout`` (type: ``int``, default: ``75000000``)
+ The I/O timeout (in microseconds) for HTTP queries (management API).
+
+``configDelay`` (type: ``int``, default: ``10000``)
+ Config refresh throttling
+ Modify the amount of time (in microseconds) before the configuration error threshold will forcefully be set to its maximum number forcing a configuration refresh.
+
+``htconfigIdleTimeout`` (type: ``int``, default: ``4294967295``)
+ Idling/Persistence for HTTP bootstrap (in microseconds).
+
+``durabilityInterval`` (type: ``int``, default: ``100000``)
+ The time (in microseconds) the client will wait between repeated probes to a given server.
+
+``durabilityTimeout`` (type: ``int``, default: ``5000000``)
+ The time (in microseconds) the client will spend sending repeated probes to a given key's vBucket masters and replicas before they are deemed not to have satisfied the durability requirements.
+
+.. tip::
+
+ Reference the `Couchbase Collection`_ extension's `predefined constants`_ documentation
+ for additional information about the available options.
+
+.. _`Couchbase PHP extension`: https://docs.couchbase.com/sdk-api/couchbase-php-client/namespaces/couchbase.html
+.. _`predefined constants`: https://docs.couchbase.com/sdk-api/couchbase-php-client/classes/Couchbase-Bucket.html
+.. _`Couchbase server`: https://couchbase.com/
+.. _`Couchbase Collection`: https://docs.couchbase.com/sdk-api/couchbase-php-client/classes/Couchbase-Collection.html
+.. _`Data Source Name (DSN)`: https://en.wikipedia.org/wiki/Data_source_name
diff --git a/components/cache/adapters/doctrine_adapter.rst b/components/cache/adapters/doctrine_adapter.rst
deleted file mode 100644
index 198ae19338c..00000000000
--- a/components/cache/adapters/doctrine_adapter.rst
+++ /dev/null
@@ -1,42 +0,0 @@
-.. index::
- single: Cache Pool
- single: Doctrine Cache
-
-.. _doctrine-adapter:
-
-Doctrine Cache Adapter
-======================
-
-This adapter wraps any class extending the `Doctrine Cache`_ abstract provider, allowing
-you to use these providers in your application as if they were Symfony Cache adapters.
-
-This adapter expects a ``\Doctrine\Common\Cache\CacheProvider`` instance as its first
-parameter, and optionally a namespace and default cache lifetime as its second and
-third parameters::
-
- use Doctrine\Common\Cache\CacheProvider;
- use Doctrine\Common\Cache\SQLite3Cache;
- use Symfony\Component\Cache\Adapter\DoctrineAdapter;
-
- $provider = new SQLite3Cache(new \SQLite3(__DIR__.'/cache/data.sqlite'), 'youTableName');
-
- $cache = new DoctrineAdapter(
-
- // a cache provider instance
- CacheProvider $provider,
-
- // a string prefixed to the keys of the items stored in this cache
- $namespace = '',
-
- // 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 the database table is truncated or its rows are otherwise deleted)
- $defaultLifetime = 0
- );
-
-.. tip::
-
- A :class:`Symfony\\Component\\Cache\\DoctrineProvider` class is also provided by the
- component to use any PSR6-compatible implementations with Doctrine-compatible classes.
-
-.. _`Doctrine Cache`: https://github.com/doctrine/cache
diff --git a/components/cache/adapters/doctrine_dbal_adapter.rst b/components/cache/adapters/doctrine_dbal_adapter.rst
new file mode 100644
index 00000000000..68732ddd3fa
--- /dev/null
+++ b/components/cache/adapters/doctrine_dbal_adapter.rst
@@ -0,0 +1,60 @@
+Doctrine DBAL Cache Adapter
+===========================
+
+The Doctrine DBAL adapters store the cache items in a table of an SQL database.
+
+.. note::
+
+ This adapter implements :class:`Symfony\\Component\\Cache\\PruneableInterface`,
+ allowing for manual :ref:`pruning of expired cache entries `
+ by calling the ``prune()`` method.
+
+The :class:`Symfony\\Component\\Cache\\Adapter\\DoctrineDbalAdapter` requires a
+`Doctrine DBAL Connection`_, or `Doctrine DBAL URL`_ as its first parameter.
+You can pass a namespace, default cache lifetime, and options array as the other
+optional arguments::
+
+ use Symfony\Component\Cache\Adapter\DoctrineDbalAdapter;
+
+ $cache = new DoctrineDbalAdapter(
+
+ // a Doctrine DBAL connection or DBAL URL
+ $databaseConnectionOrURL,
+
+ // the string prefixed to the keys of the items stored in this cache
+ $namespace = '',
+
+ // 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 the database table is truncated or its rows are otherwise deleted)
+ $defaultLifetime = 0,
+
+ // an array of options for configuring the database table and connection
+ $options = []
+ );
+
+.. note::
+
+ DBAL Connection are lazy-loaded by default; some additional options may be
+ necessary to detect the database engine and version without opening the
+ connection.
+
+The adapter uses SQL syntax that is optimized for database server that it is connected to.
+The following database servers are known to be compatible:
+
+* MySQL 5.7 and newer
+* MariaDB 10.2 and newer
+* Oracle 10g and newer
+* SQL Server 2012 and newer
+* SQLite 3.24 or later
+* PostgreSQL 9.5 or later
+
+.. note::
+
+ Newer releases of Doctrine DBAL might increase these minimal versions. Check
+ the manual page on `Doctrine DBAL Platforms`_ if your database server is
+ compatible with the installed Doctrine DBAL version.
+
+.. _`Doctrine DBAL Connection`: https://github.com/doctrine/dbal/blob/master/src/Connection.php
+.. _`Doctrine DBAL URL`: https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/configuration.html#connecting-using-a-url
+.. _`Doctrine DBAL Platforms`: https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/platforms.html
diff --git a/components/cache/adapters/filesystem_adapter.rst b/components/cache/adapters/filesystem_adapter.rst
index 33097fbd202..db877454859 100644
--- a/components/cache/adapters/filesystem_adapter.rst
+++ b/components/cache/adapters/filesystem_adapter.rst
@@ -1,14 +1,8 @@
-.. index::
- single: Cache Pool
- single: Filesystem Cache
-
-.. _component-cache-filesystem-adapter:
-
Filesystem Cache Adapter
========================
This adapter offers improved application performance for those who cannot install
-tools like :ref:`APCu ` or :ref:`Redis ` in their
+tools like :doc:`APCu ` or :doc:`Redis ` in their
environment. It stores the cache item expiration and content as regular files in
a collection of directories on a locally mounted filesystem.
@@ -39,21 +33,35 @@ 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
- (:ref:`Apcu `, :ref:`Memcached `, and
- :ref:`Redis `) or the database adapters
- (:ref:`Doctrine ` and :ref:`PDO `)
+ (:doc:`Apcu `, :doc:`Memcached `,
+ and :doc:`Redis `) or the database adapters
+ (:doc:`Doctrine DBAL `, :doc:`PDO `)
are recommended.
.. note::
- Since Symfony 3.4, this adapter implements
- :class:`Symfony\\Component\\Cache\\PruneableInterface`, enabling manual
- :ref:`pruning of expired cache items ` by
- calling its ``prune()`` method.
+ This adapter implements :class:`Symfony\\Component\\Cache\\PruneableInterface`,
+ enabling manual :ref:`pruning of expired cache items `
+ by calling its ``prune()`` method.
+
+.. _filesystem-tag-aware-adapter:
+
+Working with Tags
+-----------------
+
+In order to use tag-based invalidation, you can wrap your adapter in
+:class:`Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter`, but it's often
+more interesting to use the dedicated :class:`Symfony\\Component\\Cache\\Adapter\\FilesystemTagAwareAdapter`.
+Since tag invalidation logic is implemented using links on filesystem, this
+adapter offers better read performance when using tag-based invalidation::
+
+ use Symfony\Component\Cache\Adapter\FilesystemTagAwareAdapter;
+
+ $cache = new FilesystemTagAwareAdapter();
.. _`tmpfs`: https://wiki.archlinux.org/index.php/tmpfs
.. _`RAM disk solutions`: https://en.wikipedia.org/wiki/List_of_RAM_drive_software
diff --git a/components/cache/adapters/memcached_adapter.rst b/components/cache/adapters/memcached_adapter.rst
index 95161637b25..64baf0d4702 100644
--- a/components/cache/adapters/memcached_adapter.rst
+++ b/components/cache/adapters/memcached_adapter.rst
@@ -1,20 +1,14 @@
-.. index::
- single: Cache Pool
- single: Memcached Cache
-
-.. _memcached-adapter:
-
Memcached Cache Adapter
=======================
This adapter stores the values in-memory using one (or more) `Memcached server`_
-instances. Unlike the :ref:`APCu adapter `, and similarly to the
-:ref:`Redis adapter `, it is not limited to the current server's
+instances. Unlike the :doc:`APCu adapter `, and similarly to the
+:doc:`Redis adapter `, it is not limited to the current server's
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
@@ -120,7 +114,6 @@ option names and their respective values::
// associative array of configuration options
[
- 'compression' => true,
'libketama_compatible' => true,
'serializer' => 'igbinary',
]
@@ -139,17 +132,6 @@ Available Options
server(s). Any action that retrieves data, quits the connection, or closes
down the connection will cause the buffer to be committed.
-``compression`` (type: ``bool``, default: ``true``)
- Enables or disables payload compression, where item values longer than 100
- bytes are compressed during storage and decompressed during retrieval.
-
-``compression_type`` (type: ``string``)
- Specifies the compression method used on value payloads. when the
- **compression** option is enabled.
-
- Valid option values include ``fastlz`` and ``zlib``, with a default value
- that *varies based on flags used at compilation*.
-
``connect_timeout`` (type: ``int``, default: ``1000``)
Specifies the timeout (in milliseconds) of socket connection operations when
the ``no_block`` option is enabled.
@@ -274,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/pdo_doctrine_dbal_adapter.rst b/components/cache/adapters/pdo_adapter.rst
similarity index 60%
rename from components/cache/adapters/pdo_doctrine_dbal_adapter.rst
rename to components/cache/adapters/pdo_adapter.rst
index 841071dc586..3cdeb87427a 100644
--- a/components/cache/adapters/pdo_doctrine_dbal_adapter.rst
+++ b/components/cache/adapters/pdo_adapter.rst
@@ -1,22 +1,23 @@
-.. index::
- single: Cache Pool
- single: PDO Cache, Doctrine DBAL Cache
+PDO Cache Adapter
+=================
-.. _pdo-doctrine-adapter:
+The PDO adapters store the cache items in a table of an SQL database.
-PDO & Doctrine DBAL Cache Adapter
-=================================
+.. note::
+
+ This adapter implements :class:`Symfony\\Component\\Cache\\PruneableInterface`,
+ allowing for manual :ref:`pruning of expired cache entries `
+ by calling the ``prune()`` method.
-This adapter stores the cache items in an SQL database. It requires a :phpclass:`PDO`,
-`Doctrine DBAL Connection`_, or `Data Source Name (DSN)`_ as its first parameter, and
-optionally a namespace, default cache lifetime, and options array as its second,
-third, and forth parameters::
+The :class:`Symfony\\Component\\Cache\\Adapter\\PdoAdapter` requires a :phpclass:`PDO`,
+or `DSN`_ as its first parameter. You can pass a namespace,
+default cache lifetime, and options array as the other optional arguments::
use Symfony\Component\Cache\Adapter\PdoAdapter;
$cache = new PdoAdapter(
- // a PDO, a Doctrine DBAL connection or DSN for lazy connecting through PDO
+ // a PDO connection or DSN for lazy connecting through PDO
$databaseConnectionOrDSN,
// the string prefixed to the keys of the items stored in this cache
@@ -42,11 +43,5 @@ your code.
When passed a `Data Source Name (DSN)`_ string (instead of a database connection
class instance), the connection will be lazy-loaded when needed.
-.. note::
-
- Since Symfony 3.4, this adapter implements :class:`Symfony\\Component\\Cache\\PruneableInterface`,
- allowing for manual :ref:`pruning of expired cache entries ` by
- calling its ``prune()`` method.
-
-.. _`Doctrine DBAL Connection`: https://github.com/doctrine/dbal/blob/master/src/Connection.php
+.. _`DSN`: https://php.net/manual/pdo.drivers.php
.. _`Data Source Name (DSN)`: https://en.wikipedia.org/wiki/Data_source_name
diff --git a/components/cache/adapters/php_array_cache_adapter.rst b/components/cache/adapters/php_array_cache_adapter.rst
index 631c153f5cb..ae5ef13f790 100644
--- a/components/cache/adapters/php_array_cache_adapter.rst
+++ b/components/cache/adapters/php_array_cache_adapter.rst
@@ -1,13 +1,9 @@
-.. index::
- single: Cache Pool
- single: PHP Array Cache
-
PHP Array Cache Adapter
=======================
This adapter is a high performance cache for static data (e.g. application configuration)
that is optimized and preloaded into OPcache memory storage. It is suited for any data that
-is mostly read-only after warmup::
+is mostly read-only after warm-up::
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
@@ -23,7 +19,7 @@ is mostly read-only after warmup::
$cache = new PhpArrayAdapter(
// single file where values are cached
__DIR__ . '/somefile.cache',
- // a backup adapter, if you set values after warmup
+ // a backup adapter, if you set values after warm-up
new FilesystemAdapter()
);
$cache->warmUp($values);
diff --git a/components/cache/adapters/php_files_adapter.rst b/components/cache/adapters/php_files_adapter.rst
index 5bec5715801..6f171f0fede 100644
--- a/components/cache/adapters/php_files_adapter.rst
+++ b/components/cache/adapters/php_files_adapter.rst
@@ -1,13 +1,7 @@
-.. index::
- single: Cache Pool
- single: PHP Files Cache
-
-.. _component-cache-files-adapter:
-
PHP Files Cache Adapter
=======================
-Similarly to :ref:`Filesystem Adapter `, this cache
+Similarly to :doc:`Filesystem Adapter `, this cache
implementation writes cache entries out to disk, but unlike the Filesystem cache adapter,
the PHP Files cache adapter writes and reads back these cache files *as native PHP code*.
For example, caching the value ``['my', 'cached', 'array']`` will write out a cache
@@ -34,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
@@ -63,7 +57,7 @@ directory path as constructor arguments::
.. note::
- Since Symfony 3.4, this adapter implements :class:`Symfony\\Component\\Cache\\PruneableInterface`,
+ This adapter implements :class:`Symfony\\Component\\Cache\\PruneableInterface`,
allowing for manual :ref:`pruning of expired cache entries ` by
calling its ``prune()`` method.
diff --git a/components/cache/adapters/proxy_adapter.rst b/components/cache/adapters/proxy_adapter.rst
index 203521f0e4c..5177bf219df 100644
--- a/components/cache/adapters/proxy_adapter.rst
+++ b/components/cache/adapters/proxy_adapter.rst
@@ -1,7 +1,3 @@
-.. index::
- single: Cache Pool
- single: Proxy Cache
-
Proxy Cache Adapter
===================
diff --git a/components/cache/adapters/redis_adapter.rst b/components/cache/adapters/redis_adapter.rst
index 64bf7ab9e4c..0d099bf66e8 100644
--- a/components/cache/adapters/redis_adapter.rst
+++ b/components/cache/adapters/redis_adapter.rst
@@ -1,9 +1,3 @@
-.. index::
- single: Cache Pool
- single: Redis Cache
-
-.. _redis-adapter:
-
Redis Cache Adapter
===================
@@ -14,20 +8,21 @@ Redis Cache Adapter
:ref:`Symfony Cache configuration `
article if you are using it in a Symfony application.
-This adapter stores the values in-memory using one (or more) `Redis server`_ instances.
+This adapter stores the values in-memory using one (or more) `Redis server`_
+of `Valkey`_ server instances.
-Unlike the :ref:`APCu adapter `, and similarly to the
-:ref:`Memcached adapter `, it is not limited to the current server's
+Unlike the :doc:`APCu adapter `, and similarly to the
+:doc:`Memcached adapter `, it is not limited to the current server's
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
- ``\Redis``, ``\RedisArray``, ``RedisCluster``, or ``\Predis``.
+ ``\Redis``, ``\RedisArray``, ``RedisCluster``, ``\Relay\Relay`` or ``\Predis``.
-This adapter expects a `Redis`_, `RedisArray`_, `RedisCluster`_, or `Predis`_ instance to be
+This adapter expects a `Redis`_, `RedisArray`_, `RedisCluster`_, `Relay`_ or `Predis`_ instance to be
passed as the first parameter. A namespace and default cache lifetime can optionally be passed
as the second and third parameters::
@@ -44,13 +39,19 @@ 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
------------------------
-The :method:`Symfony\\Component\\Cache\\Adapter\\RedisAdapter::createConnection`
+The :method:`Symfony\\Component\\Cache\\Traits\\RedisTrait::createConnection`
helper method allows creating and configuring the Redis client class instance using a
`Data Source Name (DSN)`_::
@@ -61,16 +62,28 @@ helper method allows creating and configuring the Redis client class instance us
'redis://localhost'
);
+.. versionadded:: 7.3
+
+ Starting in Symfony 7.3, when using Valkey servers you can use the
+ ``valkey[s]:`` scheme instead of the ``redis[s]:`` one in your DSNs.
+
The DSN can specify either an IP/host (and an optional port) or a socket path, as well as a
-password and a database index.
+password and a database index. To enable TLS for connections, the scheme ``redis`` must be
+replaced by ``rediss`` (the second ``s`` means "secure").
.. note::
- A `Data Source Name (DSN)`_ for this adapter must use the following format.
+ A `Data Source Name (DSN)`_ for this adapter must use either one of the following formats.
.. code-block:: text
- redis://[pass@][ip|host|socket[:port]][/db-index]
+ redis[s]://[pass@][ip|host|socket[:port]][/db-index]
+
+ .. code-block:: text
+
+ redis[s]:[[user]:pass@]?[ip|host|socket[:port]][¶ms]
+
+ Values for placeholders ``[user]``, ``[:port]``, ``[/db-index]`` and ``[¶ms]`` are optional.
Below are common examples of valid DSNs showing a combination of available values::
@@ -88,20 +101,35 @@ Below are common examples of valid DSNs showing a combination of available value
// socket "/var/run/redis.sock" and auth "bad-pass"
RedisAdapter::createConnection('redis://bad-pass@/var/run/redis.sock');
- // a single DSN can define multiple servers using the following syntax:
- // host[hostname-or-IP:port] (where port is optional). Sockets must include a trailing ':'
+ // host "redis1" (docker container) with alternate DSN syntax and selecting database index "3"
+ RedisAdapter::createConnection('redis:?host[redis1:6379]&dbindex=3');
+
+ // providing credentials with alternate DSN syntax
+ RedisAdapter::createConnection('redis:default:verysecurepassword@?host[redis1:6379]&dbindex=3');
+
+ // a single DSN can also define multiple servers
RedisAdapter::createConnection(
'redis:?host[localhost]&host[localhost:6379]&host[/var/run/redis.sock:]&auth=my-password&redis_cluster=1'
);
`Redis Sentinel`_, which provides high availability for Redis, is also supported
-when using the Predis library. Use the ``redis_sentinel`` parameter to set the
-name of your service group::
+when using the PHP Redis Extension v5.2+ or the Predis library. Use the ``redis_sentinel``
+parameter to set the name of your service group::
RedisAdapter::createConnection(
'redis:?host[redis1:26379]&host[redis2:26379]&host[redis3:26379]&redis_sentinel=mymaster'
);
+ // providing credentials
+ RedisAdapter::createConnection(
+ 'redis:default:verysecurepassword@?host[redis1:26379]&host[redis2:26379]&host[redis3:26379]&redis_sentinel=mymaster'
+ );
+
+ // providing credentials and selecting database index "3"
+ RedisAdapter::createConnection(
+ 'redis:default:verysecurepassword@?host[redis1:26379]&host[redis2:26379]&host[redis3:26379]&redis_sentinel=mymaster&dbindex=3'
+ );
+
.. note::
See the :class:`Symfony\\Component\\Cache\\Traits\\RedisTrait` for more options
@@ -123,14 +151,19 @@ array of ``key => value`` pairs representing option names and their respective v
// associative array of configuration options
[
- 'compression' => true,
- 'lazy' => false,
+ 'class' => null,
'persistent' => 0,
'persistent_id' => null,
- 'tcp_keepalive' => 0,
'timeout' => 30,
'read_timeout' => 0,
'retry_interval' => 0,
+ 'tcp_keepalive' => 0,
+ 'lazy' => null,
+ 'redis_cluster' => false,
+ 'redis_sentinel' => null,
+ 'dbindex' => 0,
+ 'failover' => 'none',
+ 'ssl' => null,
]
);
@@ -138,19 +171,11 @@ array of ``key => value`` pairs representing option names and their respective v
Available Options
~~~~~~~~~~~~~~~~~
-``class`` (type: ``string``)
- Specifies the connection library to return, either ``\Redis`` or ``\Predis\Client``.
- If none is specified, it will return ``\Redis`` if the ``redis`` extension is
- available, and ``\Predis\Client`` otherwise.
-
-``compression`` (type: ``bool``, default: ``true``)
- Enables or disables compression of items. This requires phpredis v4 or higher with
- LZF support enabled.
-
-``lazy`` (type: ``bool``, default: ``false``)
- Enables or disables lazy connections to the backend. It's ``false`` by
- default when using this as a stand-alone component and ``true`` by default
- when using it inside a Symfony application.
+``class`` (type: ``string``, default: ``null``)
+ Specifies the connection library to return, either ``\Redis``, ``\Relay\Relay`` or ``\Predis\Client``.
+ If none is specified, fallback value is in following order, depending which one is available first:
+ ``\Redis``, ``\Relay\Relay``, ``\Predis\Client``. Explicitly set this to ``\Predis\Client`` for Sentinel if you are
+ running into issues when retrieving master information.
``persistent`` (type: ``int``, default: ``0``)
Enables or disables use of persistent connections. A value of ``0`` disables persistent
@@ -159,6 +184,10 @@ Available Options
``persistent_id`` (type: ``string|null``, default: ``null``)
Specifies the persistent id string to use for a persistent connection.
+``timeout`` (type: ``int``, default: ``30``)
+ Specifies the time (in seconds) used to connect to a Redis server before the
+ connection attempt times out.
+
``read_timeout`` (type: ``int``, default: ``0``)
Specifies the time (in seconds) used when performing read operations on the underlying
network resource before the operation times out.
@@ -171,9 +200,36 @@ Available Options
Specifies the `TCP-keepalive`_ timeout (in seconds) of the connection. This
requires phpredis v4 or higher and a TCP-keepalive enabled server.
-``timeout`` (type: ``int``, default: ``30``)
- Specifies the time (in seconds) used to connect to a Redis server before the
- connection attempt times out.
+``lazy`` (type: ``bool``, default: ``null``)
+ Enables or disables lazy connections to the backend. It's ``false`` by
+ default when using this as a stand-alone component and ``true`` by default
+ when using it inside a Symfony application.
+
+``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.
+
+``redis_sentinel`` (type: ``string``, default: ``null``)
+ Specifies the master name connected to the sentinels.
+
+``sentinel_master`` (type: ``string``, default: ``null``)
+ Alias of ``redis_sentinel`` option.
+
+``dbindex`` (type: ``int``, default: ``0``)
+ Specifies the database index to select.
+
+``failover`` (type: ``string``, default: ``none``)
+ Specifies failover for cluster implementations. For ``\RedisCluster`` valid options are ``none`` (default),
+ ``error``, ``distribute`` or ``slaves``. For ``\Predis\ClientInterface`` valid options are ``slaves``
+ or ``distribute``.
+
+``ssl`` (type: ``array``, default: ``null``)
+ SSL context options. See `php.net/context.ssl`_ for more information.
+
+.. versionadded:: 7.1
+
+ The option `sentinel_master` as an alias for `redis_sentinel` was introduced
+ in Symfony 7.1.
.. note::
@@ -182,10 +238,31 @@ Available Options
.. _redis-tag-aware-adapter:
+Configuring Redis
+-----------------
+
+When using Redis as cache, you should configure the ``maxmemory`` and ``maxmemory-policy``
+settings. By setting ``maxmemory``, you limit how much memory Redis is allowed to consume.
+If the amount is too low, Redis will drop entries that would still be useful and you benefit
+less from your cache. Setting the ``maxmemory-policy`` to ``allkeys-lru`` tells Redis that
+it is ok to drop data when it runs out of memory, and to first drop the oldest entries (least
+recently used). If you do not allow Redis to drop entries, it will return an error when you
+try to add data when no memory is available. An example setting could look as follows:
+
+.. code-block:: ini
+
+ maxmemory 100mb
+ maxmemory-policy allkeys-lru
+
Working with Tags
-----------------
-In order to use tag-based invalidation, you can wrap your adapter in :class:`Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter`, but when Redis is used as backend, it's often more interesting to use the dedicated :class:`Symfony\\Component\\Cache\\Adapter\\RedisTagAwareAdapter`. Since tag invalidation logic is implemented in Redis itself, this adapter offers better performance when using tag-based invalidation::
+In order to use tag-based invalidation, you can wrap your adapter in
+:class:`Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter`. However, when Redis
+is used as backend, it's often more interesting to use the dedicated
+:class:`Symfony\\Component\\Cache\\Adapter\\RedisTagAwareAdapter`. Since tag
+invalidation logic is implemented in Redis itself, this adapter offers better
+performance when using tag-based invalidation::
use Symfony\Component\Cache\Adapter\RedisAdapter;
use Symfony\Component\Cache\Adapter\RedisTagAwareAdapter;
@@ -193,13 +270,98 @@ In order to use tag-based invalidation, you can wrap your adapter in :class:`Sym
$client = RedisAdapter::createConnection('redis://localhost');
$cache = new RedisTagAwareAdapter($client);
+.. note::
+
+ When using RedisTagAwareAdapter, in order to maintain relationships between
+ tags and cache items, you have to use either ``noeviction`` or ``volatile-*``
+ in the Redis ``maxmemory-policy`` eviction policy.
+
+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/
+.. _`Valkey`: https://valkey.io/
.. _`Redis`: https://github.com/phpredis/phpredis
-.. _`RedisArray`: https://github.com/phpredis/phpredis/blob/master/arrays.markdown#readme
-.. _`RedisCluster`: https://github.com/phpredis/phpredis/blob/master/cluster.markdown#readme
+.. _`RedisArray`: https://github.com/phpredis/phpredis/blob/develop/arrays.md
+.. _`RedisCluster`: https://github.com/phpredis/phpredis/blob/develop/cluster.md
+.. _`Relay`: https://relay.so/
.. _`Predis`: https://packagist.org/packages/predis/predis
.. _`Predis Connection Parameters`: https://github.com/nrk/predis/wiki/Connection-Parameters#list-of-connection-parameters
.. _`TCP-keepalive`: https://redis.io/topics/clients#tcp-keepalive
.. _`Redis Sentinel`: https://redis.io/topics/sentinel
+.. _`Redis LRU Cache Documentation`: https://redis.io/topics/lru-cache
+.. _`php.net/context.ssl`: https://php.net/context.ssl
diff --git a/components/cache/cache_invalidation.rst b/components/cache/cache_invalidation.rst
index 22f5830cf3e..da88ea6273e 100644
--- a/components/cache/cache_invalidation.rst
+++ b/components/cache/cache_invalidation.rst
@@ -1,13 +1,9 @@
-.. index::
- single: Cache; Invalidation
- single: Cache; Tags
-
Cache Invalidation
==================
Cache invalidation is the process of removing all cached items related to a
change in the state of your model. The most basic kind of invalidation is direct
-items deletion. But when the state of a primary resource has spread across
+item deletion. But when the state of a primary resource has spread across
several cached items, keeping them in sync can be difficult.
The Symfony Cache component provides two mechanisms to help solve this problem:
@@ -28,7 +24,7 @@ To attach tags to cached items, you need to use the
:method:`Symfony\\Contracts\\Cache\\ItemInterface::tag` method that is implemented by
cache items::
- $item = $cache->get('cache_key', function (ItemInterface $item) {
+ $item = $cache->get('cache_key', function (ItemInterface $item): string {
// [...]
// add one or more tags
$item->tag('tag_1');
@@ -47,7 +43,7 @@ you can invalidate the cached items by calling
// if you know the cache key, you can also delete the item directly
$cache->delete('cache_key');
-Using tags invalidation is very useful when tracking cache keys becomes difficult.
+Using tag invalidation is very useful when tracking cache keys becomes difficult.
Tag Aware Adapters
~~~~~~~~~~~~~~~~~~
@@ -61,7 +57,8 @@ method.
.. note::
When using a Redis backend, consider using :ref:`RedisTagAwareAdapter `
- which is optimized for this purpose.
+ which is optimized for this purpose. When using filesystem, likewise consider to use
+ :ref:`FilesystemTagAwareAdapter `.
The :class:`Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter` class implements
instantaneous invalidation (time complexity is ``O(N)`` where ``N`` is the number
@@ -86,7 +83,7 @@ your fronts and have very fast invalidation checks::
.. note::
- Since Symfony 3.4, :class:`Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter`
+ :class:`Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter`
implements :class:`Symfony\\Component\\Cache\\PruneableInterface`,
enabling manual
:ref:`pruning of expired cache entries ` by
diff --git a/components/cache/cache_items.rst b/components/cache/cache_items.rst
index 027bb59f4a9..e958125c69d 100644
--- a/components/cache/cache_items.rst
+++ b/components/cache/cache_items.rst
@@ -1,8 +1,3 @@
-.. index::
- single: Cache Item
- single: Cache Expiration
- single: Cache Exceptions
-
Cache Items
===========
@@ -17,9 +12,8 @@ Cache Item Keys and Values
The **key** of a cache item is a plain string which acts as its
identifier, so it must be unique for each cache pool. You can freely choose the
keys, but they should only contain letters (A-Z, a-z), numbers (0-9) and the
-``_`` and ``.`` symbols. Other common symbols (such as ``{``, ``}``, ``(``,
-``)``, ``/``, ``\``, ``@`` and ``:``) are reserved by the PSR-6 standard for future
-uses.
+``_`` and ``.`` symbols. Other common symbols (such as ``{ } ( ) / \ @ :``) are
+reserved by the PSR-6 standard for future uses.
The **value** of a cache item can be any data represented by a type which is
serializable by PHP, such as basic types (string, integer, float, boolean, null),
@@ -32,7 +26,7 @@ The only way to create cache items is via cache pools. When using the Cache
Contracts, they are passed as arguments to the recomputation callback::
// $cache pool object was created before
- $productsCount = $cache->get('stats.products_count', function (ItemInterface $item) {
+ $productsCount = $cache->get('stats.products_count', function (ItemInterface $item): string {
// [...]
});
diff --git a/components/cache/cache_pools.rst b/components/cache/cache_pools.rst
index 375b514fe80..e50c2b67633 100644
--- a/components/cache/cache_pools.rst
+++ b/components/cache/cache_pools.rst
@@ -1,14 +1,3 @@
-.. index::
- single: Cache Pool
- single: APCu Cache
- single: Array Cache
- single: Chain Cache
- single: Doctrine Cache
- single: Filesystem Cache
- single: Memcached Cache
- single: PDO Cache, Doctrine DBAL Cache
- single: Redis Cache
-
.. _component-cache-cache-pools:
Cache Pools and Supported Adapters
@@ -36,7 +25,6 @@ ready to use in your applications.
adapters/*
-
Using the Cache Contracts
-------------------------
@@ -49,7 +37,7 @@ and deleting cache items using only two methods and a callback::
$cache = new FilesystemAdapter();
// The callable will only be executed on a cache miss.
- $value = $cache->get('my_cache_key', function (ItemInterface $item) {
+ $value = $cache->get('my_cache_key', function (ItemInterface $item): string {
$item->expiresAfter(3600);
// ... do some HTTP request or heavy computations
@@ -174,7 +162,7 @@ when all items are successfully deleted)::
If the cache component is used inside a Symfony application, you can remove
items from cache pools using the following commands (which reside within
- the :ref:`framework bundle `):
+ the :doc:`framework bundle `):
To remove *one specific item* from the *given pool*:
@@ -203,7 +191,7 @@ Pruning Cache Items
-------------------
Some cache pools do not include an automated mechanism for pruning expired cache items.
-For example, the :ref:`FilesystemAdapter ` cache
+For example, the :doc:`FilesystemAdapter ` cache
does not remove expired cache items *until an item is explicitly requested and determined to
be expired*, for example, via a call to ``Psr\Cache\CacheItemPoolInterface::getItem``.
Under certain workloads, this can cause stale cache entries to persist well past their
@@ -213,10 +201,11 @@ expired cache items.
This shortcoming has been solved through the introduction of
:class:`Symfony\\Component\\Cache\\PruneableInterface`, which defines the abstract method
:method:`Symfony\\Component\\Cache\\PruneableInterface::prune`. The
-:ref:`ChainAdapter `,
-:ref:`FilesystemAdapter `,
-:ref:`PdoAdapter `, and
-:ref:`PhpFilesAdapter ` all implement this new interface,
+:doc:`ChainAdapter `,
+:doc:`DoctrineDbalAdapter `, and
+:doc:`FilesystemAdapter `,
+:doc:`PdoAdapter `, and
+:doc:`PhpFilesAdapter ` all implement this new interface,
allowing manual removal of stale cache items::
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
@@ -225,7 +214,7 @@ allowing manual removal of stale cache items::
// ... do some set and get operations
$cache->prune();
-The :ref:`ChainAdapter ` implementation does not directly
+The :doc:`ChainAdapter ` implementation does not directly
contain any pruning logic itself. Instead, when calling the chain adapter's
:method:`Symfony\\Component\\Cache\\Adapter\\ChainAdapter::prune` method, the call is delegated to all
its compatible cache adapters (and those that do not implement ``PruneableInterface`` are
@@ -253,7 +242,7 @@ silently ignored)::
If the cache component is used inside a Symfony application, you can prune
*all items* from *all pools* using the following command (which resides within
- the :ref:`framework bundle `):
+ the :doc:`framework bundle `):
.. code-block:: terminal
diff --git a/components/cache/psr6_psr16_adapters.rst b/components/cache/psr6_psr16_adapters.rst
index 6b98d26744b..66e44b9c22d 100644
--- a/components/cache/psr6_psr16_adapters.rst
+++ b/components/cache/psr6_psr16_adapters.rst
@@ -1,8 +1,3 @@
-.. index::
- single: Cache
- single: Performance
- single: Components; Cache
-
Adapters For Interoperability between PSR-6 and PSR-16 Cache
============================================================
diff --git a/components/clock.rst b/components/clock.rst
new file mode 100644
index 00000000000..c4ac88e9092
--- /dev/null
+++ b/components/clock.rst
@@ -0,0 +1,380 @@
+The Clock Component
+===================
+
+The Clock component decouples applications from the system clock. This allows
+you to fix time to improve testability of time-sensitive logic.
+
+The component provides a ``ClockInterface`` with the following implementations
+for different use cases:
+
+:class:`Symfony\\Component\\Clock\\NativeClock`
+ Provides a way to interact with the system clock, this is the same as doing
+ ``new \DateTimeImmutable()``.
+:class:`Symfony\\Component\\Clock\\MockClock`
+ Commonly used in tests as a replacement for the ``NativeClock`` to be able
+ to freeze and change the current time using either ``sleep()`` or ``modify()``.
+:class:`Symfony\\Component\\Clock\\MonotonicClock`
+ Relies on ``hrtime()`` and provides a high resolution, monotonic clock,
+ when you need a precise stopwatch.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/clock
+
+.. include:: /components/require_autoload.rst.inc
+
+.. _clock_usage:
+
+Usage
+-----
+
+The :class:`Symfony\\Component\\Clock\\Clock` class returns the current time and
+allows to use any `PSR-20`_ compatible implementation as a global clock in your
+application::
+
+ use Symfony\Component\Clock\Clock;
+ use Symfony\Component\Clock\MockClock;
+
+ // by default, Clock uses the NativeClock implementation, but you can change
+ // this by setting any other implementation
+ Clock::set(new MockClock());
+
+ // Then, you can get the clock instance
+ $clock = Clock::get();
+
+ // Additionally, you can set a timezone
+ $clock->withTimeZone('Europe/Paris');
+
+ // From here, you can get the current time
+ $now = $clock->now();
+
+ // And sleep for any number of seconds
+ $clock->sleep(2.5);
+
+The Clock component also provides the ``now()`` function::
+
+ use function Symfony\Component\Clock\now;
+
+ // Get the current time as a DatePoint instance
+ $now = now();
+
+The ``now()`` function takes an optional ``modifier`` argument
+which will be applied to the current time::
+
+ $later = now('+3 hours');
+
+ $yesterday = now('-1 day');
+
+You can use any string `accepted by the DateTime constructor`_.
+
+Later on this page you can learn how to use this clock in your services and tests.
+When using the Clock component, you manipulate
+:class:`Symfony\\Component\\Clock\\DatePoint` instances. You can learn more
+about it in :ref:`the dedicated section `.
+
+Available Clocks Implementations
+--------------------------------
+
+The Clock component provides some ready-to-use implementations of the
+:class:`Symfony\\Component\\Clock\\ClockInterface`, which you can use
+as global clocks in your application depending on your needs.
+
+NativeClock
+~~~~~~~~~~~
+
+A clock service replaces creating a new ``DateTime`` or
+``DateTimeImmutable`` object for the current time. Instead, you inject the
+``ClockInterface`` and call ``now()``. By default, your application will likely
+use a ``NativeClock``, which always returns the current system time. In tests it is replaced with a ``MockClock``.
+
+The following example introduces a service utilizing the Clock component to
+determine the current time::
+
+ use Symfony\Component\Clock\ClockInterface;
+
+ class ExpirationChecker
+ {
+ public function __construct(
+ private ClockInterface $clock
+ ) {}
+
+ public function isExpired(DateTimeInterface $validUntil): bool
+ {
+ return $this->clock->now() > $validUntil;
+ }
+ }
+
+MockClock
+~~~~~~~~~
+
+The ``MockClock`` is instantiated with a time and does not move forward on its own. The time is
+fixed until ``sleep()`` or ``modify()`` are called. This gives you full control over what your code
+assumes is the current time.
+
+When writing a test for this service, you can check both cases where something
+is expired or not, by modifying the clock's time::
+
+ use PHPUnit\Framework\TestCase;
+ use Symfony\Component\Clock\MockClock;
+
+ class ExpirationCheckerTest extends TestCase
+ {
+ public function testIsExpired(): void
+ {
+ $clock = new MockClock('2022-11-16 15:20:00');
+ $expirationChecker = new ExpirationChecker($clock);
+ $validUntil = new DateTimeImmutable('2022-11-16 15:25:00');
+
+ // $validUntil is in the future, so it is not expired
+ $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()
+ $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
+ $this->assertFalse($expirationChecker->isExpired($validUntil));
+ }
+ }
+
+Monotonic Clock
+~~~~~~~~~~~~~~~
+
+The ``MonotonicClock`` allows you to implement a precise stopwatch; depending on
+the system up to nanosecond precision. It can be used to measure the elapsed
+time between two calls without being affected by inconsistencies sometimes introduced
+by the system clock, e.g. by updating it. Instead, it consistently increases time,
+making it especially useful for measuring performance.
+
+.. _clock_use-inside-a-service:
+
+Using a Clock inside a Service
+------------------------------
+
+Using the Clock component in your services to retrieve the current time makes
+them easier to test. For example, by using the ``MockClock`` implementation as
+the default one during tests, you will have full control to set the "current time"
+to any arbitrary date/time.
+
+In order to use this component in your services, make their classes use the
+:class:`Symfony\\Component\\Clock\\ClockAwareTrait`. Thanks to
+:ref:`service autoconfiguration `, the ``setClock()`` method
+of the trait will automatically be called by the service container.
+
+You can now call the ``$this->now()`` method to get the current time::
+
+ namespace App\TimeUtils;
+
+ use Symfony\Component\Clock\ClockAwareTrait;
+
+ class MonthSensitive
+ {
+ use ClockAwareTrait;
+
+ public function isWinterMonth(): bool
+ {
+ $now = $this->now();
+
+ return match ($now->format('F')) {
+ 'December', 'January', 'February', 'March' => true,
+ default => false,
+ };
+ }
+ }
+
+Thanks to the ``ClockAwareTrait``, and by using the ``MockClock`` implementation,
+you can set the current time arbitrarily without having to change your service code.
+This will help you test every case of your method without the need of actually
+being in a month or another.
+
+.. _clock_date-point:
+
+The ``DatePoint`` Class
+-----------------------
+
+The Clock component uses a special :class:`Symfony\\Component\\Clock\\DatePoint`
+class. This is a small wrapper on top of PHP's :phpclass:`DateTimeImmutable`.
+You can use it seamlessly everywhere a :phpclass:`DateTimeImmutable` or
+:phpclass:`DateTimeInterface` is expected. The ``DatePoint`` object fetches the
+date and time from the :class:`Symfony\\Component\\Clock\\Clock` class. This means
+that if you did any changes to the clock as stated in the
+:ref:`usage section `, it will be reflected when creating a new
+``DatePoint``. You can also create a new ``DatePoint`` instance directly, for
+instance when using it as a default value::
+
+ use Symfony\Component\Clock\DatePoint;
+
+ class Post
+ {
+ public function __construct(
+ // ...
+ private \DateTimeImmutable $createdAt = new DatePoint(),
+ ) {
+ }
+ }
+
+The constructor also allows setting a timezone or custom referenced date::
+
+ // you can specify a timezone
+ $withTimezone = new DatePoint(timezone: new \DateTimezone('UTC'));
+
+ // you can also create a DatePoint from a reference 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
+ error handling across versions of PHP, thanks to polyfilling `PHP 8.3's behavior`_
+ on the topic.
+
+``DatePoint`` also allows to set and get the microsecond part of the date and time::
+
+ $datePoint = new DatePoint();
+ $datePoint->setMicrosecond(345);
+ $microseconds = $datePoint->getMicrosecond();
+
+.. note::
+
+ This feature polyfills PHP 8.4's behavior on the topic, as microseconds manipulation
+ is not available in previous versions of PHP.
+
+.. versionadded:: 7.1
+
+ The :method:`Symfony\\Component\\Clock\\DatePoint::setMicrosecond` and
+ :method:`Symfony\\Component\\Clock\\DatePoint::getMicrosecond` methods were
+ introduced in Symfony 7.1.
+
+Storing DatePoints in the Database
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you :doc:`use Doctrine ` to work with databases, consider using the
+``date_point`` Doctrine type, which converts to/from ``DatePoint`` objects automatically::
+
+ // src/Entity/Product.php
+ namespace App\Entity;
+
+ use Doctrine\ORM\Mapping as ORM;
+ use Symfony\Component\Clock\DatePoint;
+
+ #[ORM\Entity]
+ class Product
+ {
+ // if you don't define the Doctrine type explicitly, Symfony will autodetect it:
+ #[ORM\Column]
+ private DatePoint $createdAt;
+
+ // if you prefer to define the Doctrine type explicitly:
+ #[ORM\Column(type: 'date_point')]
+ private DatePoint $updatedAt;
+
+ // ...
+ }
+
+.. versionadded:: 7.3
+
+ The ``DatePointType`` was introduced in Symfony 7.3.
+
+.. _clock_writing-tests:
+
+Writing Time-Sensitive Tests
+----------------------------
+
+The Clock component provides another trait, called :class:`Symfony\\Component\\Clock\\Test\\ClockSensitiveTrait`,
+to help you write time-sensitive tests. This trait provides methods to freeze
+time and restore the global clock after each test.
+
+Use the ``ClockSensitiveTrait::mockTime()`` method to interact with the mocked
+clock in your tests. This method accepts different types as its only argument:
+
+* A string, which can be a date to set the clock at (e.g. ``1996-07-01``) or an
+ interval to modify the clock (e.g. ``+2 days``);
+* A ``DateTimeImmutable`` to set the clock at;
+* A boolean, to freeze or restore the global clock.
+
+Let's say you want to test the method ``MonthSensitive::isWinterMonth()`` of the
+above example. This is how you can write that test::
+
+ namespace App\Tests\TimeUtils;
+
+ use App\TimeUtils\MonthSensitive;
+ use PHPUnit\Framework\TestCase;
+ use Symfony\Component\Clock\Test\ClockSensitiveTrait;
+
+ class MonthSensitiveTest extends TestCase
+ {
+ use ClockSensitiveTrait;
+
+ public function testIsWinterMonth(): void
+ {
+ $clock = static::mockTime(new \DateTimeImmutable('2022-03-02'));
+
+ $monthSensitive = new MonthSensitive();
+ $monthSensitive->setClock($clock);
+
+ $this->assertTrue($monthSensitive->isWinterMonth());
+ }
+
+ public function testIsNotWinterMonth(): void
+ {
+ $clock = static::mockTime(new \DateTimeImmutable('2023-06-02'));
+
+ $monthSensitive = new MonthSensitive();
+ $monthSensitive->setClock($clock);
+
+ $this->assertFalse($monthSensitive->isWinterMonth());
+ }
+ }
+
+This test will behave the same no matter which time of the year you run it.
+By combining the :class:`Symfony\\Component\\Clock\\ClockAwareTrait` and
+:class:`Symfony\\Component\\Clock\\Test\\ClockSensitiveTrait`, you have full
+control on your time-sensitive code's behavior.
+
+Exceptions Management
+---------------------
+
+The Clock component takes full advantage of some `PHP DateTime exceptions`_.
+If you pass an invalid string to the clock (e.g. when creating a clock or
+modifying a ``MockClock``) you'll get a ``DateMalformedStringException``. If you
+pass an invalid timezone, you'll get a ``DateInvalidTimeZoneException``::
+
+ $userInput = 'invalid timezone';
+
+ try {
+ $clock = Clock::get()->withTimeZone($userInput);
+ } catch (\DateInvalidTimeZoneException $exception) {
+ // ...
+ }
+
+These exceptions are available starting from PHP 8.3. However, thanks to the
+`symfony/polyfill-php83`_ dependency required by the Clock component, you can
+use them even if your project doesn't use PHP 8.3 yet.
+
+.. _`PSR-20`: https://www.php-fig.org/psr/psr-20/
+.. _`accepted by the DateTime constructor`: https://www.php.net/manual/en/datetime.formats.php
+.. _`PHP DateTime exceptions`: https://wiki.php.net/rfc/datetime-exceptions
+.. _`symfony/polyfill-php83`: https://github.com/symfony/polyfill-php83
+.. _`PHP 8.3's behavior`: https://wiki.php.net/rfc/datetime-exceptions
diff --git a/components/config.rst b/components/config.rst
index 7de46a6c6b7..9de03f1f869 100644
--- a/components/config.rst
+++ b/components/config.rst
@@ -1,13 +1,17 @@
-.. index::
- single: Config
- single: Components; Config
-
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/caching.rst b/components/config/caching.rst
index 833492dd45e..18620c0d8cf 100644
--- a/components/config/caching.rst
+++ b/components/config/caching.rst
@@ -1,6 +1,3 @@
-.. index::
- single: Config; Caching based on resources
-
Caching based on Resources
==========================
@@ -58,3 +55,17 @@ the cache file itself. This ``.meta`` file contains the serialized resources,
whose timestamps are used to determine if the cache is still fresh. When
not in debug mode, the cache is considered to be "fresh" as soon as it exists,
and therefore no ``.meta`` file will be generated.
+
+You can explicitly define the absolute path to the meta file::
+
+ use Symfony\Component\Config\ConfigCache;
+ use Symfony\Component\Config\Resource\FileResource;
+
+ $cachePath = __DIR__.'/cache/appUserMatcher.php';
+
+ // the third optional argument indicates the absolute path to the meta file
+ $userMatcherCache = new ConfigCache($cachePath, true, '/my/absolute/path/to/cache.meta');
+
+.. versionadded:: 7.1
+
+ The argument to customize the meta file path was introduced in Symfony 7.1.
diff --git a/components/config/definition.rst b/components/config/definition.rst
index ec51e8b6bd0..4848af33ffe 100644
--- a/components/config/definition.rst
+++ b/components/config/definition.rst
@@ -1,6 +1,3 @@
-.. index::
- single: Config; Defining and processing configuration values
-
Defining and Processing Configuration Values
============================================
@@ -57,7 +54,7 @@ implements the :class:`Symfony\\Component\\Config\\Definition\\ConfigurationInte
class DatabaseConfiguration implements ConfigurationInterface
{
- public function getConfigTreeBuilder()
+ public function getConfigTreeBuilder(): TreeBuilder
{
$treeBuilder = new TreeBuilder('database');
@@ -84,11 +81,21 @@ reflect the real structure of the configuration values::
->defaultTrue()
->end()
->scalarNode('default_connection')
- ->defaultValue('default')
+ ->defaultValue('mysql')
+ ->end()
+ ->stringNode('username')
+ ->defaultValue('root')
+ ->end()
+ ->stringNode('password')
+ ->defaultValue('root')
->end()
->end()
;
+.. versionadded:: 7.2
+
+ The ``stringNode()`` method was introduced in Symfony 7.2.
+
The root node itself is an array node, and has children, like the boolean
node ``auto_connect`` and the scalar node ``default_connection``. In general:
after defining a node, a call to ``end()`` takes you one step up in the
@@ -103,6 +110,7 @@ node definition. Node types are available for:
* scalar (generic type that includes booleans, strings, integers, floats
and ``null``)
* boolean
+* string
* integer
* float
* enum (similar to scalar, but it only allows a finite set of values)
@@ -112,6 +120,10 @@ node definition. Node types are available for:
and are created with ``node($name, $type)`` or their associated shortcut
``xxxxNode($name)`` method.
+.. versionadded:: 7.2
+
+ Support for the ``string`` type was introduced in Symfony 7.2.
+
Numeric Node Constraints
~~~~~~~~~~~~~~~~~~~~~~~~
@@ -151,11 +163,53 @@ values::
This will restrict the ``delivery`` options to be either ``standard``,
``expedited`` or ``priority``.
+You can also provide enum values to ``enumNode()``. Let's define an enumeration
+describing the possible states of the example above::
+
+ enum Delivery: string
+ {
+ case Standard = 'standard';
+ case Expedited = 'expedited';
+ case Priority = 'priority';
+ }
+
+The configuration can now be written like this::
+
+ $rootNode
+ ->children()
+ ->enumNode('delivery')
+ // You can provide all values of the enum...
+ ->values(Delivery::cases())
+ // ... or you can pass only some values next to other scalar values
+ ->values([Delivery::Priority, Delivery::Standard, 'other', false])
+ ->end()
+ ->end()
+ ;
+
+You can also use the ``enumClass()`` method to pass the FQCN of an enum
+class to the node. This will automatically set the values of the node to
+the cases of the enum::
+
+ $rootNode
+ ->children()
+ ->enumNode('delivery')
+ ->enumClass(Delivery::class)
+ ->end()
+ ->end()
+ ;
+
+When using a backed enum, the values provided to the node will be cast
+to one of the enum cases if possible.
+
+.. versionadded:: 7.3
+
+ The ``enumClass()`` method was introduced in Symfony 7.3.
+
Array Nodes
~~~~~~~~~~~
It is possible to add a deeper level to the hierarchy, by adding an array
-node. The array node itself, may have a pre-defined set of variable nodes::
+node. The array node itself, may have a predefined set of variable nodes::
$rootNode
->children()
@@ -193,7 +247,7 @@ above, it is possible to have multiple connection arrays (containing a ``driver`
``host``, etc.).
Sometimes, to improve the user experience of your application or bundle, you may
-allow to use a simple string or numeric value where an array value is required.
+allow the use of a simple string or numeric value where an array value is required.
Use the ``castToArray()`` helper to turn those variables into arrays::
->arrayNode('hosts')
@@ -435,13 +489,6 @@ The following example shows these methods in practice::
Deprecating the Option
----------------------
-.. versionadded:: 5.1
-
- The signature of the ``setDeprecated()`` method changed from
- ``setDeprecated(?string $message)`` to
- ``setDeprecated(string $package, string $version, ?string $message)``
- in Symfony 5.1.
-
You can deprecate options using the
:method:`Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition::setDeprecated`
method::
@@ -499,6 +546,30 @@ and in XML:
+You can also provide a URL to a full documentation page::
+
+ $rootNode
+ ->docUrl('Full documentation is available at https://example.com/docs/{version:major}.{version:minor}/reference.html')
+ ->children()
+ ->integerNode('entries_per_page')
+ ->defaultValue(25)
+ ->end()
+ ->end()
+ ;
+
+A few placeholders are available to customize the URL:
+
+* ``{version:major}``: The major version of the package currently installed
+* ``{version:minor}``: The minor version of the package currently installed
+* ``{package}``: The name of the package
+
+The placeholders will be replaced when printing the configuration tree with the
+``config:dump-reference`` command.
+
+.. versionadded:: 7.3
+
+ The ``docUrl()`` method was introduced in Symfony 7.3.
+
Optional Sections
-----------------
@@ -550,7 +621,9 @@ be large and you may want to split it up into sections. You can do this
by making a section a separate node and then appending it into the main
tree with ``append()``::
- public function getConfigTreeBuilder()
+ use Symfony\Component\Config\Definition\Builder\NodeDefinition;
+
+ public function getConfigTreeBuilder(): TreeBuilder
{
$treeBuilder = new TreeBuilder('database');
@@ -579,7 +652,7 @@ tree with ``append()``::
return $treeBuilder;
}
- public function addParametersNode()
+ public function addParametersNode(): NodeDefinition
{
$treeBuilder = new TreeBuilder('parameters');
@@ -655,7 +728,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.
@@ -747,10 +820,10 @@ By changing a string value into an associative array with ``name`` as the key::
->arrayNode('connection')
->beforeNormalization()
->ifString()
- ->then(function ($v) { return ['name' => $v]; })
+ ->then(function (string $v): array { return ['name' => $v]; })
->end()
->children()
- ->scalarNode('name')->isRequired()
+ ->scalarNode('name')->isRequired()->end()
// ...
->end()
->end()
@@ -785,9 +858,10 @@ A validation rule always has an "if" part. You can specify this part in
the following ways:
- ``ifTrue()``
+- ``ifFalse()``
- ``ifString()``
- ``ifNull()``
-- ``ifEmpty()`` (since Symfony 3.2)
+- ``ifEmpty()``
- ``ifArray()``
- ``ifInArray()``
- ``ifNotInArray()``
@@ -803,6 +877,10 @@ A validation rule also requires a "then" part:
Usually, "then" is a closure. Its return value will be used as a new value
for the node, instead of the node's original value.
+.. versionadded:: 7.3
+
+ The ``ifFalse()`` method was introduced in Symfony 7.3.
+
Configuring the Node Path Separator
-----------------------------------
@@ -827,7 +905,8 @@ character (``.``)::
$node = $treeBuilder->buildTree();
$children = $node->getChildren();
- $path = $children['driver']->getPath();
+ $childChildren = $children['connection']->getChildren();
+ $path = $childChildren['driver']->getPath();
// $path = 'database.connection.driver'
Use the ``setPathSeparator()`` method on the config builder to change the path
@@ -838,7 +917,8 @@ separator::
$treeBuilder->setPathSeparator('/');
$node = $treeBuilder->buildTree();
$children = $node->getChildren();
- $path = $children['driver']->getPath();
+ $childChildren = $children['connection']->getChildren();
+ $path = $childChildren['driver']->getPath();
// $path = 'database/connection/driver'
Processing Configuration Values
@@ -871,3 +951,8 @@ Otherwise the result is a clean array of configuration values::
$databaseConfiguration,
$configs
);
+
+.. 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/config/resources.rst b/components/config/resources.rst
index 73d28a5db78..f9b0fda61ae 100644
--- a/components/config/resources.rst
+++ b/components/config/resources.rst
@@ -1,16 +1,6 @@
-.. index::
- single: Config; Loading resources
-
Loading Resources
=================
-.. caution::
-
- The ``IniFileLoader`` parses the file contents using the
- :phpfunction:`parse_ini_file` function. Therefore, you can only set
- parameters to string values. To set parameters to other data types
- (e.g. boolean, integer, etc), the other loaders are recommended.
-
Loaders populate the application's configuration from different sources
like YAML files. The Config component defines the interface for such
loaders. The :doc:`Dependency Injection `
@@ -40,7 +30,7 @@ an array containing all matches.
Resource Loaders
----------------
-For each type of resource (YAML, XML, annotation, etc.) a loader must be
+For each type of resource (YAML, XML, attributes, etc.) a loader must be
defined. Each loader should implement
:class:`Symfony\\Component\\Config\\Loader\\LoaderInterface` or extend the
abstract :class:`Symfony\\Component\\Config\\Loader\\FileLoader` class,
@@ -53,7 +43,7 @@ which allows for recursively importing other resources::
class YamlUserLoader extends FileLoader
{
- public function load($resource, $type = null)
+ public function load($resource, $type = null): void
{
$configValues = Yaml::parse(file_get_contents($resource));
@@ -64,7 +54,7 @@ which allows for recursively importing other resources::
// $this->import('extra_users.yaml');
}
- public function supports($resource, $type = null)
+ public function supports($resource, $type = null): bool
{
return is_string($resource) && 'yaml' === pathinfo(
$resource,
diff --git a/components/console.rst b/components/console.rst
index 6a2abe2366e..14817240206 100644
--- a/components/console.rst
+++ b/components/console.rst
@@ -1,7 +1,3 @@
-.. index::
- single: Console; CLI
- single: Components; Console
-
The Console Component
=====================
@@ -52,6 +48,20 @@ Then, you can register the commands using
// ...
$application->add(new GenerateAdminCommand());
+You can also register inline commands and define their behavior thanks to the
+``Command::setCode()`` method::
+
+ // ...
+ $application->register('generate-admin')
+ ->addArgument('username', InputArgument::REQUIRED)
+ ->setCode(function (InputInterface $input, OutputInterface $output): int {
+ // ...
+
+ return Command::SUCCESS;
+ });
+
+This is useful when creating a :doc:`single-command application `.
+
See the :doc:`/console` article for information about how to create commands.
Learn more
@@ -63,5 +73,4 @@ Learn more
/console
/components/console/*
- /components/console/helpers/index
/console/*
diff --git a/components/console/changing_default_command.rst b/components/console/changing_default_command.rst
index 6eb9f2b5227..c69995ea395 100644
--- a/components/console/changing_default_command.rst
+++ b/components/console/changing_default_command.rst
@@ -1,6 +1,3 @@
-.. index::
- single: Console; Changing the Default Command
-
Changing the Default Command
============================
@@ -10,22 +7,24 @@ name to the ``setDefaultCommand()`` method::
namespace Acme\Console\Command;
+ use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
+ #[AsCommand(name: 'hello:world')]
class HelloWorldCommand extends Command
{
- protected static $defaultName = 'hello:world';
-
- protected function configure()
+ protected function configure(): void
{
$this->setDescription('Outputs "Hello World"');
}
- protected function execute(InputInterface $input, OutputInterface $output)
+ protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln('Hello World');
+
+ return Command::SUCCESS;
}
}
@@ -53,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/console_arguments.rst b/components/console/console_arguments.rst
index 79f5c6c1f4c..da538ac78f1 100644
--- a/components/console/console_arguments.rst
+++ b/components/console/console_arguments.rst
@@ -1,6 +1,3 @@
-.. index::
- single: Console; Console arguments
-
Understanding how Console Arguments and Options Are Handled
===========================================================
@@ -14,6 +11,7 @@ Have a look at the following command that has three options::
namespace Acme\Console\Command;
+ use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
@@ -21,14 +19,12 @@ Have a look at the following command that has three options::
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
+ #[AsCommand(name: 'demo:args', description: 'Describe args behaviors')]
class DemoArgsCommand extends Command
{
- protected static $defaultName = 'demo:args';
-
- protected function configure()
+ protected function configure(): void
{
$this
- ->setDescription('Describe args behaviors')
->setDefinition(
new InputDefinition([
new InputOption('foo', 'f'),
@@ -38,7 +34,7 @@ Have a look at the following command that has three options::
);
}
- protected function execute(InputInterface $input, OutputInterface $output)
+ protected function execute(InputInterface $input, OutputInterface $output): int
{
// ...
}
diff --git a/components/console/events.rst b/components/console/events.rst
index 7183c2e75f7..e550025b7dd 100644
--- a/components/console/events.rst
+++ b/components/console/events.rst
@@ -1,6 +1,3 @@
-.. index::
- single: Console; Events
-
Using Events
============
@@ -17,10 +14,11 @@ 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.
+ Commands called by the main command will not trigger any event, unless
+ run by the application itself, see :doc:`/console/calling_commands`.
The ``ConsoleEvents::COMMAND`` Event
------------------------------------
@@ -36,7 +34,7 @@ dispatched. Listeners receive a
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
- $dispatcher->addListener(ConsoleEvents::COMMAND, function (ConsoleCommandEvent $event) {
+ $dispatcher->addListener(ConsoleEvents::COMMAND, function (ConsoleCommandEvent $event): void {
// gets the input instance
$input = $event->getInput();
@@ -67,7 +65,7 @@ C/C++ standard::
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
- $dispatcher->addListener(ConsoleEvents::COMMAND, function (ConsoleCommandEvent $event) {
+ $dispatcher->addListener(ConsoleEvents::COMMAND, function (ConsoleCommandEvent $event): void {
// gets the command to be executed
$command = $event->getCommand();
@@ -100,7 +98,7 @@ Listeners receive a
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
- $dispatcher->addListener(ConsoleEvents::ERROR, function (ConsoleErrorEvent $event) {
+ $dispatcher->addListener(ConsoleEvents::ERROR, function (ConsoleErrorEvent $event): void {
$output = $event->getOutput();
$command = $event->getCommand();
@@ -134,7 +132,7 @@ Listeners receive a
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
- $dispatcher->addListener(ConsoleEvents::TERMINATE, function (ConsoleTerminateEvent $event) {
+ $dispatcher->addListener(ConsoleEvents::TERMINATE, function (ConsoleTerminateEvent $event): void {
// gets the output
$output = $event->getOutput();
@@ -154,4 +152,107 @@ Listeners receive a
It is then dispatched just after the ``ConsoleEvents::ERROR`` event.
The exit code received in this case is the exception code.
+ Additionally, the event is dispatched when the command is being exited on
+ a signal. You can learn more about signals in the
+ :ref:`the dedicated section `.
+
+.. _console-events_signal:
+
+The ``ConsoleEvents::SIGNAL`` Event
+-----------------------------------
+
+**Typical Purposes**: To perform some actions after the command execution was interrupted.
+
+`Signals`_ are asynchronous notifications sent to a process in order to notify
+it of an event that occurred. For example, when you press ``Ctrl + C`` in a
+command, the operating system sends the ``SIGINT`` signal to it.
+
+When a command is interrupted, Symfony dispatches the ``ConsoleEvents::SIGNAL``
+event. Listen to this event so you can perform some actions (e.g. logging some
+results, cleaning some temporary files, etc.) before finishing the command execution.
+
+Listeners receive a
+:class:`Symfony\\Component\\Console\\Event\\ConsoleSignalEvent` event::
+
+ use Symfony\Component\Console\ConsoleEvents;
+ use Symfony\Component\Console\Event\ConsoleSignalEvent;
+
+ $dispatcher->addListener(ConsoleEvents::SIGNAL, function (ConsoleSignalEvent $event): void {
+
+ // gets the signal number
+ $signal = $event->getHandlingSignal();
+
+ // sets the exit code
+ $event->setExitCode(0);
+
+ if (\SIGINT === $signal) {
+ echo "bye bye!";
+ }
+ });
+
+It is also possible to abort the exit if you want the command to continue its
+execution even after the event has been dispatched, thanks to the
+:method:`Symfony\\Component\\Console\\Event\\ConsoleSignalEvent::abortExit`
+method::
+
+ use Symfony\Component\Console\ConsoleEvents;
+ use Symfony\Component\Console\Event\ConsoleSignalEvent;
+
+ $dispatcher->addListener(ConsoleEvents::SIGNAL, function (ConsoleSignalEvent $event) {
+ $event->abortExit();
+ });
+
+.. tip::
+
+ All the available signals (``SIGINT``, ``SIGQUIT``, etc.) are defined as
+ `constants of the PCNTL PHP extension`_. The extension has to be installed
+ for these constants to be available.
+
+If you use the Console component inside a Symfony application, commands can
+handle signals themselves. To do so, implement the
+:class:`Symfony\\Component\\Console\\Command\\SignalableCommandInterface` and subscribe to one or more signals::
+
+ // src/Command/SomeCommand.php
+ namespace App\Command;
+
+ use Symfony\Component\Console\Command\Command;
+ use Symfony\Component\Console\Command\SignalableCommandInterface;
+
+ class SomeCommand extends Command implements SignalableCommandInterface
+ {
+ // ...
+
+ public function getSubscribedSignals(): array
+ {
+ // return here any of the constants defined by PCNTL extension
+ return [\SIGINT, \SIGTERM];
+ }
+
+ public function handleSignal(int $signal): int|false
+ {
+ if (\SIGINT === $signal) {
+ // ...
+ }
+
+ // ...
+
+ // return an integer to set the exit code, or
+ // false to continue normal execution
+ return 0;
+ }
+ }
+
+Symfony doesn't handle any signal received by the command (not even ``SIGKILL``,
+``SIGTERM``, etc). This behavior is intended, as it gives you the flexibility to
+handle all signals e.g. to do some tasks before terminating the command.
+
+.. tip::
+
+ If you need to fetch the signal name from its integer value (e.g. for logging),
+ you can use the
+ :method:`Symfony\\Component\\Console\\SignalRegistry\\SignalMap::getSignalName`
+ method.
+
.. _`reserved exit codes`: https://www.tldp.org/LDP/abs/html/exitcodes.html
+.. _`Signals`: https://en.wikipedia.org/wiki/Signal_(IPC)
+.. _`constants of the PCNTL PHP extension`: https://www.php.net/manual/en/pcntl.constants.php
diff --git a/components/console/helpers/cursor.rst b/components/console/helpers/cursor.rst
new file mode 100644
index 00000000000..c5cab6c6d0b
--- /dev/null
+++ b/components/console/helpers/cursor.rst
@@ -0,0 +1,96 @@
+Cursor Helper
+=============
+
+The :class:`Symfony\\Component\\Console\\Cursor` allows you to change the
+cursor position in a console command. This allows you to write on any position
+of the output:
+
+.. image:: /_images/components/console/cursor.gif
+ :alt: A command outputs on various positions on the screen, eventually drawing the letters "SF".
+
+.. code-block:: php
+
+ // src/Command/MyCommand.php
+ namespace App\Command;
+
+ use Symfony\Component\Console\Command\Command;
+ use Symfony\Component\Console\Cursor;
+ use Symfony\Component\Console\Input\InputInterface;
+ use Symfony\Component\Console\Output\OutputInterface;
+
+ class MyCommand extends Command
+ {
+ // ...
+
+ public function execute(InputInterface $input, OutputInterface $output): int
+ {
+ // ...
+
+ $cursor = new Cursor($output);
+
+ // moves the cursor to a specific column (1st argument) and
+ // row (2nd argument) position
+ $cursor->moveToPosition(7, 11);
+
+ // and write text on this position using the output
+ $output->write('My text');
+
+ // ...
+ }
+ }
+
+Using the cursor
+----------------
+
+Moving the cursor
+.................
+
+There are few methods to control moving the command cursor::
+
+ // moves the cursor 1 line up from its current position
+ $cursor->moveUp();
+
+ // moves the cursor 3 lines up from its current position
+ $cursor->moveUp(3);
+
+ // same for down
+ $cursor->moveDown();
+
+ // moves the cursor 1 column right from its current position
+ $cursor->moveRight();
+
+ // moves the cursor 3 columns right from its current position
+ $cursor->moveRight(3);
+
+ // same for left
+ $cursor->moveLeft();
+
+ // move the cursor to a specific (column, row) position from the
+ // top-left position of the terminal
+ $cursor->moveToPosition(7, 11);
+
+You can get the current command's cursor position by using::
+
+ $position = $cursor->getCurrentPosition();
+ // $position[0] // columns (aka x coordinate)
+ // $position[1] // rows (aka y coordinate)
+
+Clearing output
+...............
+
+The cursor can also clear some output on the screen::
+
+ // clears all the output from the current line
+ $cursor->clearLine();
+
+ // clears all the output from the current line after the current position
+ $cursor->clearLineAfter();
+
+ // clears all the output from the cursors' current position to the end of the screen
+ $cursor->clearOutput();
+
+ // clears the entire screen
+ $cursor->clearScreen();
+
+You also can leverage the :method:`Symfony\\Component\\Console\\Cursor::show`
+and :method:`Symfony\\Component\\Console\\Cursor::hide` methods on the cursor.
diff --git a/components/console/helpers/debug_formatter.rst b/components/console/helpers/debug_formatter.rst
index 89609da8419..10d3c67a79a 100644
--- a/components/console/helpers/debug_formatter.rst
+++ b/components/console/helpers/debug_formatter.rst
@@ -1,17 +1,14 @@
-.. index::
- single: Console Helpers; DebugFormatter Helper
-
Debug Formatter Helper
======================
The :class:`Symfony\\Component\\Console\\Helper\\DebugFormatterHelper` provides
functions to output debug information when running an external program, for
instance a process or HTTP request. For example, if you used it to output
-the results of running ``ls -la`` on a UNIX system, it might output something
-like this:
+the results of running ``figlet symfony``, it might output something like
+this:
.. image:: /_images/components/console/debug_formatter.png
- :align: center
+ :alt: Console output, with the first line showing "RUN Running figlet", followed by lines showing the output of the command prefixed with "OUT" and "RES Finished the command" as last line in the output.
Using the debug_formatter
-------------------------
@@ -81,7 +78,7 @@ using
// ...
$process = new Process(...);
- $process->run(function ($type, $buffer) use ($output, $debugFormatter, $process) {
+ $process->run(function (string $type, string $buffer) use ($output, $debugFormatter, $process): void {
$output->writeln(
$debugFormatter->progress(
spl_object_hash($process),
diff --git a/components/console/helpers/formatterhelper.rst b/components/console/helpers/formatterhelper.rst
index ba3c2743d24..d2b19915a3a 100644
--- a/components/console/helpers/formatterhelper.rst
+++ b/components/console/helpers/formatterhelper.rst
@@ -1,6 +1,3 @@
-.. index::
- single: Console Helpers; Formatter Helper
-
Formatter Helper
================
@@ -9,14 +6,20 @@ You can do more advanced things with this helper than you can in
:doc:`/console/coloring`.
The :class:`Symfony\\Component\\Console\\Helper\\FormatterHelper` is included
-in the default helper set, which you can get by calling
-:method:`Symfony\\Component\\Console\\Command\\Command::getHelperSet`::
+in the default helper set and you can get it by calling
+:method:`Symfony\\Component\\Console\\Command\\Command::getHelper`::
$formatter = $this->getHelper('formatter');
The methods return a string, which you'll usually render to the console by
passing it to the
-:method:`OutputInterface::writeln ` method.
+:method:`OutputInterface::writeln `
+method.
+
+.. note::
+
+ As an alternative, consider using the
+ :ref:`SymfonyStyle ` to display stylized blocks.
Print Messages in a Section
---------------------------
@@ -61,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
------------------------
@@ -78,11 +82,13 @@ you can write::
$truncatedMessage = $formatter->truncate($message, 7);
$output->writeln($truncatedMessage);
-And the output will be::
+And the output will be:
+
+.. code-block:: text
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
@@ -93,7 +99,9 @@ from the end of the string::
$truncatedMessage = $formatter->truncate($message, -5);
-This will result in::
+This will result in:
+
+.. code-block:: text
This is a very long message, which should be trun...
@@ -102,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::
@@ -112,3 +120,34 @@ If you don't want to use suffix at all, pass an empty string::
$truncatedMessage = $formatter->truncate('test', 10);
// result: test
// because length of the "test..." string is shorter than 10
+
+Formatting Time
+---------------
+
+Sometimes you want to format seconds to time. This is possible with the
+:method:`Symfony\\Component\\Console\\Helper\\Helper::formatTime` method.
+The first argument is the seconds to format and the second argument is the
+precision (default ``1``) of the result::
+
+ Helper::formatTime(0.001); // 1 ms
+ Helper::formatTime(42); // 42 s
+ Helper::formatTime(125); // 2 min
+ Helper::formatTime(125, 2); // 2 min, 5 s
+ Helper::formatTime(172799, 4); // 1 d, 23 h, 59 min, 59 s
+ Helper::formatTime(172799.056, 5); // 1 d, 23 h, 59 min, 59 s, 56 ms
+
+.. versionadded:: 7.3
+
+ Support for formatting up to milliseconds was introduced in Symfony 7.3.
+
+Formatting Memory
+-----------------
+
+Sometimes you want to format memory to GiB, MiB, KiB and B. This is possible with the
+:method:`Symfony\\Component\\Console\\Helper\\Helper::formatMemory` method.
+The only argument is the memory size to format::
+
+ Helper::formatMemory(512); // 512 B
+ Helper::formatMemory(1024); // 1 KiB
+ Helper::formatMemory(1024 * 1024); // 1.0 MiB
+ Helper::formatMemory(1024 * 1024 * 1024); // 1 GiB
diff --git a/components/console/helpers/index.rst b/components/console/helpers/index.rst
index 87c62ca7629..893652fb5ab 100644
--- a/components/console/helpers/index.rst
+++ b/components/console/helpers/index.rst
@@ -1,20 +1,7 @@
-.. index::
- single: Console; Console Helpers
-
The Console Helpers
===================
-.. toctree::
- :hidden:
-
- formatterhelper
- processhelper
- progressbar
- questionhelper
- table
- debug_formatter
-
The Console component comes with some useful helpers. These helpers contain
-function to ease some common tasks.
+functions to ease some common tasks.
.. include:: map.rst.inc
diff --git a/components/console/helpers/map.rst.inc b/components/console/helpers/map.rst.inc
index 68e1e722a87..73d5d4da7a0 100644
--- a/components/console/helpers/map.rst.inc
+++ b/components/console/helpers/map.rst.inc
@@ -1,6 +1,9 @@
* :doc:`/components/console/helpers/formatterhelper`
* :doc:`/components/console/helpers/processhelper`
* :doc:`/components/console/helpers/progressbar`
+* :doc:`/components/console/helpers/progressindicator`
* :doc:`/components/console/helpers/questionhelper`
* :doc:`/components/console/helpers/table`
+* :doc:`/components/console/helpers/tree`
* :doc:`/components/console/helpers/debug_formatter`
+* :doc:`/components/console/helpers/cursor`
diff --git a/components/console/helpers/processhelper.rst b/components/console/helpers/processhelper.rst
index 45572d90a66..b46d9f2e95f 100644
--- a/components/console/helpers/processhelper.rst
+++ b/components/console/helpers/processhelper.rst
@@ -1,14 +1,12 @@
-.. index::
- single: Console Helpers; Process Helper
-
Process Helper
==============
-The Process Helper shows processes as they're running and reports
-useful information about process status.
+The Process Helper shows processes as they're running and reports useful
+information about process status.
-To display process details, use the :class:`Symfony\\Component\\Console\\Helper\\ProcessHelper`
-and run your command with verbosity. For example, running the following code with
+To display process details, use the
+:class:`Symfony\\Component\\Console\\Helper\\ProcessHelper` and run your command
+with verbosity. For example, running the following code with
a very verbose verbosity (e.g. ``-vv``)::
use Symfony\Component\Process\Process;
@@ -21,24 +19,30 @@ a very verbose verbosity (e.g. ``-vv``)::
will result in this output:
.. image:: /_images/components/console/process-helper-verbose.png
+ :alt: Console output showing two lines: "RUN 'figlet' 'Symfony'" and "RES Command ran successfully".
It will result in more detailed output with debug verbosity (e.g. ``-vvv``):
.. image:: /_images/components/console/process-helper-debug.png
+ :alt: In between the command line and the result line, the command's output is now shown prefixed by "OUT".
In case the process fails, debugging is easier:
.. image:: /_images/components/console/process-helper-error-debug.png
+ :alt: The last line shows "RES 127 Command dit not run successfully", and the output lines show more the error information from the command.
-Arguments
----------
+.. note::
-There are three ways to use the process helper:
+ By default, the process helper uses the error output (``stderr``) as
+ its default output. This behavior can be changed by passing an instance of
+ :class:`Symfony\\Component\\Console\\Output\\StreamOutput` to the
+ :method:`Symfony\\Component\\Console\\Helper\\ProcessHelper::run`
+ method.
-* Using a command line string::
+Arguments
+---------
- // ...
- $helper->run($output, 'figlet Symfony');
+There are two ways to use the process helper:
* An array of arguments::
@@ -72,7 +76,7 @@ A custom process callback can be passed as the fourth argument. Refer to the
use Symfony\Component\Process\Process;
- $helper->run($output, $process, 'The process failed :(', function ($type, $data) {
+ $helper->run($output, $process, 'The process failed :(', function (string $type, string $data): void {
if (Process::ERR === $type) {
// ... do something with the stderr output
} else {
diff --git a/components/console/helpers/progressbar.rst b/components/console/helpers/progressbar.rst
index c5f07a87893..19e2d0daef5 100644
--- a/components/console/helpers/progressbar.rst
+++ b/components/console/helpers/progressbar.rst
@@ -1,6 +1,3 @@
-.. index::
- single: Console Helpers; Progress Bar
-
Progress Bar
============
@@ -8,6 +5,12 @@ When executing longer-running commands, it may be helpful to show progress
information, which updates as your command runs:
.. image:: /_images/components/console/progressbar.gif
+ :alt: Console output showing a progress bar advance to 100%, with the estimated time left, the memory usage and a special message that changes when the bar closes completion.
+
+.. note::
+
+ As an alternative, consider using the
+ :ref:`SymfonyStyle ` to display a progress bar.
To display progress details, use the
:class:`Symfony\\Component\\Console\\Helper\\ProgressBar`, pass it a total
@@ -41,11 +44,31 @@ number of units, and advance the progress as the command executes::
``$progress->advance()`` with a negative value. For example, if you call
``$progress->advance(-2)`` then it will regress the progress bar 2 steps.
+.. note::
+
+ By default, the progress bar helper uses the error output (``stderr``) as
+ its default output. This behavior can be changed by passing an instance of
+ :class:`Symfony\\Component\\Console\\Output\\StreamOutput` to the
+ :class:`Symfony\\Component\\Console\\Helper\\ProgressBar`
+ constructor.
+
Instead of advancing the bar by a number of steps (with the
:method:`Symfony\\Component\\Console\\Helper\\ProgressBar::advance` method),
you can also set the current progress by calling the
:method:`Symfony\\Component\\Console\\Helper\\ProgressBar::setProgress` method.
+If you are resuming long-standing tasks, it's useful to start drawing the progress
+bar at a certain point. Use the second optional argument of ``start()`` to set
+that starting point::
+
+ use Symfony\Component\Console\Helper\ProgressBar;
+
+ // creates a new progress bar (100 units)
+ $progressBar = new ProgressBar($output, 100);
+
+ // displays the progress bar starting at 25 completed units
+ $progressBar->start(null, 25);
+
.. tip::
If your platform doesn't support ANSI codes, updates to the progress
@@ -84,6 +107,12 @@ The progress will then be displayed as a throbber:
1/3 [=========>------------------] 33%
3/3 [============================] 100%
+.. tip::
+
+ An alternative to this is to use a
+ :doc:`/components/console/helpers/progressindicator` instead of a
+ progress bar.
+
Whenever your task is finished, don't forget to call
:method:`Symfony\\Component\\Console\\Helper\\ProgressBar::finish` to ensure
that the progress bar display is refreshed with a 100% completion.
@@ -210,10 +239,14 @@ current progress of the bar. Here is a list of the built-in placeholders:
* ``memory``: The current memory usage;
* ``message``: used to display arbitrary messages in the progress bar (as explained later).
+The time fields ``elapsed``, ``remaining`` and ``estimated`` are displayed with
+a precision of 2. That means ``172799`` seconds are displayed as
+``1 day, 23 hrs`` instead of ``1 day, 23 hrs, 59 mins, 59 secs``.
+
For instance, here is how you could set the format to be the same as the
``debug`` one::
- $progressBar->setFormat(' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%');
+ $progressBar->setFormat(' %current%/%max% [%bar%] %percent:3s%% %elapsed:16s%/%estimated:-16s% %memory:6s%');
Notice the ``:6s`` part added to some placeholders? That's how you can tweak
the appearance of the bar (formatting and alignment). The part after the colon
@@ -290,10 +323,10 @@ to display it can be customized::
// the bar width
$progressBar->setBarWidth(50);
-.. caution::
+.. warning::
- For performance reasons, Symfony redraws screen every 100ms. If this is too
- fast or to slow for your application, use the methods
+ For performance reasons, Symfony redraws the screen once every 100ms. If this is too
+ fast or too slow for your application, use the methods
:method:`Symfony\\Component\\Console\\Helper\\ProgressBar::minSecondsBetweenRedraws` and
:method:`Symfony\\Component\\Console\\Helper\\ProgressBar::maxSecondsBetweenRedraws`::
@@ -321,13 +354,23 @@ display that are not available in the list of built-in placeholders, you can
create your own. Let's see how you can create a ``remaining_steps`` placeholder
that displays the number of remaining steps::
+ // This definition is globally registered for all ProgressBar instances
ProgressBar::setPlaceholderFormatterDefinition(
'remaining_steps',
- function (ProgressBar $progressBar, OutputInterface $output) {
+ function (ProgressBar $progressBar, OutputInterface $output): int {
return $progressBar->getMaxSteps() - $progressBar->getProgress();
}
);
+It is also possible to set a placeholder formatter per ProgressBar instance
+with the ``setPlaceholderFormatter`` method::
+
+ $progressBar = new ProgressBar($output, 3, 0);
+ $progressBar->setFormat('%countdown% [%bar%]');
+ $progressBar->setPlaceholderFormatter('countdown', function (ProgressBar $progressBar) {
+ return $progressBar->getMaxSteps() - $progressBar->getProgress();
+ });
+
Custom Messages
~~~~~~~~~~~~~~~
@@ -348,8 +391,8 @@ placeholder before displaying the progress bar::
$progressBar->start();
// 0/100 -- Start
- $progressBar->advance();
$progressBar->setMessage('Task is in progress...');
+ $progressBar->advance();
// 1/100 -- Task is in progress...
Messages can be combined with custom placeholders too. In this example, the
diff --git a/components/console/helpers/progressindicator.rst b/components/console/helpers/progressindicator.rst
new file mode 100644
index 00000000000..0defe7c83fd
--- /dev/null
+++ b/components/console/helpers/progressindicator.rst
@@ -0,0 +1,155 @@
+Progress Indicator
+==================
+
+Progress indicators are useful to let users know that a command isn't stalled.
+Unlike :doc:`progress bars `, these
+indicators are used when the command duration is indeterminate (e.g. long-running
+commands, unquantifiable tasks, etc.)
+
+They work by instantiating the :class:`Symfony\\Component\\Console\\Helper\\ProgressIndicator`
+class and advancing the progress as the command executes::
+
+ use Symfony\Component\Console\Helper\ProgressIndicator;
+
+ // creates a new progress indicator
+ $progressIndicator = new ProgressIndicator($output);
+
+ // starts and displays the progress indicator with a custom message
+ $progressIndicator->start('Processing...');
+
+ $i = 0;
+ while ($i++ < 50) {
+ // ... do some work
+
+ // advances the progress indicator
+ $progressIndicator->advance();
+ }
+
+ // ensures that the progress indicator shows a final message
+ $progressIndicator->finish('Finished');
+
+Customizing the Progress Indicator
+----------------------------------
+
+Built-in Formats
+~~~~~~~~~~~~~~~~
+
+By default, the information rendered on a progress indicator depends on the current
+level of verbosity of the ``OutputInterface`` instance:
+
+.. code-block:: text
+
+ # OutputInterface::VERBOSITY_NORMAL (CLI with no verbosity flag)
+ \ Processing...
+ | Processing...
+ / Processing...
+ - Processing...
+ ✔ Finished
+
+ # OutputInterface::VERBOSITY_VERBOSE (-v)
+ \ Processing... (1 sec)
+ | Processing... (1 sec)
+ / Processing... (1 sec)
+ - Processing... (1 sec)
+ ✔ Finished (1 sec)
+
+ # OutputInterface::VERBOSITY_VERY_VERBOSE (-vv) and OutputInterface::VERBOSITY_DEBUG (-vvv)
+ \ Processing... (1 sec, 6.0 MiB)
+ | Processing... (1 sec, 6.0 MiB)
+ / Processing... (1 sec, 6.0 MiB)
+ - Processing... (1 sec, 6.0 MiB)
+ ✔ Finished (1 sec, 6.0 MiB)
+
+.. tip::
+
+ Call a command with the quiet flag (``-q``) to not display any progress indicator.
+
+Instead of relying on the verbosity mode of the current command, you can also
+force a format via the second argument of the ``ProgressIndicator``
+constructor::
+
+ $progressIndicator = new ProgressIndicator($output, 'verbose');
+
+The built-in formats are the following:
+
+* ``normal``
+* ``verbose``
+* ``very_verbose``
+
+If your terminal doesn't support ANSI, use the ``no_ansi`` variants:
+
+* ``normal_no_ansi``
+* ``verbose_no_ansi``
+* ``very_verbose_no_ansi``
+
+Custom Indicator Values
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Instead of using the built-in indicator values, you can also set your own::
+
+ $progressIndicator = new ProgressIndicator($output, 'verbose', 100, ['⠏', '⠛', '⠹', '⢸', '⣰', '⣤', '⣆', '⡇']);
+
+The progress indicator will now look like this:
+
+.. code-block:: text
+
+ ⠏ Processing...
+ ⠛ Processing...
+ ⠹ Processing...
+ ⢸ Processing...
+ ✔ Finished
+
+Once the progress finishes, it displays a special finished indicator (which defaults
+to ✔). You can replace it with your own::
+
+ $progressIndicator = new ProgressIndicator($output, finishedIndicatorValue: '🎉');
+
+ try {
+ /* do something */
+ $progressIndicator->finish('Finished');
+ } catch (\Exception) {
+ $progressIndicator->finish('Failed', '🚨');
+ }
+
+The progress indicator will now look like this:
+
+.. code-block:: text
+
+ \ Processing...
+ | Processing...
+ / Processing...
+ - Processing...
+ 🎉 Finished
+
+.. versionadded:: 7.2
+
+ The ``finishedIndicator`` parameter for the constructor was introduced in Symfony 7.2.
+ The ``finishedIndicator`` parameter for method ``finish()`` was introduced in Symfony 7.2.
+
+Customize Placeholders
+~~~~~~~~~~~~~~~~~~~~~~
+
+A progress indicator uses placeholders (a name enclosed with the ``%``
+character) to determine the output format. Here is a list of the
+built-in placeholders:
+
+* ``indicator``: The current indicator;
+* ``elapsed``: The time elapsed since the start of the progress indicator;
+* ``memory``: The current memory usage;
+* ``message``: used to display arbitrary messages in the progress indicator.
+
+For example, this is how you can customize the ``message`` placeholder::
+
+ ProgressIndicator::setPlaceholderFormatterDefinition(
+ 'message',
+ static function (ProgressIndicator $progressIndicator): string {
+ // Return any arbitrary string
+ return 'My custom message';
+ }
+ );
+
+.. note::
+
+ Placeholders customization is applied globally, which means that any
+ progress indicator displayed after the
+ ``setPlaceholderFormatterDefinition()`` call will be affected.
diff --git a/components/console/helpers/questionhelper.rst b/components/console/helpers/questionhelper.rst
index 89716c8d078..c7e064b16ca 100644
--- a/components/console/helpers/questionhelper.rst
+++ b/components/console/helpers/questionhelper.rst
@@ -1,23 +1,25 @@
-.. index::
- single: Console Helpers; Question Helper
-
Question Helper
===============
The :class:`Symfony\\Component\\Console\\Helper\\QuestionHelper` provides
functions to ask the user for more information. It is included in the default
-helper set, which you can get by calling
-:method:`Symfony\\Component\\Console\\Command\\Command::getHelperSet`::
+helper set and you can get it by calling
+:method:`Symfony\\Component\\Console\\Command\\Command::getHelper`::
$helper = $this->getHelper('question');
The Question Helper has a single method
-:method:`Symfony\\Component\\Console\\Command\\Command::ask` that needs an
+:method:`Symfony\\Component\\Console\\Helper\\QuestionHelper::ask` that needs an
:class:`Symfony\\Component\\Console\\Input\\InputInterface` instance as the
first argument, an :class:`Symfony\\Component\\Console\\Output\\OutputInterface`
instance as the second argument and a
:class:`Symfony\\Component\\Console\\Question\\Question` as last argument.
+.. note::
+
+ As an alternative, consider using the
+ :ref:`SymfonyStyle ` to ask questions.
+
Asking the User for Confirmation
--------------------------------
@@ -34,7 +36,7 @@ the following to your command::
{
// ...
- public function execute(InputInterface $input, OutputInterface $output)
+ public function execute(InputInterface $input, OutputInterface $output): int
{
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('Continue with this action?', false);
@@ -42,11 +44,17 @@ the following to your command::
if (!$helper->ask($input, $output, $question)) {
return Command::SUCCESS;
}
+
+ // ... do something here
+
+ return Command::SUCCESS;
}
}
In this case, the user will be asked "Continue with this action?". If the user
-answers with ``y`` it returns ``true`` or ``false`` if they answer with ``n``.
+answers with ``y`` (or any word, expression starting with ``y`` due to default
+answer regex, e.g ``yeti``) it returns ``true`` or ``false`` otherwise, e.g. ``n``.
+
The second argument to
:method:`Symfony\\Component\\Console\\Question\\ConfirmationQuestion::__construct`
is the default value to return if the user doesn't enter any valid input. If
@@ -66,6 +74,14 @@ the second argument is not provided, ``true`` is assumed.
The regex defaults to ``/^y/i``.
+.. note::
+
+ By default, the question helper uses the error output (``stderr``) as
+ its default output. This behavior can be changed by passing an instance of
+ :class:`Symfony\\Component\\Console\\Output\\StreamOutput` to the
+ :method:`Symfony\\Component\\Console\\Helper\\QuestionHelper::ask`
+ method.
+
Asking the User for Information
-------------------------------
@@ -75,12 +91,16 @@ if you want to know a bundle name, you can add this to your command::
use Symfony\Component\Console\Question\Question;
// ...
- public function execute(InputInterface $input, OutputInterface $output)
+ public function execute(InputInterface $input, OutputInterface $output): int
{
// ...
$question = new Question('Please enter the name of the bundle', 'AcmeDemoBundle');
$bundleName = $helper->ask($input, $output, $question);
+
+ // ... do something with the bundleName
+
+ return Command::SUCCESS;
}
The user will be asked "Please enter the name of the bundle". They can type
@@ -93,18 +113,21 @@ Let the User Choose from a List of Answers
If you have a predefined set of answers the user can choose from, you
could use a :class:`Symfony\\Component\\Console\\Question\\ChoiceQuestion`
-which makes sure that the user can only enter a valid string
-from a predefined list::
+which makes sure that the user can only enter a valid string or the index
+of the choice from a predefined list. In the example below, typing ``blue``
+or ``1`` is the same choice for the user. A default value is set with ``0``
+but ``red`` could be set instead (could be more explicit)::
use Symfony\Component\Console\Question\ChoiceQuestion;
// ...
- public function execute(InputInterface $input, OutputInterface $output)
+ public function execute(InputInterface $input, OutputInterface $output): int
{
// ...
$helper = $this->getHelper('question');
$question = new ChoiceQuestion(
'Please select your favorite color (defaults to red)',
+ // choices can also be PHP objects that implement __toString() method
['red', 'blue', 'yellow'],
0
);
@@ -114,16 +137,40 @@ from a predefined list::
$output->writeln('You have just selected: '.$color);
// ... do something with the color
+
+ return Command::SUCCESS;
}
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
-of attempts is ``null``, which means infinite number of attempts. You can define
+of attempts is ``null``, which means an infinite number of attempts. You can define
your own error message using
:method:`Symfony\\Component\\Console\\Question\\ChoiceQuestion::setErrorMessage`.
@@ -137,7 +184,7 @@ this use :method:`Symfony\\Component\\Console\\Question\\ChoiceQuestion::setMult
use Symfony\Component\Console\Question\ChoiceQuestion;
// ...
- public function execute(InputInterface $input, OutputInterface $output)
+ public function execute(InputInterface $input, OutputInterface $output): int
{
// ...
$helper = $this->getHelper('question');
@@ -150,10 +197,14 @@ this use :method:`Symfony\\Component\\Console\\Question\\ChoiceQuestion::setMult
$colors = $helper->ask($input, $output, $question);
$output->writeln('You have just selected: ' . implode(', ', $colors));
+
+ return Command::SUCCESS;
}
Now, when the user enters ``1,2``, the result will be:
-``You have just selected: blue, yellow``.
+``You have just selected: blue, yellow``. The user can also enter strings
+(e.g. ``blue,yellow``) and even mix strings and the index of the choices
+(e.g. ``blue,2``).
If the user does not enter anything, the result will be:
``You have just selected: red, blue``.
@@ -167,7 +218,7 @@ will be autocompleted as the user types::
use Symfony\Component\Console\Question\Question;
// ...
- public function execute(InputInterface $input, OutputInterface $output)
+ public function execute(InputInterface $input, OutputInterface $output): int
{
// ...
$helper = $this->getHelper('question');
@@ -177,6 +228,10 @@ will be autocompleted as the user types::
$question->setAutocompleterValues($bundles);
$bundleName = $helper->ask($input, $output, $question);
+
+ // ... do something with the bundleName
+
+ return Command::SUCCESS;
}
In more complex use cases, it may be necessary to generate suggestions on the
@@ -186,7 +241,7 @@ provide a callback function to dynamically generate suggestions::
use Symfony\Component\Console\Question\Question;
// ...
- public function execute(InputInterface $input, OutputInterface $output)
+ public function execute(InputInterface $input, OutputInterface $output): int
{
$helper = $this->getHelper('question');
@@ -203,7 +258,7 @@ provide a callback function to dynamically generate suggestions::
// where files and dirs can be found
$foundFilesAndDirs = @scandir($inputPath) ?: [];
- return array_map(function ($dirOrFile) use ($inputPath) {
+ return array_map(function (string $dirOrFile) use ($inputPath): string {
return $inputPath.$dirOrFile;
}, $foundFilesAndDirs);
};
@@ -212,6 +267,10 @@ provide a callback function to dynamically generate suggestions::
$question->setAutocompleterCallback($callback);
$filePath = $helper->ask($input, $output, $question);
+
+ // ... do something with the filePath
+
+ return Command::SUCCESS;
}
Do not Trim the Answer
@@ -223,7 +282,7 @@ You can also specify if you want to not trim the answer by setting it directly w
use Symfony\Component\Console\Question\Question;
// ...
- public function execute(InputInterface $input, OutputInterface $output)
+ public function execute(InputInterface $input, OutputInterface $output): int
{
// ...
$helper = $this->getHelper('question');
@@ -232,16 +291,15 @@ You can also specify if you want to not trim the answer by setting it directly w
$question->setTrimmable(false);
// if the users inputs 'elsa ' it will not be trimmed and you will get 'elsa ' as value
$name = $helper->ask($input, $output, $question);
+
+ // ... do something with the name
+
+ return Command::SUCCESS;
}
Accept Multiline Answers
~~~~~~~~~~~~~~~~~~~~~~~~
-.. versionadded:: 5.2
-
- The ``setMultiline()`` and ``isMultiline()`` methods were introduced in
- Symfony 5.2.
-
By default, the question helper stops reading user input when it receives a newline
character (i.e., when the user hits ``ENTER`` once). However, you may specify that
the response to a question should allow multiline answers by passing ``true`` to
@@ -250,7 +308,7 @@ the response to a question should allow multiline answers by passing ``true`` to
use Symfony\Component\Console\Question\Question;
// ...
- public function execute(InputInterface $input, OutputInterface $output)
+ public function execute(InputInterface $input, OutputInterface $output): int
{
// ...
$helper = $this->getHelper('question');
@@ -259,6 +317,10 @@ the response to a question should allow multiline answers by passing ``true`` to
$question->setMultiline(true);
$answer = $helper->ask($input, $output, $question);
+
+ // ... do something with the answer
+
+ return Command::SUCCESS;
}
Multiline questions stop reading user input after receiving an end-of-transmission
@@ -273,7 +335,7 @@ convenient for passwords::
use Symfony\Component\Console\Question\Question;
// ...
- public function execute(InputInterface $input, OutputInterface $output)
+ public function execute(InputInterface $input, OutputInterface $output): int
{
// ...
$helper = $this->getHelper('question');
@@ -283,9 +345,13 @@ convenient for passwords::
$question->setHiddenFallback(false);
$password = $helper->ask($input, $output, $question);
+
+ // ... do something with the password
+
+ 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,
@@ -306,13 +372,15 @@ convenient for passwords::
use Symfony\Component\Console\Question\ChoiceQuestion;
// ...
- public function execute(InputInterface $input, OutputInterface $output)
+ public function execute(InputInterface $input, OutputInterface $output): int
{
// ...
$helper = $this->getHelper('question');
QuestionHelper::disableStty();
// ...
+
+ return Command::SUCCESS;
}
Normalizing the Answer
@@ -328,26 +396,32 @@ method::
use Symfony\Component\Console\Question\Question;
// ...
- public function execute(InputInterface $input, OutputInterface $output)
+ public function execute(InputInterface $input, OutputInterface $output): int
{
// ...
$helper = $this->getHelper('question');
$question = new Question('Please enter the name of the bundle', 'AcmeDemoBundle');
- $question->setNormalizer(function ($value) {
+ $question->setNormalizer(function (string $value): string {
// $value can be null here
return $value ? trim($value) : '';
});
$bundleName = $helper->ask($input, $output, $question);
+
+ // ... do something with the bundleName
+
+ 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
normalizer and let the validator handle those errors.
+.. _console-validate-question-answer:
+
Validating the Answer
---------------------
@@ -360,13 +434,13 @@ method::
use Symfony\Component\Console\Question\Question;
// ...
- public function execute(InputInterface $input, OutputInterface $output)
+ public function execute(InputInterface $input, OutputInterface $output): int
{
// ...
$helper = $this->getHelper('question');
$question = new Question('Please enter the name of the bundle', 'AcmeDemoBundle');
- $question->setValidator(function ($answer) {
+ $question->setValidator(function (string $answer): string {
if (!is_string($answer) || 'Bundle' !== substr($answer, -6)) {
throw new \RuntimeException(
'The name of the bundle should be suffixed with \'Bundle\''
@@ -378,6 +452,10 @@ method::
$question->setMaxAttempts(2);
$bundleName = $helper->ask($input, $output, $question);
+
+ // ... do something with the bundleName
+
+ return Command::SUCCESS;
}
The ``$validator`` is a callback which handles the validation. It should
@@ -389,9 +467,25 @@ was successful.
You can set the max number of times to ask with the
:method:`Symfony\\Component\\Console\\Question\\Question::setMaxAttempts` method.
If you reach this max number it will use the default value. Using ``null`` means
-the amount of attempts is infinite. The user will be asked as long as they provide an
+the number of attempts is infinite. The user will be asked as long as they provide an
invalid answer and will only be able to proceed if their input is valid.
+.. tip::
+
+ You can even use the :doc:`Validator ` component to
+ validate the input by using the :method:`Symfony\\Component\\Validator\\Validation::createCallable`
+ method::
+
+ use Symfony\Component\Validator\Constraints\Regex;
+ use Symfony\Component\Validator\Validation;
+
+ $question = new Question('Please enter the name of the bundle', 'AcmeDemoBundle');
+ $validation = Validation::createCallable(new Regex(
+ pattern: '/^[a-zA-Z]+Bundle$/',
+ message: 'The name of the bundle should be suffixed with \'Bundle\'',
+ ));
+ $question->setValidator($validation);
+
Validating a Hidden Response
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -400,14 +494,17 @@ You can also use a validator with a hidden question::
use Symfony\Component\Console\Question\Question;
// ...
- public function execute(InputInterface $input, OutputInterface $output)
+ public function execute(InputInterface $input, OutputInterface $output): int
{
// ...
$helper = $this->getHelper('question');
$question = new Question('Please enter your password');
- $question->setValidator(function ($value) {
- if (trim($value) == '') {
+ $question->setNormalizer(function (?string $value): string {
+ return $value ?? '';
+ });
+ $question->setValidator(function (string $value): string {
+ if ('' === trim($value)) {
throw new \Exception('The password cannot be empty');
}
@@ -417,6 +514,10 @@ You can also use a validator with a hidden question::
$question->setMaxAttempts(20);
$password = $helper->ask($input, $output, $question);
+
+ // ... do something with the password
+
+ return Command::SUCCESS;
}
Testing a Command that Expects Input
@@ -425,12 +526,10 @@ Testing a Command that Expects Input
If you want to write a unit test for a command which expects some kind of input
from the command line, you need to set the inputs that the command expects::
- use Symfony\Component\Console\Helper\HelperSet;
- use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Tester\CommandTester;
// ...
- public function testExecute()
+ public function testExecute(): void
{
// ...
$commandTester = new CommandTester($command);
@@ -463,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 aa4c293d834..fe267599caf 100644
--- a/components/console/helpers/table.rst
+++ b/components/console/helpers/table.rst
@@ -1,21 +1,11 @@
-.. index::
- single: Console Helpers; Table
+Table Helper
+============
-Table
-=====
-
-When building a console application it may be useful to display tabular data:
-
-.. code-block:: terminal
-
- +---------------+--------------------------+------------------+
- | ISBN | Title | Author |
- +---------------+--------------------------+------------------+
- | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
- | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
- | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
- | 80-902734-1-6 | And Then There Were None | Agatha Christie |
- +---------------+--------------------------+------------------+
+When building console applications, Symfony provides several utilities for
+rendering tabular data. The simplest option is to use the table methods from
+:ref:`Symfony Style `. While convenient, this approach
+doesn't allow customization of the table's design. For more control and advanced
+features, use the ``Table`` console helper explained in this article.
To display a table, use :class:`Symfony\\Component\\Console\\Helper\\Table`,
set the headers, set the rows and then render the table::
@@ -28,7 +18,7 @@ set the headers, set the rows and then render the table::
class SomeCommand extends Command
{
- public function execute(InputInterface $input, OutputInterface $output)
+ public function execute(InputInterface $input, OutputInterface $output): int
{
$table = new Table($output);
$table
@@ -41,9 +31,27 @@ set the headers, set the rows and then render the table::
])
;
$table->render();
+
+ return Command::SUCCESS;
}
}
+This outputs:
+
+.. code-block:: terminal
+
+ +---------------+--------------------------+------------------+
+ | ISBN | Title | Author |
+ +---------------+--------------------------+------------------+
+ | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
+ | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
+ | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
+ | 80-902734-1-6 | And Then There Were None | Agatha Christie |
+ +---------------+--------------------------+------------------+
+
+Adding Table Separators
+-----------------------
+
You can add a table separator anywhere in the output by passing an instance of
:class:`Symfony\\Component\\Console\\Helper\\TableSeparator` as a row::
@@ -57,6 +65,8 @@ You can add a table separator anywhere in the output by passing an instance of
['80-902734-1-6', 'And Then There Were None', 'Agatha Christie'],
]);
+This outputs:
+
.. code-block:: terminal
+---------------+--------------------------+------------------+
@@ -69,13 +79,18 @@ You can add a table separator anywhere in the output by passing an instance of
| 80-902734-1-6 | And Then There Were None | Agatha Christie |
+---------------+--------------------------+------------------+
+Adding Table Titles
+-------------------
+
You can optionally display titles at the top and the bottom of the table::
// ...
- $table->setHeaderTitle('Books')
- $table->setFooterTitle('Page 1/2')
+ $table->setHeaderTitle('Books');
+ $table->setFooterTitle('Page 1/2');
$table->render();
+This outputs:
+
.. code-block:: terminal
+---------------+----------- Books --------+------------------+
@@ -88,6 +103,9 @@ You can optionally display titles at the top and the bottom of the table::
| 80-902734-1-6 | And Then There Were None | Agatha Christie |
+---------------+--------- Page 1/2 -------+------------------+
+Setting the Column Widths Explicitly
+------------------------------------
+
By default, the width of the columns is calculated automatically based on their
contents. Use the :method:`Symfony\\Component\\Console\\Helper\\Table::setColumnWidths`
method to set the column widths explicitly::
@@ -110,7 +128,7 @@ argument is the column width::
$table->setColumnWidth(2, 30);
$table->render();
-The output of this command will be:
+This outputs:
.. code-block:: terminal
@@ -137,7 +155,7 @@ If you prefer to wrap long contents in multiple rows, use the
$table->setColumnMaxWidth(1, 10);
$table->render();
-The output of this command will be:
+This outputs:
.. code-block:: terminal
@@ -147,20 +165,51 @@ The output of this command will be:
| 99921 | Divine Com | Dante Alighieri |
| -58-1 | edy | |
| 0-7 | | |
- | (the rest of rows...) |
+ | (the rest of the rows...) |
+-------+------------+--------------------------------+
+Rendering Vertical Tables
+-------------------------
+
+By default, table contents are displayed horizontally. You can change this behavior
+via the :method:`Symfony\\Component\\Console\\Helper\\Table::setVertical` method::
+
+ // ...
+ $table->setVertical();
+ $table->render();
+
+This outputs:
+
+.. code-block:: terminal
+
+ +------------------------------+
+ | ISBN: 99921-58-10-7 |
+ | Title: Divine Comedy |
+ | Author: Dante Alighieri |
+ |------------------------------|
+ | ISBN: 9971-5-0210-0 |
+ | Title: A Tale of Two Cities |
+ | Author: Charles Dickens |
+ +------------------------------+
+
+Customizing the Table Style
+---------------------------
+
The table style can be changed to any built-in styles via
:method:`Symfony\\Component\\Console\\Helper\\Table::setStyle`::
- // same as calling nothing
+ // this 'default' style is the one used when no style is specified
$table->setStyle('default');
- // changes the default style to compact
+Built-in Table Styles
+~~~~~~~~~~~~~~~~~~~~~
+
+**Compact**::
+
$table->setStyle('compact');
$table->render();
-This code results in:
+This outputs:
.. code-block:: terminal
@@ -170,12 +219,12 @@ This code results in:
960-425-059-0 The Lord of the Rings J. R. R. Tolkien
80-902734-1-6 And Then There Were None Agatha Christie
-You can also set the style to ``borderless``::
+**Borderless**::
$table->setStyle('borderless');
$table->render();
-which outputs:
+This outputs:
.. code-block:: terminal
@@ -188,14 +237,14 @@ which outputs:
80-902734-1-6 And Then There Were None Agatha Christie
=============== ========================== ==================
-You can also set the style to ``box``::
+**Box**::
$table->setStyle('box');
$table->render();
-which outputs:
+This outputs:
-.. code-block:: text
+.. code-block:: terminal
┌───────────────┬──────────────────────────┬──────────────────┐
│ ISBN │ Title │ Author │
@@ -206,14 +255,14 @@ which outputs:
│ 80-902734-1-6 │ And Then There Were None │ Agatha Christie │
└───────────────┴──────────────────────────┴──────────────────┘
-You can also set the style to ``box-double``::
+**Double box**::
$table->setStyle('box-double');
$table->render();
-which outputs:
+This outputs:
-.. code-block:: text
+.. code-block:: terminal
╔═══════════════╤══════════════════════════╤══════════════════╗
║ ISBN │ Title │ Author ║
@@ -224,7 +273,30 @@ which outputs:
║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
╚═══════════════╧══════════════════════════╧══════════════════╝
-If the built-in styles do not fit your need, define your own::
+**Markdown**::
+
+ $table->setStyle('markdown');
+ $table->render();
+
+This outputs:
+
+.. code-block:: terminal
+
+ | ISBN | Title | Author |
+ |---------------|--------------------------|------------------|
+ | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
+ | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
+ | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
+ | 80-902734-1-6 | And Then There Were None | Agatha Christie |
+
+.. versionadded:: 7.3
+
+ The ``markdown`` style was introduced in Symfony 7.3.
+
+Making a Custom Table Style
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If the built-in styles do not fit your needs, define your own::
use Symfony\Component\Console\Helper\TableStyle;
@@ -233,7 +305,7 @@ If the built-in styles do not fit your need, define your own::
// customizes the style
$tableStyle
- ->setDefaultCrossingChars('|>')
+ ->setHorizontalBorderChars('|>')
->setVerticalBorderChars('->')
->setDefaultCrossingChar(' ')
;
@@ -244,7 +316,7 @@ If the built-in styles do not fit your need, define your own::
Here is a full list of things you can customize:
* :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setPaddingChar`
-* :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setDefaultCrossingChars`
+* :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setHorizontalBorderChars`
* :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setVerticalBorderChars`
* :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setCrossingChars`
* :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setDefaultCrossingChar`
@@ -265,10 +337,6 @@ Here is a full list of things you can customize:
This method can also be used to override a built-in style.
-.. versionadded:: 5.2
-
- The option to style table cells was introduced in Symfony 5.2.
-
In addition to the built-in table styles, you can also apply different styles
to each table cell via :class:`Symfony\\Component\\Console\\Helper\\TableCellStyle`::
@@ -318,7 +386,7 @@ To make a table cell that spans multiple columns you can use a :class:`Symfony\\
;
$table->render();
-This results in:
+This outputs:
.. code-block:: terminal
@@ -338,10 +406,10 @@ This results in:
$table->setHeaders([
[new TableCell('Main table title', ['colspan' => 3])],
['ISBN', 'Title', 'Author'],
- ])
+ ]);
// ...
- This generates:
+ This outputs:
.. code-block:: terminal
@@ -383,7 +451,7 @@ This outputs:
| 978-0804169127 | Divine Comedy | spans multiple rows |
+----------------+---------------+---------------------+
-You can use the ``colspan`` and ``rowspan`` options at the same time which allows
+You can use the ``colspan`` and ``rowspan`` options at the same time, which allows
you to create any table layout you may wish.
.. _console-modify-rendered-tables:
@@ -406,7 +474,7 @@ The only requirement to append rows is that the table must be rendered inside a
class SomeCommand extends Command
{
- public function execute(InputInterface $input, OutputInterface $output)
+ public function execute(InputInterface $input, OutputInterface $output): int
{
$section = $output->section();
$table = new Table($section);
@@ -415,10 +483,12 @@ The only requirement to append rows is that the table must be rendered inside a
$table->render();
$table->appendRow(['Symfony']);
+
+ return Command::SUCCESS;
}
}
-This will display the following table in the terminal:
+This outputs:
.. code-block:: terminal
@@ -426,3 +496,24 @@ This will display the following table in the terminal:
| Love |
| Symfony |
+---------+
+
+.. tip::
+
+ You can create multiple lines using the :method:`Symfony\\Component\\Console\\Helper\\Table::addRows` method::
+
+ // ...
+ $table->addRows([
+ ['Hello', 'World'],
+ ['Love', 'Symfony'],
+ ]);
+ $table->render();
+ // ...
+
+ This outputs:
+
+ .. code-block:: terminal
+
+ +-------+---------+
+ | Hello | World |
+ | Love | Symfony |
+ +-------+---------+
diff --git a/components/console/helpers/tree.rst b/components/console/helpers/tree.rst
new file mode 100644
index 00000000000..b5839b74a26
--- /dev/null
+++ b/components/console/helpers/tree.rst
@@ -0,0 +1,341 @@
+Tree Helper
+===========
+
+The Tree Helper allows you to build and display tree structures in the console.
+It's commonly used to render directory hierarchies, but you can also use it to render
+any tree-like content, such us organizational charts, product category trees, taxonomies, etc.
+
+.. versionadded:: 7.3
+
+ The ``TreeHelper`` class was introduced in Symfony 7.3.
+
+Rendering a Tree
+----------------
+
+The :method:`Symfony\\Component\\Console\\Helper\\TreeHelper::createTree` method
+creates a tree structure from an array and returns a :class:`Symfony\\Component\\Console\\Helper\\Tree`
+object that can be rendered in the console.
+
+Rendering a Tree from an Array
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can build a tree from an array by passing the array to the
+:method:`Symfony\\Component\\Console\\Helper\\TreeHelper::createTree` method
+inside your console command::
+
+ namespace App\Command;
+
+ use Symfony\Component\Console\Attribute\AsCommand;
+ use Symfony\Component\Console\Command\Command;
+ use Symfony\Component\Console\Helper\TreeHelper;
+ use Symfony\Component\Console\Helper\TreeNode;
+ use Symfony\Component\Console\Input\InputInterface;
+ use Symfony\Component\Console\Output\OutputInterface;
+ use Symfony\Component\Console\Style\SymfonyStyle;
+
+ #[AsCommand(name: 'app:some-command', description: '...')]
+ class SomeCommand extends Command
+ {
+ // ...
+
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $io = new SymfonyStyle($input, $output);
+
+ $node = TreeNode::fromValues([
+ 'config/',
+ 'public/',
+ 'src/',
+ 'templates/',
+ 'tests/',
+ ]);
+
+ $tree = TreeHelper::createTree($io, $node);
+ $tree->render();
+
+ // ...
+ }
+ }
+
+This exampe would output the following:
+
+.. code-block:: terminal
+
+ ├── config/
+ ├── public/
+ ├── src/
+ ├── templates/
+ └── tests/
+
+The given contents can be defined in a multi-dimensional array::
+
+ $tree = TreeHelper::createTree($io, null, [
+ 'src' => [
+ 'Command',
+ 'Controller' => [
+ 'DefaultController.php',
+ ],
+ 'Kernel.php',
+ ],
+ 'templates' => [
+ 'base.html.twig',
+ ],
+ ]);
+
+ $tree->render();
+
+The above code will output the following tree:
+
+.. code-block:: terminal
+
+ ├── src
+ │ ├── Command
+ │ ├── Controller
+ │ │ └── DefaultController.php
+ │ └── Kernel.php
+ └── templates
+ └── base.html.twig
+
+Building a Tree Programmatically
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you don't know the tree elements beforehand, you can build the tree programmatically
+by creating a new instance of the :class:`Symfony\\Component\\Console\\Helper\\Tree`
+class and adding nodes to it::
+
+ use Symfony\Component\Console\Helper\TreeHelper;
+ use Symfony\Component\Console\Helper\TreeNode;
+
+ $root = new TreeNode('my-project/');
+ // you can pass a string directly or create a TreeNode object
+ $root->addChild('src/');
+ $root->addChild(new TreeNode('templates/'));
+
+ // create nested structures by adding child nodes to other nodes
+ $testsNode = new TreeNode('tests/');
+ $functionalTestsNode = new TreeNode('Functional/');
+ $testsNode->addChild($functionalTestsNode);
+ $root->addChild(testsNode);
+
+ $tree = TreeHelper::createTree($io, $root);
+ $tree->render();
+
+This example outputs:
+
+.. code-block:: terminal
+
+ my-project/
+ ├── src/
+ ├── templates/
+ └── tests/
+ └── Functional/
+
+If you prefer, you can build the array of elements programmatically and then
+create and render the tree like this::
+
+ $tree = TreeHelper::createTree($io, null, $array);
+ $tree->render();
+
+You can also build part of the tree from an array and then add other nodes::
+
+ $node = TreeNode::fromValues($array);
+ $node->addChild('templates');
+ // ...
+ $tree = TreeHelper::createTree($io, $node);
+ $tree->render();
+
+Customizing the Tree Style
+--------------------------
+
+Built-in Tree Styles
+~~~~~~~~~~~~~~~~~~~~
+
+The tree helper provides a few built-in styles that you can use to customize the
+output of the tree.
+
+**Default**::
+
+ TreeHelper::createTree($io, $node, [], TreeStyle::default());
+
+This outputs:
+
+.. code-block:: terminal
+
+ ├── config
+ │ ├── packages
+ │ └── routes
+ │ ├── framework.yaml
+ │ └── web_profiler.yaml
+ ├── src
+ │ ├── Command
+ │ ├── Controller
+ │ │ └── DefaultController.php
+ │ └── Kernel.php
+ └── templates
+ └── base.html.twig
+
+**Box**::
+
+ TreeHelper::createTree($io, $node, [], TreeStyle::box());
+
+This outputs:
+
+.. code-block:: terminal
+
+ ┃╸ config
+ ┃ ┃╸ packages
+ ┃ ┗╸ routes
+ ┃ ┃╸ framework.yaml
+ ┃ ┗╸ web_profiler.yaml
+ ┃╸ src
+ ┃ ┃╸ Command
+ ┃ ┃╸ Controller
+ ┃ ┃ ┗╸ DefaultController.php
+ ┃ ┗╸ Kernel.php
+ ┗╸ templates
+ ┗╸ base.html.twig
+
+**Double box**::
+
+ TreeHelper::createTree($io, $node, [], TreeStyle::doubleBox());
+
+This outputs:
+
+.. code-block:: terminal
+
+ ╠═ config
+ ║ ╠═ packages
+ ║ ╚═ routes
+ ║ ╠═ framework.yaml
+ ║ ╚═ web_profiler.yaml
+ ╠═ src
+ ║ ╠═ Command
+ ║ ╠═ Controller
+ ║ ║ ╚═ DefaultController.php
+ ║ ╚═ Kernel.php
+ ╚═ templates
+ ╚═ base.html.twig
+
+**Compact**::
+
+ TreeHelper::createTree($io, $node, [], TreeStyle::compact());
+
+This outputs:
+
+.. code-block:: terminal
+
+ ├ config
+ │ ├ packages
+ │ └ routes
+ │ ├ framework.yaml
+ │ └ web_profiler.yaml
+ ├ src
+ │ ├ Command
+ │ ├ Controller
+ │ │ └ DefaultController.php
+ │ └ Kernel.php
+ └ templates
+ └ base.html.twig
+
+**Light**::
+
+ TreeHelper::createTree($io, $node, [], TreeStyle::light());
+
+This outputs:
+
+.. code-block:: terminal
+
+ |-- config
+ | |-- packages
+ | `-- routes
+ | |-- framework.yaml
+ | `-- web_profiler.yaml
+ |-- src
+ | |-- Command
+ | |-- Controller
+ | | `-- DefaultController.php
+ | `-- Kernel.php
+ `-- templates
+ `-- base.html.twig
+
+**Minimal**::
+
+ TreeHelper::createTree($io, $node, [], TreeStyle::minimal());
+
+This outputs:
+
+.. code-block:: terminal
+
+ . config
+ . . packages
+ . . routes
+ . . framework.yaml
+ . . web_profiler.yaml
+ . src
+ . . Command
+ . . Controller
+ . . . DefaultController.php
+ . . Kernel.php
+ . templates
+ . base.html.twig
+
+**Rounded**::
+
+ TreeHelper::createTree($io, $node, [], TreeStyle::rounded());
+
+This outputs:
+
+.. code-block:: terminal
+
+ ├─ config
+ │ ├─ packages
+ │ ╰─ routes
+ │ ├─ framework.yaml
+ │ ╰─ web_profiler.yaml
+ ├─ src
+ │ ├─ Command
+ │ ├─ Controller
+ │ │ ╰─ DefaultController.php
+ │ ╰─ Kernel.php
+ ╰─ templates
+ ╰─ base.html.twig
+
+Making a Custom Tree Style
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can create your own tree style by passing the characters to the constructor
+of the :class:`Symfony\\Component\\Console\\Helper\\TreeStyle` class::
+
+ use Symfony\Component\Console\Helper\TreeHelper;
+ use Symfony\Component\Console\Helper\TreeStyle;
+
+ $customStyle = new TreeStyle('🟣 ', '🟠 ', '🔵 ', '🟢 ', '🔴 ', '🟡 ');
+
+ // Pass the custom style to the createTree method
+
+ $tree = TreeHelper::createTree($io, null, [
+ 'src' => [
+ 'Command',
+ 'Controller' => [
+ 'DefaultController.php',
+ ],
+ 'Kernel.php',
+ ],
+ 'templates' => [
+ 'base.html.twig',
+ ],
+ ], $customStyle);
+
+ $tree->render();
+
+The above code will output the following tree:
+
+.. code-block:: terminal
+
+ 🔵 🟣 🟡 src
+ 🔵 🟢 🟣 🟡 Command
+ 🔵 🟢 🟣 🟡 Controller
+ 🔵 🟢 🟢 🟠 🟡 DefaultController.php
+ 🔵 🟢 🟠 🟡 Kernel.php
+ 🔵 🟠 🟡 templates
+ 🔵 🔴 🟠 🟡 base.html.twig
diff --git a/components/console/logger.rst b/components/console/logger.rst
index 8f029e47002..c3d5c447a89 100644
--- a/components/console/logger.rst
+++ b/components/console/logger.rst
@@ -1,6 +1,3 @@
-.. index::
- single: Console; Logger
-
Using the Logger
================
@@ -19,14 +16,12 @@ PSR-3 compliant logger::
class MyDependency
{
- private $logger;
-
- public function __construct(LoggerInterface $logger)
- {
- $this->logger = $logger;
+ public function __construct(
+ private LoggerInterface $logger,
+ ) {
}
- public function doStuff()
+ public function doStuff(): void
{
$this->logger->info('I love Tony Vairelles\' hairdresser.');
}
@@ -37,30 +32,26 @@ You can rely on the logger to use this dependency inside a command::
namespace Acme\Console\Command;
use Acme\MyDependency;
+ use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Logger\ConsoleLogger;
use Symfony\Component\Console\Output\OutputInterface;
+ #[AsCommand(
+ name: 'my:command',
+ description: 'Use an external dependency requiring a PSR-3 logger'
+ )]
class MyCommand extends Command
{
- protected static $defaultName = 'my:command';
-
- protected function configure()
- {
- $this
- ->setDescription(
- 'Use an external dependency requiring a PSR-3 logger'
- )
- ;
- }
-
- protected function execute(InputInterface $input, OutputInterface $output)
+ protected function execute(InputInterface $input, OutputInterface $output): int
{
$logger = new ConsoleLogger($output);
$myDependency = new MyDependency($logger);
$myDependency->doStuff();
+
+ return Command::SUCCESS;
}
}
diff --git a/components/console/single_command_tool.rst b/components/console/single_command_tool.rst
index 500d679d1e1..97cb09bf030 100644
--- a/components/console/single_command_tool.rst
+++ b/components/console/single_command_tool.rst
@@ -1,19 +1,9 @@
-.. index::
- single: Console; Single command application
-
Building a single Command Application
=====================================
When building a command line tool, you may not need to provide several commands.
-In such case, having to pass the command name each time is tedious.
-
-.. versionadded:: 5.1
-
- The :class:`Symfony\\Component\\Console\\SingleCommandApplication` class was
- introduced in Symfony 5.1.
-
-Fortunately, it is possible to remove this need by declaring a single command
-application::
+In such a case, having to pass the command name each time is tedious. Fortunately,
+it is possible to remove this need by declaring a single command application::
#!/usr/bin/env php
setVersion('1.0.0') // Optional
->addArgument('foo', InputArgument::OPTIONAL, 'The directory')
->addOption('bar', null, InputOption::VALUE_REQUIRED)
- ->setCode(function (InputInterface $input, OutputInterface $output) {
+ ->setCode(function (InputInterface $input, OutputInterface $output): int {
// output arguments and options
})
->run();
diff --git a/components/console/usage.rst b/components/console/usage.rst
index e3a6601eec5..591994948b8 100644
--- a/components/console/usage.rst
+++ b/components/console/usage.rst
@@ -1,6 +1,3 @@
-.. index::
- single: Console; Usage
-
Using Console Commands, Shortcuts and Built-in Commands
=======================================================
@@ -68,9 +65,17 @@ You can suppress output with:
.. code-block:: terminal
+ # suppresses all output, including errors
+ $ php application.php list --silent
+
+ # suppresses all output except errors
$ php application.php list --quiet
$ php application.php list -q
+.. versionadded:: 7.2
+
+ The ``--silent`` option was introduced in Symfony 7.2.
+
You can get more verbose messages (if this is supported for a command)
with:
@@ -107,7 +112,7 @@ If you do not provide a console name then it will just output:
.. code-block:: text
- console tool
+ Console Tool
You can force turning on ANSI output coloring with:
diff --git a/components/contracts.rst b/components/contracts.rst
index 1f1cc3f6adc..56b0394397d 100644
--- a/components/contracts.rst
+++ b/components/contracts.rst
@@ -1,7 +1,3 @@
-.. index::
- single: Contracts
- single: Components; Contracts
-
The Contracts Component
=======================
@@ -61,7 +57,7 @@ convention. For example:
{
"...": "...",
"provide": {
- "symfony/cache-implementation": "1.0"
+ "symfony/cache-implementation": "3.0"
}
}
diff --git a/components/css_selector.rst b/components/css_selector.rst
index df9ddd84487..1331a11e616 100644
--- a/components/css_selector.rst
+++ b/components/css_selector.rst
@@ -1,7 +1,3 @@
-.. index::
- single: CssSelector
- single: Components; CssSelector
-
The CssSelector Component
=========================
@@ -25,8 +21,8 @@ Usage
component in any PHP application. Read the :ref:`Symfony Functional Tests `
article to learn about how to use it when creating Symfony tests.
-Why to Use CSS selectors?
-~~~~~~~~~~~~~~~~~~~~~~~~~
+Why Use CSS selectors?
+~~~~~~~~~~~~~~~~~~~~~~
When you're parsing an HTML or an XML document, by far the most powerful
method is `XPath`_.
@@ -40,7 +36,7 @@ long and unwieldy expressions.
Many developers -- particularly web developers -- are more comfortable
using CSS selectors to find elements. As well as working in stylesheets,
CSS selectors are used in JavaScript with the ``querySelectorAll()`` function
-and in popular JavaScript libraries such as jQuery, Prototype and MooTools.
+and in popular JavaScript libraries such as jQuery.
CSS selectors are less powerful than XPath, but far easier to write, read
and understand. Since they are less powerful, almost all CSS selectors can
@@ -96,7 +92,11 @@ Pseudo-classes are partially supported:
* Not supported: ``*:first-of-type``, ``*:last-of-type``, ``*:nth-of-type`` and
``*:nth-last-of-type`` (all these work with an element name (e.g.
``li:first-of-type``) but not with the ``*`` selector).
-* Supported: ``*:only-of-type``.
+* Supported: ``*:only-of-type``, ``*:scope``, ``*:is`` and ``*:where``.
+
+.. versionadded:: 7.1
+
+ The support for ``*:is`` and ``*:where`` was introduced in Symfony 7.1.
Learn more
----------
diff --git a/components/dependency_injection.rst b/components/dependency_injection.rst
index b303e96d484..93e8af711cf 100644
--- a/components/dependency_injection.rst
+++ b/components/dependency_injection.rst
@@ -1,7 +1,3 @@
-.. index::
- single: DependencyInjection
- single: Components; DependencyInjection
-
The DependencyInjection Component
=================================
@@ -35,7 +31,7 @@ you want to make available as a service::
class Mailer
{
- private $transport;
+ private string $transport;
public function __construct()
{
@@ -49,8 +45,8 @@ You can register this in the container as a service::
use Symfony\Component\DependencyInjection\ContainerBuilder;
- $containerBuilder = new ContainerBuilder();
- $containerBuilder->register('mailer', 'Mailer');
+ $container = new ContainerBuilder();
+ $container->register('mailer', 'Mailer');
An improvement to the class to make it more flexible would be to allow
the container to set the ``transport`` used. If you change the class
@@ -58,11 +54,9 @@ so this is passed into the constructor::
class Mailer
{
- private $transport;
-
- public function __construct($transport)
- {
- $this->transport = $transport;
+ public function __construct(
+ private string $transport,
+ ) {
}
// ...
@@ -72,8 +66,8 @@ Then you can set the choice of transport in the container::
use Symfony\Component\DependencyInjection\ContainerBuilder;
- $containerBuilder = new ContainerBuilder();
- $containerBuilder
+ $container = new ContainerBuilder();
+ $container
->register('mailer', 'Mailer')
->addArgument('sendmail');
@@ -87,9 +81,9 @@ the ``Mailer`` service's constructor argument::
use Symfony\Component\DependencyInjection\ContainerBuilder;
- $containerBuilder = new ContainerBuilder();
- $containerBuilder->setParameter('mailer.transport', 'sendmail');
- $containerBuilder
+ $container = new ContainerBuilder();
+ $container->setParameter('mailer.transport', 'sendmail');
+ $container
->register('mailer', 'Mailer')
->addArgument('%mailer.transport%');
@@ -99,11 +93,9 @@ like this::
class NewsletterManager
{
- private $mailer;
-
- public function __construct(\Mailer $mailer)
- {
- $this->mailer = $mailer;
+ public function __construct(
+ private \Mailer $mailer,
+ ) {
}
// ...
@@ -116,14 +108,14 @@ not exist yet. Use the ``Reference`` class to tell the container to inject the
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
- $containerBuilder = new ContainerBuilder();
+ $container = new ContainerBuilder();
- $containerBuilder->setParameter('mailer.transport', 'sendmail');
- $containerBuilder
+ $container->setParameter('mailer.transport', 'sendmail');
+ $container
->register('mailer', 'Mailer')
->addArgument('%mailer.transport%');
- $containerBuilder
+ $container
->register('newsletter_manager', 'NewsletterManager')
->addArgument(new Reference('mailer'));
@@ -132,9 +124,9 @@ it was only optional then you could use setter injection instead::
class NewsletterManager
{
- private $mailer;
+ private \Mailer $mailer;
- public function setMailer(\Mailer $mailer)
+ public function setMailer(\Mailer $mailer): void
{
$this->mailer = $mailer;
}
@@ -148,14 +140,14 @@ If you do want to though then the container can call the setter method::
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
- $containerBuilder = new ContainerBuilder();
+ $container = new ContainerBuilder();
- $containerBuilder->setParameter('mailer.transport', 'sendmail');
- $containerBuilder
+ $container->setParameter('mailer.transport', 'sendmail');
+ $container
->register('mailer', 'Mailer')
->addArgument('%mailer.transport%');
- $containerBuilder
+ $container
->register('newsletter_manager', 'NewsletterManager')
->addMethodCall('setMailer', [new Reference('mailer')]);
@@ -164,11 +156,40 @@ like this::
use Symfony\Component\DependencyInjection\ContainerBuilder;
+ $container = new ContainerBuilder();
+
+ // ...
+
+ $newsletterManager = $container->get('newsletter_manager');
+
+Getting Services That Don't Exist
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, when you try to get a service that doesn't exist, you see an exception.
+You can override this behavior as follows::
+
+ use Symfony\Component\DependencyInjection\ContainerBuilder;
+ use Symfony\Component\DependencyInjection\ContainerInterface;
+
$containerBuilder = new ContainerBuilder();
// ...
- $newsletterManager = $containerBuilder->get('newsletter_manager');
+ // the second argument is optional and defines what to do when the service doesn't exist
+ $newsletterManager = $containerBuilder->get('newsletter_manager', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE);
+
+These are all the possible behaviors:
+
+ * ``ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE``: throws an exception
+ at compile time (this is the **default** behavior);
+ * ``ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE``: throws an
+ exception at runtime, when trying to access the missing service;
+ * ``ContainerInterface::NULL_ON_INVALID_REFERENCE``: returns ``null``;
+ * ``ContainerInterface::IGNORE_ON_INVALID_REFERENCE``: ignores the wrapping
+ command asking for the reference (for instance, ignore a setter if the service
+ does not exist);
+ * ``ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE``: ignores/returns
+ ``null`` for uninitialized services or invalid references.
Avoiding your Code Becoming Dependent on the Container
------------------------------------------------------
@@ -202,8 +223,8 @@ Loading an XML config file::
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
- $containerBuilder = new ContainerBuilder();
- $loader = new XmlFileLoader($containerBuilder, new FileLocator(__DIR__));
+ $container = new ContainerBuilder();
+ $loader = new XmlFileLoader($container, new FileLocator(__DIR__));
$loader->load('services.xml');
Loading a YAML config file::
@@ -212,8 +233,8 @@ Loading a YAML config file::
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
- $containerBuilder = new ContainerBuilder();
- $loader = new YamlFileLoader($containerBuilder, new FileLocator(__DIR__));
+ $container = new ContainerBuilder();
+ $loader = new YamlFileLoader($container, new FileLocator(__DIR__));
$loader->load('services.yaml');
.. note::
@@ -237,8 +258,8 @@ into a separate config file and load it in a similar way::
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
- $containerBuilder = new ContainerBuilder();
- $loader = new PhpFileLoader($containerBuilder, new FileLocator(__DIR__));
+ $container = new ContainerBuilder();
+ $loader = new PhpFileLoader($container, new FileLocator(__DIR__));
$loader->load('services.php');
You can now set up the ``newsletter_manager`` and ``mailer`` services using
@@ -259,15 +280,16 @@ config files:
newsletter_manager:
class: NewsletterManager
calls:
- - setMailer: ['@mailer']
+ - [setMailer, ['@mailer']]
.. code-block:: xml
-
+ xsi:schemaLocation="http://symfony.com/schema/dic/services
+ https://symfony.com/schema/dic/services/services-1.0.xsd"
+ >
sendmail
@@ -290,21 +312,22 @@ config files:
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
- return function(ContainerConfigurator $configurator) {
- $configurator->parameters()
+ return static function (ContainerConfigurator $container): void {
+ $container->parameters()
// ...
->set('mailer.transport', 'sendmail')
;
- $services = $configurator->services();
+ $services = $container->services();
+ $services->set('mailer', 'Mailer')
+ ->args(['%mailer.transport%'])
+ ;
$services->set('mailer', 'Mailer')
- // the param() method was introduced in Symfony 5.2.
->args([param('mailer.transport')])
;
$services->set('newsletter_manager', 'NewsletterManager')
- // In versions earlier to Symfony 5.1 the service() function was called ref()
->call('setMailer', [service('mailer')])
;
};
diff --git a/components/dependency_injection/_imports-parameters-note.rst.inc b/components/dependency_injection/_imports-parameters-note.rst.inc
index 92868df1985..1389ca78fe3 100644
--- a/components/dependency_injection/_imports-parameters-note.rst.inc
+++ b/components/dependency_injection/_imports-parameters-note.rst.inc
@@ -2,7 +2,7 @@
Due to the way in which parameters are resolved, you cannot use them
to build paths in imports dynamically. This means that something like
- the following doesn't work:
+ **the following does not work:**
.. configuration-block::
@@ -19,8 +19,8 @@
-
+ https://symfony.com/schema/dic/services/services-1.0.xsd"
+ >
@@ -29,4 +29,8 @@
.. code-block:: php
// config/services.php
- $loader->import('%kernel.project_dir%/somefile.yaml');
+ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+ return static function (ContainerConfigurator $container): void {
+ $container->import('%kernel.project_dir%/somefile.yaml');
+ };
diff --git a/components/dependency_injection/compilation.rst b/components/dependency_injection/compilation.rst
index 8f50b2b0d0c..7f991e85b72 100644
--- a/components/dependency_injection/compilation.rst
+++ b/components/dependency_injection/compilation.rst
@@ -1,6 +1,3 @@
-.. index::
- single: DependencyInjection; Compilation
-
Compiling the Container
=======================
@@ -64,7 +61,7 @@ A very simple extension may just load configuration files into the container::
class AcmeDemoExtension implements ExtensionInterface
{
- public function load(array $configs, ContainerBuilder $container)
+ public function load(array $configs, ContainerBuilder $container): void
{
$loader = new XmlFileLoader(
$container,
@@ -93,7 +90,7 @@ The Extension must specify a ``getAlias()`` method to implement the interface::
{
// ...
- public function getAlias()
+ public function getAlias(): string
{
return 'acme_demo';
}
@@ -117,14 +114,14 @@ are loaded::
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
- $containerBuilder = new ContainerBuilder();
- $containerBuilder->registerExtension(new AcmeDemoExtension);
+ $container = new ContainerBuilder();
+ $container->registerExtension(new AcmeDemoExtension);
- $loader = new YamlFileLoader($containerBuilder, new FileLocator(__DIR__));
+ $loader = new YamlFileLoader($container, new FileLocator(__DIR__));
$loader->load('config.yaml');
// ...
- $containerBuilder->compile();
+ $container->compile();
.. note::
@@ -135,7 +132,7 @@ are loaded::
The values from those sections of the config files are passed into the first
argument of the ``load()`` method of the extension::
- public function load(array $configs, ContainerBuilder $container)
+ public function load(array $configs, ContainerBuilder $container): void
{
$foo = $configs[0]['foo']; //fooValue
$bar = $configs[0]['bar']; //barValue
@@ -153,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::
@@ -161,7 +158,7 @@ you could access the config value this way::
use Symfony\Component\Config\Definition\Processor;
// ...
- public function load(array $configs, ContainerBuilder $container)
+ public function load(array $configs, ContainerBuilder $container): void
{
$configuration = new Configuration();
$processor = new Processor();
@@ -178,12 +175,12 @@ namespace so that the relevant parts of an XML config file are passed to
the extension. The other to specify the base path to XSD files to validate
the XML configuration::
- public function getXsdValidationBasePath()
+ public function getXsdValidationBasePath(): string
{
return __DIR__.'/../Resources/config/';
}
- public function getNamespace()
+ public function getNamespace(): string
{
return 'http://www.example.com/symfony/schema/';
}
@@ -197,16 +194,19 @@ The XML version of the config would then look like this:
.. code-block:: xml
-
+
-
-
+ xmlns:acme-demo="http://www.example.com/schema/dic/acme_demo"
+ xsi:schemaLocation="http://symfony.com/schema/dic/services
+ https://symfony.com/schema/dic/services/services-1.0.xsd
+ http://www.example.com/schema/dic/acme_demo
+ https://www.example.com/schema/dic/acme_demo/acme_demo-1.0.xsd"
+ >
+
fooValue
barValue
-
+
.. note::
@@ -219,7 +219,7 @@ The processed config value can now be added as container parameters as if
it were listed in a ``parameters`` section of the config file but with the
additional benefit of merging multiple files and validation of the configuration::
- public function load(array $configs, ContainerBuilder $container)
+ public function load(array $configs, ContainerBuilder $container): void
{
$configuration = new Configuration();
$processor = new Processor();
@@ -234,7 +234,7 @@ More complex configuration requirements can be catered for in the Extension
classes. For example, you may choose to load a main service configuration
file but also load a secondary one only if a certain parameter is set::
- public function load(array $configs, ContainerBuilder $container)
+ public function load(array $configs, ContainerBuilder $container): void
{
$configuration = new Configuration();
$processor = new Processor();
@@ -251,6 +251,33 @@ file but also load a secondary one only if a certain parameter is set::
}
}
+You can also deprecate container parameters in your extension to warn users
+about not using them anymore. This helps with the migration across major versions
+of an extension.
+
+Deprecation is only possible when using PHP to configure the extension, not when
+using XML or YAML. Use the ``ContainerBuilder::deprecateParameter()`` method to
+provide the deprecation details::
+
+ public function load(array $configs, ContainerBuilder $containerBuilder)
+ {
+ // ...
+
+ $containerBuilder->setParameter('acme_demo.database_user', $configs['db_user']);
+
+ $containerBuilder->deprecateParameter(
+ 'acme_demo.database_user',
+ 'acme/database-package',
+ '1.3',
+ // optionally you can set a custom deprecation message
+ '"acme_demo.database_user" is deprecated, you should configure database credentials with the "acme_demo.database_dsn" parameter instead.'
+ );
+ }
+
+The parameter being deprecated must be set before being declared as deprecated.
+Otherwise a :class:`Symfony\\Component\\DependencyInjection\\Exception\\ParameterNotFoundException`
+exception will be thrown.
+
.. note::
Just registering an extension with the container is not enough to get
@@ -263,11 +290,11 @@ file but also load a secondary one only if a certain parameter is set::
use Symfony\Component\DependencyInjection\ContainerBuilder;
- $containerBuilder = new ContainerBuilder();
+ $container = new ContainerBuilder();
$extension = new AcmeDemoExtension();
- $containerBuilder->registerExtension($extension);
- $containerBuilder->loadFromExtension($extension->getAlias());
- $containerBuilder->compile();
+ $container->registerExtension($extension);
+ $container->loadFromExtension($extension->getAlias());
+ $container->compile();
.. note::
@@ -292,7 +319,7 @@ method is called by implementing
{
// ...
- public function prepend(ContainerBuilder $container)
+ public function prepend(ContainerBuilder $container): void
{
// ...
@@ -323,7 +350,7 @@ compilation::
class AcmeDemoExtension implements ExtensionInterface, CompilerPassInterface
{
- public function process(ContainerBuilder $container)
+ public function process(ContainerBuilder $container): void
{
// ... do something during the compilation
}
@@ -358,8 +385,8 @@ methods described in :doc:`/service_container/definitions`.
method call if some required service is not available.
A common use-case of compiler passes is to search for all service definitions
-that have a certain tag in order to process dynamically plug each into some
-other service. See the section on :ref:`service tags `
+that have a certain tag, in order to dynamically plug each one into other services.
+See the section on :ref:`service tags `
for an example.
.. _components-di-separate-compiler-passes:
@@ -377,7 +404,7 @@ class implementing the ``CompilerPassInterface``::
class CustomPass implements CompilerPassInterface
{
- public function process(ContainerBuilder $container)
+ public function process(ContainerBuilder $container): void
{
// ... do something during the compilation
}
@@ -387,8 +414,8 @@ You then need to register your custom pass with the container::
use Symfony\Component\DependencyInjection\ContainerBuilder;
- $containerBuilder = new ContainerBuilder();
- $containerBuilder->addCompilerPass(new CustomPass());
+ $container = new ContainerBuilder();
+ $container->addCompilerPass(new CustomPass());
.. note::
@@ -418,7 +445,7 @@ For example, to run your custom pass after the default removal passes have
been run, use::
// ...
- $containerBuilder->addCompilerPass(
+ $container->addCompilerPass(
new CustomPass(),
PassConfig::TYPE_AFTER_REMOVING
);
@@ -460,14 +487,22 @@ serves at dumping the compiled container::
require_once $file;
$container = new ProjectServiceContainer();
} else {
- $containerBuilder = new ContainerBuilder();
+ $container = new ContainerBuilder();
// ...
- $containerBuilder->compile();
+ $container->compile();
- $dumper = new PhpDumper($containerBuilder);
+ $dumper = new PhpDumper($container);
file_put_contents($file, $dumper->dump());
}
+.. tip::
+
+ The ``file_put_contents()`` function is not atomic. That could cause issues
+ in a production environment with multiple concurrent requests. Instead, use
+ the :ref:`dumpFile() method ` from Symfony Filesystem
+ component or other methods provided by Symfony (e.g. ``$containerConfigCache->write()``)
+ which are atomic.
+
``ProjectServiceContainer`` is the default name given to the dumped container
class. However, you can change this with the ``class`` option when you
dump it::
@@ -479,11 +514,11 @@ dump it::
require_once $file;
$container = new MyCachedContainer();
} else {
- $containerBuilder = new ContainerBuilder();
+ $container = new ContainerBuilder();
// ...
- $containerBuilder->compile();
+ $container->compile();
- $dumper = new PhpDumper($containerBuilder);
+ $dumper = new PhpDumper($container);
file_put_contents(
$file,
$dumper->dump(['class' => 'MyCachedContainer'])
@@ -511,12 +546,12 @@ application::
require_once $file;
$container = new MyCachedContainer();
} else {
- $containerBuilder = new ContainerBuilder();
+ $container = new ContainerBuilder();
// ...
- $containerBuilder->compile();
+ $container->compile();
if (!$isDebug) {
- $dumper = new PhpDumper($containerBuilder);
+ $dumper = new PhpDumper($container);
file_put_contents(
$file,
$dumper->dump(['class' => 'MyCachedContainer'])
@@ -546,14 +581,14 @@ for these resources and use them as metadata for the cache::
$containerConfigCache = new ConfigCache($file, $isDebug);
if (!$containerConfigCache->isFresh()) {
- $containerBuilder = new ContainerBuilder();
+ $container = new ContainerBuilder();
// ...
- $containerBuilder->compile();
+ $container->compile();
- $dumper = new PhpDumper($containerBuilder);
+ $dumper = new PhpDumper($container);
$containerConfigCache->write(
$dumper->dump(['class' => 'MyCachedContainer']),
- $containerBuilder->getResources()
+ $container->getResources()
);
}
@@ -564,8 +599,8 @@ Now the cached dumped container is used regardless of whether debug mode
is on or not. The difference is that the ``ConfigCache`` is set to debug
mode with its second constructor argument. When the cache is not in debug
mode the cached container will always be used if it exists. In debug mode,
-an additional metadata file is written with the timestamps of all the resource
-files. These are then checked to see if the files have changed, if they
+an additional metadata file is written with all the involved resource
+files. These are then checked to see if their timestamps have changed, if they
have the cache will be considered stale.
.. note::
diff --git a/components/dependency_injection/workflow.rst b/components/dependency_injection/workflow.rst
index 750420f4d47..777b41dfabb 100644
--- a/components/dependency_injection/workflow.rst
+++ b/components/dependency_injection/workflow.rst
@@ -1,6 +1,3 @@
-.. index::
- single: DependencyInjection; Workflow
-
Container Building Workflow
===========================
@@ -21,11 +18,11 @@ Working with a Cached Container
-------------------------------
Before building it, the kernel checks to see if a cached version of the
-container exists. The HttpKernel has a debug setting and if this is false,
+container exists. The kernel has a debug setting and if this is false,
the cached version is used if it exists. If debug is true then the kernel
:doc:`checks to see if configuration is fresh `
and if it is, the cached version of the container is used. If not then the
-container is built from the application-level configuration and the bundles's
+container is built from the application-level configuration and the bundles'
extension configuration.
Read :ref:`Dumping the Configuration for Performance `
diff --git a/components/dom_crawler.rst b/components/dom_crawler.rst
index 55b5d8bc23f..630d301302a 100644
--- a/components/dom_crawler.rst
+++ b/components/dom_crawler.rst
@@ -1,7 +1,3 @@
-.. index::
- single: DomCrawler
- single: Components; DomCrawler
-
The DomCrawler Component
========================
@@ -70,13 +66,6 @@ tree.
isn't meant to dump content, you can see the "fixed" version of your HTML
by :ref:`dumping it `.
-.. note::
-
- If you need better support for HTML5 contents or want to get rid of the
- inconsistencies of PHP's DOM extension, install the `html5-php library`_.
- The DomCrawler component will use it automatically when the content has
- an HTML5 doctype.
-
Node Filtering
~~~~~~~~~~~~~~
@@ -100,9 +89,9 @@ An anonymous function can be used to filter with more complex criteria::
$crawler = $crawler
->filter('body > p')
- ->reduce(function (Crawler $node, $i) {
+ ->reduce(function (Crawler $node, $i): bool {
// filters every other node
- return ($i % 2) == 0;
+ return ($i % 2) === 0;
});
To remove a node, the anonymous function must return ``false``.
@@ -122,7 +111,7 @@ Consider the XML below:
.. code-block:: xml
-
+
filter('body > p')->nextAll();
$crawler->filter('body > p')->previousAll();
-Get all the child or parent nodes::
+Get all the child or ancestor nodes::
$crawler->filter('body')->children();
- $crawler->filter('body > p')->parents();
+ $crawler->filter('body > p')->ancestors();
Get all the direct child nodes matching a CSS selector::
@@ -221,15 +210,36 @@ Access the value of the first node of the current selection::
// avoid the exception passing an argument that text() returns when node does not exist
$message = $crawler->filterXPath('//body/p')->text('Default text content');
- // by default, text() trims white spaces, including the internal ones
+ // by default, text() trims whitespace characters, including the internal ones
// (e.g. " foo\n bar baz \n " is returned as "foo bar baz")
// pass FALSE as the second argument to return the original text unchanged
$crawler->filterXPath('//body/p')->text('Default text content', false);
+ // innerText() is similar to text() but returns only text that is a direct
+ // descendant of the current node, excluding text from child nodes
+ $text = $crawler->filterXPath('//body/p')->innerText();
+ // if content is Foo Bar
or Bar Foo
+ // innerText() returns 'Foo' in both cases; and text() returns 'Foo Bar' and 'Bar Foo' respectively
+
+ // if there are multiple text nodes, between other child nodes, like
+ // Foo Bar Baz
+ // innerText() returns only the first text node 'Foo'
+
+ // like text(), innerText() also trims whitespace characters by default,
+ // but you can get the unchanged text by passing FALSE as argument
+ $text = $crawler->filterXPath('//body/p')->innerText(false);
+
Access the attribute value of the first node of the current selection::
$class = $crawler->filterXPath('//body/p')->attr('class');
+.. tip::
+
+ You can define the default value to use if the node or attribute is empty
+ by using the second argument of the ``attr()`` method::
+
+ $class = $crawler->filterXPath('//body/p')->attr('class', 'my-default-class');
+
Extract attribute and/or node values from the list of nodes::
$attributes = $crawler
@@ -247,7 +257,7 @@ Call an anonymous function on each node of the list::
use Symfony\Component\DomCrawler\Crawler;
// ...
- $nodeValues = $crawler->filter('p')->each(function (Crawler $node, $i) {
+ $nodeValues = $crawler->filter('p')->each(function (Crawler $node, $i): string {
return $node->text();
});
@@ -257,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) {
+ $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');
@@ -269,7 +279,9 @@ context of the crawler::
Adding the Content
~~~~~~~~~~~~~~~~~~
-The crawler supports multiple ways of adding the content::
+The crawler supports multiple ways of adding the content, but they are mutually
+exclusive, so you can only use one of them to add content (e.g. if you pass the
+content to the ``Crawler`` constructor, you can't call ``addContent()`` later)::
$crawler = new Crawler(' ');
@@ -518,15 +530,17 @@ You can virtually set and get values on the form::
// where "registration" is its own array
$values = $form->getPhpValues();
-To work with multi-dimensional fields::
+To work with multi-dimensional fields:
+
+.. code-block:: html
Pass an array of values::
@@ -625,11 +639,7 @@ the whole form or specific field(s)::
Resolving a URI
~~~~~~~~~~~~~~~
-.. versionadded:: 5.1
-
- The :class:`Symfony\\Component\\DomCrawler\\UriResolver` helper class was added in Symfony 5.1.
-
-The :class:`Symfony\\Component\\DomCrawler\\UriResolver` class takes an URI
+The :class:`Symfony\\Component\\DomCrawler\\UriResolver` class takes a URI
(relative, absolute, fragment, etc.) and turns it into an absolute URI against
another given base URI::
@@ -639,10 +649,23 @@ another given base URI::
UriResolver::resolve('?a=b', 'http://localhost/bar#foo'); // http://localhost/bar?a=b
UriResolver::resolve('../../', 'http://localhost/'); // http://localhost/
+Using a HTML5 Parser
+~~~~~~~~~~~~~~~~~~~~
+
+If you need the :class:`Symfony\\Component\\DomCrawler\\Crawler` to use an HTML5
+parser, set its ``useHtml5Parser`` constructor argument to ``true``::
+
+ use Symfony\Component\DomCrawler\Crawler;
+
+ $crawler = new Crawler(null, $uri, useHtml5Parser: true);
+
+By doing so, the crawler will use the HTML5 parser provided by the `masterminds/html5`_
+library to parse the documents.
+
Learn more
----------
* :doc:`/testing`
* :doc:`/components/css_selector`
-.. _`html5-php library`: https://github.com/Masterminds/html5-php
+.. _`masterminds/html5`: https://packagist.org/packages/masterminds/html5
diff --git a/components/event_dispatcher.rst b/components/event_dispatcher.rst
index 57b73c7b7f3..8cd676dd5fe 100644
--- a/components/event_dispatcher.rst
+++ b/components/event_dispatcher.rst
@@ -1,7 +1,3 @@
-.. index::
- single: EventDispatcher
- single: Components; EventDispatcher
-
The EventDispatcher Component
=============================
@@ -32,7 +28,7 @@ truly extensible.
Take an example from :doc:`the HttpKernel component `.
Once a ``Response`` object has been created, it may be useful to allow other
elements in the system to modify it (e.g. add some cache headers) before
-it's actually used. To make this possible, the Symfony kernel throws an
+it's actually used. To make this possible, the Symfony kernel dispatches an
event - ``kernel.response``. Here's how it works:
* A *listener* (PHP object) tells a central *dispatcher* object that it
@@ -46,9 +42,6 @@ event - ``kernel.response``. Here's how it works:
``kernel.response`` event, allowing each of them to make modifications
to the ``Response`` object.
-.. index::
- single: EventDispatcher; Events
-
Installation
------------
@@ -76,23 +69,6 @@ An :class:`Symfony\\Contracts\\EventDispatcher\\Event` instance is also
created and passed to all of the listeners. As you'll see later, the ``Event``
object itself often contains data about the event being dispatched.
-.. index::
- pair: EventDispatcher; Naming conventions
-
-Naming Conventions
-..................
-
-The unique event name can be any string, but optionally follows a few
-naming conventions:
-
-* Use only lowercase letters, numbers, dots (``.``) and underscores (``_``);
-* Prefix names with a namespace followed by a dot (e.g. ``order.*``, ``user.*``);
-* End names with a verb that indicates what action has been taken (e.g.
- ``order.placed``).
-
-.. index::
- single: EventDispatcher; Event subclasses
-
Event Names and Event Objects
.............................
@@ -126,9 +102,6 @@ listeners registered with that event::
$dispatcher = new EventDispatcher();
-.. index::
- single: EventDispatcher; Listeners
-
Connecting Listeners
~~~~~~~~~~~~~~~~~~~~
@@ -163,7 +136,7 @@ The ``addListener()`` method takes up to three arguments:
use Symfony\Contracts\EventDispatcher\Event;
- $dispatcher->addListener('acme.foo.action', function (Event $event) {
+ $dispatcher->addListener('acme.foo.action', function (Event $event): void {
// will be executed when the acme.foo.action event is dispatched
});
@@ -178,7 +151,7 @@ the ``Event`` object as the single argument::
{
// ...
- public function onFooAction(Event $event)
+ public function onFooAction(Event $event): void
{
// ... do something
}
@@ -198,26 +171,25 @@ determine which instance is passed.
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
- use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
use Symfony\Component\EventDispatcher\EventDispatcher;
- $containerBuilder = new ContainerBuilder(new ParameterBag());
+ $container = new ContainerBuilder(new ParameterBag());
// register the compiler pass that handles the 'kernel.event_listener'
// and 'kernel.event_subscriber' service tags
- $containerBuilder->addCompilerPass(new RegisterListenersPass());
+ $container->addCompilerPass(new RegisterListenersPass());
- $containerBuilder->register('event_dispatcher', EventDispatcher::class);
+ $container->register('event_dispatcher', EventDispatcher::class);
// registers an event listener
- $containerBuilder->register('listener_service_id', \AcmeListener::class)
+ $container->register('listener_service_id', \AcmeListener::class)
->addTag('kernel.event_listener', [
'event' => 'acme.foo.action',
'method' => 'onFooAction',
]);
// registers an event subscriber
- $containerBuilder->register('subscriber_service_id', \AcmeSubscriber::class)
+ $container->register('subscriber_service_id', \AcmeSubscriber::class)
->addTag('kernel.event_subscriber');
``RegisterListenersPass`` resolves aliased class names which for instance
@@ -229,21 +201,20 @@ determine which instance is passed.
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
- use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\DependencyInjection\AddEventAliasesPass;
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
use Symfony\Component\EventDispatcher\EventDispatcher;
- $containerBuilder = new ContainerBuilder(new ParameterBag());
- $containerBuilder->addCompilerPass(new AddEventAliasesPass([
+ $container = new ContainerBuilder(new ParameterBag());
+ $container->addCompilerPass(new AddEventAliasesPass([
\AcmeFooActionEvent::class => 'acme.foo.action',
]));
- $containerBuilder->addCompilerPass(new RegisterListenersPass(), PassConfig::TYPE_BEFORE_REMOVING)
+ $container->addCompilerPass(new RegisterListenersPass(), PassConfig::TYPE_BEFORE_REMOVING);
- $containerBuilder->register('event_dispatcher', EventDispatcher::class);
+ $container->register('event_dispatcher', EventDispatcher::class);
// registers an event listener
- $containerBuilder->register('listener_service_id', \AcmeListener::class)
+ $container->register('listener_service_id', \AcmeListener::class)
->addTag('kernel.event_listener', [
// will be translated to 'acme.foo.action' by RegisterListenersPass.
'event' => \AcmeFooActionEvent::class,
@@ -254,19 +225,14 @@ determine which instance is passed.
Note that ``AddEventAliasesPass`` has to be processed before ``RegisterListenersPass``.
- By default, the listeners pass assumes that the event dispatcher's service
+ The listeners pass assumes that the event dispatcher's service
id is ``event_dispatcher``, that event listeners are tagged with the
``kernel.event_listener`` tag, that event subscribers are tagged
with the ``kernel.event_subscriber`` tag and that the alias mapping is
- stored as parameter ``event_dispatcher.event_aliases``. You can change these
- default values by passing custom values to the constructors of
- ``RegisterListenersPass`` and ``AddEventAliasesPass``.
+ stored as parameter ``event_dispatcher.event_aliases``.
.. _event_dispatcher-closures-as-listeners:
-.. index::
- single: EventDispatcher; Creating and dispatching an event
-
Creating and Dispatching an Event
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -280,7 +246,7 @@ system flexible and decoupled.
Creating an Event Class
.......................
-Suppose you want to create a new event - ``order.placed`` - that is dispatched
+Suppose you want to create a new event that is dispatched
each time a customer orders a product with your application. When dispatching
this event, you'll pass a custom event instance that has access to the placed
order. Start by creating this custom event class and documenting it::
@@ -291,21 +257,14 @@ order. Start by creating this custom event class and documenting it::
use Symfony\Contracts\EventDispatcher\Event;
/**
- * The order.placed event is dispatched each time an order is created
- * in the system.
+ * This event is dispatched each time an order
+ * is placed in the system.
*/
- class OrderPlacedEvent extends Event
+ final class OrderPlacedEvent extends Event
{
- public const NAME = 'order.placed';
-
- protected $order;
+ public function __construct(private Order $order) {}
- public function __construct(Order $order)
- {
- $this->order = $order;
- }
-
- public function getOrder()
+ public function getOrder(): Order
{
return $this->order;
}
@@ -313,15 +272,6 @@ order. Start by creating this custom event class and documenting it::
Each listener now has access to the order via the ``getOrder()`` method.
-.. note::
-
- If you don't need to pass any additional data to the event listeners, you
- can also use the default
- :class:`Symfony\\Contracts\\EventDispatcher\\Event` class. In such case,
- you can document the event and its name in a generic ``StoreEvents`` class,
- similar to the :class:`Symfony\\Component\\HttpKernel\\KernelEvents`
- class.
-
Dispatch the Event
..................
@@ -339,14 +289,37 @@ of the event to dispatch::
// creates the OrderPlacedEvent and dispatches it
$event = new OrderPlacedEvent($order);
- $dispatcher->dispatch($event, OrderPlacedEvent::NAME);
+ $dispatcher->dispatch($event);
Notice that the special ``OrderPlacedEvent`` object is created and passed to
-the ``dispatch()`` method. Now, any listener to the ``order.placed``
+the ``dispatch()`` method. Now, any listener to the ``OrderPlacedEvent::class``
event will receive the ``OrderPlacedEvent``.
-.. index::
- single: EventDispatcher; Event subscribers
+.. note::
+
+ If you don't need to pass any additional data to the event listeners, you
+ can also use the default
+ :class:`Symfony\\Contracts\\EventDispatcher\\Event` class. In such case,
+ you can document the event and its name in a generic ``StoreEvents`` class,
+ similar to the :class:`Symfony\\Component\\HttpKernel\\KernelEvents`
+ class::
+
+ namespace App\Event;
+
+ class StoreEvents {
+
+ /**
+ * @Event("Symfony\Contracts\EventDispatcher\Event")
+ */
+ public const ORDER_PLACED = 'order.placed';
+ }
+
+ And use the :class:`Symfony\\Contracts\\EventDispatcher\\Event` class to
+ dispatch the event::
+
+ use Symfony\Contracts\EventDispatcher\Event;
+
+ $this->eventDispatcher->dispatch(new Event(), StoreEvents::ORDER_PLACED);
.. _event_dispatcher-using-event-subscribers:
@@ -364,7 +337,7 @@ events it should subscribe to. It implements the
interface, which requires a single static method called
:method:`Symfony\\Component\\EventDispatcher\\EventSubscriberInterface::getSubscribedEvents`.
Take the following example of a subscriber that subscribes to the
-``kernel.response`` and ``order.placed`` events::
+``kernel.response`` and ``OrderPlacedEvent::class`` events::
namespace Acme\Store\Event;
@@ -375,29 +348,30 @@ Take the following example of a subscriber that subscribes to the
class StoreSubscriber implements EventSubscriberInterface
{
- public static function getSubscribedEvents()
+ public static function getSubscribedEvents(): array
{
return [
KernelEvents::RESPONSE => [
['onKernelResponsePre', 10],
['onKernelResponsePost', -10],
],
- OrderPlacedEvent::NAME => 'onStoreOrder',
+ OrderPlacedEvent::class => 'onPlacedOrder',
];
}
- public function onKernelResponsePre(ResponseEvent $event)
+ public function onKernelResponsePre(ResponseEvent $event): void
{
// ...
}
- public function onKernelResponsePost(ResponseEvent $event)
+ public function onKernelResponsePost(ResponseEvent $event): void
{
// ...
}
- public function onStoreOrder(OrderPlacedEvent $event)
+ public function onPlacedOrder(OrderPlacedEvent $event): void
{
+ $order = $event->getOrder();
// ...
}
}
@@ -427,9 +401,6 @@ example, when the ``kernel.response`` event is triggered, the methods
``onKernelResponsePre()`` and ``onKernelResponsePost()`` are called in that
order.
-.. index::
- single: EventDispatcher; Stopping event flow
-
.. _event_dispatcher-event-propagation:
Stopping Event Flow/Propagation
@@ -444,14 +415,14 @@ inside a listener via the
use Acme\Store\Event\OrderPlacedEvent;
- public function onStoreOrder(OrderPlacedEvent $event)
+ public function onPlacedOrder(OrderPlacedEvent $event): void
{
// ...
$event->stopPropagation();
}
-Now, any listeners to ``order.placed`` that have not yet been called will
+Now, any listeners to ``OrderPlacedEvent::class`` that have not yet been called will
*not* be called.
It is possible to detect if an event was stopped by using the
@@ -464,9 +435,6 @@ method which returns a boolean value::
// ...
}
-.. index::
- single: EventDispatcher; EventDispatcher aware events and listeners
-
.. _event_dispatcher-dispatcher-aware-events:
EventDispatcher Aware Events and Listeners
@@ -477,9 +445,6 @@ name and a reference to itself to the listeners. This can lead to some advanced
applications of the ``EventDispatcher`` including dispatching other events inside
listeners, chaining events or even lazy loading listeners into the dispatcher object.
-.. index::
- single: EventDispatcher; Event name introspection
-
.. _event_dispatcher-event-name-introspection:
Event Name Introspection
@@ -491,9 +456,9 @@ is dispatched, are passed as arguments to the listener::
use Symfony\Contracts\EventDispatcher\Event;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
- class Foo
+ class MyListener
{
- public function myEventListener(Event $event, $eventName, EventDispatcherInterface $dispatcher)
+ public function myEventListener(Event $event, string $eventName, EventDispatcherInterface $dispatcher): void
{
// ... do something with the event name
}
@@ -511,17 +476,11 @@ with some other dispatchers:
Learn More
----------
-.. toctree::
- :maxdepth: 1
- :glob:
-
- /components/event_dispatcher/*
- /event_dispatcher/*
-
+* :doc:`/components/event_dispatcher/generic_event`
* :ref:`The kernel.event_listener tag `
* :ref:`The kernel.event_subscriber tag `
.. _Mediator: https://en.wikipedia.org/wiki/Mediator_pattern
.. _Observer: https://en.wikipedia.org/wiki/Observer_pattern
.. _Closures: https://www.php.net/manual/en/functions.anonymous.php
-.. _PHP callable: https://www.php.net/manual/en/language.pseudo-types.php#language.types.callback
+.. _PHP callable: https://www.php.net/manual/en/language.types.callable.php
diff --git a/components/event_dispatcher/container_aware_dispatcher.rst b/components/event_dispatcher/container_aware_dispatcher.rst
deleted file mode 100644
index 659a94cee7a..00000000000
--- a/components/event_dispatcher/container_aware_dispatcher.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-.. index::
- single: EventDispatcher; Service container aware
-
-The Container Aware Event Dispatcher
-====================================
-
-.. caution::
-
- The ``ContainerAwareEventDispatcher`` was removed in Symfony 4.0. Use
- ``EventDispatcher`` with closure-proxy injection instead.
diff --git a/components/event_dispatcher/generic_event.rst b/components/event_dispatcher/generic_event.rst
index 1f9be477151..41d0a9d66a4 100644
--- a/components/event_dispatcher/generic_event.rst
+++ b/components/event_dispatcher/generic_event.rst
@@ -1,6 +1,3 @@
-.. index::
- single: EventDispatcher
-
The Generic Event Object
========================
@@ -57,7 +54,7 @@ Passing a subject::
class FooListener
{
- public function handler(GenericEvent $event)
+ public function handler(GenericEvent $event): void
{
if ($event->getSubject() instanceof Foo) {
// ...
@@ -78,9 +75,9 @@ access the event arguments::
class FooListener
{
- public function handler(GenericEvent $event)
+ public function handler(GenericEvent $event): void
{
- if (isset($event['type']) && $event['type'] === 'foo') {
+ if (isset($event['type']) && 'foo' === $event['type']) {
// ... do something
}
@@ -97,9 +94,8 @@ Filtering data::
class FooListener
{
- public function filter(GenericEvent $event)
+ public function filter(GenericEvent $event): void
{
$event['data'] = strtolower($event['data']);
}
}
-
diff --git a/components/event_dispatcher/immutable_dispatcher.rst b/components/event_dispatcher/immutable_dispatcher.rst
index 25940825065..a6a98c47f37 100644
--- a/components/event_dispatcher/immutable_dispatcher.rst
+++ b/components/event_dispatcher/immutable_dispatcher.rst
@@ -1,6 +1,3 @@
-.. index::
- single: EventDispatcher; Immutable
-
The Immutable Event Dispatcher
==============================
@@ -16,9 +13,10 @@ To use it, first create a normal ``EventDispatcher`` dispatcher and register
some listeners or subscribers::
use Symfony\Component\EventDispatcher\EventDispatcher;
+ use Symfony\Contracts\EventDispatcher\Event;
$dispatcher = new EventDispatcher();
- $dispatcher->addListener('foo.action', function ($event) {
+ $dispatcher->addListener('foo.action', function (Event $event): void {
// ...
});
diff --git a/components/event_dispatcher/traceable_dispatcher.rst b/components/event_dispatcher/traceable_dispatcher.rst
index 33a98a2336b..7b3819e3a48 100644
--- a/components/event_dispatcher/traceable_dispatcher.rst
+++ b/components/event_dispatcher/traceable_dispatcher.rst
@@ -1,7 +1,3 @@
-.. index::
- single: EventDispatcher; Debug
- single: EventDispatcher; Traceable
-
The Traceable Event Dispatcher
==============================
diff --git a/components/expression_language.rst b/components/expression_language.rst
index edd3587aa6d..b0dd10b0f42 100644
--- a/components/expression_language.rst
+++ b/components/expression_language.rst
@@ -1,7 +1,3 @@
-.. index::
- single: Expressions
- Single: Components; Expression Language
-
The ExpressionLanguage Component
================================
@@ -18,12 +14,14 @@ Installation
.. include:: /components/require_autoload.rst.inc
-How can the Expression Engine Help Me?
---------------------------------------
+.. _how-can-the-expression-engine-help-me:
+
+How can the Expression Language Help Me?
+----------------------------------------
The purpose of the component is to allow users to use expressions inside
-configuration for more complex logic. For some examples, the Symfony Framework
-uses expressions in security, for validation rules and in route matching.
+configuration for more complex logic. For example, the Symfony Framework uses
+expressions in security, for validation rules and in route matching.
Besides using the component in the framework itself, the ExpressionLanguage
component is a perfect candidate for the foundation of a *business rule engine*.
@@ -43,9 +41,10 @@ way without using PHP and without introducing security problems:
# Send an alert when
product.stock < 15
-Expressions can be seen as a very restricted PHP sandbox and are immune to
-external injections as you must explicitly declare which variables are available
-in an expression.
+Expressions can be seen as a very restricted PHP sandbox and are less vulnerable
+to external injections because you must explicitly declare which variables are
+available in an expression (but you should still sanitize any data given by end
+users and passed to expressions).
Usage
-----
@@ -73,11 +72,66 @@ The main class of the component is
var_dump($expressionLanguage->compile('1 + 2')); // displays (1 + 2)
-Expression Syntax
------------------
+.. tip::
+
+ See :doc:`/reference/formats/expression_language` to learn the syntax of
+ the ExpressionLanguage component.
+
+Null Coalescing Operator
+........................
+
+.. note::
+
+ This content has been moved to the :ref:`null coalescing operator `
+ section of ExpressionLanguage syntax reference page.
+
+Parsing and Linting Expressions
+...............................
+
+The ExpressionLanguage component provides a way to parse and lint expressions.
+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, 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', []));
+ // displays the AST nodes of the expression which can be
+ // inspected and manipulated
+
+ $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:
+
+* ``IGNORE_UNKNOWN_VARIABLES``: don't throw an exception if a variable is not
+ defined in the expression;
+* ``IGNORE_UNKNOWN_FUNCTIONS``: don't throw an exception if a function is not
+ defined in the expression.
+
+This is how you can use these flags::
+
+ use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+ use Symfony\Component\ExpressionLanguage\Parser;
+
+ $expressionLanguage = new ExpressionLanguage();
+
+ // 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
-See :doc:`/components/expression_language/syntax` to learn the syntax of the
-ExpressionLanguage component.
+ The support for flags in the ``parse()`` and ``lint()`` methods
+ was introduced in Symfony 7.1.
Passing in Variables
--------------------
@@ -91,7 +145,7 @@ PHP type (including objects)::
class Apple
{
- public $variety;
+ public string $variety;
}
$apple = new Apple();
@@ -104,35 +158,262 @@ PHP type (including objects)::
]
)); // displays "Honeycrisp"
-For more information, see the :doc:`/components/expression_language/syntax`
-entry, especially :ref:`component-expression-objects` and :ref:`component-expression-arrays`.
+When using this component inside a Symfony application, certain objects and
+variables are automatically injected by Symfony so you can use them in your
+expressions (e.g. the request, the current user, etc.):
-.. caution::
+* :doc:`Variables available in security expressions `;
+* :doc:`Variables available in service container expressions `;
+* :ref:`Variables available in routing expressions `.
- 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
-------
-The component provides some different caching strategies, read more about them
-in :doc:`/components/expression_language/caching`.
+The ExpressionLanguage component provides a
+:method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::compile`
+method to be able to cache the expressions in plain PHP. But internally, the
+component also caches the parsed expressions, so duplicated expressions can be
+compiled/evaluated quicker.
+
+The Workflow
+~~~~~~~~~~~~
+
+Both :method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::evaluate`
+and ``compile()`` need to do some things before each can provide the return
+values. For ``evaluate()``, this overhead is even bigger.
+
+Both methods need to tokenize and parse the expression. This is done by the
+:method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::parse`
+method. It returns a :class:`Symfony\\Component\\ExpressionLanguage\\ParsedExpression`.
+Now, the ``compile()`` method just returns the string conversion of this object.
+The ``evaluate()`` method needs to loop through the "nodes" (pieces of an
+expression saved in the ``ParsedExpression``) and evaluate them on the fly.
+
+To save time, the ``ExpressionLanguage`` caches the ``ParsedExpression`` so
+it can skip the tokenization and parsing steps with duplicate expressions. The
+caching is done by a PSR-6 `CacheItemPoolInterface`_ instance (by default, it
+uses an :class:`Symfony\\Component\\Cache\\Adapter\\ArrayAdapter`). You can
+customize this by creating a custom cache pool or using one of the available
+ones and injecting this using the constructor::
+
+ use Symfony\Component\Cache\Adapter\RedisAdapter;
+ use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+
+ $cache = new RedisAdapter(...);
+ $expressionLanguage = new ExpressionLanguage($cache);
+
+.. seealso::
+
+ See the :doc:`/components/cache` documentation for more information about
+ available cache adapters.
+
+Using Parsed and Serialized Expressions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Both ``evaluate()`` and ``compile()`` can handle ``ParsedExpression`` and
+``SerializedParsedExpression``::
+
+ // ...
+
+ // the parse() method returns a ParsedExpression
+ $expression = $expressionLanguage->parse('1 + 4', []);
+
+ var_dump($expressionLanguage->evaluate($expression)); // prints 5
+
+.. code-block:: php
+
+ use Symfony\Component\ExpressionLanguage\SerializedParsedExpression;
+ // ...
+
+ $expression = new SerializedParsedExpression(
+ '1 + 4',
+ serialize($expressionLanguage->parse('1 + 4', [])->getNodes())
+ );
+
+ var_dump($expressionLanguage->evaluate($expression)); // prints 5
+
+.. _expression-language-ast:
AST Dumping and Editing
-----------------------
-The AST (*Abstract Syntax Tree*) of expressions can be dumped and manipulated
-as explained in :doc:`/components/expression_language/ast`.
+It's difficult to manipulate or inspect the expressions created with the ExpressionLanguage
+component, because the expressions are plain strings. A better approach is to
+turn those expressions into an AST. In computer science, `AST`_ (*Abstract
+Syntax Tree*) is *"a tree representation of the structure of source code written
+in a programming language"*. In Symfony, an ExpressionLanguage AST is a set of
+nodes that contain PHP classes representing the given expression.
+
+Dumping the AST
+~~~~~~~~~~~~~~~
+
+Call the :method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::getNodes`
+method after parsing any expression to get its AST::
+
+ use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+
+ $ast = (new ExpressionLanguage())
+ ->parse('1 + 2', [])
+ ->getNodes()
+ ;
+
+ // dump the AST nodes for inspection
+ var_dump($ast);
+
+ // dump the AST nodes as a string representation
+ $astAsString = $ast->dump();
+
+Manipulating the AST
+~~~~~~~~~~~~~~~~~~~~
+
+The nodes of the AST can also be dumped into a PHP array of nodes to allow
+manipulating them. Call the :method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::toArray`
+method to turn the AST into an array::
+
+ // ...
+
+ $astAsArray = (new ExpressionLanguage())
+ ->parse('1 + 2', [])
+ ->getNodes()
+ ->toArray()
+ ;
+
+.. _expression-language-extending:
+
+Extending the ExpressionLanguage
+--------------------------------
+
+The ExpressionLanguage can be extended by adding custom functions. For
+instance, in the Symfony Framework, the security has custom functions to check
+the user's role.
+
+.. note::
+
+ If you want to learn how to use functions in an expression, read
+ ":ref:`component-expression-functions`".
+
+Registering Functions
+~~~~~~~~~~~~~~~~~~~~~
+
+Functions are registered on each specific ``ExpressionLanguage`` instance.
+That means the functions can be used in any expression executed by that
+instance.
+
+To register a function, use
+:method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::register`.
+This method has 3 arguments:
+
+* **name** - The name of the function in an expression;
+* **compiler** - A function executed when compiling an expression using the
+ function;
+* **evaluator** - A function executed when the expression is evaluated.
+
+Example::
+
+ use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+
+ $expressionLanguage = new ExpressionLanguage();
+ $expressionLanguage->register('lowercase', function ($str): string {
+ return sprintf('(is_string(%1$s) ? strtolower(%1$s) : %1$s)', $str);
+ }, function ($arguments, $str): string {
+ if (!is_string($str)) {
+ return $str;
+ }
+
+ return strtolower($str);
+ });
+
+ var_dump($expressionLanguage->evaluate('lowercase("HELLO")'));
+ // this will print: hello
+
+In addition to the custom function arguments, the **evaluator** is passed an
+``arguments`` variable as its first argument, which is equal to the second
+argument of ``evaluate()`` (e.g. the "values" when evaluating an expression).
+
+.. _components-expression-language-provider:
+
+Using Expression Providers
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When you use the ``ExpressionLanguage`` class in your library, you often want
+to add custom functions. To do so, you can create a new expression provider by
+creating a class that implements
+:class:`Symfony\\Component\\ExpressionLanguage\\ExpressionFunctionProviderInterface`.
+
+This interface requires one method:
+:method:`Symfony\\Component\\ExpressionLanguage\\ExpressionFunctionProviderInterface::getFunctions`,
+which returns an array of expression functions (instances of
+:class:`Symfony\\Component\\ExpressionLanguage\\ExpressionFunction`) to
+register::
+
+ use Symfony\Component\ExpressionLanguage\ExpressionFunction;
+ use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
+
+ class StringExpressionLanguageProvider implements ExpressionFunctionProviderInterface
+ {
+ public function getFunctions(): array
+ {
+ return [
+ new ExpressionFunction('lowercase', function ($str): string {
+ return sprintf('(is_string(%1$s) ? strtolower(%1$s) : %1$s)', $str);
+ }, function ($arguments, $str): string {
+ if (!is_string($str)) {
+ return $str;
+ }
+
+ return strtolower($str);
+ }),
+ ];
+ }
+ }
+
+.. tip::
+
+ To create an expression function from a PHP function with the
+ :method:`Symfony\\Component\\ExpressionLanguage\\ExpressionFunction::fromPhp` static method::
+
+ ExpressionFunction::fromPhp('strtoupper');
+
+ Namespaced functions are supported, but they require a second argument to
+ define the name of the expression::
+
+ ExpressionFunction::fromPhp('My\strtoupper', 'my_strtoupper');
+
+You can register providers using
+:method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::registerProvider`
+or by using the second argument of the constructor::
+
+ use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+
+ // using the constructor
+ $expressionLanguage = new ExpressionLanguage(null, [
+ new StringExpressionLanguageProvider(),
+ // ...
+ ]);
+
+ // using registerProvider()
+ $expressionLanguage->registerProvider(new StringExpressionLanguageProvider());
+
+.. tip::
+
+ It is recommended to create your own ``ExpressionLanguage`` class in your
+ library. Now you can add the extension by overriding the constructor::
+
+ use Psr\Cache\CacheItemPoolInterface;
+ use Symfony\Component\ExpressionLanguage\ExpressionLanguage as BaseExpressionLanguage;
-Learn More
-----------
+ class ExpressionLanguage extends BaseExpressionLanguage
+ {
+ public function __construct(?CacheItemPoolInterface $cache = null, array $providers = [])
+ {
+ // prepends the default provider to let users override it
+ array_unshift($providers, new StringExpressionLanguageProvider());
-.. toctree::
- :maxdepth: 1
- :glob:
+ parent::__construct($cache, $providers);
+ }
+ }
- /components/expression_language/*
- /service_container/expression_language
- /reference/constraints/Expression
+.. _`AST`: https://en.wikipedia.org/wiki/Abstract_syntax_tree
+.. _`CacheItemPoolInterface`: https://github.com/php-fig/cache/blob/master/src/CacheItemPoolInterface.php
diff --git a/components/expression_language/ast.rst b/components/expression_language/ast.rst
deleted file mode 100644
index 0f15c20647a..00000000000
--- a/components/expression_language/ast.rst
+++ /dev/null
@@ -1,49 +0,0 @@
-.. index::
- single: AST; ExpressionLanguage
- single: AST; Abstract Syntax Tree
-
-Dumping and Manipulating the AST of Expressions
-===============================================
-
-Manipulating or inspecting the expressions created with the ExpressionLanguage
-component is difficult because they are plain strings. A better approach is to
-turn those expressions into an AST. In computer science, `AST`_ (*Abstract
-Syntax Tree*) is *"a tree representation of the structure of source code written
-in a programming language"*. In Symfony, a ExpressionLanguage AST is a set of
-nodes that contain PHP classes representing the given expression.
-
-Dumping the AST
----------------
-
-Call the :method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::getNodes`
-method after parsing any expression to get its AST::
-
- use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
-
- $ast = (new ExpressionLanguage())
- ->parse('1 + 2', [])
- ->getNodes()
- ;
-
- // dump the AST nodes for inspection
- var_dump($ast);
-
- // dump the AST nodes as a string representation
- $astAsString = $ast->dump();
-
-Manipulating the AST
---------------------
-
-The nodes of the AST can also be dumped into a PHP array of nodes to allow
-manipulating them. Call the :method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::toArray`
-method to turn the AST into an array::
-
- // ...
-
- $astAsArray = (new ExpressionLanguage())
- ->parse('1 + 2', [])
- ->getNodes()
- ->toArray()
- ;
-
-.. _`AST`: https://en.wikipedia.org/wiki/Abstract_syntax_tree
diff --git a/components/expression_language/caching.rst b/components/expression_language/caching.rst
deleted file mode 100644
index 770c2768ca5..00000000000
--- a/components/expression_language/caching.rst
+++ /dev/null
@@ -1,70 +0,0 @@
-.. index::
- single: Caching; ExpressionLanguage
-
-Caching Expressions Using Parser Caches
-=======================================
-
-The ExpressionLanguage component already provides a
-:method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::compile`
-method to be able to cache the expressions in plain PHP. But internally, the
-component also caches the parsed expressions, so duplicated expressions can be
-compiled/evaluated quicker.
-
-The Workflow
-------------
-
-Both :method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::evaluate`
-and ``compile()`` need to do some things before each can provide the return
-values. For ``evaluate()``, this overhead is even bigger.
-
-Both methods need to tokenize and parse the expression. This is done by the
-:method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::parse`
-method. It returns a :class:`Symfony\\Component\\ExpressionLanguage\\ParsedExpression`.
-Now, the ``compile()`` method just returns the string conversion of this object.
-The ``evaluate()`` method needs to loop through the "nodes" (pieces of an
-expression saved in the ``ParsedExpression``) and evaluate them on the fly.
-
-To save time, the ``ExpressionLanguage`` caches the ``ParsedExpression`` so
-it can skip the tokenize and parse steps with duplicate expressions. The
-caching is done by a PSR-6 `CacheItemPoolInterface`_ instance (by default, it
-uses an :class:`Symfony\\Component\\Cache\\Adapter\\ArrayAdapter`). You can
-customize this by creating a custom cache pool or using one of the available
-ones and injecting this using the constructor::
-
- use Symfony\Component\Cache\Adapter\RedisAdapter;
- use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
-
- $cache = new RedisAdapter(...);
- $expressionLanguage = new ExpressionLanguage($cache);
-
-.. seealso::
-
- See the :doc:`/components/cache` documentation for more information about
- available cache adapters.
-
-Using Parsed and Serialized Expressions
----------------------------------------
-
-Both ``evaluate()`` and ``compile()`` can handle ``ParsedExpression`` and
-``SerializedParsedExpression``::
-
- // ...
-
- // the parse() method returns a ParsedExpression
- $expression = $expressionLanguage->parse('1 + 4', []);
-
- var_dump($expressionLanguage->evaluate($expression)); // prints 5
-
-.. code-block:: php
-
- use Symfony\Component\ExpressionLanguage\SerializedParsedExpression;
- // ...
-
- $expression = new SerializedParsedExpression(
- '1 + 4',
- serialize($expressionLanguage->parse('1 + 4', [])->getNodes())
- );
-
- var_dump($expressionLanguage->evaluate($expression)); // prints 5
-
-.. _`CacheItemPoolInterface`: https://github.com/php-fig/cache/blob/master/src/CacheItemPoolInterface.php
diff --git a/components/expression_language/extending.rst b/components/expression_language/extending.rst
deleted file mode 100644
index 787d0f61d31..00000000000
--- a/components/expression_language/extending.rst
+++ /dev/null
@@ -1,136 +0,0 @@
-.. index::
- single: Extending; ExpressionLanguage
-
-Extending the ExpressionLanguage
-================================
-
-The ExpressionLanguage can be extended by adding custom functions. For
-instance, in the Symfony Framework, the security has custom functions to check
-the user's role.
-
-.. note::
-
- If you want to learn how to use functions in an expression, read
- ":ref:`component-expression-functions`".
-
-Registering Functions
----------------------
-
-Functions are registered on each specific ``ExpressionLanguage`` instance.
-That means the functions can be used in any expression executed by that
-instance.
-
-To register a function, use
-:method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::register`.
-This method has 3 arguments:
-
-* **name** - The name of the function in an expression;
-* **compiler** - A function executed when compiling an expression using the
- function;
-* **evaluator** - A function executed when the expression is evaluated.
-
-Example::
-
- use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
-
- $expressionLanguage = new ExpressionLanguage();
- $expressionLanguage->register('lowercase', function ($str) {
- return sprintf('(is_string(%1$s) ? strtolower(%1$s) : %1$s)', $str);
- }, function ($arguments, $str) {
- if (!is_string($str)) {
- return $str;
- }
-
- return strtolower($str);
- });
-
- var_dump($expressionLanguage->evaluate('lowercase("HELLO")'));
- // this will print: hello
-
-In addition to the custom function arguments, the **evaluator** is passed an
-``arguments`` variable as its first argument, which is equal to the second
-argument of ``evaluate()`` (e.g. the "values" when evaluating an expression).
-
-.. _components-expression-language-provider:
-
-Using Expression Providers
---------------------------
-
-When you use the ``ExpressionLanguage`` class in your library, you often want
-to add custom functions. To do so, you can create a new expression provider by
-creating a class that implements
-:class:`Symfony\\Component\\ExpressionLanguage\\ExpressionFunctionProviderInterface`.
-
-This interface requires one method:
-:method:`Symfony\\Component\\ExpressionLanguage\\ExpressionFunctionProviderInterface::getFunctions`,
-which returns an array of expression functions (instances of
-:class:`Symfony\\Component\\ExpressionLanguage\\ExpressionFunction`) to
-register::
-
- use Symfony\Component\ExpressionLanguage\ExpressionFunction;
- use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
-
- class StringExpressionLanguageProvider implements ExpressionFunctionProviderInterface
- {
- public function getFunctions()
- {
- return [
- new ExpressionFunction('lowercase', function ($str) {
- return sprintf('(is_string(%1$s) ? strtolower(%1$s) : %1$s)', $str);
- }, function ($arguments, $str) {
- if (!is_string($str)) {
- return $str;
- }
-
- return strtolower($str);
- }),
- ];
- }
- }
-
-.. tip::
-
- To create an expression function from a PHP function with the
- :method:`Symfony\\Component\\ExpressionLanguage\\ExpressionFunction::fromPhp` static method::
-
- ExpressionFunction::fromPhp('strtoupper');
-
- Namespaced functions are supported, but they require a second argument to
- define the name of the expression::
-
- ExpressionFunction::fromPhp('My\strtoupper', 'my_strtoupper');
-
-You can register providers using
-:method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::registerProvider`
-or by using the second argument of the constructor::
-
- use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
-
- // using the constructor
- $expressionLanguage = new ExpressionLanguage(null, [
- new StringExpressionLanguageProvider(),
- // ...
- ]);
-
- // using registerProvider()
- $expressionLanguage->registerProvider(new StringExpressionLanguageProvider());
-
-.. tip::
-
- It is recommended to create your own ``ExpressionLanguage`` class in your
- library. Now you can add the extension by overriding the constructor::
-
- use Psr\Cache\CacheItemPoolInterface;
- use Symfony\Component\ExpressionLanguage\ExpressionLanguage as BaseExpressionLanguage;
-
- class ExpressionLanguage extends BaseExpressionLanguage
- {
- public function __construct(CacheItemPoolInterface $cache = null, array $providers = [])
- {
- // prepends the default provider to let users override it
- array_unshift($providers, new StringExpressionLanguageProvider());
-
- parent::__construct($cache, $providers);
- }
- }
-
diff --git a/components/expression_language/syntax.rst b/components/expression_language/syntax.rst
deleted file mode 100644
index 045451491f5..00000000000
--- a/components/expression_language/syntax.rst
+++ /dev/null
@@ -1,322 +0,0 @@
-.. index::
- single: Syntax; ExpressionLanguage
-
-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.
-
-Supported Literals
-------------------
-
-The component supports:
-
-* **strings** - single and double quotes (e.g. ``'hello'``)
-* **numbers** - e.g. ``103``
-* **arrays** - using JSON-like notation (e.g. ``[1, 2]``)
-* **hashes** - using JSON-like notation (e.g. ``{ foo: 'bar' }``)
-* **booleans** - ``true`` and ``false``
-* **null** - ``null``
-* **exponential** - also known as scientific (e.g. ``1.99E+3`` or ``1e-2``)
-
-.. caution::
-
- A backslash (``\``) must be escaped by 4 backslashes (``\\\\``) in a string
- and 8 backslashes (``\\\\\\\\``) in a regex::
-
- echo $expressionLanguage->evaluate('"\\\\"'); // prints \
- $expressionLanguage->evaluate('"a\\\\b" matches "/^a\\\\\\\\b$/"'); // returns true
-
- Control characters (e.g. ``\n``) in expressions are replaced with
- whitespace. To avoid this, escape the sequence with a single backslash
- (e.g. ``\\n``).
-
-.. _component-expression-objects:
-
-Working with Objects
---------------------
-
-When passing objects into an expression, you can use different syntaxes to
-access properties and call methods on the object.
-
-Accessing Public Properties
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Public properties on objects can be accessed by using the ``.`` syntax, similar
-to JavaScript::
-
- class Apple
- {
- public $variety;
- }
-
- $apple = new Apple();
- $apple->variety = 'Honeycrisp';
-
- var_dump($expressionLanguage->evaluate(
- 'fruit.variety',
- [
- 'fruit' => $apple,
- ]
- ));
-
-This will print out ``Honeycrisp``.
-
-Calling Methods
-~~~~~~~~~~~~~~~
-
-The ``.`` syntax can also be used to call methods on an object, similar to
-JavaScript::
-
- class Robot
- {
- public function sayHi($times)
- {
- $greetings = [];
- for ($i = 0; $i < $times; $i++) {
- $greetings[] = 'Hi';
- }
-
- return implode(' ', $greetings).'!';
- }
- }
-
- $robot = new Robot();
-
- var_dump($expressionLanguage->evaluate(
- 'robot.sayHi(3)',
- [
- 'robot' => $robot,
- ]
- ));
-
-This will print out ``Hi Hi Hi!``.
-
-.. _component-expression-functions:
-
-Working with Functions
-----------------------
-
-You can also use registered functions in the expression by using the same
-syntax as PHP and JavaScript. The ExpressionLanguage component comes with one
-function by default: ``constant()``, which will return the value of the PHP
-constant::
-
- define('DB_USER', 'root');
-
- var_dump($expressionLanguage->evaluate(
- 'constant("DB_USER")'
- ));
-
-This will print out ``root``.
-
-.. tip::
-
- To read how to register your own functions to use in an expression, see
- ":doc:`/components/expression_language/extending`".
-
-.. _component-expression-arrays:
-
-Working with Arrays
--------------------
-
-If you pass an array into an expression, use the ``[]`` syntax to access
-array keys, similar to JavaScript::
-
- $data = ['life' => 10, 'universe' => 10, 'everything' => 22];
-
- var_dump($expressionLanguage->evaluate(
- 'data["life"] + data["universe"] + data["everything"]',
- [
- 'data' => $data,
- ]
- ));
-
-This will print out ``42``.
-
-Supported Operators
--------------------
-
-The component comes with a lot of operators:
-
-Arithmetic Operators
-~~~~~~~~~~~~~~~~~~~~
-
-* ``+`` (addition)
-* ``-`` (subtraction)
-* ``*`` (multiplication)
-* ``/`` (division)
-* ``%`` (modulus)
-* ``**`` (pow)
-
-For example::
-
- var_dump($expressionLanguage->evaluate(
- 'life + universe + everything',
- [
- 'life' => 10,
- 'universe' => 10,
- 'everything' => 22,
- ]
- ));
-
-This will print out ``42``.
-
-Bitwise Operators
-~~~~~~~~~~~~~~~~~
-
-* ``&`` (and)
-* ``|`` (or)
-* ``^`` (xor)
-
-Comparison Operators
-~~~~~~~~~~~~~~~~~~~~
-
-* ``==`` (equal)
-* ``===`` (identical)
-* ``!=`` (not equal)
-* ``!==`` (not identical)
-* ``<`` (less than)
-* ``>`` (greater than)
-* ``<=`` (less than or equal to)
-* ``>=`` (greater than or equal to)
-* ``matches`` (regex match)
-
-.. tip::
-
- To test if a string does *not* match a regex, use the logical ``not``
- operator in combination with the ``matches`` operator::
-
- $expressionLanguage->evaluate('not ("foo" matches "/bar/")'); // returns true
-
- You must use parenthesis because the unary operator ``not`` has precedence
- over the binary operator ``matches``.
-
-Examples::
-
- $ret1 = $expressionLanguage->evaluate(
- 'life == everything',
- [
- 'life' => 10,
- 'universe' => 10,
- 'everything' => 22,
- ]
- );
-
- $ret2 = $expressionLanguage->evaluate(
- 'life > everything',
- [
- 'life' => 10,
- 'universe' => 10,
- 'everything' => 22,
- ]
- );
-
-Both variables would be set to ``false``.
-
-Logical Operators
-~~~~~~~~~~~~~~~~~
-
-* ``not`` or ``!``
-* ``and`` or ``&&``
-* ``or`` or ``||``
-
-For example::
-
- $ret = $expressionLanguage->evaluate(
- 'life < universe or life < everything',
- [
- 'life' => 10,
- 'universe' => 10,
- 'everything' => 22,
- ]
- );
-
-This ``$ret`` variable will be set to ``true``.
-
-String Operators
-~~~~~~~~~~~~~~~~
-
-* ``~`` (concatenation)
-
-For example::
-
- var_dump($expressionLanguage->evaluate(
- 'firstName~" "~lastName',
- [
- 'firstName' => 'Arthur',
- 'lastName' => 'Dent',
- ]
- ));
-
-This would print out ``Arthur Dent``.
-
-Array Operators
-~~~~~~~~~~~~~~~
-
-* ``in`` (contain)
-* ``not in`` (does not contain)
-
-For example::
-
- class User
- {
- public $group;
- }
-
- $user = new User();
- $user->group = 'human_resources';
-
- $inGroup = $expressionLanguage->evaluate(
- 'user.group in ["human_resources", "marketing"]',
- [
- 'user' => $user,
- ]
- );
-
-The ``$inGroup`` would evaluate to ``true``.
-
-Numeric Operators
-~~~~~~~~~~~~~~~~~
-
-* ``..`` (range)
-
-For example::
-
- class User
- {
- public $age;
- }
-
- $user = new User();
- $user->age = 34;
-
- $expressionLanguage->evaluate(
- 'user.age in 18..45',
- [
- 'user' => $user,
- ]
- );
-
-This will evaluate to ``true``, because ``user.age`` is in the range from
-``18`` to ``45``.
-
-Ternary Operators
-~~~~~~~~~~~~~~~~~
-
-* ``foo ? 'yes' : 'no'``
-* ``foo ?: 'no'`` (equal to ``foo ? foo : 'no'``)
-* ``foo ? 'yes'`` (equal to ``foo ? 'yes' : ''``)
-
-Built-in Objects and Variables
-------------------------------
-
-When using this component inside a Symfony application, certain objects and
-variables are automatically injected by Symfony so you can use them in your
-expressions (e.g. the request, the current user, etc.):
-
-* :doc:`Variables available in security expressions `;
-* :doc:`Variables available in service container expressions `;
-* :ref:`Variables available in routing expressions `.
diff --git a/components/filesystem.rst b/components/filesystem.rst
index 6a9282bfe23..dabf3f81872 100644
--- a/components/filesystem.rst
+++ b/components/filesystem.rst
@@ -1,10 +1,8 @@
-.. index::
- single: Filesystem
-
The Filesystem Component
========================
- The Filesystem component provides basic utilities for the filesystem.
+ The Filesystem component provides platform-independent utilities for
+ filesystem operations and for file/directory paths manipulation.
Installation
------------
@@ -18,38 +16,32 @@ Installation
Usage
-----
-The :class:`Symfony\\Component\\Filesystem\\Filesystem` class is the unique
-endpoint for filesystem operations::
+The component contains two main classes called :class:`Symfony\\Component\\Filesystem\\Filesystem`
+and :class:`Symfony\\Component\\Filesystem\\Path`::
use Symfony\Component\Filesystem\Exception\IOExceptionInterface;
use Symfony\Component\Filesystem\Filesystem;
+ use Symfony\Component\Filesystem\Path;
$filesystem = new Filesystem();
try {
- $filesystem->mkdir(sys_get_temp_dir().'/'.random_int(0, 1000));
+ $filesystem->mkdir(
+ Path::normalize(sys_get_temp_dir().'/'.random_int(0, 1000)),
+ );
} catch (IOExceptionInterface $exception) {
echo "An error occurred while creating your directory at ".$exception->getPath();
}
-.. note::
-
- Methods :method:`Symfony\\Component\\Filesystem\\Filesystem::mkdir`,
- :method:`Symfony\\Component\\Filesystem\\Filesystem::exists`,
- :method:`Symfony\\Component\\Filesystem\\Filesystem::touch`,
- :method:`Symfony\\Component\\Filesystem\\Filesystem::remove`,
- :method:`Symfony\\Component\\Filesystem\\Filesystem::chmod`,
- :method:`Symfony\\Component\\Filesystem\\Filesystem::chown` and
- :method:`Symfony\\Component\\Filesystem\\Filesystem::chgrp` can receive a
- string, an array or any object implementing :phpclass:`Traversable` as
- the target argument.
+Filesystem Utilities
+--------------------
``mkdir``
~~~~~~~~~
:method:`Symfony\\Component\\Filesystem\\Filesystem::mkdir` creates a directory recursively.
On POSIX filesystems, directories are created with a default mode value
-`0777`. You can use the second argument to set your own mode::
+``0777``. You can use the second argument to set your own mode::
$filesystem->mkdir('/tmp/photos', 0700);
@@ -162,7 +154,7 @@ permissions of a file. The fourth argument is a boolean recursive option::
// sets the mode of the video to 0600
$filesystem->chmod('video.ogg', 0600);
- // changes the mod of the src directory recursively
+ // changes the mode of the src directory recursively
$filesystem->chmod('src', 0700, 0000, true);
.. note::
@@ -214,13 +206,9 @@ support symbolic links, a third boolean argument is available::
:method:`Symfony\\Component\\Filesystem\\Filesystem::readlink` read links targets.
-PHP's :phpfunction:`readlink` function returns the target of a symbolic link. However, its behavior
-is completely different under Windows and Unix. On Windows systems, ``readlink()``
-resolves recursively the children links of a link until a final target is found. On
-Unix-based systems ``readlink()`` only resolves the next link.
-
-The :method:`Symfony\\Component\\Filesystem\\Filesystem::readlink` method provided
-by the Filesystem component always behaves in the same way::
+The :method:`Symfony\\Component\\Filesystem\\Filesystem::readlink` method
+provided by the Filesystem component behaves in the same way on all operating
+systems (unlike PHP's :phpfunction:`readlink` function)::
// returns the next direct target of the link without considering the existence of the target
$filesystem->readlink('/path/to/link');
@@ -228,17 +216,22 @@ by the Filesystem component always behaves in the same way::
// returns its absolute fully resolved final version of the target (if there are nested links, they are resolved)
$filesystem->readlink('/path/to/link', true);
-Its behavior is the following::
-
- public function readlink($path, $canonicalize = false)
+Its behavior is the following:
* When ``$canonicalize`` is ``false``:
- * if ``$path`` does not exist or is not a link, it returns ``null``.
- * if ``$path`` is a link, it returns the next direct target of the link without considering the existence of the target.
+
+ * if ``$path`` does not exist or is not a link, it returns ``null``.
+ * if ``$path`` is a link, it returns the next direct target of the link without considering the existence of the target.
* When ``$canonicalize`` is ``true``:
- * if ``$path`` does not exist, it returns null.
- * if ``$path`` exists, it returns its absolute fully resolved final version.
+
+ * if ``$path`` does not exist, it returns null.
+ * if ``$path`` exists, it returns its absolute fully resolved final version.
+
+.. note::
+
+ If you wish to canonicalize the path without checking its existence, you can
+ use :method:`Symfony\\Component\\Filesystem\\Path::canonicalize` method instead.
``makePathRelative``
~~~~~~~~~~~~~~~~~~~~
@@ -252,7 +245,7 @@ absolute paths and returns the relative path from the second path to the first o
'/var/lib/symfony/src/Symfony/Component'
);
// returns 'videos/'
- $filesystem->makePathRelative('/tmp/videos', '/tmp')
+ $filesystem->makePathRelative('/tmp/videos', '/tmp');
``mirror``
~~~~~~~~~~
@@ -291,18 +284,17 @@ exception on failure::
// returns a path like : /tmp/prefix_wyjgtF.png
$filesystem->tempnam('/tmp', 'prefix_', '.png');
-.. versionadded:: 5.1
-
- The option to set a suffix in ``tempnam()`` was introduced in Symfony 5.1.
+.. _filesystem-dumpfile:
``dumpFile``
~~~~~~~~~~~~
:method:`Symfony\\Component\\Filesystem\\Filesystem::dumpFile` saves the given
-contents into a file. It does this in an atomic manner: it writes a temporary
-file first and then moves it to the new file location when it's finished.
-This means that the user will always see either the complete old file or
-complete new file (but never a partially-written file)::
+contents into a file (creating the file and its directory if they don't exist).
+It does this in an atomic manner: it writes a temporary file first and then moves
+it to the new file location when it's finished. This means that the user will
+always see either the complete old file or complete new file (but never a
+partially-written file)::
$filesystem->dumpFile('file.txt', 'Hello World');
@@ -315,10 +307,203 @@ The ``file.txt`` file contains ``Hello World`` now.
contents at the end of some file::
$filesystem->appendToFile('logs.txt', 'Email sent to user@example.com');
+ // the third argument tells whether the file should be locked when writing to it
+ $filesystem->appendToFile('logs.txt', 'Email sent to user@example.com', true);
If either the file or its containing directory doesn't exist, this method
creates them before appending the contents.
+``readFile``
+~~~~~~~~~~~~
+
+.. versionadded:: 7.1
+
+ The ``readFile()`` method was introduced in Symfony 7.1.
+
+:method:`Symfony\\Component\\Filesystem\\Filesystem::readFile` returns all the
+contents of a file as a string. Unlike the :phpfunction:`file_get_contents` function
+from PHP, it throws an exception when the given file path is not readable and
+when passing the path to a directory instead of a file::
+
+ $contents = $filesystem->readFile('/some/path/to/file.txt');
+
+The ``$contents`` variable now stores all the contents of the ``file.txt`` file.
+
+Path Manipulation Utilities
+---------------------------
+
+Dealing with file paths usually involves some difficulties:
+
+- Platform differences: file paths look different on different platforms. UNIX
+ file paths start with a slash ("/"), while Windows file paths start with a
+ system drive ("C:"). UNIX uses forward slashes, while Windows uses backslashes
+ by default.
+- Absolute/relative paths: web applications frequently need to deal with absolute
+ and relative paths. Converting one to the other properly is tricky and repetitive.
+
+:class:`Symfony\\Component\\Filesystem\\Path` provides utility methods to tackle
+those issues.
+
+Canonicalization
+~~~~~~~~~~~~~~~~
+
+Returns the shortest path name equivalent to the given path. It applies the
+following rules iteratively until no further processing can be done:
+
+- "." segments are removed;
+- ".." segments are resolved;
+- backslashes ("\\") are converted into forward slashes ("/");
+- root paths ("/" and "C:/") always terminate with a slash;
+- non-root paths never terminate with a slash;
+- schemes (such as "phar://") are kept;
+- replace ``~`` with the user's home directory.
+
+You can canonicalize a path with :method:`Symfony\\Component\\Filesystem\\Path::canonicalize`::
+
+ echo Path::canonicalize('/var/www/vhost/webmozart/../config.ini');
+ // => /var/www/vhost/config.ini
+
+You can pass absolute paths and relative paths to the
+:method:`Symfony\\Component\\Filesystem\\Path::canonicalize` method. When a
+relative path is passed, ".." segments at the beginning of the path are kept::
+
+ echo Path::canonicalize('../uploads/../config/config.yaml');
+ // => ../config/config.yaml
+
+Malformed paths are returned unchanged::
+
+ echo Path::canonicalize('C:Programs/PHP/php.ini');
+ // => C:Programs/PHP/php.ini
+
+Converting Absolute/Relative Paths
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Absolute/relative paths can be converted with the methods
+:method:`Symfony\\Component\\Filesystem\\Path::makeAbsolute`
+and :method:`Symfony\\Component\\Filesystem\\Path::makeRelative`.
+
+:method:`Symfony\\Component\\Filesystem\\Path::makeAbsolute` method expects a
+relative path and a base path to base that relative path upon::
+
+ echo Path::makeAbsolute('config/config.yaml', '/var/www/project');
+ // => /var/www/project/config/config.yaml
+
+If an absolute path is passed in the first argument, the absolute path is
+returned unchanged::
+
+ echo Path::makeAbsolute('/usr/share/lib/config.ini', '/var/www/project');
+ // => /usr/share/lib/config.ini
+
+The method resolves ".." segments, if there are any::
+
+ echo Path::makeAbsolute('../config/config.yaml', '/var/www/project/uploads');
+ // => /var/www/project/config/config.yaml
+
+This method is very useful if you want to be able to accept relative paths (for
+example, relative to the root directory of your project) and absolute paths at
+the same time.
+
+:method:`Symfony\\Component\\Filesystem\\Path::makeRelative` is the inverse
+operation to :method:`Symfony\\Component\\Filesystem\\Path::makeAbsolute`::
+
+ echo Path::makeRelative('/var/www/project/config/config.yaml', '/var/www/project');
+ // => config/config.yaml
+
+If the path is not within the base path, the method will prepend ".." segments
+as necessary::
+
+ echo Path::makeRelative('/var/www/project/config/config.yaml', '/var/www/project/uploads');
+ // => ../config/config.yaml
+
+Use :method:`Symfony\\Component\\Filesystem\\Path::isAbsolute` and
+:method:`Symfony\\Component\\Filesystem\\Path::isRelative` to check whether a
+path is absolute or relative::
+
+ Path::isAbsolute('C:\Programs\PHP\php.ini')
+ // => true
+
+All four methods internally canonicalize the passed path.
+
+Finding Longest Common Base Paths
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When you store absolute file paths on the file system, this leads to a lot of
+duplicated information::
+
+ return [
+ '/var/www/vhosts/project/httpdocs/config/config.yaml',
+ '/var/www/vhosts/project/httpdocs/config/routing.yaml',
+ '/var/www/vhosts/project/httpdocs/config/services.yaml',
+ '/var/www/vhosts/project/httpdocs/images/banana.gif',
+ '/var/www/vhosts/project/httpdocs/uploads/images/nicer-banana.gif',
+ ];
+
+Especially when storing many paths, the amount of duplicated information is
+noticeable. You can use :method:`Symfony\\Component\\Filesystem\\Path::getLongestCommonBasePath`
+to check a list of paths for a common base path::
+
+ $basePath = Path::getLongestCommonBasePath(
+ '/var/www/vhosts/project/httpdocs/config/config.yaml',
+ '/var/www/vhosts/project/httpdocs/config/routing.yaml',
+ '/var/www/vhosts/project/httpdocs/config/services.yaml',
+ '/var/www/vhosts/project/httpdocs/images/banana.gif',
+ '/var/www/vhosts/project/httpdocs/uploads/images/nicer-banana.gif'
+ );
+ // => /var/www/vhosts/project/httpdocs
+
+Use this common base path to shorten the stored paths::
+
+ return [
+ $basePath.'/config/config.yaml',
+ $basePath.'/config/routing.yaml',
+ $basePath.'/config/services.yaml',
+ $basePath.'/images/banana.gif',
+ $basePath.'/uploads/images/nicer-banana.gif',
+ ];
+
+:method:`Symfony\\Component\\Filesystem\\Path::getLongestCommonBasePath` always
+returns canonical paths.
+
+Use :method:`Symfony\\Component\\Filesystem\\Path::isBasePath` to test whether a
+path is a base path of another path::
+
+ Path::isBasePath("/var/www", "/var/www/project");
+ // => true
+
+ Path::isBasePath("/var/www", "/var/www/project/..");
+ // => true
+
+ Path::isBasePath("/var/www", "/var/www/project/../..");
+ // => false
+
+Finding Directories/Root Directories
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+PHP offers the function :phpfunction:`dirname` to obtain the directory path of a
+file path. This method has a few quirks::
+
+- ``dirname()`` does not accept backslashes on UNIX
+- ``dirname("C:/Programs")`` returns "C:", not "C:/"
+- ``dirname("C:/")`` returns ".", not "C:/"
+- ``dirname("C:")`` returns ".", not "C:/"
+- ``dirname("Programs")`` returns ".", not ""
+- ``dirname()`` does not canonicalize the result
+
+:method:`Symfony\\Component\\Filesystem\\Path::getDirectory` fixes these
+shortcomings::
+
+ echo Path::getDirectory("C:\Programs");
+ // => C:/
+
+Additionally, you can use :method:`Symfony\\Component\\Filesystem\\Path::getRoot`
+to obtain the root of a path::
+
+ echo Path::getRoot("/etc/apache2/sites-available");
+ // => /
+
+ echo Path::getRoot("C:\Programs\Apache\Config");
+ // => C:/
+
Error Handling
--------------
diff --git a/components/filesystem/lock_handler.rst b/components/filesystem/lock_handler.rst
deleted file mode 100644
index e7dab2fa625..00000000000
--- a/components/filesystem/lock_handler.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-:orphan:
-
-LockHandler
-===========
-
-.. caution::
-
- The ``LockHandler`` utility was removed in Symfony 4.0. Use the new Symfony
- :doc:`Lock component ` instead.
diff --git a/components/finder.rst b/components/finder.rst
index c0c5682d19a..cecc597ac64 100644
--- a/components/finder.rst
+++ b/components/finder.rst
@@ -1,7 +1,3 @@
-.. index::
- single: Finder
- single: Components; Finder
-
The Finder Component
====================
@@ -45,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
@@ -131,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
~~~~~~~~~~~~~~~~~~~~~
@@ -141,13 +161,22 @@ default when looking for files and directories, but you can change this with the
$finder->ignoreVCS(false);
-If the search directory contains a ``.gitignore`` file, you can reuse those
-rules to exclude files and directories from the results with the
+If the search directory and its subdirectories contain ``.gitignore`` files, you
+can reuse those rules to exclude files and directories from the results with the
:method:`Symfony\\Component\\Finder\\Finder::ignoreVCSIgnored` method::
// excludes files/directories matching the .gitignore patterns
$finder->ignoreVCSIgnored(true);
+The rules of a directory always override the rules of its parent directories.
+
+.. note::
+
+ Git looks for ``.gitignore`` files starting from the repository root directory.
+ Symfony's Finder behavior is different and it looks for ``.gitignore`` files
+ starting from the directory used to search files/directories. To be consistent
+ with Git behavior, you should explicitly search from the Git repository root.
+
File Name
~~~~~~~~~
@@ -210,7 +239,7 @@ Use the forward slash (i.e. ``/``) as the directory separator on all platforms,
including Windows. The component makes the necessary conversion internally.
The ``path()`` method accepts a string, a regular expression or an array of
-strings or regulars expressions::
+strings or regular expressions::
$finder->path('foo/bar');
$finder->path('/^foo\/bar/');
@@ -293,6 +322,7 @@ Directory Depth
By default, the Finder recursively traverses directories. Restrict the depth of
traversing with :method:`Symfony\\Component\\Finder\\Finder::depth`::
+ // this will only consider files/directories which are direct children
$finder->depth('== 0');
$finder->depth('< 3');
@@ -323,13 +353,23 @@ it is called with the file as a :class:`Symfony\\Component\\Finder\\SplFileInfo`
instance. The file is excluded from the result set if the Closure returns
``false``.
+The ``filter()`` method includes a second optional argument to prune directories.
+If set to ``true``, this method completely skips the excluded directories instead
+of traversing the entire file/directory structure and excluding them later. When
+using a closure, return ``false`` for the directories which you want to prune.
+
+Pruning directories early can improve performance significantly depending on the
+file/directory hierarchy complexity and the number of excluded directories.
+
Sorting Results
---------------
-Sort the results by name or by type (directories first, then files)::
+Sort the results by name, extension, size or type (directories first, then files)::
$finder->sortByName();
-
+ $finder->sortByCaseInsensitiveName();
+ $finder->sortByExtension();
+ $finder->sortBySize();
$finder->sortByType();
.. tip::
@@ -339,6 +379,11 @@ Sort the results by name or by type (directories first, then files)::
as its argument to use PHP's `natural sort order`_ algorithm instead (e.g.
``file1.txt``, ``file2.txt``, ``file10.txt``).
+ The ``sortByCaseInsensitiveName()`` method uses the case insensitive
+ :phpfunction:`strcasecmp` PHP function. Pass ``true`` as its argument to use
+ PHP's case insensitive `natural sort order`_ algorithm instead (i.e. the
+ :phpfunction:`strnatcasecmp` PHP function)
+
Sort the files and directories by the last accessed, changed or modified time::
$finder->sortByAccessedTime();
@@ -349,7 +394,7 @@ Sort the files and directories by the last accessed, changed or modified time::
You can also define your own sorting algorithm with the ``sort()`` method::
- $finder->sort(function (\SplFileInfo $a, \SplFileInfo $b) {
+ $finder->sort(function (\SplFileInfo $a, \SplFileInfo $b): int {
return strcmp($a->getRealPath(), $b->getRealPath());
});
diff --git a/components/form.rst b/components/form.rst
index 7ac59478ceb..44f407e4c8e 100644
--- a/components/form.rst
+++ b/components/form.rst
@@ -1,7 +1,3 @@
-.. index::
- single: Forms
- single: Components; Form
-
The Form Component
==================
@@ -121,16 +117,16 @@ The following snippet adds CSRF protection to the form factory::
use Symfony\Component\Form\Extension\Csrf\CsrfExtension;
use Symfony\Component\Form\Forms;
- use Symfony\Component\HttpFoundation\Session\Session;
+ use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Csrf\CsrfTokenManager;
use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator;
use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage;
- // creates a Session object from the HttpFoundation component
- $session = new Session();
+ // creates a RequestStack object using the current request
+ $requestStack = new RequestStack([$request]);
$csrfGenerator = new UriSafeTokenGenerator();
- $csrfStorage = new SessionTokenStorage($session);
+ $csrfStorage = new SessionTokenStorage($requestStack);
$csrfManager = new CsrfTokenManager($csrfGenerator, $csrfStorage);
$formFactory = Forms::createFormFactoryBuilder()
@@ -138,6 +134,11 @@ The following snippet adds CSRF protection to the form factory::
->addExtension(new CsrfExtension($csrfManager))
->getFormFactory();
+.. versionadded:: 7.2
+
+ Support for passing requests to the constructor of the ``RequestStack``
+ class was introduced in Symfony 7.2.
+
Internally, this extension will automatically add a hidden field to every
form (called ``_token`` by default) whose value is automatically generated by
the CSRF generator and validated when binding the form.
@@ -207,7 +208,7 @@ to bootstrap or access Twig and add the :class:`Symfony\\Bridge\\Twig\\Extension
]));
$formEngine = new TwigRendererEngine([$defaultFormTheme], $twig);
$twig->addRuntimeLoader(new FactoryRuntimeLoader([
- FormRenderer::class => function () use ($formEngine, $csrfManager) {
+ FormRenderer::class => function () use ($formEngine, $csrfManager): FormRenderer {
return new FormRenderer($formEngine, $csrfManager);
},
]));
@@ -222,10 +223,6 @@ to bootstrap or access Twig and add the :class:`Symfony\\Bridge\\Twig\\Extension
// ...
->getFormFactory();
-.. versionadded:: 1.30
-
- The ``Twig\RuntimeLoader\FactoryRuntimeLoader`` was introduced in Twig 1.30.
-
The exact details of your `Twig Configuration`_ will vary, but the goal is
always to add the :class:`Symfony\\Bridge\\Twig\\Extension\\FormExtension`
to Twig, which gives you access to the Twig functions for rendering forms.
@@ -370,10 +367,6 @@ you need to. If your application uses global or static variables (not usually a
good idea), then you can store the object on some static class or do something
similar.
-Regardless of how you architect your application, remember that you
-should only have one form factory and that you'll need to be able to access
-it throughout your application.
-
.. _component-form-intro-create-simple-form:
Creating a simple Form
@@ -382,7 +375,8 @@ Creating a simple Form
.. tip::
If you're using the Symfony Framework, then the form factory is available
- automatically as a service called ``form.factory``. Also, the default
+ automatically as a service called ``form.factory``, you can inject it as
+ ``Symfony\Component\Form\FormFactoryInterface``. Also, the default
base controller class has a :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController::createFormBuilder`
method, which is a shortcut to fetch the form factory and call ``createBuilder()``
on it.
@@ -393,35 +387,20 @@ is created from the form factory.
.. configuration-block::
- .. code-block:: php-standalone
-
- use Symfony\Component\Form\Extension\Core\Type\TextType;
- use Symfony\Component\Form\Extension\Core\Type\DateType;
-
- // ...
-
- $form = $formFactory->createBuilder()
- ->add('task', TextType::class)
- ->add('dueDate', DateType::class)
- ->getForm();
-
- var_dump($twig->render('new.html.twig', [
- 'form' => $form->createView(),
- ]));
-
.. code-block:: php-symfony
// src/Controller/TaskController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
- use Symfony\Component\HttpFoundation\Request;
- use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
+ use Symfony\Component\Form\Extension\Core\Type\TextType;
+ use Symfony\Component\HttpFoundation\Request;
+ use Symfony\Component\HttpFoundation\Response;
class TaskController extends AbstractController
{
- public function new(Request $request)
+ public function new(Request $request): Response
{
// createFormBuilder is a shortcut to get the "form factory"
// and then call "createBuilder()" on it
@@ -437,6 +416,22 @@ is created from the form factory.
}
}
+ .. code-block:: php-standalone
+
+ use Symfony\Component\Form\Extension\Core\Type\DateType;
+ use Symfony\Component\Form\Extension\Core\Type\TextType;
+
+ // ...
+
+ $form = $formFactory->createBuilder()
+ ->add('task', TextType::class)
+ ->add('dueDate', DateType::class)
+ ->getForm();
+
+ var_dump($twig->render('new.html.twig', [
+ 'form' => $form->createView(),
+ ]));
+
As you can see, creating a form is like writing a recipe: you call ``add()``
for each new field you want to create. The first argument to ``add()`` is the
name of your field, and the second is the fully qualified class name. The Form
@@ -453,35 +448,19 @@ an "edit" form), pass in the default data when creating your form builder:
.. configuration-block::
- .. code-block:: php-standalone
-
- use Symfony\Component\Form\Extension\Core\Type\FormType;
- use Symfony\Component\Form\Extension\Core\Type\TextType;
- use Symfony\Component\Form\Extension\Core\Type\DateType;
-
- // ...
-
- $defaults = [
- 'dueDate' => new \DateTime('tomorrow'),
- ];
-
- $form = $formFactory->createBuilder(FormType::class, $defaults)
- ->add('task', TextType::class)
- ->add('dueDate', DateType::class)
- ->getForm();
-
.. code-block:: php-symfony
// src/Controller/DefaultController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
- use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
+ use Symfony\Component\Form\Extension\Core\Type\TextType;
+ use Symfony\Component\HttpFoundation\Response;
class DefaultController extends AbstractController
{
- public function new(Request $request)
+ public function new(Request $request): Response
{
$defaults = [
'dueDate' => new \DateTime('tomorrow'),
@@ -496,6 +475,23 @@ an "edit" form), pass in the default data when creating your form builder:
}
}
+ .. code-block:: php-standalone
+
+ use Symfony\Component\Form\Extension\Core\Type\DateType;
+ use Symfony\Component\Form\Extension\Core\Type\FormType;
+ use Symfony\Component\Form\Extension\Core\Type\TextType;
+
+ // ...
+
+ $defaults = [
+ 'dueDate' => new \DateTime('tomorrow'),
+ ];
+
+ $form = $formFactory->createBuilder(FormType::class, $defaults)
+ ->add('task', TextType::class)
+ ->add('dueDate', DateType::class)
+ ->getForm();
+
.. tip::
In this example, the default data is an array. Later, when you use the
@@ -517,11 +513,11 @@ done by passing a special form "view" object to your template (notice the
{{ form_start(form) }}
{{ form_widget(form) }}
-
+
{{ form_end(form) }}
.. image:: /_images/form/simple-form.png
- :align: center
+ :alt: An HTML form showing a text box labelled "Task", three select boxes for a year, month and day labelled "Due date" and a button labelled "Create Task".
That's it! By printing ``form_widget(form)``, each field in the form is
rendered, along with a label and error message (if there is one). While this is
@@ -539,19 +535,6 @@ by :method:`Symfony\\Component\\Form\\Form::handleRequest` to determine whether
.. configuration-block::
- .. code-block:: php-standalone
-
- use Symfony\Component\Form\Extension\Core\Type\FormType;
-
- // ...
-
- $formBuilder = $formFactory->createBuilder(FormType::class, null, [
- 'action' => '/search',
- 'method' => 'GET',
- ]);
-
- // ...
-
.. code-block:: php-symfony
// src/Controller/DefaultController.php
@@ -559,10 +542,11 @@ by :method:`Symfony\\Component\\Form\\Form::handleRequest` to determine whether
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\FormType;
+ use Symfony\Component\HttpFoundation\Response;
class DefaultController extends AbstractController
{
- public function search()
+ public function search(): Response
{
$formBuilder = $this->createFormBuilder(null, [
'action' => '/search',
@@ -573,46 +557,28 @@ by :method:`Symfony\\Component\\Form\\Form::handleRequest` to determine whether
}
}
-.. _component-form-intro-handling-submission:
-
-Handling Form Submissions
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To handle form submissions, use the :method:`Symfony\\Component\\Form\\Form::handleRequest`
-method:
-
-.. configuration-block::
-
.. code-block:: php-standalone
- use Symfony\Component\HttpFoundation\Request;
- use Symfony\Component\HttpFoundation\RedirectResponse;
- use Symfony\Component\Form\Extension\Core\Type\DateType;
- use Symfony\Component\Form\Extension\Core\Type\TextType;
+ use Symfony\Component\Form\Extension\Core\Type\FormType;
// ...
- $form = $formFactory->createBuilder()
- ->add('task', TextType::class)
- ->add('dueDate', DateType::class)
- ->getForm();
-
- $request = Request::createFromGlobals();
-
- $form->handleRequest($request);
+ $formBuilder = $formFactory->createBuilder(FormType::class, null, [
+ 'action' => '/search',
+ 'method' => 'GET',
+ ]);
- if ($form->isSubmitted() && $form->isValid()) {
- $data = $form->getData();
+ // ...
- // ... perform some action, such as saving the data to the database
+.. _component-form-intro-handling-submission:
- $response = new RedirectResponse('/task/success');
- $response->prepare($request);
+Handling Form Submissions
+~~~~~~~~~~~~~~~~~~~~~~~~~
- return $response->send();
- }
+To handle form submissions, use the :method:`Symfony\\Component\\Form\\Form::handleRequest`
+method:
- // ...
+.. configuration-block::
.. code-block:: php-symfony
@@ -622,10 +588,11 @@ method:
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
+ use Symfony\Component\HttpFoundation\Response;
class TaskController extends AbstractController
{
- public function new(Request $request)
+ public function new(Request $request): Response
{
$form = $this->createFormBuilder()
->add('task', TextType::class)
@@ -646,16 +613,54 @@ method:
}
}
+ .. code-block:: php-standalone
+
+ use Symfony\Component\Form\Extension\Core\Type\DateType;
+ use Symfony\Component\Form\Extension\Core\Type\TextType;
+ use Symfony\Component\HttpFoundation\RedirectResponse;
+ use Symfony\Component\HttpFoundation\Request;
+
+ // ...
+
+ $form = $formFactory->createBuilder()
+ ->add('task', TextType::class)
+ ->add('dueDate', DateType::class)
+ ->getForm();
+
+ $request = Request::createFromGlobals();
+
+ $form->handleRequest($request);
+
+ if ($form->isSubmitted() && $form->isValid()) {
+ $data = $form->getData();
+
+ // ... perform some action, such as saving the data to the database
+
+ $response = new RedirectResponse('/task/success');
+ $response->prepare($request);
+
+ return $response->send();
+ }
+
+ // ...
+
+.. warning::
+
+ The form's ``createView()`` method should be called *after* ``handleRequest()`` is
+ called. Otherwise, when using :doc:`form events `, changes done
+ in the ``*_SUBMIT`` events won't be applied to the view (like validation errors).
+
This defines a common form "workflow", which contains 3 different possibilities:
-1) On the initial GET request (i.e. when the user "surfs" to your page),
+#. On the initial GET request (i.e. when the user "surfs" to your page),
build your form and render it;
-If the request is a POST, process the submitted data (via :method:`Symfony\\Component\\Form\\Form::handleRequest`).
-Then:
+ If the request is a POST, process the submitted data (via :method:`Symfony\\Component\\Form\\Form::handleRequest`).
-2) if the form is invalid, re-render the form (which will now contain errors);
-3) if the form is valid, perform some action and redirect.
+ Then:
+
+#. if the form is invalid, re-render the form (which will now contain errors);
+#. if the form is valid, perform some action and redirect.
Luckily, you don't need to decide whether or not a form has been submitted.
Just pass the current request to the :method:`Symfony\\Component\\Form\\Form::handleRequest`
@@ -671,39 +676,21 @@ option when building each field:
.. configuration-block::
- .. code-block:: php-standalone
-
- use Symfony\Component\Validator\Constraints\NotBlank;
- use Symfony\Component\Validator\Constraints\Type;
- use Symfony\Component\Form\Extension\Core\Type\TextType;
- use Symfony\Component\Form\Extension\Core\Type\DateType;
-
- $form = $formFactory->createBuilder()
- ->add('task', TextType::class, [
- 'constraints' => new NotBlank(),
- ])
- ->add('dueDate', DateType::class, [
- 'constraints' => [
- new NotBlank(),
- new Type(\DateTime::class),
- ]
- ])
- ->getForm();
-
.. code-block:: php-symfony
// src/Controller/DefaultController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
- use Symfony\Component\Validator\Constraints\NotBlank;
- use Symfony\Component\Validator\Constraints\Type;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
+ use Symfony\Component\HttpFoundation\Response;
+ use Symfony\Component\Validator\Constraints\NotBlank;
+ use Symfony\Component\Validator\Constraints\Type;
class DefaultController extends AbstractController
{
- public function new(Request $request)
+ public function new(Request $request): Response
{
$form = $this->createFormBuilder()
->add('task', TextType::class, [
@@ -713,13 +700,32 @@ option when building each field:
'constraints' => [
new NotBlank(),
new Type(\DateTime::class),
- ]
+ ],
])
->getForm();
// ...
}
}
+ .. code-block:: php-standalone
+
+ use Symfony\Component\Form\Extension\Core\Type\DateType;
+ use Symfony\Component\Form\Extension\Core\Type\TextType;
+ use Symfony\Component\Validator\Constraints\NotBlank;
+ use Symfony\Component\Validator\Constraints\Type;
+
+ $form = $formFactory->createBuilder()
+ ->add('task', TextType::class, [
+ 'constraints' => new NotBlank(),
+ ])
+ ->add('dueDate', DateType::class, [
+ 'constraints' => [
+ new NotBlank(),
+ new Type(\DateTime::class),
+ ],
+ ])
+ ->getForm();
+
When the form is bound, these validation constraints will be applied automatically
and the errors will display next to the fields on error.
@@ -747,11 +753,11 @@ method to access the list of errors. It returns a
// "firstName" field
$errors = $form['firstName']->getErrors();
- // a FormErrorIterator instance in a flattened structure
+ // a FormErrorIterator instance including child forms in a flattened structure
// use getOrigin() to determine the form causing the error
$errors = $form->getErrors(true);
- // a FormErrorIterator instance representing the form tree structure
+ // a FormErrorIterator instance including child forms without flattening the output structure
$errors = $form->getErrors(true, false);
Clearing Form Errors
@@ -777,4 +783,4 @@ Learn more
/form/*
.. _Twig: https://twig.symfony.com
-.. _`Twig Configuration`: https://twig.symfony.com/doc/2.x/intro.html
+.. _`Twig Configuration`: https://twig.symfony.com/doc/3.x/intro.html
diff --git a/components/http_foundation.rst b/components/http_foundation.rst
index c4662198bdb..1cb87aafb24 100644
--- a/components/http_foundation.rst
+++ b/components/http_foundation.rst
@@ -1,8 +1,3 @@
-.. index::
- single: HTTP
- single: HttpFoundation
- single: Components; HttpFoundation
-
The HttpFoundation Component
============================
@@ -81,19 +76,21 @@ can be accessed via several public properties:
(``$request->headers->get('User-Agent')``).
Each property is a :class:`Symfony\\Component\\HttpFoundation\\ParameterBag`
-instance (or a sub-class of), which is a data holder class:
+instance (or a subclass of), which is a data holder class:
-* ``request``: :class:`Symfony\\Component\\HttpFoundation\\ParameterBag`;
+* ``request``: :class:`Symfony\\Component\\HttpFoundation\\ParameterBag` or
+ :class:`Symfony\\Component\\HttpFoundation\\InputBag` if the data is
+ coming from ``$_POST`` parameters;
-* ``query``: :class:`Symfony\\Component\\HttpFoundation\\ParameterBag`;
+* ``query``: :class:`Symfony\\Component\\HttpFoundation\\InputBag`;
-* ``cookies``: :class:`Symfony\\Component\\HttpFoundation\\ParameterBag`;
+* ``cookies``: :class:`Symfony\\Component\\HttpFoundation\\InputBag`;
* ``attributes``: :class:`Symfony\\Component\\HttpFoundation\\ParameterBag`;
-* ``files``: :class:`Symfony\\Component\\HttpFoundation\\FileBag`;
+* ``files``: :class:`Symfony\\Component\\HttpFoundation\\FileBag`;
-* ``server``: :class:`Symfony\\Component\\HttpFoundation\\ServerBag`;
+* ``server``: :class:`Symfony\\Component\\HttpFoundation\\ServerBag`;
* ``headers``: :class:`Symfony\\Component\\HttpFoundation\\HeaderBag`.
@@ -142,8 +139,18 @@ has some methods to filter the input values:
:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::getInt`
Returns the parameter value converted to integer;
+:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::getEnum`
+ Returns the parameter value converted to a PHP enum;
+
+:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::getString`
+ Returns the parameter value as a string;
+
:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::filter`
Filters the parameter by using the PHP :phpfunction:`filter_var` function.
+ If invalid values are found, a
+ :class:`Symfony\\Component\\HttpKernel\\Exception\\BadRequestHttpException`
+ is thrown. The ``FILTER_NULL_ON_FAILURE`` flag can be used to ignore invalid
+ values.
All getters take up to two arguments: the first one is the parameter name
and the second one is the default value to return if the parameter does not
@@ -161,18 +168,23 @@ exist::
// returns 'baz'
When PHP imports the request query, it handles request parameters like
-``foo[bar]=baz`` in a special way as it creates an array. So you can get the
-``foo`` parameter and you will get back an array with a ``bar`` element::
+``foo[bar]=baz`` in a special way as it creates an array. The ``get()`` method
+doesn't support returning arrays, so you need to use the following code::
// the query string is '?foo[bar]=baz'
- $request->query->get('foo');
+ // don't use $request->query->get('foo'); use the following instead:
+ $request->query->all('foo');
// returns ['bar' => 'baz']
+ // if the requested parameter does not exist, an empty array is returned:
+ $request->query->all('qux');
+ // returns []
+
$request->query->get('foo[bar]');
// returns null
- $request->query->get('foo')['bar'];
+ $request->query->all()['foo']['bar'];
// returns 'baz'
.. _component-foundation-attributes:
@@ -188,9 +200,21 @@ Finally, the raw data sent with the request body can be accessed using
$content = $request->getContent();
-For instance, this may be useful to process a JSON string sent to the
+For instance, this may be useful to process an XML string sent to the
application by a remote service using the HTTP POST method.
+If the request body is a JSON string, it can be accessed using
+:method:`Symfony\\Component\\HttpFoundation\\Request::toArray`::
+
+ $data = $request->toArray();
+
+If the request data could be ``$_POST`` data *or* a JSON string, you can use
+the :method:`Symfony\\Component\\HttpFoundation\\Request::getPayload` method
+which returns an instance of :class:`Symfony\\Component\\HttpFoundation\\InputBag`
+wrapping this data::
+
+ $data = $request->getPayload();
+
Identifying a Request
~~~~~~~~~~~~~~~~~~~~~
@@ -236,9 +260,9 @@ Accessing the Session
~~~~~~~~~~~~~~~~~~~~~
If you have a session attached to the request, you can access it via the
-:method:`Symfony\\Component\\HttpFoundation\\Request::getSession` method;
-the
-:method:`Symfony\\Component\\HttpFoundation\\Request::hasPreviousSession`
+``getSession()`` method of the :class:`Symfony\\Component\\HttpFoundation\\Request`
+or :class:`Symfony\\Component\\HttpFoundation\\RequestStack` class;
+the :method:`Symfony\\Component\\HttpFoundation\\Request::hasPreviousSession`
method tells you if the request contains a session which was started in one of
the previous requests.
@@ -276,10 +300,6 @@ this complexity and defines some methods for the most common tasks::
HeaderUtils::parseQuery('foo[bar.baz]=qux');
// => ['foo' => ['bar.baz' => 'qux']]
-.. versionadded:: 5.2
-
- The ``parseQuery()`` method was introduced in Symfony 5.2.
-
Accessing ``Accept-*`` Headers Data
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -335,13 +355,115 @@ analysis purposes. Use the ``anonymize()`` method from the
use Symfony\Component\HttpFoundation\IpUtils;
$ipv4 = '123.234.235.236';
- $anonymousIpv4 = IPUtils::anonymize($ipv4);
+ $anonymousIpv4 = IpUtils::anonymize($ipv4);
// $anonymousIpv4 = '123.234.235.0'
$ipv6 = '2a01:198:603:10:396e:4789:8e99:890f';
- $anonymousIpv6 = IPUtils::anonymize($ipv6);
+ $anonymousIpv6 = IpUtils::anonymize($ipv6);
// $anonymousIpv6 = '2a01:198:603:10::'
+If you need even more anonymization, you can use the second and third parameters
+of the ``anonymize()`` method to specify the number of bytes that should be
+anonymized depending on the IP address format::
+
+ $ipv4 = '123.234.235.236';
+ $anonymousIpv4 = IpUtils::anonymize($ipv4, 3);
+ // $anonymousIpv4 = '123.0.0.0'
+
+ $ipv6 = '2a01:198:603:10:396e:4789:8e99:890f';
+ // (you must define the second argument (bytes to anonymize in IPv4 addresses)
+ // even when you are only anonymizing IPv6 addresses)
+ $anonymousIpv6 = IpUtils::anonymize($ipv6, 3, 10);
+ // $anonymousIpv6 = '2a01:198:603::'
+
+.. versionadded:: 7.2
+
+ The ``v4Bytes`` and ``v6Bytes`` parameters of the ``anonymize()`` method
+ were introduced in Symfony 7.2.
+
+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
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you need to know if an IP address belongs to a private subnet, you can
+use the ``isPrivateIp()`` method from the
+:class:`Symfony\\Component\\HttpFoundation\\IpUtils` to do that::
+
+ use Symfony\Component\HttpFoundation\IpUtils;
+
+ $ipv4 = '192.168.1.1';
+ $isPrivate = IpUtils::isPrivateIp($ipv4);
+ // $isPrivate = true
+
+ $ipv6 = '2a01:198:603:10:396e:4789:8e99:890f';
+ $isPrivate = IpUtils::isPrivateIp($ipv6);
+ // $isPrivate = false
+
+Matching a Request Against a Set of Rules
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The HttpFoundation component provides some matcher classes that allow you to
+check if a given request meets certain conditions (e.g. it comes from some IP
+address, it uses a certain HTTP method, etc.):
+
+* :class:`Symfony\\Component\\HttpFoundation\\RequestMatcher\\AttributesRequestMatcher`
+* :class:`Symfony\\Component\\HttpFoundation\\RequestMatcher\\ExpressionRequestMatcher`
+* :class:`Symfony\\Component\\HttpFoundation\\RequestMatcher\\HeaderRequestMatcher`
+* :class:`Symfony\\Component\\HttpFoundation\\RequestMatcher\\HostRequestMatcher`
+* :class:`Symfony\\Component\\HttpFoundation\\RequestMatcher\\IpsRequestMatcher`
+* :class:`Symfony\\Component\\HttpFoundation\\RequestMatcher\\IsJsonRequestMatcher`
+* :class:`Symfony\\Component\\HttpFoundation\\RequestMatcher\\MethodRequestMatcher`
+* :class:`Symfony\\Component\\HttpFoundation\\RequestMatcher\\PathRequestMatcher`
+* :class:`Symfony\\Component\\HttpFoundation\\RequestMatcher\\PortRequestMatcher`
+* :class:`Symfony\\Component\\HttpFoundation\\RequestMatcher\\QueryParameterRequestMatcher`
+* :class:`Symfony\\Component\\HttpFoundation\\RequestMatcher\\SchemeRequestMatcher`
+
+You can use them individually or combine them using the
+:class:`Symfony\\Component\\HttpFoundation\\ChainRequestMatcher` class::
+
+ use Symfony\Component\HttpFoundation\ChainRequestMatcher;
+ use Symfony\Component\HttpFoundation\RequestMatcher\HostRequestMatcher;
+ use Symfony\Component\HttpFoundation\RequestMatcher\PathRequestMatcher;
+ use Symfony\Component\HttpFoundation\RequestMatcher\SchemeRequestMatcher;
+
+ // use only one criteria to match the request
+ $schemeMatcher = new SchemeRequestMatcher('https');
+ if ($schemeMatcher->matches($request)) {
+ // ...
+ }
+
+ // use a set of criteria to match the request
+ $matcher = new ChainRequestMatcher([
+ new HostRequestMatcher('example.com'),
+ new PathRequestMatcher('/admin'),
+ ]);
+
+ if ($matcher->matches($request)) {
+ // ...
+ }
+
+.. versionadded:: 7.1
+
+ The ``HeaderRequestMatcher`` and ``QueryParameterRequestMatcher`` were
+ introduced in Symfony 7.1.
+
Accessing other Data
~~~~~~~~~~~~~~~~~~~~
@@ -433,6 +555,14 @@ Sending the response to the client is done by calling the method
$response->send();
+The ``send()`` method takes an optional ``flush`` argument. If set to
+``false``, functions like ``fastcgi_finish_request()`` or
+``litespeed_finish_request()`` are not called. This is useful when debugging
+your application to see which exceptions are thrown in listeners of the
+:class:`Symfony\\Component\\HttpKernel\\Event\\TerminateEvent`. You can learn
+more about it in
+:ref:`the dedicated section about Kernel events `.
+
Setting Cookies
~~~~~~~~~~~~~~~
@@ -463,9 +593,15 @@ a new object with the modified property::
->withDomain('.example.com')
->withSecure(true);
-.. versionadded:: 5.1
+It is possible to define partitioned cookies, also known as `CHIPS`_, by using the
+:method:`Symfony\\Component\\HttpFoundation\\Cookie::withPartitioned` method::
+
+ $cookie = Cookie::create('foo')
+ ->withValue('bar')
+ ->withPartitioned();
- The ``with*()`` methods were introduced in Symfony 5.1.
+ // you can also set the partitioned argument to true when using the `create()` factory method
+ $cookie = Cookie::create('name', 'value', partitioned: true);
Managing the HTTP Cache
~~~~~~~~~~~~~~~~~~~~~~~
@@ -479,6 +615,8 @@ of methods to manipulate the HTTP headers related to the cache:
* :method:`Symfony\\Component\\HttpFoundation\\Response::setExpires`
* :method:`Symfony\\Component\\HttpFoundation\\Response::setMaxAge`
* :method:`Symfony\\Component\\HttpFoundation\\Response::setSharedMaxAge`
+* :method:`Symfony\\Component\\HttpFoundation\\Response::setStaleIfError`
+* :method:`Symfony\\Component\\HttpFoundation\\Response::setStaleWhileRevalidate`
* :method:`Symfony\\Component\\HttpFoundation\\Response::setTtl`
* :method:`Symfony\\Component\\HttpFoundation\\Response::setClientTtl`
* :method:`Symfony\\Component\\HttpFoundation\\Response::setLastModified`
@@ -506,16 +644,13 @@ call::
'proxy_revalidate' => false,
'max_age' => 600,
's_maxage' => 600,
+ 'stale_if_error' => 86400,
+ 'stale_while_revalidate' => 60,
'immutable' => true,
'last_modified' => new \DateTime(),
- 'etag' => 'abcdef'
+ 'etag' => 'abcdef',
]);
-.. versionadded:: 5.1
-
- The ``must_revalidate``, ``no_cache``, ``no_store``, ``no_transform`` and
- ``proxy_revalidate`` directives were introduced in Symfony 5.1.
-
To check if the Response validators (``ETag``, ``Last-Modified``) match a
conditional value specified in the client Request, use the
:method:`Symfony\\Component\\HttpFoundation\\Response::isNotModified`
@@ -546,13 +681,24 @@ Streaming a Response
~~~~~~~~~~~~~~~~~~~~
The :class:`Symfony\\Component\\HttpFoundation\\StreamedResponse` class allows
-you to stream the Response back to the client. The response content is
-represented by a PHP callable instead of a string::
+you to stream the Response back to the client. The response content can be
+represented by a string iterable::
use Symfony\Component\HttpFoundation\StreamedResponse;
+ $chunks = ['Hello', ' World'];
+
$response = new StreamedResponse();
- $response->setCallback(function () {
+ $response->setChunks($chunks);
+ $response->send();
+
+For most complex use cases, the response content can be instead represented by
+a PHP callable::
+
+ use Symfony\Component\HttpFoundation\StreamedResponse;
+
+ $response = new StreamedResponse();
+ $response->setCallback(function (): void {
var_dump('Hello World');
flush();
sleep(2);
@@ -573,7 +719,103 @@ represented by a PHP callable instead of a string::
header in the response::
// disables FastCGI buffering in nginx only for this response
- $response->headers->set('X-Accel-Buffering', 'no')
+ $response->headers->set('X-Accel-Buffering', 'no');
+
+.. versionadded:: 7.3
+
+ Support for using string iterables was introduced in Symfony 7.3.
+
+Streaming a JSON Response
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The :class:`Symfony\\Component\\HttpFoundation\\StreamedJsonResponse` allows to
+stream large JSON responses using PHP generators to keep the used resources low.
+
+The class constructor expects an array which represents the JSON structure and
+includes the list of contents to stream. In addition to PHP generators, which are
+recommended to minimize memory usage, it also supports any kind of PHP Traversable
+containing JSON serializable data::
+
+ use Symfony\Component\HttpFoundation\StreamedJsonResponse;
+
+ // any method or function returning a PHP Generator
+ function loadArticles(): \Generator {
+ yield ['title' => 'Article 1'];
+ yield ['title' => 'Article 2'];
+ yield ['title' => 'Article 3'];
+ };
+
+ $response = new StreamedJsonResponse(
+ // JSON structure with generators in which will be streamed as a list
+ [
+ '_embedded' => [
+ 'articles' => loadArticles(),
+ ],
+ ],
+ );
+
+When loading data via Doctrine, you can use the ``toIterable()`` method to
+fetch results row by row and minimize resources consumption.
+See the `Doctrine Batch processing`_ documentation for more::
+
+ public function __invoke(): Response
+ {
+ return new StreamedJsonResponse(
+ [
+ '_embedded' => [
+ 'articles' => $this->loadArticles(),
+ ],
+ ],
+ );
+ }
+
+ public function loadArticles(): \Generator
+ {
+ // get the $entityManager somehow (e.g. via constructor injection)
+ $entityManager = ...
+
+ $queryBuilder = $entityManager->createQueryBuilder();
+ $queryBuilder->from(Article::class, 'article');
+ $queryBuilder->select('article.id')
+ ->addSelect('article.title')
+ ->addSelect('article.description');
+
+ return $queryBuilder->getQuery()->toIterable();
+ }
+
+If you return a lot of data, consider calling the :phpfunction:`flush` function
+after some specific item count to send the contents to the browser::
+
+ public function loadArticles(): \Generator
+ {
+ // ...
+
+ $count = 0;
+ foreach ($queryBuilder->getQuery()->toIterable() as $article) {
+ yield $article;
+
+ if (0 === ++$count % 100) {
+ flush();
+ }
+ }
+ }
+
+Alternatively, you can also pass any iterable to ``StreamedJsonResponse``,
+including generators::
+
+ public function loadArticles(): \Generator
+ {
+ yield ['title' => 'Article 1'];
+ yield ['title' => 'Article 2'];
+ yield ['title' => 'Article 3'];
+ }
+
+ public function __invoke(): Response
+ {
+ // ...
+
+ return new StreamedJsonResponse(loadArticles());
+ }
.. _component-http-foundation-serving-files:
@@ -610,9 +852,10 @@ Alternatively, if you are serving a static file, you can use a
The ``BinaryFileResponse`` will automatically handle ``Range`` and
``If-Range`` headers from the request. It also supports ``X-Sendfile``
-(see for `nginx`_ and `Apache`_). To make use of it, you need to determine
-whether or not the ``X-Sendfile-Type`` header should be trusted and call
-:method:`Symfony\\Component\\HttpFoundation\\BinaryFileResponse::trustXSendfileTypeHeader`
+(see `FrankenPHP X-Sendfile and X-Accel-Redirect headers`_,
+`nginx X-Accel-Redirect header`_ and `Apache mod_xsendfile module`_). To make use
+of it, you need to determine whether or not the ``X-Sendfile-Type`` header should
+be trusted and call :method:`Symfony\\Component\\HttpFoundation\\BinaryFileResponse::trustXSendfileTypeHeader`
if it should::
BinaryFileResponse::trustXSendfileTypeHeader();
@@ -651,6 +894,23 @@ It is possible to delete the file after the response is sent with the
:method:`Symfony\\Component\\HttpFoundation\\BinaryFileResponse::deleteFileAfterSend` method.
Please note that this will not work when the ``X-Sendfile`` header is set.
+Alternatively, ``BinaryFileResponse`` supports instances of ``\SplTempFileObject``.
+This is useful when you want to serve a file that has been created in memory
+and that will be automatically deleted after the response is sent::
+
+ use Symfony\Component\HttpFoundation\BinaryFileResponse;
+
+ $file = new \SplTempFileObject();
+ $file->fwrite('Hello World');
+ $file->rewind();
+
+ $response = new BinaryFileResponse($file);
+
+.. versionadded:: 7.1
+
+ The support for ``\SplTempFileObject`` in ``BinaryFileResponse``
+ was introduced in Symfony 7.1.
+
If the size of the served file is unknown (e.g. because it's being generated on the fly,
or because a PHP stream filter is registered on it, etc.), you can pass a ``Stream``
instance to ``BinaryFileResponse``. This will disable ``Range`` and ``Content-Length``
@@ -659,7 +919,7 @@ handling, switching to chunked encoding instead::
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\File\Stream;
- $stream = new Stream('path/to/stream');
+ $stream = new Stream('path/to/stream');
$response = new BinaryFileResponse($stream);
.. note::
@@ -694,9 +954,11 @@ class, which can make this even easier::
// if you know the data to send when creating the response
$response = new JsonResponse(['data' => 123]);
- // if you don't know the data to send when creating the response
+ // if you don't know the data to send or if you want to customize the encoding options
$response = new JsonResponse();
// ...
+ // configure any custom encoding options (if needed, it must be called before "setData()")
+ //$response->setEncodingOptions(JsonResponse::DEFAULT_ENCODING_OPTIONS | \JSON_PRESERVE_ZERO_FRACTION);
$response->setData(['data' => 123]);
// if the data to send is already encoded in JSON
@@ -705,10 +967,10 @@ class, which can make this even easier::
The ``JsonResponse`` class sets the ``Content-Type`` header to
``application/json`` and encodes your data to JSON when needed.
-.. caution::
+.. danger::
To avoid XSSI `JSON Hijacking`_, you should pass an associative array
- as the outer-most array to ``JsonResponse`` and not an indexed array so
+ as the outermost array to ``JsonResponse`` and not an indexed array so
that the final result is an object (e.g. ``{"object": "not inside an array"}``)
instead of an array (e.g. ``[{"object": "inside an array"}]``). Read
the `OWASP guidelines`_ for more information.
@@ -716,6 +978,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
~~~~~~~~~~~~~~
@@ -734,7 +1006,7 @@ the response content will look like this:
Session
-------
-The session information is in its own document: :doc:`/components/http_foundation/sessions`.
+The session information is in its own document: :doc:`/session`.
Safe Content Preference
-----------------------
@@ -752,11 +1024,6 @@ Symfony offers two methods to interact with this preference:
* :method:`Symfony\\Component\\HttpFoundation\\Request::preferSafeContent`;
* :method:`Symfony\\Component\\HttpFoundation\\Response::setContentSafe`;
-.. versionadded:: 5.1
-
- The ``preferSafeContent()`` and ``setContentSafe()`` methods were introduced
- in Symfony 5.1.
-
The following example shows how to detect if the user agent prefers "safe" content::
if ($request->preferSafeContent()) {
@@ -765,6 +1032,37 @@ The following example shows how to detect if the user agent prefers "safe" conte
$response->setContentSafe();
return $response;
+
+Generating Relative and Absolute URLs
+-------------------------------------
+
+Generating absolute and relative URLs for a given path is a common need
+in some applications. In Twig templates you can use the
+:ref:`absolute_url() ` and
+:ref:`relative_path() ` functions to do that.
+
+The :class:`Symfony\\Component\\HttpFoundation\\UrlHelper` class provides the
+same functionality for PHP code via the ``getAbsoluteUrl()`` and ``getRelativePath()``
+methods. You can inject this as a service anywhere in your application::
+
+ // src/Normalizer/UserApiNormalizer.php
+ namespace App\Normalizer;
+
+ use Symfony\Component\HttpFoundation\UrlHelper;
+
+ class UserApiNormalizer
+ {
+ public function __construct(
+ private UrlHelper $urlHelper,
+ ) {
+ }
+
+ public function normalize($user): array
+ {
+ return [
+ 'avatar' => $this->urlHelper->getAbsoluteUrl($user->avatar()->path()),
+ ];
+ }
}
Learn More
@@ -774,14 +1072,17 @@ Learn More
:maxdepth: 1
:glob:
- /components/http_foundation/*
/controller
/controller/*
- /session/*
+ /session
/http_cache/*
-.. _nginx: https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile/
-.. _Apache: https://tn123.org/mod_xsendfile/
+.. _`FrankenPHP X-Sendfile and X-Accel-Redirect headers`: https://frankenphp.dev/docs/x-sendfile/
+.. _`nginx X-Accel-Redirect header`: https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_ignore_headers
+.. _`Apache mod_xsendfile module`: https://github.com/nmaier/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
+.. _`CHIPS`: https://developer.mozilla.org/en-US/docs/Web/Privacy/Partitioned_cookies
diff --git a/components/http_foundation/session_configuration.rst b/components/http_foundation/session_configuration.rst
deleted file mode 100644
index c8b29fb00b4..00000000000
--- a/components/http_foundation/session_configuration.rst
+++ /dev/null
@@ -1,290 +0,0 @@
-.. index::
- single: HTTP
- single: HttpFoundation, Sessions
-
-Configuring Sessions and Save Handlers
-======================================
-
-This article deals with how to configure session management and fine tune it
-to your specific needs. This documentation covers save handlers, which
-store and retrieve session data, and configuring session behavior.
-
-Save Handlers
-~~~~~~~~~~~~~
-
-The PHP session workflow has 6 possible operations that may occur. The normal
-session follows ``open``, ``read``, ``write`` and ``close``, with the possibility
-of ``destroy`` and ``gc`` (garbage collection which will expire any old sessions:
-``gc`` is called randomly according to PHP's configuration and if called, it is
-invoked after the ``open`` operation). You can read more about this at
-`php.net/session.customhandler`_
-
-Native PHP Save Handlers
-------------------------
-
-So-called native handlers, are save handlers which are either compiled into
-PHP or provided by PHP extensions, such as PHP-SQLite, PHP-Memcached and so on.
-
-All native save handlers are internal to PHP and as such, have no public facing API.
-They must be configured by ``php.ini`` directives, usually ``session.save_path`` and
-potentially other driver specific directives. Specific details can be found in
-the docblock of the ``setOptions()`` method of each class. For instance, the one
-provided by the Memcached extension can be found on :phpmethod:`php.net `.
-
-While native save handlers can be activated by directly using
-``ini_set('session.save_handler', $name);``, Symfony provides a convenient way to
-activate these in the same way as it does for custom handlers.
-
-Symfony provides drivers for the following native save handler as an example:
-
-* :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeFileSessionHandler`
-
-Example usage::
-
- use Symfony\Component\HttpFoundation\Session\Session;
- use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler;
- use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
-
- $sessionStorage = new NativeSessionStorage([], new NativeFileSessionHandler());
- $session = new Session($sessionStorage);
-
-.. note::
-
- With the exception of the ``files`` handler which is built into PHP and
- always available, the availability of the other handlers depends on those
- PHP extensions being active at runtime.
-
-.. note::
-
- Native save handlers provide a quick solution to session storage, however,
- in complex systems where you need more control, custom save handlers may
- provide more freedom and flexibility. Symfony provides several implementations
- which you may further customize as required.
-
-Custom Save Handlers
---------------------
-
-Custom handlers are those which completely replace PHP's built-in session save
-handlers by providing six callback functions which PHP calls internally at
-various points in the session workflow.
-
-The Symfony HttpFoundation component provides some by default and these can
-serve as examples if you wish to write your own.
-
-* :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\PdoSessionHandler`
-* :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MemcachedSessionHandler`
-* :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MigratingSessionHandler`
-* :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\RedisSessionHandler`
-* :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MongoDbSessionHandler`
-* :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NullSessionHandler`
-
-Example usage::
-
- use Symfony\Component\HttpFoundation\Session\Session;
- use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
- use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
-
- $pdo = new \PDO(...);
- $sessionStorage = new NativeSessionStorage([], new PdoSessionHandler($pdo));
- $session = new Session($sessionStorage);
-
-Migrating Between Save Handlers
--------------------------------
-
-If your application changes the way sessions are stored, use the
-:class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MigratingSessionHandler`
-to migrate between old and new save handlers without losing session data.
-
-This is the recommended migration workflow:
-
-#. Switch to the migrating handler, with your new handler as the write-only one.
- The old handler behaves as usual and sessions get written to the new one::
-
- $sessionStorage = new MigratingSessionHandler($oldSessionStorage, $newSessionStorage);
-
-#. After your session gc period, verify that the data in the new handler is correct.
-#. Update the migrating handler to use the old handler as the write-only one, so
- the sessions will now be read from the new handler. This step allows easier rollbacks::
-
- $sessionStorage = new MigratingSessionHandler($newSessionStorage, $oldSessionStorage);
-
-#. After verifying that the sessions in your application are working, switch
- from the migrating handler to the new handler.
-
-Configuring PHP Sessions
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-The :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage`
-can configure most of the ``php.ini`` configuration directives which are documented
-at `php.net/session.configuration`_.
-
-To configure these settings, pass the keys (omitting the initial ``session.`` part
-of the key) as a key-value array to the ``$options`` constructor argument.
-Or set them via the
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage::setOptions`
-method.
-
-For the sake of clarity, some key options are explained in this documentation.
-
-Session Cookie Lifetime
-~~~~~~~~~~~~~~~~~~~~~~~
-
-For security, session tokens are generally recommended to be sent as session cookies.
-You can configure the lifetime of session cookies by specifying the lifetime
-(in seconds) using the ``cookie_lifetime`` key in the constructor's ``$options``
-argument in :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage`.
-
-Setting a ``cookie_lifetime`` to ``0`` will cause the cookie to live only as
-long as the browser remains open. Generally, ``cookie_lifetime`` would be set to
-a relatively large number of days, weeks or months. It is not uncommon to set
-cookies for a year or more depending on the application.
-
-Since session cookies are just a client-side token, they are less important in
-controlling the fine details of your security settings which ultimately can only
-be securely controlled from the server side.
-
-.. note::
-
- The ``cookie_lifetime`` setting is the number of seconds the cookie should live
- for, it is not a Unix timestamp. The resulting session cookie will be stamped
- with an expiry time of ``time()`` + ``cookie_lifetime`` where the time is taken
- from the server.
-
-Configuring Garbage Collection
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-When a session opens, PHP will call the ``gc`` handler randomly according to the
-probability set by ``session.gc_probability`` / ``session.gc_divisor``. For
-example if these were set to ``5/100`` respectively, it would mean a probability
-of 5%. Similarly, ``3/4`` would mean a 3 in 4 chance of being called, i.e. 75%.
-
-If the garbage collection handler is invoked, PHP will pass the value stored in
-the ``php.ini`` directive ``session.gc_maxlifetime``. The meaning in this context is
-that any stored session that was saved more than ``gc_maxlifetime`` ago should be
-deleted. This allows one to expire records based on idle time.
-
-However, some operating systems (e.g. Debian) do their own session handling and set
-the ``session.gc_probability`` variable to ``0`` to stop PHP doing garbage
-collection. That's why Symfony now overwrites this value to ``1``.
-
-If you wish to use the original value set in your ``php.ini``, add the following
-configuration:
-
-.. code-block:: yaml
-
- # config/packages/framework.yaml
- framework:
- session:
- gc_probability: null
-
-You can configure these settings by passing ``gc_probability``, ``gc_divisor``
-and ``gc_maxlifetime`` in an array to the constructor of
-:class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage`
-or to the :method:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage::setOptions`
-method.
-
-Session Lifetime
-~~~~~~~~~~~~~~~~
-
-When a new session is created, meaning Symfony issues a new session cookie
-to the client, the cookie will be stamped with an expiry time. This is
-calculated by adding the PHP runtime configuration value in
-``session.cookie_lifetime`` with the current server time.
-
-.. note::
-
- PHP will only issue a cookie once. The client is expected to store that cookie
- for the entire lifetime. A new cookie will only be issued when the session is
- destroyed, the browser cookie is deleted, or the session ID is regenerated
- using the ``migrate()`` or ``invalidate()`` methods of the ``Session`` class.
-
- The initial cookie lifetime can be set by configuring ``NativeSessionStorage``
- using the ``setOptions(['cookie_lifetime' => 1234])`` method.
-
-.. note::
-
- A cookie lifetime of ``0`` means the cookie expires when the browser is closed.
-
-Session Idle Time/Keep Alive
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-There are often circumstances where you may want to protect, or minimize
-unauthorized use of a session when a user steps away from their terminal while
-logged in by destroying the session after a certain period of idle time. For
-example, it is common for banking applications to log the user out after just
-5 to 10 minutes of inactivity. Setting the cookie lifetime here is not
-appropriate because that can be manipulated by the client, so we must do the expiry
-on the server side. The easiest way is to implement this via garbage collection
-which runs reasonably frequently. The ``cookie_lifetime`` would be set to a
-relatively high value, and the garbage collection ``gc_maxlifetime`` would be set
-to destroy sessions at whatever the desired idle period is.
-
-The other option is specifically check if a session has expired after the
-session is started. The session can be destroyed as required. This method of
-processing can allow the expiry of sessions to be integrated into the user
-experience, for example, by displaying a message.
-
-Symfony records some basic metadata about each session to give you complete
-freedom in this area.
-
-Session Cache Limiting
-~~~~~~~~~~~~~~~~~~~~~~
-
-To avoid users seeing stale data, it's common for session-enabled resources to be
-sent with headers that disable caching. For this purpose PHP Sessions has the
-``sessions.cache_limiter`` option, which determines which headers, if any, will be
-sent with the response when the session in started.
-
-Upon construction,
-:class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage`
-sets this global option to ``""`` (send no headers) in case the developer wishes to
-use a :class:`Symfony\\Component\\HttpFoundation\\Response` object to manage
-response headers.
-
-.. caution::
-
- If you rely on PHP Sessions to manage HTTP caching, you *must* manually set the
- ``cache_limiter`` option in
- :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage`
- to a non-empty value.
-
- For example, you may set it to PHP's default value during construction:
-
- Example usage::
-
- use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
-
- $options['cache_limiter'] = session_cache_limiter();
- $sessionStorage = new NativeSessionStorage($options);
-
-Session Metadata
-~~~~~~~~~~~~~~~~
-
-Sessions are decorated with some basic metadata to enable fine control over the
-security settings. The session object has a getter for the metadata,
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getMetadataBag` which
-exposes an instance of :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\MetadataBag`::
-
- $session->getMetadataBag()->getCreated();
- $session->getMetadataBag()->getLastUsed();
-
-Both methods return a Unix timestamp (relative to the server).
-
-This metadata can be used to explicitly expire a session on access, e.g.::
-
- $session->start();
- if (time() - $session->getMetadataBag()->getLastUsed() > $maxIdleTime) {
- $session->invalidate();
- throw new SessionExpired(); // redirect to expired session page
- }
-
-It is also possible to tell what the ``cookie_lifetime`` was set to for a
-particular cookie by reading the ``getLifetime()`` method::
-
- $session->getMetadataBag()->getLifetime();
-
-The expiry time of the cookie can be determined by adding the created
-timestamp and the lifetime.
-
-.. _`php.net/session.customhandler`: https://www.php.net/session.customhandler
-.. _`php.net/session.configuration`: https://www.php.net/session.configuration
diff --git a/components/http_foundation/session_php_bridge.rst b/components/http_foundation/session_php_bridge.rst
deleted file mode 100644
index 00f57e59e4f..00000000000
--- a/components/http_foundation/session_php_bridge.rst
+++ /dev/null
@@ -1,48 +0,0 @@
-.. index::
- single: HTTP
- single: HttpFoundation, Sessions
-
-Integrating with Legacy Sessions
-================================
-
-Sometimes it may be necessary to integrate Symfony into a legacy application
-where you do not initially have the level of control you require.
-
-As stated elsewhere, Symfony Sessions are designed to replace the use of
-PHP's native ``session_*()`` functions and use of the ``$_SESSION``
-superglobal. Additionally, it is mandatory for Symfony to start the session.
-
-However, when there really are circumstances where this is not possible, you
-can use a special storage bridge
-:class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\PhpBridgeSessionStorage`
-which is designed to allow Symfony to work with a session started outside of
-the Symfony HttpFoundation component. You are warned that things can interrupt
-this use-case unless you are careful: for example the legacy application
-erases ``$_SESSION``.
-
-A typical use of this might look like this::
-
- use Symfony\Component\HttpFoundation\Session\Session;
- use Symfony\Component\HttpFoundation\Session\Storage\PhpBridgeSessionStorage;
-
- // legacy application configures session
- ini_set('session.save_handler', 'files');
- ini_set('session.save_path', '/tmp');
- session_start();
-
- // Get Symfony to interface with this existing session
- $session = new Session(new PhpBridgeSessionStorage());
-
- // symfony will now interface with the existing PHP session
- $session->start();
-
-This will allow you to start using the Symfony Session API and allow migration
-of your application to Symfony sessions.
-
-.. note::
-
- Symfony sessions store data like attributes in special 'Bags' which use a
- key in the ``$_SESSION`` superglobal. This means that a Symfony session
- cannot access arbitrary keys in ``$_SESSION`` that may be set by the legacy
- application, although all the ``$_SESSION`` contents will be saved when
- the session is saved.
diff --git a/components/http_foundation/session_testing.rst b/components/http_foundation/session_testing.rst
deleted file mode 100644
index 7d8a570c17e..00000000000
--- a/components/http_foundation/session_testing.rst
+++ /dev/null
@@ -1,58 +0,0 @@
-.. index::
- single: HTTP
- single: HttpFoundation, Sessions
-
-Testing with Sessions
-=====================
-
-Symfony is designed from the ground up with code-testability in mind. In order
-to test your code which utilizes sessions, we provide two separate mock storage
-mechanisms for both unit testing and functional testing.
-
-Testing code using real sessions is tricky because PHP's workflow state is global
-and it is not possible to have multiple concurrent sessions in the same PHP
-process.
-
-The mock storage engines simulate the PHP session workflow without actually
-starting one allowing you to test your code without complications. You may also
-run multiple instances in the same PHP process.
-
-The mock storage drivers do not read or write the system globals
-``session_id()`` or ``session_name()``. Methods are provided to simulate this if
-required:
-
-* :method:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageInterface::getId`: Gets the
- session ID.
-
-* :method:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageInterface::setId`: Sets the
- session ID.
-
-* :method:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageInterface::getName`: Gets the
- session name.
-
-* :method:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageInterface::setName`: Sets the
- session name.
-
-Unit Testing
-------------
-
-For unit testing where it is not necessary to persist the session, you should
-swap out the default storage engine with
-:class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\MockArraySessionStorage`::
-
- use Symfony\Component\HttpFoundation\Session\Session;
- use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
-
- $session = new Session(new MockArraySessionStorage());
-
-Functional Testing
-------------------
-
-For functional testing where you may need to persist session data across
-separate PHP processes, change the storage engine to
-:class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\MockFileSessionStorage`::
-
- use Symfony\Component\HttpFoundation\Session\Session;
- use Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage;
-
- $session = new Session(new MockFileSessionStorage());
diff --git a/components/http_foundation/sessions.rst b/components/http_foundation/sessions.rst
deleted file mode 100644
index 9c9479e3e5e..00000000000
--- a/components/http_foundation/sessions.rst
+++ /dev/null
@@ -1,346 +0,0 @@
-.. index::
- single: HTTP
- single: HttpFoundation, Sessions
-
-Session Management
-==================
-
-The Symfony HttpFoundation component has a very powerful and flexible session
-subsystem which is designed to provide session management through a clear
-object-oriented interface using a variety of session storage drivers.
-
-Sessions are used via the :class:`Symfony\\Component\\HttpFoundation\\Session\\Session`
-implementation of :class:`Symfony\\Component\\HttpFoundation\\Session\\SessionInterface` interface.
-
-.. caution::
-
- Make sure your PHP session isn't already started before using the Session
- class. If you have a legacy session system that starts your session, see
- :doc:`Legacy Sessions `.
-
-Quick example::
-
- use Symfony\Component\HttpFoundation\Session\Session;
-
- $session = new Session();
- $session->start();
-
- // set and get session attributes
- $session->set('name', 'Drak');
- $session->get('name');
-
- // set flash messages
- $session->getFlashBag()->add('notice', 'Profile updated');
-
- // retrieve messages
- foreach ($session->getFlashBag()->get('notice', []) as $message) {
- echo ''.$message.'
';
- }
-
-.. note::
-
- Symfony sessions are designed to replace several native PHP functions.
- Applications should avoid using ``session_start()``, ``session_regenerate_id()``,
- ``session_id()``, ``session_name()``, and ``session_destroy()`` and instead
- use the APIs in the following section.
-
-.. note::
-
- While it is recommended to explicitly start a session, a session will actually
- start on demand, that is, if any session request is made to read/write session
- data.
-
-.. caution::
-
- Symfony sessions are incompatible with ``php.ini`` directive ``session.auto_start = 1``
- This directive should be turned off in ``php.ini``, in the webserver directives or
- in ``.htaccess``.
-
-Session API
-~~~~~~~~~~~
-
-The :class:`Symfony\\Component\\HttpFoundation\\Session\\Session` class implements
-:class:`Symfony\\Component\\HttpFoundation\\Session\\SessionInterface`.
-
-The :class:`Symfony\\Component\\HttpFoundation\\Session\\Session` has the
-following API, divided into a couple of groups.
-
-Session Workflow
-................
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::start`
- Starts the session - do not use ``session_start()``.
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::migrate`
- Regenerates the session ID - do not use ``session_regenerate_id()``.
- This method can optionally change the lifetime of the new cookie that will
- be emitted by calling this method.
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::invalidate`
- Clears all session data and regenerates session ID. Do not use ``session_destroy()``.
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getId`
- Gets the session ID. Do not use ``session_id()``.
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::setId`
- Sets the session ID. Do not use ``session_id()``.
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getName`
- Gets the session name. Do not use ``session_name()``.
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::setName`
- Sets the session name. Do not use ``session_name()``.
-
-Session Attributes
-..................
-
-The session attributes are stored internally in a "Bag", a PHP object that acts
-like an array. They can be set, removed, checked, etc. using the methods
-explained later in this article for the ``AttributeBagInterface`` class. See
-:ref:`attribute-bag-interface`.
-
-In addition, a few methods exist for "Bag" management:
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::registerBag`
- Registers a :class:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface`.
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getBag`
- Gets a :class:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface` by
- bag name.
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getFlashBag`
- Gets the :class:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface`.
- This is just a shortcut for convenience.
-
-Session Metadata
-................
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getMetadataBag`
- Gets the :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\MetadataBag`
- which contains information about the session.
-
-Session Data Management
-~~~~~~~~~~~~~~~~~~~~~~~
-
-PHP's session management requires the use of the ``$_SESSION`` super-global,
-however, this interferes somewhat with code testability and encapsulation in an
-OOP paradigm. To help overcome this, Symfony uses *session bags* linked to the
-session to encapsulate a specific dataset of attributes or flash messages.
-
-This approach also mitigates namespace pollution within the ``$_SESSION``
-super-global because each bag stores all its data under a unique namespace.
-This allows Symfony to peacefully co-exist with other applications or libraries
-that might use the ``$_SESSION`` super-global and all data remains completely
-compatible with Symfony's session management.
-
-Symfony provides two kinds of storage bags, with two separate implementations.
-Everything is written against interfaces so you may extend or create your own
-bag types if necessary.
-
-:class:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface` has
-the following API which is intended mainly for internal purposes:
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface::getStorageKey`
- Returns the key which the bag will ultimately store its array under in ``$_SESSION``.
- Generally this value can be left at its default and is for internal use.
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface::initialize`
- This is called internally by Symfony session storage classes to link bag data
- to the session.
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface::getName`
- Returns the name of the session bag.
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface::clear`
- Clears out data from bag.
-
-.. _attribute-bag-interface:
-
-Attributes
-~~~~~~~~~~
-
-The purpose of the bags implementing the :class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface`
-is to handle session attribute storage. This might include things like user ID,
-and "Remember Me" login settings or other user based state information.
-
-:class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBag`
- This is the standard default implementation.
-
-:class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\NamespacedAttributeBag`
- This implementation allows for attributes to be stored in a structured namespace.
-
-:class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface`
-has the API
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::set`
- Sets an attribute by name (``set('name', 'value')``).
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::get`
- Gets an attribute by name (``get('name')``) and can define a default
- value when the attribute doesn't exist (``get('name', 'default_value')``).
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::all`
- Gets all attributes as an associative array of ``name => value``.
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::has`
- Returns ``true`` if the attribute exists.
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::replace`
- Sets multiple attributes at once using an associative array (``name => value``).
- If the attributes existed, they are replaced; if not, they are created.
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::remove`
- Deletes an attribute by name and returns its value.
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::clear`
- Deletes all attributes.
-
-Example::
-
- use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
- use Symfony\Component\HttpFoundation\Session\Session;
- use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
-
- $session = new Session(new NativeSessionStorage(), new AttributeBag());
- $session->set('token', 'a6c1e0b6');
- // ...
- $token = $session->get('token');
- // if the attribute may or may not exist, you can define a default value for it
- $token = $session->get('attribute-name', 'default-attribute-value');
- // ...
- $session->clear();
-
-.. _namespaced-attributes:
-
-Namespaced Attributes
-.....................
-
-Any plain key-value storage system is limited in the extent to which
-complex data can be stored since each key must be unique. You can achieve
-namespacing by introducing a naming convention to the keys so different parts of
-your application could operate without clashing. For example, ``module1.foo`` and
-``module2.foo``. However, sometimes this is not very practical when the attributes
-data is an array, for example a set of tokens. In this case, managing the array
-becomes a burden because you have to retrieve the array then process it and
-store it again::
-
- $tokens = [
- 'tokens' => [
- 'a' => 'a6c1e0b6',
- 'b' => 'f4a7b1f3',
- ],
- ];
-
-So any processing of this might quickly get ugly, even adding a token to the array::
-
- $tokens = $session->get('tokens');
- $tokens['c'] = $value;
- $session->set('tokens', $tokens);
-
-With structured namespacing, the key can be translated to the array
-structure like this using a namespace character (which defaults to ``/``)::
-
- // ...
- use Symfony\Component\HttpFoundation\Session\Attribute\NamespacedAttributeBag;
-
- $session = new Session(new NativeSessionStorage(), new NamespacedAttributeBag());
- $session->set('tokens/c', $value);
-
-Flash Messages
-~~~~~~~~~~~~~~
-
-The purpose of the :class:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface`
-is to provide a way of setting and retrieving messages on a per session basis.
-The usual workflow would be to set flash messages in a request and to display them
-after a page redirect. For example, a user submits a form which hits an update
-controller, and after processing the controller redirects the page to either the
-updated page or an error page. Flash messages set in the previous page request
-would be displayed immediately on the subsequent page load for that session.
-This is however just one application for flash messages.
-
-:class:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\AutoExpireFlashBag`
- In this implementation, messages set in one page-load will
- be available for display only on the next page load. These messages will auto
- expire regardless of if they are retrieved or not.
-
-:class:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBag`
- In this implementation, messages will remain in the session until
- they are explicitly retrieved or cleared. This makes it possible to use ESI
- caching.
-
-:class:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface`
-has the API
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::add`
- Adds a flash message to the stack of specified type.
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::set`
- Sets flashes by type; This method conveniently takes both single messages as
- a ``string`` or multiple messages in an ``array``.
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::get`
- Gets flashes by type and clears those flashes from the bag.
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::setAll`
- Sets all flashes, accepts a keyed array of arrays ``type => [messages]``.
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::all`
- Gets all flashes (as a keyed array of arrays) and clears the flashes from the bag.
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::peek`
- Gets flashes by type (read only).
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::peekAll`
- Gets all flashes (read only) as keyed array of arrays.
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::has`
- Returns true if the type exists, false if not.
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::keys`
- Returns an array of the stored flash types.
-
-:method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::clear`
- Clears the bag.
-
-For simple applications it is usually sufficient to have one flash message per
-type, for example a confirmation notice after a form is submitted. However,
-flash messages are stored in a keyed array by flash ``$type`` which means your
-application can issue multiple messages for a given type. This allows the API
-to be used for more complex messaging in your application.
-
-Examples of setting multiple flashes::
-
- use Symfony\Component\HttpFoundation\Session\Session;
-
- $session = new Session();
- $session->start();
-
- // add flash messages
- $session->getFlashBag()->add(
- 'warning',
- 'Your config file is writable, it should be set read-only'
- );
- $session->getFlashBag()->add('error', 'Failed to update name');
- $session->getFlashBag()->add('error', 'Another error');
-
-Displaying the flash messages might look as follows.
-
-Display one type of message::
-
- // display warnings
- foreach ($session->getFlashBag()->get('warning', []) as $message) {
- echo ''.$message.'
';
- }
-
- // display errors
- foreach ($session->getFlashBag()->get('error', []) as $message) {
- echo ''.$message.'
';
- }
-
-Compact method to process display all flashes at once::
-
- foreach ($session->getFlashBag()->all() as $type => $messages) {
- foreach ($messages as $message) {
- echo ''.$message.'
';
- }
- }
diff --git a/components/http_kernel.rst b/components/http_kernel.rst
index c0da0fd6cfa..62d1e92d89b 100644
--- a/components/http_kernel.rst
+++ b/components/http_kernel.rst
@@ -1,15 +1,10 @@
-.. index::
- single: HTTP
- single: HttpKernel
- single: Components; HttpKernel
-
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 system (Drupal).
+ component. It's flexible enough to create a full-stack framework (Symfony)
+ or an advanced CMS (Drupal).
Installation
------------
@@ -20,8 +15,10 @@ Installation
.. include:: /components/require_autoload.rst.inc
-The Workflow of a Request
--------------------------
+.. _the-workflow-of-a-request:
+
+The Request-Response Lifecycle
+------------------------------
.. seealso::
@@ -31,11 +28,10 @@ The Workflow of a Request
:doc:`/event_dispatcher` articles to learn about how to use it to create
controllers and define events in Symfony applications.
-
Every HTTP web interaction begins with a request and ends with a response.
Your job as a developer is to create PHP code that reads the request information
(e.g. the URL) and creates and returns a response (e.g. an HTML page or JSON string).
-This is a simplified overview of the request workflow in Symfony applications:
+This is a simplified overview of the request-response lifecycle in Symfony applications:
#. The **user** asks for a **resource** in a **browser**;
#. The **browser** sends a **request** to the **server**;
@@ -65,21 +61,23 @@ that system::
*/
public function handle(
Request $request,
- int $type = self::MASTER_REQUEST,
+ int $type = self::MAIN_REQUEST,
bool $catch = true
- );
+ ): Response;
}
Internally, :method:`HttpKernel::handle() ` -
the concrete implementation of :method:`HttpKernelInterface::handle() ` -
-defines a workflow that starts with a :class:`Symfony\\Component\\HttpFoundation\\Request`
+defines a lifecycle that starts with a :class:`Symfony\\Component\\HttpFoundation\\Request`
and ends with a :class:`Symfony\\Component\\HttpFoundation\\Response`.
.. raw:: html
-
+
-The exact details of this workflow are the key to understanding how the kernel
+The exact details of this lifecycle are the key to understanding how the kernel
(and the Symfony Framework or any other library that uses the kernel) works.
HttpKernel: Driven by Events
@@ -131,17 +129,10 @@ listeners to the events discussed below::
// trigger the kernel.terminate event
$kernel->terminate($request, $response);
-See ":ref:`http-kernel-working-example`" for a more concrete implementation.
+See ":ref:`A full working example `" for a more concrete implementation.
For general information on adding listeners to the events below, see
-:ref:`http-kernel-creating-listener`.
-
-.. caution::
-
- As of 3.1 the :class:`Symfony\\Component\\HttpKernel\\HttpKernel` accepts a
- fourth argument, which must be an instance of
- :class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolverInterface`.
- In 4.0 this argument will become mandatory.
+:ref:`Creating an Event Listener `.
.. seealso::
@@ -236,7 +227,7 @@ This implementation is explained more in the sidebar below::
interface ControllerResolverInterface
{
- public function getController(Request $request);
+ public function getController(Request $request): callable|false;
}
Internally, the ``HttpKernel::handle()`` method first calls
@@ -249,7 +240,7 @@ on the request's information.
The Symfony Framework uses the built-in
:class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver`
- class (actually, it uses a sub-class with some extra functionality
+ class (actually, it uses a subclass with some extra functionality
mentioned below). This class leverages the information that was placed
on the ``Request`` object's ``attributes`` property during the ``RouterListener``.
@@ -270,11 +261,6 @@ on the request's information.
b) A new instance of your controller class is instantiated with no
constructor arguments.
- c) If the controller implements :class:`Symfony\\Component\\DependencyInjection\\ContainerAwareInterface`,
- ``setContainer()`` is called on the controller object and the container
- is passed to it. This step is also specific to the :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerResolver`
- sub-class used by the Symfony Framework.
-
.. _component-http-kernel-kernel-controller:
3) The ``kernel.controller`` Event
@@ -289,7 +275,11 @@ After the controller callable has been determined, ``HttpKernel::handle()``
dispatches the ``kernel.controller`` event. Listeners to this event might initialize
some part of the system that needs to be initialized after certain things
have been determined (e.g. the controller, routing information) but before
-the controller is executed. For some examples, see the Symfony section below.
+the controller is executed.
+
+Another typical use-case for this event is to retrieve the attributes from
+the controller using the :method:`Symfony\\Component\\HttpKernel\\Event\\ControllerEvent::getAttributes`
+method. See the Symfony section below for some examples.
Listeners to this event can also change the controller callable completely
by calling :method:`ControllerEvent::setController `
@@ -297,18 +287,15 @@ on the event object that's passed to listeners on this event.
.. sidebar:: ``kernel.controller`` in the Symfony Framework
- There are a few minor listeners to the ``kernel.controller`` event in
- the Symfony Framework, and many deal with collecting profiler data when
- the profiler is enabled.
+ An interesting listener to ``kernel.controller`` in the Symfony
+ Framework is :class:`Symfony\\Component\\HttpKernel\\EventListener\\CacheAttributeListener`.
+ This class fetches ``#[Cache]`` attribute configuration from the
+ controller and uses it to configure :doc:`HTTP caching `
+ on the response.
- One interesting listener comes from the `SensioFrameworkExtraBundle`_. This
- listener's `@ParamConverter`_ functionality allows you to pass a full object
- (e.g. a ``Post`` object) to your controller instead of a scalar value (e.g.
- an ``id`` parameter that was on your route). The listener -
- ``ParamConverterListener`` - uses reflection to look at each of the
- arguments of the controller and tries to use different methods to convert
- those to objects, which are then stored in the ``attributes`` property of
- the ``Request`` object. Read the next section to see why this is important.
+ There are a few other minor listeners to the ``kernel.controller`` event in
+ the Symfony Framework that deal with collecting profiler data when the
+ profiler is enabled.
4) Getting the Controller Arguments
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -347,10 +334,10 @@ of arguments that should be passed when executing that callable.
available through the `variadic`_ argument.
This functionality is provided by resolvers implementing the
- :class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolverInterface`.
+ :class:`Symfony\\Component\\HttpKernel\\Controller\\ValueResolverInterface`.
There are four implementations which provide the default behavior of
Symfony but customization is the key here. By implementing the
- ``ArgumentValueResolverInterface`` yourself and passing this to the
+ ``ValueResolverInterface`` yourself and passing this to the
``ArgumentResolver``, you can extend this functionality.
.. _component-http-kernel-calling-controller:
@@ -358,7 +345,7 @@ of arguments that should be passed when executing that callable.
5) Calling the Controller
~~~~~~~~~~~~~~~~~~~~~~~~~
-The next step ``HttpKernel::handle()`` does is executing the controller.
+The next step of ``HttpKernel::handle()`` is executing the controller.
The job of the controller is to build the response for the given resource.
This could be an HTML page, a JSON string or anything else. Unlike every
@@ -409,12 +396,12 @@ return a ``Response``.
.. sidebar:: ``kernel.view`` in the Symfony Framework
- There is no default listener inside the Symfony Framework for the ``kernel.view``
- event. However, `SensioFrameworkExtraBundle`_ *does* add a listener to this
- event. If your controller returns an array, and you place the `@Template`_
- annotation above the controller, 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.
+ 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
+ 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.
Additionally, a popular community bundle `FOSRestBundle`_ implements
a listener on this event which aims to give you a robust view layer
@@ -484,11 +471,11 @@ 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
- API is able to send a response to the client while the server's PHP process
+ PHP function. This means that at the moment, only the `PHP FPM`_ API and the
+ `FrankenPHP`_ server are able to send a response to the client while the server's PHP process
still performs some tasks. With all other server APIs, listeners to ``kernel.terminate``
are still executed, but the response is not sent to the client until they
are all completed.
@@ -498,16 +485,10 @@ as possible to the client (e.g. sending emails).
Using the ``kernel.terminate`` event is optional, and should only be
called if your kernel implements :class:`Symfony\\Component\\HttpKernel\\TerminableInterface`.
-.. sidebar:: ``kernel.terminate`` in the Symfony Framework
-
- If you use the :ref:`memory spooling ` option of the
- default Symfony mailer, then the `EmailSenderListener`_ is activated, which
- actually delivers any emails that you scheduled to send during the request.
-
.. _component-http-kernel-kernel-exception:
-Handling Exceptions: the ``kernel.exception`` Event
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+9) Handling Exceptions: the ``kernel.exception`` Event
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Typical Purposes**: Handle some type of exception and create an appropriate
``Response`` to return for the exception
@@ -515,14 +496,16 @@ Handling Exceptions: the ``kernel.exception`` Event
:ref:`Kernel Events Information Table `
If an exception is thrown at any point inside ``HttpKernel::handle()``, another
-event - ``kernel.exception`` is thrown. Internally, the body of the ``handle()``
-function is wrapped in a try-catch block. When any exception is thrown, the
+event - ``kernel.exception`` is dispatched. Internally, the body of the ``handle()``
+method is wrapped in a try-catch block. When any exception is thrown, the
``kernel.exception`` event is dispatched so that your system can somehow respond
to the exception.
.. raw:: html
-
+
Each listener to this event is passed a :class:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent`
object, which you can use to access the original exception via the
@@ -537,6 +520,17 @@ comes with an :class:`Symfony\\Component\\HttpKernel\\EventListener\\ErrorListen
which if you choose to use, will do this and more by default (see the sidebar
below for more details).
+The :class:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent` exposes the
+:method:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent::isKernelTerminating`
+method, which you can use to determine if the kernel is currently terminating
+at the moment the exception was thrown.
+
+.. versionadded:: 7.1
+
+ The
+ :method:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent::isKernelTerminating`
+ method was introduced in Symfony 7.1.
+
.. note::
When setting a response for the ``kernel.exception`` event, the propagation
@@ -596,7 +590,7 @@ on creating and attaching event listeners, see :doc:`/components/event_dispatche
The name of each of the "kernel" events is defined as a constant on the
:class:`Symfony\\Component\\HttpKernel\\KernelEvents` class. Additionally, each
-event listener is passed a single argument, which is some sub-class of :class:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent`.
+event listener is passed a single argument, which is some subclass of :class:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent`.
This object contains information about the current state of the system and
each event has their own event object:
@@ -643,7 +637,7 @@ else that can be used to create a working example::
$routes = new RouteCollection();
$routes->add('hello', new Route('/hello/{name}', [
- '_controller' => function (Request $request) {
+ '_controller' => function (Request $request): Response {
return new Response(
sprintf("Hello %s", $request->get('name'))
);
@@ -673,7 +667,7 @@ Sub Requests
------------
In addition to the "main" request that's sent into ``HttpKernel::handle()``,
-you can also send so-called "sub request". A sub request looks and acts like
+you can also send a so-called "sub request". A sub request looks and acts like
any other request, but typically serves to render just one small portion of
a page instead of a full page. You'll most commonly make sub-requests from
your controller (or perhaps from inside a template, that's being rendered by
@@ -681,7 +675,9 @@ your controller).
.. raw:: html
-
+
To execute a sub request, use ``HttpKernel::handle()``, but change the second
argument as follows::
@@ -701,45 +697,51 @@ argument as follows::
This creates another full request-response cycle where this new ``Request`` is
transformed into a ``Response``. The only difference internally is that some
-listeners (e.g. security) may only act upon the master request. Each listener
-is passed some sub-class of :class:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent`,
-whose :method:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent::isMasterRequest`
-can be used to check if the current request is a "master" or "sub" request.
+listeners (e.g. security) may only act upon the main request. Each listener
+is passed some subclass of :class:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent`,
+whose :method:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent::isMainRequest`
+method can be used to check if the current request is a "main" or "sub" request.
-For example, a listener that only needs to act on the master request may
+For example, a listener that only needs to act on the main request may
look like this::
use Symfony\Component\HttpKernel\Event\RequestEvent;
// ...
- public function onKernelRequest(RequestEvent $event)
+ public function onKernelRequest(RequestEvent $event): void
{
- if (!$event->isMasterRequest()) {
+ if (!$event->isMainRequest()) {
return;
}
// ...
}
+.. note::
+
+ The default value of the ``_format`` request attribute is ``html``. If your
+ sub request returns a different format (e.g. ``json``) you can set it by
+ defining the ``_format`` attribute explicitly on the request::
+
+ $request->attributes->set('_format', 'json');
+
.. _http-kernel-resource-locator:
Locating Resources
------------------
The HttpKernel component is responsible of the bundle mechanism used in Symfony
-applications. The key feature of the bundles is that they allow to override any
-resource used by the application (config files, templates, controllers,
-translation files, etc.)
+applications. One of the key features of the bundles is that you can use logic
+paths instead of physical paths to refer to any of their resources (config files,
+templates, controllers, translation files, etc.)
-This overriding mechanism works because resources are referenced not by their
-physical path but by their logical path. For example, the ``services.xml`` file
-stored in the ``Resources/config/`` directory of a bundle called FooBundle is
-referenced as ``@FooBundle/Resources/config/services.xml``. This logical path
-will work when the application overrides that file and even if you change the
-directory of FooBundle.
+This allows to import resources even if you don't know where in the filesystem a
+bundle will be installed. For example, the ``services.xml`` file stored in the
+``Resources/config/`` directory of a bundle called FooBundle can be referenced as
+``@FooBundle/Resources/config/services.xml`` instead of ``__DIR__/Resources/config/services.xml``.
-The HttpKernel component provides a method called :method:`Symfony\\Component\\HttpKernel\\Kernel::locateResource`
-which can be used to transform logical paths into physical paths::
+This is possible thanks to the :method:`Symfony\\Component\\HttpKernel\\Kernel::locateResource`
+method provided by the kernel, which transforms logical paths into physical paths::
$path = $kernel->locateResource('@FooBundle/Resources/config/services.xml');
@@ -755,8 +757,5 @@ Learn more
.. _reflection: https://www.php.net/manual/en/book.reflection.php
.. _FOSRestBundle: https://github.com/friendsofsymfony/FOSRestBundle
.. _`PHP FPM`: https://www.php.net/manual/en/install.fpm.php
-.. _`SensioFrameworkExtraBundle`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html
-.. _`@ParamConverter`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
-.. _`@Template`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/view.html
-.. _`EmailSenderListener`: https://github.com/symfony/swiftmailer-bundle/blob/master/EventListener/EmailSenderListener.php
.. _variadic: https://www.php.net/manual/en/functions.arguments.php#functions.variable-arg-list
+.. _`FrankenPHP`: https://frankenphp.dev
diff --git a/components/index.rst b/components/index.rst
deleted file mode 100644
index bf28bf3b5d8..00000000000
--- a/components/index.rst
+++ /dev/null
@@ -1,16 +0,0 @@
-The Components
-==============
-
-.. seealso::
-
- See the dedicated `Symfony Components`_ webpage for a full overview of decoupled
- and reusable Symfony components.
-
-.. toctree::
- :maxdepth: 1
- :glob:
-
- using_components
- *
-
-.. _`Symfony Components`: https://symfony.com/components
diff --git a/components/inflector.rst b/components/inflector.rst
deleted file mode 100644
index c42d6ebaeaa..00000000000
--- a/components/inflector.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-.. index::
- single: Inflector
- single: Components; Inflector
-
-The Inflector Component
-=======================
-
-.. deprecated:: 5.1
-
- The Inflector component was deprecated in Symfony 5.1 and its code was moved
- into the :doc:`String ` component.
- :ref:`Read the new Inflector docs `.
diff --git a/components/intl.rst b/components/intl.rst
index cb120034615..ba3cbdcb959 100644
--- a/components/intl.rst
+++ b/components/intl.rst
@@ -1,19 +1,7 @@
-.. index::
- single: Intl
- single: Components; Intl
-
The Intl Component
==================
This component provides access to the localization data of the `ICU library`_.
- It also provides a PHP replacement layer for the C `intl extension`_.
-
-.. caution::
-
- The replacement layer is limited to the ``en`` locale. If you want to use
- other locales, you should `install the intl extension`_. There is no conflict
- between the two because, even if you use the extension, this package can still
- be useful to access the ICU data.
.. seealso::
@@ -30,30 +18,6 @@ Installation
.. include:: /components/require_autoload.rst.inc
-If you install the component via Composer, the following classes and functions
-of the intl extension will be automatically provided if the intl extension is
-not loaded:
-
-* :phpclass:`Collator`
-* :phpclass:`IntlDateFormatter`
-* :phpclass:`Locale`
-* :phpclass:`NumberFormatter`
-* :phpfunction:`intl_error_name`
-* :phpfunction:`intl_is_failure`
-* :phpfunction:`intl_get_error_code`
-* :phpfunction:`intl_get_error_message`
-
-When the intl extension is not available, the following classes are used to
-replace the intl classes:
-
-* :class:`Symfony\\Component\\Intl\\Collator\\Collator`
-* :class:`Symfony\\Component\\Intl\\DateFormatter\\IntlDateFormatter`
-* :class:`Symfony\\Component\\Intl\\Locale\\Locale`
-* :class:`Symfony\\Component\\Intl\\NumberFormatter\\NumberFormatter`
-* :class:`Symfony\\Component\\Intl\\Globals\\IntlGlobals`
-
-Composer automatically exposes these classes in the global namespace.
-
Accessing ICU Data
------------------
@@ -68,8 +32,8 @@ This component provides the following ICU data:
Language and Script Names
~~~~~~~~~~~~~~~~~~~~~~~~~
-The ``Languages`` class provides access to the name of all languages
-according to the `ISO 639-1 alpha-2`_ list and the `ISO 639-2 alpha-3`_ list::
+The :class:`Symfony\\Component\\Intl\\Languages` class provides access to the name of all languages
+according to the `ISO 639-1 alpha-2`_ list and the `ISO 639-2 alpha-3 (2T)`_ list::
use Symfony\Component\Intl\Languages;
@@ -110,7 +74,7 @@ to catching the exception, you can also check if a given language code is valid:
$isValidLanguage = Languages::exists($languageCode);
-Or if you have a alpha3 language code you want to check::
+Or if you have an alpha3 language code you want to check::
$isValidLanguage = Languages::alpha3CodeExists($alpha3Code);
@@ -120,7 +84,7 @@ You may convert codes between two-letter alpha2 and three-letter alpha3 codes::
$alpha2Code = Languages::getAlpha2Code($alpha3Code);
-The ``Scripts`` class provides access to the optional four-letter script code
+The :class:`Symfony\\Component\\Intl\\Scripts` class provides access to the optional four-letter script code
that can follow the language code according to the `Unicode ISO 15924 Registry`_
(e.g. ``HANS`` in ``zh_HANS`` for simplified Chinese and ``HANT`` in ``zh_HANT``
for traditional Chinese)::
@@ -154,9 +118,9 @@ to catching the exception, you can also check if a given script code is valid::
Country Names
~~~~~~~~~~~~~
-The ``Countries`` class provides access to the name of all countries according
-to the `ISO 3166-1 alpha-2`_ list and the `ISO 3166-1 alpha-3`_ list
-of officially recognized countries and territories::
+The :class:`Symfony\\Component\\Intl\\Countries` class provides access to the
+name of all countries according to the `ISO 3166-1 alpha-2`_ list and the
+`ISO 3166-1 alpha-3`_ list of officially recognized countries and territories::
use Symfony\Component\Intl\Countries;
@@ -197,7 +161,7 @@ to catching the exception, you can also check if a given country code is valid::
$isValidCountry = Countries::exists($alpha2Code);
-Or if you have a alpha3 country code you want to check::
+Or if you have an alpha3 country code you want to check::
$isValidCountry = Countries::alpha3CodeExists($alpha3Code);
@@ -207,14 +171,45 @@ You may convert codes between two-letter alpha2 and three-letter alpha3 codes::
$alpha2Code = Countries::getAlpha2Code($alpha3Code);
+Numeric Country Codes
+~~~~~~~~~~~~~~~~~~~~~
+
+The `ISO 3166-1 numeric`_ standard defines three-digit country codes to represent
+countries, dependent territories, and special areas of geographical interest.
+
+The main advantage over the ISO 3166-1 alphabetic codes (alpha-2 and alpha-3) is
+that these numeric codes are independent from the writing system. The alphabetic
+codes use the 26-letter English alphabet, which might be unavailable or difficult
+to use for people and systems using non-Latin scripts (e.g. Arabic or Japanese).
+
+The :class:`Symfony\\Component\\Intl\\Countries` class provides access to these
+numeric country codes::
+
+ use Symfony\Component\Intl\Countries;
+
+ \Locale::setDefault('en');
+
+ $numericCodes = Countries::getNumericCodes();
+ // ('alpha2Code' => 'numericCode')
+ // => ['AA' => '958', 'AD' => '020', ...]
+
+ $numericCode = Countries::getNumericCode('FR');
+ // => '250'
+
+ $alpha2 = Countries::getAlpha2FromNumeric('250');
+ // => 'FR'
+
+ $exists = Countries::numericCodeExists('250');
+ // => true
+
Locales
~~~~~~~
A locale is the combination of a language, a region and some parameters that
-define the interface preferences of the user. For example, "Chinese" is the
-language and ``zh_Hans_MO`` is the locale for "Chinese" (language) + "Simplified"
-(script) + "Macau SAR China" (region). The ``Locales`` class provides access to
-the name of all locales::
+define the interface preferences of the user. For example, "Chinese" is the
+language and ``zh_Hans_MO`` is the locale for "Chinese" (language) + "Simplified"
+(script) + "Macau SAR China" (region). The :class:`Symfony\\Component\\Intl\\Locales`
+class provides access to the name of all locales::
use Symfony\Component\Intl\Locales;
@@ -245,8 +240,8 @@ to catching the exception, you can also check if a given locale code is valid::
Currencies
~~~~~~~~~~
-The ``Currencies`` class provides access to the name of all currencies as well
-as some of their information (symbol, fraction digits, etc.)::
+The :class:`Symfony\\Component\\Intl\\Currencies` class provides access to the name
+of all currencies as well as some of their information (symbol, fraction digits, etc.)::
use Symfony\Component\Intl\Currencies;
@@ -262,15 +257,37 @@ as some of their information (symbol, fraction digits, etc.)::
$symbol = Currencies::getSymbol('INR');
// => '₹'
- $fractionDigits = Currencies::getFractionDigits('INR');
- // => 2
+The fraction digits methods return the number of decimal digits to display when
+formatting numbers with this currency. Depending on the currency, this value
+can change if the number is used in cash transactions or in other scenarios
+(e.g. accounting)::
+
+ // Indian rupee defines the same value for both
+ $fractionDigits = Currencies::getFractionDigits('INR'); // returns: 2
+ $cashFractionDigits = Currencies::getCashFractionDigits('INR'); // returns: 2
- $roundingIncrement = Currencies::getRoundingIncrement('INR');
- // => 0
+ // Swedish krona defines different values
+ $fractionDigits = Currencies::getFractionDigits('SEK'); // returns: 2
+ $cashFractionDigits = Currencies::getCashFractionDigits('SEK'); // returns: 0
-All methods (except for ``getFractionDigits()`` and ``getRoundingIncrement()``)
-accept the translation locale as the last, optional parameter, which defaults to
-the current default locale::
+Some currencies require to round numbers to the nearest increment of some value
+(e.g. 5 cents). This increment might be different if numbers are formatted for
+cash transactions or other scenarios (e.g. accounting)::
+
+ // Indian rupee defines the same value for both
+ $roundingIncrement = Currencies::getRoundingIncrement('INR'); // returns: 0
+ $cashRoundingIncrement = Currencies::getCashRoundingIncrement('INR'); // returns: 0
+
+ // Canadian dollar defines different values because they have eliminated
+ // the smaller coins (1-cent and 2-cent) and prices in cash must be rounded to
+ // 5 cents (e.g. if price is 7.42 you pay 7.40; if price is 7.48 you pay 7.50)
+ $roundingIncrement = Currencies::getRoundingIncrement('CAD'); // returns: 0
+ $cashRoundingIncrement = Currencies::getCashRoundingIncrement('CAD'); // returns: 5
+
+All methods (except for ``getFractionDigits()``, ``getCashFractionDigits()``,
+``getRoundingIncrement()`` and ``getCashRoundingIncrement()``) accept the
+translation locale as the last, optional parameter, which defaults to the
+current default locale::
$currencies = Currencies::getNames('de');
// => ['AFN' => 'Afghanischer Afghani', 'EGP' => 'Ägyptisches Pfund', ...]
@@ -289,8 +306,9 @@ to catching the exception, you can also check if a given currency code is valid:
Timezones
~~~~~~~~~
-The ``Timezones`` class provides several utilities related to timezones. First,
-you can get the name and values of all timezones in all languages::
+The :class:`Symfony\\Component\\Intl\\Timezones` class provides several utilities
+related to timezones. First, you can get the name and values of all timezones in
+all languages::
use Symfony\Component\Intl\Timezones;
@@ -323,7 +341,7 @@ translate into any locale with the ``getName()`` method shown earlier::
The reverse lookup is also possible thanks to the ``getCountryCode()`` method,
which returns the code of the country where the given timezone ID belongs to::
- $countryCode = Timezones::getCountryCode('America/Vancouver')
+ $countryCode = Timezones::getCountryCode('America/Vancouver');
// => $countryCode = 'CA' (CA = Canada)
The `UTC/GMT time offsets`_ of all timezones are provided by ``getRawOffset()``
@@ -353,8 +371,8 @@ arguments to get the offset at any given point in time::
The string representation of the GMT offset can vary depending on the locale, so
you can pass the locale as the third optional argument::
- $offset = Timezones::getGmtOffset('Europe/Madrid', strtotime('October 28, 2019'), 'ar')); // $offset = 'غرينتش+01:00'
- $offset = Timezones::getGmtOffset('Europe/Madrid', strtotime('October 28, 2019'), 'dz')); // $offset = 'ཇི་ཨེམ་ཏི་+01:00'
+ $offset = Timezones::getGmtOffset('Europe/Madrid', strtotime('October 28, 2019'), 'ar'); // $offset = 'غرينتش+01:00'
+ $offset = Timezones::getGmtOffset('Europe/Madrid', strtotime('October 28, 2019'), 'dz'); // $offset = 'ཇི་ཨེམ་ཏི་+01:00'
If the given timezone ID doesn't exist, the methods trigger a
:class:`Symfony\\Component\\Intl\\Exception\\MissingResourceException`. In addition
@@ -362,6 +380,27 @@ to catching the exception, you can also check if a given timezone ID is valid::
$isValidTimezone = Timezones::exists($timezoneId);
+.. _component-intl-emoji-transliteration:
+
+Emoji Transliteration
+~~~~~~~~~~~~~~~~~~~~~
+
+Symfony provides utilities to translate emojis into their textual representation
+in all languages. Read the documentation about :ref:`emoji transliteration `
+to learn more about this feature.
+
+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 Intl 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/intl/Resources/bin/compress
+
Learn more
----------
@@ -375,13 +414,12 @@ Learn more
/reference/forms/types/locale
/reference/forms/types/timezone
-.. _intl extension: https://www.php.net/manual/en/book.intl.php
-.. _install the intl extension: https://www.php.net/manual/en/intl.setup.php
-.. _ICU library: http://site.icu-project.org/
+.. _ICU library: https://icu.unicode.org/
.. _`Unicode ISO 15924 Registry`: https://www.unicode.org/iso15924/iso15924-codes.html
.. _`ISO 3166-1 alpha-2`: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
.. _`ISO 3166-1 alpha-3`: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3
+.. _`ISO 3166-1 numeric`: https://en.wikipedia.org/wiki/ISO_3166-1_numeric
.. _`UTC/GMT time offsets`: https://en.wikipedia.org/wiki/List_of_UTC_time_offsets
.. _`daylight saving time (DST)`: https://en.wikipedia.org/wiki/Daylight_saving_time
.. _`ISO 639-1 alpha-2`: https://en.wikipedia.org/wiki/ISO_639-1
-.. _`ISO 639-2 alpha-3`: https://en.wikipedia.org/wiki/ISO_639-2
+.. _`ISO 639-2 alpha-3 (2T)`: https://en.wikipedia.org/wiki/ISO_639-2
diff --git a/components/ldap.rst b/components/ldap.rst
index 8e73b077760..e52a341986c 100644
--- a/components/ldap.rst
+++ b/components/ldap.rst
@@ -1,7 +1,3 @@
-.. index::
- single: Ldap
- single: Components; Ldap
-
The Ldap Component
==================
@@ -74,10 +70,23 @@ 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.
+You can also use the :method:`Symfony\\Component\\Ldap\\Ldap::saslBind` method
+for binding to an LDAP server using `SASL`_::
+
+ // this method defines other optional arguments like $mech, $realm, $authcId, etc.
+ $ldap->saslBind($dn, $password);
+
+After binding to the LDAP server, you can use the :method:`Symfony\\Component\\Ldap\\Ldap::whoami`
+method to get the distinguished name (DN) of the authenticated and authorized user.
+
+.. versionadded:: 7.2
+
+ The ``saslBind()`` and ``whoami()`` methods were introduced in Symfony 7.2.
+
Once bound (or if you enabled anonymous authentication on your
LDAP server), you may query the LDAP server using the
:method:`Symfony\\Component\\Ldap\\Ldap::query` method::
@@ -115,6 +124,10 @@ to the ``LDAP_SCOPE_BASE`` scope of :phpfunction:`ldap_read`) and ``SCOPE_ONE``
$query = $ldap->query('dc=symfony,dc=com', '...', ['scope' => QueryInterface::SCOPE_ONE]);
+Use the ``filter`` option to only retrieve some specific attributes:
+
+ $query = $ldap->query('dc=symfony,dc=com', '...', ['filter' => ['cn', 'mail']);
+
Creating or Updating Entries
----------------------------
@@ -139,6 +152,13 @@ delete existing ones::
$query = $ldap->query('dc=symfony,dc=com', '(&(objectclass=person)(ou=Maintainers))');
$result = $query->execute();
$entry = $result[0];
+
+ $phoneNumber = $entry->getAttribute('phoneNumber');
+ $isContractor = $entry->hasAttribute('contractorCompany');
+ // attribute names in getAttribute() and hasAttribute() methods are case-sensitive
+ // pass FALSE as the second method argument to make them case-insensitive
+ $isContractor = $entry->hasAttribute('contractorCompany', false);
+
$entry->setAttribute('email', ['fabpot@symfony.com']);
$entryManager->update($entry);
@@ -176,3 +196,5 @@ Possible operation types are ``LDAP_MODIFY_BATCH_ADD``, ``LDAP_MODIFY_BATCH_REMO
``LDAP_MODIFY_BATCH_REMOVE_ALL``, ``LDAP_MODIFY_BATCH_REPLACE``. Parameter
``$values`` must be ``NULL`` when using ``LDAP_MODIFY_BATCH_REMOVE_ALL``
operation type.
+
+.. _`SASL`: https://en.wikipedia.org/wiki/Simple_Authentication_and_Security_Layer
diff --git a/components/lock.rst b/components/lock.rst
index 09c7abab8eb..b8ba38c8fc7 100644
--- a/components/lock.rst
+++ b/components/lock.rst
@@ -1,13 +1,12 @@
-.. index::
- single: Lock
- single: Components; Lock
-
The Lock Component
==================
The Lock Component creates and manages `locks`_, a mechanism to provide
exclusive access to a shared resource.
+If you're using the Symfony Framework, read the
+:doc:`Symfony Framework Lock documentation `.
+
Installation
------------
@@ -39,11 +38,11 @@ resource. Then, a call to the :method:`Symfony\\Component\\Lock\\LockInterface::
method will try to acquire the lock::
// ...
- $lock = $factory->createLock('pdf-invoice-generation');
+ $lock = $factory->createLock('pdf-creation');
if ($lock->acquire()) {
- // The resource "pdf-invoice-generation" is locked.
- // You can compute and generate invoice safely here.
+ // The resource "pdf-creation" is locked.
+ // You can compute and generate the invoice safely here.
$lock->release();
}
@@ -53,50 +52,116 @@ method can be safely called repeatedly, even if the lock is already acquired.
.. note::
- Unlike other implementations, the Lock Component distinguishes locks
- instances even when they are created for the same resource. If a lock has
- to be used by several services, they should share the same ``Lock`` instance
- returned by the ``LockFactory::createLock`` method.
+ Unlike other implementations, the Lock Component distinguishes lock
+ instances even when they are created for the same resource. It means that for
+ a given scope and resource one lock instance can be acquired multiple times.
+ If a lock has to be used by several services, they should share the same ``Lock``
+ instance returned by the ``LockFactory::createLock`` method.
.. tip::
If you don't release the lock explicitly, it will be released automatically
- on instance destruction. In some cases, it can be useful to lock a resource
+ upon instance destruction. In some cases, it can be useful to lock a resource
across several requests. To disable the automatic release behavior, set the
third argument of the ``createLock()`` method to ``false``.
+Serializing Locks
+-----------------
+
+The :class:`Symfony\\Component\\Lock\\Key` contains the state of the
+:class:`Symfony\\Component\\Lock\\Lock` and can be serialized. This
+allows the user to begin a long job in a process by acquiring the lock, and
+continue the job in another process using the same lock.
+
+First, you may create a serializable class containing the resource and the
+key of the lock::
+
+ // src/Lock/RefreshTaxonomy.php
+ namespace App\Lock;
+
+ use Symfony\Component\Lock\Key;
+
+ class RefreshTaxonomy
+ {
+ public function __construct(
+ private object $article,
+ private Key $key,
+ ) {
+ }
+
+ public function getArticle(): object
+ {
+ return $this->article;
+ }
+
+ public function getKey(): Key
+ {
+ return $this->key;
+ }
+ }
+
+Then, you can use this class to dispatch all that's needed for another process
+to handle the rest of the job::
+
+ use App\Lock\RefreshTaxonomy;
+ use Symfony\Component\Lock\Key;
+
+ $key = new Key('article.'.$article->getId());
+ $lock = $factory->createLockFromKey(
+ $key,
+ 300, // ttl
+ false // autoRelease
+ );
+ $lock->acquire(true);
+
+ $this->bus->dispatch(new RefreshTaxonomy($article, $key));
+
+.. note::
+
+ Don't forget to set the ``autoRelease`` argument to ``false`` in the
+ ``Lock`` instantiation to avoid releasing the lock when the destructor is
+ called.
+
+Not all stores are compatible with serialization and cross-process locking: for
+example, the kernel will automatically release semaphores acquired by the
+:ref:`SemaphoreStore ` store. If you use an incompatible
+store (see :ref:`lock stores ` for supported stores), an
+exception will be thrown when the application tries to serialize the key.
+
.. _lock-blocking-locks:
Blocking Locks
--------------
By default, when a lock cannot be acquired, the ``acquire`` method returns
-``false`` immediately. To wait (indefinitely) until the lock
-can be created, pass ``true`` as the argument of the ``acquire()`` method. This
-is called a **blocking lock** because the execution of your application stops
-until the lock is acquired.
-
-Some of the built-in ``Store`` classes support this feature. When they don't,
-they can be decorated with the ``RetryTillSaveStore`` class::
+``false`` immediately. To wait (indefinitely) until the lock can be created,
+pass ``true`` as the argument of the ``acquire()`` method. This is called a
+**blocking lock** because the execution of your application stops until the
+lock is acquired::
use Symfony\Component\Lock\LockFactory;
- use Symfony\Component\Lock\Store\RedisStore;
- use Symfony\Component\Lock\Store\RetryTillSaveStore;
+ use Symfony\Component\Lock\Store\FlockStore;
- $store = new RedisStore(new \Predis\Client('tcp://localhost:6379'));
- $store = new RetryTillSaveStore($store);
+ $store = new FlockStore('/var/stores');
$factory = new LockFactory($store);
- $lock = $factory->createLock('notification-flush');
+ $lock = $factory->createLock('pdf-creation');
$lock->acquire(true);
+When the store does not support blocking locks by implementing the
+:class:`Symfony\\Component\\Lock\\BlockingStoreInterface` interface (see
+:ref:`lock stores ` for supported stores), the ``Lock`` class
+will retry to acquire the lock in a non-blocking way until the lock is
+acquired.
+
Expiring Locks
--------------
Locks created remotely are difficult to manage because there is no way for the
remote ``Store`` to know if the locker process is still alive. Due to bugs,
-fatal errors or segmentation faults, it cannot be guaranteed that ``release()``
-method will be called, which would cause the resource to be locked infinitely.
+fatal errors or segmentation faults, it cannot be guaranteed that the
+``release()`` method will be called, which would cause the resource to be
+locked infinitely.
The best solution in those cases is to create **expiring locks**, which are
released automatically after some amount of time has passed (called TTL for
@@ -110,8 +175,8 @@ job; if it's too long and the process crashes before calling the ``release()``
method, the resource will stay locked until the timeout::
// ...
- // create an expiring lock that lasts 30 seconds
- $lock = $factory->createLock('charts-generation', 30);
+ // create an expiring lock that lasts 30 seconds (default is 300.0)
+ $lock = $factory->createLock('pdf-creation', ttl: 30);
if (!$lock->acquire()) {
return;
@@ -124,7 +189,7 @@ method, the resource will stay locked until the timeout::
.. tip::
- To avoid letting the lock in a locking state, it's recommended to wrap the
+ To avoid leaving the lock in a locked state, it's recommended to wrap the
job in a try/catch/finally block to always try to release the expiring lock.
In case of long-running tasks, it's better to start with a not too long TTL and
@@ -132,7 +197,7 @@ then use the :method:`Symfony\\Component\\Lock\\LockInterface::refresh` method
to reset the TTL to its original value::
// ...
- $lock = $factory->createLock('charts-generation', 30);
+ $lock = $factory->createLock('pdf-creation', ttl: 30);
if (!$lock->acquire()) {
return;
@@ -153,7 +218,7 @@ to reset the TTL to its original value::
Another useful technique for long-running tasks is to pass a custom TTL as
an argument of the ``refresh()`` method to change the default lock TTL::
- $lock = $factory->createLock('charts-generation', 30);
+ $lock = $factory->createLock('pdf-creation', ttl: 30);
// ...
// refresh the lock for 30 seconds
$lock->refresh();
@@ -162,13 +227,109 @@ to reset the TTL to its original value::
$lock->refresh(600);
This component also provides two useful methods related to expiring locks:
-``getExpiringDate()`` (which returns ``null`` or a ``\DateTimeImmutable``
-object) and ``isExpired()`` (which returns a boolean).
+``getRemainingLifetime()`` (which returns ``null`` or a ``float``
+as seconds) and ``isExpired()`` (which returns a boolean).
+
+Automatically Releasing The Lock
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Locks are automatically released when their Lock objects are destroyed. This is
+an implementation detail that is important when sharing Locks between
+processes. In the example below, ``pcntl_fork()`` creates two processes and the
+Lock will be released automatically as soon as one process finishes::
+
+ // ...
+ $lock = $factory->createLock('pdf-creation');
+ if (!$lock->acquire()) {
+ return;
+ }
+
+ $pid = pcntl_fork();
+ if (-1 === $pid) {
+ // Could not fork
+ exit(1);
+ } elseif ($pid) {
+ // Parent process
+ sleep(30);
+ } else {
+ // Child process
+ echo 'The lock will be released now.';
+ exit(0);
+ }
+ // ...
+
+.. note::
+
+ In order for the above example to work, the `PCNTL`_ extension must be
+ installed.
+
+To disable this behavior, set the ``autoRelease`` argument of
+``LockFactory::createLock()`` to ``false``. That will make the lock acquired
+for 3600 seconds or until ``Lock::release()`` is called::
+
+ $lock = $factory->createLock(
+ 'pdf-creation',
+ 3600, // ttl
+ false // autoRelease
+ );
+
+Shared Locks
+------------
+
+A shared or `readers-writer lock`_ is a synchronization primitive that allows
+concurrent access for read-only operations, while write operations require
+exclusive access. This means that multiple threads can read the data in parallel
+but an exclusive lock is needed for writing or modifying data. They are used for
+example for data structures that cannot be updated atomically and are invalid
+until the update is complete.
+
+Use the :method:`Symfony\\Component\\Lock\\SharedLockInterface::acquireRead`
+method to acquire a read-only lock, and
+:method:`Symfony\\Component\\Lock\\LockInterface::acquire` method to acquire a
+write lock::
+
+ $lock = $factory->createLock('user-'.$user->id);
+ if (!$lock->acquireRead()) {
+ return;
+ }
+
+Similar to the ``acquire()`` method, pass ``true`` as the argument of ``acquireRead()``
+to acquire the lock in a blocking mode::
+
+ $lock = $factory->createLock('user-'.$user->id);
+ $lock->acquireRead(true);
+
+.. note::
+
+ The `priority policy`_ of Symfony's shared locks depends on the underlying
+ store (e.g. Redis store prioritizes readers vs writers).
+
+When a read-only lock is acquired with the ``acquireRead()`` method, it's
+possible to **promote** the lock, and change it to a write lock, by calling the
+``acquire()`` method::
+
+ $lock = $factory->createLock('user-'.$userId);
+ $lock->acquireRead(true);
+
+ if (!$this->shouldUpdate($userId)) {
+ return;
+ }
+
+ $lock->acquire(true); // Promote the lock to a write lock
+ $this->update($userId);
+
+In the same way, it's possible to **demote** a write lock, and change it to a
+read-only lock by calling the ``acquireRead()`` method.
+
+When the provided store does not implement the
+:class:`Symfony\\Component\\Lock\\SharedLockStoreInterface` interface (see
+:ref:`lock stores ` for supported stores), the ``Lock`` class
+will fallback to a write lock by calling the ``acquire()`` method.
The Owner of The Lock
---------------------
-Locks that are acquired for the first time are owned [1]_ by the ``Lock`` instance that acquired
+Locks that are acquired for the first time are :ref:`owned ` by the ``Lock`` instance that acquired
it. If you need to check whether the current ``Lock`` instance is (still) the owner of
a lock, you can use the ``isAcquired()`` method::
@@ -176,8 +337,8 @@ a lock, you can use the ``isAcquired()`` method::
// We (still) own the lock
}
-Because of the fact that some lock stores have expiring locks (as seen and explained
-above), it is possible for an instance to lose the lock it acquired automatically::
+Because some lock stores have expiring locks, it is possible for an instance to
+lose the lock it acquired automatically::
// If we cannot acquire ourselves, it means some other process is already working on it
if (!$lock->acquire()) {
@@ -198,18 +359,24 @@ above), it is possible for an instance to lose the lock it acquired automaticall
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
you have to use ``acquire()`` for this. The ``isAcquired()`` method is used to check
- if the lock has been acquired by the **current process** only!
+ if the lock has been acquired by the **current process** only.
+
+.. _lock-owner-technical-details:
-.. [1] Technically, the true owners of the lock are the ones that share the same instance of ``Key``,
+.. note::
+
+ Technically, the true owners of the lock are the ones that share the same instance of ``Key``,
not ``Lock``. But from a user perspective, ``Key`` is internal and you will likely only be working
with the ``Lock`` instance so it's easier to think of the ``Lock`` instance as being the one that
is the owner of the lock.
+.. _lock-stores:
+
Available Stores
----------------
@@ -219,17 +386,30 @@ 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
-============================================ ====== ======== ========
-:ref:`FlockStore ` local yes no
-:ref:`MemcachedStore ` remote no yes
-:ref:`MongoDbStore ` remote no yes
-:ref:`PdoStore ` remote no yes
-:ref:`RedisStore ` remote no yes
-:ref:`SemaphoreStore ` local yes no
-:ref:`ZookeeperStore ` remote 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::
+
+ Symfony includes two other special stores that are mostly useful for testing:
+ ``InMemoryStore``, which saves locks in memory during a process, and ``NullStore``,
+ which doesn't persist anything.
+
+.. versionadded:: 7.2
+
+ The :class:`Symfony\\Component\\Lock\\Store\\NullStore` was introduced in Symfony 7.2.
.. _lock-store-flock:
@@ -247,11 +427,11 @@ 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
- drive or a remote store based on PDO, Redis or Memcached.
+ drive or a remote store.
.. _lock-store-memcached:
@@ -273,17 +453,11 @@ support blocking, and expects a TTL to avoid stalled locks::
Memcached does not support TTL lower than 1 second.
-.. _lock-store-pdo:
-
.. _lock-store-mongodb:
MongoDbStore
~~~~~~~~~~~~
-.. versionadded:: 5.1
-
- The ``MongoDbStore`` was introduced in Symfony 5.1.
-
The MongoDbStore saves locks on a MongoDB server ``>=2.2``, it requires a
``\MongoDB\Collection`` or ``\MongoDB\Client`` from `mongodb/mongodb`_ or a
`MongoDB Connection String`_.
@@ -294,7 +468,7 @@ avoid stalled locks::
$mongo = 'mongodb://localhost/database?collection=lock';
$options = [
- 'gcProbablity' => 0.001,
+ 'gcProbability' => 0.001,
'database' => 'myapp',
'collection' => 'lock',
'uriOptions' => [],
@@ -307,10 +481,10 @@ The ``MongoDbStore`` takes the following ``$options`` (depending on the first pa
============= ================================================================================================
Option Description
============= ================================================================================================
-gcProbablity Should a TTL Index be created expressed as a probability from 0.0 to 1.0 (Defaults to ``0.001``)
+gcProbability Should a TTL Index be created expressed as a probability from 0.0 to 1.0 (Defaults to ``0.001``)
database The name of the database
collection The name of the collection
-uriOptions Array of uri options for `MongoDBClient::__construct`_
+uriOptions Array of URI options for `MongoDBClient::__construct`_
driverOptions Array of driver options for `MongoDBClient::__construct`_
============= ================================================================================================
@@ -336,18 +510,18 @@ MongoDB Connection String:
The ``collection`` querystring parameter is not part of the `MongoDB Connection String`_ definition.
It is used to allow constructing a ``MongoDbStore`` using a `Data Source Name (DSN)`_ without ``$options``.
+.. _lock-store-pdo:
PdoStore
~~~~~~~~
-The PdoStore saves locks in an SQL database. It requires a `PDO`_ connection, a
-`Doctrine DBAL Connection`_, or a `Data Source Name (DSN)`_. This store does not
-support blocking, and expects a TTL to avoid stalled locks::
+The PdoStore saves locks in an SQL database. It requires a `PDO`_ connection or a `Data Source Name (DSN)`_.
+This store does not support blocking, and expects a TTL to avoid stalled locks::
use Symfony\Component\Lock\Store\PdoStore;
- // a PDO, a Doctrine DBAL connection or DSN for lazy connecting through PDO
- $databaseConnectionOrDSN = 'mysql:host=127.0.0.1;dbname=lock';
+ // a PDO instance or DSN for lazy connecting through PDO
+ $databaseConnectionOrDSN = 'mysql:host=127.0.0.1;dbname=app';
$store = new PdoStore($databaseConnectionOrDSN, ['db_username' => 'myuser', 'db_password' => 'mypassword']);
.. note::
@@ -360,13 +534,85 @@ You can also create this table explicitly by calling the
:method:`Symfony\\Component\\Lock\\Store\\PdoStore::createTable` method in
your code.
+.. _lock-store-dbal:
+
+DoctrineDbalStore
+~~~~~~~~~~~~~~~~~
+
+The DoctrineDbalStore saves locks in an SQL database. It is identical to PdoStore
+but requires a `Doctrine DBAL Connection`_, or a `Doctrine DBAL URL`_. This store
+does not support blocking, and expects a TTL to avoid stalled locks::
+
+ use Symfony\Component\Lock\Store\DoctrineDbalStore;
+
+ // a Doctrine DBAL connection or DSN
+ $connectionOrURL = 'mysql://myuser:mypassword@127.0.0.1/app';
+ $store = new DoctrineDbalStore($connectionOrURL);
+
+.. note::
+
+ This store does not support TTL lower than 1 second.
+
+The table where values are stored will be automatically generated when your run
+the command:
+
+.. code-block:: terminal
+
+ $ php bin/console make:migration
+
+If you prefer to create the table yourself and it has not already been created, you can
+create this table explicitly by calling the
+:method:`Symfony\\Component\\Lock\\Store\\DoctrineDbalStore::createTable` method.
+You can also add this table to your schema by calling
+:method:`Symfony\\Component\\Lock\\Store\\DoctrineDbalStore::configureSchema` method
+in your code
+
+If the table has not been created upstream, it will be created automatically on the first call to
+the :method:`Symfony\\Component\\Lock\\Store\\DoctrineDbalStore::save` method.
+
+.. _lock-store-pgsql:
+
+PostgreSqlStore
+~~~~~~~~~~~~~~~
+
+The PostgreSqlStore uses `Advisory Locks`_ provided by PostgreSQL. It requires a
+`PDO`_ connection or a `Data Source Name (DSN)`_. It supports native blocking, as well as sharing
+locks::
+
+ use Symfony\Component\Lock\Store\PostgreSqlStore;
+
+ // a PDO instance or DSN for lazy connecting through PDO
+ $databaseConnectionOrDSN = 'pgsql:host=localhost;port=5634;dbname=app';
+ $store = new PostgreSqlStore($databaseConnectionOrDSN, ['db_username' => 'myuser', 'db_password' => 'mypassword']);
+
+In opposite to the ``PdoStore``, the ``PostgreSqlStore`` does not need a table to
+store locks and it does not expire.
+
+.. _lock-store-dbal-pgsql:
+
+DoctrineDbalPostgreSqlStore
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The DoctrineDbalPostgreSqlStore uses `Advisory Locks`_ provided by PostgreSQL.
+It is identical to PostgreSqlStore but requires a `Doctrine DBAL Connection`_ or
+a `Doctrine DBAL URL`_. It supports native blocking, as well as sharing locks::
+
+ use Symfony\Component\Lock\Store\DoctrineDbalPostgreSqlStore;
+
+ // a Doctrine Connection or DSN
+ $databaseConnectionOrDSN = 'postgresql+advisory://myuser:mypassword@127.0.0.1:5634/lock';
+ $store = new DoctrineDbalPostgreSqlStore($databaseConnectionOrDSN);
+
+In opposite to the ``DoctrineDbalStore``, the ``DoctrineDbalPostgreSqlStore`` does not need a table to
+store locks and does not expire.
+
.. _lock-store-redis:
RedisStore
~~~~~~~~~~
The RedisStore saves locks on a Redis server, it requires a Redis connection
-implementing the ``\Redis``, ``\RedisArray``, ``\RedisCluster`` or
+implementing the ``\Redis``, ``\RedisArray``, ``\RedisCluster``, ``\Relay\Relay`` or
``\Predis`` classes. This store does not support blocking, and expects a TTL to
avoid stalled locks::
@@ -394,10 +640,10 @@ CombinedStore
~~~~~~~~~~~~~
The CombinedStore is designed for High Availability applications because it
-manages several stores in sync (for example, several Redis servers). When a lock
-is being acquired, it forwards the call to all the managed stores, and it
-collects their responses. If a simple majority of stores have acquired the lock,
-then the lock is considered as acquired; otherwise as not acquired::
+manages several stores in sync (for example, several Redis servers). When a
+lock is acquired, it forwards the call to all the managed stores, and it
+collects their responses. If a simple majority of stores have acquired the
+lock, then the lock is considered acquired::
use Symfony\Component\Lock\Store\CombinedStore;
use Symfony\Component\Lock\Store\RedisStore;
@@ -415,14 +661,19 @@ then the lock is considered as acquired; otherwise as not acquired::
Instead of the simple majority strategy (``ConsensusStrategy``) an
``UnanimousStrategy`` can be used to require the lock to be acquired in all
-the stores.
+the stores::
-.. caution::
+ use Symfony\Component\Lock\Store\CombinedStore;
+ use Symfony\Component\Lock\Strategy\UnanimousStrategy;
+
+ $store = new CombinedStore($stores, new UnanimousStrategy());
+
+.. 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
working when a single server fails (because this strategy requires that the
- lock is acquired in more than half of the servers).
+ lock is acquired for more than half of the servers).
.. _lock-store-zookeeper:
@@ -450,7 +701,7 @@ PHP process is terminated::
Reliability
-----------
-The component guarantees that the same resource can't be lock twice as long as
+The component guarantees that the same resource can't be locked twice as long as
the component is used in the following way.
Remote Stores
@@ -459,21 +710,17 @@ Remote Stores
Remote stores (:ref:`MemcachedStore `,
:ref:`MongoDbStore `,
:ref:`PdoStore `,
+:ref:`PostgreSqlStore `,
:ref:`RedisStore ` and
:ref:`ZookeeperStore `) use a unique token to recognize
the true owner of the lock. This token is stored in the
:class:`Symfony\\Component\\Lock\\Key` object and is used internally by
-the ``Lock``, therefore this key must not be shared between processes (session,
-caching, fork, ...).
-
-.. caution::
+the ``Lock``.
- Do not share a key between processes.
-
-Every concurrent process must store the ``Lock`` in the same server. Otherwise two
+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
@@ -494,10 +741,10 @@ The ``Lock`` provides several methods to check its health. The ``isExpired()``
method checks whether or not its lifetime is over and the ``getRemainingLifetime()``
method returns its time to live in seconds.
-Using the above methods, a more robust code would be::
+Using the above methods, a robust code would be::
// ...
- $lock = $factory->createLock('invoice-publication', 30);
+ $lock = $factory->createLock('pdf-creation', 30);
if (!$lock->acquire()) {
return;
@@ -512,24 +759,24 @@ Using the above methods, a more 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 leave is enough to perform the task.
+ 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
when choosing the right TTL.
-By design, locks are stored in servers with a defined lifetime. If the date or
+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.
@@ -538,11 +785,11 @@ FlockStore
~~~~~~~~~~
By using the file system, this ``Store`` is reliable as long as concurrent
-processes use the same physical directory to stores locks.
+processes use the same physical directory to store locks.
Processes 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.
+Be careful when updating a Kubernetes or Swarm service because, for a short
+period of time, there can be two containers running in parallel.
The absolute path to the directory must remain the same. Be careful of symlinks
that could change at anytime: Capistrano and blue/green deployment often use
@@ -551,22 +798,21 @@ 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 locks directory.
+ on the same machine and using the same absolute path to the lock directory.
- By definition, usage of ``FlockStore`` in an HTTP context is incompatible
- with multiple front servers, unless to ensure that the same resource will
- always be locked on the same machine or to use a well configured shared file
- system.
+ Using a ``FlockStore`` in an HTTP context is incompatible with multiple
+ front servers, unless to ensure that the same resource will always be
+ locked on the same machine or to use a well configured shared file system.
-Files on the file system can be removed during a maintenance operation. For instance,
-to clean up the ``/tmp`` directory or after a reboot of the machine when a directory
-uses tmpfs. It's not an issue if the lock is released when the process ended, but
-it is in case of ``Lock`` reused between requests.
+Files on the file system can be removed during a maintenance operation. For
+instance, to clean up the ``/tmp`` directory or after a reboot of the machine
+when a directory uses ``tmpfs``. It's not an issue if the lock is released when
+the process ended, but it is in case of ``Lock`` reused between requests.
-.. caution::
+.. danger::
Do not store locks on a volatile file system if they have to be reused in
several requests.
@@ -576,12 +822,12 @@ MemcachedStore
The way Memcached works is to store items in memory. That means that by using
the :ref:`MemcachedStore ` the locks are not persisted
-and may disappear by mistake at anytime.
+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.
@@ -589,7 +835,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
@@ -599,7 +845,7 @@ When the Memcached service is shared and used for multiple usage, Locks could be
removed by mistake. For instance some implementation of the PSR-6 ``clear()``
method uses the Memcached's ``flush()`` method which purges and removes everything.
-.. caution::
+.. danger::
The method ``flush()`` must not be called, or locks should be stored in a
dedicated Memcached service away from Cache.
@@ -607,18 +853,18 @@ 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 in MongoDB an indexed field's value can be
- `a maximum of 1024 bytes in length`_ inclusive of structural overhead.
+ collection. Beware that an indexed field's value in MongoDB can be
+ `a maximum of 1024 bytes in length`_ including the structural overhead.
A TTL index must be used to automatically clean up expired locks.
Such an index can be created manually:
.. code-block:: javascript
- db.lock.ensureIndex(
+ db.lock.createIndex(
{ "expires_at": 1 },
{ "expireAfterSeconds": 0 }
)
@@ -629,32 +875,33 @@ about `Expire Data from Collections by Setting TTL`_ in MongoDB.
.. tip::
- ``MongoDbStore`` will attempt to automatically create a TTL index.
- It's recommended to set constructor option ``gcProbablity = 0.0`` to
+ ``MongoDbStore`` will attempt to automatically create a TTL index. It's
+ 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
locks don't expire prematurely; the lock TTL should be set with enough extra
time in ``expireAfterSeconds`` to account for any clock drift between nodes.
-``writeConcern``, ``readConcern`` and ``readPreference`` are not specified by
-MongoDbStore meaning the collection's settings will take effect. Read more
-about `Replica Set Read and Write Semantics`_ in MongoDB.
+``writeConcern`` and ``readConcern`` are not specified by MongoDbStore meaning
+the collection's settings will take effect.
+``readPreference`` is ``primary`` for all queries.
+Read more about `Replica Set Read and Write Semantics`_ in MongoDB.
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 nodes, or always use the same node.
+ 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;``.
@@ -663,22 +910,36 @@ 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.
+PostgreSqlStore
+~~~~~~~~~~~~~~~
+
+The PostgreSqlStore relies on the `Advisory Locks`_ properties of the PostgreSQL
+database. That means that by using :ref:`PostgreSqlStore `
+the locks will be automatically released at the end of the session in case the
+client cannot unlock for any reason.
+
+If the PostgreSQL service or the machine hosting it restarts, every lock would
+be lost without notifying the running processes.
+
+If the TCP connection is lost, the PostgreSQL may release locks without
+notifying the application.
+
RedisStore
~~~~~~~~~~
The way Redis works is to store items in memory. That means that by using
the :ref:`RedisStore ` the locks are not persisted
-and may disappear by mistake at anytime.
+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.
@@ -692,7 +953,7 @@ be lost without notifying the running processes.
When the Redis service is shared and used for multiple usages, locks could be
removed by mistake.
-.. caution::
+.. danger::
The command ``FLUSHDB`` must not be called, or locks should be stored in a
dedicated Redis service away from Cache.
@@ -700,13 +961,13 @@ removed by mistake.
CombinedStore
~~~~~~~~~~~~~
-Combined stores allow to store locks across several backends. It's a common
+Combined stores allow the storage of locks across several backends. It's a common
mistake to think that the lock mechanism will be more reliable. This is wrong.
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.
@@ -724,13 +985,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 process are stopped
+ 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.
@@ -772,8 +1033,10 @@ are still running.
.. _`a maximum of 1024 bytes in length`: https://docs.mongodb.com/manual/reference/limits/#Index-Key-Limit
.. _`ACID`: https://en.wikipedia.org/wiki/ACID
+.. _`Advisory Locks`: https://www.postgresql.org/docs/current/explicit-locking.html
.. _`Data Source Name (DSN)`: https://en.wikipedia.org/wiki/Data_source_name
.. _`Doctrine DBAL Connection`: https://github.com/doctrine/dbal/blob/master/src/Connection.php
+.. _`Doctrine DBAL URL`: https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
.. _`Expire Data from Collections by Setting TTL`: https://docs.mongodb.com/manual/tutorial/expire-data/
.. _`locks`: https://en.wikipedia.org/wiki/Lock_(computer_science)
.. _`MongoDB Connection String`: https://docs.mongodb.com/manual/reference/connection-string/
@@ -783,3 +1046,6 @@ are still running.
.. _`PHP semaphore functions`: https://www.php.net/manual/en/book.sem.php
.. _`Replica Set Read and Write Semantics`: https://docs.mongodb.com/manual/applications/replication/
.. _`ZooKeeper`: https://zookeeper.apache.org/
+.. _`readers-writer lock`: https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock
+.. _`priority policy`: https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock#Priority_policies
+.. _`PCNTL`: https://www.php.net/manual/book.pcntl.php
diff --git a/components/messenger.rst b/components/messenger.rst
index 57c39cd06a3..8d6652fb160 100644
--- a/components/messenger.rst
+++ b/components/messenger.rst
@@ -1,7 +1,3 @@
-.. index::
- single: Messenger
- single: Components; Messenger
-
The Messenger Component
=======================
@@ -31,7 +27,9 @@ Concepts
.. raw:: html
-
+
**Sender**:
Responsible for serializing and sending messages to *something*. This
@@ -56,7 +54,7 @@ Concepts
which means they can tweak the envelope, by adding stamps to it or even
replacing it, as well as interrupt the middleware chain. Middleware are called
both when a message is originally dispatched and again later when a message
- is received from a transport,
+ is received from a transport.
**Envelope**:
Messenger specific concept, it gives full flexibility inside the message bus,
@@ -77,7 +75,7 @@ middleware stack. The component comes with a set of middleware that you can use.
When using the message bus with Symfony's FrameworkBundle, the following middleware
are configured for you:
-#. :class:`Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware` (enables asynchronous processing, logs the processing of your messages if you pass a logger)
+#. :class:`Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware` (enables asynchronous processing, logs the processing of your messages if you provide a logger)
#. :class:`Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware` (calls the registered handler(s))
Example::
@@ -115,7 +113,7 @@ that will do the required processing for your message::
class MyMessageHandler
{
- public function __invoke(MyMessage $message)
+ public function __invoke(MyMessage $message): void
{
// Message processing...
}
@@ -142,21 +140,43 @@ through the transport layer, use the ``SerializerStamp`` stamp::
]))
);
-At the moment, the Symfony Messenger has the following built-in envelope stamps:
-
-#. :class:`Symfony\\Component\\Messenger\\Stamp\\SerializerStamp`,
- to configure the serialization groups used by the transport.
-#. :class:`Symfony\\Component\\Messenger\\Stamp\\ValidationStamp`,
- to configure the validation groups used when the validation middleware is enabled.
-#. :class:`Symfony\\Component\\Messenger\\Stamp\\ReceivedStamp`,
- an internal stamp that marks the message as received from a transport.
-#. :class:`Symfony\\Component\\Messenger\\Stamp\\SentStamp`,
- a stamp that marks the message as sent by a specific sender.
- Allows accessing the sender FQCN and the alias if available from the
- :class:`Symfony\\Component\\Messenger\\Transport\\Sender\\SendersLocator`.
-#. :class:`Symfony\\Component\\Messenger\\Stamp\\HandledStamp`,
- a stamp that marks the message as handled by a specific handler.
- Allows accessing the handler returned value and the handler name.
+Here are some important envelope stamps that are shipped with the Symfony Messenger:
+
+* :class:`Symfony\\Component\\Messenger\\Stamp\\DelayStamp`,
+ to delay handling of an asynchronous message.
+* :class:`Symfony\\Component\\Messenger\\Stamp\\DispatchAfterCurrentBusStamp`,
+ to make the message be handled after the current bus has executed. Read more
+ at :ref:`messenger-transactional-messages`.
+* :class:`Symfony\\Component\\Messenger\\Stamp\\HandledStamp`,
+ a stamp that marks the message as handled by a specific handler.
+ Allows accessing the handler returned value and the handler name.
+* :class:`Symfony\\Component\\Messenger\\Stamp\\ReceivedStamp`,
+ an internal stamp that marks the message as received from a transport.
+* :class:`Symfony\\Component\\Messenger\\Stamp\\SentStamp`,
+ a stamp that marks the message as sent by a specific sender.
+ Allows accessing the sender FQCN and the alias if available from the
+ :class:`Symfony\\Component\\Messenger\\Transport\\Sender\\SendersLocator`.
+* :class:`Symfony\\Component\\Messenger\\Stamp\\SerializerStamp`,
+ to configure the serialization groups used by the transport.
+* :class:`Symfony\\Component\\Messenger\\Stamp\\ValidationStamp`,
+ 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\\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 `.
+
+.. note::
+
+ The :class:`Symfony\\Component\\Messenger\\Stamp\\ErrorDetailsStamp` stamp
+ contains a :class:`Symfony\\Component\\ErrorHandler\\Exception\\FlattenException`,
+ which is a representation of the exception that made the message fail. You can
+ get this exception with the
+ :method:`Symfony\\Component\\Messenger\\Stamp\\ErrorDetailsStamp::getFlattenException`
+ method. This exception is normalized thanks to the
+ :class:`Symfony\\Component\\Messenger\\Transport\\Serialization\\Normalizer\\FlattenExceptionNormalizer`
+ which helps error reporting in the Messenger context.
Instead of dealing directly with the messages in the middleware you receive the envelope.
Hence you can inspect the envelope content and its stamps, or add any::
@@ -227,13 +247,10 @@ you can create your own message sender::
class ImportantActionToEmailSender implements SenderInterface
{
- private $mailer;
- private $toEmail;
-
- public function __construct(MailerInterface $mailer, string $toEmail)
- {
- $this->mailer = $mailer;
- $this->toEmail = $toEmail;
+ public function __construct(
+ private MailerInterface $mailer,
+ private string $toEmail,
+ ) {
}
public function send(Envelope $envelope): Envelope
@@ -279,19 +296,22 @@ do is to write your own CSV receiver::
class NewOrdersFromCsvFileReceiver implements ReceiverInterface
{
- private $serializer;
- private $filePath;
-
- public function __construct(SerializerInterface $serializer, string $filePath)
- {
- $this->serializer = $serializer;
- $this->filePath = $filePath;
+ private $connection;
+
+ public function __construct(
+ private SerializerInterface $serializer,
+ private string $filePath,
+ ) {
+ // Available connection bundled with the Messenger component
+ // can be found in "Symfony\Component\Messenger\Bridge\*\Transport\Connection".
+ $this->connection = /* create your connection */;
}
public function get(): iterable
{
// Receive the envelope according to your transport ($yourEnvelope here),
// in most cases, using a connection is the easiest solution.
+ $yourEnvelope = $this->connection->get();
if (null === $yourEnvelope) {
return [];
}
@@ -317,7 +337,9 @@ do is to write your own CSV receiver::
public function reject(Envelope $envelope): void
{
// In the case of a custom connection
- $this->connection->reject($this->findCustomStamp($envelope)->getId());
+ $id = /* get the message id thanks to information or stamps present in the envelope */;
+
+ $this->connection->reject($id);
}
}
@@ -340,4 +362,4 @@ Learn more
/messenger/*
.. _`blog posts about command buses`: https://matthiasnoback.nl/tags/command%20bus/
-.. _`SimpleBus project`: http://docs.simplebus.io/en/latest/
+.. _`SimpleBus project`: https://docs.simplebus.io/en/latest/
diff --git a/components/mime.rst b/components/mime.rst
index 14a981397c6..c043b342ebc 100644
--- a/components/mime.rst
+++ b/components/mime.rst
@@ -1,8 +1,3 @@
-.. index::
- single: MIME
- single: MIME Messages
- single: Components; MIME
-
The Mime Component
==================
@@ -34,7 +29,7 @@ complexity to provide two ways of creating MIME messages:
* A high-level API based on the :class:`Symfony\\Component\\Mime\\Email` class
to quickly create email messages with all the common features;
* A low-level API based on the :class:`Symfony\\Component\\Mime\\Message` class
- to have an absolute control over every single part of the email message.
+ to have absolute control over every single part of the email message.
Usage
-----
@@ -56,7 +51,7 @@ methods to compose the entire email message::
->html('Lorem ipsum ...
')
;
-This only purpose of this component is to create the email messages. Use the
+The only purpose of this component is to create the email messages. Use the
:doc:`Mailer component ` to actually send them.
Twig Integration
@@ -99,12 +94,12 @@ extension:
.. code-block:: terminal
- $ composer require twig/cssinliner-extension
+ $ composer require twig/cssinliner-extra
Now, enable the extension::
// ...
- use Twig\CssInliner\CssInlinerExtension;
+ use Twig\Extra\CssInliner\CssInlinerExtension;
$loader = new FilesystemLoader(__DIR__.'/templates');
$twig = new Environment($loader);
@@ -238,10 +233,10 @@ MIME types and file name extensions::
$exts = $mimeTypes->getExtensions('image/jpeg');
// $exts = ['jpeg', 'jpg', 'jpe']
- $mimeTypes = $mimeTypes->getMimeTypes('js');
- // $mimeTypes = ['application/javascript', 'application/x-javascript', 'text/javascript']
- $mimeTypes = $mimeTypes->getMimeTypes('apk');
- // $mimeTypes = ['application/vnd.android.package-archive']
+ $types = $mimeTypes->getMimeTypes('js');
+ // $types = ['application/javascript', 'application/x-javascript', 'text/javascript']
+ $types = $mimeTypes->getMimeTypes('apk');
+ // $types = ['application/vnd.android.package-archive']
These methods return arrays with one or more elements. The element position
indicates its priority, so the first returned extension is the preferred one.
diff --git a/components/options_resolver.rst b/components/options_resolver.rst
index 075b42937a9..6f3a6751f28 100644
--- a/components/options_resolver.rst
+++ b/components/options_resolver.rst
@@ -1,7 +1,3 @@
-.. index::
- single: OptionsResolver
- single: Components; OptionsResolver
-
The OptionsResolver Component
=============================
@@ -27,7 +23,7 @@ Imagine you have a ``Mailer`` class which has four options: ``host``,
class Mailer
{
- protected $options;
+ protected array $options;
public function __construct(array $options = [])
{
@@ -41,7 +37,7 @@ check which options are set::
class Mailer
{
// ...
- public function sendMail($from, $to)
+ public function sendMail($from, $to): void
{
$mail = ...;
@@ -55,7 +51,7 @@ check which options are set::
}
Also, the default values of the options are buried in the business logic of your
-code. Use the :phpfunction:`array_replace` to fix that::
+code. Use :phpfunction:`array_replace` to fix that::
class Mailer
{
@@ -125,7 +121,7 @@ code::
{
// ...
- public function sendMail($from, $to)
+ public function sendMail($from, $to): void
{
$mail = ...;
$mail->setHost($this->options['host']);
@@ -151,7 +147,7 @@ It's a good practice to split the option configuration into a separate method::
$this->options = $resolver->resolve($options);
}
- public function configureOptions(OptionsResolver $resolver)
+ public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'host' => 'smtp.example.org',
@@ -170,7 +166,7 @@ than processing options. Second, sub-classes may now override the
// ...
class GoogleMailer extends Mailer
{
- public function configureOptions(OptionsResolver $resolver)
+ public function configureOptions(OptionsResolver $resolver): void
{
parent::configureOptions($resolver);
@@ -193,7 +189,7 @@ For example, to make the ``host`` option required, you can do::
{
// ...
- public function configureOptions(OptionsResolver $resolver)
+ public function configureOptions(OptionsResolver $resolver): void
{
// ...
$resolver->setRequired('host');
@@ -217,7 +213,7 @@ one required option::
{
// ...
- public function configureOptions(OptionsResolver $resolver)
+ public function configureOptions(OptionsResolver $resolver): void
{
// ...
$resolver->setRequired(['host', 'username', 'password']);
@@ -232,7 +228,7 @@ retrieve the names of all required options::
// ...
class GoogleMailer extends Mailer
{
- public function configureOptions(OptionsResolver $resolver)
+ public function configureOptions(OptionsResolver $resolver): void
{
parent::configureOptions($resolver);
@@ -255,7 +251,7 @@ been set::
{
// ...
- public function configureOptions(OptionsResolver $resolver)
+ public function configureOptions(OptionsResolver $resolver): void
{
// ...
$resolver->setRequired('host');
@@ -265,7 +261,7 @@ been set::
// ...
class GoogleMailer extends Mailer
{
- public function configureOptions(OptionsResolver $resolver)
+ public function configureOptions(OptionsResolver $resolver): void
{
parent::configureOptions($resolver);
@@ -300,7 +296,7 @@ correctly. To validate the types of the options, call
{
// ...
- public function configureOptions(OptionsResolver $resolver)
+ public function configureOptions(OptionsResolver $resolver): void
{
// ...
@@ -309,13 +305,21 @@ correctly. To validate the types of the options, call
// specify multiple allowed types
$resolver->setAllowedTypes('port', ['null', 'int']);
+ // if you prefer, you can also use the following equivalent syntax
+ $resolver->setAllowedTypes('port', 'int|null');
// check all items in an array recursively for a type
$resolver->setAllowedTypes('dates', 'DateTime[]');
$resolver->setAllowedTypes('ports', 'int[]');
+ // the following syntax means "an array of integers or an array of strings"
+ $resolver->setAllowedTypes('endpoints', '(int|string)[]');
}
}
+.. versionadded:: 7.3
+
+ Defining type unions with the ``|`` syntax was introduced in Symfony 7.3.
+
You can pass any type for which an ``is_()`` function is defined in PHP.
You may also pass fully qualified class or interface names (which is checked
using ``instanceof``). Additionally, you can validate all items in an array
@@ -335,6 +339,8 @@ is thrown::
In sub-classes, you can use :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::addAllowedTypes`
to add additional allowed types without erasing the ones already set.
+.. _optionsresolver-validate-value:
+
Value Validation
~~~~~~~~~~~~~~~~
@@ -349,7 +355,7 @@ to verify that the passed option contains one of these values::
{
// ...
- public function configureOptions(OptionsResolver $resolver)
+ public function configureOptions(OptionsResolver $resolver): void
{
// ...
$resolver->setDefault('transport', 'sendmail');
@@ -372,10 +378,25 @@ For options with more complicated validation schemes, pass a closure which
returns ``true`` for acceptable values and ``false`` for invalid values::
// ...
- $resolver->setAllowedValues('transport', function ($value) {
+ $resolver->setAllowedValues('transport', function (string $value): bool {
// return true or false
});
+.. tip::
+
+ You can even use the :doc:`Validator ` component to validate the
+ input by using the :method:`Symfony\\Component\\Validator\\Validation::createIsValidCallable`
+ method::
+
+ use Symfony\Component\OptionsResolver\OptionsResolver;
+ use Symfony\Component\Validator\Constraints\Length;
+ use Symfony\Component\Validator\Validation;
+
+ // ...
+ $resolver->setAllowedValues('transport', Validation::createIsValidCallable(
+ new Length(min: 10)
+ ));
+
In sub-classes, you can use :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::addAllowedValues`
to add additional allowed values without erasing the ones already set.
@@ -395,12 +416,12 @@ option. You can configure a normalizer by calling
{
// ...
- public function configureOptions(OptionsResolver $resolver)
+ public function configureOptions(OptionsResolver $resolver): void
{
// ...
- $resolver->setNormalizer('host', function (Options $options, $value) {
- if ('http://' !== substr($value, 0, 7)) {
+ $resolver->setNormalizer('host', function (Options $options, string $value): string {
+ if (!str_starts_with($value, 'http://')) {
$value = 'http://'.$value;
}
@@ -417,11 +438,11 @@ if you need to use other options during normalization::
class Mailer
{
// ...
- public function configureOptions(OptionsResolver $resolver)
+ public function configureOptions(OptionsResolver $resolver): void
{
// ...
- $resolver->setNormalizer('host', function (Options $options, $value) {
- if ('http://' !== substr($value, 0, 7) && 'https://' !== substr($value, 0, 8)) {
+ $resolver->setNormalizer('host', function (Options $options, string $value): string {
+ if (!str_starts_with($value, 'http://') && !str_starts_with($value, 'https://')) {
if ('ssl' === $options['encryption']) {
$value = 'https://'.$value;
} else {
@@ -434,8 +455,8 @@ if you need to use other options during normalization::
}
}
-To normalize a new allowed value in sub-classes that are being normalized
-in parent classes use :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::addNormalizer`.
+To normalize a new allowed value in subclasses that are being normalized
+in parent classes, use :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::addNormalizer` method.
This way, the ``$value`` argument will receive the previously normalized
value, otherwise you can prepend the new normalizer by passing ``true`` as
third argument.
@@ -448,7 +469,7 @@ encryption chosen by the user of the ``Mailer`` class. More precisely, you want
to set the port to ``465`` if SSL is used and to ``25`` otherwise.
You can implement this feature by passing a closure as the default value of
-the ``port`` option. The closure receives the options as argument. Based on
+the ``port`` option. The closure receives the options as arguments. Based on
these options, you can return the desired default value::
use Symfony\Component\OptionsResolver\Options;
@@ -457,12 +478,12 @@ these options, you can return the desired default value::
class Mailer
{
// ...
- public function configureOptions(OptionsResolver $resolver)
+ public function configureOptions(OptionsResolver $resolver): void
{
// ...
$resolver->setDefault('encryption', null);
- $resolver->setDefault('port', function (Options $options) {
+ $resolver->setDefault('port', function (Options $options): int {
if ('ssl' === $options['encryption']) {
return 465;
}
@@ -472,7 +493,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.
@@ -480,7 +501,7 @@ these options, you can return the desired default value::
.. note::
The closure is only executed if the ``port`` option isn't set by the user
- or overwritten in a sub-class.
+ or overwritten in a subclass.
A previously set default value can be accessed by adding a second argument to
the closure::
@@ -489,7 +510,7 @@ the closure::
class Mailer
{
// ...
- public function configureOptions(OptionsResolver $resolver)
+ public function configureOptions(OptionsResolver $resolver): void
{
// ...
$resolver->setDefaults([
@@ -501,13 +522,13 @@ the closure::
class GoogleMailer extends Mailer
{
- public function configureOptions(OptionsResolver $resolver)
+ public function configureOptions(OptionsResolver $resolver): void
{
parent::configureOptions($resolver);
- $resolver->setDefault('host', function (Options $options, $previousValue) {
+ $resolver->setDefault('host', function (Options $options, string $previousValue): string {
if ('ssl' === $options['encryption']) {
- return 'secure.example.org'
+ return 'secure.example.org';
}
// Take default value configured in the base class
@@ -532,14 +553,14 @@ from the default::
class Mailer
{
// ...
- public function configureOptions(OptionsResolver $resolver)
+ public function configureOptions(OptionsResolver $resolver): void
{
// ...
$resolver->setDefault('port', 25);
}
// ...
- public function sendMail($from, $to)
+ public function sendMail(string $from, string $to): void
{
// Is this the default value or did the caller of the class really
// set the port to 25?
@@ -559,14 +580,14 @@ be included in the resolved options if it was actually passed to
{
// ...
- public function configureOptions(OptionsResolver $resolver)
+ public function configureOptions(OptionsResolver $resolver): void
{
// ...
$resolver->setDefined('port');
}
// ...
- public function sendMail($from, $to)
+ public function sendMail(string $from, string $to): void
{
if (array_key_exists('port', $this->options)) {
echo 'Set!';
@@ -593,7 +614,7 @@ options in one go::
class Mailer
{
// ...
- public function configureOptions(OptionsResolver $resolver)
+ public function configureOptions(OptionsResolver $resolver): void
{
// ...
$resolver->setDefined(['port', 'encryption']);
@@ -609,7 +630,7 @@ let you find out which options are defined::
{
// ...
- public function configureOptions(OptionsResolver $resolver)
+ public function configureOptions(OptionsResolver $resolver): void
{
parent::configureOptions($resolver);
@@ -639,9 +660,9 @@ default value::
{
// ...
- public function configureOptions(OptionsResolver $resolver)
+ public function configureOptions(OptionsResolver $resolver): void
{
- $resolver->setDefault('spool', function (OptionsResolver $spoolResolver) {
+ $resolver->setOptions('spool', function (OptionsResolver $spoolResolver): void {
$spoolResolver->setDefaults([
'type' => 'file',
'path' => '/path/to/spool',
@@ -651,7 +672,7 @@ default value::
});
}
- public function sendMail($from, $to)
+ public function sendMail(string $from, string $to): void
{
if ('memory' === $this->options['spool']['type']) {
// ...
@@ -665,6 +686,16 @@ default value::
],
]);
+.. deprecated:: 7.3
+
+ Defining nested options via :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setDefault`
+ is deprecated since Symfony 7.3. Use the :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setOptions`
+ method instead, which also allows defining default values for prototyped options.
+
+.. versionadded:: 7.3
+
+ The ``setOptions()`` method was introduced in Symfony 7.3.
+
Nested options also support required options, validation (type, value) and
normalization of their values. If the default value of a nested option depends
on another option defined in the parent level, add a second ``Options`` argument
@@ -674,10 +705,10 @@ to the closure to access to them::
{
// ...
- public function configureOptions(OptionsResolver $resolver)
+ public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefault('sandbox', false);
- $resolver->setDefault('spool', function (OptionsResolver $spoolResolver, Options $parent) {
+ $resolver->setOptions('spool', function (OptionsResolver $spoolResolver, Options $parent): void {
$spoolResolver->setDefaults([
'type' => $parent['sandbox'] ? 'memory' : 'file',
// ...
@@ -686,7 +717,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
@@ -698,15 +729,15 @@ In same way, parent options can access to the nested options as normal arrays::
{
// ...
- public function configureOptions(OptionsResolver $resolver)
+ public function configureOptions(OptionsResolver $resolver): void
{
- $resolver->setDefault('spool', function (OptionsResolver $spoolResolver) {
+ $resolver->setOptions('spool', function (OptionsResolver $spoolResolver): void {
$spoolResolver->setDefaults([
'type' => 'file',
// ...
]);
});
- $resolver->setDefault('profiling', function (Options $options) {
+ $resolver->setOptions('profiling', function (Options $options): void {
return 'file' === $options['spool']['type'];
});
}
@@ -717,15 +748,53 @@ In same way, parent options can access to the nested options as normal arrays::
The fact that an option is defined as nested means that you must pass
an array of values to resolve it at runtime.
-Deprecating the Option
-~~~~~~~~~~~~~~~~~~~~~~
+Prototype Options
+~~~~~~~~~~~~~~~~~
-.. versionadded:: 5.1
+There are situations where you will have to resolve and validate a set of
+options that may repeat many times within another option. Let's imagine a
+``connections`` option that will accept an array of database connections
+with ``host``, ``database``, ``user`` and ``password`` each.
- The signature of the ``setDeprecated()`` method changed from
- ``setDeprecated(string $option, ?string $message)`` to
- ``setDeprecated(string $option, string $package, string $version, $message)``
- in Symfony 5.1.
+The best way to implement this is to define the ``connections`` option as prototype::
+
+ $resolver->setOptions('connections', function (OptionsResolver $connResolver): void {
+ $connResolver
+ ->setPrototype(true)
+ ->setRequired(['host', 'database'])
+ ->setDefaults(['user' => 'root', 'password' => null]);
+ });
+
+According to the prototype definition in the example above, it is possible
+to have multiple connection arrays like the following::
+
+ $resolver->resolve([
+ 'connections' => [
+ 'default' => [
+ 'host' => '127.0.0.1',
+ 'database' => 'symfony',
+ ],
+ 'test' => [
+ 'host' => '127.0.0.1',
+ 'database' => 'symfony_test',
+ 'user' => 'test',
+ 'password' => 'test',
+ ],
+ // ...
+ ],
+ ]);
+
+The array keys (``default``, ``test``, etc.) of this prototype option are
+validation-free and can be any arbitrary value that helps differentiate the
+connections.
+
+.. note::
+
+ A prototype option can only be defined inside a nested option and
+ during its resolution it will expect an array of arrays.
+
+Deprecating the Option
+~~~~~~~~~~~~~~~~~~~~~~
Once an option is outdated or you decided not to maintain it anymore, you can
deprecate it using the :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setDeprecated`
@@ -739,11 +808,14 @@ method::
->setDeprecated('hostname', 'acme/package', '1.2')
// you can also pass a custom deprecation message (%name% placeholder is available)
+ // %name% placeholder will be replaced by the deprecated option.
+ // This outputs the following deprecation message:
+ // Since acme/package 1.2: The option "hostname" is deprecated, use "host" instead.
->setDeprecated(
'hostname',
'acme/package',
'1.2',
- 'The option "hostname" is deprecated, use "host" instead.'
+ 'The option "%name%" is deprecated, use "host" instead.'
)
;
@@ -757,9 +829,13 @@ 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::
+
+ All deprecation messages are displayed in the profiler logs in the "Deprecations" tab.
+
Instead of passing the message, you may also pass a closure which returns
a string (the deprecation message) or an empty string to ignore the deprecation.
This closure is useful to only deprecate some of the allowed types or values of
@@ -769,7 +845,7 @@ the option::
->setDefault('encryption', null)
->setDefault('port', null)
->setAllowedTypes('port', ['null', 'int'])
- ->setDeprecated('port', 'acme/package', '1.2', function (Options $options, $value) {
+ ->setDeprecated('port', 'acme/package', '1.2', function (Options $options, ?int $value): string {
if (null === $value) {
return 'Passing "null" to option "port" is deprecated, pass an integer instead.';
}
@@ -791,6 +867,26 @@ the option::
This closure receives as argument the value of the option after validating it
and before normalizing it when the option is being resolved.
+Ignore not defined Options
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, all options are resolved and validated, resulting in a
+:class:`Symfony\\Component\\OptionsResolver\\Exception\\UndefinedOptionsException`
+if an unknown option is passed. You can ignore not defined options by using the
+:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::ignoreUndefined` method::
+
+ // ...
+ $resolver
+ ->setDefined(['hostname'])
+ ->setIgnoreUndefined(true)
+ ;
+
+ // option "version" will be ignored
+ $resolver->resolve([
+ 'hostname' => 'acme/package',
+ 'version' => '1.2.3'
+ ]);
+
Chaining Option Configurations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -805,25 +901,22 @@ method::
class InvoiceMailer
{
// ...
- public function configureOptions(OptionsResolver $resolver)
+ public function configureOptions(OptionsResolver $resolver): void
{
// ...
$resolver->define('host')
->required()
->default('smtp.example.org')
- ->allowedTypes('string');
+ ->allowedTypes('string')
+ ->info('The IP address or hostname');
+
$resolver->define('transport')
->required()
->default('transport')
- ->allowedValues(['sendmail', 'mail', 'smtp']);
+ ->allowedValues('sendmail', 'mail', 'smtp');
}
}
-.. versionadded:: 5.1
-
- The :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::define` method
- was introduced in Symfony 5.1.
-
Performance Tweaks
~~~~~~~~~~~~~~~~~~
@@ -836,9 +929,9 @@ can change your code to do the configuration only once per class::
// ...
class Mailer
{
- private static $resolversByClass = [];
+ private static array $resolversByClass = [];
- protected $options;
+ protected array $options;
public function __construct(array $options = [])
{
@@ -854,7 +947,7 @@ can change your code to do the configuration only once per class::
$this->options = self::$resolversByClass[$class]->resolve($options);
}
- public function configureOptions(OptionsResolver $resolver)
+ public function configureOptions(OptionsResolver $resolver): void
{
// ...
}
@@ -869,9 +962,9 @@ method ``clearOptionsConfig()`` and call it periodically::
// ...
class Mailer
{
- private static $resolversByClass = [];
+ private static array $resolversByClass = [];
- public static function clearOptionsConfig()
+ public static function clearOptionsConfig(): void
{
self::$resolversByClass = [];
}
@@ -881,3 +974,21 @@ method ``clearOptionsConfig()`` and call it periodically::
That's it! You now have all the tools and knowledge needed to process
options in your code.
+
+Getting More Insights
+~~~~~~~~~~~~~~~~~~~~~
+
+Use the ``OptionsResolverIntrospector`` to inspect the options definitions
+inside an ``OptionsResolver`` instance::
+
+ use Symfony\Component\OptionsResolver\Debug\OptionsResolverIntrospector;
+ use Symfony\Component\OptionsResolver\OptionsResolver;
+
+ $resolver = new OptionsResolver();
+ $resolver->setDefaults([
+ 'host' => 'smtp.example.org',
+ 'port' => 25,
+ ]);
+
+ $introspector = new OptionsResolverIntrospector($resolver);
+ $introspector->getDefault('host'); // Retrieves "smtp.example.org"
diff --git a/components/phpunit_bridge.rst b/components/phpunit_bridge.rst
index 2e824973354..5ce4c003a11 100644
--- a/components/phpunit_bridge.rst
+++ b/components/phpunit_bridge.rst
@@ -1,7 +1,3 @@
-.. index::
- single: PHPUnitBridge
- single: Components; PHPUnitBridge
-
The PHPUnit Bridge
==================
@@ -11,8 +7,8 @@ The PHPUnit Bridge
It comes with the following features:
-* Forces the tests to use a consistent locale (``C``) (if you create
- locale-sensitive tests, use PHPUnit's ``setLocale()`` method);
+* Sets by default a consistent locale (``C``) for your tests (if you
+ create locale-sensitive tests, use PHPUnit's ``setLocale()`` method);
* Auto-register ``class_exists`` to load Doctrine annotations (when used);
@@ -23,10 +19,11 @@ It comes with the following features:
* Provides a ``ClockMock``, ``DnsMock`` and ``ClassExistsMock`` classes for tests
sensitive to time, network or class existence;
-* Provides a modified version of PHPUnit that allows 1. separating the
- dependencies of your app from those of phpunit to prevent any unwanted
- constraints to apply; 2. running tests in parallel when a test suite is split
- in several phpunit.xml files; 3. recording and replaying skipped tests;
+* Provides a modified version of PHPUnit that allows:
+
+ #. separating the dependencies of your app from those of phpunit to prevent any unwanted constraints to apply;
+ #. running tests in parallel when a test suite is split in several phpunit.xml files;
+ #. recording and replaying skipped tests;
* It allows to create tests that are compatible with multiple PHPUnit versions
(because it provides polyfills for missing methods, namespaced aliases for
@@ -48,13 +45,13 @@ Installation
always use its very latest stable major version to get the most accurate
deprecation report.
-If you plan to :ref:`write-assertions-about-deprecations` and use the regular
+If you plan to :ref:`write assertions about deprecations ` and use the regular
PHPUnit script (not the modified PHPUnit script provided by Symfony), you have
to register a new `test listener`_ called ``SymfonyTestsListener``:
.. code-block:: xml
-
+
@@ -203,7 +200,7 @@ message, enclosed with ``/``. For example, with:
.. code-block:: xml
-
+
@@ -219,13 +216,15 @@ message, enclosed with ``/``. For example, with:
`PHPUnit`_ will stop your test suite once a deprecation notice is triggered whose
message contains the ``"foobar"`` string.
+.. _making-tests-fail:
+
Making Tests Fail
~~~~~~~~~~~~~~~~~
-By default, any non-legacy-tagged or any non-`@-silenced <@-silencing operator>`_
+By default, any non-legacy-tagged or any non-silenced (`@-silencing operator`_)
deprecation notices will make tests fail. Alternatively, you can configure
an arbitrary threshold by setting ``SYMFONY_DEPRECATIONS_HELPER`` to
-``max[total]=320`` for instance. It will make the tests fails only if a
+``max[total]=320`` for instance. It will make the tests fail only if a
higher number of deprecation notices is reached (``0`` is the default
value).
@@ -233,7 +232,7 @@ You can have even finer-grained control by using other keys of the ``max``
array, which are ``self``, ``direct``, and ``indirect``. The
``SYMFONY_DEPRECATIONS_HELPER`` environment variable accepts a URL-encoded
string, meaning you can combine thresholds and any other configuration setting,
-like this: ``SYMFONY_DEPRECATIONS_HELPER=max[total]=42&max[self]=0&verbose=0``
+like this: ``SYMFONY_DEPRECATIONS_HELPER='max[total]=42&max[self]=0&verbose=0'``
Internal deprecations
.....................
@@ -254,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.
@@ -289,6 +288,55 @@ Here is a summary that should help you pick the right configuration:
| | cannot afford to use one of the modes above. |
+------------------------+-----------------------------------------------------+
+Ignoring Deprecations
+.....................
+
+If your application has some deprecations that you can't fix for some reasons,
+you can tell Symfony to ignore them.
+
+You need first to create a text file where each line is a deprecation to ignore
+defined as a regular expression. Lines beginning with a hash (``#``) are
+considered comments:
+
+.. code-block:: terminal
+
+ # This file contains patterns to be ignored while testing for use of
+ # deprecated code.
+
+ %The "Symfony\\Component\\Validator\\Context\\ExecutionContextInterface::.*\(\)" method is considered internal Used by the validator engine\. (Should not be called by user\W+code\. )?It may change without further notice\. You should not extend it from "[^"]+"\.%
+ %The "PHPUnit\\Framework\\TestCase::addWarning\(\)" method is considered internal%
+
+Then, you can run the following command to use that file and ignore those deprecations:
+
+.. code-block:: terminal
+
+ $ SYMFONY_DEPRECATIONS_HELPER='ignoreFile=./tests/baseline-ignore' ./vendor/bin/simple-phpunit
+
+Baseline Deprecations
+.....................
+
+You can also take a snapshot of deprecations currently triggered by your application
+code, and ignore those during your test runs, still reporting newly added ones.
+The trick is to create a file with the allowed deprecations and define it as the
+"deprecation baseline". Deprecations inside that file are ignored but the rest of
+deprecations are still reported.
+
+First, generate the file with the allowed deprecations (run the same command
+whenever you want to update the existing file):
+
+.. code-block:: terminal
+
+ $ SYMFONY_DEPRECATIONS_HELPER='generateBaseline=true&baselineFile=./tests/allowed.json' ./vendor/bin/simple-phpunit
+
+This command stores all the deprecations reported while running tests in the
+given file path and encoded in JSON.
+
+Then, you can run the following command to use that file and ignore those deprecations:
+
+.. code-block:: terminal
+
+ $ SYMFONY_DEPRECATIONS_HELPER='baselineFile=./tests/allowed.json' ./vendor/bin/simple-phpunit
+
Disabling the Verbose Output
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -300,9 +348,9 @@ It's also possible to change verbosity per deprecation type. For example, using
``quiet[]=indirect&quiet[]=other`` will hide details for deprecations of types
"indirect" and "other".
-.. versionadded:: 5.1
-
- The ``quiet`` option was introduced in Symfony 5.1.
+The ``quiet`` option hides details for the specified deprecation types, but will
+not change the outcome in terms of exit code. That's what :ref:`max `
+is for, and both settings are orthogonal.
Disabling the Deprecation Helper
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -312,8 +360,6 @@ to completely disable the deprecation helper. This is useful to make use of the
rest of features provided by this component without getting errors or messages
related to deprecations.
-.. _write-assertions-about-deprecations:
-
Deprecation Notices at Autoloading Time
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -346,9 +392,46 @@ the compiling and warming up of the container:
$ php bin/console debug:container --deprecations
-.. versionadded:: 5.1
+Log Deprecations
+~~~~~~~~~~~~~~~~
+
+For turning the verbose output off and write it to a log file instead you can use
+``SYMFONY_DEPRECATIONS_HELPER='logFile=/path/deprecations.log'``.
+
+Setting The Locale For Tests
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, the PHPUnit Bridge forces the locale to ``C`` to avoid locale
+issues in tests. This behavior can be changed by setting the
+``SYMFONY_PHPUNIT_LOCALE`` environment variable to the desired locale:
+
+.. code-block:: bash
+
+ # .env.test
+ SYMFONY_PHPUNIT_LOCALE="fr_FR"
+
+Alternatively, you can set this environment variable in the PHPUnit
+configuration file:
+
+.. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
- The ``--deprecations`` option was introduced in Symfony 5.1.
+Finally, if you want to avoid the bridge to force any locale, you can set the
+``SYMFONY_PHPUNIT_LOCALE`` environment variable to ``0``.
+
+.. _write-assertions-about-deprecations:
Write Assertions about Deprecations
-----------------------------------
@@ -371,7 +454,7 @@ times (order matters)::
/**
* @group legacy
*/
- public function testDeprecatedCode()
+ public function testDeprecatedCode(): void
{
// test some code that triggers the following deprecation:
// trigger_deprecation('vendor-name/package-name', '5.1', 'This "Foo" method is deprecated.');
@@ -385,11 +468,6 @@ times (order matters)::
}
}
-.. deprecated:: 5.1
-
- Symfony versions previous to 5.1 also included a ``@expectedDeprecation``
- annotation to test deprecations, but it was deprecated in favor of the method.
-
Display the Full Stack Trace
----------------------------
@@ -441,32 +519,6 @@ PHPUnit to remove the return type (introduced in PHPUnit 8) from ``setUp()``,
``tearDown()``, ``setUpBeforeClass()`` and ``tearDownAfterClass()`` methods.
This allows you to write a test compatible with both PHP 5 and PHPUnit 8.
-Alternatively, you can use the trait :class:`Symfony\\Bridge\\PhpUnit\\SetUpTearDownTrait`,
-which provides the right signature for the ``setUp()``, ``tearDown()``,
-``setUpBeforeClass()`` and ``tearDownAfterClass()`` methods and delegates the
-call to the ``doSetUp()``, ``doTearDown()``, ``doSetUpBeforeClass()`` and
-``doTearDownAfterClass()`` methods::
-
- use PHPUnit\Framework\TestCase;
- use Symfony\Bridge\PhpUnit\SetUpTearDownTrait;
-
- class MyTest extends TestCase
- {
- // when using the SetUpTearDownTrait, methods like doSetUp() can
- // be defined with and without the 'void' return type, as you wish
- use SetUpTearDownTrait;
-
- private function doSetUp()
- {
- // ...
- }
-
- protected function doSetUp(): void
- {
- // ...
- }
- }
-
Using Namespaced PHPUnit Classes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -488,7 +540,7 @@ If you have this kind of time-related tests::
class MyTest extends TestCase
{
- public function testSomething()
+ public function testSomething(): void
{
$stopwatch = new Stopwatch();
@@ -500,8 +552,8 @@ If you have this kind of time-related tests::
}
}
-You used the :doc:`Symfony Stopwatch Component ` to
-calculate the duration time of your process, here 10 seconds. However, depending
+You calculated the duration time of your process using the Stopwatch utilities to
+:ref:`profile Symfony applications `. However, depending
on the load of the server or the processes running on your local machine, the
``$duration`` could for example be ``10.000023s`` instead of ``10s``.
@@ -514,13 +566,14 @@ Clock Mocking
The :class:`Symfony\\Bridge\\PhpUnit\\ClockMock` class provided by this bridge
allows you to mock the PHP's built-in time functions ``time()``, ``microtime()``,
-``sleep()``, ``usleep()`` and ``gmdate()``. Additionally the function ``date()``
-is mocked so it uses the mocked time if no timestamp is specified.
+``sleep()``, ``usleep()``, ``gmdate()``, and ``hrtime()``. Additionally the
+function ``date()`` is mocked so it uses the mocked time if no timestamp is
+specified.
Other functions with an optional timestamp parameter that defaults to ``time()``
will still use the system time instead of the mocked time. This means that you
may need to change some code in your tests. For example, instead of ``new DateTime()``,
-you should use ``DateTime::createFromFormat('U', time())`` to use the mocked
+you should use ``DateTime::createFromFormat('U', (string) time())`` to use the mocked
``time()`` function.
To use the ``ClockMock`` class in your test, add the ``@group time-sensitive``
@@ -554,7 +607,7 @@ test::
*/
class MyTest extends TestCase
{
- public function testSomething()
+ public function testSomething(): void
{
$stopwatch = new Stopwatch();
@@ -568,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.
@@ -582,7 +635,7 @@ different class, do it explicitly using ``ClockMock::register(MyClass::class)``:
class MyClass
{
- public function getTimeInHours()
+ public function getTimeInHours(): void
{
return time() / 3600;
}
@@ -600,7 +653,7 @@ different class, do it explicitly using ``ClockMock::register(MyClass::class)``:
*/
class MyTest extends TestCase
{
- public function testGetTimeInHours()
+ public function testGetTimeInHours(): void
{
ClockMock::register(MyClass::class);
@@ -648,7 +701,7 @@ associated to a valid host::
class MyTest extends TestCase
{
- public function testEmail()
+ public function testEmail(): void
{
$validator = new DomainValidator(['checkDnsRecord' => true]);
$isValid = $validator->validate('example.com');
@@ -657,7 +710,7 @@ associated to a valid host::
}
}
-In order to avoid making a real network connection, add the ``@dns-sensitive``
+In order to avoid making a real network connection, add the ``@group dns-sensitive``
annotation to the class and use the ``DnsMock::withMockedHosts()`` to configure
the data you expect to get for the given hosts::
@@ -670,7 +723,7 @@ the data you expect to get for the given hosts::
*/
class DomainValidatorTest extends TestCase
{
- public function testEmails()
+ public function testEmails(): void
{
DnsMock::withMockedHosts([
'example.com' => [['type' => 'A', 'ip' => '1.2.3.4']],
@@ -711,6 +764,7 @@ reason, this component also provides mocks for these PHP functions:
* :phpfunction:`class_exists`
* :phpfunction:`interface_exists`
* :phpfunction:`trait_exists`
+* :phpfunction:`enum_exists`
Use Case
~~~~~~~~
@@ -740,7 +794,7 @@ are installed during tests) would look like::
class MyClassTest extends TestCase
{
- public function testHello()
+ public function testHello(): void
{
$class = new MyClass();
$result = $class->hello(); // "The dependency behavior."
@@ -761,7 +815,7 @@ classes, interfaces and/or traits for the code to run::
{
// ...
- public function testHelloDefault()
+ public function testHelloDefault(): void
{
ClassExistsMock::register(MyClass::class);
ClassExistsMock::withMockedClasses([DependencyClass::class => false]);
@@ -773,6 +827,16 @@ classes, interfaces and/or traits for the code to run::
}
}
+Note that mocking a class with ``ClassExistsMock::withMockedClasses()``
+will make :phpfunction:`class_exists`, :phpfunction:`interface_exists`
+and :phpfunction:`trait_exists` return true.
+
+To register an enumeration and mock :phpfunction:`enum_exists`,
+``ClassExistsMock::withMockedEnums()`` must be used. Note that, like in
+PHP 8.1 and later, calling ``class_exists`` on a enum will return ``true``.
+That's why calling ``ClassExistsMock::withMockedEnums()`` will also register the enum
+as a mocked class.
+
Troubleshooting
---------------
@@ -789,7 +853,7 @@ namespaces in the ``phpunit.xml`` file, as done for example in the
.. code-block:: xml
-
+
@@ -809,10 +873,16 @@ namespaces in the ``phpunit.xml`` file, as done for example in the
Under the hood, a PHPUnit listener injects the mocked functions in the tested
classes' namespace. In order to work as expected, the listener has to run before
-the tested class ever runs. By default, the mocked functions are created when the
-annotation are found and the corresponding tests are run. Depending on how your
-tests are constructed, this might be too late. In this case, you will need to declare
-the namespaces of the tested classes in your ``phpunit.xml.dist``.
+the tested class ever runs.
+
+By default, the mocked functions are created when the annotation are found and
+the corresponding tests are run. Depending on how your tests are constructed,
+this might be too late.
+
+You can either:
+
+* Declare the namespaces of the tested classes in your ``phpunit.xml.dist``;
+* Register the namespaces at the end of the ``config/bootstrap.php`` file.
.. code-block:: xml
@@ -828,6 +898,16 @@ the namespaces of the tested classes in your ``phpunit.xml.dist``.
+::
+
+ // config/bootstrap.php
+ use Symfony\Bridge\PhpUnit\ClockMock;
+
+ // ...
+ if ('test' === $_SERVER['APP_ENV']) {
+ ClockMock::register('Acme\\MyClassTest\\');
+ }
+
Modified PHPUnit script
-----------------------
@@ -848,18 +928,6 @@ configured by the ``SYMFONY_PHPUNIT_DIR`` env var, or in the same directory as
the ``simple-phpunit`` if it is not provided. It's also possible to set this
env var in the ``phpunit.xml.dist`` file.
-By default, these are the PHPUnit versions used depending on the installed PHP versions:
-
-===================== ===============================
-Installed PHP version PHPUnit version used by default
-===================== ===============================
-PHP <= 5.5 PHPUnit 4.8
-PHP 5.6 PHPUnit 5.7
-PHP 7.0 PHPUnit 6.5
-PHP 7.1 PHPUnit 7.5
-PHP >= 7.2 PHPUnit 8.3
-===================== ===============================
-
If you have installed the bridge through Composer, you can run it by calling e.g.:
.. code-block:: terminal
@@ -868,7 +936,7 @@ If you have installed the bridge through Composer, you can run it by calling e.g
.. tip::
- It's possible to change the base version of PHPUnit by setting the
+ It's possible to change the PHPUnit version by setting the
``SYMFONY_PHPUNIT_VERSION`` env var in the ``phpunit.xml.dist`` file (e.g.
`` ``). This is the
preferred method as it can be committed to your version control repository.
@@ -876,6 +944,10 @@ If you have installed the bridge through Composer, you can run it by calling e.g
It's also possible to set ``SYMFONY_PHPUNIT_VERSION`` as a real env var
(not defined in a :ref:`dotenv file `).
+ In the same way, ``SYMFONY_MAX_PHPUNIT_VERSION`` will set the maximum version
+ of PHPUnit to be considered. This is useful when testing a framework that does
+ not support the latest version(s) of PHPUnit.
+
.. tip::
If you still need to use ``prophecy`` (but not ``symfony/yaml``),
@@ -883,6 +955,22 @@ If you have installed the bridge through Composer, you can run it by calling e.g
It's also possible to set this env var in the ``phpunit.xml.dist`` file.
+.. tip::
+
+ It is also possible to require additional packages that will be installed along
+ with the rest of the needed PHPUnit packages using the ``SYMFONY_PHPUNIT_REQUIRE``
+ env variable. This is specially useful for installing PHPUnit plugins without
+ having to add them to your main ``composer.json`` file. The required packages
+ need to be separated with a space.
+
+ .. code-block:: xml
+
+
+
+
+
+
+
Code Coverage Listener
----------------------
@@ -895,7 +983,7 @@ Consider the following example::
class Bar
{
- public function barMethod()
+ public function barMethod(): string
{
return 'bar';
}
@@ -903,14 +991,12 @@ Consider the following example::
class Foo
{
- private $bar;
-
- public function __construct(Bar $bar)
- {
- $this->bar = $bar;
+ public function __construct(
+ private Bar $bar,
+ ) {
}
- public function fooMethod()
+ public function fooMethod(): string
{
$this->bar->barMethod();
@@ -920,7 +1006,7 @@ Consider the following example::
class FooTest extends PHPUnit\Framework\TestCase
{
- public function test()
+ public function test(): void
{
$bar = new Bar();
$foo = new Foo($bar);
@@ -946,7 +1032,7 @@ Add the following configuration to the ``phpunit.xml.dist`` file:
.. code-block:: xml
-
+
@@ -989,13 +1075,13 @@ not find the SUT:
.. _`PHPUnit`: https://phpunit.de
-.. _`PHPUnit event listener`: https://phpunit.de/manual/current/en/extending-phpunit.html#extending-phpunit.PHPUnit_Framework_TestListener
+.. _`PHPUnit event listener`: https://docs.phpunit.de/en/10.0/extending-phpunit.html#phpunit-s-event-system
.. _`ErrorHandler component`: https://github.com/symfony/error-handler
-.. _`PHPUnit's assertStringMatchesFormat()`: https://phpunit.de/manual/current/en/appendixes.assertions.html#appendixes.assertions.assertStringMatchesFormat
+.. _`PHPUnit's assertStringMatchesFormat()`: https://docs.phpunit.de/en/9.6/assertions.html#assertstringmatchesformat
.. _`PHP error handler`: https://www.php.net/manual/en/book.errorfunc.php
-.. _`environment variable`: https://phpunit.de/manual/current/en/appendixes.configuration.html#appendixes.configuration.php-ini-constants-variables
+.. _`environment variable`: https://docs.phpunit.de/en/9.6/configuration.html#the-env-element
.. _`@-silencing operator`: https://www.php.net/manual/en/language.operators.errorcontrol.php
.. _`Travis CI`: https://travis-ci.org/
-.. _`test listener`: https://phpunit.de/manual/current/en/appendixes.configuration.html#appendixes.configuration.test-listeners
-.. _`@covers`: https://phpunit.de/manual/current/en/appendixes.annotations.html#appendixes.annotations.covers
+.. _`test listener`: https://docs.phpunit.de/en/9.6/configuration.html#the-extensions-element
+.. _`@covers`: https://docs.phpunit.de/en/9.6/annotations.html#covers
.. _`PHP namespace resolutions rules`: https://www.php.net/manual/en/language.namespaces.rules.php
diff --git a/components/process.rst b/components/process.rst
index c7b286fec71..7552537e82e 100644
--- a/components/process.rst
+++ b/components/process.rst
@@ -1,7 +1,3 @@
-.. index::
- single: Process
- single: Components; Process
-
The Process Component
=====================
@@ -14,7 +10,6 @@ Installation
$ composer require symfony/process
-
.. include:: /components/require_autoload.rst.inc
Usage
@@ -102,10 +97,29 @@ with a non-zero code)::
:method:`Symfony\\Component\\Process\\Process::getLastOutputTime` method.
This method returns ``null`` if the process wasn't started!
+Configuring Process Options
+---------------------------
+
+Symfony uses the PHP :phpfunction:`proc_open` function to run the processes.
+You can configure the options passed to the ``other_options`` argument of
+``proc_open()`` using the ``setOptions()`` method::
+
+ $process = new Process(['...', '...', '...']);
+ // this option allows a subprocess to continue running after the main script exited
+ $process->setOptions(['create_new_console' => true]);
+
+.. warning::
+
+ Most of the options defined by ``proc_open()`` (such as ``create_new_console``
+ and ``suppress_errors``) are only supported on Windows operating systems.
+ Check out the `PHP documentation for proc_open()`_ before using them.
+
+.. _process-using-features-from-the-os-shell:
+
Using Features From the OS Shell
--------------------------------
-Using array of arguments is the recommended way to define commands. This
+Using an array of arguments is the recommended way to define commands. This
saves you from any escaping and allows sending signals seamlessly
(e.g. to stop processes while they run)::
@@ -177,7 +191,7 @@ anonymous function to the
use Symfony\Component\Process\Process;
$process = new Process(['ls', '-lsa']);
- $process->run(function ($type, $buffer) {
+ $process->run(function ($type, $buffer): void {
if (Process::ERR === $type) {
echo 'ERR > '.$buffer;
} else {
@@ -240,7 +254,7 @@ are done doing other stuff::
**synchronously** inside this event. Be aware that ``kernel.terminate``
is called only if you use PHP-FPM.
-.. caution::
+.. danger::
Beware also that if you do that, the said PHP-FPM process will not be
available to serve any new request until the subprocess is finished. This
@@ -255,7 +269,7 @@ in the output and its type::
$process = new Process(['ls', '-lsa']);
$process->start();
- $process->wait(function ($type, $buffer) {
+ $process->wait(function ($type, $buffer): void {
if (Process::ERR === $type) {
echo 'ERR > '.$buffer;
} else {
@@ -274,7 +288,7 @@ process and checks its output to wait until its fully initialized::
// ... do other things
// waits until the given anonymous function returns true
- $process->waitUntil(function ($type, $output) {
+ $process->waitUntil(function ($type, $output): bool {
return $output === 'Ready. Waiting for commands...';
});
@@ -316,7 +330,7 @@ provides the :class:`Symfony\\Component\\Process\\InputStream` class::
echo $process->getOutput();
The :method:`Symfony\\Component\\Process\\InputStream::write` method accepts scalars,
-stream resources or ``Traversable`` objects as argument. As shown in the above example,
+stream resources or ``Traversable`` objects as arguments. As shown in the above example,
you need to explicitly call the :method:`Symfony\\Component\\Process\\InputStream::close`
method when you are done writing to the standard input of the subprocess.
@@ -343,6 +357,35 @@ The input of a process can also be defined using `PHP streams`_::
// will echo: 'foobar'
echo $process->getOutput();
+Using TTY and PTY Modes
+-----------------------
+
+All examples above show that your program has control over the input of a
+process (using ``setInput()``) and the output from that process (using
+``getOutput()``). The Process component has two special modes that tweak
+the relationship between your program and the process: teletype (tty) and
+pseudo-teletype (pty).
+
+In TTY mode, you connect the input and output of the process to the input
+and output of your program. This allows for instance to open an editor like
+Vim or Nano as a process. You enable TTY mode by calling
+:method:`Symfony\\Component\\Process\\Process::setTty`::
+
+ $process = new Process(['vim']);
+ $process->setTty(true);
+ $process->run();
+
+ // As the output is connected to the terminal, it is no longer possible
+ // to read or modify the output from the process!
+ dump($process->getOutput()); // null
+
+In PTY mode, your program behaves as a terminal for the process instead of
+a plain input and output. Some programs behave differently when
+interacting with a real terminal instead of another program. For instance,
+some programs prompt for a password when talking with a terminal. Use
+:method:`Symfony\\Component\\Process\\Process::setPty` to enable this
+mode.
+
Stopping a Process
------------------
@@ -350,7 +393,7 @@ Any asynchronous process can be stopped at any time with the
:method:`Symfony\\Component\\Process\\Process::stop` method. This method takes
two arguments: a timeout and a signal. Once the timeout is reached, the signal
is sent to the running process. The default signal sent to a process is ``SIGKILL``.
-Please read the :ref:`signal documentation below`
+Please read the :ref:`signal documentation below `
to find out more about signal handling in the Process component::
$process = new Process(['ls', '-lsa']);
@@ -374,22 +417,35 @@ instead::
);
$process->run();
-Using a Prepared Command Line
------------------------------
-
-You can run the process by using a a prepared command line using the
-double bracket notation. You can use a placeholder in order to have a
-process that can only be changed with the values and without changing
-the PHP code::
+Executing a PHP Child Process with the Same Configuration
+---------------------------------------------------------
- use Symfony\Component\Process\Process;
+When you start a PHP process, it uses the default configuration defined in
+your ``php.ini`` file. You can bypass these options with the ``-d`` command line
+option. For example, if ``memory_limit`` is set to ``256M``, you can disable this
+memory limit when running some command like this:
+``php -d memory_limit=-1 bin/console app:my-command``.
- $process = Process::fromShellCommandline('echo "$name"');
- $process->run(null, ['name' => 'Elsa']);
+However, if you run the command via the Symfony ``Process`` class, PHP will use
+the settings defined in the ``php.ini`` file. You can solve this issue by using
+the :class:`Symfony\\Component\\Process\\PhpSubprocess` class to run the command::
-.. caution::
+ use Symfony\Component\Process\Process;
- A prepared command line will not be escaped automatically!
+ class MyCommand extends Command
+ {
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ // the memory_limit (and any other config option) of this command is
+ // the one defined in php.ini instead of the new values (optionally)
+ // passed via the '-d' command option
+ $childProcess = new Process(['bin/console', 'cache:pool:prune']);
+
+ // the memory_limit (and any other config option) of this command takes
+ // into account the values (optionally) passed via the '-d' command option
+ $childProcess = new PhpSubprocess(['bin/console', 'cache:pool:prune']);
+ }
+ }
Process Timeout
---------------
@@ -425,10 +481,6 @@ check regularly::
You can get the process start time using the ``getStartTime()`` method.
- .. versionadded:: 5.1
-
- The ``getStartTime()`` method was introduced in Symfony 5.1.
-
.. _reference-process-signal:
Process Idle Timeout
@@ -461,6 +513,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
-----------
@@ -488,7 +554,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.
@@ -499,10 +565,31 @@ Use :method:`Symfony\\Component\\Process\\Process::disableOutput` and
However, it is possible to pass a callback to the ``start``, ``run`` or ``mustRun``
methods to handle process output in a streaming fashion.
+Finding an Executable
+---------------------
+
+The Process component provides a utility class called
+:class:`Symfony\\Component\\Process\\ExecutableFinder` which finds
+and returns the absolute path of an executable::
+
+ use Symfony\Component\Process\ExecutableFinder;
+
+ $executableFinder = new ExecutableFinder();
+ $chromedriverPath = $executableFinder->find('chromedriver');
+ // $chromedriverPath = '/usr/local/bin/chromedriver' (the result will be different on your computer)
+
+The :method:`Symfony\\Component\\Process\\ExecutableFinder::find` method also takes extra parameters to specify a default value
+to return and extra directories where to look for the executable::
+
+ use Symfony\Component\Process\ExecutableFinder;
+
+ $executableFinder = new ExecutableFinder();
+ $chromedriverPath = $executableFinder->find('chromedriver', '/path/to/chromedriver', ['local-bin/']);
+
Finding the Executable PHP Binary
---------------------------------
-This component also provides a utility class called
+This component also provides a special utility class called
:class:`Symfony\\Component\\Process\\PhpExecutableFinder` which returns the
absolute path of the executable PHP binary available on your server::
@@ -527,3 +614,4 @@ whether `TTY`_ is supported on the current operating system::
.. _`PHP streams`: https://www.php.net/manual/en/book.stream.php
.. _`output_buffering`: https://www.php.net/manual/en/outcontrol.configuration.php
.. _`TTY`: https://en.wikipedia.org/wiki/Tty_(unix)
+.. _`PHP documentation for proc_open()`: https://www.php.net/manual/en/function.proc-open.php
diff --git a/components/property_access.rst b/components/property_access.rst
index c99091c4e15..f608640fa9b 100644
--- a/components/property_access.rst
+++ b/components/property_access.rst
@@ -1,11 +1,7 @@
-.. index::
- single: PropertyAccess
- single: Components; PropertyAccess
-
The PropertyAccess Component
============================
- The PropertyAccess component provides function to read and write from/to an
+ The PropertyAccess component provides functions to read and write from/to an
object or array using a simple string notation.
Installation
@@ -30,6 +26,8 @@ default configuration::
$propertyAccessor = PropertyAccess::createPropertyAccessor();
+.. _property-access-reading-arrays:
+
Reading from Arrays
-------------------
@@ -63,6 +61,9 @@ method::
// Symfony\Component\PropertyAccess\Exception\NoSuchIndexException
$value = $propertyAccessor->getValue($person, '[age]');
+ // You can avoid the exception by adding the nullsafe operator
+ $value = $propertyAccessor->getValue($person, '[age?]');
+
You can also use multi dimensional arrays::
// ...
@@ -72,12 +73,24 @@ You can also use multi dimensional arrays::
],
[
'first_name' => 'Ryan',
- ]
+ ],
];
var_dump($propertyAccessor->getValue($persons, '[0][first_name]')); // 'Wouter'
var_dump($propertyAccessor->getValue($persons, '[1][first_name]')); // 'Ryan'
+.. tip::
+
+ If the key of the array contains a dot ``.`` or a left square bracket ``[``,
+ you must escape those characters with a backslash. In the above example,
+ if the array key was ``first.name`` instead of ``first_name``, you should
+ access its value as follows::
+
+ var_dump($propertyAccessor->getValue($persons, '[0][first\.name]')); // 'Wouter'
+ var_dump($propertyAccessor->getValue($persons, '[1][first\.name]')); // 'Ryan'
+
+ Right square brackets ``]`` don't need to be escaped in array keys.
+
Reading from Objects
--------------------
@@ -101,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
@@ -119,9 +132,9 @@ it with ``get``. So the actual method becomes ``getFirstName()``::
// ...
class Person
{
- private $firstName = 'Wouter';
+ private string $firstName = 'Wouter';
- public function getFirstName()
+ public function getFirstName(): string
{
return $this->firstName;
}
@@ -141,15 +154,15 @@ getters, this means that you can do something like this::
// ...
class Person
{
- private $author = true;
- private $children = [];
+ private bool $author = true;
+ private array $children = [];
- public function isAuthor()
+ public function isAuthor(): bool
{
return $this->author;
}
- public function hasChildren()
+ public function hasChildren(): bool
{
return 0 !== count($this->children);
}
@@ -178,7 +191,7 @@ method::
// ...
class Person
{
- public $name;
+ public string $name;
}
$person = new Person();
@@ -190,6 +203,35 @@ method::
// instead of throwing an exception the following code returns null
$value = $propertyAccessor->getValue($person, 'birthday');
+Accessing Nullable Property Paths
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Consider the following PHP code::
+
+ class Person
+ {
+ }
+
+ class Comment
+ {
+ public ?Person $person = null;
+ public string $message;
+ }
+
+ $comment = new Comment();
+ $comment->message = 'test';
+
+Given that ``$person`` is nullable, an object graph like ``comment.person.profile``
+will trigger an exception when the ``$person`` property is ``null``. The solution
+is to mark all nullable properties with the nullsafe operator (``?``)::
+
+ // This code throws an exception of type
+ // Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException
+ var_dump($propertyAccessor->getValue($comment, 'person.firstname'));
+
+ // If a property marked with the nullsafe operator is null, the expression is
+ // no longer evaluated and null is returned immediately without throwing an exception
+ var_dump($propertyAccessor->getValue($comment, 'person?.firstname')); // null
.. _components-property-access-magic-get:
@@ -201,47 +243,50 @@ The ``getValue()`` method can also use the magic ``__get()`` method::
// ...
class Person
{
- private $children = [
+ private array $children = [
'Wouter' => [...],
];
- public function __get($id)
+ public function __get($id): mixed
{
return $this->children[$id];
}
+
+ public function __isset($id): bool
+ {
+ return isset($this->children[$id]);
+ }
}
$person = new Person();
var_dump($propertyAccessor->getValue($person, 'Wouter')); // [...]
-.. versionadded:: 5.2
+.. warning::
- The magic ``__get()`` method can be disabled since in Symfony 5.2.
- see `Enable other Features`_.
+ When implementing the magic ``__get()`` method, you also need to implement
+ ``__isset()``.
.. _components-property-access-magic-call:
Magic ``__call()`` Method
~~~~~~~~~~~~~~~~~~~~~~~~~
-At last, ``getValue()`` can use the magic ``__call()`` method, but you need to
+Lastly, ``getValue()`` can use the magic ``__call()`` method, but you need to
enable this feature by using :class:`Symfony\\Component\\PropertyAccess\\PropertyAccessorBuilder`::
// ...
class Person
{
- private $children = [
+ private array $children = [
'wouter' => [...],
];
- public function __call($name, $args)
+ public function __call($name, $args): mixed
{
$property = lcfirst(substr($name, 3));
if ('get' === substr($name, 0, 3)) {
- return isset($this->children[$property])
- ? $this->children[$property]
- : null;
+ return $this->children[$property] ?? null;
} elseif ('set' === substr($name, 0, 3)) {
$value = 1 == count($args) ? $args[0] : null;
$this->children[$property] = $value;
@@ -258,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`
@@ -291,26 +336,26 @@ can use setters, the magic ``__set()`` method or properties to set values::
// ...
class Person
{
- public $firstName;
- private $lastName;
- private $children = [];
+ public string $firstName;
+ private string $lastName;
+ private array $children = [];
- public function setLastName($name)
+ public function setLastName($name): void
{
$this->lastName = $name;
}
- public function getLastName()
+ public function getLastName(): string
{
return $this->lastName;
}
- public function getChildren()
+ public function getChildren(): array
{
return $this->children;
}
- public function __set($property, $value)
+ public function __set($property, $value): void
{
$this->$property = $value;
}
@@ -332,15 +377,13 @@ see `Enable other Features`_::
// ...
class Person
{
- private $children = [];
+ private array $children = [];
- public function __call($name, $args)
+ public function __call($name, $args): mixed
{
$property = lcfirst(substr($name, 3));
if ('get' === substr($name, 0, 3)) {
- return isset($this->children[$property])
- ? $this->children[$property]
- : null;
+ return $this->children[$property] ?? null;
} elseif ('set' === substr($name, 0, 3)) {
$value = 1 == count($args) ? $args[0] : null;
$this->children[$property] = $value;
@@ -360,10 +403,10 @@ see `Enable other Features`_::
var_dump($person->getWouter()); // [...]
-.. versionadded:: 5.2
+.. note::
- The magic ``__set()`` method can be disabled since in Symfony 5.2.
- see `Enable other Features`_.
+ The ``__set()`` method support is enabled by default.
+ See `Enable other Features`_ if you want to disable it.
Writing to Array Properties
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -377,7 +420,7 @@ properties through *adder* and *remover* methods::
/**
* @var string[]
*/
- private $children = [];
+ private array $children = [];
public function getChildren(): array
{
@@ -403,11 +446,51 @@ properties through *adder* and *remover* methods::
The PropertyAccess component checks for methods called ``add()``
and ``remove()``. Both methods must be defined.
For instance, in the previous example, the component looks for the ``addChild()``
-and ``removeChild()`` methods to access to the ``children`` property.
-`The Inflector component`_ is used to find the singular of a property name.
+and ``removeChild()`` methods to access the ``children`` property.
+`The String component`_ inflector is used to find the singular of a property name.
If available, *adder* and *remover* methods have priority over a *setter* method.
+Using non-standard adder/remover methods
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Sometimes, adder and remover methods don't use the standard ``add`` or ``remove`` prefix, like in this example::
+
+ // ...
+ class Team
+ {
+ // ...
+
+ public function joinTeam(string $person): void
+ {
+ $this->team[] = $person;
+ }
+
+ public function leaveTeam(string $person): void
+ {
+ foreach ($this->team as $id => $item) {
+ if ($person === $item) {
+ unset($this->team[$id]);
+
+ break;
+ }
+ }
+ }
+ }
+
+ use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
+ use Symfony\Component\PropertyAccess\PropertyAccessor;
+
+ $list = new Team();
+ $reflectionExtractor = new ReflectionExtractor(null, null, ['join', 'leave']);
+ $propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, PropertyAccessor::THROW_ON_INVALID_PROPERTY_PATH, null, $reflectionExtractor, $reflectionExtractor);
+ $propertyAccessor->setValue($person, 'team', ['kevin', 'wouter']);
+
+ var_dump($person->getTeam()); // ['kevin', 'wouter']
+
+Instead of calling ``add()`` and ``remove()``, the PropertyAccess
+component will call ``join()`` and ``leave()`` methods.
+
Checking Property Paths
-----------------------
@@ -440,15 +523,15 @@ You can also mix objects and arrays::
// ...
class Person
{
- public $firstName;
- private $children = [];
+ public string $firstName;
+ private array $children = [];
- public function setChildren($children)
+ public function setChildren($children): void
{
$this->children = $children;
}
- public function getChildren()
+ public function getChildren(): array
{
return $this->children;
}
@@ -480,10 +563,10 @@ configured to enable extra features. To do that you could use the
$propertyAccessorBuilder->enableMagicSet(); // enables magic __set
$propertyAccessorBuilder->enableMagicMethods(); // enables magic __get, __set and __call
- $propertyAccessorBuilder->disableMagicCall(); // enables magic __call
- $propertyAccessorBuilder->disableMagicGet(); // enables magic __get
- $propertyAccessorBuilder->disableMagicSet(); // enables magic __set
- $propertyAccessorBuilder->disableMagicMethods(); // enables magic __get, __set and __call
+ $propertyAccessorBuilder->disableMagicCall(); // disables magic __call
+ $propertyAccessorBuilder->disableMagicGet(); // disables magic __get
+ $propertyAccessorBuilder->disableMagicSet(); // disables magic __set
+ $propertyAccessorBuilder->disableMagicMethods(); // disables magic __get, __set and __call
// checks if magic __call, __get or __set handling are enabled
$propertyAccessorBuilder->isMagicCallEnabled(); // true or false
@@ -503,4 +586,4 @@ Or you can pass parameters directly to the constructor (not the recommended way)
// enable handling of magic __call, __set but not __get:
$propertyAccessor = new PropertyAccessor(PropertyAccessor::MAGIC_CALL | PropertyAccessor::MAGIC_SET);
-.. _The Inflector component: https://github.com/symfony/inflector
+.. _`The String component`: https://github.com/symfony/string
diff --git a/components/property_info.rst b/components/property_info.rst
index d67034b04fa..39019657ced 100644
--- a/components/property_info.rst
+++ b/components/property_info.rst
@@ -1,7 +1,3 @@
-.. index::
- single: PropertyInfo
- single: Components; PropertyInfo
-
The PropertyInfo Component
==========================
@@ -122,7 +118,7 @@ class exposes public methods to extract several types of information:
* :ref:`List of properties `: :method:`Symfony\\Component\\PropertyInfo\\PropertyListExtractorInterface::getProperties`
* :ref:`Property type `: :method:`Symfony\\Component\\PropertyInfo\\PropertyTypeExtractorInterface::getTypes`
- (including typed properties since PHP 7.4)
+ (including typed properties)
* :ref:`Property description `: :method:`Symfony\\Component\\PropertyInfo\\PropertyDescriptionExtractorInterface::getShortDescription` and :method:`Symfony\\Component\\PropertyInfo\\PropertyDescriptionExtractorInterface::getLongDescription`
* :ref:`Property access details `: :method:`Symfony\\Component\\PropertyInfo\\PropertyAccessExtractorInterface::isReadable` and :method:`Symfony\\Component\\PropertyInfo\\PropertyAccessExtractorInterface::isWritable`
* :ref:`Property initializable through the constructor `: :method:`Symfony\\Component\\PropertyInfo\\PropertyInitializableExtractorInterface::isInitializable`
@@ -187,6 +183,26 @@ for a property::
See :ref:`components-property-info-type` for info about the ``Type`` class.
+Documentation Block
+~~~~~~~~~~~~~~~~~~~
+
+Extractors that implement :class:`Symfony\\Component\\PropertyInfo\\PropertyDocBlockExtractorInterface`
+can provide the full documentation block for a property as a string::
+
+ $docBlock = $propertyInfo->getDocBlock($class, $property);
+ /*
+ Example Result
+ --------------
+ string(79):
+ This is the subsequent paragraph in the DocComment.
+ It can span multiple lines.
+ */
+
+.. versionadded:: 7.1
+
+ The :class:`Symfony\\Component\\PropertyInfo\\PropertyDocBlockExtractorInterface`
+ interface was introduced in Symfony 7.1.
+
.. _property-info-description:
Description Information
@@ -208,7 +224,7 @@ strings::
Example Result
--------------
string(79):
- These is the subsequent paragraph in the DocComment.
+ This is the subsequent paragraph in the DocComment.
It can span multiple lines.
*/
@@ -229,7 +245,9 @@ provide whether properties are readable or writable as booleans::
The :class:`Symfony\\Component\\PropertyInfo\\Extractor\\ReflectionExtractor` looks
for getter/isser/setter/hasser method in addition to whether or not a property is public
to determine if it's accessible. This based on how the :doc:`PropertyAccess `
-works.
+works. It assumes camel case style method names following `PSR-1`_. For example,
+both ``myProperty`` and ``my_property`` properties are readable if there's a
+``getMyProperty()`` method and writable if there's a ``setMyProperty()`` method.
.. _property-info-initializable:
@@ -323,15 +341,20 @@ this returns ``true`` if:
``@var SomeClass``, ``@var SomeClass``,
``@var Doctrine\Common\Collections\Collection``, etc.)
-``Type::getCollectionKeyType()`` & ``Type::getCollectionValueType()``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+``Type::getCollectionKeyTypes()`` & ``Type::getCollectionValueTypes()``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If the property is a collection, additional type objects may be returned
for both the key and value types of the collection (if the information is
-available), via the :method:`Type::getCollectionKeyType() `
-and :method:`Type::getCollectionValueType() `
+available), via the :method:`Type::getCollectionKeyTypes() `
+and :method:`Type::getCollectionValueTypes() `
methods.
+.. note::
+
+ The ``list`` pseudo type is returned by the PropertyInfo component as an
+ array with integer as the key type.
+
.. _`components-property-info-extractors`:
Extractors
@@ -357,7 +380,7 @@ Using PHP reflection, the :class:`Symfony\\Component\\PropertyInfo\\Extractor\\R
provides list, type and access information from setter and accessor methods.
It can also give the type of a property (even extracting it from the constructor
arguments), and if it is initializable through the constructor. It supports
-return and scalar types for PHP 7::
+return and scalar types::
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
@@ -410,6 +433,54 @@ library is present::
// Description information.
$phpDocExtractor->getShortDescription($class, $property);
$phpDocExtractor->getLongDescription($class, $property);
+ $phpDocExtractor->getDocBlock($class, $property);
+
+.. versionadded:: 7.1
+
+ The :method:`Symfony\\Component\\PropertyInfo\\Extractor\\PhpDocExtractor::getDocBlock`
+ method was introduced in Symfony 7.1.
+
+PhpStanExtractor
+~~~~~~~~~~~~~~~~
+
+.. note::
+
+ This extractor depends on the `phpstan/phpdoc-parser`_ and
+ `phpdocumentor/reflection-docblock`_ libraries.
+
+This extractor fetches information thanks to the PHPStan parser. It gathers
+information from annotations of properties and methods, such as ``@var``,
+``@param`` or ``@return``::
+
+ // src/Domain/Foo.php
+ class Foo
+ {
+ /**
+ * @param string $bar
+ */
+ public function __construct(
+ private string $bar,
+ ) {
+ }
+ }
+
+ // Extraction.php
+ use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor;
+ use App\Domain\Foo;
+
+ $phpStanExtractor = new PhpStanExtractor();
+
+ // Type information.
+ $phpStanExtractor->getTypesFromConstructor(Foo::class, 'bar');
+ // Description information.
+ $phpStanExtractor->getShortDescription($class, 'bar');
+ $phpStanExtractor->getLongDescription($class, 'bar');
+
+.. versionadded:: 7.3
+
+ The :method:`Symfony\\Component\\PropertyInfo\\Extractor\\PhpStanExtractor::getShortDescription`
+ and :method:`Symfony\\Component\\PropertyInfo\\Extractor\\PhpStanExtractor::getLongDescription`
+ methods were introduced in Symfony 7.3.
SerializerExtractor
~~~~~~~~~~~~~~~~~~~
@@ -418,24 +489,25 @@ 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::
- use Doctrine\Common\Annotations\AnnotationReader;
use Symfony\Component\PropertyInfo\Extractor\SerializerExtractor;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
- use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
+ use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
- $serializerClassMetadataFactory = new ClassMetadataFactory(
- new AnnotationLoader(new AnnotationReader)
- );
+ $serializerClassMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
$serializerExtractor = new SerializerExtractor($serializerClassMetadataFactory);
- // List information.
- $serializerExtractor->getProperties($class);
+ // the `serializer_groups` option must be configured (may be set to null)
+ $serializerExtractor->getProperties($class, ['serializer_groups' => ['mygroup']]);
+
+If ``serializer_groups`` is set to ``null``, serializer groups metadata won't be
+checked but you will get only the properties considered by the Serializer
+Component (notably the ``#[Ignore]`` attribute is taken into account).
DoctrineExtractor
~~~~~~~~~~~~~~~~~
@@ -466,6 +538,33 @@ with the ``property_info`` service in the Symfony Framework::
// Type information.
$doctrineExtractor->getTypes($class, $property);
+.. _components-property-information-constructor-extractor:
+
+ConstructorExtractor
+~~~~~~~~~~~~~~~~~~~~
+
+The :class:`Symfony\\Component\\PropertyInfo\\Extractor\\ConstructorExtractor`
+tries to extract properties information by using either the
+:class:`Symfony\\Component\\PropertyInfo\\Extractor\\PhpStanExtractor` or
+the :class:`Symfony\\Component\\PropertyInfo\\Extractor\\ReflectionExtractor`
+on the constructor arguments::
+
+ // src/Domain/Foo.php
+ class Foo
+ {
+ public function __construct(
+ private string $bar,
+ ) {
+ }
+ }
+
+ // Extraction.php
+ use App\Domain\Foo;
+ use Symfony\Component\PropertyInfo\Extractor\ConstructorExtractor;
+
+ $constructorExtractor = new ConstructorExtractor([new ReflectionExtractor()]);
+ $constructorExtractor->getTypes(Foo::class, 'bar')[0]->getBuiltinType(); // returns 'string'
+
.. _`components-property-information-extractors-creation`:
Creating Your Own Extractors
@@ -473,6 +572,7 @@ Creating Your Own Extractors
You can create your own property information extractors by creating a
class that implements one or more of the following interfaces:
+:class:`Symfony\\Component\\PropertyInfo\\Extractor\\ConstructorArgumentTypeExtractorInterface`,
:class:`Symfony\\Component\\PropertyInfo\\PropertyAccessExtractorInterface`,
:class:`Symfony\\Component\\PropertyInfo\\PropertyDescriptionExtractorInterface`,
:class:`Symfony\\Component\\PropertyInfo\\PropertyListExtractorInterface`,
@@ -490,9 +590,16 @@ service by defining it as a service with one or more of the following
* ``property_info.access_extractor`` if it provides access information.
* ``property_info.initializable_extractor`` if it provides initializable information
(it checks if a property can be initialized through the constructor).
+* ``property_info.constructor_extractor`` if it provides type information from the constructor argument.
+
+ .. versionadded:: 7.3
+
+ The ``property_info.constructor_extractor`` tag was introduced in Symfony 7.3.
+.. _`PSR-1`: https://www.php-fig.org/psr/psr-1/
.. _`phpDocumentor Reflection`: https://github.com/phpDocumentor/ReflectionDocBlock
.. _`phpdocumentor/reflection-docblock`: https://packagist.org/packages/phpdocumentor/reflection-docblock
+.. _`phpstan/phpdoc-parser`: https://packagist.org/packages/phpstan/phpdoc-parser
.. _`Doctrine ORM`: https://www.doctrine-project.org/projects/orm.html
.. _`symfony/serializer`: https://packagist.org/packages/symfony/serializer
.. _`symfony/doctrine-bridge`: https://packagist.org/packages/symfony/doctrine-bridge
diff --git a/components/psr7.rst b/components/psr7.rst
index 2df3c6fc3af..04a3b9148b5 100644
--- a/components/psr7.rst
+++ b/components/psr7.rst
@@ -1,6 +1,3 @@
-.. index::
- single: PSR-7
-
The PSR-7 Bridge
================
@@ -33,8 +30,8 @@ Converting from HttpFoundation Objects to PSR-7
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The bridge provides an interface of a factory called
-:class:`Symfony\\Bridge\\PsrHttpMessage\\HttpMessageFactoryInterface`
-that builds objects implementing PSR-7 interfaces from HttpFoundation objects.
+`HttpMessageFactoryInterface`_ that builds objects implementing PSR-7
+interfaces from HttpFoundation objects.
The following code snippet explains how to convert a :class:`Symfony\\Component\\HttpFoundation\\Request`
to a ``Nyholm\Psr7\ServerRequest`` class implementing the
@@ -69,8 +66,8 @@ Converting Objects implementing PSR-7 Interfaces to HttpFoundation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
On the other hand, the bridge provide a factory interface called
-:class:`Symfony\\Bridge\\PsrHttpMessage\\HttpFoundationFactoryInterface`
-that builds HttpFoundation objects from objects implementing PSR-7 interfaces.
+`HttpFoundationFactoryInterface`_ that builds HttpFoundation objects from
+objects implementing PSR-7 interfaces.
The next snippet explain how to convert an object implementing the
``Psr\Http\Message\ServerRequestInterface`` interface to a
@@ -96,3 +93,5 @@ to a :class:`Symfony\\Component\\HttpFoundation\\Response` instance::
.. _`PSR-7`: https://www.php-fig.org/psr/psr-7/
.. _`PSR-17`: https://www.php-fig.org/psr/psr-17/
.. _`libraries that implement psr/http-factory-implementation`: https://packagist.org/providers/psr/http-factory-implementation
+.. _`HttpMessageFactoryInterface`: https://github.com/symfony/psr-http-message-bridge/blob/main/HttpMessageFactoryInterface.php
+.. _`HttpFoundationFactoryInterface`: https://github.com/symfony/psr-http-message-bridge/blob/main/HttpFoundationFactoryInterface.php
diff --git a/components/runtime.rst b/components/runtime.rst
new file mode 100644
index 00000000000..4eb75de2a75
--- /dev/null
+++ b/components/runtime.rst
@@ -0,0 +1,493 @@
+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`_, `FrankenPHP`_ etc. without any changes.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/runtime
+
+.. include:: /components/require_autoload.rst.inc
+
+Usage
+-----
+
+The Runtime component abstracts most bootstrapping logic as so-called
+*runtimes*, allowing you to write front-controllers in a generic way.
+For instance, the Runtime component allows Symfony's ``public/index.php``
+to look like this::
+
+ // public/index.php
+ use App\Kernel;
+
+ require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
+
+ return function (array $context): Kernel {
+ return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
+ };
+
+So how does this front-controller work? At first, the special
+``autoload_runtime.php`` file is automatically created by the Composer plugin in
+the component. This file runs the following logic:
+
+#. It instantiates a :class:`Symfony\\Component\\Runtime\\RuntimeInterface`;
+#. The callable (returned by ``public/index.php``) is passed to the Runtime, whose job
+ is to resolve the arguments (in this example: ``array $context``);
+#. Then, this callable is called to get the application (``App\Kernel``);
+#. At last, the Runtime is used to run the application (i.e. calling
+ ``$kernel->handle(Request::createFromGlobals())->send()``).
+
+.. warning::
+
+ If you use the Composer ``--no-plugins`` option, the ``autoload_runtime.php``
+ file won't be created.
+
+ If you use the Composer ``--no-scripts`` option, make sure your Composer version
+ is ``>=2.1.3``; otherwise the ``autoload_runtime.php`` file won't be created.
+
+To make a console application, the bootstrap code would look like::
+
+ #!/usr/bin/env php
+ setCode(static function (InputInterface $input, OutputInterface $output): void {
+ $output->write('Hello World');
+ });
+
+ return $command;
+ };
+
+:class:`Symfony\\Component\\Console\\Application`
+ Useful with console applications with more than one command. This will use the
+ :class:`Symfony\\Component\\Runtime\\Runner\\Symfony\\ConsoleApplicationRunner`::
+
+ use Symfony\Component\Console\Application;
+ use Symfony\Component\Console\Command\Command;
+ use Symfony\Component\Console\Input\InputInterface;
+ use Symfony\Component\Console\Output\OutputInterface;
+
+ require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
+
+ return static function (array $context): Application {
+ $command = new Command('hello');
+ $command->setCode(static function (InputInterface $input, OutputInterface $output): void {
+ $output->write('Hello World');
+ });
+
+ $app = new Application();
+ $app->add($command);
+ $app->setDefaultCommand('hello', true);
+
+ return $app;
+ };
+
+The ``GenericRuntime`` and ``SymfonyRuntime`` also support these generic
+applications:
+
+:class:`Symfony\\Component\\Runtime\\RunnerInterface`
+ The ``RunnerInterface`` is a way to use a custom application with the
+ generic Runtime::
+
+ // public/index.php
+ use Symfony\Component\Runtime\RunnerInterface;
+
+ require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
+
+ return static function (): RunnerInterface {
+ return new class implements RunnerInterface {
+ public function run(): int
+ {
+ echo 'Hello World';
+
+ return 0;
+ }
+ };
+ };
+
+``callable``
+ Your "application" can also be a ``callable``. The first callable will return
+ the "application" and the second callable is the "application" itself::
+
+ // public/index.php
+ require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
+
+ return static function (): callable {
+ $app = static function(): int {
+ echo 'Hello World';
+
+ return 0;
+ };
+
+ return $app;
+ };
+
+``void``
+ If the callable doesn't return anything, the ``SymfonyRuntime`` will assume
+ everything is fine::
+
+ require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
+
+ return function (): void {
+ echo 'Hello world';
+ };
+
+Using Options
+~~~~~~~~~~~~~
+
+Some behavior of the Runtimes can be modified through runtime options. They
+can be set using the ``APP_RUNTIME_OPTIONS`` environment variable::
+
+ $_SERVER['APP_RUNTIME_OPTIONS'] = [
+ 'project_dir' => '/var/task',
+ ];
+
+ require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
+
+ // ...
+
+You can also configure ``extra.runtime`` in ``composer.json``:
+
+.. code-block:: json
+
+ {
+ "require": {
+ "...": "..."
+ },
+ "extra": {
+ "runtime": {
+ "project_dir": "/var/task"
+ }
+ }
+ }
+
+Then, update your Composer files (running ``composer dump-autoload``, for instance),
+so that the ``vendor/autoload_runtime.php`` files gets regenerated with the new option.
+
+The following options are supported by the ``SymfonyRuntime``:
+
+``env`` (default: ``APP_ENV`` environment variable, or ``"dev"``)
+ To define the name of the environment the app runs in.
+``disable_dotenv`` (default: ``false``)
+ To disable looking for ``.env`` files.
+``dotenv_path`` (default: ``.env``)
+ To define the path of dot-env files.
+``dotenv_overload`` (default: ``false``)
+ To tell Dotenv whether to override ``.env`` vars with ``.env.local`` (or other ``.env.*`` files)
+``use_putenv``
+ To tell Dotenv to set env vars using ``putenv()`` (NOT RECOMMENDED).
+``prod_envs`` (default: ``["prod"]``)
+ To define the names of the production envs.
+``test_envs`` (default: ``["test"]``)
+ To define the names of the test envs.
+
+Besides these, the ``GenericRuntime`` and ``SymfonyRuntime`` also support
+these options:
+
+``debug`` (default: the value of the env var defined by ``debug_var_name`` option
+ (usually, ``APP_DEBUG``), or ``true`` if such env var is not defined)
+ Toggles the :ref:`debug mode ` of Symfony applications (e.g. to
+ display errors)
+``runtimes``
+ Maps "application types" to a ``GenericRuntime`` implementation that
+ knows how to deal with each of them.
+``error_handler`` (default: :class:`Symfony\\Component\\Runtime\\Internal\\BasicErrorHandler` or :class:`Symfony\\Component\\Runtime\\Internal\\SymfonyErrorHandler` for ``SymfonyRuntime``)
+ Defines the class to use to handle PHP errors.
+``env_var_name`` (default: ``"APP_ENV"``)
+ Defines the name of the env var that stores the name of the
+ :ref:`configuration environment `
+ to use when running the application.
+``debug_var_name`` (default: ``"APP_DEBUG"``)
+ Defines the name of the env var that stores the value of the
+ :ref:`debug mode ` flag to use when running the application.
+
+Create Your Own Runtime
+-----------------------
+
+This is an advanced topic that describes the internals of the Runtime component.
+
+Using the Runtime component will benefit maintainers because the bootstrap
+logic could be versioned as a part of a normal package. If the application
+author decides to use this component, the package maintainer of the Runtime
+class will have more control and can fix bugs and add features.
+
+The Runtime component is designed to be totally generic and able to run any
+application outside of the global state in 6 steps:
+
+#. The main entry point returns a *callable* (the "app") that wraps the application;
+#. The *app callable* is passed to ``RuntimeInterface::getResolver()``, which returns
+ a :class:`Symfony\\Component\\Runtime\\ResolverInterface`. This resolver returns
+ an array with the app callable (or something that decorates this callable) at
+ index 0 and all its resolved arguments at index 1.
+#. The *app callable* is invoked with its arguments, it will return an object that
+ represents the application.
+#. This *application object* is passed to ``RuntimeInterface::getRunner()``, which
+ returns a :class:`Symfony\\Component\\Runtime\\RunnerInterface`: an instance
+ that knows how to "run" the application object.
+#. The ``RunnerInterface::run(object $application)`` is called and it returns the
+ exit status code as ``int``.
+#. The PHP engine is terminated with this status code.
+
+When creating a new runtime, there are two things to consider: First, what arguments
+will the end user use? Second, what will the user's application look like?
+
+For instance, imagine you want to create a runtime for `ReactPHP`_:
+
+**What arguments will the end user use?**
+
+For a generic ReactPHP application, no special arguments are
+typically required. This means that you can use the
+:class:`Symfony\\Component\\Runtime\\GenericRuntime`.
+
+**What will the user's application look like?**
+
+There is also no typical React application, so you might want to rely on
+the `PSR-15`_ interfaces for HTTP request handling.
+
+However, a ReactPHP application will need some special logic to *run*. That logic
+is added in a new class implementing :class:`Symfony\\Component\\Runtime\\RunnerInterface`::
+
+ use Psr\Http\Message\ResponseInterface;
+ use Psr\Http\Message\ServerRequestInterface;
+ use Psr\Http\Server\RequestHandlerInterface;
+ use React\EventLoop\Factory as ReactFactory;
+ use React\Http\Server as ReactHttpServer;
+ use React\Socket\Server as ReactSocketServer;
+ use Symfony\Component\Runtime\RunnerInterface;
+
+ class ReactPHPRunner implements RunnerInterface
+ {
+ public function __construct(
+ private RequestHandlerInterface $application,
+ private int $port,
+ ) {
+ }
+
+ public function run(): int
+ {
+ $application = $this->application;
+ $loop = ReactFactory::create();
+
+ // configure ReactPHP to correctly handle the PSR-15 application
+ $server = new ReactHttpServer(
+ $loop,
+ function (ServerRequestInterface $request) use ($application): ResponseInterface {
+ return $application->handle($request);
+ }
+ );
+
+ // start the ReactPHP server
+ $socket = new ReactSocketServer($this->port, $loop);
+ $server->listen($socket);
+
+ $loop->run();
+
+ return 0;
+ }
+ }
+
+By extending the ``GenericRuntime``, you make sure that the application is
+always using this ``ReactPHPRunner``::
+
+ use Symfony\Component\Runtime\GenericRuntime;
+ use Symfony\Component\Runtime\RunnerInterface;
+
+ class ReactPHPRuntime extends GenericRuntime
+ {
+ private int $port;
+
+ public function __construct(array $options)
+ {
+ $this->port = $options['port'] ?? 8080;
+ parent::__construct($options);
+ }
+
+ public function getRunner(?object $application): RunnerInterface
+ {
+ if ($application instanceof RequestHandlerInterface) {
+ return new ReactPHPRunner($application, $this->port);
+ }
+
+ // if it's not a PSR-15 application, use the GenericRuntime to
+ // run the application (see "Resolvable Applications" above)
+ return parent::getRunner($application);
+ }
+ }
+
+The end user will now be able to create front controller like::
+
+ require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
+
+ return function (array $context): SomeCustomPsr15Application {
+ return new SomeCustomPsr15Application();
+ };
+
+.. _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/security.rst b/components/security.rst
deleted file mode 100644
index 9985b611c63..00000000000
--- a/components/security.rst
+++ /dev/null
@@ -1,65 +0,0 @@
-.. index::
- single: Security
-
-The Security Component
-======================
-
- The Security component provides a complete security system for your web
- application. It ships with facilities for authenticating using HTTP basic
- authentication, interactive form login or X.509 certificate login, but also
- allows you to implement your own authentication strategies. Furthermore, the
- component provides ways to authorize authenticated users based on their
- roles.
-
-Installation
-------------
-
-The Security component is divided into several smaller sub-components which can
-be used separately:
-
-``symfony/security-core``
- It provides all the common security features, from authentication to
- authorization and from encoding passwords to loading users.
-
-``symfony/security-http``
- It integrates the core sub-component with the HTTP protocol to handle HTTP
- requests and responses.
-
-``symfony/security-csrf``
- It provides protection against `CSRF attacks`_.
-
-``symfony/security-guard``
- It brings many layers of authentication together, allowing the creation
- of complex authentication systems.
-
-You can install each of them separately in your project:
-
-.. code-block:: terminal
-
- $ composer require symfony/security-core
- $ composer require symfony/security-http
- $ composer require symfony/security-csrf
- $ composer require symfony/security-guard
-
-.. include:: /components/require_autoload.rst.inc
-
-.. seealso::
-
- This article explains how to use the Security features as an independent
- component in any PHP application. Read the :doc:`/security` article to learn
- about how to use it in Symfony applications.
-
-Learn More
-----------
-
-.. toctree::
- :maxdepth: 1
- :glob:
-
- /components/security/*
- /security
- /security/*
- /reference/configuration/security
- /reference/constraints/UserPassword
-
-.. _`CSRF attacks`: https://en.wikipedia.org/wiki/Cross-site_request_forgery
diff --git a/components/security/authentication.rst b/components/security/authentication.rst
deleted file mode 100644
index f33cde59a18..00000000000
--- a/components/security/authentication.rst
+++ /dev/null
@@ -1,326 +0,0 @@
-.. index::
- single: Security, Authentication
-
-Authentication
-==============
-
-When a request points to a secured area, and one of the listeners from the
-firewall map is able to extract the user's credentials from the current
-:class:`Symfony\\Component\\HttpFoundation\\Request` object, it should create
-a token, containing these credentials. The next thing the listener should
-do is ask the authentication manager to validate the given token, and return
-an *authenticated* token if the supplied credentials were found to be valid.
-The listener should then store the authenticated token using
-:class:`the token storage `::
-
- use Symfony\Component\HttpKernel\Event\RequestEvent;
- use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
- use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
- use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
-
- class SomeAuthenticationListener
- {
- /**
- * @var TokenStorageInterface
- */
- private $tokenStorage;
-
- /**
- * @var AuthenticationManagerInterface
- */
- private $authenticationManager;
-
- /**
- * @var string Uniquely identifies the secured area
- */
- private $providerKey;
-
- // ...
-
- public function __invoke(RequestEvent $event)
- {
- $request = $event->getRequest();
-
- $username = ...;
- $password = ...;
-
- $unauthenticatedToken = new UsernamePasswordToken(
- $username,
- $password,
- $this->providerKey
- );
-
- $authenticatedToken = $this
- ->authenticationManager
- ->authenticate($unauthenticatedToken);
-
- $this->tokenStorage->setToken($authenticatedToken);
- }
- }
-
-.. note::
-
- A token can be of any class, as long as it implements
- :class:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface`.
-
-The Authentication Manager
---------------------------
-
-The default authentication manager is an instance of
-:class:`Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationProviderManager`::
-
- use Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager;
- use Symfony\Component\Security\Core\Exception\AuthenticationException;
-
- // instances of Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface
- $providers = [...];
-
- $authenticationManager = new AuthenticationProviderManager($providers);
-
- try {
- $authenticatedToken = $authenticationManager
- ->authenticate($unauthenticatedToken);
- } catch (AuthenticationException $exception) {
- // authentication failed
- }
-
-The ``AuthenticationProviderManager``, when instantiated, receives several
-authentication providers, each supporting a different type of token.
-
-.. note::
-
- You may write your own authentication manager, the only requirement is that
- it implements :class:`Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationManagerInterface`.
-
-.. _authentication_providers:
-
-Authentication Providers
-------------------------
-
-Each provider (since it implements
-:class:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\AuthenticationProviderInterface`)
-has a :method:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\AuthenticationProviderInterface::supports` method
-by which the ``AuthenticationProviderManager``
-can determine if it supports the given token. If this is the case, the
-manager then calls the provider's :method:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\AuthenticationProviderInterface::authenticate` method.
-This method should return an authenticated token or throw an
-:class:`Symfony\\Component\\Security\\Core\\Exception\\AuthenticationException`
-(or any other exception extending it).
-
-Authenticating Users by their Username and Password
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-An authentication provider will attempt to authenticate a user based on
-the credentials they provided. Usually these are a username and a password.
-Most web applications store their user's username and a hash of the user's
-password combined with a randomly generated salt. This means that the average
-authentication would consist of fetching the salt and the hashed password
-from the user data storage, hash the password the user has just provided
-(e.g. using a login form) with the salt and compare both to determine if
-the given password is valid.
-
-This functionality is offered by the :class:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\DaoAuthenticationProvider`.
-It fetches the user's data from a :class:`Symfony\\Component\\Security\\Core\\User\\UserProviderInterface`,
-uses a :class:`Symfony\\Component\\Security\\Core\\Encoder\\PasswordEncoderInterface`
-to create a hash of the password and returns an authenticated token if the
-password was valid::
-
- use Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider;
- use Symfony\Component\Security\Core\Encoder\EncoderFactory;
- use Symfony\Component\Security\Core\User\InMemoryUserProvider;
- use Symfony\Component\Security\Core\User\UserChecker;
-
- $userProvider = new InMemoryUserProvider(
- [
- 'admin' => [
- // password is "foo"
- 'password' => '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg==',
- 'roles' => ['ROLE_ADMIN'],
- ],
- ]
- );
-
- // for some extra checks: is account enabled, locked, expired, etc.
- $userChecker = new UserChecker();
-
- // an array of password encoders (see below)
- $encoderFactory = new EncoderFactory(...);
-
- $daoProvider = new DaoAuthenticationProvider(
- $userProvider,
- $userChecker,
- 'secured_area',
- $encoderFactory
- );
-
- $daoProvider->authenticate($unauthenticatedToken);
-
-.. note::
-
- The example above demonstrates the use of the "in-memory" user provider,
- but you may use any user provider, as long as it implements
- :class:`Symfony\\Component\\Security\\Core\\User\\UserProviderInterface`.
- It is also possible to let multiple user providers try to find the user's
- data, using the :class:`Symfony\\Component\\Security\\Core\\User\\ChainUserProvider`.
-
-The Password Encoder Factory
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The :class:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\DaoAuthenticationProvider`
-uses an encoder factory to create a password encoder for a given type of
-user. This allows you to use different encoding strategies for different
-types of users. The default :class:`Symfony\\Component\\Security\\Core\\Encoder\\EncoderFactory`
-receives an array of encoders::
-
- use Acme\Entity\LegacyUser;
- use Symfony\Component\Security\Core\Encoder\EncoderFactory;
- use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder;
- use Symfony\Component\Security\Core\User\User;
-
- $defaultEncoder = new MessageDigestPasswordEncoder('sha512', true, 5000);
- $weakEncoder = new MessageDigestPasswordEncoder('md5', true, 1);
-
- $encoders = [
- User::class => $defaultEncoder,
- LegacyUser::class => $weakEncoder,
- // ...
- ];
- $encoderFactory = new EncoderFactory($encoders);
-
-Each encoder should implement :class:`Symfony\\Component\\Security\\Core\\Encoder\\PasswordEncoderInterface`
-or be an array with a ``class`` and an ``arguments`` key, which allows the
-encoder factory to construct the encoder only when it is needed.
-
-Creating a custom Password Encoder
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-There are many built-in password encoders. But if you need to create your
-own, it needs to follow these rules:
-
-#. The class must implement :class:`Symfony\\Component\\Security\\Core\\Encoder\\PasswordEncoderInterface`;
-
-#. The implementations of
- :method:`Symfony\\Component\\Security\\Core\\Encoder\\PasswordEncoderInterface::encodePassword`
- and
- :method:`Symfony\\Component\\Security\\Core\\Encoder\\PasswordEncoderInterface::isPasswordValid`
- must first of all make sure the password is not too long, i.e. the password length is no longer
- than 4096 characters. This is for security reasons (see `CVE-2013-5750`_), and you can use the
- :method:`Symfony\\Component\\Security\\Core\\Encoder\\BasePasswordEncoder::isPasswordTooLong`
- method for this check::
-
- use Symfony\Component\Security\Core\Encoder\BasePasswordEncoder;
- use Symfony\Component\Security\Core\Exception\BadCredentialsException;
-
- class FoobarEncoder extends BasePasswordEncoder
- {
- public function encodePassword($raw, $salt)
- {
- if ($this->isPasswordTooLong($raw)) {
- throw new BadCredentialsException('Invalid password.');
- }
-
- // ...
- }
-
- public function isPasswordValid($encoded, $raw, $salt)
- {
- if ($this->isPasswordTooLong($raw)) {
- return false;
- }
-
- // ...
- }
- }
-
-Using Password Encoders
-~~~~~~~~~~~~~~~~~~~~~~~
-
-When the :method:`Symfony\\Component\\Security\\Core\\Encoder\\EncoderFactory::getEncoder`
-method of the password encoder factory is called with the user object as
-its first argument, it will return an encoder of type :class:`Symfony\\Component\\Security\\Core\\Encoder\\PasswordEncoderInterface`
-which should be used to encode this user's password::
-
- // a Acme\Entity\LegacyUser instance
- $user = ...;
-
- // the password that was submitted, e.g. when registering
- $plainPassword = ...;
-
- $encoder = $encoderFactory->getEncoder($user);
-
- // returns $weakEncoder (see above)
- $encodedPassword = $encoder->encodePassword($plainPassword, $user->getSalt());
-
- $user->setPassword($encodedPassword);
-
- // ... save the user
-
-Now, when you want to check if the submitted password (e.g. when trying to log
-in) is correct, you can use::
-
- // fetch the Acme\Entity\LegacyUser
- $user = ...;
-
- // the submitted password, e.g. from the login form
- $plainPassword = ...;
-
- $validPassword = $encoder->isPasswordValid(
- $user->getPassword(), // the encoded password
- $plainPassword, // the submitted password
- $user->getSalt()
- );
-
-Authentication Events
----------------------
-
-The security component provides the following authentication events:
-
-=============================== ================================================================= ==============================================================================
-Name Event Constant Argument Passed to the Listener
-=============================== ================================================================= ==============================================================================
-security.authentication.success ``AuthenticationEvents::AUTHENTICATION_SUCCESS`` :class:`Symfony\\Component\\Security\\Core\\Event\\AuthenticationSuccessEvent`
-security.authentication.failure ``AuthenticationEvents::AUTHENTICATION_FAILURE`` :class:`Symfony\\Component\\Security\\Core\\Event\\AuthenticationFailureEvent`
-security.interactive_login ``SecurityEvents::INTERACTIVE_LOGIN`` :class:`Symfony\\Component\\Security\\Http\\Event\\InteractiveLoginEvent`
-security.switch_user ``SecurityEvents::SWITCH_USER`` :class:`Symfony\\Component\\Security\\Http\\Event\\SwitchUserEvent`
-security.logout_on_change ``Symfony\Component\Security\Http\Event\DeauthenticatedEvent`` :class:`Symfony\\Component\\Security\\Http\\Event\\DeauthenticatedEvent`
-=============================== ================================================================= ==============================================================================
-
-Authentication Success and Failure Events
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-When a provider authenticates the user, a ``security.authentication.success``
-event is dispatched. But beware - this event may fire, for example, on *every*
-request if you have session-based authentication, if ``always_authenticate_before_granting``
-is enabled or if token is not authenticated before AccessListener is invoked.
-See ``security.interactive_login`` below if you need to do something when a user *actually* logs in.
-
-When a provider attempts authentication but fails (i.e. throws an ``AuthenticationException``),
-a ``security.authentication.failure`` event is dispatched. You could listen on
-the ``security.authentication.failure`` event, for example, in order to log
-failed login attempts.
-
-Security Events
-~~~~~~~~~~~~~~~
-
-The ``security.interactive_login`` event is triggered after a user has actively
-logged into your website. It is important to distinguish this action from
-non-interactive authentication methods, such as:
-
-* authentication based on your session.
-* authentication using a HTTP basic header.
-
-You could listen on the ``security.interactive_login`` event, for example, in
-order to give your user a welcome flash message every time they log in.
-
-The ``security.switch_user`` event is triggered every time you activate
-the ``switch_user`` firewall listener.
-
-The ``Symfony\Component\Security\Http\Event\DeauthenticatedEvent`` event is triggered when a token has been deauthenticated
-because of a user change, it can help you doing some clean-up task when a logout has been triggered.
-
-.. seealso::
-
- For more information on switching users, see
- :doc:`/security/impersonating_user`.
-
-.. _`CVE-2013-5750`: https://symfony.com/blog/cve-2013-5750-security-issue-in-fosuserbundle-login-form
diff --git a/components/security/authorization.rst b/components/security/authorization.rst
deleted file mode 100644
index c074cf14b75..00000000000
--- a/components/security/authorization.rst
+++ /dev/null
@@ -1,281 +0,0 @@
-.. index::
- single: Security, Authorization
-
-Authorization
-=============
-
-When any of the authentication providers (see :ref:`authentication_providers`)
-has verified the still-unauthenticated token, an authenticated token will
-be returned. The authentication listener should set this token directly
-in the :class:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\Storage\\TokenStorageInterface`
-using its :method:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\Storage\\TokenStorageInterface::setToken`
-method.
-
-From then on, the user is authenticated, i.e. identified. Now, other parts
-of the application can use the token to decide whether or not the user may
-request a certain URI, or modify a certain object. This decision will be made
-by an instance of :class:`Symfony\\Component\\Security\\Core\\Authorization\\AccessDecisionManagerInterface`.
-
-An authorization decision will always be based on a few things:
-
-* The current token
- For instance, the token's :method:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface::getRoleNames`
- method may be used to retrieve the roles of the current user (e.g.
- ``ROLE_SUPER_ADMIN``), or a decision may be based on the class of the token.
-* A set of attributes
- Each attribute stands for a certain right the user should have, e.g.
- ``ROLE_ADMIN`` to make sure the user is an administrator.
-* An object (optional)
- Any object for which access control needs to be checked, like
- an article or a comment object.
-
-.. _components-security-access-decision-manager:
-
-Access Decision Manager
------------------------
-
-Since deciding whether or not a user is authorized to perform a certain
-action can be a complicated process, the standard :class:`Symfony\\Component\\Security\\Core\\Authorization\\AccessDecisionManager`
-itself depends on multiple voters, and makes a final verdict based on all
-the votes (either positive, negative or neutral) it has received. It
-recognizes several strategies:
-
-``affirmative`` (default)
- grant access as soon as there is one voter granting access;
-
-``consensus``
- grant access if there are more voters granting access than there are denying;
-
-``unanimous``
- only grant access if none of the voters has denied access. If all voters
- abstained from voting, the decision is based on the ``allow_if_all_abstain``
- config option (which defaults to ``false``).
-
-``priority``
- grants or denies access by the first voter that does not abstain;
-
- .. versionadded:: 5.1
-
- The ``priority`` version strategy was introduced in Symfony 5.1.
-
-Usage of the available options in detail::
-
- use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;
-
- // instances of Symfony\Component\Security\Core\Authorization\Voter\VoterInterface
- $voters = [...];
-
- // one of "affirmative", "consensus", "unanimous", "priority"
- $strategy = ...;
-
- // whether or not to grant access when all voters abstain
- $allowIfAllAbstainDecisions = ...;
-
- // whether or not to grant access when there is no majority (applies only to the "consensus" strategy)
- $allowIfEqualGrantedDeniedDecisions = ...;
-
- $accessDecisionManager = new AccessDecisionManager(
- $voters,
- $strategy,
- $allowIfAllAbstainDecisions,
- $allowIfEqualGrantedDeniedDecisions
- );
-
-.. seealso::
-
- You can change the default strategy in the
- :ref:`configuration `.
-
-Voters
-------
-
-Voters are instances
-of :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`,
-which means they have to implement a few methods which allows the decision
-manager to use them:
-
-``vote(TokenInterface $token, $object, array $attributes)``
- this method will do the actual voting and return a value equal to one
- of the class constants of :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`,
- i.e. ``VoterInterface::ACCESS_GRANTED``, ``VoterInterface::ACCESS_DENIED``
- or ``VoterInterface::ACCESS_ABSTAIN``;
-
-The Security component contains some standard voters which cover many use
-cases:
-
-AuthenticatedVoter
-~~~~~~~~~~~~~~~~~~
-
-The :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AuthenticatedVoter`
-voter supports the attributes ``IS_AUTHENTICATED_FULLY``,
-``IS_AUTHENTICATED_REMEMBERED``, ``IS_AUTHENTICATED_ANONYMOUSLY``,
-to grant access based on the current level of authentication, i.e. is the
-user fully authenticated, or only based on a "remember-me" cookie, or even
-authenticated anonymously?
-
-It also supports the attributes ``IS_ANONYMOUS``, ``IS_REMEMBERED``,
-``IS_IMPERSONATED`` to grant access based on a specific state of
-authentication.
-
-.. versionadded:: 5.1
-
- The ``IS_ANONYMOUS``, ``IS_REMEMBERED`` and ``IS_IMPERSONATED``
- attributes were introduced in Symfony 5.1.
-
-::
-
- use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver;
-
- $trustResolver = new AuthenticationTrustResolver();
-
- $authenticatedVoter = new AuthenticatedVoter($trustResolver);
-
- // instance of Symfony\Component\Security\Core\Authentication\Token\TokenInterface
- $token = ...;
-
- // any object
- $object = ...;
-
- $vote = $authenticatedVoter->vote($token, $object, ['IS_AUTHENTICATED_FULLY']);
-
-RoleVoter
-~~~~~~~~~
-
-The :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\RoleVoter`
-supports attributes starting with ``ROLE_`` and grants access to the user
-when at least one required ``ROLE_*`` attribute can be found in the array of
-roles returned by the token's :method:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface::getRoleNames`
-method::
-
- use Symfony\Component\Security\Core\Authorization\Voter\RoleVoter;
-
- $roleVoter = new RoleVoter('ROLE_');
-
- $roleVoter->vote($token, $object, ['ROLE_ADMIN']);
-
-RoleHierarchyVoter
-~~~~~~~~~~~~~~~~~~
-
-The :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\RoleHierarchyVoter`
-extends :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\RoleVoter`
-and provides some additional functionality: it knows how to handle a
-hierarchy of roles. For instance, a ``ROLE_SUPER_ADMIN`` role may have sub-roles
-``ROLE_ADMIN`` and ``ROLE_USER``, so that when a certain object requires the
-user to have the ``ROLE_ADMIN`` role, it grants access to users who in fact
-have the ``ROLE_ADMIN`` role, but also to users having the ``ROLE_SUPER_ADMIN``
-role::
-
- use Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter;
- use Symfony\Component\Security\Core\Role\RoleHierarchy;
-
- $hierarchy = [
- 'ROLE_SUPER_ADMIN' => ['ROLE_ADMIN', 'ROLE_USER'],
- ];
-
- $roleHierarchy = new RoleHierarchy($hierarchy);
-
- $roleHierarchyVoter = new RoleHierarchyVoter($roleHierarchy);
-
-ExpressionVoter
-~~~~~~~~~~~~~~~
-
-The :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\ExpressionVoter`
-grants access based on the evaluation of expressions created with the
-:doc:`ExpressionLanguage component `. These
-expressions have access to a number of
-:ref:`special security variables `::
-
- use Symfony\Component\ExpressionLanguage\Expression;
- use Symfony\Component\Security\Core\Authorization\Voter\ExpressionVoter;
-
- // Symfony\Component\Security\Core\Authorization\ExpressionLanguage;
- $expressionLanguage = ...;
-
- // instance of Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface
- $trustResolver = ...;
-
- // Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface
- $authorizationChecker = ...;
-
- $expressionVoter = new ExpressionVoter($expressionLanguage, $trustResolver, $authorizationChecker);
-
- // instance of Symfony\Component\Security\Core\Authentication\Token\TokenInterface
- $token = ...;
-
- // any object
- $object = ...;
-
- $expression = new Expression(
- '"ROLE_ADMIN" in role_names or (not is_anonymous() and user.isSuperAdmin())'
- )
-
- $vote = $expressionVoter->vote($token, $object, [$expression]);
-
-.. note::
-
- When you make your own voter, you can use its constructor to inject any
- dependencies it needs to come to a decision.
-
-Roles
------
-
-Roles are strings that give expression to a certain right the user has (e.g.
-*"edit a blog post"*, *"create an invoice"*). You can freely choose those
-strings. The only requirement is that they must start with the ``ROLE_`` prefix
-(e.g. ``ROLE_POST_EDIT``, ``ROLE_INVOICE_CREATE``).
-
-Using the Decision Manager
---------------------------
-
-The Access Listener
-~~~~~~~~~~~~~~~~~~~
-
-The access decision manager can be used at any point in a request to decide whether
-or not the current user is entitled to access a given resource. One optional,
-but useful, method for restricting access based on a URL pattern is the
-:class:`Symfony\\Component\\Security\\Http\\Firewall\\AccessListener`,
-which is one of the firewall listeners (see :ref:`firewall_listeners`) that
-is triggered for each request matching the firewall map (see :ref:`firewall`).
-
-It uses an access map (which should be an instance of :class:`Symfony\\Component\\Security\\Http\\AccessMapInterface`)
-which contains request matchers and a corresponding set of attributes that
-are required for the current user to get access to the application::
-
- use Symfony\Component\HttpFoundation\RequestMatcher;
- use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
- use Symfony\Component\Security\Http\AccessMap;
- use Symfony\Component\Security\Http\Firewall\AccessListener;
-
- $accessMap = new AccessMap();
- $tokenStorage = new TokenStorage();
- $requestMatcher = new RequestMatcher('^/admin');
- $accessMap->add($requestMatcher, ['ROLE_ADMIN']);
-
- $accessListener = new AccessListener(
- $tokenStorage,
- $accessDecisionManager,
- $accessMap,
- $authenticationManager
- );
-
-Authorization Checker
-~~~~~~~~~~~~~~~~~~~~~
-
-The access decision manager is also available to other parts of the application
-via the :method:`Symfony\\Component\\Security\\Core\\Authorization\\AuthorizationChecker::isGranted`
-method of the :class:`Symfony\\Component\\Security\\Core\\Authorization\\AuthorizationChecker`.
-A call to this method will directly delegate the question to the access
-decision manager::
-
- use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
- use Symfony\Component\Security\Core\Exception\AccessDeniedException;
-
- $authorizationChecker = new AuthorizationChecker(
- $tokenStorage,
- $authenticationManager,
- $accessDecisionManager
- );
-
- if (!$authorizationChecker->isGranted('ROLE_ADMIN')) {
- throw new AccessDeniedException();
- }
diff --git a/components/security/firewall.rst b/components/security/firewall.rst
deleted file mode 100644
index adb0fae6e4a..00000000000
--- a/components/security/firewall.rst
+++ /dev/null
@@ -1,164 +0,0 @@
-.. index::
- single: Security, Firewall
-
-The Firewall and Authorization
-==============================
-
-Central to the Security component is authorization. This is handled by an instance
-of :class:`Symfony\\Component\\Security\\Core\\Authorization\\AuthorizationCheckerInterface`.
-When all steps in the process of authenticating the user have been taken successfully,
-you can ask the authorization checker if the authenticated user has access to a
-certain action or resource of the application::
-
- use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
- use Symfony\Component\Security\Core\Exception\AccessDeniedException;
-
- // instance of Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface
- $tokenStorage = ...;
-
- // instance of Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface
- $authenticationManager = ...;
-
- // instance of Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface
- $accessDecisionManager = ...;
-
- $authorizationChecker = new AuthorizationChecker(
- $tokenStorage,
- $authenticationManager,
- $accessDecisionManager
- );
-
- // ... authenticate the user
-
- if (!$authorizationChecker->isGranted('ROLE_ADMIN')) {
- throw new AccessDeniedException();
- }
-
-.. note::
-
- Read the dedicated articles to learn more about :doc:`/components/security/authentication`
- and :doc:`/components/security/authorization`.
-
-.. _firewall:
-
-A Firewall for HTTP Requests
-----------------------------
-
-Authenticating a user is done by the firewall. An application may have
-multiple secured areas, so the firewall is configured using a map of these
-secured areas. For each of these areas, the map contains a request matcher
-and a collection of listeners. The request matcher gives the firewall the
-ability to find out if the current request points to a secured area.
-The listeners are then asked if the current request can be used to authenticate
-the user::
-
- use Symfony\Component\HttpFoundation\RequestMatcher;
- use Symfony\Component\Security\Http\Firewall\ExceptionListener;
- use Symfony\Component\Security\Http\FirewallMap;
-
- $firewallMap = new FirewallMap();
-
- $requestMatcher = new RequestMatcher('^/secured-area/');
-
- // array of callables
- $listeners = [...];
-
- $exceptionListener = new ExceptionListener(...);
-
- $firewallMap->add($requestMatcher, $listeners, $exceptionListener);
-
-The firewall map will be given to the firewall as its first argument, together
-with the event dispatcher that is used by the :class:`Symfony\\Component\\HttpKernel\\HttpKernel`::
-
- use Symfony\Component\HttpKernel\KernelEvents;
- use Symfony\Component\Security\Http\Firewall;
-
- // the EventDispatcher used by the HttpKernel
- $dispatcher = ...;
-
- $firewall = new Firewall($firewallMap, $dispatcher);
-
- $dispatcher->addListener(
- KernelEvents::REQUEST,
- [$firewall, 'onKernelRequest']
- );
-
-The firewall is registered to listen to the ``kernel.request`` event that
-will be dispatched by the HttpKernel at the beginning of each request
-it processes. This way, the firewall may prevent the user from going any
-further than allowed.
-
-Firewall Config
-~~~~~~~~~~~~~~~
-
-The information about a given firewall, such as its name, provider, context,
-entry point and access denied URL, is provided by instances of the
-:class:`Symfony\\Bundle\\SecurityBundle\\Security\\FirewallConfig` class.
-
-This object can be accessed through the ``getFirewallConfig(Request $request)``
-method of the :class:`Symfony\\Bundle\\SecurityBundle\\Security\\FirewallMap` class and
-through the ``getConfig()`` method of the
-:class:`Symfony\\Bundle\\SecurityBundle\\Security\\FirewallContext` class.
-
-.. _firewall_listeners:
-
-Firewall Listeners
-~~~~~~~~~~~~~~~~~~
-
-When the firewall gets notified of the ``kernel.request`` event, it asks
-the firewall map if the request matches one of the secured areas. The first
-secured area that matches the request will return a set of corresponding
-firewall listeners (which each is a callable).
-These listeners will all be asked to handle the current request. This basically
-means: find out if the current request contains any information by which
-the user might be authenticated (for instance the Basic HTTP authentication
-listener checks if the request has a header called ``PHP_AUTH_USER``).
-
-Exception Listener
-~~~~~~~~~~~~~~~~~~
-
-If any of the listeners throws an :class:`Symfony\\Component\\Security\\Core\\Exception\\AuthenticationException`,
-the exception listener that was provided when adding secured areas to the
-firewall map will jump in.
-
-The exception listener determines what happens next, based on the arguments
-it received when it was created. It may start the authentication procedure,
-perhaps ask the user to supply their credentials again (when they have only been
-authenticated based on a "remember-me" cookie), or transform the exception
-into an :class:`Symfony\\Component\\HttpKernel\\Exception\\AccessDeniedHttpException`,
-which will eventually result in an "HTTP/1.1 403: Access Denied" response.
-
-Entry Points
-~~~~~~~~~~~~
-
-When the user is not authenticated at all (i.e. when the token storage
-has no token yet), the firewall's entry point will be called to "start"
-the authentication process. An entry point should implement
-:class:`Symfony\\Component\\Security\\Http\\EntryPoint\\AuthenticationEntryPointInterface`,
-which has only one method: :method:`Symfony\\Component\\Security\\Http\\EntryPoint\\AuthenticationEntryPointInterface::start`.
-This method receives the current :class:`Symfony\\Component\\HttpFoundation\\Request`
-object and the exception by which the exception listener was triggered.
-The method should return a :class:`Symfony\\Component\\HttpFoundation\\Response`
-object. This could be, for instance, the page containing the login form or,
-in the case of Basic HTTP authentication, a response with a ``WWW-Authenticate``
-header, which will prompt the user to supply their username and password.
-
-Flow: Firewall, Authentication, Authorization
----------------------------------------------
-
-Hopefully you can now see a little bit about how the "flow" of the security
-context works:
-
-#. The Firewall is registered as a listener on the ``kernel.request`` event;
-#. At the beginning of the request, the Firewall checks the firewall map
- to see if any firewall should be active for this URL;
-#. If a firewall is found in the map for this URL, its listeners are notified;
-#. Each listener checks to see if the current request contains any authentication
- information - a listener may (a) authenticate a user, (b) throw an
- ``AuthenticationException``, or (c) do nothing (because there is no
- authentication information on the request);
-#. Once a user is authenticated, you'll use :doc:`/components/security/authorization`
- to deny access to certain resources.
-
-Read the next articles to find out more about :doc:`/components/security/authentication`
-and :doc:`/components/security/authorization`.
diff --git a/components/security/secure_tools.rst b/components/security/secure_tools.rst
deleted file mode 100644
index a9d6e0fec3a..00000000000
--- a/components/security/secure_tools.rst
+++ /dev/null
@@ -1,56 +0,0 @@
-Securely Generating Random Values
-=================================
-
-The Symfony Security component comes with a collection of nice utilities
-related to security. These utilities are used by Symfony, but you should
-also use them if you want to solve the problem they address.
-
-.. note::
-
- The functions described in this article were introduced in PHP 5.6 or 7.
- For older PHP versions, a polyfill is provided by the
- `Symfony Polyfill Component`_.
-
-Comparing Strings
-~~~~~~~~~~~~~~~~~
-
-The time it takes to compare two strings depends on their differences. This
-can be used by an attacker when the two strings represent a password for
-instance; it is known as a `Timing attack`_.
-
-When comparing two passwords, you should use the :phpfunction:`hash_equals`
-function::
-
- if (hash_equals($knownString, $userInput)) {
- // ...
- }
-
-Generating a Secure Random String
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Whenever you need to generate a secure random string, you are highly
-encouraged to use the :phpfunction:`random_bytes` function::
-
- $random = random_bytes(10);
-
-The function returns a random string, suitable for cryptographic use, of
-the number bytes passed as an argument (10 in the above example).
-
-.. tip::
-
- The ``random_bytes()`` function returns a binary string which may contain
- the ``\0`` character. This can cause trouble in several common scenarios,
- such as storing this value in a database or including it as part of the
- URL. The solution is to hash the value returned by ``random_bytes()`` with
- a hashing function such as :phpfunction:`md5` or :phpfunction:`sha1`.
-
-Generating a Secure Random Number
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-If you need to generate a cryptographically secure random integer, you should
-use the :phpfunction:`random_int` function::
-
- $random = random_int(1, 10);
-
-.. _`Timing attack`: https://en.wikipedia.org/wiki/Timing_attack
-.. _`Symfony Polyfill Component`: https://github.com/symfony/polyfill
diff --git a/components/semaphore.rst b/components/semaphore.rst
new file mode 100644
index 00000000000..5715b426053
--- /dev/null
+++ b/components/semaphore.rst
@@ -0,0 +1,73 @@
+The Semaphore Component
+=======================
+
+ The Semaphore Component manages `semaphores`_, a mechanism to provide
+ exclusive access to a shared resource.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/semaphore
+
+.. include:: /components/require_autoload.rst.inc
+
+Usage
+-----
+
+In computer science, a semaphore is a variable or abstract data type used to
+control access to a common resource by multiple processes in a concurrent
+system such as a multitasking operating system. The main difference
+with :doc:`locks ` is that semaphores allow more than one process to
+access a resource, whereas locks only allow one process.
+
+Create semaphores with the :class:`Symfony\\Component\\Semaphore\\SemaphoreFactory`
+class, which in turn requires another class to manage the storage::
+
+ use Symfony\Component\Semaphore\SemaphoreFactory;
+ use Symfony\Component\Semaphore\Store\RedisStore;
+
+ $redis = new Redis();
+ $redis->connect('172.17.0.2');
+
+ $store = new RedisStore($redis);
+ $factory = new SemaphoreFactory($store);
+
+The semaphore is created by calling the
+:method:`Symfony\\Component\\Semaphore\\SemaphoreFactory::createSemaphore`
+method. Its first argument is an arbitrary string that represents the locked
+resource. Its second argument is the maximum number of processes allowed. Then, a
+call to the :method:`Symfony\\Component\\Semaphore\\SemaphoreInterface::acquire`
+method will try to acquire the semaphore::
+
+ // ...
+ $semaphore = $factory->createSemaphore('pdf-invoice-generation', 2);
+
+ if ($semaphore->acquire()) {
+ // The resource "pdf-invoice-generation" is locked.
+ // Here you can safely compute and generate the invoice.
+
+ $semaphore->release();
+ }
+
+If the semaphore can not be acquired, the method returns ``false``. The
+``acquire()`` method can be safely called repeatedly, even if the semaphore is
+already acquired.
+
+.. note::
+
+ Unlike other implementations, the Semaphore component distinguishes
+ semaphores instances even when they are created for the same resource. If a
+ semaphore has to be used by several services, they should share the same
+ ``Semaphore`` instance returned by the ``SemaphoreFactory::createSemaphore``
+ method.
+
+.. tip::
+
+ If you don't release the semaphore explicitly, it will be released
+ automatically on instance destruction. In some cases, it can be useful to
+ lock a resource across several requests. To disable the automatic release
+ behavior, set the fifth argument of the ``createSemaphore()`` method to ``false``.
+
+.. _`semaphores`: https://en.wikipedia.org/wiki/Semaphore_(programming)
diff --git a/components/serializer.rst b/components/serializer.rst
deleted file mode 100644
index 6f15604325e..00000000000
--- a/components/serializer.rst
+++ /dev/null
@@ -1,1538 +0,0 @@
-.. index::
- single: Serializer
- single: Components; Serializer
-
-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
-
-
-
-As you can see in the picture above, an array is used as an intermediary between
-objects and serialized contents. This way, 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.
-
-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 $age;
- private $name;
- private $sportsperson;
- private $createdAt;
-
- // Getters
- public function getName()
- {
- return $this->name;
- }
-
- public function getAge()
- {
- return $this->age;
- }
-
- public function getCreatedAt()
- {
- return $this->createdAt;
- }
-
- // Issers
- public function isSportsperson()
- {
- return $this->sportsperson;
- }
-
- // Setters
- public function setName($name)
- {
- $this->name = $name;
- }
-
- public function setAge($age)
- {
- $this->age = $age;
- }
-
- public function setSportsperson($sportsperson)
- {
- $this->sportsperson = $sportsperson;
- }
-
- public function setCreatedAt($createdAt)
- {
- $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::
-
- $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, 'App\Model\Person', '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.
-
-.. _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 $foo;
-
- private $bar;
-
- public function getBar()
- {
- return $this->bar;
- }
-
- public function setBar($bar)
- {
- 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:
-
-* Annotations in PHP files::
-
- use Doctrine\Common\Annotations\AnnotationReader;
- use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
- use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
-
- $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
-
-* 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-annotations:
-
-Then, create your groups definition:
-
-.. configuration-block::
-
- .. code-block:: php-annotations
-
- namespace Acme;
-
- use Symfony\Component\Serializer\Annotation\Groups;
-
- class MyObj
- {
- /**
- * @Groups({"group1", "group2"})
- */
- public $foo;
-
- /**
- * @Groups("group3")
- */
- public function getBar() // is* methods are also supported
- {
- return $this->bar;
- }
-
- // ...
- }
-
- .. code-block:: yaml
-
- Acme\MyObj:
- attributes:
- foo:
- groups: ['group1', 'group2']
- bar:
- groups: ['group3']
-
- .. code-block:: xml
-
-
-
-
-
- group1
- group2
-
-
-
- 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->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', 'bar' => 'bar'],
- 'MyObj',
- null,
- ['groups' => ['group1', 'group3']]
- );
- // $obj2 = MyObj(foo: 'foo', 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 $familyName;
- public $givenName;
- public $company;
- }
-
- class Company
- {
- public $name;
- public $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 process.
-
-Ignoring Attributes
--------------------
-
-All attributes are included by default when serializing objects. There are two
-options to ignore some of those attributes.
-
-Option 1: Using ``@Ignore`` Annotation
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. configuration-block::
-
- .. code-block:: php-annotations
-
- namespace App\Model;
-
- use Symfony\Component\Serializer\Annotation\Ignore;
-
- class MyClass
- {
- public $foo;
-
- /**
- * @Ignore()
- */
- public $bar;
- }
-
- .. code-block:: yaml
-
- App\Model\MyClass:
- attributes:
- bar:
- ignore: true
-
- .. code-block:: xml
-
-
-
-
-
- true
-
-
-
-
-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 $name;
- public $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)
- {
- return 'org_'.$propertyName;
- }
-
- public function denormalize(string $propertyName)
- {
- // removes 'org_' prefix
- return 'org_' === substr($propertyName, 0, 4) ? 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 to 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
- {
- private $firstName;
-
- public function __construct($firstName)
- {
- $this->firstName = $firstName;
- }
-
- public function getFirstName()
- {
- 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'
-
-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 AnnotationLoader(new AnnotationReader()));
-
- $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-annotations
-
- namespace App\Entity;
-
- use Symfony\Component\Serializer\Annotation\SerializedName;
-
- class Person
- {
- /**
- * @SerializedName("customer_name")
- */
- private $firstName;
-
- public function __construct($firstName)
- {
- $this->firstName = $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"));
- // {"customer_name": "Kévin"}
-
-Serializing Boolean Attributes
-------------------------------
-
-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``, ``add``
-and ``remove``.
-
-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 ($innerObject, $outerObject, string $attributeName, string $format = null, array $context = []) {
- 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
------------
-
-There are several types of normalizers available:
-
-: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, 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``, ``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). It supports calling the constructor during the denormalization process.
-
- Objects are normalized to a map of property names to property values.
-
-: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.
- By default, it uses the `RFC3339`_ format.
-
-: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\\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`_.
-
-.. _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`_.
-
-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.
-
-The ``CsvEncoder``
-~~~~~~~~~~~~~~~~~~~
-
-The ``CsvEncoder`` encodes to and decodes from CSV.
-
-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::
-
-
-
- 1
- 2
- 1
-
-
-Be aware that this encoder will consider keys beginning with ``@`` as attributes, and will use
-the key ``#comment`` 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.
-
-.. 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 with the optional ``$encoderIgnoredNodeTypes`` argument of the
- ``XmlEncoder`` class constructor.
-
-The ``YamlEncoder``
-~~~~~~~~~~~~~~~~~~~
-
-This encoder requires the :doc:`Yaml Component ` and
-transforms from and to Yaml.
-
-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 $foo;
- public $bar = 'notNull';
- };
-
- $normalizer = new ObjectNormalizer();
- $result = $normalizer->normalize($dummy, 'json', [AbstractObjectNormalizer::SKIP_NULL_VALUES => true]);
- // ['bar' => 'notNull']
-
-.. _component-serializer-handling-circular-references:
-
-Handling Circular References
-----------------------------
-
-Circular references are common when dealing with entity relations::
-
- class Organization
- {
- private $name;
- private $members;
-
- public function setName($name)
- {
- $this->name = $name;
- }
-
- public function getName()
- {
- return $this->name;
- }
-
- public function setMembers(array $members)
- {
- $this->members = $members;
- }
-
- public function getMembers()
- {
- return $this->members;
- }
- }
-
- class Member
- {
- private $name;
- private $organization;
-
- public function setName($name)
- {
- $this->name = $name;
- }
-
- public function getName()
- {
- return $this->name;
- }
-
- public function setOrganization(Organization $organization)
- {
- $this->organization = $organization;
- }
-
- public function getOrganization()
- {
- 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, $format, $context) {
- 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"}]}
-
-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 $foo;
-
- /**
- * @var self
- */
- public $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-annotations
-
- namespace Acme;
-
- use Symfony\Component\Serializer\Annotation\MaxDepth;
-
- class MyObj
- {
- /**
- * @MaxDepth(2)
- */
- public $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 Doctrine\Common\Annotations\AnnotationReader;
- use Symfony\Component\Serializer\Annotation\MaxDepth;
- use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
- use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
- use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
- use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
- use Symfony\Component\Serializer\Serializer;
-
- class Foo
- {
- public $id;
-
- /**
- * @MaxDepth(1)
- */
- public $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 AnnotationLoader(new AnnotationReader()));
-
- // all callback parameters are optional (you can omit the ones you don't use)
- $maxDepthHandler = function ($innerObject, $outerObject, string $attributeName, string $format = null, array $context = []) {
- 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');
-
-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`` encodes this object as follows:
-
-.. code-block:: xml
-
-
-
- 1
- 2
- 1
-
-
-The array keys beginning with ``@`` are considered XML attributes::
-
- ['foo' => ['@bar' => 'value']];
-
- // is encoded as follows:
- //
- //
- //
- //
-
-Use the special ``#`` key to define the data of a node::
-
- ['foo' => ['@bar' => 'value', '#' => 'baz']];
-
- // is encoded as follows:
- //
- //
- // baz
- //
-
-Context
-~~~~~~~
-
-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:
-
-``xml_format_output``
- If set to true, formats the generated XML with line breaks and indentation.
-
-``xml_version``
- Sets the XML version attribute (default: ``1.1``).
-
-``xml_encoding``
- Sets the XML encoding attribute (default: ``utf-8``).
-
-``xml_standalone``
- Adds standalone attribute in the generated XML (default: ``true``).
-
-``xml_root_node_name``
- Sets the root node name (default: ``response``).
-
-``remove_empty_tags``
- If set to true, removes all empty tags in the generated XML (default: ``false``).
-
-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
- {
- private $foo;
- private $bar;
-
- public function __construct($foo, $bar)
- {
- $this->foo = $foo;
- $this->bar = $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 $inner;
- private $date;
-
- public function getInner()
- {
- return $this->inner;
- }
-
- public function setInner(ObjectInner $inner)
- {
- $this->inner = $inner;
- }
-
- public function setDate(\DateTimeInterface $date)
- {
- $this->date = $date;
- }
-
- public function getDate()
- {
- return $this->date;
- }
- }
-
- class ObjectInner
- {
- public $foo;
- public $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``.
-
-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 AnnotationLoader(new AnnotationReader()));
-
- $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-annotations
-
- namespace App;
-
- use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
-
- /**
- * @DiscriminatorMap(typeProperty="type", mapping={
- * "github"="App\GitHubCodeRepository",
- * "bitbucket"="App\BitBucketCodeRepository"
- * })
- */
- interface CodeRepository
- {
- // ...
- }
-
- .. code-block:: yaml
-
- App\CodeRepository:
- discriminator_map:
- type_property: type
- mapping:
- github: 'App\GitHubCodeRepository'
- bitbucket: 'App\BitBucketCodeRepository'
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-Once configured, the serializer uses the mapping to pick the correct class::
-
- $serialized = $serializer->serialize(new GitHubCodeRepository());
- // {"type": "github"}
-
- $repository = $serializer->deserialize($serialized, CodeRepository::class, 'json');
- // instanceof GitHubCodeRepository
-
-Performance
------------
-
-To figure which normalizer (or denormalizer) must be used to handle an object,
-the :class:`Symfony\\Component\\Serializer\\Serializer` class will call the
-:method:`Symfony\\Component\\Serializer\\Normalizer\\NormalizerInterface::supportsNormalization`
-(or :method:`Symfony\\Component\\Serializer\\Normalizer\\DenormalizerInterface::supportsDenormalization`)
-of all registered normalizers (or denormalizers) in a loop.
-
-The result of these methods can vary depending on the object to serialize, the
-format and the context. That's why the result **is not cached** by default and
-can result in a significant performance bottleneck.
-
-However, most normalizers (and denormalizers) always return the same result when
-the object's type and the format are the same, so the result can be cached. To
-do so, make those normalizers (and denormalizers) implement the
-:class:`Symfony\\Component\\Serializer\\Normalizer\\CacheableSupportsMethodInterface`
-and return ``true`` when
-:method:`Symfony\\Component\\Serializer\\Normalizer\\CacheableSupportsMethodInterface::hasCacheableSupportsMethod`
-is called.
-
-.. note::
-
- All built-in :ref:`normalizers and denormalizers `
- as well the ones included in `API Platform`_ natively implement this interface.
-
-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
-.. _JSON: http://www.json.org/
-.. _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
-.. _`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
diff --git a/components/stopwatch.rst b/components/stopwatch.rst
deleted file mode 100644
index e6e11d9c53e..00000000000
--- a/components/stopwatch.rst
+++ /dev/null
@@ -1,126 +0,0 @@
-.. index::
- single: Stopwatch
- single: Components; Stopwatch
-
-The Stopwatch Component
-=======================
-
- The Stopwatch component provides a way to profile code.
-
-Installation
-------------
-
-.. code-block:: terminal
-
- $ composer require symfony/stopwatch
-
-.. include:: /components/require_autoload.rst.inc
-
-Usage
------
-
-The Stopwatch component provides a consistent way to measure execution
-time of certain parts of code so that you don't constantly have to parse
-:phpfunction:`microtime` by yourself. Instead, use the
-:class:`Symfony\\Component\\Stopwatch\\Stopwatch` class::
-
- use Symfony\Component\Stopwatch\Stopwatch;
-
- $stopwatch = new Stopwatch();
-
- // starts event named 'eventName'
- $stopwatch->start('eventName');
-
- // ... run your code here
-
- $event = $stopwatch->stop('eventName');
- // you can convert $event into a string for a quick summary
- // e.g. (string) $event = '4.50 MiB - 26 ms'
-
-The :class:`Symfony\\Component\\Stopwatch\\StopwatchEvent` object can be retrieved
-from the :method:`Symfony\\Component\\Stopwatch\\Stopwatch::start`,
-:method:`Symfony\\Component\\Stopwatch\\Stopwatch::stop`,
-:method:`Symfony\\Component\\Stopwatch\\Stopwatch::lap` and
-:method:`Symfony\\Component\\Stopwatch\\Stopwatch::getEvent` methods.
-The latter should be used when you need to retrieve the duration of an event
-while it is still running.
-
-.. tip::
-
- By default, the stopwatch truncates any sub-millisecond time measure to ``0``,
- so you can't measure microseconds or nanoseconds. If you need more precision,
- pass ``true`` to the ``Stopwatch`` class constructor to enable full precision::
-
- $stopwatch = new Stopwatch(true);
-
-The stopwatch can be reset to its original state at any given time with the
-:method:`Symfony\\Component\\Stopwatch\\Stopwatch::reset` method, which deletes
-all the data measured so far.
-
-You can also provide a category name to an event::
-
- $stopwatch->start('eventName', 'categoryName');
-
-You can consider categories as a way of tagging events. For example, the
-Symfony Profiler tool uses categories to nicely color-code different events.
-
-.. tip::
-
- Read :ref:`this article ` to learn more about
- integrating the Stopwatch component into the Symfony profiler.
-
-Periods
--------
-
-As you know from the real world, all stopwatches come with two buttons:
-one to start and stop the stopwatch, and another to measure the lap time.
-This is exactly what the :method:`Symfony\\Component\\Stopwatch\\Stopwatch::lap`
-method does::
-
- $stopwatch = new Stopwatch();
- // starts event named 'foo'
- $stopwatch->start('foo');
- // ... some code goes here
- $stopwatch->lap('foo');
- // ... some code goes here
- $stopwatch->lap('foo');
- // ... some other code goes here
- $event = $stopwatch->stop('foo');
-
-Lap information is stored as "periods" within the event. To get lap information
-call::
-
- $event->getPeriods();
-
-In addition to periods, you can get other useful information from the event object.
-For example::
-
- $event->getCategory(); // returns the category the event was started in
- $event->getOrigin(); // returns the event start time in milliseconds
- $event->ensureStopped(); // stops all periods not already stopped
- $event->getStartTime(); // returns the start time of the very first period
- $event->getEndTime(); // returns the end time of the very last period
- $event->getDuration(); // returns the event duration, including all periods
- $event->getMemory(); // returns the max memory usage of all periods
-
-Sections
---------
-
-Sections are a way to logically split the timeline into groups. You can see
-how Symfony uses sections to nicely visualize the framework lifecycle in the
-Symfony Profiler tool. Here is a basic usage example using sections::
-
- $stopwatch = new Stopwatch();
-
- $stopwatch->openSection();
- $stopwatch->start('parsing_config_file', 'filesystem_operations');
- $stopwatch->stopSection('routing');
-
- $events = $stopwatch->getSectionEvents('routing');
-
-You can reopen a closed section by calling the :method:`Symfony\\Component\\Stopwatch\\Stopwatch::openSection`
-method and specifying the id of the section to be reopened::
-
- $stopwatch->openSection('routing');
- $stopwatch->start('building_config_tree');
- $stopwatch->stopSection('routing');
diff --git a/components/type_info.rst b/components/type_info.rst
new file mode 100644
index 00000000000..817c7f1d61a
--- /dev/null
+++ b/components/type_info.rst
@@ -0,0 +1,202 @@
+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.
+
+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 methods are available and can be found
+in :class:`Symfony\\Component\\TypeInfo\\TypeFactoryTrait`.
+
+You can also use a generic method that detects the type automatically::
+
+ Type::fromValue(1.1); // same as Type::float()
+ Type::fromValue('...'); // same as Type::string()
+ Type::fromValue(false); // same as Type::false()
+
+.. versionadded:: 7.3
+
+ The ``fromValue()`` method was introduced in Symfony 7.3.
+
+Resolvers
+~~~~~~~~~
+
+The second way to use the component is by using ``TypeInfo`` to resolve a type
+based on reflection or a simple string. This approach is designed for libraries
+that need a simple way to describe a class or anything with a type::
+
+ use Symfony\Component\TypeInfo\Type;
+ use Symfony\Component\TypeInfo\TypeResolver\TypeResolver;
+
+ class Dummy
+ {
+ public function __construct(
+ public int $id,
+ ) {
+ }
+ }
+
+ // 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
+
+ // 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)
+
+PHPDoc Parsing
+~~~~~~~~~~~~~~
+
+In many cases, you may not have cleanly typed properties or may need more precise
+type definitions provided by advanced PHPDoc. To achieve this, you can use a string
+resolver based on the PHPDoc annotations.
+
+First, run the command ``composer require phpstan/phpdoc-parser`` to install the
+PHP package required for string resolving. Then, follow these steps::
+
+ use Symfony\Component\TypeInfo\TypeResolver\TypeResolver;
+
+ class Dummy
+ {
+ public function __construct(
+ public int $id,
+ /** @var string[] $tags */
+ public array $tags,
+ ) {
+ }
+ }
+
+ $typeResolver = TypeResolver::create();
+ $typeResolver->resolve(new \ReflectionProperty(Dummy::class, 'id')); // returns an "int" Type
+ $typeResolver->resolve(new \ReflectionProperty(Dummy::class, 'tags')); // returns a collection with "int" as key and "string" as values Type
+
+Advanced Usages
+~~~~~~~~~~~~~~~
+
+The TypeInfo component provides various methods to manipulate and check types,
+depending on your needs.
+
+**Identify** a type::
+
+ // define a simple integer type
+ $type = Type::int();
+ // check if the type matches a specific identifier
+ $type->isIdentifiedBy(TypeIdentifier::INT); // true
+ $type->isIdentifiedBy(TypeIdentifier::STRING); // false
+
+ // define a union type (equivalent to PHP's int|string)
+ $type = Type::union(Type::string(), Type::int());
+ // now the second check is true because the union type contains the string type
+ $type->isIdentifiedBy(TypeIdentifier::INT); // true
+ $type->isIdentifiedBy(TypeIdentifier::STRING); // true
+
+ class DummyParent {}
+ class Dummy extends DummyParent implements DummyInterface {}
+
+ // define an object type
+ $type = Type::object(Dummy::class);
+
+ // check if the type is an object or matches a specific class
+ $type->isIdentifiedBy(TypeIdentifier::OBJECT); // true
+ $type->isIdentifiedBy(Dummy::class); // true
+ // check if it inherits/implements something
+ $type->isIdentifiedBy(DummyParent::class); // true
+ $type->isIdentifiedBy(DummyInterface::class); // true
+
+Checking if a type **accepts a value**::
+
+ $type = Type::int();
+ // check if the type accepts a given value
+ $type->accepts(123); // true
+ $type->accepts('z'); // false
+
+ $type = Type::union(Type::string(), Type::int());
+ // now the second check is true because the union type accepts either an int or a string value
+ $type->accepts(123); // true
+ $type->accepts('z'); // true
+
+.. versionadded:: 7.3
+
+ The :method:`Symfony\\Component\\TypeInfo\\Type::accepts`
+ method was introduced in Symfony 7.3.
+
+Using callables for **complex checks**::
+
+ class Foo
+ {
+ private int $integer;
+ private string $string;
+ private ?float $float;
+ }
+
+ $reflClass = new \ReflectionClass(Foo::class);
+
+ $resolver = TypeResolver::create();
+ $integerType = $resolver->resolve($reflClass->getProperty('integer'));
+ $stringType = $resolver->resolve($reflClass->getProperty('string'));
+ $floatType = $resolver->resolve($reflClass->getProperty('float'));
+
+ // define a callable to validate non-nullable number types
+ $isNonNullableNumber = function (Type $type): bool {
+ if ($type->isNullable()) {
+ return false;
+ }
+
+ if ($type->isIdentifiedBy(TypeIdentifier::INT) || $type->isIdentifiedBy(TypeIdentifier::FLOAT)) {
+ return true;
+ }
+
+ return false;
+ };
+
+ $integerType->isSatisfiedBy($isNonNullableNumber); // true
+ $stringType->isSatisfiedBy($isNonNullableNumber); // false
+ $floatType->isSatisfiedBy($isNonNullableNumber); // false
diff --git a/components/uid.rst b/components/uid.rst
index c8eee0ece94..27157e8cd80 100644
--- a/components/uid.rst
+++ b/components/uid.rst
@@ -1,18 +1,9 @@
-.. index::
- single: UID
- single: Components; UID
-
The UID Component
=================
The UID component provides utilities to work with `unique identifiers`_ (UIDs)
such as UUIDs and ULIDs.
-.. versionadded:: 5.1
-
- The UID component was introduced in Symfony 5.1 as an
- :doc:`experimental feature `.
-
Installation
------------
@@ -22,6 +13,8 @@ Installation
.. include:: /components/require_autoload.rst.inc
+.. _uuid:
+
UUIDs
-----
@@ -34,49 +27,257 @@ Generating UUIDs
~~~~~~~~~~~~~~~~
Use the named constructors of the ``Uuid`` class or any of the specific classes
-to create each type of UUID::
+to create each type of UUID:
+
+**UUID v1** (time-based)
+
+Generates the UUID using a timestamp and the MAC address of your device
+(`read the UUIDv1 spec `__).
+Both are obtained automatically, so you don't have to pass any constructor argument::
+
+ use Symfony\Component\Uid\Uuid;
+
+ $uuid = Uuid::v1();
+ // $uuid is an instance of Symfony\Component\Uid\UuidV1
+
+.. tip::
+
+ It's recommended to use UUIDv7 instead of UUIDv1 because it provides
+ better entropy.
+
+**UUID v2** (DCE security)
+
+Similar to UUIDv1 but with a very high likelihood of ID collision
+(`read the UUIDv2 spec `__).
+It's part of the authentication mechanism of DCE (Distributed Computing Environment)
+and the UUID includes the POSIX UIDs (user/group ID) of the user who generated it.
+This UUID variant is **not implemented** by the Uid component.
+
+**UUID v3** (name-based, MD5)
+
+Generates UUIDs from names that belong, and are unique within, some given namespace
+(`read the UUIDv3 spec `__).
+This variant is useful to generate deterministic UUIDs from arbitrary strings.
+It works by populating the UUID contents with the``md5`` hash of concatenating
+the namespace and the name::
+
+ use Symfony\Component\Uid\Uuid;
+
+ // you can use any of the predefined namespaces...
+ $namespace = Uuid::fromString(Uuid::NAMESPACE_OID);
+ // ...or use a random namespace:
+ // $namespace = Uuid::v4();
+
+ // $name can be any arbitrary string
+ $uuid = Uuid::v3($namespace, $name);
+ // $uuid is an instance of Symfony\Component\Uid\UuidV3
+
+These are the default namespaces defined by the standard:
+
+* ``Uuid::NAMESPACE_DNS`` if you are generating UUIDs for `DNS entries `__
+* ``Uuid::NAMESPACE_URL`` if you are generating UUIDs for `URLs `__
+* ``Uuid::NAMESPACE_OID`` if you are generating UUIDs for `OIDs (object identifiers) `__
+* ``Uuid::NAMESPACE_X500`` if you are generating UUIDs for `X500 DNs (distinguished names) `__
+
+**UUID v4** (random)
+
+Generates a random UUID (`read the UUIDv4 spec `__).
+Because of its randomness, it ensures uniqueness across distributed systems
+without the need for a central coordinating entity. It's privacy-friendly
+because it doesn't contain any information about where and when it was generated::
+
+ use Symfony\Component\Uid\Uuid;
+
+ $uuid = Uuid::v4();
+ // $uuid is an instance of Symfony\Component\Uid\UuidV4
+
+**UUID v5** (name-based, SHA-1)
+
+It's the same as UUIDv3 (explained above) but it uses ``sha1`` instead of
+``md5`` to hash the given namespace and name (`read the 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
+sortable (like :ref:`ULIDs `). It's more efficient for database indexing
+(`read the UUIDv6 spec `__)::
use Symfony\Component\Uid\Uuid;
- // UUID type 1 generates the UUID using the MAC address of your device and a timestamp.
- // Both are obtained automatically, so you don't have to pass any constructor argument.
- $uuid = Uuid::v1(); // $uuid is an instance of Symfony\Component\Uid\UuidV1
+ $uuid = Uuid::v6();
+ // $uuid is an instance of Symfony\Component\Uid\UuidV6
+
+.. tip::
+
+ It's recommended to use UUIDv7 instead of UUIDv6 because it provides
+ better entropy.
+
+.. _uid-uuid-v7:
- // UUID type 4 generates a random UUID, so you don't have to pass any constructor argument.
- $uuid = Uuid::v4(); // $uuid is an instance of Symfony\Component\Uid\UuidV4
+**UUID v7** (UNIX timestamp)
- // UUID type 3 and 5 generate a UUID hashing the given namespace and name. Type 3 uses
- // MD5 hashes and Type 5 uses SHA-1. The namespace is another UUID (e.g. a Type 4 UUID)
- // and the name is an arbitrary string (e.g. a product name; if it's unique).
- $namespace = Uuid::v4();
- $name = $product->getUniqueName();
+Generates time-ordered UUIDs based on a high-resolution Unix Epoch timestamp
+source (the number of milliseconds since midnight 1 Jan 1970 UTC, leap seconds excluded)
+(`read the UUIDv7 spec `__).
+It's recommended to use this version over UUIDv1 and UUIDv6 because it provides
+better entropy (and a more strict chronological order of UUID generation)::
- $uuid = Uuid::v3($namespace, $name); // $uuid is an instance of Symfony\Component\Uid\UuidV3
- $uuid = Uuid::v5($namespace, $name); // $uuid is an instance of Symfony\Component\Uid\UuidV5
+ use Symfony\Component\Uid\Uuid;
+
+ $uuid = Uuid::v7();
+ // $uuid is an instance of Symfony\Component\Uid\UuidV7
- // UUID type 6 is not part of the UUID standard. It's lexicographically sortable
- // (like ULIDs) and contains a 60-bit timestamp and 63 extra unique bits.
- // It's defined in http://gh.peabody.io/uuidv6/
- $uuid = Uuid::v6(); // $uuid is an instance of Symfony\Component\Uid\UuidV6
+**UUID v8** (custom)
-If your UUID is generated by another system, use the ``fromString()`` method to
-create an object and make use of the utilities available for Symfony UUIDs::
+Provides an RFC-compatible format intended for experimental or vendor-specific use cases
+(`read the UUIDv8 spec `__).
+You must generate the UUID value yourself. The only requirement is to set the
+variant and version bits of the UUID correctly. The rest of the UUID content is
+implementation-specific, and no particular format should be assumed::
- // this value is generated somewhere else (can also be in binary format)
- $uuidValue = 'd9e7a184-5d5b-11ea-a62a-3499710062d0';
- $uuid = Uuid::fromString($uuidValue);
+ use Symfony\Component\Uid\Uuid;
+
+ // pass your custom UUID value as the argument
+ $uuid = Uuid::v8('d9e7a184-5d5b-11ea-a62a-3499710062d0');
+ // $uuid is an instance of Symfony\Component\Uid\UuidV8
+
+If your UUID value is already generated in another format, use any of the
+following methods to create a ``Uuid`` object from it::
+
+ // all the following examples would generate the same Uuid object
+ $uuid = Uuid::fromString('d9e7a184-5d5b-11ea-a62a-3499710062d0');
+ $uuid = Uuid::fromBinary("\xd9\xe7\xa1\x84\x5d\x5b\x11\xea\xa6\x2a\x34\x99\x71\x00\x62\xd0");
+ $uuid = Uuid::fromBase32('6SWYGR8QAV27NACAHMK5RG0RPG');
+ $uuid = Uuid::fromBase58('TuetYWNHhmuSQ3xPoVLv9M');
+ $uuid = Uuid::fromRfc4122('d9e7a184-5d5b-11ea-a62a-3499710062d0');
+
+You can also use the ``UuidFactory`` to generate UUIDs. First, you may
+configure the behavior of the factory using configuration files::
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/uid.yaml
+ framework:
+ uid:
+ default_uuid_version: 7
+ name_based_uuid_version: 5
+ name_based_uuid_namespace: 6ba7b810-9dad-11d1-80b4-00c04fd430c8
+ time_based_uuid_version: 7
+ time_based_uuid_node: 121212121212
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/uid.php
+ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+ return static function (ContainerConfigurator $container): void {
+ $services = $container->services()
+ ->defaults()
+ ->autowire()
+ ->autoconfigure();
+
+ $container->extension('framework', [
+ 'uid' => [
+ 'default_uuid_version' => 7,
+ 'name_based_uuid_version' => 5,
+ 'name_based_uuid_namespace' => '6ba7b810-9dad-11d1-80b4-00c04fd430c8',
+ 'time_based_uuid_version' => 7,
+ 'time_based_uuid_node' => 121212121212,
+ ],
+ ]);
+ };
+
+Then, you can inject the factory in your services and use it to generate UUIDs based
+on the configuration you defined::
+
+ namespace App\Service;
+
+ use Symfony\Component\Uid\Factory\UuidFactory;
+
+ class FooService
+ {
+ public function __construct(
+ private UuidFactory $uuidFactory,
+ ) {
+ }
+
+ public function generate(): void
+ {
+ // This creates a UUID of the version given in the configuration file (v7 by default)
+ $uuid = $this->uuidFactory->create();
+
+ $nameBasedUuid = $this->uuidFactory->nameBased(/** ... */);
+ $randomBasedUuid = $this->uuidFactory->randomBased();
+ $timestampBased = $this->uuidFactory->timeBased();
+
+ // ...
+ }
+ }
Converting UUIDs
~~~~~~~~~~~~~~~~
Use these methods to transform the UUID object into different bases::
- $uuid = new Uuid::fromString('d9e7a184-5d5b-11ea-a62a-3499710062d0');
+ $uuid = Uuid::fromString('d9e7a184-5d5b-11ea-a62a-3499710062d0');
- $uuid->toBinary(); // string(16) "..." (binary contents can't be printed)
+ $uuid->toBinary(); // string(16) "\xd9\xe7\xa1\x84\x5d\x5b\x11\xea\xa6\x2a\x34\x99\x71\x00\x62\xd0"
$uuid->toBase32(); // string(26) "6SWYGR8QAV27NACAHMK5RG0RPG"
$uuid->toBase58(); // string(22) "TuetYWNHhmuSQ3xPoVLv9M"
$uuid->toRfc4122(); // string(36) "d9e7a184-5d5b-11ea-a62a-3499710062d0"
+ $uuid->toHex(); // string(34) "0xd9e7a1845d5b11eaa62a3499710062d0"
+ $uuid->toString(); // string(36) "d9e7a184-5d5b-11ea-a62a-3499710062d0"
+
+.. versionadded:: 7.1
+
+ The ``toString()`` method was introduced in Symfony 7.1.
+
+You can also convert some UUID versions to others::
+
+ // convert V1 to V6 or V7
+ $uuid = Uuid::v1();
+
+ $uuid->toV6(); // returns a Symfony\Component\Uid\UuidV6 instance
+ $uuid->toV7(); // returns a Symfony\Component\Uid\UuidV7 instance
+
+ // convert V6 to V7
+ $uuid = Uuid::v6();
+
+ $uuid->toV7(); // returns a Symfony\Component\Uid\UuidV7 instance
+
+.. versionadded:: 7.1
+
+ The :method:`Symfony\\Component\\Uid\\UuidV1::toV6`,
+ :method:`Symfony\\Component\\Uid\\UuidV1::toV7` and
+ :method:`Symfony\\Component\\Uid\\UuidV6::toV7`
+ methods were introduced in Symfony 7.1.
Working with UUIDs
~~~~~~~~~~~~~~~~~~
@@ -97,9 +298,12 @@ UUID objects created with the ``Uuid`` class can use the following methods
$uuid = Uuid::v4();
$uuid instanceof UuidV4; // true
- // getting the UUID time (it's only available in certain UUID types)
+ // getting the UUID datetime (it's only available in certain UUID types)
$uuid = Uuid::v1();
- $uuid->getTime(); // e.g. float(1584111384.2613)
+ $uuid->getDateTime(); // returns a \DateTimeImmutable instance
+
+ // checking if a given value is valid as UUID
+ $isValid = Uuid::isValid($uuid); // true or false
// comparing UUIDs and checking for equality
$uuid1 = Uuid::v1();
@@ -112,6 +316,120 @@ UUID objects created with the ``Uuid`` class can use the following methods
// * int < 0 if $uuid1 is less than $uuid4
$uuid1->compare($uuid4); // e.g. int(4)
+If you're working with different UUIDs format and want to validate them,
+you can use the ``$format`` parameter of the :method:`Symfony\\Component\\Uid\\Uuid::isValid`
+method to specify the UUID format you're expecting::
+
+ use Symfony\Component\Uid\Uuid;
+
+ $isValid = Uuid::isValid('90067ce4-f083-47d2-a0f4-c47359de0f97', Uuid::FORMAT_RFC_4122); // accept only RFC 4122 UUIDs
+ $isValid = Uuid::isValid('3aJ7CNpDMfXPZrCsn4Cgey', Uuid::FORMAT_BASE_32 | Uuid::FORMAT_BASE_58); // accept multiple formats
+
+The following constants are available:
+
+* ``Uuid::FORMAT_BINARY``
+* ``Uuid::FORMAT_BASE_32``
+* ``Uuid::FORMAT_BASE_58``
+* ``Uuid::FORMAT_RFC_4122``
+* ``Uuid::FORMAT_RFC_9562`` (equivalent to ``Uuid::FORMAT_RFC_4122``)
+
+You can also use the ``Uuid::FORMAT_ALL`` constant to accept any UUID format.
+By default, only the RFC 4122 format is accepted.
+
+.. versionadded:: 7.2
+
+ The ``$format`` parameter of the :method:`Symfony\\Component\\Uid\\Uuid::isValid`
+ method and the related constants were introduced in Symfony 7.2.
+
+Storing UUIDs in Databases
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you :doc:`use Doctrine `, consider using the ``uuid`` Doctrine
+type, which converts to/from UUID objects automatically::
+
+ // src/Entity/Product.php
+ namespace App\Entity;
+
+ use Doctrine\ORM\Mapping as ORM;
+ use Symfony\Bridge\Doctrine\Types\UuidType;
+ use Symfony\Component\Uid\Uuid;
+
+ #[ORM\Entity(repositoryClass: ProductRepository::class)]
+ class Product
+ {
+ #[ORM\Column(type: UuidType::NAME)]
+ private Uuid $someProperty;
+
+ // ...
+ }
+
+There's also a Doctrine generator to help auto-generate UUID values for the
+entity primary keys::
+
+ namespace App\Entity;
+
+ use Doctrine\ORM\Mapping as ORM;
+ use Symfony\Bridge\Doctrine\Types\UuidType;
+ use Symfony\Component\Uid\Uuid;
+
+ class User implements UserInterface
+ {
+ #[ORM\Id]
+ #[ORM\Column(type: UuidType::NAME, unique: true)]
+ #[ORM\GeneratedValue(strategy: 'CUSTOM')]
+ #[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
+ private ?Uuid $id;
+
+ public function getId(): ?Uuid
+ {
+ return $this->id;
+ }
+
+ // ...
+ }
+
+.. 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
+queries or building the query yourself, you'll need to set ``uuid`` as the type
+of the UUID parameters::
+
+ // src/Repository/ProductRepository.php
+
+ // ...
+ use Doctrine\DBAL\ParameterType;
+ use Symfony\Bridge\Doctrine\Types\UuidType;
+
+ class ProductRepository extends ServiceEntityRepository
+ {
+ // ...
+
+ public function findUserProducts(User $user): array
+ {
+ $qb = $this->createQueryBuilder('p')
+ // ...
+ // add UuidType::NAME as the third argument to tell Doctrine that this is a UUID
+ ->setParameter('user', $user->getUuid(), UuidType::NAME)
+
+ // alternatively, you can convert it to a value compatible with
+ // the type inferred by Doctrine
+ ->setParameter('user', $user->getUuid()->toBinary(), ParameterType::BINARY)
+ ;
+
+ // ...
+ }
+ }
+
+.. _ulid:
+
ULIDs
-----
@@ -123,6 +441,13 @@ ULIDs are an alternative to UUIDs when using those is impractical. They provide
128-bit compatibility with UUID, they are lexicographically sortable and they
are encoded as 26-character strings (vs 36-character UUIDs).
+.. note::
+
+ If you generate more than one ULID during the same millisecond in the
+ same process then the random portion is incremented by one bit in order
+ to provide monotonicity for sorting. The random portion is not random
+ compared to the previous ULID in this case.
+
Generating ULIDs
~~~~~~~~~~~~~~~~
@@ -132,12 +457,43 @@ Instantiate the ``Ulid`` class to generate a random ULID value::
$ulid = new Ulid(); // e.g. 01AN4Z07BY79KA1307SR9X4MV3
-If your ULID is generated by another system, use the ``fromString()`` method to
-create an object and make use of the utilities available for Symfony ULIDs::
+If your ULID value is already generated in another format, use any of the
+following methods to create a ``Ulid`` object from it::
+
+ // all the following examples would generate the same Ulid object
+ $ulid = Ulid::fromString('01E439TP9XJZ9RPFH3T1PYBCR8');
+ $ulid = Ulid::fromBinary("\x01\x71\x06\x9d\x59\x3d\x97\xd3\x8b\x3e\x23\xd0\x6d\xe5\xb3\x08");
+ $ulid = Ulid::fromBase32('01E439TP9XJZ9RPFH3T1PYBCR8');
+ $ulid = Ulid::fromBase58('1BKocMc5BnrVcuq2ti4Eqm');
+ $ulid = Ulid::fromRfc4122('0171069d-593d-97d3-8b3e-23d06de5b308');
+
+Like UUIDs, ULIDs have their own factory, ``UlidFactory``, that can be used to generate them::
+
+ namespace App\Service;
- // this value is generated somewhere else (can also be in binary format)
- $ulidValue = '01E439TP9XJZ9RPFH3T1PYBCR8';
- $ulid = Ulid::fromString($ulidValue);
+ use Symfony\Component\Uid\Factory\UlidFactory;
+
+ class FooService
+ {
+ public function __construct(
+ private UlidFactory $ulidFactory,
+ ) {
+ }
+
+ public function generate(): void
+ {
+ $ulid = $this->ulidFactory->create();
+
+ // ...
+ }
+ }
+
+There's also a special ``NilUlid`` class to represent ULID ``null`` values::
+
+ use Symfony\Component\Uid\NilUlid;
+
+ $ulid = new NilUlid();
+ // equivalent to $ulid = new Ulid('00000000000000000000000000');
Converting ULIDs
~~~~~~~~~~~~~~~~
@@ -146,10 +502,11 @@ Use these methods to transform the ULID object into different bases::
$ulid = Ulid::fromString('01E439TP9XJZ9RPFH3T1PYBCR8');
- $ulid->toBinary(); // string(16) "..." (binary contents can't be printed)
+ $ulid->toBinary(); // string(16) "\x01\x71\x06\x9d\x59\x3d\x97\xd3\x8b\x3e\x23\xd0\x6d\xe5\xb3\x08"
$ulid->toBase32(); // string(26) "01E439TP9XJZ9RPFH3T1PYBCR8"
$ulid->toBase58(); // string(22) "1BKocMc5BnrVcuq2ti4Eqm"
$ulid->toRfc4122(); // string(36) "0171069d-593d-97d3-8b3e-23d06de5b308"
+ $ulid->toHex(); // string(34) "0x0171069d593d97d38b3e23d06de5b308"
Working with ULIDs
~~~~~~~~~~~~~~~~~~
@@ -164,14 +521,205 @@ ULID objects created with the ``Ulid`` class can use the following methods::
// checking if a given value is valid as ULID
$isValid = Ulid::isValid($ulidValue); // true or false
- // getting the ULID time
- $ulid1->getTime(); // e.g. float(1584111384.2613)
+ // getting the ULID datetime
+ $ulid1->getDateTime(); // returns a \DateTimeImmutable instance
// comparing ULIDs and checking for equality
$ulid1->equals($ulid2); // false
// this method returns $ulid1 <=> $ulid2
$ulid1->compare($ulid2); // e.g. int(-1)
+Storing ULIDs in Databases
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you :doc:`use Doctrine `, consider using the ``ulid`` Doctrine
+type, which converts to/from ULID objects automatically::
+
+ // src/Entity/Product.php
+ namespace App\Entity;
+
+ use Doctrine\ORM\Mapping as ORM;
+ use Symfony\Bridge\Doctrine\Types\UlidType;
+ use Symfony\Component\Uid\Ulid;
+
+ #[ORM\Entity(repositoryClass: ProductRepository::class)]
+ class Product
+ {
+ #[ORM\Column(type: UlidType::NAME)]
+ private Ulid $someProperty;
+
+ // ...
+ }
+
+There's also a Doctrine generator to help auto-generate ULID values for the
+entity primary keys::
+
+ namespace App\Entity;
+
+ use Doctrine\ORM\Mapping as ORM;
+ use Symfony\Bridge\Doctrine\Types\UlidType;
+ use Symfony\Component\Uid\Ulid;
+
+ class Product
+ {
+ #[ORM\Id]
+ #[ORM\Column(type: UlidType::NAME, unique: true)]
+ #[ORM\GeneratedValue(strategy: 'CUSTOM')]
+ #[ORM\CustomIdGenerator(class: 'doctrine.ulid_generator')]
+ private ?Ulid $id;
+
+ public function getId(): ?Ulid
+ {
+ return $this->id;
+ }
+
+ // ...
+ }
+
+.. 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
+queries or building the query yourself, you'll need to set ``ulid`` as the type
+of the ULID parameters::
+
+ // src/Repository/ProductRepository.php
+
+ // ...
+ use Symfony\Bridge\Doctrine\Types\UlidType;
+
+ class ProductRepository extends ServiceEntityRepository
+ {
+ // ...
+
+ public function findUserProducts(User $user): array
+ {
+ $qb = $this->createQueryBuilder('p')
+ // ...
+ // add UlidType::NAME as the third argument to tell Doctrine that this is a ULID
+ ->setParameter('user', $user->getUlid(), UlidType::NAME)
+
+ // alternatively, you can convert it to a value compatible with
+ // the type inferred by Doctrine
+ ->setParameter('user', $user->getUlid()->toBinary())
+ ;
+
+ // ...
+ }
+ }
+
+Generating and Inspecting UUIDs/ULIDs in the Console
+----------------------------------------------------
+
+This component provides several commands to generate and inspect UUIDs/ULIDs in
+the console. They are not enabled by default, so you must add the following
+configuration in your application before using these commands:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/services.yaml
+ services:
+ Symfony\Component\Uid\Command\GenerateUlidCommand: ~
+ Symfony\Component\Uid\Command\GenerateUuidCommand: ~
+ Symfony\Component\Uid\Command\InspectUlidCommand: ~
+ Symfony\Component\Uid\Command\InspectUuidCommand: ~
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/services.php
+ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+ use Symfony\Component\Uid\Command\GenerateUlidCommand;
+ use Symfony\Component\Uid\Command\GenerateUuidCommand;
+ use Symfony\Component\Uid\Command\InspectUlidCommand;
+ use Symfony\Component\Uid\Command\InspectUuidCommand;
+
+ return static function (ContainerConfigurator $container): void {
+ // ...
+
+ $services
+ ->set(GenerateUlidCommand::class)
+ ->set(GenerateUuidCommand::class)
+ ->set(InspectUlidCommand::class)
+ ->set(InspectUuidCommand::class);
+ };
+
+Now you can generate UUIDs/ULIDs as follows (add the ``--help`` option to the
+commands to learn about all their options):
+
+.. code-block:: terminal
+
+ # generate 1 random-based UUID
+ $ php bin/console uuid:generate --random-based
+
+ # generate 1 time-based UUID with a specific node
+ $ php bin/console uuid:generate --time-based=now --node=fb3502dc-137e-4849-8886-ac90d07f64a7
+
+ # generate 2 UUIDs and output them in base58 format
+ $ php bin/console uuid:generate --count=2 --format=base58
+
+ # generate 1 ULID with the current time as the timestamp
+ $ php bin/console ulid:generate
+
+ # generate 1 ULID with a specific timestamp
+ $ php bin/console ulid:generate --time="2021-02-02 14:00:00"
+
+ # generate 2 ULIDs and output them in RFC4122 format
+ $ php bin/console ulid:generate --count=2 --format=rfc4122
+
+In addition to generating new UIDs, you can also inspect them with the following
+commands to show all the information for a given UID:
+
+.. code-block:: terminal
+
+ $ php bin/console uuid:inspect d0a3a023-f515-4fe0-915c-575e63693998
+ ---------------------- --------------------------------------
+ Label Value
+ ---------------------- --------------------------------------
+ Version 4
+ Canonical (RFC 4122) d0a3a023-f515-4fe0-915c-575e63693998
+ Base 58 SmHvuofV4GCF7QW543rDD9
+ Base 32 6GMEG27X8N9ZG92Q2QBSHPJECR
+ ---------------------- --------------------------------------
+
+ $ php bin/console ulid:inspect 01F2TTCSYK1PDRH73Z41BN1C4X
+ --------------------- --------------------------------------
+ Label Value
+ --------------------- --------------------------------------
+ Canonical (Base 32) 01F2TTCSYK1PDRH73Z41BN1C4X
+ Base 58 1BYGm16jS4kX3VYCysKKq6
+ RFC 4122 0178b5a6-67d3-0d9b-889c-7f205750b09d
+ --------------------- --------------------------------------
+ Timestamp 2021-04-09 08:01:24.947
+ --------------------- --------------------------------------
+
.. _`unique identifiers`: https://en.wikipedia.org/wiki/UID
.. _`UUIDs`: https://en.wikipedia.org/wiki/Universally_unique_identifier
.. _`ULIDs`: https://github.com/ulid/spec
diff --git a/components/using_components.rst b/components/using_components.rst
index 31a0f24d1be..f975be7e1b2 100644
--- a/components/using_components.rst
+++ b/components/using_components.rst
@@ -1,7 +1,3 @@
-.. index::
- single: Components; Installation
- single: Components; Usage
-
.. _how-to-install-and-use-the-symfony2-components:
How to Install and Use the Symfony Components
diff --git a/components/validator.rst b/components/validator.rst
index a88b13d0089..12c61507257 100644
--- a/components/validator.rst
+++ b/components/validator.rst
@@ -1,7 +1,3 @@
-.. index::
- single: Validator
- single: Components; Validator
-
The Validator Component
=======================
@@ -40,7 +36,7 @@ characters long::
$validator = Validation::createValidator();
$violations = $validator->validate('Bernhard', [
- new Length(['min' => 10]),
+ new Length(min: 10),
new NotBlank(),
]);
@@ -57,7 +53,7 @@ If you have lots of validation errors, you can filter them by error code::
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
- $violations = $validator->validate(...);
+ $violations = $validator->validate(/* ... */);
if (0 !== count($violations->findByCodes(UniqueEntity::NOT_UNIQUE_ERROR))) {
// handle this specific error (display some message, send an email, etc.)
}
diff --git a/components/validator/metadata.rst b/components/validator/metadata.rst
index f5df3fa68de..782e1ee216f 100755
--- a/components/validator/metadata.rst
+++ b/components/validator/metadata.rst
@@ -1,6 +1,3 @@
-.. index::
- single: Validator; Metadata
-
Metadata
========
@@ -20,14 +17,14 @@ the ``Author`` class has at least 3 characters::
class Author
{
- private $firstName;
+ private string $firstName;
- public static function loadValidatorMetadata(ClassMetadata $metadata)
+ public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
$metadata->addPropertyConstraint('firstName', new Assert\NotBlank());
$metadata->addPropertyConstraint(
'firstName',
- new Assert\Length(["min" => 3])
+ new Assert\Length(min: 3)
);
}
}
@@ -37,13 +34,13 @@ Getters
Constraints can also be applied to the value returned by any public *getter*
method, which are the methods whose names start with ``get``, ``has`` or ``is``.
-This feature allows to validate your objects dynamically.
+This feature allows validating your objects dynamically.
Suppose that, for security reasons, you want to validate that a password field
doesn't match the first name of the user. First, create a public method called
``isPasswordSafe()`` to define this custom validation logic::
- public function isPasswordSafe()
+ public function isPasswordSafe(): bool
{
return $this->firstName !== $this->password;
}
@@ -56,18 +53,18 @@ Then, add the Validator component configuration to the class::
class Author
{
- public static function loadValidatorMetadata(ClassMetadata $metadata)
+ public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
- $metadata->addGetterConstraint('passwordSafe', new Assert\IsTrue([
- 'message' => 'The password cannot match your first name',
- ]));
+ $metadata->addGetterConstraint('passwordSafe', new Assert\IsTrue(
+ message: 'The password cannot match your first name',
+ ));
}
}
Classes
-------
-Some constraints allow to validate the entire object. For example, the
+Some constraints allow validating the entire object. For example, the
:doc:`Callback ` constraint is a generic
constraint that's applied to the class itself.
@@ -77,7 +74,7 @@ validation logic::
// ...
use Symfony\Component\Validator\Context\ExecutionContextInterface;
- public function validate(ExecutionContextInterface $context)
+ public function validate(ExecutionContextInterface $context): void
{
// ...
}
@@ -90,7 +87,7 @@ Then, add the Validator component configuration to the class::
class Author
{
- public static function loadValidatorMetadata(ClassMetadata $metadata)
+ public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
$metadata->addConstraint(new Assert\Callback('validate'));
}
diff --git a/components/validator/resources.rst b/components/validator/resources.rst
index 7f9b02fb544..7d6cd0e8e5d 100644
--- a/components/validator/resources.rst
+++ b/components/validator/resources.rst
@@ -1,6 +1,3 @@
-.. index::
- single: Validator; Loading Resources
-
Loading Resources
=================
@@ -40,15 +37,15 @@ In this example, the validation metadata is retrieved executing the
class User
{
- protected $name;
+ protected string $name;
- public static function loadValidatorMetadata(ClassMetadata $metadata)
+ public static function loadValidatorMetadata(ClassMetadata $metadata): void
{
$metadata->addPropertyConstraint('name', new Assert\NotBlank());
- $metadata->addPropertyConstraint('name', new Assert\Length([
- 'min' => 5,
- 'max' => 20,
- ]));
+ $metadata->addPropertyConstraint('name', new Assert\Length(
+ min: 5,
+ max: 20,
+ ));
}
}
@@ -76,7 +73,7 @@ configure the locations of these files::
.. note::
- If you want to load YAML mapping files then you will also need to install
+ If you want to load YAML mapping files, then you will also need to install
:doc:`the Yaml component `.
.. tip::
@@ -86,40 +83,27 @@ configure the locations of these files::
:method:`Symfony\\Component\\Validator\\ValidatorBuilder::addXmlMappings`
to configure an array of file paths.
-The AnnotationLoader
---------------------
+The AttributeLoader
+-------------------
-At last, the component provides an
-:class:`Symfony\\Component\\Validator\\Mapping\\Loader\\AnnotationLoader` to get
-the metadata from the annotations of the class. Annotations are defined as ``@``
-prefixed classes included in doc block comments (``/** ... */``). For example::
+The component provides an
+:class:`Symfony\\Component\\Validator\\Mapping\\Loader\\AttributeLoader` to get
+the metadata from the attributes of the class. For example::
use Symfony\Component\Validator\Constraints as Assert;
// ...
class User
{
- /**
- * @Assert\NotBlank
- */
- protected $name;
+ #[Assert\NotBlank]
+ protected string $name;
}
-To enable the annotation loader, call the
-:method:`Symfony\\Component\\Validator\\ValidatorBuilder::enableAnnotationMapping`
-method. It takes an optional annotation reader instance, which defaults to
-``Doctrine\Common\Annotations\AnnotationReader``::
-
- use Symfony\Component\Validator\Validation;
-
- $validator = Validation::createValidatorBuilder()
- ->enableAnnotationMapping()
- ->getValidator();
-
-To disable the annotation loader after it was enabled, call
-:method:`Symfony\\Component\\Validator\\ValidatorBuilder::disableAnnotationMapping`.
+To enable the attribute loader, call the
+:method:`Symfony\\Component\\Validator\\ValidatorBuilder::enableAttributeMapping` method.
-.. include:: /_includes/_annotation_loader_tip.rst.inc
+To disable the attribute loader after it was enabled, call
+:method:`Symfony\\Component\\Validator\\ValidatorBuilder::disableAttributeMapping`.
Using Multiple Loaders
----------------------
@@ -134,7 +118,7 @@ multiple mappings::
use Symfony\Component\Validator\Validation;
$validator = Validation::createValidatorBuilder()
- ->enableAnnotationMapping()
+ ->enableAttributeMapping()
->addMethodMapping('loadValidatorMetadata')
->addXmlMapping('validator/validation.xml')
->getValidator();
@@ -149,13 +133,13 @@ instance.
To solve this problem, call the :method:`Symfony\\Component\\Validator\\ValidatorBuilder::setMappingCache`
method of the Validator builder and pass your own caching class (which must
-implement the PSR-6 interface :class:`Psr\\Cache\\CacheItemPoolInterface`)::
+implement the PSR-6 interface ``Psr\Cache\CacheItemPoolInterface``)::
use Symfony\Component\Validator\Validation;
$validator = Validation::createValidatorBuilder()
// ... add loaders
- ->setMappingCache(new SomePsr6Cache());
+ ->setMappingCache(new SomePsr6Cache())
->getValidator();
.. note::
@@ -187,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 e89c822c21f..c6966a692af 100644
--- a/components/var_dumper.rst
+++ b/components/var_dumper.rst
@@ -1,7 +1,3 @@
-.. index::
- single: VarDumper
- single: Components; VarDumper
-
The VarDumper Component
=======================
@@ -66,12 +62,13 @@ current PHP SAPI:
You can also select the output format explicitly defining the
``VAR_DUMPER_FORMAT`` environment variable and setting its value to either
- ``html`` or ``cli``.
+ ``html``, ``cli`` or :ref:`server `.
.. note::
If you want to catch the dump output as a string, please read the
- :doc:`advanced documentation ` which contains examples of it.
+ :ref:`advanced section ` which contains examples of
+ it.
You'll also learn how to change the format or redirect the output to
wherever you want.
@@ -131,22 +128,27 @@ the :ref:`dump_destination option ` of the
-
-
+ http://symfony.com/schema/dic/debug
+ https://symfony.com/schema/dic/debug/debug-1.0.xsd"
+ >
.. code-block:: php
// config/packages/debug.php
- $container->loadFromExtension('debug', [
- 'dump_destination' => 'tcp://%env(VAR_DUMPER_SERVER)%',
- ]);
+ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+ return static function (ContainerConfigurator $container): void {
+ $container->extension('debug', [
+ 'dump_destination' => 'tcp://%env(VAR_DUMPER_SERVER)%',
+ ]);
+ };
Outside a Symfony application, use the :class:`Symfony\\Component\\VarDumper\\Dumper\\ServerDumper` class::
@@ -167,8 +169,8 @@ Outside a Symfony application, use the :class:`Symfony\\Component\\VarDumper\\Du
'source' => new SourceContextProvider(),
]);
- VarDumper::setHandler(function ($var) use ($cloner, $dumper) {
- $dumper->dump($cloner->cloneVar($var));
+ VarDumper::setHandler(function (mixed $var) use ($cloner, $dumper): ?string {
+ return $dumper->dump($cloner->cloneVar($var));
});
.. note::
@@ -186,6 +188,35 @@ Then you can use the following command to start a server out-of-the-box:
$ ./vendor/bin/var-dump-server
[OK] Server listening on tcp://127.0.0.1:9912
+.. _var-dumper-dump-server-format:
+
+Configuring the Dump Server with Environment Variables
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you prefer to not modify the application configuration (e.g. to quickly debug
+a project given to you) use the ``VAR_DUMPER_FORMAT`` env var.
+
+First, start the server as usual:
+
+.. code-block:: terminal
+
+ $ ./vendor/bin/var-dump-server
+
+Then, run your code with the ``VAR_DUMPER_FORMAT=server`` env var by configuring
+this value in the :ref:`.env file of your application `. For
+console commands, you can also define this env var as follows:
+
+.. code-block:: terminal
+
+ $ VAR_DUMPER_FORMAT=server [your-cli-command]
+
+.. note::
+
+ The host used by the ``server`` format is the one configured in the
+ ``VAR_DUMPER_SERVER`` env var or ``127.0.0.1:9912`` if none is defined.
+ If you prefer, you can also configure the host in the ``VAR_DUMPER_FORMAT``
+ env var like this: ``VAR_DUMPER_FORMAT=tcp://127.0.0.1:1234``.
+
DebugBundle and Twig Integration
--------------------------------
@@ -225,7 +256,7 @@ option. Read more about this and other options in
finished, press ``Esc.`` to hide the box again.
If you want to use your browser search input, press ``Ctrl. + F`` or
- ``Cmd. + F`` again while having focus on VarDumper's search input.
+ ``Cmd. + F`` again while focusing on VarDumper's search input.
Using the VarDumper Component in your PHPUnit Test Suite
--------------------------------------------------------
@@ -263,7 +294,7 @@ Example::
{
use VarDumperTestTrait;
- protected function setUp()
+ protected function setUp(): void
{
$casters = [
\DateTimeInterface::class => static function (\DateTimeInterface $date, array $a, Stub $stub): array {
@@ -280,7 +311,7 @@ Example::
$this->setUpVarDumper($casters, $flags);
}
- public function testWithDumpEquals()
+ public function testWithDumpEquals(): void
{
$testedVar = [123, 'foo'];
@@ -319,6 +350,7 @@ then its dump representation::
dump($var);
.. image:: /_images/components/var_dumper/01-simple.png
+ :alt: Dump output showing the array with length five and all keys and values.
.. note::
@@ -336,31 +368,33 @@ then its dump representation::
dump($var);
.. image:: /_images/components/var_dumper/02-multi-line-str.png
+ :alt: Dump output showing the string on multiple lines in between three quotes.
.. code-block:: php
class PropertyExample
{
- public $publicProperty = 'The `+` prefix denotes public properties,';
- protected $protectedProperty = '`#` protected ones and `-` private ones.';
- private $privateProperty = 'Hovering a property shows a reminder.';
+ public string $publicProperty = 'The `+` prefix denotes public properties,';
+ protected string $protectedProperty = '`#` protected ones and `-` private ones.';
+ private string $privateProperty = 'Hovering a property shows a reminder.';
}
$var = new PropertyExample();
dump($var);
.. image:: /_images/components/var_dumper/03-object.png
+ :alt: Dump output showing the PropertyExample object and all three properties with their values.
.. 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
class DynamicPropertyExample
{
- public $declaredProperty = 'This property is declared in the class definition';
+ public string $declaredProperty = 'This property is declared in the class definition';
}
$var = new DynamicPropertyExample();
@@ -368,18 +402,20 @@ then its dump representation::
dump($var);
.. image:: /_images/components/var_dumper/04-dynamic-property.png
+ :alt: Dump output showing the DynamicPropertyExample object and both declared and undeclared properties with their values.
.. code-block:: php
class ReferenceExample
{
- public $info = "Circular and sibling references are displayed as `#number`.\nHovering them highlights all instances in the same dump.\n";
+ public string $info = "Circular and sibling references are displayed as `#number`.\nHovering them highlights all instances in the same dump.\n";
}
$var = new ReferenceExample();
$var->aCircularReference = $var;
dump($var);
.. image:: /_images/components/var_dumper/05-soft-ref.png
+ :alt: Dump output showing the "aCircularReference" property value referencing the parent object, instead of showing all properties again.
.. code-block:: php
@@ -393,6 +429,7 @@ then its dump representation::
dump($var);
.. image:: /_images/components/var_dumper/06-constants.png
+ :alt: Dump output with the "E_WARNING" constant shown as value of "severity".
.. code-block:: php
@@ -406,6 +443,7 @@ then its dump representation::
dump($var);
.. image:: /_images/components/var_dumper/07-hard-ref.png
+ :alt: Dump output showing the referenced arrays.
.. code-block:: php
@@ -416,6 +454,7 @@ then its dump representation::
dump($var);
.. image:: /_images/components/var_dumper/08-virtual-property.png
+ :alt: Dump output of the ArrayObject.
.. code-block:: php
@@ -429,12 +468,426 @@ then its dump representation::
dump($var);
.. image:: /_images/components/var_dumper/09-cut.png
+ :alt: Dump output where the children of the Container object are hidden.
+
+.. code-block:: php
+
+ class Foo
+ {
+ // $foo is uninitialized, which is different from being null
+ private int|float $foo;
+ public ?string $baz = null;
+ }
+
+ $var = new Foo();
+ dump($var);
+
+.. image:: /_images/components/var_dumper/10-uninitialized.png
+ :alt: Dump output where the uninitialized property is represented by a question mark followed by the type definition.
+
+.. _var-dumper-advanced:
+
+Advanced Usage
+--------------
+
+The ``dump()`` function is just a thin wrapper and a more convenient way to call
+:method:`VarDumper::dump() `.
+You can change the behavior of this function by calling
+:method:`VarDumper::setHandler($callable) `.
+Calls to ``dump()`` will then be forwarded to ``$callable``.
+
+By adding a handler, you can customize the `Cloners`_, `Dumpers`_ and `Casters`_
+as explained below. A simple implementation of a handler function might look
+like this::
+
+ use Symfony\Component\VarDumper\Cloner\VarCloner;
+ use Symfony\Component\VarDumper\Dumper\CliDumper;
+ use Symfony\Component\VarDumper\Dumper\HtmlDumper;
+ use Symfony\Component\VarDumper\VarDumper;
+
+ VarDumper::setHandler(function (mixed $var): ?string {
+ $cloner = new VarCloner();
+ $dumper = 'cli' === PHP_SAPI ? new CliDumper() : new HtmlDumper();
+
+ return $dumper->dump($cloner->cloneVar($var));
+ });
+
+Cloners
+~~~~~~~
+
+A cloner is used to create an intermediate representation of any PHP variable.
+Its output is a :class:`Symfony\\Component\\VarDumper\\Cloner\\Data`
+object that wraps this representation.
+
+You can create a ``Data`` object this way::
+
+ use Symfony\Component\VarDumper\Cloner\VarCloner;
+
+ $cloner = new VarCloner();
+ $data = $cloner->cloneVar($myVar);
+ // this is commonly then passed to the dumper
+ // see the example at the top of this page
+ // $dumper->dump($data);
+
+Whatever the cloned data structure, resulting ``Data`` objects are always
+serializable.
+
+A cloner applies limits when creating the representation, so that one
+can represent only a subset of the cloned variable.
+Before calling :method:`Symfony\\Component\\VarDumper\\Cloner\\VarCloner::cloneVar`,
+you can configure these limits:
+
+:method:`Symfony\\Component\\VarDumper\\Cloner\\VarCloner::setMaxItems`
+ Configures the maximum number of items that will be cloned
+ *past the minimum nesting depth*. Items are counted using a breadth-first
+ algorithm so that lower level items have higher priority than deeply nested
+ items. Specifying ``-1`` removes the limit.
+
+:method:`Symfony\\Component\\VarDumper\\Cloner\\VarCloner::setMinDepth`
+ Configures the minimum tree depth where we are guaranteed to clone
+ all the items. After this depth is reached, only ``setMaxItems``
+ items will be cloned. The default value is ``1``, which is consistent
+ with older Symfony versions.
+
+:method:`Symfony\\Component\\VarDumper\\Cloner\\VarCloner::setMaxString`
+ Configures the maximum number of characters that will be cloned before
+ cutting overlong strings. Specifying ``-1`` removes the limit.
+
+Before dumping it, you can further limit the resulting
+:class:`Symfony\\Component\\VarDumper\\Cloner\\Data` object using the following methods:
+
+:method:`Symfony\\Component\\VarDumper\\Cloner\\Data::withMaxDepth`
+ Limits dumps in the depth dimension.
+
+:method:`Symfony\\Component\\VarDumper\\Cloner\\Data::withMaxItemsPerDepth`
+ Limits the number of items per depth level.
+
+:method:`Symfony\\Component\\VarDumper\\Cloner\\Data::withRefHandles`
+ Removes internal objects' handles for sparser output (useful for tests).
+
+:method:`Symfony\\Component\\VarDumper\\Cloner\\Data::seek`
+ Selects only sub-parts of already cloned arrays, objects or resources.
+
+Unlike the previous limits on cloners that remove data on purpose, these can
+be changed back and forth before dumping since they do not affect the
+intermediate representation internally.
+
+.. note::
+
+ When no limit is applied, a :class:`Symfony\\Component\\VarDumper\\Cloner\\Data`
+ object is as accurate as the native :phpfunction:`serialize` function,
+ and thus could be used for purposes beyond debugging.
+
+Dumpers
+~~~~~~~
+
+A dumper is responsible for outputting a string representation of a PHP variable,
+using a :class:`Symfony\\Component\\VarDumper\\Cloner\\Data` object as input.
+The destination and the formatting of this output vary with dumpers.
+
+This component comes with an :class:`Symfony\\Component\\VarDumper\\Dumper\\HtmlDumper`
+for HTML output and a :class:`Symfony\\Component\\VarDumper\\Dumper\\CliDumper`
+for optionally colored command line output.
+
+For example, if you want to dump some ``$variable``, do::
+
+ use Symfony\Component\VarDumper\Cloner\VarCloner;
+ use Symfony\Component\VarDumper\Dumper\CliDumper;
+
+ $cloner = new VarCloner();
+ $dumper = new CliDumper();
+
+ $dumper->dump($cloner->cloneVar($variable));
+
+By using the first argument of the constructor, you can select the output
+stream where the dump will be written. By default, the ``CliDumper`` writes
+on ``php://stdout`` and the ``HtmlDumper`` on ``php://output``. But any PHP
+stream (resource or URL) is acceptable.
+
+Instead of a stream destination, you can also pass it a ``callable`` that
+will be called repeatedly for each line generated by a dumper. This
+callable can be configured using the first argument of a dumper's constructor,
+but also using the
+:method:`Symfony\\Component\\VarDumper\\Dumper\\AbstractDumper::setOutput`
+method or the second argument of the
+:method:`Symfony\\Component\\VarDumper\\Dumper\\AbstractDumper::dump` method.
+
+For example, to get a dump as a string in a variable, you can do::
+
+ use Symfony\Component\VarDumper\Cloner\VarCloner;
+ use Symfony\Component\VarDumper\Dumper\CliDumper;
+
+ $cloner = new VarCloner();
+ $dumper = new CliDumper();
+ $output = '';
+
+ $dumper->dump(
+ $cloner->cloneVar($variable),
+ 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
+ $output .= str_repeat(' ', $depth).$line."\n";
+ }
+ }
+ );
-Learn More
-----------
+ // $output is now populated with the dump representation of $variable
-.. toctree::
- :maxdepth: 1
- :glob:
+Another option for doing the same could be::
- var_dumper/*
+ use Symfony\Component\VarDumper\Cloner\VarCloner;
+ use Symfony\Component\VarDumper\Dumper\CliDumper;
+
+ $cloner = new VarCloner();
+ $dumper = new CliDumper();
+ $output = fopen('php://memory', 'r+b');
+
+ $dumper->dump($cloner->cloneVar($variable), $output);
+ $output = stream_get_contents($output, -1, 0);
+
+ // $output is now populated with the dump representation of $variable
+
+.. tip::
+
+ You can pass ``true`` to the second argument of the
+ :method:`Symfony\\Component\\VarDumper\\Dumper\\AbstractDumper::dump`
+ method to make it return the dump as a string::
+
+ $output = $dumper->dump($cloner->cloneVar($variable), true);
+
+Dumpers implement the :class:`Symfony\\Component\\VarDumper\\Dumper\\DataDumperInterface`
+interface that specifies the
+:method:`dump(Data $data) `
+method. They also typically implement the
+:class:`Symfony\\Component\\VarDumper\\Cloner\\DumperInterface` that frees
+them from re-implementing the logic required to walk through a
+:class:`Symfony\\Component\\VarDumper\\Cloner\\Data` object's internal structure.
+
+The :class:`Symfony\\Component\\VarDumper\\Dumper\\HtmlDumper` uses a dark
+theme by default. Use the :method:`Symfony\\Component\\VarDumper\\Dumper\\HtmlDumper::setTheme`
+method to use a light theme::
+
+ // ...
+ $htmlDumper->setTheme('light');
+
+The :class:`Symfony\\Component\\VarDumper\\Dumper\\HtmlDumper` limits string
+length and nesting depth of the output to make it more readable. These options
+can be overridden by the third optional parameter of the
+:method:`dump(Data $data) `
+method::
+
+ use Symfony\Component\VarDumper\Dumper\HtmlDumper;
+
+ $output = fopen('php://memory', 'r+b');
+
+ $dumper = new HtmlDumper();
+ $dumper->dump($var, $output, [
+ // 1 and 160 are the default values for these options
+ 'maxDepth' => 1,
+ 'maxStringLength' => 160,
+ ]);
+
+The output format of a dumper can be fine tuned by the two flags
+``DUMP_STRING_LENGTH`` and ``DUMP_LIGHT_ARRAY`` which are passed as a bitmap
+in the third constructor argument. They can also be set via environment
+variables when using
+:method:`assertDumpEquals($dump, $data, $filter, $message) `
+during unit testing.
+
+The ``$filter`` argument of ``assertDumpEquals()`` can be used to pass a
+bit field of ``Caster::EXCLUDE_*`` constants and influences the expected
+output produced by the different casters.
+
+If ``DUMP_STRING_LENGTH`` is set, then the length of a string is displayed
+next to its content::
+
+ use Symfony\Component\VarDumper\Cloner\VarCloner;
+ use Symfony\Component\VarDumper\Dumper\AbstractDumper;
+ use Symfony\Component\VarDumper\Dumper\CliDumper;
+
+ $varCloner = new VarCloner();
+ $var = ['test'];
+
+ $dumper = new CliDumper();
+ echo $dumper->dump($varCloner->cloneVar($var), true);
+
+ // array:1 [
+ // 0 => "test"
+ // ]
+
+ $dumper = new CliDumper(null, null, AbstractDumper::DUMP_STRING_LENGTH);
+ echo $dumper->dump($varCloner->cloneVar($var), true);
+
+ // (added string length before the string)
+ // array:1 [
+ // 0 => (4) "test"
+ // ]
+
+If ``DUMP_LIGHT_ARRAY`` is set, then arrays are dumped in a shortened format
+similar to PHP's short array notation::
+
+ use Symfony\Component\VarDumper\Cloner\VarCloner;
+ use Symfony\Component\VarDumper\Dumper\AbstractDumper;
+ use Symfony\Component\VarDumper\Dumper\CliDumper;
+
+ $varCloner = new VarCloner();
+ $var = ['test'];
+
+ $dumper = new CliDumper();
+ echo $dumper->dump($varCloner->cloneVar($var), true);
+
+ // array:1 [
+ // 0 => "test"
+ // ]
+
+ $dumper = new CliDumper(null, null, AbstractDumper::DUMP_LIGHT_ARRAY);
+ echo $dumper->dump($varCloner->cloneVar($var), true);
+
+ // (no more array:1 prefix)
+ // [
+ // 0 => "test"
+ // ]
+
+If you would like to use both options, then you can combine them by
+using the logical OR operator ``|``::
+
+ use Symfony\Component\VarDumper\Cloner\VarCloner;
+ use Symfony\Component\VarDumper\Dumper\AbstractDumper;
+ use Symfony\Component\VarDumper\Dumper\CliDumper;
+
+ $varCloner = new VarCloner();
+ $var = ['test'];
+
+ $dumper = new CliDumper(null, null, AbstractDumper::DUMP_STRING_LENGTH | AbstractDumper::DUMP_LIGHT_ARRAY);
+ echo $dumper->dump($varCloner->cloneVar($var), true);
+
+ // [
+ // 0 => (4) "test"
+ // ]
+
+Casters
+~~~~~~~
+
+Objects and resources nested in a PHP variable are "cast" to arrays in the
+intermediate :class:`Symfony\\Component\\VarDumper\\Cloner\\Data`
+representation. You can customize the array representation for each object/resource
+by hooking a Caster into this process. The component already includes many
+casters for base PHP classes and other common classes.
+
+If you want to build your own Caster, you can register one before cloning
+a PHP variable. Casters are registered using either a Cloner's constructor
+or its ``addCasters()`` method::
+
+ use Symfony\Component\VarDumper\Cloner\VarCloner;
+
+ $myCasters = [...];
+ $cloner = new VarCloner($myCasters);
+
+ // or
+
+ $cloner->addCasters($myCasters);
+
+The provided ``$myCasters`` argument is an array that maps a class,
+an interface or a resource type to a callable::
+
+ $myCasters = [
+ 'FooClass' => $myFooClassCallableCaster,
+ ':bar resource' => $myBarResourceCallableCaster,
+ ];
+
+As you can notice, resource types are prefixed by a ``:`` to prevent
+colliding with a class name.
+
+Because an object has one main class and potentially many parent classes
+or interfaces, many casters can be applied to one object. In this case,
+casters are called one after the other, starting from casters bound to the
+interfaces, the parents classes and then the main class. Several casters
+can also be registered for the same resource type/class/interface.
+They are called in registration order.
+
+Casters are responsible for returning the properties of the object or resource
+being cloned in an array. They are callables that accept five arguments:
+
+* the object or resource being cast;
+* an array modeled for objects after PHP's native ``(array)`` cast operator;
+* a :class:`Symfony\\Component\\VarDumper\\Cloner\\Stub` object
+ representing the main properties of the object (class, type, etc.);
+* true/false when the caster is called nested in a structure or not;
+* A bit field of :class:`Symfony\\Component\\VarDumper\\Caster\\Caster` ``::EXCLUDE_*``
+ constants.
+
+Here is a simple caster not doing anything::
+
+ use Symfony\Component\VarDumper\Cloner\Stub;
+
+ function myCaster(mixed $object, array $array, Stub $stub, bool $isNested, int $filter): array
+ {
+ // ... populate/alter $array to your needs
+
+ return $array;
+ }
+
+For objects, the ``$array`` parameter comes pre-populated using PHP's native
+``(array)`` casting operator or with the return value of ``$object->__debugInfo()``
+if the magic method exists. Then, the return value of one Caster is given
+as the array argument to the next Caster in the chain.
+
+When casting with the ``(array)`` operator, PHP prefixes protected properties
+with a ``\0*\0`` and private ones with the class owning the property. For example,
+``\0Foobar\0`` will be the prefix for all private properties of objects of
+type Foobar. Casters follow this convention and add two more prefixes: ``\0~\0``
+is used for virtual properties and ``\0+\0`` for dynamic ones (runtime added
+properties not in the class declaration).
+
+.. note::
+
+ Although you can, it is advised to not alter the state of an object
+ while casting it in a Caster.
+
+.. tip::
+
+ Before writing your own casters, you should check the existing ones.
+
+Adding Semantics with Metadata
+..............................
+
+Since casters are hooked on specific classes or interfaces, they know about the
+objects they manipulate. By altering the ``$stub`` object (the third argument of
+any caster), one can transfer this knowledge to the resulting ``Data`` object,
+thus to dumpers. To help you do this (see the source code for how it works),
+the component comes with a set of wrappers for common additional semantics. You
+can use:
+
+* :class:`Symfony\\Component\\VarDumper\\Caster\\ConstStub` to wrap a value that is
+ best represented by a PHP constant;
+* :class:`Symfony\\Component\\VarDumper\\Caster\\ClassStub` to wrap a PHP identifier
+ (*i.e.* a class name, a method name, an interface, *etc.*);
+* :class:`Symfony\\Component\\VarDumper\\Caster\\CutStub` to replace big noisy
+ objects/strings/*etc.* by ellipses;
+* :class:`Symfony\\Component\\VarDumper\\Caster\\CutArrayStub` to keep only some
+ useful keys of an array;
+* :class:`Symfony\\Component\\VarDumper\\Caster\\ImgStub` to wrap an image;
+* :class:`Symfony\\Component\\VarDumper\\Caster\\EnumStub` to wrap a set of virtual
+ values (*i.e.* values that do not exist as properties in the original PHP data
+ structure, but are worth listing alongside with real ones);
+* :class:`Symfony\\Component\\VarDumper\\Caster\\LinkStub` to wrap strings that can
+ be turned into links by dumpers;
+* :class:`Symfony\\Component\\VarDumper\\Caster\\TraceStub` and their
+* :class:`Symfony\\Component\\VarDumper\\Caster\\FrameStub` and
+* :class:`Symfony\\Component\\VarDumper\\Caster\\ArgsStub` relatives to wrap PHP
+ traces (used by :class:`Symfony\\Component\\VarDumper\\Caster\\ExceptionCaster`).
+
+For example, if you know that your ``Product`` objects have a ``brochure`` property
+that holds a file name or a URL, you can wrap them in a ``LinkStub`` to tell
+``HtmlDumper`` to make them clickable::
+
+ use Symfony\Component\VarDumper\Caster\LinkStub;
+ use Symfony\Component\VarDumper\Cloner\Stub;
+
+ function ProductCaster(Product $object, array $array, Stub $stub, bool $isNested, int $filter = 0): array
+ {
+ $array['brochure'] = new LinkStub($array['brochure']);
+
+ return $array;
+ }
diff --git a/components/var_dumper/advanced.rst b/components/var_dumper/advanced.rst
deleted file mode 100644
index 0f429c52012..00000000000
--- a/components/var_dumper/advanced.rst
+++ /dev/null
@@ -1,408 +0,0 @@
-.. index::
- single: VarDumper
- single: Components; VarDumper
-
-Advanced Usage of the VarDumper Component
-=========================================
-
-The ``dump()`` function is just a thin wrapper and a more convenient way to call
-:method:`VarDumper::dump() `.
-You can change the behavior of this function by calling
-:method:`VarDumper::setHandler($callable) `.
-Calls to ``dump()`` will then be forwarded to ``$callable``.
-
-By adding a handler, you can customize the `Cloners`_, `Dumpers`_ and `Casters`_
-as explained below. A simple implementation of a handler function might look
-like this::
-
- use Symfony\Component\VarDumper\Cloner\VarCloner;
- use Symfony\Component\VarDumper\Dumper\CliDumper;
- use Symfony\Component\VarDumper\Dumper\HtmlDumper;
- use Symfony\Component\VarDumper\VarDumper;
-
- VarDumper::setHandler(function ($var) {
- $cloner = new VarCloner();
- $dumper = 'cli' === PHP_SAPI ? new CliDumper() : new HtmlDumper();
-
- $dumper->dump($cloner->cloneVar($var));
- });
-
-Cloners
--------
-
-A cloner is used to create an intermediate representation of any PHP variable.
-Its output is a :class:`Symfony\\Component\\VarDumper\\Cloner\\Data`
-object that wraps this representation.
-
-You can create a ``Data`` object this way::
-
- use Symfony\Component\VarDumper\Cloner\VarCloner;
-
- $cloner = new VarCloner();
- $data = $cloner->cloneVar($myVar);
- // this is commonly then passed to the dumper
- // see the example at the top of this page
- // $dumper->dump($data);
-
-Whatever the cloned data structure, resulting ``Data`` objects are always
-serializable.
-
-A cloner applies limits when creating the representation, so that one
-can represent only a subset of the cloned variable.
-Before calling :method:`Symfony\\Component\\VarDumper\\Cloner\\VarCloner::cloneVar`,
-you can configure these limits:
-
-:method:`Symfony\\Component\\VarDumper\\Cloner\\VarCloner::setMaxItems`
- Configures the maximum number of items that will be cloned
- *past the minimum nesting depth*. Items are counted using a breadth-first
- algorithm so that lower level items have higher priority than deeply nested
- items. Specifying ``-1`` removes the limit.
-
-:method:`Symfony\\Component\\VarDumper\\Cloner\\VarCloner::setMinDepth`
- Configures the minimum tree depth where we are guaranteed to clone
- all the items. After this depth is reached, only ``setMaxItems``
- items will be cloned. The default value is ``1``, which is consistent
- with older Symfony versions.
-
-:method:`Symfony\\Component\\VarDumper\\Cloner\\VarCloner::setMaxString`
- Configures the maximum number of characters that will be cloned before
- cutting overlong strings. Specifying ``-1`` removes the limit.
-
-Before dumping it, you can further limit the resulting
-:class:`Symfony\\Component\\VarDumper\\Cloner\\Data` object using the following methods:
-
-:method:`Symfony\\Component\\VarDumper\\Cloner\\Data::withMaxDepth`
- Limits dumps in the depth dimension.
-
-:method:`Symfony\\Component\\VarDumper\\Cloner\\Data::withMaxItemsPerDepth`
- Limits the number of items per depth level.
-
-:method:`Symfony\\Component\\VarDumper\\Cloner\\Data::withRefHandles`
- Removes internal objects' handles for sparser output (useful for tests).
-
-:method:`Symfony\\Component\\VarDumper\\Cloner\\Data::seek`
- Selects only sub-parts of already cloned arrays, objects or resources.
-
-Unlike the previous limits on cloners that remove data on purpose, these can
-be changed back and forth before dumping since they do not affect the
-intermediate representation internally.
-
-.. note::
-
- When no limit is applied, a :class:`Symfony\\Component\\VarDumper\\Cloner\\Data`
- object is as accurate as the native :phpfunction:`serialize` function,
- and thus could be used for purposes beyond debugging.
-
-Dumpers
--------
-
-A dumper is responsible for outputting a string representation of a PHP variable,
-using a :class:`Symfony\\Component\\VarDumper\\Cloner\\Data` object as input.
-The destination and the formatting of this output vary with dumpers.
-
-This component comes with an :class:`Symfony\\Component\\VarDumper\\Dumper\\HtmlDumper`
-for HTML output and a :class:`Symfony\\Component\\VarDumper\\Dumper\\CliDumper`
-for optionally colored command line output.
-
-For example, if you want to dump some ``$variable``, do::
-
- use Symfony\Component\VarDumper\Cloner\VarCloner;
- use Symfony\Component\VarDumper\Dumper\CliDumper;
-
- $cloner = new VarCloner();
- $dumper = new CliDumper();
-
- $dumper->dump($cloner->cloneVar($variable));
-
-By using the first argument of the constructor, you can select the output
-stream where the dump will be written. By default, the ``CliDumper`` writes
-on ``php://stdout`` and the ``HtmlDumper`` on ``php://output``. But any PHP
-stream (resource or URL) is acceptable.
-
-Instead of a stream destination, you can also pass it a ``callable`` that
-will be called repeatedly for each line generated by a dumper. This
-callable can be configured using the first argument of a dumper's constructor,
-but also using the
-:method:`Symfony\\Component\\VarDumper\\Dumper\\AbstractDumper::setOutput`
-method or the second argument of the
-:method:`Symfony\\Component\\VarDumper\\Dumper\\AbstractDumper::dump` method.
-
-For example, to get a dump as a string in a variable, you can do::
-
- use Symfony\Component\VarDumper\Cloner\VarCloner;
- use Symfony\Component\VarDumper\Dumper\CliDumper;
-
- $cloner = new VarCloner();
- $dumper = new CliDumper();
- $output = '';
-
- $dumper->dump(
- $cloner->cloneVar($variable),
- function ($line, $depth) use (&$output) {
- // A negative depth means "end of dump"
- if ($depth >= 0) {
- // Adds a two spaces indentation to the line
- $output .= str_repeat(' ', $depth).$line."\n";
- }
- }
- );
-
- // $output is now populated with the dump representation of $variable
-
-Another option for doing the same could be::
-
- use Symfony\Component\VarDumper\Cloner\VarCloner;
- use Symfony\Component\VarDumper\Dumper\CliDumper;
-
- $cloner = new VarCloner();
- $dumper = new CliDumper();
- $output = fopen('php://memory', 'r+b');
-
- $dumper->dump($cloner->cloneVar($variable), $output);
- $output = stream_get_contents($output, -1, 0);
-
- // $output is now populated with the dump representation of $variable
-
-.. tip::
-
- You can pass ``true`` to the second argument of the
- :method:`Symfony\\Component\\VarDumper\\Dumper\\AbstractDumper::dump`
- method to make it return the dump as a string::
-
- $output = $dumper->dump($cloner->cloneVar($variable), true);
-
-Dumpers implement the :class:`Symfony\\Component\\VarDumper\\Dumper\\DataDumperInterface`
-interface that specifies the
-:method:`dump(Data $data) `
-method. They also typically implement the
-:class:`Symfony\\Component\\VarDumper\\Cloner\\DumperInterface` that frees
-them from re-implementing the logic required to walk through a
-:class:`Symfony\\Component\\VarDumper\\Cloner\\Data` object's internal structure.
-
-The :class:`Symfony\\Component\\VarDumper\\Dumper\\HtmlDumper` uses a dark
-theme by default. Use the :method:`Symfony\\Component\\VarDumper\\Dumper\\HtmlDumper::setTheme`
-method to use a light theme::
-
- // ...
- $htmlDumper->setTheme('light');
-
-The :class:`Symfony\\Component\\VarDumper\\Dumper\\HtmlDumper` limits string
-length and nesting depth of the output to make it more readable. These options
-can be overridden by the third optional parameter of the
-:method:`dump(Data $data) `
-method::
-
- use Symfony\Component\VarDumper\Dumper\HtmlDumper;
-
- $output = fopen('php://memory', 'r+b');
-
- $dumper = new HtmlDumper();
- $dumper->dump($var, $output, [
- // 1 and 160 are the default values for these options
- 'maxDepth' => 1,
- 'maxStringLength' => 160
- ]);
-
-The output format of a dumper can be fine tuned by the two flags
-``DUMP_STRING_LENGTH`` and ``DUMP_LIGHT_ARRAY`` which are passed as a bitmap
-in the third constructor argument. They can also be set via environment
-variables when using
-:method:`assertDumpEquals($dump, $data, $filter, $message) `
-during unit testing.
-
-The ``$filter`` argument of ``assertDumpEquals()`` can be used to pass a
-bit field of ``Caster::EXCLUDE_*`` constants and influences the expected
-output produced by the different casters.
-
-If ``DUMP_STRING_LENGTH`` is set, then the length of a string is displayed
-next to its content::
-
- use Symfony\Component\VarDumper\Cloner\VarCloner;
- use Symfony\Component\VarDumper\Dumper\AbstractDumper;
- use Symfony\Component\VarDumper\Dumper\CliDumper;
-
- $varCloner = new VarCloner();
- $var = ['test'];
-
- $dumper = new CliDumper();
- echo $dumper->dump($varCloner->cloneVar($var), true);
-
- // array:1 [
- // 0 => "test"
- // ]
-
- $dumper = new CliDumper(null, null, AbstractDumper::DUMP_STRING_LENGTH);
- echo $dumper->dump($varCloner->cloneVar($var), true);
-
- // (added string length before the string)
- // array:1 [
- // 0 => (4) "test"
- // ]
-
-If ``DUMP_LIGHT_ARRAY`` is set, then arrays are dumped in a shortened format
-similar to PHP's short array notation::
-
- use Symfony\Component\VarDumper\Cloner\VarCloner;
- use Symfony\Component\VarDumper\Dumper\AbstractDumper;
- use Symfony\Component\VarDumper\Dumper\CliDumper;
-
- $varCloner = new VarCloner();
- $var = ['test'];
-
- $dumper = new CliDumper();
- echo $dumper->dump($varCloner->cloneVar($var), true);
-
- // array:1 [
- // 0 => "test"
- // ]
-
- $dumper = new CliDumper(null, null, AbstractDumper::DUMP_LIGHT_ARRAY);
- echo $dumper->dump($varCloner->cloneVar($var), true);
-
- // (no more array:1 prefix)
- // [
- // 0 => "test"
- // ]
-
-If you would like to use both options, then you can combine them by
-using the logical OR operator ``|``::
-
- use Symfony\Component\VarDumper\Cloner\VarCloner;
- use Symfony\Component\VarDumper\Dumper\AbstractDumper;
- use Symfony\Component\VarDumper\Dumper\CliDumper;
-
- $varCloner = new VarCloner();
- $var = ['test'];
-
- $dumper = new CliDumper(null, null, AbstractDumper::DUMP_STRING_LENGTH | AbstractDumper::DUMP_LIGHT_ARRAY);
- echo $dumper->dump($varCloner->cloneVar($var), true);
-
- // [
- // 0 => (4) "test"
- // ]
-
-Casters
--------
-
-Objects and resources nested in a PHP variable are "cast" to arrays in the
-intermediate :class:`Symfony\\Component\\VarDumper\\Cloner\\Data`
-representation. You can customize the array representation for each object/resource
-by hooking a Caster into this process. The component already includes many
-casters for base PHP classes and other common classes.
-
-If you want to build your own Caster, you can register one before cloning
-a PHP variable. Casters are registered using either a Cloner's constructor
-or its ``addCasters()`` method::
-
- use Symfony\Component\VarDumper\Cloner\VarCloner;
-
- $myCasters = [...];
- $cloner = new VarCloner($myCasters);
-
- // or
-
- $cloner->addCasters($myCasters);
-
-The provided ``$myCasters`` argument is an array that maps a class,
-an interface or a resource type to a callable::
-
- $myCasters = [
- 'FooClass' => $myFooClassCallableCaster,
- ':bar resource' => $myBarResourceCallableCaster,
- ];
-
-As you can notice, resource types are prefixed by a ``:`` to prevent
-colliding with a class name.
-
-Because an object has one main class and potentially many parent classes
-or interfaces, many casters can be applied to one object. In this case,
-casters are called one after the other, starting from casters bound to the
-interfaces, the parents classes and then the main class. Several casters
-can also be registered for the same resource type/class/interface.
-They are called in registration order.
-
-Casters are responsible for returning the properties of the object or resource
-being cloned in an array. They are callables that accept five arguments:
-
-* the object or resource being casted;
-* an array modeled for objects after PHP's native ``(array)`` cast operator;
-* a :class:`Symfony\\Component\\VarDumper\\Cloner\\Stub` object
- representing the main properties of the object (class, type, etc.);
-* true/false when the caster is called nested in a structure or not;
-* A bit field of :class:`Symfony\\Component\\VarDumper\\Caster\\Caster` ``::EXCLUDE_*``
- constants.
-
-Here is a simple caster not doing anything::
-
- use Symfony\Component\VarDumper\Cloner\Stub;
-
- function myCaster($object, $array, Stub $stub, $isNested, $filter)
- {
- // ... populate/alter $array to your needs
-
- return $array;
- }
-
-For objects, the ``$array`` parameter comes pre-populated using PHP's native
-``(array)`` casting operator or with the return value of ``$object->__debugInfo()``
-if the magic method exists. Then, the return value of one Caster is given
-as the array argument to the next Caster in the chain.
-
-When casting with the ``(array)`` operator, PHP prefixes protected properties
-with a ``\0*\0`` and private ones with the class owning the property. For example,
-``\0Foobar\0`` will be the prefix for all private properties of objects of
-type Foobar. Casters follow this convention and add two more prefixes: ``\0~\0``
-is used for virtual properties and ``\0+\0`` for dynamic ones (runtime added
-properties not in the class declaration).
-
-.. note::
-
- Although you can, it is advised to not alter the state of an object
- while casting it in a Caster.
-
-.. tip::
-
- Before writing your own casters, you should check the existing ones.
-
-Adding Semantics with Metadata
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Since casters are hooked on specific classes or interfaces, they know about the
-objects they manipulate. By altering the ``$stub`` object (the third argument of
-any caster), one can transfer this knowledge to the resulting ``Data`` object,
-thus to dumpers. To help you do this (see the source code for how it works),
-the component comes with a set of wrappers for common additional semantics. You
-can use:
-
-* :class:`Symfony\\Component\\VarDumper\\Caster\\ConstStub` to wrap a value that is
- best represented by a PHP constant;
-* :class:`Symfony\\Component\\VarDumper\\Caster\\ClassStub` to wrap a PHP identifier
- (*i.e.* a class name, a method name, an interface, *etc.*);
-* :class:`Symfony\\Component\\VarDumper\\Caster\\CutStub` to replace big noisy
- objects/strings/*etc.* by ellipses;
-* :class:`Symfony\\Component\\VarDumper\\Caster\\CutArrayStub` to keep only some
- useful keys of an array;
-* :class:`Symfony\\Component\\VarDumper\\Caster\\ImgStub` to wrap an image;
-* :class:`Symfony\\Component\\VarDumper\\Caster\\EnumStub` to wrap a set of virtual
- values (*i.e.* values that do not exist as properties in the original PHP data
- structure, but are worth listing alongside with real ones);
-* :class:`Symfony\\Component\\VarDumper\\Caster\\LinkStub` to wrap strings that can
- be turned into links by dumpers;
-* :class:`Symfony\\Component\\VarDumper\\Caster\\TraceStub` and their
-* :class:`Symfony\\Component\\VarDumper\\Caster\\FrameStub` and
-* :class:`Symfony\\Component\\VarDumper\\Caster\\ArgsStub` relatives to wrap PHP
- traces (used by :class:`Symfony\\Component\\VarDumper\\Caster\\ExceptionCaster`).
-
-For example, if you know that your ``Product`` objects have a ``brochure`` property
-that holds a file name or a URL, you can wrap them in a ``LinkStub`` to tell
-``HtmlDumper`` to make them clickable::
-
- use Symfony\Component\VarDumper\Caster\LinkStub;
- use Symfony\Component\VarDumper\Cloner\Stub;
-
- function ProductCaster(Product $object, $array, Stub $stub, $isNested, $filter = 0)
- {
- $array['brochure'] = new LinkStub($array['brochure']);
-
- return $array;
- }
diff --git a/components/var_exporter.rst b/components/var_exporter.rst
index bf8f9b1f85a..fc6b34868db 100644
--- a/components/var_exporter.rst
+++ b/components/var_exporter.rst
@@ -1,7 +1,3 @@
-.. index::
- single: VarExporter
- single: Components; VarExporter
-
The VarExporter Component
=========================
@@ -28,7 +24,7 @@ PHP code, similar to PHP's :phpfunction:`var_export` function::
$exported = VarExporter::export($someVariable);
// store the $exported data in some file or cache system for later reuse
- $data = file_put_contents('exported.php', $exported);
+ $data = file_put_contents('exported.php', 'bar = $bar;
}
@@ -75,7 +71,6 @@ following class hierarchy::
When exporting the ``ConcreteClass`` data with VarExporter, the generated PHP
file looks like this::
- $propertyValue]);
+The instantiator can also populate the property of a parent class. Assuming ``Bar``
+is the parent class of ``Foo`` and defines a ``privateBarProperty`` attribute::
+
+ use Symfony\Component\VarExporter\Instantiator;
+
// creates a Foo instance and sets a private property defined on its parent Bar class
$fooObject = Instantiator::instantiate(Foo::class, [], [
Bar::class => ['privateBarProperty' => $propertyValue],
@@ -118,15 +122,216 @@ any other methods::
Instances of ``ArrayObject``, ``ArrayIterator`` and ``SplObjectHash`` can be
created by using the special ``"\0"`` property name to define their internal value::
- // Creates an SplObjectHash where $info1 is associated to $object1, etc.
+ use Symfony\Component\VarExporter\Instantiator;
+
+ // creates an SplObjectStorage where $info1 is associated with $object1, etc.
$theObject = Instantiator::instantiate(SplObjectStorage::class, [
- "\0" => [$object1, $info1, $object2, $info2...]
+ "\0" => [$object1, $info1, $object2, $info2...],
]);
// creates an ArrayObject populated with $inputArray
$theObject = Instantiator::instantiate(ArrayObject::class, [
- "\0" => [$inputArray]
+ "\0" => [$inputArray],
+ ]);
+
+Hydrator
+~~~~~~~~
+
+Instead of populating objects that don't exist yet (using the instantiator),
+sometimes you want to populate properties of an already existing object. This is
+the goal of the :class:`Symfony\\Component\\VarExporter\\Hydrator`. Here is a
+basic usage of the hydrator populating a property of an object::
+
+ use Symfony\Component\VarExporter\Hydrator;
+
+ $object = new Foo();
+ Hydrator::hydrate($object, ['propertyName' => $propertyValue]);
+
+The hydrator can also populate the property of a parent class. Assuming ``Bar``
+is the parent class of ``Foo`` and defines a ``privateBarProperty`` attribute::
+
+ use Symfony\Component\VarExporter\Hydrator;
+
+ $object = new Foo();
+ Hydrator::hydrate($object, [], [
+ Bar::class => ['privateBarProperty' => $propertyValue],
+ ]);
+
+ // alternatively, you can use the special "\0" syntax
+ Hydrator::hydrate($object, ["\0Bar\0privateBarProperty" => $propertyValue]);
+
+Instances of ``ArrayObject``, ``ArrayIterator`` and ``SplObjectHash`` can be
+populated by using the special ``"\0"`` property name to define their internal value::
+
+ use Symfony\Component\VarExporter\Hydrator;
+
+ // creates an SplObjectHash where $info1 is associated with $object1, etc.
+ $storage = new SplObjectStorage();
+ Hydrator::hydrate($storage, [
+ "\0" => [$object1, $info1, $object2, $info2...],
]);
+ // creates an ArrayObject populated with $inputArray
+ $arrayObject = new ArrayObject();
+ Hydrator::hydrate($arrayObject, [
+ "\0" => [$inputArray],
+ ]);
+
+Creating Lazy Objects
+---------------------
+
+Lazy-objects are objects instantiated empty and populated on-demand. This is
+particularly useful when you have for example properties in your classes that
+requires some heavy computation to determine their value. In this case, you
+may want to trigger the property's value processing only when you actually need
+its value. Thanks to this, the heavy computation won't be done if you never use
+this property. The VarExporter component is bundled with two traits helping
+you implement such mechanism easily in your classes.
+
+.. _var-exporter_ghost-objects:
+
+LazyGhostTrait
+~~~~~~~~~~~~~~
+
+Ghost objects are empty objects, which see their properties populated the first
+time any method is called. Thanks to :class:`Symfony\\Component\\VarExporter\\LazyGhostTrait`,
+the implementation of the lazy mechanism is eased. The ``MyLazyObject::populateHash()``
+method will be called only when the object is actually used and needs to be
+initialized::
+
+ namespace App\Hash;
+
+ use Symfony\Component\VarExporter\LazyGhostTrait;
+
+ class HashProcessor
+ {
+ use LazyGhostTrait;
+
+ // This property may require a heavy computation to have its value
+ public readonly string $hash;
+
+ public function __construct()
+ {
+ self::createLazyGhost(initializer: $this->populateHash(...), instance: $this);
+ }
+
+ private function populateHash(array $data): void
+ {
+ // Compute $this->hash value with the passed data
+ }
+ }
+
+:class:`Symfony\\Component\\VarExporter\\LazyGhostTrait` also allows to
+convert non-lazy classes to lazy ones::
+
+ namespace App\Hash;
+
+ use Symfony\Component\VarExporter\LazyGhostTrait;
+
+ class HashProcessor
+ {
+ public readonly string $hash;
+
+ public function __construct(array $data)
+ {
+ $this->populateHash($data);
+ }
+
+ private function populateHash(array $data): void
+ {
+ // ...
+ }
+
+ public function validateHash(): bool
+ {
+ // ...
+ }
+ }
+
+ class LazyHashProcessor extends HashProcessor
+ {
+ use LazyGhostTrait;
+ }
+
+ $processor = LazyHashProcessor::createLazyGhost(initializer: function (HashProcessor $instance): void {
+ // Do any operation you need here: call setters, getters, methods to validate the hash, etc.
+ $data = /** Retrieve required data to compute the hash */;
+ $instance->__construct(...$data);
+ $instance->validateHash();
+ });
+
+While you never query ``$processor->hash`` value, heavy methods will never be
+triggered. But still, the ``$processor`` object exists and can be used in your
+code, passed to methods, functions, etc.
+
+Ghost objects unfortunately can't work with abstract classes or internal PHP
+classes. Nevertheless, the VarExporter component covers this need with the help
+of :ref:`Virtual Proxies `.
+
+.. _var-exporter_virtual-proxies:
+
+LazyProxyTrait
+~~~~~~~~~~~~~~
+
+The purpose of virtual proxies in the same one as
+:ref:`ghost objects `, but their internal behavior is
+totally different. Where ghost objects requires to extend a base class, virtual
+proxies take advantage of the **Liskov Substitution principle**. This principle
+describes that if two objects are implementing the same interface, you can swap
+between the different implementations without breaking your application. This is
+what virtual proxies take advantage of. To use virtual proxies, you may use
+:class:`Symfony\\Component\\VarExporter\\ProxyHelper` to generate proxy's class
+code::
+
+ namespace App\Hash;
+
+ use Symfony\Component\VarExporter\ProxyHelper;
+
+ interface ProcessorInterface
+ {
+ public function getHash(): bool;
+ }
+
+ abstract class AbstractProcessor implements ProcessorInterface
+ {
+ protected string $hash;
+
+ public function getHash(): bool
+ {
+ return $this->hash;
+ }
+ }
+
+ class HashProcessor extends AbstractProcessor
+ {
+ public function __construct(array $data)
+ {
+ $this->populateHash($data);
+ }
+
+ private function populateHash(array $data): void
+ {
+ // ...
+ }
+ }
+
+ $proxyCode = ProxyHelper::generateLazyProxy(new \ReflectionClass(AbstractProcessor::class));
+ // $proxyCode contains the actual proxy and the reference to LazyProxyTrait.
+ // In production env, this should be dumped into a file to avoid calling eval().
+ eval('class HashProcessorProxy'.$proxyCode);
+
+ $processor = HashProcessorProxy::createLazyProxy(initializer: function (): ProcessorInterface {
+ $data = /** Retrieve required data to compute the hash */;
+ $instance = new HashProcessor(...$data);
+
+ // Do any operation you need here: call setters, getters, methods to validate the hash, etc.
+
+ return $instance;
+ });
+
+Just like ghost objects, while you never query ``$processor->hash``, its value
+will not be computed. The main difference with ghost objects is that this time,
+a proxy of an abstract class was created. This also works with internal PHP class.
+
.. _`OPcache`: https://www.php.net/opcache
.. _`PSR-2`: https://www.php-fig.org/psr/psr-2/
diff --git a/components/workflow.rst b/components/workflow.rst
index a35602f1ac2..e3da25b3476 100644
--- a/components/workflow.rst
+++ b/components/workflow.rst
@@ -1,7 +1,3 @@
-.. index::
- single: Workflow
- single: Components; Workflow
-
The Workflow Component
======================
@@ -26,13 +22,14 @@ process is called a *place*. You do also define *transitions* that describe
the action to get from one place to another.
.. image:: /_images/components/workflow/states_transitions.png
+ :alt: An example state diagram for a workflow, showing transitions and places.
A set of places and transitions creates a **definition**. A workflow needs
a ``Definition`` and a way to write the states to the objects (i.e. an
instance of a :class:`Symfony\\Component\\Workflow\\MarkingStore\\MarkingStoreInterface`).
Consider the following example for a blog post. A post can have one of a number
-of predefined statuses (`draft`, `reviewed`, `rejected`, `published`). In a workflow,
+of predefined statuses (``draft``, ``reviewed``, ``rejected``, ``published``). In a workflow,
these statuses are called **places**. You can define the workflow like this::
use Symfony\Component\Workflow\DefinitionBuilder;
@@ -58,33 +55,14 @@ The ``Workflow`` can now help you to decide what *transitions* (actions) are all
on a blog post depending on what *place* (state) it is in. This will keep your domain
logic in one place and not spread all over your application.
-When you define multiple workflows you should 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));
-
Usage
-----
-When you have configured a ``Registry`` with your workflows,
-you can retrieve a workflow from it and use it as follows::
+Here's an example of using the workflow defined above::
// ...
// Consider that $blogPost is in place "draft" by default
$blogPost = new BlogPost();
- $workflow = $registry->get($blogPost);
$workflow->can($blogPost, 'publish'); // False
$workflow->can($blogPost, 'to_review'); // True
@@ -94,6 +72,19 @@ you can retrieve a workflow from it and use it as follows::
$workflow->can($blogPost, 'publish'); // True
$workflow->getEnabledTransitions($blogPost); // $blogPost can perform transition "publish" or "reject"
+Initialization
+--------------
+
+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::
+
+ // ...
+ $blogPost = new BlogPost();
+
+ // initiate workflow
+ $workflow->getMarking($blogPost);
+
Learn more
----------
diff --git a/components/yaml.rst b/components/yaml.rst
index 29b8114ff53..471b59dcf5c 100644
--- a/components/yaml.rst
+++ b/components/yaml.rst
@@ -1,7 +1,3 @@
-.. index::
- single: Yaml
- single: Components; Yaml
-
The Yaml Component
==================
@@ -18,13 +14,9 @@ standard for all programming languages. YAML is a great format for your
configuration files. YAML files are as expressive as XML files and as readable
as INI files.
-The Symfony Yaml Component implements a selected subset of features defined in
-the `YAML 1.2 version specification`_.
-
.. tip::
- Learn more about the Yaml component in the
- :doc:`/components/yaml/yaml_format` article.
+ Learn more about :doc:`YAML specifications `.
Installation
------------
@@ -49,7 +41,7 @@ compact block collections and multi-document files.
Real Parser
~~~~~~~~~~~
-It sports a real parser and is able to parse a large subset of the YAML
+It supports a real parser and is able to parse a large subset of the YAML
specification, for all your configuration needs. It also means that the parser
is pretty robust, easy to understand, and simple enough to extend.
@@ -222,6 +214,8 @@ During the parsing of the YAML contents, all the ``_`` characters are removed
from the numeric literal contents, so there is not a limit in the number of
underscores you can include or the way you group contents.
+.. _yaml-flags:
+
Advanced Usage: Flags
---------------------
@@ -247,7 +241,7 @@ And parse them by using the ``PARSE_OBJECT`` flag::
The YAML component uses PHP's ``serialize()`` method to generate a string
representation of the object.
-.. caution::
+.. danger::
Object serialization is specific to this implementation, other PHP YAML
parsers will likely not recognize the ``php/object`` tag and non-PHP
@@ -338,18 +332,62 @@ syntax to parse them as proper PHP constants::
$parameters = Yaml::parse($yaml, Yaml::PARSE_CONSTANT);
// $parameters = ['foo' => 'PHP_INT_SIZE', 'bar' => 8];
+Parsing PHP Enumerations
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+The YAML parser supports `PHP enumerations`_, both unit and backed enums.
+By default, they are parsed as regular strings. Use the ``PARSE_CONSTANT`` flag
+and the special ``!php/enum`` syntax to parse them as proper PHP enums::
+
+ enum FooEnum: string
+ {
+ case Foo = 'foo';
+ case Bar = 'bar';
+ }
+
+ // ...
+
+ $yaml = '{ foo: FooEnum::Foo, bar: !php/enum FooEnum::Foo }';
+ $parameters = Yaml::parse($yaml, Yaml::PARSE_CONSTANT);
+ // the value of the 'foo' key is a string because it missed the `!php/enum` syntax
+ // $parameters = ['foo' => 'FooEnum::Foo', 'bar' => FooEnum::Foo];
+
+ $yaml = '{ foo: FooEnum::Foo, bar: !php/enum FooEnum::Foo->value }';
+ $parameters = Yaml::parse($yaml, Yaml::PARSE_CONSTANT);
+ // the value of the 'foo' key is a string because it missed the `!php/enum` syntax
+ // $parameters = ['foo' => 'FooEnum::Foo', 'bar' => 'foo'];
+
+You can also use ``!php/enum`` to get all the enumeration cases by only
+giving the enumeration FQCN::
+
+ enum FooEnum: string
+ {
+ case Foo = 'foo';
+ case Bar = 'bar';
+ }
+
+ // ...
+
+ $yaml = '{ bar: !php/enum FooEnum }';
+ $parameters = Yaml::parse($yaml, Yaml::PARSE_CONSTANT);
+ // $parameters = ['bar' => ['foo', 'bar']];
+
+.. versionadded:: 7.1
+
+ The support for using the enum FQCN without specifying a case
+ was introduced in Symfony 7.1.
+
Parsing and Dumping of Binary Data
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-You can dump binary data by using the ``DUMP_BASE64_BINARY_DATA`` flag::
+Non UTF-8 encoded strings are dumped as base64 encoded data::
$imageContents = file_get_contents(__DIR__.'/images/logo.png');
- $dumped = Yaml::dump(['logo' => $imageContents], 2, 4, Yaml::DUMP_BASE64_BINARY_DATA);
+ $dumped = Yaml::dump(['logo' => $imageContents]);
// logo: !!binary iVBORw0KGgoAAAANSUhEUgAAA6oAAADqCAY...
-Binary data is automatically parsed if they include the ``!!binary`` YAML tag
-(there's no need to pass any flag to the Yaml parser)::
+Binary data is automatically parsed if they include the ``!!binary`` YAML tag::
$dumped = 'logo: !!binary iVBORw0KGgoAAAANSUhEUgAAA6oAAADqCAY...';
$parsed = Yaml::parse($dumped);
@@ -390,6 +428,80 @@ you can dump them as ``~`` with the ``DUMP_NULL_AS_TILDE`` flag::
$dumped = Yaml::dump(['foo' => null], 2, 4, Yaml::DUMP_NULL_AS_TILDE);
// foo: ~
+Another valid representation of the ``null`` value is an empty string. You can
+use the ``DUMP_NULL_AS_EMPTY`` flag to dump null values as empty strings::
+
+ $dumped = Yaml::dump(['foo' => null], 2, 4, Yaml::DUMP_NULL_AS_EMPTY);
+ // foo:
+
+.. versionadded:: 7.3
+
+ The ``DUMP_NULL_AS_EMPTY`` flag was introduced in Symfony 7.3.
+
+Dumping Numeric Keys as Strings
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, digit-only array keys are dumped as integers. You can use the
+``DUMP_NUMERIC_KEY_AS_STRING`` flag if you want to dump string-only keys::
+
+ $dumped = Yaml::dump([200 => 'foo']);
+ // 200: foo
+
+ $dumped = Yaml::dump([200 => 'foo'], 2, 4, Yaml::DUMP_NUMERIC_KEY_AS_STRING);
+ // '200': foo
+
+Dumping Double Quotes on Values
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, only unsafe string values are enclosed in double quotes (for example,
+if they are reserved words or contain newlines and spaces). Use the
+``DUMP_FORCE_DOUBLE_QUOTES_ON_VALUES`` flag to add double quotes to all string values::
+
+ $dumped = Yaml::dump([
+ 'foo' => 'bar', 'some foo' => 'some bar', 'x' => 3.14, 'y' => true, 'z' => null,
+ ]);
+ // foo: bar, 'some foo': 'some bar', x: 3.14, 'y': true, z: null
+
+ $dumped = Yaml::dump([
+ 'foo' => 'bar', 'some foo' => 'some bar', 'x' => 3.14, 'y' => true, 'z' => null,
+ ], 2, 4, Yaml::DUMP_FORCE_DOUBLE_QUOTES_ON_VALUES);
+ // "foo": "bar", "some foo": "some bar", "x": 3.14, "y": true, "z": null
+
+.. versionadded:: 7.3
+
+ The ``Yaml::DUMP_FORCE_DOUBLE_QUOTES_ON_VALUES`` flag was introduced in Symfony 7.3.
+
+Dumping Collection of Maps
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When the YAML component dumps collections of maps, it uses a hyphen on a separate
+line as a delimiter:
+
+.. code-block:: yaml
+
+ planets:
+ -
+ name: Mercury
+ distance: 57910000
+ -
+ name: Jupiter
+ distance: 778500000
+
+To produce a more compact output where the delimiter is included within the map,
+use the ``Yaml::DUMP_COMPACT_NESTED_MAPPING`` flag:
+
+.. code-block:: yaml
+
+ planets:
+ - name: Mercury
+ distance: 57910000
+ - name: Jupiter
+ distance: 778500000
+
+.. versionadded:: 7.3
+
+ The ``Yaml::DUMP_COMPACT_NESTED_MAPPING`` flag was introduced in Symfony 7.3.
+
Syntax Validation
~~~~~~~~~~~~~~~~~
@@ -433,12 +545,15 @@ Then, execute the script for validating contents:
# or contents passed to STDIN
$ cat path/to/file.yaml | php lint.php
+ # you can also exclude one or more files from linting
+ $ php lint.php path/to/directory --exclude=path/to/directory/foo.yaml --exclude=path/to/directory/bar.yaml
+
The result is written to STDOUT and uses a plain text format by default.
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::
@@ -446,15 +561,6 @@ Add the ``--format`` option to get the output in JSON format:
YAML files. This may for example be useful for recognizing deprecations of
contents of YAML files during automated tests.
-Learn More
-----------
-
-.. toctree::
- :maxdepth: 1
- :glob:
-
- yaml/*
-
.. _`YAML`: https://yaml.org/
-.. _`YAML 1.2 version specification`: https://yaml.org/spec/1.2/spec.html
.. _`ISO-8601`: https://www.iso.org/iso-8601-date-and-time-format.html
+.. _`PHP enumerations`: https://www.php.net/manual/en/language.types.enumerations.php
diff --git a/configuration.rst b/configuration.rst
index 76ac0df6c3e..35bc2fb7eec 100644
--- a/configuration.rst
+++ b/configuration.rst
@@ -1,6 +1,3 @@
-.. index::
- single: Configuration
-
Configuring Symfony
===================
@@ -18,22 +15,20 @@ directory, which has this default structure:
│ ├─ bundles.php
│ ├─ routes.yaml
│ └─ services.yaml
- ├─ ...
-The ``routes.yaml`` file defines the :doc:`routing configuration `;
-the ``services.yaml`` file configures the services of the
-:doc:`service container `; the ``bundles.php`` file enables/
-disables packages in your application.
+* The ``routes.yaml`` file defines the :doc:`routing configuration `;
+* The ``services.yaml`` file configures the services of the :doc:`service container `;
+* The ``bundles.php`` file enables/disables packages in your application;
+* The ``config/packages/`` directory stores the configuration of every package
+ installed in your application.
-You'll be working mostly in the ``config/packages/`` directory. This directory
-stores the configuration of every package installed in your application.
Packages (also called "bundles" in Symfony and "plugins/modules" in other
projects) add ready-to-use features to your projects.
When using :ref:`Symfony Flex `, which is enabled by default in
Symfony applications, packages update the ``bundles.php`` file and create new
files in ``config/packages/`` automatically during their installation. For
-example, this is the default file created by the "API Platform" package:
+example, this is the default file created by the "API Platform" bundle:
.. code-block:: yaml
@@ -42,9 +37,9 @@ example, this is the default file created by the "API Platform" package:
mapping:
paths: ['%kernel.project_dir%/src/Entity']
-Splitting the configuration into lots of small files is intimidating for some
+Splitting the configuration into lots of small files might appear intimidating for some
Symfony newcomers. However, you'll get used to them quickly and you rarely need
-to change these files after package installation
+to change these files after package installation.
.. tip::
@@ -52,27 +47,39 @@ to change these files after package installation
:doc:`Symfony Configuration Reference ` or run the
``config:dump-reference`` command.
+.. _configuration-formats:
+
Configuration Formats
~~~~~~~~~~~~~~~~~~~~~
Unlike other frameworks, Symfony doesn't impose a specific format on you to
-configure your applications. Symfony lets you choose between YAML, XML and PHP
-and throughout the Symfony documentation, all configuration examples will be
+configure your applications, but lets you choose between YAML, XML and PHP.
+Throughout the Symfony documentation, all configuration examples will be
shown in these three formats.
There isn't any practical difference between formats. In fact, Symfony
-transforms and caches all of them into PHP before running the application, so
-there's not even any performance difference between them.
+transforms all of them into PHP and caches them before running the application,
+so there's not even any performance difference.
YAML is used by default when installing packages because it's concise and very
readable. These are the main advantages and disadvantages of each format:
* **YAML**: simple, clean and readable, but not all IDEs support autocompletion
- and validation for it. :doc:`Learn the YAML syntax `;
-* **XML**:autocompleted/validated by most IDEs and is parsed natively by PHP,
+ and validation for it. :doc:`Learn the YAML syntax `;
+* **XML**: autocompleted/validated by most IDEs and is parsed natively by PHP,
but sometimes it generates configuration considered too verbose. `Learn the XML syntax`_;
-* **PHP**: very powerful and it allows you to create dynamic configuration, but the
- resulting configuration is less readable than the other formats.
+* **PHP**: very powerful and it allows you to create dynamic configuration with
+ arrays or a :ref:`ConfigBuilder `.
+
+.. note::
+
+ By default Symfony loads the configuration files defined in YAML and PHP
+ formats. If you define configuration in XML format, update the
+ :method:`Symfony\\Bundle\\FrameworkBundle\\Kernel\\MicroKernelTrait::configureContainer`
+ and/or
+ :method:`Symfony\\Bundle\\FrameworkBundle\\Kernel\\MicroKernelTrait::configureRoutes`
+ methods in the ``src/Kernel.php`` file to add support for the ``.xml`` file
+ extension.
Importing Configuration Files
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -88,11 +95,15 @@ configuration files, even if they use a different format:
# config/services.yaml
imports:
- { resource: 'legacy_config.php' }
- # ignore_errors silently discards errors if the loaded file doesn't exist
- - { resource: 'my_config_file.xml', ignore_errors: true }
+
# glob expressions are also supported to load multiple files
- { resource: '/etc/myapp/*.yaml' }
+ # ignore_errors: not_found silently discards errors if the loaded file doesn't exist
+ - { resource: 'my_config_file.xml', ignore_errors: not_found }
+ # ignore_errors: true silently discards all errors (including invalid code and not found)
+ - { resource: 'my_other_config_file.xml', ignore_errors: true }
+
# ...
.. code-block:: xml
@@ -108,10 +119,13 @@ configuration files, even if they use a different format:
-
-
+
+
+
+
+
@@ -122,12 +136,17 @@ configuration files, even if they use a different format:
// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
- return static function (ContainerConfigurator $container) {
+ return static function (ContainerConfigurator $container): void {
$container->import('legacy_config.php');
- // ignore_errors (3rd parameter) silently discards errors if the loaded file doesn't exist
- $container->import('my_config_file.xml', null, true);
+
// glob expressions are also supported to load multiple files
$container->import('/etc/myapp/*.yaml');
+
+ // the third optional argument of import() is 'ignore_errors'
+ // 'ignore_errors' set to 'not_found' silently discards errors if the loaded file doesn't exist
+ $container->import('my_config_file.yaml', null, 'not_found');
+ // 'ignore_errors' set to true silently discards all errors (including invalid code and not found)
+ $container->import('my_config_file.yaml', null, true);
};
// ...
@@ -167,6 +186,9 @@ reusable configuration value. By convention, parameters are defined under the
app.some_constant: !php/const GLOBAL_CONSTANT
app.another_constant: !php/const App\Entity\BlogPost::MAX_ITEMS
+ # Enum case as parameter values
+ app.some_enum: !php/enum App\Enum\PostState::Published
+
# ...
.. code-block:: xml
@@ -204,6 +226,9 @@ reusable configuration value. By convention, parameters are defined under the
GLOBAL_CONSTANT
App\Entity\BlogPost::MAX_ITEMS
+
+
+ App\Enum\PostState::Published
@@ -215,8 +240,9 @@ reusable configuration value. By convention, parameters are defined under the
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use App\Entity\BlogPost;
+ use App\Enum\PostState;
- return static function (ContainerConfigurator $container) {
+ return static function (ContainerConfigurator $container): void {
$container->parameters()
// the parameter name is an arbitrary string (the 'app.' prefix is recommended
// to better differentiate your parameters from Symfony parameters).
@@ -233,15 +259,18 @@ reusable configuration value. By convention, parameters are defined under the
// PHP constants as parameter values
->set('app.some_constant', GLOBAL_CONSTANT)
- ->set('app.another_constant', BlogPost::MAX_ITEMS);
+ ->set('app.another_constant', BlogPost::MAX_ITEMS)
+
+ // Enum case as parameter values
+ ->set('app.some_enum', PostState::Published);
};
// ...
-.. caution::
+.. warning::
- When using XML configuration, the values between ```` tags are
- not trimmed. This means that the value of the following parameter will be
+ 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
``'\n something@example.com\n'``:
.. code-block:: xml
@@ -250,6 +279,15 @@ reusable configuration value. By convention, parameters are defined under the
something@example.com
+ If you want to trim the value of your parameter, use the ``trim`` attribute.
+ When using it, the value of the following parameter will be ``something@example.com``:
+
+ .. code-block:: xml
+
+
+ something@example.com
+
+
Once defined, you can reference this parameter value from any other
configuration file using a special syntax: wrap the parameter name in two ``%``
(e.g. ``%app.admin_email%``):
@@ -263,8 +301,6 @@ configuration file using a special syntax: wrap the parameter name in two ``%``
# any string surrounded by two % is replaced by that parameter value
email_address: '%app.admin_email%'
- # ...
-
.. code-block:: xml
@@ -287,21 +323,24 @@ configuration file using a special syntax: wrap the parameter name in two ``%``
// config/packages/some_package.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+ use function Symfony\Component\DependencyInjection\Loader\Configurator\param;
- return static function (ContainerConfigurator $container) {
+ return static function (ContainerConfigurator $container): void {
$container->extension('some_package', [
- // any string surrounded by two % is replaced by that parameter value
- 'email_address' => '%app.admin_email%',
+ // when using the param() function, you only have to pass the parameter name...
+ 'email_address' => param('app.admin_email'),
- // ...
+ // ... but if you prefer it, you can also pass the name as a string
+ // surrounded by two % (same as in YAML and XML formats) and Symfony will
+ // replace it by that parameter value
+ 'email_address' => '%app.admin_email%',
]);
};
-
.. note::
If some parameter value includes the ``%`` character, you need to escape it
- by adding another ``%`` so Symfony doesn't consider it a reference to a
+ by adding another ``%``, so Symfony doesn't consider it a reference to a
parameter name:
.. configuration-block::
@@ -325,7 +364,7 @@ configuration file using a special syntax: wrap the parameter name in two ``%``
// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
- return static function (ContainerConfigurator $container) {
+ return static function (ContainerConfigurator $container): void {
$container->parameters()
->set('url_pattern', 'http://symfony.com/?foo=%%s&bar=%%d');
};
@@ -336,14 +375,32 @@ Configuration parameters are very common in Symfony applications. Some packages
even define their own parameters (e.g. when installing the translation package,
a new ``locale`` parameter is added to the ``config/services.yaml`` file).
+.. tip::
+
+ 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 :doc:`Compiler Passes `
+ to declare some temporary parameters that won't be available later in the application.
+
+Configuration parameters are usually validation-free, but you can ensure that
+essential parameters for your application's functionality are not empty::
+
+ /** @var ContainerBuilder $container */
+ $container->parameterCannotBeEmpty('app.private_key', 'Did you forget to set a value for the "app.private_key" parameter?');
+
+If a non-empty parameter is ``null``, an empty string ``''``, or an empty array ``[]``,
+Symfony will throw an exception. This validation is **not** made at compile time
+but when attempting to retrieve the value of the parameter.
+
+.. versionadded:: 7.2
+
+ Validating non-empty parameters was introduced in Symfony 7.2.
+
.. seealso::
Later in this article you can read how to
:ref:`get configuration parameters in controllers and services `.
-.. index::
- single: Environments; Introduction
-
.. _page-creation-environments:
.. _page-creation-prod-cache-clear:
.. _configuration-environments:
@@ -351,7 +408,7 @@ a new ``locale`` parameter is added to the ``config/services.yaml`` file).
Configuration Environments
--------------------------
-You have just one application, but whether you realize it or not, you need it
+You have only one application, but whether you realize it or not, you need it
to behave differently at different times:
* While **developing**, you want to log everything and expose nice debugging tools;
@@ -363,15 +420,19 @@ The files stored in ``config/packages/`` are used by Symfony to configure the
the application behavior by changing which configuration files are loaded.
That's the idea of Symfony's **configuration environments**.
-A typical Symfony application begins with three environments: ``dev`` (for local
-development), ``prod`` (for production servers) and ``test`` (for
-:doc:`automated tests `). When running the application, Symfony loads
-the configuration files in this order (the last files can override the values
-set in the previous ones):
+A typical Symfony application begins with three environments:
-#. ``config/packages/*.yaml`` (and ``*.xml`` and ``*.php`` files too);
-#. ``config/packages//*.yaml`` (and ``*.xml`` and ``*.php`` files too);
-#. ``config/packages/services.yaml`` (and ``services.xml`` and ``services.php`` files too);
+* ``dev`` for local development,
+* ``prod`` for production servers,
+* ``test`` for :doc:`automated tests `.
+
+When running the application, Symfony loads the configuration files in this
+order (the last files can override the values set in the previous ones):
+
+#. The files in ``config/packages/*.``;
+#. the files in ``config/packages//*.``;
+#. ``config/services.``;
+#. ``config/services_.``.
Take the ``framework`` package, installed by default, as an example:
@@ -389,6 +450,90 @@ In reality, each environment differs only somewhat from others. This means that
all environments share a large base of common configuration, which is put in
files directly in the ``config/packages/`` directory.
+.. tip::
+
+ You can also define options for different environments in a single
+ configuration file using the special ``when`` keyword:
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/webpack_encore.yaml
+ webpack_encore:
+ # ...
+ output_path: '%kernel.project_dir%/public/build'
+ strict_mode: true
+ cache: false
+
+ # cache is enabled only in the "prod" environment
+ when@prod:
+ webpack_encore:
+ cache: true
+
+ # disable strict mode only in the "test" environment
+ when@test:
+ webpack_encore:
+ strict_mode: false
+
+ # YAML syntax allows to reuse contents using "anchors" (&some_name) and "aliases" (*some_name).
+ # In this example, 'test' configuration uses the exact same configuration as in 'prod'
+ when@prod: &webpack_prod
+ webpack_encore:
+ # ...
+ when@test: *webpack_prod
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/framework.php
+ use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
+ use Symfony\Config\WebpackEncoreConfig;
+
+ return static function (WebpackEncoreConfig $webpackEncore, ContainerConfigurator $container): void {
+ $webpackEncore
+ ->outputPath('%kernel.project_dir%/public/build')
+ ->strictMode(true)
+ ->cache(false)
+ ;
+
+ // cache is enabled only in the "prod" environment
+ if ('prod' === $container->env()) {
+ $webpackEncore->cache(true);
+ }
+
+ // disable strict mode only in the "test" environment
+ if ('test' === $container->env()) {
+ $webpackEncore->strictMode(false);
+ }
+ };
+
.. seealso::
See the ``configureContainer()`` method of
@@ -447,66 +592,84 @@ going to production:
use `symbolic links`_ between ``config/packages//``
directories to reuse the same configuration.
+Instead of creating new environments, you can use environment variables as
+explained in the following section. This way you can use the same application
+and environment (e.g. ``prod``) but change its behavior thanks to the
+configuration based on environment variables (e.g. to run the application in
+different scenarios: staging, quality assurance, client review, etc.)
+
.. _config-env-vars:
Configuration Based on Environment Variables
--------------------------------------------
-Using `environment variables`_ (or "env vars" for short) is a common practice to
-configure options that depend on where the application is run (e.g. the database
-credentials are usually different in production versus your local machine). If
-the values are sensitive, you can even :doc:`encrypt them as secrets `.
+Using `environment variables`_ (or "env vars" for short) is a common practice to:
-You can reference environment variables using the special syntax
-``%env(ENV_VAR_NAME)%``. The values of these options are resolved at runtime
-(only once per request, to not impact performance).
+* Configure options that depend on where the application is run (e.g. the database
+ credentials are usually different in production versus your local machine);
+* Configure options that can change dynamically in a production environment (e.g.
+ to update the value of an expired API key without having to redeploy the entire
+ application).
-This example shows how you could configure the database connection using an env var:
+In other cases, it's recommended to keep using :ref:`configuration parameters `.
+
+Use the special syntax ``%env(ENV_VAR_NAME)%`` to reference environment variables.
+The values of these options are resolved at runtime (only once per request, to
+not impact performance) so you can change the application behavior without having
+to clear the cache.
+
+This example shows how you could configure the application secret using an env var:
.. configuration-block::
.. code-block:: yaml
- # config/packages/doctrine.yaml
- doctrine:
- dbal:
- # by convention the env var names are always uppercase
- url: '%env(resolve:DATABASE_URL)%'
+ # config/packages/framework.yaml
+ framework:
+ # by convention the env var names are always uppercase
+ secret: '%env(APP_SECRET)%'
# ...
.. code-block:: xml
-
+
+ http://symfony.com/schema/dic/symfony
+ https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
-
-
-
-
+
+
.. code-block:: php
- // config/packages/doctrine.php
+ // config/packages/framework.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
- return static function (ContainerConfigurator $container) {
- $container->extension('doctrine', [
- 'dbal' => [
- // by convention the env var names are always uppercase
- 'url' => '%env(resolve:DATABASE_URL)%',
- ]
+ return static function (ContainerConfigurator $container): void {
+ $container->extension('framework', [
+ // by convention the env var names are always uppercase
+ 'secret' => '%env(APP_SECRET)%',
]);
};
+.. note::
+
+ Your env vars can also be accessed via the PHP super globals ``$_ENV`` and
+ ``$_SERVER`` (both are equivalent)::
+
+ $databaseUrl = $_ENV['DATABASE_URL']; // mysql://db_user:db_password@127.0.0.1:3306/db_name
+ $env = $_SERVER['APP_ENV']; // prod
+
+ However, in Symfony applications there's no need to use this, because the
+ configuration system provides a better way of working with env vars.
+
.. seealso::
The values of env vars can only be strings, but Symfony includes some
@@ -519,12 +682,70 @@ To define the value of an env var, you have several options:
* :ref:`Encrypt the value as a secret `;
* Set the value as a real environment variable in your shell or your web server.
+If your application tries to use an env var that hasn't been defined, you'll see
+an exception. You can prevent that by defining a default value for the env var.
+To do so, define a parameter with the same name as the env var using this syntax:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/framework.yaml
+ parameters:
+ # if the SECRET env var value is not defined anywhere, Symfony uses this value
+ env(SECRET): 'some_secret'
+
+ # ...
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+ some_secret
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/framework.php
+ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+ use Symfony\Component\DependencyInjection\ContainerBuilder;
+ use Symfony\Config\FrameworkConfig;
+
+ return static function (ContainerBuilder $container, FrameworkConfig $framework) {
+ // if the SECRET env var value is not defined anywhere, Symfony uses this value
+ $container->setParameter('env(SECRET)', 'some_secret');
+
+ // ...
+ };
+
.. tip::
- Some hosts - like SymfonyCloud - offer easy `utilities to manage env vars`_
+ Some hosts - like Platform.sh - offer easy `utilities to manage env vars`_
in production.
-.. caution::
+.. note::
+
+ Some configuration features are not compatible with env vars. For example,
+ defining some container parameters conditionally based on the existence of
+ another configuration option. When using an env var, the configuration option
+ always exists, because its value will be ``null`` when the related env var
+ is not defined.
+
+.. danger::
Beware that dumping the contents of the ``$_SERVER`` and ``$_ENV`` variables
or outputting the ``phpinfo()`` contents will display the values of the
@@ -565,6 +786,11 @@ In addition to your own env vars, this ``.env`` file also contains the env vars
defined by the third-party packages installed in your application (they are
added automatically by :ref:`Symfony Flex ` when installing packages).
+.. tip::
+
+ Since the ``.env`` file is read and parsed on every request, you don't need to
+ clear the Symfony cache or restart the PHP container if you're using Docker.
+
.env File Syntax
................
@@ -583,7 +809,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``.
@@ -604,7 +830,7 @@ Embed commands via ``$()`` (not supported on Windows):
START_TIME=$(date)
-.. caution::
+.. warning::
Using ``$()`` might not work depending on your shell.
@@ -646,7 +872,10 @@ the right situation:
but the overrides only apply to one environment.
*Real* environment variables always win over env vars created by any of the
-``.env`` files.
+``.env`` files. Note that this behavior depends on the
+`variables_order `_
+configuration, which must contain an ``E`` to expose the ``$_ENV`` superglobal.
+This is the default configuration in PHP.
The ``.env`` and ``.env.`` files should be committed to the
repository because they are the same for all developers and machines. However,
@@ -654,11 +883,24 @@ the env files ending in ``.local`` (``.env.local`` and ``.env..loca
**should not be committed** because only you will use them. In fact, the
``.gitignore`` file that comes with Symfony prevents them from being committed.
-.. caution::
+Overriding Environment Variables Defined By The System
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Applications created before November 2018 had a slightly different system,
- involving a ``.env.dist`` file. For information about upgrading, see:
- :doc:`configuration/dot-env-changes`.
+If you need to override an environment variable defined by the system, use the
+``overrideExistingVars`` parameter defined by the
+:method:`Symfony\\Component\\Dotenv\\Dotenv::loadEnv`,
+:method:`Symfony\\Component\\Dotenv\\Dotenv::bootEnv`, and
+:method:`Symfony\\Component\\Dotenv\\Dotenv::populate` methods::
+
+ use Symfony\Component\Dotenv\Dotenv;
+
+ $dotenv = new Dotenv();
+ $dotenv->loadEnv(__DIR__.'/.env', overrideExistingVars: true);
+
+ // ...
+
+This will override environment variables defined by the system but it **won't**
+override environment variables defined in ``.env`` files.
.. _configuration-env-var-in-prod:
@@ -666,25 +908,87 @@ Configuring Environment Variables in Production
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In production, the ``.env`` files are also parsed and loaded on each request. So
-the easiest way to define env vars is by deploying a ``.env.local`` file to your
+the easiest way to define env vars is by creating a ``.env.local`` file on your
production server(s) with your production values.
-To improve performance, you can optionally run the ``dump-env`` command (available
-in :ref:`Symfony Flex ` 1.2 or later):
+To improve performance, you can optionally run the ``dump-env`` Composer command:
.. code-block:: terminal
# parses ALL .env files and dumps their final values to .env.local.php
$ composer dump-env prod
+.. sidebar:: Dumping Environment Variables without Composer
+
+ If you don't have Composer installed in production, you can use the
+ ``dotenv:dump`` command instead (available in :ref:`Symfony Flex `
+ 1.2 or later). The command is not registered by default, so you must register
+ first in your services:
+
+ .. code-block:: yaml
+
+ # config/services.yaml
+ services:
+ Symfony\Component\Dotenv\Command\DotenvDumpCommand: ~
+
+ Then, run the command:
+
+ .. code-block:: terminal
+
+ # parses ALL .env files and dumps their final values to .env.local.php
+ $ APP_ENV=prod APP_DEBUG=0 php bin/console dotenv:dump
+
After running this command, Symfony will load the ``.env.local.php`` file to
get the environment variables and will not spend time parsing the ``.env`` files.
.. tip::
- Update your deployment tools/workflow to run the ``dump-env`` command after
+ Update your deployment tools/workflow to run the ``dotenv:dump`` command after
each deploy to improve the application performance.
+Storing Environment Variables In Other Files
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, the environment variables are stored in the ``.env`` file located
+at the root of your project. However, you can store them in other files in
+multiple ways.
+
+If you use the :doc:`Runtime component `, the dotenv
+path is part of the options you can set in your ``composer.json`` file:
+
+.. code-block:: json
+
+ {
+ // ...
+ "extra": {
+ // ...
+ "runtime": {
+ "dotenv_path": "my/custom/path/to/.env"
+ }
+ }
+ }
+
+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;
+
+ (new Dotenv())->bootEnv(dirname(__DIR__).'my/custom/path/to/.env');
+
+Symfony will then look for the environment variables in that file, but also in
+the local and environment-specific files (e.g. ``.*.local`` and
+``.*..local``). Read
+: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.
+
.. _configuration-secrets:
Encrypting Environment Variables (Secrets)
@@ -697,20 +1001,54 @@ you can encrypt the value using the :doc:`secrets management system `,
+ the autoconfiguration feature will enable and tag this service automatically.
+ Otherwise, you need to register and :doc:`tag your service `
+ with the ``container.env_var_loader`` tag.
+
+Let's say you have a JSON file named ``env.json`` containing your environment
+variables:
+
+.. code-block:: json
+
+ {
+ "vars": {
+ "APP_ENV": "prod",
+ "APP_DEBUG": false
+ }
+ }
+
+You can define a class like the following ``JsonEnvVarLoader`` to populate the
+environment variables from the file::
+
+ namespace App\DependencyInjection;
+
+ use Symfony\Component\DependencyInjection\EnvVarLoaderInterface;
+
+ final class JsonEnvVarLoader implements EnvVarLoaderInterface
+ {
+ private const ENV_VARS_FILE = 'env.json';
+
+ public function loadEnvVars(): array
+ {
+ $fileName = __DIR__.\DIRECTORY_SEPARATOR.self::ENV_VARS_FILE;
+ if (!is_file($fileName)) {
+ // throw an exception or just ignore this loader, depending on your needs
+ }
+
+ $content = json_decode(file_get_contents($fileName), true);
+
+ return $content['vars'];
+ }
+ }
+
+That's it! Now the application will look for a ``env.json`` file in the
+current directory to populate environment variables (in addition to the
+already existing ``.env`` files).
+
+.. tip::
+
+ If you want an env var to have a value on a certain environment but to fallback
+ on loaders on another environment, assign an empty value to the env var for
+ the environment you want to use loaders:
+
+ .. code-block:: bash
+
+ # .env (or .env.local)
+ APP_ENV=prod
+
+ # .env.prod (or .env.prod.local) - this will fallback on the loaders you defined
+ APP_ENV=
+
.. _configuration-accessing-parameters:
Accessing Configuration Parameters
@@ -739,12 +1145,13 @@ use the ``getParameter()`` helper::
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+ use Symfony\Component\HttpFoundation\Response;
class UserController extends AbstractController
{
// ...
- public function index()
+ public function index(): Response
{
$projectDir = $this->getParameter('kernel.project_dir');
$adminEmail = $this->getParameter('app.admin_email');
@@ -798,7 +1205,7 @@ doesn't work for parameters:
use App\Service\MessageGenerator;
- return static function (ContainerConfigurator $container) {
+ return static function (ContainerConfigurator $container): void {
$container->parameters()
->set('app.contents_dir', '...');
@@ -853,19 +1260,14 @@ whenever a service/controller defines a ``$projectDir`` argument, use this:
// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
- use App\Controller\LuckyController;
- use Psr\Log\LoggerInterface;
- use Symfony\Component\DependencyInjection\Reference;
-
- return static function (ContainerConfigurator $container) {
+ return static function (ContainerConfigurator $container): void {
$container->services()
- ->set(LuckyController::class)
- ->public()
- ->args([
- // pass this value to any $projectDir argument for any service
- // that's created in this file (including controller arguments)
- '$projectDir' => '%kernel.project_dir%',
- ]);
+ ->defaults()
+ // pass this value to any $projectDir argument for any service
+ // that's created in this file (including controller arguments)
+ ->bind('$projectDir', '%kernel.project_dir%');
+
+ // ...
};
.. seealso::
@@ -887,14 +1289,12 @@ parameters at once by type-hinting any of its constructor arguments with the
class MessageGenerator
{
- private $params;
-
- public function __construct(ContainerBagInterface $params)
- {
- $this->params = $params;
+ public function __construct(
+ private ContainerBagInterface $params,
+ ) {
}
- public function someMethod()
+ public function someMethod(): void
{
// get any container parameter from $this->params, which stores all of them
$sender = $this->params->get('mailer_sender');
@@ -902,6 +1302,52 @@ parameters at once by type-hinting any of its constructor arguments with the
}
}
+.. _config-config-builder:
+
+Using PHP ConfigBuilders
+------------------------
+
+Writing PHP config is sometimes difficult because you end up with large nested
+arrays and you have no autocompletion help from your favorite IDE. A way to
+address this is to use "ConfigBuilders". They are objects that will help you
+build these arrays.
+
+Symfony generates the ConfigBuilder classes automatically in the
+:ref:`kernel build directory ` for all the
+bundles installed in your application. By convention they all live in the
+namespace ``Symfony\Config``::
+
+ // config/packages/security.php
+ use Symfony\Config\SecurityConfig;
+
+ return static function (SecurityConfig $security): void {
+ $security->firewall('main')
+ ->pattern('^/*')
+ ->lazy(true)
+ ->security(false);
+
+ $security
+ ->roleHierarchy('ROLE_ADMIN', ['ROLE_USER'])
+ ->roleHierarchy('ROLE_SUPER_ADMIN', ['ROLE_ADMIN', 'ROLE_ALLOWED_TO_SWITCH'])
+ ->accessControl()
+ ->path('^/user')
+ ->roles('ROLE_USER');
+
+ $security->accessControl(['path' => '^/admin', 'roles' => 'ROLE_ADMIN']);
+ };
+
+.. note::
+
+ Only root classes in the namespace ``Symfony\Config`` are ConfigBuilders.
+ Nested configs (e.g. ``\Symfony\Config\Framework\CacheConfig``) are regular
+ PHP objects which aren't autowired when using them as an argument type.
+
+.. note::
+
+ In order to get ConfigBuilders autocompletion in your IDE/editor, make sure
+ to not exclude the directory where these classes are generated (by default,
+ in ``var/cache/dev/Symfony/Config/``).
+
Keep Going!
-----------
@@ -926,4 +1372,4 @@ And all the other topics related to configuration:
.. _`Learn the XML syntax`: https://en.wikipedia.org/wiki/XML
.. _`environment variables`: https://en.wikipedia.org/wiki/Environment_variable
.. _`symbolic links`: https://en.wikipedia.org/wiki/Symbolic_link
-.. _`utilities to manage env vars`: https://symfony.com/doc/master/cloud/cookbooks/env.html
+.. _`utilities to manage env vars`: https://symfony.com/doc/current/cloud/env.html
diff --git a/configuration/dot-env-changes.rst b/configuration/dot-env-changes.rst
deleted file mode 100644
index 9c9c9df2e81..00000000000
--- a/configuration/dot-env-changes.rst
+++ /dev/null
@@ -1,93 +0,0 @@
-Nov 2018 Changes to .env & How to Update
-========================================
-
-In November 2018, several changes were made to the core Symfony *recipes* related
-to the ``.env`` file. These changes make working with environment variables easier
-and more consistent - especially when writing functional tests.
-
-If your app was started before November 2018, your app **does not require any changes
-to keep working**. However, if/when you are ready to take advantage of these improvements,
-you will need to make a few small updates.
-
-What Changed Exactly?
----------------------
-
-But first, what changed? On a high-level, not much. Here's a summary of the most
-important changes:
-
-* A) The ``.env.dist`` file no longer exists. Its contents should be moved to your
- ``.env`` file (see the next point).
-
-* B) The ``.env`` file **is** now committed to your repository. It was previously ignored
- via the ``.gitignore`` file (the updated recipe does not ignore this file). Because
- this file is committed, it should contain non-sensitive, default values. Basically,
- the ``.env.dist`` file was moved to ``.env``.
-
-* C) A ``.env.local`` file can now be created to *override* values in ``.env`` for
- your machine. This file is ignored in the new ``.gitignore``.
-
-* D) When testing, your ``.env`` file is now read, making it consistent with all
- other environments. You can also create a ``.env.test`` file for test-environment
- overrides.
-
-* E) `One further change to the recipe in January 2019`_ means that your ``.env``
- files are *always* loaded, even if you set an ``APP_ENV=prod`` environment
- variable. The purpose is for the ``.env`` files to define default values that
- you can override if you want to with real environment values.
-
-There are a few other improvements, but these are the most important. To take advantage
-of these, you *will* need to modify a few files in your existing app.
-
-Updating My Application
------------------------
-
-If you created your application after November 15th 2018, you don't need to make
-any changes! Otherwise, here is the list of changes you'll need to make - these
-changes can be made to any Symfony 3.4 or higher app:
-
-#. Update your ``public/index.php`` file to add the code of the `public/index.php`_
- file provided by Symfony. If you've customized this file, make sure to keep
- those changes (but add the rest of the changes made by Symfony).
-
-#. Update your ``bin/console`` file to add the code of the `bin/console`_ file
- provided by Symfony.
-
-#. Update ``.gitignore``:
-
- .. code-block:: diff
-
- # .gitignore
- # ...
-
- ###> symfony/framework-bundle ###
- - /.env
- + /.env.local
- + /.env.local.php
- + /.env.*.local
-
- # ...
-
-#. Rename ``.env`` to ``.env.local`` and ``.env.dist`` to ``.env``:
-
- .. code-block:: terminal
-
- # Unix
- $ mv .env .env.local
- $ git mv .env.dist .env
-
- # Windows
- C:\> move .env .env.local
- C:\> git mv .env.dist .env
-
- You can also update the `comment on the top of .env`_ to reflect the new changes.
-
-#. If you're using PHPUnit, you will also need to `create a new .env.test`_ file
- and update your `phpunit.xml.dist file`_ so it loads the ``tests/bootstrap.php``
- file.
-
-.. _`public/index.php`: https://github.com/symfony/recipes/blob/master/symfony/framework-bundle/5.1/public/index.php
-.. _`bin/console`: https://github.com/symfony/recipes/blob/master/symfony/console/5.1/bin/console
-.. _`comment on the top of .env`: https://github.com/symfony/recipes/blob/master/symfony/flex/1.0/.env
-.. _`create a new .env.test`: https://github.com/symfony/recipes/blob/master/symfony/phpunit-bridge/3.3/.env.test
-.. _`phpunit.xml.dist file`: https://github.com/symfony/recipes/blob/master/symfony/phpunit-bridge/3.3/phpunit.xml.dist
-.. _`One further change to the recipe in January 2019`: https://github.com/symfony/recipes/pull/501
diff --git a/configuration/env_var_processors.rst b/configuration/env_var_processors.rst
index b9782d270cd..2e82104db66 100644
--- a/configuration/env_var_processors.rst
+++ b/configuration/env_var_processors.rst
@@ -1,6 +1,3 @@
-.. index::
- single: Environment Variable Processors; env vars
-
.. _env-var-processors:
Environment Variable Processors
@@ -44,11 +41,17 @@ processor to turn the value of the ``HTTP_PORT`` env var into an integer:
.. code-block:: php
// config/packages/framework.php
- $container->loadFromExtension('framework', [
- 'router' => [
- 'http_port' => '%env(int:HTTP_PORT)%',
- ],
- ]);
+ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+ use Symfony\Config\FrameworkConfig;
+
+ return static function (FrameworkConfig $framework): void {
+ $framework->router()
+ ->httpPort('%env(int:HTTP_PORT)%')
+ // or
+ ->httpPort(env('HTTP_PORT')->int())
+ ;
+ };
Built-In Environment Variable Processors
----------------------------------------
@@ -90,14 +93,20 @@ Symfony provides the following env var processors:
.. code-block:: php
// config/packages/framework.php
- $container->setParameter('env(SECRET)', 'some_secret');
- $container->loadFromExtension('framework', [
- 'secret' => '%env(string:SECRET)%',
- ]);
+ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+ use Symfony\Component\DependencyInjection\ContainerBuilder;
+ use Symfony\Config\FrameworkConfig;
+
+ return static function (ContainerBuilder $container, FrameworkConfig $framework): void {
+ $container->setParameter('env(SECRET)', 'some_secret');
+ $framework->secret(env('SECRET')->string());
+ };
``env(bool:FOO)``
- Casts ``FOO`` to a bool (``true`` values are ``'true'``, ``'on'``, ``'yes'``
- and all numbers except ``0`` and ``0.0``; everything else is ``false``):
+ Casts ``FOO`` to a bool (``true`` values are ``'true'``, ``'on'``, ``'yes'``,
+ all numbers except ``0`` and ``0.0`` and all numeric strings except ``'0'``
+ and ``'0.0'``; everything else is ``false``):
.. configuration-block::
@@ -131,10 +140,50 @@ Symfony provides the following env var processors:
.. code-block:: php
// config/packages/framework.php
- $container->setParameter('env(HTTP_METHOD_OVERRIDE)', 'true');
- $container->loadFromExtension('framework', [
- 'http_method_override' => '%env(bool:HTTP_METHOD_OVERRIDE)%',
- ]);
+ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+ use Symfony\Component\DependencyInjection\ContainerBuilder;
+ use Symfony\Config\FrameworkConfig;
+
+ return static function (ContainerBuilder $container, FrameworkConfig $framework): void {
+ $container->setParameter('env(HTTP_METHOD_OVERRIDE)', 'true');
+ $framework->httpMethodOverride(env('HTTP_METHOD_OVERRIDE')->bool());
+ };
+
+``env(not:FOO)``
+ Casts ``FOO`` to a bool (just as ``env(bool:...)`` does) except it returns the inverted value
+ (falsy values are returned as ``true``, truthy values are returned as ``false``):
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/services.yaml
+ parameters:
+ safe_for_production: '%env(not:APP_DEBUG)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ %env(not:APP_DEBUG)%
+
+
+
+
+ .. code-block:: php
+
+ // config/services.php
+ $container->setParameter('safe_for_production', '%env(not:APP_DEBUG)%');
``env(int:FOO)``
Casts ``FOO`` to an int.
@@ -180,15 +229,15 @@ Symfony provides the following env var processors:
.. code-block:: php
// config/packages/security.php
- $container->setParameter('env(HEALTH_CHECK_METHOD)', 'Symfony\Component\HttpFoundation\Request::METHOD_HEAD');
- $container->loadFromExtension('security', [
- 'access_control' => [
- [
- 'path' => '^/health-check$',
- 'methods' => '%env(const:HEALTH_CHECK_METHOD)%',
- ],
- ],
- ]);
+ use Symfony\Component\DependencyInjection\ContainerBuilder;
+ use Symfony\Config\SecurityConfig;
+
+ return static function (ContainerBuilder $container, SecurityConfig $security): void {
+ $container->setParameter('env(HEALTH_CHECK_METHOD)', 'Symfony\Component\HttpFoundation\Request::METHOD_HEAD');
+ $security->accessControl()
+ ->path('^/health-check$')
+ ->methods([env('HEALTH_CHECK_METHOD')->const()]);
+ };
``env(base64:FOO)``
Decodes the content of ``FOO``, which is a base64 encoded string.
@@ -203,9 +252,8 @@ Symfony provides the following env var processors:
# config/packages/framework.yaml
parameters:
- env(TRUSTED_HOSTS): '["10.0.0.1", "10.0.0.2"]'
- framework:
- trusted_hosts: '%env(json:TRUSTED_HOSTS)%'
+ env(ALLOWED_LANGUAGES): '["en","de","es"]'
+ app_allowed_languages: '%env(json:ALLOWED_LANGUAGES)%'
.. code-block:: xml
@@ -220,19 +268,23 @@ Symfony provides the following env var processors:
https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
- ["10.0.0.1", "10.0.0.2"]
+ ["en","de","es"]
+ %env(json:ALLOWED_LANGUAGES)%
-
-
.. code-block:: php
// config/packages/framework.php
- $container->setParameter('env(TRUSTED_HOSTS)', '["10.0.0.1", "10.0.0.2"]');
- $container->loadFromExtension('framework', [
- 'trusted_hosts' => '%env(json:TRUSTED_HOSTS)%',
- ]);
+ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+ use Symfony\Component\DependencyInjection\ContainerBuilder;
+ use Symfony\Config\FrameworkConfig;
+
+ return static function (ContainerBuilder $container): void {
+ $container->setParameter('env(ALLOWED_LANGUAGES)', '["en","de","es"]');
+ $container->setParameter('app_allowed_languages', '%env(json:ALLOWED_LANGUAGES)%');
+ };
``env(resolve:FOO)``
If the content of ``FOO`` includes container parameters (with the syntax
@@ -244,8 +296,7 @@ Symfony provides the following env var processors:
# config/packages/sentry.yaml
parameters:
- env(HOST): '10.0.0.1'
- sentry_host: '%env(HOST)%'
+ sentry_host: '10.0.0.1'
env(SENTRY_DSN): 'http://%sentry_host%/project'
sentry:
dsn: '%env(resolve:SENTRY_DSN)%'
@@ -260,8 +311,7 @@ Symfony provides the following env var processors:
https://symfony.com/schema/dic/services/services-1.0.xsd">
- 10.0.0.1
- %env(HOST)%
+ 10.0.0.1
http://%sentry_host%/project
@@ -271,8 +321,7 @@ Symfony provides the following env var processors:
.. code-block:: php
// config/packages/sentry.php
- $container->setParameter('env(HOST)', '10.0.0.1');
- $container->setParameter('sentry_host', '%env(HOST)%');
+ $container->setParameter('sentry_host', '10.0.0.1');
$container->setParameter('env(SENTRY_DSN)', 'http://%sentry_host%/project');
$container->loadFromExtension('sentry', [
'dsn' => '%env(resolve:SENTRY_DSN)%',
@@ -281,12 +330,94 @@ Symfony provides the following env var processors:
``env(csv:FOO)``
Decodes the content of ``FOO``, which is a CSV-encoded string:
- .. code-block:: yaml
+ .. configuration-block::
- parameters:
- env(TRUSTED_HOSTS): "10.0.0.1, 10.0.0.2"
- framework:
- trusted_hosts: '%env(csv:TRUSTED_HOSTS)%'
+ .. code-block:: yaml
+
+ # config/packages/framework.yaml
+ parameters:
+ env(ALLOWED_LANGUAGES): "en,de,es"
+ app_allowed_languages: '%env(csv:ALLOWED_LANGUAGES)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ en,de,es
+ %env(csv:ALLOWED_LANGUAGES)%
+
+
+
+ .. code-block:: php
+
+ // config/packages/framework.php
+ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+ use Symfony\Component\DependencyInjection\ContainerBuilder;
+ use Symfony\Config\FrameworkConfig;
+
+ return static function (ContainerBuilder $container): void {
+ $container->setParameter('env(ALLOWED_LANGUAGES)', 'en,de,es');
+ $container->setParameter('app_allowed_languages', '%env(csv:ALLOWED_LANGUAGES)%');
+ };
+
+``env(shuffle:FOO)``
+ Randomly shuffles values of the ``FOO`` env var, which must be an array.
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/framework.yaml
+ parameters:
+ env(REDIS_NODES): "127.0.0.1:6380,127.0.0.1:6381"
+ services:
+ RedisCluster:
+ class: RedisCluster
+ arguments: [null, "%env(shuffle:csv:REDIS_NODES)%"]
+
+ .. code-block:: xml
+
+
+
+
+
+
+ redis://127.0.0.1:6380,redis://127.0.0.1:6381
+
+
+
+
+ null
+ %env(shuffle:csv:REDIS_NODES)%
+
+
+
+
+ .. code-block:: php
+
+ // config/services.php
+ use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
+
+ return static function (ContainerConfigurator $containerConfigurator): void {
+ $container = $containerConfigurator->services()
+ ->set(\RedisCluster::class, \RedisCluster::class)->args([null, '%env(shuffle:csv:REDIS_NODES)%']);
+ };
``env(file:FOO)``
Returns the contents of a file whose path is the value of the ``FOO`` env var:
@@ -297,7 +428,7 @@ Symfony provides the following env var processors:
# config/packages/framework.yaml
parameters:
- env(AUTH_FILE): '../config/auth.json'
+ env(AUTH_FILE): '%kernel.project_dir%/config/auth.json'
google:
auth: '%env(file:AUTH_FILE)%'
@@ -338,7 +469,7 @@ Symfony provides the following env var processors:
# config/packages/framework.yaml
parameters:
- env(PHP_FILE): '../config/.runtime-evaluated.php'
+ env(PHP_FILE): '%kernel.project_dir%/config/.runtime-evaluated.php'
app:
auth: '%env(require:PHP_FILE)%'
@@ -366,7 +497,7 @@ Symfony provides the following env var processors:
// config/packages/framework.php
$container->setParameter('env(PHP_FILE)', '../config/.runtime-evaluated.php');
$container->loadFromExtension('app', [
- 'auth' => '%env(require:AUTH_FILE)%',
+ 'auth' => '%env(require:PHP_FILE)%',
]);
``env(trim:FOO)``
@@ -380,7 +511,7 @@ Symfony provides the following env var processors:
# config/packages/framework.yaml
parameters:
- env(AUTH_FILE): '../config/auth.json'
+ env(AUTH_FILE): '%kernel.project_dir%/config/auth.json'
google:
auth: '%env(trim:file:AUTH_FILE)%'
@@ -489,8 +620,8 @@ Symfony provides the following env var processors:
$container->setParameter('private_key', '%env(default:raw_key:file:PRIVATE_KEY)%');
$container->setParameter('raw_key', '%env(PRIVATE_KEY)%');
- When the fallback parameter is omitted (e.g. ``env(default::API_KEY)``), the
- value returned is ``null``.
+ When the fallback parameter is omitted (e.g. ``env(default::API_KEY)``), then the
+ returned value is ``null``.
``env(url:FOO)``
Parses an absolute URL and returns its components as an associative array.
@@ -509,9 +640,9 @@ Symfony provides the following env var processors:
clients:
default:
hosts:
- - { host: '%env(key:host:url:MONGODB_URL)%', port: '%env(key:port:url:MONGODB_URL)%' }
- username: '%env(key:user:url:MONGODB_URL)%'
- password: '%env(key:pass:url:MONGODB_URL)%'
+ - { host: '%env(string:key:host:url:MONGODB_URL)%', port: '%env(int:key:port:url:MONGODB_URL)%' }
+ username: '%env(string:key:user:url:MONGODB_URL)%'
+ password: '%env(string:key:pass:url:MONGODB_URL)%'
connections:
default:
database_name: '%env(key:path:url:MONGODB_URL)%'
@@ -526,8 +657,8 @@ Symfony provides the following env var processors:
https://symfony.com/schema/dic/services/services-1.0.xsd">
-
-
+
+
@@ -541,12 +672,12 @@ Symfony provides the following env var processors:
'default' => [
'hosts' => [
[
- 'host' => '%env(key:host:url:MONGODB_URL)%',
- 'port' => '%env(key:port:url:MONGODB_URL)%',
+ 'host' => '%env(string:key:host:url:MONGODB_URL)%',
+ 'port' => '%env(int:key:port:url:MONGODB_URL)%',
],
],
- 'username' => '%env(key:user:url:MONGODB_URL)%',
- 'password' => '%env(key:pass:url:MONGODB_URL)%',
+ 'username' => '%env(string:key:user:url:MONGODB_URL)%',
+ 'password' => '%env(string:key:pass:url:MONGODB_URL)%',
],
],
'connections' => [
@@ -556,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.
@@ -607,18 +738,187 @@ Symfony provides the following env var processors:
],
]);
+``env(enum:FooEnum:BAR)``
+ Tries to convert an environment variable to an actual ``\BackedEnum`` value.
+ This processor takes the fully qualified name of the ``\BackedEnum`` as an argument::
+
+ // App\Enum\Suit.php
+ enum Suit: string
+ {
+ case Clubs = 'clubs';
+ case Spades = 'spades';
+ case Diamonds = 'diamonds';
+ case Hearts = 'hearts';
+ }
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/services.yaml
+ parameters:
+ suit: '%env(enum:App\Enum\Suit:CARD_SUIT)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ %env(enum:App\Enum\Suit:CARD_SUIT)%
+
+
+
+ .. code-block:: php
+
+ // config/services.php
+ $container->setParameter('suit', '%env(enum:App\Enum\Suit:CARD_SUIT)%');
+
+ The value stored in the ``CARD_SUIT`` env var would be a string (e.g. ``'spades'``)
+ but the application will use the enum value (e.g. ``Suit::Spades``).
+
+``env(defined:NO_FOO)``
+ Evaluates to ``true`` if the env var exists and its value is not ``''``
+ (an empty string) or ``null``; it returns ``false`` otherwise.
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/services.yaml
+ parameters:
+ typed_env: '%env(defined:FOO)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/services.php
+ $container->setParameter('typed_env', '%env(defined:FOO)%');
+
+.. _urlencode_environment_variable_processor:
+
+``env(urlencode:FOO)``
+ Encodes the content of the ``FOO`` env var using the :phpfunction:`urlencode`
+ PHP function. This is especially useful when ``FOO`` value is not compatible
+ with DSN syntax.
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/framework.yaml
+ parameters:
+ env(DATABASE_URL): 'mysql://db_user:foo@b$r@127.0.0.1:3306/db_name'
+ encoded_database_url: '%env(urlencode:DATABASE_URL)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ mysql://db_user:foo@b$r@127.0.0.1:3306/db_name
+ %env(urlencode:DATABASE_URL)%
+
+
+
+ .. code-block:: php
+
+ // config/packages/framework.php
+ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+ use Symfony\Component\DependencyInjection\ContainerBuilder;
+ use Symfony\Config\FrameworkConfig;
+
+ return static function (ContainerBuilder $container): void {
+ $container->setParameter('env(DATABASE_URL)', 'mysql://db_user:foo@b$r@127.0.0.1:3306/db_name');
+ $container->setParameter('encoded_database_url', '%env(urlencode:DATABASE_URL)%');
+ };
+
+ .. versionadded:: 7.1
+
+ The ``env(urlencode:...)`` env var processor was introduced in Symfony 7.1.
+
It is also possible to combine any number of processors:
-.. code-block:: yaml
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/framework.yaml
+ parameters:
+ env(AUTH_FILE): "%kernel.project_dir%/config/auth.json"
+ google:
+ # 1. gets the value of the AUTH_FILE env var
+ # 2. replaces the values of any config param to get the config path
+ # 3. gets the content of the file stored in that path
+ # 4. JSON-decodes the content of the file and returns it
+ auth: '%env(json:file:resolve:AUTH_FILE)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ %kernel.project_dir%/config/auth.json
+
+
+
+
+
+
+
+
- parameters:
- env(AUTH_FILE): "%kernel.project_dir%/config/auth.json"
- google:
- # 1. gets the value of the AUTH_FILE env var
- # 2. replaces the values of any config param to get the config path
- # 3. gets the content of the file stored in that path
- # 4. JSON-decodes the content of the file and returns it
- auth: '%env(json:file:resolve:AUTH_FILE)%'
+ .. code-block:: php
+
+ // config/packages/framework.php
+ $container->setParameter('env(AUTH_FILE)', '%kernel.project_dir%/config/auth.json');
+ // 1. gets the value of the AUTH_FILE env var
+ // 2. replaces the values of any config param to get the config path
+ // 3. gets the content of the file stored in that path
+ // 4. JSON-decodes the content of the file and returns it
+ $container->loadFromExtension('google', [
+ 'auth' => '%env(json:file:resolve:AUTH_FILE)%',
+ ]);
Custom Environment Variable Processors
--------------------------------------
@@ -631,14 +931,14 @@ create a class that implements
class LowercasingEnvVarProcessor implements EnvVarProcessorInterface
{
- public function getEnv($prefix, $name, \Closure $getEnv)
+ public function getEnv(string $prefix, string $name, \Closure $getEnv): string
{
$env = $getEnv($name);
return strtolower($env);
}
- public static function getProvidedTypes()
+ public static function getProvidedTypes(): array
{
return [
'lowercase' => 'string',
diff --git a/configuration/front_controllers_and_kernel.rst b/configuration/front_controllers_and_kernel.rst
index ba702cbdc49..b55f66afc33 100644
--- a/configuration/front_controllers_and_kernel.rst
+++ b/configuration/front_controllers_and_kernel.rst
@@ -1,7 +1,3 @@
-.. index::
- single: How the front controller, ``Kernel`` and environments
- work together
-
Understanding how the Front Controller, Kernel and Environments Work together
=============================================================================
@@ -30,7 +26,7 @@ The `front controller`_ is a design pattern; it is a section of code that *all*
requests served by an application run through.
In the Symfony Skeleton, this role is taken by the ``index.php`` file in the
-``public/`` directory. This is the very first PHP script executed when a
+``public/`` directory. This is the very first PHP script that is run when a
request is processed.
The main purpose of the front controller is to create an instance of the
@@ -122,9 +118,6 @@ new kernel.
But odds are high that you don't need to change things like this on the
fly by having several ``Kernel`` implementations.
-.. index::
- single: Configuration; Debug mode
-
.. _debug-mode:
Debug Mode
@@ -135,7 +128,7 @@ should run in "debug mode". Regardless of the
:ref:`configuration environment `, a Symfony
application can be run with debug mode set to ``true`` or ``false``.
-This affects many things in the application, such as displaying stacktraces on
+This affects many things in the application, such as displaying stack traces on
error pages or if cache files are dynamically rebuilt on each request. Though
not a requirement, debug mode is generally set to ``true`` for the ``dev`` and
``test`` environments and ``false`` for the ``prod`` environment.
@@ -190,10 +183,13 @@ parameter used, for example, to turn Twig's debug mode on:
.. code-block:: php
- $container->loadFromExtension('twig', [
- 'debug' => '%kernel.debug%',
+ // config/packages/twig.php
+ use Symfony\Config\TwigConfig;
+
+ return static function (TwigConfig $twig): void {
// ...
- ]);
+ $twig->debug('%kernel.debug%');
+ };
The Environments
----------------
@@ -216,9 +212,6 @@ config files found on ``config/packages/*`` and then, the files found on
``config/packages/ENVIRONMENT_NAME/``. You are free to implement this method
differently if you need a more sophisticated way of loading your configuration.
-.. index::
- single: Environments; Cache directory
-
Environments and the Cache Directory
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -244,17 +237,16 @@ the directory of the environment you're using (most commonly ``dev/`` while
developing and debugging). While it can vary, the ``var/cache/dev/`` directory
includes the following:
-``appDevDebugProjectContainer.php``
+``App_KernelDevDebugContainer.php``
The cached "service container" that represents the cached application
configuration.
-``appDevUrlGenerator.php``
- The PHP class generated from the routing configuration and used when
- generating URLs.
+``url_generating_routes.php``
+ The cached routing configuration used when generating URLs.
-``appDevUrlMatcher.php``
- The PHP class used for route matching - look here to see the compiled regular
- expression logic used to match incoming URLs to different routes.
+``url_matching_routes.php``
+ The cached configuration used for route matching - look here to see the compiled
+ regular expression logic used to match incoming URLs to different routes.
``twig/``
This directory contains all the cached Twig templates.
diff --git a/configuration/micro_kernel_trait.rst b/configuration/micro_kernel_trait.rst
index 84d8da1f028..c372d876651 100644
--- a/configuration/micro_kernel_trait.rst
+++ b/configuration/micro_kernel_trait.rst
@@ -16,59 +16,89 @@ via Composer:
.. code-block:: terminal
- $ composer require symfony/config symfony/http-kernel \
- symfony/http-foundation symfony/routing \
- symfony/dependency-injection symfony/framework-bundle
+ $ composer require symfony/framework-bundle symfony/runtime
-Next, create an ``index.php`` file that defines the kernel class and executes it::
+Next, create an ``index.php`` file that defines the kernel class and runs it:
- // index.php
- use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
- use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
- use Symfony\Component\HttpFoundation\JsonResponse;
- use Symfony\Component\HttpFoundation\Request;
- use Symfony\Component\HttpKernel\Kernel as BaseKernel;
- use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
+.. configuration-block::
- require __DIR__.'/vendor/autoload.php';
+ .. code-block:: php-attributes
- class Kernel extends BaseKernel
- {
- use MicroKernelTrait;
+ // index.php
+ use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
+ use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
+ use Symfony\Component\HttpFoundation\JsonResponse;
+ use Symfony\Component\HttpFoundation\Request;
+ use Symfony\Component\HttpKernel\Kernel as BaseKernel;
+ use Symfony\Component\Routing\Attribute\Route;
- public function registerBundles(): array
- {
- return [
- new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
- ];
- }
+ require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
- protected function configureContainer(ContainerConfigurator $c): void
+ class Kernel extends BaseKernel
{
- // PHP equivalent of config/packages/framework.yaml
- $c->extension('framework', [
- 'secret' => 'S0ME_SECRET'
- ]);
- }
+ use MicroKernelTrait;
- protected function configureRoutes(RoutingConfigurator $routes): void
- {
- $routes->add('random_number', '/random/{limit}')->controller([$this, 'randomNumber']);
+ protected function configureContainer(ContainerConfigurator $container): void
+ {
+ // PHP equivalent of config/packages/framework.yaml
+ $container->extension('framework', [
+ 'secret' => 'S0ME_SECRET'
+ ]);
+ }
+
+ #[Route('/random/{limit}', name: 'random_number')]
+ public function randomNumber(int $limit): JsonResponse
+ {
+ return new JsonResponse([
+ 'number' => random_int(0, $limit),
+ ]);
+ }
}
- public function randomNumber(int $limit): JsonResponse
+ return static function (array $context) {
+ return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
+ };
+
+ .. code-block:: php
+
+ // index.php
+ use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
+ use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
+ use Symfony\Component\HttpFoundation\JsonResponse;
+ use Symfony\Component\HttpFoundation\Request;
+ use Symfony\Component\HttpKernel\Kernel as BaseKernel;
+ use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
+
+ require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
+
+ class Kernel extends BaseKernel
{
- return new JsonResponse([
- 'number' => random_int(0, $limit),
- ]);
+ use MicroKernelTrait;
+
+ protected function configureContainer(ContainerConfigurator $container): void
+ {
+ // PHP equivalent of config/packages/framework.yaml
+ $container->extension('framework', [
+ 'secret' => 'S0ME_SECRET'
+ ]);
+ }
+
+ protected function configureRoutes(RoutingConfigurator $routes): void
+ {
+ $routes->add('random_number', '/random/{limit}')->controller([$this, 'randomNumber']);
+ }
+
+ public function randomNumber(int $limit): JsonResponse
+ {
+ return new JsonResponse([
+ 'number' => random_int(0, $limit),
+ ]);
+ }
}
- }
- $kernel = new Kernel('dev', true);
- $request = Request::createFromGlobals();
- $response = $kernel->handle($request);
- $response->send();
- $kernel->terminate($request, $response);
+ return static function (array $context) {
+ return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
+ };
That's it! To test it, start the :doc:`Symfony Local Web Server
`:
@@ -79,6 +109,23 @@ That's it! To test it, start the :doc:`Symfony Local Web Server
Then see the JSON response in your browser: http://localhost:8000/random/10
+.. tip::
+
+ If your kernel only defines a single controller, you can use an invokable method::
+
+ class Kernel extends BaseKernel
+ {
+ use MicroKernelTrait;
+
+ // ...
+
+ #[Route('/random/{limit}', name: 'random_number')]
+ public function __invoke(int $limit): JsonResponse
+ {
+ // ...
+ }
+ }
+
The Methods of a "Micro" Kernel
-------------------------------
@@ -86,24 +133,90 @@ When you use the ``MicroKernelTrait``, your kernel needs to have exactly three m
that define your bundles, your services and your routes:
**registerBundles()**
- This is the same ``registerBundles()`` that you see in a normal kernel.
+ This is the same ``registerBundles()`` that you see in a normal kernel. By
+ default, the micro kernel only registers the ``FrameworkBundle``. If you need
+ to register more bundles, override this method::
+
+ use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
+ use Symfony\Bundle\TwigBundle\TwigBundle;
+ // ...
+
+ class Kernel extends BaseKernel
+ {
+ use MicroKernelTrait;
+
+ // ...
+
+ public function registerBundles(): array
+ {
+ yield new FrameworkBundle();
+ yield new TwigBundle();
+ }
+ }
-**configureContainer(ContainerConfigurator $c)**
+**configureContainer(ContainerConfigurator $container)**
This method builds and configures the container. In practice, you will use
``extension()`` to configure different bundles (this is the equivalent
of what you see in a normal ``config/packages/*`` file). You can also register
services directly in PHP or load external configuration files (shown below).
**configureRoutes(RoutingConfigurator $routes)**
- Your job in this method is to add routes to the application. The
- ``RoutingConfigurator`` has methods that make adding routes in PHP more
- fun. You can also load external routing files (shown below).
+ In this method, you can use the ``RoutingConfigurator`` object to define routes
+ in your application and associate them to the controllers defined in this very
+ same file.
+
+ However, it's more convenient to define the controller routes using PHP attributes,
+ as shown above. That's why this method is commonly used only to load external
+ routing files (e.g. from bundles) as shown below.
+
+Adding Interfaces to "Micro" Kernel
+-----------------------------------
+
+When using the ``MicroKernelTrait``, you can also implement the
+``CompilerPassInterface`` to automatically register the kernel itself as a
+compiler pass as explained in the dedicated
+:ref:`compiler pass section `. If the
+:class:`Symfony\\Component\\DependencyInjection\\Extension\\ExtensionInterface`
+is implemented when using the ``MicroKernelTrait``, then the kernel will
+be automatically registered as an extension. You can learn more about it in
+the dedicated section about
+:ref:`managing configuration with extensions `.
+
+It is also possible to implement the ``EventSubscriberInterface`` to handle
+events directly from the kernel, again it will be registered automatically::
+
+ // ...
+ use App\Exception\Danger;
+ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+ use Symfony\Component\HttpKernel\Event\ExceptionEvent;
+ use Symfony\Component\HttpKernel\KernelEvents;
+
+ class Kernel extends BaseKernel implements EventSubscriberInterface
+ {
+ use MicroKernelTrait;
+
+ // ...
+
+ public function onKernelException(ExceptionEvent $event): void
+ {
+ if ($event->getThrowable() instanceof Danger) {
+ $event->setResponse(new Response('It\'s dangerous to go alone. Take this ⚔'));
+ }
+ }
+
+ public static function getSubscribedEvents(): array
+ {
+ return [
+ KernelEvents::EXCEPTION => 'onKernelException',
+ ];
+ }
+ }
Advanced Example: Twig, Annotations and the Web Debug Toolbar
-------------------------------------------------------------
The purpose of the ``MicroKernelTrait`` is *not* to have a single-file application.
-Instead, its goal to give you the power to choose your bundles and structure.
+Instead, its goal is to give you the power to choose your bundles and structure.
First, you'll probably want to put your PHP classes in an ``src/`` directory. Configure
your ``composer.json`` file to load from there:
@@ -123,14 +236,20 @@ your ``composer.json`` file to load from there:
Then, run ``composer dump-autoload`` to dump your new autoload config.
-Now, suppose you want to use Twig and load routes via annotations. Instead of
-putting *everything* in ``index.php``, create a new ``src/Kernel.php`` to
-hold the kernel. Now it looks like this::
+Now, suppose you want to define a custom configuration for your app,
+use Twig and load routes via annotations. Instead of putting *everything*
+in ``index.php``, create a new ``src/Kernel.php`` to hold the kernel.
+Now it looks like this::
// src/Kernel.php
namespace App;
+ use App\DependencyInjection\AppExtension;
+ use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
+ use Symfony\Bundle\TwigBundle\TwigBundle;
+ use Symfony\Bundle\WebProfilerBundle\WebProfilerBundle;
+ use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
@@ -139,26 +258,27 @@ hold the kernel. Now it looks like this::
{
use MicroKernelTrait;
- public function registerBundles(): array
+ public function registerBundles(): iterable
{
- $bundles = [
- new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
- new \Symfony\Bundle\TwigBundle\TwigBundle(),
- ];
+ yield new FrameworkBundle();
+ yield new TwigBundle();
- if ($this->getEnvironment() == 'dev') {
- $bundles[] = new \Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
+ if ('dev' === $this->getEnvironment()) {
+ yield new WebProfilerBundle();
}
+ }
- return $bundles;
+ protected function build(ContainerBuilder $containerBuilder): void
+ {
+ $containerBuilder->registerExtension(new AppExtension());
}
- protected function configureContainer(ContainerConfigurator $c): void
+ protected function configureContainer(ContainerConfigurator $container): void
{
- $c->import(__DIR__.'/../config/framework.yaml');
+ $container->import(__DIR__.'/../config/framework.yaml');
// register all classes in /src/ as service
- $c->services()
+ $container->services()
->load('App\\', __DIR__.'/*')
->autowire()
->autoconfigure()
@@ -166,7 +286,7 @@ hold the kernel. Now it looks like this::
// configure WebProfilerBundle only if the bundle is enabled
if (isset($this->bundles['WebProfilerBundle'])) {
- $c->extension('web_profiler', [
+ $container->extension('web_profiler', [
'toolbar' => true,
'intercept_redirects' => false,
]);
@@ -177,32 +297,59 @@ hold the kernel. Now it looks like this::
{
// import the WebProfilerRoutes, only if the bundle is enabled
if (isset($this->bundles['WebProfilerBundle'])) {
- $routes->import('@WebProfilerBundle/Resources/config/routing/wdt.xml')->prefix('/_wdt');
- $routes->import('@WebProfilerBundle/Resources/config/routing/profiler.xml')->prefix('/_profiler');
+ $routes->import('@WebProfilerBundle/Resources/config/routing/wdt.php', 'php')->prefix('/_wdt');
+ $routes->import('@WebProfilerBundle/Resources/config/routing/profiler.php', 'php')->prefix('/_profiler');
}
- // load the annotation routes
- $routes->import(__DIR__.'/Controller/', 'annotation');
+ // load the routes defined as PHP attributes
+ // (use 'annotation' as the second argument if you define routes as annotations)
+ $routes->import(__DIR__.'/Controller/', 'attribute');
}
- // optional, to use the standard Symfony cache directory
- public function getCacheDir(): string
- {
- return __DIR__.'/../var/cache/'.$this->getEnvironment();
- }
-
- // optional, to use the standard Symfony logs directory
- public function getLogDir(): string
- {
- return __DIR__.'/../var/log';
- }
+ // optionally, you can define the getCacheDir() and getLogDir() methods
+ // to override the default locations for these directories
}
+
+.. versionadded:: 7.3
+
+ The ``wdt.php`` and ``profiler.php`` files were introduced in Symfony 7.3.
+ Previously, you had to import ``wdt.xml`` and ``profiler.xml``
+
Before continuing, run this command to add support for the new dependencies:
.. code-block:: terminal
- $ composer require symfony/yaml symfony/twig-bundle symfony/web-profiler-bundle doctrine/annotations
+ $ composer require symfony/yaml symfony/twig-bundle symfony/web-profiler-bundle
+
+Next, create a new extension class that defines your app configuration and
+add a service conditionally based on the ``foo`` value::
+
+ // src/DependencyInjection/AppExtension.php
+ namespace App\DependencyInjection;
+
+ use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator;
+ use Symfony\Component\DependencyInjection\ContainerBuilder;
+ use Symfony\Component\DependencyInjection\Extension\AbstractExtension;
+ use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
+
+ class AppExtension extends AbstractExtension
+ {
+ public function configure(DefinitionConfigurator $definition): void
+ {
+ $definition->rootNode()
+ ->children()
+ ->booleanNode('foo')->defaultTrue()->end()
+ ->end();
+ }
+
+ public function loadExtension(array $config, ContainerConfigurator $containerConfigurator, ContainerBuilder $containerBuilder): void
+ {
+ if ($config['foo']) {
+ $containerBuilder->register('foo_service', \stdClass::class);
+ }
+ }
+ }
Unlike the previous kernel, this loads an external ``config/framework.yaml`` file,
because the configuration started to get bigger:
@@ -234,14 +381,17 @@ because the configuration started to get bigger:
.. code-block:: php
// config/framework.php
- $container->loadFromExtension('framework', [
- 'secret' => 'S0ME_SECRET',
- 'profiler' => [
- 'only_exceptions' => false,
- ],
- ]);
-
-This also loads annotation routes from an ``src/Controller/`` directory, which
+ use Symfony\Config\FrameworkConfig;
+
+ return static function (FrameworkConfig $framework): void {
+ $framework
+ ->secret('SOME_SECRET')
+ ->profiler()
+ ->onlyExceptions(false)
+ ;
+ };
+
+This also loads attribute routes from an ``src/Controller/`` directory, which
has one file in it::
// src/Controller/MicroController.php
@@ -249,13 +399,11 @@ has one file in it::
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
- use Symfony\Component\Routing\Annotation\Route;
+ use Symfony\Component\Routing\Attribute\Route;
class MicroController extends AbstractController
{
- /**
- * @Route("/random/{limit}")
- */
+ #[Route('/random/{limit}')]
public function randomNumber(int $limit): Response
{
$number = random_int(0, $limit);
@@ -287,12 +435,9 @@ Finally, you need a front controller to boot and run the application. Create a
// public/index.php
use App\Kernel;
- use Doctrine\Common\Annotations\AnnotationRegistry;
use Symfony\Component\HttpFoundation\Request;
- $loader = require __DIR__.'/../vendor/autoload.php';
- // auto-load annotations
- AnnotationRegistry::registerLoader([$loader, 'loadClass']);
+ require __DIR__.'/../vendor/autoload.php';
$kernel = new Kernel('dev', true);
$request = Request::createFromGlobals();
diff --git a/configuration/multiple_kernels.rst b/configuration/multiple_kernels.rst
index a43aa491920..ec8742213b5 100644
--- a/configuration/multiple_kernels.rst
+++ b/configuration/multiple_kernels.rst
@@ -1,225 +1,429 @@
-.. index::
- single: kernel, performance
+How to Create Multiple Symfony Applications with a Single Kernel
+================================================================
+
+In Symfony applications, incoming requests are usually processed by the front
+controller at ``public/index.php``, which instantiates the ``src/Kernel.php``
+class to create the application kernel. This kernel loads the bundles, the
+configuration, and handles the request to generate the response.
+
+The current implementation of the Kernel class serves as a convenient default
+for a single application. However, it can also manage multiple applications.
+While the Kernel typically runs the same application with different
+configurations based on various :ref:`environments `,
+it can be adapted to run different applications with specific bundles and configuration.
+
+These are some of the common use cases for creating multiple applications with a
+single Kernel:
+
+* An application that defines an API can be divided into two segments to improve
+ performance. The first segment serves the regular web application, while the
+ second segment exclusively responds to API requests. This approach requires
+ loading fewer bundles and enabling fewer features for the second part, thus
+ optimizing performance;
+* A highly sensitive application could be divided into two parts for enhanced
+ security. The first part would only load routes corresponding to the publicly
+ exposed sections of the application. The second part would load the remainder
+ of the application, with its access safeguarded by the web server;
+* A monolithic application could be gradually transformed into a more
+ distributed architecture, such as micro-services. This approach allows for a
+ seamless migration of a large application while still sharing common
+ configurations and components.
+
+Turning a Single Application into Multiple Applications
+-------------------------------------------------------
+
+These are the steps required to convert a single application into a new one that
+supports multiple applications:
+
+1. Create a new application;
+2. Update the Kernel class to support multiple applications;
+3. Add a new ``APP_ID`` environment variable;
+4. Update the front controllers.
+
+The following example shows how to create a new application for the API of a new
+Symfony project.
+
+Step 1) Create a new Application
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This example follows the `Shared Kernel`_ pattern: all applications maintain an
+isolated context, but they can share common bundles, configuration, and code if
+desired. The optimal approach will depend on your specific needs and
+requirements, so it's up to you to decide which best suits your project.
+
+First, create a new ``apps`` directory at the root of your project, which will
+hold all the necessary applications. Each application will follow a simplified
+directory structure like the one described in :doc:`Symfony Best Practice `:
-How To Create Symfony Applications with Multiple Kernels
-========================================================
+.. code-block:: text
-.. caution::
+ your-project/
+ ├─ apps/
+ │ └─ api/
+ │ ├─ config/
+ │ │ ├─ bundles.php
+ │ │ ├─ routes.yaml
+ │ │ └─ services.yaml
+ │ └─ src/
+ ├─ bin/
+ │ └─ console
+ ├─ config/
+ ├─ public/
+ │ └─ index.php
+ ├─ src/
+ │ └─ Kernel.php
- Creating applications with multiple kernels is no longer recommended by
- Symfony. Consider creating multiple small applications instead.
+.. note::
-In most Symfony applications, incoming requests are processed by the
-``public/index.php`` front controller, which instantiates the ``src/Kernel.php``
-class to create the application kernel that loads the bundles and handles the
-request to generate the response.
+ Note that the ``config/`` and ``src/`` directories at the root of the
+ project will represent the shared context among all applications within the
+ ``apps/`` directory. Therefore, you should carefully consider what is
+ common and what should be placed in the specific application.
-This single kernel approach is a convenient default, but Symfony applications
-can define any number of kernels. Whereas
-:ref:`environments ` execute the same application
-with different configurations, kernels can execute different parts of the same
-application.
+.. tip::
-These are some of the common use cases for creating multiple kernels:
+ You might also consider renaming the namespace for the shared context, from
+ ``App`` to ``Shared``, as it will make it easier to distinguish and provide
+ clearer meaning to this context.
-* An application that defines an API could define two kernels for performance
- reasons. The first kernel would serve the regular application and the second
- one would only respond to the API requests, loading less bundles and enabling
- less features;
-* A highly sensitive application could define two kernels. The first one would
- only load the routes that match the parts of the application exposed publicly.
- The second kernel would load the rest of the application and its access would
- be protected by the web server;
-* A micro-services oriented application could define several kernels to
- enable/disable services selectively turning a traditional monolith application
- into several micro-applications.
+Since the new ``apps/api/src/`` directory will host the PHP code related to the
+API, you have to update the ``composer.json`` file to include it in the autoload
+section:
-Adding a new Kernel to the Application
---------------------------------------
+.. code-block:: json
-Creating a new kernel in a Symfony application is a three-step process:
+ {
+ "autoload": {
+ "psr-4": {
+ "Shared\\": "src/",
+ "Api\\": "apps/api/src/"
+ }
+ }
+ }
-1. Create a new front controller to load the new kernel;
-2. Create the new kernel class;
-3. Define the configuration loaded by the new kernel.
+Additionally, don't forget to run ``composer dump-autoload`` to generate the
+autoload files.
-The following example shows how to create a new kernel for the API of a given
-Symfony application.
+Step 2) Update the Kernel class to support Multiple Applications
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Step 1) Create a new Front Controller
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Since there will be multiple applications, it's better to add a new property
+``string $id`` to the Kernel to identify the application being loaded. This
+property will also allow you to split the cache, logs, and configuration files
+in order to avoid collisions with other applications. Moreover, it contributes
+to performance optimization, as each application will load only the required
+resources::
-Instead of creating the new front controller from scratch, it's easier to
-duplicate the existing one. For example, create ``public/api.php`` from
-``public/index.php``.
+ // src/Kernel.php
+ namespace Shared;
-Then, update the code of the new front controller to instantiate the new kernel
-class instead of the usual ``Kernel`` class::
+ 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;
- // public/api.php
- // ...
- $kernel = new ApiKernel(
- $_SERVER['APP_ENV'] ?? 'dev',
- $_SERVER['APP_DEBUG'] ?? ('prod' !== ($_SERVER['APP_ENV'] ?? 'dev'))
- );
- // ...
-
-.. tip::
+ class Kernel extends BaseKernel
+ {
+ use MicroKernelTrait;
- Another approach is to keep the existing ``index.php`` front controller, but
- add an ``if`` statement to load the different kernel based on the URL (e.g.
- if the URL starts with ``/api``, use the ``ApiKernel``).
+ public function __construct(string $environment, bool $debug, private string $id)
+ {
+ parent::__construct($environment, $debug);
+ }
-Step 2) Create the new Kernel Class
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ public function getSharedConfigDir(): string
+ {
+ return $this->getProjectDir().'/config';
+ }
-Now you need to define the ``ApiKernel`` class used by the new front controller.
-The easiest way to do this is by duplicating the existing ``src/Kernel.php``
-file and make the needed changes.
+ public function getAppConfigDir(): string
+ {
+ return $this->getProjectDir().'/apps/'.$this->id.'/config';
+ }
-In this example, the ``ApiKernel`` will load less bundles than the default
-Kernel. Be sure to also change the location of the cache, logs and configuration
-files so they don't collide with the files from ``src/Kernel.php``::
+ public function registerBundles(): iterable
+ {
+ $sharedBundles = require $this->getSharedConfigDir().'/bundles.php';
+ $appBundles = require $this->getAppConfigDir().'/bundles.php';
+
+ // load common bundles, such as the FrameworkBundle, as well as
+ // specific bundles required exclusively for the app itself
+ foreach (array_merge($sharedBundles, $appBundles) as $class => $envs) {
+ if ($envs[$this->environment] ?? $envs['all'] ?? false) {
+ yield new $class();
+ }
+ }
+ }
- // src/ApiKernel.php
- use Symfony\Component\Config\Loader\LoaderInterface;
- use Symfony\Component\DependencyInjection\ContainerBuilder;
- use Symfony\Component\HttpKernel\Kernel;
+ public function getCacheDir(): string
+ {
+ // divide cache for each application
+ return ($_SERVER['APP_CACHE_DIR'] ?? $this->getProjectDir().'/var/cache').'/'.$this->id.'/'.$this->environment;
+ }
- class ApiKernel extends Kernel
- {
- // ...
+ public function getLogDir(): string
+ {
+ // divide logs for each application
+ return ($_SERVER['APP_LOG_DIR'] ?? $this->getProjectDir().'/var/log').'/'.$this->id;
+ }
- public function registerBundles()
+ protected function configureContainer(ContainerConfigurator $container): void
{
- // load only the bundles strictly needed for the API...
+ // load common config files, such as the framework.yaml, as well as
+ // specific configs required exclusively for the app itself
+ $this->doConfigureContainer($container, $this->getSharedConfigDir());
+ $this->doConfigureContainer($container, $this->getAppConfigDir());
}
- public function getCacheDir()
+ protected function configureRoutes(RoutingConfigurator $routes): void
{
- return dirname(__DIR__).'/var/cache/api/'.$this->getEnvironment();
+ // load common routes files, such as the routes/framework.yaml, as well as
+ // specific routes required exclusively for the app itself
+ $this->doConfigureRoutes($routes, $this->getSharedConfigDir());
+ $this->doConfigureRoutes($routes, $this->getAppConfigDir());
}
- public function getLogDir()
+ private function doConfigureContainer(ContainerConfigurator $container, string $configDir): void
{
- return dirname(__DIR__).'/var/log/api';
+ $container->import($configDir.'/{packages}/*.{php,yaml}');
+ $container->import($configDir.'/{packages}/'.$this->environment.'/*.{php,yaml}');
+
+ if (is_file($configDir.'/services.yaml')) {
+ $container->import($configDir.'/services.yaml');
+ $container->import($configDir.'/{services}_'.$this->environment.'.yaml');
+ } else {
+ $container->import($configDir.'/{services}.php');
+ }
}
- public function configureContainer(ContainerBuilder $container, LoaderInterface $loader)
+ private function doConfigureRoutes(RoutingConfigurator $routes, string $configDir): void
{
- // load only the config files strictly needed for the API
- $confDir = $this->getProjectDir().'/config';
- $loader->load($confDir.'/api/*'.self::CONFIG_EXTS, 'glob');
- if (is_dir($confDir.'/api/'.$this->environment)) {
- $loader->load($confDir.'/api/'.$this->environment.'/**/*'.self::CONFIG_EXTS, 'glob');
+ $routes->import($configDir.'/{routes}/'.$this->environment.'/*.{php,yaml}');
+ $routes->import($configDir.'/{routes}/*.{php,yaml}');
+
+ if (is_file($configDir.'/routes.yaml')) {
+ $routes->import($configDir.'/routes.yaml');
+ } else {
+ $routes->import($configDir.'/{routes}.php');
+ }
+
+ if (false !== ($fileName = (new \ReflectionObject($this))->getFileName())) {
+ $routes->import($fileName, 'attribute');
}
}
}
-Step 3) Define the Kernel Configuration
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+This example reuses the default implementation to import the configuration and
+routes based on a given configuration directory. As shown earlier, this
+approach will import both the shared and the app-specific resources.
-Finally, define the configuration files that the new ``ApiKernel`` will load.
-According to the above code, this config will live in one or multiple files
-stored in ``config/api/`` and ``config/api/ENVIRONMENT_NAME/`` directories.
+Step 3) Add a new APP_ID environment variable
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-The new configuration files can be created from scratch when you load just a few
-bundles, because it will be small. Otherwise, duplicate the existing
-config files in ``config/packages/`` or better, import them and override the
-needed options.
+Next, define a new environment variable that identifies the current application.
+This new variable can be added to the ``.env`` file to provide a default value,
+but it should typically be added to your web server configuration.
-Executing Commands with a Different Kernel
-------------------------------------------
+.. code-block:: bash
-The ``bin/console`` script used to run Symfony commands always uses the default
-``Kernel`` class to build the application and load the commands. If you need
-to execute console commands using the new kernel, duplicate the ``bin/console``
-script and rename it (e.g. ``bin/api``).
+ # .env
+ APP_ID=api
-Then, replace the ``Kernel`` instance by your own kernel instance
-(e.g. ``ApiKernel``) and now you can execute commands using the new kernel
-(e.g. ``php bin/api cache:clear``) Now you can use execute commands using the
-new kernel.
+.. warning::
-.. note::
+ The value of this variable must match the application directory within
+ ``apps/`` as it is used in the Kernel to load the specific application
+ configuration.
+
+Step 4) Update the Front Controllers
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In this final step, update the front controllers ``public/index.php`` and
+``bin/console`` to pass the value of the ``APP_ID`` variable to the Kernel
+instance. This will allow the Kernel to load and run the specified
+application::
+
+ // public/index.php
+ use Shared\Kernel;
+ // ...
+
+ return function (array $context): Kernel {
+ return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG'], $context['APP_ID']);
+ };
- The commands available for each console script (e.g. ``bin/console`` and
- ``bin/api``) can differ because they depend on the bundles enabled for each
- kernel, which could be different.
+Similar to configuring the required ``APP_ENV`` and ``APP_DEBUG`` values, the
+third argument of the Kernel constructor is now also necessary to set the
+application ID, which is derived from an external configuration.
-Rendering Templates Defined in a Different Kernel
--------------------------------------------------
+For the second front controller, define a new console option to allow passing
+the application ID to run under CLI context::
-If you follow the Symfony Best Practices, the templates of the default kernel
-will be stored in ``templates/``. Trying to render those templates in a
-different kernel will result in a *There are no registered paths for namespace
-"__main__"* error.
+ // bin/console
+ use Shared\Kernel;
+ use Symfony\Bundle\FrameworkBundle\Console\Application;
+ use Symfony\Component\Console\Input\InputInterface;
+ use Symfony\Component\Console\Input\InputOption;
-In order to solve this issue, add the following configuration to your kernel:
+ return function (InputInterface $input, array $context): Application {
+ $kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG'], $input->getParameterOption(['--id', '-i'], $context['APP_ID']));
+
+ $application = new Application($kernel);
+ $application->getDefinition()
+ ->addOption(new InputOption('--id', '-i', InputOption::VALUE_REQUIRED, 'The App ID'))
+ ;
+
+ return $application;
+ };
+
+That's it!
+
+Executing Commands
+------------------
+
+The ``bin/console`` script, which is used to run Symfony commands, always uses
+the ``Kernel`` class to build the application and load the commands. If you
+need to run console commands for a specific application, you can provide the
+``--id`` option along with the appropriate identity value:
+
+.. code-block:: terminal
+
+ php bin/console cache:clear --id=api
+ // or
+ php bin/console cache:clear -iapi
+
+ // alternatively
+ export APP_ID=api
+ php bin/console cache:clear
+
+You might want to update the composer auto-scripts section to run multiple
+commands simultaneously. This example shows the commands of two different
+applications called ``api`` and ``admin``:
+
+.. code-block:: json
+
+ {
+ "scripts": {
+ "auto-scripts": {
+ "cache:clear -iapi": "symfony-cmd",
+ "cache:clear -iadmin": "symfony-cmd",
+ "assets:install %PUBLIC_DIR% -iapi": "symfony-cmd",
+ "assets:install %PUBLIC_DIR% -iadmin --no-cleanup": "symfony-cmd"
+ }
+ }
+ }
+
+Then, run ``composer auto-scripts`` to test it!
+
+.. note::
+
+ The commands available for each console script (e.g. ``bin/console -iapi``
+ and ``bin/console -iadmin``) can differ because they depend on the bundles
+ enabled for each application, which could be different.
+
+Rendering Templates
+-------------------
+
+Let's consider that you need to create another app called ``admin``. If you
+follow the :doc:`Symfony Best Practices `, the shared Kernel
+templates will be located in the ``templates/`` directory at the project's root.
+For admin-specific templates, you can create a new directory
+``apps/admin/templates/`` which you will need to manually configure under the
+Admin application:
.. code-block:: yaml
- # config/api/twig.yaml
+ # apps/admin/config/packages/twig.yaml
twig:
paths:
- # allows to use api/templates/ dir in the ApiKernel
- "%kernel.project_dir%/api/templates": ~
+ '%kernel.project_dir%/apps/admin/templates': Admin
+
+Then, use this Twig namespace to reference any template within the Admin
+application only, for example ``@Admin/form/fields.html.twig``.
-Running Tests Using a Different Kernel
---------------------------------------
+Running Tests
+-------------
-In Symfony applications, functional tests extend by default from the
-:class:`Symfony\\Bundle\\FrameworkBundle\\Test\\WebTestCase` class. Inside that
-class, a method called ``getKernelClass()`` tries to find the class of the kernel
-to use to run the application during tests. The logic of this method does not
-support multiple kernel applications, so your tests won't use the right kernel.
+In Symfony applications, functional tests typically extend from
+the :class:`Symfony\\Bundle\\FrameworkBundle\\Test\\WebTestCase` class by
+default. Within its parent class, ``KernelTestCase``, there is a method called
+``createKernel()`` that attempts to create the kernel responsible for running
+the application during tests. However, the current logic of this method doesn't
+include the new application ID argument, so you need to update it::
-The solution is to create a custom base class for functional tests extending
-from ``WebTestCase`` class and overriding the ``getKernelClass()`` method to
-return the fully qualified class name of the kernel to use::
+ // apps/api/tests/ApiTestCase.php
+ namespace Api\Tests;
+ use Shared\Kernel;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
+ use Symfony\Component\HttpKernel\KernelInterface;
- // tests needing the ApiKernel to work, now must extend this
- // ApiTestCase class instead of the default WebTestCase class
class ApiTestCase extends WebTestCase
{
- protected static function getKernelClass()
+ protected static function createKernel(array $options = []): KernelInterface
{
- return 'App\ApiKernel';
+ $env = $options['environment'] ?? $_ENV['APP_ENV'] ?? $_SERVER['APP_ENV'] ?? 'test';
+ $debug = $options['debug'] ?? (bool) ($_ENV['APP_DEBUG'] ?? $_SERVER['APP_DEBUG'] ?? true);
+
+ return new Kernel($env, $debug, 'api');
}
+ }
- // this is needed because the KernelTestCase class keeps a reference to
- // the previously created kernel in its static $kernel property. Thus,
- // if your functional tests do not run in isolated processes, a later run
- // test for a different kernel will reuse the previously created instance,
- // which points to a different kernel
- protected function tearDown()
- {
- parent::tearDown();
+.. note::
+
+ This examples uses a hardcoded application ID value because the tests
+ extending this ``ApiTestCase`` class will focus solely on the ``api`` tests.
+
+Now, create a ``tests/`` directory inside the ``apps/api/`` application. Then,
+update both the ``composer.json`` file and ``phpunit.xml`` configuration about
+its existence:
- static::$class = null;
+.. code-block:: json
+
+ {
+ "autoload-dev": {
+ "psr-4": {
+ "Shared\\Tests\\": "tests/",
+ "Api\\Tests\\": "apps/api/tests/"
+ }
}
}
-Adding more Kernels to the Application
---------------------------------------
+Remember to run ``composer dump-autoload`` to generate the autoload files.
+
+And, here is the update needed for the ``phpunit.xml`` file:
+
+.. code-block:: xml
-If your application is very complex and you create several kernels, it's better
-to store them in their own directories instead of messing with lots of files in
-the default ``src/`` directory:
+
+
+ tests
+
+
+ apps/api/tests
+
+
+
+Adding more Applications
+------------------------
+
+Now you can begin adding more applications as needed, such as an ``admin``
+application to manage the project's configuration and permissions. To do that,
+you will have to repeat the step 1 only:
.. code-block:: text
- project/
- ├─ src/
- │ ├─ ...
- │ └─ Kernel.php
- ├─ api/
- │ ├─ ...
- │ └─ ApiKernel.php
- ├─ ...
- └─ public/
- ├─ ...
- ├─ api.php
- └─ index.php
+ your-project/
+ ├─ apps/
+ │ ├─ admin/
+ │ │ ├─ config/
+ │ │ │ ├─ bundles.php
+ │ │ │ ├─ routes.yaml
+ │ │ │ └─ services.yaml
+ │ │ └─ src/
+ │ └─ api/
+ │ └─ ...
+
+Additionally, you might need to update your web server configuration to set the
+``APP_ID=admin`` under a different domain.
+
+.. _`Shared Kernel`: http://ddd.fed.wiki.org/view/shared-kernel
diff --git a/configuration/override_dir_structure.rst b/configuration/override_dir_structure.rst
index d09916daae7..e5dff35b6d0 100644
--- a/configuration/override_dir_structure.rst
+++ b/configuration/override_dir_structure.rst
@@ -1,6 +1,3 @@
-.. index::
- single: Override Symfony
-
How to Override Symfony's default Directory Structure
=====================================================
@@ -25,7 +22,57 @@ override it to create your own structure:
│ ├─ cache/
│ ├─ log/
│ └─ ...
- └─ vendor/
+ ├─ vendor/
+ └─ .env
+
+.. _override-env-dir:
+
+Override the Environment (DotEnv) Files Directory
+-------------------------------------------------
+
+By default, the :ref:`.env configuration file ` is located at
+the root directory of the project. If you store it in a different location,
+define the ``runtime.dotenv_path`` option in the ``composer.json`` file:
+
+.. code-block:: json
+
+ {
+ "...": "...",
+ "extra": {
+ "...": "...",
+ "runtime": {
+ "dotenv_path": "my/custom/path/to/.env"
+ }
+ }
+ }
+
+Then, update your Composer files (running ``composer dump-autoload``, for instance),
+so that the ``vendor/autoload_runtime.php`` files gets regenerated with the new
+``.env`` path.
+
+You can also set up different ``.env`` paths for your console and web server
+calls. Edit the ``public/index.php`` and/or ``bin/console`` files to define the
+new file path.
+
+Console script::
+
+ // bin/console
+
+ // ...
+ $_SERVER['APP_RUNTIME_OPTIONS']['dotenv_path'] = 'some/custom/path/to/.env';
+
+ require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
+ // ...
+
+Web front-controller::
+
+ // public/index.php
+
+ // ...
+ $_SERVER['APP_RUNTIME_OPTIONS']['dotenv_path'] = 'another/custom/path/to/.env';
+
+ require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
+ // ...
.. _override-config-dir:
@@ -51,7 +98,7 @@ Changing the cache directory can be achieved by overriding the
{
// ...
- public function getCacheDir()
+ public function getCacheDir(): string
{
return dirname(__DIR__).'/var/'.$this->environment.'/cache';
}
@@ -61,10 +108,10 @@ In this code, ``$this->environment`` is the current environment (i.e. ``dev``).
In this case you have changed the location of the cache directory to
``var/{environment}/cache/``.
-You can also change the cache directory defining an environment variable named
-``APP_CACHE_DIR`` whose value is the full path of the cache folder.
+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
@@ -85,11 +132,11 @@ your application::
// src/Kernel.php
// ...
- class Kernel extends Kernel
+ class Kernel extends BaseKernel
{
// ...
- public function getLogDir()
+ public function getLogDir(): string
{
return dirname(__DIR__).'/var/'.$this->environment.'/log';
}
@@ -106,8 +153,9 @@ Override the Templates Directory
--------------------------------
If your templates are not stored in the default ``templates/`` directory, use
-the :ref:`twig.paths ` configuration option to define your
-own templates directory (or directories):
+the :ref:`twig.default_path ` configuration
+option to define your own templates directory (use :ref:`twig.paths `
+for multiple directories):
.. configuration-block::
@@ -116,12 +164,12 @@ own templates directory (or directories):
# config/packages/twig.yaml
twig:
# ...
- paths: ["%kernel.project_dir%/resources/views"]
+ default_path: "%kernel.project_dir%/resources/views"
.. code-block:: xml
-
+
- %kernel.project_dir%/resources/views
+ %kernel.project_dir%/resources/views
@@ -139,18 +187,18 @@ own templates directory (or directories):
.. code-block:: php
// config/packages/twig.php
- $container->loadFromExtension('twig', [
- 'paths' => [
- '%kernel.project_dir%/resources/views',
- ],
- ]);
+ use Symfony\Config\TwigConfig;
+
+ return static function (TwigConfig $twig): void {
+ $twig->defaultPath('%kernel.project_dir%/resources/views');
+ };
Override the Translations Directory
-----------------------------------
If your translation files are not stored in the default ``translations/``
-directory, use the :ref:`framework.translator.paths `
-configuration option to define your own translations directory (or directories):
+directory, use the :ref:`framework.translator.default_path `
+configuration option to define your own translations directory (use :ref:`framework.translator.paths ` for multiple directories):
.. configuration-block::
@@ -160,12 +208,12 @@ configuration option to define your own translations directory (or directories):
framework:
translator:
# ...
- paths: ["%kernel.project_dir%/i18n"]
+ default_path: "%kernel.project_dir%/i18n"
.. code-block:: xml
-
+
- %kernel.project_dir%/i18n
+ %kernel.project_dir%/i18n
@@ -185,13 +233,13 @@ configuration option to define your own translations directory (or directories):
.. code-block:: php
// config/packages/translation.php
- $container->loadFromExtension('framework', [
- 'translator' => [
- 'paths' => [
- '%kernel.project_dir%/i18n',
- ],
- ],
- ]);
+ use Symfony\Config\FrameworkConfig;
+
+ return static function (FrameworkConfig $framework): void {
+ $framework->translator()
+ ->defaultPath('%kernel.project_dir%/i18n')
+ ;
+ };
.. _override-web-dir:
.. _override-the-web-directory:
@@ -200,12 +248,12 @@ Override the Public Directory
-----------------------------
If you need to rename or move your ``public/`` directory, the only thing you
-need to guarantee is that the path to the ``var/`` directory is still correct in
+need to guarantee is that the path to the ``vendor/`` directory is still correct in
your ``index.php`` front controller. If you renamed the directory, you're fine.
But if you moved it in some way, you may need to modify these paths inside those
files::
- require_once __DIR__.'/../path/to/vendor/autoload.php';
+ require_once __DIR__.'/../path/to/vendor/autoload_runtime.php';
You also need to change the ``extra.public-dir`` option in the ``composer.json``
file:
@@ -241,7 +289,7 @@ option in your ``composer.json`` file like this:
"config": {
"bin-dir": "bin",
"vendor-dir": "/some/dir/vendor"
- },
+ }
}
.. tip::
diff --git a/configuration/secrets.rst b/configuration/secrets.rst
index 45f133d1d36..285b89d521e 100644
--- a/configuration/secrets.rst
+++ b/configuration/secrets.rst
@@ -1,6 +1,3 @@
-.. index::
- single: Secrets
-
How to Keep Sensitive Information Secret
========================================
@@ -14,10 +11,7 @@ store them by using Symfony's secrets management system - sometimes called a
.. note::
- The Secrets system requires the sodium PHP extension that is bundled
- with PHP 7.2. If you're using an earlier PHP version, you can
- install the `libsodium`_ PHP extension or use the
- `paragonie/sodium_compat`_ package.
+ The Secrets system requires the Sodium PHP extension.
.. _secrets-generate-keys:
@@ -48,12 +42,12 @@ running:
.. code-block:: terminal
- $ php bin/console secrets:generate-keys --env=prod
+ $ APP_RUNTIME_ENV=prod php bin/console secrets:generate-keys
This will generate ``config/secrets/prod/prod.encrypt.public.php`` and
``config/secrets/prod/prod.decrypt.private.php``.
-.. caution::
+.. danger::
The ``prod.decrypt.private.php`` file is highly sensitive. Your team of developers
and even Continuous Integration services don't need that key. If the
@@ -78,7 +72,7 @@ Suppose you want to store your database password as a secret. By using the
$ php bin/console secrets:set DATABASE_PASSWORD
# set your production value
- $ php bin/console secrets:set DATABASE_PASSWORD --env=prod
+ $ APP_RUNTIME_ENV=prod php bin/console secrets:set DATABASE_PASSWORD
This will create a new file for the secret in ``config/secrets/dev`` and another
in ``config/secrets/prod``. You can also set the secret in a few other ways:
@@ -94,11 +88,16 @@ in ``config/secrets/prod``. You can also set the secret in a few other ways:
# or let Symfony generate a random value for you
$ php bin/console secrets:set REMEMBER_ME --random
+.. note::
+
+ There's no command to rename secrets, so you'll need to create a new secret
+ and remove the old one.
+
Referencing Secrets in Configuration Files
------------------------------------------
Secret values can be referenced in the same way as
-:ref:`environment variables`. Be careful that you don't
+:ref:`environment variables `. Be careful that you don't
accidentally define a secret *and* an environment variable with the same name:
**environment variables override secrets**.
@@ -138,11 +137,14 @@ If you stored a ``DATABASE_PASSWORD`` secret, you can reference it by:
.. code-block:: php
// config/packages/doctrine.php
- $container->loadFromExtension('doctrine', [
- 'dbal' => [
- 'password' => '%env(DATABASE_PASSWORD)%',
- ]
- ]);
+ use Symfony\Config\DoctrineConfig;
+
+ return static function (DoctrineConfig $doctrine): void {
+ $doctrine->dbal()
+ ->connection('default')
+ ->password(env('DATABASE_PASSWORD'))
+ ;
+ };
The actual value will be resolved at runtime: container compilation and cache
warmup don't need the **decryption key**.
@@ -164,6 +166,22 @@ secrets' values by passing the ``--reveal`` option:
DATABASE_PASSWORD "my secret"
------------------- ------------ -------------
+Reveal Existing Secrets
+-----------------------
+
+If you have the **decryption key**, the ``secrets:reveal`` command allows
+you to reveal a single secret's value.
+
+.. code-block:: terminal
+
+ $ php bin/console secrets:reveal DATABASE_PASSWORD
+
+ my secret
+
+.. versionadded:: 7.1
+
+ The ``secrets:reveal`` command was introduced in Symfony 7.1.
+
Remove Secrets
--------------
@@ -208,9 +226,9 @@ Listing the secrets will now also display the local variable:
DATABASE_PASSWORD "dev value" "root"
------------------- ------------- -------------
-Symfony also provides the ``secrets:decrypt-to-local`` command to decrypts
-all secrets and stores them in the local vault and ``secrets:encrypt-from-local``
-to encrypt all local secrets to the vault.
+Symfony also provides the ``secrets:decrypt-to-local`` command which decrypts
+all secrets and stores them in the local vault and the ``secrets:encrypt-from-local``
+command to encrypt all local secrets to the vault.
Secrets in the test Environment
-------------------------------
@@ -231,32 +249,32 @@ Deploy Secrets to Production
Due to the fact that decryption keys should never be committed, you will need to
manually store this file somewhere and deploy it. There are 2 ways to do that:
-1) Uploading the file:
+#. Uploading the file
-The first option is to copy the **production decryption key** -
-``config/secrets/prod/prod.decrypt.private.php`` to your server.
+ The first option is to copy the **production decryption key** -
+ ``config/secrets/prod/prod.decrypt.private.php`` to your server.
-2) Using an Environment Variable
+#. Using an Environment Variable
-The second way is to set the ``SYMFONY_DECRYPTION_SECRET`` environment variable
-to the base64 encoded value of the **production decryption key**. A fancy way to
-fetch the value of the key is:
+ The second way is to set the ``SYMFONY_DECRYPTION_SECRET`` environment variable
+ to the base64 encoded value of the **production decryption key**. A fancy way to
+ fetch the value of the key is:
-.. code-block:: terminal
+ .. code-block:: terminal
- # this command only gets the value of the key; you must also set an env var
- # in your system with this value (e.g. `export SYMFONY_DECRYPTION_SECRET=...`)
- $ php -r 'echo base64_encode(require "config/secrets/prod/prod.decrypt.private.php");'
+ # this command only gets the value of the key; you must also set an env var
+ # in your system with this value (e.g. `export SYMFONY_DECRYPTION_SECRET=...`)
+ $ php -r 'echo base64_encode(require "config/secrets/prod/prod.decrypt.private.php");'
-To improve performance (i.e. avoid decrypting secrets at runtime), you can decrypt
-your secrets during deployment to the "local" vault:
+ To improve performance (i.e. avoid decrypting secrets at runtime), you can decrypt
+ your secrets during deployment to the "local" vault:
-.. code-block:: terminal
+ .. code-block:: terminal
- $ php bin/console secrets:decrypt-to-local --force --env=prod
+ $ APP_RUNTIME_ENV=prod php bin/console secrets:decrypt-to-local --force
-This will write all the decrypted secrets into the ``.env.prod.local`` file.
-After doing this, the decryption key does *not* need to remain on the server(s).
+ This will write all the decrypted secrets into the ``.env.prod.local`` file.
+ After doing this, the decryption key does *not* need to remain on the server(s).
Rotating Secrets
----------------
@@ -293,7 +311,7 @@ The secrets system is enabled by default and some of its behavior can be configu
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/framework https://symfony.com/schema/dic/framework/framework-1.0.xsd"
>
-
+
loadFromExtension('framework', [
- 'secrets' => [
- // 'vault_directory' => '%kernel.project_dir%/config/secrets/%kernel.environment%',
- // 'local_dotenv_file' => '%kernel.project_dir%/.env.%kernel.environment%.local',
- // 'decryption_env_var' => 'base64:default::SYMFONY_DECRYPTION_SECRET',
- ],
- ]);
-
-
-.. _`libsodium`: https://pecl.php.net/package/libsodium
-.. _`paragonie/sodium_compat`: https://github.com/paragonie/sodium_compat
+ use Symfony\Config\FrameworkConfig;
+
+ return static function (FrameworkConfig $framework): void {
+ $framework->secrets()
+ // ->vaultDirectory('%kernel.project_dir%/config/secrets/%kernel.environment%')
+ // ->localDotenvFile('%kernel.project_dir%/.env.%kernel.environment%.local')
+ // ->decryptionEnvVar('base64:default::SYMFONY_DECRYPTION_SECRET')
+ ;
+ };
diff --git a/configuration/using_parameters_in_dic.rst b/configuration/using_parameters_in_dic.rst
index 8a19b4ebc39..3cac5d5049c 100644
--- a/configuration/using_parameters_in_dic.rst
+++ b/configuration/using_parameters_in_dic.rst
@@ -1,6 +1,3 @@
-.. index::
- single: Using Parameters within a Dependency Injection Class
-
Using Parameters within a Dependency Injection Class
----------------------------------------------------
@@ -10,8 +7,8 @@ There are special cases such as when you want, for instance, to use the
``%kernel.debug%`` parameter to make the services in your bundle enter
debug mode. For this case there is more work to do in order
to make the system understand the parameter value. By default,
-your parameter ``%kernel.debug%`` will be treated as a
-simple string. Consider the following example::
+your parameter ``%kernel.debug%`` will be treated as a string. Consider the
+following example::
// inside Configuration class
$rootNode
@@ -77,16 +74,16 @@ Now, examine the results to see this closely:
$container->loadFromExtension('my_bundle', [
'logging' => true,
// true, as expected
- )
- ];
+ ]
+ );
$container->loadFromExtension('my_bundle', [
'logging' => "%kernel.debug%",
// true/false (depends on 2nd parameter of Kernel),
// as expected, because %kernel.debug% inside configuration
// gets evaluated before being passed to the extension
- )
- ];
+ ]
+ );
$container->loadFromExtension('my_bundle');
// passes the string "%kernel.debug%".
@@ -104,14 +101,13 @@ be injected with this parameter via the extension as follows::
class Configuration implements ConfigurationInterface
{
- private $debug;
+ private bool $debug;
- public function __construct($debug)
+ public function __construct(private bool $debug)
{
- $this->debug = (bool) $debug;
}
- public function getConfigTreeBuilder()
+ public function getConfigTreeBuilder(): TreeBuilder
{
$treeBuilder = new TreeBuilder('my_bundle');
@@ -138,7 +134,7 @@ And set it in the constructor of ``Configuration`` via the ``Extension`` class::
{
// ...
- public function getConfiguration(array $config, ContainerBuilder $container)
+ public function getConfiguration(array $config, ContainerBuilder $container): Configuration
{
return new Configuration($container->getParameter('kernel.debug'));
}
diff --git a/console.rst b/console.rst
index 8b015737c85..24fab9885da 100644
--- a/console.rst
+++ b/console.rst
@@ -1,6 +1,3 @@
-.. index::
- single: Console; Create commands
-
Console Commands
================
@@ -9,15 +6,106 @@ The Symfony framework provides lots of commands through the ``bin/console`` scri
created with the :doc:`Console component `. You can also
use it to create your own commands.
-The Console: APP_ENV & APP_DEBUG
----------------------------------
+Running Commands
+----------------
+
+Each Symfony application comes with a large set of commands. You can use
+the ``list`` command to view all available commands in the application:
+
+.. code-block:: terminal
+
+ $ php bin/console list
+ ...
+
+ Available commands:
+ about Display information about the current project
+ completion Dump the shell completion script
+ help Display help for a command
+ list List commands
+ assets
+ assets:install Install bundle's web assets under a public directory
+ cache
+ cache:clear Clear the cache
+ ...
+
+.. note::
+
+ ``list`` is the default command, so running ``php bin/console`` is the same.
+
+If you find the command you need, you can run it with the ``--help`` option
+to view the command's documentation:
+
+.. code-block:: terminal
+
+ $ php bin/console assets:install --help
+
+.. note::
+
+ ``--help`` is one of the built-in global options from the Console component,
+ which are available for all commands, including those you can create.
+ To learn more about them, you can read
+ :ref:`this section `.
+
+APP_ENV & APP_DEBUG
+~~~~~~~~~~~~~~~~~~~
Console commands run in the :ref:`environment ` defined in the ``APP_ENV``
variable of the ``.env`` file, which is ``dev`` by default. It also reads the ``APP_DEBUG``
value to turn "debug" mode on or off (it defaults to ``1``, which is on).
To run the command in another environment or debug mode, edit the value of ``APP_ENV``
-and ``APP_DEBUG``.
+and ``APP_DEBUG``. You can also define this env vars when running the
+command, for instance:
+
+.. code-block:: terminal
+
+ # clears the cache for the prod environment
+ $ APP_ENV=prod php bin/console cache:clear
+
+.. _console-completion-setup:
+
+Console Completion
+~~~~~~~~~~~~~~~~~~
+
+If you are using the Bash, Zsh or Fish shell, you can install Symfony's
+completion script to get auto completion when typing commands in the
+terminal. All commands support name and option completion, and some can
+even complete values.
+
+.. image:: /_images/components/console/completion.gif
+ :alt: The terminal completes the command name "secrets:remove" and the argument "SOME_OTHER_SECRET".
+
+First, you have to install the completion script *once*. Run
+``bin/console completion --help`` for the installation instructions for
+your shell.
+
+.. note::
+
+ When using Bash, make sure you installed and setup the "bash completion"
+ package for your OS (typically named ``bash-completion``).
+
+After installing and restarting your terminal, you're all set to use
+completion (by default, by pressing the Tab key).
+
+.. tip::
+
+ Many PHP tools are built using the Symfony Console component (e.g.
+ Composer, PHPstan and Behat). If they are using version 5.4 or higher,
+ you can also install their completion script to enable console completion:
+
+ .. code-block:: terminal
+
+ $ 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
------------------
@@ -29,23 +117,18 @@ want a command to create a user::
// src/Command/CreateUserCommand.php
namespace App\Command;
+ use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
+ // the name of the command is what users type after "php bin/console"
+ #[AsCommand(name: 'app:create-user')]
class CreateUserCommand extends Command
{
- // the name of the command (the part after "bin/console")
- protected static $defaultName = 'app:create-user';
-
- protected function configure()
- {
- // ...
- }
-
- protected function execute(InputInterface $input, OutputInterface $output)
+ protected function execute(InputInterface $input, OutputInterface $output): int
{
- // ... put here the code to run in your command
+ // ... put here the code to create the user
// this method must return an integer number with the "exit status code"
// of the command. You can also use these constants to make code more readable
@@ -57,33 +140,50 @@ want a command to create a user::
// or return this if some error happened during the execution
// (it's equivalent to returning int(1))
// return Command::FAILURE;
+
+ // or return this to indicate incorrect command usage; e.g. invalid options
+ // or missing arguments (it's equivalent to returning int(2))
+ // return Command::INVALID
}
}
-.. versionadded:: 5.1
-
- The ``Command::SUCCESS`` and ``Command::FAILURE`` constants were introduced
- in Symfony 5.1.
-
Configuring the Command
------------------------
+~~~~~~~~~~~~~~~~~~~~~~~
You can optionally define a description, help message and the
-:doc:`input options and arguments `::
+:doc:`input options and arguments ` by overriding the
+``configure()`` method::
+
+ // src/Command/CreateUserCommand.php
// ...
- protected function configure()
+ class CreateUserCommand extends Command
{
- $this
- // the short description shown while running "php bin/console list"
- ->setDescription('Creates a new user.')
-
- // the full command description shown when running the command with
- // the "--help" option
- ->setHelp('This command allows you to create a user...')
- ;
+ // ...
+ protected function configure(): void
+ {
+ $this
+ // the command description shown when running "php bin/console list"
+ ->setDescription('Creates a new user.')
+ // the command help shown when running the command with the "--help" option
+ ->setHelp('This command allows you to create a user...')
+ ;
+ }
}
+.. tip::
+
+ Using the ``#[AsCommand]`` attribute to define a description instead of
+ using the ``setDescription()`` method allows to get the command description without
+ instantiating its class. This makes the ``php bin/console list`` command run
+ much faster.
+
+ If you want to always run the ``list`` command fast, add the ``--short`` option
+ to it (``php bin/console list --short``). This will avoid instantiating command
+ classes, but it won't show any description for commands that use the
+ ``setDescription()`` method instead of the attribute to define the command
+ description.
+
The ``configure()`` method is called automatically at the end of the command
constructor. If your command defines its own constructor, set the properties
first and then call to the parent constructor, to make those properties
@@ -107,7 +207,7 @@ available in the ``configure()`` method::
parent::__construct();
}
- protected function configure()
+ protected function configure(): void
{
$this
// ...
@@ -116,18 +216,39 @@ available in the ``configure()`` method::
}
}
+.. _console_registering-the-command:
+
Registering the Command
------------------------
+~~~~~~~~~~~~~~~~~~~~~~~
+
+You can register the command by adding the ``AsCommand`` attribute to it::
-Symfony commands must be registered as services and :doc:`tagged `
-with the ``console.command`` tag. If you're using the
+ // src/Command/CreateUserCommand.php
+ namespace App\Command;
+
+ use Symfony\Component\Console\Attribute\AsCommand;
+ use Symfony\Component\Console\Command\Command;
+
+ #[AsCommand(
+ name: 'app:create-user',
+ description: 'Creates a new user.',
+ hidden: false,
+ aliases: ['app:add-user']
+ )]
+ class CreateUserCommand extends Command
+ {
+ // ...
+ }
+
+If you can't use PHP attributes, register the command as a service and
+:doc:`tag it ` with the ``console.command`` tag. If you're using the
:ref:`default services.yaml configuration `,
this is already done for you, thanks to :ref:`autoconfiguration `.
-Executing the Command
----------------------
+Running the Command
+~~~~~~~~~~~~~~~~~~~
-After configuring and registering the command, you can execute it in the terminal:
+After configuring and registering the command, you can run it in the terminal:
.. code-block:: terminal
@@ -143,7 +264,7 @@ The ``execute()`` method has access to the output stream to write messages to
the console::
// ...
- protected function execute(InputInterface $input, OutputInterface $output)
+ protected function execute(InputInterface $input, OutputInterface $output): int
{
// outputs multiple lines to the console (adding "\n" at the end of each line)
$output->writeln([
@@ -152,7 +273,7 @@ the console::
'',
]);
- // the value returned by someMethod() can be an iterator (https://secure.php.net/iterator)
+ // the value returned by someMethod() can be an iterator (https://php.net/iterator)
// that generates and returns the messages with the 'yield' PHP keyword
$output->writeln($this->someMethod());
@@ -187,33 +308,51 @@ called "output sections". Create one or more of these sections when you need to
clear and overwrite the output information.
Sections are created with the
-:method:`Symfony\\Component\\Console\\Output\\ConsoleOutput::section` method,
-which returns an instance of
+:method:`ConsoleOutput::section() `
+method, which returns an instance of
:class:`Symfony\\Component\\Console\\Output\\ConsoleSectionOutput`::
+ // ...
+ use Symfony\Component\Console\Output\ConsoleOutputInterface;
+
class MyCommand extends Command
{
- protected function execute(InputInterface $input, OutputInterface $output)
+ protected function execute(InputInterface $input, OutputInterface $output): int
{
+ if (!$output instanceof ConsoleOutputInterface) {
+ throw new \LogicException('This command accepts only an instance of "ConsoleOutputInterface".');
+ }
+
$section1 = $output->section();
$section2 = $output->section();
+
$section1->writeln('Hello');
$section2->writeln('World!');
+ sleep(1);
// Output displays "Hello\nWorld!\n"
// overwrite() replaces all the existing section contents with the given content
$section1->overwrite('Goodbye');
+ sleep(1);
// Output now displays "Goodbye\nWorld!\n"
// clear() deletes all the section contents...
$section2->clear();
+ sleep(1);
// Output now displays "Goodbye\n"
// ...but you can also delete a given number of lines
// (this example deletes the last two lines of the section)
$section1->clear(2);
+ sleep(1);
// Output is now completely empty!
+ // setting the max height of a section will make new lines replace the old ones
+ $section1->setMaxHeight(2);
+ $section1->writeln('Line1');
+ $section1->writeln('Line2');
+ $section1->writeln('Line3');
+
return Command::SUCCESS;
}
}
@@ -227,6 +366,11 @@ 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.
+.. warning::
+
+ Terminals only allow overwriting the visible content, so you must take into
+ account the console height when trying to write/overwrite section contents.
+
Console Input
-------------
@@ -235,7 +379,7 @@ Use input options or arguments to pass information to the command::
use Symfony\Component\Console\Input\InputArgument;
// ...
- protected function configure()
+ protected function configure(): void
{
$this
// configure an argument
@@ -245,7 +389,7 @@ Use input options or arguments to pass information to the command::
}
// ...
- public function execute(InputInterface $input, OutputInterface $output)
+ public function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln([
'User Creator',
@@ -288,18 +432,15 @@ as a service, you can use normal dependency injection. Imagine you have a
class CreateUserCommand extends Command
{
- private $userManager;
-
- public function __construct(UserManager $userManager)
- {
- $this->userManager = $userManager;
-
+ public function __construct(
+ private UserManager $userManager,
+ ){
parent::__construct();
}
// ...
- protected function execute(InputInterface $input, OutputInterface $output)
+ protected function execute(InputInterface $input, OutputInterface $output): int
{
// ...
@@ -326,8 +467,10 @@ command:
This method is executed after ``initialize()`` and before ``execute()``.
Its purpose is to check if some of the options/arguments are missing
and interactively ask the user for those values. This is the last place
- where you can ask for missing options/arguments. After this command,
- missing options/arguments will result in an error.
+ where you can ask for missing required options/arguments. This method is
+ called before validating the input.
+ Note that it will not be called when the command is run without interaction
+ (e.g. when passing the ``--no-interaction`` global option flag).
:method:`Symfony\\Component\\Console\\Command\\Command::execute` *(required)*
This method is executed after ``interact()`` and ``initialize()``.
@@ -353,10 +496,10 @@ console::
class CreateUserCommandTest extends KernelTestCase
{
- public function testExecute()
+ public function testExecute(): void
{
- $kernel = static::createKernel();
- $application = new Application($kernel);
+ self::bootKernel();
+ $application = new Application(self::$kernel);
$command = $application->find('app:create-user');
$commandTester = new CommandTester($command);
@@ -366,8 +509,12 @@ console::
// prefix the key with two dashes when passing options,
// e.g: '--some-option' => 'option_value',
+ // use brackets for testing array value,
+ // e.g: '--some-option' => ['option_value'],
]);
+ $commandTester->assertCommandIsSuccessful();
+
// the output of the command in the console
$output = $commandTester->getDisplay();
$this->assertStringContainsString('Username: Wouter', $output);
@@ -376,23 +523,65 @@ console::
}
}
+If you are using a :doc:`single-command application `,
+call ``setAutoExit(false)`` on it to get the command result in ``CommandTester``.
+
.. tip::
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.
+.. warning::
+
+ When testing commands using the :class:`Symfony\\Component\\Console\\Tester\\ApplicationTester`
+ class, don't forget to disable the auto exit flag::
+
+ $application = new Application();
+ $application->setAutoExit(false);
+
+ $tester = new ApplicationTester($application);
+
+.. warning::
+
+ When testing ``InputOption::VALUE_NONE`` command options, you must pass ``true``
+ to them::
+
+ $commandTester = new CommandTester($command);
+ $commandTester->execute(['--some-option' => true]);
+
.. note::
When using the Console component in a standalone project, use
- :class:`Symfony\\Component\\Console\\Application `
+ :class:`Symfony\\Component\\Console\\Application`
and extend the normal ``\PHPUnit\Framework\TestCase``.
+When testing your commands, it could be useful to understand how your command
+reacts on different settings like the width and the height of the terminal, or
+even the color mode being used. You have access to such information thanks to the
+:class:`Symfony\\Component\\Console\\Terminal` class::
+
+ use Symfony\Component\Console\Terminal;
+
+ $terminal = new Terminal();
+
+ // gets the number of lines available
+ $height = $terminal->getHeight();
+
+ // gets the number of columns available
+ $width = $terminal->getWidth();
+
+ // gets the color mode
+ $colorMode = $terminal->getColorMode();
+
+ // changes the color mode
+ $colorMode = $terminal->setColorMode(AnsiColorMode::Ansi24);
+
Logging Command Errors
----------------------
@@ -402,6 +591,41 @@ registers an :doc:`event subscriber ` to listen to the
:ref:`ConsoleEvents::TERMINATE event ` and adds a log
message whenever a command doesn't finish with the ``0`` `exit status`_.
+Using Events And Handling Signals
+---------------------------------
+
+When a command is running, many events are dispatched, one of them allows to
+react to signals, read more in :doc:`this section `.
+
+Profiling Commands
+------------------
+
+Symfony allows to profile the execution of any command, including yours. First,
+make sure that the :ref:`debug mode ` and the :doc:`profiler `
+are enabled. Then, add the ``--profile`` option when running the command:
+
+.. code-block:: terminal
+
+ $ php bin/console --profile app:my-command
+
+Symfony will now collect data about the command execution, which is helpful to
+debug errors or check other issues. When the command execution is over, the
+profile is accessible through the web page of the profiler.
+
+.. tip::
+
+ If you run the command in verbose mode (adding the ``-v`` option), Symfony
+ will display in the output a clickable link to the command profile (if your
+ terminal supports links). If you run it in debug verbosity (``-vvv``) you'll
+ also see the time and memory consumed by the command.
+
+.. 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
+ profile. Moreover, consider using the ``--limit`` option to only process a few
+ messages to make the profile more readable in the profiler.
+
Learn More
----------
@@ -417,8 +641,11 @@ tools capable of helping you with different tasks:
* :doc:`/components/console/helpers/questionhelper`: interactively ask the user for information
* :doc:`/components/console/helpers/formatterhelper`: customize the output colorization
* :doc:`/components/console/helpers/progressbar`: shows a progress bar
+* :doc:`/components/console/helpers/progressindicator`: shows a progress indicator
* :doc:`/components/console/helpers/table`: displays tabular data as a table
* :doc:`/components/console/helpers/debug_formatter`: provides functions to
output debug information when running an external program
+* :doc:`/components/console/helpers/processhelper`: allows to run processes using ``DebugFormatterHelper``
+* :doc:`/components/console/helpers/cursor`: allows to manipulate the cursor in the terminal
.. _`exit status`: https://en.wikipedia.org/wiki/Exit_status
diff --git a/console/calling_commands.rst b/console/calling_commands.rst
index d7b2c9918d7..dd1f0b12ff9 100644
--- a/console/calling_commands.rst
+++ b/console/calling_commands.rst
@@ -1,51 +1,66 @@
How to Call Other Commands
==========================
-If a command depends on another one being run before it, instead of asking the
-user to remember the order of execution, you can call it directly yourself.
-This is also useful if you want to create a "meta" command that just 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 Doctrine2 proxies, dumping web assets, ...).
+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, ...).
-Calling a command from another one is straightforward::
+Use the :method:`Symfony\\Component\\Console\\Application::doRun`. Then, create
+a new :class:`Symfony\\Component\\Console\\Input\\ArrayInput` with the
+arguments and options you want to pass to the command. The command name must be
+the first argument.
+Eventually, calling the ``doRun()`` method actually runs the command and returns
+the returned code from the command (return value from command ``execute()``
+method)::
+
+ // ...
+ use Symfony\Component\Console\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
- // ...
- protected function execute(InputInterface $input, OutputInterface $output)
+ class CreateUserCommand extends Command
{
- $command = $this->getApplication()->find('demo:greet');
-
- $arguments = [
- 'name' => 'Fabien',
- '--yell' => true,
- ];
+ // ...
- $greetInput = new ArrayInput($arguments);
- $returnCode = $command->run($greetInput, $output);
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $greetInput = new ArrayInput([
+ // the command name is passed as first argument
+ 'command' => 'demo:greet',
+ 'name' => 'Fabien',
+ '--yell' => true,
+ ]);
- // ...
- }
+ // disable interactive behavior for the greet command
+ $greetInput->setInteractive(false);
-First, you :method:`Symfony\\Component\\Console\\Application::find` the
-command you want to execute by passing the command name. Then, you need to create
-a new :class:`Symfony\\Component\\Console\\Input\\ArrayInput` with the arguments
-and options you want to pass to the command.
+ $returnCode = $this->getApplication()->doRun($greetInput, $output);
-Eventually, calling the ``run()`` method actually executes the command and
-returns the returned code from the command (return value from command's
-``execute()`` method).
+ // ...
+ }
+ }
.. tip::
If you want to suppress the output of the executed command, pass a
:class:`Symfony\\Component\\Console\\Output\\NullOutput` as the second
- argument to ``$command->run()``.
+ argument to ``$application->doRun()``.
+
+.. note::
+
+ Using ``doRun()`` instead of ``run()`` prevents autoexiting and allows to
+ return the exit code instead.
+
+ Also, using ``$this->getApplication()->doRun()`` instead of
+ ``$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``
@@ -54,6 +69,6 @@ returns the returned code from the command (return value from command's
.. note::
- Most of the times, calling a command from code that is not executed on the
+ Most of the time, calling a command from code that is not executed on the
command line is not a good idea. The main reason is that the command's
output is optimized for the console and not to be passed to other commands.
diff --git a/console/coloring.rst b/console/coloring.rst
index 3684d71709d..8b6655d6b71 100644
--- a/console/coloring.rst
+++ b/console/coloring.rst
@@ -1,8 +1,10 @@
How to Color and Style the Console Output
=========================================
-By using colors in the command output, you can distinguish different types of
-output (e.g. important messages, titles, comments, etc.).
+Symfony provides an optional :doc:`console style ` to render the
+input and output of commands in a consistent way. If you prefer to apply your
+own style, use the utilities explained in this article to show colors in the command
+output (e.g. to differentiate between important messages, titles, comments, etc.).
.. note::
@@ -40,13 +42,21 @@ It is possible to define your own styles using the
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
// ...
- $outputStyle = new OutputFormatterStyle('red', 'yellow', ['bold', 'blink']);
+ $outputStyle = new OutputFormatterStyle('red', '#ff0', ['bold', 'blink']);
$output->getFormatter()->setStyle('fire', $outputStyle);
$output->writeln('foo>');
-Available foreground and background colors are: ``black``, ``red``, ``green``,
-``yellow``, ``blue``, ``magenta``, ``cyan`` and ``white``.
+Any hex color is supported for foreground and background colors. Besides that, these named colors are supported:
+``black``, ``red``, ``green``, ``yellow``, ``blue``, ``magenta``, ``cyan``, ``white``,
+``gray``, ``bright-red``, ``bright-green``, ``bright-yellow``, ``bright-blue``,
+``bright-magenta``, ``bright-cyan`` and ``bright-white``.
+
+.. note::
+
+ If the terminal doesn't support true colors, the given color is replaced by
+ the nearest color depending on the terminal capabilities. E.g. ``#c0392b`` is
+ degraded to ``#d75f5f`` in 256-color terminals and to ``red`` in 8-color terminals.
And available options are: ``bold``, ``underscore``, ``blink``, ``reverse``
(enables the "reverse video" mode where the background and foreground colors
@@ -56,9 +66,12 @@ commonly used when asking the user to type sensitive information).
You can also set these colors and options directly inside the tag name::
- // green text
+ // using named colors
$output->writeln('foo>');
+ // using hexadecimal colors
+ $output->writeln('foo>');
+
// black text on a cyan background
$output->writeln('foo>');
@@ -87,7 +100,7 @@ you can click on the *"Symfony Homepage"* text to open its URL in your default
browser. Otherwise, you'll see *"Symfony Homepage"* as regular text and the URL
will be lost.
-.. _Cmder: https://cmder.net/
+.. _Cmder: https://github.com/cmderdev/cmder
.. _ConEmu: https://conemu.github.io/
.. _ANSICON: https://github.com/adoxa/ansicon/releases
.. _Mintty: https://mintty.github.io/
diff --git a/console/command_in_controller.rst b/console/command_in_controller.rst
index 6559ae15c0a..74af9e17c15 100644
--- a/console/command_in_controller.rst
+++ b/console/command_in_controller.rst
@@ -1,6 +1,3 @@
-.. index::
- single: Console; How to Call a Command from a controller
-
How to Call a Command from a Controller
=======================================
@@ -8,23 +5,21 @@ The :doc:`Console component documentation ` covers how to
create a console command. This article covers how to use a console command
directly from your controller.
-You may have the need to execute some function that is only available in a
-console command. Usually, you should refactor the command and move some logic
-into a service that can be reused in the controller. However, when the command
-is part of a third-party library, you wouldn't want to modify or duplicate
-their code. Instead, you can execute the command directly.
+You may have the need to call some function that is only available in a console
+command. Usually, you should refactor the command and move some logic into a
+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
overhead.
-Imagine you want to send spooled Swift Mailer messages by
-:doc:`using the swiftmailer:spool:send command `.
-Run this command from inside your controller via::
+Imagine you want to run the ``debug:twig`` from inside your controller::
- // src/Controller/SpoolController.php
+ // src/Controller/DebugTwigController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Console\Application;
@@ -34,19 +29,21 @@ Run this command from inside your controller via::
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\KernelInterface;
- class SpoolController extends AbstractController
+ class DebugTwigController extends AbstractController
{
- public function sendSpool($messages = 10, KernelInterface $kernel)
+ public function debugTwig(KernelInterface $kernel): Response
{
$application = new Application($kernel);
$application->setAutoExit(false);
$input = new ArrayInput([
- 'command' => 'swiftmailer:spool:send',
+ 'command' => 'debug:twig',
// (optional) define the value of command arguments
'fooArgument' => 'barValue',
// (optional) pass options to the command
- '--message-limit' => $messages,
+ '--bar' => 'fooValue',
+ // (optional) pass options without value
+ '--baz' => true,
]);
// You can use NullOutput() if you don't need the output
@@ -64,9 +61,10 @@ Run this command from inside your controller via::
Showing Colorized Command Output
--------------------------------
-By telling the ``BufferedOutput`` it is decorated via the second parameter,
-it will return the Ansi color-coded content. The `SensioLabs AnsiToHtml converter`_
-can be used to convert this to colorful HTML.
+By telling the :class:`Symfony\\Component\\Console\\Output\\BufferedOutput`
+it is decorated via the second parameter, it will return the Ansi color-coded
+content. The `SensioLabs AnsiToHtml converter`_ can be used to convert this to
+colorful HTML.
First, require the package:
@@ -76,7 +74,7 @@ First, require the package:
Now, use it in your controller::
- // src/Controller/SpoolController.php
+ // src/Controller/DebugTwigController.php
namespace App\Controller;
use SensioLabs\AnsiConverter\AnsiToHtmlConverter;
@@ -85,9 +83,9 @@ Now, use it in your controller::
use Symfony\Component\HttpFoundation\Response;
// ...
- class SpoolController extends AbstractController
+ class DebugTwigController extends AbstractController
{
- public function sendSpool($messages = 10)
+ public function sendSpool(int $messages = 10): Response
{
// ...
$output = new BufferedOutput(
diff --git a/console/commands_as_services.rst b/console/commands_as_services.rst
index 9b1e19ac8eb..1393879a1df 100644
--- a/console/commands_as_services.rst
+++ b/console/commands_as_services.rst
@@ -1,6 +1,3 @@
-.. index::
- single: Console; Commands as Services
-
How to Define Commands as Services
==================================
@@ -18,30 +15,28 @@ For example, suppose you want to log something from within your command::
namespace App\Command;
use Psr\Log\LoggerInterface;
+ use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
+ #[AsCommand(name: 'app:sunshine')]
class SunshineCommand extends Command
{
- protected static $defaultName = 'app:sunshine';
- private $logger;
-
- public function __construct(LoggerInterface $logger)
- {
- $this->logger = $logger;
-
+ public function __construct(
+ private LoggerInterface $logger,
+ ) {
// you *must* call the parent constructor
parent::__construct();
}
- protected function configure()
+ protected function configure(): void
{
$this
->setDescription('Good morning!');
}
- protected function execute(InputInterface $input, OutputInterface $output)
+ protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->logger->info('Waking up the sun');
// ...
@@ -52,10 +47,11 @@ For example, suppose you want to log something from within your command::
If you're using the :ref:`default services.yaml configuration `,
the command class will automatically be registered as a service and passed the ``$logger``
-argument (thanks to autowiring). In other words, *just* by creating this class, everything
-works! You can call the ``app:sunshine`` command and start logging.
+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
@@ -67,12 +63,15 @@ works! You can call the ``app:sunshine`` command and start logging.
Lazy Loading
------------
-To make your command lazily loaded, either define its ``$defaultName`` static property::
+To make your command lazily loaded, either define its name using the PHP
+``AsCommand`` attribute::
+
+ use Symfony\Component\Console\Attribute\AsCommand;
+ // ...
+ #[AsCommand(name: 'app:sunshine')]
class SunshineCommand extends Command
{
- protected static $defaultName = 'app:sunshine';
-
// ...
}
@@ -131,6 +130,8 @@ 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
+ the underlying command factory will not be executed.
diff --git a/console/hide_commands.rst b/console/hide_commands.rst
index 814888fe660..44a69d09289 100644
--- a/console/hide_commands.rst
+++ b/console/hide_commands.rst
@@ -4,29 +4,23 @@ How to Hide Console Commands
By default, all console commands are listed when executing the console application
script without arguments or when using the ``list`` command.
-However, sometimes commands are not intended to be executed by end-users; for
+However, sometimes commands are not intended to be run by end-users; for
example, commands for the legacy parts of the application, commands exclusively
-executed through scheduled tasks, etc.
+run through scheduled tasks, etc.
-In those cases, you can define the command as **hidden** by setting the
-``setHidden()`` method to ``true`` in the command configuration::
+In those cases, you can define the command as **hidden** by setting to ``true``
+the ``hidden`` property of the ``AsCommand`` attribute::
// src/Command/LegacyCommand.php
namespace App\Command;
+ use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
+ #[AsCommand(name: 'app:legacy', hidden: true)]
class LegacyCommand extends Command
{
- protected static $defaultName = 'app:legacy';
-
- protected function configure()
- {
- $this
- ->setHidden(true)
- // ...
- ;
- }
+ // ...
}
Hidden commands behave the same as normal commands but they are no longer displayed
diff --git a/console/input.rst b/console/input.rst
index d71d4aaf9f8..7a978687066 100644
--- a/console/input.rst
+++ b/console/input.rst
@@ -21,7 +21,7 @@ and make the ``name`` argument required::
{
// ...
- protected function configure()
+ protected function configure(): void
{
$this
// ...
@@ -42,7 +42,7 @@ You now have access to a ``last_name`` argument in your command::
{
// ...
- protected function execute(InputInterface $input, OutputInterface $output)
+ protected function execute(InputInterface $input, OutputInterface $output): int
{
$text = 'Hi '.$input->getArgument('name');
@@ -134,10 +134,17 @@ how many times in a row the message should be printed::
$this
// ...
->addOption(
+ // this is the name that users must type to pass this option (e.g. --iterations=5)
'iterations',
+ // this is the optional shortcut of the option name, which usually is just a letter
+ // (e.g. `i`, so users pass it as `-i`); use it for commonly used options
+ // or options with long names
null,
+ // this is the type of option (e.g. requires a value, can be passed more than once, etc.)
InputOption::VALUE_REQUIRED,
+ // the option description displayed when showing the command help
'How many times should the message be printed?',
+ // the default value of the option (for those which allow to pass values)
1
)
;
@@ -186,27 +193,28 @@ flag:
;
Note that to comply with the `docopt standard`_, long options can specify their
-values after a white space or an ``=`` sign (e.g. ``--iterations 5`` or
-``--iterations=5``), but short options can only use white spaces or no
+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 white space,
+ 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
command name. For example, ``php bin/console --iterations 5 app:greet Fabien``
is ambiguous; Symfony would interpret ``5`` as the command name. To avoid
this situation, always place options after the command name, or avoid using
a space to separate the option name from its value.
-There are four option variants you can use:
+There are five option variants you can use:
``InputOption::VALUE_IS_ARRAY``
This option accepts multiple values (e.g. ``--dir=/foo --dir=/bar``);
``InputOption::VALUE_NONE``
- Do not accept input for this option (e.g. ``--yell``). This is the default
- behavior of options;
+ Do not accept input for this option (e.g. ``--yell``). The value returned
+ from is a boolean (``false`` if the option is not provided).
+ This is the default behavior of options;
``InputOption::VALUE_REQUIRED``
This value is required (e.g. ``--iterations=5`` or ``-i5``), the option
@@ -216,7 +224,11 @@ There are four option variants you can use:
This option may or may not have a value (e.g. ``--yell`` or
``--yell=loud``).
-You can combine ``VALUE_IS_ARRAY`` with ``VALUE_REQUIRED`` or
+``InputOption::VALUE_NEGATABLE``
+ Accept either the flag (e.g. ``--yell``) or its negation (e.g.
+ ``--no-yell``).
+
+You need to combine ``VALUE_IS_ARRAY`` with ``VALUE_REQUIRED`` or
``VALUE_OPTIONAL`` like this::
$this
@@ -249,7 +261,7 @@ optionally accepts a value, but it's a bit tricky. Consider this example::
)
;
-This option can be used in 3 ways: ``greet --yell``, ``greet yell=louder``,
+This option can be used in 3 ways: ``greet --yell``, ``greet --yell=louder``,
and ``greet``. However, it's hard to distinguish between passing the option
without a value (``greet --yell``) and not passing the option (``greet``).
@@ -299,4 +311,157 @@ 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
+---------------------------------------
+
+If :ref:`Console completion is installed `,
+command and option names will be auto completed by the shell. However, you
+can also implement value completion for the input in your commands. For
+instance, you may want to complete all usernames from the database in the
+``name`` argument of your greet command.
+
+To achieve this, use the 5th argument of ``addArgument()`` or the 6th argument of ``addOption()``::
+
+ // ...
+ use Symfony\Component\Console\Completion\CompletionInput;
+ use Symfony\Component\Console\Completion\CompletionSuggestions;
+
+ class GreetCommand extends Command
+ {
+ // ...
+ protected function configure(): void
+ {
+ $this
+ ->addArgument(
+ 'names',
+ InputArgument::IS_ARRAY,
+ 'Who do you want to greet (separate multiple names with a space)?',
+ null,
+ function (CompletionInput $input): array {
+ // the value the user already typed, e.g. when typing "app:greet Fa" before
+ // pressing Tab, this will contain "Fa"
+ $currentValue = $input->getCompletionValue();
+
+ // get the list of username names from somewhere (e.g. the database)
+ // you may use $currentValue to filter down the names
+ $availableUsernames = ...;
+
+ // then suggested the usernames as values
+ return $availableUsernames;
+ }
+ )
+ ;
+ }
+ }
+
+That's all you need! Assuming users "Fabien" and "Fabrice" exist, pressing
+tab after typing ``app:greet Fa`` will give you these names as a suggestion.
+
+.. tip::
+
+ The shell script is able to handle huge amounts of suggestions and will
+ automatically filter the suggested values based on the existing input
+ from the user. You do not have to implement any filter logic in the
+ command.
+
+ You may use ``CompletionInput::getCompletionValue()`` to get the
+ current input if that helps improving performance (e.g. by reducing the
+ number of rows fetched from the database).
+
+Testing the Completion script
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The Console component comes with a special
+:class:`Symfony\\Component\\Console\\Tester\\CommandCompletionTester` class
+to help you unit test the completion logic::
+
+ // ...
+ use Symfony\Component\Console\Application;
+
+ class GreetCommandTest extends TestCase
+ {
+ public function testComplete(): void
+ {
+ $application = new Application();
+ $application->add(new GreetCommand());
+
+ // create a new tester with the greet command
+ $tester = new CommandCompletionTester($application->get('app:greet'));
+
+ // complete the input without any existing input (the empty string represents
+ // the position of the cursor)
+ $suggestions = $tester->complete(['']);
+ $this->assertSame(['Fabien', 'Fabrice', 'Wouter'], $suggestions);
+
+ // If you filter the values inside your own code (not recommended, unless you
+ // need to improve performance of e.g. a database query), you can test this
+ // by passing the user input
+ $suggestions = $tester->complete(['Fa']);
+ $this->assertSame(['Fabien', 'Fabrice'], $suggestions);
+ }
+ }
+
+.. _console-global-options:
+
+Command Global Options
+----------------------
+
+The Console component adds some predefined options to all commands:
+
+* ``--verbose``: sets the verbosity level (e.g. ``1`` the default, ``2`` and
+ ``3``, or you can use respective shortcuts ``-v``, ``-vv`` and ``-vvv``)
+* ``--silent``: disables all output and interaction, including errors
+* ``--quiet``: disables output and interaction, but errors are still displayed
+* ``--no-interaction``: disables interaction
+* ``--version``: outputs the version number of the console application
+* ``--help``: displays the command help
+* ``--ansi|--no-ansi``: whether to force of disable coloring the output
+
+.. versionadded:: 7.2
+
+ The ``--silent`` option was introduced in Symfony 7.2.
+
+When using the ``FrameworkBundle``, two more options are predefined:
+
+* ``--env``: sets the Kernel configuration environment (defaults to ``APP_ENV``)
+* ``--no-debug``: disables Kernel debug (defaults to ``APP_DEBUG``)
+
+So your custom commands can use them too out-of-the-box.
+
.. _`docopt standard`: http://docopt.org/
diff --git a/console/lazy_commands.rst b/console/lazy_commands.rst
index 553490c845e..487ef32955f 100644
--- a/console/lazy_commands.rst
+++ b/console/lazy_commands.rst
@@ -10,15 +10,25 @@ The traditional way of adding commands to your application is to use
:method:`Symfony\\Component\\Console\\Application::add`, which expects a
``Command`` instance as an argument.
+This approach can have downsides as some commands might be expensive to
+instantiate in which case you may want to lazy-load them. Note however that lazy-loading
+is not absolute. Indeed a few commands such as ``list``, ``help`` or ``_complete`` can
+require to instantiate other commands although they are lazy. For example ``list`` needs
+to get the name and description of all commands, which might require the command to be
+instantiated to get.
+
In order to lazy-load commands, you need to register an intermediate loader
which will be responsible for returning ``Command`` instances::
use App\Command\HeavyCommand;
use Symfony\Component\Console\Application;
+ use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\CommandLoader\FactoryCommandLoader;
$commandLoader = new FactoryCommandLoader([
- 'app:heavy' => function () { return new HeavyCommand(); },
+ // Note that the `list` command will still instantiate that command
+ // in this example.
+ 'app:heavy' => static fn(): Command => new HeavyCommand(),
]);
$application = new Application();
@@ -35,6 +45,28 @@ method accepts any
:class:`Symfony\\Component\\Console\\CommandLoader\\CommandLoaderInterface`
instance so you can use your own implementation.
+Another way to do so is to take advantage of ``Symfony\Component\Console\Command\LazyCommand``::
+
+ use App\Command\HeavyCommand;
+ use Symfony\Component\Console\Application;
+ use Symfony\Component\Console\Command\Command;
+ use Symfony\Component\Console\CommandLoader\FactoryCommandLoader;
+
+ // In this case although the command is instantiated, the underlying command factory
+ // will not be executed unless the command is actually executed or one tries to access
+ // its input definition to know its argument or option inputs.
+ $lazyCommand = new LazyCommand(
+ 'app:heavy',
+ [],
+ 'This is another more complete form of lazy command.',
+ false,
+ static fn (): Command => new HeavyCommand(),
+ );
+
+ $application = new Application();
+ $application->add($lazyCommand);
+ $application->run();
+
Built-in Command Loaders
------------------------
@@ -45,10 +77,11 @@ The :class:`Symfony\\Component\\Console\\CommandLoader\\FactoryCommandLoader`
class provides a way of getting commands lazily loaded as it takes an
array of ``Command`` factories as its only constructor argument::
+ use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\CommandLoader\FactoryCommandLoader;
$commandLoader = new FactoryCommandLoader([
- 'app:foo' => function () { return new FooCommand(); },
+ 'app:foo' => function (): Command { return new FooCommand(); },
'app:bar' => [BarCommand::class, 'create'],
]);
@@ -68,13 +101,13 @@ with command names as keys and service identifiers as values::
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
use Symfony\Component\DependencyInjection\ContainerBuilder;
- $containerBuilder = new ContainerBuilder();
- $containerBuilder->register(FooCommand::class, FooCommand::class);
- $containerBuilder->compile();
+ $container = new ContainerBuilder();
+ $container->register(FooCommand::class, FooCommand::class);
+ $container->compile();
- $commandLoader = new ContainerCommandLoader($containerBuilder, [
+ $commandLoader = new ContainerCommandLoader($container, [
'app:foo' => FooCommand::class,
]);
Like this, executing the ``app:foo`` command will load the ``FooCommand`` service
-by calling ``$containerBuilder->get(FooCommand::class)``.
+by calling ``$container->get(FooCommand::class)``.
diff --git a/console/lockable_trait.rst b/console/lockable_trait.rst
index 1b810c85f15..0f4a4900e17 100644
--- a/console/lockable_trait.rst
+++ b/console/lockable_trait.rst
@@ -1,9 +1,9 @@
-Prevent Multiple Executions of a Console Command
-================================================
+Prevent Running the Same Console Command Multiple Times
+=======================================================
-A simple but effective way to prevent multiple executions of the same command in
-a single server is to use `locks`_. The :doc:`Lock component `
-provides multiple classes to create locks based on the filesystem (:ref:`FlockStore `),
+You can use `locks`_ to prevent the same command from running multiple times on
+the same server. The :doc:`Lock component ` provides multiple
+classes to create locks based on the filesystem (:ref:`FlockStore `),
shared memory (:ref:`SemaphoreStore `) and even databases
and Redis servers.
@@ -22,7 +22,7 @@ that adds two convenient methods to lock and release commands::
// ...
- protected function execute(InputInterface $input, OutputInterface $output)
+ protected function execute(InputInterface $input, OutputInterface $output): int
{
if (!$this->lock()) {
$output->writeln('The command is already running in another process.');
@@ -38,11 +38,33 @@ that adds two convenient methods to lock and release commands::
// if not released explicitly, Symfony releases the lock
// automatically when the execution of the command ends
$this->release();
+
+ return Command::SUCCESS;
+ }
+ }
+
+The LockableTrait will use the ``SemaphoreStore`` if available and will default
+to ``FlockStore`` otherwise. You can override this behavior by setting
+a ``$lockFactory`` property with your own lock factory::
+
+ // ...
+ use Symfony\Component\Console\Command\Command;
+ use Symfony\Component\Console\Command\LockableTrait;
+ use Symfony\Component\Lock\LockFactory;
+
+ class UpdateContentsCommand extends Command
+ {
+ use LockableTrait;
+
+ public function __construct(private LockFactory $lockFactory)
+ {
}
+
+ // ...
}
-.. versionadded:: 5.1
+.. versionadded:: 7.1
- The ``Command::SUCCESS`` constant was introduced in Symfony 5.1.
+ The ``$lockFactory`` property was introduced in Symfony 7.1.
.. _`locks`: https://en.wikipedia.org/wiki/Lock_(computer_science)
diff --git a/console/style.rst b/console/style.rst
index c800e56f976..e1e5df38ffe 100644
--- a/console/style.rst
+++ b/console/style.rst
@@ -1,6 +1,3 @@
-.. index::
- single: Console; Style commands
-
How to Style a Console Command
==============================
@@ -21,7 +18,7 @@ Consider for example the code used to display the title of the following command
{
// ...
- protected function execute(InputInterface $input, OutputInterface $output)
+ protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln([
'Lorem Ipsum Dolor Sit Amet>',
@@ -62,7 +59,7 @@ title of the command::
{
// ...
- protected function execute(InputInterface $input, OutputInterface $output)
+ protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$io->title('Lorem Ipsum Dolor Sit Amet');
@@ -99,6 +96,8 @@ Titling Methods
// ...
+.. _symfony-style-content:
+
Content Methods
~~~~~~~~~~~~~~~
@@ -165,6 +164,37 @@ Content Methods
['foo4' => 'bar4']
);
+:method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::createTable`
+ Creates an instance of :class:`Symfony\\Component\\Console\\Helper\\Table`
+ styled according to the Symfony Style Guide, which allows you to use
+ features such as dynamically appending rows.
+
+:method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::tree`
+ It displays the given nested array as a formatted directory/file tree
+ structure in the console output::
+
+ $io->tree([
+ 'src' => [
+ 'Controller' => [
+ 'DefaultController.php',
+ ],
+ 'Kernel.php',
+ ],
+ 'templates' => [
+ 'base.html.twig',
+ ],
+ ]);
+
+.. versionadded:: 7.3
+
+ The ``SymfonyStyle::tree()`` and the ``SymfonyStyle::createTree()`` methods
+ were introduced in Symfony 7.3.
+
+:method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::createTree`
+ Creates an instance of :class:`Symfony\\Component\\Console\\Helper\\TreeHelper`
+ styled according to the Symfony Style Guide, which allows you to use
+ features such as dynamically nesting nodes.
+
:method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::newLine`
It displays a blank line in the command output. Although it may seem useful,
most of the times you won't need it at all. The reason is that every helper
@@ -213,6 +243,8 @@ Admonition Methods
'Aenean sit amet arcu vitae sem faucibus porta',
]);
+.. _symfony-style-progressbar:
+
Progress Bar Methods
~~~~~~~~~~~~~~~~~~~~
@@ -243,6 +275,22 @@ Progress Bar Methods
$io->progressFinish();
+:method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::progressIterate`
+ If your progress bar loops over an iterable collection, use the
+ ``progressIterate()`` helper::
+
+ $iterable = [1, 2];
+
+ foreach ($io->progressIterate($iterable) as $value) {
+ // ... do some work
+ }
+
+:method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::createProgressBar`
+ Creates an instance of :class:`Symfony\\Component\\Console\\Helper\\ProgressBar`
+ styled according to the Symfony Style Guide.
+
+.. _symfony-style-questions:
+
User Input Methods
~~~~~~~~~~~~~~~~~~
@@ -259,7 +307,7 @@ User Input Methods
In case you need to validate the given value, pass a callback validator as
the third argument::
- $io->ask('Number of workers to start', 1, function ($number) {
+ $io->ask('Number of workers to start', '1', function (string $number): int {
if (!is_numeric($number)) {
throw new \RuntimeException('You must type a number.');
}
@@ -276,7 +324,7 @@ User Input Methods
In case you need to validate the given value, pass a callback validator as
the second argument::
- $io->askHidden('What is your password?', function ($password) {
+ $io->askHidden('What is your password?', function (string $password): string {
if (empty($password)) {
throw new \RuntimeException('Password cannot be empty.');
}
@@ -305,9 +353,24 @@ User Input Methods
$io->choice('Select the queue to analyze', ['queue1', 'queue2', 'queue3'], 'queue1');
+ Finally, you can allow users to select multiple choices. To do so, users must
+ separate each choice with a comma (e.g. typing ``1, 2`` will select choice 1
+ and 2)::
+
+ $io->choice('Select the queue to analyze', ['queue1', 'queue2', 'queue3'], multiSelect: true);
+
+.. _symfony-style-blocks:
+
Result Methods
~~~~~~~~~~~~~~
+.. note::
+
+ If you print any URL it won't be broken/cut, it will be clickable - if the terminal provides it. If the "well
+ formatted output" is more important, you can switch it off::
+
+ $io->getOutputWrapper()->setAllowCutUrls(true);
+
:method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::success`
It displays the given string or array of strings highlighted as a successful
message (with a green background and the ``[OK]`` label). It's meant to be
@@ -325,6 +388,23 @@ Result Methods
'Consectetur adipiscing elit',
]);
+:method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::info`
+ It's similar to the ``success()`` method (the given string or array of strings
+ are displayed with a green background) but the ``[OK]`` label is not prefixed.
+ It's meant to be used once to display the final result of executing the given
+ command, without showing the result as a successful or failed one::
+
+ // use simple strings for short info messages
+ $io->info('Lorem ipsum dolor sit amet');
+
+ // ...
+
+ // consider using arrays when displaying long info messages
+ $io->info([
+ 'Lorem ipsum dolor sit amet',
+ 'Consectetur adipiscing elit',
+ ]);
+
:method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::warning`
It displays the given string or array of strings highlighted as a warning
message (with a red background and the ``[WARNING]`` label). It's meant to be
@@ -359,6 +439,34 @@ Result Methods
'Consectetur adipiscing elit',
]);
+Configuring the Default Styles
+------------------------------
+
+By default, Symfony Styles wrap all contents to avoid having lines of text that
+are too long. The only exception is URLs, which are not wrapped, no matter how
+long they are. This is done to enable clickable URLs in terminals that support them.
+
+If you prefer to wrap all contents, including URLs, use this method::
+
+ // src/Command/GreetCommand.php
+ namespace App\Command;
+
+ // ...
+ use Symfony\Component\Console\Style\SymfonyStyle;
+
+ class GreetCommand extends Command
+ {
+ // ...
+
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $io = new SymfonyStyle($input, $output);
+ $io->getOutputWrapper()->setAllowCutUrls(true);
+
+ // ...
+ }
+ }
+
Defining your Own Styles
------------------------
@@ -391,7 +499,7 @@ of your commands to change their appearance::
{
// ...
- protected function execute(InputInterface $input, OutputInterface $output)
+ protected function execute(InputInterface $input, OutputInterface $output): int
{
// Before
$io = new SymfonyStyle($input, $output);
diff --git a/console/verbosity.rst b/console/verbosity.rst
index c16737c2b61..9910dca0c3d 100644
--- a/console/verbosity.rst
+++ b/console/verbosity.rst
@@ -7,7 +7,10 @@ messages, but you can control their verbosity with the ``-q`` and ``-v`` options
.. code-block:: terminal
- # do not output any message (not even the command result messages)
+ # suppress all output, including errors
+ $ php bin/console some-command --silent
+
+ # suppress all output (even the command result messages) but display errors
$ php bin/console some-command -q
$ php bin/console some-command --quiet
@@ -23,6 +26,10 @@ messages, but you can control their verbosity with the ``-q`` and ``-v`` options
# display all messages (useful to debug errors)
$ php bin/console some-command -vvv
+.. versionadded:: 7.2
+
+ The ``--silent`` option was introduced in Symfony 7.2.
+
The verbosity level can also be controlled globally for all commands with the
``SHELL_VERBOSITY`` environment variable (the ``-q`` and ``-v`` options still
have more precedence over the value of ``SHELL_VERBOSITY``):
@@ -30,6 +37,7 @@ have more precedence over the value of ``SHELL_VERBOSITY``):
===================== ========================= ===========================================
Console option ``SHELL_VERBOSITY`` value Equivalent PHP constant
===================== ========================= ===========================================
+``--silent`` ``-2`` ``OutputInterface::VERBOSITY_SILENT``
``-q`` or ``--quiet`` ``-1`` ``OutputInterface::VERBOSITY_QUIET``
(none) ``0`` ``OutputInterface::VERBOSITY_NORMAL``
``-v`` ``1`` ``OutputInterface::VERBOSITY_VERBOSE``
@@ -49,7 +57,7 @@ level. For example::
{
// ...
- public function execute(InputInterface $input, OutputInterface $output)
+ public function execute(InputInterface $input, OutputInterface $output): int
{
$user = new User(...);
@@ -58,7 +66,7 @@ level. For example::
'Password: '.$input->getArgument('password'),
]);
- // available methods: ->isQuiet(), ->isVerbose(), ->isVeryVerbose(), ->isDebug()
+ // available methods: ->isSilent(), ->isQuiet(), ->isVerbose(), ->isVeryVerbose(), ->isDebug()
if ($output->isVerbose()) {
$output->writeln('User class: '.get_class($user));
}
@@ -68,13 +76,24 @@ level. For example::
'Will only be printed in verbose mode or higher',
OutputInterface::VERBOSITY_VERBOSE
);
+
+ return Command::SUCCESS;
}
}
-When the quiet level is used, all output is suppressed as the default
+.. versionadded:: 7.2
+
+ The ``isSilent()`` method was introduced in Symfony 7.2.
+
+When the silent or quiet level are used, all output is suppressed as the default
:method:`Symfony\\Component\\Console\\Output\\Output::write` method returns
without actually printing.
+.. tip::
+
+ When using the ``silent`` verbosity, errors won't be displayed in the console
+ but they will still be logged through the :doc:`Symfony logger ` integration.
+
.. tip::
The MonologBridge provides a :class:`Symfony\\Bridge\\Monolog\\Handler\\ConsoleHandler`
diff --git a/contributing/code/bc.rst b/contributing/code/bc.rst
index d0c82ab1c1f..497c70fb01d 100644
--- a/contributing/code/bc.rst
+++ b/contributing/code/bc.rst
@@ -4,15 +4,13 @@ Our Backward Compatibility Promise
Ensuring smooth upgrades of your projects is our first priority. That's why
we promise you backward compatibility (BC) for all minor Symfony releases.
You probably recognize this strategy as `Semantic Versioning`_. In short,
-Semantic Versioning means that only major releases (such as 2.0, 3.0 etc.) are
-allowed to break backward compatibility. Minor releases (such as 2.5, 2.6 etc.)
+Semantic Versioning means that only major releases (such as 5.0, 6.0 etc.) are
+allowed to break backward compatibility. Minor releases (such as 5.1, 5.2 etc.)
may introduce new features, but must do so without breaking the existing API of
-that release branch (2.x in the previous example).
+that release branch (5.x in the previous example).
-.. caution::
-
- This promise was introduced with Symfony 2.3 and does not apply to previous
- versions of Symfony.
+We also provide deprecation message triggered in the code base to help you with
+the migration process across major releases.
However, backward compatibility comes in many different flavors. In fact, almost
every change that we make to the framework can potentially break an application.
@@ -32,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
@@ -55,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.
@@ -72,7 +70,7 @@ backward compatibility promise:
+-----------------------------------------------+-----------------------------+
| Type hint against the interface | Yes |
+-----------------------------------------------+-----------------------------+
-| Call a method | Yes |
+| Call a method | Yes :ref:`[10] ` |
+-----------------------------------------------+-----------------------------+
| **If you implement the interface and...** | **Then we guarantee BC...** |
+-----------------------------------------------+-----------------------------+
@@ -91,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
@@ -114,13 +112,13 @@ covered by our backward compatibility promise:
+-----------------------------------------------+-----------------------------+
| Access a public property | Yes |
+-----------------------------------------------+-----------------------------+
-| Call a public method | Yes |
+| Call a public method | Yes :ref:`[10] ` |
+-----------------------------------------------+-----------------------------+
| **If you extend the class and...** | **Then we guarantee BC...** |
+-----------------------------------------------+-----------------------------+
| Access a protected property | Yes |
+-----------------------------------------------+-----------------------------+
-| Call a protected method | Yes |
+| Call a protected method | Yes :ref:`[10] ` |
+-----------------------------------------------+-----------------------------+
| Override a public property | Yes |
+-----------------------------------------------+-----------------------------+
@@ -148,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.
@@ -190,12 +188,12 @@ Changing Interfaces
This table tells you which changes you are allowed to do when working on
Symfony's interfaces:
-============================================== ==============
-Type of Change Change Allowed
-============================================== ==============
+============================================== ============== ===============
+Type of Change Change Allowed Notes
+============================================== ============== ===============
Remove entirely No
Change name or namespace No
-Add parent interface Yes [2]_
+Add parent interface Yes :ref:`[2] `
Remove parent interface No
**Methods**
Add method No
@@ -204,14 +202,14 @@ Change name No
Move to parent interface Yes
Add argument without a default value No
Add argument with a default value No
-Remove argument No [3]_
+Remove argument No :ref:`[3] `
Add default value to an argument No
Remove default value of an argument No
Add type hint to an argument No
Remove type hint of an argument No
Change argument type No
Add return type No
-Remove return type No [9]_
+Remove return type No :ref:`[9] `
Change return type No
**Static Methods**
Turn non static into static No
@@ -219,8 +217,8 @@ Turn static into non static No
**Constants**
Add constant Yes
Remove constant No
-Change value of a constant Yes [1]_ [5]_
-============================================== ==============
+Change value of a constant Yes :ref:`[1] ` :ref:`[5] `
+============================================== ============== ===============
Changing Classes
~~~~~~~~~~~~~~~~
@@ -228,102 +226,110 @@ Changing Classes
This table tells you which changes you are allowed to do when working on
Symfony's classes:
-================================================== ==============
-Type of Change Change Allowed
-================================================== ==============
-Remove entirely No
-Make final No [6]_
-Make abstract No
-Change name or namespace No
-Change parent class Yes [4]_
-Add interface Yes
-Remove interface No
+======================================================================== ============== ===============
+Type of Change Change Allowed Notes
+======================================================================== ============== ===============
+Remove entirely No
+Make final No :ref:`[6] `
+Make abstract No
+Change name or namespace No
+Change parent class Yes :ref:`[4] `
+Add interface Yes
+Remove interface No
**Public Properties**
-Add public property Yes
-Remove public property No
-Reduce visibility No
-Move to parent class Yes
+Add public property Yes
+Remove public property No
+Reduce visibility No
+Move to parent class Yes
**Protected Properties**
-Add protected property Yes
-Remove protected property No [7]_
-Reduce visibility No [7]_
-Make public No [7]_
-Move to parent class Yes
+Add protected property Yes
+Remove protected property No :ref:`[7] `
+Reduce visibility No :ref:`[7] `
+Make public No :ref:`[7] `
+Move to parent class Yes
**Private Properties**
-Add private property Yes
-Make public or protected Yes
-Remove private property Yes
+Add private property Yes
+Make public or protected Yes
+Remove private property Yes
**Constructors**
-Add constructor without mandatory arguments Yes [1]_
-Remove constructor No
-Reduce visibility of a public constructor No
-Reduce visibility of a protected constructor No [7]_
-Move to parent class Yes
+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] `
+Move to parent class Yes
**Destructors**
-Add destructor Yes
-Remove destructor No
-Move to parent class Yes
+Add destructor Yes
+Remove destructor No
+Move to parent class Yes
**Public Methods**
-Add public method Yes
-Remove public method No
-Change name No
-Reduce visibility No
-Make final No [6]_
-Move to parent class Yes
-Add argument without a default value No
-Add argument with a default value No [7]_ [8]_
-Remove argument No [3]_
-Add default value to an argument No [7]_ [8]_
-Remove default value of an argument No
-Add type hint to an argument No [7]_ [8]_
-Remove type hint of an argument No [7]_ [8]_
-Change argument type No [7]_ [8]_
-Add return type No [7]_ [8]_
-Remove return type No [7]_ [8]_ [9]_
-Change return type No [7]_ [8]_
+Add public method Yes
+Remove public method No
+Change name No
+Reduce visibility No
+Make final No :ref:`[6] `
+Move to parent class Yes
+:ref:`Add argument without a default value ` No
+:ref:`Add argument with a default value ` No :ref:`[7] ` :ref:`[8] `
+Remove argument No :ref:`[3] `
+Add default value to an argument No :ref:`[7] ` :ref:`[8] `
+Remove default value of an argument No
+Add type hint to an argument No :ref:`[7] ` :ref:`[8] `
+Remove type hint of an argument No :ref:`[7] ` :ref:`[8] `
+Change argument type No :ref:`[7] ` :ref:`[8] `
+Add return type No :ref:`[7] ` :ref:`[8] `
+Remove return type No :ref:`[7] ` :ref:`[8] ` :ref:`[9] `
+Change return type No :ref:`[7] ` :ref:`[8] `
**Protected Methods**
-Add protected method Yes
-Remove protected method No [7]_
-Change name No [7]_
-Reduce visibility No [7]_
-Make final No [6]_
-Make public No [7]_ [8]_
-Move to parent class Yes
-Add argument without a default value No [7]_
-Add argument with a default value No [7]_ [8]_
-Remove argument No [3]_
-Add default value to an argument No [7]_ [8]_
-Remove default value of an argument No [7]_
-Add type hint to an argument No [7]_ [8]_
-Remove type hint of an argument No [7]_ [8]_
-Change argument type No [7]_ [8]_
-Add return type No [7]_ [8]_
-Remove return type No [7]_ [8]_ [9]_
-Change return type No [7]_ [8]_
+Add protected method Yes
+Remove protected method No :ref:`[7] `
+Change name No :ref:`[7] `
+Reduce visibility No :ref:`[7] `
+Make final No :ref:`[6] `
+Make public No :ref:`[7] ` :ref:`[8] `
+Move to parent class Yes
+:ref:`Add argument without a default value ` No
+:ref:`Add argument with a default value ` No :ref:`[7] ` :ref:`[8] `
+Remove argument No :ref:`[3] `
+Add default value to an argument No :ref:`[7] ` :ref:`[8] `
+Remove default value of an argument No :ref:`[7] `
+Add type hint to an argument No :ref:`[7] ` :ref:`[8] `
+Remove type hint of an argument No :ref:`[7] ` :ref:`[8] `
+Change argument type No :ref:`[7] ` :ref:`[8] `
+Add return type No :ref:`[7] ` :ref:`[8] `
+Remove return type No :ref:`[7] ` :ref:`[8] ` :ref:`[9] `
+Change return type No :ref:`[7] ` :ref:`[8] `
**Private Methods**
-Add private method Yes
-Remove private method Yes
-Change name Yes
-Make public or protected Yes
-Add argument without a default value Yes
-Add argument with a default value Yes
-Remove argument Yes
-Add default value to an argument Yes
-Remove default value of an argument Yes
-Add type hint to an argument Yes
-Remove type hint of an argument Yes
-Change argument type Yes
-Add return type Yes
-Remove return type Yes
-Change return type Yes
+Add private method Yes
+Remove private method Yes
+Change name Yes
+Make public or protected Yes
+Add argument without a default value Yes
+Add argument with a default value Yes
+Remove argument Yes
+Add default value to an argument Yes
+Remove default value of an argument Yes
+Add type hint to an argument Yes
+Remove type hint of an argument Yes
+Change argument type Yes
+Add return type Yes
+Remove return type Yes
+Change return type Yes
**Static Methods and Properties**
-Turn non static into static No [7]_ [8]_
-Turn static into non static No
+Turn non static into static No :ref:`[7] ` :ref:`[8] `
+Turn static into non static No
**Constants**
-Add constant Yes
-Remove constant No
-Change value of a constant Yes [1]_ [5]_
-================================================== ==============
+Add constant Yes
+Remove constant No
+Change value of a constant Yes :ref:`[1] ` :ref:`[5] `
+======================================================================== ============== ===============
Changing Traits
~~~~~~~~~~~~~~~
@@ -331,118 +337,212 @@ Changing Traits
This table tells you which changes you are allowed to do when working on
Symfony's traits:
-================================================== ==============
-Type of Change Change Allowed
-================================================== ==============
-Remove entirely No
-Change name or namespace No
-Use another trait Yes
+=============================================================================== ============== ===============
+Type of Change Change Allowed Notes
+=============================================================================== ============== ===============
+Remove entirely No
+Change name or namespace No
+Use another trait Yes
**Public Properties**
-Add public property Yes
-Remove public property No
-Reduce visibility No
-Move to a used trait Yes
+Add public property Yes
+Remove public property No
+Reduce visibility No
+Move to a used trait Yes
**Protected Properties**
-Add protected property Yes
-Remove protected property No
-Reduce visibility No
-Make public No
-Move to a used trait Yes
+Add protected property Yes
+Remove protected property No
+Reduce visibility No
+Make public No
+Move to a used trait Yes
**Private Properties**
-Add private property Yes
-Remove private property No
-Make public or protected Yes
-Move to a used trait Yes
+Add private property Yes
+Remove private property No
+Make public or protected Yes
+Move to a used trait Yes
**Constructors and destructors**
-Have constructor or destructor No
+Have constructor or destructor No
**Public Methods**
-Add public method Yes
-Remove public method No
-Change name No
-Reduce visibility No
-Make final No [6]_
-Move to used trait Yes
-Add argument without a default value No
-Add argument with a default value No
-Remove argument No
-Add default value to an argument No
-Remove default value of an argument No
-Add type hint to an argument No
-Remove type hint of an argument No
-Change argument type No
-Change return type No
+Add public method Yes
+Remove public method No
+Change name No
+Reduce visibility No
+Make final No :ref:`[6] `
+Move to used trait Yes
+:ref:`Add argument without a default value ` No
+:ref:`Add argument with a default value ` No
+Remove argument No
+Add default value to an argument No
+Remove default value of an argument No
+Add type hint to an argument No
+Remove type hint of an argument No
+Change argument type No
+Change return type No
**Protected Methods**
-Add protected method Yes
-Remove protected method No
-Change name No
-Reduce visibility No
-Make final No [6]_
-Make public No [8]_
-Move to used trait Yes
-Add argument without a default value No
-Add argument with a default value No
-Remove argument No
-Add default value to an argument No
-Remove default value of an argument No
-Add type hint to an argument No
-Remove type hint of an argument No
-Change argument type No
-Change return type No
+Add protected method Yes
+Remove protected method No
+Change name No
+Reduce visibility No
+Make final No :ref:`[6] `
+Make public No :ref:`[8] `
+Move to used trait Yes
+:ref:`Add argument without a default value ` No
+:ref:`Add argument with a default value ` No
+Remove argument No
+Add default value to an argument No
+Remove default value of an argument No
+Add type hint to an argument No
+Remove type hint of an argument No
+Change argument type No
+Change return type No
**Private Methods**
-Add private method Yes
-Remove private method No
-Change name No
-Make public or protected Yes
-Move to used trait Yes
-Add argument without a default value No
-Add argument with a default value No
-Remove argument No
-Add default value to an argument No
-Remove default value of an argument No
-Add type hint to an argument No
-Remove type hint of an argument No
-Change argument type No
-Add return type No
-Remove return type No
-Change return type No
+Add private method Yes
+Remove private method No
+Change name No
+Make public or protected Yes
+Move to used trait Yes
+Add argument without a default value No
+Add argument with a default value No
+Remove argument No
+Add default value to an argument No
+Remove default value of an argument No
+Add type hint to an argument No
+Remove type hint of an argument No
+Change argument type No
+Add return type No
+Remove return type No
+Change return type No
**Static Methods and Properties**
-Turn non static into static No
-Turn static into non static No
-================================================== ==============
+Turn non static into static No
+Turn static into non static No
+=============================================================================== ============== ===============
+
+Notes
+~~~~~
+
+.. _note-1:
+
+**[1]** Should be avoided. When done, this change must be documented in the
+UPGRADE file.
+
+.. _note-2:
+
+**[2]** The added parent interface must not introduce any new methods that don't
+exist in the interface already.
+
+.. _note-3:
+
+**[3]** Only the last optional argument(s) of a method may be removed, as PHP
+does not care about additional arguments that you pass to a method.
+
+.. _note-4:
+
+**[4]** When changing the parent class, the original parent class must remain an
+ancestor of the class.
+
+.. _note-5:
+
+**[5]** The value of a constant may only be changed when the constants aren't
+used in configuration (e.g. Yaml and XML files), as these do not support
+constants and have to hardcode the value. For instance, event name constants
+can't change the value without introducing a BC break. Additionally, if a
+constant will likely be used in objects that are serialized, the value of a
+constant should not be changed.
+
+.. _note-6:
+
+**[6]** Allowed using the ``@final`` annotation.
+
+.. _note-7:
+
+**[7]** Allowed if the class is final. Classes that received the ``@final``
+annotation after their first release are considered final in their next major
+version. Changing an argument type is only possible with a parent type. Changing
+a return type is only possible with a child type.
+
+.. _note-8:
+
+**[8]** Allowed if the method is final. Methods that received the ``@final``
+annotation after their first release are considered final in their next major
+version. Changing an argument type is only possible with a parent type. Changing
+a return type is only possible with a child type.
+
+.. _note-9:
+
+**[9]** Allowed for the ``void`` return type.
+
+.. _note-10:
+
+**[10]** Parameter names are only covered by the compatibility promise for
+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
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As you read above, many changes are not allowed because they would represent a
+backward compatibility break. However, we want to be able to improve the code and
+its features over time and that can be done thanks to some strategies that
+allow to still do some unallowed changes in several steps that ensure backward
+compatibility and a smooth upgrade path. Some of them are described in the next
+sections.
+
+.. _add-argument-public-method:
+
+Adding an Argument to a Public Method
+.....................................
+
+Adding a new argument to a public method is possible only if this is the last
+argument of the method.
+
+If that's the case, here is how to do it properly in a minor version:
+
+#. Add the argument as a comment in the signature::
+
+ // the new argument can be optional
+ public function say(string $text, /* bool $stripWhitespace = true */): void
+ {
+ }
+
+ // or required
+ public function say(string $text, /* bool $stripWhitespace */): void
+ {
+ }
+
+#. Document the new argument in a PHPDoc::
+
+ /**
+ * @param bool $stripWhitespace
+ */
-.. [1] Should be avoided. When done, this change must be documented in the
- UPGRADE file.
+#. Use ``func_num_args`` and ``func_get_arg`` to retrieve the argument in the
+ method::
-.. [2] The added parent interface must not introduce any new methods that don't
- exist in the interface already.
+ $stripWhitespace = 2 <= \func_num_args() ? func_get_arg(1) : false;
-.. [3] Only the last optional argument(s) of a method may be removed, as PHP
- does not care about additional arguments that you pass to a method.
+ Note that the default value is ``false`` to keep the current behavior.
-.. [4] When changing the parent class, the original parent class must remain an
- ancestor of the class.
+#. If the argument has a default value that will change the current behavior,
+ warn the user::
-.. [5] The value of a constant may only be changed when the constants aren't
- used in configuration (e.g. Yaml and XML files), as these do not support
- constants and have to hardcode the value. For instance, event name
- constants can't change the value without introducing a BC break.
- Additionally, if a constant will likely be used in objects that are
- serialized, the value of a constant should not be changed.
+ trigger_deprecation('symfony/COMPONENT', 'X.Y', 'Not passing the "bool $stripWhitespace" argument explicitly is deprecated, its default value will change to X in Z.0.');
-.. [6] Allowed using the ``@final`` annotation.
+#. If the argument has no default value, warn the user that is going to be
+ required in the next major version::
-.. [7] Allowed if the class is final. Classes that received the ``@final``
- annotation after their first release are considered final in their
- next major version.
- Changing an argument type is only possible with a parent type.
- Changing a return type is only possible with a child type.
+ if (\func_num_args() < 2) {
+ trigger_deprecation('symfony/COMPONENT', 'X.Y', 'The "%s()" method will have a new "bool $stripWhitespace" argument in version Z.0, not defining it is deprecated.', __METHOD__);
-.. [8] Allowed if the method is final. Methods that received the ``@final``
- annotation after their first release are considered final in their
- next major version.
- Changing an argument type is only possible with a parent type.
- Changing a return type is only possible with a child type.
+ $stripWhitespace = false;
+ } else {
+ $stripWhitespace = func_get_arg(1);
+ }
-.. [9] Allowed for the ``void`` return type.
+#. In the next major version (``X.0``), uncomment the argument, remove the
+ PHPDoc if there is no need for a description, and remove the
+ ``func_get_arg`` code and the warning if any.
.. _`Semantic Versioning`: https://semver.org/
diff --git a/contributing/code/bugs.rst b/contributing/code/bugs.rst
index 271c7f1203b..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.
@@ -14,9 +14,8 @@ Before submitting a bug:
* Double-check the official :doc:`documentation ` to see if you're not misusing the
framework;
-* Ask for assistance on `Stack Overflow`_, on the #support channel of
- `the Symfony Slack`_ or on the ``#symfony`` `IRC channel`_ if you're not sure if
- your issue really is a bug.
+* Ask for assistance on `Stack Overflow`_ or on the #support channel of
+ `the Symfony Slack`_ if you're not sure if your issue really is a bug.
If your problem definitely looks like a bug, report it using the official bug
`tracker`_ and follow some basic rules:
@@ -33,7 +32,10 @@ If your problem definitely looks like a bug, report it using the official bug
* Give as much detail as possible about your environment (OS, PHP version,
Symfony version, enabled extensions, ...);
-* If you want to provide a stack trace you got on an HTML page, be sure to
+* If there was an exception and you would like to report it, it is
+ valuable to provide the :doc:`stack trace
+ ` for that exception.
+ If you want to provide a stack trace you got on an HTML page, be sure to
provide the plain text version, which should appear at the bottom of the
page. *Do not* provide it as a screenshot, since search engines will not be
able to index the text inside them. Same goes for errors encountered in a
@@ -45,7 +47,6 @@ If your problem definitely looks like a bug, report it using the official bug
* *(optional)* Attach a :doc:`patch `.
.. _`Stack Overflow`: https://stackoverflow.com/questions/tagged/symfony
-.. _IRC channel: https://symfony.com/irc
.. _the Symfony Slack: https://symfony.com/slack-invite
.. _tracker: https://github.com/symfony/symfony/issues
.. _ HTML tag: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details
diff --git a/contributing/code/conventions.rst b/contributing/code/conventions.rst
index a62bdd68587..455bc8de0ed 100644
--- a/contributing/code/conventions.rst
+++ b/contributing/code/conventions.rst
@@ -7,8 +7,10 @@ coding standards and conventions used in the core framework to make it more
consistent and predictable. You are encouraged to follow them in your own
code, but you don't need to.
-Method Names
-------------
+.. _method-names:
+
+Naming a Method
+---------------
When an object has a "main" many relation with related "things"
(objects, parameters, ...), the method names are normalized:
@@ -77,19 +79,63 @@ must be used instead (where ``XXX`` is the name of the related thing):
``replaceXXX()``, on the other hand, cannot add new elements. If an unrecognized
key is passed to ``replaceXXX()`` it must throw an exception.
+Writing a CHANGELOG Entry
+-------------------------
+
+When adding a new feature in a minor version or deprecating an existing
+behavior, an entry to the relevant CHANGELOG(s) should be added.
+
+New features and deprecations must be described in a file named
+``CHANGELOG.md`` that should be at the root directory of the modified
+Component, Bridge or Bundle.
+
+The file must be written with the Markdown syntax and follow the following
+conventions:
+
+* The main title is always ``CHANGELOG``;
+
+* Each entry must be added to a minor version section (like ``5.3``) as a list
+ element;
+
+* No third level sections are allowed;
+
+* Messages should follow the :ref:`commit message conventions `:
+ should be short, capitalize the line, do not end with a period, use an
+ imperative verb to start the line;
+
+* New entries must be added on top of the list.
+
+Here is a complete example for reference:
+
+.. code-block:: markdown
+
+ CHANGELOG
+ =========
+
+ 5.3
+ ---
+
+ * Add `MagicConfig` that allows configuring things
+
+.. note::
+
+ The main ``CHANGELOG-*`` files at the ``symfony/symfony`` root directory
+ are automatically generated when releases are prepared and should never be
+ modified manually.
+
.. _contributing-code-conventions-deprecations:
Deprecating Code
----------------
-From time to time, some classes and/or methods are deprecated in the
-framework; that happens when a feature implementation cannot be changed
-because of backward compatibility issues, but we still want to propose a
-"better" alternative. In that case, the old implementation can be **deprecated**.
+From time to time, some classes and/or methods are deprecated in the framework;
+that happens when a feature implementation cannot be changed because of
+backward compatibility issues, but we still want to propose a "better"
+alternative. In that case, the old implementation can be **deprecated**.
Deprecations must only be introduced on the next minor version of the impacted
-component (or bundle, or bridge, or contract).
-They can exceptionally be introduced on previous supported versions if they are critical.
+component (or bundle, or bridge, or contract). They can exceptionally be
+introduced on previous supported versions if they are critical.
A new class (or interface, or trait) cannot be introduced as deprecated, or
contain deprecated methods.
@@ -121,7 +167,7 @@ A deprecation must also be triggered to help people with the migration
trigger_deprecation('symfony/package-name', '5.1', 'The "%s" class is deprecated, use "%s" instead.', Deprecated::class, Replacement::class);
-When deprecating a whole class the ``trigger_error()`` call should be placed
+When deprecating a whole class the ``trigger_deprecation()`` call should be placed
after the use declarations, like in this example from `ServiceRouterLoader`_::
namespace Symfony\Component\Routing\Loader\DependencyInjection;
@@ -135,45 +181,61 @@ after the use declarations, like in this example from `ServiceRouterLoader`_::
*/
class ServiceRouterLoader extends ObjectRouteLoader
-.. _`ServiceRouterLoader`: https://github.com/symfony/symfony/blob/4.4/src/Symfony/Component/Routing/Loader/DependencyInjection/ServiceRouterLoader.php
+The deprecation must be added to the ``CHANGELOG.md`` file of the impacted component:
-The deprecation must be added to the ``CHANGELOG.md`` file of the impacted component::
+.. code-block:: markdown
- 4.4.0
- -----
+ 4.4
+ ---
- * Deprecated the `Deprecated` class, use `Replacement` instead.
+ * Deprecate the `Deprecated` class, use `Replacement` instead
It must also be added to the ``UPGRADE.md`` file of the targeted minor version
-(``UPGRADE-4.4.md`` in our example)::
+(``UPGRADE-4.4.md`` in our example):
+
+.. code-block:: markdown
DependencyInjection
-------------------
- * Deprecated the `Deprecated` class, use `Replacement` instead.
+ * Deprecate the `Deprecated` class, use `Replacement` instead
Finally, its consequences must be added to the ``UPGRADE.md`` file of the next major version
-(``UPGRADE-5.0.md`` in our example)::
+(``UPGRADE-5.0.md`` in our example):
+
+.. code-block:: markdown
DependencyInjection
-------------------
- * Removed the `Deprecated` class, use `Replacement` instead.
+ * Remove the `Deprecated` class, use `Replacement` instead
All these tasks are mandatory and must be done in the same pull request.
Removing Deprecated Code
------------------------
-Removing deprecated code can only be done once every 2 years, on the next major version of the
-impacted component (``master`` branch).
+Removing deprecated code can only be done once every two years, on the next
+major version of the impacted component (``6.0`` branch, ``7.0`` branch, etc.).
+
+When removing deprecated code, the consequences of the deprecation must be added
+to the ``CHANGELOG.md`` file of the impacted component:
-When removing deprecated code, the consequences of the deprecation must be added to the ``CHANGELOG.md`` file
-of the impacted component::
+.. code-block:: markdown
- 5.0.0
- -----
+ 5.0
+ ---
- * Removed the `Deprecated` class, use `Replacement` instead.
+ * Remove the `Deprecated` class, use `Replacement` instead
This task is mandatory and must be done in the same pull request.
+
+Naming Commands and Options
+---------------------------
+
+Commands and their options should be named and described using the English
+imperative mood (i.e. 'run' instead of 'runs', 'list' instead of 'lists'). Using
+the imperative mood is concise and consistent with similar command-line
+interfaces (such as Unix man pages).
+
+.. _`ServiceRouterLoader`: https://github.com/symfony/symfony/blob/4.4/src/Symfony/Component/Routing/Loader/DependencyInjection/ServiceRouterLoader.php
diff --git a/contributing/code/core_team.rst b/contributing/code/core_team.rst
deleted file mode 100644
index 3601d5b3fcd..00000000000
--- a/contributing/code/core_team.rst
+++ /dev/null
@@ -1,200 +0,0 @@
-Symfony Core Team
-=================
-
-The **Symfony Core** team is the group of developers that determine the
-direction and evolution of the Symfony project. Their votes rule if the
-features and patches proposed by the community are approved or rejected.
-
-All the Symfony Core members are long-time contributors with solid technical
-expertise and they have demonstrated a strong commitment to drive the project
-forward.
-
-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.
-
-Core Organization
------------------
-
-Symfony Core members are divided into groups. Each member can only belong to one
-group at a time. The privileges granted to a group are automatically granted to
-all higher priority groups.
-
-The Symfony Core groups, in descending order of priority, are as follows:
-
-1. **Project Leader**
-
-* Elects members in any other group;
-* Merges pull requests in all Symfony repositories.
-
-2. **Mergers Team**
-
-* Merge pull requests on the main Symfony repository.
-
-In addition, there are other groups created to manage specific topics:
-
-**Security Team**
-
-* Manage the whole security process (triaging reported vulnerabilities, fixing
- the reported issues, coordinating the release of security fixes, etc.)
-
-**Recipes Team**
-
-* Manage the recipes in the main and contrib recipe repositories.
-
-**Documentation Team**
-
-* Manage the whole `symfony-docs repository`_.
-
-Active Core Members
-~~~~~~~~~~~~~~~~~~~
-
-* **Project Leader**:
-
- * **Fabien Potencier** (`fabpot`_).
-
-* **Mergers Team** (``@symfony/mergers`` on GitHub):
-
- * **Nicolas Grekas** (`nicolas-grekas`_);
- * **Christophe Coevoet** (`stof`_);
- * **Christian Flothmann** (`xabbuh`_);
- * **Tobias Schultze** (`Tobion`_);
- * **Kévin Dunglas** (`dunglas`_);
- * **Jakub Zalas** (`jakzal`_);
- * **Javier Eguiluz** (`javiereguiluz`_);
- * **Grégoire Pineau** (`lyrixx`_);
- * **Ryan Weaver** (`weaverryan`_);
- * **Robin Chalas** (`chalasr`_);
- * **Maxime Steinhausser** (`ogizanagi`_);
- * **Samuel Rozé** (`sroze`_);
- * **Yonel Ceruto** (`yceruto`_).
-
-* **Security Team** (``@symfony/security`` on GitHub):
-
- * **Fabien Potencier** (`fabpot`_);
- * **Michael Cullum** (`michaelcullum`_).
-
-* **Recipes Team**:
-
- * **Fabien Potencier** (`fabpot`_);
- * **Tobias Nyholm** (`Nyholm`_).
-
-* **Documentation Team** (``@symfony/team-symfony-docs`` on GitHub):
-
- * **Fabien Potencier** (`fabpot`_);
- * **Ryan Weaver** (`weaverryan`_);
- * **Christian Flothmann** (`xabbuh`_);
- * **Wouter De Jong** (`wouterj`_);
- * **Jules Pietri** (`HeahDude`_);
- * **Javier Eguiluz** (`javiereguiluz`_).
- * **Oskar Stark** (`OskarStark`_).
-
-Former Core Members
-~~~~~~~~~~~~~~~~~~~
-
-They are no longer part of the core team, but we are very grateful for all their
-Symfony contributions:
-
-* **Bernhard Schussek** (`webmozart`_);
-* **Abdellatif AitBoudad** (`aitboudad`_);
-* **Romain Neutron** (`romainneutron`_);
-* **Jordi Boggiano** (`Seldaek`_).
-
-Core Membership Application
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-At present, new Symfony Core membership applications are not accepted.
-
-Core Membership Revocation
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-A Symfony Core membership can be revoked for any of the following reasons:
-
-* Refusal to follow the rules and policies stated in this document;
-* Lack of activity for the past six months;
-* Willful negligence or intent to harm the Symfony project;
-* Upon decision of the **Project Leader**.
-
-Should new Symfony Core memberships be accepted in the future, revoked
-members must wait at least 12 months before re-applying.
-
-Code Development Rules
-----------------------
-
-Symfony project development is based on pull requests proposed by any member
-of the Symfony community. Pull request acceptance or rejection is decided based
-on the votes cast by the Symfony Core members.
-
-Pull Request Voting Policy
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-* ``-1`` votes must always be justified by technical and objective reasons;
-
-* ``+1`` votes do not require justification, unless there is at least one
- ``-1`` vote;
-
-* 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
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-A pull request **can be merged** if:
-
-* It is a minor change [1]_;
-
-* Enough time was given for peer reviews;
-
-* At least two **Merger Team** members voted ``+1`` (only one if the submitter
- is part of the Merger team) and no Core member voted ``-1`` (via GitHub
- reviews or as comments).
-
-Pull Request Merging Process
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-All code must be committed to the repository through pull requests, except for
-minor changes [1]_ which can be committed directly to the repository.
-
-**Mergers** must always use the command-line ``gh`` tool provided by the
-**Project Leader** to merge the pull requests.
-
-Release Policy
-~~~~~~~~~~~~~~
-
-The **Project Leader** is also the release manager for every Symfony version.
-
-Symfony Core Rules and Protocol Amendments
-------------------------------------------
-
-The rules described in this document may be amended at anytime at the
-discretion of the **Project Leader**.
-
-.. [1] Minor changes comprise typos, DocBlock fixes, code standards
- violations, and minor CSS, JavaScript and HTML modifications.
-
-.. _`symfony-docs repository`: https://github.com/symfony/symfony-docs
-.. _`fabpot`: https://github.com/fabpot/
-.. _`webmozart`: https://github.com/webmozart/
-.. _`Tobion`: https://github.com/Tobion/
-.. _`nicolas-grekas`: https://github.com/nicolas-grekas/
-.. _`stof`: https://github.com/stof/
-.. _`dunglas`: https://github.com/dunglas/
-.. _`jakzal`: https://github.com/jakzal/
-.. _`Seldaek`: https://github.com/Seldaek/
-.. _`weaverryan`: https://github.com/weaverryan/
-.. _`aitboudad`: https://github.com/aitboudad/
-.. _`xabbuh`: https://github.com/xabbuh/
-.. _`javiereguiluz`: https://github.com/javiereguiluz/
-.. _`lyrixx`: https://github.com/lyrixx/
-.. _`chalasr`: https://github.com/chalasr/
-.. _`ogizanagi`: https://github.com/ogizanagi/
-.. _`Nyholm`: https://github.com/Nyholm
-.. _`sroze`: https://github.com/sroze
-.. _`yceruto`: https://github.com/yceruto
-.. _`michaelcullum`: https://github.com/michaelcullum
-.. _`wouterj`: https://github.com/wouterj
-.. _`HeahDude`: https://github.com/HeahDude
-.. _`OskarStark`: https://github.com/OskarStark
-.. _`romainneutron`: https://github.com/romainneutron
diff --git a/contributing/code/index.rst b/contributing/code/index.rst
index 0bb29d6abc4..b4cf85441b0 100644
--- a/contributing/code/index.rst
+++ b/contributing/code/index.rst
@@ -5,10 +5,10 @@ Contributing Code
:maxdepth: 2
bugs
+ stack_trace
reproducer
pull_requests
maintenance
- core_team
security
tests
bc
diff --git a/contributing/code/license.rst b/contributing/code/license.rst
index 8c7c2fd19db..0a4eaafce0d 100644
--- a/contributing/code/license.rst
+++ b/contributing/code/license.rst
@@ -5,7 +5,7 @@ Symfony Code License
Symfony code is released under `the MIT license`_:
-Copyright (c) 2004-2020 Fabien Potencier
+Copyright (c) 2004-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/contributing/code/maintenance.rst b/contributing/code/maintenance.rst
index 854cd74b219..27e4fd73ea0 100644
--- a/contributing/code/maintenance.rst
+++ b/contributing/code/maintenance.rst
@@ -16,21 +16,23 @@ acceptable changes.
When documentation (or PHPDoc) is not in sync with the code, code behavior
should always be considered as being the correct one.
-Besides bug fixes, other minor changes can be accepted in a patch version:
+To avoid backward compatibility breaks, we tend to be very strict about changes
+accepted for patch versions.
-* **Performance improvement**: Performance improvement should only be accepted
- if the changes are local (located in one class) and only for algorithmic
- issues (any such patches must come with numbers that show a significant
- improvement on real-world code);
+Besides bug fixes, other minor changes might be accepted in a patch version on
+a case by case basis:
-* **Newer versions of PHP**: Fixes that add support for newer versions of
- PHP are acceptable if they don't break the unit test suite;
+* **Newer versions of PHP**: Fixes that add support for newer versions of PHP
+ are acceptable if they don't break the unit test suite, but we never add
+ support for newer PHP features;
* **Newer versions of popular OSes**: Fixes that add support for newer versions
of popular OSes (Linux, MacOS and Windows) are acceptable if they don't break
- the unit test suite;
+ the unit test suite, but we never add support for newer PHP features or newer
+ versions of OSes;
-* **Translations**: Translation updates and additions are accepted;
+* **Translations**: Translation updates and additions are always merged in the
+ oldest maintained branch;
* **External data**: Updates for external data included in Symfony can be
updated (like ICU for instance);
@@ -39,19 +41,43 @@ Besides bug fixes, other minor changes can be accepted in a patch version:
of a dependency is possible, bumping to a major one or increasing PHP
minimal version is not;
+* **Tests**: Tests that increase the code coverage can be added.
+
+The following changes are **generally not accepted** in a patch version, except
+on a case by case basis (mostly when this is related to fixing a security
+issue):
+
+* **Performance improvement**: Performance improvement should only be accepted
+ if the changes are local (located in one class) and only for algorithmic
+ issues (any such patches must come with numbers that show a significant
+ improvement on real-world code);
+
* **Coding standard and refactoring**: Coding standard fixes or code
- refactoring are not recommended but can be accepted for consistency with the
- existing code base, if they are not too invasive, and if merging them on
- master would not lead to complex branch merging;
+ refactoring are almost never accepted except for consistency with the
+ existing code base, if they are not too invasive, and if merging them into
+ higher branches would not lead to complex branch merging.
-* **Tests**: Tests that increase the code coverage can be added.
+* **Adding new classes or non private methods**: While working on a bug fix,
+ never introduce new classes or public/protected methods (or global
+ functions).
+
+* **Adding configuration options**: Introducing new configuration options is
+ never allowed.
+
+* **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 (aka the *master* branch). For instance, the following changes
-are never accepted in a patch version:
+version instead. For instance, the following changes are never accepted in a
+patch version:
* **New features**;
+* **Security hardening**;
+
* **Backward compatibility breaks**: Note that backward compatibility breaks
can be done when fixing a security issue if it would not be possible to fix
it otherwise;
@@ -74,7 +100,7 @@ are never accepted in a patch version:
.. note::
This policy is designed to enable a continuous upgrade path that allows one
- to move forward with newest Symfony versions in the safest way. One should
+ to move forward with the newest Symfony versions in the safest way. One should
be able to move PHP versions, OS or Symfony versions almost independently.
That's the reason why supporting the latest PHP versions or OS features is
considered as bug fixes.
diff --git a/contributing/code/pull_requests.rst b/contributing/code/pull_requests.rst
index be882bfee18..6b40e940dfb 100644
--- a/contributing/code/pull_requests.rst
+++ b/contributing/code/pull_requests.rst
@@ -1,6 +1,12 @@
Proposing a Change
==================
+.. admonition:: Screencast
+ :class: screencast
+
+ Do you prefer video tutorials? Check out the `Contributing Back To Symfony`_
+ screencast series.
+
A pull request, "PR" for short, is the best way to provide a bug fix or to
propose enhancements to Symfony.
@@ -25,7 +31,7 @@ Before working on Symfony, setup a friendly environment with the following
software:
* Git;
-* PHP version 7.2.5 or above.
+* PHP version 8.2 or above.
Configure Git
~~~~~~~~~~~~~
@@ -81,6 +87,8 @@ Get the Symfony source code:
* Fork the `Symfony repository`_ (click on the "Fork" button);
+* Uncheck the "Copy the ``X.Y`` branch only";
+
* After the "forking action" has completed, clone your fork locally
(this will create a ``symfony`` directory):
@@ -93,7 +101,7 @@ Get the Symfony source code:
.. code-block:: terminal
$ cd symfony
- $ git remote add upstream git://github.com/symfony/symfony.git
+ $ git remote add upstream https://github.com/symfony/symfony.git
Check that the current Tests Pass
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -101,12 +109,6 @@ Check that the current Tests Pass
Now that Symfony is installed, check that all unit tests pass for your
environment as explained in the dedicated :doc:`document `.
-.. tip::
-
- If tests are failing, check on `Travis-CI`_ if the same test is
- failing there as well. In that case you do not need to be concerned
- about the test failing locally.
-
.. _step-2-work-on-your-patch:
Step 3: Work on your Pull Request
@@ -124,25 +126,32 @@ Choose the right Branch
Before working on a PR, you must determine on which branch you need to
work:
-* ``3.4``, if you are fixing a bug for an existing feature or want to make a
- change that falls into the :doc:`list of acceptable changes in patch versions
- ` (you may have to choose a higher branch if
- the feature you are fixing was introduced in a later version);
+* If you are fixing a bug for an existing feature or want to make a change
+ that falls into the :doc:`list of acceptable changes in patch versions
+ `, pick the oldest concerned maintained
+ branch (you can find them on the `Symfony releases page`_). E.g. if you
+ found a bug introduced in ``v5.1.10``, you need to work on ``5.4``.
-* ``master``, if you are adding a new feature.
+* ``7.2``, if you are adding a new feature.
The only exception is when a new :doc:`major Symfony version `
- (4.0, 5.0, etc.) comes out every two years. Because of the
+ (5.0, 6.0, etc.) comes out every two years. Because of the
:ref:`special development process ` of those versions,
- you need to use the previous minor version for the features (e.g. use ``3.4``
- instead of ``4.0``, use ``4.4`` instead of ``5.0``, etc.)
+ you need to use the previous minor version for the features (e.g. use ``5.4``
+ instead of ``6.0``, use ``6.4`` instead of ``7.0``, etc.)
.. note::
All bug fixes merged into maintenance branches are also merged into more
recent branches on a regular basis. For instance, if you submit a PR
- for the ``3.4`` branch, the PR will also be applied by the core team on
- the ``master`` branch.
+ for the ``5.4`` branch, the PR will also be applied by the core team on
+ all the ``6.x`` branches that are still maintained.
+
+During the :ref:`stabilization phase `, the development branch is in
+feature freeze. Please help the community prepare for the new version release. If you want to submit a
+new feature pull request, you should target the next version. For example, if ``6.3`` reached feature
+freeze, new features should target ``6.4``. If the ``6.4`` branch does not yet exist, target ``6.3``
+and rebase your pull requests once the branch is created.
Create a Topic Branch
~~~~~~~~~~~~~~~~~~~~~
@@ -152,25 +161,25 @@ topic branch:
.. code-block:: terminal
- $ git checkout -b BRANCH_NAME master
+ $ git checkout -b BRANCH_NAME 6.1
-Or, if you want to provide a bug fix for the ``3.4`` branch, first track the remote
-``3.4`` branch locally:
+Or, if you want to provide a bug fix for the ``5.4`` branch, first track the remote
+``5.4`` branch locally:
.. code-block:: terminal
- $ git checkout -t origin/3.4
+ $ git checkout --track origin/5.4
-Then create a new branch off the ``3.4`` branch to work on the bug fix:
+Then create a new branch off the ``5.4`` branch to work on the bug fix:
.. code-block:: terminal
- $ git checkout -b BRANCH_NAME 3.4
+ $ git checkout -b BRANCH_NAME 5.4
.. tip::
- Use a descriptive name for your branch (``ticket_XXX`` where ``XXX`` is the
- ticket number is a good convention for bug fixes).
+ Use a descriptive name for your branch (``fix_XXX`` where ``XXX`` is the
+ issue number is a good convention for bug fixes).
The above checkout commands automatically switch the code to the newly created
branch (check the branch you are working on with ``git branch``).
@@ -224,7 +233,20 @@ in mind the following:
* Never fix coding standards in some existing code as it makes the code review
more difficult;
-* Write good commit messages (see the tip below).
+.. _commit-messages:
+
+* Write good commit messages: Start by a short subject line (the first line),
+ followed by a blank line and a more detailed description.
+
+ The subject line should start with the Component, Bridge or Bundle you are
+ working on in square brackets (``[DependencyInjection]``,
+ ``[FrameworkBundle]``, ...).
+
+ Then, capitalize the sentence, do not end with a period, and use an
+ imperative verb to start.
+
+ Here is a full example of a subject line: ``[MagicBundle] Add `MagicConfig`
+ that allows configuring things``.
.. tip::
@@ -233,16 +255,7 @@ in mind the following:
as defined in `PSR-1`_ and `PSR-2`_.
A status is posted below the pull request description with a summary
- of any problems it detects or any `Travis-CI`_ build failures.
-
-.. tip::
-
- A good commit message is composed of a summary (the first line),
- optionally followed by a blank line and a more detailed description. The
- summary should start with the Component you are working on in square
- brackets (``[DependencyInjection]``, ``[FrameworkBundle]``, ...). Use a
- verb (``fixed ...``, ``added ...``, ...) to start the summary and don't
- add a period at the end.
+ of any problems it detects or any GitHub Actions build failures.
.. _prepare-your-patch-for-submission:
@@ -277,15 +290,15 @@ while to finish your changes):
.. code-block:: terminal
- $ git checkout master
+ $ git checkout 6.x
$ git fetch upstream
- $ git merge upstream/master
+ $ git merge upstream/6.x
$ git checkout BRANCH_NAME
- $ git rebase master
+ $ git rebase 6.x
.. tip::
- Replace ``master`` with the branch you selected previously (e.g. ``3.4``)
+ Replace ``6.x`` with the branch you selected previously (e.g. ``5.4``)
if you are working on a bug fix.
When doing the ``rebase`` command, you might have to fix merge conflicts.
@@ -312,8 +325,8 @@ You can now make a pull request on the ``symfony/symfony`` GitHub repository.
.. tip::
- Take care to point your pull request towards ``symfony:3.4`` if you want
- the core team to pull a bug fix based on the ``3.4`` branch.
+ Take care to point your pull request towards ``symfony:5.4`` if you want
+ the core team to pull a bug fix based on the ``5.4`` branch.
To ease the core team work, always include the modified components in your
pull request message, like in:
@@ -331,7 +344,7 @@ Symfony as quickly as possible.
Some answers to the questions trigger some more requirements:
* If you answer yes to "Bug fix?", check if the bug is already listed in the
- Symfony issues and reference it/them in "Fixed tickets";
+ Symfony issues and reference it/them in "Issues";
* If you answer yes to "New feature?", you must submit a pull request to the
documentation and reference it under the "Doc PR" section;
@@ -366,8 +379,7 @@ because you want early feedback on your work, add an item to todo-list:
- [ ] gather feedback for my changes
As long as you have items in the todo-list, please prefix the pull request
-title with "[WIP]". If you do not yet want to trigger the automated tests,
-you can also set the PR to `draft status`_.
+title with "[WIP]".
In the pull request description, give as much detail as possible about your
changes (don't hesitate to give code examples to illustrate your points). If
@@ -392,22 +404,114 @@ perspective, please join the ``#contribs`` channel on `Symfony Slack`_. If you
receive feedback you find abusive please contact the
:doc:`CARE team `.
-The :doc:`core team ` is responsible for deciding
+The :doc:`core team ` is responsible for deciding
which PR gets merged, so their feedback is the most relevant. So do not feel
pressured to refactor your code immediately when someone provides feedback.
+Automated Feedback
+~~~~~~~~~~~~~~~~~~
+
+There are many automated scripts that will provide feedback on a pull request.
+
+fabbot
+""""""
+
+`fabbot`_ will review code style, check for common typos and make sure the git
+history looks good. If there are any issues, fabbot will often suggest what changes
+that should be done. Most of the time you get a command to run to automatically
+fix the changes.
+
+It is rare, but fabbot could be wrong. One should verify if the suggested changes
+make sense and that they are related to the pull request.
+
+Psalm
+"""""
+
+`Psalm`_ will make a comment on a pull request if it discovers any potential
+type errors. The Psalm errors are not always correct, but each should be reviewed
+and discussed. A pull request should not update the Psalm baseline nor add ``@psalm-``
+annotations.
+
+After the `Psalm phar is installed`_, the analysis can be run locally with:
+
+.. code-block:: terminal
+
+ $ psalm.phar src/Symfony/Component/Workflow
+
+Automated Tests
+~~~~~~~~~~~~~~~
+
+A series of automated tests will run when submitting the pull request.
+These test the code under different conditions, to be sure nothing
+important is broken. Test failures can be unrelated to your changes. If you
+think this is the case, you can check if the target branch has the same
+errors and leave a comment on your PR.
+
+Otherwise, the test failure might be caused by your changes. The following
+test scenarios run on each change:
+
+``PHPUnit / Tests``
+ This job runs on Ubuntu using multiple PHP versions (each in their
+ own job). These jobs run the testsuite just like you would do locally.
+
+ A failure in these jobs often indicates a bug in the code.
+
+``PHPUnit / Tests (high-deps)``
+ This job checks each package (bridge, bundle or component) in ``src/``
+ individually by calling ``composer update`` and ``phpunit`` from inside
+ each package.
+
+ A failure in this job often indicates a missing package in the
+ ``composer.json`` of the failing package (e.g.
+ ``src/Symfony/Bundle/FrameworkBundle/composer.json``).
+
+ This job also runs relevant packages using a "flipped" test (indicated
+ by a ``^`` suffix in the package name). These tests checkout the
+ previous major release (e.g. ``5.4`` for a pull requests on ``6.3``)
+ and run the tests with your branch as dependency.
+
+ A failure in these flipped tests indicate a backwards compatibility
+ break in your changes.
+
+``PHPUnit / Tests (low-deps)``
+ This job also checks each package individually, but then uses
+ ``composer update --prefer-lowest`` before running the tests.
+
+ A failure in this job often indicates a wrong version range or a
+ missing package in the ``composer.json`` of the failing package.
+
+``continuous-integration/appveyor/pr``
+ This job runs on Windows using the x86 architecture and the lowest
+ supported PHP version. All tests first run without extra PHP
+ extensions. Then, all skipped tests are run using all required PHP
+ extensions.
+
+ A failure in this job often indicate that your changes do not support
+ Windows, x86 or PHP with minimal extensions.
+
+``Integration / Tests``
+ Integration tests require other services (e.g. Redis or RabbitMQ) to
+ run. This job only runs the tests in the ``integration`` PHPUnit group.
+
+ A failure in this job indicates a bug in the communication with these
+ services.
+
+``PHPUnit / Tests (experimental)``
+ This job always passes (even with failing tests) and is used by the
+ core team to prepare for the upcoming PHP versions.
+
.. _rework-your-patch:
Rework your Pull Request
~~~~~~~~~~~~~~~~~~~~~~~~
Based on the feedback on the pull request, you might need to rework your
-PR. Before re-submitting the PR, rebase with ``upstream/master`` or
-``upstream/3.4``, don't merge; and force the push to the origin:
+PR. Before re-submitting the PR, rebase with ``upstream/6.x`` or
+``upstream/5.4``, don't merge; and force the push to the origin:
.. code-block:: terminal
- $ git rebase -f upstream/master
+ $ git rebase -f upstream/6.x
$ git push --force origin BRANCH_NAME
.. note::
@@ -423,13 +527,15 @@ before merging.
.. _ProGit: https://git-scm.com/book
.. _GitHub: https://github.com/join
-.. _`GitHub's documentation`: https://help.github.com/github/using-git/ignoring-files
+.. _`GitHub's documentation`: https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
.. _Symfony repository: https://github.com/symfony/symfony
+.. _Symfony releases page: https://symfony.com/releases#maintained-symfony-branches
.. _`documentation repository`: https://github.com/symfony/symfony-docs
.. _`fabbot`: https://fabbot.io
+.. _`Psalm`: https://psalm.dev/
.. _`PSR-1`: https://www.php-fig.org/psr/psr-1/
.. _`PSR-2`: https://www.php-fig.org/psr/psr-2/
.. _`searching on GitHub`: https://github.com/symfony/symfony/issues?q=+is%3Aopen+
.. _`Symfony Slack`: https://symfony.com/slack-invite
-.. _`Travis-CI`: https://travis-ci.org/symfony/symfony
-.. _`draft status`: https://help.github.com/github/collaborating-with-issues-and-pull-requests/about-pull-requests#draft-pull-requests
+.. _`Psalm phar is installed`: https://psalm.dev/docs/running_psalm/installation/
+.. _`Contributing Back To Symfony`: https://symfonycasts.com/screencast/contributing
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/security.rst b/contributing/code/security.rst
index 32401d658f9..ba8949971a4 100644
--- a/contributing/code/security.rst
+++ b/contributing/code/security.rst
@@ -13,6 +13,32 @@ bug tracker and don't publish it publicly. Instead, all security issues must
be sent to **security [at] symfony.com**. Emails sent to this address are
forwarded to the Symfony core team private mailing-list.
+The following issues are not considered security issues and should be handled
+as regular bug fixes (if you have any doubts, don't hesitate to send us an
+email for confirmation):
+
+* Any security issues found in debug tools that must never be enabled in
+ production (including the web profiler or anything enabled when ``APP_DEBUG``
+ is set to ``true`` or ``APP_ENV`` set to anything but ``prod``);
+
+* Any security issues found in classes provided to help for testing that should
+ never be used in production (like for instance mock classes that contain
+ ``Mock`` in their name or classes in the ``Test`` namespace);
+
+* Any fix that can be classified as **security hardening** like route
+ enumeration, login throttling bypasses, denial of service attacks, timing
+ attacks, or lack of ``SensitiveParameter`` attributes.
+
+In any case, the core team has the final decision on which issues are
+considered security vulnerabilities.
+
+Security Bug Bounties
+---------------------
+
+Symfony is an Open-Source project where most of the work is done by volunteers.
+We appreciate that developers are trying to find security issues in Symfony and
+report them responsibly, but we are currently unable to pay bug bounties.
+
Resolving Process
-----------------
@@ -130,7 +156,7 @@ score for Impact is capped at 6. Each area is scored between 0 and 4.*
on an end-users system, or the server that it runs on? (0-4)
* Availability: Is the availability of a service or application affected? Is
it reduced availability or total loss of availability of a service /
- application? Availability includes networked services (e.g., databases) or
+ application? Availability includes networked services (e.g. databases) or
resources such as consumption of network bandwidth, processor cycles, or
disk space. (0-4)
diff --git a/contributing/code/stack_trace.rst b/contributing/code/stack_trace.rst
new file mode 100644
index 00000000000..6fd6987d4e3
--- /dev/null
+++ b/contributing/code/stack_trace.rst
@@ -0,0 +1,189 @@
+Getting a Stack Trace
+=====================
+
+When :doc:`reporting a bug ` for an
+exception or a wrong behavior in code, it is crucial that you provide
+one or several stack traces. To understand why, you first have to
+understand what a stack trace is, and how it can be useful to you as a
+developer, and also to library maintainers.
+
+Anatomy of a Stack Trace
+------------------------
+
+A stack trace is called that way because it allows one to see a trail of
+function calls leading to a point in code since the beginning of the
+program. That point is not necessarily an exception. For instance, you
+can use the native PHP function ``debug_print_backtrace()`` to get such
+a trace. For each line in the trace, you get a file and a function or
+method call, and the line number for that call. This is often of great
+help for understanding the flow of your program and how it can end up in
+unexpected places, such as lines of code where exceptions are thrown.
+
+Stack Traces and Exceptions
+---------------------------
+
+In PHP, every exception comes with its own stack trace, which is
+displayed by default if the exception is not caught. When using Symfony,
+such exceptions go through a custom exception handler, which enhances
+them in various ways before displaying them according to the current
+Server API (CLI or not).
+This means a better way to get a stack trace when you do not need the
+program to continue is to throw an exception, as follows:
+``throw new \Exception();``
+
+Nested Exceptions
+-----------------
+
+When applications get bigger, complexity is often tackled with layers of
+architecture that need to be kept separate. For instance, if you have a
+web application that makes a call to a remote API, it might be good to
+wrap exceptions thrown when making that call with exceptions that have
+special meaning in your domain, and to build appropriate HTTP exceptions
+from those. Exceptions can be nested by using the ``$previous``
+argument that appears in the signature of the ``Exception`` class:
+``public __construct ([ string $message = "" [, int $code = 0 [, Throwable $previous = NULL ]]] )``
+This means that sometimes, when you get an exception from an
+application, you might actually get several of them.
+
+What to look for in a Stack Trace
+---------------------------------
+
+When using a library, you will call code that you did not write. When
+using a framework, it is the opposite: because you follow the
+conventions of the framework, `the framework finds your code and calls
+it `_, and does
+things for you beforehand, like routing or access control.
+Symfony being both a framework and library of components, it calls your
+code and then your code might call it. This means you will always have
+at least 2 parts, very often 3 in your stack traces when using Symfony:
+a part that starts in one of the entry points of the framework
+(``bin/console`` or ``public/index.php`` in most cases), and ends when
+reaching your code, most times in a command or in a controller found under
+``src``. Then, either the exception is thrown in your code or in
+libraries you call. If it is the latter, there should be a third part in
+the stack trace with calls made in files under ``vendor``. Before
+landing in that directory, code goes through numerous review processes
+and CI pipelines, which means it should be less likely to be the source
+of the issue than code from your application, so it is important that
+you focus first on lines starting with ``src``, and look for anything
+suspicious or unexpected, like method calls that are not supposed to
+happen.
+
+Next, you can have a look at what packages are involved. Files under
+``vendor`` are organized by Composer in the following way:
+``vendor/acme/router`` where ``acme`` is the vendor, ``router`` the
+library and ``acme/router`` the Composer package. If you plan on
+reporting the bug, make sure to report it to the library throwing the
+exception. ``composer home acme/router`` should lead you to the right
+place for that. As Symfony is a mono-repository, use ``composer home
+symfony/symfony`` when reporting a bug for any component.
+
+Getting Stack Traces with Symfony
+---------------------------------
+
+Now that we have all this in mind, let us see how to get a stack trace
+with Symfony.
+
+Stack Traces in your Web Browser
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Several things need to be paid attention to when picking a stack trace
+from your development environment through a web browser:
+
+1. Are there several exceptions? If yes, the most interesting one is
+ often exception 1/n which, is shown *last* in the default exception page
+ (it is the one marked as ``exception [1/2]`` in the below example).
+2. Under the "Stack Traces" tab, you will find exceptions in plain
+ text, so that you can easily share them in e.g. bug reports. Make
+ sure to **remove any sensitive information** before doing so.
+3. You may notice there is a logs tab too; this tab does not have to do
+ with stack traces, it only contains logs produced in arbitrary places
+ in your application. They may or may not relate to the exception you
+ are getting, but are not what the term "stack trace" refers to.
+
+.. image:: /_images/contributing/code/stack-trace.gif
+ :alt: The default Symfony exception page with the "Exceptions", "Logs" and "Stack Traces" tabs.
+ :class: with-browser
+
+Since stack traces may contain sensitive data, they should not be
+exposed in production. Getting a stack trace from your production
+environment, although more involving, is still possible with solutions
+that include but are not limited to sending them to an email address
+with Monolog.
+
+Stack Traces in the CLI
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Exceptions might occur when running a Symfony command. By default, only
+the message is shown because it is often enough to understand what is
+going on:
+
+.. code-block:: terminal
+
+ $ php bin/console debug:exception
+
+
+ Command "debug:exception" is not defined.
+
+ Did you mean one of these?
+ debug:autowiring
+ debug:config
+ debug:container
+ debug:event-dispatcher
+ debug:form
+ debug:router
+ debug:translation
+ debug:twig
+
+
+If that is not the case, you can obtain a stack trace by increasing the
+:doc:`verbosity level ` with ``--verbose``:
+
+.. code-block:: terminal
+
+ $ php bin/console --verbose debug:exception
+
+ In Application.php line 644:
+
+ [Symfony\Component\Console\Exception\CommandNotFoundException]
+ Command "debug:exception" is not defined.
+
+ Did you mean one of these?
+ debug:autowiring
+ debug:config
+ debug:container
+ debug:event-dispatcher
+ debug:form
+ debug:router
+ debug:translation
+ debug:twig
+
+
+ Exception trace:
+ at /app/vendor/symfony/console/Application.php:644
+ Symfony\Component\Console\Application->find() at /app/vendor/symfony/framework-bundle/Console/Application.php:116
+ Symfony\Bundle\FrameworkBundle\Console\Application->find() at /app/vendor/symfony/console/Application.php:228
+ Symfony\Component\Console\Application->doRun() at /app/vendor/symfony/framework-bundle/Console/Application.php:82
+ Symfony\Bundle\FrameworkBundle\Console\Application->doRun() at /app/vendor/symfony/console/Application.php:140
+ Symfony\Component\Console\Application->run() at /app/bin/console:42
+
+Stack Traces and API Calls
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When getting an exception from an API, you might not get a stack trace,
+or it might be displayed in a way that is not suitable for sharing.
+Luckily, when in the dev environment, you can obtain a plain text stack
+trace by using the profiler. To find the profile, you can have a look
+at the ``X-Debug-Token-Link`` response headers:
+
+.. code-block:: terminal
+
+ $ curl --head http://localhost:8000/api/posts/1
+ … more headers
+ X-Debug-Token: 110e1e
+ X-Debug-Token-Link: http://localhost:8000/_profiler/110e1e
+ X-Robots-Tag: noindex
+ X-Previous-Debug-Token: 209101
+
+Following that link will lead you to a page very similar to the one
+described above in `Stack Traces in your Web Browser`_.
diff --git a/contributing/code/standards.rst b/contributing/code/standards.rst
index 88e015dc961..ebfde7dfab4 100644
--- a/contributing/code/standards.rst
+++ b/contributing/code/standards.rst
@@ -5,8 +5,8 @@ Symfony code is contributed by thousands of developers around the world. To make
every piece of code look and feel familiar, Symfony defines some coding standards
that all contributions must follow.
-These Symfony coding standards are based on the `PSR-1`_, `PSR-2`_ and `PSR-4`_
-standards, so you may already know most of them.
+These Symfony coding standards are based on the `PSR-1`_, `PSR-2`_, `PSR-4`_
+and `PSR-12`_ standards, so you may already know most of them.
Making your Code Follow the Coding Standards
--------------------------------------------
@@ -47,30 +47,24 @@ short example containing most features described below::
*/
class FooBar
{
- const SOME_CONST = 42;
+ public const SOME_CONST = 42;
- /**
- * @var string
- */
- private $fooBar;
-
- private $qux;
+ private string $fooBar;
/**
- * @param string $dummy Some argument description
+ * @param $dummy some argument description
*/
- public function __construct($dummy, Qux $qux)
- {
+ public function __construct(
+ string $dummy,
+ private Qux $qux,
+ ) {
$this->fooBar = $this->transformText($dummy);
- $this->qux = $qux;
}
/**
- * @return string
- *
* @deprecated
*/
- public function someDeprecatedMethod()
+ public function someDeprecatedMethod(): string
{
trigger_deprecation('symfony/package-name', '5.1', 'The %s() method is deprecated, use Acme\Baz::someMethod() instead.', __METHOD__);
@@ -78,16 +72,13 @@ short example containing most features described below::
}
/**
- * Transforms the input given as first argument.
- *
- * @param bool|string $dummy Some argument description
- * @param array $options An options collection to be used within the transformation
+ * Transforms the input given as the first argument.
*
- * @return string|null The transformed input
+ * @param $options an options collection to be used within the transformation
*
- * @throws \RuntimeException When an invalid option is provided
+ * @throws \RuntimeException when an invalid option is provided
*/
- private function transformText($dummy, array $options = [])
+ private function transformText(bool|string $dummy, array $options = []): ?string
{
$defaultOptions = [
'some_default' => 'values',
@@ -100,16 +91,13 @@ short example containing most features described below::
}
}
- $mergedOptions = array_merge(
- $defaultOptions,
- $options
- );
+ $mergedOptions = array_merge($defaultOptions, $options);
if (true === $dummy) {
return 'something';
}
- if (is_string($dummy)) {
+ if (\is_string($dummy)) {
if ('values' === $mergedOptions['some_default']) {
return substr($dummy, 0, 5);
}
@@ -122,11 +110,8 @@ short example containing most features described below::
/**
* Performs some basic operations for a given value.
- *
- * @param mixed $value Some value to operate against
- * @param bool $theSwitch Some switch to control the method's flow
*/
- private function performOperations($value = null, $theSwitch = false)
+ private function performOperations(mixed $value = null, bool $theSwitch = false): void
{
if (!$theSwitch) {
return;
@@ -162,6 +147,8 @@ Structure
* Use ``return null;`` when a function explicitly returns ``null`` values and
use ``return;`` when the function returns ``void`` values;
+* Do not add the ``void`` return type to methods in tests;
+
* Use braces to indicate control structure body regardless of the number of
statements it contains;
@@ -180,13 +167,34 @@ Structure
to increase readability;
* Declare all the arguments on the same line as the method/function name, no
- matter how many arguments there are;
+ matter how many arguments there are. The only exception are constructor methods
+ using `constructor property promotion`_, where each parameter must be on a new
+ line with `trailing comma`_;
* Use parentheses when instantiating classes regardless of the number of
arguments the constructor has;
* Exception and error message strings must be concatenated using :phpfunction:`sprintf`;
+* Exception and error messages must not contain backticks,
+ even when referring to a technical element (such as a method or variable name).
+ Double quotes must be used at all time:
+
+ .. code-block:: diff
+
+ - Expected `foo` option to be one of ...
+ + Expected "foo" option to be one of ...
+
+* Exception and error messages must start with a capital letter and finish with a dot ``.``;
+
+* Exception, error and deprecation messages containing a class name must
+ use ``get_debug_type()`` instead of ``::class`` to retrieve it:
+
+ .. code-block:: diff
+
+ - throw new \Exception(sprintf('Command "%s" failed.', $command::class));
+ + throw new \Exception(sprintf('Command "%s" failed.', get_debug_type($command)));
+
* Do not use ``else``, ``elseif``, ``break`` after ``if`` and ``case`` conditions
which return or throw something;
@@ -203,11 +211,15 @@ 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 namespaces for all PHP classes and `UpperCamelCase`_ for their names (e.g.
- ``ConsoleLogger``);
+* Use SCREAMING_SNAKE_CASE for constants (e.g. ``InputArgument::IS_ARRAY``);
+
+* Use `UpperCamelCase`_ for enumeration cases (e.g. ``InputArgumentMode::IsArray``);
+
+* Use namespaces for all PHP classes, interfaces, traits and enums and
+ `UpperCamelCase`_ for their names (e.g. ``ConsoleLogger``);
* Prefix all abstract classes with ``Abstract`` except PHPUnit ``*TestCase``.
Please note some early Symfony classes do not follow this convention and
@@ -218,8 +230,17 @@ Naming Conventions
* Suffix traits with ``Trait``;
+* Don't use a dedicated suffix for classes or enumerations (e.g. like ``Class``
+ or ``Enum``), except for the cases listed below.
+
* Suffix exceptions with ``Exception``;
+* Prefix PHP attributes that relate to service configuration with ``As``
+ (e.g. ``#[AsCommand]``, ``#[AsEventListener]``, etc.);
+
+* Prefix PHP attributes that relate to controller arguments with ``Map``
+ (e.g. ``#[MapEntity]``, ``#[MapCurrentUser]``, etc.);
+
* Use UpperCamelCase for naming PHP files (e.g. ``EnvVarProcessor.php``) and
snake case for naming Twig templates and web assets (``section_layout.html.twig``,
``index.scss``);
@@ -253,24 +274,33 @@ Service Naming Conventions
Documentation
~~~~~~~~~~~~~
-* Add PHPDoc blocks for all classes, methods, and functions (though you may
- be asked to remove PHPDoc that do not add value);
+* Add PHPDoc blocks for classes, methods, and functions only when they add
+ relevant information that does not duplicate the name, native type
+ declaration or context (e.g. ``instanceof`` checks);
+
+* Only use annotations and types defined in `the PHPDoc reference`_. In
+ order to improve types for static analysis, the following annotations are
+ also allowed:
+
+ * `Generics`_, with the exception of ``@template-covariant``.
+ * `Conditional return types`_ using the vendor-prefixed ``@psalm-return``;
+ * `Class constants`_;
+ * `Callable types`_;
* Group annotations together so that annotations of the same type immediately
follow each other, and annotations of a different type are separated by a
single blank line;
-* Omit the ``@return`` tag if the method does not return anything;
-
-* The ``@package`` and ``@subpackage`` annotations are not used;
+* Omit the ``@return`` annotation if the method does not return anything;
-* Don't inline PHPDoc blocks, even when they contain just one tag (e.g. don't
- put ``/** {@inheritdoc} */`` in a single line);
+* Don't use one-line PHPDoc blocks on classes, methods and functions, even
+ when they contain just one annotation (e.g. don't put ``/** {@inheritdoc} */``
+ in a single line);
* When adding a new class or when making significant changes to an existing class,
an ``@author`` tag with personal contact information may be added, or expanded.
Please note it is possible to have the personal contact information updated or
- removed per request to the :doc:`core team `.
+ removed per request to the :doc:`core team `.
License
~~~~~~~
@@ -283,8 +313,16 @@ License
.. _`PSR-1`: https://www.php-fig.org/psr/psr-1/
.. _`PSR-2`: https://www.php-fig.org/psr/psr-2/
.. _`PSR-4`: https://www.php-fig.org/psr/psr-4/
+.. _`PSR-12`: https://www.php-fig.org/psr/psr-12/
.. _`identical comparison`: https://www.php.net/manual/en/language.operators.comparison.php
.. _`Yoda conditions`: https://en.wikipedia.org/wiki/Yoda_conditions
.. _`camelCase`: https://en.wikipedia.org/wiki/Camel_case
.. _`UpperCamelCase`: https://en.wikipedia.org/wiki/Camel_case
.. _`snake_case`: https://en.wikipedia.org/wiki/Snake_case
+.. _`constructor property promotion`: https://www.php.net/manual/en/language.oop5.decon.php#language.oop5.decon.constructor.promotion
+.. _`trailing comma`: https://wiki.php.net/rfc/trailing_comma_in_parameter_list
+.. _`the PHPDoc reference`: https://docs.phpdoc.org/3.0/guide/references/phpdoc/index.html
+.. _`Conditional return types`: https://psalm.dev/docs/annotating_code/type_syntax/conditional_types/
+.. _`Class constants`: https://psalm.dev/docs/annotating_code/type_syntax/value_types/#regular-class-constants
+.. _`Callable types`: https://psalm.dev/docs/annotating_code/type_syntax/callable_types/
+.. _`Generics`: https://psalm.dev/docs/annotating_code/templated_annotations/
diff --git a/contributing/code/tests.rst b/contributing/code/tests.rst
index 3ba250a50bb..060e3eda02b 100644
--- a/contributing/code/tests.rst
+++ b/contributing/code/tests.rst
@@ -3,7 +3,7 @@
Running Symfony Tests
=====================
-The Symfony project uses a third-party service which automatically runs tests
+The Symfony project uses a CI (Continuous Integration) service which automatically runs tests
for any submitted :doc:`patch `. If the new code breaks any test,
the pull request will show an error message with a link to the full error details.
@@ -24,6 +24,16 @@ tests, such as Doctrine, Twig and Monolog. To do so,
$ composer update
+.. tip::
+
+ Dependencies might fail to update and in this case Composer might need you to
+ tell it what Symfony version you are working on.
+ To do so set ``COMPOSER_ROOT_VERSION`` variable, e.g.:
+
+ .. code-block:: terminal
+
+ $ COMPOSER_ROOT_VERSION=7.2.x-dev composer update
+
.. _running:
Running the Tests
@@ -55,7 +65,7 @@ what's going on and if the tests are broken because of the new code.
to see colored test results.
.. _`install Composer`: https://getcomposer.org/download/
-.. _Cmder: https://cmder.net/
+.. _Cmder: https://cmder.app/
.. _ConEmu: https://conemu.github.io/
.. _ANSICON: https://github.com/adoxa/ansicon/releases
.. _Mintty: https://mintty.github.io/
diff --git a/contributing/code_of_conduct/care_team.rst b/contributing/code_of_conduct/care_team.rst
index ae342b89999..1b15850da39 100644
--- a/contributing/code_of_conduct/care_team.rst
+++ b/contributing/code_of_conduct/care_team.rst
@@ -19,41 +19,42 @@ the CARE team or if you prefer contact only individual members of the CARE team.
Members
-------
-Here are all the members of the CARE team (in alphabetic order). You can contact
-any of them directly using the contact details below or you can also contact all
-of them at once by emailing **care@symfony.com**:
+Here are all the members of the CARE team (sorted alphabetically by surname).
+You can contact any of them directly using the contact details below or you can
+also contact all of them at once by emailing ** care@symfony.com **.
-* **Emilie Lorenzo**
+* **Timo Bakx**
- * *E-mail*: emilie.lorenzo [at] symfony.com
- * *Twitter*: `@EmilieLorenzo `_
- * *SymfonyConnect*: `emilielorenzo `_
+ * *E-mail*: timobakx [at] gmail.com
+ * *Twitter*: `@TimoBakx `_
+ * *SymfonyConnect*: `timobakx `_
+ * *SymfonySlack*: `@Timo Bakx `_
-* **Michelle Sanver**
+* **Zan Baldwin**
- * *E-mail*: michelle [at] liip.ch
- * *Twitter*: `@michellesanver `_
- * *SymfonyConnect*: `michellesanver `_
+ * *E-mail*: hello [at] zanbaldwin.com
+ * *Twitter*: `@ZanBaldwin `_
+ * *SymfonyConnect*: `zanbaldwin `_
+ * *SymfonySlack*: `@Zan `_
+
+* **Valentine Boineau**
+
+ * *E-mail*: valentine.boineau [at] gmail.com
+ * *Twitter*: `@BoineauV `_
+ * *SymfonyConnect*: `valentineboineau `_
+ * *SymfonySlack*: `@Valentine `_
* **Tobias Nyholm**
* *E-mail*: tobias.nyholm [at] gmail.com
* *Twitter*: `@tobiasnyholm `_
* *SymfonyConnect*: `tobias `_
+ * *SymfonySlack*: `@Tobias Nyholm `_
About the CARE Team
-------------------
-The :doc:`Symfony project leader ` appoints the CARE
+The :doc:`Symfony project leader ` appoints the CARE
team with candidates they see fit. The CARE team will consist of at least
3 people. The team should be representing as many demographics as possible,
ideally from different employers.
-
-CARE Team Transparency Reports
-------------------------------
-
-The CARE team publishes a transparency report at the end of each year:
-
-* `Symfony Code of Conduct Transparency Report 2018`_.
-
-.. _`Symfony Code of Conduct Transparency Report 2018`: https://symfony.com/blog/symfony-code-of-conduct-transparency-report-2018
diff --git a/contributing/code_of_conduct/code_of_conduct.rst b/contributing/code_of_conduct/code_of_conduct.rst
index 16ec8f53eb1..6202fdad424 100644
--- a/contributing/code_of_conduct/code_of_conduct.rst
+++ b/contributing/code_of_conduct/code_of_conduct.rst
@@ -4,12 +4,15 @@ Code of Conduct
Our Pledge
----------
-In the interest of fostering an open and welcoming environment, we as
-contributors and maintainers pledge to making participation in our project and
-our community a harassment-free experience for everyone, regardless of age, body
-size, disability, ethnic origin, gender identity and expression, level of experience,
-education, socio-economic status, nationality, personal appearance,
-religion, or sexual identity and orientation.
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, caste, color, religion, or sexual
+identity and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
Our Standards
-------------
@@ -17,67 +20,115 @@ Our Standards
Examples of behavior that contributes to creating a positive environment
include:
-* Using welcoming and inclusive language
-* Being respectful of differing viewpoints and experiences
-* Gracefully accepting constructive criticism
-* Focusing on what is best for the community
-* Showing empathy towards other community members
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the overall
+ community
-Examples of unacceptable behavior by participants include:
+Examples of unacceptable behavior include:
-* The use of sexualized language or imagery and unwelcome sexual attention or
- advances
-* Trolling, insulting/derogatory comments, and personal or political attacks
+* The use of sexualized language or imagery, and sexual attention or advances of
+ any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
-* Publishing others' private information, such as a physical or electronic
- address, without explicit permission
+* Publishing others’ private information, such as a physical or email address,
+ without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
Our Responsibilities
--------------------
-:doc:`CoC Active Response Ensurers, or CARE`,
-are responsible for clarifying the standards of acceptable
-behavior and are expected to take appropriate and fair corrective action in
-response to any instances of unacceptable behavior.
+:doc:`CoC Active Response Ensurers (CARE) team members `
+are responsible for clarifying and enforcing our standards of acceptable
+behavior and will take appropriate and fair corrective action in response to any
+behavior that they deem inappropriate, threatening, offensive, or harmful.
-CARE team members have the right and responsibility to remove, edit, or
-reject comments, commits, code, wiki edits, issues, and other contributions
-that are not aligned to this Code of Conduct, or to ban temporarily or
-permanently any contributor for other behaviors that they deem inappropriate,
-threatening, offensive, or harmful.
+CARE team members have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
Scope
-----
-This Code of Conduct applies both within project spaces and in public spaces
-when an individual is representing the project or its community. Examples of
-representing a project or community include using an official project e-mail
-address, posting via an official social media account, or acting as an appointed
-representative at an online or offline event. Representation of a project may be
-further defined and clarified by CARE team members.
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
Enforcement
-----------
Instances of abusive, harassing, or otherwise unacceptable behavior
-:doc:`may be reported `
-by contacting the :doc:`CARE team members `.
-All complaints will be reviewed and investigated and will result in a response that
-is deemed necessary and appropriate to the circumstances. The CARE team is
-obligated to maintain confidentiality with regard to the reporter of an incident.
-Further details of specific enforcement policies may be posted separately.
+:doc:`may be reported ` by
+contacting the :doc:`CARE team members `.
+All complaints will be reviewed and investigated promptly and fairly.
+
+CARE team members are obligated to respect the privacy and security of the
+reporter of any incident.
+
+Enforcement Guidelines
+----------------------
+
+The :doc:`CARE team members ` will
+follow these Community Impact Guidelines in determining the consequences for any
+action they deem in violation of this Code of Conduct:
+
+1. Correction
+~~~~~~~~~~~~~
+
+Community Impact: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+Consequence: A private, written warning from a CARE team member, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+2. Warning
+~~~~~~~~~~
-CARE team members who do not follow or enforce the Code of Conduct in good
-faith may face temporary or permanent repercussions as determined by the
-:doc:`core team `.
+Community Impact: A violation through a single incident or series of actions.
+
+Consequence: A warning with consequences for continued behavior. No interaction
+with the people involved, including unsolicited interaction with those enforcing
+the Code of Conduct, for a specified period of time. This includes avoiding
+interactions in community spaces as well as external channels like social media.
+Violating these terms may lead to a temporary or permanent ban.
+
+3. Temporary Ban
+~~~~~~~~~~~~~~~~
+
+Community Impact: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+Consequence: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+4. Permanent Ban
+~~~~~~~~~~~~~~~~
+
+Community Impact: Demonstrating a pattern of violation of community standards,
+including sustained inappropriate behavior, harassment of an individual, or
+aggression toward or disparagement of classes of individuals.
+
+Consequence: A permanent ban from any sort of public interaction within the
+community.
Attribution
-----------
-This Code of Conduct is adapted from the `Contributor Covenant`_, version 1.4,
-available at https://www.contributor-covenant.org/version/1/4/code-of-conduct/
+This Code of Conduct is adapted from the `Contributor Covenant`_, version 2.1,
+available at https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
+
+Community Impact Guidelines were inspired by `Mozilla’s code of conduct enforcement ladder`_.
Related Documents
-----------------
@@ -90,3 +141,4 @@ Related Documents
concrete_example_document
.. _Contributor Covenant: https://www.contributor-covenant.org
+.. _Mozilla’s code of conduct enforcement ladder: https://github.com/mozilla/diversity
diff --git a/contributing/code_of_conduct/concrete_example_document.rst b/contributing/code_of_conduct/concrete_example_document.rst
index ddd1c9b84c8..60ffe2527db 100644
--- a/contributing/code_of_conduct/concrete_example_document.rst
+++ b/contributing/code_of_conduct/concrete_example_document.rst
@@ -21,7 +21,9 @@ Concrete Examples
* Pattern of inappropriate social contact, such as requesting/assuming
inappropriate levels of intimacy with others;
* Continued one-on-one communication after requests to cease;
-* Putting down people based on their technology choices or their work.
+* Putting down people based on their technology choices or their work;
+* Taking photographs of a conference attendee or speaker in the foreground and
+ publishing them without their permission.
The original list is inspired and modified from `geek feminism`_ and
confirmed by experiences from PHPWomen.
diff --git a/contributing/code_of_conduct/reporting_guidelines.rst b/contributing/code_of_conduct/reporting_guidelines.rst
index 63c4e820ce6..a00394bce65 100644
--- a/contributing/code_of_conduct/reporting_guidelines.rst
+++ b/contributing/code_of_conduct/reporting_guidelines.rst
@@ -76,7 +76,7 @@ members will not be included in any communication on the incidents as well as re
created related to the incidents.
CARE team members are expected to inform the CARE team and the reporters
-in case of conflicts on interest and recuse themselves if this is deemed a problem.
+in case of a conflict of interest, and recuse themselves if this is deemed to be a problem.
Appealing the response
----------------------
@@ -93,6 +93,6 @@ Reporting Guidelines derived from those of the `Stumptown Syndicate`_ and the
Adopted by `Symfony`_ organizers on 21 February 2018.
-.. _`Stumptown Syndicate`: http://stumptownsyndicate.org/code-of-conduct/reporting-guidelines/
+.. _`Stumptown Syndicate`: https://github.com/stumpsyn/policies/blob/master/reporting_guidelines.md/
.. _`Django Software Foundation`: https://www.djangoproject.com/conduct/reporting/
.. _`Symfony`: https://symfony.com
diff --git a/contributing/community/mentoring.rst b/contributing/community/mentoring.rst
index 040a6ee90f0..511a61e6e82 100644
--- a/contributing/community/mentoring.rst
+++ b/contributing/community/mentoring.rst
@@ -7,7 +7,7 @@ it might still seem overwhelming - contributing can be complex! For this
purpose we created a dedicated `Symfony Slack`_ channel called `#mentoring`_
to connect new contributors to long-time contributors. This is a great way
to get one-on-one advice on the entire process. These long-time contributors
-do really want to help new contributors - so feel free to ask anything!
+truly want to help new contributors - so feel free to ask anything!
.. _`Symfony Slack`: https://symfony.com/slack-invite
.. _`#mentoring`: https://symfony-devs.slack.com/messages/mentoring
diff --git a/contributing/community/releases.rst b/contributing/community/releases.rst
index 260a357c596..2c5a796e9b5 100644
--- a/contributing/community/releases.rst
+++ b/contributing/community/releases.rst
@@ -7,18 +7,19 @@ release and maintain its different versions.
Symfony releases follow the `semantic versioning`_ strategy and they are
published through a *time-based model*:
-* A new **Symfony patch version** (e.g. 4.4.9, 5.0.9, 5.1.1) comes out roughly every
+* A new **Symfony patch version** (e.g. 5.4.12, 6.1.9) comes out roughly every
month. It only contains bug fixes, so you can safely upgrade your applications;
-* A new **Symfony minor version** (e.g. 4.4, 5.1) comes out every *six months*:
- one in *May* and one in *November*. It contains bug fixes and new features, but
- it doesn't include any breaking change, so you can safely upgrade your applications;
-* A new **Symfony major version** (e.g. 4.0, 5.0, 6.0) comes out every *two years*.
- It can contain breaking changes, so you may need to do some changes in your
- applications before upgrading.
+* A new **Symfony minor version** (e.g. 5.4, 6.0, 6.1) comes out every *six months*:
+ one in *May* and one in *November*. It contains bug fixes and new features,
+ can contain new deprecations but it doesn't include any breaking change,
+ so you can safely upgrade your applications;
+* A new **Symfony major version** (e.g. 5.0, 6.0, 7.0) comes out every *two years*
+ in November of odd years (e.g. 2019, 2021, 2023). It can contain breaking changes,
+ so you may need to do some changes in your applications before upgrading.
.. tip::
- `Subscribe to Symfony Roadmap notifications`_ to receive an email when a new
+ `Subscribe to Symfony Release notifications`_ to receive an email when a new
Symfony version is published or when a Symfony version reaches its end of life.
.. _contributing-release-development:
@@ -26,6 +27,13 @@ published through a *time-based model*:
Development
-----------
+.. note::
+
+ The Symfony project is an open-source community-driven development framework.
+ There is no roadmap written or defined in advance. Every feature request
+ may or may not be developed in future versions based on the community.
+ Symfony Core Team members can help move things forward if there's enough interest.
+
The full development period for any major or minor version lasts six months and
is divided into two phases:
@@ -42,7 +50,7 @@ final release.
.. tip::
- Check out the `Symfony Roadmap`_ to learn more about any specific version.
+ Check out the `Symfony Release`_ to learn more about any specific version.
.. _contributing-release-maintenance:
.. _symfony-versions:
@@ -53,7 +61,7 @@ Maintenance
Starting from the Symfony 3.x branch, the number of minor versions is limited to
five per branch (X.0, X.1, X.2, X.3 and X.4). The last minor version of a branch
-(e.g. 3.4, 4.4, 5.4) is considered a **long-term support version** and the other
+(e.g. 5.4, 6.4) is considered a **long-term support version** and the other
ones are considered **standard versions**:
======================= ===================== ================================
@@ -80,27 +88,49 @@ of Symfony to the next one.
When a feature implementation cannot be replaced with a better one without
breaking backward compatibility, Symfony deprecates the old implementation and
-adds a new preferred one along side. Read the
+adds a new preferred one alongside. Read the
:ref:`conventions ` document to
learn more about how deprecations are handled in Symfony.
.. _major-version-development:
This deprecation policy also requires a custom development process for major
-versions (4.0, 5.0, 6.0, etc.) In those cases, Symfony develops at the same time
-two versions: the new major one (e.g. 4.0) and the latest version of the
-previous branch (e.g. 3.4).
+versions (6.0, 7.0, etc.) In those cases, Symfony develops at the same time
+two versions: the new major one (e.g. 6.0) and the latest version of the
+previous branch (e.g. 5.4).
Both versions have the same new features, but they differ in the deprecated
-features. The oldest version (3.4 in this example) contains all the deprecated
-features whereas the new version (4.0 in this example) removes all of them.
+features. The oldest version (5.4 in this example) contains all the deprecated
+features whereas the new version (6.0 in this example) removes all of them.
-This allows you to upgrade your projects to the latest minor version (e.g. 3.4),
+This allows you to upgrade your projects to the latest minor version (e.g. 5.4),
see all the deprecation messages and fix them. Once you have fixed all those
-deprecations, you can upgrade to the new major version (e.g. 4.0) without
+deprecations, you can upgrade to the new major version (e.g. 6.0) without
effort, because it contains the same features (the only difference are the
deprecated features, which your project no longer uses).
+PHP Compatibility
+-----------------
+
+The **minimum** PHP version is decided for each **major** Symfony version by consensus
+amongst the :doc:`core team ` and documented as
+part of the :ref:`technical requirements for running Symfony applications
+`.
+
+Throughout each Symfony release's support lifetime, all released versions of PHP
+including new major versions will be supported. In this way, the **maximum** supported
+version of PHP for a maintained Symfony release is the latest released
+one that is publicly available.
+
+For out-of-support releases of Symfony, the latest PHP version at time of EOL is the last
+supported PHP version. Newer versions of PHP may or may not function.
+
+.. note::
+
+ By exception to the rule, bumping the minimum **minor** version of PHP is
+ possible for a **minor** Symfony version when this helps fix important
+ issues.
+
Rationale
---------
@@ -132,6 +162,6 @@ period to upgrade. Companies wanting more stability use the LTS versions: a new
version is published every two years and there is a year to upgrade.
.. _`semantic versioning`: https://semver.org/
-.. _`Subscribe to Symfony Roadmap notifications`: https://symfony.com/account/notifications
-.. _`Symfony Roadmap`: https://symfony.com/releases
+.. _`Subscribe to Symfony Release notifications`: https://symfony.com/account/notifications
+.. _`Symfony Release`: https://symfony.com/releases
.. _`professional Symfony support`: https://sensiolabs.com/
diff --git a/contributing/community/review-comments.rst b/contributing/community/review-comments.rst
index 36bad6d7221..5b9bc932205 100644
--- a/contributing/community/review-comments.rst
+++ b/contributing/community/review-comments.rst
@@ -28,8 +28,8 @@ constructive, respectful and helpful reviews and replies.
welcoming place for everyone. **You are free to disagree with
someone's opinions, but don't be disrespectful.**
-First of, accept that many programming decisions are opinions.
-Discuss trade offs, which you prefer, and reach a resolution quickly.
+It’s important to accept that many programming decisions are opinions.
+Discuss trade-offs, which you prefer, and reach a resolution quickly.
It's not about being right or wrong, but using what works.
Tone of Voice
@@ -118,13 +118,13 @@ If a piece of code is in fact wrong, explain why:
* "We only provide integration with very popular projects (e.g. we integrate Bootstrap but not your own CSS framework)"
* "This would require adding lots of code and making lots of changes for a feature that doesn't look so important.
- That could hurt maintaining in the future."
+ That could hurt maintenance in the future."
Asking for Changes
------------------
Rarely something is perfect from the start, while the code itself is good.
-It may not be optimal or conform the Symfony coding style.
+It may not be optimal or conform to the Symfony coding style.
Again, understand the author already spent time on the issue and asking
for (small) changes may be misinterpreted or seen as a personal attack.
@@ -143,13 +143,12 @@ Use words like "Please", "Thank you" and "Could you" instead of making demands;
* "Please use 4 spaces instead of tabs", "This needs be on the previous line";
-During a pull request review you can usually leave more then one comment,
+During a pull request review you can usually leave more than one comment,
you don't have to use "Please" all the time. But it wouldn't hurt.
It may not seem like much, but saying "Thank you" does make others feel
more welcome.
-
Preventing Escalations
----------------------
@@ -158,7 +157,7 @@ In that case, it is better to try to approach the discussion in
a different way, to not escalate further.
If you want someone to mediate, please join the ``#contribs`` channel on `Symfony Slack`_,
-to have a safe environment and keep working together on the common goals.
+to have a safe environment and keep working together on common goals.
Using Humor
-----------
@@ -172,8 +171,8 @@ to the Symfony community.** And don't marginalize someone's problems;
Even if someone's explanation is "inviting to joke about it", it's a real
problem to them. Making jokes about this doesn't help with solving their
-problem and only makes them *feel stupid*. Instead try to discover what
-the problem is really about.
+problem and only makes them *feel stupid*. Instead, try to discover the
+actual problem.
Final Words
-----------
diff --git a/contributing/community/reviews.rst b/contributing/community/reviews.rst
index 342ba431201..06426c03985 100644
--- a/contributing/community/reviews.rst
+++ b/contributing/community/reviews.rst
@@ -59,15 +59,15 @@ The steps for the review are:
#. **Is the Report Complete?**
Good bug reports contain a link to a project (the "reproduction project")
- created with the `Symfony skeleton`_ or the `Symfony website skeleton`_
- that reproduces the bug. If it doesn't, the report should at least contain
- enough information and code samples to reproduce the bug.
+ created with the `Symfony skeleton`_ that reproduces the bug. If it
+ doesn't, the report should at least contain enough information and code
+ samples to reproduce the bug.
#. **Reproduce the Bug**
Download the reproduction project and test whether the bug can be reproduced
on your system. If the reporter did not provide a reproduction project,
- create one based on one `Symfony skeleton`_ (or the `Symfony website skeleton`_).
+ create one based on one `Symfony skeleton`_.
#. **Update the Issue Status**
@@ -109,7 +109,7 @@ to understand the functionality that has been fixed or added and find out
whether the implementation is complete.
It is okay to do partial reviews! If you do a partial review, comment how far
-you got and leave the PR in "Needs Review" state.
+you got and leave the PR in the "Needs Review" state.
Pick a pull request from the `PRs in need of review`_ and follow these steps:
@@ -134,9 +134,9 @@ Pick a pull request from the `PRs in need of review`_ and follow these steps:
#. **Reproduce the Problem**
Read the issue that the pull request is supposed to fix. Reproduce the
- problem on a new project created with the `Symfony skeleton`_ (or the
- `Symfony website skeleton`_) and try to understand why it exists. If the
- linked issue already contains such a project, install it and run it on your system.
+ problem on a new project created with the `Symfony skeleton`_ and try to
+ understand why it exists. If the linked issue already contains such a
+ project, install it and run it on your system.
#. **Review the Code**
@@ -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
@@ -212,7 +212,6 @@ Pick a pull request from the `PRs in need of review`_ and follow these steps:
.. _GitHub: https://github.com
.. _Symfony issue tracker: https://github.com/symfony/symfony/issues
.. _`Symfony skeleton`: https://github.com/symfony/skeleton
-.. _`Symfony website skeleton`: https://github.com/symfony/website-skeleton
.. _create a GitHub account: https://help.github.com/github/getting-started-with-github/signing-up-for-a-new-github-account
.. _bug reports in need of review: https://github.com/symfony/symfony/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue+label%3A%22Bug%22+label%3A%22Status%3A+Needs+Review%22+
.. _PRs in need of review: https://github.com/symfony/symfony/pulls?q=is%3Aopen+is%3Apr+label%3A%22Status%3A+Needs+Review%22
diff --git a/contributing/community/speaker-mentoring.rst b/contributing/community/speaker-mentoring.rst
index d8dc6bdde71..82b25c61f57 100644
--- a/contributing/community/speaker-mentoring.rst
+++ b/contributing/community/speaker-mentoring.rst
@@ -23,7 +23,7 @@ speakers with people who are just taking their first steps in this area:
A good first step might be to give a talk at a local user group to a
smaller crowd that one knows more intimately. A next step could be to
- give a talk at conference in your first language.
+ give a talk at a conference in your first language.
The best way to find people that can review your talk idea or slides is
the `#speaker-mentoring`_ channel on `Symfony Slack`_. There are many
diff --git a/contributing/core_team.rst b/contributing/core_team.rst
new file mode 100644
index 00000000000..7b3d667a14b
--- /dev/null
+++ b/contributing/core_team.rst
@@ -0,0 +1,381 @@
+Symfony Core Team
+=================
+
+The **Symfony Core** team is the group of developers that determine the
+direction and evolution of the Symfony project. Their votes rule if the
+features and patches proposed by the community are approved or rejected.
+
+All the Symfony Core members are long-time contributors with solid technical
+expertise and they have demonstrated a strong commitment to drive the project
+forward.
+
+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.
+
+Core Team Member Role
+---------------------
+
+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 Team Member Responsibilities
+---------------------------------
+
+Core Team members are unpaid volunteers and as such, they are not expected to
+dedicate any specific amount of time on Symfony. They are expected to help the
+project in any way they can. From reviewing pull requests and writing documentation,
+to participating in discussions and helping the community in general. However,
+their involvement is completely voluntary and can be as much or as little as
+they want.
+
+Core Team Communication
+~~~~~~~~~~~~~~~~~~~~~~~
+
+As an open source project, public discussions and documentation is favored
+over private ones. All communication in the Symfony community conforms to
+the :doc:`/contributing/code_of_conduct/code_of_conduct`. Request
+assistance from other Core and CARE team members when getting in situations
+not following the Code of Conduct.
+
+Core Team members are invited in a private Slack channel, for quick
+interactions and private processes (e.g. security issues). Each member
+should feel free to ask for assistance for anything they may encounter.
+Expect no judgement from other team members.
+
+Core Organization
+-----------------
+
+Symfony Core members are divided into groups. Each member can only belong to one
+group at a time. The privileges granted to a group are automatically granted to
+all higher priority groups.
+
+The Symfony Core groups, in descending order of priority, are as follows:
+
+1. **Project Leader**
+
+ * Elects members in any other group;
+ * Merges pull requests in all Symfony repositories.
+
+2. **Mergers Team**
+
+ * Merge pull requests on the main Symfony repository.
+
+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.);
+* **Symfony UX Team**: manages the `UX repositories`_;
+* **Symfony CLI Team**: manages the `CLI repositories`_;
+* **Documentation Team**: manages the whole `symfony-docs repository`_.
+
+Active Core Members
+~~~~~~~~~~~~~~~~~~~
+
+* **Project Leader**:
+
+ * **Fabien Potencier** (`fabpot`_).
+
+* **Mergers Team** (``@symfony/mergers`` on GitHub):
+
+ * **Nicolas Grekas** (`nicolas-grekas`_);
+ * **Christophe Coevoet** (`stof`_);
+ * **Christian Flothmann** (`xabbuh`_);
+ * **Kévin Dunglas** (`dunglas`_);
+ * **Javier Eguiluz** (`javiereguiluz`_);
+ * **Grégoire Pineau** (`lyrixx`_);
+ * **Ryan Weaver** (`weaverryan`_);
+ * **Robin Chalas** (`chalasr`_);
+ * **Yonel Ceruto** (`yceruto`_);
+ * **Tobias Nyholm** (`Nyholm`_);
+ * **Wouter De Jong** (`wouterj`_);
+ * **Alexander M. Turek** (`derrabus`_);
+ * **Jérémy Derussé** (`jderusse`_);
+ * **Oskar Stark** (`OskarStark`_);
+ * **Mathieu Santostefano** (`welcomattic`_);
+ * **Kevin Bond** (`kbond`_);
+ * **Jérôme Tamarelle** (`gromnan`_);
+ * **Berislav Balogović** (`hypemc`_);
+ * **Mathias Arlaud** (`mtarld`_);
+ * **Florent Morselli** (`spomky`_);
+ * **Alexandre Daubois** (`alexandre-daubois`_).
+
+* **Security Team** (``@symfony/security`` on GitHub):
+
+ * **Fabien Potencier** (`fabpot`_);
+ * **Jérémy Derussé** (`jderusse`_).
+
+* **Symfony UX Team** (``@symfony/ux`` on GitHub):
+
+ * **Ryan Weaver** (`weaverryan`_);
+ * **Kevin Bond** (`kbond`_);
+ * **Simon André** (`smnandre`_);
+ * **Hugo Alliaume** (`kocal`_);
+ * **Matheo Daninos** (`webmamba`_).
+
+* **Symfony CLI Team** (``@symfony-cli/core`` on GitHub):
+
+ * **Fabien Potencier** (`fabpot`_);
+ * **Tugdual Saunier** (`tucksaun`_).
+
+* **Documentation Team** (``@symfony/team-symfony-docs`` on GitHub):
+
+ * **Fabien Potencier** (`fabpot`_);
+ * **Ryan Weaver** (`weaverryan`_);
+ * **Christian Flothmann** (`xabbuh`_);
+ * **Wouter De Jong** (`wouterj`_);
+ * **Javier Eguiluz** (`javiereguiluz`_).
+ * **Oskar Stark** (`OskarStark`_).
+
+Former Core Members
+~~~~~~~~~~~~~~~~~~~
+
+They are no longer part of the core team, but we are very grateful for all their
+Symfony contributions:
+
+* **Bernhard Schussek** (`webmozart`_);
+* **Abdellatif AitBoudad** (`aitboudad`_);
+* **Romain Neutron** (`romainneutron`_);
+* **Jordi Boggiano** (`Seldaek`_);
+* **Lukas Kahwe Smith** (`lsmith77`_);
+* **Jules Pietri** (`HeahDude`_);
+* **Jakub Zalas** (`jakzal`_);
+* **Samuel Rozé** (`sroze`_);
+* **Tobias Schultze** (`Tobion`_);
+* **Maxime Steinhausser** (`ogizanagi`_);
+* **Titouan Galopin** (`tgalopin`_);
+* **Michael Cullum** (`michaelcullum`_);
+* **Thomas Calvet** (`fancyweb`_).
+
+Core Membership Application
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+About once a year, the core team discusses the opportunity to invite new members.
+
+Core Membership Revocation
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A Symfony Core membership can be revoked for any of the following reasons:
+
+* Refusal to follow the rules and policies stated in this document;
+* Lack of activity for the past six months;
+* Willful negligence or intent to harm the Symfony project;
+* Upon decision of the **Project Leader**.
+
+Core Membership Compensation
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Core Team members work on Symfony on a purely voluntary basis. In return
+for their work for the Symfony project, members can get free access to
+Symfony conferences. Personal vouchers for Symfony conferences are handed out
+on request by the **Project Leader**.
+
+Code Development Rules
+----------------------
+
+Symfony project development is based on pull requests proposed by any member
+of the Symfony community. Pull request acceptance or rejection is decided based
+on the votes cast by the Symfony Core members.
+
+Pull Request Voting Policy
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+* ``-1`` votes must always be justified by technical and objective reasons;
+
+* ``+1`` votes do not require justification, unless there is at least one
+ ``-1`` vote;
+
+* 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
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A pull request **can be merged** if:
+
+* It is a :ref:`unsubstantial 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).
+
+.. _core-team_unsubstantial-changes:
+
+.. note::
+
+ Unsubstantial changes comprise typos, DocBlock fixes, code standards
+ fixes, comment, exception message tweaks, and minor CSS, JavaScript and
+ HTML modifications.
+
+Pull Request Merging Process
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+All code must be committed to the repository through pull requests, except
+for :ref:`unsubstantial change ` which can be
+committed directly to the repository.
+
+**Mergers** must always use the command-line ``gh`` tool provided by the
+**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.
+
+.. tip::
+
+ Core team members are part of the ``mergers`` group on the ``symfony``
+ Github organization. This gives them write-access to many repositories,
+ including the main ``symfony/symfony`` mono-repository.
+
+ To avoid unintentional pushes to the main project (which in turn creates
+ new versions on Packagist), Core team members are encouraged to have
+ two clones of the project locally:
+
+ #. A clone for their own contributions, which they use to push to their
+ fork on GitHub. Clear out the push URL for the Symfony repository using
+ ``git remote set-url --push origin dev://null`` (change ``origin``
+ to the Git remote pointing to the Symfony repository);
+ #. A clone for merging, which they use in combination with ``gh`` and
+ allows them to push to the main repository.
+
+Upmerging Version Branches
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To synchronize changes in all versions, version branches are regularly
+merged from oldest to latest, called "upmerging". This is a manual process.
+There is no strict policy on when this occurs, but usually not more than
+once a day and at least once before monthly releases.
+
+Before starting the upmerge, Git must be configured to provide a merge
+summary by running:
+
+.. code-block:: terminal
+
+ # Run command in the "symfony" repository
+ $ git config merge.stat true
+
+The upmerge should always be done on all maintained versions at the same
+time. Refer to `the releases page`_ to find all actively maintained
+versions (indicated by a green color).
+
+The process follows these steps:
+
+#. Start on the oldest version and make sure it's up to date with the
+ upstream repository;
+#. Check-out the second oldest version, update from upstream and merge the
+ previous version from the local branch;
+#. Continue this process until you reached the latest version;
+#. Push the branches to the repository and monitor the test suite. Failure
+ might indicate hidden/missed merge conflicts.
+
+.. code-block:: terminal
+
+ # 'origin' is refered to as the main upstream project
+ $ git fetch origin
+
+ # update the local branches
+ $ git checkout 6.4
+ $ git reset --hard origin/6.4
+ $ git checkout 7.2
+ $ git reset --hard origin/7.2
+ $ git checkout 7.3
+ $ git reset --hard origin/7.3
+
+ # upmerge 6.4 into 7.2
+ $ git checkout 7.2
+ $ git merge --no-ff 6.4
+ # ... resolve conflicts
+ $ git commit
+
+ # upmerge 7.2 into 7.3
+ $ git checkout 7.3
+ $ git merge --no-ff 7.2
+ # ... resolve conflicts
+ $ git commit
+
+ $ git push origin 7.3 7.2 6.4
+
+.. warning::
+
+ Upmerges must be explicit, i.e. no fast-forward merges.
+
+.. tip::
+
+ Solving merge conflicts can be challenging. You can always ping other
+ Core team members to help you in the process (e.g. members that merged
+ a specific conflicting change).
+
+Release Policy
+~~~~~~~~~~~~~~
+
+The **Project Leader** is also the release manager for every Symfony version.
+
+Symfony Core Rules and Protocol Amendments
+------------------------------------------
+
+The rules described in this document may be amended at any time at the
+discretion of the **Project Leader**.
+
+.. _`symfony-docs repository`: https://github.com/symfony/symfony-docs
+.. _`UX repositories`: https://github.com/symfony/ux
+.. _`CLI repositories`: https://github.com/symfony-cli
+.. _`fabpot`: https://github.com/fabpot/
+.. _`webmozart`: https://github.com/webmozart/
+.. _`Tobion`: https://github.com/Tobion/
+.. _`nicolas-grekas`: https://github.com/nicolas-grekas/
+.. _`stof`: https://github.com/stof/
+.. _`dunglas`: https://github.com/dunglas/
+.. _`jakzal`: https://github.com/jakzal/
+.. _`Seldaek`: https://github.com/Seldaek/
+.. _`weaverryan`: https://github.com/weaverryan/
+.. _`aitboudad`: https://github.com/aitboudad/
+.. _`xabbuh`: https://github.com/xabbuh/
+.. _`javiereguiluz`: https://github.com/javiereguiluz/
+.. _`lyrixx`: https://github.com/lyrixx/
+.. _`chalasr`: https://github.com/chalasr/
+.. _`ogizanagi`: https://github.com/ogizanagi/
+.. _`Nyholm`: https://github.com/Nyholm
+.. _`sroze`: https://github.com/sroze
+.. _`yceruto`: https://github.com/yceruto
+.. _`michaelcullum`: https://github.com/michaelcullum
+.. _`wouterj`: https://github.com/wouterj
+.. _`HeahDude`: https://github.com/HeahDude
+.. _`OskarStark`: https://github.com/OskarStark
+.. _`romainneutron`: https://github.com/romainneutron
+.. _`lsmith77`: https://github.com/lsmith77/
+.. _`derrabus`: https://github.com/derrabus/
+.. _`jderusse`: https://github.com/jderusse/
+.. _`tgalopin`: https://github.com/tgalopin/
+.. _`fancyweb`: https://github.com/fancyweb/
+.. _`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/
+.. _`hypemc`: https://github.com/hypemc/
+.. _`mtarld`: https://github.com/mtarld/
+.. _`spomky`: https://github.com/spomky/
+.. _`alexandre-daubois`: https://github.com/alexandre-daubois/
+.. _`tucksaun`: https://github.com/tucksaun/
+.. _`the releases page`: https://symfony.com/releases
diff --git a/contributing/diversity/further_reading.rst b/contributing/diversity/further_reading.rst
new file mode 100644
index 00000000000..8bb07c39c97
--- /dev/null
+++ b/contributing/diversity/further_reading.rst
@@ -0,0 +1,56 @@
+Further Reading / Viewing
+=========================
+
+This is a non-exhaustive list of further reading on the topic of diversity.
+
+Diversity in Open Source
+------------------------
+
+`Sage Sharp - What makes a good community? `_
+`Ashe Dryden - The Ethics of Unpaid Labor and the OSS Community `_
+`Model View Culture - The Dehumanizing Myth of the Meritocracy `_
+`Annalee - How “Good Intent” Undermines Diversity and Inclusion `_
+`Karolina Szczur - Building Inclusive Communities `_
+
+Code of Conduct
+---------------
+
+`Karolina Szczur - When a Code of Conduct becomes harmful `_
+`Ashe Dryden - Codes of Conduct 101 + FAQ `_
+`Phil Sturgeon - Codes of Conduct: Maybe They're Not So Bad? `_
+
+Inclusive language
+------------------
+
+`Jenée Desmond-Harris - Why I’m finally convinced it's time to stop saying "you guys" `_
+`inclusive language presentations `_
+
+Other talks and Blog Posts
+--------------------------
+
+`Lena Reinhard – A Talk About Nothing `_
+`Lena Reinhard - A Talk about Everything `_
+`Sage Sharp - SCALE: Improving Diversity with Maslow’s hierarchy `_
+`UCSF - Unconscious Bias