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
new file mode 100644
index 00000000000..2ecd4fc7d2d
--- /dev/null
+++ b/.doctor-rst.yaml
@@ -0,0 +1,122 @@
+rules:
+ american_english: ~
+ avoid_repetetive_words: ~
+ blank_line_after_anchor: ~
+ blank_line_after_directive: ~
+ blank_line_before_directive: ~
+ composer_dev_option_not_at_the_end: ~
+ correct_code_block_directive_based_on_the_content: ~
+ deprecated_directive_should_have_version: ~
+ 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: ~
+ 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: ~
+ 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: ~
+ valid_inline_highlighted_namespaces: ~
+ valid_use_statements: ~
+ versionadded_directive_should_have_version: ~
+ yaml_instead_of_yml_suffix: ~
+
+ # master
+ versionadded_directive_major_version:
+ major_version: 7
+
+ versionadded_directive_min_version:
+ min_version: '7.0'
+
+ deprecated_directive_major_version:
+ major_version: 7
+
+ deprecated_directive_min_version:
+ 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:
+ - '/``.yml``/'
+ - '/(.*)\.orm\.yml/' # currently DoctrineBundle only supports .yml
+ 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'
+ - 'The bin/console Command'
+ - '.. _`LDAP injection`: http://projects.webappsec.org/w/page/13246947/LDAP%20Injection'
+ - '.. 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";'
+ - '.. versionadded:: 0.2' # MercureBundle
+ - '.. 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/.editorconfig b/.editorconfig
index 6f5286431d4..f9366facfb0 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,8 +1,8 @@
root = true
-[*.{rst,rst.inc}]
+[*]
indent_style = space
-indent_size = 2
+indent_size = 4
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 00000000000..9eb5d91783b
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,30 @@
+# GithubActions workflows
+/.github/workflows* @OskarStark
+
+# Console
+/console* @chalasr
+/components/console* @chalasr
+
+# Form
+/forms.rst @xabbuh @HeahDude
+/components/form* @xabbuh @HeahDude
+/reference/forms* @xabbuh @HeahDude
+
+# PropertyInfo
+/components/property_info* @dunglas
+
+# Security
+/security* @chalasr
+/components/security* @chalasr
+
+# Validator
+/validation/* @xabbuh @HeahDude
+/components/validator* @xabbuh @HeahDude
+/reference/constraints* @xabbuh @HeahDude
+
+# Workflow
+/workflow* @lyrixx
+/components/workflow* @lyrixx
+
+# Yaml
+/components/yaml* @xabbuh
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 bc7d6a94182..f32043e4523 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,9 +1,9 @@
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 00000000000..497dfd9b430
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,145 @@
+name: CI
+
+on:
+ push:
+ branches-ignore:
+ - 'github-comments'
+ pull_request:
+ branches-ignore:
+ - 'github-comments'
+
+permissions:
+ contents: read
+
+jobs:
+ symfony-docs-builder-build:
+ name: Build (symfony-tools/docs-builder)
+
+ runs-on: ubuntu-latest
+
+ continue-on-error: true
+
+ steps:
+ - name: "Checkout"
+ uses: actions/checkout@v4
+
+ - name: "Set-up PHP"
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: 8.4
+ coverage: none
+
+ - name: Get composer cache directory
+ id: composercache
+ working-directory: _build
+ run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
+
+ - name: Cache dependencies
+ uses: actions/cache@v3
+ with:
+ path: ${{ steps.composercache.outputs.dir }}
+ key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
+ restore-keys: ${{ runner.os }}-composer-
+
+ - name: "Install dependencies"
+ working-directory: _build
+ run: composer install --prefer-dist --no-progress
+
+ - name: "Build the docs"
+ working-directory: _build
+ run: php build.php --disable-cache
+
+ doctor-rst:
+ name: Lint (DOCtor-RST)
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: "Checkout"
+ uses: actions/checkout@v4
+
+ - name: "Create cache dir"
+ run: mkdir .cache
+
+ - name: "Extract base branch name"
+ run: echo "branch=$(echo ${GITHUB_BASE_REF:=${GITHUB_REF##*/}})" >> $GITHUB_OUTPUT
+ id: extract_base_branch
+
+ - name: "Cache DOCtor-RST"
+ 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: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:
+ 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 a5eb433eea3..b69047f69a1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,2 @@
-/_build/doctrees
-/_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/.travis.yml b/.travis.yml
deleted file mode 100644
index cccb01eef30..00000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-language: python
-
-python: 2.7
-
-sudo: false
-cache:
- directories: [$HOME/.cache/pip]
-
-install: pip install -r _build/.requirements.txt
-
-script: make -C _build SPHINXOPTS=-nW html
-
-branches:
- except:
- - github-comments
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
new file mode 100644
index 00000000000..547ac103984
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,340 @@
+LICENSE
+=======
+
+**Creative Commons Attribution-ShareAlike 3.0 Unported**
+https://creativecommons.org/licenses/by-sa/3.0/
+
+-----
+
+THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS
+PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR
+OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS
+LICENSE OR COPYRIGHT LAW IS PROHIBITED.
+
+BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE
+BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED
+TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN
+CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.
+
+1. Definitions
+--------------
+
+a. **"Adaptation"** means a work based upon the Work, or upon the Work and other
+pre-existing works, such as a translation, adaptation, derivative work,
+arrangement of music or other alterations of a literary or artistic work, or
+phonogram or performance and includes cinematographic adaptations or any other
+form in which the Work may be recast, transformed, or adapted including in any
+form recognizably derived from the original, except that a work that constitutes
+a Collection will not be considered an Adaptation for the purpose of this
+License. For the avoidance of doubt, where the Work is a musical work,
+performance or phonogram, the synchronization of the Work in timed-relation with
+a moving image ("synching") will be considered an Adaptation for the purpose of
+this License.
+
+b. **"Collection"** means a collection of literary or artistic works, such as
+encyclopedias and anthologies, or performances, phonograms or broadcasts, or
+other works or subject matter other than works listed in Section 1(f) below,
+which, by reason of the selection and arrangement of their contents, constitute
+intellectual creations, in which the Work is included in its entirety in
+unmodified form along with one or more other contributions, each constituting
+separate and independent works in themselves, which together are assembled into
+a collective whole. A work that constitutes a Collection will not be considered
+an Adaptation (as defined below) for the purposes of this License.
+
+c. **"Creative Commons Compatible License"** means a license that is listed at
+https://creativecommons.org/compatiblelicenses that has been approved by
+Creative Commons as being essentially equivalent to this License, including, at
+a minimum, because that license: (i) contains terms that have the same purpose,
+meaning and effect as the License Elements of this License; and, (ii) explicitly
+permits the relicensing of adaptations of works made available under that
+license under this License or a Creative Commons jurisdiction license with the
+same License Elements as this License.
+
+d. **"Distribute"** means to make available to the public the original and
+copies of the Work or Adaptation, as appropriate, through sale or other transfer
+of ownership.
+
+e. **"License Elements"** means the following high-level license attributes as
+selected by Licensor and indicated in the title of this License: Attribution,
+ShareAlike.
+
+f. **"Licensor"** means the individual, individuals, entity or entities that
+offer(s) the Work under the terms of this License.
+
+g. **"Original Author""** means, in the case of a literary or artistic work, the
+individual, individuals, entity or entities who created the Work or if no
+individual or entity can be identified, the publisher; and in addition (i) in
+the case of a performance the actors, singers, musicians, dancers, and other
+persons who act, sing, deliver, declaim, play in, interpret or otherwise perform
+literary or artistic works or expressions of folklore; (ii) in the case of a
+phonogram the producer being the person or legal entity who first fixes the
+sounds of a performance or other sounds; and, (iii) in the case of broadcasts,
+the organization that transmits the broadcast.
+
+h. **"Work"** means the literary and/or artistic work offered under the terms of
+this License including without limitation any production in the literary,
+scientific and artistic domain, whatever may be the mode or form of its
+expression including digital form, such as a book, pamphlet and other writing; a
+lecture, address, sermon or other work of the same nature; a dramatic or
+dramatico-musical work; a choreographic work or entertainment in dumb show; a
+musical composition with or without words; a cinematographic work to which are
+assimilated works expressed by a process analogous to cinematography; a work of
+drawing, painting, architecture, sculpture, engraving or lithography; a
+photographic work to which are assimilated works expressed by a process
+analogous to photography; a work of applied art; an illustration, map, plan,
+sketch or three-dimensional work relative to geography, topography, architecture
+or science; a performance; a broadcast; a phonogram; a compilation of data to
+the extent it is protected as a copyrightable work; or a work performed by a
+variety or circus performer to the extent it is not otherwise considered a
+literary or artistic work.
+
+i. **"You"** means an individual or entity exercising rights under this License
+who has not previously violated the terms of this License with respect to the
+Work, or who has received express permission from the Licensor to exercise
+rights under this License despite a previous violation.
+
+j. **"Publicly Perform"** means to perform public recitations of the Work and to
+communicate to the public those public recitations, by any means or process,
+including by wire or wireless means or public digital performances; to make
+available to the public Works in such a way that members of the public may
+access these Works from a place and at a place individually chosen by them; to
+perform the Work to the public by any means or process and the communication to
+the public of the performances of the Work, including by public digital
+performance; to broadcast and rebroadcast the Work by any means including signs,
+sounds or images.
+
+k. **"Reproduce"** means to make copies of the Work by any means including
+without limitation by sound or visual recordings and the right of fixation and
+reproducing fixations of the Work, including storage of a protected performance
+or phonogram in digital form or other electronic medium.
+
+2. Fair Dealing Rights
+----------------------
+
+Nothing in this License is intended to reduce, limit, or restrict any uses free
+from copyright or rights arising from limitations or exceptions that are
+provided for in connection with the copyright protection under copyright law or
+other applicable laws.
+
+3. License Grant
+----------------
+
+Subject to the terms and conditions of this License, Licensor hereby grants You
+a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the
+applicable copyright) license to exercise the rights in the Work as stated
+below:
+
+a. to Reproduce the Work, to incorporate the Work into one or more Collections,
+and to Reproduce the Work as incorporated in the Collections;
+
+b. to create and Reproduce Adaptations provided that any such Adaptation,
+including any translation in any medium, takes reasonable steps to clearly
+label, demarcate or otherwise identify that changes were made to the original
+Work. For example, a translation could be marked "The original work was
+translated from English to Spanish," or a modification could indicate "The
+original work has been modified.";
+
+c. to Distribute and Publicly Perform the Work including as incorporated in
+Collections; and,
+
+d. to Distribute and Publicly Perform Adaptations.
+
+e. For the avoidance of doubt:
+
+ 1. **Non-waivable Compulsory License Schemes.** In those jurisdictions in
+ which the right to collect royalties through any statutory or compulsory
+ licensing scheme cannot be waived, the Licensor reserves the exclusive
+ right to collect such royalties for any exercise by You of the rights
+ granted under this License;
+
+ 2. **Waivable Compulsory License Schemes.** In those jurisdictions in which
+ the right to collect royalties through any statutory or compulsory
+ licensing scheme can be waived, the Licensor waives the exclusive right to
+ collect such royalties for any exercise by You of the rights granted under
+ this License; and,
+
+ 3. **Voluntary License Schemes.** The Licensor waives the right to collect
+ royalties, whether individually or, in the event that the Licensor is a
+ member of a collecting society that administers voluntary licensing
+ schemes, via that society, from any exercise by You of the rights granted
+ under this License.
+
+The above rights may be exercised in all media and formats whether now known or
+hereafter devised. The above rights include the right to make such modifications
+as are technically necessary to exercise the rights in other media and formats.
+Subject to Section 8(f), all rights not expressly granted by Licensor are hereby
+reserved.
+
+4. Restrictions
+---------------
+
+The license granted in Section 3 above is expressly made subject to and limited
+by the following restrictions:
+
+a. You may Distribute or Publicly Perform the Work only under the terms of this
+License. You must include a copy of, or the Uniform Resource Identifier (URI)
+for, this License with every copy of the Work You Distribute or Publicly
+Perform. You may not offer or impose any terms on the Work that restrict the
+terms of this License or the ability of the recipient of the Work to exercise
+the rights granted to that recipient under the terms of the License. You may not
+sublicense the Work. You must keep intact all notices that refer to this License
+and to the disclaimer of warranties with every copy of the Work You Distribute
+or Publicly Perform. When You Distribute or Publicly Perform the Work, You may
+not impose any effective technological measures on the Work that restrict the
+ability of a recipient of the Work from You to exercise the rights granted to
+that recipient under the terms of the License. This Section 4(a) applies to the
+Work as incorporated in a Collection, but this does not require the Collection
+apart from the Work itself to be made subject to the terms of this License. If
+You create a Collection, upon notice from any Licensor You must, to the extent
+practicable, remove from the Collection any credit as required by Section 4(c),
+as requested. If You create an Adaptation, upon notice from any Licensor You
+must, to the extent practicable, remove from the Adaptation any credit as
+required by Section 4(c), as requested.
+
+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
+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),
+(ii) or (iii) (the "Applicable License"), you must comply with the terms of the
+Applicable License generally and the following provisions: (I) You must include
+a copy of, or the URI for, the Applicable License with every copy of each
+Adaptation You Distribute or Publicly Perform; (II) You may not offer or impose
+any terms on the Adaptation that restrict the terms of the Applicable License or
+the ability of the recipient of the Adaptation to exercise the rights granted to
+that recipient under the terms of the Applicable License; (III) You must keep
+intact all notices that refer to the Applicable License and to the disclaimer of
+warranties with every copy of the Work as included in the Adaptation You
+Distribute or Publicly Perform; (IV) when You Distribute or Publicly Perform the
+Adaptation, You may not impose any effective technological measures on the
+Adaptation that restrict the ability of a recipient of the Adaptation from You
+to exercise the rights granted to that recipient under the terms of the
+Applicable License. This Section 4(b) applies to the Adaptation as incorporated
+in a Collection, but this does not require the Collection apart from the
+Adaptation itself to be made subject to the terms of the Applicable License.
+
+c. If You Distribute, or Publicly Perform the Work or any Adaptations or
+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,
+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
+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
+"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
+credit will appear, if a credit for all contributing authors of the Adaptation
+or Collection appears, then as part of these credits and in a manner at least as
+prominent as the credits for the other contributing authors. For the avoidance
+of doubt, You may only use the credit required by this Section for the purpose
+of attribution in the manner set out above and, by exercising Your rights under
+this License, You may not implicitly or explicitly assert or imply any
+connection with, sponsorship or endorsement by the Original Author, Licensor
+and/or Attribution Parties, as appropriate, of You or Your use of the Work,
+without the separate, express prior written permission of the Original Author,
+Licensor and/or Attribution Parties.
+
+d. Except as otherwise agreed in writing by the Licensor or as may be otherwise
+permitted by applicable law, if You Reproduce, Distribute or Publicly Perform
+the Work either by itself or as part of any Adaptations or Collections, You must
+not distort, mutilate, modify or take other derogatory action in relation to the
+Work which would be prejudicial to the Original Author's honor or reputation.
+Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise
+of the right granted in Section 3(b) of this License (the right to make
+Adaptations) would be deemed to be a distortion, mutilation, modification or
+other derogatory action prejudicial to the Original Author's honor and
+reputation, the Licensor will waive or not assert, as appropriate, this Section,
+to the fullest extent permitted by the applicable national law, to enable You to
+reasonably exercise Your right under Section 3(b) of this License (right to make
+Adaptations) but not otherwise.
+
+5. Representations, Warranties and Disclaimer
+---------------------------------------------
+
+UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS
+THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING
+THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT
+LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR
+PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY,
+OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME
+JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH
+EXCLUSION MAY NOT APPLY TO YOU.
+
+6. Limitation on Liability
+--------------------------
+
+EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE
+LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL,
+PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE
+WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. Termination
+--------------
+
+a. This License and the rights granted hereunder will terminate automatically
+upon any breach by You of the terms of this License. Individuals or entities who
+have received Adaptations or Collections from You under this License, however,
+will not have their licenses terminated provided such individuals or entities
+remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8
+will survive any termination of this License.
+
+b. Subject to the above terms and conditions, the license granted here is
+perpetual (for the duration of the applicable copyright in the Work).
+Notwithstanding the above, Licensor reserves the right to release the Work under
+different license terms or to stop distributing the Work at any time; provided,
+however that any such election will not serve to withdraw this License (or any
+other license that has been, or is required to be, granted under the terms of
+this License), and this License will continue in full force and effect unless
+terminated as stated above.
+
+8. Miscellaneous
+----------------
+
+a. Each time You Distribute or Publicly Perform the Work or a Collection, the
+Licensor offers to the recipient a license to the Work on the same terms and
+conditions as the license granted to You under this License.
+
+b. Each time You Distribute or Publicly Perform an Adaptation, Licensor offers
+to the recipient a license to the original Work on the same terms and conditions
+as the license granted to You under this License.
+
+c. If any provision of this License is invalid or unenforceable under applicable
+law, it shall not affect the validity or enforceability of the remainder of the
+terms of this License, and without further action by the parties to this
+agreement, such provision shall be reformed to the minimum extent necessary to
+make such provision valid and enforceable.
+
+d. No term or provision of this License shall be deemed waived and no breach
+consented to unless such waiver or consent shall be in writing and signed by the
+party to be charged with such waiver or consent.
+
+e. This License constitutes the entire agreement between the parties with
+respect to the Work licensed here. There are no understandings, agreements or
+representations with respect to the Work not specified here. Licensor shall not
+be bound by any additional provisions that may appear in any communication from
+You. This License may not be modified without the mutual written agreement of
+the Licensor and You.
+
+f. The rights granted under, and the subject matter referenced, in this License
+were drafted utilizing the terminology of the Berne Convention for the
+Protection of Literary and Artistic Works (as amended on September 28, 1979),
+the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO
+Performances and Phonograms Treaty of 1996 and the Universal Copyright
+Convention (as revised on July 24, 1971). These rights and subject matter take
+effect in the relevant jurisdiction in which the License terms are sought to be
+enforced according to the corresponding provisions of the implementation of
+those treaty provisions in the applicable national law. If the standard suite of
+rights granted under applicable copyright law includes additional rights not
+granted under this License, such additional rights are deemed to be included in
+the License; this License is not intended to restrict the license of any rights
+under applicable law.
diff --git a/README.markdown b/README.markdown
deleted file mode 100644
index 1b6c0da8b50..00000000000
--- a/README.markdown
+++ /dev/null
@@ -1,38 +0,0 @@
-Symfony Documentation
-=====================
-
-This documentation is rendered online at https://symfony.com/doc/current/
-
-Contributing
-------------
-
-> **Note**
-> Unless you're documenting a feature that was introduced *after* Symfony 3.4
-> (e.g. in Symfony 4.2), all pull requests must be based off of the **3.4** branch,
-> **not** the master or older branches.
-
-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)
-
-SymfonyCloud
-------------
-
-Pull requests are automatically built by [SymfonyCloud](https://symfony.com/cloud).
-
-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 @@
+
+
+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 430bce090b0..00000000000
--- a/_build/.requirements.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-alabaster==0.7.10
-Babel==2.4.0
-docutils==0.13.1
-imagesize==0.7.1
-Jinja2==2.9.6
-MarkupSafe==1.0
-Pygments==2.2.0
-pytz==2017.2
-requests==2.20.0
-six==1.10.0
-snowballstemmer==1.2.1
-Sphinx==1.3.6
-git+https://github.com/fabpot/sphinx-php.git@7312eccce9465640752e51373a480da700e02345#egg_name=sphinx-php
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/_themes/_exts/symfonycom/__init__.py b/_build/_themes/_exts/symfonycom/__init__.py
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/_build/_themes/_exts/symfonycom/sphinx/__init__.py b/_build/_themes/_exts/symfonycom/sphinx/__init__.py
deleted file mode 100644
index 426d79826d7..00000000000
--- a/_build/_themes/_exts/symfonycom/sphinx/__init__.py
+++ /dev/null
@@ -1,163 +0,0 @@
-from sphinx.highlighting import lexers, PygmentsBridge
-from pygments.style import Style
-from pygments.formatters import HtmlFormatter
-from pygments.token import Keyword, Name, Comment, String, Error, \
- Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
-
-from sphinx.writers.html import HTMLTranslator
-from docutils import nodes
-from sphinx.locale import admonitionlabels, lazy_gettext
-
-customadmonitionlabels = admonitionlabels
-l_ = lazy_gettext
-customadmonitionlabels['best-practice'] = l_('Best Practice')
-
-def _getType(path):
- return path[:path.find('/')]
-
-def _isIndex(path):
- return 'index' in path
-
-class SensioHTMLTranslator(HTMLTranslator):
- def __init__(self, builder, *args, **kwds):
- HTMLTranslator.__init__(self, builder, *args, **kwds)
- builder.templates.environment.filters['get_type'] = _getType
- builder.templates.environment.tests['index'] = _isIndex
- self.highlightlinenothreshold = 0
-
- def visit_literal(self, node):
- self.body.append(self.starttag(node, 'code', '', CLASS='docutils literal notranslate'))
-
- def depart_literal(self, node):
- self.body.append('')
-
- def visit_admonition(self, node, name=''):
- self.body.append(self.starttag(node, 'div', CLASS=('admonition-wrapper')))
- self.body.append('
')
- if name and name != 'seealso':
- node.insert(0, nodes.title(name, customadmonitionlabels[name]))
- self.set_first_last(node)
-
- def depart_admonition(self, node=None):
- self.body.append('
-
- {% trans %}Free document hosting provided by Read the Docs.{% endtrans %}
-
-
-
-{% endif %}
-
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 b707b25a477..00000000000
--- a/_build/conf.py
+++ /dev/null
@@ -1,287 +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('_themes/_exts'))
-
-# adding PhpLexer
-from sphinx.highlighting import lexers
-from pygments.lexers.compiled import CLexer
-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.0'
-
-# 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',
- 'sensio.sphinx.refinclude', 'sensio.sphinx.configurationblock', 'sensio.sphinx.phpcode', 'sensio.sphinx.bestpractice', 'sensio.sphinx.codeblock',
- 'symfonycom.sphinx'
-]
-
-# 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 = 'sphinx'
-
-# 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['varnish3'] = CLexer()
-lexers['varnish4'] = CLexer()
-lexers['terminal'] = TerminalLexer()
-
-config_block = {
- 'apache': 'Apache',
- 'markdown': 'Markdown',
- 'nginx': 'Nginx',
- 'rst': 'reStructuredText',
- 'terminal': 'Terminal',
- 'varnish3': 'Varnish 3',
- 'varnish4': 'Varnish 4'
-}
-
-# use PHP as the primary domain
-primary_domain = 'php'
-
-# set url for API links
-api_url = 'http://api.symfony.com/master/%s'
-
-
-# -- 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"
-html_theme_path = ["_themes", ]
-
-# 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 = {}
-
-# 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 = None
-
-# 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']
-
-# 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
new file mode 100644
index 00000000000..9758b4e7397
--- /dev/null
+++ b/_build/maintainer_guide.rst
@@ -0,0 +1,378 @@
+Symfony Docs Maintainer Guide
+=============================
+
+The `symfony/symfony-docs`_ repository stores the Symfony project documentation
+and is managed by the `Symfony Docs team`_. This article explains in detail some
+of those management tasks, so it's only useful for maintainers and not regular
+readers or Symfony developers.
+
+Reviewing Pull Requests
+-----------------------
+
+All the recommendations of the `Symfony's respectful review comments`_ apply,
+but there are extra things to keep in mind for maintainers:
+
+* Always be nice in all interactions with all contributors.
+* Be extra-patient with new contributors (GitHub shows a special badge for them).
+* Don't assume that contributors know what you think is obvious (e.g. lots of
+ them don't know what to "squash commits" means).
+* Don't use acronyms like IMO, IIRC, etc. or complex English words (most
+ contributors are not native in English and it's intimidating for them).
+* Never engage in a heated discussion. Lock it right away using GitHub.
+* Never discuss non-tech issues. Some PRs are related to our Diversity initiative
+ and some people always try to drag you into politics. Never engage in that and
+ lock the issue/PR as off-topic on GitHub.
+
+Fixing Minor Issues Yourself
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+It's common for new contributors to make lots of minor mistakes in the syntax
+of the RST format used in the docs. It's also common for non English speakers to
+make minor typos.
+
+Even if your intention is good, if you add lots of comments when reviewing a
+first contribution, that person will probably not contribute again. It's better
+to fix the minor errors and typos yourself while merging. If that person
+contributes again, it's OK to mention some of the minor issues to educate them.
+
+.. code-block:: terminal
+
+ $ gh merge 11059
+
+ 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 "6.2" refs/notes/github-comments
+
+ # Now, open your editor and make the needed changes ...
+
+ $ git commit -a
+ # 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 "6.2" refs/notes/github-comments
+
+Merging Pull Requests
+---------------------
+
+Technical Requirements
+~~~~~~~~~~~~~~~~~~~~~~
+
+* `Git`_ installed and properly configured.
+* ``gh`` tool fully installed according to its installation instructions
+ (GitHub token configured, Git remote configured, etc.)
+ This is a proprietary CLI tool which only Symfony team members have access to.
+* Some previous Git experience, specially merging pull requests.
+
+First Setup
+~~~~~~~~~~~
+
+First, fork the using the GitHub web
+interface. Then:
+
+.. code-block:: terminal
+
+ # Clone your fork
+ $ git clone https://github.com//symfony-docs.git
+
+ $ cd symfony-docs/
+
+ # Add the original repo as 'upstream' remote
+ $ git remote add upstream https://github.com/symfony/symfony-docs
+
+ # Add the original repo as 'gh' remote (needed for the 'gh' tool)
+ $ git remote add gh https://github.com/symfony/symfony-docs
+
+ # Configure 'gh' in Git as the remote used by the 'gh' tool
+ $ git config gh.remote gh
+
+Merging Process
+~~~~~~~~~~~~~~~
+
+At first, it's common to make mistakes and merge things badly. Don't worry. This
+has happened to all of us and we've always been able to recover from any mistake.
+
+Step 1: Select the right branch to merge
+........................................
+
+PRs must be merged in the oldest maintained branch where they are applicable:
+
+* Here you can find the currently maintained branches: https://symfony.com/roadmap.
+* Typos and old undocumented features are merged into the oldest maintained branch.
+* New features are merged into the branch where they were introduced. This
+ usually means ``master``. And don't forget to check that new feature includes
+ the ``versionadded`` directive.
+
+It's very common for contributors (specially newcomers) to select the wrong
+branch for their PRs, so we must always check if the change should go to the
+proposed branch or not.
+
+If the branch is wrong, there's no need to ask the contributor to rebase. The
+``gh`` tool can do that for us.
+
+Step 2: Merge the pull request
+..............................
+
+Never use GitHub's web interface (or desktop clients) to merge PRs or to solve
+merge conflicts. Always use the ``gh`` tool for anything related to merges.
+
+We require two approval votes from team members before merging a PR, except if
+it's a typo, a small change or clearly an error.
+
+If a PR contains lots of commits, there's no need to ask the contributor to
+squash them. The ``gh`` tool does that automatically. The only exceptions are
+when commits are made by more than one person and when there's a merge commit.
+``gh`` can't squash commits in those cases, so it's better to ask to the
+original contributor.
+
+.. code-block:: terminal
+
+ $ cd symfony-docs/
+
+ # make sure that your local branch is updated
+ $ git checkout 4.4
+ $ git fetch upstream
+ $ git merge upstream/4.4
+
+ # merge any PR passing its GitHub number as argument
+ $ gh merge 11159
+
+ # the gh tool will ask you some questions...
+
+ # push your changes (you can merge several PRs and push once at the end)
+ $ git push origin
+ $ git push upstream
+
+It's common to have to change the branch where a PR is merged. Instead of asking
+the contributors to rebase their PRs, the "gh" tool can change the branch with
+the ``-s`` option:
+
+.. code-block:: terminal
+
+ # e.g. this PR was sent against 'master', but it's merged in '4.4'
+ $ gh merge 11160 -s 4.4
+
+Sometimes, when changing the branch, you may face rebase issues, but they are
+usually simple to fix:
+
+.. code-block:: terminal
+
+ $ gh merge 11160 -s 4.4
+
+ ...
+
+ Unable to rebase the patch for pull/11183
+ The command "'git' 'rebase' '--onto' '4.4' '5.0' 'pull/11160'" failed.
+ Exit Code: 128(Invalid exit argument)
+
+ [...]
+ Auto-merging reference/forms/types/entity.rst
+ CONFLICT (content): Merge conflict in reference/forms/types/entity.rst
+ Patch failed at 0001 Update entity.rst
+ The copy of the patch that failed is found in: .git/rebase-apply/patch
+
+ # Now, fix all the conflicts using your editor
+
+ # Add the modified files and continue the rebase
+ $ git add reference/forms/types/entity.rst ...
+ $ git rebase --continue
+
+ # Lastly, re-run the exact same original command that resulted in a conflict
+ # There's no need to change the branch or do anything else.
+ $ gh merge 11160 -s 4.4
+
+ The previous run had some conflicts. Do you want to resume the merge? (Y/n)
+
+Later in this article you can find a troubleshooting section for the errors that
+you will usually face while merging.
+
+Step 3: Merge it into the other branches
+........................................
+
+If a PR has not been merged in ``master``, you must merge it up into all the
+maintained branches until ``master``. Imagine that you are merging a PR against
+``4.4`` and the maintained branches are ``4.4``, ``5.0`` and ``master``:
+
+.. code-block:: terminal
+
+ $ git fetch upstream
+
+ $ git checkout 4.4
+ $ git merge upstream/4.4
+
+ $ gh merge 11159
+ $ git push origin
+ $ git push upstream
+
+ $ git checkout 5.0
+ $ git merge upstream/5.0
+ $ git merge --log 4.4
+ # here you can face several errors explained later
+ $ git push origin
+ $ git push upstream
+
+ $ git checkout master
+ $ git merge upstream/master
+ $ git merge --log 5.0
+ $ git push origin
+ $ git push upstream
+
+.. tip::
+
+ If you followed the full ``gh`` installation instructions you can remove the
+ ``--log`` option in the above commands.
+
+.. tip::
+
+ When the support of a Symfony branch ends, it's recommended to delete your
+ local branch to avoid merging in it unawarely:
+
+ .. code-block:: terminal
+
+ # if Symfony 3.3 goes out of maintenance today, delete your local branch
+ $ git branch -D 3.3
+
+Troubleshooting
+~~~~~~~~~~~~~~~
+
+Wrong merge of your local branch
+................................
+
+When updating your local branches before merging:
+
+.. code-block:: terminal
+
+ $ git fetch upstream
+ $ git checkout 4.4
+ $ git merge upstream/4.4
+
+It's possible that you merge a wrong upstream branch unawarely. It's usually
+easy to spot because you'll see lots of conflicts:
+
+.. code-block:: terminal
+
+ # DON'T DO THIS! It's a wrong branch merge
+ $ git checkout 4.4
+ $ git merge upstream/5.0
+
+As long as you don't push this wrong merge, there's no problem. Delete your
+local branch and check it out again:
+
+.. code-block:: terminal
+
+ $ git checkout master
+ $ git branch -D 4.4
+ $ git checkout 4.4 upstream/4.4
+
+If you did push the wrong branch merge, ask for help in the documentation
+mergers chat and we'll help solve the problem.
+
+Solving merge conflicts
+.......................
+
+When merging things to upper branches, most of the times you'll see conflicts:
+
+.. code-block:: terminal
+
+ $ git checkout 5.0
+ $ git merge upstream/5.0
+ $ git merge --log 4.4
+
+ Auto-merging security/entity_provider.rst
+ Auto-merging logging/monolog_console.rst
+ Auto-merging form/dynamic_form_modification.rst
+ Auto-merging components/phpunit_bridge.rst
+ CONFLICT (content): Merge conflict in components/phpunit_bridge.rst
+ Automatic merge failed; fix conflicts and then commit the result.
+
+Solve the conflicts with your editor (look for occurrences of ``<<<<``, which is
+the marker used by Git for conflicts) and then do this:
+
+.. code-block:: terminal
+
+ # add all the conflicting files that you fixed
+ $ git add components/phpunit_bridge.rst
+ $ git commit -a
+ $ git push origin
+ $ git push upstream
+
+.. tip::
+
+ When there are lots of conflicts, look for ``<<<<<`` with your editor in all
+ docs before committing the changes. It's common to forget about some of them.
+ If you prefer, you can run this too: ``git grep --cached "<<<<<"``.
+
+Merging deleted files
+.....................
+
+A common cause of conflict when merging PRs into upper branches are files which
+were modified by the PR but no longer exist in newer branches:
+
+.. code-block:: terminal
+
+ $ git checkout 5.0
+ $ git merge upstream/5.0
+ $ git merge --log 4.4
+
+ Auto-merging translation/debug.rst
+ CONFLICT (modify/delete): service_container/scopes.rst deleted in HEAD and
+ modified in 4.4. Version 4.4 of service_container/scopes.rst left in tree.
+ Auto-merging service_container.rst
+
+If the contents of the deleted file were moved to a different file in newer
+branches, redo the changes in the new file. Then, delete the file that Git left
+in the tree as follows:
+
+.. code-block:: terminal
+
+ # delete all the conflicting files that no longer exist in this branch
+ $ git rm service_container/scopes.rst
+ $ git commit -a
+ $ 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
+.. _`Git`: https://git-scm.com/
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 fdb85af6438..ee14c191025 100644
--- a/_build/redirection_map
+++ b/_build/redirection_map
@@ -13,7 +13,7 @@
/cookbook/email /email
/cookbook/gmail /cookbook/email/gmail
/cookbook/console /components/console
-/cookbook/tools/autoloader /components/class_loader
+/cookbook/tools/autoloader https://github.com/symfony/class-loader
/cookbook/tools/finder /components/finder
/cookbook/service_container/parentservices /service_container/parent_services
/cookbook/service_container/factories /service_container/factories
@@ -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
@@ -295,15 +295,15 @@
/components/asset/introduction /components/asset
/components/browser_kit/index /components/browser_kit
/components/browser_kit/introduction /components/browser_kit
-/components/class_loader/introduction /components/class_loader
-/components/class_loader/index /components/class_loader
-/components/class_loader/cache_class_loader /components/class_loader
-/components/class_loader/class_loader /components/class_loader
-/components/class_loader/class_map_generator /components/class_loader
-/components/class_loader/debug_class_loader /components/class_loader
-/components/class_loader/map_class_loader /components/class_loader
-/components/class_loader/map_class_loader /components/class_loader
-/components/class_loader/psr4_class_loader /components/class_loader
+/components/class_loader/introduction https://github.com/symfony/class-loader
+/components/class_loader/index https://github.com/symfony/class-loader
+/components/class_loader/cache_class_loader https://github.com/symfony/class-loader
+/components/class_loader/class_loader https://github.com/symfony/class-loader
+/components/class_loader/class_map_generator https://github.com/symfony/class-loader
+/components/class_loader/debug_class_loader https://github.com/symfony/class-loader
+/components/class_loader/map_class_loader https://github.com/symfony/class-loader
+/components/class_loader/map_class_loader https://github.com/symfony/class-loader
+/components/class_loader/psr4_class_loader https://github.com/symfony/class-loader
/components/config/introduction /components/config
/components/config/index /components/config
/components/console/helpers/tablehelper /components/console/helpers/table
@@ -345,15 +345,15 @@
/components/http_kernel/index /components/http_kernel
/components/property_access/introduction /components/property_access
/components/property_access/index /components/property_access
-/components/routing/index /components/routing
-/components/routing/introduction /components/routing
+/components/routing/index https://github.com/symfony/routing
+/components/routing/introduction https://github.com/symfony/routing
/components/routing/hostname_pattern /routing/hostname_pattern
/components/security/introduction /components/security
/components/security/index /components/security
-/components/templating/introduction /components/templating
-/components/templating/index /components/templating
-/components/templating/helpers/assetshelper /components/templating/assetshelper
-/components/templating/helpers/slotshelper /components/templating/slotshelper
+/components/templating/introduction https://github.com/symfony/templating
+/components/templating/index https://github.com/symfony/templating
+/components/templating/helpers/assetshelper https://github.com/symfony/templating
+/components/templating/helpers/slotshelper https://github.com/symfony/templating
/components/translation/introduction /components/translation
/components/translation/index /components/translation
/components/var_dumper/introduction /components/var_dumper
@@ -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
@@ -399,7 +402,7 @@
/security/acl_advanced /security/acl
/security/password_encoding /security
/weblink /web_link
-/components/weblink /components/web_link
+/components/weblink https://github.com/symfony/web-link
/frontend/encore/installation-no-flex /frontend/encore/installation
/http_cache/form_csrf_caching /security/csrf
/console/logging /console
@@ -411,5 +414,163 @@
/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
+/workflow/state-machines /workflow/workflow-and-state-machine
+/workflow/introduction /workflow/workflow-and-state-machine
+/workflow/usage /workflow
+/introduction/from_flat_php_to_symfony2 /introduction/from_flat_php_to_symfony
+/configuration/environment_variables /configuration/env_var_processors
+/configuration/configuration_organization /configuration
+/configuration/environments /configuration
+/configuration/configuration_organization /configuration
+/email/dev_environment /mailer
+/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
+/setup/built_in_web_server /setup/symfony_server
+/service_container/parameters /configuration
+/routing/generate_url_javascript /routing
+/routing/slash_in_parameter /routing
+/routing/scheme /routing
+/routing/optional_placeholders /routing
+/routing/conditions /routing
+/routing/requirements /routing
+/routing/redirect_trailing_slash /routing
+/routing/debug /routing
+/routing/service_container_parameters /routing
+/routing/redirect_in_config /routing
+/routing/external_resources /routing
+/routing/hostname_pattern /routing
+/routing/extra_information /routing
+/console/request_context /routing
+/form/action_method /forms
+/reference/requirements /setup
+/bundles/inheritance /bundles/override
+/templating /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#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
+/best_practices/index /best_practices
+/best_practices/introduction /best_practices
+/best_practices/creating-the-project /best_practices
+/best_practices/configuration /best_practices
+/best_practices/business-logic /best_practices
+/best_practices/controllers /best_practices
+/best_practices/templates /best_practices
+/best_practices/forms /best_practices
+/best_practices/i18n /best_practices
+/best_practices/security /best_practices
+/best_practices/web-assets /best_practices
+/best_practices/tests /best_practices
+/components/debug https://github.com/symfony/debug
+/components/translation https://github.com/symfony/translation
+/components/translation/usage /translation
+/components/translation/custom_formats https://github.com/symfony/translation
+/components/translation/custom_message_formatter https://github.com/symfony/translation
+/components/notifier https://github.com/symfony/notifier
+/components/routing https://github.com/symfony/routing
+/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
+/components/polyfill_ctype https://github.com/symfony/polyfill-ctype
+/components/polyfill_iconv https://github.com/symfony/polyfill-iconv
+/components/polyfill_intl_grapheme https://github.com/symfony/polyfill_intl-grapheme
+/components/polyfill_intl_icu https://github.com/symfony/polyfill_intl-icu
+/components/polyfill_intl_idn https://github.com/symfony/polyfill_intl-idn
+/components/polyfill_intl_normalizer https://github.com/symfony/polyfill_intl-normalizer
+/components/polyfill_mbstring https://github.com/symfony/polyfill-mbstring
+/components/polyfill_php54 https://github.com/symfony/polyfill-php54
+/components/polyfill_php55 https://github.com/symfony/polyfill-php55
+/components/polyfill_php56 https://github.com/symfony/polyfill-php56
+/components/polyfill_php70 https://github.com/symfony/polyfill-php70
+/components/polyfill_php71 https://github.com/symfony/polyfill-php71
+/components/polyfill_php72 https://github.com/symfony/polyfill-php72
+/components/polyfill_php73 https://github.com/symfony/polyfill-php73
+/components/polyfill_uuid https://github.com/symfony/polyfill-uuid
+/components/web_link https://github.com/symfony/web-link
+/components/templating https://github.com/symfony/templating
+/components/error_handler https://github.com/symfony/error-handler
+/components/class_loader https://github.com/symfony/class-loader
+/frontend/encore/versus-assetic /frontend
+/components/http_client /http_client
+/components/mailer /mailer
+/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/_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/string/bytes-points-graphemes.png b/_images/components/string/bytes-points-graphemes.png
new file mode 100644
index 00000000000..18d971cecf7
Binary files /dev/null and b/_images/components/string/bytes-points-graphemes.png differ
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/pull_request.png b/_images/components/workflow/pull_request.png
index 1aa8886728c..692a95345ae 100644
Binary files a/_images/components/workflow/pull_request.png and b/_images/components/workflow/pull_request.png differ
diff --git a/_images/components/workflow/pull_request_puml_styled.png b/_images/components/workflow/pull_request_puml_styled.png
new file mode 100644
index 00000000000..cda9233d731
Binary files /dev/null and b/_images/components/workflow/pull_request_puml_styled.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/errors-in-prod-environment.png b/_images/controller/error_pages/errors-in-prod-environment.png
index 79fe5341b47..808d0d70028 100644
Binary files a/_images/controller/error_pages/errors-in-prod-environment.png and b/_images/controller/error_pages/errors-in-prod-environment.png differ
diff --git a/_images/controller/error_pages/exceptions-in-dev-environment.png b/_images/controller/error_pages/exceptions-in-dev-environment.png
index 5e7da2cf6a1..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
new file mode 100644
index 00000000000..db9463b8327
--- /dev/null
+++ b/_images/form/form-custom-type-postal-address-fragment-names.svg
@@ -0,0 +1 @@
+
diff --git a/_images/form/form-custom-type-postal-address.svg b/_images/form/form-custom-type-postal-address.svg
new file mode 100644
index 00000000000..42ffce4067f
--- /dev/null
+++ b/_images/form/form-custom-type-postal-address.svg
@@ -0,0 +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 58edf13f3f3..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 86e767db9b5..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/panel.png b/_images/mercure/panel.png
new file mode 100644
index 00000000000..22b214f5ff2
Binary files /dev/null and b/_images/mercure/panel.png differ
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 8c8c4d508d1..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 @@
+
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 @@
+
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 @@
+
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
new file mode 100644
index 00000000000..84810a9783d
--- /dev/null
+++ b/_images/sources/README.md
@@ -0,0 +1,102 @@
+How to Create Symfony Images
+============================
+
+Creating Diagrams
+-----------------
+
+* Use [Dia][1] as the diagramming application;
+* Use [PT Sans Narrow][2] as the only font in all diagrams (if possible, use
+ only the "normal" weight for all contents);
+* Use 36pt as the base font size;
+* Use 0.10 cm width for lines and shape borders;
+* Use the following color palette:
+ * Text, lines and shape borders: black (#000000)
+ * Shape backgrounds:
+ * Grays: dark (#4d4d4d), medium (#b3b3b3), light (#f2f2f2)
+ * Blue: #b2d4eb
+ * Red: #ecbec0
+ * Green: #b2dec7
+ * Orange: #fddfbb
+
+In case of doubt, check the existing diagrams or ask to the
+[Symfony Documentation Team][3].
+
+### 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/`.
+
+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
+
+* 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
+
+* 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/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
new file mode 100644
index 00000000000..ca12fcdeadc
Binary files /dev/null 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
new file mode 100644
index 00000000000..1b7c6226315
Binary files /dev/null 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 ed43b8f51d8..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
new file mode 100644
index 00000000000..2c393cae9c6
--- /dev/null
+++ b/best_practices.rst
@@ -0,0 +1,460 @@
+The Symfony Framework Best Practices
+====================================
+
+This article describes the **best practices for developing web applications with
+Symfony** that fit the philosophy envisioned by the original Symfony creators.
+
+If you don't agree with some of these recommendations, they might be a good
+**starting point** that you can then **extend and fit to your specific needs**.
+You can even ignore them completely and continue using your own best practices
+and methodologies. Symfony is flexible enough to adapt to your needs.
+
+This article assumes that you already have experience developing Symfony
+applications. If you don't, read first the :doc:`Getting Started `
+section of the documentation.
+
+.. tip::
+
+ Symfony provides a sample application called `Symfony Demo`_ that follows
+ all these best practices, so you can experience them in practice.
+
+Creating the Project
+--------------------
+
+Use the Symfony Binary to Create Symfony Applications
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The Symfony binary is an executable command created in your machine when you
+`download Symfony`_. It provides multiple utilities, including the simplest way
+to create new Symfony applications:
+
+.. code-block:: terminal
+
+ $ symfony new my_project_directory
+
+Under the hood, this Symfony binary command executes the needed `Composer`_
+command to :ref:`create a new Symfony application `
+based on the current stable version.
+
+Use the Default Directory Structure
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Unless your project follows a development practice that imposes a certain
+directory structure, follow the default Symfony directory structure. It's flat,
+self-explanatory and not coupled to Symfony:
+
+.. code-block:: text
+
+ your_project/
+ ├─ assets/
+ ├─ bin/
+ │ └─ console
+ ├─ config/
+ │ ├─ packages/
+ │ ├─ routes/
+ │ └─ services.yaml
+ ├─ migrations/
+ ├─ public/
+ │ ├─ build/
+ │ └─ index.php
+ ├─ src/
+ │ ├─ Kernel.php
+ │ ├─ Command/
+ │ ├─ Controller/
+ │ ├─ DataFixtures/
+ │ ├─ Entity/
+ │ ├─ EventSubscriber/
+ │ ├─ Form/
+ │ ├─ Repository/
+ │ ├─ Security/
+ │ └─ Twig/
+ ├─ templates/
+ ├─ tests/
+ ├─ translations/
+ ├─ var/
+ │ ├─ cache/
+ │ └─ log/
+ └─ vendor/
+
+Configuration
+-------------
+
+Use Environment Variables for Infrastructure Configuration
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+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 Secrets for Sensitive Information
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+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
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+These are the options used to modify the application behavior, such as the sender
+of email notifications, or the enabled `feature toggles`_. Their value doesn't
+change per machine, so don't define them as environment variables.
+
+Define these options as :ref:`parameters ` in the
+``config/services.yaml`` file. You can override these options per
+: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
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Consider using ``app.`` as the prefix of your :ref:`parameters `
+to avoid collisions with Symfony and third-party bundles/libraries parameters.
+Then, use just one or two words to describe the purpose of the parameter:
+
+.. code-block:: yaml
+
+ # config/services.yaml
+ parameters:
+ # 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: '...'
+ # it's OK to use dots, underscores, dashes or nothing, but always
+ # be consistent and use the same format for all the parameters
+ app.dir.contents: '...'
+ app.contents-dir: '...'
+
+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:`configuration parameters `,
+define them as PHP constants in the related classes. Example::
+
+ // src/Entity/Post.php
+ namespace App\Entity;
+
+ class Post
+ {
+ public const NUMBER_OF_ITEMS = 10;
+
+ // ...
+ }
+
+The main advantage of constants is that you can use them everywhere, including
+Twig templates and Doctrine entities, whereas parameters are only available
+from places with access to the :doc:`service container `.
+
+The only notable disadvantage of using constants for this kind of configuration
+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
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When Symfony 2.0 was released, applications used :doc:`bundles ` to
+divide their code into logical features: UserBundle, ProductBundle,
+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, 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
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+: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 it unnecessary to configure
+services explicitly and simplifying the application maintenance.
+
+Use it in combination with :ref:`service autoconfiguration `
+to also add :doc:`service tags ` to the services
+needing them, such as Twig extensions, event subscribers, etc.
+
+Services Should be Private Whenever Possible
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:ref:`Make services private ` to prevent you from accessing
+those services via ``$container->get()``. Instead, you will need to use proper
+dependency injection.
+
+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 configuring services because it's friendly to
+newcomers and concise, but Symfony also supports XML and PHP configuration.
+
+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 PHP
+attributes because they are by far the most convenient and agile way of setting
+up and looking for mapping information.
+
+Controllers
+-----------
+
+Make your Controller Extend the ``AbstractController`` Base Controller
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Symfony provides a :ref:`base controller `
+which includes shortcuts for the most common needs such as rendering templates
+or checking security permissions.
+
+Extending your controllers from this base controller couples your application
+to Symfony. Coupling is generally wrong, but it may be OK in this case because
+controllers shouldn't contain any business logic. Controllers should contain
+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 Attributes to Configure Routing, Caching, and Security
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+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 get access to the most
+common services (e.g ``twig``, ``router``, ``doctrine``, etc.), directly from the
+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 Entity Value Resolvers If They Are Convenient
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+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 EntityValueResolver, it's better to make the Doctrine query
+inside the controller (e.g. by calling to a :doc:`Doctrine repository method `).
+
+Templates
+---------
+
+Use Snake Case for Template Names and Variables
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+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``).
+
+Prefix Template Fragments with an Underscore
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Template fragments, also called *"partial templates"*, allow to
+:ref:`reuse template contents `. Prefix their names
+with an underscore to better differentiate them from complete templates (e.g.
+``_user_metadata.html.twig`` or ``_caution_message.html.twig``).
+
+Forms
+-----
+
+Define your Forms as PHP Classes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Creating :ref:`forms in classes ` allows reusing
+them in different parts of the application. Besides, not creating forms in
+controllers simplifies the code and maintenance of the controllers.
+
+Add Form Buttons in Templates
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Form classes should be agnostic to where they will be used. For example, the
+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
+because the button styling (CSS class and other attributes) is defined in the
+template instead of in a PHP class.
+
+However, if you create a :doc:`form with multiple submit buttons `
+you should define them in the controller instead of the template. Otherwise, you
+won't be able to check which button was clicked when handling the form in the controller.
+
+Define Validation Constraints on the Underlying Object
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Attaching :doc:`validation constraints ` to form fields
+instead of to the mapped object prevents the validation from being reused in
+other forms or other places where the object is used.
+
+.. _best-practice-handle-form:
+
+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
+time, almost identical), so it's much simpler to let a single controller action
+handle both.
+
+.. _best-practice-internationalization:
+
+Internationalization
+--------------------
+
+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``
+file contents as you write them.
+
+Symfony also supports notes in XLIFF files, making them more user-friendly for
+translators. At the end, good translations are all about context, and these
+XLIFF notes allow you to define that context.
+
+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 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
+would be ``label.username``, *not* ``edit_form.label.username``.
+
+Security
+--------
+
+Define a Single Firewall
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Unless you have two legitimately different authentication systems and users
+(e.g. form login for the main site and a token system for your API only), it's
+recommended to have only one firewall to keep things simple.
+
+Additionally, you should use the ``anonymous`` key under your firewall. If you
+require users to be logged in for different sections of your site, use the
+:doc:`access_control ` option.
+
+Use the ``auto`` Password Hasher
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The :ref:`auto password hasher ` automatically
+selects the best possible encoder/hasher depending on your PHP installation.
+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]`` attribute.
+
+Web Assets
+----------
+
+.. _use-webpack-encore-to-process-web-assets:
+
+Use AssetMapper to Manage Web Assets
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+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
+-----
+
+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 `PHPUnit data providers`_ you can define a functional test that
+checks that all application URLs load successfully::
+
+ // tests/ApplicationAvailabilityFunctionalTest.php
+ namespace App\Tests;
+
+ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
+
+ class ApplicationAvailabilityFunctionalTest extends WebTestCase
+ {
+ /**
+ * @dataProvider urlProvider
+ */
+ public function testPageIsSuccessful($url): void
+ {
+ $client = self::createClient();
+ $client->request('GET', $url);
+
+ $this->assertResponseIsSuccessful();
+ }
+
+ public function urlProvider(): \Generator
+ {
+ yield ['/'];
+ yield ['/posts'];
+ yield ['/post/fixture-post-1'];
+ yield ['/blog/category/fixture-category'];
+ yield ['/archives'];
+ // ...
+ }
+ }
+
+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
+specific tests for each page.
+
+.. _hardcode-urls-in-a-functional-test:
+
+Hard-code URLs in a Functional Test
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+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 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/
+.. _`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/best_practices/business-logic.rst b/best_practices/business-logic.rst
deleted file mode 100644
index 8feb8163bf7..00000000000
--- a/best_practices/business-logic.rst
+++ /dev/null
@@ -1,273 +0,0 @@
-Organizing Your Business Logic
-==============================
-
-In computer software, **business logic** or domain logic is "the part of the
-program that encodes the real-world business rules that determine how data can
-be created, displayed, stored, and changed" (read `full definition`_).
-
-In Symfony applications, business logic is all the custom code you write for
-your app that's not specific to the framework (e.g. routing and controllers).
-Domain classes, Doctrine entities and regular PHP classes that are used as
-services are good examples of business logic.
-
-For most projects, you should store all your code inside the ``src/`` directory.
-Inside here, you can create whatever directories you want to organize things:
-
-.. code-block:: text
-
- symfony-project/
- ├─ config/
- ├─ public/
- ├─ src/
- │ └─ Utils/
- │ └─ MyClass.php
- ├─ tests/
- ├─ var/
- └─ vendor/
-
-.. _services-naming-and-format:
-
-Services: Naming and Configuration
-----------------------------------
-
-.. best-practice::
-
- Use autowiring to automate the configuration of application services.
-
-:doc:`Service autowiring ` is a feature provided
-by Symfony's Service Container to manage services with minimal configuration. It
-reads the type-hints on your constructor (or other methods) and automatically
-passes the correct services to each method. It can also add
-:doc:`service tags ` to the services needing them, such
-as Twig extensions, event subscribers, etc.
-
-The blog application needs a utility that can transform a post title (e.g.
-"Hello World") into a slug (e.g. "hello-world") to include it as part of the
-post URL. Let's create a new ``Slugger`` class inside ``src/Utils/``::
-
- // src/Utils/Slugger.php
- namespace App\Utils;
-
- class Slugger
- {
- public function slugify(string $value): string
- {
- // ...
- }
- }
-
-If you're using the :ref:`default services.yaml configuration `,
-this class is auto-registered as a service with the ID ``App\Utils\Slugger`` (to
-prevent against typos, import the class and write ``Slugger::class`` in your code).
-
-.. best-practice::
-
- The id of your application's services should be equal to their class name,
- except when you have multiple services configured for the same class (in that
- case, use a snake case id).
-
-Now you can use the custom slugger in any other service or controller class,
-such as the ``AdminController``::
-
- use App\Utils\Slugger;
-
- public function create(Request $request, Slugger $slugger)
- {
- // ...
-
- if ($form->isSubmitted() && $form->isValid()) {
- $slug = $slugger->slugify($post->getTitle());
- $post->setSlug($slug);
-
- // ...
- }
- }
-
-Services can also be :ref:`public or private `. If you use the
-:ref:`default services.yaml configuration `,
-all services are private by default.
-
-.. best-practice::
-
- Services should be ``private`` whenever possible. This will prevent you from
- accessing that service via ``$container->get()``. Instead, you will need to use
- dependency injection.
-
-Service Format: YAML
---------------------
-
-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.
-
-.. best-practice::
-
- Use the YAML format to configure your own services.
-
-This is controversial, and in our experience, YAML and XML usage is evenly
-distributed among developers, with a slight preference towards YAML.
-Both formats have the same performance, so this is ultimately a matter of
-personal taste.
-
-We recommend YAML because it's friendly to newcomers and concise. You can
-use any of the other formats if you prefer another format.
-
-Using a Persistence Layer
--------------------------
-
-Symfony is an HTTP framework that only cares about generating an HTTP response
-for each HTTP request. That's why Symfony doesn't provide a way to talk to
-a persistence layer (e.g. database, external API). You can choose whatever
-library or strategy you want for this.
-
-In practice, many Symfony applications rely on the independent
-`Doctrine project`_ to define their model using entities and repositories.
-Just like with business logic, we recommend storing Doctrine entities in the
-``src/Entity/`` directory.
-
-The three entities defined by our sample blog application are a good example:
-
-.. code-block:: text
-
- symfony-project/
- ├─ ...
- └─ src/
- └─ Entity/
- ├─ Comment.php
- ├─ Post.php
- └─ User.php
-
-Doctrine Mapping Information
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-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 four metadata formats: YAML, XML,
-PHP and annotations.
-
-.. best-practice::
-
- Use annotations to define the mapping information of the Doctrine entities.
-
-Annotations are by far the most convenient and agile way of setting up and
-looking for mapping information::
-
- namespace App\Entity;
-
- use Doctrine\ORM\Mapping as ORM;
- use Doctrine\Common\Collections\ArrayCollection;
-
- /**
- * @ORM\Entity
- */
- class Post
- {
- const NUMBER_OF_ITEMS = 10;
-
- /**
- * @ORM\Id
- * @ORM\GeneratedValue
- * @ORM\Column(type="integer")
- */
- private $id;
-
- /**
- * @ORM\Column(type="string")
- */
- private $title;
-
- /**
- * @ORM\Column(type="string")
- */
- private $slug;
-
- /**
- * @ORM\Column(type="text")
- */
- private $content;
-
- /**
- * @ORM\Column(type="string")
- */
- private $authorEmail;
-
- /**
- * @ORM\Column(type="datetime")
- */
- private $publishedAt;
-
- /**
- * @ORM\OneToMany(
- * targetEntity="App\Entity\Comment",
- * mappedBy="post",
- * orphanRemoval=true
- * )
- * @ORM\OrderBy({"publishedAt"="ASC"})
- */
- private $comments;
-
- public function __construct()
- {
- $this->publishedAt = new \DateTime();
- $this->comments = new ArrayCollection();
- }
-
- // getters and setters ...
- }
-
-All formats have the same performance, so this is once again ultimately a
-matter of taste.
-
-Data Fixtures
-~~~~~~~~~~~~~
-
-As fixtures support is not enabled by default in Symfony, you should execute
-the following command to install the Doctrine fixtures bundle:
-
-.. code-block:: terminal
-
- $ composer require "doctrine/doctrine-fixtures-bundle"
-
-Then, this bundle is enabled automatically, but only for the ``dev`` and
-``test`` environments::
-
- // config/bundles.php
- return [
- // ...
- Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
- ];
-
-We recommend creating just *one* `fixture class`_ for simplicity, though
-you're welcome to have more if that class gets quite large.
-
-Assuming you have at least one fixtures class and that the database access
-is configured properly, you can load your fixtures by executing the following
-command:
-
-.. code-block:: terminal
-
- $ php bin/console doctrine:fixtures:load
-
- Careful, database will be purged. Do you want to continue Y/N ? Y
- > purging database
- > loading App\DataFixtures\ORM\LoadFixtures
-
-Coding Standards
-----------------
-
-The Symfony source code follows the `PSR-1`_ and `PSR-2`_ coding standards that
-were defined by the PHP community. You can learn more about
-:doc:`the Symfony Coding standards ` and even
-use the `PHP-CS-Fixer`_, which is a command-line utility that can fix the
-coding standards of an entire codebase in a matter of seconds.
-
-----
-
-Next: :doc:`/best_practices/controllers`
-
-.. _`full definition`: https://en.wikipedia.org/wiki/Business_logic
-.. _`Doctrine project`: http://www.doctrine-project.org/
-.. _`fixture class`: https://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html#writing-simple-fixtures
-.. _`PSR-1`: https://www.php-fig.org/psr/psr-1/
-.. _`PSR-2`: https://www.php-fig.org/psr/psr-2/
-.. _`PHP-CS-Fixer`: https://github.com/FriendsOfPHP/PHP-CS-Fixer
diff --git a/best_practices/configuration.rst b/best_practices/configuration.rst
deleted file mode 100644
index ab2c38e98a5..00000000000
--- a/best_practices/configuration.rst
+++ /dev/null
@@ -1,190 +0,0 @@
-Configuration
-=============
-
-Configuration usually involves different application parts (such as infrastructure
-and security credentials) and different environments (development, production).
-That's why Symfony recommends that you split the application configuration into
-three parts.
-
-Infrastructure-Related 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
-application behavior.
-
-.. best-practice::
-
- Define the infrastructure-related configuration options as
- :doc:`environment variables `. During
- development, use the ``.env`` and ``.env.local`` files at the root of your
- project to set these.
-
-By default, Symfony adds these types of options to the ``.env`` file when
-installing new dependencies in the app:
-
-.. code-block:: bash
-
- # .env
- ###> doctrine/doctrine-bundle ###
- DATABASE_URL=sqlite:///%kernel.project_dir%/var/data/blog.sqlite
- ###< doctrine/doctrine-bundle ###
-
- ###> symfony/swiftmailer-bundle ###
- MAILER_URL=smtp://localhost?encryption=ssl&auth_mode=login&username=&password=
- ###< symfony/swiftmailer-bundle ###
-
- # ...
-
-These options aren't defined inside the ``config/services.yaml`` file because
-they have nothing to do with the application's behavior. In other words, your
-application doesn't care about the location of your database or the credentials
-to access to it, as long as the database is correctly configured.
-
-To override these variables with machine-specific or sensitive values, create a
-``.env.local`` file. This file should not be added to version control.
-
-.. caution::
-
- Beware that dumping the contents of the ``$_SERVER`` and ``$_ENV`` variables
- or outputting the ``phpinfo()`` contents will display the values of the
- environment variables, exposing sensitive information such as the database
- credentials.
-
-Canonical Parameters
-~~~~~~~~~~~~~~~~~~~~
-
-.. best-practice::
-
- Define all your application's env vars in the ``.env`` file.
-
-Symfony includes a configuration file called ``.env`` at the project root, which
-stores the canonical list of environment variables for the application. This
-file should be stored in version control and so should only contain non-sensitive
-default values.
-
-.. caution::
-
- 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`.
-
-Application-Related Configuration
----------------------------------
-
-.. best-practice::
-
- Define the application behavior related configuration options in the
- ``config/services.yaml`` file.
-
-The ``services.yaml`` file contains the options used by the application to
-modify its behavior, such as the sender of email notifications, or the enabled
-`feature toggles`_. Defining these values in ``.env`` file would add an extra
-layer of configuration that's not needed because you don't need or want these
-configuration values to change on each server.
-
-The configuration options defined in the ``services.yaml`` may vary from one
-:doc:`environment ` to another. That's why Symfony
-supports defining ``config/services_dev.yaml`` and ``config/services_prod.yaml``
-files so that you can override specific values for each environment.
-
-Constants vs Configuration Options
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-One of the most common errors when defining application configuration is to
-create new options for values that never change, such as the number of items for
-paginated results.
-
-.. best-practice::
-
- Use constants to define configuration options that rarely change.
-
-The traditional approach for defining configuration options has caused many
-Symfony applications to include an option like the following, which would be
-used to control the number of posts to display on the blog homepage:
-
-.. code-block:: yaml
-
- # config/services.yaml
- parameters:
- homepage.number_of_items: 10
-
-If you've done something like this in the past, it's likely that you've in fact
-*never* actually needed to change that value. Creating a configuration
-option for a value that you are never going to configure just isn't necessary.
-Our recommendation is to define these values as constants in your application.
-You could, for example, define a ``NUMBER_OF_ITEMS`` constant in the ``Post`` entity::
-
- // src/Entity/Post.php
- namespace App\Entity;
-
- class Post
- {
- const NUMBER_OF_ITEMS = 10;
-
- // ...
- }
-
-The main advantage of defining constants is that you can use their values
-everywhere in your application. When using parameters, they are only available
-from places with access to the Symfony container.
-
-Constants can be used for example in your Twig templates thanks to the
-`constant() function`_:
-
-.. code-block:: html+twig
-
-
- Displaying the {{ constant('NUMBER_OF_ITEMS', post) }} most recent results.
-
-
-And Doctrine entities and repositories can now easily access these values,
-whereas they cannot access the container parameters::
-
- namespace App\Repository;
-
- use App\Entity\Post;
- use Doctrine\ORM\EntityRepository;
-
- class PostRepository extends EntityRepository
- {
- public function findLatest($limit = Post::NUMBER_OF_ITEMS)
- {
- // ...
- }
- }
-
-The only notable disadvantage of using constants for this kind of configuration
-values is that you cannot redefine them easily in your tests.
-
-Parameter Naming
-----------------
-
-.. best-practice::
-
- The name of your configuration parameters should be as short as possible and
- should include a common prefix for the entire application.
-
-Using ``app.`` as the prefix of your parameters is a common practice to avoid
-collisions with Symfony and third-party bundles/libraries parameters. Then, use
-just one or two words to describe the purpose of the parameter:
-
-.. code-block:: yaml
-
- # config/services.yaml
- parameters:
- # 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: '...'
- # it's OK to use dots, underscores, dashes or nothing, but always
- # be consistent and use the same format for all the parameters
- app.dir.contents: '...'
- app.contents-dir: '...'
-
-----
-
-Next: :doc:`/best_practices/business-logic`
-
-.. _`feature toggles`: https://en.wikipedia.org/wiki/Feature_toggle
-.. _`constant() function`: https://twig.symfony.com/doc/2.x/functions/constant.html
diff --git a/best_practices/controllers.rst b/best_practices/controllers.rst
deleted file mode 100644
index 0ef6fd1d3bc..00000000000
--- a/best_practices/controllers.rst
+++ /dev/null
@@ -1,236 +0,0 @@
-Controllers
-===========
-
-Symfony follows the philosophy of *"thin controllers and fat models"*. This
-means that controllers should hold just the thin layer of *glue-code*
-needed to coordinate the different parts of the application.
-
-Your controller methods should just call to other services, trigger some events
-if needed and then return a response, but they should not contain any actual
-business logic. If they do, refactor it out of the controller and into a service.
-
-.. best-practice::
-
- Make your controller extend the ``AbstractController`` base controller
- provided by Symfony and use annotations to configure routing, caching and
- security whenever possible.
-
-Coupling the controllers to the underlying framework allows you to leverage
-all of its features and increases your productivity.
-
-And since your controllers should be thin and contain nothing more than a
-few lines of *glue-code*, spending hours trying to decouple them from your
-framework doesn't benefit you in the long run. The amount of time *wasted*
-isn't worth the benefit.
-
-In addition, using annotations for routing, caching and security simplifies
-configuration. You don't need to browse tens of files created with different
-formats (YAML, XML, PHP): all the configuration is just where you need it
-and it only uses one format.
-
-Overall, this means you should aggressively decouple your business logic
-from the framework while, at the same time, aggressively coupling your controllers
-and routing *to* the framework in order to get the most out of it.
-
-Controller Action Naming
-------------------------
-
-.. best-practice::
-
- Don't add the ``Action`` suffix to the methods of the controller actions.
-
-The first Symfony versions required that controller method names ended in
-``Action`` (e.g. ``newAction()``, ``showAction()``). This suffix became optional
-when annotations were introduced for controllers. In modern Symfony applications
-this suffix is neither required nor recommended, so you can safely remove it.
-
-Routing Configuration
----------------------
-
-To load routes defined as annotations in your controllers, add the following
-configuration to the main routing configuration file:
-
-.. code-block:: yaml
-
- # config/routes.yaml
- controllers:
- resource: '../src/Controller/'
- type: annotation
-
-This configuration will load annotations from any controller stored inside the
-``src/Controller/`` directory and even from its subdirectories. So if your application
-defines lots of controllers, it's perfectly ok to reorganize them into subdirectories:
-
-.. code-block:: text
-
- /
- ├─ ...
- └─ src/
- ├─ ...
- └─ Controller/
- ├─ DefaultController.php
- ├─ ...
- ├─ Api/
- │ ├─ ...
- │ └─ ...
- └─ Backend/
- ├─ ...
- └─ ...
-
-Template Configuration
-----------------------
-
-.. best-practice::
-
- Don't use the ``@Template`` annotation to configure the template used by
- the controller.
-
-The ``@Template`` annotation is useful, but also involves some magic. We
-don't think its benefit is worth the magic, and so recommend against using
-it.
-
-Most of the time, ``@Template`` is used without any parameters, which makes
-it more difficult to know which template is being rendered. It also makes
-it less obvious to beginners that a controller should always return a Response
-object (unless you're using a view layer).
-
-What does the Controller look like
-----------------------------------
-
-Considering all this, here is an example of what the controller should look like
-for the homepage of our app::
-
- namespace App\Controller;
-
- use App\Entity\Post;
- use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
- use Symfony\Component\Routing\Annotation\Route;
-
- class DefaultController extends AbstractController
- {
- /**
- * @Route("/", name="homepage")
- */
- public function index()
- {
- $posts = $this->getDoctrine()
- ->getRepository(Post::class)
- ->findLatest();
-
- return $this->render('default/index.html.twig', [
- 'posts' => $posts,
- ]);
- }
- }
-
-Fetching Services
------------------
-
-If you extend the base ``AbstractController`` class, you can't access services
-directly from the container via ``$this->container->get()`` or ``$this->get()``.
-Instead, you must use dependency injection to fetch services by
-:ref:`type-hinting action method arguments `:
-
-.. best-practice::
-
- Don't use ``$this->get()`` or ``$this->container->get()`` to fetch services
- from the container. Instead, use dependency injection.
-
-By not fetching services directly from the container, you can make your services
-*private*, which has :ref:`several advantages `.
-
-.. _best-practices-paramconverter:
-
-Using the ParamConverter
-------------------------
-
-If you're using Doctrine, then you can *optionally* use the `ParamConverter`_
-to automatically query for an entity and pass it as an argument to your controller.
-
-.. best-practice::
-
- Use the ParamConverter trick to automatically query for Doctrine entities
- when it's simple and convenient.
-
-For example::
-
- use App\Entity\Post;
- use Symfony\Component\Routing\Annotation\Route;
-
- /**
- * @Route("/{id}", name="admin_post_show")
- */
- public function show(Post $post)
- {
- $deleteForm = $this->createDeleteForm($post);
-
- return $this->render('admin/post/show.html.twig', [
- 'post' => $post,
- 'delete_form' => $deleteForm->createView(),
- ]);
- }
-
-Normally, you'd expect a ``$id`` argument to ``show()``. Instead, by creating a
-new argument (``$post``) and type-hinting it with the ``Post`` class (which is a
-Doctrine entity), the ParamConverter automatically queries for an object whose
-``$id`` property matches the ``{id}`` value. It will also show a 404 page if no
-``Post`` can be found.
-
-When Things Get More Advanced
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The above example works without any configuration because the wildcard name
-``{id}`` matches the name of the property on the entity. If this isn't true, or
-if you have even more complex logic, your best choice is to query for
-the entity manually. In our application, we have this situation in
-``CommentController``::
-
- /**
- * @Route("/comment/{postSlug}/new", name="comment_new")
- */
- public function new(Request $request, $postSlug)
- {
- $post = $this->getDoctrine()
- ->getRepository(Post::class)
- ->findOneBy(['slug' => $postSlug]);
-
- if (!$post) {
- throw $this->createNotFoundException();
- }
-
- // ...
- }
-
-You can also use the ``@ParamConverter`` configuration, which is infinitely
-flexible::
-
- use App\Entity\Post;
- use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
- use Symfony\Component\HttpFoundation\Request;
- use Symfony\Component\Routing\Annotation\Route;
-
- /**
- * @Route("/comment/{postSlug}/new", name="comment_new")
- * @ParamConverter("post", options={"mapping"={"postSlug"="slug"}})
- */
- public function new(Request $request, Post $post)
- {
- // ...
- }
-
-The point is this: the ParamConverter shortcut is great for most situations.
-However, there is nothing wrong with querying for entities directly if the
-ParamConverter would get complicated.
-
-Pre and Post Hooks
-------------------
-
-If you need to execute some code before or after the execution of your controllers,
-you can use the EventDispatcher component to
-:doc:`set up before and after filters `.
-
-----
-
-Next: :doc:`/best_practices/templates`
-
-.. _`ParamConverter`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
diff --git a/best_practices/creating-the-project.rst b/best_practices/creating-the-project.rst
deleted file mode 100644
index e323d6bcf7a..00000000000
--- a/best_practices/creating-the-project.rst
+++ /dev/null
@@ -1,101 +0,0 @@
-Creating the Project
-====================
-
-Installing Symfony
-------------------
-
-.. best-practice::
-
- Use Composer and Symfony Flex to create and manage Symfony applications.
-
-`Composer`_ is the package manager used by modern PHP applications to manage
-their dependencies. `Symfony Flex`_ is a Composer plugin designed to automate
-some of the most common tasks performed in Symfony applications. Using Flex is
-optional but recommended because it improves your productivity significantly.
-
-.. best-practice::
-
- Use the Symfony Skeleton to create new Symfony-based projects.
-
-The `Symfony Skeleton`_ is a minimal and empty Symfony project which you can
-base your new projects on. Unlike past Symfony versions, this skeleton installs
-the absolute bare minimum amount of dependencies to make a fully working Symfony
-project. Read the :doc:`/setup` article to learn more about installing Symfony.
-
-.. _linux-and-mac-os-x-systems:
-.. _windows-systems:
-
-Creating the Blog Application
------------------------------
-
-In your command console, browse to a directory where you have permission to
-create files and execute the following commands:
-
-.. code-block:: terminal
-
- $ cd projects/
- $ composer create-project symfony/skeleton blog
-
-This command creates a new directory called ``blog`` that contains a fresh new
-project based on the most recent stable Symfony version available.
-
-.. tip::
-
- The technical requirements to run Symfony are simple. If you want to check
- if your system meets those requirements, read :doc:`/reference/requirements`.
-
-Structuring the Application
----------------------------
-
-After creating the application, enter the ``blog/`` directory and you'll see a
-number of files and directories generated automatically. These are the most
-important ones:
-
-.. code-block:: text
-
- blog/
- ├─ bin/
- │ └─ console
- ├─ config/
- └─ public/
- │ └─ index.php
- ├─ src/
- │ └─ Kernel.php
- ├─ var/
- │ ├─ cache/
- │ └─ log/
- └─ vendor/
-
-This file and directory hierarchy is the convention proposed by Symfony to
-structure your applications. It's recommended to keep this structure because it's
-easy to navigate and most directory names are self-explanatory, but you can
-:doc:`override the location of any Symfony directory `:
-
-Application Bundles
-~~~~~~~~~~~~~~~~~~~
-
-When Symfony 2.0 was released, most developers naturally adopted the symfony
-1.x way of dividing applications into logical modules. That's why many Symfony
-applications used bundles to divide their code into logical features: UserBundle,
-ProductBundle, InvoiceBundle, etc.
-
-But a bundle is *meant* to be something that can be reused as a stand-alone
-piece of software. If UserBundle cannot be used *"as is"* in other Symfony
-applications, then it shouldn't be its own bundle. Moreover, if InvoiceBundle
-depends on ProductBundle, then there's no advantage to having two separate bundles.
-
-.. best-practice::
-
- Don't create any bundle to organize your application logic.
-
-Symfony applications can still use third-party bundles (installed in ``vendor/``)
-to add features, but you should use PHP namespaces instead of bundles to organize
-your own code.
-
-----
-
-Next: :doc:`/best_practices/configuration`
-
-.. _`Composer`: https://getcomposer.org/
-.. _`Symfony Flex`: https://github.com/symfony/flex
-.. _`Symfony Skeleton`: https://github.com/symfony/skeleton
diff --git a/best_practices/forms.rst b/best_practices/forms.rst
deleted file mode 100644
index 79a95da0be1..00000000000
--- a/best_practices/forms.rst
+++ /dev/null
@@ -1,221 +0,0 @@
-Forms
-=====
-
-Forms are one of the most misused Symfony components due to its vast scope and
-endless list of features. In this chapter we'll show you some of the best
-practices so you can leverage forms but get work done quickly.
-
-Building Forms
---------------
-
-.. best-practice::
-
- Define your forms as PHP classes.
-
-The Form component allows you to build forms right inside your controller code.
-This is perfectly fine if you don't need to reuse the form somewhere else. But
-for organization and reuse, we recommend that you define each form in its own
-PHP class::
-
- namespace App\Form;
-
- use App\Entity\Post;
- use Symfony\Component\Form\AbstractType;
- use Symfony\Component\Form\FormBuilderInterface;
- use Symfony\Component\OptionsResolver\OptionsResolver;
- use Symfony\Component\Form\Extension\Core\Type\TextareaType;
- use Symfony\Component\Form\Extension\Core\Type\EmailType;
- use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
-
- class PostType extends AbstractType
- {
- public function buildForm(FormBuilderInterface $builder, array $options)
- {
- $builder
- ->add('title')
- ->add('summary', TextareaType::class)
- ->add('content', TextareaType::class)
- ->add('authorEmail', EmailType::class)
- ->add('publishedAt', DateTimeType::class)
- ;
- }
-
- public function configureOptions(OptionsResolver $resolver)
- {
- $resolver->setDefaults([
- 'data_class' => Post::class,
- ]);
- }
- }
-
-.. best-practice::
-
- Put the form type classes in the ``App\Form`` namespace, unless you
- use other custom form classes like data transformers.
-
-To use the class, use ``createForm()`` and pass the fully qualified class name::
-
- // ...
- use App\Form\PostType;
-
- // ...
- public function new(Request $request)
- {
- $post = new Post();
- $form = $this->createForm(PostType::class, $post);
-
- // ...
- }
-
-Form Button Configuration
--------------------------
-
-Form classes should try to be agnostic to *where* they will be used. This
-makes them easier to re-use later.
-
-.. best-practice::
-
- Add buttons in the templates, not in the form classes or the controllers.
-
-The Symfony Form component allows you to add buttons as fields on your form.
-This is a nice way to simplify the template that renders your form. But if you
-add the buttons directly in your form class, this would effectively limit the
-scope of that form::
-
- class PostType extends AbstractType
- {
- public function buildForm(FormBuilderInterface $builder, array $options)
- {
- $builder
- // ...
- ->add('save', SubmitType::class, ['label' => 'Create Post'])
- ;
- }
-
- // ...
- }
-
-This form *may* have been designed for creating posts, but if you wanted
-to reuse it for editing posts, the button label would be wrong. Instead,
-some developers configure form buttons in the controller::
-
- namespace App\Controller\Admin;
-
- use App\Entity\Post;
- use App\Form\PostType;
- use Symfony\Component\HttpFoundation\Request;
- use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
- use Symfony\Component\Form\Extension\Core\Type\SubmitType;
-
- class PostController extends AbstractController
- {
- // ...
-
- public function new(Request $request)
- {
- $post = new Post();
- $form = $this->createForm(PostType::class, $post);
- $form->add('submit', SubmitType::class, [
- 'label' => 'Create',
- 'attr' => ['class' => 'btn btn-default pull-right'],
- ]);
-
- // ...
- }
- }
-
-This is also an important error, because you are mixing presentation markup
-(labels, CSS classes, etc.) with pure PHP code. Separation of concerns is
-always a good practice to follow, so put all the view-related things in the
-view layer:
-
-.. code-block:: html+twig
-
- {{ form_start(form) }}
- {{ form_widget(form) }}
-
-
- {{ form_end(form) }}
-
-Validation
-----------
-
-The :ref:`constraints ` option allows you to
-attach :doc:`validation constraints ` to any form field.
-However, doing that prevents the validation from being reused in other forms or
-other places where the mapped object is used.
-
-.. best-practice::
-
- Do not define your validation constraints in the form but on the object the
- form is mapped to.
-
-For example, to validate that the title of the post edited with a form is not
-blank, add the following in the ``Post`` object::
-
- // src/Entity/Post.php
-
- // ...
- use Symfony\Component\Validator\Constraints as Assert;
-
- class Post
- {
- /**
- * @Assert\NotBlank
- */
- public $title;
- }
-
-Rendering the Form
-------------------
-
-There are a lot of ways to render your form, ranging from rendering the entire
-thing in one line to rendering each part of each field independently. The
-best way depends on how much customization you need.
-
-One of the simplest ways - which is especially useful during development -
-is to render the form tags and use the ``form_widget()`` function to render
-all of the fields:
-
-.. code-block:: html+twig
-
- {{ form_start(form, {attr: {class: 'my-form-class'} }) }}
- {{ form_widget(form) }}
- {{ form_end(form) }}
-
-If you need more control over how your fields are rendered, then you should
-remove the ``form_widget(form)`` function and render your fields individually.
-See :doc:`/form/form_customization` for more information on this and how you
-can control *how* the form renders at a global level using form theming.
-
-Handling Form Submits
----------------------
-
-Handling a form submit usually follows a similar template::
-
- public function new(Request $request)
- {
- // build the form ...
-
- $form->handleRequest($request);
-
- if ($form->isSubmitted() && $form->isValid()) {
- $entityManager = $this->getDoctrine()->getManager();
- $entityManager->persist($post);
- $entityManager->flush();
-
- return $this->redirectToRoute('admin_post_show', [
- 'id' => $post->getId()
- ]);
- }
-
- // render the template
- }
-
-We recommend that you use a single action for both rendering the form and
-handling the form submit. For example, you *could* have a ``new()`` action that
-*only* renders the form and a ``create()`` action that *only* processes the form
-submit. Both those actions will be almost identical. So it's much simpler to let
-``new()`` handle everything.
-
-Next: :doc:`/best_practices/i18n`
diff --git a/best_practices/i18n.rst b/best_practices/i18n.rst
deleted file mode 100644
index ca4dd0c4c4b..00000000000
--- a/best_practices/i18n.rst
+++ /dev/null
@@ -1,82 +0,0 @@
-Internationalization
-====================
-
-Internationalization and localization adapt the applications and their contents
-to the specific region or language of the users. In Symfony this is an opt-in
-feature that needs to be installed before using it (``composer require symfony/translation``).
-
-Translation Source File Location
---------------------------------
-
-.. best-practice::
-
- Store the translation files in the ``translations/`` directory at the root
- of your project.
-
-Your translators' lives will be much easier if all the application translations
-are in one central location.
-
-Translation Source File Format
-------------------------------
-
-The Symfony Translation component supports lots of different translation
-formats: PHP, Qt, ``.po``, ``.mo``, JSON, CSV, INI, etc.
-
-.. best-practice::
-
- Use the XLIFF format for your translation files.
-
-Of all the available translation formats, only XLIFF and gettext have broad
-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 supports notes in XLIFF files, making them more user-friendly for
-translators. At the end, good translations are all about context, and these
-XLIFF notes allow you to define that context.
-
-.. tip::
-
- The `PHP Translation Bundle`_ includes advanced extractors that can read
- your project and automatically update the XLIFF files.
-
-Translation Keys
-----------------
-
-.. best-practice::
-
- Always use keys for translations instead of content strings.
-
-Using keys simplifies the management of the translation files because you can
-change the original contents without having to update all of 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
-would be ``label.username``, *not* ``edit_form.label.username``.
-
-Example Translation File
-------------------------
-
-Applying all the previous best practices, the sample translation file for
-English in the application would be:
-
-.. code-block:: xml
-
-
-
-
-
-
-
- title.post_list
- Post List
-
-
-
-
-
-----
-
-Next: :doc:`/best_practices/security`
-
-.. _`PHP Translation Bundle`: https://github.com/php-translation/symfony-bundle
diff --git a/best_practices/index.rst b/best_practices/index.rst
deleted file mode 100644
index 8df4abb1364..00000000000
--- a/best_practices/index.rst
+++ /dev/null
@@ -1,19 +0,0 @@
-Official Symfony Best Practices
-===============================
-
-.. toctree::
- :hidden:
-
- introduction
- creating-the-project
- configuration
- business-logic
- controllers
- templates
- forms
- i18n
- security
- web-assets
- tests
-
-.. include:: /best_practices/map.rst.inc
diff --git a/best_practices/introduction.rst b/best_practices/introduction.rst
deleted file mode 100644
index c0a2b0cb44d..00000000000
--- a/best_practices/introduction.rst
+++ /dev/null
@@ -1,106 +0,0 @@
-.. index::
- single: Symfony Framework Best Practices
-
-The Symfony Framework Best Practices
-====================================
-
-The Symfony Framework is well-known for being *really* flexible and is used
-to build micro-sites, enterprise applications that handle billions of connections
-and even as the basis for *other* frameworks. Since its release in July 2011,
-the community has learned a lot about what's possible and how to do things *best*.
-
-These community resources - like blog posts or presentations - have created
-an unofficial set of recommendations for developing Symfony applications.
-Unfortunately, a lot of these recommendations are unneeded for web applications.
-Much of the time, they unnecessarily overcomplicate things and don't follow the
-original pragmatic philosophy of Symfony.
-
-What is this Guide About?
--------------------------
-
-This guide aims to fix that by describing the **best practices for developing
-web applications with the Symfony full-stack Framework**. These are best practices
-that fit the philosophy of the framework as envisioned by its original creator
-`Fabien Potencier`_.
-
-.. note::
-
- **Best practice** is a noun that means *"a well defined procedure that is
- known to produce near-optimum results"*. And that's exactly what this
- guide aims to provide. Even if you don't agree with every recommendation,
- we believe these will help you build great applications with less complexity.
-
-This guide is **specially suited** for:
-
-* Websites and web applications developed with the full-stack Symfony Framework.
-
-For other situations, this guide might be a good **starting point** that you can
-then **extend and fit to your specific needs**:
-
-* Bundles shared publicly to the Symfony community;
-* Advanced developers or teams who have created their own standards;
-* Some complex applications that have highly customized requirements;
-* Bundles that may be shared internally within a company.
-
-We know that old habits die hard and some of you will be shocked by some
-of these best practices. But by following these, you'll be able to develop
-applications faster, with less complexity and with the same or even higher
-quality. It's also a moving target that will continue to improve.
-
-Keep in mind that these are **optional recommendations** that you and your
-team may or may not follow to develop Symfony applications. If you want to
-continue using your own best practices and methodologies, you can still do
-that. Symfony is flexible enough to adapt to your needs. That will never
-change.
-
-Who this Book Is for (Hint: It's not a Tutorial)
-------------------------------------------------
-
-Any Symfony developer, whether you are an expert or a newcomer, can read this
-guide. But since this isn't a tutorial, you'll need some basic knowledge of
-Symfony to follow everything. If you are totally new to Symfony, welcome! and
-read the :doc:`Getting Started guides ` first.
-
-We've deliberately kept this guide short. We won't repeat explanations that
-you can find in the vast Symfony documentation, like discussions about Dependency
-Injection or front controllers. We'll solely focus on explaining how to do
-what you already know.
-
-The Application
----------------
-
-In addition to this guide, a sample application called `Symfony Demo`_ has been
-developed with all these best practices in mind. Execute this command to download
-the demo application:
-
-.. code-block:: terminal
-
- $ composer create-project symfony/symfony-demo
-
-**The demo application is a simple blog engine**, because that will allow us to
-focus on the Symfony concepts and features without getting buried in difficult
-implementation details. Instead of developing the application step by step in
-this guide, you'll find selected snippets of code through the chapters.
-
-Don't Update Your Existing Applications
----------------------------------------
-
-After reading this handbook, some of you may be considering refactoring your
-existing Symfony applications. Our recommendation is sound and clear: you may
-use these best practices for **new applications** but **you should not refactor
-your existing applications to comply with these best practices**. The reasons
-for not doing it are various:
-
-* Your existing applications are not wrong, they just follow another set of
- guidelines;
-* A full codebase refactorization is prone to introduce errors in your
- applications;
-* The amount of work spent on this could be better dedicated to improving
- your tests or adding features that provide real value to the end users.
-
-----
-
-Next: :doc:`/best_practices/creating-the-project`
-
-.. _`Fabien Potencier`: https://connect.symfony.com/profile/fabpot
-.. _`Symfony Demo`: https://github.com/symfony/demo
diff --git a/best_practices/map.rst.inc b/best_practices/map.rst.inc
deleted file mode 100644
index f9dfd0c3e9d..00000000000
--- a/best_practices/map.rst.inc
+++ /dev/null
@@ -1,11 +0,0 @@
-* :doc:`/best_practices/introduction`
-* :doc:`/best_practices/creating-the-project`
-* :doc:`/best_practices/configuration`
-* :doc:`/best_practices/business-logic`
-* :doc:`/best_practices/controllers`
-* :doc:`/best_practices/templates`
-* :doc:`/best_practices/forms`
-* :doc:`/best_practices/i18n`
-* :doc:`/best_practices/security`
-* :doc:`/best_practices/web-assets`
-* :doc:`/best_practices/tests`
diff --git a/best_practices/security.rst b/best_practices/security.rst
deleted file mode 100644
index f746303d347..00000000000
--- a/best_practices/security.rst
+++ /dev/null
@@ -1,381 +0,0 @@
-Security
-========
-
-Authentication and Firewalls (i.e. Getting the User's Credentials)
-------------------------------------------------------------------
-
-You can configure Symfony to authenticate your users using any method you
-want and to load user information from any source. This is a complex topic, but
-the :doc:`Security guide ` has a lot of information about this.
-
-Regardless of your needs, authentication is configured in ``security.yaml``,
-primarily under the ``firewalls`` key.
-
-.. best-practice::
-
- Unless you have two legitimately different authentication systems and
- users (e.g. form login for the main site and a token system for your
- API only), we recommend having only *one* firewall entry with the ``anonymous``
- key enabled.
-
-Most applications only have one authentication system and one set of users.
-For this reason, you only need *one* firewall entry. There are exceptions
-of course, especially if you have separated web and API sections on your
-site. But the point is to keep things simple.
-
-Additionally, you should use the ``anonymous`` key under your firewall. If
-you need to require users to be logged in for different sections of your
-site (or maybe nearly *all* sections), use the ``access_control`` area.
-
-.. best-practice::
-
- Use the ``bcrypt`` encoder for hashing your users' passwords.
-
-If your users have a password, then we recommend hashing it using the ``bcrypt``
-encoder, instead of the traditional SHA-512 hashing encoder. The main advantages
-of ``bcrypt`` are the inclusion of a *salt* value to protect against rainbow
-table attacks, and its adaptive nature, which allows to make it slower to
-remain resistant to brute-force search attacks.
-
-.. note::
-
- :ref:`Argon2i ` is the hashing algorithm as
- recommended by industry standards, but this won't be available to you unless
- you are using PHP 7.2+ or have the `libsodium`_ extension installed.
- ``bcrypt`` is sufficient for most applications.
-
-With this in mind, here is the authentication setup from our application,
-which uses a login form to load users from the database:
-
-.. code-block:: yaml
-
- # config/packages/security.yaml
- security:
- encoders:
- App\Entity\User: bcrypt
-
- providers:
- database_users:
- entity: { class: App\Entity\User, property: username }
-
- firewalls:
- secured_area:
- pattern: ^/
- anonymous: true
- form_login:
- check_path: login
- login_path: login
-
- logout:
- path: security_logout
- target: homepage
-
- # ... access_control exists, but is not shown here
-
-.. tip::
-
- The source code for our project contains comments that explain each part.
-
-Authorization (i.e. Denying Access)
------------------------------------
-
-Symfony gives you several ways to enforce authorization, including the ``access_control``
-configuration in :doc:`security.yaml `, the
-:ref:`@Security annotation ` and using
-:ref:`isGranted ` on the ``security.authorization_checker``
-service directly.
-
-.. best-practice::
-
- * For protecting broad URL patterns, use ``access_control``;
- * Whenever possible, use the ``@Security`` annotation;
- * Check security directly on the ``security.authorization_checker`` service
- whenever you have a more complex situation.
-
-There are also different ways to centralize your authorization logic, like
-with a custom security voter:
-
-.. best-practice::
-
- Define a custom security voter to implement fine-grained restrictions.
-
-.. _best-practices-security-annotation:
-
-The @Security Annotation
-------------------------
-
-For controlling access on a controller-by-controller basis, use the ``@Security``
-annotation whenever possible. Placing it above each action makes it consistent and readable.
-
-In our application, you need the ``ROLE_ADMIN`` in order to create a new post.
-Using ``@Security``, this looks like::
-
- use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
- use Symfony\Component\Routing\Annotation\Route;
- // ...
-
- /**
- * Displays a form to create a new Post entity.
- *
- * @Route("/new", name="admin_post_new")
- * @Security("is_granted('ROLE_ADMIN')")
- */
- public function new()
- {
- // ...
- }
-
-Using Expressions for Complex Security Restrictions
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-If your security logic is a little bit more complex, you can use an :doc:`expression `
-inside ``@Security``. In the following example, a user can only access the
-controller if their email matches the value returned by the ``getAuthorEmail()``
-method on the ``Post`` object::
-
- use App\Entity\Post;
- use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
- use Symfony\Component\Routing\Annotation\Route;
-
- /**
- * @Route("/{id}/edit", name="admin_post_edit")
- * @Security("user.getEmail() == post.getAuthorEmail()")
- */
- public function edit(Post $post)
- {
- // ...
- }
-
-Notice that this requires the use of the `ParamConverter`_, which automatically
-queries for the ``Post`` object and puts it on the ``$post`` argument. This
-is what makes it possible to use the ``post`` variable in the expression.
-
-This has one major drawback: an expression in an annotation cannot
-be reused in other parts of the application. Imagine that you want to add
-a link in a template that will only be seen by authors. Right now you'll
-need to repeat the expression code using Twig syntax:
-
-.. code-block:: html+twig
-
- {% if app.user and app.user.email == post.authorEmail %}
- ...
- {% endif %}
-
-A good solution - if your logic is simple enough - can be to add a new method
-to the ``Post`` entity that checks if a given user is its author::
-
- // src/Entity/Post.php
- // ...
-
- class Post
- {
- // ...
-
- /**
- * Is the given User the author of this Post?
- *
- * @return bool
- */
- public function isAuthor(User $user = null)
- {
- return $user && $user->getEmail() === $this->getAuthorEmail();
- }
- }
-
-Now you can reuse this method both in the template and in the security expression::
-
- use App\Entity\Post;
- use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
- use Symfony\Component\Routing\Annotation\Route;
-
- /**
- * @Route("/{id}/edit", name="admin_post_edit")
- * @Security("post.isAuthor(user)")
- */
- public function edit(Post $post)
- {
- // ...
- }
-
-.. code-block:: html+twig
-
- {% if post.isAuthor(app.user) %}
- ...
- {% endif %}
-
-.. _best-practices-directly-isGranted:
-.. _checking-permissions-without-security:
-.. _manually-checking-permissions:
-
-Checking Permissions without @Security
---------------------------------------
-
-The above example with ``@Security`` only works because we're using the
-:ref:`ParamConverter `, which gives the expression
-access to the ``post`` variable. If you don't use this, or have some other
-more advanced use-case, you can always do the same security check in PHP::
-
- /**
- * @Route("/{id}/edit", name="admin_post_edit")
- */
- public function edit($id)
- {
- $post = $this->getDoctrine()
- ->getRepository(Post::class)
- ->find($id);
-
- if (!$post) {
- throw $this->createNotFoundException();
- }
-
- if (!$post->isAuthor($this->getUser())) {
- $this->denyAccessUnlessGranted('edit', $post);
- }
- // equivalent code without using the "denyAccessUnlessGranted()" shortcut:
- //
- // use Symfony\Component\Security\Core\Exception\AccessDeniedException;
- // use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface
- //
- // ...
- //
- // public function __construct(AuthorizationCheckerInterface $authorizationChecker) {
- // $this->authorizationChecker = $authorizationChecker;
- // }
- //
- // ...
- //
- // if (!$this->authorizationChecker->isGranted('edit', $post)) {
- // throw $this->createAccessDeniedException();
- // }
- //
- // ...
- }
-
-Security Voters
----------------
-
-If your security logic is complex and can't be centralized into a method like
-``isAuthor()``, you should leverage custom voters. These are much easier than
-:doc:`ACLs ` and will give you the flexibility you need in almost
-all cases.
-
-First, create a voter class. The following example shows a voter that implements
-the same ``getAuthorEmail()`` logic you used above::
-
- namespace App\Security;
-
- use App\Entity\Post;
- use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
- use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
- use Symfony\Component\Security\Core\Authorization\Voter\Voter;
- use Symfony\Component\Security\Core\User\UserInterface;
-
- class PostVoter extends Voter
- {
- const CREATE = 'create';
- const EDIT = 'edit';
-
- private $decisionManager;
-
- public function __construct(AccessDecisionManagerInterface $decisionManager)
- {
- $this->decisionManager = $decisionManager;
- }
-
- protected function supports($attribute, $subject)
- {
- if (!in_array($attribute, [self::CREATE, self::EDIT])) {
- return false;
- }
-
- if (!$subject instanceof Post) {
- return false;
- }
-
- return true;
- }
-
- protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
- {
- $user = $token->getUser();
- /** @var Post */
- $post = $subject; // $subject must be a Post instance, thanks to the supports method
-
- if (!$user instanceof UserInterface) {
- return false;
- }
-
- switch ($attribute) {
- // if the user is an admin, allow them to create new posts
- case self::CREATE:
- if ($this->decisionManager->decide($token, ['ROLE_ADMIN'])) {
- return true;
- }
-
- break;
-
- // if the user is the author of the post, allow them to edit the posts
- case self::EDIT:
- if ($user->getEmail() === $post->getAuthorEmail()) {
- return true;
- }
-
- break;
- }
-
- return false;
- }
- }
-
-If you're using the :ref:`default services.yaml configuration `,
-your application will :ref:`autoconfigure ` your security
-voter and inject an ``AccessDecisionManagerInterface`` instance into it thanks to
-:doc:`autowiring `.
-
-Now, you can use the voter with the ``@Security`` annotation::
-
- /**
- * @Route("/{id}/edit", name="admin_post_edit")
- * @Security("is_granted('edit', post)")
- */
- public function edit(Post $post)
- {
- // ...
- }
-
-You can also use this directly with the ``security.authorization_checker`` service or
-via the even easier shortcut in a controller::
-
- /**
- * @Route("/{id}/edit", name="admin_post_edit")
- */
- public function edit($id)
- {
- $post = ...; // query for the post
-
- $this->denyAccessUnlessGranted('edit', $post);
-
- // use Symfony\Component\Security\Core\Exception\AccessDeniedException;
- // use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface
- //
- // ...
- //
- // public function __construct(AuthorizationCheckerInterface $authorizationChecker) {
- // $this->authorizationChecker = $authorizationChecker;
- // }
- //
- // ...
- //
- // if (!$this->authorizationChecker->isGranted('edit', $post)) {
- // throw $this->createAccessDeniedException();
- // }
- //
- // ...
- }
-
-Next: :doc:`/best_practices/web-assets`
-
-.. _`ParamConverter`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
-.. _`@Security annotation`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/security.html
-.. _`FOSUserBundle`: https://github.com/FriendsOfSymfony/FOSUserBundle
-.. _`libsodium`: https://pecl.php.net/package/libsodium
diff --git a/best_practices/templates.rst b/best_practices/templates.rst
deleted file mode 100644
index e9970b9a8d7..00000000000
--- a/best_practices/templates.rst
+++ /dev/null
@@ -1,122 +0,0 @@
-Templates
-=========
-
-When PHP was created 20 years ago, developers loved its simplicity and how
-well it blended HTML and dynamic code. But as time passed, other template
-languages - like `Twig`_ - were created to make templating even better.
-
-.. best-practice::
-
- Use Twig templating format for your templates.
-
-Generally speaking, PHP templates are more verbose than Twig templates because
-they lack native support for lots of modern features needed by templates,
-like inheritance, automatic escaping and named arguments for filters and
-functions.
-
-Twig is the default templating format in Symfony and has the largest community
-support of all non-PHP template engines (it's used in high profile projects
-such as Drupal 8).
-
-Template Locations
-------------------
-
-.. best-practice::
-
- Store the application templates in the ``templates/`` directory at the root
- of your project.
-
-Centralizing your templates in a single location simplifies the work of your
-designers. In addition, using this directory simplifies the notation used when
-referring to templates (e.g. ``$this->render('admin/post/show.html.twig')``
-instead of ``$this->render('@SomeTwigNamespace/Admin/Posts/show.html.twig')``).
-
-.. best-practice::
-
- Use lowercased snake_case for directory and template names.
-
-This recommendation aligns with Twig best practices, where variables and template
-names use lowercased snake_case too (e.g. ``user_profile`` instead of ``userProfile``
-and ``edit_form.html.twig`` instead of ``EditForm.html.twig``).
-
-.. best-practice::
-
- Use a prefixed underscore for partial templates in template names.
-
-You often want to reuse template code using the ``include`` function to avoid
-redundant code. To determine those partials in the filesystem you should
-prefix partials and any other template without HTML body or ``extends`` tag
-with a single underscore.
-
-Twig Extensions
----------------
-
-.. best-practice::
-
- Define your Twig extensions in the ``src/Twig/`` directory. Your
- application will automatically detect them and configure them.
-
-Our application needs a custom ``md2html`` Twig filter so that we can transform
-the Markdown contents of each post into HTML. To do this, create a new
-``Markdown`` class that will be used later by the Twig extension. It needs
-to define one single method to transform Markdown content into HTML::
-
- namespace App\Utils;
-
- class Markdown
- {
- // ...
-
- public function toHtml(string $text): string
- {
- return $this->parser->text($text);
- }
- }
-
-Next, create a new Twig extension and define a filter called ``md2html`` using
-the ``Twig\TwigFilter`` class. Inject the newly defined ``Markdown`` class in the
-constructor of the Twig extension::
-
- namespace App\Twig;
-
- use App\Utils\Markdown;
- use Twig\Extension\AbstractExtension;
- use Twig\TwigFilter;
-
- class AppExtension extends AbstractExtension
- {
- private $parser;
-
- public function __construct(Markdown $parser)
- {
- $this->parser = $parser;
- }
-
- public function getFilters()
- {
- return [
- new TwigFilter('md2html', [$this, 'markdownToHtml'], [
- 'is_safe' => ['html'],
- 'pre_escape' => 'html',
- ]),
- ];
- }
-
- public function markdownToHtml($content)
- {
- return $this->parser->toHtml($content);
- }
- }
-
-And that's it!
-
-If you're using the :ref:`default services.yaml configuration `,
-you're done! Symfony will automatically know about your new service and tag it to
-be used as a Twig extension.
-
-----
-
-Next: :doc:`/best_practices/forms`
-
-.. _`Twig`: https://twig.symfony.com/
-.. _`Parsedown`: http://parsedown.org/
diff --git a/best_practices/tests.rst b/best_practices/tests.rst
deleted file mode 100644
index 2c1230cc452..00000000000
--- a/best_practices/tests.rst
+++ /dev/null
@@ -1,124 +0,0 @@
-Tests
-=====
-
-Of all the different types of test available, these best practices focus solely
-on unit and functional tests. Unit testing allows you to test the input and
-output of specific functions. Functional testing allows you to command a
-"browser" where you browse to pages on your site, click links, fill out forms
-and assert that you see certain things on the page.
-
-Unit Tests
-----------
-
-Unit tests are used to test your "business logic", which should live in classes
-that are independent of Symfony. For that reason, Symfony doesn't really
-have an opinion on what tools you use for unit testing. However, the most
-popular tools are `PHPUnit`_ and `PHPSpec`_.
-
-Functional Tests
-----------------
-
-Creating really good functional tests can be tough so some developers skip
-these completely. Don't skip the functional tests! By defining some *simple*
-functional tests, you can quickly spot any big errors before you deploy them:
-
-.. best-practice::
-
- Define a functional test that at least checks if your application pages
- are successfully loading.
-
-:ref:`PHPUnit data providers ` help you implement
-functional tests::
-
- // tests/ApplicationAvailabilityFunctionalTest.php
- namespace App\Tests;
-
- use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
-
- class ApplicationAvailabilityFunctionalTest extends WebTestCase
- {
- /**
- * @dataProvider urlProvider
- */
- public function testPageIsSuccessful($url)
- {
- $client = self::createClient();
- $client->request('GET', $url);
-
- $this->assertTrue($client->getResponse()->isSuccessful());
- }
-
- public function urlProvider()
- {
- yield ['/'];
- yield ['/posts'];
- yield ['/post/fixture-post-1'];
- yield ['/blog/category/fixture-category'];
- yield ['/archives'];
- // ...
- }
- }
-
-This code checks that all the given URLs load successfully, which means that
-their HTTP response status code is between ``200`` and ``299``. This may
-not look that useful, but given how little effort this took, it's worth
-having it in your application.
-
-In computer software, this kind of test is called `smoke testing`_ and consists
-of *"preliminary testing to reveal simple failures severe enough to reject a
-prospective software release"*.
-
-Hardcode URLs in a Functional Test
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Some of you may be asking why the previous functional test doesn't use the URL
-generator service:
-
-.. best-practice::
-
- Hardcode the URLs used in the functional tests instead of using the URL
- generator.
-
-Consider the following functional test that uses the ``router`` service to
-generate the URL of the tested page::
-
- // ...
- private $router; // consider that this holds the Symfony router service
-
- public function testBlogArchives()
- {
- $client = self::createClient();
- $url = $this->router->generate('blog_archives');
- $client->request('GET', $url);
-
- // ...
- }
-
-This will work, but it has one *huge* drawback. If a developer mistakenly
-changes the path of the ``blog_archives`` route, the test will still pass,
-but the original (old) URL won't work! This means that any bookmarks for
-that URL will be broken and you'll lose any search engine page ranking.
-
-Testing JavaScript Functionality
---------------------------------
-
-The built-in functional testing client is great, but it can't be used to
-test any JavaScript behavior on your pages. If you need to test this, consider
-using the `Mink`_ library from within PHPUnit.
-
-Of course, if you have a heavy JavaScript front-end, you should consider using
-pure JavaScript-based testing tools.
-
-Learn More about Functional Tests
----------------------------------
-
-Consider using the `HautelookAliceBundle`_ to generate real-looking data for
-your test fixtures using `Faker`_ and `Alice`_.
-
-.. _`PHPUnit`: https://phpunit.de/
-.. _`PHPSpec`: https://www.phpspec.net/
-.. _`smoke testing`: https://en.wikipedia.org/wiki/Smoke_testing_(software)
-.. _`Mink`: http://mink.behat.org
-.. _`HautelookAliceBundle`: https://github.com/hautelook/AliceBundle
-.. _`Faker`: https://github.com/fzaninotto/Faker
-.. _`Alice`: https://github.com/nelmio/alice
diff --git a/best_practices/web-assets.rst b/best_practices/web-assets.rst
deleted file mode 100644
index 271a1fa3eeb..00000000000
--- a/best_practices/web-assets.rst
+++ /dev/null
@@ -1,34 +0,0 @@
-Web Assets
-==========
-
-Web assets are things like CSS, JavaScript and image files that make the
-frontend of your site look and work great.
-
-.. best-practice::
-
- Store your assets in the ``assets/`` directory at the root of your project.
-
-Your designers' and front-end developers' lives will be much easier if all the
-application assets are in one central location.
-
-.. best-practice::
-
- Use `Webpack Encore`_ to compile, combine and minimize web assets.
-
-`Webpack`_ is the leading JavaScript module bundler that compiles, transforms
-and packages assets for usage in a browser. 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.
-
-Webpack Encore was designed to bridge the gap between Symfony applications and
-the JavaScript-based tools used in modern web applications. Check out the
-`official Webpack Encore documentation`_ to learn more about all the available
-features.
-
-----
-
-Next: :doc:`/best_practices/tests`
-
-.. _`Webpack Encore`: https://github.com/symfony/webpack-encore
-.. _`Webpack`: https://webpack.js.org/
-.. _`official Webpack Encore documentation`: https://symfony.com/doc/current/frontend.html
diff --git a/bundles.rst b/bundles.rst
index 2f0fb608c04..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
@@ -18,26 +15,27 @@ SecurityBundle, DebugBundle, etc.) They are also used to add new features in
your application via `third-party bundles`_.
Bundles used in your applications must be enabled per
-:doc:`environment ` in the ``config/bundles.php``
+:ref:`environment ` in the ``config/bundles.php``
file::
// config/bundles.php
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' and 'test', so you can't use it in 'prod'
+ // ...
+
+ // 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::
- In a default Symfony application that uses :doc:`Symfony Flex `,
+ In a default Symfony application that uses :ref:`Symfony Flex `,
bundles are enabled/disabled automatically for you when installing/removing
them, so you don't need to look at or edit this ``bundles.php`` file.
@@ -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,48 +77,85 @@ 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
--------------------------
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. Take a look at AcmeDemoBundle, as it contains some
-of the most common elements of a bundle:
+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
----------
@@ -128,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 2091016cecf..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,11 +16,12 @@ 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 the first letter uppercased);
+* Use a StudlyCaps name (i.e. camelCase with an uppercase first letter);
* Use a descriptive and short name (no more than two words);
* Prefix the name with the concatenation of the vendor (and optionally the
category namespaces);
@@ -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,82 +195,67 @@ 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
- sudo: false
- 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="weak_vendors"
- - php: 7.0
- env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" SYMFONY_DEPRECATIONS_HELPER="weak_vendors"
-
- # Test the latest stable release
- - php: 7.0
- - 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:
- # To be removed when this issue will be resolved: https://github.com/composer/composer/issues/5355
- - if [[ "$COMPOSER_FLAGS" == *"--prefer-lowest"* ]]; then composer update --prefer-dist --no-interaction --prefer-stable --quiet; fi
- - 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
------------
Bundles should set ``"type": "symfony-bundle"`` in their ``composer.json`` file.
-With this, :doc:`Symfony Flex ` will be able to automatically
+With this, :ref:`Symfony Flex ` will be able to automatically
enable your bundle when it's installed.
If your bundle requires any setup (e.g. configuration, new files, changes to
@@ -257,13 +266,13 @@ 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 symfony.com.
+used to render the documentation on the Symfony website.
Installation Instructions
~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -278,13 +287,17 @@ following standardized instructions in your ``README.md`` file.
Installation
============
+ Make sure Composer is installed globally, as explained in the
+ [installation chapter](https://getcomposer.org/doc/00-intro.md)
+ of the Composer documentation.
+
Applications that use Symfony Flex
----------------------------------
Open a command console, enter your project directory and execute:
```console
- $ composer require
+ composer require
```
Applications that don't use Symfony Flex
@@ -296,36 +309,21 @@ following standardized instructions in your ``README.md`` file.
following command to download the latest stable version of this bundle:
```console
- $ composer require
+ composer require
```
- This command requires you to have Composer installed globally, as explained
- in the [installation chapter](https://getcomposer.org/doc/00-intro.md)
- of the Composer documentation.
-
### Step 2: Enable the Bundle
Then, enable the bundle by adding it to the list of registered bundles
- in the `app/AppKernel.php` file of your project:
+ in the `config/bundles.php` file of your project:
```php
- // app/AppKernel.php
-
- // ...
- class AppKernel extends Kernel
- {
- public function registerBundles()
- {
- $bundles = [
- // ...
- new \\(),
- ];
-
- // ...
- }
+ // config/bundles.php
+ return [
// ...
- }
+ \\::class => ['all' => true],
+ ];
```
.. code-block:: rst
@@ -333,14 +331,16 @@ following standardized instructions in your ``README.md`` file.
Installation
============
- Applications that use Symfony Flex
+ Make sure Composer is installed globally, as explained in the
+ `installation chapter`_ of the Composer documentation.
+
----------------------------------
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
----------------------------------------
@@ -353,35 +353,19 @@ following standardized instructions in your ``README.md`` file.
.. code-block:: terminal
- $ composer require
-
- This command requires you to have Composer installed globally, as explained
- in the `installation chapter`_ of the Composer documentation.
+ composer require
Step 2: Enable the Bundle
~~~~~~~~~~~~~~~~~~~~~~~~~
Then, enable the bundle by adding it to the list of registered bundles
- in the ``app/AppKernel.php`` file of your project::
-
- // app/AppKernel.php
-
- // ...
- class AppKernel extends Kernel
- {
- public function registerBundles()
- {
- $bundles = [
- // ...
-
- new \\(),
- ];
-
- // ...
- }
+ in the ``config/bundles.php`` file of your project::
+ // config/bundles.php
+ return [
// ...
- }
+ \\::class => ['all' => true],
+ ];
.. _`installation chapter`: https://getcomposer.org/doc/00-intro.md
@@ -412,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
-------------
@@ -446,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
@@ -457,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::
@@ -475,8 +469,11 @@ Bundles must be versioned following the `Semantic Versioning Standard`_.
Services
--------
-If the bundle defines services, they must be prefixed with the bundle alias.
-For example, AcmeBlogBundle services must be prefixed with ``acme_blog``.
+If the bundle defines services, they must be prefixed with the bundle alias
+instead of using fully qualified class names like you do in your project
+services. For example, AcmeBlogBundle services must be prefixed with ``acme_blog``.
+The reason is that bundles shouldn't rely on features such as service autowiring
+or autoconfiguration to not impose an overhead when compiling application services.
In addition, services not meant to be used by the application directly, should
be :ref:`defined as private `. For public services,
@@ -488,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:
@@ -502,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``.
@@ -517,7 +521,24 @@ The ``composer.json`` file should include at least the following metadata:
``autoload``
This information is used by Symfony to load the classes of the bundle. It's
- recommended to use the `PSR-4`_ autoload standard.
+ 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/"
+ }
+ }
+ }
In order to make it easier for developers to find your bundle, register it on
`Packagist`_, the official repository for Composer packages.
@@ -526,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
----------
@@ -549,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 a86cca53f11..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,27 +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', [
- '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,
@@ -95,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::
@@ -105,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.
@@ -170,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;
@@ -178,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');
@@ -197,10 +301,6 @@ The ``Configuration`` class to handle the sample configuration looks like::
}
}
-.. deprecated:: 4.2
-
- Not passing the root node name to ``TreeBuilder`` was deprecated in Symfony 4.2.
-
.. seealso::
The ``Configuration`` class can be much more complicated than shown here,
@@ -215,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();
@@ -235,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"
+ >
-
+
@@ -252,13 +352,13 @@ For example, imagine your bundle has the following example config:
In your extension, you can load this and dynamically set its arguments::
- // src/Acme/SocialBundle/DependencyInjection/AcmeSocialExtension.php
- // ...
+ // src/DependencyInjection/AcmeSocialExtension.php
+ namespace Acme\SocialBundle\DependencyInjection;
- use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\Config\FileLocator;
+ use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
- public function load(array $configs, ContainerBuilder $container)
+ public function load(array $configs, ContainerBuilder $container): void
{
$loader = new XmlFileLoader($container, new FileLocator(dirname(__DIR__).'/Resources/config'));
$loader->load('services.xml');
@@ -266,7 +366,7 @@ In your extension, you can load this and dynamically set its arguments::
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
- $definition = $container->getDefinition('acme.social.twitter_client');
+ $definition = $container->getDefinition('acme_social.twitter_client');
$definition->replaceArgument(0, $config['twitter']['client_id']);
$definition->replaceArgument(1, $config['twitter']['client_secret']);
}
@@ -278,7 +378,7 @@ In your extension, you can load this and dynamically set its arguments::
:class:`Symfony\\Component\\HttpKernel\\DependencyInjection\\ConfigurableExtension`
to do this automatically for you::
- // src/Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php
+ // src/DependencyInjection/HelloExtension.php
namespace Acme\HelloBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -287,7 +387,7 @@ In your extension, you can load this and dynamically set its arguments::
class AcmeHelloExtension extends ConfigurableExtension
{
// note that this method is called loadInternal and not load
- protected function loadInternal(array $mergedConfig, ContainerBuilder $container)
+ protected function loadInternal(array $mergedConfig, ContainerBuilder $container): void
{
// ...
}
@@ -303,7 +403,7 @@ In your extension, you can load this and dynamically set its arguments::
(e.g. by overriding configurations and using :phpfunction:`isset` to check
for the existence of a value). Be aware that it'll be very hard to support XML::
- public function load(array $configs, ContainerBuilder $container)
+ public function load(array $configs, ContainerBuilder $container): void
{
$config = [];
// let resources override the previous set value
@@ -329,10 +429,10 @@ The ``config:dump-reference`` command dumps the default configuration of a
bundle in the console using the Yaml format.
As long as your bundle's configuration is located in the standard location
-(``YourBundle\DependencyInjection\Configuration``) and does not have
-a constructor it will work automatically. If you
+(``/src/DependencyInjection/Configuration``) and does not have
+a constructor, it will work automatically. If you
have something different, your ``Extension`` class must override the
-:method:`Extension::getConfiguration() `
+:method:`Extension::getConfiguration() `
method and return an instance of your ``Configuration``.
Supporting XML
@@ -359,18 +459,19 @@ In XML, the `XML namespace`_ is used to determine which elements belong to the
configuration of a specific bundle. The namespace is returned from the
:method:`Extension::getNamespace() `
method. By convention, the namespace is a URL (it doesn't have to be a valid
-URL nor does it need to exists). By default, the namespace for a bundle is
+URL nor does it need to exist). By default, the namespace for a bundle is
``http://example.org/schema/dic/DI_ALIAS``, where ``DI_ALIAS`` is the DI alias of
the extension. You might want to change this to a more professional URL::
- // src/Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php
+ // src/DependencyInjection/AcmeHelloExtension.php
+ namespace Acme\HelloBundle\DependencyInjection;
// ...
class AcmeHelloExtension extends Extension
{
// ...
- public function getNamespace()
+ public function getNamespace(): string
{
return 'http://acme_company.com/schema/dic/hello';
}
@@ -381,7 +482,7 @@ Providing an XML Schema
XML has a very useful feature called `XML schema`_. This allows you to
describe all possible elements and attributes and their values in an XML Schema
-Definition (an xsd file). This XSD file is used by IDEs for auto completion and
+Definition (an XSD file). This XSD file is used by IDEs for auto completion and
it is used by the Config component to validate the elements.
In order to use the schema, the XML configuration file must provide an
@@ -392,19 +493,20 @@ namespace is then replaced with the XSD validation base path returned from
method. This namespace is then followed by the rest of the path from the base
path to the file itself.
-By convention, the XSD file lives in the ``Resources/config/schema/``, but you
+By convention, the XSD file lives in ``config/schema/`` directory, but you
can place it anywhere you like. You should return this path as the base path::
- // src/Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php
+ // src/DependencyInjection/AcmeHelloExtension.php
+ namespace Acme\HelloBundle\DependencyInjection;
// ...
class AcmeHelloExtension extends Extension
{
// ...
- public function getXsdValidationBasePath()
+ public function getXsdValidationBasePath(): string
{
- return __DIR__.'/../Resources/config/schema';
+ return __DIR__.'/../config/schema';
}
}
@@ -414,13 +516,15 @@ Assuming the XSD file is called ``hello-1.0.xsd``, the schema location will be
.. code-block:: xml
-
+
-
+ xsi:schemaLocation="http://symfony.com/schema/dic/services
+ https://symfony.com/schema/dic/services/services-1.0.xsd
+ http://acme_company.com/schema/dic/hello
+ https://acme_company.com/schema/dic/hello/hello-1.0.xsd"
+ >
diff --git a/bundles/extension.rst b/bundles/extension.rst
index adef497bcf8..d2792efc477 100644
--- a/bundles/extension.rst
+++ b/bundles/extension.rst
@@ -1,20 +1,79 @@
-.. index::
- single: Configuration; Semantic
- single: Bundle; Extension configuration
-
How to Load Service Configuration inside a Bundle
=================================================
Services created by bundles are not defined in the main ``config/services.yaml``
file used by the application but in the bundles themselves. This article
-explains how to create and load those bundle services files.
+explains how to create and load service files using the bundle directory
+structure.
+
+There are two different ways of doing it:
+
+#. :ref:`Load your services in the main bundle class `:
+ this is recommended for new bundles and for bundles following the
+ :ref:`recommended directory structure `;
+#. :ref:`Create an extension class to load the service configuration files `:
+ this was the traditional way of doing it, but nowadays it's only recommended for
+ bundles following the :ref:`legacy directory structure `.
+
+.. _bundle-load-services-bundle-class:
+
+Loading Services Directly in your Bundle Class
+----------------------------------------------
+
+In bundles extending the :class:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle`
+class, you can define the :method:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle::loadExtension`
+method to load service definitions from configuration files::
+
+ // ...
+ use Symfony\Component\DependencyInjection\ContainerBuilder;
+ use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
+ use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
+
+ class AcmeHelloBundle extends AbstractBundle
+ {
+ public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
+ {
+ // load an XML, PHP or YAML file
+ $container->import('../config/services.xml');
+
+ // you can also add or replace parameters and services
+ $container->parameters()
+ ->set('acme_hello.phrase', $config['phrase'])
+ ;
+
+ if ($config['scream']) {
+ $container->services()
+ ->get('acme_hello.printer')
+ ->class(ScreamingPrinter::class)
+ ;
+ }
+ }
+ }
+
+This method works similar to the ``Extension::load()`` method explained below,
+but it uses a new simpler API to define and import service configuration.
+
+.. note::
+
+ Contrary to the ``$configs`` parameter in ``Extension::load()``, the
+ ``$config`` parameter is already merged and processed by the
+ ``AbstractBundle``.
+
+.. note::
+
+ The ``loadExtension()`` is called only at compile time.
+
+.. _bundle-load-services-extension:
Creating an Extension Class
---------------------------
-In order to load service configuration, you have to create a Dependency
-Injection (DI) Extension for your bundle. By default, the Extension class must
-follow these conventions (but later you'll learn how to skip them if needed):
+This is the traditional way of loading service definitions in bundles. For new
+bundles it's recommended to :ref:`load your services in the main bundle class `,
+but the traditional way of creating an extension class still works.
+
+A dependency injection extension is defined as a class that follows these
+conventions (later you'll learn how to skip them if needed):
* It has to live in the ``DependencyInjection`` namespace of the bundle;
@@ -23,13 +82,13 @@ follow these conventions (but later you'll learn how to skip them if needed):
:class:`Symfony\\Component\\DependencyInjection\\Extension\\Extension` class;
* The name is equal to the bundle name with the ``Bundle`` suffix replaced by
- ``Extension`` (e.g. the Extension class of the AcmeBundle would be called
+ ``Extension`` (e.g. the extension class of the AcmeBundle would be called
``AcmeExtension`` and the one for AcmeHelloBundle would be called
``AcmeHelloExtension``).
This is how the extension of an AcmeHelloBundle should look like::
- // src/Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php
+ // src/DependencyInjection/AcmeHelloExtension.php
namespace Acme\HelloBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -37,7 +96,7 @@ This is how the extension of an AcmeHelloBundle should look like::
class AcmeHelloExtension extends Extension
{
- public function load(array $configs, ContainerBuilder $container)
+ public function load(array $configs, ContainerBuilder $container): void
{
// ... you'll load the files here later
}
@@ -53,10 +112,11 @@ method to return the instance of the extension::
// ...
use Acme\HelloBundle\DependencyInjection\UnconventionalExtensionClass;
+ use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
class AcmeHelloBundle extends Bundle
{
- public function getContainerExtension()
+ public function getContainerExtension(): ?ExtensionInterface
{
return new UnconventionalExtensionClass();
}
@@ -72,7 +132,7 @@ class name to underscores (e.g. ``AcmeHelloExtension``'s DI alias is
``acme_hello``).
Using the ``load()`` Method
----------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
In the ``load()`` method, all services and parameters related to this extension
will be loaded. This method doesn't get the actual container instance, but a
@@ -86,17 +146,17 @@ but it is more common if you put these definitions in a configuration file
(using the YAML, XML or PHP format).
For instance, assume you have a file called ``services.xml`` in the
-``Resources/config/`` directory of your bundle, your ``load()`` method looks like::
+``config/`` directory of your bundle, your ``load()`` method looks like::
- use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\Config\FileLocator;
+ use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
// ...
- public function load(array $configs, ContainerBuilder $container)
+ public function load(array $configs, ContainerBuilder $container): void
{
$loader = new XmlFileLoader(
$container,
- new FileLocator(__DIR__.'/../Resources/config')
+ new FileLocator(__DIR__.'/../../config')
);
$loader->load('services.xml');
}
@@ -118,19 +178,15 @@ they are compiled when generating the application cache to improve the overall
performance. Define the list of annotated classes to compile in the
``addAnnotatedClassesToCompile()`` method::
- use App\Manager\UserManager;
- use App\Utils\Slugger;
-
- // ...
- public function load(array $configs, ContainerBuilder $container)
+ public function load(array $configs, ContainerBuilder $container): void
{
// ...
$this->addAnnotatedClassesToCompile([
// you can define the fully qualified class names...
- 'App\\Controller\\DefaultController',
+ 'Acme\\BlogBundle\\Controller\\AuthorController',
// ... but glob patterns are also supported:
- '**Bundle\\Controller\\',
+ 'Acme\\BlogBundle\\Form\\**',
// ...
]);
@@ -145,7 +201,7 @@ Patterns are transformed into the actual class namespaces using the classmap
generated by Composer. Therefore, before using these patterns, you must generate
the full classmap executing the ``dump-autoload`` command of Composer.
-.. caution::
+.. warning::
This technique can't be used when the classes to compile use the ``__DIR__``
or ``__FILE__`` constants, because their values will change when loading
diff --git a/bundles/index.rst b/bundles/index.rst
index 78dd8c6d4fb..58bcd13761e 100644
--- a/bundles/index.rst
+++ b/bundles/index.rst
@@ -1,5 +1,3 @@
-:orphan:
-
Bundles
=======
@@ -7,7 +5,6 @@ Bundles
:maxdepth: 2
override
- inheritance
best_practices
configuration
extension
diff --git a/bundles/inheritance.rst b/bundles/inheritance.rst
deleted file mode 100644
index d8ce372adb4..00000000000
--- a/bundles/inheritance.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-.. index::
- single: Bundle; Inheritance
-
-How to Use Bundle Inheritance to Override Parts of a Bundle
-===========================================================
-
-.. caution::
-
- Bundle inheritance was removed in Symfony 4.0, but you can
- :doc:`override any part of a bundle ` without
- using bundle inheritance.
diff --git a/bundles/override.rst b/bundles/override.rst
index b73b245d13b..f25bd785373 100644
--- a/bundles/override.rst
+++ b/bundles/override.rst
@@ -1,6 +1,3 @@
-.. index::
- single: Bundle; Inheritance
-
How to Override any Part of a Bundle
====================================
@@ -8,14 +5,6 @@ When using a third-party bundle, you might want to customize or override some of
its features. This document describes ways of overriding the most common
features of a bundle.
-.. tip::
-
- The bundle overriding mechanism means that you cannot use physical paths to
- refer to bundle's resources (e.g. ``__DIR__/config/services.xml``). Always
- use logical paths in your bundles (e.g. ``@FooBundle/Resources/config/services.xml``)
- and call the :ref:`locateResource() method `
- to turn them into physical paths when needed.
-
.. _override-templates:
Templates
@@ -23,14 +12,14 @@ Templates
Third-party bundle templates can be overridden in the
``/templates/bundles//`` directory. The new templates
-must use the same name and path (relative to ``/Resources/views/``) as
+must use the same name and path (relative to ``/templates/``) as
the original templates.
-For example, to override the ``Resources/views/Registration/confirmed.html.twig``
-template from the FOSUserBundle, create this template:
-``/templates/bundles/FOSUserBundle/Registration/confirmed.html.twig``
+For example, to override the ``templates/registration/confirmed.html.twig``
+template from the AcmeUserBundle, create this template:
+``/templates/bundles/AcmeUserBundle/registration/confirmed.html.twig``
-.. caution::
+.. warning::
If you add a template in a new location, you *may* need to clear your
cache (``php bin/console cache:clear``), even if you are in debug mode.
@@ -43,9 +32,9 @@ extend from the original template, not from the overridden one:
.. code-block:: twig
- {# templates/bundles/FOSUserBundle/Registration/confirmed.html.twig #}
+ {# templates/bundles/AcmeUserBundle/registration/confirmed.html.twig #}
{# the special '!' prefix avoids errors when extending from an overridden template #}
- {% extends "@!FOSUserBundle/Registration/confirmed.html.twig" %}
+ {% extends "@!AcmeUser/registration/confirmed.html.twig" %}
{% block some_block %}
...
@@ -92,14 +81,10 @@ inside a :doc:`compiler pass `.
Entities & Entity Mapping
-------------------------
-If a bundle defines its entity mapping in configuration files instead of
-annotations, you can override them as any other regular bundle configuration
-file. The only caveat is that you must override all those mapping configuration
-files and not just the ones you actually want to override.
-
-If a bundle provides a mapped superclass (such as the ``User`` entity in the
-FOSUserBundle) you can override its attributes and associations. Learn more
-about this feature and its limitations in `the Doctrine documentation`_.
+Overriding entity mapping is only possible if a bundle provides a mapped
+superclass (such as the ``User`` entity in the FOSUserBundle). It's possible to
+override attributes and associations in this way. Learn more about this feature
+and its limitations in `the Doctrine documentation`_.
Forms
-----
@@ -143,8 +128,8 @@ to a new validation group:
-
+ https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd"
+ >
@@ -172,12 +157,12 @@ instead of the original ones.
Translations
------------
-Translations are not related to bundles, but to :ref:`translation domains `.
+Translations are not related to bundles, but to translation domains.
For this reason, you can override any bundle translation file from the main
``translations/`` directory, as long as the new file uses the same domain.
For example, to override the translations defined in the
-``Resources/translations/FOSUserBundle.es.yml`` file of the FOSUserBundle,
-create a``/translations/FOSUserBundle.es.yml`` file.
+``translations/AcmeUserBundle.es.yaml`` file of the AcmeUserBundle,
+create a ``/translations/AcmeUserBundle.es.yaml`` file.
-.. _`the Doctrine documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html#overrides
+.. _`the Doctrine documentation`: https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/inheritance-mapping.html#overrides
diff --git a/bundles/prepend_extension.rst b/bundles/prepend_extension.rst
index 642da040128..e4099d9f81a 100644
--- a/bundles/prepend_extension.rst
+++ b/bundles/prepend_extension.rst
@@ -1,14 +1,10 @@
-.. index::
- single: Configuration; Semantic
- single: Bundle; Extension configuration
-
How to Simplify Configuration of Multiple Bundles
=================================================
When building reusable and extensible applications, developers are often
faced with a choice: either create a single large bundle or multiple smaller
bundles. Creating a single bundle has the drawback that it's impossible for
-users to choose to remove functionality they are not using. Creating multiple
+users to remove unused functionality. Creating multiple
bundles has the drawback that configuration becomes more tedious and settings
often need to be repeated for various bundles.
@@ -27,15 +23,15 @@ To give an Extension the power to do this, it needs to implement
// src/Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php
namespace Acme\HelloBundle\DependencyInjection;
- use Symfony\Component\HttpKernel\DependencyInjection\Extension;
- use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
+ use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
+ use Symfony\Component\HttpKernel\DependencyInjection\Extension;
class AcmeHelloExtension extends Extension implements PrependExtensionInterface
{
// ...
- public function prepend(ContainerBuilder $container)
+ public function prepend(ContainerBuilder $container): void
{
// ...
}
@@ -56,7 +52,7 @@ a configuration setting in multiple bundles as well as disable a flag in multipl
in case a specific other bundle is not registered::
// src/Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php
- public function prepend(ContainerBuilder $container)
+ public function prepend(ContainerBuilder $container): void
{
// get all bundles
$bundles = $container->getParameter('kernel.bundles');
@@ -65,32 +61,31 @@ in case a specific other bundle is not registered::
// disable AcmeGoodbyeBundle in bundles
$config = ['use_acme_goodbye' => false];
foreach ($container->getExtensions() as $name => $extension) {
- switch ($name) {
- case 'acme_something':
- case 'acme_other':
- // set use_acme_goodbye to false in the config of
- // acme_something and acme_other
- //
- // note that if the user manually configured
- // use_acme_goodbye to true in config/services.yaml
- // then the setting would in the end be true and not false
- $container->prependExtensionConfig($name, $config);
- break;
- }
+ match ($name) {
+ // set use_acme_goodbye to false in the config of
+ // acme_something and acme_other
+ //
+ // note that if the user manually configured
+ // use_acme_goodbye to true in config/services.yaml
+ // then the setting would in the end be true and not false
+ 'acme_something', 'acme_other' => $container->prependExtensionConfig($name, $config),
+ default => null
+ };
}
}
- // process the configuration of AcmeHelloExtension
+ // get the configuration of AcmeHelloExtension (it's a list of configuration)
$configs = $container->getExtensionConfig($this->getAlias());
- // use the Configuration class to generate a config array with
- // the settings "acme_hello"
- $config = $this->processConfiguration(new Configuration(), $configs);
-
- // check if entity_manager_name is set in the "acme_hello" configuration
- if (isset($config['entity_manager_name'])) {
- // prepend the acme_something settings with the entity_manager_name
- $config = ['entity_manager_name' => $config['entity_manager_name']];
- $container->prependExtensionConfig('acme_something', $config);
+
+ // iterate in reverse to preserve the original order after prepending the config
+ foreach (array_reverse($configs) as $config) {
+ // check if entity_manager_name is set in the "acme_hello" configuration
+ if (isset($config['entity_manager_name'])) {
+ // prepend the acme_something settings with the entity_manager_name
+ $container->prependExtensionConfig('acme_something', [
+ 'entity_manager_name' => $config['entity_manager_name'],
+ ]);
+ }
}
}
@@ -122,28 +117,103 @@ registered and the ``entity_manager_name`` setting for ``acme_hello`` is set to
xmlns:acme-something="http://example.org/schema/dic/acme_something"
xmlns:acme-other="http://example.org/schema/dic/acme_other"
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://example.org/schema/dic/acme_something
+ https://example.org/schema/dic/acme_something/acme_something-1.0.xsd
+ http://example.org/schema/dic/acme_other
+ https://example.org/schema/dic/acme_something/acme_other-1.0.xsd"
+ >
+
non_default
-
+
+
+
.. code-block:: php
// config/packages/acme_something.php
- $container->loadFromExtension('acme_something', [
+ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+ return static function (ContainerConfigurator $container): void {
+ $container->extension('acme_something', [
+ // ...
+ 'use_acme_goodbye' => false,
+ 'entity_manager_name' => 'non_default',
+ ]);
+ $container->extension('acme_other', [
+ // ...
+ 'use_acme_goodbye' => false,
+ ]);
+ };
+
+Prepending Extension in the Bundle Class
+----------------------------------------
+
+You can also prepend extension configuration directly in your
+Bundle class if you extend from the :class:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle`
+class and define the :method:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle::prependExtension`
+method::
+
+ use Symfony\Component\DependencyInjection\ContainerBuilder;
+ use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
+ use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
+
+ class FooBundle extends AbstractBundle
+ {
+ public function prependExtension(ContainerConfigurator $containerConfigurator, ContainerBuilder $containerBuilder): void
+ {
+ // prepend
+ $containerBuilder->prependExtensionConfig('framework', [
+ 'cache' => ['prefix_seed' => 'foo/bar'],
+ ]);
+
+ // prepend config from a file
+ $containerConfigurator->import('../config/packages/cache.php');
+ }
+ }
+
+.. note::
+
+ The ``prependExtension()`` method, like ``prepend()``, is called only at compile time.
+
+.. versionadded:: 7.1
+
+ Starting from Symfony 7.1, calling the :method:`Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ContainerConfigurator::import`
+ method inside ``prependExtension()`` will prepend the given configuration.
+ In previous Symfony versions, this method appended the configuration.
+
+Alternatively, you can use the ``prepend`` parameter of the
+:method:`Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ContainerConfigurator::extension`
+method::
+
+ use Symfony\Component\DependencyInjection\ContainerBuilder;
+ use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
+ use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
+
+ class FooBundle extends AbstractBundle
+ {
+ public function prependExtension(ContainerConfigurator $containerConfigurator, ContainerBuilder $containerBuilder): void
+ {
// ...
- 'use_acme_goodbye' => false,
- 'entity_manager_name' => 'non_default',
- ]);
- $container->loadFromExtension('acme_other', [
+
+ $containerConfigurator->extension('framework', [
+ 'cache' => ['prefix_seed' => 'foo/bar'],
+ ], prepend: true);
+
// ...
- 'use_acme_goodbye' => false,
- ]);
+ }
+ }
+
+.. versionadded:: 7.1
+
+ The ``prepend`` parameter of the
+ :method:`Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ContainerConfigurator::extension`
+ method was added in Symfony 7.1.
More than one Bundle using PrependExtensionInterface
----------------------------------------------------
diff --git a/cache.rst b/cache.rst
new file mode 100644
index 00000000000..83bb5b4cedc
--- /dev/null
+++ b/cache.rst
@@ -0,0 +1,980 @@
+Cache
+=====
+
+Using a cache is a great way of making your application run quicker. The Symfony cache
+component ships with many adapters to different storages. Every adapter is
+developed for high performance.
+
+The following example shows a typical usage of the cache::
+
+ use Symfony\Contracts\Cache\ItemInterface;
+
+ // The callable will only be executed on a cache miss.
+ $value = $pool->get('my_cache_key', function (ItemInterface $item): string {
+ $item->expiresAfter(3600);
+
+ // ... do some HTTP request or heavy computations
+ $computedValue = 'foobar';
+
+ return $computedValue;
+ });
+
+ echo $value; // 'foobar'
+
+ // ... and to remove the cache key
+ $pool->delete('my_cache_key');
+
+Symfony supports Cache Contracts and PSR-6/16 interfaces.
+You can read more about these at the :doc:`component documentation `.
+
+.. _cache-configuration-with-frameworkbundle:
+
+Configuring Cache with FrameworkBundle
+--------------------------------------
+
+When configuring the cache component there are a few concepts you should know
+of:
+
+**Pool**
+ This is a service that you will interact with. Each pool will always have
+ its own namespace and cache items. There is never a conflict between pools.
+**Adapter**
+ An adapter is a *template* that you use to create pools.
+**Provider**
+ A provider is a service that some adapters use to connect to the storage.
+ Redis and Memcached are examples of such adapters. If a DSN is used as the
+ provider then a service is automatically created.
+
+.. _cache-app-system:
+
+There are two pools that are always enabled by default. They are ``cache.app`` and
+``cache.system``. The system cache is used for things like annotations, serializer,
+and validation. The ``cache.app`` can be used in your code. You can configure which
+adapter (template) they use by using the ``app`` and ``system`` key like:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/cache.yaml
+ framework:
+ cache:
+ app: cache.adapter.filesystem
+ system: cache.adapter.system
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/cache.php
+ use Symfony\Config\FrameworkConfig;
+
+ return static function (FrameworkConfig $framework): void {
+ $framework->cache()
+ ->app('cache.adapter.filesystem')
+ ->system('cache.adapter.system')
+ ;
+ };
+
+.. tip::
+
+ While it is possible to reconfigure the ``system`` cache, it's recommended
+ to keep the default configuration applied to it by Symfony.
+
+The Cache component comes with a series of adapters pre-configured:
+
+* :doc:`cache.adapter.apcu `
+* :doc:`cache.adapter.array `
+* :doc:`cache.adapter.doctrine_dbal `
+* :doc:`cache.adapter.filesystem `
+* :doc:`cache.adapter.memcached `
+* :doc:`cache.adapter.pdo `
+* :doc:`cache.adapter.psr6 `
+* :doc:`cache.adapter.redis `
+* :ref:`cache.adapter.redis_tag_aware ` (Redis adapter optimized to work with tags)
+
+.. note::
+
+ There's also a special ``cache.adapter.system`` adapter. It's recommended to
+ use it for the :ref:`system cache `. This adapter uses some
+ logic to dynamically select the best possible storage based on your system
+ (either PHP files or APCu).
+
+Some of these adapters could be configured via shortcuts.
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/cache.yaml
+ framework:
+ cache:
+ directory: '%kernel.cache_dir%/pools' # Only used with cache.adapter.filesystem
+
+ default_doctrine_dbal_provider: 'doctrine.dbal.default_connection'
+ default_psr6_provider: 'app.my_psr6_service'
+ default_redis_provider: 'redis://localhost'
+ default_memcached_provider: 'memcached://localhost'
+ default_pdo_provider: 'pgsql:host=localhost'
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/cache.php
+ use Symfony\Config\FrameworkConfig;
+
+ return static function (FrameworkConfig $framework): void {
+ $framework->cache()
+ // Only used with cache.adapter.filesystem
+ ->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
+----------------------------------
+
+You can also create more customized pools:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/cache.yaml
+ framework:
+ cache:
+ default_memcached_provider: 'memcached://localhost'
+
+ pools:
+ # creates a "custom_thing.cache" service
+ # autowireable via "CacheInterface $customThingCache"
+ # uses the "app" cache configuration
+ custom_thing.cache:
+ adapter: cache.app
+
+ # creates a "my_cache_pool" service
+ # autowireable via "CacheInterface $myCachePool"
+ my_cache_pool:
+ adapter: cache.adapter.filesystem
+
+ # uses the default_memcached_provider from above
+ acme.cache:
+ adapter: cache.adapter.memcached
+
+ # control adapter's configuration
+ foobar.cache:
+ adapter: cache.adapter.memcached
+ provider: 'memcached://user:password@example.com'
+
+ # uses the "foobar.cache" pool as its backend but controls
+ # the lifetime and (like all pools) has a separate cache namespace
+ short_cache:
+ adapter: foobar.cache
+ default_lifetime: 60
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/cache.php
+ use Symfony\Config\FrameworkConfig;
+
+ return static function (FrameworkConfig $framework): void {
+ $cache = $framework->cache();
+ $cache->defaultMemcachedProvider('memcached://localhost');
+
+ // creates a "custom_thing.cache" service
+ // autowireable via "CacheInterface $customThingCache"
+ // uses the "app" cache configuration
+ $cache->pool('custom_thing.cache')
+ ->adapters(['cache.app']);
+
+ // creates a "my_cache_pool" service
+ // autowireable via "CacheInterface $myCachePool"
+ $cache->pool('my_cache_pool')
+ ->adapters(['cache.adapter.filesystem']);
+
+ // uses the default_memcached_provider from above
+ $cache->pool('acme.cache')
+ ->adapters(['cache.adapter.memcached']);
+
+ // control adapter's configuration
+ $cache->pool('foobar.cache')
+ ->adapters(['cache.adapter.memcached'])
+ ->provider('memcached://user:password@example.com');
+
+ $cache->pool('short_cache')
+ ->adapters(['foobar.cache'])
+ ->defaultLifetime(60);
+ };
+
+Each pool manages a set of independent cache keys: keys from different pools
+*never* collide, even if they share the same backend. This is achieved by prefixing
+keys with a namespace that's generated by hashing the name of the pool, the name
+of the cache adapter class and a :ref:`configurable seed `
+that defaults to the project directory and compiled container class.
+
+Each custom pool becomes a service whose service ID is the name of the pool
+(e.g. ``custom_thing.cache``). An autowiring alias is also created for each pool
+using the camel case version of its name - e.g. ``custom_thing.cache`` can be
+injected automatically by naming the argument ``$customThingCache`` and type-hinting it
+with either :class:`Symfony\\Contracts\\Cache\\CacheInterface` or
+``Psr\Cache\CacheItemPoolInterface``::
+
+ use Symfony\Contracts\Cache\CacheInterface;
+ // ...
+
+ // from a controller method
+ public function listProducts(CacheInterface $customThingCache): Response
+ {
+ // ...
+ }
+
+ // in a service
+ public function __construct(private CacheInterface $customThingCache)
+ {
+ // ...
+ }
+
+.. tip::
+
+ If you need the namespace to be interoperable with a third-party app,
+ you can take control over auto-generation by setting the ``namespace``
+ attribute of the ``cache.pool`` service tag. For example, you can
+ override the service definition of the adapter:
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/services.yaml
+ services:
+ # ...
+
+ app.cache.adapter.redis:
+ parent: 'cache.adapter.redis'
+ tags:
+ - { name: 'cache.pool', namespace: 'my_custom_namespace' }
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .. 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
+-----------------------
+
+Some providers have specific options that can be configured. The
+:doc:`RedisAdapter ` allows you to
+create providers with the options ``timeout``, ``retry_interval``. etc. To use these
+options with non-default values you need to create your own ``\Redis`` provider
+and use that when configuring the pool.
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/cache.yaml
+ framework:
+ cache:
+ pools:
+ cache.my_redis:
+ adapter: cache.adapter.redis
+ provider: app.my_custom_redis_provider
+
+ services:
+ app.my_custom_redis_provider:
+ class: \Redis
+ factory: ['Symfony\Component\Cache\Adapter\RedisAdapter', 'createConnection']
+ arguments:
+ - 'redis://localhost'
+ - { retry_interval: 2, timeout: 10 }
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+ redis://localhost
+
+ 2
+ 10
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/cache.php
+ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+ 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
+----------------------
+
+Different cache adapters have different strengths and weaknesses. Some might be
+really quick but optimized to store small items and some may be able to contain
+a lot of data but are quite slow. To get the best of both worlds you may use a
+chain of adapters.
+
+A cache chain combines several cache pools into a single one. When storing an
+item in a cache chain, Symfony stores it in all pools sequentially. When
+retrieving an item, Symfony tries to get it from the first pool. If it's not
+found, it tries the next pools until the item is found or an exception is thrown.
+Because of this behavior, it's recommended to define the adapters in the chain
+in order from fastest to slowest.
+
+If an error happens when storing an item in a pool, Symfony stores it in the
+other pools and no exception is thrown. Later, when the item is retrieved,
+Symfony stores the item automatically in all the missing pools.
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/cache.yaml
+ 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'}
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/cache.php
+ 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
+----------------
+
+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 tag could be invalidated with one function call::
+
+ use Symfony\Contracts\Cache\ItemInterface;
+ use Symfony\Contracts\Cache\TagAwareCacheInterface;
+
+ class SomeClass
+ {
+ // using autowiring to inject the cache pool
+ public function __construct(
+ private TagAwareCacheInterface $myCachePool,
+ ) {
+ }
+
+ public function someMethod(): void
+ {
+ $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): string {
+ $item->tag('foo');
+
+ return 'debug';
+ });
+
+ // Remove all cache keys tagged with "bar"
+ $this->myCachePool->invalidateTags(['bar']);
+ }
+ }
+
+The cache adapter needs to implement :class:`Symfony\\Contracts\\Cache\\TagAwareCacheInterface`
+to enable this feature. This could be added by using the following configuration.
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/cache.yaml
+ framework:
+ cache:
+ pools:
+ my_cache_pool:
+ adapter: cache.adapter.redis_tag_aware
+ tags: true
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/cache.php
+ use Symfony\Config\FrameworkConfig;
+
+ 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
+achieved by specifying the adapter.
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/cache.yaml
+ framework:
+ cache:
+ pools:
+ my_cache_pool:
+ adapter: cache.adapter.redis
+ tags: tag_pool
+ tag_pool:
+ adapter: cache.adapter.apcu
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/cache.php
+ 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::
+
+ The interface :class:`Symfony\\Contracts\\Cache\\TagAwareCacheInterface` is
+ autowired to the ``cache.app`` service.
+
+Clearing the Cache
+------------------
+
+To clear the cache you can use the ``bin/console cache:pool:clear [pool]`` command.
+That will remove all the entries from your storage and you will have to recalculate
+all the values. You can also group your pools into "cache clearers". There are 3 cache
+clearers by default:
+
+* ``cache.global_clearer``
+* ``cache.system_clearer``
+* ``cache.app_clearer``
+
+The global clearer clears all the cache items in every pool. The system cache clearer
+is used in the ``bin/console cache:clear`` command. The app clearer is the default
+clearer.
+
+To see all available cache pools:
+
+.. code-block:: terminal
+
+ $ php bin/console cache:pool:list
+
+Clear one pool:
+
+.. code-block:: terminal
+
+ $ php bin/console cache:pool:clear my_cache_pool
+
+Clear all custom pools:
+
+.. code-block:: terminal
+
+ $ 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 474a1d03704..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
@@ -17,7 +13,7 @@ For example:
-
+
This practice is no longer recommended unless the web application is extremely
simple. Hardcoding URLs can be a disadvantage because:
@@ -30,7 +26,7 @@ simple. Hardcoding URLs can be a disadvantage because:
is essential for some applications because it allows you to control how
the assets are cached. The Asset component allows you to define different
versioning strategies for each package;
-* **Moving assets location** is cumbersome and error-prone: it requires you to
+* **Moving assets' location** is cumbersome and error-prone: it requires you to
carefully update the URLs of all assets included in all templates. The Asset
component allows to move assets effortlessly just by changing the base path
value associated with the package of assets;
@@ -46,13 +42,13 @@ Installation
$ composer require symfony/asset
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
-----
+.. _asset-packages:
+
Asset Packages
~~~~~~~~~~~~~~
@@ -121,8 +117,9 @@ suffix to any asset path::
echo $package->getUrl('image.png');
// result: image.png?v1
-In case you want to modify the version format, pass a sprintf-compatible format
-string as the second argument of the ``StaticVersionStrategy`` constructor::
+In case you want to modify the version format, pass a ``sprintf``-compatible
+format string as the second argument of the ``StaticVersionStrategy``
+constructor::
// puts the 'version' word before the version value
$package = new Package(new StaticVersionStrategy('v1', '%s?version=%s'));
@@ -148,7 +145,6 @@ corresponding output file:
.. code-block:: json
- // rev-manifest.json
{
"css/app.css": "build/css/app.b916426ea1d10021f3f17ce8031f93c2.css",
"js/app.js": "build/js/app.13630905267b809161e71d0f8a0c017b.js",
@@ -161,11 +157,40 @@ In those cases, use the
use Symfony\Component\Asset\Package;
use Symfony\Component\Asset\VersionStrategy\JsonManifestVersionStrategy;
+ // assumes the JSON file above is called "rev-manifest.json"
$package = new Package(new JsonManifestVersionStrategy(__DIR__.'/rev-manifest.json'));
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\\JsonManifestVersionStrategy`
+with the :doc:`HttpClient component `::
+
+ use Symfony\Component\Asset\Package;
+ 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 JsonManifestVersionStrategy($manifestUrl, $httpClient));
+
Custom Version Strategies
.........................
@@ -178,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));
}
@@ -225,8 +250,8 @@ If you are also using the :doc:`HttpFoundation `
component in your project (for instance, in a Symfony application), the ``PathPackage``
class can take into account the context of the current request::
- use Symfony\Component\Asset\PathPackage;
use Symfony\Component\Asset\Context\RequestStackContext;
+ use Symfony\Component\Asset\PathPackage;
// ...
$pathPackage = new PathPackage(
@@ -261,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::
@@ -293,18 +318,18 @@ 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 be always served by the same
+is deterministic, meaning that each asset will always be served by the same
domain. This behavior simplifies the management of HTTP cache.
Request Context Aware Assets
@@ -315,8 +340,8 @@ account the context of the current request. In this case, only the request
scheme is considered, in order to select the appropriate base URL (HTTPs or
protocol-relative URLs for HTTPs requests, any base URL for HTTP requests)::
- use Symfony\Component\Asset\UrlPackage;
use Symfony\Component\Asset\Context\RequestStackContext;
+ use Symfony\Component\Asset\UrlPackage;
// ...
$urlPackage = new UrlPackage(
@@ -341,9 +366,9 @@ In the following example, all packages use the same versioning strategy, but
they all have different base paths::
use Symfony\Component\Asset\Package;
+ use Symfony\Component\Asset\Packages;
use Symfony\Component\Asset\PathPackage;
use Symfony\Component\Asset\UrlPackage;
- use Symfony\Component\Asset\Packages;
// ...
$versionStrategy = new StaticVersionStrategy('v1');
@@ -351,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::
@@ -367,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
@@ -401,5 +426,7 @@ improve performance::
Learn more
----------
-.. _Packagist: https://packagist.org/packages/symfony/asset
+* :doc:`How to manage CSS and JavaScript assets in Symfony applications `
+* :doc:`WebLink component ` to preload assets using HTTP/2.
+
.. _`Webpack`: https://webpack.js.org/
diff --git a/components/browser_kit.rst b/components/browser_kit.rst
index ce1cbec9077..8cf0772298c 100644
--- a/components/browser_kit.rst
+++ b/components/browser_kit.rst
@@ -1,19 +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::
-
- The BrowserKit component can only make internal requests to your application.
- If you need to make requests to external sites and applications, consider
- using `Goutte`_, a simple web scraper based on Symfony Components.
-
Installation
------------
@@ -21,8 +11,6 @@ Installation
$ composer require symfony/browser-kit
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Basic Usage
@@ -38,20 +26,19 @@ Creating a Client
~~~~~~~~~~~~~~~~~
The component only provides an abstract client and does not provide any backend
-ready to use for the HTTP layer.
-
-To create your own client, you must extend the abstract ``Client`` class and
-implement the :method:`Symfony\\Component\\BrowserKit\\Client::doRequest` method.
+ready to use for the HTTP layer. To create your own client, you must extend the
+``AbstractBrowser`` class and implement the
+:method:`Symfony\\Component\\BrowserKit\\AbstractBrowser::doRequest` method.
This method accepts a request and should return a response::
namespace Acme;
- use Symfony\Component\BrowserKit\Client as BaseClient;
+ use Symfony\Component\BrowserKit\AbstractBrowser;
use Symfony\Component\BrowserKit\Response;
- class Client extends BaseClient
+ class Client extends AbstractBrowser
{
- protected function doRequest($request)
+ protected function doRequest($request): Response
{
// ... convert request into a response
@@ -60,14 +47,15 @@ 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 `Goutte`_. For an implementation based on ``HttpKernelInterface``, have
-a look at the :class:`Symfony\\Component\\HttpKernel\\Client` provided by
-the :doc:`HttpKernel component `.
+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\\HttpClientKernel`
+provided by the :doc:`HttpKernel component `.
Making Requests
~~~~~~~~~~~~~~~
-Use the :method:`Symfony\\Component\\BrowserKit\\Client::request` method to
+Use the :method:`Symfony\\Component\\BrowserKit\\AbstractBrowser::request` method to
make HTTP requests. The first two arguments are the HTTP method and the requested
URL::
@@ -81,7 +69,17 @@ 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\\Client::xmlHttpRequest` method,
+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::
@@ -94,7 +92,7 @@ make AJAX requests::
Clicking Links
~~~~~~~~~~~~~~
-The ``Client`` object is capable of simulating link clicks. Pass the text
+The ``AbstractBrowser`` is capable of simulating link clicks. Pass the text
content of the link and the client will perform the needed HTTP GET request to
simulate the link click::
@@ -107,17 +105,35 @@ simulate the link click::
If you need the :class:`Symfony\\Component\\DomCrawler\\Link` object that
provides access to the link properties (e.g. ``$link->getMethod()``,
-``$link->getUri()``), use this other method:
+``$link->getUri()``), use this other method::
// ...
$crawler = $client->request('GET', '/product/123');
$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
~~~~~~~~~~~~~~~~
-The ``Client`` object is also capable of submitting forms. First, select the
+The ``AbstractBrowser`` is also capable of submitting forms. First, select the
form using any of its buttons and then override any of its properties (method,
field values, etc.) before submitting it::
@@ -127,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