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
new file mode 100644
index 00000000000..f9366facfb0
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,8 @@
+root = true
+
+[*]
+indent_style = space
+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/CONTRIBUTING.md b/.github/CONTRIBUTING.md
new file mode 100644
index 00000000000..acb0770920e
--- /dev/null
+++ b/.github/CONTRIBUTING.md
@@ -0,0 +1,5 @@
+Contributing
+------------
+
+We love contributors! For more information on how you can contribute to the
+Symfony documentation, please read [Contributing to the Documentation](https://symfony.com/doc/current/contributing/documentation/overview.html).
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000000..f32043e4523
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +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
new file mode 100644
index 00000000000..b69047f69a1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/_build/vendor
+/_build/output
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
deleted file mode 100644
index 278f7c2c39c..00000000000
--- a/CONTRIBUTING.md
+++ /dev/null
@@ -1,7 +0,0 @@
-Contributing
-------------
-
-We love contributors! For more information on how you can contribute to the
-Symfony documentation, please read [Contributing to the Documentation](http://symfony.com/doc/current/contributing/documentation/overview.html)
-and notice the [Pull Request Format](http://symfony.com/doc/current/contributing/documentation/overview.html#pull-request-format)
-that helps us merge your pull requests faster!
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 afa5925f03f..00000000000
--- a/README.markdown
+++ /dev/null
@@ -1,16 +0,0 @@
-Symfony Documentation
-=====================
-
-This documentation is rendered online at http://symfony.com/doc/current/
-
-Contributing
-------------
-
->**Note**
->Unless you're documenting a feature that's new to a specific version of Symfony
->(e.g. Symfony 2.1), all pull requests must be based off of the **2.0** branch,
->**not** the master or 2.1 branch.
-
-We love contributors! For more information on how you can contribute to the
-Symfony documentation, please read
-[Contributing to the Documentation](http://symfony.com/doc/current/contributing/documentation/overview.html)
diff --git a/README.md b/README.md
new file mode 100644
index 00000000000..5c063058c02
--- /dev/null
+++ b/README.md
@@ -0,0 +1,56 @@
+
" ?>
-
-
-
-By design, flash messages are meant to live for exactly one request (they're
-"gone in a flash"). They're designed to be used across redirects exactly as
-you've done in this example.
-
-.. index::
- single: Controller; Response object
-
-The Response Object
--------------------
-
-The only requirement for a controller is to return a ``Response`` object. The
-:class:`Symfony\\Component\\HttpFoundation\\Response` class is a PHP
-abstraction around the HTTP response - the text-based message filled with HTTP
-headers and content that's sent back to the client::
-
- use Symfony\Component\HttpFoundation\Response;
-
- // create a simple Response with a 200 status code (the default)
- $response = new Response('Hello '.$name, 200);
-
- // create a JSON-response with a 200 status code
- $response = new Response(json_encode(array('name' => $name)));
- $response->headers->set('Content-Type', 'application/json');
-
-.. tip::
-
- The ``headers`` property is a
- :class:`Symfony\\Component\\HttpFoundation\\HeaderBag` object with several
- useful methods for reading and mutating the ``Response`` headers. The
- header names are normalized so that using ``Content-Type`` is equivalent
- to ``content-type`` or even ``content_type``.
-
-.. tip::
-
- There are also special classes to make certain kinds of responses easier:
-
- - For JSON, there is :class:`Symfony\\Component\\HttpFoundation\\JsonResponse`.
- See :ref:`component-http-foundation-json-response`.
- - For files, there is :class:`Symfony\\Component\\HttpFoundation\\BinaryFileResponse`.
- See :ref:`component-http-foundation-serving-files`.
-
-.. index::
- single: Controller; Request object
-
-The Request Object
-------------------
-
-Besides the values of the routing placeholders, the controller also has access
-to the ``Request`` object when extending the base ``Controller`` class::
-
- $request = $this->getRequest();
-
- $request->isXmlHttpRequest(); // is it an Ajax request?
-
- $request->getPreferredLanguage(array('en', 'fr'));
-
- $request->query->get('page'); // get a $_GET parameter
-
- $request->request->get('page'); // get a $_POST parameter
-
-Like the ``Response`` object, the request headers are stored in a ``HeaderBag``
-object and are easily accessible.
-
-Final Thoughts
---------------
-
-Whenever you create a page, you'll ultimately need to write some code that
-contains the logic for that page. In Symfony, this is called a controller,
-and it's a PHP function that can do anything it needs in order to return
-the final ``Response`` object that will be returned to the user.
-
-To make life easier, you can choose to extend a base ``Controller`` class,
-which contains shortcut methods for many common controller tasks. For example,
-since you don't want to put HTML code in your controller, you can use
-the ``render()`` method to render and return the content from a template.
-
-In other chapters, you'll see how the controller can be used to persist and
-fetch objects from a database, process form submissions, handle caching and
-more.
-
-Learn more from the Cookbook
-----------------------------
-
-* :doc:`/cookbook/controller/error_pages`
-* :doc:`/cookbook/controller/service`
diff --git a/book/doctrine.rst b/book/doctrine.rst
deleted file mode 100644
index dc99a0b24a1..00000000000
--- a/book/doctrine.rst
+++ /dev/null
@@ -1,1572 +0,0 @@
-.. index::
- single: Doctrine
-
-Databases and Doctrine
-======================
-
-One of the most common and challenging tasks for any application
-involves persisting and reading information to and from a database. Fortunately,
-Symfony comes integrated with `Doctrine`_, a library whose sole goal is to
-give you powerful tools to make this easy. In this chapter, you'll learn the
-basic philosophy behind Doctrine and see how easy working with a database can
-be.
-
-.. note::
-
- Doctrine is totally decoupled from Symfony and using it is optional.
- This chapter is all about the Doctrine ORM, which aims to let you map
- objects to a relational database (such as *MySQL*, *PostgreSQL* or
- *Microsoft SQL*). If you prefer to use raw database queries, this is
- easy, and explained in the ":doc:`/cookbook/doctrine/dbal`" cookbook entry.
-
- You can also persist data to `MongoDB`_ using Doctrine ODM library. For
- more information, read the ":doc:`/bundles/DoctrineMongoDBBundle/index`"
- documentation.
-
-A Simple Example: A Product
----------------------------
-
-The easiest way to understand how Doctrine works is to see it in action.
-In this section, you'll configure your database, create a ``Product`` object,
-persist it to the database and fetch it back out.
-
-.. sidebar:: Code along with the example
-
- If you want to follow along with the example in this chapter, create
- an ``AcmeStoreBundle`` via:
-
- .. code-block:: bash
-
- $ php app/console generate:bundle --namespace=Acme/StoreBundle
-
-Configuring the Database
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-Before you really begin, you'll need to configure your database connection
-information. By convention, this information is usually configured in an
-``app/config/parameters.yml`` file:
-
-.. code-block:: yaml
-
- # app/config/parameters.yml
- parameters:
- database_driver: pdo_mysql
- database_host: localhost
- database_name: test_project
- database_user: root
- database_password: password
-
- # ...
-
-.. note::
-
- Defining the configuration via ``parameters.yml`` is just a convention.
- The parameters defined in that file are referenced by the main configuration
- file when setting up Doctrine:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- doctrine:
- dbal:
- driver: "%database_driver%"
- host: "%database_host%"
- dbname: "%database_name%"
- user: "%database_user%"
- password: "%database_password%"
-
- .. code-block:: xml
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- $configuration->loadFromExtension('doctrine', array(
- 'dbal' => array(
- 'driver' => '%database_driver%',
- 'host' => '%database_host%',
- 'dbname' => '%database_name%',
- 'user' => '%database_user%',
- 'password' => '%database_password%',
- ),
- ));
-
- By separating the database information into a separate file, you can
- easily keep different versions of the file on each server. You can also
- easily store database configuration (or any sensitive information) outside
- of your project, like inside your Apache configuration, for example. For
- more information, see :doc:`/cookbook/configuration/external_parameters`.
-
-Now that Doctrine knows about your database, you can have it create the database
-for you:
-
-.. code-block:: bash
-
- $ php app/console doctrine:database:create
-
-.. sidebar:: Setting Up The Database to be UTF8
-
- One mistake even seasoned developers make when starting a Symfony2 project
- is forgetting to setup default charset and collation on their database,
- ending up with latin type collations, which are default for most databases.
- They might even remember to do it the very first time, but forget that
- it's all gone after running a relatively common command during development:
-
- .. code-block:: bash
-
- $ php app/console doctrine:database:drop --force
- $ php app/console doctrine:database:create
-
- There's no way to configure these defaults inside Doctrine, as it tries to be
- as agnostic as possible in terms of environment configuration. One way to solve
- this problem is to configure server-level defaults.
-
- Setting UTF8 defaults for MySQL is as simple as adding a few lines to
- your configuration file (typically ``my.cnf``):
-
- .. code-block:: ini
-
- [mysqld]
- collation-server = utf8_general_ci
- character-set-server = utf8
-
-.. note::
-
- If you want to use SQLite as your database, you need to set the path
- where your database file should be stored:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- doctrine:
- dbal:
- driver: pdo_sqlite
- path: "%kernel.root_dir%/sqlite.db"
- charset: UTF8
-
- .. code-block:: xml
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- $container->loadFromExtension('doctrine', array(
- 'dbal' => array(
- 'driver' => 'pdo_sqlite',
- 'path' => '%kernel.root_dir%/sqlite.db',
- 'charset' => 'UTF-8',
- ),
- ));
-
-Creating an Entity Class
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-Suppose you're building an application where products need to be displayed.
-Without even thinking about Doctrine or databases, you already know that
-you need a ``Product`` object to represent those products. Create this class
-inside the ``Entity`` directory of your ``AcmeStoreBundle``::
-
- // src/Acme/StoreBundle/Entity/Product.php
- namespace Acme\StoreBundle\Entity;
-
- class Product
- {
- protected $name;
-
- protected $price;
-
- protected $description;
- }
-
-The class - often called an "entity", meaning *a basic class that holds data* -
-is simple and helps fulfill the business requirement of needing products
-in your application. This class can't be persisted to a database yet - it's
-just a simple PHP class.
-
-.. tip::
-
- Once you learn the concepts behind Doctrine, you can have Doctrine create
- simple entity classes for you:
-
- .. code-block:: bash
-
- $ php app/console doctrine:generate:entity --entity="AcmeStoreBundle:Product" --fields="name:string(255) price:float description:text"
-
-.. index::
- single: Doctrine; Adding mapping metadata
-
-.. _book-doctrine-adding-mapping:
-
-Add Mapping Information
-~~~~~~~~~~~~~~~~~~~~~~~
-
-Doctrine allows you to work with databases in a much more interesting way
-than just fetching rows of a column-based table into an array. Instead, Doctrine
-allows you to persist entire *objects* to the database and fetch entire objects
-out of the database. This works by mapping a PHP class to a database table,
-and the properties of that PHP class to columns on the table:
-
-.. image:: /images/book/doctrine_image_1.png
- :align: center
-
-For Doctrine to be able to do this, you just have to create "metadata", or
-configuration that tells Doctrine exactly how the ``Product`` class and its
-properties should be *mapped* to the database. This metadata can be specified
-in a number of different formats including YAML, XML or directly inside the
-``Product`` class via annotations:
-
-.. configuration-block::
-
- .. code-block:: php-annotations
-
- // src/Acme/StoreBundle/Entity/Product.php
- namespace Acme\StoreBundle\Entity;
-
- use Doctrine\ORM\Mapping as ORM;
-
- /**
- * @ORM\Entity
- * @ORM\Table(name="product")
- */
- class Product
- {
- /**
- * @ORM\Id
- * @ORM\Column(type="integer")
- * @ORM\GeneratedValue(strategy="AUTO")
- */
- protected $id;
-
- /**
- * @ORM\Column(type="string", length=100)
- */
- protected $name;
-
- /**
- * @ORM\Column(type="decimal", scale=2)
- */
- protected $price;
-
- /**
- * @ORM\Column(type="text")
- */
- protected $description;
- }
-
- .. code-block:: yaml
-
- # src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml
- Acme\StoreBundle\Entity\Product:
- type: entity
- table: product
- id:
- id:
- type: integer
- generator: { strategy: AUTO }
- fields:
- name:
- type: string
- length: 100
- price:
- type: decimal
- scale: 2
- description:
- type: text
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-.. note::
-
- A bundle can accept only one metadata definition format. For example, it's
- not possible to mix YAML metadata definitions with annotated PHP entity
- class definitions.
-
-.. tip::
-
- The table name is optional and if omitted, will be determined automatically
- based on the name of the entity class.
-
-Doctrine allows you to choose from a wide variety of different field types,
-each with their own options. For information on the available field types,
-see the :ref:`book-doctrine-field-types` section.
-
-.. seealso::
-
- You can also check out Doctrine's `Basic Mapping Documentation`_ for
- all details about mapping information. If you use annotations, you'll
- need to prepend all annotations with ``ORM\`` (e.g. ``ORM\Column(..)``),
- which is not shown in Doctrine's documentation. You'll also need to include
- the ``use Doctrine\ORM\Mapping as ORM;`` statement, which *imports* the
- ``ORM`` annotations prefix.
-
-.. caution::
-
- Be careful that your class name and properties aren't mapped to a protected
- SQL keyword (such as ``group`` or ``user``). For example, if your entity
- class name is ``Group``, then, by default, your table name will be ``group``,
- which will cause an SQL error in some engines. See Doctrine's
- `Reserved SQL keywords documentation`_ on how to properly escape these
- names. Alternatively, if you're free to choose your database schema,
- simply map to a different table name or column name. See Doctrine's
- `Persistent classes`_ and `Property Mapping`_ documentation.
-
-.. note::
-
- When using another library or program (ie. Doxygen) that uses annotations,
- you should place the ``@IgnoreAnnotation`` annotation on the class to
- indicate which annotations Symfony should ignore.
-
- For example, to prevent the ``@fn`` annotation from throwing an exception,
- add the following::
-
- /**
- * @IgnoreAnnotation("fn")
- */
- class Product
- // ...
-
-Generating Getters and Setters
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Even though Doctrine now knows how to persist a ``Product`` object to the
-database, the class itself isn't really useful yet. Since ``Product`` is just
-a regular PHP class, you need to create getter and setter methods (e.g. ``getName()``,
-``setName()``) in order to access its properties (since the properties are
-``protected``). Fortunately, Doctrine can do this for you by running:
-
-.. code-block:: bash
-
- $ php app/console doctrine:generate:entities Acme/StoreBundle/Entity/Product
-
-This command makes sure that all of the getters and setters are generated
-for the ``Product`` class. This is a safe command - you can run it over and
-over again: it only generates getters and setters that don't exist (i.e. it
-doesn't replace your existing methods).
-
-.. caution::
-
- Keep in mind that Doctrine's entity generator produces simple getters/setters.
- You should check generated entities and adjust getter/setter logic to your own
- needs.
-
-.. sidebar:: More about ``doctrine:generate:entities``
-
- With the ``doctrine:generate:entities`` command you can:
-
- * generate getters and setters;
-
- * generate repository classes configured with the
- ``@ORM\Entity(repositoryClass="...")`` annotation;
-
- * generate the appropriate constructor for 1:n and n:m relations.
-
- The ``doctrine:generate:entities`` command saves a backup of the original
- ``Product.php`` named ``Product.php~``. In some cases, the presence of
- this file can cause a "Cannot redeclare class" error. It can be safely
- removed.
-
- Note that you don't *need* to use this command. Doctrine doesn't rely
- on code generation. Like with normal PHP classes, you just need to make
- sure that your protected/private properties have getter and setter methods.
- Since this is a common thing to do when using Doctrine, this command
- was created.
-
-You can also generate all known entities (i.e. any PHP class with Doctrine
-mapping information) of a bundle or an entire namespace:
-
-.. code-block:: bash
-
- $ php app/console doctrine:generate:entities AcmeStoreBundle
- $ php app/console doctrine:generate:entities Acme
-
-.. note::
-
- Doctrine doesn't care whether your properties are ``protected`` or ``private``,
- or whether or not you have a getter or setter function for a property.
- The getters and setters are generated here only because you'll need them
- to interact with your PHP object.
-
-Creating the Database Tables/Schema
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You now have a usable ``Product`` class with mapping information so that
-Doctrine knows exactly how to persist it. Of course, you don't yet have the
-corresponding ``product`` table in your database. Fortunately, Doctrine can
-automatically create all the database tables needed for every known entity
-in your application. To do this, run:
-
-.. code-block:: bash
-
- $ php app/console doctrine:schema:update --force
-
-.. tip::
-
- Actually, this command is incredibly powerful. It compares what
- your database *should* look like (based on the mapping information of
- your entities) with how it *actually* looks, and generates the SQL statements
- needed to *update* the database to where it should be. In other words, if you add
- a new property with mapping metadata to ``Product`` and run this task
- again, it will generate the "alter table" statement needed to add that
- new column to the existing ``product`` table.
-
- An even better way to take advantage of this functionality is via
- :doc:`migrations`, which allow you to
- generate these SQL statements and store them in migration classes that
- can be run systematically on your production server in order to track
- and migrate your database schema safely and reliably.
-
-Your database now has a fully-functional ``product`` table with columns that
-match the metadata you've specified.
-
-Persisting Objects to the Database
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Now that you have a mapped ``Product`` entity and corresponding ``product``
-table, you're ready to persist data to the database. From inside a controller,
-this is pretty easy. Add the following method to the ``DefaultController``
-of the bundle:
-
-.. code-block:: php
- :linenos:
-
- // src/Acme/StoreBundle/Controller/DefaultController.php
-
- // ...
- use Acme\StoreBundle\Entity\Product;
- use Symfony\Component\HttpFoundation\Response;
-
- public function createAction()
- {
- $product = new Product();
- $product->setName('A Foo Bar');
- $product->setPrice('19.99');
- $product->setDescription('Lorem ipsum dolor');
-
- $em = $this->getDoctrine()->getManager();
- $em->persist($product);
- $em->flush();
-
- return new Response('Created product id '.$product->getId());
- }
-
-.. note::
-
- If you're following along with this example, you'll need to create a
- route that points to this action to see it work.
-
-Take a look at the previous example in more detail:
-
-* **lines 9-12** In this section, you instantiate and work with the ``$product``
- object like any other, normal PHP object.
-
-* **line 14** This line fetches Doctrine's *entity manager* object, which is
- responsible for handling the process of persisting and fetching objects
- to and from the database.
-
-* **line 15** The ``persist()`` method tells Doctrine to "manage" the ``$product``
- object. This does not actually cause a query to be made to the database (yet).
-
-* **line 16** When the ``flush()`` method is called, Doctrine looks through
- all of the objects that it's managing to see if they need to be persisted
- to the database. In this example, the ``$product`` object has not been
- persisted yet, so the entity manager executes an ``INSERT`` query and a
- row is created in the ``product`` table.
-
-.. note::
-
- In fact, since Doctrine is aware of all your managed entities, when you
- call the ``flush()`` method, it calculates an overall changeset and executes
- the most efficient query/queries possible. For example, if you persist a
- total of 100 ``Product`` objects and then subsequently call ``flush()``,
- Doctrine will create a *single* prepared statement and re-use it for each
- insert. This pattern is called *Unit of Work*, and it's used because it's
- fast and efficient.
-
-When creating or updating objects, the workflow is always the same. In the
-next section, you'll see how Doctrine is smart enough to automatically issue
-an ``UPDATE`` query if the record already exists in the database.
-
-.. tip::
-
- Doctrine provides a library that allows you to programmatically load testing
- data into your project (i.e. "fixture data"). For information, see
- :doc:`/bundles/DoctrineFixturesBundle/index`.
-
-Fetching Objects from the Database
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Fetching an object back out of the database is even easier. For example,
-suppose you've configured a route to display a specific ``Product`` based
-on its ``id`` value::
-
- public function showAction($id)
- {
- $product = $this->getDoctrine()
- ->getRepository('AcmeStoreBundle:Product')
- ->find($id);
-
- if (!$product) {
- throw $this->createNotFoundException(
- 'No product found for id '.$id
- );
- }
-
- // ... do something, like pass the $product object into a template
- }
-
-.. tip::
-
- You can achieve the equivalent of this without writing any code by using
- the ``@ParamConverter`` shortcut. See the
- :doc:`FrameworkExtraBundle documentation`
- for more details.
-
-When you query for a particular type of object, you always use what's known
-as its "repository". You can think of a repository as a PHP class whose only
-job is to help you fetch entities of a certain class. You can access the
-repository object for an entity class via::
-
- $repository = $this->getDoctrine()
- ->getRepository('AcmeStoreBundle:Product');
-
-.. note::
-
- The ``AcmeStoreBundle:Product`` string is a shortcut you can use anywhere
- in Doctrine instead of the full class name of the entity (i.e. ``Acme\StoreBundle\Entity\Product``).
- As long as your entity lives under the ``Entity`` namespace of your bundle,
- this will work.
-
-Once you have your repository, you have access to all sorts of helpful methods::
-
- // query by the primary key (usually "id")
- $product = $repository->find($id);
-
- // dynamic method names to find based on a column value
- $product = $repository->findOneById($id);
- $product = $repository->findOneByName('foo');
-
- // find *all* products
- $products = $repository->findAll();
-
- // find a group of products based on an arbitrary column value
- $products = $repository->findByPrice(19.99);
-
-.. note::
-
- Of course, you can also issue complex queries, which you'll learn more
- about in the :ref:`book-doctrine-queries` section.
-
-You can also take advantage of the useful ``findBy`` and ``findOneBy`` methods
-to easily fetch objects based on multiple conditions::
-
- // query for one product matching be name and price
- $product = $repository->findOneBy(array('name' => 'foo', 'price' => 19.99));
-
- // query for all products matching the name, ordered by price
- $products = $repository->findBy(
- array('name' => 'foo'),
- array('price' => 'ASC')
- );
-
-.. tip::
-
- When you render any page, you can see how many queries were made in the
- bottom right corner of the web debug toolbar.
-
- .. image:: /images/book/doctrine_web_debug_toolbar.png
- :align: center
- :scale: 50
- :width: 350
-
- If you click the icon, the profiler will open, showing you the exact
- queries that were made.
-
-Updating an Object
-~~~~~~~~~~~~~~~~~~
-
-Once you've fetched an object from Doctrine, updating it is easy. Suppose
-you have a route that maps a product id to an update action in a controller::
-
- public function updateAction($id)
- {
- $em = $this->getDoctrine()->getManager();
- $product = $em->getRepository('AcmeStoreBundle:Product')->find($id);
-
- if (!$product) {
- throw $this->createNotFoundException(
- 'No product found for id '.$id
- );
- }
-
- $product->setName('New product name!');
- $em->flush();
-
- return $this->redirect($this->generateUrl('homepage'));
- }
-
-Updating an object involves just three steps:
-
-#. fetching the object from Doctrine;
-#. modifying the object;
-#. calling ``flush()`` on the entity manager
-
-Notice that calling ``$em->persist($product)`` isn't necessary. Recall that
-this method simply tells Doctrine to manage or "watch" the ``$product`` object.
-In this case, since you fetched the ``$product`` object from Doctrine, it's
-already managed.
-
-Deleting an Object
-~~~~~~~~~~~~~~~~~~
-
-Deleting an object is very similar, but requires a call to the ``remove()``
-method of the entity manager::
-
- $em->remove($product);
- $em->flush();
-
-As you might expect, the ``remove()`` method notifies Doctrine that you'd
-like to remove the given entity from the database. The actual ``DELETE`` query,
-however, isn't actually executed until the ``flush()`` method is called.
-
-.. _`book-doctrine-queries`:
-
-Querying for Objects
---------------------
-
-You've already seen how the repository object allows you to run basic queries
-without any work::
-
- $repository->find($id);
-
- $repository->findOneByName('Foo');
-
-Of course, Doctrine also allows you to write more complex queries using the
-Doctrine Query Language (DQL). DQL is similar to SQL except that you should
-imagine that you're querying for one or more objects of an entity class (e.g. ``Product``)
-instead of querying for rows on a table (e.g. ``product``).
-
-When querying in Doctrine, you have two options: writing pure Doctrine queries
-or using Doctrine's Query Builder.
-
-Querying for Objects with DQL
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Imagine that you want to query for products, but only return products that
-cost more than ``19.99``, ordered from cheapest to most expensive. From inside
-a controller, do the following::
-
- $em = $this->getDoctrine()->getManager();
- $query = $em->createQuery(
- 'SELECT p FROM AcmeStoreBundle:Product p WHERE p.price > :price ORDER BY p.price ASC'
- )->setParameter('price', '19.99');
-
- $products = $query->getResult();
-
-If you're comfortable with SQL, then DQL should feel very natural. The biggest
-difference is that you need to think in terms of "objects" instead of rows
-in a database. For this reason, you select *from* ``AcmeStoreBundle:Product``
-and then alias it as ``p``.
-
-The ``getResult()`` method returns an array of results. If you're querying
-for just one object, you can use the ``getSingleResult()`` method instead::
-
- $product = $query->getSingleResult();
-
-.. caution::
-
- The ``getSingleResult()`` method throws a ``Doctrine\ORM\NoResultException``
- exception if no results are returned and a ``Doctrine\ORM\NonUniqueResultException``
- if *more* than one result is returned. If you use this method, you may
- need to wrap it in a try-catch block and ensure that only one result is
- returned (if you're querying on something that could feasibly return
- more than one result)::
-
- $query = $em->createQuery('SELECT ...')
- ->setMaxResults(1);
-
- try {
- $product = $query->getSingleResult();
- } catch (\Doctrine\Orm\NoResultException $e) {
- $product = null;
- }
- // ...
-
-The DQL syntax is incredibly powerful, allowing you to easily join between
-entities (the topic of :ref:`relations` will be
-covered later), group, etc. For more information, see the official Doctrine
-`Doctrine Query Language`_ documentation.
-
-.. sidebar:: Setting Parameters
-
- Take note of the ``setParameter()`` method. When working with Doctrine,
- it's always a good idea to set any external values as "placeholders",
- which was done in the above query:
-
- .. code-block:: text
-
- ... WHERE p.price > :price ...
-
- You can then set the value of the ``price`` placeholder by calling the
- ``setParameter()`` method::
-
- ->setParameter('price', '19.99')
-
- Using parameters instead of placing values directly in the query string
- is done to prevent SQL injection attacks and should *always* be done.
- If you're using multiple parameters, you can set their values at once
- using the ``setParameters()`` method::
-
- ->setParameters(array(
- 'price' => '19.99',
- 'name' => 'Foo',
- ))
-
-Using Doctrine's Query Builder
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Instead of writing the queries directly, you can alternatively use Doctrine's
-``QueryBuilder`` to do the same job using a nice, object-oriented interface.
-If you use an IDE, you can also take advantage of auto-completion as you
-type the method names. From inside a controller::
-
- $repository = $this->getDoctrine()
- ->getRepository('AcmeStoreBundle:Product');
-
- $query = $repository->createQueryBuilder('p')
- ->where('p.price > :price')
- ->setParameter('price', '19.99')
- ->orderBy('p.price', 'ASC')
- ->getQuery();
-
- $products = $query->getResult();
-
-The ``QueryBuilder`` object contains every method necessary to build your
-query. By calling the ``getQuery()`` method, the query builder returns a
-normal ``Query`` object, which is the same object you built directly in the
-previous section.
-
-For more information on Doctrine's Query Builder, consult Doctrine's
-`Query Builder`_ documentation.
-
-Custom Repository Classes
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-In the previous sections, you began constructing and using more complex queries
-from inside a controller. In order to isolate, test and reuse these queries,
-it's a good idea to create a custom repository class for your entity and
-add methods with your query logic there.
-
-To do this, add the name of the repository class to your mapping definition.
-
-.. configuration-block::
-
- .. code-block:: php-annotations
-
- // src/Acme/StoreBundle/Entity/Product.php
- namespace Acme\StoreBundle\Entity;
-
- use Doctrine\ORM\Mapping as ORM;
-
- /**
- * @ORM\Entity(repositoryClass="Acme\StoreBundle\Entity\ProductRepository")
- */
- class Product
- {
- //...
- }
-
- .. code-block:: yaml
-
- # src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml
- Acme\StoreBundle\Entity\Product:
- type: entity
- repositoryClass: Acme\StoreBundle\Entity\ProductRepository
- # ...
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-Doctrine can generate the repository class for you by running the same command
-used earlier to generate the missing getter and setter methods:
-
-.. code-block:: bash
-
- $ php app/console doctrine:generate:entities Acme
-
-Next, add a new method - ``findAllOrderedByName()`` - to the newly generated
-repository class. This method will query for all of the ``Product`` entities,
-ordered alphabetically.
-
-.. code-block:: php
-
- // src/Acme/StoreBundle/Entity/ProductRepository.php
- namespace Acme\StoreBundle\Entity;
-
- use Doctrine\ORM\EntityRepository;
-
- class ProductRepository extends EntityRepository
- {
- public function findAllOrderedByName()
- {
- return $this->getEntityManager()
- ->createQuery('SELECT p FROM AcmeStoreBundle:Product p ORDER BY p.name ASC')
- ->getResult();
- }
- }
-
-.. tip::
-
- The entity manager can be accessed via ``$this->getEntityManager()``
- from inside the repository.
-
-You can use this new method just like the default finder methods of the repository::
-
- $em = $this->getDoctrine()->getManager();
- $products = $em->getRepository('AcmeStoreBundle:Product')
- ->findAllOrderedByName();
-
-.. note::
-
- When using a custom repository class, you still have access to the default
- finder methods such as ``find()`` and ``findAll()``.
-
-.. _`book-doctrine-relations`:
-
-Entity Relationships/Associations
----------------------------------
-
-Suppose that the products in your application all belong to exactly one "category".
-In this case, you'll need a ``Category`` object and a way to relate a ``Product``
-object to a ``Category`` object. Start by creating the ``Category`` entity.
-Since you know that you'll eventually need to persist the class through Doctrine,
-you can let Doctrine create the class for you.
-
-.. code-block:: bash
-
- $ php app/console doctrine:generate:entity --entity="AcmeStoreBundle:Category" --fields="name:string(255)"
-
-This task generates the ``Category`` entity for you, with an ``id`` field,
-a ``name`` field and the associated getter and setter functions.
-
-Relationship Mapping Metadata
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To relate the ``Category`` and ``Product`` entities, start by creating a
-``products`` property on the ``Category`` class:
-
-.. configuration-block::
-
- .. code-block:: php-annotations
-
- // src/Acme/StoreBundle/Entity/Category.php
-
- // ...
- use Doctrine\Common\Collections\ArrayCollection;
-
- class Category
- {
- // ...
-
- /**
- * @ORM\OneToMany(targetEntity="Product", mappedBy="category")
- */
- protected $products;
-
- public function __construct()
- {
- $this->products = new ArrayCollection();
- }
- }
-
- .. code-block:: yaml
-
- # src/Acme/StoreBundle/Resources/config/doctrine/Category.orm.yml
- Acme\StoreBundle\Entity\Category:
- type: entity
- # ...
- oneToMany:
- products:
- targetEntity: Product
- mappedBy: category
- # don't forget to init the collection in entity __construct() method
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-First, since a ``Category`` object will relate to many ``Product`` objects,
-a ``products`` array property is added to hold those ``Product`` objects.
-Again, this isn't done because Doctrine needs it, but instead because it
-makes sense in the application for each ``Category`` to hold an array of
-``Product`` objects.
-
-.. note::
-
- The code in the ``__construct()`` method is important because Doctrine
- requires the ``$products`` property to be an ``ArrayCollection`` object.
- This object looks and acts almost *exactly* like an array, but has some
- added flexibility. If this makes you uncomfortable, don't worry. Just
- imagine that it's an ``array`` and you'll be in good shape.
-
-.. tip::
-
- The targetEntity value in the decorator used above can reference any entity
- with a valid namespace, not just entities defined in the same class. To
- relate to an entity defined in a different class or bundle, enter a full
- namespace as the targetEntity.
-
-Next, since each ``Product`` class can relate to exactly one ``Category``
-object, you'll want to add a ``$category`` property to the ``Product`` class:
-
-.. configuration-block::
-
- .. code-block:: php-annotations
-
- // src/Acme/StoreBundle/Entity/Product.php
-
- // ...
- class Product
- {
- // ...
-
- /**
- * @ORM\ManyToOne(targetEntity="Category", inversedBy="products")
- * @ORM\JoinColumn(name="category_id", referencedColumnName="id")
- */
- protected $category;
- }
-
- .. code-block:: yaml
-
- # src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml
- Acme\StoreBundle\Entity\Product:
- type: entity
- # ...
- manyToOne:
- category:
- targetEntity: Category
- inversedBy: products
- joinColumn:
- name: category_id
- referencedColumnName: id
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-Finally, now that you've added a new property to both the ``Category`` and
-``Product`` classes, tell Doctrine to generate the missing getter and setter
-methods for you:
-
-.. code-block:: bash
-
- $ php app/console doctrine:generate:entities Acme
-
-Ignore the Doctrine metadata for a moment. You now have two classes - ``Category``
-and ``Product`` with a natural one-to-many relationship. The ``Category``
-class holds an array of ``Product`` objects and the ``Product`` object can
-hold one ``Category`` object. In other words - you've built your classes
-in a way that makes sense for your needs. The fact that the data needs to
-be persisted to a database is always secondary.
-
-Now, look at the metadata above the ``$category`` property on the ``Product``
-class. The information here tells doctrine that the related class is ``Category``
-and that it should store the ``id`` of the category record on a ``category_id``
-field that lives on the ``product`` table. In other words, the related ``Category``
-object will be stored on the ``$category`` property, but behind the scenes,
-Doctrine will persist this relationship by storing the category's id value
-on a ``category_id`` column of the ``product`` table.
-
-.. image:: /images/book/doctrine_image_2.png
- :align: center
-
-The metadata above the ``$products`` property of the ``Category`` object
-is less important, and simply tells Doctrine to look at the ``Product.category``
-property to figure out how the relationship is mapped.
-
-Before you continue, be sure to tell Doctrine to add the new ``category``
-table, and ``product.category_id`` column, and new foreign key:
-
-.. code-block:: bash
-
- $ php app/console doctrine:schema:update --force
-
-.. note::
-
- This task should only be really used during development. For a more robust
- method of systematically updating your production database, read about
- :doc:`Doctrine migrations`.
-
-Saving Related Entities
-~~~~~~~~~~~~~~~~~~~~~~~
-
-Now you can see this new code in action! Imagine you're inside a controller::
-
- // ...
-
- use Acme\StoreBundle\Entity\Category;
- use Acme\StoreBundle\Entity\Product;
- use Symfony\Component\HttpFoundation\Response;
-
- class DefaultController extends Controller
- {
- public function createProductAction()
- {
- $category = new Category();
- $category->setName('Main Products');
-
- $product = new Product();
- $product->setName('Foo');
- $product->setPrice(19.99);
- // relate this product to the category
- $product->setCategory($category);
-
- $em = $this->getDoctrine()->getManager();
- $em->persist($category);
- $em->persist($product);
- $em->flush();
-
- return new Response(
- 'Created product id: '.$product->getId().' and category id: '.$category->getId()
- );
- }
- }
-
-Now, a single row is added to both the ``category`` and ``product`` tables.
-The ``product.category_id`` column for the new product is set to whatever
-the ``id`` is of the new category. Doctrine manages the persistence of this
-relationship for you.
-
-Fetching Related Objects
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-When you need to fetch associated objects, your workflow looks just like it
-did before. First, fetch a ``$product`` object and then access its related
-``Category``::
-
- public function showAction($id)
- {
- $product = $this->getDoctrine()
- ->getRepository('AcmeStoreBundle:Product')
- ->find($id);
-
- $categoryName = $product->getCategory()->getName();
-
- // ...
- }
-
-In this example, you first query for a ``Product`` object based on the product's
-``id``. This issues a query for *just* the product data and hydrates the
-``$product`` object with that data. Later, when you call ``$product->getCategory()->getName()``,
-Doctrine silently makes a second query to find the ``Category`` that's related
-to this ``Product``. It prepares the ``$category`` object and returns it to
-you.
-
-.. image:: /images/book/doctrine_image_3.png
- :align: center
-
-What's important is the fact that you have easy access to the product's related
-category, but the category data isn't actually retrieved until you ask for
-the category (i.e. it's "lazily loaded").
-
-You can also query in the other direction::
-
- public function showProductAction($id)
- {
- $category = $this->getDoctrine()
- ->getRepository('AcmeStoreBundle:Category')
- ->find($id);
-
- $products = $category->getProducts();
-
- // ...
- }
-
-In this case, the same things occurs: you first query out for a single ``Category``
-object, and then Doctrine makes a second query to retrieve the related ``Product``
-objects, but only once/if you ask for them (i.e. when you call ``->getProducts()``).
-The ``$products`` variable is an array of all ``Product`` objects that relate
-to the given ``Category`` object via their ``category_id`` value.
-
-.. sidebar:: Relationships and Proxy Classes
-
- This "lazy loading" is possible because, when necessary, Doctrine returns
- a "proxy" object in place of the true object. Look again at the above
- example::
-
- $product = $this->getDoctrine()
- ->getRepository('AcmeStoreBundle:Product')
- ->find($id);
-
- $category = $product->getCategory();
-
- // prints "Proxies\AcmeStoreBundleEntityCategoryProxy"
- echo get_class($category);
-
- This proxy object extends the true ``Category`` object, and looks and
- acts exactly like it. The difference is that, by using a proxy object,
- Doctrine can delay querying for the real ``Category`` data until you
- actually need that data (e.g. until you call ``$category->getName()``).
-
- The proxy classes are generated by Doctrine and stored in the cache directory.
- And though you'll probably never even notice that your ``$category``
- object is actually a proxy object, it's important to keep in mind.
-
- In the next section, when you retrieve the product and category data
- all at once (via a *join*), Doctrine will return the *true* ``Category``
- object, since nothing needs to be lazily loaded.
-
-Joining to Related Records
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-In the above examples, two queries were made - one for the original object
-(e.g. a ``Category``) and one for the related object(s) (e.g. the ``Product``
-objects).
-
-.. tip::
-
- Remember that you can see all of the queries made during a request via
- the web debug toolbar.
-
-Of course, if you know up front that you'll need to access both objects, you
-can avoid the second query by issuing a join in the original query. Add the
-following method to the ``ProductRepository`` class::
-
- // src/Acme/StoreBundle/Entity/ProductRepository.php
- public function findOneByIdJoinedToCategory($id)
- {
- $query = $this->getEntityManager()
- ->createQuery('
- SELECT p, c FROM AcmeStoreBundle:Product p
- JOIN p.category c
- WHERE p.id = :id'
- )->setParameter('id', $id);
-
- try {
- return $query->getSingleResult();
- } catch (\Doctrine\ORM\NoResultException $e) {
- return null;
- }
- }
-
-Now, you can use this method in your controller to query for a ``Product``
-object and its related ``Category`` with just one query::
-
- public function showAction($id)
- {
- $product = $this->getDoctrine()
- ->getRepository('AcmeStoreBundle:Product')
- ->findOneByIdJoinedToCategory($id);
-
- $category = $product->getCategory();
-
- // ...
- }
-
-More Information on Associations
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-This section has been an introduction to one common type of entity relationship,
-the one-to-many relationship. For more advanced details and examples of how
-to use other types of relations (e.g. ``one-to-one``, ``many-to-many``), see
-Doctrine's `Association Mapping Documentation`_.
-
-.. note::
-
- If you're using annotations, you'll need to prepend all annotations with
- ``ORM\`` (e.g. ``ORM\OneToMany``), which is not reflected in Doctrine's
- documentation. You'll also need to include the ``use Doctrine\ORM\Mapping as ORM;``
- statement, which *imports* the ``ORM`` annotations prefix.
-
-Configuration
--------------
-
-Doctrine is highly configurable, though you probably won't ever need to worry
-about most of its options. To find out more about configuring Doctrine, see
-the Doctrine section of the :doc:`reference manual`.
-
-Lifecycle Callbacks
--------------------
-
-Sometimes, you need to perform an action right before or after an entity
-is inserted, updated, or deleted. These types of actions are known as "lifecycle"
-callbacks, as they're callback methods that you need to execute during different
-stages of the lifecycle of an entity (e.g. the entity is inserted, updated,
-deleted, etc).
-
-If you're using annotations for your metadata, start by enabling the lifecycle
-callbacks. This is not necessary if you're using YAML or XML for your mapping:
-
-.. code-block:: php-annotations
-
- /**
- * @ORM\Entity()
- * @ORM\HasLifecycleCallbacks()
- */
- class Product
- {
- // ...
- }
-
-Now, you can tell Doctrine to execute a method on any of the available lifecycle
-events. For example, suppose you want to set a ``created`` date column to
-the current date, only when the entity is first persisted (i.e. inserted):
-
-.. configuration-block::
-
- .. code-block:: php-annotations
-
- /**
- * @ORM\PrePersist
- */
- public function setCreatedValue()
- {
- $this->created = new \DateTime();
- }
-
- .. code-block:: yaml
-
- # src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml
- Acme\StoreBundle\Entity\Product:
- type: entity
- # ...
- lifecycleCallbacks:
- prePersist: [setCreatedValue]
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-.. note::
-
- The above example assumes that you've created and mapped a ``created``
- property (not shown here).
-
-Now, right before the entity is first persisted, Doctrine will automatically
-call this method and the ``created`` field will be set to the current date.
-
-This can be repeated for any of the other lifecycle events, which include:
-
-* ``preRemove``
-* ``postRemove``
-* ``prePersist``
-* ``postPersist``
-* ``preUpdate``
-* ``postUpdate``
-* ``postLoad``
-* ``loadClassMetadata``
-
-For more information on what these lifecycle events mean and lifecycle callbacks
-in general, see Doctrine's `Lifecycle Events documentation`_
-
-.. sidebar:: Lifecycle Callbacks and Event Listeners
-
- Notice that the ``setCreatedValue()`` method receives no arguments. This
- is always the case for lifecycle callbacks and is intentional: lifecycle
- callbacks should be simple methods that are concerned with internally
- transforming data in the entity (e.g. setting a created/updated field,
- generating a slug value).
-
- If you need to do some heavier lifting - like perform logging or send
- an email - you should register an external class as an event listener
- or subscriber and give it access to whatever resources you need. For
- more information, see :doc:`/cookbook/doctrine/event_listeners_subscribers`.
-
-Doctrine Extensions: Timestampable, Sluggable, etc.
----------------------------------------------------
-
-Doctrine is quite flexible, and a number of third-party extensions are available
-that allow you to easily perform repeated and common tasks on your entities.
-These include thing such as *Sluggable*, *Timestampable*, *Loggable*, *Translatable*,
-and *Tree*.
-
-For more information on how to find and use these extensions, see the cookbook
-article about :doc:`using common Doctrine extensions`.
-
-.. _book-doctrine-field-types:
-
-Doctrine Field Types Reference
-------------------------------
-
-Doctrine comes with a large number of field types available. Each of these
-maps a PHP data type to a specific column type in whatever database you're
-using. The following types are supported in Doctrine:
-
-* **Strings**
-
- * ``string`` (used for shorter strings)
- * ``text`` (used for larger strings)
-
-* **Numbers**
-
- * ``integer``
- * ``smallint``
- * ``bigint``
- * ``decimal``
- * ``float``
-
-* **Dates and Times** (use a `DateTime`_ object for these fields in PHP)
-
- * ``date``
- * ``time``
- * ``datetime``
-
-* **Other Types**
-
- * ``boolean``
- * ``object`` (serialized and stored in a ``CLOB`` field)
- * ``array`` (serialized and stored in a ``CLOB`` field)
-
-For more information, see Doctrine's `Mapping Types documentation`_.
-
-Field Options
-~~~~~~~~~~~~~
-
-Each field can have a set of options applied to it. The available options
-include ``type`` (defaults to ``string``), ``name``, ``length``, ``unique``
-and ``nullable``. Take a few examples:
-
-.. configuration-block::
-
- .. code-block:: php-annotations
-
- /**
- * A string field with length 255 that cannot be null
- * (reflecting the default values for the "type", "length"
- * and *nullable* options)
- *
- * @ORM\Column()
- */
- protected $name;
-
- /**
- * A string field of length 150 that persists to an "email_address" column
- * and has a unique index.
- *
- * @ORM\Column(name="email_address", unique=true, length=150)
- */
- protected $email;
-
- .. code-block:: yaml
-
- fields:
- # A string field length 255 that cannot be null
- # (reflecting the default values for the "length" and *nullable* options)
- # type attribute is necessary in yaml definitions
- name:
- type: string
-
- # A string field of length 150 that persists to an "email_address" column
- # and has a unique index.
- email:
- type: string
- column: email_address
- length: 150
- unique: true
-
- .. code-block:: xml
-
-
-
-
-
-.. note::
-
- There are a few more options not listed here. For more details, see
- Doctrine's `Property Mapping documentation`_
-
-.. index::
- single: Doctrine; ORM console commands
- single: CLI; Doctrine ORM
-
-Console Commands
-----------------
-
-The Doctrine2 ORM integration offers several console commands under the
-``doctrine`` namespace. To view the command list you can run the console
-without any arguments:
-
-.. code-block:: bash
-
- $ php app/console
-
-A list of available commands will print out, many of which start with the
-``doctrine:`` prefix. You can find out more information about any of these
-commands (or any Symfony command) by running the ``help`` command. For example,
-to get details about the ``doctrine:database:create`` task, run:
-
-.. code-block:: bash
-
- $ php app/console help doctrine:database:create
-
-Some notable or interesting tasks include:
-
-* ``doctrine:ensure-production-settings`` - checks to see if the current
- environment is configured efficiently for production. This should always
- be run in the ``prod`` environment:
-
- .. code-block:: bash
-
- $ php app/console doctrine:ensure-production-settings --env=prod
-
-* ``doctrine:mapping:import`` - allows Doctrine to introspect an existing
- database and create mapping information. For more information, see
- :doc:`/cookbook/doctrine/reverse_engineering`.
-
-* ``doctrine:mapping:info`` - tells you all of the entities that Doctrine
- is aware of and whether or not there are any basic errors with the mapping.
-
-* ``doctrine:query:dql`` and ``doctrine:query:sql`` - allow you to execute
- DQL or SQL queries directly from the command line.
-
-.. note::
-
- To be able to load data fixtures to your database, you will need to have
- the ``DoctrineFixturesBundle`` bundle installed. To learn how to do it,
- read the ":doc:`/bundles/DoctrineFixturesBundle/index`" entry of the
- documentation.
-
-.. tip::
-
- This page shows working with Doctrine within a controller. You may also
- want to work with Doctrine elsewhere in your application. The
- :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::getDoctrine`
- method of the controller returns the ``doctrine`` service, you can work with
- this in the same way elsewhere by injecting this into your own
- services. See :doc:`/book/service_container` for more on creating
- your own services.
-
-Summary
--------
-
-With Doctrine, you can focus on your objects and how they're useful in your
-application and worry about database persistence second. This is because
-Doctrine allows you to use any PHP object to hold your data and relies on
-mapping metadata information to map an object's data to a particular database
-table.
-
-And even though Doctrine revolves around a simple concept, it's incredibly
-powerful, allowing you to create complex queries and subscribe to events
-that allow you to take different actions as objects go through their persistence
-lifecycle.
-
-For more information about Doctrine, see the *Doctrine* section of the
-:doc:`cookbook`, which includes the following articles:
-
-* :doc:`/bundles/DoctrineFixturesBundle/index`
-* :doc:`/cookbook/doctrine/common_extensions`
-
-.. _`Doctrine`: http://www.doctrine-project.org/
-.. _`MongoDB`: http://www.mongodb.org/
-.. _`Basic Mapping Documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html
-.. _`Query Builder`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/query-builder.html
-.. _`Doctrine Query Language`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html
-.. _`Association Mapping Documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html
-.. _`DateTime`: http://php.net/manual/en/class.datetime.php
-.. _`Mapping Types Documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#doctrine-mapping-types
-.. _`Property Mapping documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#property-mapping
-.. _`Lifecycle Events documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#lifecycle-events
-.. _`Reserved SQL keywords documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#quoting-reserved-words
-.. _`Persistent classes`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#persistent-classes
-.. _`Property Mapping`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#property-mapping
diff --git a/book/forms.rst b/book/forms.rst
deleted file mode 100644
index 4cdd6ba5f5e..00000000000
--- a/book/forms.rst
+++ /dev/null
@@ -1,1673 +0,0 @@
-.. index::
- single: Forms
-
-Forms
-=====
-
-Dealing with HTML forms is one of the most common - and challenging - tasks for
-a web developer. Symfony2 integrates a Form component that makes dealing with
-forms easy. In this chapter, you'll build a complex form from the ground-up,
-learning the most important features of the form library along the way.
-
-.. note::
-
- The Symfony form component is a standalone library that can be used outside
- of Symfony2 projects. For more information, see the `Symfony2 Form Component`_
- on Github.
-
-.. index::
- single: Forms; Create a simple form
-
-Creating a Simple Form
-----------------------
-
-Suppose you're building a simple todo list application that will need to
-display "tasks". Because your users will need to edit and create tasks, you're
-going to need to build a form. But before you begin, first focus on the generic
-``Task`` class that represents and stores the data for a single task::
-
- // src/Acme/TaskBundle/Entity/Task.php
- namespace Acme\TaskBundle\Entity;
-
- class Task
- {
- protected $task;
-
- protected $dueDate;
-
- public function getTask()
- {
- return $this->task;
- }
- public function setTask($task)
- {
- $this->task = $task;
- }
-
- public function getDueDate()
- {
- return $this->dueDate;
- }
- public function setDueDate(\DateTime $dueDate = null)
- {
- $this->dueDate = $dueDate;
- }
- }
-
-.. note::
-
- If you're coding along with this example, create the ``AcmeTaskBundle``
- first by running the following command (and accepting all of the default
- options):
-
- .. code-block:: bash
-
- $ php app/console generate:bundle --namespace=Acme/TaskBundle
-
-This class is a "plain-old-PHP-object" because, so far, it has nothing
-to do with Symfony or any other library. It's quite simply a normal PHP object
-that directly solves a problem inside *your* application (i.e. the need to
-represent a task in your application). Of course, by the end of this chapter,
-you'll be able to submit data to a ``Task`` instance (via an HTML form), validate
-its data, and persist it to the database.
-
-.. index::
- single: Forms; Create a form in a controller
-
-Building the Form
-~~~~~~~~~~~~~~~~~
-
-Now that you've created a ``Task`` class, the next step is to create and
-render the actual HTML form. In Symfony2, this is done by building a form
-object and then rendering it in a template. For now, this can all be done
-from inside a controller::
-
- // src/Acme/TaskBundle/Controller/DefaultController.php
- namespace Acme\TaskBundle\Controller;
-
- use Symfony\Bundle\FrameworkBundle\Controller\Controller;
- use Acme\TaskBundle\Entity\Task;
- use Symfony\Component\HttpFoundation\Request;
-
- class DefaultController extends Controller
- {
- public function newAction(Request $request)
- {
- // create a task and give it some dummy data for this example
- $task = new Task();
- $task->setTask('Write a blog post');
- $task->setDueDate(new \DateTime('tomorrow'));
-
- $form = $this->createFormBuilder($task)
- ->add('task', 'text')
- ->add('dueDate', 'date')
- ->getForm();
-
- return $this->render('AcmeTaskBundle:Default:new.html.twig', array(
- 'form' => $form->createView(),
- ));
- }
- }
-
-.. tip::
-
- This example shows you how to build your form directly in the controller.
- Later, in the ":ref:`book-form-creating-form-classes`" section, you'll learn
- how to build your form in a standalone class, which is recommended as
- your form becomes reusable.
-
-Creating a form requires relatively little code because Symfony2 form objects
-are built with a "form builder". The form builder's purpose is to allow you
-to write simple form "recipes", and have it do all the heavy-lifting of actually
-building the form.
-
-In this example, you've added two fields to your form - ``task`` and ``dueDate`` -
-corresponding to the ``task`` and ``dueDate`` properties of the ``Task`` class.
-You've also assigned each a "type" (e.g. ``text``, ``date``), which, among
-other things, determines which HTML form tag(s) is rendered for that field.
-
-Symfony2 comes with many built-in types that will be discussed shortly
-(see :ref:`book-forms-type-reference`).
-
-.. index::
- single: Forms; Basic template rendering
-
-Rendering the Form
-~~~~~~~~~~~~~~~~~~
-
-Now that the form has been created, the next step is to render it. This is
-done by passing a special form "view" object to your template (notice the
-``$form->createView()`` in the controller above) and using a set of form
-helper functions:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
- {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
- {{ form(form) }}
-
- .. code-block:: html+php
-
-
- form($form) ?>
-
-.. image:: /images/book/form-simple.png
- :align: center
-
-.. note::
-
- This example assumes that you submit the form in a "POST" request and to
- the same URL that it was displayed in. You will learn later how to
- change the request method and the target URL of the form.
-
-That's it! By printing ``form(form)``, each field in the form is rendered, along
-with a label and error message (if there is one). The ``form`` function also
-surrounds everything in the necessary HTML ``form`` tag. As easy as this is,
-it's not very flexible (yet). Usually, you'll want to render each form field
-individually so you can control how the form looks. You'll learn how to do
-that in the ":ref:`form-rendering-template`" section.
-
-Before moving on, notice how the rendered ``task`` input field has the value
-of the ``task`` property from the ``$task`` object (i.e. "Write a blog post").
-This is the first job of a form: to take data from an object and translate
-it into a format that's suitable for being rendered in an HTML form.
-
-.. tip::
-
- The form system is smart enough to access the value of the protected
- ``task`` property via the ``getTask()`` and ``setTask()`` methods on the
- ``Task`` class. Unless a property is public, it *must* have a "getter" and
- "setter" method so that the form component can get and put data onto the
- property. For a Boolean property, you can use an "isser" or "hasser" method
- (e.g. ``isPublished()`` or ``hasReminder()``) instead of a getter (e.g.
- ``getPublished()`` or ``getReminder()``).
-
- .. versionadded:: 2.1
- Support for "hasser" methods was added in Symfony 2.1.
-
-.. index::
- single: Forms; Handling form submission
-
-.. _book-form-handling-form-submissions:
-
-Handling Form Submissions
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The second job of a form is to translate user-submitted data back to the
-properties of an object. To make this happen, the submitted data from the
-user must be written into the form. Add the following functionality to your
-controller::
-
- // ...
- use Symfony\Component\HttpFoundation\Request;
-
- public function newAction(Request $request)
- {
- // just setup a fresh $task object (remove the dummy data)
- $task = new Task();
-
- $form = $this->createFormBuilder($task)
- ->add('task', 'text')
- ->add('dueDate', 'date')
- ->getForm();
-
- $form->handleRequest($request);
-
- if ($form->isValid()) {
- // perform some action, such as saving the task to the database
-
- return $this->redirect($this->generateUrl('task_success'));
- }
-
- // ...
- }
-
-.. versionadded:: 2.3
- The :method:`Symfony\Component\Form\FormInterface::handleRequest` method was
- added in Symfony 2.3. Previously, the ``$request`` was passed to the
- ``submit`` method - a strategy which is deprecated and will be removed
- in Symfony 3.0. For details on that method, see :ref:`cookbook-form-submit-request`.
-
-This controller follows a common pattern for handling forms, and has three
-possible paths:
-
-#. When initially loading the page in a browser, the form is simply created and
- rendered. :method:`Symfony\Component\Form\FormInterface::handleRequest`
- recognizes that the form was not submitted and does nothing.
- :method:`Symfony\Component\Form\FormInterface::isValid` returns ``false``
- if the form was not submitted.
-
-#. When the user submits the form, :method:`Symfony\Component\Form\FormInterface::handleRequest`
- recognizes this and immediately writes the submitted data back into the
- ``task`` and ``dueDate`` properties of the ``$task`` object. Then this object
- is validated. If it is invalid (validation is covered in the next section),
- :method:`Symfony\Component\Form\FormInterface::isValid` returns ``false``
- again, so the form is rendered together with all validation errors;
-
- .. note::
-
- You can use the method :method:`Symfony\Component\Form\FormInterface::isSubmitted`
- to check whether a form was submitted, regardless of whether or not the
- submitted data is actually valid.
-
-#. When the user submits the form with valid data, the submitted data is again
- written into the form, but this time :method:`Symfony\Component\Form\FormInterface::isValid`
- returns ``true``. Now you have the opportunity to perform some actions using
- the ``$task`` object (e.g. persisting it to the database) before redirecting
- the user to some other page (e.g. a "thank you" or "success" page).
-
- .. note::
-
- Redirecting a user after a successful form submission prevents the user
- from being able to hit "refresh" and re-post the data.
-
-.. index::
- single: Forms; Validation
-
-Form Validation
----------------
-
-In the previous section, you learned how a form can be submitted with valid
-or invalid data. In Symfony2, validation is applied to the underlying object
-(e.g. ``Task``). In other words, the question isn't whether the "form" is
-valid, but whether or not the ``$task`` object is valid after the form has
-applied the submitted data to it. Calling ``$form->isValid()`` is a shortcut
-that asks the ``$task`` object whether or not it has valid data.
-
-Validation is done by adding a set of rules (called constraints) to a class. To
-see this in action, add validation constraints so that the ``task`` field cannot
-be empty and the ``dueDate`` field cannot be empty and must be a valid \DateTime
-object.
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # Acme/TaskBundle/Resources/config/validation.yml
- Acme\TaskBundle\Entity\Task:
- properties:
- task:
- - NotBlank: ~
- dueDate:
- - NotBlank: ~
- - Type: \DateTime
-
- .. code-block:: php-annotations
-
- // Acme/TaskBundle/Entity/Task.php
- use Symfony\Component\Validator\Constraints as Assert;
-
- class Task
- {
- /**
- * @Assert\NotBlank()
- */
- public $task;
-
- /**
- * @Assert\NotBlank()
- * @Assert\Type("\DateTime")
- */
- protected $dueDate;
- }
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
- \DateTime
-
-
-
- .. code-block:: php
-
- // Acme/TaskBundle/Entity/Task.php
- use Symfony\Component\Validator\Mapping\ClassMetadata;
- use Symfony\Component\Validator\Constraints\NotBlank;
- use Symfony\Component\Validator\Constraints\Type;
-
- class Task
- {
- // ...
-
- public static function loadValidatorMetadata(ClassMetadata $metadata)
- {
- $metadata->addPropertyConstraint('task', new NotBlank());
-
- $metadata->addPropertyConstraint('dueDate', new NotBlank());
- $metadata->addPropertyConstraint('dueDate', new Type('\DateTime'));
- }
- }
-
-That's it! If you re-submit the form with invalid data, you'll see the
-corresponding errors printed out with the form.
-
-.. _book-forms-html5-validation-disable:
-
-.. sidebar:: HTML5 Validation
-
- As of HTML5, many browsers can natively enforce certain validation constraints
- on the client side. The most common validation is activated by rendering
- a ``required`` attribute on fields that are required. For browsers that
- support HTML5, this will result in a native browser message being displayed
- if the user tries to submit the form with that field blank.
-
- Generated forms take full advantage of this new feature by adding sensible
- HTML attributes that trigger the validation. The client-side validation,
- however, can be disabled by adding the ``novalidate`` attribute to the
- ``form`` tag or ``formnovalidate`` to the submit tag. This is especially
- useful when you want to test your server-side validation constraints,
- but are being prevented by your browser from, for example, submitting
- blank fields.
-
-Validation is a very powerful feature of Symfony2 and has its own
-:doc:`dedicated chapter`.
-
-.. index::
- single: Forms; Validation groups
-
-.. _book-forms-validation-groups:
-
-Validation Groups
-~~~~~~~~~~~~~~~~~
-
-.. tip::
-
- If you're not using :ref:`validation groups `,
- then you can skip this section.
-
-If your object takes advantage of :ref:`validation groups `,
-you'll need to specify which validation group(s) your form should use::
-
- $form = $this->createFormBuilder($users, array(
- 'validation_groups' => array('registration'),
- ))->add(...);
-
-If you're creating :ref:`form classes` (a
-good practice), then you'll need to add the following to the ``setDefaultOptions()``
-method::
-
- use Symfony\Component\OptionsResolver\OptionsResolverInterface;
-
- public function setDefaultOptions(OptionsResolverInterface $resolver)
- {
- $resolver->setDefaults(array(
- 'validation_groups' => array('registration'),
- ));
- }
-
-In both of these cases, *only* the ``registration`` validation group will
-be used to validate the underlying object.
-
-Groups based on Submitted Data
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. versionadded:: 2.1
- The ability to specify a callback or Closure in ``validation_groups``
- is new to version 2.1
-
-If you need some advanced logic to determine the validation groups (e.g.
-based on submitted data), you can set the ``validation_groups`` option
-to an array callback, or a ``Closure``::
-
- use Symfony\Component\OptionsResolver\OptionsResolverInterface;
-
- public function setDefaultOptions(OptionsResolverInterface $resolver)
- {
- $resolver->setDefaults(array(
- 'validation_groups' => array('Acme\\AcmeBundle\\Entity\\Client', 'determineValidationGroups'),
- ));
- }
-
-This will call the static method ``determineValidationGroups()`` on the
-``Client`` class after the form is submitted, but before validation is executed.
-The Form object is passed as an argument to that method (see next example).
-You can also define whole logic inline by using a Closure::
-
- use Symfony\Component\Form\FormInterface;
- use Symfony\Component\OptionsResolver\OptionsResolverInterface;
-
- public function setDefaultOptions(OptionsResolverInterface $resolver)
- {
- $resolver->setDefaults(array(
- 'validation_groups' => function(FormInterface $form) {
- $data = $form->getData();
- if (Entity\Client::TYPE_PERSON == $data->getType()) {
- return array('person');
- } else {
- return array('company');
- }
- },
- ));
- }
-
-.. index::
- single: Forms; Built-in field types
-
-.. _book-forms-type-reference:
-
-Built-in Field Types
---------------------
-
-Symfony comes standard with a large group of field types that cover all of
-the common form fields and data types you'll encounter:
-
-.. include:: /reference/forms/types/map.rst.inc
-
-You can also create your own custom field types. This topic is covered in
-the ":doc:`/cookbook/form/create_custom_field_type`" article of the cookbook.
-
-.. index::
- single: Forms; Field type options
-
-Field Type Options
-~~~~~~~~~~~~~~~~~~
-
-Each field type has a number of options that can be used to configure it.
-For example, the ``dueDate`` field is currently being rendered as 3 select
-boxes. However, the :doc:`date field` can be
-configured to be rendered as a single text box (where the user would enter
-the date as a string in the box)::
-
- ->add('dueDate', 'date', array('widget' => 'single_text'))
-
-.. image:: /images/book/form-simple2.png
- :align: center
-
-Each field type has a number of different options that can be passed to it.
-Many of these are specific to the field type and details can be found in
-the documentation for each type.
-
-.. sidebar:: The ``required`` option
-
- The most common option is the ``required`` option, which can be applied to
- any field. By default, the ``required`` option is set to ``true``, meaning
- that HTML5-ready browsers will apply client-side validation if the field
- is left blank. If you don't want this behavior, either set the ``required``
- option on your field to ``false`` or :ref:`disable HTML5 validation`.
-
- Also note that setting the ``required`` option to ``true`` will **not**
- result in server-side validation to be applied. In other words, if a
- user submits a blank value for the field (either with an old browser
- or web service, for example), it will be accepted as a valid value unless
- you use Symfony's ``NotBlank`` or ``NotNull`` validation constraint.
-
- In other words, the ``required`` option is "nice", but true server-side
- validation should *always* be used.
-
-.. sidebar:: The ``label`` option
-
- The label for the form field can be set using the ``label`` option,
- which can be applied to any field::
-
- ->add('dueDate', 'date', array(
- 'widget' => 'single_text',
- 'label' => 'Due Date',
- ))
-
- The label for a field can also be set in the template rendering the
- form, see below.
-
-.. index::
- single: Forms; Field type guessing
-
-.. _book-forms-field-guessing:
-
-Field Type Guessing
--------------------
-
-Now that you've added validation metadata to the ``Task`` class, Symfony
-already knows a bit about your fields. If you allow it, Symfony can "guess"
-the type of your field and set it up for you. In this example, Symfony can
-guess from the validation rules that both the ``task`` field is a normal
-``text`` field and the ``dueDate`` field is a ``date`` field::
-
- public function newAction()
- {
- $task = new Task();
-
- $form = $this->createFormBuilder($task)
- ->add('task')
- ->add('dueDate', null, array('widget' => 'single_text'))
- ->getForm();
- }
-
-The "guessing" is activated when you omit the second argument to the ``add()``
-method (or if you pass ``null`` to it). If you pass an options array as the
-third argument (done for ``dueDate`` above), these options are applied to
-the guessed field.
-
-.. caution::
-
- If your form uses a specific validation group, the field type guesser
- will still consider *all* validation constraints when guessing your
- field types (including constraints that are not part of the validation
- group(s) being used).
-
-.. index::
- single: Forms; Field type guessing
-
-Field Type Options Guessing
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-In addition to guessing the "type" for a field, Symfony can also try to guess
-the correct values of a number of field options.
-
-.. tip::
-
- When these options are set, the field will be rendered with special HTML
- attributes that provide for HTML5 client-side validation. However, it
- doesn't generate the equivalent server-side constraints (e.g. ``Assert\Length``).
- And though you'll need to manually add your server-side validation, these
- field type options can then be guessed from that information.
-
-* ``required``: The ``required`` option can be guessed based on the validation
- rules (i.e. is the field ``NotBlank`` or ``NotNull``) or the Doctrine metadata
- (i.e. is the field ``nullable``). This is very useful, as your client-side
- validation will automatically match your validation rules.
-
-* ``max_length``: If the field is some sort of text field, then the ``max_length``
- option can be guessed from the validation constraints (if ``Length`` or
- ``Range`` is used) or from the Doctrine metadata (via the field's length).
-
-.. note::
-
- These field options are *only* guessed if you're using Symfony to guess
- the field type (i.e. omit or pass ``null`` as the second argument to ``add()``).
-
-If you'd like to change one of the guessed values, you can override it by
-passing the option in the options field array::
-
- ->add('task', null, array('max_length' => 4))
-
-.. index::
- single: Forms; Rendering in a template
-
-.. _form-rendering-template:
-
-Rendering a Form in a Template
-------------------------------
-
-So far, you've seen how an entire form can be rendered with just one line
-of code. Of course, you'll usually need much more flexibility when rendering:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
- {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
- {{ form_start(form) }}
- {{ form_errors(form) }}
-
- {{ form_row(form.task) }}
- {{ form_row(form.dueDate) }}
-
-
- {{ form_end(form) }}
-
- .. code-block:: html+php
-
-
- start($form) ?>
- errors($form) ?>
-
- row($form['task']) ?>
- row($form['dueDate']) ?>
-
-
- end($form) ?>
-
-Take a look at each part:
-
-* ``form_start(form)`` - Renders the start tag of the form.
-
-* ``form_errors(form)`` - Renders any errors global to the whole form
- (field-specific errors are displayed next to each field);
-
-* ``form_row(form.dueDate)`` - Renders the label, any errors, and the HTML
- form widget for the given field (e.g. ``dueDate``) inside, by default, a
- ``div`` element;
-
-* ``form_end()`` - Renders the end tag of the form and any fields that have not
- yet been rendered. This is useful for rendering hidden fields and taking
- advantage of the automatic :ref:`CSRF Protection`.
-
-The majority of the work is done by the ``form_row`` helper, which renders
-the label, errors and HTML form widget of each field inside a ``div`` tag
-by default. In the :ref:`form-theming` section, you'll learn how the ``form_row``
-output can be customized on many different levels.
-
-.. tip::
-
- You can access the current data of your form via ``form.vars.value``:
-
- .. configuration-block::
-
- .. code-block:: jinja
-
- {{ form.vars.value.task }}
-
- .. code-block:: html+php
-
- get('value')->getTask() ?>
-
-.. index::
- single: Forms; Rendering each field by hand
-
-Rendering each Field by Hand
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The ``form_row`` helper is great because you can very quickly render each
-field of your form (and the markup used for the "row" can be customized as
-well). But since life isn't always so simple, you can also render each field
-entirely by hand. The end-product of the following is the same as when you
-used the ``form_row`` helper:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
- {{ form_start(form) }}
- {{ form_errors(form) }}
-
-
-
-
-
- end($form) ?>
-
-If the auto-generated label for a field isn't quite right, you can explicitly
-specify it:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
- {{ form_label(form.task, 'Task Description') }}
-
- .. code-block:: html+php
-
- label($form['task'], 'Task Description') ?>
-
-Some field types have additional rendering options that can be passed
-to the widget. These options are documented with each type, but one common
-options is ``attr``, which allows you to modify attributes on the form element.
-The following would add the ``task_field`` class to the rendered input text
-field:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
- {{ form_widget(form.task, {'attr': {'class': 'task_field'}}) }}
-
- .. code-block:: html+php
-
- widget($form['task'], array(
- 'attr' => array('class' => 'task_field'),
- )) ?>
-
-If you need to render form fields "by hand" then you can access individual
-values for fields such as the ``id``, ``name`` and ``label``. For example
-to get the ``id``:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
- {{ form.task.vars.id }}
-
- .. code-block:: html+php
-
- get('id') ?>
-
-To get the value used for the form field's name attribute you need to use
-the ``full_name`` value:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
- {{ form.task.vars.full_name }}
-
- .. code-block:: html+php
-
- get('full_name') ?>
-
-Twig Template Function Reference
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-If you're using Twig, a full reference of the form rendering functions is
-available in the :doc:`reference manual`.
-Read this to know everything about the helpers available and the options
-that can be used with each.
-
-.. index::
- single: Forms; Changing the action and method
-
-.. _book-forms-changing-action-and-method:
-
-Changing the Action and Method of a Form
-----------------------------------------
-
-So far, the ``form_start()`` helper has been used to render the form's start
-tag and we assumed that each form is submitted to the same URL in a POST request.
-Sometimes you want to change these parameters. You can do so in a few different
-ways. If you build your form in the controller, you can use ``setAction()`` and
-``setMethod()``::
-
- $form = $this->createFormBuilder($task)
- ->setAction($this->generateUrl('target_route'))
- ->setMethod('GET')
- ->add('task', 'text')
- ->add('dueDate', 'date')
- ->getForm();
-
-.. note::
-
- This example assumes that you've created a route called ``target_route``
- that points to the controller that processes the form.
-
-In :ref:`book-form-creating-form-classes` you will learn how to move the
-form building code into separate classes. When using an external form class
-in the controller, you can pass the action and method as form options::
-
- $form = $this->createForm(new TaskType(), $task, array(
- 'action' => $this->generateUrl('target_route'),
- 'method' => 'GET',
- ));
-
-Finally, you can override the action and method in the template by passing them
-to the ``form()`` or the ``form_start()`` helper:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
- {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
- {{ form(form, {'action': path('target_route'), 'method': 'GET'}) }}
-
- {{ form_start(form, {'action': path('target_route'), 'method': 'GET'}) }}
-
- .. code-block:: html+php
-
-
- form($form, array(
- 'action' => $view['router']->generate('target_route'),
- 'method' => 'GET',
- )) ?>
-
- start($form, array(
- 'action' => $view['router']->generate('target_route'),
- 'method' => 'GET',
- )) ?>
-
-.. note::
-
- If the form's method is not GET or POST, but PUT, PATCH or DELETE, Symfony2
- will insert a hidden field with the name "_method" that stores this method.
- The form will be submitted in a normal POST request, but Symfony2's router
- is capable of detecting the "_method" parameter and will interpret the
- request as PUT, PATCH or DELETE request. Read the cookbook chapter
- ":doc:`/cookbook/routing/method_parameters`" for more information.
-
-.. index::
- single: Forms; Creating form classes
-
-.. _book-form-creating-form-classes:
-
-Creating Form Classes
----------------------
-
-As you've seen, a form can be created and used directly in a controller.
-However, a better practice is to build the form in a separate, standalone PHP
-class, which can then be reused anywhere in your application. Create a new class
-that will house the logic for building the task form::
-
- // src/Acme/TaskBundle/Form/Type/TaskType.php
- namespace Acme\TaskBundle\Form\Type;
-
- use Symfony\Component\Form\AbstractType;
- use Symfony\Component\Form\FormBuilderInterface;
-
- class TaskType extends AbstractType
- {
- public function buildForm(FormBuilderInterface $builder, array $options)
- {
- $builder->add('task');
- $builder->add('dueDate', null, array('widget' => 'single_text'));
- }
-
- public function getName()
- {
- return 'task';
- }
- }
-
-This new class contains all the directions needed to create the task form
-(note that the ``getName()`` method should return a unique identifier for this
-form "type"). It can be used to quickly build a form object in the controller::
-
- // src/Acme/TaskBundle/Controller/DefaultController.php
-
- // add this new use statement at the top of the class
- use Acme\TaskBundle\Form\Type\TaskType;
-
- public function newAction()
- {
- $task = ...;
- $form = $this->createForm(new TaskType(), $task);
-
- // ...
- }
-
-Placing the form logic into its own class means that the form can be easily
-reused elsewhere in your project. This is the best way to create forms, but
-the choice is ultimately up to you.
-
-.. _book-forms-data-class:
-
-.. sidebar:: Setting the ``data_class``
-
- Every form needs to know the name of the class that holds the underlying
- data (e.g. ``Acme\TaskBundle\Entity\Task``). Usually, this is just guessed
- based off of the object passed to the second argument to ``createForm``
- (i.e. ``$task``). Later, when you begin embedding forms, this will no
- longer be sufficient. So, while not always necessary, it's generally a
- good idea to explicitly specify the ``data_class`` option by adding the
- following to your form type class::
-
- use Symfony\Component\OptionsResolver\OptionsResolverInterface;
-
- public function setDefaultOptions(OptionsResolverInterface $resolver)
- {
- $resolver->setDefaults(array(
- 'data_class' => 'Acme\TaskBundle\Entity\Task',
- ));
- }
-
-.. tip::
-
- When mapping forms to objects, all fields are mapped. Any fields on the
- form that do not exist on the mapped object will cause an exception to
- be thrown.
-
- In cases where you need extra fields in the form (for example: a "do you
- agree with these terms" checkbox) that will not be mapped to the underlying
- object, you need to set the ``mapped`` option to ``false``::
-
- use Symfony\Component\Form\FormBuilderInterface;
-
- public function buildForm(FormBuilderInterface $builder, array $options)
- {
- $builder->add('task');
- $builder->add('dueDate', null, array('mapped' => false));
- }
-
- Additionally, if there are any fields on the form that aren't included in
- the submitted data, those fields will be explicitly set to ``null``.
-
- The field data can be accessed in a controller with::
-
- $form->get('dueDate')->getData();
-
-.. index::
- pair: Forms; Doctrine
-
-Forms and Doctrine
-------------------
-
-The goal of a form is to translate data from an object (e.g. ``Task``) to an
-HTML form and then translate user-submitted data back to the original object. As
-such, the topic of persisting the ``Task`` object to the database is entirely
-unrelated to the topic of forms. But, if you've configured the ``Task`` class
-to be persisted via Doctrine (i.e. you've added
-:ref:`mapping metadata` for it), then persisting
-it after a form submission can be done when the form is valid::
-
- if ($form->isValid()) {
- $em = $this->getDoctrine()->getManager();
- $em->persist($task);
- $em->flush();
-
- return $this->redirect($this->generateUrl('task_success'));
- }
-
-If, for some reason, you don't have access to your original ``$task`` object,
-you can fetch it from the form::
-
- $task = $form->getData();
-
-For more information, see the :doc:`Doctrine ORM chapter`.
-
-The key thing to understand is that when the form is submitted, the submitted
-data is transferred to the underlying object immediately. If you want to
-persist that data, you simply need to persist the object itself (which already
-contains the submitted data).
-
-.. index::
- single: Forms; Embedded forms
-
-Embedded Forms
---------------
-
-Often, you'll want to build a form that will include fields from many different
-objects. For example, a registration form may contain data belonging to
-a ``User`` object as well as many ``Address`` objects. Fortunately, this
-is easy and natural with the form component.
-
-Embedding a Single Object
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Suppose that each ``Task`` belongs to a simple ``Category`` object. Start,
-of course, by creating the ``Category`` object::
-
- // src/Acme/TaskBundle/Entity/Category.php
- namespace Acme\TaskBundle\Entity;
-
- use Symfony\Component\Validator\Constraints as Assert;
-
- class Category
- {
- /**
- * @Assert\NotBlank()
- */
- public $name;
- }
-
-Next, add a new ``category`` property to the ``Task`` class::
-
- // ...
-
- class Task
- {
- // ...
-
- /**
- * @Assert\Type(type="Acme\TaskBundle\Entity\Category")
- */
- protected $category;
-
- // ...
-
- public function getCategory()
- {
- return $this->category;
- }
-
- public function setCategory(Category $category = null)
- {
- $this->category = $category;
- }
- }
-
-Now that your application has been updated to reflect the new requirements,
-create a form class so that a ``Category`` object can be modified by the user::
-
- // src/Acme/TaskBundle/Form/Type/CategoryType.php
- namespace Acme\TaskBundle\Form\Type;
-
- use Symfony\Component\Form\AbstractType;
- use Symfony\Component\Form\FormBuilderInterface;
- use Symfony\Component\OptionsResolver\OptionsResolverInterface;
-
- class CategoryType extends AbstractType
- {
- public function buildForm(FormBuilderInterface $builder, array $options)
- {
- $builder->add('name');
- }
-
- public function setDefaultOptions(OptionsResolverInterface $resolver)
- {
- $resolver->setDefaults(array(
- 'data_class' => 'Acme\TaskBundle\Entity\Category',
- ));
- }
-
- public function getName()
- {
- return 'category';
- }
- }
-
-The end goal is to allow the ``Category`` of a ``Task`` to be modified right
-inside the task form itself. To accomplish this, add a ``category`` field
-to the ``TaskType`` object whose type is an instance of the new ``CategoryType``
-class:
-
-.. code-block:: php
-
- use Symfony\Component\Form\FormBuilderInterface;
-
- public function buildForm(FormBuilderInterface $builder, array $options)
- {
- // ...
-
- $builder->add('category', new CategoryType());
- }
-
-The fields from ``CategoryType`` can now be rendered alongside those from
-the ``TaskType`` class. To activate validation on CategoryType, add
-the ``cascade_validation`` option to ``TaskType``::
-
- public function setDefaultOptions(OptionsResolverInterface $resolver)
- {
- $resolver->setDefaults(array(
- 'data_class' => 'Acme\TaskBundle\Entity\Task',
- 'cascade_validation' => true,
- ));
- }
-
-Render the ``Category`` fields in the same way
-as the original ``Task`` fields:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
- {# ... #}
-
-
-
-
-
-When the user submits the form, the submitted data for the ``Category`` fields
-are used to construct an instance of ``Category``, which is then set on the
-``category`` field of the ``Task`` instance.
-
-The ``Category`` instance is accessible naturally via ``$task->getCategory()``
-and can be persisted to the database or used however you need.
-
-Embedding a Collection of Forms
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You can also embed a collection of forms into one form (imagine a ``Category``
-form with many ``Product`` sub-forms). This is done by using the ``collection``
-field type.
-
-For more information see the ":doc:`/cookbook/form/form_collections`" cookbook
-entry and the :doc:`collection` field type reference.
-
-.. index::
- single: Forms; Theming
- single: Forms; Customizing fields
-
-.. _form-theming:
-
-Form Theming
-------------
-
-Every part of how a form is rendered can be customized. You're free to change
-how each form "row" renders, change the markup used to render errors, or
-even customize how a ``textarea`` tag should be rendered. Nothing is off-limits,
-and different customizations can be used in different places.
-
-Symfony uses templates to render each and every part of a form, such as
-``label`` tags, ``input`` tags, error messages and everything else.
-
-In Twig, each form "fragment" is represented by a Twig block. To customize
-any part of how a form renders, you just need to override the appropriate block.
-
-In PHP, each form "fragment" is rendered via an individual template file.
-To customize any part of how a form renders, you just need to override the
-existing template by creating a new one.
-
-To understand how this works, customize the ``form_row`` fragment and
-add a class attribute to the ``div`` element that surrounds each row. To
-do this, create a new template file that will store the new markup:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
- {# src/Acme/TaskBundle/Resources/views/Form/fields.html.twig #}
- {% block form_row %}
- {% spaceless %}
-
-
-The ``form_row`` form fragment is used when rendering most fields via the
-``form_row`` function. To tell the form component to use your new ``form_row``
-fragment defined above, add the following to the top of the template that
-renders the form:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
- {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
- {% form_theme form 'AcmeTaskBundle:Form:fields.html.twig' %}
-
- {% form_theme form 'AcmeTaskBundle:Form:fields.html.twig' 'AcmeTaskBundle:Form:fields2.html.twig' %}
-
- {{ form(form) }}
-
- .. code-block:: html+php
-
-
- setTheme($form, array('AcmeTaskBundle:Form')) ?>
-
- setTheme($form, array('AcmeTaskBundle:Form', 'AcmeTaskBundle:Form')) ?>
-
- form($form) ?>
-
-The ``form_theme`` tag (in Twig) "imports" the fragments defined in the given
-template and uses them when rendering the form. In other words, when the
-``form_row`` function is called later in this template, it will use the ``form_row``
-block from your custom theme (instead of the default ``form_row`` block
-that ships with Symfony).
-
-Your custom theme does not have to override all the blocks. When rendering a block
-which is not overridden in your custom theme, the theming engine will fall back
-to the global theme (defined at the bundle level).
-
-If several custom themes are provided they will be searched in the listed order
-before falling back to the global theme.
-
-To customize any portion of a form, you just need to override the appropriate
-fragment. Knowing exactly which block or file to override is the subject of
-the next section.
-
-.. versionadded:: 2.1
- An alternate Twig syntax for ``form_theme`` has been introduced in 2.1. It accepts
- any valid Twig expression (the most noticeable difference is using an array when
- using multiple themes).
-
- .. code-block:: html+jinja
-
- {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
-
- {% form_theme form with 'AcmeTaskBundle:Form:fields.html.twig' %}
-
- {% form_theme form with ['AcmeTaskBundle:Form:fields.html.twig', 'AcmeTaskBundle:Form:fields2.html.twig'] %}
-
-For a more extensive discussion, see :doc:`/cookbook/form/form_customization`.
-
-.. index::
- single: Forms; Template fragment naming
-
-.. _form-template-blocks:
-
-Form Fragment Naming
-~~~~~~~~~~~~~~~~~~~~
-
-In Symfony, every part of a form that is rendered - HTML form elements, errors,
-labels, etc - is defined in a base theme, which is a collection of blocks
-in Twig and a collection of template files in PHP.
-
-In Twig, every block needed is defined in a single template file (`form_div_layout.html.twig`_)
-that lives inside the `Twig Bridge`_. Inside this file, you can see every block
-needed to render a form and every default field type.
-
-In PHP, the fragments are individual template files. By default they are located in
-the `Resources/views/Form` directory of the framework bundle (`view on GitHub`_).
-
-Each fragment name follows the same basic pattern and is broken up into two pieces,
-separated by a single underscore character (``_``). A few examples are:
-
-* ``form_row`` - used by ``form_row`` to render most fields;
-* ``textarea_widget`` - used by ``form_widget`` to render a ``textarea`` field
- type;
-* ``form_errors`` - used by ``form_errors`` to render errors for a field;
-
-Each fragment follows the same basic pattern: ``type_part``. The ``type`` portion
-corresponds to the field *type* being rendered (e.g. ``textarea``, ``checkbox``,
-``date``, etc) whereas the ``part`` portion corresponds to *what* is being
-rendered (e.g. ``label``, ``widget``, ``errors``, etc). By default, there
-are 4 possible *parts* of a form that can be rendered:
-
-+-------------+--------------------------+---------------------------------------------------------+
-| ``label`` | (e.g. ``form_label``) | renders the field's label |
-+-------------+--------------------------+---------------------------------------------------------+
-| ``widget`` | (e.g. ``form_widget``) | renders the field's HTML representation |
-+-------------+--------------------------+---------------------------------------------------------+
-| ``errors`` | (e.g. ``form_errors``) | renders the field's errors |
-+-------------+--------------------------+---------------------------------------------------------+
-| ``row`` | (e.g. ``form_row``) | renders the field's entire row (label, widget & errors) |
-+-------------+--------------------------+---------------------------------------------------------+
-
-.. note::
-
- There are actually 2 other *parts* - ``rows`` and ``rest`` -
- but you should rarely if ever need to worry about overriding them.
-
-By knowing the field type (e.g. ``textarea``) and which part you want to
-customize (e.g. ``widget``), you can construct the fragment name that needs
-to be overridden (e.g. ``textarea_widget``).
-
-.. index::
- single: Forms; Template fragment inheritance
-
-Template Fragment Inheritance
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-In some cases, the fragment you want to customize will appear to be missing.
-For example, there is no ``textarea_errors`` fragment in the default themes
-provided with Symfony. So how are the errors for a textarea field rendered?
-
-The answer is: via the ``form_errors`` fragment. When Symfony renders the errors
-for a textarea type, it looks first for a ``textarea_errors`` fragment before
-falling back to the ``form_errors`` fragment. Each field type has a *parent*
-type (the parent type of ``textarea`` is ``text``, its parent is ``form``),
-and Symfony uses the fragment for the parent type if the base fragment doesn't
-exist.
-
-So, to override the errors for *only* ``textarea`` fields, copy the
-``form_errors`` fragment, rename it to ``textarea_errors`` and customize it. To
-override the default error rendering for *all* fields, copy and customize the
-``form_errors`` fragment directly.
-
-.. tip::
-
- The "parent" type of each field type is available in the
- :doc:`form type reference` for each field type.
-
-.. index::
- single: Forms; Global Theming
-
-Global Form Theming
-~~~~~~~~~~~~~~~~~~~
-
-In the above example, you used the ``form_theme`` helper (in Twig) to "import"
-the custom form fragments into *just* that form. You can also tell Symfony
-to import form customizations across your entire project.
-
-Twig
-....
-
-To automatically include the customized blocks from the ``fields.html.twig``
-template created earlier in *all* templates, modify your application configuration
-file:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- twig:
- form:
- resources:
- - 'AcmeTaskBundle:Form:fields.html.twig'
- # ...
-
- .. code-block:: xml
-
-
-
-
- AcmeTaskBundle:Form:fields.html.twig
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- $container->loadFromExtension('twig', array(
- 'form' => array(
- 'resources' => array(
- 'AcmeTaskBundle:Form:fields.html.twig',
- ),
- ),
- // ...
- ));
-
-Any blocks inside the ``fields.html.twig`` template are now used globally
-to define form output.
-
-.. sidebar:: Customizing Form Output all in a Single File with Twig
-
- In Twig, you can also customize a form block right inside the template
- where that customization is needed:
-
- .. code-block:: html+jinja
-
- {% extends '::base.html.twig' %}
-
- {# import "_self" as the form theme #}
- {% form_theme form _self %}
-
- {# make the form fragment customization #}
- {% block form_row %}
- {# custom field row output #}
- {% endblock form_row %}
-
- {% block content %}
- {# ... #}
-
- {{ form_row(form.task) }}
- {% endblock %}
-
- The ``{% form_theme form _self %}`` tag allows form blocks to be customized
- directly inside the template that will use those customizations. Use
- this method to quickly make form output customizations that will only
- ever be needed in a single template.
-
- .. caution::
-
- This ``{% form_theme form _self %}`` functionality will *only* work
- if your template extends another. If your template does not, you
- must point ``form_theme`` to a separate template.
-
-PHP
-...
-
-To automatically include the customized templates from the ``Acme/TaskBundle/Resources/views/Form``
-directory created earlier in *all* templates, modify your application configuration
-file:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- framework:
- templating:
- form:
- resources:
- - 'AcmeTaskBundle:Form'
- # ...
-
-
- .. code-block:: xml
-
-
-
-
-
- AcmeTaskBundle:Form
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- $container->loadFromExtension('framework', array(
- 'templating' => array(
- 'form' => array(
- 'resources' => array(
- 'AcmeTaskBundle:Form',
- ),
- ),
- )
- // ...
- ));
-
-Any fragments inside the ``Acme/TaskBundle/Resources/views/Form`` directory
-are now used globally to define form output.
-
-.. index::
- single: Forms; CSRF protection
-
-.. _forms-csrf:
-
-CSRF Protection
----------------
-
-CSRF - or `Cross-site request forgery`_ - is a method by which a malicious
-user attempts to make your legitimate users unknowingly submit data that
-they don't intend to submit. Fortunately, CSRF attacks can be prevented by
-using a CSRF token inside your forms.
-
-The good news is that, by default, Symfony embeds and validates CSRF tokens
-automatically for you. This means that you can take advantage of the CSRF
-protection without doing anything. In fact, every form in this chapter has
-taken advantage of the CSRF protection!
-
-CSRF protection works by adding a hidden field to your form - called ``_token``
-by default - that contains a value that only you and your user knows. This
-ensures that the user - not some other entity - is submitting the given data.
-Symfony automatically validates the presence and accuracy of this token.
-
-The ``_token`` field is a hidden field and will be automatically rendered
-if you include the ``form_end()`` function in your template, which ensures
-that all un-rendered fields are output.
-
-The CSRF token can be customized on a form-by-form basis. For example::
-
- use Symfony\Component\OptionsResolver\OptionsResolverInterface;
-
- class TaskType extends AbstractType
- {
- // ...
-
- public function setDefaultOptions(OptionsResolverInterface $resolver)
- {
- $resolver->setDefaults(array(
- 'data_class' => 'Acme\TaskBundle\Entity\Task',
- 'csrf_protection' => true,
- 'csrf_field_name' => '_token',
- // a unique key to help generate the secret token
- 'intention' => 'task_item',
- ));
- }
-
- // ...
- }
-
-To disable CSRF protection, set the ``csrf_protection`` option to false.
-Customizations can also be made globally in your project. For more information,
-see the :ref:`form configuration reference `
-section.
-
-.. note::
-
- The ``intention`` option is optional but greatly enhances the security of
- the generated token by making it different for each form.
-
-.. index::
- single: Forms; With no class
-
-Using a Form without a Class
-----------------------------
-
-In most cases, a form is tied to an object, and the fields of the form get
-and store their data on the properties of that object. This is exactly what
-you've seen so far in this chapter with the `Task` class.
-
-But sometimes, you may just want to use a form without a class, and get back
-an array of the submitted data. This is actually really easy::
-
- // make sure you've imported the Request namespace above the class
- use Symfony\Component\HttpFoundation\Request;
- // ...
-
- public function contactAction(Request $request)
- {
- $defaultData = array('message' => 'Type your message here');
- $form = $this->createFormBuilder($defaultData)
- ->add('name', 'text')
- ->add('email', 'email')
- ->add('message', 'textarea')
- ->getForm();
-
- $form->handleRequest($request);
-
- if ($form->isValid()) {
- // data is an array with "name", "email", and "message" keys
- $data = $form->getData();
- }
-
- // ... render the form
- }
-
-By default, a form actually assumes that you want to work with arrays of
-data, instead of an object. There are exactly two ways that you can change
-this behavior and tie the form to an object instead:
-
-#. Pass an object when creating the form (as the first argument to ``createFormBuilder``
- or the second argument to ``createForm``);
-
-#. Declare the ``data_class`` option on your form.
-
-If you *don't* do either of these, then the form will return the data as
-an array. In this example, since ``$defaultData`` is not an object (and
-no ``data_class`` option is set), ``$form->getData()`` ultimately returns
-an array.
-
-.. tip::
-
- You can also access POST values (in this case "name") directly through
- the request object, like so::
-
- $this->get('request')->request->get('name');
-
- Be advised, however, that in most cases using the getData() method is
- a better choice, since it returns the data (usually an object) after
- it's been transformed by the form framework.
-
-Adding Validation
-~~~~~~~~~~~~~~~~~
-
-The only missing piece is validation. Usually, when you call ``$form->isValid()``,
-the object is validated by reading the constraints that you applied to that
-class. If your form is mapped to an object (i.e. you're using the ``data_class``
-option or passing an object to your form), this is almost always the approach
-you want to use. See :doc:`/book/validation` for more details.
-
-.. _form-option-constraints:
-
-But if the form is not mapped to an object and you instead want to retrieve a
-simple array of your submitted data, how can you add constraints to the data of
-your form?
-
-The answer is to setup the constraints yourself, and attach them to the individual
-fields. The overall approach is covered a bit more in the :ref:`validation chapter`,
-but here's a short example:
-
-.. versionadded:: 2.1
- The ``constraints`` option, which accepts a single constraint or an array
- of constraints (before 2.1, the option was called ``validation_constraint``,
- and only accepted a single constraint) is new to Symfony 2.1.
-
-.. code-block:: php
-
- use Symfony\Component\Validator\Constraints\Length;
- use Symfony\Component\Validator\Constraints\NotBlank;
-
- $builder
- ->add('firstName', 'text', array(
- 'constraints' => new Length(array('min' => 3)),
- ))
- ->add('lastName', 'text', array(
- 'constraints' => array(
- new NotBlank(),
- new Length(array('min' => 3)),
- ),
- ))
- ;
-
-.. tip::
-
- If you are using Validation Groups, you need to either reference the
- ``Default`` group when creating the form, or set the correct group on
- the constraint you are adding.
-
-.. code-block:: php
-
- new NotBlank(array('groups' => array('create', 'update'))
-
-
-Final Thoughts
---------------
-
-You now know all of the building blocks necessary to build complex and
-functional forms for your application. When building forms, keep in mind that
-the first goal of a form is to translate data from an object (``Task``) to an
-HTML form so that the user can modify that data. The second goal of a form is to
-take the data submitted by the user and to re-apply it to the object.
-
-There's still much more to learn about the powerful world of forms, such as
-how to handle :doc:`file uploads with Doctrine
-` or how to create a form where a dynamic
-number of sub-forms can be added (e.g. a todo list where you can keep adding
-more fields via Javascript before submitting). See the cookbook for these
-topics. Also, be sure to lean on the
-:doc:`field type reference documentation`, which
-includes examples of how to use each field type and its options.
-
-Learn more from the Cookbook
-----------------------------
-
-* :doc:`/cookbook/doctrine/file_uploads`
-* :doc:`File Field Reference `
-* :doc:`Creating Custom Field Types `
-* :doc:`/cookbook/form/form_customization`
-* :doc:`/cookbook/form/dynamic_form_modification`
-* :doc:`/cookbook/form/data_transformers`
-
-.. _`Symfony2 Form Component`: https://github.com/symfony/Form
-.. _`DateTime`: http://php.net/manual/en/class.datetime.php
-.. _`Twig Bridge`: https://github.com/symfony/symfony/tree/2.2/src/Symfony/Bridge/Twig
-.. _`form_div_layout.html.twig`: https://github.com/symfony/symfony/blob/2.2/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig
-.. _`Cross-site request forgery`: http://en.wikipedia.org/wiki/Cross-site_request_forgery
-.. _`view on GitHub`: https://github.com/symfony/symfony/tree/2.2/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form
diff --git a/book/from_flat_php_to_symfony2.rst b/book/from_flat_php_to_symfony2.rst
deleted file mode 100644
index ff684736571..00000000000
--- a/book/from_flat_php_to_symfony2.rst
+++ /dev/null
@@ -1,763 +0,0 @@
-Symfony2 versus Flat PHP
-========================
-
-**Why is Symfony2 better than just opening up a file and writing flat PHP?**
-
-If you've never used a PHP framework, aren't familiar with the MVC philosophy,
-or just wonder what all the *hype* is around Symfony2, this chapter is for
-you. Instead of *telling* you that Symfony2 allows you to develop faster and
-better software than with flat PHP, you'll see for yourself.
-
-In this chapter, you'll write a simple application in flat PHP, and then
-refactor it to be more organized. You'll travel through time, seeing the
-decisions behind why web development has evolved over the past several years
-to where it is now.
-
-By the end, you'll see how Symfony2 can rescue you from mundane tasks and
-let you take back control of your code.
-
-A simple Blog in flat PHP
--------------------------
-
-In this chapter, you'll build the token blog application using only flat PHP.
-To begin, create a single page that displays blog entries that have been
-persisted to the database. Writing in flat PHP is quick and dirty:
-
-.. code-block:: html+php
-
-
-
-
-
-
- List of Posts
-
-
-
-
-
-
-
-
-That's quick to write, fast to execute, and, as your app grows, impossible
-to maintain. There are several problems that need to be addressed:
-
-* **No error-checking**: What if the connection to the database fails?
-
-* **Poor organization**: If the application grows, this single file will become
- increasingly unmaintainable. Where should you put code to handle a form
- submission? How can you validate data? Where should code go for sending
- emails?
-
-* **Difficult to reuse code**: Since everything is in one file, there's no
- way to reuse any part of the application for other "pages" of the blog.
-
-.. note::
-
- Another problem not mentioned here is the fact that the database is
- tied to MySQL. Though not covered here, Symfony2 fully integrates `Doctrine`_,
- a library dedicated to database abstraction and mapping.
-
-Let's get to work on solving these problems and more.
-
-Isolating the Presentation
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The code can immediately gain from separating the application "logic" from
-the code that prepares the HTML "presentation":
-
-.. code-block:: html+php
-
-
-
-
- List of Posts
-
-
-
-
-
-
-By convention, the file that contains all of the application logic - ``index.php`` -
-is known as a "controller". The term :term:`controller` is a word you'll hear
-a lot, regardless of the language or framework you use. It refers simply
-to the area of *your* code that processes user input and prepares the response.
-
-In this case, the controller prepares data from the database and then includes
-a template to present that data. With the controller isolated, you could
-easily change *just* the template file if you needed to render the blog
-entries in some other format (e.g. ``list.json.php`` for JSON format).
-
-Isolating the Application (Domain) Logic
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-So far the application contains only one page. But what if a second page
-needed to use the same database connection, or even the same array of blog
-posts? Refactor the code so that the core behavior and data-access functions
-of the application are isolated in a new file called ``model.php``:
-
-.. code-block:: html+php
-
-
-
-
-
-
-
-
-
-
-
-
-The template (``templates/list.php``) can now be simplified to "extend"
-the layout:
-
-.. code-block:: html+php
-
-
-
-
-
-
-
-
-
-You've now introduced a methodology that allows for the reuse of the
-layout. Unfortunately, to accomplish this, you're forced to use a few ugly
-PHP functions (``ob_start()``, ``ob_get_clean()``) in the template. Symfony2
-uses a ``Templating`` component that allows this to be accomplished cleanly
-and easily. You'll see it in action shortly.
-
-Adding a Blog "show" Page
--------------------------
-
-The blog "list" page has now been refactored so that the code is better-organized
-and reusable. To prove it, add a blog "show" page, which displays an individual
-blog post identified by an ``id`` query parameter.
-
-To begin, create a new function in the ``model.php`` file that retrieves
-an individual blog result based on a given id::
-
- // model.php
- function get_post_by_id($id)
- {
- $link = open_database_connection();
-
- $id = intval($id);
- $query = 'SELECT date, title, body FROM post WHERE id = '.$id;
- $result = mysql_query($query);
- $row = mysql_fetch_assoc($result);
-
- close_database_connection($link);
-
- return $row;
- }
-
-Next, create a new file called ``show.php`` - the controller for this new
-page:
-
-.. code-block:: html+php
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Creating the second page is now very easy and no code is duplicated. Still,
-this page introduces even more lingering problems that a framework can solve
-for you. For example, a missing or invalid ``id`` query parameter will cause
-the page to crash. It would be better if this caused a 404 page to be rendered,
-but this can't really be done easily yet. Worse, had you forgotten to clean
-the ``id`` parameter via the ``intval()`` function, your
-entire database would be at risk for an SQL injection attack.
-
-Another major problem is that each individual controller file must include
-the ``model.php`` file. What if each controller file suddenly needed to include
-an additional file or perform some other global task (e.g. enforce security)?
-As it stands now, that code would need to be added to every controller file.
-If you forget to include something in one file, hopefully it doesn't relate
-to security...
-
-A "Front Controller" to the Rescue
-----------------------------------
-
-The solution is to use a :term:`front controller`: a single PHP file through
-which *all* requests are processed. With a front controller, the URIs for the
-application change slightly, but start to become more flexible:
-
-.. code-block:: text
-
- Without a front controller
- /index.php => Blog post list page (index.php executed)
- /show.php => Blog post show page (show.php executed)
-
- With index.php as the front controller
- /index.php => Blog post list page (index.php executed)
- /index.php/show => Blog post show page (index.php executed)
-
-.. tip::
- The ``index.php`` portion of the URI can be removed if using Apache
- rewrite rules (or equivalent). In that case, the resulting URI of the
- blog show page would be simply ``/show``.
-
-When using a front controller, a single PHP file (``index.php`` in this case)
-renders *every* request. For the blog post show page, ``/index.php/show`` will
-actually execute the ``index.php`` file, which is now responsible for routing
-requests internally based on the full URI. As you'll see, a front controller
-is a very powerful tool.
-
-Creating the Front Controller
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You're about to take a **big** step with the application. With one file handling
-all requests, you can centralize things such as security handling, configuration
-loading, and routing. In this application, ``index.php`` must now be smart
-enough to render the blog post list page *or* the blog post show page based
-on the requested URI:
-
-.. code-block:: html+php
-
-
Page Not Found
';
- }
-
-For organization, both controllers (formerly ``index.php`` and ``show.php``)
-are now PHP functions and each has been moved into a separate file, ``controllers.php``:
-
-.. code-block:: php
-
- function list_action()
- {
- $posts = get_all_posts();
- require 'templates/list.php';
- }
-
- function show_action($id)
- {
- $post = get_post_by_id($id);
- require 'templates/show.php';
- }
-
-As a front controller, ``index.php`` has taken on an entirely new role, one
-that includes loading the core libraries and routing the application so that
-one of the two controllers (the ``list_action()`` and ``show_action()``
-functions) is called. In reality, the front controller is beginning to look and
-act a lot like Symfony2's mechanism for handling and routing requests.
-
-.. tip::
-
- Another advantage of a front controller is flexible URLs. Notice that
- the URL to the blog post show page could be changed from ``/show`` to ``/read``
- by changing code in only one location. Before, an entire file needed to
- be renamed. In Symfony2, URLs are even more flexible.
-
-By now, the application has evolved from a single PHP file into a structure
-that is organized and allows for code reuse. You should be happier, but far
-from satisfied. For example, the "routing" system is fickle, and wouldn't
-recognize that the list page (``/index.php``) should be accessible also via ``/``
-(if Apache rewrite rules were added). Also, instead of developing the blog,
-a lot of time is being spent working on the "architecture" of the code (e.g.
-routing, calling controllers, templates, etc.). More time will need to be
-spent to handle form submissions, input validation, logging and security.
-Why should you have to reinvent solutions to all these routine problems?
-
-Add a Touch of Symfony2
-~~~~~~~~~~~~~~~~~~~~~~~
-
-Symfony2 to the rescue. Before actually using Symfony2, you need to download
-it. This can be done by using Composer, which takes care of downloading the
-correct version and all its dependencies and provides an autoloader. An
-autoloader is a tool that makes it possible to start using PHP classes
-without explicitly including the file containing the class.
-
-In your root directory, create a ``composer.json`` file with the following
-content:
-
-.. code-block:: json
-
- {
- "require": {
- "symfony/symfony": "2.2.*"
- },
- "autoload": {
- "files": ["model.php","controllers.php"]
- }
- }
-
-Next, `download Composer`_ and then run the following command, which will download Symfony
-into a vendor/ directory:
-
-.. code-block:: bash
-
- $ php composer.phar install
-
-Beside downloading your dependencies, Composer generates a ``vendor/autoload.php`` file,
-which takes care of autoloading for all the files in the Symfony Framework as well as
-the files mentioned in the autoload section of your ``composer.json``.
-
-Core to Symfony's philosophy is the idea that an application's main job is
-to interpret each request and return a response. To this end, Symfony2 provides
-both a :class:`Symfony\\Component\\HttpFoundation\\Request` and a
-:class:`Symfony\\Component\\HttpFoundation\\Response` class. These classes are
-object-oriented representations of the raw HTTP request being processed and
-the HTTP response being returned. Use them to improve the blog:
-
-.. code-block:: html+php
-
- getPathInfo();
- if ('/' == $uri) {
- $response = list_action();
- } elseif ('/show' == $uri && $request->query->has('id')) {
- $response = show_action($request->query->get('id'));
- } else {
- $html = '
Page Not Found
';
- $response = new Response($html, 404);
- }
-
- // echo the headers and send the response
- $response->send();
-
-The controllers are now responsible for returning a ``Response`` object.
-To make this easier, you can add a new ``render_template()`` function, which,
-incidentally, acts quite a bit like the Symfony2 templating engine:
-
-.. code-block:: php
-
- // controllers.php
- use Symfony\Component\HttpFoundation\Response;
-
- function list_action()
- {
- $posts = get_all_posts();
- $html = render_template('templates/list.php', array('posts' => $posts));
-
- return new Response($html);
- }
-
- function show_action($id)
- {
- $post = get_post_by_id($id);
- $html = render_template('templates/show.php', array('post' => $post));
-
- return new Response($html);
- }
-
- // helper function to render templates
- function render_template($path, array $args)
- {
- extract($args);
- ob_start();
- require $path;
- $html = ob_get_clean();
-
- return $html;
- }
-
-By bringing in a small part of Symfony2, the application is more flexible and
-reliable. The ``Request`` provides a dependable way to access information
-about the HTTP request. Specifically, the ``getPathInfo()`` method returns
-a cleaned URI (always returning ``/show`` and never ``/index.php/show``).
-So, even if the user goes to ``/index.php/show``, the application is intelligent
-enough to route the request through ``show_action()``.
-
-The ``Response`` object gives flexibility when constructing the HTTP response,
-allowing HTTP headers and content to be added via an object-oriented interface.
-And while the responses in this application are simple, this flexibility
-will pay dividends as your application grows.
-
-The Sample Application in Symfony2
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The blog has come a *long* way, but it still contains a lot of code for such
-a simple application. Along the way, you've made a simple routing
-system and a method using ``ob_start()`` and ``ob_get_clean()`` to render
-templates. If, for some reason, you needed to continue building this "framework"
-from scratch, you could at least use Symfony's standalone `Routing`_ and
-`Templating`_ components, which already solve these problems.
-
-Instead of re-solving common problems, you can let Symfony2 take care of
-them for you. Here's the same sample application, now built in Symfony2::
-
- // src/Acme/BlogBundle/Controller/BlogController.php
- namespace Acme\BlogBundle\Controller;
-
- use Symfony\Bundle\FrameworkBundle\Controller\Controller;
-
- class BlogController extends Controller
- {
- public function listAction()
- {
- $posts = $this->get('doctrine')->getManager()
- ->createQuery('SELECT p FROM AcmeBlogBundle:Post p')
- ->execute();
-
- return $this->render(
- 'AcmeBlogBundle:Blog:list.html.php',
- array('posts' => $posts)
- );
- }
-
- public function showAction($id)
- {
- $post = $this->get('doctrine')
- ->getManager()
- ->getRepository('AcmeBlogBundle:Post')
- ->find($id)
- ;
-
- if (!$post) {
- // cause the 404 page not found to be displayed
- throw $this->createNotFoundException();
- }
-
- return $this->render(
- 'AcmeBlogBundle:Blog:show.html.php',
- array('post' => $post)
- );
- }
- }
-
-The two controllers are still lightweight. Each uses the :doc:`Doctrine ORM library`
-to retrieve objects from the database and the ``Templating`` component to
-render a template and return a ``Response`` object. The list template is
-now quite a bit simpler:
-
-.. code-block:: html+php
-
-
- extend('::layout.html.php') ?>
-
- set('title', 'List of Posts') ?>
-
-
-
-The layout is nearly identical:
-
-.. code-block:: html+php
-
-
-
-
-
- output(
- 'title',
- 'Default title'
- ) ?>
-
-
- output('_content') ?>
-
-
-
-.. note::
-
- The show template is left as an exercise, as it should be trivial to
- create based on the list template.
-
-When Symfony2's engine (called the ``Kernel``) boots up, it needs a map so
-that it knows which controllers to execute based on the request information.
-A routing configuration map provides this information in a readable format:
-
-.. code-block:: yaml
-
- # app/config/routing.yml
- blog_list:
- path: /blog
- defaults: { _controller: AcmeBlogBundle:Blog:list }
-
- blog_show:
- path: /blog/show/{id}
- defaults: { _controller: AcmeBlogBundle:Blog:show }
-
-Now that Symfony2 is handling all the mundane tasks, the front controller
-is dead simple. And since it does so little, you'll never have to touch
-it once it's created (and if you use a Symfony2 distribution, you won't
-even need to create it!)::
-
- // web/app.php
- require_once __DIR__.'/../app/bootstrap.php';
- require_once __DIR__.'/../app/AppKernel.php';
-
- use Symfony\Component\HttpFoundation\Request;
-
- $kernel = new AppKernel('prod', false);
- $kernel->handle(Request::createFromGlobals())->send();
-
-The front controller's only job is to initialize Symfony2's engine (``Kernel``)
-and pass it a ``Request`` object to handle. Symfony2's core then uses the
-routing map to determine which controller to call. Just like before, the
-controller method is responsible for returning the final ``Response`` object.
-There's really not much else to it.
-
-For a visual representation of how Symfony2 handles each request, see the
-:ref:`request flow diagram`.
-
-Where Symfony2 Delivers
-~~~~~~~~~~~~~~~~~~~~~~~
-
-In the upcoming chapters, you'll learn more about how each piece of Symfony
-works and the recommended organization of a project. For now, let's see how
-migrating the blog from flat PHP to Symfony2 has improved life:
-
-* Your application now has **clear and consistently organized code** (though
- Symfony doesn't force you into this). This promotes **reusability** and
- allows for new developers to be productive in your project more quickly;
-
-* 100% of the code you write is for *your* application. You **don't need
- to develop or maintain low-level utilities** such as :ref:`autoloading`,
- :doc:`routing`, or rendering :doc:`controllers`;
-
-* Symfony2 gives you **access to open source tools** such as Doctrine and the
- Templating, Security, Form, Validation and Translation components (to name
- a few);
-
-* The application now enjoys **fully-flexible URLs** thanks to the ``Routing``
- component;
-
-* Symfony2's HTTP-centric architecture gives you access to powerful tools
- such as **HTTP caching** powered by **Symfony2's internal HTTP cache** or
- more powerful tools such as `Varnish`_. This is covered in a later chapter
- all about :doc:`caching`.
-
-And perhaps best of all, by using Symfony2, you now have access to a whole
-set of **high-quality open source tools developed by the Symfony2 community**!
-A good selection of Symfony2 community tools can be found on `KnpBundles.com`_.
-
-Better templates
-----------------
-
-If you choose to use it, Symfony2 comes standard with a templating engine
-called `Twig`_ that makes templates faster to write and easier to read.
-It means that the sample application could contain even less code! Take,
-for example, the list template written in Twig:
-
-.. code-block:: html+jinja
-
- {# src/Acme/BlogBundle/Resources/views/Blog/list.html.twig #}
- {% extends "::layout.html.twig" %}
-
- {% block title %}List of Posts{% endblock %}
-
- {% block body %}
-
- {% endblock %}
-
-The corresponding ``layout.html.twig`` template is also easier to write:
-
-.. code-block:: html+jinja
-
- {# app/Resources/views/layout.html.twig #}
-
-
-
- {% block title %}Default title{% endblock %}
-
-
- {% block body %}{% endblock %}
-
-
-
-Twig is well-supported in Symfony2. And while PHP templates will always
-be supported in Symfony2, the many advantages of Twig will continue to
-be discussed. For more information, see the :doc:`templating chapter`.
-
-Learn more from the Cookbook
-----------------------------
-
-* :doc:`/cookbook/templating/PHP`
-* :doc:`/cookbook/controller/service`
-
-.. _`Doctrine`: http://www.doctrine-project.org
-.. _`download Composer`: http://getcomposer.org/download/
-.. _`Routing`: https://github.com/symfony/Routing
-.. _`Templating`: https://github.com/symfony/Templating
-.. _`KnpBundles.com`: http://knpbundles.com/
-.. _`Twig`: http://twig.sensiolabs.org
-.. _`Varnish`: https://www.varnish-cache.org/
-.. _`PHPUnit`: http://www.phpunit.de
diff --git a/book/http_cache.rst b/book/http_cache.rst
deleted file mode 100644
index c14cb9f9f3e..00000000000
--- a/book/http_cache.rst
+++ /dev/null
@@ -1,1084 +0,0 @@
-.. index::
- single: Cache
-
-HTTP Cache
-==========
-
-The nature of rich web applications means that they're dynamic. No matter
-how efficient your application, each request will always contain more overhead
-than serving a static file.
-
-And for most Web applications, that's fine. Symfony2 is lightning fast, and
-unless you're doing some serious heavy-lifting, each request will come back
-quickly without putting too much stress on your server.
-
-But as your site grows, that overhead can become a problem. The processing
-that's normally performed on every request should be done only once. This
-is exactly what caching aims to accomplish.
-
-Caching on the Shoulders of Giants
-----------------------------------
-
-The most effective way to improve performance of an application is to cache
-the full output of a page and then bypass the application entirely on each
-subsequent request. Of course, this isn't always possible for highly dynamic
-websites, or is it? In this chapter, you'll see how the Symfony2 cache
-system works and why this is the best possible approach.
-
-The Symfony2 cache system is different because it relies on the simplicity
-and power of the HTTP cache as defined in the :term:`HTTP specification`.
-Instead of reinventing a caching methodology, Symfony2 embraces the standard
-that defines basic communication on the Web. Once you understand the fundamental
-HTTP validation and expiration caching models, you'll be ready to master
-the Symfony2 cache system.
-
-For the purposes of learning how to cache with Symfony2, the
-subject is covered in four steps:
-
-#. A :ref:`gateway cache `, or reverse proxy, is
- an independent layer that sits in front of your application. The reverse
- proxy caches responses as they're returned from your application and answers
- requests with cached responses before they hit your application. Symfony2
- provides its own reverse proxy, but any reverse proxy can be used.
-
-#. :ref:`HTTP cache ` headers are used
- to communicate with the gateway cache and any other caches between your
- application and the client. Symfony2 provides sensible defaults and a
- powerful interface for interacting with the cache headers.
-
-#. HTTP :ref:`expiration and validation `
- are the two models used for determining whether cached content is *fresh*
- (can be reused from the cache) or *stale* (should be regenerated by the
- application).
-
-#. :ref:`Edge Side Includes ` (ESI) allow HTTP
- cache to be used to cache page fragments (even nested fragments) independently.
- With ESI, you can even cache an entire page for 60 minutes, but an embedded
- sidebar for only 5 minutes.
-
-Since caching with HTTP isn't unique to Symfony, many articles already exist
-on the topic. If you're new to HTTP caching, Ryan
-Tomayko's article `Things Caches Do`_ is *highly* recommended . Another in-depth resource is Mark
-Nottingham's `Cache Tutorial`_.
-
-.. index::
- single: Cache; Proxy
- single: Cache; Reverse proxy
- single: Cache; Gateway
-
-.. _gateway-caches:
-
-Caching with a Gateway Cache
-----------------------------
-
-When caching with HTTP, the *cache* is separated from your application entirely
-and sits between your application and the client making the request.
-
-The job of the cache is to accept requests from the client and pass them
-back to your application. The cache will also receive responses back from
-your application and forward them on to the client. The cache is the "middle-man"
-of the request-response communication between the client and your application.
-
-Along the way, the cache will store each response that is deemed "cacheable"
-(See :ref:`http-cache-introduction`). If the same resource is requested again,
-the cache sends the cached response to the client, ignoring your application
-entirely.
-
-This type of cache is known as a HTTP gateway cache and many exist such
-as `Varnish`_, `Squid in reverse proxy mode`_, and the Symfony2 reverse proxy.
-
-.. index::
- single: Cache; Types of
-
-Types of Caches
-~~~~~~~~~~~~~~~
-
-But a gateway cache isn't the only type of cache. In fact, the HTTP cache
-headers sent by your application are consumed and interpreted by up to three
-different types of caches:
-
-* *Browser caches*: Every browser comes with its own local cache that is
- mainly useful for when you hit "back" or for images and other assets.
- The browser cache is a *private* cache as cached resources aren't shared
- with anyone else;
-
-* *Proxy caches*: A proxy is a *shared* cache as many people can be behind a
- single one. It's usually installed by large corporations and ISPs to reduce
- latency and network traffic;
-
-* *Gateway caches*: Like a proxy, it's also a *shared* cache but on the server
- side. Installed by network administrators, it makes websites more scalable,
- reliable and performant.
-
-.. tip::
-
- Gateway caches are sometimes referred to as reverse proxy caches,
- surrogate caches, or even HTTP accelerators.
-
-.. note::
-
- The significance of *private* versus *shared* caches will become more
- obvious when caching responses containing content that is
- specific to exactly one user (e.g. account information) is discussed.
-
-Each response from your application will likely go through one or both of
-the first two cache types. These caches are outside of your control but follow
-the HTTP cache directions set in the response.
-
-.. index::
- single: Cache; Symfony2 reverse proxy
-
-.. _`symfony-gateway-cache`:
-
-Symfony2 Reverse Proxy
-~~~~~~~~~~~~~~~~~~~~~~
-
-Symfony2 comes with a reverse proxy (also called a gateway cache) written
-in PHP. Enable it and cacheable responses from your application will start
-to be cached right away. Installing it is just as easy. Each new Symfony2
-application comes with a pre-configured caching kernel (``AppCache``) that
-wraps the default one (``AppKernel``). The caching Kernel *is* the reverse
-proxy.
-
-To enable caching, modify the code of a front controller to use the caching
-kernel::
-
- // web/app.php
- require_once __DIR__.'/../app/bootstrap.php.cache';
- require_once __DIR__.'/../app/AppKernel.php';
- require_once __DIR__.'/../app/AppCache.php';
-
- use Symfony\Component\HttpFoundation\Request;
-
- $kernel = new AppKernel('prod', false);
- $kernel->loadClassCache();
- // wrap the default AppKernel with the AppCache one
- $kernel = new AppCache($kernel);
- $request = Request::createFromGlobals();
- $response = $kernel->handle($request);
- $response->send();
- $kernel->terminate($request, $response);
-
-The caching kernel will immediately act as a reverse proxy - caching responses
-from your application and returning them to the client.
-
-.. tip::
-
- The cache kernel has a special ``getLog()`` method that returns a string
- representation of what happened in the cache layer. In the development
- environment, use it to debug and validate your cache strategy::
-
- error_log($kernel->getLog());
-
-The ``AppCache`` object has a sensible default configuration, but it can be
-finely tuned via a set of options you can set by overriding the
-:method:`Symfony\\Bundle\\FrameworkBundle\\HttpCache\\HttpCache::getOptions`
-method::
-
- // app/AppCache.php
- use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache;
-
- class AppCache extends HttpCache
- {
- protected function getOptions()
- {
- return array(
- 'debug' => false,
- 'default_ttl' => 0,
- 'private_headers' => array('Authorization', 'Cookie'),
- 'allow_reload' => false,
- 'allow_revalidate' => false,
- 'stale_while_revalidate' => 2,
- 'stale_if_error' => 60,
- );
- }
- }
-
-.. tip::
-
- Unless overridden in ``getOptions()``, the ``debug`` option will be set
- to automatically be the debug value of the wrapped ``AppKernel``.
-
-Here is a list of the main options:
-
-* ``default_ttl``: The number of seconds that a cache entry should be
- considered fresh when no explicit freshness information is provided in a
- response. Explicit ``Cache-Control`` or ``Expires`` headers override this
- value (default: ``0``);
-
-* ``private_headers``: Set of request headers that trigger "private"
- ``Cache-Control`` behavior on responses that don't explicitly state whether
- the response is ``public`` or ``private`` via a ``Cache-Control`` directive.
- (default: ``Authorization`` and ``Cookie``);
-
-* ``allow_reload``: Specifies whether the client can force a cache reload by
- including a ``Cache-Control`` "no-cache" directive in the request. Set it to
- ``true`` for compliance with RFC 2616 (default: ``false``);
-
-* ``allow_revalidate``: Specifies whether the client can force a cache
- revalidate by including a ``Cache-Control`` "max-age=0" directive in the
- request. Set it to ``true`` for compliance with RFC 2616 (default: false);
-
-* ``stale_while_revalidate``: Specifies the default number of seconds (the
- granularity is the second as the Response TTL precision is a second) during
- which the cache can immediately return a stale response while it revalidates
- it in the background (default: ``2``); this setting is overridden by the
- ``stale-while-revalidate`` HTTP ``Cache-Control`` extension (see RFC 5861);
-
-* ``stale_if_error``: Specifies the default number of seconds (the granularity
- is the second) during which the cache can serve a stale response when an
- error is encountered (default: ``60``). This setting is overridden by the
- ``stale-if-error`` HTTP ``Cache-Control`` extension (see RFC 5861).
-
-If ``debug`` is ``true``, Symfony2 automatically adds a ``X-Symfony-Cache``
-header to the response containing useful information about cache hits and
-misses.
-
-.. sidebar:: Changing from one Reverse Proxy to Another
-
- The Symfony2 reverse proxy is a great tool to use when developing your
- website or when you deploy your website to a shared host where you cannot
- install anything beyond PHP code. But being written in PHP, it cannot
- be as fast as a proxy written in C. That's why it is highly recommended you
- use Varnish or Squid on your production servers if possible. The good
- news is that the switch from one proxy server to another is easy and
- transparent as no code modification is needed in your application. Start
- easy with the Symfony2 reverse proxy and upgrade later to Varnish when
- your traffic increases.
-
- For more information on using Varnish with Symfony2, see the
- :doc:`How to use Varnish ` cookbook chapter.
-
-.. note::
-
- The performance of the Symfony2 reverse proxy is independent of the
- complexity of the application. That's because the application kernel is
- only booted when the request needs to be forwarded to it.
-
-.. index::
- single: Cache; HTTP
-
-.. _http-cache-introduction:
-
-Introduction to HTTP Caching
-----------------------------
-
-To take advantage of the available cache layers, your application must be
-able to communicate which responses are cacheable and the rules that govern
-when/how that cache should become stale. This is done by setting HTTP cache
-headers on the response.
-
-.. tip::
-
- Keep in mind that "HTTP" is nothing more than the language (a simple text
- language) that web clients (e.g. browsers) and web servers use to communicate
- with each other. HTTP caching is the part of that language that allows clients
- and servers to exchange information related to caching.
-
-HTTP specifies four response cache headers that are looked at here:
-
-* ``Cache-Control``
-* ``Expires``
-* ``ETag``
-* ``Last-Modified``
-
-The most important and versatile header is the ``Cache-Control`` header,
-which is actually a collection of various cache information.
-
-.. note::
-
- Each of the headers will be explained in full detail in the
- :ref:`http-expiration-validation` section.
-
-.. index::
- single: Cache; Cache-Control header
- single: HTTP headers; Cache-Control
-
-The Cache-Control Header
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-The ``Cache-Control`` header is unique in that it contains not one, but various
-pieces of information about the cacheability of a response. Each piece of
-information is separated by a comma:
-
- Cache-Control: private, max-age=0, must-revalidate
-
- Cache-Control: max-age=3600, must-revalidate
-
-Symfony provides an abstraction around the ``Cache-Control`` header to make
-its creation more manageable::
-
- // ...
-
- use Symfony\Component\HttpFoundation\Response;
-
- $response = new Response();
-
- // mark the response as either public or private
- $response->setPublic();
- $response->setPrivate();
-
- // set the private or shared max age
- $response->setMaxAge(600);
- $response->setSharedMaxAge(600);
-
- // set a custom Cache-Control directive
- $response->headers->addCacheControlDirective('must-revalidate', true);
-
-Public vs Private Responses
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Both gateway and proxy caches are considered "shared" caches as the cached
-content is shared by more than one user. If a user-specific response were
-ever mistakenly stored by a shared cache, it might be returned later to any
-number of different users. Imagine if your account information were cached
-and then returned to every subsequent user who asked for their account page!
-
-To handle this situation, every response may be set to be public or private:
-
-* *public*: Indicates that the response may be cached by both private and
- shared caches;
-
-* *private*: Indicates that all or part of the response message is intended
- for a single user and must not be cached by a shared cache.
-
-Symfony conservatively defaults each response to be private. To take advantage
-of shared caches (like the Symfony2 reverse proxy), the response will need
-to be explicitly set as public.
-
-.. index::
- single: Cache; Safe methods
-
-Safe Methods
-~~~~~~~~~~~~
-
-HTTP caching only works for "safe" HTTP methods (like GET and HEAD). Being
-safe means that you never change the application's state on the server when
-serving the request (you can of course log information, cache data, etc).
-This has two very reasonable consequences:
-
-* You should *never* change the state of your application when responding
- to a GET or HEAD request. Even if you don't use a gateway cache, the presence
- of proxy caches mean that any GET or HEAD request may or may not actually
- hit your server;
-
-* Don't expect PUT, POST or DELETE methods to cache. These methods are meant
- to be used when mutating the state of your application (e.g. deleting a
- blog post). Caching them would prevent certain requests from hitting and
- mutating your application.
-
-Caching Rules and Defaults
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-HTTP 1.1 allows caching anything by default unless there is an explicit
-``Cache-Control`` header. In practice, most caches do nothing when requests
-have a cookie, an authorization header, use a non-safe method (i.e. PUT, POST,
-DELETE), or when responses have a redirect status code.
-
-Symfony2 automatically sets a sensible and conservative ``Cache-Control``
-header when none is set by the developer by following these rules:
-
-* If no cache header is defined (``Cache-Control``, ``Expires``, ``ETag``
- or ``Last-Modified``), ``Cache-Control`` is set to ``no-cache``, meaning
- that the response will not be cached;
-
-* If ``Cache-Control`` is empty (but one of the other cache headers is present),
- its value is set to ``private, must-revalidate``;
-
-* But if at least one ``Cache-Control`` directive is set, and no 'public' or
- ``private`` directives have been explicitly added, Symfony2 adds the
- ``private`` directive automatically (except when ``s-maxage`` is set).
-
-.. _http-expiration-validation:
-
-HTTP Expiration and Validation
-------------------------------
-
-The HTTP specification defines two caching models:
-
-* With the `expiration model`_, you simply specify how long a response should
- be considered "fresh" by including a ``Cache-Control`` and/or an ``Expires``
- header. Caches that understand expiration will not make the same request
- until the cached version reaches its expiration time and becomes "stale";
-
-* When pages are really dynamic (i.e. their representation changes often),
- the `validation model`_ is often necessary. With this model, the
- cache stores the response, but asks the server on each request whether
- or not the cached response is still valid. The application uses a unique
- response identifier (the ``Etag`` header) and/or a timestamp (the ``Last-Modified``
- header) to check if the page has changed since being cached.
-
-The goal of both models is to never generate the same response twice by relying
-on a cache to store and return "fresh" responses.
-
-.. sidebar:: Reading the HTTP Specification
-
- The HTTP specification defines a simple but powerful language in which
- clients and servers can communicate. As a web developer, the request-response
- model of the specification dominates your work. Unfortunately, the actual
- specification document - `RFC 2616`_ - can be difficult to read.
-
- There is an on-going effort (`HTTP Bis`_) to rewrite the RFC 2616. It does
- not describe a new version of HTTP, but mostly clarifies the original HTTP
- specification. The organization is also improved as the specification
- is split into seven parts; everything related to HTTP caching can be
- found in two dedicated parts (`P4 - Conditional Requests`_ and `P6 -
- Caching: Browser and intermediary caches`_).
-
- As a web developer, you are strongly urged to read the specification. Its
- clarity and power - even more than ten years after its creation - is
- invaluable. Don't be put-off by the appearance of the spec - its contents
- are much more beautiful than its cover.
-
-.. index::
- single: Cache; HTTP expiration
-
-Expiration
-~~~~~~~~~~
-
-The expiration model is the more efficient and straightforward of the two
-caching models and should be used whenever possible. When a response is cached
-with an expiration, the cache will store the response and return it directly
-without hitting the application until it expires.
-
-The expiration model can be accomplished using one of two, nearly identical,
-HTTP headers: ``Expires`` or ``Cache-Control``.
-
-.. index::
- single: Cache; Expires header
- single: HTTP headers; Expires
-
-Expiration with the ``Expires`` Header
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-According to the HTTP specification, "the ``Expires`` header field gives
-the date/time after which the response is considered stale." The ``Expires``
-header can be set with the ``setExpires()`` ``Response`` method. It takes a
-``DateTime`` instance as an argument::
-
- $date = new DateTime();
- $date->modify('+600 seconds');
-
- $response->setExpires($date);
-
-The resulting HTTP header will look like this:
-
-.. code-block:: text
-
- Expires: Thu, 01 Mar 2011 16:00:00 GMT
-
-.. note::
-
- The ``setExpires()`` method automatically converts the date to the GMT
- timezone as required by the specification.
-
-Note that in HTTP versions before 1.1 the origin server wasn't required to
-send the ``Date`` header. Consequently the cache (e.g. the browser) might
-need to rely onto his local clock to evaluate the ``Expires`` header making
-the lifetime calculation vulnerable to clock skew. Another limitation
-of the ``Expires`` header is that the specification states that "HTTP/1.1
-servers should not send ``Expires`` dates more than one year in the future."
-
-.. index::
- single: Cache; Cache-Control header
- single: HTTP headers; Cache-Control
-
-Expiration with the ``Cache-Control`` Header
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Because of the ``Expires`` header limitations, most of the time, you should
-use the ``Cache-Control`` header instead. Recall that the ``Cache-Control``
-header is used to specify many different cache directives. For expiration,
-there are two directives, ``max-age`` and ``s-maxage``. The first one is
-used by all caches, whereas the second one is only taken into account by
-shared caches::
-
- // Sets the number of seconds after which the response
- // should no longer be considered fresh
- $response->setMaxAge(600);
-
- // Same as above but only for shared caches
- $response->setSharedMaxAge(600);
-
-The ``Cache-Control`` header would take on the following format (it may have
-additional directives):
-
-.. code-block:: text
-
- Cache-Control: max-age=600, s-maxage=600
-
-.. index::
- single: Cache; Validation
-
-Validation
-~~~~~~~~~~
-
-When a resource needs to be updated as soon as a change is made to the underlying
-data, the expiration model falls short. With the expiration model, the application
-won't be asked to return the updated response until the cache finally becomes
-stale.
-
-The validation model addresses this issue. Under this model, the cache continues
-to store responses. The difference is that, for each request, the cache asks
-the application whether or not the cached response is still valid. If the
-cache *is* still valid, your application should return a 304 status code
-and no content. This tells the cache that it's ok to return the cached response.
-
-Under this model, you mainly save bandwidth as the representation is not
-sent twice to the same client (a 304 response is sent instead). But if you
-design your application carefully, you might be able to get the bare minimum
-data needed to send a 304 response and save CPU also (see below for an implementation
-example).
-
-.. tip::
-
- The 304 status code means "Not Modified". It's important because with
- this status code do *not* contain the actual content being requested.
- Instead, the response is simply a light-weight set of directions that
- tell cache that it should use its stored version.
-
-Like with expiration, there are two different HTTP headers that can be used
-to implement the validation model: ``ETag`` and ``Last-Modified``.
-
-.. index::
- single: Cache; Etag header
- single: HTTP headers; Etag
-
-Validation with the ``ETag`` Header
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The ``ETag`` header is a string header (called the "entity-tag") that uniquely
-identifies one representation of the target resource. It's entirely generated
-and set by your application so that you can tell, for example, if the ``/about``
-resource that's stored by the cache is up-to-date with what your application
-would return. An ``ETag`` is like a fingerprint and is used to quickly compare
-if two different versions of a resource are equivalent. Like fingerprints,
-each ``ETag`` must be unique across all representations of the same resource.
-
-To see a simple implementation, generate the ETag as the md5 of the content::
-
- public function indexAction()
- {
- $response = $this->render('MyBundle:Main:index.html.twig');
- $response->setETag(md5($response->getContent()));
- $response->setPublic(); // make sure the response is public/cacheable
- $response->isNotModified($this->getRequest());
-
- return $response;
- }
-
-The :method:`Symfony\\Component\\HttpFoundation\\Response::isNotModified`
-method compares the ``ETag`` sent with the ``Request`` with the one set
-on the ``Response``. If the two match, the method automatically sets the
-``Response`` status code to 304.
-
-This algorithm is simple enough and very generic, but you need to create the
-whole ``Response`` before being able to compute the ETag, which is sub-optimal.
-In other words, it saves on bandwidth, but not CPU cycles.
-
-In the :ref:`optimizing-cache-validation` section, you'll see how validation
-can be used more intelligently to determine the validity of a cache without
-doing so much work.
-
-.. tip::
-
- Symfony2 also supports weak ETags by passing ``true`` as the second
- argument to the
- :method:`Symfony\\Component\\HttpFoundation\\Response::setETag` method.
-
-.. index::
- single: Cache; Last-Modified header
- single: HTTP headers; Last-Modified
-
-Validation with the ``Last-Modified`` Header
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The ``Last-Modified`` header is the second form of validation. According
-to the HTTP specification, "The ``Last-Modified`` header field indicates
-the date and time at which the origin server believes the representation
-was last modified." In other words, the application decides whether or not
-the cached content has been updated based on whether or not it's been updated
-since the response was cached.
-
-For instance, you can use the latest update date for all the objects needed to
-compute the resource representation as the value for the ``Last-Modified``
-header value::
-
- public function showAction($articleSlug)
- {
- // ...
-
- $articleDate = new \DateTime($article->getUpdatedAt());
- $authorDate = new \DateTime($author->getUpdatedAt());
-
- $date = $authorDate > $articleDate ? $authorDate : $articleDate;
-
- $response->setLastModified($date);
- // Set response as public. Otherwise it will be private by default.
- $response->setPublic();
-
- if ($response->isNotModified($this->getRequest())) {
- return $response;
- }
-
- // ... do more work to populate the response with the full content
-
- return $response;
- }
-
-The :method:`Symfony\\Component\\HttpFoundation\\Response::isNotModified`
-method compares the ``If-Modified-Since`` header sent by the request with
-the ``Last-Modified`` header set on the response. If they are equivalent,
-the ``Response`` will be set to a 304 status code.
-
-.. note::
-
- The ``If-Modified-Since`` request header equals the ``Last-Modified``
- header of the last response sent to the client for the particular resource.
- This is how the client and server communicate with each other and decide
- whether or not the resource has been updated since it was cached.
-
-.. index::
- single: Cache; Conditional get
- single: HTTP; 304
-
-.. _optimizing-cache-validation:
-
-Optimizing your Code with Validation
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The main goal of any caching strategy is to lighten the load on the application.
-Put another way, the less you do in your application to return a 304 response,
-the better. The ``Response::isNotModified()`` method does exactly that by
-exposing a simple and efficient pattern::
-
- use Symfony\Component\HttpFoundation\Response;
-
- public function showAction($articleSlug)
- {
- // Get the minimum information to compute
- // the ETag or the Last-Modified value
- // (based on the Request, data is retrieved from
- // a database or a key-value store for instance)
- $article = ...;
-
- // create a Response with a ETag and/or a Last-Modified header
- $response = new Response();
- $response->setETag($article->computeETag());
- $response->setLastModified($article->getPublishedAt());
-
- // Set response as public. Otherwise it will be private by default.
- $response->setPublic();
-
- // Check that the Response is not modified for the given Request
- if ($response->isNotModified($this->getRequest())) {
- // return the 304 Response immediately
- return $response;
- } else {
- // do more work here - like retrieving more data
- $comments = ...;
-
- // or render a template with the $response you've already started
- return $this->render(
- 'MyBundle:MyController:article.html.twig',
- array('article' => $article, 'comments' => $comments),
- $response
- );
- }
- }
-
-When the ``Response`` is not modified, the ``isNotModified()`` automatically sets
-the response status code to ``304``, removes the content, and removes some
-headers that must not be present for ``304`` responses (see
-:method:`Symfony\\Component\\HttpFoundation\\Response::setNotModified`).
-
-.. index::
- single: Cache; Vary
- single: HTTP headers; Vary
-
-Varying the Response
-~~~~~~~~~~~~~~~~~~~~
-
-So far, it's been assumed that each URI has exactly one representation of the
-target resource. By default, HTTP caching is done by using the URI of the
-resource as the cache key. If two people request the same URI of a cacheable
-resource, the second person will receive the cached version.
-
-Sometimes this isn't enough and different versions of the same URI need to
-be cached based on one or more request header values. For instance, if you
-compress pages when the client supports it, any given URI has two representations:
-one when the client supports compression, and one when it does not. This
-determination is done by the value of the ``Accept-Encoding`` request header.
-
-In this case, you need the cache to store both a compressed and uncompressed
-version of the response for the particular URI and return them based on the
-request's ``Accept-Encoding`` value. This is done by using the ``Vary`` response
-header, which is a comma-separated list of different headers whose values
-trigger a different representation of the requested resource:
-
-.. code-block:: text
-
- Vary: Accept-Encoding, User-Agent
-
-.. tip::
-
- This particular ``Vary`` header would cache different versions of each
- resource based on the URI and the value of the ``Accept-Encoding`` and
- ``User-Agent`` request header.
-
-The ``Response`` object offers a clean interface for managing the ``Vary``
-header::
-
- // set one vary header
- $response->setVary('Accept-Encoding');
-
- // set multiple vary headers
- $response->setVary(array('Accept-Encoding', 'User-Agent'));
-
-The ``setVary()`` method takes a header name or an array of header names for
-which the response varies.
-
-Expiration and Validation
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You can of course use both validation and expiration within the same ``Response``.
-As expiration wins over validation, you can easily benefit from the best of
-both worlds. In other words, by using both expiration and validation, you
-can instruct the cache to serve the cached content, while checking back
-at some interval (the expiration) to verify that the content is still valid.
-
-.. index::
- pair: Cache; Configuration
-
-More Response Methods
-~~~~~~~~~~~~~~~~~~~~~
-
-The Response class provides many more methods related to the cache. Here are
-the most useful ones::
-
- // Marks the Response stale
- $response->expire();
-
- // Force the response to return a proper 304 response with no content
- $response->setNotModified();
-
-Additionally, most cache-related HTTP headers can be set via the single
-:method:`Symfony\\Component\\HttpFoundation\\Response::setCache` method::
-
- // Set cache settings in one call
- $response->setCache(array(
- 'etag' => $etag,
- 'last_modified' => $date,
- 'max_age' => 10,
- 's_maxage' => 10,
- 'public' => true,
- // 'private' => true,
- ));
-
-.. index::
- single: Cache; ESI
- single: ESI
-
-.. _edge-side-includes:
-
-Using Edge Side Includes
-------------------------
-
-Gateway caches are a great way to make your website perform better. But they
-have one limitation: they can only cache whole pages. If you can't cache
-whole pages or if parts of a page has "more" dynamic parts, you are out of
-luck. Fortunately, Symfony2 provides a solution for these cases, based on a
-technology called `ESI`_, or Edge Side Includes. Akamaï wrote this specification
-almost 10 years ago, and it allows specific parts of a page to have a different
-caching strategy than the main page.
-
-The ESI specification describes tags you can embed in your pages to communicate
-with the gateway cache. Only one tag is implemented in Symfony2, ``include``,
-as this is the only useful one outside of Akamaï context:
-
-.. code-block:: html
-
-
-
-
-
-
-
-
-
-
-
-
-
-.. note::
-
- Notice from the example that each ESI tag has a fully-qualified URL.
- An ESI tag represents a page fragment that can be fetched via the given
- URL.
-
-When a request is handled, the gateway cache fetches the entire page from
-its cache or requests it from the backend application. If the response contains
-one or more ESI tags, these are processed in the same way. In other words,
-the gateway cache either retrieves the included page fragment from its cache
-or requests the page fragment from the backend application again. When all
-the ESI tags have been resolved, the gateway cache merges each into the main
-page and sends the final content to the client.
-
-All of this happens transparently at the gateway cache level (i.e. outside
-of your application). As you'll see, if you choose to take advantage of ESI
-tags, Symfony2 makes the process of including them almost effortless.
-
-Using ESI in Symfony2
-~~~~~~~~~~~~~~~~~~~~~
-
-First, to use ESI, be sure to enable it in your application configuration:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- framework:
- # ...
- esi: { enabled: true }
-
- .. code-block:: xml
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- $container->loadFromExtension('framework', array(
- // ...
- 'esi' => array('enabled' => true),
- ));
-
-Now, suppose you have a page that is relatively static, except for a news
-ticker at the bottom of the content. With ESI, you can cache the news ticker
-independent of the rest of the page.
-
-.. code-block:: php
-
- public function indexAction()
- {
- $response = $this->render('MyBundle:MyController:index.html.twig');
- // set the shared max age - which also marks the response as public
- $response->setSharedMaxAge(600);
-
- return $response;
- }
-
-In this example, the full-page cache has a lifetime of ten minutes.
-Next, include the news ticker in the template by embedding an action.
-This is done via the ``render`` helper (See :ref:`templating-embedding-controller`
-for more details).
-
-As the embedded content comes from another page (or controller for that
-matter), Symfony2 uses the standard ``render`` helper to configure ESI tags:
-
-.. configuration-block::
-
- .. code-block:: jinja
-
- {# you can use a controller reference #}
- {{ render_esi(controller('...:news', { 'max': 5 })) }}
-
- {# ... or a URL #}
- {{ render_esi(url('latest_news', { 'max': 5 })) }}
-
- .. code-block:: html+php
-
- render(
- new ControllerReference('...:news', array('max' => 5)),
- array('renderer' => 'esi'))
- ?>
-
- render(
- $view['router']->generate('latest_news', array('max' => 5), true),
- array('renderer' => 'esi'),
- ) ?>
-
-By using the ``esi`` renderer (via the ``render_esi`` Twig function), you
-tell Symfony2 that the action should be rendered as an ESI tag. You might be
-wondering why you would want to use a helper instead of just writing the ESI
-tag yourself. That's because using a helper makes your application work even
-if there is no gateway cache installed.
-
-When using the default ``render`` function (or setting the renderer to
-``inline``), Symfony2 merges the included page content into the main one
-before sending the response to the client. But if you use the ``esi`` renderer
-(i.e. call ``render_esi``), *and* if Symfony2 detects that it's talking to a
-gateway cache that supports ESI, it generates an ESI include tag. But if there
-is no gateway cache or if it does not support ESI, Symfony2 will just merge
-the included page content within the main one as it would have done if you had
-used ``render``.
-
-.. note::
-
- Symfony2 detects if a gateway cache supports ESI via another Akamaï
- specification that is supported out of the box by the Symfony2 reverse
- proxy.
-
-The embedded action can now specify its own caching rules, entirely independent
-of the master page.
-
-.. code-block:: php
-
- public function newsAction($max)
- {
- // ...
-
- $response->setSharedMaxAge(60);
- }
-
-With ESI, the full page cache will be valid for 600 seconds, but the news
-component cache will only last for 60 seconds.
-
-When using a controller reference, the ESI tag should reference the embedded
-action as an accessible URL so the gateway cache can fetch it independently of
-the rest of the page. Symfony2 takes care of generating a unique URL for any
-controller reference and it is able to route them properly thanks to a
-listener that must be enabled in your configuration:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- framework:
- # ...
- fragments: { path: /_fragment }
-
- .. code-block:: xml
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- $container->loadFromExtension('framework', array(
- // ...
- 'fragments' => array('path' => '/_fragment'),
- ));
-
-One great advantage of the ESI renderer is that you can make your application
-as dynamic as needed and at the same time, hit the application as little as
-possible.
-
-.. tip::
-
- The listener listener only responds to local IP addresses or trusted
- proxies.
-
-.. note::
-
- Once you start using ESI, remember to always use the ``s-maxage``
- directive instead of ``max-age``. As the browser only ever receives the
- aggregated resource, it is not aware of the sub-components, and so it will
- obey the ``max-age`` directive and cache the entire page. And you don't
- want that.
-
-The ``render_esi`` helper supports two other useful options:
-
-* ``alt``: used as the ``alt`` attribute on the ESI tag, which allows you
- to specify an alternative URL to be used if the ``src`` cannot be found;
-
-* ``ignore_errors``: if set to true, an ``onerror`` attribute will be added
- to the ESI with a value of ``continue`` indicating that, in the event of
- a failure, the gateway cache will simply remove the ESI tag silently.
-
-.. index::
- single: Cache; Invalidation
-
-.. _http-cache-invalidation:
-
-Cache Invalidation
-------------------
-
- "There are only two hard things in Computer Science: cache invalidation
- and naming things." --Phil Karlton
-
-You should never need to invalidate cached data because invalidation is already
-taken into account natively in the HTTP cache models. If you use validation,
-you never need to invalidate anything by definition; and if you use expiration
-and need to invalidate a resource, it means that you set the expires date
-too far away in the future.
-
-.. note::
-
- Since invalidation is a topic specific to each type of reverse proxy,
- if you don't worry about invalidation, you can switch between reverse
- proxies without changing anything in your application code.
-
-Actually, all reverse proxies provide ways to purge cached data, but you
-should avoid them as much as possible. The most standard way is to purge the
-cache for a given URL by requesting it with the special ``PURGE`` HTTP method.
-
-Here is how you can configure the Symfony2 reverse proxy to support the
-``PURGE`` HTTP method::
-
- // app/AppCache.php
-
- // ...
- use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache;
- use Symfony\Component\HttpFoundation\Request;
- use Symfony\Component\HttpFoundation\Response;
-
- class AppCache extends HttpCache
- {
- protected function invalidate(Request $request, $catch = false)
- {
- if ('PURGE' !== $request->getMethod()) {
- return parent::invalidate($request, $catch);
- }
-
- $response = new Response();
- if (!$this->getStore()->purge($request->getUri())) {
- $response->setStatusCode(404, 'Not purged');
- } else {
- $response->setStatusCode(200, 'Purged');
- }
-
- return $response;
- }
- }
-
-.. caution::
-
- You must protect the ``PURGE`` HTTP method somehow to avoid random people
- purging your cached data.
-
-Summary
--------
-
-Symfony2 was designed to follow the proven rules of the road: HTTP. Caching
-is no exception. Mastering the Symfony2 cache system means becoming familiar
-with the HTTP cache models and using them effectively. This means that, instead
-of relying only on Symfony2 documentation and code examples, you have access
-to a world of knowledge related to HTTP caching and gateway caches such as
-Varnish.
-
-Learn more from the Cookbook
-----------------------------
-
-* :doc:`/cookbook/cache/varnish`
-
-.. _`Things Caches Do`: http://tomayko.com/writings/things-caches-do
-.. _`Cache Tutorial`: http://www.mnot.net/cache_docs/
-.. _`Varnish`: https://www.varnish-cache.org/
-.. _`Squid in reverse proxy mode`: http://wiki.squid-cache.org/SquidFaq/ReverseProxy
-.. _`expiration model`: http://tools.ietf.org/html/rfc2616#section-13.2
-.. _`validation model`: http://tools.ietf.org/html/rfc2616#section-13.3
-.. _`RFC 2616`: http://tools.ietf.org/html/rfc2616
-.. _`HTTP Bis`: http://tools.ietf.org/wg/httpbis/
-.. _`P4 - Conditional Requests`: http://tools.ietf.org/html/draft-ietf-httpbis-p4-conditional-12
-.. _`P6 - Caching: Browser and intermediary caches`: http://tools.ietf.org/html/draft-ietf-httpbis-p6-cache-12
-.. _`ESI`: http://www.w3.org/TR/esi-lang
diff --git a/book/http_fundamentals.rst b/book/http_fundamentals.rst
deleted file mode 100644
index 16520777863..00000000000
--- a/book/http_fundamentals.rst
+++ /dev/null
@@ -1,572 +0,0 @@
-.. index::
- single: Symfony2 Fundamentals
-
-Symfony2 and HTTP Fundamentals
-==============================
-
-Congratulations! By learning about Symfony2, you're well on your way towards
-being a more *productive*, *well-rounded* and *popular* web developer (actually,
-you're on your own for the last part). Symfony2 is built to get back to
-basics: to develop tools that let you develop faster and build more robust
-applications, while staying out of your way. Symfony is built on the best
-ideas from many technologies: the tools and concepts you're about to learn
-represent the efforts of thousands of people, over many years. In other words,
-you're not just learning "Symfony", you're learning the fundamentals of the
-web, development best practices, and how to use many amazing new PHP libraries,
-inside or independently of Symfony2. So, get ready.
-
-True to the Symfony2 philosophy, this chapter begins by explaining the fundamental
-concept common to web development: HTTP. Regardless of your background or
-preferred programming language, this chapter is a **must-read** for everyone.
-
-HTTP is Simple
---------------
-
-HTTP (Hypertext Transfer Protocol to the geeks) is a text language that allows
-two machines to communicate with each other. That's it! For example, when
-checking for the latest `xkcd`_ comic, the following (approximate) conversation
-takes place:
-
-.. image:: /images/http-xkcd.png
- :align: center
-
-And while the actual language used is a bit more formal, it's still dead-simple.
-HTTP is the term used to describe this simple text-based language. And no
-matter how you develop on the web, the goal of your server is *always* to
-understand simple text requests, and return simple text responses.
-
-Symfony2 is built from the ground-up around that reality. Whether you realize
-it or not, HTTP is something you use everyday. With Symfony2, you'll learn
-how to master it.
-
-.. index::
- single: HTTP; Request-response paradigm
-
-Step1: The Client sends a Request
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Every conversation on the web starts with a *request*. The request is a text
-message created by a client (e.g. a browser, an iPhone app, etc) in a
-special format known as HTTP. The client sends that request to a server,
-and then waits for the response.
-
-Take a look at the first part of the interaction (the request) between a
-browser and the xkcd web server:
-
-.. image:: /images/http-xkcd-request.png
- :align: center
-
-In HTTP-speak, this HTTP request would actually look something like this:
-
-.. code-block:: text
-
- GET / HTTP/1.1
- Host: xkcd.com
- Accept: text/html
- User-Agent: Mozilla/5.0 (Macintosh)
-
-This simple message communicates *everything* necessary about exactly which
-resource the client is requesting. The first line of an HTTP request is the
-most important and contains two things: the URI and the HTTP method.
-
-The URI (e.g. ``/``, ``/contact``, etc) is the unique address or location
-that identifies the resource the client wants. The HTTP method (e.g. ``GET``)
-defines what you want to *do* with the resource. The HTTP methods are the
-*verbs* of the request and define the few common ways that you can act upon
-the resource:
-
-+----------+---------------------------------------+
-| *GET* | Retrieve the resource from the server |
-+----------+---------------------------------------+
-| *POST* | Create a resource on the server |
-+----------+---------------------------------------+
-| *PUT* | Update the resource on the server |
-+----------+---------------------------------------+
-| *DELETE* | Delete the resource from the server |
-+----------+---------------------------------------+
-
-With this in mind, you can imagine what an HTTP request might look like to
-delete a specific blog entry, for example:
-
-.. code-block:: text
-
- DELETE /blog/15 HTTP/1.1
-
-.. note::
-
- There are actually nine HTTP methods defined by the HTTP specification,
- but many of them are not widely used or supported. In reality, many modern
- browsers don't support the ``PUT`` and ``DELETE`` methods.
-
-In addition to the first line, an HTTP request invariably contains other
-lines of information called request headers. The headers can supply a wide
-range of information such as the requested ``Host``, the response formats
-the client accepts (``Accept``) and the application the client is using to
-make the request (``User-Agent``). Many other headers exist and can be found
-on Wikipedia's `List of HTTP header fields`_ article.
-
-Step 2: The Server returns a Response
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Once a server has received the request, it knows exactly which resource the
-client needs (via the URI) and what the client wants to do with that resource
-(via the method). For example, in the case of a GET request, the server
-prepares the resource and returns it in an HTTP response. Consider the response
-from the xkcd web server:
-
-.. image:: /images/http-xkcd.png
- :align: center
-
-Translated into HTTP, the response sent back to the browser will look something
-like this:
-
-.. code-block:: text
-
- HTTP/1.1 200 OK
- Date: Sat, 02 Apr 2011 21:05:05 GMT
- Server: lighttpd/1.4.19
- Content-Type: text/html
-
-
-
-
-
-The HTTP response contains the requested resource (the HTML content in this
-case), as well as other information about the response. The first line is
-especially important and contains the HTTP response status code (200 in this
-case). The status code communicates the overall outcome of the request back
-to the client. Was the request successful? Was there an error? Different
-status codes exist that indicate success, an error, or that the client needs
-to do something (e.g. redirect to another page). A full list can be found
-on Wikipedia's `List of HTTP status codes`_ article.
-
-Like the request, an HTTP response contains additional pieces of information
-known as HTTP headers. For example, one important HTTP response header is
-``Content-Type``. The body of the same resource could be returned in multiple
-different formats like HTML, XML, or JSON and the ``Content-Type`` header uses
-Internet Media Types like ``text/html`` to tell the client which format is
-being returned. A list of common media types can be found on Wikipedia's
-`List of common media types`_ article.
-
-Many other headers exist, some of which are very powerful. For example, certain
-headers can be used to create a powerful caching system.
-
-Requests, Responses and Web Development
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-This request-response conversation is the fundamental process that drives all
-communication on the web. And as important and powerful as this process is,
-it's inescapably simple.
-
-The most important fact is this: regardless of the language you use, the
-type of application you build (web, mobile, JSON API), or the development
-philosophy you follow, the end goal of an application is **always** to understand
-each request and create and return the appropriate response.
-
-Symfony is architected to match this reality.
-
-.. tip::
-
- To learn more about the HTTP specification, read the original `HTTP 1.1 RFC`_
- or the `HTTP Bis`_, which is an active effort to clarify the original
- specification. A great tool to check both the request and response headers
- while browsing is the `Live HTTP Headers`_ extension for Firefox.
-
-.. index::
- single: Symfony2 Fundamentals; Requests and responses
-
-Requests and Responses in PHP
------------------------------
-
-So how do you interact with the "request" and create a "response" when using
-PHP? In reality, PHP abstracts you a bit from the whole process::
-
- $uri = $_SERVER['REQUEST_URI'];
- $foo = $_GET['foo'];
-
- header('Content-type: text/html');
- echo 'The URI requested is: '.$uri;
- echo 'The value of the "foo" parameter is: '.$foo;
-
-As strange as it sounds, this small application is in fact taking information
-from the HTTP request and using it to create an HTTP response. Instead of
-parsing the raw HTTP request message, PHP prepares superglobal variables
-such as ``$_SERVER`` and ``$_GET`` that contain all the information from
-the request. Similarly, instead of returning the HTTP-formatted text response,
-you can use the ``header()`` function to create response headers and simply
-print out the actual content that will be the content portion of the response
-message. PHP will create a true HTTP response and return it to the client:
-
-.. code-block:: text
-
- HTTP/1.1 200 OK
- Date: Sat, 03 Apr 2011 02:14:33 GMT
- Server: Apache/2.2.17 (Unix)
- Content-Type: text/html
-
- The URI requested is: /testing?foo=symfony
- The value of the "foo" parameter is: symfony
-
-Requests and Responses in Symfony
----------------------------------
-
-Symfony provides an alternative to the raw PHP approach via two classes that
-allow you to interact with the HTTP request and response in an easier way.
-The :class:`Symfony\\Component\\HttpFoundation\\Request` class is a simple
-object-oriented representation of the HTTP request message. With it, you
-have all the request information at your fingertips::
-
- use Symfony\Component\HttpFoundation\Request;
-
- $request = Request::createFromGlobals();
-
- // the URI being requested (e.g. /about) minus any query parameters
- $request->getPathInfo();
-
- // retrieve GET and POST variables respectively
- $request->query->get('foo');
- $request->request->get('bar', 'default value if bar does not exist');
-
- // retrieve SERVER variables
- $request->server->get('HTTP_HOST');
-
- // retrieves an instance of UploadedFile identified by foo
- $request->files->get('foo');
-
- // retrieve a COOKIE value
- $request->cookies->get('PHPSESSID');
-
- // retrieve an HTTP request header, with normalized, lowercase keys
- $request->headers->get('host');
- $request->headers->get('content_type');
-
- $request->getMethod(); // GET, POST, PUT, DELETE, HEAD
- $request->getLanguages(); // an array of languages the client accepts
-
-As a bonus, the ``Request`` class does a lot of work in the background that
-you'll never need to worry about. For example, the ``isSecure()`` method
-checks the *three* different values in PHP that can indicate whether or not
-the user is connecting via a secured connection (i.e. ``https``).
-
-.. sidebar:: ParameterBags and Request attributes
-
- As seen above, the ``$_GET`` and ``$_POST`` variables are accessible via
- the public ``query`` and ``request`` properties respectively. Each of
- these objects is a :class:`Symfony\\Component\\HttpFoundation\\ParameterBag`
- object, which has methods like
- :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::get`,
- :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::has`,
- :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::all` and more.
- In fact, every public property used in the previous example is some instance
- of the ParameterBag.
-
- .. _book-fundamentals-attributes:
-
- The Request class also has a public ``attributes`` property, which holds
- special data related to how the application works internally. For the
- Symfony2 framework, the ``attributes`` holds the values returned by the
- matched route, like ``_controller``, ``id`` (if you have an ``{id}``
- wildcard), and even the name of the matched route (``_route``). The
- ``attributes`` property exists entirely to be a place where you can
- prepare and store context-specific information about the request.
-
-
-Symfony also provides a ``Response`` class: a simple PHP representation of
-an HTTP response message. This allows your application to use an object-oriented
-interface to construct the response that needs to be returned to the client::
-
- use Symfony\Component\HttpFoundation\Response;
- $response = new Response();
-
- $response->setContent('
Hello world!
');
- $response->setStatusCode(200);
- $response->headers->set('Content-Type', 'text/html');
-
- // prints the HTTP headers followed by the content
- $response->send();
-
-If Symfony offered nothing else, you would already have a toolkit for easily
-accessing request information and an object-oriented interface for creating
-the response. Even as you learn the many powerful features in Symfony, keep
-in mind that the goal of your application is always *to interpret a request
-and create the appropriate response based on your application logic*.
-
-.. tip::
-
- The ``Request`` and ``Response`` classes are part of a standalone component
- included with Symfony called ``HttpFoundation``. This component can be
- used entirely independently of Symfony and also provides classes for handling
- sessions and file uploads.
-
-The Journey from the Request to the Response
---------------------------------------------
-
-Like HTTP itself, the ``Request`` and ``Response`` objects are pretty simple.
-The hard part of building an application is writing what comes in between.
-In other words, the real work comes in writing the code that interprets the
-request information and creates the response.
-
-Your application probably does many things, like sending emails, handling
-form submissions, saving things to a database, rendering HTML pages and protecting
-content with security. How can you manage all of this and still keep your
-code organized and maintainable?
-
-Symfony was created to solve these problems so that you don't have to.
-
-The Front Controller
-~~~~~~~~~~~~~~~~~~~~
-
-Traditionally, applications were built so that each "page" of a site was
-its own physical file:
-
-.. code-block:: text
-
- index.php
- contact.php
- blog.php
-
-There are several problems with this approach, including the inflexibility
-of the URLs (what if you wanted to change ``blog.php`` to ``news.php`` without
-breaking all of your links?) and the fact that each file *must* manually
-include some set of core files so that security, database connections and
-the "look" of the site can remain consistent.
-
-A much better solution is to use a :term:`front controller`: a single PHP
-file that handles every request coming into your application. For example:
-
-+------------------------+------------------------+
-| ``/index.php`` | executes ``index.php`` |
-+------------------------+------------------------+
-| ``/index.php/contact`` | executes ``index.php`` |
-+------------------------+------------------------+
-| ``/index.php/blog`` | executes ``index.php`` |
-+------------------------+------------------------+
-
-.. tip::
-
- Using Apache's ``mod_rewrite`` (or equivalent with other web servers),
- the URLs can easily be cleaned up to be just ``/``, ``/contact`` and
- ``/blog``.
-
-Now, every request is handled exactly the same way. Instead of individual URLs
-executing different PHP files, the front controller is *always* executed,
-and the routing of different URLs to different parts of your application
-is done internally. This solves both problems with the original approach.
-Almost all modern web apps do this - including apps like WordPress.
-
-Stay Organized
-~~~~~~~~~~~~~~
-
-Inside your front controller, you have to figure out which code should be
-executed and what the content to return should be. To figure this out, you'll
-need to check the incoming URI and execute different parts of your code depending
-on that value. This can get ugly quickly::
-
- // index.php
- use Symfony\Component\HttpFoundation\Request;
- use Symfony\Component\HttpFoundation\Response;
- $request = Request::createFromGlobals();
- $path = $request->getPathInfo(); // the URI path being requested
-
- if (in_array($path, array('', '/'))) {
- $response = new Response('Welcome to the homepage.');
- } elseif ($path == '/contact') {
- $response = new Response('Contact us');
- } else {
- $response = new Response('Page not found.', 404);
- }
- $response->send();
-
-Solving this problem can be difficult. Fortunately it's *exactly* what Symfony
-is designed to do.
-
-The Symfony Application Flow
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-When you let Symfony handle each request, life is much easier. Symfony follows
-the same simple pattern for every request:
-
-.. _request-flow-figure:
-
-.. figure:: /images/request-flow.png
- :align: center
- :alt: Symfony2 request flow
-
- Incoming requests are interpreted by the routing and passed to controller
- functions that return ``Response`` objects.
-
-Each "page" of your site is defined in a routing configuration file that
-maps different URLs to different PHP functions. The job of each PHP function,
-called a :term:`controller`, is to use information from the request - along
-with many other tools Symfony makes available - to create and return a ``Response``
-object. In other words, the controller is where *your* code goes: it's where
-you interpret the request and create a response.
-
-It's that easy! To review:
-
-* Each request executes a front controller file;
-
-* The routing system determines which PHP function should be executed based
- on information from the request and routing configuration you've created;
-
-* The correct PHP function is executed, where your code creates and returns
- the appropriate ``Response`` object.
-
-A Symfony Request in Action
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Without diving into too much detail, here is this process in action. Suppose
-you want to add a ``/contact`` page to your Symfony application. First, start
-by adding an entry for ``/contact`` to your routing configuration file:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/routing.yml
- contact:
- path: /contact
- defaults: { _controller: AcmeDemoBundle:Main:contact }
-
- .. code-block:: xml
-
-
- AcmeBlogBundle:Main:contact
-
-
- .. code-block:: php
-
- // app/config/routing.php
- use Symfony\Component\Routing\RouteCollection;
- use Symfony\Component\Routing\Route;
-
- $collection = new RouteCollection();
- $collection->add('contact', new Route('/contact', array(
- '_controller' => 'AcmeBlogBundle:Main:contact',
- )));
-
- return $collection;
-
-.. note::
-
- This example uses :doc:`YAML` to define the routing
- configuration. Routing configuration can also be written in other formats
- such as XML or PHP.
-
-When someone visits the ``/contact`` page, this route is matched, and the
-specified controller is executed. As you'll learn in the :doc:`routing chapter`,
-the ``AcmeDemoBundle:Main:contact`` string is a short syntax that points to a
-specific PHP method ``contactAction`` inside a class called ``MainController``::
-
- // src/Acme/DemoBundle/Controller/MainController.php
- use Symfony\Component\HttpFoundation\Response;
-
- class MainController
- {
- public function contactAction()
- {
- return new Response('
Contact us!
');
- }
- }
-
-In this very simple example, the controller simply creates a
-:class:`Symfony\\Component\\HttpFoundation\\Response` object with the HTML
-"``
Contact us!
"``. In the :doc:`controller chapter`,
-you'll learn how a controller can render templates, allowing your "presentation"
-code (i.e. anything that actually writes out HTML) to live in a separate
-template file. This frees up the controller to worry only about the hard
-stuff: interacting with the database, handling submitted data, or sending
-email messages.
-
-Symfony2: Build your App, not your Tools.
------------------------------------------
-
-You now know that the goal of any app is to interpret each incoming request
-and create an appropriate response. As an application grows, it becomes more
-difficult to keep your code organized and maintainable. Invariably, the same
-complex tasks keep coming up over and over again: persisting things to the
-database, rendering and reusing templates, handling form submissions, sending
-emails, validating user input and handling security.
-
-The good news is that none of these problems is unique. Symfony provides
-a framework full of tools that allow you to build your application, not your
-tools. With Symfony2, nothing is imposed on you: you're free to use the full
-Symfony framework, or just one piece of Symfony all by itself.
-
-.. index::
- single: Symfony2 Components
-
-Standalone Tools: The Symfony2 *Components*
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-So what *is* Symfony2? First, Symfony2 is a collection of over twenty independent
-libraries that can be used inside *any* PHP project. These libraries, called
-the *Symfony2 Components*, contain something useful for almost any situation,
-regardless of how your project is developed. To name a few:
-
-* :doc:`HttpFoundation` - Contains
- the ``Request`` and ``Response`` classes, as well as other classes for handling
- sessions and file uploads;
-
-* :doc:`Routing` - Powerful and fast routing system that
- allows you to map a specific URI (e.g. ``/contact``) to some information
- about how that request should be handled (e.g. execute the ``contactAction()``
- method);
-
-* `Form`_ - A full-featured and flexible framework for creating forms and
- handling form submissions;
-
-* `Validator`_ A system for creating rules about data and then validating
- whether or not user-submitted data follows those rules;
-
-* :doc:`ClassLoader` An autoloading library that allows
- PHP classes to be used without needing to manually ``require`` the files
- containing those classes;
-
-* :doc:`Templating` A toolkit for rendering templates,
- handling template inheritance (i.e. a template is decorated with a layout)
- and performing other common template tasks;
-
-* `Security`_ - A powerful library for handling all types of security inside
- an application;
-
-* `Translation`_ A framework for translating strings in your application.
-
-Each and every one of these components is decoupled and can be used in *any*
-PHP project, regardless of whether or not you use the Symfony2 framework.
-Every part is made to be used if needed and replaced when necessary.
-
-The Full Solution: The Symfony2 *Framework*
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-So then, what *is* the Symfony2 *Framework*? The *Symfony2 Framework* is
-a PHP library that accomplishes two distinct tasks:
-
-#. Provides a selection of components (i.e. the Symfony2 Components) and
- third-party libraries (e.g. `Swiftmailer`_ for sending emails);
-
-#. Provides sensible configuration and a "glue" library that ties all of these
- pieces together.
-
-The goal of the framework is to integrate many independent tools in order
-to provide a consistent experience for the developer. Even the framework
-itself is a Symfony2 bundle (i.e. a plugin) that can be configured or replaced
-entirely.
-
-Symfony2 provides a powerful set of tools for rapidly developing web applications
-without imposing on your application. Normal users can quickly start development
-by using a Symfony2 distribution, which provides a project skeleton with
-sensible defaults. For more advanced users, the sky is the limit.
-
-.. _`xkcd`: http://xkcd.com/
-.. _`HTTP 1.1 RFC`: http://www.w3.org/Protocols/rfc2616/rfc2616.html
-.. _`HTTP Bis`: http://datatracker.ietf.org/wg/httpbis/
-.. _`Live HTTP Headers`: https://addons.mozilla.org/en-US/firefox/addon/live-http-headers/
-.. _`List of HTTP status codes`: http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-.. _`List of HTTP header fields`: http://en.wikipedia.org/wiki/List_of_HTTP_header_fields
-.. _`List of common media types`: http://en.wikipedia.org/wiki/Internet_media_type#List_of_common_media_types
-.. _`Form`: https://github.com/symfony/Form
-.. _`Validator`: https://github.com/symfony/Validator
-.. _`Security`: https://github.com/symfony/Security
-.. _`Translation`: https://github.com/symfony/Translation
-.. _`Swiftmailer`: http://swiftmailer.org/
diff --git a/book/index.rst b/book/index.rst
deleted file mode 100755
index 915b0fc7a7f..00000000000
--- a/book/index.rst
+++ /dev/null
@@ -1,27 +0,0 @@
-The Book
-========
-
-.. toctree::
- :hidden:
-
- http_fundamentals
- from_flat_php_to_symfony2
- installation
- page_creation
- controller
- routing
- templating
- doctrine
- propel
- testing
- validation
- forms
- security
- http_cache
- translation
- service_container
- performance
- internals
- stable_api
-
-.. include:: /book/map.rst.inc
diff --git a/book/installation.rst b/book/installation.rst
deleted file mode 100644
index b4167818ece..00000000000
--- a/book/installation.rst
+++ /dev/null
@@ -1,374 +0,0 @@
-.. index::
- single: Installation
-
-Installing and Configuring Symfony
-==================================
-
-The goal of this chapter is to get you up and running with a working application
-built on top of Symfony. Fortunately, Symfony offers "distributions", which
-are functional Symfony "starter" projects that you can download and begin
-developing in immediately.
-
-.. tip::
-
- If you're looking for instructions on how best to create a new project
- and store it via source control, see `Using Source Control`_.
-
-Installing a Symfony2 Distribution
-----------------------------------
-
-.. tip::
-
- First, check that you have installed and configured a Web server (such
- as Apache) with PHP 5.3.8 or higher. For more information on Symfony2
- requirements, see the :doc:`requirements reference`.
-
-Symfony2 packages "distributions", which are fully-functional applications
-that include the Symfony2 core libraries, a selection of useful bundles, a
-sensible directory structure and some default configuration. When you download
-a Symfony2 distribution, you're downloading a functional application skeleton
-that can be used immediately to begin developing your application.
-
-Start by visiting the Symfony2 download page at `http://symfony.com/download`_.
-On this page, you'll see the *Symfony Standard Edition*, which is the main
-Symfony2 distribution. There are 2 ways to get your project started:
-
-Option 1) Composer
-~~~~~~~~~~~~~~~~~~
-
-`Composer`_ is a dependency management library for PHP, which you can use
-to download the Symfony2 Standard Edition.
-
-Start by `downloading Composer`_ anywhere onto your local computer. If you
-have curl installed, it's as easy as:
-
-.. code-block:: bash
-
- curl -s https://getcomposer.org/installer | php
-
-.. note::
-
- If your computer is not ready to use Composer, you'll see some recommendations
- when running this command. Follow those recommendations to get Composer
- working properly.
-
-Composer is an executable PHAR file, which you can use to download the Standard
-Distribution:
-
-.. code-block:: bash
-
- php composer.phar create-project symfony/framework-standard-edition /path/to/webroot/Symfony 2.2.0
-
-.. tip::
-
- For an exact version, replace `2.2.0` with the latest Symfony version
- (e.g. 2.2.1). For details, see the `Symfony Installation Page`_
-
-.. tip::
-
- To download the vendor files faster, add the ``--prefer-dist`` option at
- the end of any Composer command.
-
-This command may take several minutes to run as Composer downloads the Standard
-Distribution along with all of the vendor libraries that it needs. When it finishes,
-you should have a directory that looks something like this:
-
-.. code-block:: text
-
- path/to/webroot/ <- your web server directory (sometimes named htdocs or public)
- Symfony/ <- the new directory
- app/
- cache/
- config/
- logs/
- src/
- ...
- vendor/
- ...
- web/
- app.php
- ...
-
-Option 2) Download an Archive
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You can also download an archive of the Standard Edition. Here, you'll
-need to make two choices:
-
-* Download either a ``.tgz`` or ``.zip`` archive - both are equivalent, download
- whatever you're more comfortable using;
-
-* Download the distribution with or without vendors. If you're planning on
- using more third-party libraries or bundles and managing them via Composer,
- you should probably download "without vendors".
-
-Download one of the archives somewhere under your local web server's root
-directory and unpack it. From a UNIX command line, this can be done with
-one of the following commands (replacing ``###`` with your actual filename):
-
-.. code-block:: bash
-
- # for .tgz file
- $ tar zxvf Symfony_Standard_Vendors_2.2.###.tgz
-
- # for a .zip file
- $ unzip Symfony_Standard_Vendors_2.2.###.zip
-
-If you've downloaded "without vendors", you'll definitely need to read the
-next section.
-
-.. note::
-
- You can easily override the default directory structure. See
- :doc:`/cookbook/configuration/override_dir_structure` for more
- information.
-
-All public files and the front controller that handles incoming requests in
-a Symfony2 application live in the ``Symfony/web/`` directory. So, assuming
-you unpacked the archive into your web server's or virtual host's document root,
-your application's URLs will start with ``http://localhost/Symfony/web/``.
-To get nice and short URLs you should point the document root of your web
-server or virtual host to the ``Symfony/web/`` directory. Though this is not
-required for development it is recommended when your application goes into
-production as all system and configuration files become inaccessible to clients.
-For information on configuring your specific web server document root, see
-the following documentation: `Apache`_ | `Nginx`_ .
-
-.. note::
-
- The following examples assume you don't touch the document root settings
- so all URLs start with ``http://localhost/Symfony/web/``
-
-.. _installation-updating-vendors:
-
-Updating Vendors
-~~~~~~~~~~~~~~~~
-
-At this point, you've downloaded a fully-functional Symfony project in which
-you'll start to develop your own application. A Symfony project depends on
-a number of external libraries. These are downloaded into the `vendor/` directory
-of your project via a library called `Composer`_.
-
-Depending on how you downloaded Symfony, you may or may not need to do update
-your vendors right now. But, updating your vendors is always safe, and guarantees
-that you have all the vendor libraries you need.
-
-Step 1: Get `Composer`_ (The great new PHP packaging system)
-
-.. code-block:: bash
-
- curl -s http://getcomposer.org/installer | php
-
-Make sure you download ``composer.phar`` in the same folder where
-the ``composer.json`` file is located (this is your Symfony project
-root by default).
-
-Step 2: Install vendors
-
-.. code-block:: bash
-
- $ php composer.phar install
-
-This command downloads all of the necessary vendor libraries - including
-Symfony itself - into the ``vendor/`` directory.
-
-.. note::
-
- If you don't have ``curl`` installed, you can also just download the ``installer``
- file manually at http://getcomposer.org/installer. Place this file into your
- project and then run:
-
- .. code-block:: bash
-
- php installer
- php composer.phar install
-
-.. tip::
-
- When running ``php composer.phar install`` or ``php composer.phar update``,
- composer will execute post install/update commands to clear the cache
- and install assets. By default, the assets will be copied into your ``web``
- directory.
-
- Instead of copying your Symfony assets, you can create symlinks if
- your operating system supports it. To create symlinks, add an entry
- in the ``extra`` node of your composer.json file with the key
- ``symfony-assets-install`` and the value ``symlink``:
-
-
- .. code-block:: json
-
- "extra": {
- "symfony-app-dir": "app",
- "symfony-web-dir": "web",
- "symfony-assets-install": "symlink"
- }
-
- When passing ``relative`` instead of ``symlink`` to symfony-assets-install,
- the command will generate relative symlinks.
-
-Configuration and Setup
-~~~~~~~~~~~~~~~~~~~~~~~
-
-At this point, all of the needed third-party libraries now live in the ``vendor/``
-directory. You also have a default application setup in ``app/`` and some
-sample code inside the ``src/`` directory.
-
-Symfony2 comes with a visual server configuration tester to help make sure
-your Web server and PHP are configured to use Symfony. Use the following URL
-to check your configuration:
-
-.. code-block:: text
-
- http://localhost/config.php
-
-If there are any issues, correct them now before moving on.
-
-.. sidebar:: Setting up Permissions
-
- One common issue is that the ``app/cache`` and ``app/logs`` directories
- must be writable both by the web server and the command line user. On
- a UNIX system, if your web server user is different from your command
- line user, you can run the following commands just once in your project
- to ensure that permissions will be setup properly.
-
- **Note that not all web servers run as the user** ``www-data`` as in the examples
- below. Instead, check which user *your* web server is being run as and
- use it in place of ``www-data``.
-
- On a UNIX system, this can be done with one of the following commands:
-
- .. code-block:: bash
-
- $ ps aux | grep httpd
-
- or
-
- .. code-block:: bash
-
- $ ps aux | grep apache
-
- **1. Using ACL on a system that supports chmod +a**
-
- Many systems allow you to use the ``chmod +a`` command. Try this first,
- and if you get an error - try the next method. Be sure to replace ``www-data``
- with your web server user on the first ``chmod`` command:
-
- .. code-block:: bash
-
- $ rm -rf app/cache/*
- $ rm -rf app/logs/*
-
- $ sudo chmod +a "www-data allow delete,write,append,file_inherit,directory_inherit" app/cache app/logs
- $ sudo chmod +a "`whoami` allow delete,write,append,file_inherit,directory_inherit" app/cache app/logs
-
- **2. Using Acl on a system that does not support chmod +a**
-
- Some systems don't support ``chmod +a``, but do support another utility
- called ``setfacl``. You may need to `enable ACL support`_ on your partition
- and install setfacl before using it (as is the case with Ubuntu), like
- so:
-
- .. code-block:: bash
-
- $ sudo setfacl -R -m u:www-data:rwX -m u:`whoami`:rwX app/cache app/logs
- $ sudo setfacl -dR -m u:www-data:rwx -m u:`whoami`:rwx app/cache app/logs
-
- **3. Without using ACL**
-
- If you don't have access to changing the ACL of the directories, you will
- need to change the umask so that the cache and log directories will
- be group-writable or world-writable (depending if the web server user
- and the command line user are in the same group or not). To achieve
- this, put the following line at the beginning of the ``app/console``,
- ``web/app.php`` and ``web/app_dev.php`` files::
-
- umask(0002); // This will let the permissions be 0775
-
- // or
-
- umask(0000); // This will let the permissions be 0777
-
- Note that using the ACL is recommended when you have access to them
- on your server because changing the umask is not thread-safe.
-
-When everything is fine, click on "Go to the Welcome page" to request your
-first "real" Symfony2 webpage:
-
-.. code-block:: text
-
- http://localhost/app_dev.php/
-
-Symfony2 should welcome and congratulate you for your hard work so far!
-
-.. image:: /images/quick_tour/welcome.png
-
-.. tip::
-
- To get nice and short urls you should point the document root of your
- webserver or virtual host to the ``Symfony/web/`` directory. Though
- this is not required for development it is recommended at the time your
- application goes into production as all system and configuration files
- become inaccessible to clients then. For information on configuring
- your specific web server document root, read
- :doc:`/cookbook/configuration/web_server_configuration`
- or consult the official documentation of your webserver:
- `Apache`_ | `Nginx`_ .
-
-Beginning Development
----------------------
-
-Now that you have a fully-functional Symfony2 application, you can begin
-development! Your distribution may contain some sample code - check the
-``README.md`` file included with the distribution (open it as a text file)
-to learn about what sample code was included with your distribution.
-
-If you're new to Symfony, check out ":doc:`page_creation`", where you'll
-learn how to create pages, change configuration, and do everything else you'll
-need in your new application.
-
-Be sure to also check out the :doc:`Cookbook`, which contains
-a wide variety of articles about solving specific problems with Symfony.
-
-.. note::
-
- If you want to remove the sample code from your distribution, take a look
- at this cookbook article: ":doc:`/cookbook/bundles/remove`"
-
-Using Source Control
---------------------
-
-If you're using a version control system like ``Git`` or ``Subversion``, you
-can setup your version control system and begin committing your project to
-it as normal. The Symfony Standard edition *is* the starting point for your
-new project.
-
-For specific instructions on how best to setup your project to be stored
-in git, see :doc:`/cookbook/workflow/new_project_git`.
-
-Ignoring the ``vendor/`` Directory
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-If you've downloaded the archive *without vendors*, you can safely ignore
-the entire ``vendor/`` directory and not commit it to source control. With
-``Git``, this is done by creating and adding the following to a ``.gitignore``
-file:
-
-.. code-block:: text
-
- /vendor/
-
-Now, the vendor directory won't be committed to source control. This is fine
-(actually, it's great!) because when someone else clones or checks out the
-project, he/she can simply run the ``php composer.phar install`` script to
-install all the necessary project dependencies.
-
-.. _`enable ACL support`: https://help.ubuntu.com/community/FilePermissionsACLs
-.. _`http://symfony.com/download`: http://symfony.com/download
-.. _`Git`: http://git-scm.com/
-.. _`GitHub Bootcamp`: http://help.github.com/set-up-git-redirect
-.. _`Composer`: http://getcomposer.org/
-.. _`downloading Composer`: http://getcomposer.org/download/
-.. _`Apache`: http://httpd.apache.org/docs/current/mod/core.html#documentroot
-.. _`Nginx`: http://wiki.nginx.org/Symfony
-.. _`Symfony Installation Page`: http://symfony.com/download
diff --git a/book/internals.rst b/book/internals.rst
deleted file mode 100644
index d61951b86fb..00000000000
--- a/book/internals.rst
+++ /dev/null
@@ -1,717 +0,0 @@
-.. index::
- single: Internals
-
-Internals
-=========
-
-Looks like you want to understand how Symfony2 works and how to extend it.
-That makes me very happy! This section is an in-depth explanation of the
-Symfony2 internals.
-
-.. note::
-
- You need to read this section only if you want to understand how Symfony2
- works behind the scene, or if you want to extend Symfony2.
-
-Overview
---------
-
-The Symfony2 code is made of several independent layers. Each layer is built
-on top of the previous one.
-
-.. tip::
-
- Autoloading is not managed by the framework directly; it's done by using
- Composer's autoloader (``vendor/autoload.php``), which is included in
- the ``app/autoload.php`` file.
-
-``HttpFoundation`` Component
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The deepest level is the :namespace:`Symfony\\Component\\HttpFoundation`
-component. HttpFoundation provides the main objects needed to deal with HTTP.
-It is an Object-Oriented abstraction of some native PHP functions and
-variables:
-
-* The :class:`Symfony\\Component\\HttpFoundation\\Request` class abstracts
- the main PHP global variables like ``$_GET``, ``$_POST``, ``$_COOKIE``,
- ``$_FILES``, and ``$_SERVER``;
-
-* The :class:`Symfony\\Component\\HttpFoundation\\Response` class abstracts
- some PHP functions like ``header()``, ``setcookie()``, and ``echo``;
-
-* The :class:`Symfony\\Component\\HttpFoundation\\Session` class and
- :class:`Symfony\\Component\\HttpFoundation\\SessionStorage\\SessionStorageInterface`
- interface abstract session management ``session_*()`` functions.
-
-.. note::
-
- Read more about the :doc:`HttpFoundation Component `.
-
-``HttpKernel`` Component
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-On top of HttpFoundation is the :namespace:`Symfony\\Component\\HttpKernel`
-component. HttpKernel handles the dynamic part of HTTP; it is a thin wrapper
-on top of the Request and Response classes to standardize the way requests are
-handled. It also provides extension points and tools that makes it the ideal
-starting point to create a Web framework without too much overhead.
-
-It also optionally adds configurability and extensibility, thanks to the
-Dependency Injection component and a powerful plugin system (bundles).
-
-.. seealso::
-
- Read more about the :doc:`HttpKernel Component `,
- :doc:`Dependency Injection ` and
- :doc:`Bundles `.
-
-``FrameworkBundle`` Bundle
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The :namespace:`Symfony\\Bundle\\FrameworkBundle` bundle is the bundle that
-ties the main components and libraries together to make a lightweight and fast
-MVC framework. It comes with a sensible default configuration and conventions
-to ease the learning curve.
-
-.. index::
- single: Internals; Kernel
-
-Kernel
-------
-
-The :class:`Symfony\\Component\\HttpKernel\\HttpKernel` class is the central
-class of Symfony2 and is responsible for handling client requests. Its main
-goal is to "convert" a :class:`Symfony\\Component\\HttpFoundation\\Request`
-object to a :class:`Symfony\\Component\\HttpFoundation\\Response` object.
-
-Every Symfony2 Kernel implements
-:class:`Symfony\\Component\\HttpKernel\\HttpKernelInterface`::
-
- function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true)
-
-.. index::
- single: Internals; Controller resolver
-
-Controllers
-~~~~~~~~~~~
-
-To convert a Request to a Response, the Kernel relies on a "Controller". A
-Controller can be any valid PHP callable.
-
-The Kernel delegates the selection of what Controller should be executed
-to an implementation of
-:class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface`::
-
- public function getController(Request $request);
-
- public function getArguments(Request $request, $controller);
-
-The
-:method:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface::getController`
-method returns the Controller (a PHP callable) associated with the given
-Request. The default implementation
-(:class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver`)
-looks for a ``_controller`` request attribute that represents the controller
-name (a "class::method" string, like ``Bundle\BlogBundle\PostController:indexAction``).
-
-.. tip::
-
- The default implementation uses the
- :class:`Symfony\\Bundle\\FrameworkBundle\\EventListener\\RouterListener`
- to define the ``_controller`` Request attribute (see :ref:`kernel-core-request`).
-
-The
-:method:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface::getArguments`
-method returns an array of arguments to pass to the Controller callable. The
-default implementation automatically resolves the method arguments, based on
-the Request attributes.
-
-.. sidebar:: Matching Controller method arguments from Request attributes
-
- For each method argument, Symfony2 tries to get the value of a Request
- attribute with the same name. If it is not defined, the argument default
- value is used if defined::
-
- // Symfony2 will look for an 'id' attribute (mandatory)
- // and an 'admin' one (optional)
- public function showAction($id, $admin = true)
- {
- // ...
- }
-
-.. index::
- single: Internals; Request handling
-
-Handling Requests
-~~~~~~~~~~~~~~~~~
-
-The :method:`Symfony\\Component\\HttpKernel\\HttpKernel::handle` method
-takes a ``Request`` and *always* returns a ``Response``. To convert the
-``Request``, ``handle()`` relies on the Resolver and an ordered chain of
-Event notifications (see the next section for more information about each
-Event):
-
-#. Before doing anything else, the ``kernel.request`` event is notified -- if
- one of the listeners returns a ``Response``, it jumps to step 8 directly;
-
-#. The Resolver is called to determine the Controller to execute;
-
-#. Listeners of the ``kernel.controller`` event can now manipulate the
- Controller callable the way they want (change it, wrap it, ...);
-
-#. The Kernel checks that the Controller is actually a valid PHP callable;
-
-#. The Resolver is called to determine the arguments to pass to the Controller;
-
-#. The Kernel calls the Controller;
-
-#. If the Controller does not return a ``Response``, listeners of the
- ``kernel.view`` event can convert the Controller return value to a ``Response``;
-
-#. Listeners of the ``kernel.response`` event can manipulate the ``Response``
- (content and headers);
-
-#. The Response is returned.
-
-If an Exception is thrown during processing, the ``kernel.exception`` is
-notified and listeners are given a chance to convert the Exception to a
-Response. If that works, the ``kernel.response`` event is notified; if not, the
-Exception is re-thrown.
-
-If you don't want Exceptions to be caught (for embedded requests for
-instance), disable the ``kernel.exception`` event by passing ``false`` as the
-third argument to the ``handle()`` method.
-
-.. index::
- single: Internals; Internal requests
-
-Internal Requests
-~~~~~~~~~~~~~~~~~
-
-At any time during the handling of a request (the 'master' one), a sub-request
-can be handled. You can pass the request type to the ``handle()`` method (its
-second argument):
-
-* ``HttpKernelInterface::MASTER_REQUEST``;
-* ``HttpKernelInterface::SUB_REQUEST``.
-
-The type is passed to all events and listeners can act accordingly (some
-processing must only occur on the master request).
-
-.. index::
- pair: Kernel; Event
-
-Events
-~~~~~~
-
-Each event thrown by the Kernel is a subclass of
-:class:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent`. This means that
-each event has access to the same basic information:
-
-* :method:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent::getRequestType`
- - returns the *type* of the request (``HttpKernelInterface::MASTER_REQUEST``
- or ``HttpKernelInterface::SUB_REQUEST``);
-
-* :method:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent::getKernel`
- - returns the Kernel handling the request;
-
-* :method:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent::getRequest`
- - returns the current ``Request`` being handled.
-
-``getRequestType()``
-....................
-
-The ``getRequestType()`` method allows listeners to know the type of the
-request. For instance, if a listener must only be active for master requests,
-add the following code at the beginning of your listener method::
-
- use Symfony\Component\HttpKernel\HttpKernelInterface;
-
- if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
- // return immediately
- return;
- }
-
-.. tip::
-
- If you are not yet familiar with the Symfony2 Event Dispatcher, read the
- :doc:`Event Dispatcher Component Documentation`
- section first.
-
-.. index::
- single: Event; kernel.request
-
-.. _kernel-core-request:
-
-``kernel.request`` Event
-........................
-
-*Event Class*: :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseEvent`
-
-The goal of this event is to either return a ``Response`` object immediately
-or setup variables so that a Controller can be called after the event. Any
-listener can return a ``Response`` object via the ``setResponse()`` method on
-the event. In this case, all other listeners won't be called.
-
-This event is used by ``FrameworkBundle`` to populate the ``_controller``
-``Request`` attribute, via the
-:class:`Symfony\\Bundle\\FrameworkBundle\\EventListener\\RouterListener`. RequestListener
-uses a :class:`Symfony\\Component\\Routing\\RouterInterface` object to match
-the ``Request`` and determine the Controller name (stored in the
-``_controller`` ``Request`` attribute).
-
-.. seealso::
-
- Read more on the :ref:`kernel.request event `.
-
-.. index::
- single: Event; kernel.controller
-
-``kernel.controller`` Event
-...........................
-
-*Event Class*: :class:`Symfony\\Component\\HttpKernel\\Event\\FilterControllerEvent`
-
-This event is not used by ``FrameworkBundle``, but can be an entry point used
-to modify the controller that should be executed::
-
- use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
-
- public function onKernelController(FilterControllerEvent $event)
- {
- $controller = $event->getController();
- // ...
-
- // the controller can be changed to any PHP callable
- $event->setController($controller);
- }
-
-.. seealso::
-
- Read more on the :ref:`kernel.controller event `.
-
-.. index::
- single: Event; kernel.view
-
-``kernel.view`` Event
-.....................
-
-*Event Class*: :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForControllerResultEvent`
-
-This event is not used by ``FrameworkBundle``, but it can be used to implement
-a view sub-system. This event is called *only* if the Controller does *not*
-return a ``Response`` object. The purpose of the event is to allow some other
-return value to be converted into a ``Response``.
-
-The value returned by the Controller is accessible via the
-``getControllerResult`` method::
-
- use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
- use Symfony\Component\HttpFoundation\Response;
-
- public function onKernelView(GetResponseForControllerResultEvent $event)
- {
- $val = $event->getControllerResult();
- $response = new Response();
-
- // ... some how customize the Response from the return value
-
- $event->setResponse($response);
- }
-
-.. seealso::
-
- Read more on the :ref:`kernel.view event `.
-
-.. index::
- single: Event; kernel.response
-
-``kernel.response`` Event
-.........................
-
-*Event Class*: :class:`Symfony\\Component\\HttpKernel\\Event\\FilterResponseEvent`
-
-The purpose of this event is to allow other systems to modify or replace the
-``Response`` object after its creation::
-
- public function onKernelResponse(FilterResponseEvent $event)
- {
- $response = $event->getResponse();
-
- // ... modify the response object
- }
-
-The ``FrameworkBundle`` registers several listeners:
-
-* :class:`Symfony\\Component\\HttpKernel\\EventListener\\ProfilerListener`:
- collects data for the current request;
-
-* :class:`Symfony\\Bundle\\WebProfilerBundle\\EventListener\\WebDebugToolbarListener`:
- injects the Web Debug Toolbar;
-
-* :class:`Symfony\\Component\\HttpKernel\\EventListener\\ResponseListener`: fixes the
- Response ``Content-Type`` based on the request format;
-
-* :class:`Symfony\\Component\\HttpKernel\\EventListener\\EsiListener`: adds a
- ``Surrogate-Control`` HTTP header when the Response needs to be parsed for
- ESI tags.
-
-.. seealso::
-
- Read more on the :ref:`kernel.response event `.
-
-.. index::
- single: Event; kernel.terminate
-
-``kernel.terminate`` Event
-..........................
-
-.. versionadded:: 2.1
- The ``kernel.terminate`` event is new since Symfony 2.1.
-
-The purpose of this event is to perform "heavier" tasks after the response
-was already served to the client.
-
-.. seealso::
-
- Read more on the :ref:`kernel.terminate event `.
-
-.. index::
- single: Event; kernel.exception
-
-.. _kernel-kernel.exception:
-
-``kernel.exception`` Event
-..........................
-
-*Event Class*: :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent`
-
-``FrameworkBundle`` registers an
-:class:`Symfony\\Component\\HttpKernel\\EventListener\\ExceptionListener` that
-forwards the ``Request`` to a given Controller (the value of the
-``exception_listener.controller`` parameter -- must be in the
-``class::method`` notation).
-
-A listener on this event can create and set a ``Response`` object, create
-and set a new ``Exception`` object, or do nothing::
-
- use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
- use Symfony\Component\HttpFoundation\Response;
-
- public function onKernelException(GetResponseForExceptionEvent $event)
- {
- $exception = $event->getException();
- $response = new Response();
- // setup the Response object based on the caught exception
- $event->setResponse($response);
-
- // you can alternatively set a new Exception
- // $exception = new \Exception('Some special exception');
- // $event->setException($exception);
- }
-
-.. note::
-
- As Symfony ensures that the Response status code is set to the most
- appropriate one depending on the exception, setting the status on the
- response won't work. If you want to overwrite the status code (which you
- should not without a good reason), set the ``X-Status-Code`` header::
-
- return new Response('Error', 404 /* ignored */, array('X-Status-Code' => 200));
-
-.. index::
- single: Event Dispatcher
-
-The Event Dispatcher
---------------------
-
-The event dispatcher is a standalone component that is responsible for much
-of the underlying logic and flow behind a Symfony request. For more information,
-see the :doc:`Event Dispatcher Component Documentation`.
-
-.. seealso::
-
- Read more on the :ref:`kernel.exception event `.
-
-.. index::
- single: Profiler
-
-.. _internals-profiler:
-
-Profiler
---------
-
-When enabled, the Symfony2 profiler collects useful information about each
-request made to your application and store them for later analysis. Use the
-profiler in the development environment to help you to debug your code and
-enhance performance; use it in the production environment to explore problems
-after the fact.
-
-You rarely have to deal with the profiler directly as Symfony2 provides
-visualizer tools like the Web Debug Toolbar and the Web Profiler. If you use
-the Symfony2 Standard Edition, the profiler, the web debug toolbar, and the
-web profiler are all already configured with sensible settings.
-
-.. note::
-
- The profiler collects information for all requests (simple requests,
- redirects, exceptions, Ajax requests, ESI requests; and for all HTTP
- methods and all formats). It means that for a single URL, you can have
- several associated profiling data (one per external request/response
- pair).
-
-.. index::
- single: Profiler; Visualizing
-
-Visualizing Profiling Data
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Using the Web Debug Toolbar
-...........................
-
-In the development environment, the web debug toolbar is available at the
-bottom of all pages. It displays a good summary of the profiling data that
-gives you instant access to a lot of useful information when something does
-not work as expected.
-
-If the summary provided by the Web Debug Toolbar is not enough, click on the
-token link (a string made of 13 random characters) to access the Web Profiler.
-
-.. note::
-
- If the token is not clickable, it means that the profiler routes are not
- registered (see below for configuration information).
-
-Analyzing Profiling data with the Web Profiler
-..............................................
-
-The Web Profiler is a visualization tool for profiling data that you can use
-in development to debug your code and enhance performance; but it can also be
-used to explore problems that occur in production. It exposes all information
-collected by the profiler in a web interface.
-
-.. index::
- single: Profiler; Using the profiler service
-
-Accessing the Profiling information
-...................................
-
-You don't need to use the default visualizer to access the profiling
-information. But how can you retrieve profiling information for a specific
-request after the fact? When the profiler stores data about a Request, it also
-associates a token with it; this token is available in the ``X-Debug-Token``
-HTTP header of the Response::
-
- $profile = $container->get('profiler')->loadProfileFromResponse($response);
-
- $profile = $container->get('profiler')->loadProfile($token);
-
-.. tip::
-
- When the profiler is enabled but not the web debug toolbar, or when you
- want to get the token for an Ajax request, use a tool like Firebug to get
- the value of the ``X-Debug-Token`` HTTP header.
-
-Use the :method:`Symfony\\Component\\HttpKernel\\Profiler\\Profiler::find`
-method to access tokens based on some criteria::
-
- // get the latest 10 tokens
- $tokens = $container->get('profiler')->find('', '', 10);
-
- // get the latest 10 tokens for all URL containing /admin/
- $tokens = $container->get('profiler')->find('', '/admin/', 10);
-
- // get the latest 10 tokens for local requests
- $tokens = $container->get('profiler')->find('127.0.0.1', '', 10);
-
-If you want to manipulate profiling data on a different machine than the one
-where the information were generated, use the
-:method:`Symfony\\Component\\HttpKernel\\Profiler\\Profiler::export` and
-:method:`Symfony\\Component\\HttpKernel\\Profiler\\Profiler::import` methods::
-
- // on the production machine
- $profile = $container->get('profiler')->loadProfile($token);
- $data = $profiler->export($profile);
-
- // on the development machine
- $profiler->import($data);
-
-.. index::
- single: Profiler; Visualizing
-
-Configuration
-.............
-
-The default Symfony2 configuration comes with sensible settings for the
-profiler, the web debug toolbar, and the web profiler. Here is for instance
-the configuration for the development environment:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # load the profiler
- framework:
- profiler: { only_exceptions: false }
-
- # enable the web profiler
- web_profiler:
- toolbar: true
- intercept_redirects: true
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // load the profiler
- $container->loadFromExtension('framework', array(
- 'profiler' => array('only-exceptions' => false),
- ));
-
- // enable the web profiler
- $container->loadFromExtension('web_profiler', array(
- 'toolbar' => true,
- 'intercept-redirects' => true,
- 'verbose' => true,
- ));
-
-When ``only-exceptions`` is set to ``true``, the profiler only collects data
-when an exception is thrown by the application.
-
-When ``intercept-redirects`` is set to ``true``, the web profiler intercepts
-the redirects and gives you the opportunity to look at the collected data
-before following the redirect.
-
-If you enable the web profiler, you also need to mount the profiler routes:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- _profiler:
- resource: @WebProfilerBundle/Resources/config/routing/profiler.xml
- prefix: /_profiler
-
- .. code-block:: xml
-
-
-
- .. code-block:: php
-
- $collection->addCollection($loader->import("@WebProfilerBundle/Resources/config/routing/profiler.xml"), '/_profiler');
-
-As the profiler adds some overhead, you might want to enable it only under
-certain circumstances in the production environment. The ``only-exceptions``
-settings limits profiling to 500 pages, but what if you want to get
-information when the client IP comes from a specific address, or for a limited
-portion of the website? You can use a request matcher:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # enables the profiler only for request coming for the 192.168.0.0 network
- framework:
- profiler:
- matcher: { ip: 192.168.0.0/24 }
-
- # enables the profiler only for the /admin URLs
- framework:
- profiler:
- matcher: { path: "^/admin/" }
-
- # combine rules
- framework:
- profiler:
- matcher: { ip: 192.168.0.0/24, path: "^/admin/" }
-
- # use a custom matcher instance defined in the "custom_matcher" service
- framework:
- profiler:
- matcher: { service: custom_matcher }
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // enables the profiler only for request coming for the 192.168.0.0 network
- $container->loadFromExtension('framework', array(
- 'profiler' => array(
- 'matcher' => array('ip' => '192.168.0.0/24'),
- ),
- ));
-
- // enables the profiler only for the /admin URLs
- $container->loadFromExtension('framework', array(
- 'profiler' => array(
- 'matcher' => array('path' => '^/admin/'),
- ),
- ));
-
- // combine rules
- $container->loadFromExtension('framework', array(
- 'profiler' => array(
- 'matcher' => array('ip' => '192.168.0.0/24', 'path' => '^/admin/'),
- ),
- ));
-
- # use a custom matcher instance defined in the "custom_matcher" service
- $container->loadFromExtension('framework', array(
- 'profiler' => array(
- 'matcher' => array('service' => 'custom_matcher'),
- ),
- ));
-
-Learn more from the Cookbook
-----------------------------
-
-* :doc:`/cookbook/testing/profiling`
-* :doc:`/cookbook/profiler/data_collector`
-* :doc:`/cookbook/event_dispatcher/class_extension`
-* :doc:`/cookbook/event_dispatcher/method_behavior`
-
-.. _`Symfony2 Dependency Injection component`: https://github.com/symfony/DependencyInjection
diff --git a/book/map.rst.inc b/book/map.rst.inc
deleted file mode 100644
index 573c8027524..00000000000
--- a/book/map.rst.inc
+++ /dev/null
@@ -1,19 +0,0 @@
-* :doc:`/book/http_fundamentals`
-* :doc:`/book/from_flat_php_to_symfony2`
-* :doc:`/book/installation`
-* :doc:`/book/page_creation`
-* :doc:`/book/controller`
-* :doc:`/book/routing`
-* :doc:`/book/templating`
-* :doc:`/book/doctrine`
-* :doc:`/book/propel`
-* :doc:`/book/testing`
-* :doc:`/book/validation`
-* :doc:`/book/forms`
-* :doc:`/book/security`
-* :doc:`/book/http_cache`
-* :doc:`/book/translation`
-* :doc:`/book/service_container`
-* :doc:`/book/performance`
-* :doc:`/book/internals`
-* :doc:`/book/stable_api`
diff --git a/book/page_creation.rst b/book/page_creation.rst
deleted file mode 100644
index f90ae6d4f48..00000000000
--- a/book/page_creation.rst
+++ /dev/null
@@ -1,1007 +0,0 @@
-.. index::
- single: Page creation
-
-Creating Pages in Symfony2
-==========================
-
-Creating a new page in Symfony2 is a simple two-step process:
-
-* *Create a route*: A route defines the URL (e.g. ``/about``) to your page
- and specifies a controller (which is a PHP function) that Symfony2 should
- execute when the URL of an incoming request matches the route path;
-
-* *Create a controller*: A controller is a PHP function that takes the incoming
- request and transforms it into the Symfony2 ``Response`` object that's
- returned to the user.
-
-This simple approach is beautiful because it matches the way that the Web works.
-Every interaction on the Web is initiated by an HTTP request. The job of
-your application is simply to interpret the request and return the appropriate
-HTTP response.
-
-Symfony2 follows this philosophy and provides you with tools and conventions
-to keep your application organized as it grows in users and complexity.
-
-.. index::
- single: Page creation; Example
-
-The "Hello Symfony!" Page
--------------------------
-
-Start by building a spin-off of the classic "Hello World!" application. When
-you're finished, the user will be able to get a personal greeting (e.g. "Hello Symfony")
-by going to the following URL:
-
-.. code-block:: text
-
- http://localhost/app_dev.php/hello/Symfony
-
-Actually, you'll be able to replace ``Symfony`` with any other name to be
-greeted. To create the page, follow the simple two-step process.
-
-.. note::
-
- The tutorial assumes that you've already downloaded Symfony2 and configured
- your webserver. The above URL assumes that ``localhost`` points to the
- ``web`` directory of your new Symfony2 project. For detailed information
- on this process, see the documentation on the web server you are using.
- Here's the relevant documentation page for some web server you might be using:
-
- * For Apache HTTP Server, refer to `Apache's DirectoryIndex documentation`_
- * For Nginx, refer to `Nginx HttpCoreModule location documentation`_
-
-Before you begin: Create the Bundle
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Before you begin, you'll need to create a *bundle*. In Symfony2, a :term:`bundle`
-is like a plugin, except that all of the code in your application will live
-inside a bundle.
-
-A bundle is nothing more than a directory that houses everything related
-to a specific feature, including PHP classes, configuration, and even stylesheets
-and Javascript files (see :ref:`page-creation-bundles`).
-
-To create a bundle called ``AcmeHelloBundle`` (a play bundle that you'll
-build in this chapter), run the following command and follow the on-screen
-instructions (use all of the default options):
-
-.. code-block:: bash
-
- $ php app/console generate:bundle --namespace=Acme/HelloBundle --format=yml
-
-Behind the scenes, a directory is created for the bundle at ``src/Acme/HelloBundle``.
-A line is also automatically added to the ``app/AppKernel.php`` file so that
-the bundle is registered with the kernel::
-
- // app/AppKernel.php
- public function registerBundles()
- {
- $bundles = array(
- ...,
- new Acme\HelloBundle\AcmeHelloBundle(),
- );
- // ...
-
- return $bundles;
- }
-
-Now that you have a bundle setup, you can begin building your application
-inside the bundle.
-
-Step 1: Create the Route
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-By default, the routing configuration file in a Symfony2 application is
-located at ``app/config/routing.yml``. Like all configuration in Symfony2,
-you can also choose to use XML or PHP out of the box to configure routes.
-
-If you look at the main routing file, you'll see that Symfony already added
-an entry when you generated the ``AcmeHelloBundle``:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/routing.yml
- acme_hello:
- resource: "@AcmeHelloBundle/Resources/config/routing.yml"
- prefix: /
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/routing.php
- use Symfony\Component\Routing\RouteCollection;
- use Symfony\Component\Routing\Route;
-
- $collection = new RouteCollection();
- $collection->addCollection(
- $loader->import('@AcmeHelloBundle/Resources/config/routing.php'),
- '/',
- );
-
- return $collection;
-
-This entry is pretty basic: it tells Symfony to load routing configuration
-from the ``Resources/config/routing.yml`` file that lives inside the ``AcmeHelloBundle``.
-This means that you place routing configuration directly in ``app/config/routing.yml``
-or organize your routes throughout your application, and import them from here.
-
-Now that the ``routing.yml`` file from the bundle is being imported, add
-the new route that defines the URL of the page that you're about to create:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # src/Acme/HelloBundle/Resources/config/routing.yml
- hello:
- path: /hello/{name}
- defaults: { _controller: AcmeHelloBundle:Hello:index }
-
- .. code-block:: xml
-
-
-
-
-
-
-
- AcmeHelloBundle:Hello:index
-
-
-
- .. code-block:: php
-
- // src/Acme/HelloBundle/Resources/config/routing.php
- use Symfony\Component\Routing\RouteCollection;
- use Symfony\Component\Routing\Route;
-
- $collection = new RouteCollection();
- $collection->add('hello', new Route('/hello/{name}', array(
- '_controller' => 'AcmeHelloBundle:Hello:index',
- )));
-
- return $collection;
-
-The routing consists of two basic pieces: the ``path``, which is the URL
-that this route will match, and a ``defaults`` array, which specifies the
-controller that should be executed. The placeholder syntax in the path
-(``{name}``) is a wildcard. It means that ``/hello/Ryan``, ``/hello/Fabien``
-or any other similar URL will match this route. The ``{name}`` placeholder
-parameter will also be passed to the controller so that you can use its value
-to personally greet the user.
-
-.. note::
-
- The routing system has many more great features for creating flexible
- and powerful URL structures in your application. For more details, see
- the chapter all about :doc:`Routing `.
-
-Step 2: Create the Controller
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-When a URL such as ``/hello/Ryan`` is handled by the application, the ``hello``
-route is matched and the ``AcmeHelloBundle:Hello:index`` controller is executed
-by the framework. The second step of the page-creation process is to create
-that controller.
-
-The controller - ``AcmeHelloBundle:Hello:index`` is the *logical* name of
-the controller, and it maps to the ``indexAction`` method of a PHP class
-called ``Acme\HelloBundle\Controller\HelloController``. Start by creating this file
-inside your ``AcmeHelloBundle``::
-
- // src/Acme/HelloBundle/Controller/HelloController.php
- namespace Acme\HelloBundle\Controller;
-
- class HelloController
- {
- }
-
-In reality, the controller is nothing more than a PHP method that you create
-and Symfony executes. This is where your code uses information from the request
-to build and prepare the resource being requested. Except in some advanced
-cases, the end product of a controller is always the same: a Symfony2 ``Response``
-object.
-
-Create the ``indexAction`` method that Symfony will execute when the ``hello``
-route is matched::
-
- // src/Acme/HelloBundle/Controller/HelloController.php
- namespace Acme\HelloBundle\Controller;
-
- use Symfony\Component\HttpFoundation\Response;
-
- class HelloController
- {
- public function indexAction($name)
- {
- return new Response('Hello '.$name.'!');
- }
- }
-
-The controller is simple: it creates a new ``Response`` object, whose first
-argument is the content that should be used in the response (a small HTML
-page in this example).
-
-Congratulations! After creating only a route and a controller, you already
-have a fully-functional page! If you've setup everything correctly, your
-application should greet you:
-
-.. code-block:: text
-
- http://localhost/app_dev.php/hello/Ryan
-
-.. tip::
-
- You can also view your app in the "prod" :ref:`environment`
- by visiting:
-
- .. code-block:: text
-
- http://localhost/app.php/hello/Ryan
-
- If you get an error, it's likely because you need to clear your cache
- by running:
-
- .. code-block:: bash
-
- $ php app/console cache:clear --env=prod --no-debug
-
-An optional, but common, third step in the process is to create a template.
-
-.. note::
-
- Controllers are the main entry point for your code and a key ingredient
- when creating pages. Much more information can be found in the
- :doc:`Controller Chapter `.
-
-Optional Step 3: Create the Template
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Templates allow you to move all of the presentation (e.g. HTML code) into
-a separate file and reuse different portions of the page layout. Instead
-of writing the HTML inside the controller, render a template instead:
-
-.. code-block:: php
- :linenos:
-
- // src/Acme/HelloBundle/Controller/HelloController.php
- namespace Acme\HelloBundle\Controller;
-
- use Symfony\Bundle\FrameworkBundle\Controller\Controller;
-
- class HelloController extends Controller
- {
- public function indexAction($name)
- {
- return $this->render(
- 'AcmeHelloBundle:Hello:index.html.twig',
- array('name' => $name)
- );
-
- // render a PHP template instead
- // return $this->render(
- // 'AcmeHelloBundle:Hello:index.html.php',
- // array('name' => $name)
- // );
- }
- }
-
-.. note::
-
- In order to use the :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::render`
- method, your controller must extend the
- :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller` class,
- which adds shortcuts for tasks that are common inside controllers. This
- is done in the above example by adding the ``use`` statement on line 4
- and then extending ``Controller`` on line 6.
-
-The ``render()`` method creates a ``Response`` object filled with the content
-of the given, rendered template. Like any other controller, you will ultimately
-return that ``Response`` object.
-
-Notice that there are two different examples for rendering the template.
-By default, Symfony2 supports two different templating languages: classic
-PHP templates and the succinct but powerful `Twig`_ templates. Don't be
-alarmed - you're free to choose either or even both in the same project.
-
-The controller renders the ``AcmeHelloBundle:Hello:index.html.twig`` template,
-which uses the following naming convention:
-
- **BundleName**:**ControllerName**:**TemplateName**
-
-This is the *logical* name of the template, which is mapped to a physical
-location using the following convention.
-
- **/path/to/BundleName**/Resources/views/**ControllerName**/**TemplateName**
-
-In this case, ``AcmeHelloBundle`` is the bundle name, ``Hello`` is the
-controller, and ``index.html.twig`` the template:
-
-.. configuration-block::
-
- .. code-block:: jinja
- :linenos:
-
- {# src/Acme/HelloBundle/Resources/views/Hello/index.html.twig #}
- {% extends '::base.html.twig' %}
-
- {% block body %}
- Hello {{ name }}!
- {% endblock %}
-
- .. code-block:: html+php
-
-
- extend('::base.html.php') ?>
-
- Hello escape($name) ?>!
-
-Step through the Twig template line-by-line:
-
-* *line 2*: The ``extends`` token defines a parent template. The template
- explicitly defines a layout file inside of which it will be placed.
-
-* *line 4*: The ``block`` token says that everything inside should be placed
- inside a block called ``body``. As you'll see, it's the responsibility
- of the parent template (``base.html.twig``) to ultimately render the
- block called ``body``.
-
-The parent template, ``::base.html.twig``, is missing both the **BundleName**
-and **ControllerName** portions of its name (hence the double colon (``::``)
-at the beginning). This means that the template lives outside of the bundles
-and in the ``app`` directory:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
- {# app/Resources/views/base.html.twig #}
-
-
-
-
- {% block title %}Welcome!{% endblock %}
- {% block stylesheets %}{% endblock %}
-
-
-
- {% block body %}{% endblock %}
- {% block javascripts %}{% endblock %}
-
-
-
- .. code-block:: html+php
-
-
-
-
-
-
- output('title', 'Welcome!') ?>
- output('stylesheets') ?>
-
-
-
- output('_content') ?>
- output('javascripts') ?>
-
-
-
-The base template file defines the HTML layout and renders the ``body`` block
-that you defined in the ``index.html.twig`` template. It also renders a ``title``
-block, which you could choose to define in the ``index.html.twig`` template.
-Since you did not define the ``title`` block in the child template, it defaults
-to "Welcome!".
-
-Templates are a powerful way to render and organize the content for your
-page. A template can render anything, from HTML markup, to CSS code, or anything
-else that the controller may need to return.
-
-In the lifecycle of handling a request, the templating engine is simply
-an optional tool. Recall that the goal of each controller is to return a
-``Response`` object. Templates are a powerful, but optional, tool for creating
-the content for that ``Response`` object.
-
-.. index::
- single: Directory Structure
-
-The Directory Structure
------------------------
-
-After just a few short sections, you already understand the philosophy behind
-creating and rendering pages in Symfony2. You've also already begun to see
-how Symfony2 projects are structured and organized. By the end of this section,
-you'll know where to find and put different types of files and why.
-
-Though entirely flexible, by default, each Symfony :term:`application` has
-the same basic and recommended directory structure:
-
-* ``app/``: This directory contains the application configuration;
-
-* ``src/``: All the project PHP code is stored under this directory;
-
-* ``vendor/``: Any vendor libraries are placed here by convention;
-
-* ``web/``: This is the web root directory and contains any publicly accessible files;
-
-.. _the-web-directory:
-
-The Web Directory
-~~~~~~~~~~~~~~~~~
-
-The web root directory is the home of all public and static files including
-images, stylesheets, and JavaScript files. It is also where each
-:term:`front controller` lives::
-
- // web/app.php
- require_once __DIR__.'/../app/bootstrap.php.cache';
- require_once __DIR__.'/../app/AppKernel.php';
-
- use Symfony\Component\HttpFoundation\Request;
-
- $kernel = new AppKernel('prod', false);
- $kernel->loadClassCache();
- $kernel->handle(Request::createFromGlobals())->send();
-
-The front controller file (``app.php`` in this example) is the actual PHP
-file that's executed when using a Symfony2 application and its job is to
-use a Kernel class, ``AppKernel``, to bootstrap the application.
-
-.. tip::
-
- Having a front controller means different and more flexible URLs than
- are used in a typical flat PHP application. When using a front controller,
- URLs are formatted in the following way:
-
- .. code-block:: text
-
- http://localhost/app.php/hello/Ryan
-
- The front controller, ``app.php``, is executed and the "internal:" URL
- ``/hello/Ryan`` is routed internally using the routing configuration.
- By using Apache ``mod_rewrite`` rules, you can force the ``app.php`` file
- to be executed without needing to specify it in the URL:
-
- .. code-block:: text
-
- http://localhost/hello/Ryan
-
-Though front controllers are essential in handling every request, you'll
-rarely need to modify or even think about them. They'll be mentioned again
-briefly in the `Environments`_ section.
-
-The Application (``app``) Directory
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-As you saw in the front controller, the ``AppKernel`` class is the main entry
-point of the application and is responsible for all configuration. As such,
-it is stored in the ``app/`` directory.
-
-This class must implement two methods that define everything that Symfony
-needs to know about your application. You don't even need to worry about
-these methods when starting - Symfony fills them in for you with sensible
-defaults.
-
-* ``registerBundles()``: Returns an array of all bundles needed to run the
- application (see :ref:`page-creation-bundles`);
-
-* ``registerContainerConfiguration()``: Loads the main application configuration
- resource file (see the `Application Configuration`_ section).
-
-In day-to-day development, you'll mostly use the ``app/`` directory to modify
-configuration and routing files in the ``app/config/`` directory (see
-`Application Configuration`_). It also contains the application cache
-directory (``app/cache``), a log directory (``app/logs``) and a directory
-for application-level resource files, such as templates (``app/Resources``).
-You'll learn more about each of these directories in later chapters.
-
-.. _autoloading-introduction-sidebar:
-
-.. sidebar:: Autoloading
-
- When Symfony is loading, a special file - ``vendor/autoload.php`` - is
- included. This file is created by Composer and will autoload all
- application files living in the `src/` folder as well as all
- third-party libraries mentioned in the ``composer.json`` file.
-
- Because of the autoloader, you never need to worry about using ``include``
- or ``require`` statements. Instead, Composer uses the namespace of a class
- to determine its location and automatically includes the file on your
- behalf the instant you need a class.
-
- The autoloader is already configured to look in the ``src/`` directory
- for any of your PHP classes. For autoloading to work, the class name and
- path to the file have to follow the same pattern:
-
- .. code-block:: text
-
- Class Name:
- Acme\HelloBundle\Controller\HelloController
- Path:
- src/Acme/HelloBundle/Controller/HelloController.php
-
-The Source (``src``) Directory
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Put simply, the ``src/`` directory contains all of the actual code (PHP code,
-templates, configuration files, stylesheets, etc) that drives *your* application.
-When developing, the vast majority of your work will be done inside one or
-more bundles that you create in this directory.
-
-But what exactly is a :term:`bundle`?
-
-.. _page-creation-bundles:
-
-The Bundle System
------------------
-
-A bundle is similar to a plugin in other software, but even better. The key
-difference is that *everything* is a bundle in Symfony2, including both the
-core framework functionality and the code written for your application.
-Bundles are first-class citizens in Symfony2. This gives you the flexibility
-to use pre-built features packaged in `third-party bundles`_ or to distribute
-your own bundles. It makes it easy to pick and choose which features to enable
-in your application and to optimize them the way you want.
-
-.. note::
-
- While you'll learn the basics here, an entire cookbook entry is devoted
- to the organization and best practices of :doc:`bundles`.
-
-A bundle is simply a structured set of files within a directory that implement
-a single feature. You might create a ``BlogBundle``, a ``ForumBundle`` or
-a bundle for user management (many of these exist already as open source
-bundles). Each directory contains everything related to that feature, including
-PHP files, templates, stylesheets, JavaScripts, tests and anything else.
-Every aspect of a feature exists in a bundle and every feature lives in a
-bundle.
-
-An application is made up of bundles as defined in the ``registerBundles()``
-method of the ``AppKernel`` class::
-
- // app/AppKernel.php
- public function registerBundles()
- {
- $bundles = array(
- new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
- new Symfony\Bundle\SecurityBundle\SecurityBundle(),
- new Symfony\Bundle\TwigBundle\TwigBundle(),
- new Symfony\Bundle\MonologBundle\MonologBundle(),
- new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),
- new Symfony\Bundle\DoctrineBundle\DoctrineBundle(),
- new Symfony\Bundle\AsseticBundle\AsseticBundle(),
- new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
- new JMS\SecurityExtraBundle\JMSSecurityExtraBundle(),
- );
-
- if (in_array($this->getEnvironment(), array('dev', 'test'))) {
- $bundles[] = new Acme\DemoBundle\AcmeDemoBundle();
- $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
- $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();
- $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
- }
-
- return $bundles;
- }
-
-With the ``registerBundles()`` method, you have total control over which bundles
-are used by your application (including the core Symfony bundles).
-
-.. tip::
-
- A bundle can live *anywhere* as long as it can be autoloaded (via the
- autoloader configured at ``app/autoload.php``).
-
-Creating a Bundle
-~~~~~~~~~~~~~~~~~
-
-The Symfony Standard Edition comes with a handy task that creates a fully-functional
-bundle for you. Of course, creating a bundle by hand is pretty easy as well.
-
-To show you how simple the bundle system is, create a new bundle called
-``AcmeTestBundle`` and enable it.
-
-.. tip::
-
- 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``).
-
-Start by creating a ``src/Acme/TestBundle/`` directory and adding a new file
-called ``AcmeTestBundle.php``::
-
- // src/Acme/TestBundle/AcmeTestBundle.php
- namespace Acme\TestBundle;
-
- use Symfony\Component\HttpKernel\Bundle\Bundle;
-
- class AcmeTestBundle extends Bundle
- {
- }
-
-.. tip::
-
- The name ``AcmeTestBundle`` 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``).
-
-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
-of the bundle.
-
-Now that you've created the bundle, enable it via the ``AppKernel`` class::
-
- // app/AppKernel.php
- public function registerBundles()
- {
- $bundles = array(
- ...,
- // register your bundles
- new Acme\TestBundle\AcmeTestBundle(),
- );
- // ...
-
- return $bundles;
- }
-
-And while it doesn't do anything yet, ``AcmeTestBundle`` is now ready to
-be used.
-
-And as easy as this is, Symfony also provides a command-line interface for
-generating a basic bundle skeleton:
-
-.. code-block:: bash
-
- $ php app/console generate:bundle --namespace=Acme/TestBundle
-
-The bundle skeleton generates with a basic controller, template and routing
-resource that can be customized. You'll learn more about Symfony2's command-line
-tools later.
-
-.. tip::
-
- Whenever creating a new bundle or using a third-party bundle, always make
- sure the bundle has been enabled in ``registerBundles()``. When using
- the ``generate:bundle`` command, this is done for you.
-
-Bundle Directory Structure
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The directory structure of a bundle is simple and flexible. By default, the
-bundle system follows a set of conventions that help to keep code consistent
-between all Symfony2 bundles. Take a look at ``AcmeHelloBundle``, as it contains
-some of the most common elements of a bundle:
-
-* ``Controller/`` contains the controllers of the bundle (e.g. ``HelloController.php``);
-
-* ``DependencyInjection/`` holds certain dependency injection extension classes,
- which may import service configuration, register compiler passes or more
- (this directory is not necessary);
-
-* ``Resources/config/`` houses configuration, including routing configuration
- (e.g. ``routing.yml``);
-
-* ``Resources/views/`` holds templates organized by controller name (e.g.
- ``Hello/index.html.twig``);
-
-* ``Resources/public/`` contains web assets (images, stylesheets, etc) and is
- copied or symbolically linked into the project ``web/`` directory via
- the ``assets:install`` console command;
-
-* ``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.
-
-As you move through the book, 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.
-
-Application Configuration
--------------------------
-
-An application consists of a collection of bundles representing all of the
-features and capabilities of your application. Each bundle can be customized
-via configuration files written in YAML, XML or PHP. By default, the main
-configuration file lives in the ``app/config/`` directory and is called
-either ``config.yml``, ``config.xml`` or ``config.php`` depending on which
-format you prefer:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- imports:
- - { resource: parameters.yml }
- - { resource: security.yml }
-
- framework:
- secret: "%secret%"
- router: { resource: "%kernel.root_dir%/config/routing.yml" }
- # ...
-
- # Twig Configuration
- twig:
- debug: "%kernel.debug%"
- strict_variables: "%kernel.debug%"
-
- # ...
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- $this->import('parameters.yml');
- $this->import('security.yml');
-
- $container->loadFromExtension('framework', array(
- 'secret' => '%secret%',
- 'router' => array('resource' => '%kernel.root_dir%/config/routing.php'),
- // ...
- ),
- ));
-
- // Twig Configuration
- $container->loadFromExtension('twig', array(
- 'debug' => '%kernel.debug%',
- 'strict_variables' => '%kernel.debug%',
- ));
-
- // ...
-
-.. note::
-
- You'll learn exactly how to load each file/format in the next section
- `Environments`_.
-
-Each top-level entry like ``framework`` or ``twig`` defines the configuration
-for a particular bundle. For example, the ``framework`` key defines the configuration
-for the core Symfony ``FrameworkBundle`` and includes configuration for the
-routing, templating, and other core systems.
-
-For now, don't worry about the specific configuration options in each section.
-The configuration file ships with sensible defaults. As you read more and
-explore each part of Symfony2, you'll learn about the specific configuration
-options of each feature.
-
-.. sidebar:: Configuration Formats
-
- Throughout the chapters, all configuration examples will be shown in all
- three formats (YAML, XML and PHP). Each has its own advantages and
- disadvantages. The choice of which to use is up to you:
-
- * *YAML*: Simple, clean and readable (learn more about yaml in
- ":doc:`/components/yaml/yaml_format`");
-
- * *XML*: More powerful than YAML at times and supports IDE autocompletion;
-
- * *PHP*: Very powerful but less readable than standard configuration formats.
-
-Default Configuration Dump
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. versionadded:: 2.1
- The ``config:dump-reference`` command was added in Symfony 2.1
-
-You can dump the default configuration for a bundle in yaml to the console using
-the ``config:dump-reference`` command. Here is an example of dumping the default
-FrameworkBundle configuration:
-
-.. code-block:: text
-
- app/console config:dump-reference FrameworkBundle
-
-The extension alias (configuration key) can also be used:
-
-.. code-block:: text
-
- app/console config:dump-reference framework
-
-.. note::
-
- See the cookbook article: :doc:`How to expose a Semantic Configuration for
- a Bundle` for information on adding
- configuration for your own bundle.
-
-.. index::
- single: Environments; Introduction
-
-.. _environments-summary:
-
-Environments
-------------
-
-An application can run in various environments. The different environments
-share the same PHP code (apart from the front controller), but use different
-configuration. For instance, a ``dev`` environment will log warnings and
-errors, while a ``prod`` environment will only log errors. Some files are
-rebuilt on each request in the ``dev`` environment (for the developer's convenience),
-but cached in the ``prod`` environment. All environments live together on
-the same machine and execute the same application.
-
-A Symfony2 project generally begins with three environments (``dev``, ``test``
-and ``prod``), though creating new environments is easy. You can view your
-application in different environments simply by changing the front controller
-in your browser. To see the application in the ``dev`` environment, access
-the application via the development front controller:
-
-.. code-block:: text
-
- http://localhost/app_dev.php/hello/Ryan
-
-If you'd like to see how your application will behave in the production environment,
-call the ``prod`` front controller instead:
-
-.. code-block:: text
-
- http://localhost/app.php/hello/Ryan
-
-Since the ``prod`` environment is optimized for speed; the configuration,
-routing and Twig templates are compiled into flat PHP classes and cached.
-When viewing changes in the ``prod`` environment, you'll need to clear these
-cached files and allow them to rebuild:
-
-.. code-block:: bash
-
- $ php app/console cache:clear --env=prod --no-debug
-
-.. note::
-
- If you open the ``web/app.php`` file, you'll find that it's configured explicitly
- to use the ``prod`` environment::
-
- $kernel = new AppKernel('prod', false);
-
- You can create a new front controller for a new environment by copying
- this file and changing ``prod`` to some other value.
-
-.. note::
-
- The ``test`` environment is used when running automated tests and cannot
- be accessed directly through the browser. See the :doc:`testing chapter`
- for more details.
-
-.. index::
- single: Environments; Configuration
-
-Environment Configuration
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The ``AppKernel`` class is responsible for actually loading the configuration
-file of your choice::
-
- // app/AppKernel.php
- public function registerContainerConfiguration(LoaderInterface $loader)
- {
- $loader->load(
- __DIR__.'/config/config_'.$this->getEnvironment().'.yml'
- );
- }
-
-You already know that the ``.yml`` extension can be changed to ``.xml`` or
-``.php`` if you prefer to use either XML or PHP to write your configuration.
-Notice also that each environment loads its own configuration file. Consider
-the configuration file for the ``dev`` environment.
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config_dev.yml
- imports:
- - { resource: config.yml }
-
- framework:
- router: { resource: "%kernel.root_dir%/config/routing_dev.yml" }
- profiler: { only_exceptions: false }
-
- # ...
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config_dev.php
- $loader->import('config.php');
-
- $container->loadFromExtension('framework', array(
- 'router' => array('resource' => '%kernel.root_dir%/config/routing_dev.php'),
- 'profiler' => array('only-exceptions' => false),
- ));
-
- // ...
-
-The ``imports`` key is similar to a PHP ``include`` statement and guarantees
-that the main configuration file (``config.yml``) is loaded first. The rest
-of the file tweaks the default configuration for increased logging and other
-settings conducive to a development environment.
-
-Both the ``prod`` and ``test`` environments follow the same model: each environment
-imports the base configuration file and then modifies its configuration values
-to fit the needs of the specific environment. This is just a convention,
-but one that allows you to reuse most of your configuration and customize
-just pieces of it between environments.
-
-Summary
--------
-
-Congratulations! You've now seen every fundamental aspect of Symfony2 and have
-hopefully discovered how easy and flexible it can be. And while there are
-*a lot* of features still to come, be sure to keep the following basic points
-in mind:
-
-* Creating a page is a three-step process involving a **route**, a **controller**
- and (optionally) a **template**;
-
-* Each project contains just a few main directories: ``web/`` (web assets and
- the front controllers), ``app/`` (configuration), ``src/`` (your bundles),
- and ``vendor/`` (third-party code) (there's also a ``bin/`` directory that's
- used to help updated vendor libraries);
-
-* Each feature in Symfony2 (including the Symfony2 framework core) is organized
- into a *bundle*, which is a structured set of files for that feature;
-
-* The **configuration** for each bundle lives in the ``Resources/config``
- directory of the bundle and can be specified in YAML, XML or PHP;
-
-* The global **application configuration** lives in the ``app/config``
- directory;
-
-* Each **environment** is accessible via a different front controller (e.g.
- ``app.php`` and ``app_dev.php``) and loads a different configuration file.
-
-From here, each chapter will introduce you to more and more powerful tools
-and advanced concepts. The more you know about Symfony2, the more you'll
-appreciate the flexibility of its architecture and the power it gives you
-to rapidly develop applications.
-
-.. _`Twig`: http://twig.sensiolabs.org
-.. _`third-party bundles`: http://knpbundles.com
-.. _`Symfony Standard Edition`: http://symfony.com/download
-.. _`Apache's DirectoryIndex documentation`: http://httpd.apache.org/docs/2.0/mod/mod_dir.html
-.. _`Nginx HttpCoreModule location documentation`: http://wiki.nginx.org/HttpCoreModule#location
diff --git a/book/performance.rst b/book/performance.rst
deleted file mode 100644
index 32720e3a2e5..00000000000
--- a/book/performance.rst
+++ /dev/null
@@ -1,142 +0,0 @@
-.. index::
- single: Tests
-
-Performance
-===========
-
-Symfony2 is fast, right out of the box. Of course, if you really need speed,
-there are many ways that you can make Symfony even faster. In this chapter,
-you'll explore many of the most common and powerful ways to make your Symfony
-application even faster.
-
-.. index::
- single: Performance; Byte code cache
-
-Use a Byte Code Cache (e.g. APC)
---------------------------------
-
-One of the best (and easiest) things that you should do to improve your performance
-is to use a "byte code cache". The idea of a byte code cache is to remove
-the need to constantly recompile the PHP source code. There are a number of
-`byte code caches`_ available, some of which are open source. The most widely
-used byte code cache is probably `APC`_
-
-Using a byte code cache really has no downside, and Symfony2 has been architected
-to perform really well in this type of environment.
-
-Further Optimizations
-~~~~~~~~~~~~~~~~~~~~~
-
-Byte code caches usually monitor the source files for changes. This ensures
-that if the source of a file changes, the byte code is recompiled automatically.
-This is really convenient, but obviously adds overhead.
-
-For this reason, some byte code caches offer an option to disable these checks.
-Obviously, when disabling these checks, it will be up to the server admin
-to ensure that the cache is cleared whenever any source files change. Otherwise,
-the updates you've made won't be seen.
-
-For example, to disable these checks in APC, simply add ``apc.stat=0`` to
-your php.ini configuration.
-
-.. index::
- single: Performance; Autoloader
-
-Use Composer's Class Map Functionality
---------------------------------------
-
-By default, the Symfony2 standard edition uses Composer's autoloader
-in the `autoload.php`_ file. This autoloader is easy to use, as it will
-automatically find any new classes that you've placed in the registered
-directories.
-
-Unfortunately, this comes at a cost, as the loader iterates over all configured
-namespaces to find a particular file, making ``file_exists`` calls until it
-finally finds the file it's looking for.
-
-The simplest solution is to tell Composer to build a "class map" (i.e. a
-big array of the locations of all the classes). This can be done from the
-command line, and might become part of your deploy process:
-
-.. code-block:: bash
-
- php composer.phar dump-autoload --optimize
-
-Internally, this builds the big class map array in ``vendor/composer/autoload_classmap.php``.
-
-Caching the Autoloader with APC
--------------------------------
-
-Another solution is to to cache the location of each class after it's located
-the first time. Symfony comes with a class - :class:`Symfony\\Component\\ClassLoader\\ApcClassLoader` -
-that does exactly this. To use it, just adapt your front controller file.
-If you're using the Standard Distribution, this code should already be available
-as comments in this file::
-
- // app.php
- // ...
-
- $loader = require_once __DIR__.'/../app/bootstrap.php.cache';
-
- // Use APC for autoloading to improve performance
- // Change 'sf2' by the prefix you want in order to prevent key conflict with another application
- /*
- $loader = new ApcClassLoader('sf2', $loader);
- $loader->register(true);
- */
-
- // ...
-
-.. note::
-
- When using the APC autoloader, if you add new classes, they will be found
- automatically and everything will work the same as before (i.e. no
- reason to "clear" the cache). However, if you change the location of a
- particular namespace or prefix, you'll need to flush your APC cache. Otherwise,
- the autoloader will still be looking at the old location for all classes
- inside that namespace.
-
-.. index::
- single: Performance; Bootstrap files
-
-Use Bootstrap Files
--------------------
-
-To ensure optimal flexibility and code reuse, Symfony2 applications leverage
-a variety of classes and 3rd party components. But loading all of these classes
-from separate files on each request can result in some overhead. To reduce
-this overhead, the Symfony2 Standard Edition provides a script to generate
-a so-called `bootstrap file`_, consisting of multiple classes definitions
-in a single file. By including this file (which contains a copy of many of
-the core classes), Symfony no longer needs to include any of the source files
-containing those classes. This will reduce disc IO quite a bit.
-
-If you're using the Symfony2 Standard Edition, then you're probably already
-using the bootstrap file. To be sure, open your front controller (usually
-``app.php``) and check to make sure that the following line exists::
-
- require_once __DIR__.'/../app/bootstrap.php.cache';
-
-Note that there are two disadvantages when using a bootstrap file:
-
-* the file needs to be regenerated whenever any of the original sources change
- (i.e. when you update the Symfony2 source or vendor libraries);
-
-* when debugging, one will need to place break points inside the bootstrap file.
-
-If you're using Symfony2 Standard Edition, the bootstrap file is automatically
-rebuilt after updating the vendor libraries via the ``php composer.phar install``
-command.
-
-Bootstrap Files and Byte Code Caches
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Even when using a byte code cache, performance will improve when using a bootstrap
-file since there will be fewer files to monitor for changes. Of course if this
-feature is disabled in the byte code cache (e.g. ``apc.stat=0`` in APC), there
-is no longer a reason to use a bootstrap file.
-
-.. _`byte code caches`: http://en.wikipedia.org/wiki/List_of_PHP_accelerators
-.. _`APC`: http://php.net/manual/en/book.apc.php
-.. _`autoload.php`: https://github.com/symfony/symfony-standard/blob/master/app/autoload.php
-.. _`bootstrap file`: https://github.com/sensio/SensioDistributionBundle/blob/master/Composer/ScriptHandler.php
diff --git a/book/propel.rst b/book/propel.rst
deleted file mode 100644
index bfc51a772a1..00000000000
--- a/book/propel.rst
+++ /dev/null
@@ -1,459 +0,0 @@
-.. index::
- single: Propel
-
-Databases and Propel
-====================
-
-One of the most common and challenging tasks for any application
-involves persisting and reading information to and from a database. Symfony2
-does not come integrated with any ORMs but the Propel integration is easy.
-To install Propel, read `Working With Symfony2`_ on the Propel documentation.
-
-A Simple Example: A Product
----------------------------
-
-In this section, you'll configure your database, create a ``Product`` object,
-persist it to the database and fetch it back out.
-
-.. sidebar:: Code along with the example
-
- If you want to follow along with the example in this chapter, create an
- ``AcmeStoreBundle`` via:
-
- .. code-block:: bash
-
- $ php app/console generate:bundle --namespace=Acme/StoreBundle
-
-Configuring the Database
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-Before you can start, you'll need to configure your database connection
-information. By convention, this information is usually configured in an
-``app/config/parameters.yml`` file:
-
-.. code-block:: yaml
-
- # app/config/parameters.yml
- parameters:
- database_driver: mysql
- database_host: localhost
- database_name: test_project
- database_user: root
- database_password: password
- database_charset: UTF8
-
-.. note::
-
- Defining the configuration via ``parameters.yml`` is just a convention. The
- parameters defined in that file are referenced by the main configuration
- file when setting up Propel:
-
-These parameters defined in ``parameters.yml`` can now be included in the
-configuration file (``config.yml``):
-
-.. code-block:: yaml
-
- propel:
- dbal:
- driver: "%database_driver%"
- user: "%database_user%"
- password: "%database_password%"
- dsn: "%database_driver%:host=%database_host%;dbname=%database_name%;charset=%database_charset%"
-
-Now that Propel knows about your database, Symfony2 can create the database for
-you:
-
-.. code-block:: bash
-
- $ php app/console propel:database:create
-
-.. note::
-
- In this example, you have one configured connection, named ``default``. If
- you want to configure more than one connection, read the `PropelBundle
- configuration section`_.
-
-Creating a Model Class
-~~~~~~~~~~~~~~~~~~~~~~
-
-In the Propel world, ActiveRecord classes are known as **models** because classes
-generated by Propel contain some business logic.
-
-.. note::
-
- For people who use Symfony2 with Doctrine2, **models** are equivalent to
- **entities**.
-
-Suppose you're building an application where products need to be displayed.
-First, create a ``schema.xml`` file inside the ``Resources/config`` directory
-of your ``AcmeStoreBundle``:
-
-.. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-Building the Model
-~~~~~~~~~~~~~~~~~~
-
-After creating your ``schema.xml``, generate your model from it by running:
-
-.. code-block:: bash
-
- $ php app/console propel:model:build
-
-This generates each model class to quickly develop your application in the
-``Model/`` directory the ``AcmeStoreBundle`` bundle.
-
-Creating the Database Tables/Schema
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Now you have a usable ``Product`` class and all you need to persist it. Of
-course, you don't yet have the corresponding ``product`` table in your
-database. Fortunately, Propel can automatically create all the database tables
-needed for every known model in your application. To do this, run:
-
-.. code-block:: bash
-
- $ php app/console propel:sql:build
- $ php app/console propel:sql:insert --force
-
-Your database now has a fully-functional ``product`` table with columns that
-match the schema you've specified.
-
-.. tip::
-
- You can run the last three commands combined by using the following
- command: ``php app/console propel:build --insert-sql``.
-
-Persisting Objects to the Database
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Now that you have a ``Product`` object and corresponding ``product`` table,
-you're ready to persist data to the database. From inside a controller, this
-is pretty easy. Add the following method to the ``DefaultController`` of the
-bundle::
-
- // src/Acme/StoreBundle/Controller/DefaultController.php
-
- // ...
- use Acme\StoreBundle\Model\Product;
- use Symfony\Component\HttpFoundation\Response;
-
- public function createAction()
- {
- $product = new Product();
- $product->setName('A Foo Bar');
- $product->setPrice(19.99);
- $product->setDescription('Lorem ipsum dolor');
-
- $product->save();
-
- return new Response('Created product id '.$product->getId());
- }
-
-In this piece of code, you instantiate and work with the ``$product`` object.
-When you call the ``save()`` method on it, you persist it to the database. No
-need to use other services, the object knows how to persist itself.
-
-.. note::
-
- If you're following along with this example, you'll need to create a
- :doc:`route ` that points to this action to see it in action.
-
-Fetching Objects from the Database
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Fetching an object back from the database is even easier. For example, suppose
-you've configured a route to display a specific ``Product`` based on its ``id``
-value::
-
- // ...
- use Acme\StoreBundle\Model\ProductQuery;
-
- public function showAction($id)
- {
- $product = ProductQuery::create()
- ->findPk($id);
-
- if (!$product) {
- throw $this->createNotFoundException(
- 'No product found for id '.$id
- );
- }
-
- // ... do something, like pass the $product object into a template
- }
-
-Updating an Object
-~~~~~~~~~~~~~~~~~~
-
-Once you've fetched an object from Propel, updating it is easy. Suppose you
-have a route that maps a product id to an update action in a controller::
-
- // ...
- use Acme\StoreBundle\Model\ProductQuery;
-
- public function updateAction($id)
- {
- $product = ProductQuery::create()
- ->findPk($id);
-
- if (!$product) {
- throw $this->createNotFoundException(
- 'No product found for id '.$id
- );
- }
-
- $product->setName('New product name!');
- $product->save();
-
- return $this->redirect($this->generateUrl('homepage'));
- }
-
-Updating an object involves just three steps:
-
-#. fetching the object from Propel (line 6 - 13);
-#. modifying the object (line 15);
-#. saving it (line 16).
-
-Deleting an Object
-~~~~~~~~~~~~~~~~~~
-
-Deleting an object is very similar to updating, but requires a call to the
-``delete()`` method on the object::
-
- $product->delete();
-
-Querying for Objects
---------------------
-
-Propel provides generated ``Query`` classes to run both basic and complex queries
-without any work::
-
- \Acme\StoreBundle\Model\ProductQuery::create()->findPk($id);
-
- \Acme\StoreBundle\Model\ProductQuery::create()
- ->filterByName('Foo')
- ->findOne();
-
-Imagine that you want to query for products which cost more than 19.99, ordered
-from cheapest to most expensive. From inside a controller, do the following::
-
- $products = \Acme\StoreBundle\Model\ProductQuery::create()
- ->filterByPrice(array('min' => 19.99))
- ->orderByPrice()
- ->find();
-
-In one line, you get your products in a powerful oriented object way. No need
-to waste your time with SQL or whatever, Symfony2 offers fully object oriented
-programming and Propel respects the same philosophy by providing an awesome
-abstraction layer.
-
-If you want to reuse some queries, you can add your own methods to the
-``ProductQuery`` class::
-
- // src/Acme/StoreBundle/Model/ProductQuery.php
- class ProductQuery extends BaseProductQuery
- {
- public function filterByExpensivePrice()
- {
- return $this
- ->filterByPrice(array('min' => 1000));
- }
- }
-
-But note that Propel generates a lot of methods for you and a simple
-``findAllOrderedByName()`` can be written without any effort::
-
- \Acme\StoreBundle\Model\ProductQuery::create()
- ->orderByName()
- ->find();
-
-Relationships/Associations
---------------------------
-
-Suppose that the products in your application all belong to exactly one
-"category". In this case, you'll need a ``Category`` object and a way to relate
-a ``Product`` object to a ``Category`` object.
-
-Start by adding the ``category`` definition in your ``schema.xml``:
-
-.. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Create the classes:
-
-.. code-block:: bash
-
- $ php app/console propel:model:build
-
-Assuming you have products in your database, you don't want lose them. Thanks to
-migrations, Propel will be able to update your database without losing existing
-data.
-
-.. code-block:: bash
-
- $ php app/console propel:migration:generate-diff
- $ php app/console propel:migration:migrate
-
-Your database has been updated, you can continue to write your application.
-
-Saving Related Objects
-~~~~~~~~~~~~~~~~~~~~~~
-
-Now, try the code in action. Imagine you're inside a controller::
-
- // ...
- use Acme\StoreBundle\Model\Category;
- use Acme\StoreBundle\Model\Product;
- use Symfony\Component\HttpFoundation\Response;
-
- class DefaultController extends Controller
- {
- public function createProductAction()
- {
- $category = new Category();
- $category->setName('Main Products');
-
- $product = new Product();
- $product->setName('Foo');
- $product->setPrice(19.99);
- // relate this product to the category
- $product->setCategory($category);
-
- // save the whole
- $product->save();
-
- return new Response(
- 'Created product id: '.$product->getId().' and category id: '.$category->getId()
- );
- }
- }
-
-Now, a single row is added to both the ``category`` and product tables. The
-``product.category_id`` column for the new product is set to whatever the id is
-of the new category. Propel manages the persistence of this relationship for
-you.
-
-Fetching Related Objects
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-When you need to fetch associated objects, your workflow looks just like it did
-before. First, fetch a ``$product`` object and then access its related
-``Category``::
-
- // ...
- use Acme\StoreBundle\Model\ProductQuery;
-
- public function showAction($id)
- {
- $product = ProductQuery::create()
- ->joinWithCategory()
- ->findPk($id);
-
- $categoryName = $product->getCategory()->getName();
-
- // ...
- }
-
-Note, in the above example, only one query was made.
-
-More information on Associations
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You will find more information on relations by reading the dedicated chapter on
-`Relationships`_.
-
-Lifecycle Callbacks
--------------------
-
-Sometimes, you need to perform an action right before or after an object is
-inserted, updated, or deleted. These types of actions are known as "lifecycle"
-callbacks or "hooks", as they're callback methods that you need to execute
-during different stages of the lifecycle of an object (e.g. the object is
-inserted, updated, deleted, etc).
-
-To add a hook, just add a new method to the object class::
-
- // src/Acme/StoreBundle/Model/Product.php
-
- // ...
- class Product extends BaseProduct
- {
- public function preInsert(\PropelPDO $con = null)
- {
- // do something before the object is inserted
- }
- }
-
-Propel provides the following hooks:
-
-* ``preInsert()`` code executed before insertion of a new object
-* ``postInsert()`` code executed after insertion of a new object
-* ``preUpdate()`` code executed before update of an existing object
-* ``postUpdate()`` code executed after update of an existing object
-* ``preSave()`` code executed before saving an object (new or existing)
-* ``postSave()`` code executed after saving an object (new or existing)
-* ``preDelete()`` code executed before deleting an object
-* ``postDelete()`` code executed after deleting an object
-
-
-Behaviors
----------
-
-All bundled behaviors in Propel are working with Symfony2. To get more
-information about how to use Propel behaviors, look at the `Behaviors reference
-section`_.
-
-Commands
---------
-
-You should read the dedicated section for `Propel commands in Symfony2`_.
-
-.. _`Working With Symfony2`: http://propelorm.org/cookbook/symfony2/working-with-symfony2.html#installation
-.. _`PropelBundle configuration section`: http://propelorm.org/cookbook/symfony2/working-with-symfony2.html#configuration
-.. _`Relationships`: http://propelorm.org/documentation/04-relationships.html
-.. _`Behaviors reference section`: http://propelorm.org/documentation/#behaviors_reference
-.. _`Propel commands in Symfony2`: http://propelorm.org/cookbook/symfony2/working-with-symfony2#the_commands
diff --git a/book/routing.rst b/book/routing.rst
deleted file mode 100644
index 09ebf2d94d8..00000000000
--- a/book/routing.rst
+++ /dev/null
@@ -1,1249 +0,0 @@
-.. index::
- single: Routing
-
-Routing
-=======
-
-Beautiful URLs are an absolute must for any serious web application. This
-means leaving behind ugly URLs like ``index.php?article_id=57`` in favor
-of something like ``/read/intro-to-symfony``.
-
-Having flexibility is even more important. What if you need to change the
-URL of a page from ``/blog`` to ``/news``? How many links should you need to
-hunt down and update to make the change? If you're using Symfony's router,
-the change is simple.
-
-The Symfony2 router lets you define creative URLs that you map to different
-areas of your application. By the end of this chapter, you'll be able to:
-
-* Create complex routes that map to controllers
-* Generate URLs inside templates and controllers
-* Load routing resources from bundles (or anywhere else)
-* Debug your routes
-
-.. index::
- single: Routing; Basics
-
-Routing in Action
------------------
-
-A *route* is a map from a URL path to a controller. For example, suppose
-you want to match any URL like ``/blog/my-post`` or ``/blog/all-about-symfony``
-and send it to a controller that can look up and render that blog entry.
-The route is simple:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/routing.yml
- blog_show:
- path: /blog/{slug}
- defaults: { _controller: AcmeBlogBundle:Blog:show }
-
- .. code-block:: xml
-
-
-
-
-
-
- AcmeBlogBundle:Blog:show
-
-
-
- .. code-block:: php
-
- // app/config/routing.php
- use Symfony\Component\Routing\RouteCollection;
- use Symfony\Component\Routing\Route;
-
- $collection = new RouteCollection();
- $collection->add('blog_show', new Route('/blog/{slug}', array(
- '_controller' => 'AcmeBlogBundle:Blog:show',
- )));
-
- return $collection;
-
-.. versionadded:: 2.2
- The ``path`` option is new in Symfony2.2, ``pattern`` is used in older
- versions.
-
-The path defined by the ``blog_show`` route acts like ``/blog/*`` where
-the wildcard is given the name ``slug``. For the URL ``/blog/my-blog-post``,
-the ``slug`` variable gets a value of ``my-blog-post``, which is available
-for you to use in your controller (keep reading).
-
-The ``_controller`` parameter is a special key that tells Symfony which controller
-should be executed when a URL matches this route. The ``_controller`` string
-is called the :ref:`logical name`. It follows a
-pattern that points to a specific PHP class and method::
-
- // src/Acme/BlogBundle/Controller/BlogController.php
- namespace Acme\BlogBundle\Controller;
-
- use Symfony\Bundle\FrameworkBundle\Controller\Controller;
-
- class BlogController extends Controller
- {
- public function showAction($slug)
- {
- // use the $slug variable to query the database
- $blog = ...;
-
- return $this->render('AcmeBlogBundle:Blog:show.html.twig', array(
- 'blog' => $blog,
- ));
- }
- }
-
-Congratulations! You've just created your first route and connected it to
-a controller. Now, when you visit ``/blog/my-post``, the ``showAction`` controller
-will be executed and the ``$slug`` variable will be equal to ``my-post``.
-
-This is the goal of the Symfony2 router: to map the URL of a request to a
-controller. Along the way, you'll learn all sorts of tricks that make mapping
-even the most complex URLs easy.
-
-.. index::
- single: Routing; Under the hood
-
-Routing: Under the Hood
------------------------
-
-When a request is made to your application, it contains an address to the
-exact "resource" that the client is requesting. This address is called the
-URL, (or URI), and could be ``/contact``, ``/blog/read-me``, or anything
-else. Take the following HTTP request for example:
-
-.. code-block:: text
-
- GET /blog/my-blog-post
-
-The goal of the Symfony2 routing system is to parse this URL and determine
-which controller should be executed. The whole process looks like this:
-
-#. The request is handled by the Symfony2 front controller (e.g. ``app.php``);
-
-#. The Symfony2 core (i.e. Kernel) asks the router to inspect the request;
-
-#. The router matches the incoming URL to a specific route and returns information
- about the route, including the controller that should be executed;
-
-#. The Symfony2 Kernel executes the controller, which ultimately returns
- a ``Response`` object.
-
-.. figure:: /images/request-flow.png
- :align: center
- :alt: Symfony2 request flow
-
- The routing layer is a tool that translates the incoming URL into a specific
- controller to execute.
-
-.. index::
- single: Routing; Creating routes
-
-Creating Routes
----------------
-
-Symfony loads all the routes for your application from a single routing configuration
-file. The file is usually ``app/config/routing.yml``, but can be configured
-to be anything (including an XML or PHP file) via the application configuration
-file:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- framework:
- # ...
- router: { resource: "%kernel.root_dir%/config/routing.yml" }
-
- .. code-block:: xml
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- $container->loadFromExtension('framework', array(
- // ...
- 'router' => array('resource' => '%kernel.root_dir%/config/routing.php'),
- ));
-
-.. tip::
-
- Even though all routes are loaded from a single file, it's common practice
- to include additional routing resources. To do so, just point out in the
- main routing configuration file which external files should be included.
- See the :ref:`routing-include-external-resources` section for more
- information.
-
-Basic Route Configuration
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Defining a route is easy, and a typical application will have lots of routes.
-A basic route consists of just two parts: the ``path`` to match and a
-``defaults`` array:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- _welcome:
- path: /
- defaults: { _controller: AcmeDemoBundle:Main:homepage }
-
- .. code-block:: xml
-
-
-
-
-
-
- AcmeDemoBundle:Main:homepage
-
-
-
-
- .. code-block:: php
-
- use Symfony\Component\Routing\RouteCollection;
- use Symfony\Component\Routing\Route;
-
- $collection = new RouteCollection();
- $collection->add('_welcome', new Route('/', array(
- '_controller' => 'AcmeDemoBundle:Main:homepage',
- )));
-
- return $collection;
-
-This route matches the homepage (``/``) and maps it to the ``AcmeDemoBundle:Main:homepage``
-controller. The ``_controller`` string is translated by Symfony2 into an
-actual PHP function and executed. That process will be explained shortly
-in the :ref:`controller-string-syntax` section.
-
-.. index::
- single: Routing; Placeholders
-
-Routing with Placeholders
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Of course the routing system supports much more interesting routes. Many
-routes will contain one or more named "wildcard" placeholders:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- blog_show:
- path: /blog/{slug}
- defaults: { _controller: AcmeBlogBundle:Blog:show }
-
- .. code-block:: xml
-
-
-
-
-
-
- AcmeBlogBundle:Blog:show
-
-
-
- .. code-block:: php
-
- use Symfony\Component\Routing\RouteCollection;
- use Symfony\Component\Routing\Route;
-
- $collection = new RouteCollection();
- $collection->add('blog_show', new Route('/blog/{slug}', array(
- '_controller' => 'AcmeBlogBundle:Blog:show',
- )));
-
- return $collection;
-
-The path will match anything that looks like ``/blog/*``. Even better,
-the value matching the ``{slug}`` placeholder will be available inside your
-controller. In other words, if the URL is ``/blog/hello-world``, a ``$slug``
-variable, with a value of ``hello-world``, will be available in the controller.
-This can be used, for example, to load the blog post matching that string.
-
-The path will *not*, however, match simply ``/blog``. That's because,
-by default, all placeholders are required. This can be changed by adding
-a placeholder value to the ``defaults`` array.
-
-Required and Optional Placeholders
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To make things more exciting, add a new route that displays a list of all
-the available blog posts for this imaginary blog application:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- blog:
- path: /blog
- defaults: { _controller: AcmeBlogBundle:Blog:index }
-
- .. code-block:: xml
-
-
-
-
-
-
- AcmeBlogBundle:Blog:index
-
-
-
- .. code-block:: php
-
- use Symfony\Component\Routing\RouteCollection;
- use Symfony\Component\Routing\Route;
-
- $collection = new RouteCollection();
- $collection->add('blog', new Route('/blog', array(
- '_controller' => 'AcmeBlogBundle:Blog:index',
- )));
-
- return $collection;
-
-So far, this route is as simple as possible - it contains no placeholders
-and will only match the exact URL ``/blog``. But what if you need this route
-to support pagination, where ``/blog/2`` displays the second page of blog
-entries? Update the route to have a new ``{page}`` placeholder:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- blog:
- path: /blog/{page}
- defaults: { _controller: AcmeBlogBundle:Blog:index }
-
- .. code-block:: xml
-
-
-
-
-
-
- AcmeBlogBundle:Blog:index
-
-
-
- .. code-block:: php
-
- use Symfony\Component\Routing\RouteCollection;
- use Symfony\Component\Routing\Route;
-
- $collection = new RouteCollection();
- $collection->add('blog', new Route('/blog/{page}', array(
- '_controller' => 'AcmeBlogBundle:Blog:index',
- )));
-
- return $collection;
-
-Like the ``{slug}`` placeholder before, the value matching ``{page}`` will
-be available inside your controller. Its value can be used to determine which
-set of blog posts to display for the given page.
-
-But hold on! Since placeholders are required by default, this route will
-no longer match on simply ``/blog``. Instead, to see page 1 of the blog,
-you'd need to use the URL ``/blog/1``! Since that's no way for a rich web
-app to behave, modify the route to make the ``{page}`` parameter optional.
-This is done by including it in the ``defaults`` collection:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- blog:
- path: /blog/{page}
- defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }
-
- .. code-block:: xml
-
-
-
-
-
-
- AcmeBlogBundle:Blog:index
- 1
-
-
-
- .. code-block:: php
-
- use Symfony\Component\Routing\RouteCollection;
- use Symfony\Component\Routing\Route;
-
- $collection = new RouteCollection();
- $collection->add('blog', new Route('/blog/{page}', array(
- '_controller' => 'AcmeBlogBundle:Blog:index',
- 'page' => 1,
- )));
-
- return $collection;
-
-By adding ``page`` to the ``defaults`` key, the ``{page}`` placeholder is no
-longer required. The URL ``/blog`` will match this route and the value of
-the ``page`` parameter will be set to ``1``. The URL ``/blog/2`` will also
-match, giving the ``page`` parameter a value of ``2``. Perfect.
-
-+---------+------------+
-| /blog | {page} = 1 |
-+---------+------------+
-| /blog/1 | {page} = 1 |
-+---------+------------+
-| /blog/2 | {page} = 2 |
-+---------+------------+
-
-.. tip::
-
- Routes with optional parameters at the end will not match on requests
- with a trailing slash (i.e. ``/blog/`` will not match, ``/blog`` will match).
-
-.. index::
- single: Routing; Requirements
-
-Adding Requirements
-~~~~~~~~~~~~~~~~~~~
-
-Take a quick look at the routes that have been created so far:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- blog:
- path: /blog/{page}
- defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }
-
- blog_show:
- path: /blog/{slug}
- defaults: { _controller: AcmeBlogBundle:Blog:show }
-
- .. code-block:: xml
-
-
-
-
-
-
- AcmeBlogBundle:Blog:index
- 1
-
-
-
- AcmeBlogBundle:Blog:show
-
-
-
- .. code-block:: php
-
- use Symfony\Component\Routing\RouteCollection;
- use Symfony\Component\Routing\Route;
-
- $collection = new RouteCollection();
- $collection->add('blog', new Route('/blog/{page}', array(
- '_controller' => 'AcmeBlogBundle:Blog:index',
- 'page' => 1,
- )));
-
- $collection->add('blog_show', new Route('/blog/{show}', array(
- '_controller' => 'AcmeBlogBundle:Blog:show',
- )));
-
- return $collection;
-
-Can you spot the problem? Notice that both routes have patterns that match
-URL's that look like ``/blog/*``. The Symfony router will always choose the
-**first** matching route it finds. In other words, the ``blog_show`` route
-will *never* be matched. Instead, a URL like ``/blog/my-blog-post`` will match
-the first route (``blog``) and return a nonsense value of ``my-blog-post``
-to the ``{page}`` parameter.
-
-+--------------------+-------+-----------------------+
-| URL | route | parameters |
-+====================+=======+=======================+
-| /blog/2 | blog | {page} = 2 |
-+--------------------+-------+-----------------------+
-| /blog/my-blog-post | blog | {page} = my-blog-post |
-+--------------------+-------+-----------------------+
-
-The answer to the problem is to add route *requirements*. The routes in this
-example would work perfectly if the ``/blog/{page}`` path *only* matched
-URLs where the ``{page}`` portion is an integer. Fortunately, regular expression
-requirements can easily be added for each parameter. For example:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- blog:
- path: /blog/{page}
- defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }
- requirements:
- page: \d+
-
- .. code-block:: xml
-
-
-
-
-
-
- AcmeBlogBundle:Blog:index
- 1
- \d+
-
-
-
- .. code-block:: php
-
- use Symfony\Component\Routing\RouteCollection;
- use Symfony\Component\Routing\Route;
-
- $collection = new RouteCollection();
- $collection->add('blog', new Route('/blog/{page}', array(
- '_controller' => 'AcmeBlogBundle:Blog:index',
- 'page' => 1,
- ), array(
- 'page' => '\d+',
- )));
-
- return $collection;
-
-The ``\d+`` requirement is a regular expression that says that the value of
-the ``{page}`` parameter must be a digit (i.e. a number). The ``blog`` route
-will still match on a URL like ``/blog/2`` (because 2 is a number), but it
-will no longer match a URL like ``/blog/my-blog-post`` (because ``my-blog-post``
-is *not* a number).
-
-As a result, a URL like ``/blog/my-blog-post`` will now properly match the
-``blog_show`` route.
-
-+--------------------+-----------+-----------------------+
-| URL | route | parameters |
-+====================+===========+=======================+
-| /blog/2 | blog | {page} = 2 |
-+--------------------+-----------+-----------------------+
-| /blog/my-blog-post | blog_show | {slug} = my-blog-post |
-+--------------------+-----------+-----------------------+
-
-.. sidebar:: Earlier Routes always Win
-
- What this all means is that the order of the routes is very important.
- If the ``blog_show`` route were placed above the ``blog`` route, the
- URL ``/blog/2`` would match ``blog_show`` instead of ``blog`` since the
- ``{slug}`` parameter of ``blog_show`` has no requirements. By using proper
- ordering and clever requirements, you can accomplish just about anything.
-
-Since the parameter requirements are regular expressions, the complexity
-and flexibility of each requirement is entirely up to you. Suppose the homepage
-of your application is available in two different languages, based on the
-URL:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- homepage:
- path: /{culture}
- defaults: { _controller: AcmeDemoBundle:Main:homepage, culture: en }
- requirements:
- culture: en|fr
-
- .. code-block:: xml
-
-
-
-
-
-
- AcmeDemoBundle:Main:homepage
- en
- en|fr
-
-
-
- .. code-block:: php
-
- use Symfony\Component\Routing\RouteCollection;
- use Symfony\Component\Routing\Route;
-
- $collection = new RouteCollection();
- $collection->add('homepage', new Route('/{culture}', array(
- '_controller' => 'AcmeDemoBundle:Main:homepage',
- 'culture' => 'en',
- ), array(
- 'culture' => 'en|fr',
- )));
-
- return $collection;
-
-For incoming requests, the ``{culture}`` portion of the URL is matched against
-the regular expression ``(en|fr)``.
-
-+-----+--------------------------+
-| / | {culture} = en |
-+-----+--------------------------+
-| /en | {culture} = en |
-+-----+--------------------------+
-| /fr | {culture} = fr |
-+-----+--------------------------+
-| /es | *won't match this route* |
-+-----+--------------------------+
-
-.. index::
- single: Routing; Method requirement
-
-Adding HTTP Method Requirements
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-In addition to the URL, you can also match on the *method* of the incoming
-request (i.e. GET, HEAD, POST, PUT, DELETE). Suppose you have a contact form
-with two controllers - one for displaying the form (on a GET request) and one
-for processing the form when it's submitted (on a POST request). This can
-be accomplished with the following route configuration:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- contact:
- path: /contact
- defaults: { _controller: AcmeDemoBundle:Main:contact }
- methods: [GET]
-
- contact_process:
- path: /contact
- defaults: { _controller: AcmeDemoBundle:Main:contactProcess }
- methods: [POST]
-
- .. code-block:: xml
-
-
-
-
-
-
- AcmeDemoBundle:Main:contact
-
-
-
- AcmeDemoBundle:Main:contactProcess
-
-
-
- .. code-block:: php
-
- use Symfony\Component\Routing\RouteCollection;
- use Symfony\Component\Routing\Route;
-
- $collection = new RouteCollection();
- $collection->add('contact', new Route('/contact', array(
- '_controller' => 'AcmeDemoBundle:Main:contact',
- ), array(), array(), '', array(), array('GET')));
-
- $collection->add('contact_process', new Route('/contact', array(
- '_controller' => 'AcmeDemoBundle:Main:contactProcess',
- ), array(), array(), '', array(), array('POST')));
-
- return $collection;
-
-.. versionadded:: 2.2
- The ``methods`` option is added in Symfony2.2. Use the ``_method``
- requirement in older versions.
-
-Despite the fact that these two routes have identical paths (``/contact``),
-the first route will match only GET requests and the second route will match
-only POST requests. This means that you can display the form and submit the
-form via the same URL, while using distinct controllers for the two actions.
-
-.. note::
-
- If no ``methods`` are specified, the route will match on *all* methods.
-
-Adding a Host
-~~~~~~~~~~~~~
-
-.. versionadded:: 2.2
- Host matching support was added in Symfony 2.2
-
-You can also match on the HTTP *host* of the incoming request. For more
-information, see :doc:`/components/routing/hostname_pattern` in the Routing
-component documentation.
-
-.. index::
- single: Routing; Advanced example
- single: Routing; _format parameter
-
-.. _advanced-routing-example:
-
-Advanced Routing Example
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-At this point, you have everything you need to create a powerful routing
-structure in Symfony. The following is an example of just how flexible the
-routing system can be:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- article_show:
- path: /articles/{culture}/{year}/{title}.{_format}
- defaults: { _controller: AcmeDemoBundle:Article:show, _format: html }
- requirements:
- culture: en|fr
- _format: html|rss
- year: \d+
-
- .. code-block:: xml
-
-
-
-
-
-
- AcmeDemoBundle:Article:show
- html
- en|fr
- html|rss
- \d+
-
-
-
- .. code-block:: php
-
- use Symfony\Component\Routing\RouteCollection;
- use Symfony\Component\Routing\Route;
-
- $collection = new RouteCollection();
- $collection->add('homepage', new Route('/articles/{culture}/{year}/{title}.{_format}', array(
- '_controller' => 'AcmeDemoBundle:Article:show',
- '_format' => 'html',
- ), array(
- 'culture' => 'en|fr',
- '_format' => 'html|rss',
- 'year' => '\d+',
- )));
-
- return $collection;
-
-As you've seen, this route will only match if the ``{culture}`` portion of
-the URL is either ``en`` or ``fr`` and if the ``{year}`` is a number. This
-route also shows how you can use a dot between placeholders instead of
-a slash. URLs matching this route might look like:
-
-* ``/articles/en/2010/my-post``
-* ``/articles/fr/2010/my-post.rss``
-* ``/articles/en/2013/my-latest-post.html``
-
-.. _book-routing-format-param:
-
-.. sidebar:: The Special ``_format`` Routing Parameter
-
- This example also highlights the special ``_format`` routing parameter.
- When using this parameter, the matched value becomes the "request format"
- of the ``Request`` object. Ultimately, the request format is used for such
- things such as setting the ``Content-Type`` of the response (e.g. a ``json``
- request format translates into a ``Content-Type`` of ``application/json``).
- It can also be used in the controller to render a different template for
- each value of ``_format``. The ``_format`` parameter is a very powerful way
- to render the same content in different formats.
-
-.. note::
-
- Sometimes you want to make certain parts of your routes globally configurable.
- Symfony2.1 provides you with a way to do this by leveraging service container
- parameters. Read more about this in ":doc:`/cookbook/routing/service_container_parameters`.
-
-Special Routing Parameters
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-As you've seen, each routing parameter or default value is eventually available
-as an argument in the controller method. Additionally, there are three parameters
-that are special: each adds a unique piece of functionality inside your application:
-
-* ``_controller``: As you've seen, this parameter is used to determine which
- controller is executed when the route is matched;
-
-* ``_format``: Used to set the request format (:ref:`read more`);
-
-* ``_locale``: Used to set the locale on the request (:ref:`read more`);
-
-.. tip::
-
- If you use the ``_locale`` parameter in a route, that value will also
- be stored on the session so that subsequent requests keep this same locale.
-
-.. index::
- single: Routing; Controllers
- single: Controller; String naming format
-
-.. _controller-string-syntax:
-
-Controller Naming Pattern
--------------------------
-
-Every route must have a ``_controller`` parameter, which dictates which
-controller should be executed when that route is matched. This parameter
-uses a simple string pattern called the *logical controller name*, which
-Symfony maps to a specific PHP method and class. The pattern has three parts,
-each separated by a colon:
-
- **bundle**:**controller**:**action**
-
-For example, a ``_controller`` value of ``AcmeBlogBundle:Blog:show`` means:
-
-+----------------+------------------+-------------+
-| Bundle | Controller Class | Method Name |
-+================+==================+=============+
-| AcmeBlogBundle | BlogController | showAction |
-+----------------+------------------+-------------+
-
-The controller might look like this::
-
- // src/Acme/BlogBundle/Controller/BlogController.php
- namespace Acme\BlogBundle\Controller;
-
- use Symfony\Bundle\FrameworkBundle\Controller\Controller;
-
- class BlogController extends Controller
- {
- public function showAction($slug)
- {
- // ...
- }
- }
-
-Notice that Symfony adds the string ``Controller`` to the class name (``Blog``
-=> ``BlogController``) and ``Action`` to the method name (``show`` => ``showAction``).
-
-You could also refer to this controller using its fully-qualified class name
-and method: ``Acme\BlogBundle\Controller\BlogController::showAction``.
-But if you follow some simple conventions, the logical name is more concise
-and allows more flexibility.
-
-.. note::
-
- In addition to using the logical name or the fully-qualified class name,
- Symfony supports a third way of referring to a controller. This method
- uses just one colon separator (e.g. ``service_name:indexAction``) and
- refers to the controller as a service (see :doc:`/cookbook/controller/service`).
-
-Route Parameters and Controller Arguments
------------------------------------------
-
-The route parameters (e.g. ``{slug}``) are especially important because
-each is made available as an argument to the controller method::
-
- public function showAction($slug)
- {
- // ...
- }
-
-In reality, the entire ``defaults`` collection is merged with the parameter
-values to form a single array. Each key of that array is available as an
-argument on the controller.
-
-In other words, for each argument of your controller method, Symfony looks
-for a route parameter of that name and assigns its value to that argument.
-In the advanced example above, any combination (in any order) of the following
-variables could be used as arguments to the ``showAction()`` method:
-
-* ``$culture``
-* ``$year``
-* ``$title``
-* ``$_format``
-* ``$_controller``
-
-Since the placeholders and ``defaults`` collection are merged together, even
-the ``$_controller`` variable is available. For a more detailed discussion,
-see :ref:`route-parameters-controller-arguments`.
-
-.. tip::
-
- You can also use a special ``$_route`` variable, which is set to the
- name of the route that was matched.
-
-.. index::
- single: Routing; Importing routing resources
-
-.. _routing-include-external-resources:
-
-Including External Routing Resources
-------------------------------------
-
-All routes are loaded via a single configuration file - usually ``app/config/routing.yml``
-(see `Creating Routes`_ above). Commonly, however, you'll want to load routes
-from other places, like a routing file that lives inside a bundle. This can
-be done by "importing" that file:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/routing.yml
- acme_hello:
- resource: "@AcmeHelloBundle/Resources/config/routing.yml"
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/routing.php
- use Symfony\Component\Routing\RouteCollection;
-
- $collection = new RouteCollection();
- $collection->addCollection($loader->import("@AcmeHelloBundle/Resources/config/routing.php"));
-
- return $collection;
-
-.. note::
-
- When importing resources from YAML, the key (e.g. ``acme_hello``) is meaningless.
- Just be sure that it's unique so no other lines override it.
-
-The ``resource`` key loads the given routing resource. In this example the
-resource is the full path to a file, where the ``@AcmeHelloBundle`` shortcut
-syntax resolves to the path of that bundle. The imported file might look
-like this:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # src/Acme/HelloBundle/Resources/config/routing.yml
- acme_hello:
- path: /hello/{name}
- defaults: { _controller: AcmeHelloBundle:Hello:index }
-
- .. code-block:: xml
-
-
-
-
-
-
-
- AcmeHelloBundle:Hello:index
-
-
-
- .. code-block:: php
-
- // src/Acme/HelloBundle/Resources/config/routing.php
- use Symfony\Component\Routing\RouteCollection;
- use Symfony\Component\Routing\Route;
-
- $collection = new RouteCollection();
- $collection->add('acme_hello', new Route('/hello/{name}', array(
- '_controller' => 'AcmeHelloBundle:Hello:index',
- )));
-
- return $collection;
-
-The routes from this file are parsed and loaded in the same way as the main
-routing file.
-
-Prefixing Imported Routes
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You can also choose to provide a "prefix" for the imported routes. For example,
-suppose you want the ``acme_hello`` route to have a final path of ``/admin/hello/{name}``
-instead of simply ``/hello/{name}``:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/routing.yml
- acme_hello:
- resource: "@AcmeHelloBundle/Resources/config/routing.yml"
- prefix: /admin
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/routing.php
- use Symfony\Component\Routing\RouteCollection;
-
- $collection = new RouteCollection();
- $collection->addCollection($loader->import("@AcmeHelloBundle/Resources/config/routing.php"), '/admin');
-
- return $collection;
-
-The string ``/admin`` will now be prepended to the path of each route loaded
-from the new routing resource.
-
-.. tip::
-
- You can also define routes using annotations. See the
- :doc:`FrameworkExtraBundle documentation`
- to see how.
-
-Adding a Host regex to Imported Routes
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. versionadded:: 2.2
- Host matching support was added in Symfony 2.2
-
-You can set the host regex on imported routes. For more information, see
-:ref:`component-routing-host-imported`.
-
-.. index::
- single: Routing; Debugging
-
-Visualizing & Debugging Routes
-------------------------------
-
-While adding and customizing routes, it's helpful to be able to visualize
-and get detailed information about your routes. A great way to see every route
-in your application is via the ``router:debug`` console command. Execute
-the command by running the following from the root of your project.
-
-.. code-block:: bash
-
- $ php app/console router:debug
-
-This command will print a helpful list of *all* the configured routes in
-your application:
-
-.. code-block:: text
-
- homepage ANY /
- contact GET /contact
- contact_process POST /contact
- article_show ANY /articles/{culture}/{year}/{title}.{_format}
- blog ANY /blog/{page}
- blog_show ANY /blog/{slug}
-
-You can also get very specific information on a single route by including
-the route name after the command:
-
-.. code-block:: bash
-
- $ php app/console router:debug article_show
-
-Likewise, if you want to test whether a URL matches a given route, you can
-use the ``router:match`` console command:
-
-.. code-block:: bash
-
- $ php app/console router:match /blog/my-latest-post
-
-This command will print which route the URL matches.
-
-.. code-block:: text
-
- Route "blog_show" matches
-
-.. index::
- single: Routing; Generating URLs
-
-Generating URLs
----------------
-
-The routing system should also be used to generate URLs. In reality, routing
-is a bi-directional system: mapping the URL to a controller+parameters and
-a route+parameters back to a URL. The
-:method:`Symfony\\Component\\Routing\\Router::match` and
-:method:`Symfony\\Component\\Routing\\Router::generate` methods form this bi-directional
-system. Take the ``blog_show`` example route from earlier::
-
- $params = $this->get('router')->match('/blog/my-blog-post');
- // array(
- // 'slug' => 'my-blog-post',
- // '_controller' => 'AcmeBlogBundle:Blog:show',
- // )
-
- $uri = $this->get('router')->generate('blog_show', array('slug' => 'my-blog-post'));
- // /blog/my-blog-post
-
-To generate a URL, you need to specify the name of the route (e.g. ``blog_show``)
-and any wildcards (e.g. ``slug = my-blog-post``) used in the path for that
-route. With this information, any URL can easily be generated::
-
- class MainController extends Controller
- {
- public function showAction($slug)
- {
- // ...
-
- $url = $this->generateUrl(
- 'blog_show',
- array('slug' => 'my-blog-post')
- );
- }
- }
-
-.. note::
-
- In controllers that extend Symfony's base
- :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller`,
- you can use the
- :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::generateUrl`
- method, which call's the router service's
- :method:`Symfony\\Component\\Routing\\Router::generate` method.
-
-In an upcoming section, you'll learn how to generate URLs from inside templates.
-
-.. tip::
-
- If the frontend of your application uses AJAX requests, you might want
- to be able to generate URLs in JavaScript based on your routing configuration.
- By using the `FOSJsRoutingBundle`_, you can do exactly that:
-
- .. code-block:: javascript
-
- var url = Routing.generate(
- 'blog_show',
- {"slug": 'my-blog-post'}
- );
-
- For more information, see the documentation for that bundle.
-
-.. index::
- single: Routing; Absolute URLs
-
-Generating Absolute URLs
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-By default, the router will generate relative URLs (e.g. ``/blog``). To generate
-an absolute URL, simply pass ``true`` to the third argument of the ``generate()``
-method::
-
- $router->generate('blog_show', array('slug' => 'my-blog-post'), true);
- // http://www.example.com/blog/my-blog-post
-
-.. note::
-
- The host that's used when generating an absolute URL is the host of
- the current ``Request`` object. This is detected automatically based
- on server information supplied by PHP. When generating absolute URLs for
- scripts run from the command line, you'll need to manually set the desired
- host on the ``RequestContext`` object::
-
- $router->getContext()->setHost('www.example.com');
-
-.. index::
- single: Routing; Generating URLs in a template
-
-Generating URLs with Query Strings
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The ``generate`` method takes an array of wildcard values to generate the URI.
-But if you pass extra ones, they will be added to the URI as a query string::
-
- $router->generate('blog', array('page' => 2, 'category' => 'Symfony'));
- // /blog/2?category=Symfony
-
-Generating URLs from a template
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The most common place to generate a URL is from within a template when linking
-between pages in your application. This is done just as before, but using
-a template helper function:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
-
- Read this blog post.
-
-
- .. code-block:: html+php
-
-
- Read this blog post.
-
-
-Absolute URLs can also be generated.
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
-
- Read this blog post.
-
-
- .. code-block:: html+php
-
-
- Read this blog post.
-
-
-Summary
--------
-
-Routing is a system for mapping the URL of incoming requests to the controller
-function that should be called to process the request. It both allows you
-to specify beautiful URLs and keeps the functionality of your application
-decoupled from those URLs. Routing is a two-way mechanism, meaning that it
-should also be used to generate URLs.
-
-Learn more from the Cookbook
-----------------------------
-
-* :doc:`/cookbook/routing/scheme`
-
-.. _`FOSJsRoutingBundle`: https://github.com/FriendsOfSymfony/FOSJsRoutingBundle
diff --git a/book/security.rst b/book/security.rst
deleted file mode 100644
index 7d9ecf0ef63..00000000000
--- a/book/security.rst
+++ /dev/null
@@ -1,2054 +0,0 @@
-.. index::
- single: Security
-
-Security
-========
-
-Security is a two-step process whose goal is to prevent a user from accessing
-a resource that he/she should not have access to.
-
-In the first step of the process, the security system identifies who the user
-is by requiring the user to submit some sort of identification. This is called
-**authentication**, and it means that the system is trying to find out who
-you are.
-
-Once the system knows who you are, the next step is to determine if you should
-have access to a given resource. This part of the process is called **authorization**,
-and it means that the system is checking to see if you have privileges to
-perform a certain action.
-
-.. image:: /images/book/security_authentication_authorization.png
- :align: center
-
-Since the best way to learn is to see an example, start by securing your
-application with HTTP Basic authentication.
-
-.. note::
-
- `Symfony's security component`_ is available as a standalone PHP library
- for use inside any PHP project.
-
-Basic Example: HTTP Authentication
-----------------------------------
-
-The security component can be configured via your application configuration.
-In fact, most standard security setups are just a matter of using the right
-configuration. The following configuration tells Symfony to secure any URL
-matching ``/admin/*`` and to ask the user for credentials using basic HTTP
-authentication (i.e. the old-school username/password box):
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/security.yml
- security:
- firewalls:
- secured_area:
- pattern: ^/
- anonymous: ~
- http_basic:
- realm: "Secured Demo Area"
-
- access_control:
- - { path: ^/admin, roles: ROLE_ADMIN }
-
- providers:
- in_memory:
- memory:
- users:
- ryan: { password: ryanpass, roles: 'ROLE_USER' }
- admin: { password: kitten, roles: 'ROLE_ADMIN' }
-
- encoders:
- Symfony\Component\Security\Core\User\User: plaintext
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/security.php
- $container->loadFromExtension('security', array(
- 'firewalls' => array(
- 'secured_area' => array(
- 'pattern' => '^/',
- 'anonymous' => array(),
- 'http_basic' => array(
- 'realm' => 'Secured Demo Area',
- ),
- ),
- ),
- 'access_control' => array(
- array('path' => '^/admin', 'role' => 'ROLE_ADMIN'),
- ),
- 'providers' => array(
- 'in_memory' => array(
- 'memory' => array(
- 'users' => array(
- 'ryan' => array('password' => 'ryanpass', 'roles' => 'ROLE_USER'),
- 'admin' => array('password' => 'kitten', 'roles' => 'ROLE_ADMIN'),
- ),
- ),
- ),
- ),
- 'encoders' => array(
- 'Symfony\Component\Security\Core\User\User' => 'plaintext',
- ),
- ));
-
-.. tip::
-
- A standard Symfony distribution separates the security configuration
- into a separate file (e.g. ``app/config/security.yml``). If you don't
- have a separate security file, you can put the configuration directly
- into your main config file (e.g. ``app/config/config.yml``).
-
-The end result of this configuration is a fully-functional security system
-that looks like the following:
-
-* There are two users in the system (``ryan`` and ``admin``);
-* Users authenticate themselves via the basic HTTP authentication prompt;
-* Any URL matching ``/admin/*`` is secured, and only the ``admin`` user
- can access it;
-* All URLs *not* matching ``/admin/*`` are accessible by all users (and the
- user is never prompted to login).
-
-Let's look briefly at how security works and how each part of the configuration
-comes into play.
-
-How Security Works: Authentication and Authorization
-----------------------------------------------------
-
-Symfony's security system works by determining who a user is (i.e. authentication)
-and then checking to see if that user should have access to a specific resource
-or URL.
-
-.. _book-security-firewalls:
-
-Firewalls (Authentication)
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-When a user makes a request to a URL that's protected by a firewall, the
-security system is activated. The job of the firewall is to determine whether
-or not the user needs to be authenticated, and if he does, to send a response
-back to the user initiating the authentication process.
-
-A firewall is activated when the URL of an incoming request matches the configured
-firewall's regular expression ``pattern`` config value. In this example, the
-``pattern`` (``^/``) will match *every* incoming request. The fact that the
-firewall is activated does *not* mean, however, that the HTTP authentication
-username and password box is displayed for every URL. For example, any user
-can access ``/foo`` without being prompted to authenticate.
-
-.. image:: /images/book/security_anonymous_user_access.png
- :align: center
-
-This works first because the firewall allows *anonymous users* via the ``anonymous``
-configuration parameter. In other words, the firewall doesn't require the
-user to fully authenticate immediately. And because no special ``role`` is
-needed to access ``/foo`` (under the ``access_control`` section), the request
-can be fulfilled without ever asking the user to authenticate.
-
-If you remove the ``anonymous`` key, the firewall will *always* make a user
-fully authenticate immediately.
-
-Access Controls (Authorization)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-If a user requests ``/admin/foo``, however, the process behaves differently.
-This is because of the ``access_control`` configuration section that says
-that any URL matching the regular expression pattern ``^/admin`` (i.e. ``/admin``
-or anything matching ``/admin/*``) requires the ``ROLE_ADMIN`` role. Roles
-are the basis for most authorization: a user can access ``/admin/foo`` only
-if it has the ``ROLE_ADMIN`` role.
-
-.. image:: /images/book/security_anonymous_user_denied_authorization.png
- :align: center
-
-Like before, when the user originally makes the request, the firewall doesn't
-ask for any identification. However, as soon as the access control layer
-denies the user access (because the anonymous user doesn't have the ``ROLE_ADMIN``
-role), the firewall jumps into action and initiates the authentication process.
-The authentication process depends on the authentication mechanism you're
-using. For example, if you're using the form login authentication method,
-the user will be redirected to the login page. If you're using HTTP authentication,
-the user will be sent an HTTP 401 response so that the user sees the username
-and password box.
-
-The user now has the opportunity to submit its credentials back to the application.
-If the credentials are valid, the original request can be re-tried.
-
-.. image:: /images/book/security_ryan_no_role_admin_access.png
- :align: center
-
-In this example, the user ``ryan`` successfully authenticates with the firewall.
-But since ``ryan`` doesn't have the ``ROLE_ADMIN`` role, he's still denied
-access to ``/admin/foo``. Ultimately, this means that the user will see some
-sort of message indicating that access has been denied.
-
-.. tip::
-
- When Symfony denies the user access, the user sees an error screen and
- receives a 403 HTTP status code (``Forbidden``). You can customize the
- access denied error screen by following the directions in the
- :ref:`Error Pages` cookbook entry
- to customize the 403 error page.
-
-Finally, if the ``admin`` user requests ``/admin/foo``, a similar process
-takes place, except now, after being authenticated, the access control layer
-will let the request pass through:
-
-.. image:: /images/book/security_admin_role_access.png
- :align: center
-
-The request flow when a user requests a protected resource is straightforward,
-but incredibly flexible. As you'll see later, authentication can be handled
-in any number of ways, including via a form login, X.509 certificate, or by
-authenticating the user via Twitter. Regardless of the authentication method,
-the request flow is always the same:
-
-#. A user accesses a protected resource;
-#. The application redirects the user to the login form;
-#. The user submits its credentials (e.g. username/password);
-#. The firewall authenticates the user;
-#. The authenticated user re-tries the original request.
-
-.. note::
-
- The *exact* process actually depends a little bit on which authentication
- mechanism you're using. For example, when using form login, the user
- submits its credentials to one URL that processes the form (e.g. ``/login_check``)
- and then is redirected back to the originally requested URL (e.g. ``/admin/foo``).
- But with HTTP authentication, the user submits its credentials directly
- to the original URL (e.g. ``/admin/foo``) and then the page is returned
- to the user in that same request (i.e. no redirect).
-
- These types of idiosyncrasies shouldn't cause you any problems, but they're
- good to keep in mind.
-
-.. tip::
-
- You'll also learn later how *anything* can be secured in Symfony2, including
- specific controllers, objects, or even PHP methods.
-
-.. _book-security-form-login:
-
-Using a Traditional Login Form
-------------------------------
-
-.. tip::
-
- In this section, you'll learn how to create a basic login form that continues
- to use the hard-coded users that are defined in the ``security.yml`` file.
-
- To load users from the database, please read :doc:`/cookbook/security/entity_provider`.
- By reading that article and this section, you can create a full login form
- system that loads users from the database.
-
-So far, you've seen how to blanket your application beneath a firewall and
-then protect access to certain areas with roles. By using HTTP Authentication,
-you can effortlessly tap into the native username/password box offered by
-all browsers. However, Symfony supports many authentication mechanisms out
-of the box. For details on all of them, see the
-:doc:`Security Configuration Reference`.
-
-In this section, you'll enhance this process by allowing the user to authenticate
-via a traditional HTML login form.
-
-First, enable form login under your firewall:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/security.yml
- security:
- firewalls:
- secured_area:
- pattern: ^/
- anonymous: ~
- form_login:
- login_path: login
- check_path: login_check
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/security.php
- $container->loadFromExtension('security', array(
- 'firewalls' => array(
- 'secured_area' => array(
- 'pattern' => '^/',
- 'anonymous' => array(),
- 'form_login' => array(
- 'login_path' => 'login',
- 'check_path' => 'login_check',
- ),
- ),
- ),
- ));
-
-.. tip::
-
- If you don't need to customize your ``login_path`` or ``check_path``
- values (the values used here are the default values), you can shorten
- your configuration:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- form_login: ~
-
- .. code-block:: xml
-
-
-
- .. code-block:: php
-
- 'form_login' => array(),
-
-Now, when the security system initiates the authentication process, it will
-redirect the user to the login form (``/login`` by default). Implementing this
-login form visually is your job. First, the create two routes we used in the
-security configuration: the ``login`` route will display the login form (i.e.
-``/login``) and the ``login_check`` route will handle the login form
-submission (i.e. ``/login_check``):
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/routing.yml
- login:
- pattern: /login
- defaults: { _controller: AcmeSecurityBundle:Security:login }
- login_check:
- pattern: /login_check
-
- .. code-block:: xml
-
-
-
-
-
-
-
- AcmeSecurityBundle:Security:login
-
-
-
-
-
- .. code-block:: php
-
- // app/config/routing.php
- use Symfony\Component\Routing\RouteCollection;
- use Symfony\Component\Routing\Route;
-
- $collection = new RouteCollection();
- $collection->add('login', new Route('/login', array(
- '_controller' => 'AcmeDemoBundle:Security:login',
- )));
- $collection->add('login_check', new Route('/login_check', array()));
-
- return $collection;
-
-.. note::
-
- You will *not* need to implement a controller for the ``/login_check``
- URL as the firewall will automatically catch and process any form submitted
- to this URL.
-
-.. versionadded:: 2.1
- As of Symfony 2.1, you *must* have routes configured for your ``login_path``,
- ``check_path`` ``logout`` keys. These keys can be route names (as shown
- in this example) or URLs that have routes configured for them.
-
-Notice that the name of the ``login`` route matches the``login_path`` config
-value, as that's where the security system will redirect users that need
-to login.
-
-Next, create the controller that will display the login form::
-
- // src/Acme/SecurityBundle/Controller/SecurityController.php;
- namespace Acme\SecurityBundle\Controller;
-
- use Symfony\Bundle\FrameworkBundle\Controller\Controller;
- use Symfony\Component\Security\Core\SecurityContext;
-
- class SecurityController extends Controller
- {
- public function loginAction()
- {
- $request = $this->getRequest();
- $session = $request->getSession();
-
- // get the login error if there is one
- if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {
- $error = $request->attributes->get(
- SecurityContext::AUTHENTICATION_ERROR
- );
- } else {
- $error = $session->get(SecurityContext::AUTHENTICATION_ERROR);
- $session->remove(SecurityContext::AUTHENTICATION_ERROR);
- }
-
- return $this->render(
- 'AcmeSecurityBundle:Security:login.html.twig',
- array(
- // last username entered by the user
- 'last_username' => $session->get(SecurityContext::LAST_USERNAME),
- 'error' => $error,
- )
- );
- }
- }
-
-Don't let this controller confuse you. As you'll see in a moment, when the
-user submits the form, the security system automatically handles the form
-submission for you. If the user had submitted an invalid username or password,
-this controller reads the form submission error from the security system so
-that it can be displayed back to the user.
-
-In other words, your job is to display the login form and any login errors
-that may have occurred, but the security system itself takes care of checking
-the submitted username and password and authenticating the user.
-
-Finally, create the corresponding template:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
- {# src/Acme/SecurityBundle/Resources/views/Security/login.html.twig #}
- {% if error %}
-
-
-
-
-
-.. tip::
-
- The ``error`` variable passed into the template is an instance of
- :class:`Symfony\\Component\\Security\\Core\\Exception\\AuthenticationException`.
- It may contain more information - or even sensitive information - about
- the authentication failure, so use it wisely!
-
-The form has very few requirements. First, by submitting the form to ``/login_check``
-(via the ``login_check`` route), the security system will intercept the form
-submission and process the form for you automatically. Second, the security
-system expects the submitted fields to be called ``_username`` and ``_password``
-(these field names can be :ref:`configured`).
-
-And that's it! When you submit the form, the security system will automatically
-check the user's credentials and either authenticate the user or send the
-user back to the login form where the error can be displayed.
-
-Let's review the whole process:
-
-#. The user tries to access a resource that is protected;
-#. The firewall initiates the authentication process by redirecting the
- user to the login form (``/login``);
-#. The ``/login`` page renders login form via the route and controller created
- in this example;
-#. The user submits the login form to ``/login_check``;
-#. The security system intercepts the request, checks the user's submitted
- credentials, authenticates the user if they are correct, and sends the
- user back to the login form if they are not.
-
-By default, if the submitted credentials are correct, the user will be redirected
-to the original page that was requested (e.g. ``/admin/foo``). If the user
-originally went straight to the login page, he'll be redirected to the homepage.
-This can be highly customized, allowing you to, for example, redirect the
-user to a specific URL.
-
-For more details on this and how to customize the form login process in general,
-see :doc:`/cookbook/security/form_login`.
-
-.. _book-security-common-pitfalls:
-
-.. sidebar:: Avoid Common Pitfalls
-
- When setting up your login form, watch out for a few common pitfalls.
-
- **1. Create the correct routes**
-
- First, be sure that you've defined the ``login`` and ``login_check``
- routes correctly and that they correspond to the ``login_path`` and
- ``check_path`` config values. A misconfiguration here can mean that you're
- redirected to a 404 page instead of the login page, or that submitting
- the login form does nothing (you just see the login form over and over
- again).
-
- **2. Be sure the login page isn't secure**
-
- Also, be sure that the login page does *not* require any roles to be
- viewed. For example, the following configuration - which requires the
- ``ROLE_ADMIN`` role for all URLs (including the ``/login`` URL), will
- cause a redirect loop:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- access_control:
- - { path: ^/, roles: ROLE_ADMIN }
-
- .. code-block:: xml
-
-
-
-
-
- .. code-block:: php
-
- 'access_control' => array(
- array('path' => '^/', 'role' => 'ROLE_ADMIN'),
- ),
-
- Removing the access control on the ``/login`` URL fixes the problem:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- access_control:
- - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- - { path: ^/, roles: ROLE_ADMIN }
-
- .. code-block:: xml
-
-
-
-
-
-
- .. code-block:: php
-
- 'access_control' => array(
- array('path' => '^/login', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY'),
- array('path' => '^/', 'role' => 'ROLE_ADMIN'),
- ),
-
- Also, if your firewall does *not* allow for anonymous users, you'll need
- to create a special firewall that allows anonymous users for the login
- page:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- firewalls:
- login_firewall:
- pattern: ^/login$
- anonymous: ~
- secured_area:
- pattern: ^/
- form_login: ~
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- 'firewalls' => array(
- 'login_firewall' => array(
- 'pattern' => '^/login$',
- 'anonymous' => array(),
- ),
- 'secured_area' => array(
- 'pattern' => '^/',
- 'form_login' => array(),
- ),
- ),
-
- **3. Be sure ``/login_check`` is behind a firewall**
-
- Next, make sure that your ``check_path`` URL (e.g. ``/login_check``)
- is behind the firewall you're using for your form login (in this example,
- the single firewall matches *all* URLs, including ``/login_check``). If
- ``/login_check`` doesn't match any firewall, you'll receive a ``Unable
- to find the controller for path "/login_check"`` exception.
-
- **4. Multiple firewalls don't share security context**
-
- If you're using multiple firewalls and you authenticate against one firewall,
- you will *not* be authenticated against any other firewalls automatically.
- Different firewalls are like different security systems. To do this you have
- to explicitly specify the same :ref:`reference-security-firewall-context`
- for different firewalls. But usually for most applications, having one
- main firewall is enough.
-
-Authorization
--------------
-
-The first step in security is always authentication: the process of verifying
-who the user is. With Symfony, authentication can be done in any way - via
-a form login, basic HTTP Authentication, or even via Facebook.
-
-Once the user has been authenticated, authorization begins. Authorization
-provides a standard and powerful way to decide if a user can access any resource
-(a URL, a model object, a method call, ...). This works by assigning specific
-roles to each user, and then requiring different roles for different resources.
-
-The process of authorization has two different sides:
-
-#. The user has a specific set of roles;
-#. A resource requires a specific role in order to be accessed.
-
-In this section, you'll focus on how to secure different resources (e.g. URLs,
-method calls, etc) with different roles. Later, you'll learn more about how
-roles are created and assigned to users.
-
-Securing Specific URL Patterns
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The most basic way to secure part of your application is to secure an entire
-URL pattern. You've seen this already in the first example of this chapter,
-where anything matching the regular expression pattern ``^/admin`` requires
-the ``ROLE_ADMIN`` role.
-
-.. caution::
-
- Understanding exactly how ``access_control`` works is **very** important
- to make sure your application is properly secured. See :ref:`security-book-access-control-explanation`
- below for detailed information.
-
-You can define as many URL patterns as you need - each is a regular expression.
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/security.yml
- security:
- # ...
- access_control:
- - { path: ^/admin/users, roles: ROLE_SUPER_ADMIN }
- - { path: ^/admin, roles: ROLE_ADMIN }
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/security.php
- $container->loadFromExtension('security', array(
- // ...
- 'access_control' => array(
- array('path' => '^/admin/users', 'role' => 'ROLE_SUPER_ADMIN'),
- array('path' => '^/admin', 'role' => 'ROLE_ADMIN'),
- ),
- ));
-
-.. tip::
-
- Prepending the path with ``^`` ensures that only URLs *beginning* with
- the pattern are matched. For example, a path of simply ``/admin`` (without
- the ``^``) would correctly match ``/admin/foo`` but would also match URLs
- like ``/foo/admin``.
-
-.. _security-book-access-control-explanation:
-
-Understanding how ``access_control`` works
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-For each incoming request, Symfony2 checks each ``access_control`` entry
-to find *one* that matches the current request. As soon as it finds a matching
-``access_control`` entry, it stops - only the **first** matching ``access_control``
-is used to enforce access.
-
-Each ``access_control`` has several options that configure two different
-things: (a) :ref:`should the incoming request match this access control entry`
-and (b) :ref:`once it matches, should some sort of access restriction be enforced`:
-
-.. _security-book-access-control-matching-options:
-
-**(a) Matching Options**
-
-Symfony2 creates an instance of :class:`Symfony\\Component\\HttpFoundation\\RequestMatcher`
-for each ``access_control`` entry, which determines whether or not a given
-access control should be used on this request. The following ``access_control``
-options are used for matching:
-
-* ``path``
-* ``ip`` or ``ips``
-* ``host``
-* ``methods``
-
-Take the following ``access_control`` entries as an example:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/security.yml
- security:
- # ...
- access_control:
- - { path: ^/admin, roles: ROLE_USER_IP, ip: 127.0.0.1 }
- - { path: ^/admin, roles: ROLE_USER_HOST, host: symfony.com }
- - { path: ^/admin, roles: ROLE_USER_METHOD, methods: [POST, PUT] }
- - { path: ^/admin, roles: ROLE_USER }
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- 'access_control' => array(
- array('path' => '^/admin', 'role' => 'ROLE_USER_IP', 'ip' => '127.0.0.1'),
- array('path' => '^/admin', 'role' => 'ROLE_USER_HOST', 'host' => 'symfony.com'),
- array('path' => '^/admin', 'role' => 'ROLE_USER_METHOD', 'method' => 'POST, PUT'),
- array('path' => '^/admin', 'role' => 'ROLE_USER'),
- ),
-
-For each incoming request, Symfony will decided which ``access_control``
-to use based on the URI, the client's IP address, the incoming host name,
-and the request method. Remember, the first rule that matches is used, and
-if ``ip``, ``host`` or ``method`` are not specified for an entry, that ``access_control``
-will match any ``ip``, ``host`` or ``method``:
-
-+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
-| **URI** | **IP** | **HOST** | **METHOD** | ``access_control`` | Why? |
-+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
-| ``/admin/user`` | 127.0.0.1 | example.com | GET | rule #1 (``ROLE_USER_IP``) | The URI matches ``path`` and the IP matches ``ip``. |
-+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
-| ``/admin/user`` | 127.0.0.1 | symfony.com | GET | rule #1 (``ROLE_USER_IP``) | The ``path`` and ``ip`` still match. This would also match |
-| | | | | | the ``ROLE_USER_HOST`` entry, but *only* the **first** |
-| | | | | | ``access_control`` match is used. |
-+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
-| ``/admin/user`` | 168.0.0.1 | symfony.com | GET | rule #2 (``ROLE_USER_HOST``) | The ``ip`` doesn't match the first rule, so the second |
-| | | | | | rule (which matches) is used. |
-+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
-| ``/admin/user`` | 168.0.0.1 | symfony.com | POST | rule #2 (``ROLE_USER_HOST``) | The second rule still matches. This would also match the |
-| | | | | | third rule (``ROLE_USER_METHOD``), but only the **first** |
-| | | | | | matched ``access_control`` is used. |
-+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
-| ``/admin/user`` | 168.0.0.1 | example.com | POST | rule #3 (``ROLE_USER_METHOD``) | The ``ip`` and ``host`` don't match the first two entries, |
-| | | | | | but the third - ``ROLE_USER_METHOD`` - matches and is used. |
-+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
-| ``/admin/user`` | 168.0.0.1 | example.com | GET | rule #4 (``ROLE_USER``) | The ``ip``, ``host`` and ``method`` prevent the first |
-| | | | | | three entries from matching. But since the URI matches the |
-| | | | | | ``path`` pattern of the ``ROLE_USER`` entry, it is used. |
-+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
-| ``/foo`` | 127.0.0.1 | symfony.com | POST | matches no entries | This doesn't match any ``access_control`` rules, since its |
-| | | | | | URI doesn't match any of the ``path`` values. |
-+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
-
-.. _security-book-access-control-enforcement-options:
-
-**(b) Access Enforcement**
-
-Once Symfony2 has decided which ``access_control`` entry matches (if any),
-it then *enforces* access restrictions based on the ``roles`` and ``requires_channel``
-options:
-
-* ``role`` If the user does not have the given role(s), then access is denied
- (internally, an :class:`Symfony\\Component\\Security\\Core\\Exception\\AccessDeniedException`
- is thrown);
-
-* ``requires_channel`` If the incoming request's channel (e.g. ``http``)
- does not match this value (e.g. ``https``), the user will be redirected
- (e.g. redirected from ``http`` to ``https``, or vice versa).
-
-.. tip::
-
- If access is denied, the system will try to authenticate the user if not
- already (e.g. redirect the user to the login page). If the user is already
- logged in, the 403 "access denied" error page will be shown. See
- :doc:`/cookbook/controller/error_pages` for more information.
-
-.. _book-security-securing-ip:
-
-Securing by IP
-~~~~~~~~~~~~~~
-
-Certain situations may arise when you may need to restrict access to a given
-path based on IP. This is particularly relevant in the case of
-:ref:`Edge Side Includes` (ESI), for example. When ESI is
-enabled, it's recommended to secure access to ESI URLs. Indeed, some ESI may
-contain some private content like the current logged in user's information. To
-prevent any direct access to these resources from a web browser (by guessing the
-ESI URL pattern), the ESI route **must** be secured to be only visible from
-the trusted reverse proxy cache.
-
-.. versionadded:: 2.3
- Version 2.3 allows multiple IP addresses in a single rule with the ``ips: [a, b]``
- construct. Prior to 2.3, users should create one rule per IP address to match and
- use the ``ip`` key instead of ``ips``.
-
-Here is an example of how you might secure all ESI routes that start with a
-given prefix, ``/esi``, from outside access:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/security.yml
- security:
- # ...
- access_control:
- - { path: ^/esi, roles: IS_AUTHENTICATED_ANONYMOUSLY, ips: [127.0.0.1, ::1] }
- - { path: ^/esi, roles: ROLE_NO_ACCESS }
-
- .. code-block:: xml
-
-
-
-
-
-
- .. code-block:: php
-
- 'access_control' => array(
- array('path' => '^/esi', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY', 'ips' => '127.0.0.1, ::1'),
- array('path' => '^/esi', 'role' => 'ROLE_NO_ACCESS'),
- ),
-
-Here is how it works when the path is ``/esi/something`` coming from the
-``10.0.0.1`` IP:
-
-* The first access control rule is ignored as the ``path`` matches but the
- ``ip`` does not match either of the IPs listed;
-
-* The second access control rule is enabled (the only restriction being the
- ``path`` and it matches): as the user cannot have the ``ROLE_NO_ACCESS``
- role as it's not defined, access is denied (the ``ROLE_NO_ACCESS`` role can
- be anything that does not match an existing role, it just serves as a trick
- to always deny access).
-
-Now, if the same request comes from ``127.0.0.1`` or ``::1`` (the IPv6 loopback
-address):
-
-* Now, the first access control rule is enabled as both the ``path`` and the
- ``ip`` match: access is allowed as the user always has the
- ``IS_AUTHENTICATED_ANONYMOUSLY`` role.
-
-* The second access rule is not examined as the first rule matched.
-
-.. _book-security-securing-channel:
-
-Securing by Channel
-~~~~~~~~~~~~~~~~~~~
-
-You can also require a user to access a URL via SSL; just use the
-``requires_channel`` argument in any ``access_control`` entries:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/security.yml
- security:
- # ...
- access_control:
- - { path: ^/cart/checkout, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
-
- .. code-block:: xml
-
-
-
-
-
- .. code-block:: php
-
- 'access_control' => array(
- array('path' => '^/cart/checkout', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY', 'requires_channel' => 'https'),
- ),
-
-.. _book-security-securing-controller:
-
-Securing a Controller
-~~~~~~~~~~~~~~~~~~~~~
-
-Protecting your application based on URL patterns is easy, but may not be
-fine-grained enough in certain cases. When necessary, you can easily force
-authorization from inside a controller::
-
- // ...
- use Symfony\Component\Security\Core\Exception\AccessDeniedException;
-
- public function helloAction($name)
- {
- if (false === $this->get('security.context')->isGranted('ROLE_ADMIN')) {
- throw new AccessDeniedException();
- }
-
- // ...
- }
-
-.. _book-security-securing-controller-annotations:
-
-You can also choose to install and use the optional ``JMSSecurityExtraBundle``,
-which can secure your controller using annotations::
-
- // ...
- use JMS\SecurityExtraBundle\Annotation\Secure;
-
- /**
- * @Secure(roles="ROLE_ADMIN")
- */
- public function helloAction($name)
- {
- // ...
- }
-
-For more information, see the `JMSSecurityExtraBundle`_ documentation. If you're
-using Symfony's Standard Distribution, this bundle is available by default.
-If not, you can easily download and install it.
-
-Securing other Services
-~~~~~~~~~~~~~~~~~~~~~~~
-
-In fact, anything in Symfony can be protected using a strategy similar to
-the one seen in the previous section. For example, suppose you have a service
-(i.e. a PHP class) whose job is to send emails from one user to another.
-You can restrict use of this class - no matter where it's being used from -
-to users that have a specific role.
-
-For more information on how you can use the security component to secure
-different services and methods in your application, see :doc:`/cookbook/security/securing_services`.
-
-Access Control Lists (ACLs): Securing Individual Database Objects
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Imagine you are designing a blog system where your users can comment on your
-posts. Now, you want a user to be able to edit his own comments, but not
-those of other users. Also, as the admin user, you yourself want to be able
-to edit *all* comments.
-
-The security component comes with an optional access control list (ACL) system
-that you can use when you need to control access to individual instances
-of an object in your system. *Without* ACL, you can secure your system so that
-only certain users can edit blog comments in general. But *with* ACL, you
-can restrict or allow access on a comment-by-comment basis.
-
-For more information, see the cookbook article: :doc:`/cookbook/security/acl`.
-
-Users
------
-
-In the previous sections, you learned how you can protect different resources
-by requiring a set of *roles* for a resource. This section explores
-the other side of authorization: users.
-
-Where do Users come from? (*User Providers*)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-During authentication, the user submits a set of credentials (usually a username
-and password). The job of the authentication system is to match those credentials
-against some pool of users. So where does this list of users come from?
-
-In Symfony2, users can come from anywhere - a configuration file, a database
-table, a web service, or anything else you can dream up. Anything that provides
-one or more users to the authentication system is known as a "user provider".
-Symfony2 comes standard with the two most common user providers: one that
-loads users from a configuration file and one that loads users from a database
-table.
-
-Specifying Users in a Configuration File
-........................................
-
-The easiest way to specify your users is directly in a configuration file.
-In fact, you've seen this already in the example in this chapter.
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/security.yml
- security:
- # ...
- providers:
- default_provider:
- memory:
- users:
- ryan: { password: ryanpass, roles: 'ROLE_USER' }
- admin: { password: kitten, roles: 'ROLE_ADMIN' }
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/security.php
- $container->loadFromExtension('security', array(
- // ...
- 'providers' => array(
- 'default_provider' => array(
- 'memory' => array(
- 'users' => array(
- 'ryan' => array('password' => 'ryanpass', 'roles' => 'ROLE_USER'),
- 'admin' => array('password' => 'kitten', 'roles' => 'ROLE_ADMIN'),
- ),
- ),
- ),
- ),
- ));
-
-This user provider is called the "in-memory" user provider, since the users
-aren't stored anywhere in a database. The actual user object is provided
-by Symfony (:class:`Symfony\\Component\\Security\\Core\\User\\User`).
-
-.. tip::
- Any user provider can load users directly from configuration by specifying
- the ``users`` configuration parameter and listing the users beneath it.
-
-.. caution::
-
- If your username is completely numeric (e.g. ``77``) or contains a dash
- (e.g. ``user-name``), you should use that alternative syntax when specifying
- users in YAML:
-
- .. code-block:: yaml
-
- users:
- - { name: 77, password: pass, roles: 'ROLE_USER' }
- - { name: user-name, password: pass, roles: 'ROLE_USER' }
-
-For smaller sites, this method is quick and easy to setup. For more complex
-systems, you'll want to load your users from the database.
-
-.. _book-security-user-entity:
-
-Loading Users from the Database
-...............................
-
-If you'd like to load your users via the Doctrine ORM, you can easily do
-this by creating a ``User`` class and configuring the ``entity`` provider.
-
-.. tip::
-
- A high-quality open source bundle is available that allows your users
- to be stored via the Doctrine ORM or ODM. Read more about the `FOSUserBundle`_
- on GitHub.
-
-With this approach, you'll first create your own ``User`` class, which will
-be stored in the database.
-
-.. code-block:: php
-
- // src/Acme/UserBundle/Entity/User.php
- namespace Acme\UserBundle\Entity;
-
- use Symfony\Component\Security\Core\User\UserInterface;
- use Doctrine\ORM\Mapping as ORM;
-
- /**
- * @ORM\Entity
- */
- class User implements UserInterface
- {
- /**
- * @ORM\Column(type="string", length=255)
- */
- protected $username;
-
- // ...
- }
-
-As far as the security system is concerned, the only requirement for your
-custom user class is that it implements the :class:`Symfony\\Component\\Security\\Core\\User\\UserInterface`
-interface. This means that your concept of a "user" can be anything, as long
-as it implements this interface.
-
-.. versionadded:: 2.1
- In Symfony 2.1, the ``equals`` method was removed from ``UserInterface``.
- If you need to override the default implementation of comparison logic,
- implement the new :class:`Symfony\\Component\\Security\\Core\\User\\EquatableInterface`
- interface.
-
-.. note::
-
- The user object will be serialized and saved in the session during requests,
- therefore it is recommended that you `implement the \Serializable interface`_
- in your user object. This is especially important if your ``User`` class
- has a parent class with private properties.
-
-Next, configure an ``entity`` user provider, and point it to your ``User``
-class:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/security.yml
- security:
- providers:
- main:
- entity: { class: Acme\UserBundle\Entity\User, property: username }
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/security.php
- $container->loadFromExtension('security', array(
- 'providers' => array(
- 'main' => array(
- 'entity' => array('class' => 'Acme\UserBundle\Entity\User', 'property' => 'username'),
- ),
- ),
- ));
-
-With the introduction of this new provider, the authentication system will
-attempt to load a ``User`` object from the database by using the ``username``
-field of that class.
-
-.. note::
- This example is just meant to show you the basic idea behind the ``entity``
- provider. For a full working example, see :doc:`/cookbook/security/entity_provider`.
-
-For more information on creating your own custom provider (e.g. if you needed
-to load users via a web service), see :doc:`/cookbook/security/custom_provider`.
-
-.. _book-security-encoding-user-password:
-
-Encoding the User's Password
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-So far, for simplicity, all the examples have stored the users' passwords
-in plain text (whether those users are stored in a configuration file or in
-a database somewhere). Of course, in a real application, you'll want to encode
-your users' passwords for security reasons. This is easily accomplished by
-mapping your User class to one of several built-in "encoders". For example,
-to store your users in memory, but obscure their passwords via ``sha1``,
-do the following:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/security.yml
- security:
- # ...
- providers:
- in_memory:
- memory:
- users:
- ryan: { password: bb87a29949f3a1ee0559f8a57357487151281386, roles: 'ROLE_USER' }
- admin: { password: 74913f5cd5f61ec0bcfdb775414c2fb3d161b620, roles: 'ROLE_ADMIN' }
-
- encoders:
- Symfony\Component\Security\Core\User\User:
- algorithm: sha1
- iterations: 1
- encode_as_base64: false
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/security.php
- $container->loadFromExtension('security', array(
- // ...
- 'providers' => array(
- 'in_memory' => array(
- 'memory' => array(
- 'users' => array(
- 'ryan' => array('password' => 'bb87a29949f3a1ee0559f8a57357487151281386', 'roles' => 'ROLE_USER'),
- 'admin' => array('password' => '74913f5cd5f61ec0bcfdb775414c2fb3d161b620', 'roles' => 'ROLE_ADMIN'),
- ),
- ),
- ),
- ),
- 'encoders' => array(
- 'Symfony\Component\Security\Core\User\User' => array(
- 'algorithm' => 'sha1',
- 'iterations' => 1,
- 'encode_as_base64' => false,
- ),
- ),
- ));
-
-By setting the ``iterations`` to ``1`` and the ``encode_as_base64`` to false,
-the password is simply run through the ``sha1`` algorithm one time and without
-any extra encoding. You can now calculate the hashed password either programmatically
-(e.g. ``hash('sha1', 'ryanpass')``) or via some online tool like `functions-online.com`_
-
-If you're creating your users dynamically (and storing them in a database),
-you can use even tougher hashing algorithms and then rely on an actual password
-encoder object to help you encode passwords. For example, suppose your User
-object is ``Acme\UserBundle\Entity\User`` (like in the above example). First,
-configure the encoder for that user:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/security.yml
- security:
- # ...
-
- encoders:
- Acme\UserBundle\Entity\User: sha512
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/security.php
- $container->loadFromExtension('security', array(
- // ...
- 'encoders' => array(
- 'Acme\UserBundle\Entity\User' => 'sha512',
- ),
- ));
-
-In this case, you're using the stronger ``sha512`` algorithm. Also, since
-you've simply specified the algorithm (``sha512``) as a string, the system
-will default to hashing your password 5000 times in a row and then encoding
-it as base64. In other words, the password has been greatly obfuscated so
-that the hashed password can't be decoded (i.e. you can't determine the password
-from the hashed password).
-
-.. versionadded:: 2.2
- As of Symfony 2.2 you can also use the :ref:`PBKDF2`
- and :ref:`BCrypt` password encoders.
-
-Determining the Hashed Password
-...............................
-
-If you have some sort of registration form for users, you'll need to be able
-to determine the hashed password so that you can set it on your user. No
-matter what algorithm you configure for your user object, the hashed password
-can always be determined in the following way from a controller::
-
- $factory = $this->get('security.encoder_factory');
- $user = new Acme\UserBundle\Entity\User();
-
- $encoder = $factory->getEncoder($user);
- $password = $encoder->encodePassword('ryanpass', $user->getSalt());
- $user->setPassword($password);
-
-Retrieving the User Object
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-After authentication, the ``User`` object of the current user can be accessed
-via the ``security.context`` service. From inside a controller, this will
-look like::
-
- public function indexAction()
- {
- $user = $this->get('security.context')->getToken()->getUser();
- }
-
-In a controller this can be shortcut to:
-
-.. code-block:: php
-
- public function indexAction()
- {
- $user = $this->getUser();
- }
-
-
-.. note::
-
- Anonymous users are technically authenticated, meaning that the ``isAuthenticated()``
- method of an anonymous user object will return true. To check if your
- user is actually authenticated, check for the ``IS_AUTHENTICATED_FULLY``
- role.
-
-In a Twig Template this object can be accessed via the ``app.user`` key,
-which calls the :method:`GlobalVariables::getUser()`
-method:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
-
Username: {{ app.user.username }}
-
- .. code-block:: html+php
-
-
Username: getUser()->getUsername() ?>
-
-
-Using Multiple User Providers
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Each authentication mechanism (e.g. HTTP Authentication, form login, etc)
-uses exactly one user provider, and will use the first declared user provider
-by default. But what if you want to specify a few users via configuration
-and the rest of your users in the database? This is possible by creating
-a new provider that chains the two together:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/security.yml
- security:
- providers:
- chain_provider:
- chain:
- providers: [in_memory, user_db]
- in_memory:
- memory:
- users:
- foo: { password: test }
- user_db:
- entity: { class: Acme\UserBundle\Entity\User, property: username }
-
- .. code-block:: xml
-
-
-
-
-
- in_memory
- user_db
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/security.php
- $container->loadFromExtension('security', array(
- 'providers' => array(
- 'chain_provider' => array(
- 'chain' => array(
- 'providers' => array('in_memory', 'user_db'),
- ),
- ),
- 'in_memory' => array(
- 'memory' => array(
- 'users' => array(
- 'foo' => array('password' => 'test'),
- ),
- ),
- ),
- 'user_db' => array(
- 'entity' => array('class' => 'Acme\UserBundle\Entity\User', 'property' => 'username'),
- ),
- ),
- ));
-
-Now, all authentication mechanisms will use the ``chain_provider``, since
-it's the first specified. The ``chain_provider`` will, in turn, try to load
-the user from both the ``in_memory`` and ``user_db`` providers.
-
-.. tip::
-
- If you have no reasons to separate your ``in_memory`` users from your
- ``user_db`` users, you can accomplish this even more easily by combining
- the two sources into a single provider:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/security.yml
- security:
- providers:
- main_provider:
- memory:
- users:
- foo: { password: test }
- entity:
- class: Acme\UserBundle\Entity\User,
- property: username
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/security.php
- $container->loadFromExtension('security', array(
- 'providers' => array(
- 'main_provider' => array(
- 'memory' => array(
- 'users' => array(
- 'foo' => array('password' => 'test'),
- ),
- ),
- 'entity' => array('class' => 'Acme\UserBundle\Entity\User', 'property' => 'username'),
- ),
- ),
- ));
-
-You can also configure the firewall or individual authentication mechanisms
-to use a specific provider. Again, unless a provider is specified explicitly,
-the first provider is always used:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/security.yml
- security:
- firewalls:
- secured_area:
- # ...
- provider: user_db
- http_basic:
- realm: "Secured Demo Area"
- provider: in_memory
- form_login: ~
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/security.php
- $container->loadFromExtension('security', array(
- 'firewalls' => array(
- 'secured_area' => array(
- // ...
- 'provider' => 'user_db',
- 'http_basic' => array(
- // ...
- 'provider' => 'in_memory',
- ),
- 'form_login' => array(),
- ),
- ),
- ));
-
-In this example, if a user tries to login via HTTP authentication, the authentication
-system will use the ``in_memory`` user provider. But if the user tries to
-login via the form login, the ``user_db`` provider will be used (since it's
-the default for the firewall as a whole).
-
-For more information about user provider and firewall configuration, see
-the :doc:`/reference/configuration/security`.
-
-Roles
------
-
-The idea of a "role" is key to the authorization process. Each user is assigned
-a set of roles and then each resource requires one or more roles. If the user
-has the required roles, access is granted. Otherwise access is denied.
-
-Roles are pretty simple, and are basically strings that you can invent and
-use as needed (though roles are objects internally). For example, if you
-need to start limiting access to the blog admin section of your website,
-you could protect that section using a ``ROLE_BLOG_ADMIN`` role. This role
-doesn't need to be defined anywhere - you can just start using it.
-
-.. note::
-
- All roles **must** begin with the ``ROLE_`` prefix to be managed by
- Symfony2. If you define your own roles with a dedicated ``Role`` class
- (more advanced), don't use the ``ROLE_`` prefix.
-
-Hierarchical Roles
-~~~~~~~~~~~~~~~~~~
-
-Instead of associating many roles to users, you can define role inheritance
-rules by creating a role hierarchy:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/security.yml
- security:
- role_hierarchy:
- ROLE_ADMIN: ROLE_USER
- ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
-
- .. code-block:: xml
-
-
-
- ROLE_USER
- ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH
-
-
- .. code-block:: php
-
- // app/config/security.php
- $container->loadFromExtension('security', array(
- 'role_hierarchy' => array(
- 'ROLE_ADMIN' => 'ROLE_USER',
- 'ROLE_SUPER_ADMIN' => array('ROLE_ADMIN', 'ROLE_ALLOWED_TO_SWITCH'),
- ),
- ));
-
-In the above configuration, users with ``ROLE_ADMIN`` role will also have the
-``ROLE_USER`` role. The ``ROLE_SUPER_ADMIN`` role has ``ROLE_ADMIN``, ``ROLE_ALLOWED_TO_SWITCH``
-and ``ROLE_USER`` (inherited from ``ROLE_ADMIN``).
-
-Logging Out
------------
-
-Usually, you'll also want your users to be able to log out. Fortunately,
-the firewall can handle this automatically for you when you activate the
-``logout`` config parameter:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/security.yml
- security:
- firewalls:
- secured_area:
- # ...
- logout:
- path: /logout
- target: /
- # ...
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/security.php
- $container->loadFromExtension('security', array(
- 'firewalls' => array(
- 'secured_area' => array(
- // ...
- 'logout' => array('path' => 'logout', 'target' => '/'),
- ),
- ),
- // ...
- ));
-
-Once this is configured under your firewall, sending a user to ``/logout``
-(or whatever you configure the ``path`` to be), will un-authenticate the
-current user. The user will then be sent to the homepage (the value defined
-by the ``target`` parameter). Both the ``path`` and ``target`` config parameters
-default to what's specified here. In other words, unless you need to customize
-them, you can omit them entirely and shorten your configuration:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- logout: ~
-
- .. code-block:: xml
-
-
-
- .. code-block:: php
-
- 'logout' => array(),
-
-Note that you will *not* need to implement a controller for the ``/logout``
-URL as the firewall takes care of everything. You *do*, however, need to create
-a route so that you can use it to generate the URL:
-
-.. caution::
-
- As of Symfony 2.1, you *must* have a route that corresponds to your logout
- path. Without this route, logging out will not work.
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/routing.yml
- logout:
- path: /logout
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/routing.php
- use Symfony\Component\Routing\RouteCollection;
- use Symfony\Component\Routing\Route;
-
- $collection = new RouteCollection();
- $collection->add('logout', new Route('/logout', array()));
-
- return $collection;
-
-Once the user has been logged out, he will be redirected to whatever path
-is defined by the ``target`` parameter above (e.g. the ``homepage``). For
-more information on configuring the logout, see the
-:doc:`Security Configuration Reference`.
-
-.. _book-security-template:
-
-Access Control in Templates
----------------------------
-
-If you want to check if the current user has a role inside a template, use
-the built-in helper function:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
- {% if is_granted('ROLE_ADMIN') %}
- Delete
- {% endif %}
-
- .. code-block:: html+php
-
- isGranted('ROLE_ADMIN')): ?>
- Delete
-
-
-.. note::
-
- If you use this function and are *not* at a URL where there is a firewall
- active, an exception will be thrown. Again, it's almost always a good
- idea to have a main firewall that covers all URLs (as has been shown
- in this chapter).
-
-Access Control in Controllers
------------------------------
-
-If you want to check if the current user has a role in your controller, use
-the :method:`Symfony\\Component\\Security\\Core\\SecurityContext::isGranted`
-method of the security context::
-
- public function indexAction()
- {
- // show different content to admin users
- if ($this->get('security.context')->isGranted('ROLE_ADMIN')) {
- // ... load admin content here
- }
-
- // ... load other regular content here
- }
-
-.. note::
-
- A firewall must be active or an exception will be thrown when the ``isGranted``
- method is called. See the note above about templates for more details.
-
-Impersonating a User
---------------------
-
-Sometimes, it's useful to be able to switch from one user to another without
-having to logout and login again (for instance when you are debugging or trying
-to understand a bug a user sees that you can't reproduce). This can be easily
-done by activating the ``switch_user`` firewall listener:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/security.yml
- security:
- firewalls:
- main:
- # ...
- switch_user: true
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/security.php
- $container->loadFromExtension('security', array(
- 'firewalls' => array(
- 'main'=> array(
- // ...
- 'switch_user' => true
- ),
- ),
- ));
-
-To switch to another user, just add a query string with the ``_switch_user``
-parameter and the username as the value to the current URL:
-
-.. code-block:: text
-
- http://example.com/somewhere?_switch_user=thomas
-
-To switch back to the original user, use the special ``_exit`` username:
-
-.. code-block:: text
-
- http://example.com/somewhere?_switch_user=_exit
-
-During impersonation, the user is provided with a special role called
-``ROLE_PREVIOUS_ADMIN``. In a template, for instance, this role can be used
-to show a link to exit impersonation:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
- {% if is_granted('ROLE_PREVIOUS_ADMIN') %}
- Exit impersonation
- {% endif %}
-
- .. code-block:: html+php
-
- isGranted('ROLE_PREVIOUS_ADMIN')): ?>
-
- Exit impersonation
-
-
-
-Of course, this feature needs to be made available to a small group of users.
-By default, access is restricted to users having the ``ROLE_ALLOWED_TO_SWITCH``
-role. The name of this role can be modified via the ``role`` setting. For
-extra security, you can also change the query parameter name via the ``parameter``
-setting:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/security.yml
- security:
- firewalls:
- main:
- # ...
- switch_user: { role: ROLE_ADMIN, parameter: _want_to_be_this_user }
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/security.php
- $container->loadFromExtension('security', array(
- 'firewalls' => array(
- 'main'=> array(
- // ...
- 'switch_user' => array('role' => 'ROLE_ADMIN', 'parameter' => '_want_to_be_this_user'),
- ),
- ),
- ));
-
-Stateless Authentication
-------------------------
-
-By default, Symfony2 relies on a cookie (the Session) to persist the security
-context of the user. But if you use certificates or HTTP authentication for
-instance, persistence is not needed as credentials are available for each
-request. In that case, and if you don't need to store anything else between
-requests, you can activate the stateless authentication (which means that no
-cookie will be ever created by Symfony2):
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/security.yml
- security:
- firewalls:
- main:
- http_basic: ~
- stateless: true
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/security.php
- $container->loadFromExtension('security', array(
- 'firewalls' => array(
- 'main' => array('http_basic' => array(), 'stateless' => true),
- ),
- ));
-
-.. note::
-
- If you use a form login, Symfony2 will create a cookie even if you set
- ``stateless`` to ``true``.
-
-Utilities
----------
-
-.. versionadded:: 2.2
- The ``StringUtils`` and ``SecureRandom`` classes were added in Symfony 2.2
-
-The Symfony Security Component comes with a collection of nice utilities related
-to security. These utilities are used by Symfony, but you should also use
-them if you want to solve the problem they address.
-
-Comparing Strings
-~~~~~~~~~~~~~~~~~
-
-The time it takes to compare two strings depends on their differences. This
-can be used by an attacker when the two strings represent a password for
-instance; it is known as a `Timing attack`_.
-
-Internally, when comparing two passwords, Symfony uses a constant-time
-algorithm; you can use the same strategy in your own code thanks to the
-:class:`Symfony\\Component\\Security\\Core\\Util\\StringUtils` class::
-
- use Symfony\Component\Security\Core\Util\StringUtils;
-
- // is password1 equals to password2?
- $bool = StringUtils::equals($password1, $password2);
-
-Generating a secure Random Number
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Whenever you need to generate a secure random number, you are highly
-encouraged to use the Symfony
-:class:`Symfony\\Component\\Security\\Core\\Util\\SecureRandom` class::
-
- use Symfony\Component\Security\Core\Util\SecureRandom;
-
- $generator = new SecureRandom();
- $random = $generator->nextBytes(10);
-
-The
-:method:`Symfony\\Component\\Security\\Core\\Util\\SecureRandom::nextBytes`
-methods returns a random string composed of the number of characters passed as
-an argument (10 in the above example).
-
-The SecureRandom class works better when OpenSSL is installed but when it's
-not available, it falls back to an internal algorithm, which needs a seed file
-to work correctly. Just pass a file name to enable it::
-
- $generator = new SecureRandom('/some/path/to/store/the/seed.txt');
- $random = $generator->nextBytes(10);
-
-.. note::
-
- You can also access a secure random instance directly from the Symfony
- dependency injection container; its name is ``security.secure_random``.
-
-Final Words
------------
-
-Security can be a deep and complex issue to solve correctly in your application.
-Fortunately, Symfony's security component follows a well-proven security
-model based around *authentication* and *authorization*. Authentication,
-which always happens first, is handled by a firewall whose job is to determine
-the identity of the user through several different methods (e.g. HTTP authentication,
-login form, etc). In the cookbook, you'll find examples of other methods
-for handling authentication, including how to implement a "remember me" cookie
-functionality.
-
-Once a user is authenticated, the authorization layer can determine whether
-or not the user should have access to a specific resource. Most commonly,
-*roles* are applied to URLs, classes or methods and if the current user
-doesn't have that role, access is denied. The authorization layer, however,
-is much deeper, and follows a system of "voting" so that multiple parties
-can determine if the current user should have access to a given resource.
-Find out more about this and other topics in the cookbook.
-
-Learn more from the Cookbook
-----------------------------
-
-* :doc:`Forcing HTTP/HTTPS `
-* :doc:`Blacklist users by IP address with a custom voter `
-* :doc:`Access Control Lists (ACLs) `
-* :doc:`/cookbook/security/remember_me`
-
-.. _`Symfony's security component`: https://github.com/symfony/Security
-.. _`JMSSecurityExtraBundle`: http://jmsyst.com/bundles/JMSSecurityExtraBundle/1.2
-.. _`FOSUserBundle`: https://github.com/FriendsOfSymfony/FOSUserBundle
-.. _`implement the \Serializable interface`: http://php.net/manual/en/class.serializable.php
-.. _`functions-online.com`: http://www.functions-online.com/sha1.html
-.. _`Timing attack`: http://en.wikipedia.org/wiki/Timing_attack
diff --git a/book/service_container.rst b/book/service_container.rst
deleted file mode 100644
index 1de05025ee0..00000000000
--- a/book/service_container.rst
+++ /dev/null
@@ -1,956 +0,0 @@
-.. index::
- single: Service Container
- single: Dependency Injection; Container
-
-Service Container
-=================
-
-A modern PHP application is full of objects. One object may facilitate the
-delivery of email messages while another may allow you to persist information
-into a database. In your application, you may create an object that manages
-your product inventory, or another object that processes data from a third-party
-API. The point is that a modern application does many things and is organized
-into many objects that handle each task.
-
-This chapter is about a special PHP object in Symfony2 that helps
-you instantiate, organize and retrieve the many objects of your application.
-This object, called a service container, will allow you to standardize and
-centralize the way objects are constructed in your application. The container
-makes your life easier, is super fast, and emphasizes an architecture that
-promotes reusable and decoupled code. Since all core Symfony2 classes
-use the container, you'll learn how to extend, configure and use any object
-in Symfony2. In large part, the service container is the biggest contributor
-to the speed and extensibility of Symfony2.
-
-Finally, configuring and using the service container is easy. By the end
-of this chapter, you'll be comfortable creating your own objects via the
-container and customizing objects from any third-party bundle. You'll begin
-writing code that is more reusable, testable and decoupled, simply because
-the service container makes writing good code so easy.
-
-.. tip::
-
- If you want to know a lot more after reading this chapter, check out
- the :doc:`Dependency Injection Component Documentation`.
-
-.. index::
- single: Service Container; What is a service?
-
-What is a Service?
-------------------
-
-Put simply, a :term:`Service` is any PHP object that performs some sort of
-"global" task. It's a purposefully-generic name used in computer science
-to describe an object that's created for a specific purpose (e.g. delivering
-emails). Each service is used throughout your application whenever you need
-the specific functionality it provides. You don't have to do anything special
-to make a service: simply write a PHP class with some code that accomplishes
-a specific task. Congratulations, you've just created a service!
-
-.. note::
-
- As a rule, a PHP object is a service if it is used globally in your
- application. A single ``Mailer`` service is used globally to send
- email messages whereas the many ``Message`` objects that it delivers
- are *not* services. Similarly, a ``Product`` object is not a service,
- but an object that persists ``Product`` objects to a database *is* a service.
-
-So what's the big deal then? The advantage of thinking about "services" is
-that you begin to think about separating each piece of functionality in your
-application into a series of services. Since each service does just one job,
-you can easily access each service and use its functionality wherever you
-need it. Each service can also be more easily tested and configured since
-it's separated from the other functionality in your application. This idea
-is called `service-oriented architecture`_ and is not unique to Symfony2
-or even PHP. Structuring your application around a set of independent service
-classes is a well-known and trusted object-oriented best-practice. These skills
-are key to being a good developer in almost any language.
-
-.. index::
- single: Service Container; What is a service container?
-
-What is a Service Container?
-----------------------------
-
-A :term:`Service Container` (or *dependency injection container*) is simply
-a PHP object that manages the instantiation of services (i.e. objects).
-
-For example, suppose you have a simple PHP class that delivers email messages.
-Without a service container, you must manually create the object whenever
-you need it::
-
- use Acme\HelloBundle\Mailer;
-
- $mailer = new Mailer('sendmail');
- $mailer->send('ryan@foobar.net', ...);
-
-This is easy enough. The imaginary ``Mailer`` class allows you to configure
-the method used to deliver the email messages (e.g. ``sendmail``, ``smtp``, etc).
-But what if you wanted to use the mailer service somewhere else? You certainly
-don't want to repeat the mailer configuration *every* time you need to use
-the ``Mailer`` object. What if you needed to change the ``transport`` from
-``sendmail`` to ``smtp`` everywhere in the application? You'd need to hunt
-down every place you create a ``Mailer`` service and change it.
-
-.. index::
- single: Service Container; Configuring services
-
-Creating/Configuring Services in the Container
-----------------------------------------------
-
-A better answer is to let the service container create the ``Mailer`` object
-for you. In order for this to work, you must *teach* the container how to
-create the ``Mailer`` service. This is done via configuration, which can
-be specified in YAML, XML or PHP:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- services:
- my_mailer:
- class: Acme\HelloBundle\Mailer
- arguments: [sendmail]
-
- .. code-block:: xml
-
-
-
-
- sendmail
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- use Symfony\Component\DependencyInjection\Definition;
-
- $container->setDefinition('my_mailer', new Definition(
- 'Acme\HelloBundle\Mailer',
- array('sendmail')
- ));
-
-.. note::
-
- When Symfony2 initializes, it builds the service container using the
- application configuration (``app/config/config.yml`` by default). The
- exact file that's loaded is dictated by the ``AppKernel::registerContainerConfiguration()``
- method, which loads an environment-specific configuration file (e.g.
- ``config_dev.yml`` for the ``dev`` environment or ``config_prod.yml``
- for ``prod``).
-
-An instance of the ``Acme\HelloBundle\Mailer`` object is now available via
-the service container. The container is available in any traditional Symfony2
-controller where you can access the services of the container via the ``get()``
-shortcut method::
-
- class HelloController extends Controller
- {
- // ...
-
- public function sendEmailAction()
- {
- // ...
- $mailer = $this->get('my_mailer');
- $mailer->send('ryan@foobar.net', ...);
- }
- }
-
-When you ask for the ``my_mailer`` service from the container, the container
-constructs the object and returns it. This is another major advantage of
-using the service container. Namely, a service is *never* constructed until
-it's needed. If you define a service and never use it on a request, the service
-is never created. This saves memory and increases the speed of your application.
-This also means that there's very little or no performance hit for defining
-lots of services. Services that are never used are never constructed.
-
-As an added bonus, the ``Mailer`` service is only created once and the same
-instance is returned each time you ask for the service. This is almost always
-the behavior you'll need (it's more flexible and powerful), but you'll learn
-later how you can configure a service that has multiple instances in the
-":doc:`/cookbook/service_container/scopes`" cookbook article.
-
-.. _book-service-container-parameters:
-
-Service Parameters
-------------------
-
-The creation of new services (i.e. objects) via the container is pretty
-straightforward. Parameters make defining services more organized and flexible:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- parameters:
- my_mailer.class: Acme\HelloBundle\Mailer
- my_mailer.transport: sendmail
-
- services:
- my_mailer:
- class: "%my_mailer.class%"
- arguments: ["%my_mailer.transport%"]
-
- .. code-block:: xml
-
-
-
- Acme\HelloBundle\Mailer
- sendmail
-
-
-
-
- %my_mailer.transport%
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- use Symfony\Component\DependencyInjection\Definition;
-
- $container->setParameter('my_mailer.class', 'Acme\HelloBundle\Mailer');
- $container->setParameter('my_mailer.transport', 'sendmail');
-
- $container->setDefinition('my_mailer', new Definition(
- '%my_mailer.class%',
- array('%my_mailer.transport%')
- ));
-
-The end result is exactly the same as before - the difference is only in
-*how* you defined the service. By surrounding the ``my_mailer.class`` and
-``my_mailer.transport`` strings in percent (``%``) signs, the container knows
-to look for parameters with those names. When the container is built, it
-looks up the value of each parameter and uses it in the service definition.
-
-.. versionadded:: 2.1
- Escaping the ``@`` character in YAML parameter values is new in Symfony 2.1.9
- and Symfony 2.2.1.
-
-.. note::
-
- If you want to use a string that starts with an ``@`` sign as a parameter
- value (i.e. a very safe mailer password) in a yaml file, you need to escape
- it by adding another ``@`` sign (This only applies to the YAML format):
-
- .. code-block:: yaml
-
- # app/config/parameters.yml
- parameters:
- # This will be parsed as string "@securepass"
- mailer_password: "@@securepass"
-
-.. note::
-
- The percent sign inside a parameter or argument, as part of the string, must
- be escaped with another percent sign:
-
- .. code-block:: xml
-
- http://symfony.com/?foo=%%s&bar=%%d
-
-.. caution::
-
- You may receive a
- :class:`Symfony\\Component\\DependencyInjection\\Exception\\ScopeWideningInjectionException`
- when passing the ``request`` service as an argument. To understand this
- problem better and learn how to solve it, refer to the cookbook article
- :doc:`/cookbook/service_container/scopes`.
-
-The purpose of parameters is to feed information into services. Of course
-there was nothing wrong with defining the service without using any parameters.
-Parameters, however, have several advantages:
-
-* separation and organization of all service "options" under a single
- ``parameters`` key;
-
-* parameter values can be used in multiple service definitions;
-
-* when creating a service in a bundle (this follows shortly), using parameters
- allows the service to be easily customized in your application.
-
-The choice of using or not using parameters is up to you. High-quality
-third-party bundles will *always* use parameters as they make the service
-stored in the container more configurable. For the services in your application,
-however, you may not need the flexibility of parameters.
-
-Array Parameters
-~~~~~~~~~~~~~~~~
-
-Parameters can also contain array values. See :ref:`component-di-parameters-array`.
-
-Importing other Container Configuration Resources
--------------------------------------------------
-
-.. tip::
-
- In this section, service configuration files are referred to as *resources*.
- This is to highlight the fact that, while most configuration resources
- will be files (e.g. YAML, XML, PHP), Symfony2 is so flexible that configuration
- could be loaded from anywhere (e.g. a database or even via an external
- web service).
-
-The service container is built using a single configuration resource
-(``app/config/config.yml`` by default). All other service configuration
-(including the core Symfony2 and third-party bundle configuration) must
-be imported from inside this file in one way or another. This gives you absolute
-flexibility over the services in your application.
-
-External service configuration can be imported in two different ways. The
-first - and most common method - is via the ``imports`` directive. Later, you'll
-learn about the second method, which is the flexible and preferred method
-for importing service configuration from third-party bundles.
-
-.. index::
- single: Service Container; Imports
-
-.. _service-container-imports-directive:
-
-Importing Configuration with ``imports``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-So far, you've placed your ``my_mailer`` service container definition directly
-in the application configuration file (e.g. ``app/config/config.yml``). Of
-course, since the ``Mailer`` class itself lives inside the ``AcmeHelloBundle``,
-it makes more sense to put the ``my_mailer`` container definition inside the
-bundle as well.
-
-First, move the ``my_mailer`` container definition into a new container resource
-file inside ``AcmeHelloBundle``. If the ``Resources`` or ``Resources/config``
-directories don't exist, create them.
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # src/Acme/HelloBundle/Resources/config/services.yml
- parameters:
- my_mailer.class: Acme\HelloBundle\Mailer
- my_mailer.transport: sendmail
-
- services:
- my_mailer:
- class: "%my_mailer.class%"
- arguments: ["%my_mailer.transport%"]
-
- .. code-block:: xml
-
-
-
- Acme\HelloBundle\Mailer
- sendmail
-
-
-
-
- %my_mailer.transport%
-
-
-
- .. code-block:: php
-
- // src/Acme/HelloBundle/Resources/config/services.php
- use Symfony\Component\DependencyInjection\Definition;
-
- $container->setParameter('my_mailer.class', 'Acme\HelloBundle\Mailer');
- $container->setParameter('my_mailer.transport', 'sendmail');
-
- $container->setDefinition('my_mailer', new Definition(
- '%my_mailer.class%',
- array('%my_mailer.transport%')
- ));
-
-The definition itself hasn't changed, only its location. Of course the service
-container doesn't know about the new resource file. Fortunately, you can
-easily import the resource file using the ``imports`` key in the application
-configuration.
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- imports:
- - { resource: "@AcmeHelloBundle/Resources/config/services.yml" }
-
- .. code-block:: xml
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- $this->import('@AcmeHelloBundle/Resources/config/services.php');
-
-The ``imports`` directive allows your application to include service container
-configuration resources from any other location (most commonly from bundles).
-The ``resource`` location, for files, is the absolute path to the resource
-file. The special ``@AcmeHello`` syntax resolves the directory path of
-the ``AcmeHelloBundle`` bundle. This helps you specify the path to the resource
-without worrying later if you move the ``AcmeHelloBundle`` to a different
-directory.
-
-.. index::
- single: Service Container; Extension configuration
-
-.. _service-container-extension-configuration:
-
-Importing Configuration via Container Extensions
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-When developing in Symfony2, you'll most commonly use the ``imports`` directive
-to import container configuration from the bundles you've created specifically
-for your application. Third-party bundle container configuration, including
-Symfony2 core services, are usually loaded using another method that's more
-flexible and easy to configure in your application.
-
-Here's how it works. Internally, each bundle defines its services very much
-like you've seen so far. Namely, a bundle uses one or more configuration
-resource files (usually XML) to specify the parameters and services for that
-bundle. However, instead of importing each of these resources directly from
-your application configuration using the ``imports`` directive, you can simply
-invoke a *service container extension* inside the bundle that does the work for
-you. A service container extension is a PHP class created by the bundle author
-to accomplish two things:
-
-* import all service container resources needed to configure the services for
- the bundle;
-
-* provide semantic, straightforward configuration so that the bundle can
- be configured without interacting with the flat parameters of the bundle's
- service container configuration.
-
-In other words, a service container extension configures the services for
-a bundle on your behalf. And as you'll see in a moment, the extension provides
-a sensible, high-level interface for configuring the bundle.
-
-Take the ``FrameworkBundle`` - the core Symfony2 framework bundle - as an
-example. The presence of the following code in your application configuration
-invokes the service container extension inside the ``FrameworkBundle``:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- framework:
- secret: xxxxxxxxxx
- form: true
- csrf_protection: true
- router: { resource: "%kernel.root_dir%/config/routing.yml" }
- # ...
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- $container->loadFromExtension('framework', array(
- 'secret' => 'xxxxxxxxxx',
- 'form' => array(),
- 'csrf-protection' => array(),
- 'router' => array('resource' => '%kernel.root_dir%/config/routing.php'),
-
- // ...
- ));
-
-When the configuration is parsed, the container looks for an extension that
-can handle the ``framework`` configuration directive. The extension in question,
-which lives in the ``FrameworkBundle``, is invoked and the service configuration
-for the ``FrameworkBundle`` is loaded. If you remove the ``framework`` key
-from your application configuration file entirely, the core Symfony2 services
-won't be loaded. The point is that you're in control: the Symfony2 framework
-doesn't contain any magic or perform any actions that you don't have control
-over.
-
-Of course you can do much more than simply "activate" the service container
-extension of the ``FrameworkBundle``. Each extension allows you to easily
-customize the bundle, without worrying about how the internal services are
-defined.
-
-In this case, the extension allows you to customize the ``error_handler``,
-``csrf_protection``, ``router`` configuration and much more. Internally,
-the ``FrameworkBundle`` uses the options specified here to define and configure
-the services specific to it. The bundle takes care of creating all the necessary
-``parameters`` and ``services`` for the service container, while still allowing
-much of the configuration to be easily customized. As an added bonus, most
-service container extensions are also smart enough to perform validation -
-notifying you of options that are missing or the wrong data type.
-
-When installing or configuring a bundle, see the bundle's documentation for
-how the services for the bundle should be installed and configured. The options
-available for the core bundles can be found inside the :doc:`Reference Guide`.
-
-.. note::
-
- Natively, the service container only recognizes the ``parameters``,
- ``services``, and ``imports`` directives. Any other directives
- are handled by a service container extension.
-
-If you want to expose user friendly configuration in your own bundles, read the
-":doc:`/cookbook/bundles/extension`" cookbook recipe.
-
-.. index::
- single: Service Container; Referencing services
-
-Referencing (Injecting) Services
---------------------------------
-
-So far, the original ``my_mailer`` service is simple: it takes just one argument
-in its constructor, which is easily configurable. As you'll see, the real
-power of the container is realized when you need to create a service that
-depends on one or more other services in the container.
-
-As an example, suppose you have a new service, ``NewsletterManager``,
-that helps to manage the preparation and delivery of an email message to
-a collection of addresses. Of course the ``my_mailer`` service is already
-really good at delivering email messages, so you'll use it inside ``NewsletterManager``
-to handle the actual delivery of the messages. This pretend class might look
-something like this::
-
- // src/Acme/HelloBundle/Newsletter/NewsletterManager.php
- namespace Acme\HelloBundle\Newsletter;
-
- use Acme\HelloBundle\Mailer;
-
- class NewsletterManager
- {
- protected $mailer;
-
- public function __construct(Mailer $mailer)
- {
- $this->mailer = $mailer;
- }
-
- // ...
- }
-
-Without using the service container, you can create a new ``NewsletterManager``
-fairly easily from inside a controller::
-
- use Acme\HelloBundle\Newsletter\NewsletterManager;
-
- // ...
-
- public function sendNewsletterAction()
- {
- $mailer = $this->get('my_mailer');
- $newsletter = new NewsletterManager($mailer);
- // ...
- }
-
-This approach is fine, but what if you decide later that the ``NewsletterManager``
-class needs a second or third constructor argument? What if you decide to
-refactor your code and rename the class? In both cases, you'd need to find every
-place where the ``NewsletterManager`` is instantiated and modify it. Of course,
-the service container gives you a much more appealing option:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # src/Acme/HelloBundle/Resources/config/services.yml
- parameters:
- # ...
- newsletter_manager.class: Acme\HelloBundle\Newsletter\NewsletterManager
-
- services:
- my_mailer:
- # ...
- newsletter_manager:
- class: "%newsletter_manager.class%"
- arguments: ["@my_mailer"]
-
- .. code-block:: xml
-
-
-
-
- Acme\HelloBundle\Newsletter\NewsletterManager
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // src/Acme/HelloBundle/Resources/config/services.php
- use Symfony\Component\DependencyInjection\Definition;
- use Symfony\Component\DependencyInjection\Reference;
-
- // ...
- $container->setParameter(
- 'newsletter_manager.class',
- 'Acme\HelloBundle\Newsletter\NewsletterManager'
- );
-
- $container->setDefinition('my_mailer', ...);
- $container->setDefinition('newsletter_manager', new Definition(
- '%newsletter_manager.class%',
- array(new Reference('my_mailer'))
- ));
-
-In YAML, the special ``@my_mailer`` syntax tells the container to look for
-a service named ``my_mailer`` and to pass that object into the constructor
-of ``NewsletterManager``. In this case, however, the specified service ``my_mailer``
-must exist. If it does not, an exception will be thrown. You can mark your
-dependencies as optional - this will be discussed in the next section.
-
-Using references is a very powerful tool that allows you to create independent service
-classes with well-defined dependencies. In this example, the ``newsletter_manager``
-service needs the ``my_mailer`` service in order to function. When you define
-this dependency in the service container, the container takes care of all
-the work of instantiating the objects.
-
-Optional Dependencies: Setter Injection
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Injecting dependencies into the constructor in this manner is an excellent
-way of ensuring that the dependency is available to use. If you have optional
-dependencies for a class, then "setter injection" may be a better option. This
-means injecting the dependency using a method call rather than through the
-constructor. The class would look like this::
-
- namespace Acme\HelloBundle\Newsletter;
-
- use Acme\HelloBundle\Mailer;
-
- class NewsletterManager
- {
- protected $mailer;
-
- public function setMailer(Mailer $mailer)
- {
- $this->mailer = $mailer;
- }
-
- // ...
- }
-
-Injecting the dependency by the setter method just needs a change of syntax:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # src/Acme/HelloBundle/Resources/config/services.yml
- parameters:
- # ...
- newsletter_manager.class: Acme\HelloBundle\Newsletter\NewsletterManager
-
- services:
- my_mailer:
- # ...
- newsletter_manager:
- class: "%newsletter_manager.class%"
- calls:
- - [setMailer, ["@my_mailer"]]
-
- .. code-block:: xml
-
-
-
-
- Acme\HelloBundle\Newsletter\NewsletterManager
-
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // src/Acme/HelloBundle/Resources/config/services.php
- use Symfony\Component\DependencyInjection\Definition;
- use Symfony\Component\DependencyInjection\Reference;
-
- // ...
- $container->setParameter(
- 'newsletter_manager.class',
- 'Acme\HelloBundle\Newsletter\NewsletterManager'
- );
-
- $container->setDefinition('my_mailer', ...);
- $container->setDefinition('newsletter_manager', new Definition(
- '%newsletter_manager.class%'
- ))->addMethodCall('setMailer', array(
- new Reference('my_mailer'),
- ));
-
-.. note::
-
- The approaches presented in this section are called "constructor injection"
- and "setter injection". The Symfony2 service container also supports
- "property injection".
-
-Making References Optional
---------------------------
-
-Sometimes, one of your services may have an optional dependency, meaning
-that the dependency is not required for your service to work properly. In
-the example above, the ``my_mailer`` service *must* exist, otherwise an exception
-will be thrown. By modifying the ``newsletter_manager`` service definition,
-you can make this reference optional. The container will then inject it if
-it exists and do nothing if it doesn't:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # src/Acme/HelloBundle/Resources/config/services.yml
- parameters:
- # ...
-
- services:
- newsletter_manager:
- class: "%newsletter_manager.class%"
- arguments: ["@?my_mailer"]
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // src/Acme/HelloBundle/Resources/config/services.php
- use Symfony\Component\DependencyInjection\Definition;
- use Symfony\Component\DependencyInjection\Reference;
- use Symfony\Component\DependencyInjection\ContainerInterface;
-
- // ...
- $container->setParameter(
- 'newsletter_manager.class',
- 'Acme\HelloBundle\Newsletter\NewsletterManager'
- );
-
- $container->setDefinition('my_mailer', ...);
- $container->setDefinition('newsletter_manager', new Definition(
- '%newsletter_manager.class%',
- array(
- new Reference(
- 'my_mailer',
- ContainerInterface::IGNORE_ON_INVALID_REFERENCE
- )
- )
- ));
-
-In YAML, the special ``@?`` syntax tells the service container that the dependency
-is optional. Of course, the ``NewsletterManager`` must also be written to
-allow for an optional dependency::
-
- public function __construct(Mailer $mailer = null)
- {
- // ...
- }
-
-Core Symfony and Third-Party Bundle Services
---------------------------------------------
-
-Since Symfony2 and all third-party bundles configure and retrieve their services
-via the container, you can easily access them or even use them in your own
-services. To keep things simple, Symfony2 by default does not require that
-controllers be defined as services. Furthermore Symfony2 injects the entire
-service container into your controller. For example, to handle the storage of
-information on a user's session, Symfony2 provides a ``session`` service,
-which you can access inside a standard controller as follows::
-
- public function indexAction($bar)
- {
- $session = $this->get('session');
- $session->set('foo', $bar);
-
- // ...
- }
-
-In Symfony2, you'll constantly use services provided by the Symfony core or
-other third-party bundles to perform tasks such as rendering templates (``templating``),
-sending emails (``mailer``), or accessing information on the request (``request``).
-
-You can take this a step further by using these services inside services that
-you've created for your application. Beginning by modifying the ``NewsletterManager``
-to use the real Symfony2 ``mailer`` service (instead of the pretend ``my_mailer``).
-Also pass the templating engine service to the ``NewsletterManager``
-so that it can generate the email content via a template::
-
- namespace Acme\HelloBundle\Newsletter;
-
- use Symfony\Component\Templating\EngineInterface;
-
- class NewsletterManager
- {
- protected $mailer;
-
- protected $templating;
-
- public function __construct(\Swift_Mailer $mailer, EngineInterface $templating)
- {
- $this->mailer = $mailer;
- $this->templating = $templating;
- }
-
- // ...
- }
-
-Configuring the service container is easy:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- services:
- newsletter_manager:
- class: "%newsletter_manager.class%"
- arguments: ["@mailer", "@templating"]
-
- .. code-block:: xml
-
-
-
-
-
-
- .. code-block:: php
-
- $container->setDefinition('newsletter_manager', new Definition(
- '%newsletter_manager.class%',
- array(
- new Reference('mailer'),
- new Reference('templating'),
- )
- ));
-
-The ``newsletter_manager`` service now has access to the core ``mailer``
-and ``templating`` services. This is a common way to create services specific
-to your application that leverage the power of different services within
-the framework.
-
-.. tip::
-
- Be sure that the ``swiftmailer`` entry appears in your application
- configuration. As was mentioned in :ref:`service-container-extension-configuration`,
- the ``swiftmailer`` key invokes the service extension from the
- ``SwiftmailerBundle``, which registers the ``mailer`` service.
-
-.. _book-service-container-tags:
-
-Tags
-----
-
-In the same way that a blog post on the Web might be tagged with things such
-as "Symfony" or "PHP", services configured in your container can also be
-tagged. In the service container, a tag implies that the service is meant
-to be used for a specific purpose. Take the following example:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- services:
- foo.twig.extension:
- class: Acme\HelloBundle\Extension\FooExtension
- tags:
- - { name: twig.extension }
-
- .. code-block:: xml
-
-
-
-
-
- .. code-block:: php
-
- $definition = new Definition('Acme\HelloBundle\Extension\FooExtension');
- $definition->addTag('twig.extension');
- $container->setDefinition('foo.twig.extension', $definition);
-
-The ``twig.extension`` tag is a special tag that the ``TwigBundle`` uses
-during configuration. By giving the service this ``twig.extension`` tag,
-the bundle knows that the ``foo.twig.extension`` service should be registered
-as a Twig extension with Twig. In other words, Twig finds all services tagged
-with ``twig.extension`` and automatically registers them as extensions.
-
-Tags, then, are a way to tell Symfony2 or other third-party bundles that
-your service should be registered or used in some special way by the bundle.
-
-The following is a list of tags available with the core Symfony2 bundles.
-Each of these has a different effect on your service and many tags require
-additional arguments (beyond just the ``name`` parameter).
-
-For a list of all the tags available in the core Symfony Framework, check
-out :doc:`/reference/dic_tags`.
-
-Debugging Services
-------------------
-
-You can find out what services are registered with the container using the
-console. To show all services and the class for each service, run:
-
-.. code-block:: bash
-
- $ php app/console container:debug
-
-By default only public services are shown, but you can also view private services:
-
-.. code-block:: bash
-
- $ php app/console container:debug --show-private
-
-You can get more detailed information about a particular service by specifying
-its id:
-
-.. code-block:: bash
-
- $ php app/console container:debug my_mailer
-
-Learn more
-----------
-
-* :doc:`/components/dependency_injection/parameters`
-* :doc:`/components/dependency_injection/compilation`
-* :doc:`/components/dependency_injection/definitions`
-* :doc:`/components/dependency_injection/factories`
-* :doc:`/components/dependency_injection/parentservices`
-* :doc:`/components/dependency_injection/tags`
-* :doc:`/cookbook/controller/service`
-* :doc:`/cookbook/service_container/scopes`
-* :doc:`/cookbook/service_container/compiler_passes`
-* :doc:`/components/dependency_injection/advanced`
-
-.. _`service-oriented architecture`: http://wikipedia.org/wiki/Service-oriented_architecture
diff --git a/book/stable_api.rst b/book/stable_api.rst
deleted file mode 100644
index d7fbdaf481e..00000000000
--- a/book/stable_api.rst
+++ /dev/null
@@ -1,43 +0,0 @@
-.. index::
- single: Stable API
-
-The Symfony2 Stable API
-=======================
-
-The Symfony2 stable API is a subset of all Symfony2 published public methods
-(components and core bundles) that share the following properties:
-
-* The namespace and class name won't change;
-* The method name won't change;
-* The method signature (arguments and return value type) won't change;
-* The semantic of what the method does won't change.
-
-The implementation itself can change though. The only valid case for a change
-in the stable API is in order to fix a security issue.
-
-The stable API is based on a whitelist, tagged with `@api`. Therefore,
-everything not tagged explicitly is not part of the stable API.
-
-.. tip::
-
- Any third party bundle should also publish its own stable API.
-
-As of Symfony 2.0, the following components have a public tagged API:
-
-* BrowserKit
-* ClassLoader
-* Console
-* CssSelector
-* DependencyInjection
-* DomCrawler
-* EventDispatcher
-* Finder
-* HttpFoundation
-* HttpKernel
-* Locale
-* Process
-* Routing
-* Templating
-* Translation
-* Validator
-* Yaml
diff --git a/book/templating.rst b/book/templating.rst
deleted file mode 100644
index 656f790f262..00000000000
--- a/book/templating.rst
+++ /dev/null
@@ -1,1554 +0,0 @@
-.. index::
- single: Templating
-
-Creating and using Templates
-============================
-
-As you know, the :doc:`controller ` is responsible for
-handling each request that comes into a Symfony2 application. In reality,
-the controller delegates the most of the heavy work to other places so that
-code can be tested and reused. When a controller needs to generate HTML,
-CSS or any other content, it hands the work off to the templating engine.
-In this chapter, you'll learn how to write powerful templates that can be
-used to return content to the user, populate email bodies, and more. You'll
-learn shortcuts, clever ways to extend templates and how to reuse template
-code.
-
-.. note::
-
- How to render templates is covered in the :ref:`controller `
- page of the book.
-
-
-.. index::
- single: Templating; What is a template?
-
-Templates
----------
-
-A template is simply a text file that can generate any text-based format
-(HTML, XML, CSV, LaTeX ...). The most familiar type of template is a *PHP*
-template - a text file parsed by PHP that contains a mix of text and PHP code:
-
-.. code-block:: html+php
-
-
-
-
- Welcome to Symfony!
-
-
-
-
-
-
-
-
-.. index:: Twig; Introduction
-
-But Symfony2 packages an even more powerful templating language called `Twig`_.
-Twig allows you to write concise, readable templates that are more friendly
-to web designers and, in several ways, more powerful than PHP templates:
-
-.. code-block:: html+jinja
-
-
-
-
- Welcome to Symfony!
-
-
-
-
-
-
-Twig defines two types of special syntax:
-
-* ``{{ ... }}``: "Says something": prints a variable or the result of an
- expression to the template;
-
-* ``{% ... %}``: "Does something": a **tag** that controls the logic of the
- template; it is used to execute statements such as for-loops for example.
-
-.. note::
-
- There is a third syntax used for creating comments: ``{# this is a comment #}``.
- This syntax can be used across multiple lines like the PHP-equivalent
- ``/* comment */`` syntax.
-
-Twig also contains **filters**, which modify content before being rendered.
-The following makes the ``title`` variable all uppercase before rendering
-it:
-
-.. code-block:: jinja
-
- {{ title|upper }}
-
-Twig comes with a long list of `tags`_ and `filters`_ that are available
-by default. You can even `add your own extensions`_ to Twig as needed.
-
-.. tip::
-
- Registering a Twig extension is as easy as creating a new service and tagging
- it with ``twig.extension`` :ref:`tag`.
-
-As you'll see throughout the documentation, Twig also supports functions
-and new functions can be easily added. For example, the following uses a
-standard ``for`` tag and the ``cycle`` function to print ten div tags, with
-alternating ``odd``, ``even`` classes:
-
-.. code-block:: html+jinja
-
- {% for i in 0..10 %}
-
-
-
- {% endfor %}
-
-Throughout this chapter, template examples will be shown in both Twig and PHP.
-
-.. tip::
-
- If you *do* choose to not use Twig and you disable it, you'll need to implement
- your own exception handler via the ``kernel.exception`` event.
-
-.. sidebar:: Why Twig?
-
- Twig templates are meant to be simple and won't process PHP tags. This
- is by design: the Twig template system is meant to express presentation,
- not program logic. The more you use Twig, the more you'll appreciate
- and benefit from this distinction. And of course, you'll be loved by
- web designers everywhere.
-
- Twig can also do things that PHP can't, such as whitespace control,
- sandboxing, automatic and contextual output escaping, and the inclusion of
- custom functions and filters that only affect templates. Twig contains
- little features that make writing templates easier and more concise. Take
- the following example, which combines a loop with a logical ``if``
- statement:
-
- .. code-block:: html+jinja
-
-
- {% for user in users if user.active %}
-
{{ user.username }}
- {% else %}
-
No users found
- {% endfor %}
-
-
-.. index::
- pair: Twig; Cache
-
-Twig Template Caching
-~~~~~~~~~~~~~~~~~~~~~
-
-Twig is fast. Each Twig template is compiled down to a native PHP class
-that is rendered at runtime. The compiled classes are located in the
-``app/cache/{environment}/twig`` directory (where ``{environment}`` is the
-environment, such as ``dev`` or ``prod``) and in some cases can be useful
-while debugging. See :ref:`environments-summary` for more information on
-environments.
-
-When ``debug`` mode is enabled (common in the ``dev`` environment), a Twig
-template will be automatically recompiled when changes are made to it. This
-means that during development you can happily make changes to a Twig template
-and instantly see the changes without needing to worry about clearing any
-cache.
-
-When ``debug`` mode is disabled (common in the ``prod`` environment), however,
-you must clear the Twig cache directory so that the Twig templates will
-regenerate. Remember to do this when deploying your application.
-
-.. index::
- single: Templating; Inheritance
-
-Template Inheritance and Layouts
---------------------------------
-
-More often than not, templates in a project share common elements, like the
-header, footer, sidebar or more. In Symfony2, this problem is thought about
-differently: a template can be decorated by another one. This works
-exactly the same as PHP classes: template inheritance allows you to build
-a base "layout" template that contains all the common elements of your site
-defined as **blocks** (think "PHP class with base methods"). A child template
-can extend the base layout and override any of its blocks (think "PHP subclass
-that overrides certain methods of its parent class").
-
-First, build a base layout file:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
- {# app/Resources/views/base.html.twig #}
-
-
-
-
- {% block title %}Test Application{% endblock %}
-
-
-
-
-
-
-.. note::
-
- Though the discussion about template inheritance will be in terms of Twig,
- the philosophy is the same between Twig and PHP templates.
-
-This template defines the base HTML skeleton document of a simple two-column
-page. In this example, three ``{% block %}`` areas are defined (``title``,
-``sidebar`` and ``body``). Each block may be overridden by a child template
-or left with its default implementation. This template could also be rendered
-directly. In that case the ``title``, ``sidebar`` and ``body`` blocks would
-simply retain the default values used in this template.
-
-A child template might look like this:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
- {# src/Acme/BlogBundle/Resources/views/Blog/index.html.twig #}
- {% extends '::base.html.twig' %}
-
- {% block title %}My cool blog posts{% endblock %}
-
- {% block body %}
- {% for entry in blog_entries %}
-
-
- stop() ?>
-
-.. note::
-
- The parent template is identified by a special string syntax
- (``::base.html.twig``) that indicates that the template lives in the
- ``app/Resources/views`` directory of the project. This naming convention is
- explained fully in :ref:`template-naming-locations`.
-
-The key to template inheritance is the ``{% extends %}`` tag. This tells
-the templating engine to first evaluate the base template, which sets up
-the layout and defines several blocks. The child template is then rendered,
-at which point the ``title`` and ``body`` blocks of the parent are replaced
-by those from the child. Depending on the value of ``blog_entries``, the
-output might look like this:
-
-.. code-block:: html
-
-
-
-
-
- My cool blog posts
-
-
-
-
-
-
-Notice that since the child template didn't define a ``sidebar`` block, the
-value from the parent template is used instead. Content within a ``{% block %}``
-tag in a parent template is always used by default.
-
-You can use as many levels of inheritance as you want. In the next section,
-a common three-level inheritance model will be explained along with how templates
-are organized inside a Symfony2 project.
-
-When working with template inheritance, here are some tips to keep in mind:
-
-* If you use ``{% extends %}`` in a template, it must be the first tag in
- that template;
-
-* The more ``{% block %}`` tags you have in your base templates, the better.
- Remember, child templates don't have to define all parent blocks, so create
- as many blocks in your base templates as you want and give each a sensible
- default. The more blocks your base templates have, the more flexible your
- layout will be;
-
-* If you find yourself duplicating content in a number of templates, it probably
- means you should move that content to a ``{% block %}`` in a parent template.
- In some cases, a better solution may be to move the content to a new template
- and ``include`` it (see :ref:`including-templates`);
-
-* If you need to get the content of a block from the parent template, you
- can use the ``{{ parent() }}`` function. This is useful if you want to add
- to the contents of a parent block instead of completely overriding it:
-
- .. code-block:: html+jinja
-
- {% block sidebar %}
-
Table of Contents
-
- {# ... #}
-
- {{ parent() }}
- {% endblock %}
-
-.. index::
- single: Templating; Naming conventions
- single: Templating; File locations
-
-.. _template-naming-locations:
-
-Template Naming and Locations
------------------------------
-
-.. versionadded:: 2.2
- Namespaced path support was added in 2.2, allowing for template names
- like ``@AcmeDemo/layout.html.twig``. See :doc:`/cookbook/templating/namespaced_paths`
- for more details.
-
-By default, templates can live in two different locations:
-
-* ``app/Resources/views/``: The applications ``views`` directory can contain
- application-wide base templates (i.e. your application's layouts) as well as
- templates that override bundle templates (see
- :ref:`overriding-bundle-templates`);
-
-* ``path/to/bundle/Resources/views/``: Each bundle houses its templates in its
- ``Resources/views`` directory (and subdirectories). The majority of templates
- will live inside a bundle.
-
-Symfony2 uses a **bundle**:**controller**:**template** string syntax for
-templates. This allows for several different types of templates, each which
-lives in a specific location:
-
-* ``AcmeBlogBundle:Blog:index.html.twig``: This syntax is used to specify a
- template for a specific page. The three parts of the string, each separated
- by a colon (``:``), mean the following:
-
- * ``AcmeBlogBundle``: (*bundle*) the template lives inside the
- ``AcmeBlogBundle`` (e.g. ``src/Acme/BlogBundle``);
-
- * ``Blog``: (*controller*) indicates that the template lives inside the
- ``Blog`` subdirectory of ``Resources/views``;
-
- * ``index.html.twig``: (*template*) the actual name of the file is
- ``index.html.twig``.
-
- Assuming that the ``AcmeBlogBundle`` lives at ``src/Acme/BlogBundle``, the
- final path to the layout would be ``src/Acme/BlogBundle/Resources/views/Blog/index.html.twig``.
-
-* ``AcmeBlogBundle::layout.html.twig``: This syntax refers to a base template
- that's specific to the ``AcmeBlogBundle``. Since the middle, "controller",
- portion is missing (e.g. ``Blog``), the template lives at
- ``Resources/views/layout.html.twig`` inside ``AcmeBlogBundle``.
-
-* ``::base.html.twig``: This syntax refers to an application-wide base template
- or layout. Notice that the string begins with two colons (``::``), meaning
- that both the *bundle* and *controller* portions are missing. This means
- that the template is not located in any bundle, but instead in the root
- ``app/Resources/views/`` directory.
-
-In the :ref:`overriding-bundle-templates` section, you'll find out how each
-template living inside the ``AcmeBlogBundle``, for example, can be overridden
-by placing a template of the same name in the ``app/Resources/AcmeBlogBundle/views/``
-directory. This gives the power to override templates from any vendor bundle.
-
-.. tip::
-
- Hopefully the template naming syntax looks familiar - it's the same naming
- convention used to refer to :ref:`controller-string-syntax`.
-
-Template Suffix
-~~~~~~~~~~~~~~~
-
-The **bundle**:**controller**:**template** format of each template specifies
-*where* the template file is located. Every template name also has two extensions
-that specify the *format* and *engine* for that template.
-
-* **AcmeBlogBundle:Blog:index.html.twig** - HTML format, Twig engine
-
-* **AcmeBlogBundle:Blog:index.html.php** - HTML format, PHP engine
-
-* **AcmeBlogBundle:Blog:index.css.twig** - CSS format, Twig engine
-
-By default, any Symfony2 template can be written in either Twig or PHP, and
-the last part of the extension (e.g. ``.twig`` or ``.php``) specifies which
-of these two *engines* should be used. The first part of the extension,
-(e.g. ``.html``, ``.css``, etc) is the final format that the template will
-generate. Unlike the engine, which determines how Symfony2 parses the template,
-this is simply an organizational tactic used in case the same resource needs
-to be rendered as HTML (``index.html.twig``), XML (``index.xml.twig``),
-or any other format. For more information, read the :ref:`template-formats`
-section.
-
-.. note::
-
- The available "engines" can be configured and even new engines added.
- See :ref:`Templating Configuration` for more details.
-
-.. index::
- single: Templating; Tags and helpers
- single: Templating; Helpers
-
-Tags and Helpers
-----------------
-
-You already understand the basics of templates, how they're named and how
-to use template inheritance. The hardest parts are already behind you. In
-this section, you'll learn about a large group of tools available to help
-perform the most common template tasks such as including other templates,
-linking to pages and including images.
-
-Symfony2 comes bundled with several specialized Twig tags and functions that
-ease the work of the template designer. In PHP, the templating system provides
-an extensible *helper* system that provides useful features in a template
-context.
-
-You've already seen a few built-in Twig tags (``{% block %}`` & ``{% extends %}``)
-as well as an example of a PHP helper (``$view['slots']``). Here you will learn a
-few more.
-
-.. index::
- single: Templating; Including other templates
-
-.. _including-templates:
-
-Including other Templates
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You'll often want to include the same template or code fragment on several
-different pages. For example, in an application with "news articles", the
-template code displaying an article might be used on the article detail page,
-on a page displaying the most popular articles, or in a list of the latest
-articles.
-
-When you need to reuse a chunk of PHP code, you typically move the code to
-a new PHP class or function. The same is true for templates. By moving the
-reused template code into its own template, it can be included from any other
-template. First, create the template that you'll need to reuse.
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
- {# src/Acme/ArticleBundle/Resources/views/Article/articleDetails.html.twig #}
-
{{ article.title }}
-
by {{ article.authorName }}
-
-
- {{ article.body }}
-
-
- .. code-block:: html+php
-
-
-
getTitle() ?>
-
by getAuthorName() ?>
-
-
- getBody() ?>
-
-
-Including this template from any other template is simple:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
- {# src/Acme/ArticleBundle/Resources/views/Article/list.html.twig #}
- {% extends 'AcmeArticleBundle::layout.html.twig' %}
-
- {% block body %}
-
-
-
- render(
- 'AcmeArticleBundle:Article:articleDetails.html.php',
- array('article' => $article)
- ) ?>
-
- stop() ?>
-
-The template is included using the ``{{ include() }}`` function. Notice that the
-template name follows the same typical convention. The ``articleDetails.html.twig``
-template uses an ``article`` variable. This is passed in by the ``list.html.twig``
-template using the ``with`` command.
-
-.. tip::
-
- The ``{'article': article}`` syntax is the standard Twig syntax for hash
- maps (i.e. an array with named keys). If you needed to pass in multiple
- elements, it would look like this: ``{'foo': foo, 'bar': bar}``.
-
-.. versionadded:: 2.2
- The ``include()`` function is a new Twig feature that's available in
- Symfony 2.2. Prior, the ``{% include %}`` tag was used.
-
-.. index::
- single: Templating; Embedding action
-
-.. _templating-embedding-controller:
-
-Embedding Controllers
-~~~~~~~~~~~~~~~~~~~~~
-
-In some cases, you need to do more than include a simple template. Suppose
-you have a sidebar in your layout that contains the three most recent articles.
-Retrieving the three articles may include querying the database or performing
-other heavy logic that can't be done from within a template.
-
-The solution is to simply embed the result of an entire controller from your
-template. First, create a controller that renders a certain number of recent
-articles::
-
- // src/Acme/ArticleBundle/Controller/ArticleController.php
- class ArticleController extends Controller
- {
- public function recentArticlesAction($max = 3)
- {
- // make a database call or other logic
- // to get the "$max" most recent articles
- $articles = ...;
-
- return $this->render(
- 'AcmeArticleBundle:Article:recentList.html.twig',
- array('articles' => $articles)
- );
- }
- }
-
-The ``recentList`` template is perfectly straightforward:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
- {# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #}
- {% for article in articles %}
-
- {{ article.title }}
-
- {% endfor %}
-
- .. code-block:: html+php
-
-
-
-
- getTitle() ?>
-
-
-
-.. note::
-
- Notice that the article URL is hardcoded in this example
- (e.g. ``/article/*slug*``). This is a bad practice. In the next section,
- you'll learn how to do this correctly.
-
-To include the controller, you'll need to refer to it using the standard
-string syntax for controllers (i.e. **bundle**:**controller**:**action**):
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
- {# app/Resources/views/base.html.twig #}
-
- {# ... #}
-
-
-Whenever you find that you need a variable or a piece of information that
-you don't have access to in a template, consider rendering a controller.
-Controllers are fast to execute and promote good code organization and reuse.
-
-Asynchronous Content with hinclude.js
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. versionadded:: 2.1
- hinclude.js support was added in Symfony 2.1
-
-Controllers can be embedded asynchronously using the hinclude.js_ javascript library.
-As the embedded content comes from another page (or controller for that matter),
-Symfony2 uses the standard ``render`` helper to configure ``hinclude`` tags:
-
-.. configuration-block::
-
- .. code-block:: jinja
-
- {% render url('...') with {}, {'standalone': 'js'} %}
-
- .. code-block:: php
-
- render(
- new ControllerReference('...'),
- array('renderer' => 'hinclude')
- ) ?>
-
- render(
- $view['router']->generate('...'),
- array('renderer' => 'hinclude')
- ) ?>
-
-.. note::
-
- hinclude.js_ needs to be included in your page to work.
-
-.. note::
-
- When using a controller instead of a URL, you must enable the Symfony
- ``fragments`` configuration:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- framework:
- # ...
- fragments: { path: /_fragment }
-
- .. code-block:: xml
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- $container->loadFromExtension('framework', array(
- // ...
- 'fragments' => array('path' => '/_fragment'),
- ));
-
-Default content (while loading or if javascript is disabled) can be set globally
-in your application configuration:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- framework:
- # ...
- templating:
- hinclude_default_template: AcmeDemoBundle::hinclude.html.twig
-
- .. code-block:: xml
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- $container->loadFromExtension('framework', array(
- // ...
- 'templating' => array(
- 'hinclude_default_template' => array('AcmeDemoBundle::hinclude.html.twig'),
- ),
- ));
-
-.. versionadded:: 2.2
- Default templates per render function was added in Symfony 2.2
-
-You can define default templates per ``render`` function (which will override
-any global default template that is defined):
-
-.. configuration-block::
-
- .. code-block:: jinja
-
- {{ render_hinclude(controller('...'), {'default': 'AcmeDemoBundle:Default:content.html.twig'}) }}
-
- .. code-block:: php
-
- render(
- new ControllerReference('...'),
- array(
- 'renderer' => 'hinclude',
- 'default' => 'AcmeDemoBundle:Default:content.html.twig',
- )
- ) ?>
-
-Or you can also specify a string to display as the default content:
-
-.. configuration-block::
-
- .. code-block:: jinja
-
- {{ render_hinclude(controller('...'), {'default': 'Loading...'}) }}
-
- .. code-block:: php
-
- render(
- new ControllerReference('...'),
- array(
- 'renderer' => 'hinclude',
- 'default' => 'Loading...',
- )
- ) ?>
-
-.. index::
- single: Templating; Linking to pages
-
-.. _book-templating-pages:
-
-Linking to Pages
-~~~~~~~~~~~~~~~~
-
-Creating links to other pages in your application is one of the most common
-jobs for a template. Instead of hardcoding URLs in templates, use the ``path``
-Twig function (or the ``router`` helper in PHP) to generate URLs based on
-the routing configuration. Later, if you want to modify the URL of a particular
-page, all you'll need to do is change the routing configuration; the templates
-will automatically generate the new URL.
-
-First, link to the "_welcome" page, which is accessible via the following routing
-configuration:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- _welcome:
- path: /
- defaults: { _controller: AcmeDemoBundle:Welcome:index }
-
- .. code-block:: xml
-
-
- AcmeDemoBundle:Welcome:index
-
-
- .. code-block:: php
-
- $collection = new RouteCollection();
- $collection->add('_welcome', new Route('/', array(
- '_controller' => 'AcmeDemoBundle:Welcome:index',
- )));
-
- return $collection;
-
-To link to the page, just use the ``path`` Twig function and refer to the route:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
- Home
-
- .. code-block:: html+php
-
- Home
-
-As expected, this will generate the URL ``/``. Now for a more complicated
-route:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- article_show:
- path: /article/{slug}
- defaults: { _controller: AcmeArticleBundle:Article:show }
-
- .. code-block:: xml
-
-
- AcmeArticleBundle:Article:show
-
-
- .. code-block:: php
-
- $collection = new RouteCollection();
- $collection->add('article_show', new Route('/article/{slug}', array(
- '_controller' => 'AcmeArticleBundle:Article:show',
- )));
-
- return $collection;
-
-In this case, you need to specify both the route name (``article_show``) and
-a value for the ``{slug}`` parameter. Using this route, revisit the
-``recentList`` template from the previous section and link to the articles
-correctly:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
- {# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #}
- {% for article in articles %}
-
- {{ article.title }}
-
- {% endfor %}
-
- .. code-block:: html+php
-
-
-
-
- getTitle() ?>
-
-
-
-.. tip::
-
- You can also generate an absolute URL by using the ``url`` Twig function:
-
- .. code-block:: html+jinja
-
- Home
-
- The same can be done in PHP templates by passing a third argument to
- the ``generate()`` method:
-
- .. code-block:: html+php
-
- Home
-
-.. index::
- single: Templating; Linking to assets
-
-.. _book-templating-assets:
-
-Linking to Assets
-~~~~~~~~~~~~~~~~~
-
-Templates also commonly refer to images, Javascript, stylesheets and other
-assets. Of course you could hard-code the path to these assets (e.g. ``/images/logo.png``),
-but Symfony2 provides a more dynamic option via the ``asset`` Twig function:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
-
-
-
-
- .. code-block:: html+php
-
-
-
-
-
-The ``asset`` function's main purpose is to make your application more portable.
-If your application lives at the root of your host (e.g. http://example.com),
-then the rendered paths should be ``/images/logo.png``. But if your application
-lives in a subdirectory (e.g. http://example.com/my_app), each asset path
-should render with the subdirectory (e.g. ``/my_app/images/logo.png``). The
-``asset`` function takes care of this by determining how your application is
-being used and generating the correct paths accordingly.
-
-Additionally, if you use the ``asset`` function, Symfony can automatically
-append a query string to your asset, in order to guarantee that updated static
-assets won't be cached when deployed. For example, ``/images/logo.png`` might
-look like ``/images/logo.png?v2``. For more information, see the :ref:`ref-framework-assets-version`
-configuration option.
-
-.. index::
- single: Templating; Including stylesheets and Javascripts
- single: Stylesheets; Including stylesheets
- single: Javascript; Including Javascripts
-
-Including Stylesheets and Javascripts in Twig
----------------------------------------------
-
-No site would be complete without including Javascript files and stylesheets.
-In Symfony, the inclusion of these assets is handled elegantly by taking
-advantage of Symfony's template inheritance.
-
-.. tip::
-
- This section will teach you the philosophy behind including stylesheet
- and Javascript assets in Symfony. Symfony also packages another library,
- called Assetic, which follows this philosophy but allows you to do much
- more interesting things with those assets. For more information on
- using Assetic see :doc:`/cookbook/assetic/asset_management`.
-
-
-Start by adding two blocks to your base template that will hold your assets:
-one called ``stylesheets`` inside the ``head`` tag and another called ``javascripts``
-just above the closing ``body`` tag. These blocks will contain all of the
-stylesheets and Javascripts that you'll need throughout your site:
-
-.. code-block:: html+jinja
-
- {# app/Resources/views/base.html.twig #}
-
-
- {# ... #}
-
- {% block stylesheets %}
-
- {% endblock %}
-
-
- {# ... #}
-
- {% block javascripts %}
-
- {% endblock %}
-
-
-
-That's easy enough! But what if you need to include an extra stylesheet or
-Javascript from a child template? For example, suppose you have a contact
-page and you need to include a ``contact.css`` stylesheet *just* on that
-page. From inside that contact page's template, do the following:
-
-.. code-block:: html+jinja
-
- {# src/Acme/DemoBundle/Resources/views/Contact/contact.html.twig #}
- {% extends '::base.html.twig' %}
-
- {% block stylesheets %}
- {{ parent() }}
-
-
- {% endblock %}
-
- {# ... #}
-
-In the child template, you simply override the ``stylesheets`` block and
-put your new stylesheet tag inside of that block. Of course, since you want
-to add to the parent block's content (and not actually *replace* it), you
-should use the ``parent()`` Twig function to include everything from the ``stylesheets``
-block of the base template.
-
-You can also include assets located in your bundles' ``Resources/public`` folder.
-You will need to run the ``php app/console assets:install target [--symlink]``
-command, which moves (or symlinks) files into the correct location. (target
-is by default "web").
-
-.. code-block:: html+jinja
-
-
-
-The end result is a page that includes both the ``main.css`` and ``contact.css``
-stylesheets.
-
-Global Template Variables
--------------------------
-
-During each request, Symfony2 will set a global template variable ``app``
-in both Twig and PHP template engines by default. The ``app`` variable
-is a :class:`Symfony\\Bundle\\FrameworkBundle\\Templating\\GlobalVariables`
-instance which will give you access to some application specific variables
-automatically:
-
-* ``app.security`` - The security context.
-* ``app.user`` - The current user object.
-* ``app.request`` - The request object.
-* ``app.session`` - The session object.
-* ``app.environment`` - The current environment (dev, prod, etc).
-* ``app.debug`` - True if in debug mode. False otherwise.
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
-
Username: {{ app.user.username }}
- {% if app.debug %}
-
Request method: {{ app.request.method }}
-
Application Environment: {{ app.environment }}
- {% endif %}
-
- .. code-block:: html+php
-
-
Username: getUser()->getUsername() ?>
- getDebug()): ?>
-
Request method: getRequest()->getMethod() ?>
-
Application Environment: getEnvironment() ?>
-
-
-.. tip::
-
- You can add your own global template variables. See the cookbook example
- on :doc:`Global Variables`.
-
-.. index::
- single: Templating; The templating service
-
-Configuring and using the ``templating`` Service
-------------------------------------------------
-
-The heart of the template system in Symfony2 is the templating ``Engine``.
-This special object is responsible for rendering templates and returning
-their content. When you render a template in a controller, for example,
-you're actually using the templating engine service. For example::
-
- return $this->render('AcmeArticleBundle:Article:index.html.twig');
-
-is equivalent to::
-
- use Symfony\Component\HttpFoundation\Response;
-
- $engine = $this->container->get('templating');
- $content = $engine->render('AcmeArticleBundle:Article:index.html.twig');
-
- return $response = new Response($content);
-
-.. _template-configuration:
-
-The templating engine (or "service") is preconfigured to work automatically
-inside Symfony2. It can, of course, be configured further in the application
-configuration file:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- framework:
- # ...
- templating: { engines: ['twig'] }
-
- .. code-block:: xml
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- $container->loadFromExtension('framework', array(
- // ...
-
- 'templating' => array(
- 'engines' => array('twig'),
- ),
- ));
-
-Several configuration options are available and are covered in the
-:doc:`Configuration Appendix`.
-
-.. note::
-
- The ``twig`` engine is mandatory to use the webprofiler (as well as many
- third-party bundles).
-
-.. index::
- single: Template; Overriding templates
-
-.. _overriding-bundle-templates:
-
-Overriding Bundle Templates
----------------------------
-
-The Symfony2 community prides itself on creating and maintaining high quality
-bundles (see `KnpBundles.com`_) for a large number of different features.
-Once you use a third-party bundle, you'll likely need to override and customize
-one or more of its templates.
-
-Suppose you've included the imaginary open-source ``AcmeBlogBundle`` in your
-project (e.g. in the ``src/Acme/BlogBundle`` directory). And while you're
-really happy with everything, you want to override the blog "list" page to
-customize the markup specifically for your application. By digging into the
-``Blog`` controller of the ``AcmeBlogBundle``, you find the following::
-
- public function indexAction()
- {
- // some logic to retrieve the blogs
- $blogs = ...;
-
- $this->render(
- 'AcmeBlogBundle:Blog:index.html.twig',
- array('blogs' => $blogs)
- );
- }
-
-When the ``AcmeBlogBundle:Blog:index.html.twig`` is rendered, Symfony2 actually
-looks in two different locations for the template:
-
-#. ``app/Resources/AcmeBlogBundle/views/Blog/index.html.twig``
-#. ``src/Acme/BlogBundle/Resources/views/Blog/index.html.twig``
-
-To override the bundle template, just copy the ``index.html.twig`` template
-from the bundle to ``app/Resources/AcmeBlogBundle/views/Blog/index.html.twig``
-(the ``app/Resources/AcmeBlogBundle`` directory won't exist, so you'll need
-to create it). You're now free to customize the template.
-
-.. caution::
-
- If you add a template in a new location, you *may* need to clear your
- cache (``php app/console cache:clear``), even if you are in debug mode.
-
-This logic also applies to base bundle templates. Suppose also that each
-template in ``AcmeBlogBundle`` inherits from a base template called
-``AcmeBlogBundle::layout.html.twig``. Just as before, Symfony2 will look in
-the following two places for the template:
-
-#. ``app/Resources/AcmeBlogBundle/views/layout.html.twig``
-#. ``src/Acme/BlogBundle/Resources/views/layout.html.twig``
-
-Once again, to override the template, just copy it from the bundle to
-``app/Resources/AcmeBlogBundle/views/layout.html.twig``. You're now free to
-customize this copy as you see fit.
-
-If you take a step back, you'll see that Symfony2 always starts by looking in
-the ``app/Resources/{BUNDLE_NAME}/views/`` directory for a template. If the
-template doesn't exist there, it continues by checking inside the
-``Resources/views`` directory of the bundle itself. This means that all bundle
-templates can be overridden by placing them in the correct ``app/Resources``
-subdirectory.
-
-.. note::
-
- You can also override templates from within a bundle by using bundle
- inheritance. For more information, see :doc:`/cookbook/bundles/inheritance`.
-
-.. _templating-overriding-core-templates:
-
-.. index::
- single: Template; Overriding exception templates
-
-Overriding Core Templates
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Since the Symfony2 framework itself is just a bundle, core templates can be
-overridden in the same way. For example, the core ``TwigBundle`` contains
-a number of different "exception" and "error" templates that can be overridden
-by copying each from the ``Resources/views/Exception`` directory of the
-``TwigBundle`` to, you guessed it, the
-``app/Resources/TwigBundle/views/Exception`` directory.
-
-.. index::
- single: Templating; Three-level inheritance pattern
-
-Three-level Inheritance
------------------------
-
-One common way to use inheritance is to use a three-level approach. This
-method works perfectly with the three different types of templates that were just
-covered:
-
-* Create a ``app/Resources/views/base.html.twig`` file that contains the main
- layout for your application (like in the previous example). Internally, this
- template is called ``::base.html.twig``;
-
-* Create a template for each "section" of your site. For example, an ``AcmeBlogBundle``,
- would have a template called ``AcmeBlogBundle::layout.html.twig`` that contains
- only blog section-specific elements;
-
- .. code-block:: html+jinja
-
- {# src/Acme/BlogBundle/Resources/views/layout.html.twig #}
- {% extends '::base.html.twig' %}
-
- {% block body %}
-
Blog Application
-
- {% block content %}{% endblock %}
- {% endblock %}
-
-* Create individual templates for each page and make each extend the appropriate
- section template. For example, the "index" page would be called something
- close to ``AcmeBlogBundle:Blog:index.html.twig`` and list the actual blog posts.
-
- .. code-block:: html+jinja
-
- {# src/Acme/BlogBundle/Resources/views/Blog/index.html.twig #}
- {% extends 'AcmeBlogBundle::layout.html.twig' %}
-
- {% block content %}
- {% for entry in blog_entries %}
-
{{ entry.title }}
-
{{ entry.body }}
- {% endfor %}
- {% endblock %}
-
-Notice that this template extends the section template -(``AcmeBlogBundle::layout.html.twig``)
-which in-turn extends the base application layout (``::base.html.twig``).
-This is the common three-level inheritance model.
-
-When building your application, you may choose to follow this method or simply
-make each page template extend the base application template directly
-(e.g. ``{% extends '::base.html.twig' %}``). The three-template model is
-a best-practice method used by vendor bundles so that the base template for
-a bundle can be easily overridden to properly extend your application's base
-layout.
-
-.. index::
- single: Templating; Output escaping
-
-Output Escaping
----------------
-
-When generating HTML from a template, there is always a risk that a template
-variable may output unintended HTML or dangerous client-side code. The result
-is that dynamic content could break the HTML of the resulting page or allow
-a malicious user to perform a `Cross Site Scripting`_ (XSS) attack. Consider
-this classic example:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
- Hello {{ name }}
-
- .. code-block:: html+php
-
- Hello
-
-Imagine that the user enters the following code as his/her name:
-
-.. code-block:: text
-
-
-
-Without any output escaping, the resulting template will cause a JavaScript
-alert box to pop up:
-
-.. code-block:: html
-
- Hello
-
-And while this seems harmless, if a user can get this far, that same user
-should also be able to write JavaScript that performs malicious actions
-inside the secure area of an unknowing, legitimate user.
-
-The answer to the problem is output escaping. With output escaping on, the
-same template will render harmlessly, and literally print the ``script``
-tag to the screen:
-
-.. code-block:: html
-
- Hello <script>alert('helloe')</script>
-
-The Twig and PHP templating systems approach the problem in different ways.
-If you're using Twig, output escaping is on by default and you're protected.
-In PHP, output escaping is not automatic, meaning you'll need to manually
-escape where necessary.
-
-Output Escaping in Twig
-~~~~~~~~~~~~~~~~~~~~~~~
-
-If you're using Twig templates, then output escaping is on by default. This
-means that you're protected out-of-the-box from the unintentional consequences
-of user-submitted code. By default, the output escaping assumes that content
-is being escaped for HTML output.
-
-In some cases, you'll need to disable output escaping when you're rendering
-a variable that is trusted and contains markup that should not be escaped.
-Suppose that administrative users are able to write articles that contain
-HTML code. By default, Twig will escape the article body.
-
-To render it normally, add the ``raw`` filter:
-
-.. code-block:: jinja
-
- {{ article.body|raw }}
-
-You can also disable output escaping inside a ``{% block %}`` area or
-for an entire template. For more information, see `Output Escaping`_ in
-the Twig documentation.
-
-Output Escaping in PHP
-~~~~~~~~~~~~~~~~~~~~~~
-
-Output escaping is not automatic when using PHP templates. This means that
-unless you explicitly choose to escape a variable, you're not protected. To
-use output escaping, use the special ``escape()`` view method:
-
-.. code-block:: html+php
-
- Hello escape($name) ?>
-
-By default, the ``escape()`` method assumes that the variable is being rendered
-within an HTML context (and thus the variable is escaped to be safe for HTML).
-The second argument lets you change the context. For example, to output something
-in a JavaScript string, use the ``js`` context:
-
-.. code-block:: html+php
-
- var myMsg = 'Hello escape($name, 'js') ?>';
-
-.. index::
- single: Templating; Formats
-
-.. _template-formats:
-
-Debugging
----------
-
-.. versionadded:: 2.0.9
- This feature is available as of Twig ``1.5.x``, which was first shipped
- with Symfony 2.0.9.
-
-When using PHP, you can use ``var_dump()`` if you need to quickly find the
-value of a variable passed. This is useful, for example, inside your controller.
-The same can be achieved when using Twig by using the debug extension. This
-needs to be enabled in the config:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- services:
- acme_hello.twig.extension.debug:
- class: Twig_Extension_Debug
- tags:
- - { name: 'twig.extension' }
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- use Symfony\Component\DependencyInjection\Definition;
-
- $definition = new Definition('Twig_Extension_Debug');
- $definition->addTag('twig.extension');
- $container->setDefinition('acme_hello.twig.extension.debug', $definition);
-
-Template parameters can then be dumped using the ``dump`` function:
-
-.. code-block:: html+jinja
-
- {# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #}
- {{ dump(articles) }}
-
- {% for article in articles %}
-
- {{ article.title }}
-
- {% endfor %}
-
-
-The variables will only be dumped if Twig's ``debug`` setting (in ``config.yml``)
-is ``true``. By default this means that the variables will be dumped in the
-``dev`` environment but not the ``prod`` environment.
-
-Syntax Checking
----------------
-
-.. versionadded:: 2.1
- The ``twig:lint`` command was added in Symfony 2.1
-
-You can check for syntax errors in Twig templates using the ``twig:lint``
-console command:
-
-.. code-block:: bash
-
- # You can check by filename:
- $ php app/console twig:lint src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig
-
- # or by directory:
- $ php app/console twig:lint src/Acme/ArticleBundle/Resources/views
-
- # or using the bundle name:
- $ php app/console twig:lint @AcmeArticleBundle
-
-Template Formats
-----------------
-
-Templates are a generic way to render content in *any* format. And while in
-most cases you'll use templates to render HTML content, a template can just
-as easily generate JavaScript, CSS, XML or any other format you can dream of.
-
-For example, the same "resource" is often rendered in several different formats.
-To render an article index page in XML, simply include the format in the
-template name:
-
-* *XML template name*: ``AcmeArticleBundle:Article:index.xml.twig``
-* *XML template filename*: ``index.xml.twig``
-
-In reality, this is nothing more than a naming convention and the template
-isn't actually rendered differently based on its format.
-
-In many cases, you may want to allow a single controller to render multiple
-different formats based on the "request format". For that reason, a common
-pattern is to do the following::
-
- public function indexAction()
- {
- $format = $this->getRequest()->getRequestFormat();
-
- return $this->render('AcmeBlogBundle:Blog:index.'.$format.'.twig');
- }
-
-The ``getRequestFormat`` on the ``Request`` object defaults to ``html``,
-but can return any other format based on the format requested by the user.
-The request format is most often managed by the routing, where a route can
-be configured so that ``/contact`` sets the request format to ``html`` while
-``/contact.xml`` sets the format to ``xml``. For more information, see the
-:ref:`Advanced Example in the Routing chapter `.
-
-To create links that include the format parameter, include a ``_format``
-key in the parameter hash:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
-
- PDF Version
-
-
- .. code-block:: html+php
-
-
- PDF Version
-
-
-Final Thoughts
---------------
-
-The templating engine in Symfony is a powerful tool that can be used each time
-you need to generate presentational content in HTML, XML or any other format.
-And though templates are a common way to generate content in a controller,
-their use is not mandatory. The ``Response`` object returned by a controller
-can be created with or without the use of a template::
-
- // creates a Response object whose content is the rendered template
- $response = $this->render('AcmeArticleBundle:Article:index.html.twig');
-
- // creates a Response object whose content is simple text
- $response = new Response('response content');
-
-Symfony's templating engine is very flexible and two different template
-renderers are available by default: the traditional *PHP* templates and the
-sleek and powerful *Twig* templates. Both support a template hierarchy and
-come packaged with a rich set of helper functions capable of performing
-the most common tasks.
-
-Overall, the topic of templating should be thought of as a powerful tool
-that's at your disposal. In some cases, you may not need to render a template,
-and in Symfony2, that's absolutely fine.
-
-Learn more from the Cookbook
-----------------------------
-
-* :doc:`/cookbook/templating/PHP`
-* :doc:`/cookbook/controller/error_pages`
-* :doc:`/cookbook/templating/twig_extension`
-
-.. _`Twig`: http://twig.sensiolabs.org
-.. _`KnpBundles.com`: http://knpbundles.com
-.. _`Cross Site Scripting`: http://en.wikipedia.org/wiki/Cross-site_scripting
-.. _`Output Escaping`: http://twig.sensiolabs.org/doc/api.html#escaper-extension
-.. _`tags`: http://twig.sensiolabs.org/doc/tags/index.html
-.. _`filters`: http://twig.sensiolabs.org/doc/filters/index.html
-.. _`add your own extensions`: http://twig.sensiolabs.org/doc/advanced.html#creating-an-extension
-.. _`hinclude.js`: http://mnot.github.com/hinclude/
diff --git a/book/testing.rst b/book/testing.rst
deleted file mode 100644
index d042b7cefb2..00000000000
--- a/book/testing.rst
+++ /dev/null
@@ -1,819 +0,0 @@
-.. index::
- single: Tests
-
-Testing
-=======
-
-Whenever you write a new line of code, you also potentially add new bugs.
-To build better and more reliable applications, you should test your code
-using both functional and unit tests.
-
-The PHPUnit Testing Framework
------------------------------
-
-Symfony2 integrates with an independent library - called PHPUnit - to give
-you a rich testing framework. This chapter won't cover PHPUnit itself, but
-it has its own excellent `documentation`_.
-
-.. note::
-
- Symfony2 works with PHPUnit 3.5.11 or later, though version 3.6.4 is
- needed to test the Symfony core code itself.
-
-Each test - whether it's a unit test or a functional test - is a PHP class
-that should live in the `Tests/` subdirectory of your bundles. If you follow
-this rule, then you can run all of your application's tests with the following
-command:
-
-.. code-block:: bash
-
- # specify the configuration directory on the command line
- $ phpunit -c app/
-
-The ``-c`` option tells PHPUnit to look in the ``app/`` directory for a configuration
-file. If you're curious about the PHPUnit options, check out the ``app/phpunit.xml.dist``
-file.
-
-.. tip::
-
- Code coverage can be generated with the ``--coverage-html`` option.
-
-.. index::
- single: Tests; Unit tests
-
-Unit Tests
-----------
-
-A unit test is usually a test against a specific PHP class. If you want to
-test the overall behavior of your application, see the section about `Functional Tests`_.
-
-Writing Symfony2 unit tests is no different than writing standard PHPUnit
-unit tests. Suppose, for example, that you have an *incredibly* simple class
-called ``Calculator`` in the ``Utility/`` directory of your bundle::
-
- // src/Acme/DemoBundle/Utility/Calculator.php
- namespace Acme\DemoBundle\Utility;
-
- class Calculator
- {
- public function add($a, $b)
- {
- return $a + $b;
- }
- }
-
-To test this, create a ``CalculatorTest`` file in the ``Tests/Utility`` directory
-of your bundle::
-
- // src/Acme/DemoBundle/Tests/Utility/CalculatorTest.php
- namespace Acme\DemoBundle\Tests\Utility;
-
- use Acme\DemoBundle\Utility\Calculator;
-
- class CalculatorTest extends \PHPUnit_Framework_TestCase
- {
- public function testAdd()
- {
- $calc = new Calculator();
- $result = $calc->add(30, 12);
-
- // assert that your calculator added the numbers correctly!
- $this->assertEquals(42, $result);
- }
- }
-
-.. note::
-
- By convention, the ``Tests/`` sub-directory should replicate the directory
- of your bundle. So, if you're testing a class in your bundle's ``Utility/``
- directory, put the test in the ``Tests/Utility/`` directory.
-
-Just like in your real application - autoloading is automatically enabled
-via the ``bootstrap.php.cache`` file (as configured by default in the ``phpunit.xml.dist``
-file).
-
-Running tests for a given file or directory is also very easy:
-
-.. code-block:: bash
-
- # run all tests in the Utility directory
- $ phpunit -c app src/Acme/DemoBundle/Tests/Utility/
-
- # run tests for the Calculator class
- $ phpunit -c app src/Acme/DemoBundle/Tests/Utility/CalculatorTest.php
-
- # run all tests for the entire Bundle
- $ phpunit -c app src/Acme/DemoBundle/
-
-.. index::
- single: Tests; Functional tests
-
-Functional Tests
-----------------
-
-Functional tests check the integration of the different layers of an
-application (from the routing to the views). They are no different from unit
-tests as far as PHPUnit is concerned, but they have a very specific workflow:
-
-* Make a request;
-* Test the response;
-* Click on a link or submit a form;
-* Test the response;
-* Rinse and repeat.
-
-Your First Functional Test
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Functional tests are simple PHP files that typically live in the ``Tests/Controller``
-directory of your bundle. If you want to test the pages handled by your
-``DemoController`` class, start by creating a new ``DemoControllerTest.php``
-file that extends a special ``WebTestCase`` class.
-
-For example, the Symfony2 Standard Edition provides a simple functional test
-for its ``DemoController`` (`DemoControllerTest`_) that reads as follows::
-
- // src/Acme/DemoBundle/Tests/Controller/DemoControllerTest.php
- namespace Acme\DemoBundle\Tests\Controller;
-
- use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
-
- class DemoControllerTest extends WebTestCase
- {
- public function testIndex()
- {
- $client = static::createClient();
-
- $crawler = $client->request('GET', '/demo/hello/Fabien');
-
- $this->assertGreaterThan(
- 0,
- $crawler->filter('html:contains("Hello Fabien")')->count()
- );
- }
- }
-
-.. tip::
-
- To run your functional tests, the ``WebTestCase`` class bootstraps the
- kernel of your application. In most cases, this happens automatically.
- However, if your kernel is in a non-standard directory, you'll need
- to modify your ``phpunit.xml.dist`` file to set the ``KERNEL_DIR`` environment
- variable to the directory of your kernel:
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-The ``createClient()`` method returns a client, which is like a browser that
-you'll use to crawl your site::
-
- $crawler = $client->request('GET', '/demo/hello/Fabien');
-
-The ``request()`` method (see :ref:`more about the request method`)
-returns a :class:`Symfony\\Component\\DomCrawler\\Crawler` object which can
-be used to select elements in the Response, click on links, and submit forms.
-
-.. tip::
-
- The Crawler only works when the response is an XML or an HTML document.
- To get the raw content response, call ``$client->getResponse()->getContent()``.
-
-Click on a link by first selecting it with the Crawler using either an XPath
-expression or a CSS selector, then use the Client to click on it. For example,
-the following code finds all links with the text ``Greet``, then selects
-the second one, and ultimately clicks on it::
-
- $link = $crawler->filter('a:contains("Greet")')->eq(1)->link();
-
- $crawler = $client->click($link);
-
-Submitting a form is very similar; select a form button, optionally override
-some form values, and submit the corresponding form::
-
- $form = $crawler->selectButton('submit')->form();
-
- // set some values
- $form['name'] = 'Lucas';
- $form['form_name[subject]'] = 'Hey there!';
-
- // submit the form
- $crawler = $client->submit($form);
-
-.. tip::
-
- The form can also handle uploads and contains methods to fill in different types
- of form fields (e.g. ``select()`` and ``tick()``). For details, see the
- `Forms`_ section below.
-
-Now that you can easily navigate through an application, use assertions to test
-that it actually does what you expect it to. Use the Crawler to make assertions
-on the DOM::
-
- // Assert that the response matches a given CSS selector.
- $this->assertGreaterThan(0, $crawler->filter('h1')->count());
-
-Or, test against the Response content directly if you just want to assert that
-the content contains some text, or if the Response is not an XML/HTML
-document::
-
- $this->assertRegExp(
- '/Hello Fabien/',
- $client->getResponse()->getContent()
- );
-
-.. _book-testing-request-method-sidebar:
-
-.. sidebar:: More about the ``request()`` method:
-
- The full signature of the ``request()`` method is::
-
- request(
- $method,
- $uri,
- array $parameters = array(),
- array $files = array(),
- array $server = array(),
- $content = null,
- $changeHistory = true
- )
-
- The ``server`` array is the raw values that you'd expect to normally
- find in the PHP `$_SERVER`_ superglobal. For example, to set the `Content-Type`,
- `Referer` and `X-Requested-With' HTTP headers, you'd pass the following (mind
- the `HTTP_` prefix for non standard headers)::
-
- $client->request(
- 'GET',
- '/demo/hello/Fabien',
- array(),
- array(),
- array(
- 'CONTENT_TYPE' => 'application/json',
- 'HTTP_REFERER' => '/foo/bar',
- 'HTTP_X-Requested-With' => 'XMLHttpRequest',
- )
- );
-
-.. index::
- single: Tests; Assertions
-
-.. sidebar:: Useful Assertions
-
- To get you started faster, here is a list of the most common and
- useful test assertions::
-
- // Assert that there is at least one h2 tag
- // with the class "subtitle"
- $this->assertGreaterThan(
- 0,
- $crawler->filter('h2.subtitle')->count()
- );
-
- // Assert that there are exactly 4 h2 tags on the page
- $this->assertCount(4, $crawler->filter('h2'));
-
- // Assert that the "Content-Type" header is "application/json"
- $this->assertTrue(
- $client->getResponse()->headers->contains(
- 'Content-Type',
- 'application/json'
- )
- );
-
- // Assert that the response content matches a regexp.
- $this->assertRegExp('/foo/', $client->getResponse()->getContent());
-
- // Assert that the response status code is 2xx
- $this->assertTrue($client->getResponse()->isSuccessful());
- // Assert that the response status code is 404
- $this->assertTrue($client->getResponse()->isNotFound());
- // Assert a specific 200 status code
- $this->assertEquals(
- 200,
- $client->getResponse()->getStatusCode()
- );
-
- // Assert that the response is a redirect to /demo/contact
- $this->assertTrue(
- $client->getResponse()->isRedirect('/demo/contact')
- );
- // or simply check that the response is a redirect to any URL
- $this->assertTrue($client->getResponse()->isRedirect());
-
-.. index::
- single: Tests; Client
-
-Working with the Test Client
------------------------------
-
-The Test Client simulates an HTTP client like a browser and makes requests
-into your Symfony2 application::
-
- $crawler = $client->request('GET', '/hello/Fabien');
-
-The ``request()`` method takes the HTTP method and a URL as arguments and
-returns a ``Crawler`` instance.
-
-Use the Crawler to find DOM elements in the Response. These elements can then
-be used to click on links and submit forms::
-
- $link = $crawler->selectLink('Go elsewhere...')->link();
- $crawler = $client->click($link);
-
- $form = $crawler->selectButton('validate')->form();
- $crawler = $client->submit($form, array('name' => 'Fabien'));
-
-The ``click()`` and ``submit()`` methods both return a ``Crawler`` object.
-These methods are the best way to browse your application as it takes care
-of a lot of things for you, like detecting the HTTP method from a form and
-giving you a nice API for uploading files.
-
-.. tip::
-
- You will learn more about the ``Link`` and ``Form`` objects in the
- :ref:`Crawler` section below.
-
-The ``request`` method can also be used to simulate form submissions directly
-or perform more complex requests::
-
- // Directly submit a form (but using the Crawler is easier!)
- $client->request('POST', '/submit', array('name' => 'Fabien'));
-
- // Submit a raw JSON string in the request body
- $client->request(
- 'POST',
- '/submit',
- array(),
- array(),
- array('CONTENT_TYPE' => 'application/json'),
- '{"name":"Fabien"}'
- );
-
- // Form submission with a file upload
- use Symfony\Component\HttpFoundation\File\UploadedFile;
-
- $photo = new UploadedFile(
- '/path/to/photo.jpg',
- 'photo.jpg',
- 'image/jpeg',
- 123
- );
- $client->request(
- 'POST',
- '/submit',
- array('name' => 'Fabien'),
- array('photo' => $photo)
- );
-
- // Perform a DELETE requests, and pass HTTP headers
- $client->request(
- 'DELETE',
- '/post/12',
- array(),
- array(),
- array('PHP_AUTH_USER' => 'username', 'PHP_AUTH_PW' => 'pa$$word')
- );
-
-Last but not least, you can force each request to be executed in its own PHP
-process to avoid any side-effects when working with several clients in the same
-script::
-
- $client->insulate();
-
-Browsing
-~~~~~~~~
-
-The Client supports many operations that can be done in a real browser::
-
- $client->back();
- $client->forward();
- $client->reload();
-
- // Clears all cookies and the history
- $client->restart();
-
-Accessing Internal Objects
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. versionadded:: 2.3
- The ``getInternalRequest()`` and ``getInternalResponse()`` method were
- added in Symfony 2.3.
-
-If you use the client to test your application, you might want to access the
-client's internal objects::
-
- $history = $client->getHistory();
- $cookieJar = $client->getCookieJar();
-
-You can also get the objects related to the latest request::
-
- // the HttpKernel request instance
- $request = $client->getRequest();
-
- // the BrowserKit request instance
- $request = $client->getInternalRequest();
-
- // the HttpKernel response instance
- $response = $client->getResponse();
-
- // the BrowserKit response instance
- $response = $client->getInternalResponse();
-
- $crawler = $client->getCrawler();
-
-If your requests are not insulated, you can also access the ``Container`` and
-the ``Kernel``::
-
- $container = $client->getContainer();
- $kernel = $client->getKernel();
-
-Accessing the Container
-~~~~~~~~~~~~~~~~~~~~~~~
-
-It's highly recommended that a functional test only tests the Response. But
-under certain very rare circumstances, you might want to access some internal
-objects to write assertions. In such cases, you can access the dependency
-injection container::
-
- $container = $client->getContainer();
-
-Be warned that this does not work if you insulate the client or if you use an
-HTTP layer. For a list of services available in your application, use the
-``container:debug`` console task.
-
-.. tip::
-
- If the information you need to check is available from the profiler, use
- it instead.
-
-Accessing the Profiler Data
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-On each request, you can enable the Symfony profiler to collect data about the
-internal handling of that request. For example, the profiler could be used to
-verify that a given page executes less than a certain number of database
-queries when loading.
-
-To get the Profiler for the last request, do the following::
-
- // enable the profiler for the very next request
- $client->enableProfiler();
-
- $crawler = $client->request('GET', '/profiler');
-
- // get the profile
- $profile = $client->getProfile();
-
-For specific details on using the profiler inside a test, see the
-:doc:`/cookbook/testing/profiling` cookbook entry.
-
-Redirecting
-~~~~~~~~~~~
-
-When a request returns a redirect response, the client does not follow
-it automatically. You can examine the response and force a redirection
-afterwards with the ``followRedirect()`` method::
-
- $crawler = $client->followRedirect();
-
-If you want the client to automatically follow all redirects, you can
-force him with the ``followRedirects()`` method::
-
- $client->followRedirects();
-
-.. index::
- single: Tests; Crawler
-
-.. _book-testing-crawler:
-
-The Crawler
------------
-
-A Crawler instance is returned each time you make a request with the Client.
-It allows you to traverse HTML documents, select nodes, find links and forms.
-
-Traversing
-~~~~~~~~~~
-
-Like jQuery, the Crawler has methods to traverse the DOM of an HTML/XML
-document. For example, the following finds all ``input[type=submit]`` elements,
-selects the last one on the page, and then selects its immediate parent element::
-
- $newCrawler = $crawler->filter('input[type=submit]')
- ->last()
- ->parents()
- ->first()
- ;
-
-Many other methods are also available:
-
-+------------------------+----------------------------------------------------+
-| Method | Description |
-+========================+====================================================+
-| ``filter('h1.title')`` | Nodes that match the CSS selector |
-+------------------------+----------------------------------------------------+
-| ``filterXpath('h1')`` | Nodes that match the XPath expression |
-+------------------------+----------------------------------------------------+
-| ``eq(1)`` | Node for the specified index |
-+------------------------+----------------------------------------------------+
-| ``first()`` | First node |
-+------------------------+----------------------------------------------------+
-| ``last()`` | Last node |
-+------------------------+----------------------------------------------------+
-| ``siblings()`` | Siblings |
-+------------------------+----------------------------------------------------+
-| ``nextAll()`` | All following siblings |
-+------------------------+----------------------------------------------------+
-| ``previousAll()`` | All preceding siblings |
-+------------------------+----------------------------------------------------+
-| ``parents()`` | Returns the parent nodes |
-+------------------------+----------------------------------------------------+
-| ``children()`` | Returns children nodes |
-+------------------------+----------------------------------------------------+
-| ``reduce($lambda)`` | Nodes for which the callable does not return false |
-+------------------------+----------------------------------------------------+
-
-Since each of these methods returns a new ``Crawler`` instance, you can
-narrow down your node selection by chaining the method calls::
-
- $crawler
- ->filter('h1')
- ->reduce(function ($node, $i)
- {
- if (!$node->getAttribute('class')) {
- return false;
- }
- })
- ->first();
-
-.. tip::
-
- Use the ``count()`` function to get the number of nodes stored in a Crawler:
- ``count($crawler)``
-
-Extracting Information
-~~~~~~~~~~~~~~~~~~~~~~
-
-The Crawler can extract information from the nodes::
-
- // Returns the attribute value for the first node
- $crawler->attr('class');
-
- // Returns the node value for the first node
- $crawler->text();
-
- // Extracts an array of attributes for all nodes
- // (_text returns the node value)
- // returns an array for each element in crawler,
- // each with the value and href
- $info = $crawler->extract(array('_text', 'href'));
-
- // Executes a lambda for each node and return an array of results
- $data = $crawler->each(function ($node, $i)
- {
- return $node->attr('href');
- });
-
-Links
-~~~~~
-
-To select links, you can use the traversing methods above or the convenient
-``selectLink()`` shortcut::
-
- $crawler->selectLink('Click here');
-
-This selects all links that contain the given text, or clickable images for
-which the ``alt`` attribute contains the given text. Like the other filtering
-methods, this returns another ``Crawler`` object.
-
-Once you've selected a link, you have access to a special ``Link`` object,
-which has helpful methods specific to links (such as ``getMethod()`` and
-``getUri()``). To click on the link, use the Client's ``click()`` method
-and pass it a ``Link`` object::
-
- $link = $crawler->selectLink('Click here')->link();
-
- $client->click($link);
-
-Forms
-~~~~~
-
-Just like links, you select forms with the ``selectButton()`` method::
-
- $buttonCrawlerNode = $crawler->selectButton('submit');
-
-.. note::
-
- Notice that you select form buttons and not forms as a form can have several
- buttons; if you use the traversing API, keep in mind that you must look for a
- button.
-
-The ``selectButton()`` method can select ``button`` tags and submit ``input``
-tags. It uses several different parts of the buttons to find them:
-
-* The ``value`` attribute value;
-
-* The ``id`` or ``alt`` attribute value for images;
-
-* The ``id`` or ``name`` attribute value for ``button`` tags.
-
-Once you have a Crawler representing a button, call the ``form()`` method
-to get a ``Form`` instance for the form wrapping the button node::
-
- $form = $buttonCrawlerNode->form();
-
-When calling the ``form()`` method, you can also pass an array of field values
-that overrides the default ones::
-
- $form = $buttonCrawlerNode->form(array(
- 'name' => 'Fabien',
- 'my_form[subject]' => 'Symfony rocks!',
- ));
-
-And if you want to simulate a specific HTTP method for the form, pass it as a
-second argument::
-
- $form = $buttonCrawlerNode->form(array(), 'DELETE');
-
-The Client can submit ``Form`` instances::
-
- $client->submit($form);
-
-The field values can also be passed as a second argument of the ``submit()``
-method::
-
- $client->submit($form, array(
- 'name' => 'Fabien',
- 'my_form[subject]' => 'Symfony rocks!',
- ));
-
-For more complex situations, use the ``Form`` instance as an array to set the
-value of each field individually::
-
- // Change the value of a field
- $form['name'] = 'Fabien';
- $form['my_form[subject]'] = 'Symfony rocks!';
-
-There is also a nice API to manipulate the values of the fields according to
-their type::
-
- // Select an option or a radio
- $form['country']->select('France');
-
- // Tick a checkbox
- $form['like_symfony']->tick();
-
- // Upload a file
- $form['photo']->upload('/path/to/lucas.jpg');
-
-.. tip::
-
- You can get the values that will be submitted by calling the ``getValues()``
- method on the ``Form`` object. The uploaded files are available in a
- separate array returned by ``getFiles()``. The ``getPhpValues()`` and
- ``getPhpFiles()`` methods also return the submitted values, but in the
- PHP format (it converts the keys with square brackets notation - e.g.
- ``my_form[subject]`` - to PHP arrays).
-
-.. index::
- pair: Tests; Configuration
-
-Testing Configuration
----------------------
-
-The Client used by functional tests creates a Kernel that runs in a special
-``test`` environment. Since Symfony loads the ``app/config/config_test.yml``
-in the ``test`` environment, you can tweak any of your application's settings
-specifically for testing.
-
-For example, by default, the swiftmailer is configured to *not* actually
-deliver emails in the ``test`` environment. You can see this under the ``swiftmailer``
-configuration option:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config_test.yml
-
- # ...
- swiftmailer:
- disable_delivery: true
-
- .. code-block:: xml
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config_test.php
-
- // ...
- $container->loadFromExtension('swiftmailer', array(
- 'disable_delivery' => true,
- ));
-
-You can also use a different environment entirely, or override the default
-debug mode (``true``) by passing each as options to the ``createClient()``
-method::
-
- $client = static::createClient(array(
- 'environment' => 'my_test_env',
- 'debug' => false,
- ));
-
-If your application behaves according to some HTTP headers, pass them as the
-second argument of ``createClient()``::
-
- $client = static::createClient(array(), array(
- 'HTTP_HOST' => 'en.example.com',
- 'HTTP_USER_AGENT' => 'MySuperBrowser/1.0',
- ));
-
-You can also override HTTP headers on a per request basis::
-
- $client->request('GET', '/', array(), array(), array(
- 'HTTP_HOST' => 'en.example.com',
- 'HTTP_USER_AGENT' => 'MySuperBrowser/1.0',
- ));
-
-.. tip::
-
- The test client is available as a service in the container in the ``test``
- environment (or wherever the :ref:`framework.test`
- option is enabled). This means you can override the service entirely
- if you need to.
-
-.. index::
- pair: PHPUnit; Configuration
-
-PHPUnit Configuration
-~~~~~~~~~~~~~~~~~~~~~
-
-Each application has its own PHPUnit configuration, stored in the
-``phpunit.xml.dist`` file. You can edit this file to change the defaults or
-create a ``phpunit.xml`` file to tweak the configuration for your local machine.
-
-.. tip::
-
- Store the ``phpunit.xml.dist`` file in your code repository, and ignore the
- ``phpunit.xml`` file.
-
-By default, only the tests stored in "standard" bundles are run by the
-``phpunit`` command (standard being tests in the ``src/*/Bundle/Tests`` or
-``src/*/Bundle/*Bundle/Tests`` directories) But you can easily add more
-directories. For instance, the following configuration adds the tests from
-the installed third-party bundles:
-
-.. code-block:: xml
-
-
-
-
- ../src/*/*Bundle/Tests
- ../src/Acme/Bundle/*Bundle/Tests
-
-
-
-To include other directories in the code coverage, also edit the ````
-section:
-
-.. code-block:: xml
-
-
-
-
- ../src
-
- ../src/*/*Bundle/Resources
- ../src/*/*Bundle/Tests
- ../src/Acme/Bundle/*Bundle/Resources
- ../src/Acme/Bundle/*Bundle/Tests
-
-
-
-
-Learn more
-----------
-
-* :doc:`/components/dom_crawler`
-* :doc:`/components/css_selector`
-* :doc:`/cookbook/testing/http_authentication`
-* :doc:`/cookbook/testing/insulating_clients`
-* :doc:`/cookbook/testing/profiling`
-* :doc:`/cookbook/testing/bootstrap`
-
-
-.. _`DemoControllerTest`: https://github.com/symfony/symfony-standard/blob/master/src/Acme/DemoBundle/Tests/Controller/DemoControllerTest.php
-.. _`$_SERVER`: http://php.net/manual/en/reserved.variables.server.php
-.. _`documentation`: http://www.phpunit.de/manual/3.5/en/
diff --git a/book/translation.rst b/book/translation.rst
deleted file mode 100644
index a44347d826c..00000000000
--- a/book/translation.rst
+++ /dev/null
@@ -1,1012 +0,0 @@
-.. index::
- single: Translations
-
-Translations
-============
-
-The term "internationalization" (often abbreviated `i18n`_) refers to the process
-of abstracting strings and other locale-specific pieces out of your application
-and into a layer where they can be translated and converted based on the user's
-locale (i.e. language and country). For text, this means wrapping each with a
-function capable of translating the text (or "message") into the language of
-the user::
-
- // text will *always* print out in English
- echo 'Hello World';
-
- // text can be translated into the end-user's language or
- // default to English
- echo $translator->trans('Hello World');
-
-.. note::
-
- The term *locale* refers roughly to the user's language and country. It
- can be any string that your application uses to manage translations
- and other format differences (e.g. currency format). The
- `ISO639-1`_ *language* code, an underscore (``_``), then the `ISO3166 Alpha-2`_ *country*
- code (e.g. ``fr_FR`` for French/France) is recommended.
-
-In this chapter, you'll learn how to prepare an application to support multiple
-locales and then how to create translations for multiple locales. Overall,
-the process has several common steps:
-
-#. Enable and configure Symfony's ``Translation`` component;
-
-#. Abstract strings (i.e. "messages") by wrapping them in calls to the ``Translator``;
-
-#. Create translation resources for each supported locale that translate
- each message in the application;
-
-#. Determine, set and manage the user's locale for the request and optionally
- on the user's entire session.
-
-.. index::
- single: Translations; Configuration
-
-Configuration
--------------
-
-Translations are handled by a ``Translator`` :term:`service` that uses the
-user's locale to lookup and return translated messages. Before using it,
-enable the ``Translator`` in your configuration:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- framework:
- translator: { fallback: en }
-
- .. code-block:: xml
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- $container->loadFromExtension('framework', array(
- 'translator' => array('fallback' => 'en'),
- ));
-
-The ``fallback`` option defines the fallback locale when a translation does
-not exist in the user's locale.
-
-.. tip::
-
- When a translation does not exist for a locale, the translator first tries
- to find the translation for the language (``fr`` if the locale is
- ``fr_FR`` for instance). If this also fails, it looks for a translation
- using the fallback locale.
-
-The locale used in translations is the one stored on the request. This is
-typically set via a ``_locale`` attribute on your routes (see :ref:`book-translation-locale-url`).
-
-.. index::
- single: Translations; Basic translation
-
-Basic Translation
------------------
-
-Translation of text is done through the ``translator`` service
-(:class:`Symfony\\Component\\Translation\\Translator`). To translate a block
-of text (called a *message*), use the
-:method:`Symfony\\Component\\Translation\\Translator::trans` method. Suppose,
-for example, that you're translating a simple message from inside a controller::
-
- // ...
- use Symfony\Component\HttpFoundation\Response;
-
- public function indexAction()
- {
- $t = $this->get('translator')->trans('Symfony2 is great');
-
- return new Response($t);
- }
-
-When this code is executed, Symfony2 will attempt to translate the message
-"Symfony2 is great" based on the ``locale`` of the user. For this to work,
-you need to tell Symfony2 how to translate the message via a "translation
-resource", which is a collection of message translations for a given locale.
-This "dictionary" of translations can be created in several different formats,
-XLIFF being the recommended format:
-
-.. configuration-block::
-
- .. code-block:: xml
-
-
-
-
-
-
-
- Symfony2 is great
- J'aime Symfony2
-
-
-
-
-
- .. code-block:: php
-
- // messages.fr.php
- return array(
- 'Symfony2 is great' => 'J\'aime Symfony2',
- );
-
- .. code-block:: yaml
-
- # messages.fr.yml
- Symfony2 is great: J'aime Symfony2
-
-Now, if the language of the user's locale is French (e.g. ``fr_FR`` or ``fr_BE``),
-the message will be translated into ``J'aime Symfony2``.
-
-The Translation Process
-~~~~~~~~~~~~~~~~~~~~~~~
-
-To actually translate the message, Symfony2 uses a simple process:
-
-* The ``locale`` of the current user, which is stored on the request (or
- stored as ``_locale`` on the session), is determined;
-
-* A catalog of translated messages is loaded from translation resources defined
- for the ``locale`` (e.g. ``fr_FR``). Messages from the fallback locale are
- also loaded and added to the catalog if they don't already exist. The end
- result is a large "dictionary" of translations. See `Message Catalogues`_
- for more details;
-
-* If the message is located in the catalog, the translation is returned. If
- not, the translator returns the original message.
-
-When using the ``trans()`` method, Symfony2 looks for the exact string inside
-the appropriate message catalog and returns it (if it exists).
-
-.. index::
- single: Translations; Message placeholders
-
-Message Placeholders
-~~~~~~~~~~~~~~~~~~~~
-
-Sometimes, a message containing a variable needs to be translated::
-
- // ...
- use Symfony\Component\HttpFoundation\Response;
-
- public function indexAction($name)
- {
- $t = $this->get('translator')->trans('Hello '.$name);
-
- return new Response($t);
- }
-
-However, creating a translation for this string is impossible since the translator
-will try to look up the exact message, including the variable portions
-(e.g. "Hello Ryan" or "Hello Fabien"). Instead of writing a translation
-for every possible iteration of the ``$name`` variable, you can replace the
-variable with a "placeholder"::
-
- // ...
- use Symfony\Component\HttpFoundation\Response;
-
- public function indexAction($name)
- {
- $t = $this->get('translator')->trans(
- 'Hello %name%',
- array('%name%' => $name)
- );
-
- return new Response($t);
- }
-
-Symfony2 will now look for a translation of the raw message (``Hello %name%``)
-and *then* replace the placeholders with their values. Creating a translation
-is done just as before:
-
-.. configuration-block::
-
- .. code-block:: xml
-
-
-
-
-
-
-
- Hello %name%
- Bonjour %name%
-
-
-
-
-
- .. code-block:: php
-
- // messages.fr.php
- return array(
- 'Hello %name%' => 'Bonjour %name%',
- );
-
- .. code-block:: yaml
-
- # messages.fr.yml
- 'Hello %name%': Bonjour %name%
-
-.. note::
-
- The placeholders can take on any form as the full message is reconstructed
- using the PHP `strtr function`_. However, the ``%var%`` notation is
- required when translating in Twig templates, and is overall a sensible
- convention to follow.
-
-As you've seen, creating a translation is a two-step process:
-
-#. Abstract the message that needs to be translated by processing it through
- the ``Translator``.
-
-#. Create a translation for the message in each locale that you choose to
- support.
-
-The second step is done by creating message catalogues that define the translations
-for any number of different locales.
-
-.. index::
- single: Translations; Message catalogues
-
-Message Catalogues
-------------------
-
-When a message is translated, Symfony2 compiles a message catalogue for the
-user's locale and looks in it for a translation of the message. A message
-catalogue is like a dictionary of translations for a specific locale. For
-example, the catalogue for the ``fr_FR`` locale might contain the following
-translation:
-
-.. code-block:: text
-
- Symfony2 is Great => J'aime Symfony2
-
-It's the responsibility of the developer (or translator) of an internationalized
-application to create these translations. Translations are stored on the
-filesystem and discovered by Symfony, thanks to some conventions.
-
-.. tip::
-
- Each time you create a *new* translation resource (or install a bundle
- that includes a translation resource), be sure to clear your cache so
- that Symfony can discover the new translation resource:
-
- .. code-block:: bash
-
- $ php app/console cache:clear
-
-.. index::
- single: Translations; Translation resource locations
-
-Translation Locations and Naming Conventions
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Symfony2 looks for message files (i.e. translations) in the following locations:
-
-* the ``/Resources/translations`` directory;
-
-* the ``/Resources//translations`` directory;
-
-* the ``Resources/translations/`` directory of the bundle.
-
-The locations are listed with the highest priority first. That is you can
-override the translation messages of a bundle in any of the top 2 directories.
-
-The override mechanism works at a key level: only the overridden keys need
-to be listed in a higher priority message file. When a key is not found
-in a message file, the translator will automatically fall back to the lower
-priority message files.
-
-The filename of the translations is also important as Symfony2 uses a convention
-to determine details about the translations. Each message file must be named
-according to the following path: ``domain.locale.loader``:
-
-* **domain**: An optional way to organize messages into groups (e.g. ``admin``,
- ``navigation`` or the default ``messages``) - see `Using Message Domains`_;
-
-* **locale**: The locale that the translations are for (e.g. ``en_GB``, ``en``, etc);
-
-* **loader**: How Symfony2 should load and parse the file (e.g. ``xliff``,
- ``php`` or ``yml``).
-
-The loader can be the name of any registered loader. By default, Symfony
-provides the following loaders:
-
-* ``xliff``: XLIFF file;
-* ``php``: PHP file;
-* ``yml``: YAML file.
-
-The choice of which loader to use is entirely up to you and is a matter of
-taste.
-
-.. note::
-
- You can also store translations in a database, or any other storage by
- providing a custom class implementing the
- :class:`Symfony\\Component\\Translation\\Loader\\LoaderInterface` interface.
-
-.. index::
- single: Translations; Creating translation resources
-
-Creating Translations
-~~~~~~~~~~~~~~~~~~~~~
-
-The act of creating translation files is an important part of "localization"
-(often abbreviated `L10n`_). Translation files consist of a series of
-id-translation pairs for the given domain and locale. The source is the identifier
-for the individual translation, and can be the message in the main locale (e.g.
-"Symfony is great") of your application or a unique identifier (e.g.
-"symfony2.great" - see the sidebar below):
-
-.. configuration-block::
-
- .. code-block:: xml
-
-
-
-
-
-
-
- Symfony2 is great
- J'aime Symfony2
-
-
- symfony2.great
- J'aime Symfony2
-
-
-
-
-
- .. code-block:: php
-
- // src/Acme/DemoBundle/Resources/translations/messages.fr.php
- return array(
- 'Symfony2 is great' => 'J\'aime Symfony2',
- 'symfony2.great' => 'J\'aime Symfony2',
- );
-
- .. code-block:: yaml
-
- # src/Acme/DemoBundle/Resources/translations/messages.fr.yml
- Symfony2 is great: J'aime Symfony2
- symfony2.great: J'aime Symfony2
-
-Symfony2 will discover these files and use them when translating either
-"Symfony2 is great" or "symfony2.great" into a French language locale (e.g.
-``fr_FR`` or ``fr_BE``).
-
-.. sidebar:: Using Real or Keyword Messages
-
- This example illustrates the two different philosophies when creating
- messages to be translated::
-
- $t = $translator->trans('Symfony2 is great');
-
- $t = $translator->trans('symfony2.great');
-
- In the first method, messages are written in the language of the default
- locale (English in this case). That message is then used as the "id"
- when creating translations.
-
- In the second method, messages are actually "keywords" that convey the
- idea of the message. The keyword message is then used as the "id" for
- any translations. In this case, translations must be made for the default
- locale (i.e. to translate ``symfony2.great`` to ``Symfony2 is great``).
-
- The second method is handy because the message key won't need to be changed
- in every translation file if you decide that the message should actually
- read "Symfony2 is really great" in the default locale.
-
- The choice of which method to use is entirely up to you, but the "keyword"
- format is often recommended.
-
- Additionally, the ``php`` and ``yaml`` file formats support nested ids to
- avoid repeating yourself if you use keywords instead of real text for your
- ids:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- symfony2:
- is:
- great: Symfony2 is great
- amazing: Symfony2 is amazing
- has:
- bundles: Symfony2 has bundles
- user:
- login: Login
-
- .. code-block:: php
-
- return array(
- 'symfony2' => array(
- 'is' => array(
- 'great' => 'Symfony2 is great',
- 'amazing' => 'Symfony2 is amazing',
- ),
- 'has' => array(
- 'bundles' => 'Symfony2 has bundles',
- ),
- ),
- 'user' => array(
- 'login' => 'Login',
- ),
- );
-
- The multiple levels are flattened into single id/translation pairs by
- adding a dot (.) between every level, therefore the above examples are
- equivalent to the following:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- symfony2.is.great: Symfony2 is great
- symfony2.is.amazing: Symfony2 is amazing
- symfony2.has.bundles: Symfony2 has bundles
- user.login: Login
-
- .. code-block:: php
-
- return array(
- 'symfony2.is.great' => 'Symfony2 is great',
- 'symfony2.is.amazing' => 'Symfony2 is amazing',
- 'symfony2.has.bundles' => 'Symfony2 has bundles',
- 'user.login' => 'Login',
- );
-
-.. index::
- single: Translations; Message domains
-
-Using Message Domains
----------------------
-
-As you've seen, message files are organized into the different locales that
-they translate. The message files can also be organized further into "domains".
-When creating message files, the domain is the first portion of the filename.
-The default domain is ``messages``. For example, suppose that, for organization,
-translations were split into three different domains: ``messages``, ``admin``
-and ``navigation``. The French translation would have the following message
-files:
-
-* ``messages.fr.xliff``
-* ``admin.fr.xliff``
-* ``navigation.fr.xliff``
-
-When translating strings that are not in the default domain (``messages``),
-you must specify the domain as the third argument of ``trans()``::
-
- $this->get('translator')->trans('Symfony2 is great', array(), 'admin');
-
-Symfony2 will now look for the message in the ``admin`` domain of the user's
-locale.
-
-.. index::
- single: Translations; User's locale
-
-Handling the User's Locale
---------------------------
-
-The locale of the current user is stored in the request and is accessible
-via the ``request`` object::
-
- // access the request object in a standard controller
- $request = $this->getRequest();
-
- $locale = $request->getLocale();
-
- $request->setLocale('en_US');
-
-.. index::
- single: Translations; Fallback and default locale
-
-It is also possible to store the locale in the session instead of on a per
-request basis. If you do this, each subsequent request will have this locale.
-
-.. code-block:: php
-
- $this->get('session')->set('_locale', 'en_US');
-
-See the :ref:`book-translation-locale-url` section below about setting the
-locale via routing.
-
-Fallback and Default Locale
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-If the locale hasn't been set explicitly in the session, the ``fallback_locale``
-configuration parameter will be used by the ``Translator``. The parameter
-defaults to ``en`` (see `Configuration`_).
-
-Alternatively, you can guarantee that a locale is set on each user's request
-by defining a ``default_locale`` for the framework:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- framework:
- default_locale: en
-
- .. code-block:: xml
-
-
-
- en
-
-
- .. code-block:: php
-
- // app/config/config.php
- $container->loadFromExtension('framework', array(
- 'default_locale' => 'en',
- ));
-
-.. versionadded:: 2.1
- The ``default_locale`` parameter was defined under the session key
- originally, however, as of 2.1 this has been moved. This is because the
- locale is now set on the request instead of the session.
-
-.. _book-translation-locale-url:
-
-The Locale and the URL
-~~~~~~~~~~~~~~~~~~~~~~
-
-Since you can store the locale of the user in the session, it may be tempting
-to use the same URL to display a resource in many different languages based
-on the user's locale. For example, ``http://www.example.com/contact`` could
-show content in English for one user and French for another user. Unfortunately,
-this violates a fundamental rule of the Web: that a particular URL returns
-the same resource regardless of the user. To further muddy the problem, which
-version of the content would be indexed by search engines?
-
-A better policy is to include the locale in the URL. This is fully-supported
-by the routing system using the special ``_locale`` parameter:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- contact:
- path: /{_locale}/contact
- defaults: { _controller: AcmeDemoBundle:Contact:index, _locale: en }
- requirements:
- _locale: en|fr|de
-
- .. code-block:: xml
-
-
- AcmeDemoBundle:Contact:index
- en
- en|fr|de
-
-
- .. code-block:: php
-
- use Symfony\Component\Routing\RouteCollection;
- use Symfony\Component\Routing\Route;
-
- $collection = new RouteCollection();
- $collection->add('contact', new Route('/{_locale}/contact', array(
- '_controller' => 'AcmeDemoBundle:Contact:index',
- '_locale' => 'en',
- ), array(
- '_locale' => 'en|fr|de',
- )));
-
- return $collection;
-
-When using the special `_locale` parameter in a route, the matched locale
-will *automatically be set on the user's session*. In other words, if a user
-visits the URI ``/fr/contact``, the locale ``fr`` will automatically be set
-as the locale for the user's session.
-
-You can now use the user's locale to create routes to other translated pages
-in your application.
-
-.. index::
- single: Translations; Pluralization
-
-Pluralization
--------------
-
-Message pluralization is a tough topic as the rules can be quite complex. For
-instance, here is the mathematic representation of the Russian pluralization
-rules::
-
- (($number % 10 == 1) && ($number % 100 != 11))
- ? 0
- : ((($number % 10 >= 2)
- && ($number % 10 <= 4)
- && (($number % 100 < 10)
- || ($number % 100 >= 20)))
- ? 1
- : 2
- );
-
-As you can see, in Russian, you can have three different plural forms, each
-given an index of 0, 1 or 2. For each form, the plural is different, and
-so the translation is also different.
-
-When a translation has different forms due to pluralization, you can provide
-all the forms as a string separated by a pipe (``|``)::
-
- 'There is one apple|There are %count% apples'
-
-To translate pluralized messages, use the
-:method:`Symfony\\Component\\Translation\\Translator::transChoice` method::
-
- $t = $this->get('translator')->transChoice(
- 'There is one apple|There are %count% apples',
- 10,
- array('%count%' => 10)
- );
-
-The second argument (``10`` in this example), is the *number* of objects being
-described and is used to determine which translation to use and also to populate
-the ``%count%`` placeholder.
-
-Based on the given number, the translator chooses the right plural form.
-In English, most words have a singular form when there is exactly one object
-and a plural form for all other numbers (0, 2, 3...). So, if ``count`` is
-``1``, the translator will use the first string (``There is one apple``)
-as the translation. Otherwise it will use ``There are %count% apples``.
-
-Here is the French translation::
-
- 'Il y a %count% pomme|Il y a %count% pommes'
-
-Even if the string looks similar (it is made of two sub-strings separated by a
-pipe), the French rules are different: the first form (no plural) is used when
-``count`` is ``0`` or ``1``. So, the translator will automatically use the
-first string (``Il y a %count% pomme``) when ``count`` is ``0`` or ``1``.
-
-Each locale has its own set of rules, with some having as many as six different
-plural forms with complex rules behind which numbers map to which plural form.
-The rules are quite simple for English and French, but for Russian, you'd
-may want a hint to know which rule matches which string. To help translators,
-you can optionally "tag" each string::
-
- 'one: There is one apple|some: There are %count% apples'
-
- 'none_or_one: Il y a %count% pomme|some: Il y a %count% pommes'
-
-The tags are really only hints for translators and don't affect the logic
-used to determine which plural form to use. The tags can be any descriptive
-string that ends with a colon (``:``). The tags also do not need to be the
-same in the original message as in the translated one.
-
-.. tip::
-
- As tags are optional, the translator doesn't use them (the translator will
- only get a string based on its position in the string).
-
-Explicit Interval Pluralization
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The easiest way to pluralize a message is to let Symfony2 use internal logic
-to choose which string to use based on a given number. Sometimes, you'll
-need more control or want a different translation for specific cases (for
-``0``, or when the count is negative, for example). For such cases, you can
-use explicit math intervals::
-
- '{0} There are no apples|{1} There is one apple|]1,19] There are %count% apples|[20,Inf] There are many apples'
-
-The intervals follow the `ISO 31-11`_ notation. The above string specifies
-four different intervals: exactly ``0``, exactly ``1``, ``2-19``, and ``20``
-and higher.
-
-You can also mix explicit math rules and standard rules. In this case, if
-the count is not matched by a specific interval, the standard rules take
-effect after removing the explicit rules::
-
- '{0} There are no apples|[20,Inf] There are many apples|There is one apple|a_few: There are %count% apples'
-
-For example, for ``1`` apple, the standard rule ``There is one apple`` will
-be used. For ``2-19`` apples, the second standard rule ``There are %count%
-apples`` will be selected.
-
-An :class:`Symfony\\Component\\Translation\\Interval` can represent a finite set
-of numbers::
-
- {1,2,3,4}
-
-Or numbers between two other numbers::
-
- [1, +Inf[
- ]-1,2[
-
-The left delimiter can be ``[`` (inclusive) or ``]`` (exclusive). The right
-delimiter can be ``[`` (exclusive) or ``]`` (inclusive). Beside numbers, you
-can use ``-Inf`` and ``+Inf`` for the infinite.
-
-.. index::
- single: Translations; In templates
-
-Translations in Templates
--------------------------
-
-Most of the time, translation occurs in templates. Symfony2 provides native
-support for both Twig and PHP templates.
-
-.. _book-translation-tags:
-
-Twig Templates
-~~~~~~~~~~~~~~
-
-Symfony2 provides specialized Twig tags (``trans`` and ``transchoice``) to
-help with message translation of *static blocks of text*:
-
-.. code-block:: jinja
-
- {% trans %}Hello %name%{% endtrans %}
-
- {% transchoice count %}
- {0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples
- {% endtranschoice %}
-
-The ``transchoice`` tag automatically gets the ``%count%`` variable from
-the current context and passes it to the translator. This mechanism only
-works when you use a placeholder following the ``%var%`` pattern.
-
-.. tip::
-
- If you need to use the percent character (``%``) in a string, escape it by
- doubling it: ``{% trans %}Percent: %percent%%%{% endtrans %}``
-
-You can also specify the message domain and pass some additional variables:
-
-.. code-block:: jinja
-
- {% trans with {'%name%': 'Fabien'} from "app" %}Hello %name%{% endtrans %}
-
- {% trans with {'%name%': 'Fabien'} from "app" into "fr" %}Hello %name%{% endtrans %}
-
- {% transchoice count with {'%name%': 'Fabien'} from "app" %}
- {0} %name%, there are no apples|{1} %name%, there is one apple|]1,Inf] %name%, there are %count% apples
- {% endtranschoice %}
-
-.. _book-translation-filters:
-
-The ``trans`` and ``transchoice`` filters can be used to translate *variable
-texts* and complex expressions:
-
-.. code-block:: jinja
-
- {{ message|trans }}
-
- {{ message|transchoice(5) }}
-
- {{ message|trans({'%name%': 'Fabien'}, "app") }}
-
- {{ message|transchoice(5, {'%name%': 'Fabien'}, 'app') }}
-
-.. tip::
-
- Using the translation tags or filters have the same effect, but with
- one subtle difference: automatic output escaping is only applied to
- translations using a filter. In other words, if you need to be sure
- that your translated is *not* output escaped, you must apply the
- ``raw`` filter after the translation filter:
-
- .. code-block:: jinja
-
- {# text translated between tags is never escaped #}
- {% trans %}
-
foo
- {% endtrans %}
-
- {% set message = '
foo
' %}
-
- {# strings and variables translated via a filter is escaped by default #}
- {{ message|trans|raw }}
- {{ '
bar
'|trans|raw }}
-
-.. tip::
-
- You can set the translation domain for an entire Twig template with a single tag:
-
- .. code-block:: jinja
-
- {% trans_default_domain "app" %}
-
- Note that this only influences the current template, not any "included"
- templates (in order to avoid side effects).
-
-.. versionadded:: 2.1
- The ``trans_default_domain`` tag is new in Symfony2.1
-
-PHP Templates
-~~~~~~~~~~~~~
-
-The translator service is accessible in PHP templates through the
-``translator`` helper:
-
-.. code-block:: html+php
-
- trans('Symfony2 is great') ?>
-
- transChoice(
- '{0} There is no apples|{1} There is one apple|]1,Inf[ There are %count% apples',
- 10,
- array('%count%' => 10)
- ) ?>
-
-Forcing the Translator Locale
------------------------------
-
-When translating a message, Symfony2 uses the locale from the current request
-or the ``fallback`` locale if necessary. You can also manually specify the
-locale to use for translation::
-
- $this->get('translator')->trans(
- 'Symfony2 is great',
- array(),
- 'messages',
- 'fr_FR'
- );
-
- $this->get('translator')->transChoice(
- '{0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples',
- 10,
- array('%count%' => 10),
- 'messages',
- 'fr_FR'
- );
-
-Translating Database Content
-----------------------------
-
-The translation of database content should be handled by Doctrine through
-the `Translatable Extension`_. For more information, see the documentation
-for that library.
-
-.. _book-translation-constraint-messages:
-
-Translating Constraint Messages
--------------------------------
-
-The best way to understand constraint translation is to see it in action. To start,
-suppose you've created a plain-old-PHP object that you need to use somewhere in
-your application::
-
- // src/Acme/BlogBundle/Entity/Author.php
- namespace Acme\BlogBundle\Entity;
-
- class Author
- {
- public $name;
- }
-
-Add constraints though any of the supported methods. Set the message option to the
-translation source text. For example, to guarantee that the $name property is not
-empty, add the following:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # src/Acme/BlogBundle/Resources/config/validation.yml
- Acme\BlogBundle\Entity\Author:
- properties:
- name:
- - NotBlank: { message: "author.name.not_blank" }
-
- .. code-block:: php-annotations
-
- // src/Acme/BlogBundle/Entity/Author.php
- use Symfony\Component\Validator\Constraints as Assert;
-
- class Author
- {
- /**
- * @Assert\NotBlank(message = "author.name.not_blank")
- */
- public $name;
- }
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // src/Acme/BlogBundle/Entity/Author.php
-
- // ...
- use Symfony\Component\Validator\Mapping\ClassMetadata;
- use Symfony\Component\Validator\Constraints\NotBlank;
-
- class Author
- {
- public $name;
-
- public static function loadValidatorMetadata(ClassMetadata $metadata)
- {
- $metadata->addPropertyConstraint('name', new NotBlank(array(
- 'message' => 'author.name.not_blank',
- )));
- }
- }
-
-Create a translation file under the ``validators`` catalog for the constraint messages, typically in the ``Resources/translations/`` directory of the bundle. See `Message Catalogues`_ for more details.
-
-.. configuration-block::
-
- .. code-block:: xml
-
-
-
-
-
-
-
- author.name.not_blank
- Please enter an author name.
-
-
-
-
-
- .. code-block:: php
-
- // validators.en.php
- return array(
- 'author.name.not_blank' => 'Please enter an author name.',
- );
-
- .. code-block:: yaml
-
- # validators.en.yml
- author.name.not_blank: Please enter an author name.
-
-Summary
--------
-
-With the Symfony2 Translation component, creating an internationalized application
-no longer needs to be a painful process and boils down to just a few basic
-steps:
-
-* Abstract messages in your application by wrapping each in either the
- :method:`Symfony\\Component\\Translation\\Translator::trans` or
- :method:`Symfony\\Component\\Translation\\Translator::transChoice` methods;
-
-* Translate each message into multiple locales by creating translation message
- files. Symfony2 discovers and processes each file because its name follows
- a specific convention;
-
-* Manage the user's locale, which is stored on the request, but can also
- be set on the user's session.
-
-.. _`i18n`: http://en.wikipedia.org/wiki/Internationalization_and_localization
-.. _`L10n`: http://en.wikipedia.org/wiki/Internationalization_and_localization
-.. _`strtr function`: http://www.php.net/manual/en/function.strtr.php
-.. _`ISO 31-11`: http://en.wikipedia.org/wiki/Interval_(mathematics)#Notations_for_intervals
-.. _`Translatable Extension`: https://github.com/l3pp4rd/DoctrineExtensions
-.. _`ISO3166 Alpha-2`: http://en.wikipedia.org/wiki/ISO_3166-1#Current_codes
-.. _`ISO639-1`: http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
diff --git a/book/validation.rst b/book/validation.rst
deleted file mode 100644
index 6f7956ddcce..00000000000
--- a/book/validation.rst
+++ /dev/null
@@ -1,861 +0,0 @@
-.. index::
- single: Validation
-
-Validation
-==========
-
-Validation is a very common task in web applications. Data entered in forms
-needs to be validated. Data also needs to be validated before it is written
-into a database or passed to a web service.
-
-Symfony2 ships with a `Validator`_ component that makes this task easy and
-transparent. This component is based on the
-`JSR303 Bean Validation specification`_.
-
-.. index::
- single: Validation; The basics
-
-The Basics of Validation
-------------------------
-
-The best way to understand validation is to see it in action. To start, suppose
-you've created a plain-old-PHP object that you need to use somewhere in
-your application::
-
- // src/Acme/BlogBundle/Entity/Author.php
- namespace Acme\BlogBundle\Entity;
-
- class Author
- {
- public $name;
- }
-
-So far, this is just an ordinary class that serves some purpose inside your
-application. The goal of validation is to tell you whether or not the data
-of an object is valid. For this to work, you'll configure a list of rules
-(called :ref:`constraints`) that the object must
-follow in order to be valid. These rules can be specified via a number of
-different formats (YAML, XML, annotations, or PHP).
-
-For example, to guarantee that the ``$name`` property is not empty, add the
-following:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # src/Acme/BlogBundle/Resources/config/validation.yml
- Acme\BlogBundle\Entity\Author:
- properties:
- name:
- - NotBlank: ~
-
- .. code-block:: php-annotations
-
- // src/Acme/BlogBundle/Entity/Author.php
-
- // ...
- use Symfony\Component\Validator\Constraints as Assert;
-
- class Author
- {
- /**
- * @Assert\NotBlank()
- */
- public $name;
- }
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // src/Acme/BlogBundle/Entity/Author.php
-
- // ...
- use Symfony\Component\Validator\Mapping\ClassMetadata;
- use Symfony\Component\Validator\Constraints\NotBlank;
-
- class Author
- {
- public $name;
-
- public static function loadValidatorMetadata(ClassMetadata $metadata)
- {
- $metadata->addPropertyConstraint('name', new NotBlank());
- }
- }
-
-.. tip::
-
- Protected and private properties can also be validated, as well as "getter"
- methods (see `validator-constraint-targets`).
-
-.. index::
- single: Validation; Using the validator
-
-Using the ``validator`` Service
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Next, to actually validate an ``Author`` object, use the ``validate`` method
-on the ``validator`` service (class :class:`Symfony\\Component\\Validator\\Validator`).
-The job of the ``validator`` is easy: to read the constraints (i.e. rules)
-of a class and verify whether or not the data on the object satisfies those
-constraints. If validation fails, an array of errors is returned. Take this
-simple example from inside a controller::
-
- // ...
- use Symfony\Component\HttpFoundation\Response;
- use Acme\BlogBundle\Entity\Author;
-
- public function indexAction()
- {
- $author = new Author();
- // ... do something to the $author object
-
- $validator = $this->get('validator');
- $errors = $validator->validate($author);
-
- if (count($errors) > 0) {
- return new Response(print_r($errors, true));
- } else {
- return new Response('The author is valid! Yes!');
- }
- }
-
-If the ``$name`` property is empty, you will see the following error
-message:
-
-.. code-block:: text
-
- Acme\BlogBundle\Author.name:
- This value should not be blank
-
-If you insert a value into the ``name`` property, the happy success message
-will appear.
-
-.. tip::
-
- Most of the time, you won't interact directly with the ``validator``
- service or need to worry about printing out the errors. Most of the time,
- you'll use validation indirectly when handling submitted form data. For
- more information, see the :ref:`book-validation-forms`.
-
-You could also pass the collection of errors into a template.
-
-.. code-block:: php
-
- if (count($errors) > 0) {
- return $this->render('AcmeBlogBundle:Author:validate.html.twig', array(
- 'errors' => $errors,
- ));
- } else {
- // ...
- }
-
-Inside the template, you can output the list of errors exactly as needed:
-
-.. configuration-block::
-
- .. code-block:: html+jinja
-
- {# src/Acme/BlogBundle/Resources/views/Author/validate.html.twig #}
-
The author has the following errors
-
- {% for error in errors %}
-
{{ error.message }}
- {% endfor %}
-
-
- .. code-block:: html+php
-
-
-
The author has the following errors
-
-
-
getMessage() ?>
-
-
-
-.. note::
-
- Each validation error (called a "constraint violation"), is represented by
- a :class:`Symfony\\Component\\Validator\\ConstraintViolation` object.
-
-.. index::
- single: Validation; Validation with forms
-
-.. _book-validation-forms:
-
-Validation and Forms
-~~~~~~~~~~~~~~~~~~~~
-
-The ``validator`` service can be used at any time to validate any object.
-In reality, however, you'll usually work with the ``validator`` indirectly
-when working with forms. Symfony's form library uses the ``validator`` service
-internally to validate the underlying object after values have been submitted.
-The constraint violations on the object are converted into ``FieldError``
-objects that can easily be displayed with your form. The typical form submission
-workflow looks like the following from inside a controller::
-
- // ...
- use Acme\BlogBundle\Entity\Author;
- use Acme\BlogBundle\Form\AuthorType;
- use Symfony\Component\HttpFoundation\Request;
-
- public function updateAction(Request $request)
- {
- $author = new Author();
- $form = $this->createForm(new AuthorType(), $author);
-
- $form->handleRequest($request);
-
- if ($form->isValid()) {
- // the validation passed, do something with the $author object
-
- return $this->redirect($this->generateUrl(...));
- }
-
- return $this->render('BlogBundle:Author:form.html.twig', array(
- 'form' => $form->createView(),
- ));
- }
-
-.. note::
-
- This example uses an ``AuthorType`` form class, which is not shown here.
-
-For more information, see the :doc:`Forms` chapter.
-
-.. index::
- pair: Validation; Configuration
-
-.. _book-validation-configuration:
-
-Configuration
--------------
-
-The Symfony2 validator is enabled by default, but you must explicitly enable
-annotations if you're using the annotation method to specify your constraints:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- framework:
- validation: { enable_annotations: true }
-
- .. code-block:: xml
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- $container->loadFromExtension('framework', array(
- 'validation' => array(
- 'enable_annotations' => true,
- ),
- ));
-
-.. index::
- single: Validation; Constraints
-
-.. _validation-constraints:
-
-Constraints
------------
-
-The ``validator`` is designed to validate objects against *constraints* (i.e.
-rules). In order to validate an object, simply map one or more constraints
-to its class and then pass it to the ``validator`` service.
-
-Behind the scenes, a constraint is simply a PHP object that makes an assertive
-statement. In real life, a constraint could be: "The cake must not be burned".
-In Symfony2, constraints are similar: they are assertions that a condition
-is true. Given a value, a constraint will tell you whether or not that value
-adheres to the rules of the constraint.
-
-Supported Constraints
-~~~~~~~~~~~~~~~~~~~~~
-
-Symfony2 packages a large number of the most commonly-needed constraints:
-
-.. include:: /reference/constraints/map.rst.inc
-
-You can also create your own custom constraints. This topic is covered in
-the ":doc:`/cookbook/validation/custom_constraint`" article of the cookbook.
-
-.. index::
- single: Validation; Constraints configuration
-
-.. _book-validation-constraint-configuration:
-
-Constraint Configuration
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-Some constraints, like :doc:`NotBlank`,
-are simple whereas others, like the :doc:`Choice`
-constraint, have several configuration options available. Suppose that the
-``Author`` class has another property, ``gender`` that can be set to either
-"male" or "female":
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # src/Acme/BlogBundle/Resources/config/validation.yml
- Acme\BlogBundle\Entity\Author:
- properties:
- gender:
- - Choice: { choices: [male, female], message: Choose a valid gender. }
-
- .. code-block:: php-annotations
-
- // src/Acme/BlogBundle/Entity/Author.php
- use Symfony\Component\Validator\Constraints as Assert;
-
- class Author
- {
- /**
- * @Assert\Choice(
- * choices = { "male", "female" },
- * message = "Choose a valid gender."
- * )
- */
- public $gender;
- }
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // src/Acme/BlogBundle/Entity/Author.php
-
- // ...
- use Symfony\Component\Validator\Mapping\ClassMetadata;
- use Symfony\Component\Validator\Constraints\Choice;
-
- class Author
- {
- public $gender;
-
- public static function loadValidatorMetadata(ClassMetadata $metadata)
- {
- $metadata->addPropertyConstraint('gender', new Choice(array(
- 'choices' => array('male', 'female'),
- 'message' => 'Choose a valid gender.',
- )));
- }
- }
-
-.. _validation-default-option:
-
-The options of a constraint can always be passed in as an array. Some constraints,
-however, also allow you to pass the value of one, "*default*", option in place
-of the array. In the case of the ``Choice`` constraint, the ``choices``
-options can be specified in this way.
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # src/Acme/BlogBundle/Resources/config/validation.yml
- Acme\BlogBundle\Entity\Author:
- properties:
- gender:
- - Choice: [male, female]
-
- .. code-block:: php-annotations
-
- // src/Acme/BlogBundle/Entity/Author.php
-
- // ...
- use Symfony\Component\Validator\Constraints as Assert;
-
- class Author
- {
- /**
- * @Assert\Choice({"male", "female"})
- */
- protected $gender;
- }
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
- male
- female
-
-
-
-
-
- .. code-block:: php
-
- // src/Acme/BlogBundle/Entity/Author.php
-
- // ...
- use Symfony\Component\Validator\Mapping\ClassMetadata;
- use Symfony\Component\Validator\Constraints\Choice;
-
- class Author
- {
- protected $gender;
-
- public static function loadValidatorMetadata(ClassMetadata $metadata)
- {
- $metadata->addPropertyConstraint(
- 'gender',
- new Choice(array('male', 'female'))
- );
- }
- }
-
-This is purely meant to make the configuration of the most common option of
-a constraint shorter and quicker.
-
-If you're ever unsure of how to specify an option, either check the API documentation
-for the constraint or play it safe by always passing in an array of options
-(the first method shown above).
-
-Translation Constraint Messages
--------------------------------
-
-For information on translating the constraint messages, see
-:ref:`book-translation-constraint-messages`.
-
-.. index::
- single: Validation; Constraint targets
-
-.. _validator-constraint-targets:
-
-Constraint Targets
-------------------
-
-Constraints can be applied to a class property (e.g. ``name``) or a public
-getter method (e.g. ``getFullName``). The first is the most common and easy
-to use, but the second allows you to specify more complex validation rules.
-
-.. index::
- single: Validation; Property constraints
-
-.. _validation-property-target:
-
-Properties
-~~~~~~~~~~
-
-Validating class properties is the most basic validation technique. Symfony2
-allows you to validate private, protected or public properties. The next
-listing shows you how to configure the ``$firstName`` property of an ``Author``
-class to have at least 3 characters.
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # src/Acme/BlogBundle/Resources/config/validation.yml
- Acme\BlogBundle\Entity\Author:
- properties:
- firstName:
- - NotBlank: ~
- - Length:
- min: 3
-
- .. code-block:: php-annotations
-
- // Acme/BlogBundle/Entity/Author.php
-
- // ...
- use Symfony\Component\Validator\Constraints as Assert;
-
- class Author
- {
- /**
- * @Assert\NotBlank()
- * @Assert\Length(min = "3")
- */
- private $firstName;
- }
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // src/Acme/BlogBundle/Entity/Author.php
-
- // ...
- use Symfony\Component\Validator\Mapping\ClassMetadata;
- use Symfony\Component\Validator\Constraints\NotBlank;
- use Symfony\Component\Validator\Constraints\Length;
-
- class Author
- {
- private $firstName;
-
- public static function loadValidatorMetadata(ClassMetadata $metadata)
- {
- $metadata->addPropertyConstraint('firstName', new NotBlank());
- $metadata->addPropertyConstraint(
- 'firstName',
- new Length(array("min" => 3)));
- }
- }
-
-.. index::
- single: Validation; Getter constraints
-
-Getters
-~~~~~~~
-
-Constraints can also be applied to the return value of a method. Symfony2
-allows you to add a constraint to any public method whose name starts with
-"get" or "is". In this guide, both of these types of methods are referred
-to as "getters".
-
-The benefit of this technique is that it allows you to validate your object
-dynamically. For example, suppose you want to make sure that a password field
-doesn't match the first name of the user (for security reasons). You can
-do this by creating an ``isPasswordLegal`` method, and then asserting that
-this method must return ``true``:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # src/Acme/BlogBundle/Resources/config/validation.yml
- Acme\BlogBundle\Entity\Author:
- getters:
- passwordLegal:
- - "True": { message: "The password cannot match your first name" }
-
- .. code-block:: php-annotations
-
- // src/Acme/BlogBundle/Entity/Author.php
-
- // ...
- use Symfony\Component\Validator\Constraints as Assert;
-
- class Author
- {
- /**
- * @Assert\True(message = "The password cannot match your first name")
- */
- public function isPasswordLegal()
- {
- // return true or false
- }
- }
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // src/Acme/BlogBundle/Entity/Author.php
-
- // ...
- use Symfony\Component\Validator\Mapping\ClassMetadata;
- use Symfony\Component\Validator\Constraints\True;
-
- class Author
- {
- public static function loadValidatorMetadata(ClassMetadata $metadata)
- {
- $metadata->addGetterConstraint('passwordLegal', new True(array(
- 'message' => 'The password cannot match your first name',
- )));
- }
- }
-
-Now, create the ``isPasswordLegal()`` method, and include the logic you need::
-
- public function isPasswordLegal()
- {
- return ($this->firstName != $this->password);
- }
-
-.. note::
-
- The keen-eyed among you will have noticed that the prefix of the getter
- ("get" or "is") is omitted in the mapping. This allows you to move the
- constraint to a property with the same name later (or vice versa) without
- changing your validation logic.
-
-.. _validation-class-target:
-
-Classes
-~~~~~~~
-
-Some constraints apply to the entire class being validated. For example,
-the :doc:`Callback` constraint is a generic
-constraint that's applied to the class itself. When that class is validated,
-methods specified by that constraint are simply executed so that each can
-provide more custom validation.
-
-.. _book-validation-validation-groups:
-
-Validation Groups
------------------
-
-So far, you've been able to add constraints to a class and ask whether or
-not that class passes all of the defined constraints. In some cases, however,
-you'll need to validate an object against only *some* of the constraints
-on that class. To do this, you can organize each constraint into one or more
-"validation groups", and then apply validation against just one group of
-constraints.
-
-For example, suppose you have a ``User`` class, which is used both when a
-user registers and when a user updates his/her contact information later:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # src/Acme/BlogBundle/Resources/config/validation.yml
- Acme\BlogBundle\Entity\User:
- properties:
- email:
- - Email: { groups: [registration] }
- password:
- - NotBlank: { groups: [registration] }
- - Length: { min: 7, groups: [registration] }
- city:
- - Length:
- min: 2
-
- .. code-block:: php-annotations
-
- // src/Acme/BlogBundle/Entity/User.php
- namespace Acme\BlogBundle\Entity;
-
- use Symfony\Component\Security\Core\User\UserInterface;
- use Symfony\Component\Validator\Constraints as Assert;
-
- class User implements UserInterface
- {
- /**
- * @Assert\Email(groups={"registration"})
- */
- private $email;
-
- /**
- * @Assert\NotBlank(groups={"registration"})
- * @Assert\Length(min=7, groups={"registration"})
- */
- private $password;
-
- /**
- * @Assert\Length(min = "2")
- */
- private $city;
- }
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // src/Acme/BlogBundle/Entity/User.php
- namespace Acme\BlogBundle\Entity;
-
- use Symfony\Component\Validator\Mapping\ClassMetadata;
- use Symfony\Component\Validator\Constraints\Email;
- use Symfony\Component\Validator\Constraints\NotBlank;
- use Symfony\Component\Validator\Constraints\Length;
-
- class User
- {
- public static function loadValidatorMetadata(ClassMetadata $metadata)
- {
- $metadata->addPropertyConstraint('email', new Email(array(
- 'groups' => array('registration'),
- )));
-
- $metadata->addPropertyConstraint('password', new NotBlank(array(
- 'groups' => array('registration'),
- )));
- $metadata->addPropertyConstraint('password', new Length(array(
- 'min' => 7,
- 'groups' => array('registration')
- )));
-
- $metadata->addPropertyConstraint(
- 'city',
- Length(array("min" => 3)));
- }
- }
-
-With this configuration, there are two validation groups:
-
-* ``Default`` - contains the constraints not assigned to any other group;
-
-* ``registration`` - contains the constraints on the ``email`` and ``password``
- fields only.
-
-To tell the validator to use a specific group, pass one or more group names
-as the second argument to the ``validate()`` method::
-
- $errors = $validator->validate($author, array('registration'));
-
-Of course, you'll usually work with validation indirectly through the form
-library. For information on how to use validation groups inside forms, see
-:ref:`book-forms-validation-groups`.
-
-.. index::
- single: Validation; Validating raw values
-
-.. _book-validation-raw-values:
-
-Validating Values and Arrays
-----------------------------
-
-So far, you've seen how you can validate entire objects. But sometimes, you
-just want to validate a simple value - like to verify that a string is a valid
-email address. This is actually pretty easy to do. From inside a controller,
-it looks like this::
-
- use Symfony\Component\Validator\Constraints\Email;
- // ...
-
- public function addEmailAction($email)
- {
- $emailConstraint = new Email();
- // all constraint "options" can be set this way
- $emailConstraint->message = 'Invalid email address';
-
- // use the validator to validate the value
- $errorList = $this->get('validator')->validateValue(
- $email,
- $emailConstraint
- );
-
- if (count($errorList) == 0) {
- // this IS a valid email address, do something
- } else {
- // this is *not* a valid email address
- $errorMessage = $errorList[0]->getMessage();
-
- // ... do something with the error
- }
-
- // ...
- }
-
-By calling ``validateValue`` on the validator, you can pass in a raw value and
-the constraint object that you want to validate that value against. A full
-list of the available constraints - as well as the full class name for each
-constraint - is available in the :doc:`constraints reference`
-section .
-
-The ``validateValue`` method returns a :class:`Symfony\\Component\\Validator\\ConstraintViolationList`
-object, which acts just like an array of errors. Each error in the collection
-is a :class:`Symfony\\Component\\Validator\\ConstraintViolation` object,
-which holds the error message on its `getMessage` method.
-
-Final Thoughts
---------------
-
-The Symfony2 ``validator`` is a powerful tool that can be leveraged to
-guarantee that the data of any object is "valid". The power behind validation
-lies in "constraints", which are rules that you can apply to properties or
-getter methods of your object. And while you'll most commonly use the validation
-framework indirectly when using forms, remember that it can be used anywhere
-to validate any object.
-
-Learn more from the Cookbook
-----------------------------
-
-* :doc:`/cookbook/validation/custom_constraint`
-
-.. _Validator: https://github.com/symfony/Validator
-.. _JSR303 Bean Validation specification: http://jcp.org/en/jsr/detail?id=303
diff --git a/bundles.rst b/bundles.rst
new file mode 100644
index 00000000000..878bee3af4a
--- /dev/null
+++ b/bundles.rst
@@ -0,0 +1,171 @@
+.. _page-creation-bundles:
+
+The Bundle System
+=================
+
+.. warning::
+
+ In Symfony versions prior to 4.0, it was recommended to organize your own
+ 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
+features of Symfony framework are implemented with bundles (FrameworkBundle,
+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
+: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],
+ // ...
+
+ // 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 :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.
+
+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 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 new class called ``AcmeBlogBundle``::
+
+ // src/AcmeBlogBundle.php
+ namespace Acme\BlogBundle;
+
+ use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
+
+ 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 AcmeBlogBundle follows the standard
+ :ref:`Bundle naming conventions `. You could
+ 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
+of the bundle. Now that you've created the bundle, enable it::
+
+ // config/bundles.php
+ return [
+ // ...
+ Acme\BlogBundle\AcmeBlogBundle::class => ['all' => true],
+ ];
+
+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:
+
+``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).
+
+``config/``
+ Houses configuration, including routing configuration (e.g. ``routes.php``).
+
+``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.
+
+``src/``
+ Contains all PHP classes related to the bundle logic (e.g. ``Controller/CategoryController.php``).
+
+``templates/``
+ Holds templates organized by controller name (e.g. ``category/show.html.twig``).
+
+``tests/``
+ Holds all tests for the bundle.
+
+``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::
+
+ 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
+----------
+
+* :doc:`/bundles/override`
+* :doc:`/bundles/best_practices`
+* :doc:`/bundles/configuration`
+* :doc:`/bundles/extension`
+* :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
new file mode 100644
index 00000000000..37dc386b8e4
--- /dev/null
+++ b/bundles/best_practices.rst
@@ -0,0 +1,571 @@
+Best Practices for Reusable Bundles
+===================================
+
+This article is all about how to structure your **reusable bundles** to be
+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.
+
+.. _bundles-naming-conventions:
+
+Bundle Name
+-----------
+
+A bundle is also a PHP namespace. The namespace must follow the `PSR-4`_
+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 (which is
+a class that extends :class:`Symfony\\Component\\HttpKernel\\Bundle\\Bundle`).
+The bundle class name must follow these rules:
+
+* Use only alphanumeric characters and underscores;
+* Use a StudlyCaps name (i.e. camelCase with an uppercase first letter);
+* 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);
+* Suffix the name with ``Bundle``.
+
+Here are some valid bundle namespaces and class names:
+
+========================== ==================
+Namespace Bundle Class Name
+========================== ==================
+``Acme\Bundle\BlogBundle`` AcmeBlogBundle
+``Acme\BlogBundle`` AcmeBlogBundle
+========================== ==================
+
+By convention, the ``getName()`` method of the bundle class should return the
+class name.
+
+.. note::
+
+ If you share your bundle publicly, you must use the bundle class name as
+ the name of the repository (AcmeBlogBundle and not BlogBundle for instance).
+
+.. note::
+
+ Symfony core Bundles do not prefix the Bundle class with ``Symfony``
+ and always add a ``Bundle`` sub-namespace; for example:
+ :class:`Symfony\\Bundle\\FrameworkBundle\\FrameworkBundle`.
+
+Each bundle has an alias, which is the lower-cased short version of the bundle
+name using underscores (``acme_blog`` for AcmeBlogBundle). This alias
+is used to enforce uniqueness within a project and for defining bundle's
+configuration options (see below for some usage examples).
+
+Directory Structure
+-------------------
+
+The following is the recommended directory structure of an AcmeBlogBundle:
+
+.. code-block:: text
+
+ /
+ ├── 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:
+
+* ``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`_;
+* ``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.
+
+The bundle directory is read-only. If you need to write temporary files, store
+them under the ``cache/`` or ``log/`` directory of the host application. Tools
+can generate files in the bundle directory structure, but only if the generated
+files are going to be part of the repository.
+
+The following classes and files have specific emplacements (some are mandatory
+and others are just conventions followed by most developers):
+
+=================================================== ========================================
+Type Directory
+=================================================== ========================================
+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
+-------
+
+The bundle directory structure is used as the namespace hierarchy. For
+instance, a ``ContentController`` controller which is stored in
+``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 `.
+
+Some classes should be seen as facades and should be as short as possible, like
+Commands, Helpers, Listeners and Controllers.
+
+Classes that connect to the event dispatcher should be suffixed with
+``Listener``.
+
+Exception classes should be stored in an ``Exception`` sub-namespace.
+
+Vendors
+-------
+
+A bundle must not embed third-party PHP libraries. It should rely on the
+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 test suite must be executable with a simple ``phpunit`` command run from
+ a sample application;
+* The functional tests should only be used to test the response output and
+ some profiling information if you have some;
+* The tests should cover at least 95% of the code base.
+
+.. note::
+
+ A test suite must not contain ``AllTests.php`` scripts, but must rely on the
+ existence of a ``phpunit.xml.dist`` file.
+
+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, like `GitHub Actions`_
+and `Travis CI`_.
+
+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 ``4.x`` and ``5.x`` if
+ support is claimed for both).
+
+Thus, a bundle supporting PHP 7.3, 7.4 and 8.0, and Symfony 4.4 and 5.x should
+have at least this test matrix:
+
+=========== =============== ===================
+PHP version Symfony version Composer flags
+=========== =============== ===================
+7.3 ``4.*`` ``--prefer-lowest``
+7.4 ``5.*``
+8.0 ``5.*``
+=========== =============== ===================
+
+.. 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 5.x for all Symfony packages
+ export SYMFONY_REQUIRE=5.*
+ # alternatively you can run this command to update composer.json config
+ # composer config extra.symfony.require "5.*"
+
+ # 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, :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
+``.gitignore``, etc), then you should create a `Symfony Flex recipe`_.
+
+Documentation
+-------------
+
+All classes and functions must come with full PHPDoc.
+
+Extensive documentation should also be provided in the ``docs/``
+directory.
+The index file (for example ``docs/index.rst`` or
+``docs/index.md``) is the only mandatory file and must be the entry
+point for the documentation. The
+:doc:`reStructuredText (rST) ` is the format
+used to render the documentation on the Symfony website.
+
+Installation Instructions
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In order to ease the installation of third-party bundles, consider using the
+following standardized instructions in your ``README.md`` file.
+
+.. configuration-block::
+
+ .. code-block:: markdown
+
+ 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
+ ```
+
+ Applications that don't use Symfony Flex
+ ----------------------------------------
+
+ ### Step 1: Download the Bundle
+
+ Open a command console, enter your project directory and execute the
+ following command to download the latest stable version of this bundle:
+
+ ```console
+ composer require
+ ```
+
+ ### Step 2: Enable the Bundle
+
+ Then, enable the bundle by adding it to the list of registered bundles
+ in the `config/bundles.php` file of your project:
+
+ ```php
+ // config/bundles.php
+
+ return [
+ // ...
+ \\::class => ['all' => true],
+ ];
+ ```
+
+ .. code-block:: rst
+
+ Installation
+ ============
+
+ 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:: terminal
+
+ composer require
+
+ Applications that don't use Symfony Flex
+ ----------------------------------------
+
+ Step 1: Download the Bundle
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Open a command console, enter your project directory and execute the
+ following command to download the latest stable version of this bundle:
+
+ .. code-block:: terminal
+
+ composer require
+
+ Step 2: Enable the Bundle
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Then, enable the bundle by adding it to the list of registered bundles
+ 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
+
+The example above assumes that you are installing the latest stable version of
+the bundle, where you don't have to provide the package version number
+(e.g. ``composer require friendsofsymfony/user-bundle``). If the installation
+instructions refer to some past bundle version or to some unstable version,
+include the version constraint (e.g. ``composer require friendsofsymfony/user-bundle "~2.0@dev"``).
+
+Optionally, you can add more installation steps (*Step 3*, *Step 4*, etc.) to
+explain other required installation tasks, such as registering routes or
+dumping assets.
+
+Routing
+-------
+
+If the bundle provides routes, they must be prefixed with the bundle alias.
+For example, if your bundle is called AcmeBlogBundle, all its routes must be
+prefixed with ``acme_blog_``.
+
+Templates
+---------
+
+If a bundle provides templates, they must use Twig. A bundle must not provide
+a main layout, except if it provides a full working application.
+
+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 (``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
+-------------
+
+To provide more flexibility, a bundle can provide configurable settings by
+using the Symfony built-in mechanisms.
+
+For simple configuration settings, rely on the default ``parameters`` entry of
+the Symfony configuration. Symfony parameters are simple key/value pairs; a
+value being any valid PHP value. Each parameter name should start with the
+bundle alias, though this is just a best-practice suggestion. The rest of the
+parameter name will use a period (``.``) to separate different parts (e.g.
+``acme_blog.author.email``).
+
+The end user can provide values in any configuration file:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/services.yaml
+ parameters:
+ acme_blog.author.email: 'fabien@example.com'
+
+ .. code-block:: xml
+
+
+
+
+
+ fabien@example.com
+
+
+
+
+ .. code-block:: php
+
+ // config/services.php
+ 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::
+
+ $container->getParameter('acme_blog.author.email');
+
+While this mechanism requires the least effort, you should consider using the
+more advanced :doc:`semantic bundle configuration ` to
+make your configuration more robust.
+
+Versioning
+----------
+
+Bundles must be versioned following the `Semantic Versioning Standard`_.
+
+Services
+--------
+
+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,
+:ref:`aliases should be created ` from the interface/class
+to the service id. For example, in MonologBundle, an alias is created from
+``Psr\Log\LoggerInterface`` to ``logger`` so that the ``LoggerInterface`` type-hint
+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:
+ :doc:`How to Load Service Configuration inside a Bundle `.
+
+Composer Metadata
+-----------------
+
+The ``composer.json`` file should include at least the following metadata:
+
+``name``
+ 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 a hyphen. For example: AcmeBlogBundle
+ is transformed into ``blog-bundle`` and AcmeSocialConnectBundle is
+ transformed into ``social-connect-bundle``.
+
+``description``
+ A brief explanation of the purpose of the bundle.
+
+``type``
+ Use the ``symfony-bundle`` value.
+
+``license``
+ a string (or array of strings) with a `valid license identifier`_, such as ``MIT``.
+
+``autoload``
+ This information is used by Symfony to load the classes of the bundle. It's
+ recommended to use the `PSR-4`_ autoload standard: use the namespace as key,
+ and the location of the bundle's main class (relative to ``composer.json``)
+ as value. 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.
+
+Resources
+---------
+
+If the bundle references any resources (config files, translation files, etc.),
+you can use physical paths (e.g. ``__DIR__/config/services.xml``).
+
+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
+----------
+
+* :doc:`/bundles/extension`
+* :doc:`/bundles/configuration`
+
+.. _`PSR-4`: https://www.php-fig.org/psr/psr-4/
+.. _`Symfony Flex recipe`: https://github.com/symfony/recipes
+.. _`Semantic Versioning Standard`: https://semver.org/
+.. _`Packagist`: https://packagist.org/
+.. _`choose any license`: https://choosealicense.com/
+.. _`valid license identifier`: https://spdx.org/licenses/
+.. _`GitHub Actions`: https://docs.github.com/en/free-pro-team@latest/actions
+.. _`Travis CI`: https://docs.travis-ci.com/
diff --git a/bundles/configuration.rst b/bundles/configuration.rst
new file mode 100644
index 00000000000..dedfada2ea2
--- /dev/null
+++ b/bundles/configuration.rst
@@ -0,0 +1,539 @@
+How to Create Friendly Configuration for a Bundle
+=================================================
+
+If you open your main application configuration directory (usually
+``config/packages/``), you'll see a number of different files, such as
+``framework.yaml``, ``twig.yaml`` and ``doctrine.yaml``. Each of these
+configures a specific bundle, allowing you to define options at a high level and
+then let the bundle make all the low-level, complex changes based on your
+settings.
+
+For example, the following configuration tells the FrameworkBundle to enable the
+form integration, which involves the definition of quite a few services as well
+as integration of other related components:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/framework.yaml
+ framework:
+ form: true
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // 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 X/Twitter. To make your bundle configurable to the user, you
+can add some configuration that looks like this:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/acme_social.yaml
+ acme_social:
+ twitter:
+ client_id: 123
+ client_secret: your_secret
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/acme_social.php
+ 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,
+options. As the bundle developer, you then parse through that configuration and
+load correct services and parameters inside an "Extension" class.
+
+.. note::
+
+ 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).
+
+.. seealso::
+
+ Read more about the extension in :doc:`/bundles/extension`.
+
+.. tip::
+
+ 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
+ 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.
+
+.. seealso::
+
+ For parameter handling within a dependency injection container see
+ :doc:`/configuration/using_parameters_in_dic`.
+
+Processing the ``$configs`` Array
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+First things first, you have to create an extension class as explained in
+:doc:`/bundles/extension`.
+
+Whenever a user includes the ``acme_social`` key (which is the DI alias) in a
+configuration file, the configuration under it is added to an array of
+configurations and passed to the ``load()`` method of your extension (Symfony
+automatically converts XML and YAML to an array).
+
+For the configuration example in the previous section, the array passed to your
+``load()`` method will look like this::
+
+ [
+ [
+ 'twitter' => [
+ 'client_id' => 123,
+ 'client_secret' => 'your_secret',
+ ],
+ ],
+ ]
+
+Notice that this is an *array of arrays*, not just a single flat array of the
+configuration values. This is intentional, as it allows Symfony to parse several
+configuration resources. For example, if ``acme_social`` appears in another
+configuration file - say ``config/packages/dev/acme_social.yaml`` - with
+different values beneath it, the incoming array might look like this::
+
+ [
+ // values from config/packages/acme_social.yaml
+ [
+ 'twitter' => [
+ 'client_id' => 123,
+ 'client_secret' => 'your_secret',
+ ],
+ ],
+ // values from config/packages/dev/acme_social.yaml
+ [
+ 'twitter' => [
+ 'client_id' => 456,
+ ],
+ ],
+ ]
+
+The order of the two arrays depends on which one is set first.
+
+But don't worry! Symfony's Config component will help you merge these values,
+provide defaults and give the user validation errors on bad configuration.
+Here's how it works. Create a ``Configuration`` class in the
+``DependencyInjection`` directory and build a tree that defines the structure
+of your bundle's configuration.
+
+The ``Configuration`` class to handle the sample configuration looks like::
+
+ // src/DependencyInjection/Configuration.php
+ namespace Acme\SocialBundle\DependencyInjection;
+
+ use Symfony\Component\Config\Definition\Builder\TreeBuilder;
+ use Symfony\Component\Config\Definition\ConfigurationInterface;
+
+ class Configuration implements ConfigurationInterface
+ {
+ public function getConfigTreeBuilder(): TreeBuilder
+ {
+ $treeBuilder = new TreeBuilder('acme_social');
+
+ $treeBuilder->getRootNode()
+ ->children()
+ ->arrayNode('twitter')
+ ->children()
+ ->integerNode('client_id')->end()
+ ->scalarNode('client_secret')->end()
+ ->end()
+ ->end() // twitter
+ ->end()
+ ;
+
+ return $treeBuilder;
+ }
+ }
+
+.. seealso::
+
+ The ``Configuration`` class can be much more complicated than shown here,
+ supporting "prototype" nodes, advanced validation, XML-specific normalization
+ and advanced merging. You can read more about this in
+ :doc:`the Config component documentation `. You
+ can also see it in action by checking out some core Configuration
+ classes, such as the one from the `FrameworkBundle Configuration`_ or the
+ `TwigBundle Configuration`_.
+
+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/DependencyInjection/AcmeSocialExtension.php
+ public function load(array $configs, ContainerBuilder $container): void
+ {
+ $configuration = new Configuration();
+
+ $config = $this->processConfiguration($configuration, $configs);
+
+ // you now have these 2 config keys
+ // $config['twitter']['client_id'] and $config['twitter']['client_secret']
+ }
+
+The ``processConfiguration()`` method uses the configuration tree you've defined
+in the ``Configuration`` class to validate, normalize and merge all the
+configuration arrays together.
+
+Now, you can use the ``$config`` variable to modify a service provided by your bundle.
+For example, imagine your bundle has the following example config:
+
+.. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+In your extension, you can load this and dynamically set its arguments::
+
+ // src/DependencyInjection/AcmeSocialExtension.php
+ namespace Acme\SocialBundle\DependencyInjection;
+
+ use Symfony\Component\Config\FileLocator;
+ use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
+
+ public function load(array $configs, ContainerBuilder $container): void
+ {
+ $loader = new XmlFileLoader($container, new FileLocator(dirname(__DIR__).'/Resources/config'));
+ $loader->load('services.xml');
+
+ $configuration = new Configuration();
+ $config = $this->processConfiguration($configuration, $configs);
+
+ $definition = $container->getDefinition('acme_social.twitter_client');
+ $definition->replaceArgument(0, $config['twitter']['client_id']);
+ $definition->replaceArgument(1, $config['twitter']['client_secret']);
+ }
+
+.. tip::
+
+ Instead of calling ``processConfiguration()`` in your extension each time you
+ provide some configuration options, you might want to use the
+ :class:`Symfony\\Component\\HttpKernel\\DependencyInjection\\ConfigurableExtension`
+ to do this automatically for you::
+
+ // src/DependencyInjection/HelloExtension.php
+ namespace Acme\HelloBundle\DependencyInjection;
+
+ use Symfony\Component\DependencyInjection\ContainerBuilder;
+ use Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension;
+
+ class AcmeHelloExtension extends ConfigurableExtension
+ {
+ // note that this method is called loadInternal and not load
+ protected function loadInternal(array $mergedConfig, ContainerBuilder $container): void
+ {
+ // ...
+ }
+ }
+
+ This class uses the ``getConfiguration()`` method to get the Configuration
+ instance.
+
+.. sidebar:: Processing the Configuration yourself
+
+ Using the Config component is fully optional. The ``load()`` method gets an
+ array of configuration values. You can instead parse these arrays yourself
+ (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): void
+ {
+ $config = [];
+ // let resources override the previous set value
+ foreach ($configs as $subConfig) {
+ $config = array_merge($config, $subConfig);
+ }
+
+ // ... now use the flat $config array
+ }
+
+Modifying the Configuration of Another Bundle
+---------------------------------------------
+
+If you have multiple bundles that depend on each other, it may be useful to
+allow one ``Extension`` class to modify the configuration passed to another
+bundle's ``Extension`` class. This can be achieved using a prepend extension.
+For more details, see :doc:`/bundles/prepend_extension`.
+
+Dump the Configuration
+----------------------
+
+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
+(``/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 and return an instance of your ``Configuration``.
+
+Supporting XML
+--------------
+
+Symfony allows people to provide the configuration in three different formats:
+Yaml, XML and PHP. Both Yaml and PHP use the same syntax and are supported by
+default when using the Config component. Supporting XML requires you to do some
+more things. But when sharing your bundle with others, it is recommended that
+you follow these steps.
+
+Make your Config Tree ready for XML
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The Config component provides some methods by default to allow it to correctly
+process XML configuration. See ":ref:`component-config-normalization`" of the
+component documentation. However, you can do some optional things as well, this
+will improve the experience of using XML configuration:
+
+Choosing an XML Namespace
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+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 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/DependencyInjection/AcmeHelloExtension.php
+ namespace Acme\HelloBundle\DependencyInjection;
+
+ // ...
+ class AcmeHelloExtension extends Extension
+ {
+ // ...
+
+ public function getNamespace(): string
+ {
+ return 'http://acme_company.com/schema/dic/hello';
+ }
+ }
+
+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
+it is used by the Config component to validate the elements.
+
+In order to use the schema, the XML configuration file must provide an
+``xsi:schemaLocation`` attribute pointing to the XSD file for a certain XML
+namespace. This location always starts with the XML namespace. This XML
+namespace is then replaced with the XSD validation base path returned from
+:method:`Extension::getXsdValidationBasePath() `
+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 ``config/schema/`` directory, but you
+can place it anywhere you like. You should return this path as the base path::
+
+ // src/DependencyInjection/AcmeHelloExtension.php
+ namespace Acme\HelloBundle\DependencyInjection;
+
+ // ...
+ class AcmeHelloExtension extends Extension
+ {
+ // ...
+
+ public function getXsdValidationBasePath(): string
+ {
+ return __DIR__.'/../config/schema';
+ }
+ }
+
+Assuming the XSD file is called ``hello-1.0.xsd``, the schema location will be
+``https://acme_company.com/schema/dic/hello/hello-1.0.xsd``:
+
+.. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+.. _`FrameworkBundle Configuration`: https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
+.. _`TwigBundle Configuration`: https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php
+.. _`XML namespace`: https://en.wikipedia.org/wiki/XML_namespace
+.. _`XML schema`: https://en.wikipedia.org/wiki/XML_schema
+.. _`snake case`: https://en.wikipedia.org/wiki/Snake_case
diff --git a/bundles/extension.rst b/bundles/extension.rst
new file mode 100644
index 00000000000..d2792efc477
--- /dev/null
+++ b/bundles/extension.rst
@@ -0,0 +1,208 @@
+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 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
+---------------------------
+
+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;
+
+* It has to implement the :class:`Symfony\\Component\\DependencyInjection\\Extension\\ExtensionInterface`,
+ which is usually achieved by extending the
+ :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
+ ``AcmeExtension`` and the one for AcmeHelloBundle would be called
+ ``AcmeHelloExtension``).
+
+This is how the extension of an AcmeHelloBundle should look like::
+
+ // src/DependencyInjection/AcmeHelloExtension.php
+ namespace Acme\HelloBundle\DependencyInjection;
+
+ use Symfony\Component\DependencyInjection\ContainerBuilder;
+ use Symfony\Component\DependencyInjection\Extension\Extension;
+
+ class AcmeHelloExtension extends Extension
+ {
+ public function load(array $configs, ContainerBuilder $container): void
+ {
+ // ... you'll load the files here later
+ }
+ }
+
+Manually Registering an Extension Class
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When not following the conventions, you will have to manually register your
+extension. To do this, you should override the
+:method:`Bundle::getContainerExtension() `
+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(): ?ExtensionInterface
+ {
+ return new UnconventionalExtensionClass();
+ }
+ }
+
+In addition, when the new Extension class name doesn't follow the naming
+conventions, you must also override the
+:method:`Extension::getAlias() `
+method to return the correct DI alias. The DI alias is the name used to refer to
+the bundle in the container (e.g. in the ``config/packages/`` files). By
+default, this is done by removing the ``Extension`` suffix and converting the
+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
+copy. This container only has the parameters from the actual container. After
+loading the services and parameters, the copy will be merged into the actual
+container, to ensure all services and parameters are also added to the actual
+container.
+
+In the ``load()`` method, you can use PHP code to register service definitions,
+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
+``config/`` directory of your bundle, your ``load()`` method looks like::
+
+ use Symfony\Component\Config\FileLocator;
+ use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
+
+ // ...
+ public function load(array $configs, ContainerBuilder $container): void
+ {
+ $loader = new XmlFileLoader(
+ $container,
+ new FileLocator(__DIR__.'/../../config')
+ );
+ $loader->load('services.xml');
+ }
+
+The other available loaders are ``YamlFileLoader`` and ``PhpFileLoader``.
+
+Using Configuration to Change the Services
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The Extension is also the class that handles the configuration for that
+particular bundle (e.g. the configuration in ``config/packages/.yaml``).
+To read more about it, see the ":doc:`/bundles/configuration`" article.
+
+Adding Classes to Compile
+-------------------------
+
+Bundles can hint Symfony about which of their classes contain annotations so
+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::
+
+ public function load(array $configs, ContainerBuilder $container): void
+ {
+ // ...
+
+ $this->addAnnotatedClassesToCompile([
+ // you can define the fully qualified class names...
+ 'Acme\\BlogBundle\\Controller\\AuthorController',
+ // ... but glob patterns are also supported:
+ 'Acme\\BlogBundle\\Form\\**',
+
+ // ...
+ ]);
+ }
+
+.. note::
+
+ If some class extends from other classes, all its parents are automatically
+ included in the list of classes to compile.
+
+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.
+
+.. 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
+ these classes from the ``classes.php`` file.
diff --git a/bundles/index.rst b/bundles/index.rst
index d8f1298f5d8..58bcd13761e 100644
--- a/bundles/index.rst
+++ b/bundles/index.rst
@@ -1,13 +1,11 @@
-The Symfony Standard Edition Bundles
-====================================
+Bundles
+=======
.. toctree::
- :hidden:
+ :maxdepth: 2
- SensioFrameworkExtraBundle/index
- SensioGeneratorBundle/index
- DoctrineFixturesBundle/index
- DoctrineMigrationsBundle/index
- DoctrineMongoDBBundle/index
-
-.. include:: /bundles/map.rst.inc
+ override
+ best_practices
+ configuration
+ extension
+ prepend_extension
diff --git a/bundles/map.rst.inc b/bundles/map.rst.inc
deleted file mode 100644
index 44424cc6a1d..00000000000
--- a/bundles/map.rst.inc
+++ /dev/null
@@ -1,10 +0,0 @@
-* :doc:`SensioFrameworkExtraBundle `
-* :doc:`SensioGeneratorBundle `
-* `JMSSecurityExtraBundle`_
-* `JMSDiExtraBundle`_
-* :doc:`DoctrineFixturesBundle `
-* :doc:`DoctrineMigrationsBundle `
-* :doc:`DoctrineMongoDBBundle `
-
-.. _`JMSSecurityExtraBundle`: http://jmsyst.com/bundles/JMSSecurityExtraBundle/1.2
-.. _`JMSDiExtraBundle`: http://jmsyst.com/bundles/JMSDiExtraBundle/1.1
diff --git a/bundles/override.rst b/bundles/override.rst
new file mode 100644
index 00000000000..f25bd785373
--- /dev/null
+++ b/bundles/override.rst
@@ -0,0 +1,168 @@
+How to Override any Part of a Bundle
+====================================
+
+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.
+
+.. _override-templates:
+
+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 ``/templates/``) as
+the original templates.
+
+For example, to override the ``templates/registration/confirmed.html.twig``
+template from the AcmeUserBundle, create this template:
+``/templates/bundles/AcmeUserBundle/registration/confirmed.html.twig``
+
+.. 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.
+
+Instead of overriding an entire template, you may just want to override one or
+more blocks. However, since you are overriding the template you want to extend
+from, you would end up in an infinite loop error. The solution is to use the
+special ``!`` prefix in the template name to tell Symfony that you want to
+extend from the original template, not from the overridden one:
+
+.. code-block:: twig
+
+ {# templates/bundles/AcmeUserBundle/registration/confirmed.html.twig #}
+ {# the special '!' prefix avoids errors when extending from an overridden template #}
+ {% extends "@!AcmeUser/registration/confirmed.html.twig" %}
+
+ {% block some_block %}
+ ...
+ {% endblock %}
+
+.. _templating-overriding-core-templates:
+
+.. tip::
+
+ Symfony internals use some bundles too, so you can apply the same technique
+ to override the core Symfony templates. For example, you can
+ :doc:`customize error pages ` overriding TwigBundle
+ templates.
+
+Routing
+-------
+
+Routing is never automatically imported in Symfony. If you want to include
+the routes from any bundle, then they must be manually imported from somewhere
+in your application (e.g. ``config/routes.yaml``).
+
+The easiest way to "override" a bundle's routing is to never import it at
+all. Instead of importing a third-party bundle's routing, copy
+that routing file into your application, modify it, and import it instead.
+
+Controllers
+-----------
+
+If the controller is a service, see the next section on how to override it.
+Otherwise, define a new route + controller with the same path associated to the
+controller you want to override (and make sure that the new route is loaded
+before the bundle one).
+
+Services & Configuration
+------------------------
+
+If you want to modify the services created by a bundle, you can use
+:doc:`service decoration `.
+
+If you want to do more advanced manipulations, like removing services created by
+other bundles, you must work with :doc:`service definitions `
+inside a :doc:`compiler pass `.
+
+Entities & Entity Mapping
+-------------------------
+
+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
+-----
+
+Existing form types can be modified defining
+:doc:`form type extensions `.
+
+.. _override-validation:
+
+Validation Metadata
+-------------------
+
+Symfony loads all validation configuration files from every bundle and
+combines them into one validation metadata tree. This means you are able to
+add new constraints to a property, but you cannot override them.
+
+To overcome this, the 3rd party bundle needs to have configuration for
+:doc:`validation groups `. For instance, the FOSUserBundle
+has this configuration. To create your own validation, add the constraints
+to a new validation group:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/validator/validation.yaml
+ FOS\UserBundle\Model\User:
+ properties:
+ plainPassword:
+ - NotBlank:
+ groups: [AcmeValidation]
+ - Length:
+ min: 6
+ minMessage: fos_user.password.short
+ groups: [AcmeValidation]
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Now, update the FOSUserBundle configuration, so it uses your validation groups
+instead of the original ones.
+
+.. _override-translations:
+
+Translations
+------------
+
+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
+``translations/AcmeUserBundle.es.yaml`` file of the AcmeUserBundle,
+create a ``/translations/AcmeUserBundle.es.yaml`` file.
+
+.. _`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
new file mode 100644
index 00000000000..e4099d9f81a
--- /dev/null
+++ b/bundles/prepend_extension.rst
@@ -0,0 +1,223 @@
+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 remove unused functionality. Creating multiple
+bundles has the drawback that configuration becomes more tedious and settings
+often need to be repeated for various bundles.
+
+It is possible to remove the disadvantage of the multiple bundle approach by
+enabling a single Extension to prepend the settings for any bundle. It can use
+the settings defined in the ``config/*`` files to prepend settings just as if
+they had been written explicitly by the user in the application configuration.
+
+For example, this could be used to configure the entity manager name to use in
+multiple bundles. Or it can be used to enable an optional feature that depends
+on another bundle being loaded as well.
+
+To give an Extension the power to do this, it needs to implement
+:class:`Symfony\\Component\\DependencyInjection\\Extension\\PrependExtensionInterface`::
+
+ // src/Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php
+ namespace Acme\HelloBundle\DependencyInjection;
+
+ 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): void
+ {
+ // ...
+ }
+ }
+
+Inside the :method:`Symfony\\Component\\DependencyInjection\\Extension\\PrependExtensionInterface::prepend`
+method, developers have full access to the :class:`Symfony\\Component\\DependencyInjection\\ContainerBuilder`
+instance just before the :method:`Symfony\\Component\\DependencyInjection\\Extension\\ExtensionInterface::load`
+method is called on each of the registered bundle Extensions. In order to
+prepend settings to a bundle extension developers can use the
+:method:`Symfony\\Component\\DependencyInjection\\ContainerBuilder::prependExtensionConfig`
+method on the :class:`Symfony\\Component\\DependencyInjection\\ContainerBuilder`
+instance. As this method only prepends settings, any other settings done explicitly
+inside the ``config/*`` files would override these prepended settings.
+
+The following example illustrates how to prepend
+a configuration setting in multiple bundles as well as disable a flag in multiple bundles
+in case a specific other bundle is not registered::
+
+ // src/Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php
+ public function prepend(ContainerBuilder $container): void
+ {
+ // get all bundles
+ $bundles = $container->getParameter('kernel.bundles');
+ // determine if AcmeGoodbyeBundle is registered
+ if (!isset($bundles['AcmeGoodbyeBundle'])) {
+ // disable AcmeGoodbyeBundle in bundles
+ $config = ['use_acme_goodbye' => false];
+ foreach ($container->getExtensions() as $name => $extension) {
+ 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
+ };
+ }
+ }
+
+ // get the configuration of AcmeHelloExtension (it's a list of configuration)
+ $configs = $container->getExtensionConfig($this->getAlias());
+
+ // 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'],
+ ]);
+ }
+ }
+ }
+
+The above would be the equivalent of writing the following into the
+``config/packages/acme_something.yaml`` in case AcmeGoodbyeBundle is not
+registered and the ``entity_manager_name`` setting for ``acme_hello`` is set to
+``non_default``:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/acme_something.yaml
+ acme_something:
+ # ...
+ use_acme_goodbye: false
+ entity_manager_name: non_default
+
+ acme_other:
+ # ...
+ use_acme_goodbye: false
+
+ .. code-block:: xml
+
+
+
+
+
+
+ non_default
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/acme_something.php
+ 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
+ {
+ // ...
+
+ $containerConfigurator->extension('framework', [
+ 'cache' => ['prefix_seed' => 'foo/bar'],
+ ], prepend: true);
+
+ // ...
+ }
+ }
+
+.. 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
+----------------------------------------------------
+
+If there is more than one bundle that prepends the same extension and defines
+the same key, the bundle that is registered **first** will take priority:
+next bundles won't override this specific config setting.
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
new file mode 100644
index 00000000000..d6d3f485859
--- /dev/null
+++ b/components/asset.rst
@@ -0,0 +1,432 @@
+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 hard-code the URLs of web assets.
+For example:
+
+.. code-block:: html
+
+
+
+
+
+
+
+This practice is no longer recommended unless the web application is extremely
+simple. Hardcoding URLs can be a disadvantage because:
+
+* **Templates get verbose**: you have to write the full path for each
+ asset. When using the Asset component, you can group assets in packages to
+ avoid repeating the common part of their path;
+* **Versioning is difficult**: it has to be custom managed for each
+ application. Adding a version (e.g. ``main.css?v=5``) to the asset URLs
+ 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
+ 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;
+* **It's nearly impossible to use multiple CDNs**: this technique requires
+ you to change the URL of the asset randomly for each request. The Asset component
+ provides out-of-the-box support for any number of multiple CDNs, both regular
+ (``http://``) and secure (``https://``).
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/asset
+
+.. include:: /components/require_autoload.rst.inc
+
+Usage
+-----
+
+.. _asset-packages:
+
+Asset Packages
+~~~~~~~~~~~~~~
+
+The Asset component manages assets through packages. A package groups all the
+assets which share the same properties: versioning strategy, base path, CDN hosts,
+etc. In the following basic example, a package is created to manage assets without
+any versioning::
+
+ use Symfony\Component\Asset\Package;
+ use Symfony\Component\Asset\VersionStrategy\EmptyVersionStrategy;
+
+ $package = new Package(new EmptyVersionStrategy());
+
+ // Absolute path
+ echo $package->getUrl('/image.png');
+ // result: /image.png
+
+ // Relative path
+ echo $package->getUrl('image.png');
+ // result: image.png
+
+Packages implement :class:`Symfony\\Component\\Asset\\PackageInterface`,
+which defines the following two methods:
+
+:method:`Symfony\\Component\\Asset\\PackageInterface::getVersion`
+ Returns the asset version for an asset.
+
+:method:`Symfony\\Component\\Asset\\PackageInterface::getUrl`
+ Returns an absolute or root-relative public path.
+
+With a package, you can:
+
+A) :ref:`version the assets `;
+B) set a :ref:`common base path ` (e.g. ``/css``)
+ for the assets;
+C) :ref:`configure a CDN ` for the assets
+
+.. _component-assets-versioning:
+
+Versioned Assets
+~~~~~~~~~~~~~~~~
+
+One of the main features of the Asset component is the ability to manage
+the versioning of the application's assets. Asset versions are commonly used
+to control how these assets are cached.
+
+Instead of relying on a simple version mechanism, the Asset component allows
+you to define advanced versioning strategies via PHP classes. The two built-in
+strategies are the :class:`Symfony\\Component\\Asset\\VersionStrategy\\EmptyVersionStrategy`,
+which doesn't add any version to the asset and :class:`Symfony\\Component\\Asset\\VersionStrategy\\StaticVersionStrategy`,
+which allows you to set the version with a format string.
+
+In this example, the ``StaticVersionStrategy`` is used to append the ``v1``
+suffix to any asset path::
+
+ use Symfony\Component\Asset\Package;
+ use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy;
+
+ $package = new Package(new StaticVersionStrategy('v1'));
+
+ // Absolute path
+ echo $package->getUrl('/image.png');
+ // result: /image.png?v1
+
+ // Relative 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::
+
+ // puts the 'version' word before the version value
+ $package = new Package(new StaticVersionStrategy('v1', '%s?version=%s'));
+
+ echo $package->getUrl('/image.png');
+ // result: /image.png?version=v1
+
+ // puts the asset version before its path
+ $package = new Package(new StaticVersionStrategy('v1', '%2$s/%1$s'));
+
+ echo $package->getUrl('/image.png');
+ // result: /v1/image.png
+
+ echo $package->getUrl('image.png');
+ // result: v1/image.png
+
+JSON File Manifest
+..................
+
+A popular strategy to manage asset versioning, which is used by tools such as
+`Webpack`_, is to generate a JSON file mapping all source file names to their
+corresponding output file:
+
+.. code-block:: json
+
+ {
+ "css/app.css": "build/css/app.b916426ea1d10021f3f17ce8031f93c2.css",
+ "js/app.js": "build/js/app.13630905267b809161e71d0f8a0c017b.js",
+ "...": "..."
+ }
+
+In those cases, use the
+:class:`Symfony\\Component\\Asset\\VersionStrategy\\JsonManifestVersionStrategy`::
+
+ 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
+.........................
+
+Use the :class:`Symfony\\Component\\Asset\\VersionStrategy\\VersionStrategyInterface`
+to define your own versioning strategy. For example, your application may need
+to append the current date to all its web assets in order to bust the cache
+every day::
+
+ use Symfony\Component\Asset\VersionStrategy\VersionStrategyInterface;
+
+ class DateVersionStrategy implements VersionStrategyInterface
+ {
+ private string $version;
+
+ public function __construct()
+ {
+ $this->version = date('Ymd');
+ }
+
+ public function getVersion(string $path): string
+ {
+ return $this->version;
+ }
+
+ public function applyVersion(string $path): string
+ {
+ return sprintf('%s?v=%s', $path, $this->getVersion($path));
+ }
+ }
+
+.. _component-assets-path-package:
+
+Grouped Assets
+~~~~~~~~~~~~~~
+
+Often, many assets live under a common path (e.g. ``/static/images``). If
+that's your case, replace the default :class:`Symfony\\Component\\Asset\\Package`
+class with :class:`Symfony\\Component\\Asset\\PathPackage` to avoid repeating
+that path over and over again::
+
+ use Symfony\Component\Asset\PathPackage;
+ // ...
+
+ $pathPackage = new PathPackage('/static/images', new StaticVersionStrategy('v1'));
+
+ echo $pathPackage->getUrl('logo.png');
+ // result: /static/images/logo.png?v1
+
+ // Base path is ignored when using absolute paths
+ echo $pathPackage->getUrl('/logo.png');
+ // result: /logo.png?v1
+
+Request Context Aware Assets
+............................
+
+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\Context\RequestStackContext;
+ use Symfony\Component\Asset\PathPackage;
+ // ...
+
+ $pathPackage = new PathPackage(
+ '/static/images',
+ new StaticVersionStrategy('v1'),
+ new RequestStackContext($requestStack)
+ );
+
+ echo $pathPackage->getUrl('logo.png');
+ // result: /somewhere/static/images/logo.png?v1
+
+ // Both "base path" and "base url" are ignored when using absolute path for asset
+ echo $pathPackage->getUrl('/logo.png');
+ // result: /logo.png?v1
+
+Now that the request context is set, the ``PathPackage`` will prepend the
+current request base URL. So, for example, if your entire site is hosted under
+the ``/somewhere`` directory of your web server root directory and the configured
+base path is ``/static/images``, all paths will be prefixed with
+``/somewhere/static/images``.
+
+.. _component-assets-cdn:
+
+Absolute Assets and CDNs
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Applications that host their assets on different domains and CDNs (*Content
+Delivery Networks*) should use the :class:`Symfony\\Component\\Asset\\UrlPackage`
+class to generate absolute URLs for their assets::
+
+ use Symfony\Component\Asset\UrlPackage;
+ // ...
+
+ $urlPackage = new UrlPackage(
+ 'https://static.example.com/images/',
+ new StaticVersionStrategy('v1')
+ );
+
+ echo $urlPackage->getUrl('/logo.png');
+ // result: https://static.example.com/images/logo.png?v1
+
+You can also pass a schema-agnostic URL::
+
+ use Symfony\Component\Asset\UrlPackage;
+ // ...
+
+ $urlPackage = new UrlPackage(
+ '//static.example.com/images/',
+ new StaticVersionStrategy('v1')
+ );
+
+ echo $urlPackage->getUrl('/logo.png');
+ // result: //static.example.com/images/logo.png?v1
+
+This is useful because assets will automatically be requested via HTTPS if
+a visitor is viewing your site in https. If you want to use this, make sure
+that your CDN host supports HTTPS.
+
+In case you serve assets from more than one domain to improve application
+performance, pass an array of URLs as the first argument to the ``UrlPackage``
+constructor::
+
+ use Symfony\Component\Asset\UrlPackage;
+ // ...
+
+ $urls = [
+ 'https://static1.example.com/images/',
+ 'https://static2.example.com/images/',
+ ];
+ $urlPackage = new UrlPackage($urls, new StaticVersionStrategy('v1'));
+
+ echo $urlPackage->getUrl('/logo.png');
+ // result: https://static1.example.com/images/logo.png?v1
+ echo $urlPackage->getUrl('/icon.png');
+ // result: https://static2.example.com/images/icon.png?v1
+
+For each asset, one of the URLs will be randomly used. But, the selection
+is deterministic, meaning that each asset will always be served by the same
+domain. This behavior simplifies the management of HTTP cache.
+
+Request Context Aware Assets
+............................
+
+Similarly to application-relative assets, absolute assets can also take into
+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\Context\RequestStackContext;
+ use Symfony\Component\Asset\UrlPackage;
+ // ...
+
+ $urlPackage = new UrlPackage(
+ ['http://example.com/', 'https://example.com/'],
+ new StaticVersionStrategy('v1'),
+ new RequestStackContext($requestStack)
+ );
+
+ echo $urlPackage->getUrl('/logo.png');
+ // assuming the RequestStackContext says that we are on a secure host
+ // result: https://example.com/logo.png?v1
+
+Named Packages
+~~~~~~~~~~~~~~
+
+Applications that manage lots of different assets may need to group them in
+packages with the same versioning strategy and base path. The Asset component
+includes a :class:`Symfony\\Component\\Asset\\Packages` class to simplify
+management of several packages.
+
+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;
+ // ...
+
+ $versionStrategy = new StaticVersionStrategy('v1');
+
+ $defaultPackage = new Package($versionStrategy);
+
+ $namedPackages = [
+ '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 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::
+
+ echo $packages->getUrl('/main.css');
+ // result: /main.css?v1
+
+ echo $packages->getUrl('/logo.png', 'img');
+ // result: https://img.example.com/logo.png?v1
+
+ echo $packages->getUrl('resume.pdf', 'doc');
+ // result: /somewhere/deep/for/documents/resume.pdf?v1
+
+Local Files and Other Protocols
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In addition to HTTP this component supports other protocols (such as ``file://``
+and ``ftp://``). This allows for example to serve local files in order to
+improve performance::
+
+ use Symfony\Component\Asset\UrlPackage;
+ // ...
+
+ $localPackage = new UrlPackage(
+ 'file:///path/to/images/',
+ new EmptyVersionStrategy()
+ );
+
+ $ftpPackage = new UrlPackage(
+ 'ftp://example.com/images/',
+ new EmptyVersionStrategy()
+ );
+
+ echo $localPackage->getUrl('/logo.png');
+ // result: file:///path/to/images/logo.png
+
+ echo $ftpPackage->getUrl('/logo.png');
+ // result: ftp://example.com/images/logo.png
+
+Learn more
+----------
+
+* :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
new file mode 100644
index 00000000000..bcb8f7b3c8e
--- /dev/null
+++ b/components/browser_kit.rst
@@ -0,0 +1,409 @@
+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.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/browser-kit
+
+.. include:: /components/require_autoload.rst.inc
+
+Basic Usage
+-----------
+
+.. seealso::
+
+ This article explains how to use the BrowserKit features as an independent
+ component in any PHP application. Read the :ref:`Symfony Functional Tests `
+ article to learn about how to use it in Symfony applications.
+
+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
+``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\AbstractBrowser;
+ use Symfony\Component\BrowserKit\Response;
+
+ class Client extends AbstractBrowser
+ {
+ protected function doRequest($request): Response
+ {
+ // ... convert request into a response
+
+ return new Response($content, $status, $headers);
+ }
+ }
+
+For a simple implementation of a browser based on the HTTP layer, have a look
+at the :class:`Symfony\\Component\\BrowserKit\\HttpBrowser` provided by
+:ref:`this component `. For an implementation based
+on ``HttpKernelInterface``, have a look at the :class:`Symfony\\Component\\HttpKernel\\HttpClientKernel`
+provided by the :doc:`HttpKernel component `.
+
+Making Requests
+~~~~~~~~~~~~~~~
+
+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::
+
+ use Acme\Client;
+
+ $client = new Client();
+ $crawler = $client->request('GET', '/');
+
+The value returned by the ``request()`` method is an instance of the
+:class:`Symfony\\Component\\DomCrawler\\Crawler` class, provided by the
+:doc:`DomCrawler component `, which allows accessing
+and traversing HTML elements programmatically.
+
+The :method:`Symfony\\Component\\BrowserKit\\AbstractBrowser::jsonRequest` method,
+which defines the same arguments as the ``request()`` method, is a shortcut to
+convert the request parameters into a JSON string and set the needed HTTP headers::
+
+ use Acme\Client;
+
+ $client = new Client();
+ // this encodes parameters as JSON and sets the required CONTENT_TYPE and HTTP_ACCEPT headers
+ $crawler = $client->jsonRequest('GET', '/', ['some_parameter' => 'some_value']);
+
+The :method:`Symfony\\Component\\BrowserKit\\AbstractBrowser::xmlHttpRequest` method,
+which defines the same arguments as the ``request()`` method, is a shortcut to
+make AJAX requests::
+
+ use Acme\Client;
+
+ $client = new Client();
+ // the required HTTP_X_REQUESTED_WITH header is added automatically
+ $crawler = $client->xmlHttpRequest('GET', '/');
+
+Clicking Links
+~~~~~~~~~~~~~~
+
+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::
+
+ use Acme\Client;
+
+ $client = new Client();
+ $client->request('GET', '/product/123');
+
+ $crawler = $client->clickLink('Go elsewhere...');
+
+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::
+
+ // ...
+ $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 ``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::
+
+ use Acme\Client;
+
+ $client = new Client();
+ $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 `.
+
+ This array of information is stored in the :class:`Symfony\\Component\\HttpFoundation\\Request`
+ object's ``attributes`` array. Adding the routing information here doesn't
+ do anything yet, but is used next when resolving the controller.
+
+.. _component-http-kernel-resolve-controller:
+
+2) Resolve the Controller
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Assuming that no ``kernel.request`` listener was able to create a ``Response``,
+the next step in HttpKernel is to determine and prepare (i.e. resolve) the
+controller. The controller is the part of the end-application's code that
+is responsible for creating and returning the ``Response`` for a specific page.
+The only requirement is that it is a PHP callable - i.e. a function, method
+on an object or a ``Closure``.
+
+But *how* you determine the exact controller for a request is entirely up
+to your application. This is the job of the "controller resolver" - a class
+that implements :class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface`
+and is one of the constructor arguments to ``HttpKernel``.
+
+Your job is to create a class that implements the interface and fill in its
+method: ``getController()``. In fact, one default implementation already
+exists, which you can use directly or learn from:
+:class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver`.
+This implementation is explained more in the sidebar below::
+
+ namespace Symfony\Component\HttpKernel\Controller;
+
+ use Symfony\Component\HttpFoundation\Request;
+
+ interface ControllerResolverInterface
+ {
+ public function getController(Request $request): callable|false;
+ }
+
+Internally, the ``HttpKernel::handle()`` method first calls
+:method:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface::getController`
+on the controller resolver. This method is passed the ``Request`` and is responsible
+for somehow determining and returning a PHP callable (the controller) based
+on the request's information.
+
+.. sidebar:: Resolving the Controller in the Symfony Framework
+
+ The Symfony Framework uses the built-in
+ :class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver`
+ class (actually, it uses a subclass with some extra functionality
+ mentioned below). This class leverages the information that was placed
+ on the ``Request`` object's ``attributes`` property during the ``RouterListener``.
+
+ **getController**
+
+ The ``ControllerResolver`` looks for a ``_controller``
+ key on the ``Request`` object's attributes property (recall that this
+ information is typically placed on the ``Request`` via the ``RouterListener``).
+ This string is then transformed into a PHP callable by doing the following:
+
+ a) If the ``_controller`` key doesn't follow the recommended PHP namespace
+ format (e.g. ``App\Controller\DefaultController::index``) its format is
+ transformed into it. For example, the legacy ``FooBundle:Default:index``
+ format would be changed to ``Acme\FooBundle\Controller\DefaultController::indexAction``.
+ This transformation is specific to the :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerResolver`
+ sub-class used by the Symfony Framework.
+
+ b) A new instance of your controller class is instantiated with no
+ constructor arguments.
+
+.. _component-http-kernel-kernel-controller:
+
+3) The ``kernel.controller`` Event
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**Typical Purposes**: Initialize things or change the controller just before
+the controller is executed.
+
+:ref:`Kernel Events Information Table `
+
+After the controller callable has been determined, ``HttpKernel::handle()``
+dispatches the ``kernel.controller`` event. Listeners to this event might initialize
+some part of the system that needs to be initialized after certain things
+have been determined (e.g. the controller, routing information) but before
+the controller is executed.
+
+Another typical use-case for this event is to retrieve the attributes from
+the controller using the :method:`Symfony\\Component\\HttpKernel\\Event\\ControllerEvent::getAttributes`
+method. See the Symfony section below for some examples.
+
+Listeners to this event can also change the controller callable completely
+by calling :method:`ControllerEvent::setController `
+on the event object that's passed to listeners on this event.
+
+.. sidebar:: ``kernel.controller`` in the Symfony Framework
+
+ An interesting listener to ``kernel.controller`` in the Symfony
+ Framework is :class:`Symfony\\Component\\HttpKernel\\EventListener\\CacheAttributeListener`.
+ This class fetches ``#[Cache]`` attribute configuration from the
+ controller and uses it to configure :doc:`HTTP caching `
+ on the response.
+
+ There are a few other minor listeners to the ``kernel.controller`` event in
+ the Symfony Framework that deal with collecting profiler data when the
+ profiler is enabled.
+
+4) Getting the Controller Arguments
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Next, ``HttpKernel::handle()`` calls
+:method:`ArgumentResolverInterface::getArguments() `.
+Remember that the controller returned in ``getController()`` is a callable.
+The purpose of ``getArguments()`` is to return the array of arguments that
+should be passed to that controller. Exactly how this is done is completely
+up to your design, though the built-in :class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver`
+is a good example.
+
+At this point the kernel has a PHP callable (the controller) and an array
+of arguments that should be passed when executing that callable.
+
+.. sidebar:: Getting the Controller Arguments in the Symfony Framework
+
+ Now that you know exactly what the controller callable (usually a method
+ inside a controller object) is, the ``ArgumentResolver`` uses `reflection`_
+ on the callable to return an array of the *names* of each of the arguments.
+ It then iterates over each of these arguments and uses the following tricks
+ to determine which value should be passed for each argument:
+
+ a) If the ``Request`` attributes bag contains a key that matches the name
+ of the argument, that value is used. For example, if the first argument
+ to a controller is ``$slug`` and there is a ``slug`` key in the ``Request``
+ ``attributes`` bag, that value is used (and typically this value came
+ from the ``RouterListener``).
+
+ b) If the argument in the controller is type-hinted with Symfony's
+ :class:`Symfony\\Component\\HttpFoundation\\Request` object, the
+ ``Request`` is passed in as the value.
+
+ c) If the function or method argument is `variadic`_ and the ``Request``
+ ``attributes`` bag contains an array for that argument, they will all be
+ available through the `variadic`_ argument.
+
+ This functionality is provided by resolvers implementing the
+ :class:`Symfony\\Component\\HttpKernel\\Controller\\ValueResolverInterface`.
+ There are four implementations which provide the default behavior of
+ Symfony but customization is the key here. By implementing the
+ ``ValueResolverInterface`` yourself and passing this to the
+ ``ArgumentResolver``, you can extend this functionality.
+
+.. _component-http-kernel-calling-controller:
+
+5) Calling the Controller
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The next step of ``HttpKernel::handle()`` is executing the controller.
+
+The job of the controller is to build the response for the given resource.
+This could be an HTML page, a JSON string or anything else. Unlike every
+other part of the process so far, this step is implemented by the "end-developer",
+for each page that is built.
+
+Usually, the controller will return a ``Response`` object. If this is true,
+then the work of the kernel is just about done! In this case, the next step
+is the :ref:`kernel.response ` event.
+
+But if the controller returns anything besides a ``Response``, then the kernel
+has a little bit more work to do - :ref:`kernel.view `
+(since the end goal is *always* to generate a ``Response`` object).
+
+.. note::
+
+ A controller must return *something*. If a controller returns ``null``,
+ an exception will be thrown immediately.
+
+.. _component-http-kernel-kernel-view:
+
+6) The ``kernel.view`` Event
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**Typical Purposes**: Transform a non-``Response`` return value from a controller
+into a ``Response``
+
+:ref:`Kernel Events Information Table `
+
+If the controller doesn't return a ``Response`` object, then the kernel dispatches
+another event - ``kernel.view``. The job of a listener to this event is to
+use the return value of the controller (e.g. an array of data or an object)
+to create a ``Response``.
+
+This can be useful if you want to use a "view" layer: instead of returning
+a ``Response`` from the controller, you return data that represents the page.
+A listener to this event could then use this data to create a ``Response`` that
+is in the correct format (e.g HTML, JSON, etc).
+
+At this stage, if no listener sets a response on the event, then an exception
+is thrown: either the controller *or* one of the view listeners must always
+return a ``Response``.
+
+.. note::
+
+ When setting a response for the ``kernel.view`` event, the propagation
+ is stopped. This means listeners with lower priority won't be executed.
+
+.. sidebar:: ``kernel.view`` in the Symfony Framework
+
+ There is a default listener inside the Symfony Framework for the ``kernel.view``
+ event. If your controller action returns an array, and you apply the
+ :ref:`#[Template] attribute ` to that
+ controller action, then this listener renders a template, passes the array
+ you returned from your controller to that template, and creates a ``Response``
+ containing the returned content from that template.
+
+ Additionally, a popular community bundle `FOSRestBundle`_ implements
+ a listener on this event which aims to give you a robust view layer
+ capable of using a single controller to return many different content-type
+ responses (e.g. HTML, JSON, XML, etc).
+
+.. _component-http-kernel-kernel-response:
+
+7) The ``kernel.response`` Event
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**Typical Purposes**: Modify the ``Response`` object just before it is sent
+
+:ref:`Kernel Events Information Table `
+
+The end goal of the kernel is to transform a ``Request`` into a ``Response``. The
+``Response`` might be created during the :ref:`kernel.request `
+event, returned from the :ref:`controller `,
+or returned by one of the listeners to the :ref:`kernel.view `
+event.
+
+Regardless of who creates the ``Response``, another event - ``kernel.response``
+is dispatched directly afterwards. A typical listener to this event will modify
+the ``Response`` object in some way, such as modifying headers, adding cookies,
+or even changing the content of the ``Response`` itself (e.g. injecting some
+JavaScript before the end ```` tag of an HTML response).
+
+After this event is dispatched, the final ``Response`` object is returned
+from :method:`Symfony\\Component\\HttpKernel\\HttpKernel::handle`. In the
+most typical use-case, you can then call the :method:`Symfony\\Component\\HttpFoundation\\Response::send`
+method, which sends the headers and prints the ``Response`` content.
+
+.. sidebar:: ``kernel.response`` in the Symfony Framework
+
+ There are several minor listeners on this event inside the Symfony Framework,
+ and most modify the response in some way. For example, the
+ :class:`Symfony\\Bundle\\WebProfilerBundle\\EventListener\\WebDebugToolbarListener`
+ injects some JavaScript at the bottom of your page in the ``dev`` environment
+ which causes the web debug toolbar to be displayed. Another listener,
+ :class:`Symfony\\Component\\Security\\Http\\Firewall\\ContextListener`
+ serializes the current user's information into the
+ session so that it can be reloaded on the next request.
+
+.. _component-http-kernel-kernel-terminate:
+
+8) The ``kernel.terminate`` Event
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**Typical Purposes**: To perform some "heavy" action after the response has
+been streamed to the user
+
+:ref:`Kernel Events Information Table `
+
+The final event of the HttpKernel process is ``kernel.terminate`` and is unique
+because it occurs *after* the ``HttpKernel::handle()`` method, and after the
+response is sent to the user. Recall from above, then the code that uses
+the kernel, ends like this::
+
+ // sends the headers and echoes the content
+ $response->send();
+
+ // triggers the kernel.terminate event
+ $kernel->terminate($request, $response);
+
+As you can see, by calling ``$kernel->terminate`` after sending the response,
+you will trigger the ``kernel.terminate`` event where you can perform certain
+actions that you may have delayed in order to return the response as quickly
+as possible to the client (e.g. sending emails).
+
+.. warning::
+
+ Internally, the HttpKernel makes use of the :phpfunction:`fastcgi_finish_request`
+ PHP function. This means that at the moment, only the `PHP FPM`_ API and the
+ `FrankenPHP`_ server are able to send a response to the client while the server's PHP process
+ still performs some tasks. With all other server APIs, listeners to ``kernel.terminate``
+ are still executed, but the response is not sent to the client until they
+ are all completed.
+
+.. note::
+
+ Using the ``kernel.terminate`` event is optional, and should only be
+ called if your kernel implements :class:`Symfony\\Component\\HttpKernel\\TerminableInterface`.
+
+.. _component-http-kernel-kernel-exception:
+
+9) Handling Exceptions: the ``kernel.exception`` Event
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**Typical Purposes**: Handle some type of exception and create an appropriate
+``Response`` to return for the exception
+
+:ref:`Kernel Events Information Table `
+
+If an exception is thrown at any point inside ``HttpKernel::handle()``, another
+event - ``kernel.exception`` is dispatched. Internally, the body of the ``handle()``
+method is wrapped in a try-catch block. When any exception is thrown, the
+``kernel.exception`` event is dispatched so that your system can somehow respond
+to the exception.
+
+.. raw:: html
+
+
+
+Each listener to this event is passed a :class:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent`
+object, which you can use to access the original exception via the
+:method:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent::getThrowable`
+method. A typical listener on this event will check for a certain type of
+exception and create an appropriate error ``Response``.
+
+For example, to generate a 404 page, you might throw a special type of exception
+and then add a listener on this event that looks for this exception and
+creates and returns a 404 ``Response``. In fact, the HttpKernel component
+comes with an :class:`Symfony\\Component\\HttpKernel\\EventListener\\ErrorListener`,
+which if you choose to use, will do this and more by default (see the sidebar
+below for more details).
+
+The :class:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent` exposes the
+:method:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent::isKernelTerminating`
+method, which you can use to determine if the kernel is currently terminating
+at the moment the exception was thrown.
+
+.. versionadded:: 7.1
+
+ The
+ :method:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent::isKernelTerminating`
+ method was introduced in Symfony 7.1.
+
+.. note::
+
+ When setting a response for the ``kernel.exception`` event, the propagation
+ is stopped. This means listeners with lower priority won't be executed.
+
+.. sidebar:: ``kernel.exception`` in the Symfony Framework
+
+ There are two main listeners to ``kernel.exception`` when using the
+ Symfony Framework.
+
+ **ErrorListener in the HttpKernel Component**
+
+ The first comes core to the HttpKernel component
+ and is called :class:`Symfony\\Component\\HttpKernel\\EventListener\\ErrorListener`.
+ The listener has several goals:
+
+ 1) The thrown exception is converted into a
+ :class:`Symfony\\Component\\ErrorHandler\\Exception\\FlattenException`
+ object, which contains all the information about the request, but which
+ can be printed and serialized.
+
+ 2) If the original exception implements
+ :class:`Symfony\\Component\\HttpKernel\\Exception\\HttpExceptionInterface`,
+ then ``getStatusCode()`` and ``getHeaders()`` are called on the exception
+ and used to populate the headers and status code of the ``FlattenException``
+ object. The idea is that these are used in the next step when creating
+ the final response. If you want to set custom HTTP headers, you can always
+ use the ``setHeaders()`` method on exceptions derived from the
+ :class:`Symfony\\Component\\HttpKernel\\Exception\\HttpException` class.
+
+ 3) If the original exception implements
+ :class:`Symfony\\Component\\HttpFoundation\\Exception\\RequestExceptionInterface`,
+ then the status code of the ``FlattenException`` object is populated with
+ ``400`` and no other headers are modified.
+
+ 4) A controller is executed and passed the flattened exception. The exact
+ controller to render is passed as a constructor argument to this listener.
+ This controller will return the final ``Response`` for this error page.
+
+ **ExceptionListener in the Security Component**
+
+ The other important listener is the
+ :class:`Symfony\\Component\\Security\\Http\\Firewall\\ExceptionListener`.
+ The goal of this listener is to handle security exceptions and, when
+ appropriate, *help* the user to authenticate (e.g. redirect to the login
+ page).
+
+.. _http-kernel-creating-listener:
+
+Creating an Event Listener
+--------------------------
+
+As you've seen, you can create and attach event listeners to any of the events
+dispatched during the ``HttpKernel::handle()`` cycle. Typically a listener is a PHP
+class with a method that's executed, but it can be anything. For more information
+on creating and attaching event listeners, see :doc:`/components/event_dispatcher`.
+
+The name of each of the "kernel" events is defined as a constant on the
+:class:`Symfony\\Component\\HttpKernel\\KernelEvents` class. Additionally, each
+event listener is passed a single argument, which is some subclass of :class:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent`.
+This object contains information about the current state of the system and
+each event has their own event object:
+
+.. _component-http-kernel-event-table:
+
+=========================== ====================================== ========================================================================
+Name ``KernelEvents`` Constant Argument passed to the listener
+=========================== ====================================== ========================================================================
+kernel.request ``KernelEvents::REQUEST`` :class:`Symfony\\Component\\HttpKernel\\Event\\RequestEvent`
+kernel.controller ``KernelEvents::CONTROLLER`` :class:`Symfony\\Component\\HttpKernel\\Event\\ControllerEvent`
+kernel.controller_arguments ``KernelEvents::CONTROLLER_ARGUMENTS`` :class:`Symfony\\Component\\HttpKernel\\Event\\ControllerArgumentsEvent`
+kernel.view ``KernelEvents::VIEW`` :class:`Symfony\\Component\\HttpKernel\\Event\\ViewEvent`
+kernel.response ``KernelEvents::RESPONSE`` :class:`Symfony\\Component\\HttpKernel\\Event\\ResponseEvent`
+kernel.finish_request ``KernelEvents::FINISH_REQUEST`` :class:`Symfony\\Component\\HttpKernel\\Event\\FinishRequestEvent`
+kernel.terminate ``KernelEvents::TERMINATE`` :class:`Symfony\\Component\\HttpKernel\\Event\\TerminateEvent`
+kernel.exception ``KernelEvents::EXCEPTION`` :class:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent`
+=========================== ====================================== ========================================================================
+
+.. _http-kernel-working-example:
+
+A full Working Example
+----------------------
+
+When using the HttpKernel component, you're free to attach any listeners
+to the core events, use any controller resolver that implements the
+:class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface` and
+use any argument resolver that implements the
+:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolverInterface`.
+However, the HttpKernel component comes with some built-in listeners and everything
+else that can be used to create a working example::
+
+ use Symfony\Component\EventDispatcher\EventDispatcher;
+ use Symfony\Component\HttpFoundation\Request;
+ use Symfony\Component\HttpFoundation\RequestStack;
+ use Symfony\Component\HttpFoundation\Response;
+ use Symfony\Component\HttpKernel\Controller\ArgumentResolver;
+ use Symfony\Component\HttpKernel\Controller\ControllerResolver;
+ use Symfony\Component\HttpKernel\EventListener\RouterListener;
+ use Symfony\Component\HttpKernel\HttpKernel;
+ use Symfony\Component\Routing\Matcher\UrlMatcher;
+ use Symfony\Component\Routing\RequestContext;
+ use Symfony\Component\Routing\Route;
+ use Symfony\Component\Routing\RouteCollection;
+
+ $routes = new RouteCollection();
+ $routes->add('hello', new Route('/hello/{name}', [
+ '_controller' => function (Request $request): Response {
+ return new Response(
+ sprintf("Hello %s", $request->get('name'))
+ );
+ }]
+ ));
+
+ $request = Request::createFromGlobals();
+
+ $matcher = new UrlMatcher($routes, new RequestContext());
+
+ $dispatcher = new EventDispatcher();
+ $dispatcher->addSubscriber(new RouterListener($matcher, new RequestStack()));
+
+ $controllerResolver = new ControllerResolver();
+ $argumentResolver = new ArgumentResolver();
+
+ $kernel = new HttpKernel($dispatcher, $controllerResolver, new RequestStack(), $argumentResolver);
+
+ $response = $kernel->handle($request);
+ $response->send();
+
+ $kernel->terminate($request, $response);
+
+.. _http-kernel-sub-requests:
+
+Sub Requests
+------------
+
+In addition to the "main" request that's sent into ``HttpKernel::handle()``,
+you can also send a so-called "sub request". A sub request looks and acts like
+any other request, but typically serves to render just one small portion of
+a page instead of a full page. You'll most commonly make sub-requests from
+your controller (or perhaps from inside a template, that's being rendered by
+your controller).
+
+.. raw:: html
+
+
+
+To execute a sub request, use ``HttpKernel::handle()``, but change the second
+argument as follows::
+
+ use Symfony\Component\HttpFoundation\Request;
+ use Symfony\Component\HttpKernel\HttpKernelInterface;
+
+ // ...
+
+ // create some other request manually as needed
+ $request = new Request();
+ // for example, possibly set its _controller manually
+ $request->attributes->set('_controller', '...');
+
+ $response = $kernel->handle($request, HttpKernelInterface::SUB_REQUEST);
+ // do something with this response
+
+This creates another full request-response cycle where this new ``Request`` is
+transformed into a ``Response``. The only difference internally is that some
+listeners (e.g. security) may only act upon the main request. Each listener
+is passed some subclass of :class:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent`,
+whose :method:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent::isMainRequest`
+method can be used to check if the current request is a "main" or "sub" request.
+
+For example, a listener that only needs to act on the main request may
+look like this::
+
+ use Symfony\Component\HttpKernel\Event\RequestEvent;
+ // ...
+
+ public function onKernelRequest(RequestEvent $event): void
+ {
+ if (!$event->isMainRequest()) {
+ return;
+ }
+
+ // ...
+ }
+
+.. note::
+
+ The default value of the ``_format`` request attribute is ``html``. If your
+ sub request returns a different format (e.g. ``json``) you can set it by
+ defining the ``_format`` attribute explicitly on the request::
+
+ $request->attributes->set('_format', 'json');
+
+.. _http-kernel-resource-locator:
+
+Locating Resources
+------------------
+
+The HttpKernel component is responsible of the bundle mechanism used in Symfony
+applications. One of the key features of the bundles is that you can use logic
+paths instead of physical paths to refer to any of their resources (config files,
+templates, controllers, translation files, etc.)
+
+This allows to import resources even if you don't know where in the filesystem a
+bundle will be installed. For example, the ``services.xml`` file stored in the
+``Resources/config/`` directory of a bundle called FooBundle can be referenced as
+``@FooBundle/Resources/config/services.xml`` instead of ``__DIR__/Resources/config/services.xml``.
+
+This is possible thanks to the :method:`Symfony\\Component\\HttpKernel\\Kernel::locateResource`
+method provided by the kernel, which transforms logical paths into physical paths::
+
+ $path = $kernel->locateResource('@FooBundle/Resources/config/services.xml');
+
+Learn more
+----------
+
+.. toctree::
+ :maxdepth: 1
+ :glob:
+
+ /reference/events
+
+.. _reflection: https://www.php.net/manual/en/book.reflection.php
+.. _FOSRestBundle: https://github.com/friendsofsymfony/FOSRestBundle
+.. _`PHP FPM`: https://www.php.net/manual/en/install.fpm.php
+.. _variadic: https://www.php.net/manual/en/functions.arguments.php#functions.variable-arg-list
+.. _`FrankenPHP`: https://frankenphp.dev
diff --git a/components/http_kernel/index.rst b/components/http_kernel/index.rst
deleted file mode 100644
index 202549bc9bd..00000000000
--- a/components/http_kernel/index.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-HTTP Kernel
-===========
-
-.. toctree::
- :maxdepth: 2
-
- introduction
diff --git a/components/http_kernel/introduction.rst b/components/http_kernel/introduction.rst
deleted file mode 100644
index 309b20b6a42..00000000000
--- a/components/http_kernel/introduction.rst
+++ /dev/null
@@ -1,689 +0,0 @@
-.. index::
- single: HTTP
- single: HttpKernel
- single: Components; HttpKernel
-
-The HttpKernel Component
-========================
-
- The HttpKernel Component provides a structured process for converting
- a ``Request`` into a ``Response`` by making use of the event dispatcher.
- It's flexible enough to create a full-stack framework (Symfony), a micro-framework
- (Silex) or an advanced CMS system (Drupal).
-
-Installation
-------------
-
-You can install the component in many different ways:
-
-* Use the official Git repository (https://github.com/symfony/HttpKernel);
-* :doc:`Install it via Composer` (``symfony/http-kernel`` on Packagist_).
-
-The Workflow of a Request
--------------------------
-
-Every HTTP web interaction begins with a request and ends with a response.
-Your job as a developer is to create PHP code that reads the request information
-(e.g. the URL) and creates and returns a response (e.g. an HTML page or JSON string).
-
-.. image:: /images/components/http_kernel/request-response-flow.png
- :align: center
-
-Typically, some sort of framework or system is built to handle all the repetitive
-tasks (e.g. routing, security, etc) so that a developer can easily build
-each *page* of the application. Exactly *how* these systems are built varies
-greatly. The HttpKernel component provides an interface that formalizes
-the process of starting with a request and creating the appropriate response.
-The component is meant to be the heart of any application or framework, no
-matter how varied the architecture of that system::
-
- namespace Symfony\Component\HttpKernel;
-
- use Symfony\Component\HttpFoundation\Request;
-
- interface HttpKernelInterface
- {
- // ...
-
- /**
- * @return Response A Response instance
- */
- public function handle(
- Request $request,
- $type = self::MASTER_REQUEST,
- $catch = true
- );
- }
-
-Internally, :method:`HttpKernel::handle()` -
-the concrete implementation of :method:`HttpKernelInterface::handle()` -
-defines a workflow that starts with a :class:`Symfony\\Component\\HttpFoundation\\Request`
-and ends with a :class:`Symfony\\Component\\HttpFoundation\\Response`.
-
-.. image:: /images/components/http_kernel/01-workflow.png
- :align: center
-
-The exact details of this workflow are the key to understanding how the kernel
-(and the Symfony Framework or any other library that uses the kernel) works.
-
-HttpKernel: Driven by Events
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The ``HttpKernel::handle()`` method works internally by dispatching events.
-This makes the method both flexible, but also a bit abstract, since all the
-"work" of a framework/application built with HttpKernel is actually done
-in event listeners.
-
-To help explain this process, this document looks at each step of the process
-and talks about how one specific implementation of the HttpKernel - the Symfony
-Framework - works.
-
-Initially, using the :class:`Symfony\\Component\\HttpKernel\\HttpKernel`
-is really simple, and involves creating an :doc:`event dispatcher`
-and a :ref:`controller resolver`
-(explained below). To complete your working kernel, you'll add more event
-listeners to the events discussed below::
-
- use Symfony\Component\HttpFoundation\Request;
- use Symfony\Component\HttpKernel\HttpKernel;
- use Symfony\Component\EventDispatcher\EventDispatcher;
- use Symfony\Component\HttpKernel\Controller\ControllerResolver;
-
- // create the Request object
- $request = Request::createFromGlobals();
-
- $dispatcher = new EventDispatcher();
- // ... add some event listeners
-
- // create your controller resolver
- $resolver = new ControllerResolver();
- // instantiate the kernel
- $kernel = new HttpKernel($dispatcher, $resolver);
-
- // actually execute the kernel, which turns the request into a response
- // by dispatching events, calling a controller, and returning the response
- $response = $kernel->handle($request);
-
- // echo the content and send the headers
- $response->send();
-
- // triggers the kernel.terminate event
- $kernel->terminate($request, $response);
-
-See ":ref:`http-kernel-working-example`" for a more concrete implementation.
-
-For general information on adding listeners to the events below, see
-:ref:`http-kernel-creating-listener`.
-
-.. tip::
-
- Fabien Potencier also wrote a wonderful series on using the ``HttpKernel``
- component and other Symfony2 components to create your own framework. See
- `Create your own framework... on top of the Symfony2 Components`_.
-
-.. _component-http-kernel-kernel-request:
-
-1) The ``kernel.request`` event
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-**Typical Purposes**: To add more information to the ``Request``, initialize
-parts of the system, or return a ``Response`` if possible (e.g. a security
-layer that denies access)
-
-:ref:`Kernel Events Information Table`
-
-The first event that is dispatched inside :method:`HttpKernel::handle`
-is ``kernel.request``, which may have a variety of different listeners.
-
-.. image:: /images/components/http_kernel/02-kernel-request.png
- :align: center
-
-Listeners of this event can be quite varied. Some listeners - such as a security
-listener - might have enough information to create a ``Response`` object immediately.
-For example, if a security listener determined that a user doesn't have access,
-that listener may return a :class:`Symfony\\Component\\HttpFoundation\\RedirectResponse`
-to the login page or a 403 Access Denied response.
-
-If a ``Response`` is returned at this stage, the process skips directly to
-the :ref:`kernel.response` event.
-
-.. image:: /images/components/http_kernel/03-kernel-request-response.png
- :align: center
-
-Other listeners simply initialize things or add more information to the request.
-For example, a listener might determine and set the locale on the ``Request``
-object.
-
-Another common listener is routing. A router listener may process the ``Request``
-and determine the controller that should be rendered (see the next section).
-In fact, the ``Request`` object has an ":ref:`attributes`"
-bag which is a perfect spot to store this extra, application-specific data
-about the request. This means that if your router listener somehow determines
-the controller, it can store it on the ``Request`` attributes (which can be used
-by your controller resolver).
-
-Overall, the purpose of the ``kernel.request`` event is either to create and
-return a ``Response`` directly, or to add information to the ``Request``
-(e.g. setting the locale or setting some other information on the ``Request``
-attributes).
-
-.. sidebar:: ``kernel.request`` in the Symfony Framework
-
- The most important listener to ``kernel.request`` in the Symfony Framework
- is the :class:`Symfony\\Component\\HttpKernel\\EventListener\\RouterListener`.
- This class executes the routing layer, which returns an *array* of information
- about the matched request, including the ``_controller`` and any placeholders
- that are in the route's pattern (e.g. ``{slug}``). See
- :doc:`Routing Component`.
-
- This array of information is stored in the :class:`Symfony\\Component\\HttpFoundation\\Request`
- object's ``attributes`` array. Adding the routing information here doesn't
- do anything yet, but is used next when resolving the controller.
-
-.. _component-http-kernel-resolve-controller:
-
-2) Resolve the Controller
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Assuming that no ``kernel.request`` listener was able to create a ``Response``,
-the next step in HttpKernel is to determine and prepare (i.e. resolve) the
-controller. The controller is the part of the end-application's code that
-is responsible for creating and returning the ``Response`` for a specific page.
-The only requirement is that it is a PHP callable - i.e. a function, method
-on an object, or a ``Closure``.
-
-But *how* you determine the exact controller for a request is entirely up
-to your application. This is the job of the "controller resolver" - a class
-that implements :class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface`
-and is one of the constructor arguments to ``HttpKernel``.
-
-.. image:: /images/components/http_kernel/04-resolve-controller.png
- :align: center
-
-Your job is to create a class that implements the interface and fill in its
-two methods: ``getController`` and ``getArguments``. In fact, one default
-implementation already exists, which you can use directly or learn from:
-:class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver`.
-This implementation is explained more in the sidebar below::
-
- namespace Symfony\Component\HttpKernel\Controller;
-
- use Symfony\Component\HttpFoundation\Request;
-
- interface ControllerResolverInterface
- {
- public function getController(Request $request);
-
- public function getArguments(Request $request, $controller);
- }
-
-Internally, the ``HttpKernel::handle`` method first calls
-:method:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface::getController`
-on the controller resolver. This method is passed the ``Request`` and is responsible
-for somehow determining and returning a PHP callable (the controller) based
-on the request's information.
-
-The second method, :method:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface::getArguments`,
-will be called after another event - ``kernel.controller`` - is dispatched.
-
-.. sidebar:: Resolving the Controller in the Symfony2 Framework
-
- The Symfony Framework uses the built-in
- :class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver`
- class (actually, it uses a sub-class, which some extra functionality
- mentioned below). This class leverages the information that was placed
- on the ``Request`` object's ``attributes`` property during the ``RouterListener``.
-
- **getController**
-
- The ``ControllerResolver`` looks for a ``_controller``
- key on the ``Request`` object's attributes property (recall that this
- information is typically placed on the ``Request`` via the ``RouterListener``).
- This string is then transformed into a PHP callable by doing the following:
-
- a) The ``AcmeDemoBundle:Default:index`` format of the ``_controller`` key
- is changed to another string that contains the full class and method
- name of the controller by following the convention used in Symfony2 - e.g.
- ``Acme\DemoBundle\Controller\DefaultController::indexAction``. This transformation
- is specific to the :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerResolver`
- sub-class used by the Symfony2 Framework.
-
- b) A new instance of your controller class is instantiated with no
- constructor arguments.
-
- c) If the controller implements :class:`Symfony\\Component\\DependencyInjection\\ContainerAwareInterface`,
- ``setContainer`` is called on the controller object and the container
- is passed to it. This step is also specific to the :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerResolver`
- sub-class used by the Symfony2 Framework.
-
- There are also a few other variations on the above process (e.g. if
- you're registering your controllers as services).
-
-.. _component-http-kernel-kernel-controller:
-
-3) The ``kernel.controller`` event
-----------------------------------
-
-**Typical Purposes**: Initialize things or change the controller just before
-the controller is executed.
-
-:ref:`Kernel Events Information Table`
-
-After the controller callable has been determined, ``HttpKernel::handle``
-dispatches the ``kernel.controller`` event. Listeners to this event might initialize
-some part of the system that needs to be initialized after certain things
-have been determined (e.g. the controller, routing information) but before
-the controller is executed. For some examples, see the Symfony2 section below.
-
-.. image:: /images/components/http_kernel/06-kernel-controller.png
- :align: center
-
-Listeners to this event can also change the controller callable completely
-by calling :method:`FilterControllerEvent::setController`
-on the event object that's passed to listeners on this event.
-
-.. sidebar:: ``kernel.controller`` in the Symfony Framework
-
- There are a few minor listeners to the ``kernel.controller`` event in
- the Symfony Framework, and many deal with collecting profiler data when
- the profiler is enabled.
-
- One interesting listener comes from the :doc:`SensioFrameworkExtraBundle `,
- which is packaged with the Symfony Standard Edition. This listener's
- :doc:`@ParamConverter`
- functionality allows you to pass a full object (e.g. a ``Post`` object)
- to your controller instead of a scalar value (e.g. an ``id`` parameter
- that was on your route). The listener - ``ParamConverterListener`` - uses
- reflection to look at each of the arguments of the controller and tries
- to use different methods to convert those to objects, which are then
- stored in the ``attributes`` property of the ``Request`` object. Read the
- next section to see why this is important.
-
-4) Getting the Controller Arguments
------------------------------------
-
-Next, ``HttpKernel::handle`` calls
-:method:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface::getArguments`.
-Remember that the controller returned in ``getController`` is a callable.
-The purpose of ``getArguments`` is to return the array of arguments that
-should be passed to that controller. Exactly how this is done is completely
-up to your design, though the built-in :class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver`
-is a good example.
-
-.. image:: /images/components/http_kernel/07-controller-arguments.png
- :align: center
-
-At this point the kernel has a PHP callable (the controller) and an array
-of arguments that should be passed when executing that callable.
-
-.. sidebar:: Getting the Controller Arguments in the Symfony2 Framework
-
- Now that you know exactly what the controller callable (usually a method
- inside a controller object) is, the ``ControllerResolver`` uses `reflection`_
- on the callable to return an array of the *names* of each of the arguments.
- It then iterates over each of these arguments and uses the following tricks
- to determine which value should be passed for each argument:
-
- a) If the ``Request`` attributes bag contains a key that matches the name
- of the argument, that value is used. For example, if the first argument
- to a controller is ``$slug``, and there is a ``slug`` key in the ``Request``
- ``attributes`` bag, that value is used (and typically this value came
- from the ``RouterListener``).
-
- b) If the argument in the controller is type-hinted with Symfony's
- :class:`Symfony\\Component\\HttpFoundation\\Request` object, then the
- ``Request`` is passed in as the value.
-
-.. _component-http-kernel-calling-controller:
-
-5) Calling the Controller
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The next step is simple! ``HttpKernel::handle`` executes the controller.
-
-.. image:: /images/components/http_kernel/08-call-controller.png
- :align: center
-
-The job of the controller is to build the response for the given resource.
-This could be an HTML page, a JSON string or anything else. Unlike every
-other part of the process so far, this step is implemented by the "end-developer",
-for each page that is built.
-
-Usually, the controller will return a ``Response`` object. If this is true,
-then the work of the kernel is just about done! In this case, the next step
-is the :ref:`kernel.response` event.
-
-.. image:: /images/components/http_kernel/09-controller-returns-response.png
- :align: center
-
-But if the controller returns anything besides a ``Response``, then the kernel
-has a little bit more work to do - :ref:`kernel.view`
-(since the end goal is *always* to generate a ``Response`` object).
-
-.. note::
-
- A controller must return *something*. If a controller returns ``null``,
- an exception will be thrown immediately.
-
-.. _component-http-kernel-kernel-view:
-
-6) The ``kernel.view`` event
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-**Typical Purposes**: Transform a non-``Response`` return value from a controller
-into a ``Response``
-
-:ref:`Kernel Events Information Table`
-
-If the controller doesn't return a ``Response`` object, then the kernel dispatches
-another event - ``kernel.view``. The job of a listener to this event is to
-use the return value of the controller (e.g. an array of data or an object)
-to create a ``Response``.
-
-.. image:: /images/components/http_kernel/10-kernel-view.png
- :align: center
-
-This can be useful if you want to use a "view" layer: instead of returning
-a ``Response`` from the controller, you return data that represents the page.
-A listener to this event could then use this data to create a ``Response`` that
-is in the correct format (e.g HTML, json, etc).
-
-At this stage, if no listener sets a response on the event, then an exception
-is thrown: either the controller *or* one of the view listeners must always
-return a ``Response``.
-
-.. sidebar:: ``kernel.view`` in the Symfony Framework
-
- There is no default listener inside the Symfony Framework for the ``kernel.view``
- event. However, one core bundle -
- :doc:`SensioFrameworkExtraBundle ` -
- *does* add a listener to this event. If your controller returns an array,
- and you place the :doc:`@Template`
- annotation above the controller, then this listener renders a template,
- passes the array you returned from your controller to that template,
- and creates a ``Response`` containing the returned content from that
- template.
-
- Additionally, a popular community bundle `FOSRestBundle`_ implements
- a listener on this event which aims to give you a robust view layer
- capable of using a single controller to return many different content-type
- responses (e.g. HTML, JSON, XML, etc).
-
-.. _component-http-kernel-kernel-response:
-
-7) The ``kernel.response`` event
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-**Typical Purposes**: Modify the ``Response`` object just before it is sent
-
-:ref:`Kernel Events Information Table`
-
-The end goal of the kernel is to transform a ``Request`` into a ``Response``. The
-``Response`` might be created during the :ref:`kernel.request`
-event, returned from the :ref:`controller`,
-or returned by one of the listeners to the :ref:`kernel.view`
-event.
-
-Regardless of who creates the ``Response``, another event - ``kernel.response``
-is dispatched directly afterwards. A typical listener to this event will modify
-the ``Response`` object in some way, such as modifying headers, adding cookies,
-or even changing the content of the ``Response`` itself (e.g. injecting some
-JavaScript before the end ```` tag of an HTML response).
-
-After this event is dispatched, the final ``Response`` object is returned
-from :method:`Symfony\\Component\\HttpKernel\\HttpKernel::handle`. In the
-most typical use-case, you can then call the :method:`Symfony\\Component\\HttpFoundation\\Response::send`
-method, which sends the headers and prints the ``Response`` content.
-
-.. sidebar:: ``kernel.response`` in the Symfony Framework
-
- There are several minor listeners on this event inside the Symfony Framework,
- and most modify the response in some way. For example, the
- :class:`Symfony\\Bundle\\WebProfilerBundle\\EventListener\\WebDebugToolbarListener`
- injects some JavaScript at the bottom of your page in the ``dev`` environment
- which causes the web debug toolbar to be displayed. Another listener,
- :class:`Symfony\\Component\\Security\\Http\\Firewall\\ContextListener`
- serializes the current user's information into the
- session so that it can be reloaded on the next request.
-
-.. _component-http-kernel-kernel-terminate:
-
-8) The ``kernel.terminate`` event
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. versionadded:: 2.1
- The ``kernel.terminate`` event is new to Symfony 2.1.
-
-**Typical Purposes**: To perform some "heavy" action after the response has
-been streamed to the user
-
-:ref:`Kernel Events Information Table`
-
-The final event of the HttpKernel process is ``kernel.terminate`` and is unique
-because it occurs *after* the ``HttpKernel::handle`` method, and after the
-response is sent to the user. Recall from above, then the code that uses
-the kernel, ends like this::
-
- // echo the content and send the headers
- $response->send();
-
- // triggers the kernel.terminate event
- $kernel->terminate($request, $response);
-
-As you can see, by calling ``$kernel->terminate`` after sending the response,
-you will trigger the ``kernel.terminate`` event where you can perform certain
-actions that you may have delayed in order to return the response as quickly
-as possible to the client (e.g. sending emails).
-
-.. note::
-
- Using the ``kernel.terminate`` event is optional, and should only be
- called if your kernel implements :class:`Symfony\\Component\\HttpKernel\\TerminableInterface`.
-
-.. sidebar:: ``kernel.terminate`` in the Symfony Framework
-
- If you use the ``SwiftmailerBundle`` with Symfony2 and use ``memory``
- spooling, then the :class:`Symfony\\Bundle\\SwiftmailerBundle\\EventListener\\EmailSenderListener`
- is activated, which actually delivers any emails that you scheduled to
- send during the request.
-
-.. _component-http-kernel-kernel-exception:
-
-Handling Exceptions:: the ``kernel.exception`` event
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-**Typical Purposes**: Handle some type of exception and create an appropriate
-``Response`` to return for the exception
-
-:ref:`Kernel Events Information Table`
-
-If an exception is thrown at any point inside ``HttpKernel::handle``, another
-event - ``kernel.exception`` is thrown. Internally, the body of the ``handle``
-function is wrapped in a try-catch block. When any exception is thrown, the
-``kernel.exception`` event is dispatched so that your system can somehow respond
-to the exception.
-
-.. image:: /images/components/http_kernel/11-kernel-exception.png
- :align: center
-
-Each listener to this event is passed a :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent`
-object, which you can use to access the original exception via the
-:method:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent::getException`
-method. A typical listener on this event will check for a certain type of
-exception and create an appropriate error ``Response``.
-
-For example, to generate a 404 page, you might throw a special type of exception
-and then add a listener on this event that looks for this exception and
-creates and returns a 404 ``Response``. In fact, the ``HttpKernel`` component
-comes with an :class:`Symfony\\Component\\HttpKernel\\EventListener\\ExceptionListener`,
-which if you choose to use, will do this and more by default (see the sidebar
-below for more details).
-
-.. sidebar:: ``kernel.exception`` in the Symfony Framework
-
- There are two main listeners to ``kernel.exception`` when using the
- Symfony Framework.
-
- **ExceptionListener in HttpKernel**
-
- The first comes core to the ``HttpKernel`` component
- and is called :class:`Symfony\\Component\\HttpKernel\\EventListener\\ExceptionListener`.
- The listener has several goals:
-
- 1) The thrown exception is converted into a
- :class:`Symfony\\Component\\HttpKernel\\Exception\\FlattenException`
- object, which contains all the information about the request, but which
- can be printed and serialized.
-
- 2) If the original exception implements
- :class:`Symfony\\Component\\HttpKernel\\Exception\\HttpExceptionInterface`,
- then ``getStatusCode`` and ``getHeaders`` are called on the exception
- and used to populate the headers and status code of the ``FlattenException``
- object. The idea is that these are used in the next step when creating
- the final response.
-
- 3) A controller is executed and passed the flattened exception. The exact
- controller to render is passed as a constructor argument to this listener.
- This controller will return the final ``Response`` for this error page.
-
- **ExceptionListener in Security**
-
- The other important listener is the
- :class:`Symfony\\Component\\Security\\Http\\Firewall\\ExceptionListener`.
- The goal of this listener is to handle security exceptions and, when
- appropriate, *help* the user to authenticate (e.g. redirect to the login
- page).
-
-.. _http-kernel-creating-listener:
-
-Creating an Event Listener
---------------------------
-
-As you've seen, you can create and attach event listeners to any of the events
-dispatched during the ``HttpKernel::handle`` cycle. Typically a listener is a PHP
-class with a method that's executed, but it can be anything. For more information
-on creating and attaching event listeners, see :doc:`/components/event_dispatcher/introduction`.
-
-The name of each of the "kernel" events is defined as a constant on the
-:class:`Symfony\\Component\\HttpKernel\\KernelEvents` class. Additionally, each
-event listener is passed a single argument, which is some sub-class of :class:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent`.
-This object contains information about the current state of the system and
-each event has their own event object:
-
-.. _component-http-kernel-event-table:
-
-+-------------------+-------------------------------+-------------------------------------------------------------------------------------+
-| **Name** | ``KernelEvents`` **Constant** | **Argument passed to the listener** |
-+-------------------+-------------------------------+-------------------------------------------------------------------------------------+
-| kernel.request | ``KernelEvents::REQUEST`` | :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseEvent` |
-+-------------------+-------------------------------+-------------------------------------------------------------------------------------+
-| kernel.controller | ``KernelEvents::CONTROLLER`` | :class:`Symfony\\Component\\HttpKernel\\Event\\FilterControllerEvent` |
-+-------------------+-------------------------------+-------------------------------------------------------------------------------------+
-| kernel.view | ``KernelEvents::VIEW`` | :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForControllerResultEvent` |
-+-------------------+-------------------------------+-------------------------------------------------------------------------------------+
-| kernel.response | ``KernelEvents::RESPONSE`` | :class:`Symfony\\Component\\HttpKernel\\Event\\FilterResponseEvent` |
-+-------------------+-------------------------------+-------------------------------------------------------------------------------------+
-| kernel.terminate | ``KernelEvents::TERMINATE`` | :class:`Symfony\\Component\\HttpKernel\\Event\\PostResponseEvent` |
-+-------------------+-------------------------------+-------------------------------------------------------------------------------------+
-| kernel.exception | ``KernelEvents::EXCEPTION`` | :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent` |
-+-------------------+-------------------------------+-------------------------------------------------------------------------------------+
-
-.. _http-kernel-working-example:
-
-A Full Working Example
-----------------------
-
-When using the HttpKernel component, you're free to attach any listeners
-to the core events and use any controller resolver that implements the
-:class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface`.
-However, the HttpKernel component comes with some built-in listeners and
-a built-in ControllerResolver that can be used to create a working example::
-
- use Symfony\Component\HttpFoundation\Request;
- use Symfony\Component\HttpFoundation\Response;
- use Symfony\Component\HttpKernel\HttpKernel;
- use Symfony\Component\EventDispatcher\EventDispatcher;
- use Symfony\Component\HttpKernel\Controller\ControllerResolver;
- use Symfony\Component\Routing\RouteCollection;
- use Symfony\Component\Routing\Route;
- use Symfony\Component\Routing\Matcher\UrlMatcher;
- use Symfony\Component\Routing\RequestContext;
-
- $routes = new RouteCollection();
- $routes->add('hello', new Route('/hello/{name}', array(
- '_controller' => function (Request $request) {
- return new Response(sprintf("Hello %s", $request->get('name')));
- }
- ),
- ));
-
- $request = Request::createFromGlobals();
-
- $matcher = new UrlMatcher($routes, new RequestContext());
-
- $dispatcher = new EventDispatcher();
- $dispatcher->addSubscriber(new RouterListener($matcher));
-
- $resolver = new ControllerResolver();
- $kernel = new HttpKernel($dispatcher, $resolver);
-
- $response = $kernel->handle($request);
- $response->send();
-
- $kernel->terminate($request, $response);
-
-Sub Requests
-------------
-
-In addition to the "main" request that's sent into ``HttpKernel::handle``,
-you can also send so-called "sub request". A sub request looks and acts like
-any other request, but typically serves to render just one small portion of
-a page instead of a full page. You'll most commonly make sub-requests from
-your controller (or perhaps from inside a template, that's being rendered by
-your controller).
-
-.. image:: /images/components/http_kernel/sub-request.png
- :align: center
-
-To execute a sub request, use ``HttpKernel::handle``, but change the second
-arguments as follows::
-
- use Symfony\Component\HttpFoundation\Request;
- use Symfony\Component\HttpKernel\HttpKernelInterface;
-
- // ...
-
- // create some other request manually as needed
- $request = new Request();
- // for example, possibly set its _controller manually
- $request->attributes->add('_controller', '...');
-
- $response = $kernel->handle($request, HttpKernelInterface::SUB_REQUEST);
- // do something with this response
-
-This creates another full request-response cycle where this new ``Request`` is
-transformed into a ``Response``. The only difference internally is that some
-listeners (e.g. security) may only act upon the master request. Each listener
-is passed some sub-class of :class:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent`,
-whose :method:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent::getRequestType`
-can be used to figure out if the current request is a "master" or "sub" request.
-
-For example, a listener that only needs to act on the master request may
-look like this::
-
- use Symfony\Component\HttpKernel\HttpKernelInterface;
- // ...
-
- public function onKernelRequest(GetResponseEvent $event)
- {
- if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
- return;
- }
-
- // ...
- }
-
-.. _Packagist: https://packagist.org/packages/symfony/http-kernel
-.. _reflection: http://php.net/manual/en/book.reflection.php
-.. _FOSRestBundle: https://github.com/friendsofsymfony/FOSRestBundle
-.. _`Create your own framework... on top of the Symfony2 Components`: http://fabien.potencier.org/article/50/create-your-own-framework-on-top-of-the-symfony2-components-part-1
diff --git a/components/index.rst b/components/index.rst
deleted file mode 100644
index c7ab9d0e113..00000000000
--- a/components/index.rst
+++ /dev/null
@@ -1,31 +0,0 @@
-The Components
-==============
-
-.. toctree::
- :hidden:
-
- using_components
- class_loader
- config/index
- console/index
- css_selector
- debug
- dom_crawler
- dependency_injection/index
- event_dispatcher/index
- filesystem
- finder
- http_foundation/index
- http_kernel/index
- intl
- options_resolver
- process
- property_access/index
- routing/index
- security/index
- serializer
- stopwatch
- templating
- yaml/index
-
-.. include:: /components/map.rst.inc
diff --git a/components/intl.rst b/components/intl.rst
index cf42b9778d5..ba3cbdcb959 100644
--- a/components/intl.rst
+++ b/components/intl.rst
@@ -1,413 +1,425 @@
-.. index::
- single: Intl
- single: Components; Intl
-
The Intl Component
==================
- A PHP replacement layer for the C `intl extension`_ that also provides
- access to the localization data of the `ICU library`_.
-
-.. versionadded:: 2.3
-
- The Intl component was added in Symfony 2.3. In earlier versions of Symfony,
- you should use the Locale component instead.
+ This component provides access to the localization data of the `ICU library`_.
-.. caution::
+.. seealso::
- The replacement layer is limited to the locale "en". If you want to use
- other locales, you should `install the intl extension`_ instead.
+ This article explains how to use the Intl features as an independent component
+ in any PHP application. Read the :doc:`/translation` article to learn about
+ how to internationalize and manage the user locale in Symfony applications.
Installation
------------
-You can install the component in two different ways:
+.. code-block:: terminal
-* Using the official Git repository (https://github.com/symfony/Intl);
-* :doc:`Install it via Composer` (``symfony/intl`` on `Packagist`_).
+ $ composer require symfony/intl
-If you install the component via Composer, the following classes and functions
-of the intl extension will be automatically provided if the intl extension is
-not loaded:
+.. include:: /components/require_autoload.rst.inc
-* :phpclass:`Collator`
-* :phpclass:`IntlDateFormatter`
-* :phpclass:`Locale`
-* :phpclass:`NumberFormatter`
-* :phpfunction:`intl_error_name`
-* :phpfunction:`intl_is_failure`
-* :phpfunction:`intl_get_error_code`
-* :phpfunction:`intl_get_error_message`
+Accessing ICU Data
+------------------
-When the intl extension is not available, the following classes are used to
-replace the intl classes:
+This component provides the following ICU data:
-* :class:`Symfony\\Component\\Intl\\Collator\\Collator`
-* :class:`Symfony\\Component\\Intl\\DateFormatter\\IntlDateFormatter`
-* :class:`Symfony\\Component\\Intl\\Locale\\Locale`
-* :class:`Symfony\\Component\\Intl\\NumberFormatter\\NumberFormatter`
-* :class:`Symfony\\Component\\Intl\\Globals\\IntlGlobals`
+* `Language and Script Names`_
+* `Country Names`_
+* `Locales`_
+* `Currencies`_
+* `Timezones`_
-Composer automatically exposes these classes in the global namespace.
+Language and Script Names
+~~~~~~~~~~~~~~~~~~~~~~~~~
-If you don't use Composer but the
-:doc:`Symfony ClassLoader component`, you need to
-expose them manually by adding the following lines to your autoload code::
+The :class:`Symfony\\Component\\Intl\\Languages` class provides access to the name of all languages
+according to the `ISO 639-1 alpha-2`_ list and the `ISO 639-2 alpha-3 (2T)`_ list::
- if (!function_exists('intl_is_failure')) {
- require '/path/to/Icu/Resources/stubs/functions.php';
+ use Symfony\Component\Intl\Languages;
- $loader->registerPrefixFallback('/path/to/Icu/Resources/stubs');
- }
+ \Locale::setDefault('en');
-.. sidebar:: ICU and Deployment Problems
+ $languages = Languages::getNames();
+ // ('languageCode' => 'languageName')
+ // => ['ab' => 'Abkhazian', 'ace' => 'Achinese', ...]
- The intl extension internally uses the `ICU library`_ to obtain localization
- data such as number formats in different languages, country names and more.
- To make this data accessible to userland PHP libraries, Symfony2 ships a copy
- in the `ICU component`_.
+ $languages = Languages::getAlpha3Names();
+ // ('languageCode' => 'languageName')
+ // => ['abk' => 'Abkhazian', 'ace' => 'Achinese', ...]
- Depending on the ICU version compiled with your intl extension, a matching
- version of that component needs to be installed. It sounds complicated,
- but usually Composer does this for you automatically:
+ $language = Languages::getName('fr');
+ // => 'French'
- * 1.0.*: when the intl extension is not available
- * 1.1.*: when intl is compiled with ICU 4.0 or higher
- * 1.2.*: when intl is compiled with ICU 4.4 or higher
+ $language = Languages::getAlpha3Name('fra');
+ // => 'French'
- These versions are important when you deploy your application to a **server with
- a lower ICU version** than your development machines, because deployment will
- fail if
+All methods accept the translation locale as the last, optional parameter,
+which defaults to the current default locale::
- * the development machines are compiled with ICU 4.4 or higher, but the
- server is compiled with a lower ICU version than 4.4;
- * the intl extension is available on the development machines but not on
- the server.
+ $languages = Languages::getNames('de');
+ // => ['ab' => 'Abchasisch', 'ace' => 'Aceh', ...]
- For example, consider that your development machines ship ICU 4.8 and the server
- ICU 4.2. When you run ``php composer.phar update`` on the development machine, version
- 1.2.* of the ICU component will be installed. But after deploying the
- application, ``php composer.phar install`` will fail with the following error:
+ $languages = Languages::getAlpha3Names('de');
+ // => ['abk' => 'Abchasisch', 'ace' => 'Aceh', ...]
- .. code-block:: bash
+ $language = Languages::getName('fr', 'de');
+ // => 'Französisch'
- $ php composer.phar install
- Loading composer repositories with package information
- Installing dependencies from lock file
- Your requirements could not be resolved to an installable set of packages.
+ $language = Languages::getAlpha3Name('fra', 'de');
+ // => 'Französisch'
- Problem 1
- - symfony/icu 1.2.x requires lib-icu >=4.4 -> the requested linked
- library icu has the wrong version installed or is missing from your
- system, make sure to have the extension providing it.
+If the given locale doesn't exist, the methods trigger a
+:class:`Symfony\\Component\\Intl\\Exception\\MissingResourceException`. In addition
+to catching the exception, you can also check if a given language code is valid::
- The error tells you that the requested version of the ICU component, version
- 1.2, is not compatible with PHP's ICU version 4.2.
+ $isValidLanguage = Languages::exists($languageCode);
- One solution to this problem is to run ``php composer.phar update`` instead of
- ``php composer.phar install``. It is highly recommended **not** to do this. The
- ``update`` command will install the latest versions of each Composer dependency
- to your production server and potentially break the application.
+Or if you have an alpha3 language code you want to check::
- A better solution is to fix your composer.json to the version required by the
- production server. First, determine the ICU version on the server:
+ $isValidLanguage = Languages::alpha3CodeExists($alpha3Code);
- .. code-block:: bash
+You may convert codes between two-letter alpha2 and three-letter alpha3 codes::
- $ php -i | grep ICU
- ICU version => 4.2.1
+ $alpha3Code = Languages::getAlpha3Code($alpha2Code);
- Then fix the ICU component in your composer.json file to a matching version:
+ $alpha2Code = Languages::getAlpha2Code($alpha3Code);
- .. code-block:: json
+The :class:`Symfony\\Component\\Intl\\Scripts` class provides access to the optional four-letter script code
+that can follow the language code according to the `Unicode ISO 15924 Registry`_
+(e.g. ``HANS`` in ``zh_HANS`` for simplified Chinese and ``HANT`` in ``zh_HANT``
+for traditional Chinese)::
- "require: {
- "symfony/icu": "1.1.*"
- }
+ use Symfony\Component\Intl\Scripts;
- Set the version to
+ \Locale::setDefault('en');
- * "1.0.*" if the server does not have the intl extension installed;
- * "1.1.*" if the server is compiled with ICU 4.2 or lower.
+ $scripts = Scripts::getNames();
+ // ('scriptCode' => 'scriptName')
+ // => ['Adlm' => 'Adlam', 'Afak' => 'Afaka', ...]
- Finally, run ``php composer.phar update symfony/icu`` on your development machine, test
- extensively and deploy again. The installation of the dependencies will now
- succeed.
+ $script = Scripts::getName('Hans');
+ // => 'Simplified'
-Writing and Reading Resource Bundles
-------------------------------------
+All methods accept the translation locale as the last, optional parameter,
+which defaults to the current default locale::
-The :phpclass:`ResourceBundle` class is not currently supporting by this component.
-Instead, it includes a set of readers and writers for reading and writing
-arrays (or array-like objects) from/to resource bundle files. The following
-classes are supported:
+ $scripts = Scripts::getNames('de');
+ // => ['Adlm' => 'Adlam', 'Afak' => 'Afaka', ...]
-* `TextBundleWriter`_
-* `PhpBundleWriter`_
-* `BinaryBundleReader`_
-* `PhpBundleReader`_
-* `BufferedBundleReader`_
-* `StructuredBundleReader`_
+ $script = Scripts::getName('Hans', 'de');
+ // => 'Vereinfacht'
-Continue reading if you are interested in how to use these classes. Otherwise
-skip this section and jump to `Accessing ICU Data`_.
+If the given script code doesn't exist, the methods trigger a
+:class:`Symfony\\Component\\Intl\\Exception\\MissingResourceException`. In addition
+to catching the exception, you can also check if a given script code is valid::
-TextBundleWriter
-~~~~~~~~~~~~~~~~
+ $isValidScript = Scripts::exists($scriptCode);
-The :class:`Symfony\\Component\\Intl\\ResourceBundle\\Writer\\TextBundleWriter`
-writes an array or an array-like object to a plain-text resource bundle. The
-resulting .txt file can be converted to a binary .res file with the
-:class:`Symfony\\Component\\Intl\\ResourceBundle\\Compiler\\BundleCompiler`
-class::
+Country Names
+~~~~~~~~~~~~~
- use Symfony\Component\Intl\ResourceBundle\Writer\TextBundleWriter;
- use Symfony\Component\Intl\ResourceBundle\Compiler\BundleCompiler;
+The :class:`Symfony\\Component\\Intl\\Countries` class provides access to the
+name of all countries according to the `ISO 3166-1 alpha-2`_ list and the
+`ISO 3166-1 alpha-3`_ list of officially recognized countries and territories::
- $writer = new TextBundleWriter();
- $writer->write('/path/to/bundle', 'en', array(
- 'Data' => array(
- 'entry1',
- 'entry2',
- // ...
- ),
- ));
+ use Symfony\Component\Intl\Countries;
- $compiler = new BundleCompiler();
- $compiler->compile('/path/to/bundle', '/path/to/binary/bundle');
+ \Locale::setDefault('en');
-The command "genrb" must be available for the
-:class:`Symfony\\Component\\Intl\\ResourceBundle\\Compiler\\BundleCompiler` to
-work. If the command is located in a non-standard location, you can pass its
-path to the
-:class:`Symfony\\Component\\Intl\\ResourceBundle\\Compiler\\BundleCompiler`
-constructor.
+ $countries = Countries::getNames();
+ // ('alpha2Code' => 'countryName')
+ // => ['AF' => 'Afghanistan', 'AX' => 'Åland Islands', ...]
-PhpBundleWriter
-~~~~~~~~~~~~~~~
+ $countries = Countries::getAlpha3Names();
+ // ('alpha3Code' => 'countryName')
+ // => ['AFG' => 'Afghanistan', 'ALA' => 'Åland Islands', ...]
-The :class:`Symfony\\Component\\Intl\\ResourceBundle\\Writer\\PhpBundleWriter`
-writes an array or an array-like object to a .php resource bundle::
+ $country = Countries::getName('GB');
+ // => 'United Kingdom'
- use Symfony\Component\Intl\ResourceBundle\Writer\PhpBundleWriter;
+ $country = Countries::getAlpha3Name('NOR');
+ // => 'Norway'
- $writer = new PhpBundleWriter();
- $writer->write('/path/to/bundle', 'en', array(
- 'Data' => array(
- 'entry1',
- 'entry2',
- // ...
- ),
- ));
+All methods accept the translation locale as the last, optional parameter,
+which defaults to the current default locale::
-BinaryBundleReader
-~~~~~~~~~~~~~~~~~~
+ $countries = Countries::getNames('de');
+ // => ['AF' => 'Afghanistan', 'EG' => 'Ägypten', ...]
-The :class:`Symfony\\Component\\Intl\\ResourceBundle\\Reader\\BinaryBundleReader`
-reads binary resource bundle files and returns an array or an array-like object.
-This class currently only works with the `intl extension`_ installed::
+ $countries = Countries::getAlpha3Names('de');
+ // => ['AFG' => 'Afghanistan', 'EGY' => 'Ägypten', ...]
- use Symfony\Component\Intl\ResourceBundle\Reader\BinaryBundleReader;
+ $country = Countries::getName('GB', 'de');
+ // => 'Vereinigtes Königreich'
- $reader = new BinaryBundleReader();
- $data = $reader->read('/path/to/bundle', 'en');
+ $country = Countries::getAlpha3Name('GBR', 'de');
+ // => 'Vereinigtes Königreich'
- echo $data['Data']['entry1'];
+If the given country code doesn't exist, the methods trigger a
+:class:`Symfony\\Component\\Intl\\Exception\\MissingResourceException`. In addition
+to catching the exception, you can also check if a given country code is valid::
-PhpBundleReader
-~~~~~~~~~~~~~~~
+ $isValidCountry = Countries::exists($alpha2Code);
-The :class:`Symfony\\Component\\Intl\\ResourceBundle\\Reader\\PhpBundleReader`
-reads resource bundles from .php files and returns an array or an array-like
-object::
+Or if you have an alpha3 country code you want to check::
- use Symfony\Component\Intl\ResourceBundle\Reader\PhpBundleReader;
+ $isValidCountry = Countries::alpha3CodeExists($alpha3Code);
- $reader = new PhpBundleReader();
- $data = $reader->read('/path/to/bundle', 'en');
+You may convert codes between two-letter alpha2 and three-letter alpha3 codes::
- echo $data['Data']['entry1'];
+ $alpha3Code = Countries::getAlpha3Code($alpha2Code);
-BufferedBundleReader
-~~~~~~~~~~~~~~~~~~~~
+ $alpha2Code = Countries::getAlpha2Code($alpha3Code);
-The :class:`Symfony\\Component\\Intl\\ResourceBundle\\Reader\\BufferedBundleReader`
-wraps another reader, but keeps the last N reads in a buffer, where N is a
-buffer size passed to the constructor::
+Numeric Country Codes
+~~~~~~~~~~~~~~~~~~~~~
- use Symfony\Component\Intl\ResourceBundle\Reader\BinaryBundleReader;
- use Symfony\Component\Intl\ResourceBundle\Reader\BufferedBundleReader;
+The `ISO 3166-1 numeric`_ standard defines three-digit country codes to represent
+countries, dependent territories, and special areas of geographical interest.
- $reader = new BufferedBundleReader(new BinaryBundleReader(), 10);
+The main advantage over the ISO 3166-1 alphabetic codes (alpha-2 and alpha-3) is
+that these numeric codes are independent from the writing system. The alphabetic
+codes use the 26-letter English alphabet, which might be unavailable or difficult
+to use for people and systems using non-Latin scripts (e.g. Arabic or Japanese).
- // actually reads the file
- $data = $reader->read('/path/to/bundle', 'en');
+The :class:`Symfony\\Component\\Intl\\Countries` class provides access to these
+numeric country codes::
- // returns data from the buffer
- $data = $reader->read('/path/to/bundle', 'en');
+ use Symfony\Component\Intl\Countries;
- // actually reads the file
- $data = $reader->read('/path/to/bundle', 'fr');
+ \Locale::setDefault('en');
-StructuredBundleReader
-~~~~~~~~~~~~~~~~~~~~~~
+ $numericCodes = Countries::getNumericCodes();
+ // ('alpha2Code' => 'numericCode')
+ // => ['AA' => '958', 'AD' => '020', ...]
-The :class:`Symfony\\Component\\Intl\\ResourceBundle\\Reader\\StructuredBundleReader`
-wraps another reader and offers a
-:method:`Symfony\\Component\\Intl\\ResourceBundle\\Reader\\StructuredBundleReaderInterface::readEntry`
-method for reading an entry of the resource bundle without having to worry
-whether array keys are set or not. If a path cannot be resolved, ``null`` is
-returned::
+ $numericCode = Countries::getNumericCode('FR');
+ // => '250'
- use Symfony\Component\Intl\ResourceBundle\Reader\BinaryBundleReader;
- use Symfony\Component\Intl\ResourceBundle\Reader\StructuredBundleReader;
+ $alpha2 = Countries::getAlpha2FromNumeric('250');
+ // => 'FR'
- $reader = new StructuredBundleReader(new BinaryBundleReader());
+ $exists = Countries::numericCodeExists('250');
+ // => true
- $data = $reader->read('/path/to/bundle', 'en');
+Locales
+~~~~~~~
- // Produces an error if the key "Data" does not exist
- echo $data['Data']['entry1'];
+A locale is the combination of a language, a region and some parameters that
+define the interface preferences of the user. For example, "Chinese" is the
+language and ``zh_Hans_MO`` is the locale for "Chinese" (language) + "Simplified"
+(script) + "Macau SAR China" (region). The :class:`Symfony\\Component\\Intl\\Locales`
+class provides access to the name of all locales::
- // Returns null if the key "Data" does not exist
- echo $reader->readEntry('/path/to/bundle', 'en', array('Data', 'entry1'));
+ use Symfony\Component\Intl\Locales;
-Additionally, the
-:method:`Symfony\\Component\\Intl\\ResourceBundle\\Reader\\StructuredBundleReaderInterface::readEntry`
-method resolves fallback locales. For example, the fallback locale of "en_GB" is
-"en". For single-valued entries (strings, numbers etc.), the entry will be read
-from the fallback locale if it cannot be found in the more specific locale. For
-multi-valued entries (arrays), the values of the more specific and the fallback
-locale will be merged. In order to suppress this behavior, the last parameter
-``$fallback`` can be set to ``false``::
+ \Locale::setDefault('en');
- echo $reader->readEntry('/path/to/bundle', 'en', array('Data', 'entry1'), false);
+ $locales = Locales::getNames();
+ // ('localeCode' => 'localeName')
+ // => ['af' => 'Afrikaans', 'af_NA' => 'Afrikaans (Namibia)', ...]
-Accessing ICU Data
-------------------
+ $locale = Locales::getName('zh_Hans_MO');
+ // => 'Chinese (Simplified, Macau SAR China)'
-The ICU data is located in several "resource bundles". You can access a PHP
-wrapper of these bundles through the static
-:class:`Symfony\\Component\\Intl\\Intl` class. At the moment, the following
-data is supported:
+All methods accept the translation locale as the last, optional parameter,
+which defaults to the current default locale::
-* `Language and Script Names`_
-* `Country Names`_
-* `Locales`_
-* `Currencies`_
+ $locales = Locales::getNames('de');
+ // => ['af' => 'Afrikaans', 'af_NA' => 'Afrikaans (Namibia)', ...]
-Language and Script Names
-~~~~~~~~~~~~~~~~~~~~~~~~~
+ $locale = Locales::getName('zh_Hans_MO', 'de');
+ // => 'Chinesisch (Vereinfacht, Sonderverwaltungsregion Macau)'
+
+If the given locale code doesn't exist, the methods trigger a
+:class:`Symfony\\Component\\Intl\\Exception\\MissingResourceException`. In addition
+to catching the exception, you can also check if a given locale code is valid::
+
+ $isValidLocale = Locales::exists($localeCode);
+
+Currencies
+~~~~~~~~~~
-The translations of language and script names can be found in the language
-bundle::
+The :class:`Symfony\\Component\\Intl\\Currencies` class provides access to the name
+of all currencies as well as some of their information (symbol, fraction digits, etc.)::
- use Symfony\Component\Intl\Intl;
+ use Symfony\Component\Intl\Currencies;
\Locale::setDefault('en');
- $languages = Intl::getLanguageBundle()->getLanguageNames();
- // => array('ab' => 'Abkhazian', ...)
+ $currencies = Currencies::getNames();
+ // ('currencyCode' => 'currencyName')
+ // => ['AFN' => 'Afghan Afghani', 'ALL' => 'Albanian Lek', ...]
- $language = Intl::getLanguageBundle()->getLanguageName('de');
- // => 'German'
+ $currency = Currencies::getName('INR');
+ // => 'Indian Rupee'
- $language = Intl::getLanguageBundle()->getLanguageName('de', 'AT');
- // => 'Austrian German'
+ $symbol = Currencies::getSymbol('INR');
+ // => '₹'
- $scripts = Intl::getLanguageBundle()->getScriptNames();
- // => array('Arab' => 'Arabic', ...)
+The fraction digits methods return the number of decimal digits to display when
+formatting numbers with this currency. Depending on the currency, this value
+can change if the number is used in cash transactions or in other scenarios
+(e.g. accounting)::
- $script = Intl::getLanguageBundle()->getScriptName('Hans');
- // => 'Simplified'
+ // Indian rupee defines the same value for both
+ $fractionDigits = Currencies::getFractionDigits('INR'); // returns: 2
+ $cashFractionDigits = Currencies::getCashFractionDigits('INR'); // returns: 2
-All methods accept the translation locale as the last, optional parameter,
-which defaults to the current default locale::
+ // Swedish krona defines different values
+ $fractionDigits = Currencies::getFractionDigits('SEK'); // returns: 2
+ $cashFractionDigits = Currencies::getCashFractionDigits('SEK'); // returns: 0
- $languages = Intl::getLanguageBundle()->getLanguageNames('de');
- // => array('ab' => 'Abchasisch', ...)
+Some currencies require to round numbers to the nearest increment of some value
+(e.g. 5 cents). This increment might be different if numbers are formatted for
+cash transactions or other scenarios (e.g. accounting)::
-Country Names
-~~~~~~~~~~~~~
+ // Indian rupee defines the same value for both
+ $roundingIncrement = Currencies::getRoundingIncrement('INR'); // returns: 0
+ $cashRoundingIncrement = Currencies::getCashRoundingIncrement('INR'); // returns: 0
-The translations of country names can be found in the region bundle::
+ // Canadian dollar defines different values because they have eliminated
+ // the smaller coins (1-cent and 2-cent) and prices in cash must be rounded to
+ // 5 cents (e.g. if price is 7.42 you pay 7.40; if price is 7.48 you pay 7.50)
+ $roundingIncrement = Currencies::getRoundingIncrement('CAD'); // returns: 0
+ $cashRoundingIncrement = Currencies::getCashRoundingIncrement('CAD'); // returns: 5
- use Symfony\Component\Intl\Intl;
+All methods (except for ``getFractionDigits()``, ``getCashFractionDigits()``,
+``getRoundingIncrement()`` and ``getCashRoundingIncrement()``) accept the
+translation locale as the last, optional parameter, which defaults to the
+current default locale::
- \Locale::setDefault('en');
+ $currencies = Currencies::getNames('de');
+ // => ['AFN' => 'Afghanischer Afghani', 'EGP' => 'Ägyptisches Pfund', ...]
- $countries = Intl::getRegionBundle()->getCountryNames();
- // => array('AF' => 'Afghanistan', ...)
+ $currency = Currencies::getName('INR', 'de');
+ // => 'Indische Rupie'
- $country = Intl::getRegionBundle()->getCountryName('GB');
- // => 'United Kingdom'
+If the given currency code doesn't exist, the methods trigger a
+:class:`Symfony\\Component\\Intl\\Exception\\MissingResourceException`. In addition
+to catching the exception, you can also check if a given currency code is valid::
-All methods accept the translation locale as the last, optional parameter,
-which defaults to the current default locale::
+ $isValidCurrency = Currencies::exists($currencyCode);
- $countries = Intl::getRegionBundle()->getCountryNames('de');
- // => array('AF' => 'Afghanistan', ...)
+.. _component-intl-timezones:
-Locales
-~~~~~~~
+Timezones
+~~~~~~~~~
-The translations of locale names can be found in the locale bundle::
+The :class:`Symfony\\Component\\Intl\\Timezones` class provides several utilities
+related to timezones. First, you can get the name and values of all timezones in
+all languages::
- use Symfony\Component\Intl\Intl;
+ use Symfony\Component\Intl\Timezones;
\Locale::setDefault('en');
- $locales = Intl::getLocaleBundle()->getLocaleNames();
- // => array('af' => 'Afrikaans', ...)
+ $timezones = Timezones::getNames();
+ // ('timezoneID' => 'timezoneValue')
+ // => ['America/Eirunepe' => 'Acre Time (Eirunepe)', 'America/Rio_Branco' => 'Acre Time (Rio Branco)', ...]
- $locale = Intl::getLocaleBundle()->getLocaleName('zh_Hans_MO');
- // => 'Chinese (Simplified, Macau SAR China)'
+ $timezone = Timezones::getName('Africa/Nairobi');
+ // => 'East Africa Time (Nairobi)'
All methods accept the translation locale as the last, optional parameter,
which defaults to the current default locale::
- $locales = Intl::getLocaleBundle()->getLocaleNames('de');
- // => array('af' => 'Afrikaans', ...)
+ $timezones = Timezones::getNames('de');
+ // => ['America/Eirunepe' => 'Acre-Zeit (Eirunepe)', 'America/Rio_Branco' => 'Acre-Zeit (Rio Branco)', ...]
-Currencies
-~~~~~~~~~~
+ $timezone = Timezones::getName('Africa/Nairobi', 'de');
+ // => 'Ostafrikanische Zeit (Nairobi)'
-The translations of currency names and other currency-related information can
-be found in the currency bundle::
+You can also get all the timezones that exist in a given country. The
+``forCountryCode()`` method returns one or more timezone IDs, which you can
+translate into any locale with the ``getName()`` method shown earlier::
- use Symfony\Component\Intl\Intl;
+ // unlike language codes, country codes are always uppercase (CL = Chile)
+ $timezones = Timezones::forCountryCode('CL');
+ // => ['America/Punta_Arenas', 'America/Santiago', 'Pacific/Easter']
- \Locale::setDefault('en');
+The reverse lookup is also possible thanks to the ``getCountryCode()`` method,
+which returns the code of the country where the given timezone ID belongs to::
- $currencies = Intl::getCurrencyBundle()->getCurrencyNames();
- // => array('AFN' => 'Afghan Afghani', ...)
+ $countryCode = Timezones::getCountryCode('America/Vancouver');
+ // => $countryCode = 'CA' (CA = Canada)
- $currency = Intl::getCurrencyBundle()->getCurrencyName('INR');
- // => 'Indian Rupee'
+The `UTC/GMT time offsets`_ of all timezones are provided by ``getRawOffset()``
+(which returns an integer representing the offset in seconds) and
+``getGmtOffset()`` (which returns a string representation of the offset to
+display it to users)::
- $symbol = Intl::getCurrencyBundle()->getCurrencySymbol('INR');
- // => '₹'
+ $offset = Timezones::getRawOffset('Etc/UTC'); // $offset = 0
+ $offset = Timezones::getRawOffset('America/Buenos_Aires'); // $offset = -10800
+ $offset = Timezones::getRawOffset('Asia/Katmandu'); // $offset = 20700
+
+ $offset = Timezones::getGmtOffset('Etc/UTC'); // $offset = 'GMT+00:00'
+ $offset = Timezones::getGmtOffset('America/Buenos_Aires'); // $offset = 'GMT-03:00'
+ $offset = Timezones::getGmtOffset('Asia/Katmandu'); // $offset = 'GMT+05:45'
+
+The timezone offset can vary in time because of the `daylight saving time (DST)`_
+practice. By default these methods use the ``time()`` PHP function to get the
+current timezone offset value, but you can pass a timestamp as their second
+arguments to get the offset at any given point in time::
+
+ // In 2019, the DST period in Madrid (Spain) went from March 31 to October 27
+ $offset = Timezones::getRawOffset('Europe/Madrid', strtotime('March 31, 2019')); // $offset = 3600
+ $offset = Timezones::getRawOffset('Europe/Madrid', strtotime('April 1, 2019')); // $offset = 7200
+ $offset = Timezones::getGmtOffset('Europe/Madrid', strtotime('October 27, 2019')); // $offset = 'GMT+02:00'
+ $offset = Timezones::getGmtOffset('Europe/Madrid', strtotime('October 28, 2019')); // $offset = 'GMT+01:00'
+
+The string representation of the GMT offset can vary depending on the locale, so
+you can pass the locale as the third optional argument::
+
+ $offset = Timezones::getGmtOffset('Europe/Madrid', strtotime('October 28, 2019'), 'ar'); // $offset = 'غرينتش+01:00'
+ $offset = Timezones::getGmtOffset('Europe/Madrid', strtotime('October 28, 2019'), 'dz'); // $offset = 'ཇི་ཨེམ་ཏི་+01:00'
+
+If the given timezone ID doesn't exist, the methods trigger a
+:class:`Symfony\\Component\\Intl\\Exception\\MissingResourceException`. In addition
+to catching the exception, you can also check if a given timezone ID is valid::
+
+ $isValidTimezone = Timezones::exists($timezoneId);
+
+.. _component-intl-emoji-transliteration:
+
+Emoji Transliteration
+~~~~~~~~~~~~~~~~~~~~~
+
+Symfony provides utilities to translate emojis into their textual representation
+in all languages. Read the documentation about :ref:`emoji transliteration `
+to learn more about this feature.
+
+Disk Space
+----------
+
+If you need to save disk space (e.g. because you deploy to some service with tight size
+constraints), run this command (e.g. as an automated script after ``composer install``) to compress the
+internal Symfony Intl data files using the PHP ``zlib`` extension:
- $fractionDigits = Intl::getCurrencyBundle()->getFractionDigits('INR');
- // => 2
+.. code-block:: terminal
- $roundingIncrement = Intl::getCurrencyBundle()->getRoundingIncrement('INR');
- // => 0
+ # adjust the path to the 'compress' binary based on your application installation
+ $ php ./vendor/symfony/intl/Resources/bin/compress
-All methods (except for
-:method:`Symfony\\Component\\Intl\\ResourceBundle\\CurrencyBundleInterface::getFractionDigits`
-and
-:method:`Symfony\\Component\\Intl\\ResourceBundle\\CurrencyBundleInterface::getRoundingIncrement`)
-accept the translation locale as the last, optional parameter, which defaults
-to the current default locale::
+Learn more
+----------
- $currencies = Intl::getCurrencyBundle()->getCurrencyNames('de');
- // => array('AFN' => 'Afghanische Afghani', ...)
+.. toctree::
+ :maxdepth: 1
+ :glob:
-That's all you need to know for now. Have fun coding!
+ /reference/forms/types/country
+ /reference/forms/types/currency
+ /reference/forms/types/language
+ /reference/forms/types/locale
+ /reference/forms/types/timezone
-.. _Packagist: https://packagist.org/packages/symfony/intl
-.. _ICU component: https://packagist.org/packages/symfony/icu
-.. _intl extension: http://www.php.net/manual/en/book.intl.php
-.. _install the intl extension: http://www.php.net/manual/en/intl.setup.php
-.. _ICU library: http://site.icu-project.org/
+.. _ICU library: https://icu.unicode.org/
+.. _`Unicode ISO 15924 Registry`: https://www.unicode.org/iso15924/iso15924-codes.html
+.. _`ISO 3166-1 alpha-2`: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
+.. _`ISO 3166-1 alpha-3`: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3
+.. _`ISO 3166-1 numeric`: https://en.wikipedia.org/wiki/ISO_3166-1_numeric
+.. _`UTC/GMT time offsets`: https://en.wikipedia.org/wiki/List_of_UTC_time_offsets
+.. _`daylight saving time (DST)`: https://en.wikipedia.org/wiki/Daylight_saving_time
+.. _`ISO 639-1 alpha-2`: https://en.wikipedia.org/wiki/ISO_639-1
+.. _`ISO 639-2 alpha-3 (2T)`: https://en.wikipedia.org/wiki/ISO_639-2
diff --git a/components/ldap.rst b/components/ldap.rst
new file mode 100644
index 00000000000..e52a341986c
--- /dev/null
+++ b/components/ldap.rst
@@ -0,0 +1,200 @@
+The Ldap Component
+==================
+
+ The Ldap component provides a means to connect to an LDAP server (OpenLDAP or Active Directory).
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/ldap
+
+.. include:: /components/require_autoload.rst.inc
+
+Usage
+-----
+
+The :class:`Symfony\\Component\\Ldap\\Ldap` class provides methods to authenticate
+and query against an LDAP server.
+
+The ``Ldap`` class uses an :class:`Symfony\\Component\\Ldap\\Adapter\\AdapterInterface`
+to communicate with an LDAP server. The :class:`adapter `
+for PHP's built-in LDAP extension, for example, can be configured using the
+following options:
+
+``host``
+ IP or hostname of the LDAP server
+
+``port``
+ Port used to access the LDAP server
+
+``version``
+ The version of the LDAP protocol to use
+
+``encryption``
+ The encryption protocol: ``ssl``, ``tls`` or ``none`` (default)
+
+``connection_string``
+ You may use this option instead of ``host`` and ``port`` to connect to the
+ LDAP server
+
+``optReferrals``
+ Specifies whether to automatically follow referrals returned by the LDAP server
+
+``options``
+ LDAP server's options as defined in
+ :class:`ConnectionOptions `
+
+For example, to connect to a start-TLS secured LDAP server::
+
+ use Symfony\Component\Ldap\Ldap;
+
+ $ldap = Ldap::create('ext_ldap', [
+ 'host' => 'my-server',
+ 'encryption' => 'ssl',
+ ]);
+
+Or you could directly specify a connection string::
+
+ use Symfony\Component\Ldap\Ldap;
+
+ $ldap = Ldap::create('ext_ldap', ['connection_string' => 'ldaps://my-server:636']);
+
+The :method:`Symfony\\Component\\Ldap\\Ldap::bind` method
+authenticates a previously configured connection using both the
+distinguished name (DN) and the password of a user::
+
+ use Symfony\Component\Ldap\Ldap;
+ // ...
+
+ $ldap->bind($dn, $password);
+
+.. danger::
+
+ When the LDAP server allows unauthenticated binds, a blank password will always be valid.
+
+You can also use the :method:`Symfony\\Component\\Ldap\\Ldap::saslBind` method
+for binding to an LDAP server using `SASL`_::
+
+ // this method defines other optional arguments like $mech, $realm, $authcId, etc.
+ $ldap->saslBind($dn, $password);
+
+After binding to the LDAP server, you can use the :method:`Symfony\\Component\\Ldap\\Ldap::whoami`
+method to get the distinguished name (DN) of the authenticated and authorized user.
+
+.. versionadded:: 7.2
+
+ The ``saslBind()`` and ``whoami()`` methods were introduced in Symfony 7.2.
+
+Once bound (or if you enabled anonymous authentication on your
+LDAP server), you may query the LDAP server using the
+:method:`Symfony\\Component\\Ldap\\Ldap::query` method::
+
+ use Symfony\Component\Ldap\Ldap;
+ // ...
+
+ $query = $ldap->query('dc=symfony,dc=com', '(&(objectclass=person)(ou=Maintainers))');
+ $results = $query->execute();
+
+ foreach ($results as $entry) {
+ // Do something with the results
+ }
+
+By default, LDAP entries are lazy-loaded. If you wish to fetch
+all entries in a single call and do something with the results'
+array, you may use the
+:method:`Symfony\\Component\\Ldap\\Adapter\\ExtLdap\\Collection::toArray` method::
+
+ use Symfony\Component\Ldap\Ldap;
+ // ...
+
+ $query = $ldap->query('dc=symfony,dc=com', '(&(objectclass=person)(ou=Maintainers))');
+ $results = $query->execute()->toArray();
+
+ // Do something with the results array
+
+By default, LDAP queries use the ``Symfony\Component\Ldap\Adapter\QueryInterface::SCOPE_SUB``
+scope, which corresponds to the ``LDAP_SCOPE_SUBTREE`` scope of the
+:phpfunction:`ldap_search` function. You can also use ``SCOPE_BASE`` (related
+to the ``LDAP_SCOPE_BASE`` scope of :phpfunction:`ldap_read`) and ``SCOPE_ONE``
+(related to the ``LDAP_SCOPE_ONELEVEL`` scope of :phpfunction:`ldap_list`)::
+
+ use Symfony\Component\Ldap\Adapter\QueryInterface;
+
+ $query = $ldap->query('dc=symfony,dc=com', '...', ['scope' => QueryInterface::SCOPE_ONE]);
+
+Use the ``filter`` option to only retrieve some specific attributes:
+
+ $query = $ldap->query('dc=symfony,dc=com', '...', ['filter' => ['cn', 'mail']);
+
+Creating or Updating Entries
+----------------------------
+
+The Ldap component provides means to create new LDAP entries, update or even
+delete existing ones::
+
+ use Symfony\Component\Ldap\Entry;
+ use Symfony\Component\Ldap\Ldap;
+ // ...
+
+ $entry = new Entry('cn=Fabien Potencier,dc=symfony,dc=com', [
+ 'sn' => ['fabpot'],
+ 'objectClass' => ['inetOrgPerson'],
+ ]);
+
+ $entryManager = $ldap->getEntryManager();
+
+ // Creating a new entry
+ $entryManager->add($entry);
+
+ // Finding and updating an existing entry
+ $query = $ldap->query('dc=symfony,dc=com', '(&(objectclass=person)(ou=Maintainers))');
+ $result = $query->execute();
+ $entry = $result[0];
+
+ $phoneNumber = $entry->getAttribute('phoneNumber');
+ $isContractor = $entry->hasAttribute('contractorCompany');
+ // attribute names in getAttribute() and hasAttribute() methods are case-sensitive
+ // pass FALSE as the second method argument to make them case-insensitive
+ $isContractor = $entry->hasAttribute('contractorCompany', false);
+
+ $entry->setAttribute('email', ['fabpot@symfony.com']);
+ $entryManager->update($entry);
+
+ // Adding or removing values to a multi-valued attribute is more efficient than using update()
+ $entryManager->addAttributeValues($entry, 'telephoneNumber', ['+1.111.222.3333', '+1.222.333.4444']);
+ $entryManager->removeAttributeValues($entry, 'telephoneNumber', ['+1.111.222.3333', '+1.222.333.4444']);
+
+ // Removing an existing entry
+ $entryManager->remove(new Entry('cn=Test User,dc=symfony,dc=com'));
+
+Batch Updating
+______________
+
+Use the entry manager's :method:`Symfony\\Component\\Ldap\\Adapter\\ExtLdap\\EntryManager::applyOperations`
+method to update multiple attributes at once::
+
+ use Symfony\Component\Ldap\Entry;
+ use Symfony\Component\Ldap\Ldap;
+ // ...
+
+ $entry = new Entry('cn=Fabien Potencier,dc=symfony,dc=com', [
+ 'sn' => ['fabpot'],
+ 'objectClass' => ['inetOrgPerson'],
+ ]);
+
+ $entryManager = $ldap->getEntryManager();
+
+ // Adding multiple email addresses at once
+ $entryManager->applyOperations($entry->getDn(), [
+ new UpdateOperation(LDAP_MODIFY_BATCH_ADD, 'mail', 'new1@example.com'),
+ new UpdateOperation(LDAP_MODIFY_BATCH_ADD, 'mail', 'new2@example.com'),
+ ]);
+
+Possible operation types are ``LDAP_MODIFY_BATCH_ADD``, ``LDAP_MODIFY_BATCH_REMOVE``,
+``LDAP_MODIFY_BATCH_REMOVE_ALL``, ``LDAP_MODIFY_BATCH_REPLACE``. Parameter
+``$values`` must be ``NULL`` when using ``LDAP_MODIFY_BATCH_REMOVE_ALL``
+operation type.
+
+.. _`SASL`: https://en.wikipedia.org/wiki/Simple_Authentication_and_Security_Layer
diff --git a/components/lock.rst b/components/lock.rst
new file mode 100644
index 00000000000..b8ba38c8fc7
--- /dev/null
+++ b/components/lock.rst
@@ -0,0 +1,1051 @@
+The Lock Component
+==================
+
+ The Lock Component creates and manages `locks`_, a mechanism to provide
+ exclusive access to a shared resource.
+
+If you're using the Symfony Framework, read the
+:doc:`Symfony Framework Lock documentation `.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/lock
+
+.. include:: /components/require_autoload.rst.inc
+
+Usage
+-----
+
+Locks are used to guarantee exclusive access to some shared resource. In
+Symfony applications, you can use locks for example to ensure that a command is
+not executed more than once at the same time (on the same or different servers).
+
+Locks are created using a :class:`Symfony\\Component\\Lock\\LockFactory` class,
+which in turn requires another class to manage the storage of locks::
+
+ use Symfony\Component\Lock\LockFactory;
+ use Symfony\Component\Lock\Store\SemaphoreStore;
+
+ $store = new SemaphoreStore();
+ $factory = new LockFactory($store);
+
+The lock is created by calling the :method:`Symfony\\Component\\Lock\\LockFactory::createLock`
+method. Its first argument is an arbitrary string that represents the locked
+resource. Then, a call to the :method:`Symfony\\Component\\Lock\\LockInterface::acquire`
+method will try to acquire the lock::
+
+ // ...
+ $lock = $factory->createLock('pdf-creation');
+
+ if ($lock->acquire()) {
+ // The resource "pdf-creation" is locked.
+ // You can compute and generate the invoice safely here.
+
+ $lock->release();
+ }
+
+If the lock can not be acquired, the method returns ``false``. The ``acquire()``
+method can be safely called repeatedly, even if the lock is already acquired.
+
+.. note::
+
+ Unlike other implementations, the Lock Component distinguishes lock
+ instances even when they are created for the same resource. It means that for
+ a given scope and resource one lock instance can be acquired multiple times.
+ If a lock has to be used by several services, they should share the same ``Lock``
+ instance returned by the ``LockFactory::createLock`` method.
+
+.. tip::
+
+ If you don't release the lock explicitly, it will be released automatically
+ upon instance destruction. In some cases, it can be useful to lock a resource
+ across several requests. To disable the automatic release behavior, set the
+ third argument of the ``createLock()`` method to ``false``.
+
+Serializing Locks
+-----------------
+
+The :class:`Symfony\\Component\\Lock\\Key` contains the state of the
+:class:`Symfony\\Component\\Lock\\Lock` and can be serialized. This
+allows the user to begin a long job in a process by acquiring the lock, and
+continue the job in another process using the same lock.
+
+First, you may create a serializable class containing the resource and the
+key of the lock::
+
+ // src/Lock/RefreshTaxonomy.php
+ namespace App\Lock;
+
+ use Symfony\Component\Lock\Key;
+
+ class RefreshTaxonomy
+ {
+ public function __construct(
+ private object $article,
+ private Key $key,
+ ) {
+ }
+
+ public function getArticle(): object
+ {
+ return $this->article;
+ }
+
+ public function getKey(): Key
+ {
+ return $this->key;
+ }
+ }
+
+Then, you can use this class to dispatch all that's needed for another process
+to handle the rest of the job::
+
+ use App\Lock\RefreshTaxonomy;
+ use Symfony\Component\Lock\Key;
+
+ $key = new Key('article.'.$article->getId());
+ $lock = $factory->createLockFromKey(
+ $key,
+ 300, // ttl
+ false // autoRelease
+ );
+ $lock->acquire(true);
+
+ $this->bus->dispatch(new RefreshTaxonomy($article, $key));
+
+.. note::
+
+ Don't forget to set the ``autoRelease`` argument to ``false`` in the
+ ``Lock`` instantiation to avoid releasing the lock when the destructor is
+ called.
+
+Not all stores are compatible with serialization and cross-process locking: for
+example, the kernel will automatically release semaphores acquired by the
+:ref:`SemaphoreStore ` store. If you use an incompatible
+store (see :ref:`lock stores ` for supported stores), an
+exception will be thrown when the application tries to serialize the key.
+
+.. _lock-blocking-locks:
+
+Blocking Locks
+--------------
+
+By default, when a lock cannot be acquired, the ``acquire`` method returns
+``false`` immediately. To wait (indefinitely) until the lock can be created,
+pass ``true`` as the argument of the ``acquire()`` method. This is called a
+**blocking lock** because the execution of your application stops until the
+lock is acquired::
+
+ use Symfony\Component\Lock\LockFactory;
+ use Symfony\Component\Lock\Store\FlockStore;
+
+ $store = new FlockStore('/var/stores');
+ $factory = new LockFactory($store);
+
+ $lock = $factory->createLock('pdf-creation');
+ $lock->acquire(true);
+
+When the store does not support blocking locks by implementing the
+:class:`Symfony\\Component\\Lock\\BlockingStoreInterface` interface (see
+:ref:`lock stores ` for supported stores), the ``Lock`` class
+will retry to acquire the lock in a non-blocking way until the lock is
+acquired.
+
+Expiring Locks
+--------------
+
+Locks created remotely are difficult to manage because there is no way for the
+remote ``Store`` to know if the locker process is still alive. Due to bugs,
+fatal errors or segmentation faults, it cannot be guaranteed that the
+``release()`` method will be called, which would cause the resource to be
+locked infinitely.
+
+The best solution in those cases is to create **expiring locks**, which are
+released automatically after some amount of time has passed (called TTL for
+*Time To Live*). This time, in seconds, is configured as the second argument of
+the ``createLock()`` method. If needed, these locks can also be released early
+with the ``release()`` method.
+
+The trickiest part when working with expiring locks is choosing the right TTL.
+If it's too short, other processes could acquire the lock before finishing the
+job; if it's too long and the process crashes before calling the ``release()``
+method, the resource will stay locked until the timeout::
+
+ // ...
+ // create an expiring lock that lasts 30 seconds (default is 300.0)
+ $lock = $factory->createLock('pdf-creation', ttl: 30);
+
+ if (!$lock->acquire()) {
+ return;
+ }
+ try {
+ // perform a job during less than 30 seconds
+ } finally {
+ $lock->release();
+ }
+
+.. tip::
+
+ To avoid leaving the lock in a locked state, it's recommended to wrap the
+ job in a try/catch/finally block to always try to release the expiring lock.
+
+In case of long-running tasks, it's better to start with a not too long TTL and
+then use the :method:`Symfony\\Component\\Lock\\LockInterface::refresh` method
+to reset the TTL to its original value::
+
+ // ...
+ $lock = $factory->createLock('pdf-creation', ttl: 30);
+
+ if (!$lock->acquire()) {
+ return;
+ }
+ try {
+ while (!$finished) {
+ // perform a small part of the job.
+
+ // renew the lock for 30 more seconds.
+ $lock->refresh();
+ }
+ } finally {
+ $lock->release();
+ }
+
+.. tip::
+
+ Another useful technique for long-running tasks is to pass a custom TTL as
+ an argument of the ``refresh()`` method to change the default lock TTL::
+
+ $lock = $factory->createLock('pdf-creation', ttl: 30);
+ // ...
+ // refresh the lock for 30 seconds
+ $lock->refresh();
+ // ...
+ // refresh the lock for 600 seconds (next refresh() call will be 30 seconds again)
+ $lock->refresh(600);
+
+This component also provides two useful methods related to expiring locks:
+``getRemainingLifetime()`` (which returns ``null`` or a ``float``
+as seconds) and ``isExpired()`` (which returns a boolean).
+
+Automatically Releasing The Lock
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Locks are automatically released when their Lock objects are destroyed. This is
+an implementation detail that is important when sharing Locks between
+processes. In the example below, ``pcntl_fork()`` creates two processes and the
+Lock will be released automatically as soon as one process finishes::
+
+ // ...
+ $lock = $factory->createLock('pdf-creation');
+ if (!$lock->acquire()) {
+ return;
+ }
+
+ $pid = pcntl_fork();
+ if (-1 === $pid) {
+ // Could not fork
+ exit(1);
+ } elseif ($pid) {
+ // Parent process
+ sleep(30);
+ } else {
+ // Child process
+ echo 'The lock will be released now.';
+ exit(0);
+ }
+ // ...
+
+.. note::
+
+ In order for the above example to work, the `PCNTL`_ extension must be
+ installed.
+
+To disable this behavior, set the ``autoRelease`` argument of
+``LockFactory::createLock()`` to ``false``. That will make the lock acquired
+for 3600 seconds or until ``Lock::release()`` is called::
+
+ $lock = $factory->createLock(
+ 'pdf-creation',
+ 3600, // ttl
+ false // autoRelease
+ );
+
+Shared Locks
+------------
+
+A shared or `readers-writer lock`_ is a synchronization primitive that allows
+concurrent access for read-only operations, while write operations require
+exclusive access. This means that multiple threads can read the data in parallel
+but an exclusive lock is needed for writing or modifying data. They are used for
+example for data structures that cannot be updated atomically and are invalid
+until the update is complete.
+
+Use the :method:`Symfony\\Component\\Lock\\SharedLockInterface::acquireRead`
+method to acquire a read-only lock, and
+:method:`Symfony\\Component\\Lock\\LockInterface::acquire` method to acquire a
+write lock::
+
+ $lock = $factory->createLock('user-'.$user->id);
+ if (!$lock->acquireRead()) {
+ return;
+ }
+
+Similar to the ``acquire()`` method, pass ``true`` as the argument of ``acquireRead()``
+to acquire the lock in a blocking mode::
+
+ $lock = $factory->createLock('user-'.$user->id);
+ $lock->acquireRead(true);
+
+.. note::
+
+ The `priority policy`_ of Symfony's shared locks depends on the underlying
+ store (e.g. Redis store prioritizes readers vs writers).
+
+When a read-only lock is acquired with the ``acquireRead()`` method, it's
+possible to **promote** the lock, and change it to a write lock, by calling the
+``acquire()`` method::
+
+ $lock = $factory->createLock('user-'.$userId);
+ $lock->acquireRead(true);
+
+ if (!$this->shouldUpdate($userId)) {
+ return;
+ }
+
+ $lock->acquire(true); // Promote the lock to a write lock
+ $this->update($userId);
+
+In the same way, it's possible to **demote** a write lock, and change it to a
+read-only lock by calling the ``acquireRead()`` method.
+
+When the provided store does not implement the
+:class:`Symfony\\Component\\Lock\\SharedLockStoreInterface` interface (see
+:ref:`lock stores ` for supported stores), the ``Lock`` class
+will fallback to a write lock by calling the ``acquire()`` method.
+
+The Owner of The Lock
+---------------------
+
+Locks that are acquired for the first time are :ref:`owned ` by the ``Lock`` instance that acquired
+it. If you need to check whether the current ``Lock`` instance is (still) the owner of
+a lock, you can use the ``isAcquired()`` method::
+
+ if ($lock->isAcquired()) {
+ // We (still) own the lock
+ }
+
+Because some lock stores have expiring locks, it is possible for an instance to
+lose the lock it acquired automatically::
+
+ // If we cannot acquire ourselves, it means some other process is already working on it
+ if (!$lock->acquire()) {
+ return;
+ }
+
+ $this->beginTransaction();
+
+ // Perform a very long process that might exceed TTL of the lock
+
+ if ($lock->isAcquired()) {
+ // Still all good, no other instance has acquired the lock in the meantime, we're safe
+ $this->commit();
+ } else {
+ // Bummer! Our lock has apparently exceeded TTL and another process has started in
+ // the meantime so it's not safe for us to commit.
+ $this->rollback();
+ throw new \Exception('Process failed');
+ }
+
+.. warning::
+
+ A common pitfall might be to use the ``isAcquired()`` method to check if
+ a lock has already been acquired by any process. As you can see in this example
+ you have to use ``acquire()`` for this. The ``isAcquired()`` method is used to check
+ if the lock has been acquired by the **current process** only.
+
+.. _lock-owner-technical-details:
+
+.. note::
+
+ Technically, the true owners of the lock are the ones that share the same instance of ``Key``,
+ not ``Lock``. But from a user perspective, ``Key`` is internal and you will likely only be working
+ with the ``Lock`` instance so it's easier to think of the ``Lock`` instance as being the one that
+ is the owner of the lock.
+
+.. _lock-stores:
+
+Available Stores
+----------------
+
+Locks are created and managed in ``Stores``, which are classes that implement
+:class:`Symfony\\Component\\Lock\\PersistingStoreInterface` and, optionally,
+:class:`Symfony\\Component\\Lock\\BlockingStoreInterface`.
+
+The component includes the following built-in store types:
+
+========================================================== ====== ======== ======== ======= =============
+Store Scope Blocking Expiring Sharing Serialization
+========================================================== ====== ======== ======== ======= =============
+:ref:`FlockStore ` local yes no yes no
+:ref:`MemcachedStore ` remote no yes no yes
+:ref:`MongoDbStore ` remote no yes no yes
+:ref:`PdoStore ` remote no yes no yes
+:ref:`DoctrineDbalStore ` remote no yes no yes
+:ref:`PostgreSqlStore ` remote yes no yes no
+:ref:`DoctrineDbalPostgreSqlStore ` remote yes no yes no
+:ref:`RedisStore ` remote no yes yes yes
+:ref:`SemaphoreStore ` local yes no no no
+:ref:`ZookeeperStore ` remote no no no no
+========================================================== ====== ======== ======== ======= =============
+
+.. tip::
+
+ Symfony includes two other special stores that are mostly useful for testing:
+ ``InMemoryStore``, which saves locks in memory during a process, and ``NullStore``,
+ which doesn't persist anything.
+
+.. versionadded:: 7.2
+
+ The :class:`Symfony\\Component\\Lock\\Store\\NullStore` was introduced in Symfony 7.2.
+
+.. _lock-store-flock:
+
+FlockStore
+~~~~~~~~~~
+
+The FlockStore uses the file system on the local computer to create the locks.
+It does not support expiration, but the lock is automatically released when the
+lock object goes out of scope and is freed by the garbage collector (for example
+when the PHP process ends)::
+
+ use Symfony\Component\Lock\Store\FlockStore;
+
+ // the argument is the path of the directory where the locks are created
+ // if none is given, sys_get_temp_dir() is used internally.
+ $store = new FlockStore('/var/stores');
+
+.. warning::
+
+ Beware that some file systems (such as some types of NFS) do not support
+ locking. In those cases, it's better to use a directory on a local disk
+ drive or a remote store.
+
+.. _lock-store-memcached:
+
+MemcachedStore
+~~~~~~~~~~~~~~
+
+The MemcachedStore saves locks on a Memcached server, it requires a Memcached
+connection implementing the ``\Memcached`` class. This store does not
+support blocking, and expects a TTL to avoid stalled locks::
+
+ use Symfony\Component\Lock\Store\MemcachedStore;
+
+ $memcached = new \Memcached();
+ $memcached->addServer('localhost', 11211);
+
+ $store = new MemcachedStore($memcached);
+
+.. note::
+
+ Memcached does not support TTL lower than 1 second.
+
+.. _lock-store-mongodb:
+
+MongoDbStore
+~~~~~~~~~~~~
+
+The MongoDbStore saves locks on a MongoDB server ``>=2.2``, it requires a
+``\MongoDB\Collection`` or ``\MongoDB\Client`` from `mongodb/mongodb`_ or a
+`MongoDB Connection String`_.
+This store does not support blocking and expects a TTL to
+avoid stalled locks::
+
+ use Symfony\Component\Lock\Store\MongoDbStore;
+
+ $mongo = 'mongodb://localhost/database?collection=lock';
+ $options = [
+ 'gcProbability' => 0.001,
+ 'database' => 'myapp',
+ 'collection' => 'lock',
+ 'uriOptions' => [],
+ 'driverOptions' => [],
+ ];
+ $store = new MongoDbStore($mongo, $options);
+
+The ``MongoDbStore`` takes the following ``$options`` (depending on the first parameter type):
+
+============= ================================================================================================
+Option Description
+============= ================================================================================================
+gcProbability Should a TTL Index be created expressed as a probability from 0.0 to 1.0 (Defaults to ``0.001``)
+database The name of the database
+collection The name of the collection
+uriOptions Array of URI options for `MongoDBClient::__construct`_
+driverOptions Array of driver options for `MongoDBClient::__construct`_
+============= ================================================================================================
+
+When the first parameter is a:
+
+``MongoDB\Collection``:
+
+- ``$options['database']`` is ignored
+- ``$options['collection']`` is ignored
+
+``MongoDB\Client``:
+
+- ``$options['database']`` is mandatory
+- ``$options['collection']`` is mandatory
+
+MongoDB Connection String:
+
+- ``$options['database']`` is used otherwise ``/path`` from the DSN, at least one is mandatory
+- ``$options['collection']`` is used otherwise ``?collection=`` from the DSN, at least one is mandatory
+
+.. note::
+
+ The ``collection`` querystring parameter is not part of the `MongoDB Connection String`_ definition.
+ It is used to allow constructing a ``MongoDbStore`` using a `Data Source Name (DSN)`_ without ``$options``.
+
+.. _lock-store-pdo:
+
+PdoStore
+~~~~~~~~
+
+The PdoStore saves locks in an SQL database. It requires a `PDO`_ connection or a `Data Source Name (DSN)`_.
+This store does not support blocking, and expects a TTL to avoid stalled locks::
+
+ use Symfony\Component\Lock\Store\PdoStore;
+
+ // a PDO instance or DSN for lazy connecting through PDO
+ $databaseConnectionOrDSN = 'mysql:host=127.0.0.1;dbname=app';
+ $store = new PdoStore($databaseConnectionOrDSN, ['db_username' => 'myuser', 'db_password' => 'mypassword']);
+
+.. note::
+
+ This store does not support TTL lower than 1 second.
+
+The table where values are stored is created automatically on the first call to
+the :method:`Symfony\\Component\\Lock\\Store\\PdoStore::save` method.
+You can also create this table explicitly by calling the
+:method:`Symfony\\Component\\Lock\\Store\\PdoStore::createTable` method in
+your code.
+
+.. _lock-store-dbal:
+
+DoctrineDbalStore
+~~~~~~~~~~~~~~~~~
+
+The DoctrineDbalStore saves locks in an SQL database. It is identical to PdoStore
+but requires a `Doctrine DBAL Connection`_, or a `Doctrine DBAL URL`_. This store
+does not support blocking, and expects a TTL to avoid stalled locks::
+
+ use Symfony\Component\Lock\Store\DoctrineDbalStore;
+
+ // a Doctrine DBAL connection or DSN
+ $connectionOrURL = 'mysql://myuser:mypassword@127.0.0.1/app';
+ $store = new DoctrineDbalStore($connectionOrURL);
+
+.. note::
+
+ This store does not support TTL lower than 1 second.
+
+The table where values are stored will be automatically generated when your run
+the command:
+
+.. code-block:: terminal
+
+ $ php bin/console make:migration
+
+If you prefer to create the table yourself and it has not already been created, you can
+create this table explicitly by calling the
+:method:`Symfony\\Component\\Lock\\Store\\DoctrineDbalStore::createTable` method.
+You can also add this table to your schema by calling
+:method:`Symfony\\Component\\Lock\\Store\\DoctrineDbalStore::configureSchema` method
+in your code
+
+If the table has not been created upstream, it will be created automatically on the first call to
+the :method:`Symfony\\Component\\Lock\\Store\\DoctrineDbalStore::save` method.
+
+.. _lock-store-pgsql:
+
+PostgreSqlStore
+~~~~~~~~~~~~~~~
+
+The PostgreSqlStore uses `Advisory Locks`_ provided by PostgreSQL. It requires a
+`PDO`_ connection or a `Data Source Name (DSN)`_. It supports native blocking, as well as sharing
+locks::
+
+ use Symfony\Component\Lock\Store\PostgreSqlStore;
+
+ // a PDO instance or DSN for lazy connecting through PDO
+ $databaseConnectionOrDSN = 'pgsql:host=localhost;port=5634;dbname=app';
+ $store = new PostgreSqlStore($databaseConnectionOrDSN, ['db_username' => 'myuser', 'db_password' => 'mypassword']);
+
+In opposite to the ``PdoStore``, the ``PostgreSqlStore`` does not need a table to
+store locks and it does not expire.
+
+.. _lock-store-dbal-pgsql:
+
+DoctrineDbalPostgreSqlStore
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The DoctrineDbalPostgreSqlStore uses `Advisory Locks`_ provided by PostgreSQL.
+It is identical to PostgreSqlStore but requires a `Doctrine DBAL Connection`_ or
+a `Doctrine DBAL URL`_. It supports native blocking, as well as sharing locks::
+
+ use Symfony\Component\Lock\Store\DoctrineDbalPostgreSqlStore;
+
+ // a Doctrine Connection or DSN
+ $databaseConnectionOrDSN = 'postgresql+advisory://myuser:mypassword@127.0.0.1:5634/lock';
+ $store = new DoctrineDbalPostgreSqlStore($databaseConnectionOrDSN);
+
+In opposite to the ``DoctrineDbalStore``, the ``DoctrineDbalPostgreSqlStore`` does not need a table to
+store locks and does not expire.
+
+.. _lock-store-redis:
+
+RedisStore
+~~~~~~~~~~
+
+The RedisStore saves locks on a Redis server, it requires a Redis connection
+implementing the ``\Redis``, ``\RedisArray``, ``\RedisCluster``, ``\Relay\Relay`` or
+``\Predis`` classes. This store does not support blocking, and expects a TTL to
+avoid stalled locks::
+
+ use Symfony\Component\Lock\Store\RedisStore;
+
+ $redis = new \Redis();
+ $redis->connect('localhost');
+
+ $store = new RedisStore($redis);
+
+.. _lock-store-semaphore:
+
+SemaphoreStore
+~~~~~~~~~~~~~~
+
+The SemaphoreStore uses the `PHP semaphore functions`_ to create the locks::
+
+ use Symfony\Component\Lock\Store\SemaphoreStore;
+
+ $store = new SemaphoreStore();
+
+.. _lock-store-combined:
+
+CombinedStore
+~~~~~~~~~~~~~
+
+The CombinedStore is designed for High Availability applications because it
+manages several stores in sync (for example, several Redis servers). When a
+lock is acquired, it forwards the call to all the managed stores, and it
+collects their responses. If a simple majority of stores have acquired the
+lock, then the lock is considered acquired::
+
+ use Symfony\Component\Lock\Store\CombinedStore;
+ use Symfony\Component\Lock\Store\RedisStore;
+ use Symfony\Component\Lock\Strategy\ConsensusStrategy;
+
+ $stores = [];
+ foreach (['server1', 'server2', 'server3'] as $server) {
+ $redis = new \Redis();
+ $redis->connect($server);
+
+ $stores[] = new RedisStore($redis);
+ }
+
+ $store = new CombinedStore($stores, new ConsensusStrategy());
+
+Instead of the simple majority strategy (``ConsensusStrategy``) an
+``UnanimousStrategy`` can be used to require the lock to be acquired in all
+the stores::
+
+ use Symfony\Component\Lock\Store\CombinedStore;
+ use Symfony\Component\Lock\Strategy\UnanimousStrategy;
+
+ $store = new CombinedStore($stores, new UnanimousStrategy());
+
+.. warning::
+
+ In order to get high availability when using the ``ConsensusStrategy``, the
+ minimum cluster size must be three servers. This allows the cluster to keep
+ working when a single server fails (because this strategy requires that the
+ lock is acquired for more than half of the servers).
+
+.. _lock-store-zookeeper:
+
+ZookeeperStore
+~~~~~~~~~~~~~~
+
+The ZookeeperStore saves locks on a `ZooKeeper`_ server. It requires a ZooKeeper
+connection implementing the ``\Zookeeper`` class. This store does not
+support blocking and expiration but the lock is automatically released when the
+PHP process is terminated::
+
+ use Symfony\Component\Lock\Store\ZookeeperStore;
+
+ $zookeeper = new \Zookeeper('localhost:2181');
+ // use the following to define a high-availability cluster:
+ // $zookeeper = new \Zookeeper('localhost1:2181,localhost2:2181,localhost3:2181');
+
+ $store = new ZookeeperStore($zookeeper);
+
+.. note::
+
+ Zookeeper does not require a TTL as the nodes used for locking are ephemeral
+ and die when the PHP process is terminated.
+
+Reliability
+-----------
+
+The component guarantees that the same resource can't be locked twice as long as
+the component is used in the following way.
+
+Remote Stores
+~~~~~~~~~~~~~
+
+Remote stores (:ref:`MemcachedStore `,
+:ref:`MongoDbStore `,
+:ref:`PdoStore `,
+:ref:`PostgreSqlStore `,
+:ref:`RedisStore ` and
+:ref:`ZookeeperStore `) use a unique token to recognize
+the true owner of the lock. This token is stored in the
+:class:`Symfony\\Component\\Lock\\Key` object and is used internally by
+the ``Lock``.
+
+Every concurrent process must store the ``Lock`` on the same server. Otherwise two
+different machines may allow two different processes to acquire the same ``Lock``.
+
+.. warning::
+
+ To guarantee that the same server will always be safe, do not use Memcached
+ behind a LoadBalancer, a cluster or round-robin DNS. Even if the main server
+ is down, the calls must not be forwarded to a backup or failover server.
+
+Expiring Stores
+~~~~~~~~~~~~~~~
+
+Expiring stores (:ref:`MemcachedStore `,
+:ref:`MongoDbStore `,
+:ref:`PdoStore ` and
+:ref:`RedisStore `)
+guarantee that the lock is acquired only for the defined duration of time. If
+the task takes longer to be accomplished, then the lock can be released by the
+store and acquired by someone else.
+
+The ``Lock`` provides several methods to check its health. The ``isExpired()``
+method checks whether or not its lifetime is over and the ``getRemainingLifetime()``
+method returns its time to live in seconds.
+
+Using the above methods, a robust code would be::
+
+ // ...
+ $lock = $factory->createLock('pdf-creation', 30);
+
+ if (!$lock->acquire()) {
+ return;
+ }
+ while (!$finished) {
+ if ($lock->getRemainingLifetime() <= 5) {
+ if ($lock->isExpired()) {
+ // lock was lost, perform a rollback or send a notification
+ throw new \RuntimeException('Lock lost during the overall process');
+ }
+
+ $lock->refresh();
+ }
+
+ // Perform the task whose duration MUST be less than 5 seconds
+ }
+
+.. warning::
+
+ Choose wisely the lifetime of the ``Lock`` and check whether its remaining
+ time to live is enough to perform the task.
+
+.. warning::
+
+ Storing a ``Lock`` usually takes a few milliseconds, but network conditions
+ may increase that time a lot (up to a few seconds). Take that into account
+ when choosing the right TTL.
+
+By design, locks are stored on servers with a defined lifetime. If the date or
+time of the machine changes, a lock could be released sooner than expected.
+
+.. warning::
+
+ To guarantee that date won't change, the NTP service should be disabled
+ and the date should be updated when the service is stopped.
+
+FlockStore
+~~~~~~~~~~
+
+By using the file system, this ``Store`` is reliable as long as concurrent
+processes use the same physical directory to store locks.
+
+Processes must run on the same machine, virtual machine or container.
+Be careful when updating a Kubernetes or Swarm service because, for a short
+period of time, there can be two containers running in parallel.
+
+The absolute path to the directory must remain the same. Be careful of symlinks
+that could change at anytime: Capistrano and blue/green deployment often use
+that trick. Be careful when the path to that directory changes between two
+deployments.
+
+Some file systems (such as some types of NFS) do not support locking.
+
+.. warning::
+
+ All concurrent processes must use the same physical file system by running
+ on the same machine and using the same absolute path to the lock directory.
+
+ Using a ``FlockStore`` in an HTTP context is incompatible with multiple
+ front servers, unless to ensure that the same resource will always be
+ locked on the same machine or to use a well configured shared file system.
+
+Files on the file system can be removed during a maintenance operation. For
+instance, to clean up the ``/tmp`` directory or after a reboot of the machine
+when a directory uses ``tmpfs``. It's not an issue if the lock is released when
+the process ended, but it is in case of ``Lock`` reused between requests.
+
+.. danger::
+
+ Do not store locks on a volatile file system if they have to be reused in
+ several requests.
+
+MemcachedStore
+~~~~~~~~~~~~~~
+
+The way Memcached works is to store items in memory. That means that by using
+the :ref:`MemcachedStore ` the locks are not persisted
+and may disappear by mistake at any time.
+
+If the Memcached service or the machine hosting it restarts, every lock would
+be lost without notifying the running processes.
+
+.. warning::
+
+ To avoid that someone else acquires a lock after a restart, it's recommended
+ to delay service start and wait at least as long as the longest lock TTL.
+
+By default Memcached uses a LRU mechanism to remove old entries when the service
+needs space to add new items.
+
+.. warning::
+
+ The number of items stored in Memcached must be under control. If it's not
+ possible, LRU should be disabled and Lock should be stored in a dedicated
+ Memcached service away from Cache.
+
+When the Memcached service is shared and used for multiple usage, Locks could be
+removed by mistake. For instance some implementation of the PSR-6 ``clear()``
+method uses the Memcached's ``flush()`` method which purges and removes everything.
+
+.. danger::
+
+ The method ``flush()`` must not be called, or locks should be stored in a
+ dedicated Memcached service away from Cache.
+
+MongoDbStore
+~~~~~~~~~~~~
+
+.. warning::
+
+ The locked resource name is indexed in the ``_id`` field of the lock
+ collection. Beware that an indexed field's value in MongoDB can be
+ `a maximum of 1024 bytes in length`_ including the structural overhead.
+
+A TTL index must be used to automatically clean up expired locks.
+Such an index can be created manually:
+
+.. code-block:: javascript
+
+ db.lock.createIndex(
+ { "expires_at": 1 },
+ { "expireAfterSeconds": 0 }
+ )
+
+Alternatively, the method ``MongoDbStore::createTtlIndex(int $expireAfterSeconds = 0)``
+can be called once to create the TTL index during database setup. Read more
+about `Expire Data from Collections by Setting TTL`_ in MongoDB.
+
+.. tip::
+
+ ``MongoDbStore`` will attempt to automatically create a TTL index. It's
+ recommended to set constructor option ``gcProbability`` to ``0.0`` to
+ disable this behavior if you have manually dealt with TTL index creation.
+
+.. warning::
+
+ This store relies on all PHP application and database nodes to have
+ synchronized clocks for lock expiry to occur at the correct time. To ensure
+ locks don't expire prematurely; the lock TTL should be set with enough extra
+ time in ``expireAfterSeconds`` to account for any clock drift between nodes.
+
+``writeConcern`` and ``readConcern`` are not specified by MongoDbStore meaning
+the collection's settings will take effect.
+``readPreference`` is ``primary`` for all queries.
+Read more about `Replica Set Read and Write Semantics`_ in MongoDB.
+
+PdoStore
+~~~~~~~~
+
+The PdoStore relies on the `ACID`_ properties of the SQL engine.
+
+.. warning::
+
+ In a cluster configured with multiple primaries, ensure writes are
+ synchronously propagated to every node, or always use the same node.
+
+.. warning::
+
+ Some SQL engines like MySQL allow to disable the unique constraint check.
+ Ensure that this is not the case ``SET unique_checks=1;``.
+
+In order to purge old locks, this store uses a current datetime to define an
+expiration date reference. This mechanism relies on all server nodes to
+have synchronized clocks.
+
+.. warning::
+
+ To ensure locks don't expire prematurely; the TTLs should be set with
+ enough extra time to account for any clock drift between nodes.
+
+PostgreSqlStore
+~~~~~~~~~~~~~~~
+
+The PostgreSqlStore relies on the `Advisory Locks`_ properties of the PostgreSQL
+database. That means that by using :ref:`PostgreSqlStore `
+the locks will be automatically released at the end of the session in case the
+client cannot unlock for any reason.
+
+If the PostgreSQL service or the machine hosting it restarts, every lock would
+be lost without notifying the running processes.
+
+If the TCP connection is lost, the PostgreSQL may release locks without
+notifying the application.
+
+RedisStore
+~~~~~~~~~~
+
+The way Redis works is to store items in memory. That means that by using
+the :ref:`RedisStore ` the locks are not persisted
+and may disappear by mistake at any time.
+
+If the Redis service or the machine hosting it restarts, every locks would
+be lost without notifying the running processes.
+
+.. warning::
+
+ To avoid that someone else acquires a lock after a restart, it's recommended
+ to delay service start and wait at least as long as the longest lock TTL.
+
+.. tip::
+
+ Redis can be configured to persist items on disk, but this option would
+ slow down writes on the service. This could go against other uses of the
+ server.
+
+When the Redis service is shared and used for multiple usages, locks could be
+removed by mistake.
+
+.. danger::
+
+ The command ``FLUSHDB`` must not be called, or locks should be stored in a
+ dedicated Redis service away from Cache.
+
+CombinedStore
+~~~~~~~~~~~~~
+
+Combined stores allow the storage of locks across several backends. It's a common
+mistake to think that the lock mechanism will be more reliable. This is wrong.
+The ``CombinedStore`` will be, at best, as reliable as the least reliable of
+all managed stores. As soon as one managed store returns erroneous information,
+the ``CombinedStore`` won't be reliable.
+
+.. warning::
+
+ All concurrent processes must use the same configuration, with the same
+ amount of managed stored and the same endpoint.
+
+.. tip::
+
+ Instead of using a cluster of Redis or Memcached servers, it's better to use
+ a ``CombinedStore`` with a single server per managed store.
+
+SemaphoreStore
+~~~~~~~~~~~~~~
+
+Semaphores are handled by the Kernel level. In order to be reliable, processes
+must run on the same machine, virtual machine or container. Be careful when
+updating a Kubernetes or Swarm service because for a short period of time, there
+can be two running containers in parallel.
+
+.. warning::
+
+ All concurrent processes must use the same machine. Before starting a
+ concurrent process on a new machine, check that other processes are stopped
+ on the old one.
+
+.. warning::
+
+ When running on systemd with non-system user and option ``RemoveIPC=yes``
+ (default value), locks are deleted by systemd when that user logs out.
+ Check that process is run with a system user (UID <= SYS_UID_MAX) with
+ ``SYS_UID_MAX`` defined in ``/etc/login.defs``, or set the option
+ ``RemoveIPC=off`` in ``/etc/systemd/logind.conf``.
+
+ZookeeperStore
+~~~~~~~~~~~~~~
+
+The way ZookeeperStore works is by maintaining locks as ephemeral nodes on the
+server. That means that by using :ref:`ZookeeperStore `
+the locks will be automatically released at the end of the session in case the
+client cannot unlock for any reason.
+
+If the ZooKeeper service or the machine hosting it restarts, every lock would
+be lost without notifying the running processes.
+
+.. tip::
+
+ To use ZooKeeper's high-availability feature, you can setup a cluster of
+ multiple servers so that in case one of the server goes down, the majority
+ will still be up and serving the requests. All the available servers in the
+ cluster will see the same state.
+
+.. note::
+
+ As this store does not support multi-level node locks, since the clean up of
+ intermediate nodes becomes an overhead, all locks are maintained at the root
+ level.
+
+Overall
+~~~~~~~
+
+Changing the configuration of stores should be done very carefully. For
+instance, during the deployment of a new version. Processes with new
+configuration must not be started while old processes with old configuration
+are still running.
+
+.. _`a maximum of 1024 bytes in length`: https://docs.mongodb.com/manual/reference/limits/#Index-Key-Limit
+.. _`ACID`: https://en.wikipedia.org/wiki/ACID
+.. _`Advisory Locks`: https://www.postgresql.org/docs/current/explicit-locking.html
+.. _`Data Source Name (DSN)`: https://en.wikipedia.org/wiki/Data_source_name
+.. _`Doctrine DBAL Connection`: https://github.com/doctrine/dbal/blob/master/src/Connection.php
+.. _`Doctrine DBAL URL`: https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
+.. _`Expire Data from Collections by Setting TTL`: https://docs.mongodb.com/manual/tutorial/expire-data/
+.. _`locks`: https://en.wikipedia.org/wiki/Lock_(computer_science)
+.. _`MongoDB Connection String`: https://docs.mongodb.com/manual/reference/connection-string/
+.. _`mongodb/mongodb`: https://packagist.org/packages/mongodb/mongodb
+.. _`MongoDBClient::__construct`: https://docs.mongodb.com/php-library/current/reference/method/MongoDBClient__construct/
+.. _`PDO`: https://www.php.net/pdo
+.. _`PHP semaphore functions`: https://www.php.net/manual/en/book.sem.php
+.. _`Replica Set Read and Write Semantics`: https://docs.mongodb.com/manual/applications/replication/
+.. _`ZooKeeper`: https://zookeeper.apache.org/
+.. _`readers-writer lock`: https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock
+.. _`priority policy`: https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock#Priority_policies
+.. _`PCNTL`: https://www.php.net/manual/book.pcntl.php
diff --git a/components/map.rst.inc b/components/map.rst.inc
deleted file mode 100644
index e29df506e55..00000000000
--- a/components/map.rst.inc
+++ /dev/null
@@ -1,118 +0,0 @@
-* :doc:`/components/using_components`
-
-* **Class Loader**
-
- * :doc:`/components/class_loader`
-
-* :doc:`/components/config/index`
-
- * :doc:`/components/config/introduction`
- * :doc:`/components/config/resources`
- * :doc:`/components/config/caching`
- * :doc:`/components/config/definition`
-
-* :doc:`/components/console/index`
-
- * :doc:`/components/console/introduction`
- * :doc:`/components/console/usage`
- * :doc:`/components/console/single_command_tool`
- * :doc:`/components/console/events`
- * :doc:`/components/console/helpers/index`
-
-* **CSS Selector**
-
- * :doc:`/components/css_selector`
-
-* **Debug**
-
- * :doc:`/components/debug`
-
-* :doc:`/components/dependency_injection/index`
-
- * :doc:`/components/dependency_injection/introduction`
- * :doc:`/components/dependency_injection/types`
- * :doc:`/components/dependency_injection/parameters`
- * :doc:`/components/dependency_injection/definitions`
- * :doc:`/components/dependency_injection/compilation`
- * :doc:`/components/dependency_injection/tags`
- * :doc:`/components/dependency_injection/factories`
- * :doc:`/components/dependency_injection/configurators`
- * :doc:`/components/dependency_injection/parentservices`
- * :doc:`/components/dependency_injection/advanced`
- * :doc:`/components/dependency_injection/workflow`
-
-* **DOM Crawler**
-
- * :doc:`/components/dom_crawler`
-
-* :doc:`/components/event_dispatcher/index`
-
- * :doc:`/components/event_dispatcher/introduction`
- * :doc:`/components/event_dispatcher/container_aware_dispatcher`
- * :doc:`/components/event_dispatcher/generic_event`
-
-* **Filesystem**
-
- * :doc:`/components/filesystem`
-
-* **Finder**
-
- * :doc:`/components/finder`
-
-* :doc:`/components/http_foundation/index`
-
- * :doc:`/components/http_foundation/introduction`
- * :doc:`/components/http_foundation/sessions`
- * :doc:`/components/http_foundation/session_configuration`
- * :doc:`/components/http_foundation/session_testing`
- * :doc:`/components/http_foundation/session_php_bridge`
- * :doc:`/components/http_foundation/trusting_proxies`
-
-* :doc:`/components/http_kernel/index`
-
- * :doc:`/components/http_kernel/introduction`
-
-* **Intl**
-
- * :doc:`/components/intl`
-
-* **Options Resolver**
-
- * :doc:`/components/options_resolver`
-
-* **Process**
-
- * :doc:`/components/process`
-
-* :doc:`/components/property_access/index`
-
- * :doc:`/components/property_access/introduction`
-
-* :doc:`/components/routing/index`
-
- * :doc:`/components/routing/introduction`
- * :doc:`/components/routing/hostname_pattern`
-
-* **Serializer**
-
- * :doc:`/components/serializer`
-
-* **Stopwatch**
-
- * :doc:`/components/stopwatch`
-
-* :doc:`/components/security/index`
-
- * :doc:`/components/security/introduction`
- * :doc:`/components/security/firewall`
- * :doc:`/components/security/authentication`
- * :doc:`/components/security/authorization`
-
-* **Templating**
-
- * :doc:`/components/templating`
-
-* :doc:`/components/yaml/index`
-
- * :doc:`/components/yaml/introduction`
- * :doc:`/components/yaml/yaml_format`
diff --git a/components/messenger.rst b/components/messenger.rst
new file mode 100644
index 00000000000..8d6652fb160
--- /dev/null
+++ b/components/messenger.rst
@@ -0,0 +1,365 @@
+The Messenger Component
+=======================
+
+ The Messenger component helps applications send and receive messages to/from
+ other applications or via message queues.
+
+ The component is greatly inspired by Matthias Noback's series of
+ `blog posts about command buses`_ and the `SimpleBus project`_.
+
+.. seealso::
+
+ This article explains how to use the Messenger features as an independent
+ component in any PHP application. Read the :doc:`/messenger` article to
+ learn about how to use it in Symfony applications.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/messenger
+
+.. include:: /components/require_autoload.rst.inc
+
+Concepts
+--------
+
+.. raw:: html
+
+
+
+**Sender**:
+ Responsible for serializing and sending messages to *something*. This
+ something can be a message broker or a third party API for example.
+
+**Receiver**:
+ Responsible for retrieving, deserializing and forwarding messages to handler(s).
+ This can be a message queue puller or an API endpoint for example.
+
+**Handler**:
+ Responsible for handling messages using the business logic applicable to the messages.
+ Handlers are called by the ``HandleMessageMiddleware`` middleware.
+
+**Middleware**:
+ Middleware can access the message and its wrapper (the envelope) while it is
+ dispatched through the bus.
+ Literally *"the software in the middle"*, those are not about core concerns
+ (business logic) of an application. Instead, they are cross cutting concerns
+ applicable throughout the application and affecting the entire message bus.
+ For instance: logging, validating a message, starting a transaction, ...
+ They are also responsible for calling the next middleware in the chain,
+ which means they can tweak the envelope, by adding stamps to it or even
+ replacing it, as well as interrupt the middleware chain. Middleware are called
+ both when a message is originally dispatched and again later when a message
+ is received from a transport.
+
+**Envelope**:
+ Messenger specific concept, it gives full flexibility inside the message bus,
+ by wrapping the messages into it, allowing to add useful information inside
+ through *envelope stamps*.
+
+**Envelope Stamps**:
+ Piece of information you need to attach to your message: serializer context
+ to use for transport, markers identifying a received message or any sort of
+ metadata your middleware or transport layer may use.
+
+Bus
+---
+
+The bus is used to dispatch messages. The behavior of the bus is in its ordered
+middleware stack. The component comes with a set of middleware that you can use.
+
+When using the message bus with Symfony's FrameworkBundle, the following middleware
+are configured for you:
+
+#. :class:`Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware` (enables asynchronous processing, logs the processing of your messages if you provide a logger)
+#. :class:`Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware` (calls the registered handler(s))
+
+Example::
+
+ use App\Message\MyMessage;
+ use App\MessageHandler\MyMessageHandler;
+ use Symfony\Component\Messenger\Handler\HandlersLocator;
+ use Symfony\Component\Messenger\MessageBus;
+ use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware;
+
+ $handler = new MyMessageHandler();
+
+ $bus = new MessageBus([
+ new HandleMessageMiddleware(new HandlersLocator([
+ MyMessage::class => [$handler],
+ ])),
+ ]);
+
+ $bus->dispatch(new MyMessage(/* ... */));
+
+.. note::
+
+ Every middleware needs to implement the :class:`Symfony\\Component\\Messenger\\Middleware\\MiddlewareInterface`.
+
+Handlers
+--------
+
+Once dispatched to the bus, messages will be handled by a "message handler". A
+message handler is a PHP callable (i.e. a function or an instance of a class)
+that will do the required processing for your message::
+
+ namespace App\MessageHandler;
+
+ use App\Message\MyMessage;
+
+ class MyMessageHandler
+ {
+ public function __invoke(MyMessage $message): void
+ {
+ // Message processing...
+ }
+ }
+
+.. _messenger-envelopes:
+
+Adding Metadata to Messages (Envelopes)
+---------------------------------------
+
+If you need to add metadata or some configuration to a message, wrap it with the
+:class:`Symfony\\Component\\Messenger\\Envelope` class and add stamps.
+For example, to set the serialization groups used when the message goes
+through the transport layer, use the ``SerializerStamp`` stamp::
+
+ use Symfony\Component\Messenger\Envelope;
+ use Symfony\Component\Messenger\Stamp\SerializerStamp;
+
+ $bus->dispatch(
+ (new Envelope($message))->with(new SerializerStamp([
+ // groups are applied to the whole message, so make sure
+ // to define the group for every embedded object
+ 'groups' => ['my_serialization_groups'],
+ ]))
+ );
+
+Here are some important envelope stamps that are shipped with the Symfony Messenger:
+
+* :class:`Symfony\\Component\\Messenger\\Stamp\\DelayStamp`,
+ to delay handling of an asynchronous message.
+* :class:`Symfony\\Component\\Messenger\\Stamp\\DispatchAfterCurrentBusStamp`,
+ to make the message be handled after the current bus has executed. Read more
+ at :ref:`messenger-transactional-messages`.
+* :class:`Symfony\\Component\\Messenger\\Stamp\\HandledStamp`,
+ a stamp that marks the message as handled by a specific handler.
+ Allows accessing the handler returned value and the handler name.
+* :class:`Symfony\\Component\\Messenger\\Stamp\\ReceivedStamp`,
+ an internal stamp that marks the message as received from a transport.
+* :class:`Symfony\\Component\\Messenger\\Stamp\\SentStamp`,
+ a stamp that marks the message as sent by a specific sender.
+ Allows accessing the sender FQCN and the alias if available from the
+ :class:`Symfony\\Component\\Messenger\\Transport\\Sender\\SendersLocator`.
+* :class:`Symfony\\Component\\Messenger\\Stamp\\SerializerStamp`,
+ to configure the serialization groups used by the transport.
+* :class:`Symfony\\Component\\Messenger\\Stamp\\ValidationStamp`,
+ to configure the validation groups used when the validation middleware is enabled.
+* :class:`Symfony\\Component\\Messenger\\Stamp\\ErrorDetailsStamp`,
+ an internal stamp when a message fails due to an exception in the handler.
+* :class:`Symfony\\Component\\Scheduler\\Messenger\\ScheduledStamp`,
+ a stamp that marks the message as produced by a scheduler. This helps
+ differentiate it from messages created "manually". You can learn more about it
+ in the :doc:`Scheduler documentation `.
+
+.. note::
+
+ The :class:`Symfony\\Component\\Messenger\\Stamp\\ErrorDetailsStamp` stamp
+ contains a :class:`Symfony\\Component\\ErrorHandler\\Exception\\FlattenException`,
+ which is a representation of the exception that made the message fail. You can
+ get this exception with the
+ :method:`Symfony\\Component\\Messenger\\Stamp\\ErrorDetailsStamp::getFlattenException`
+ method. This exception is normalized thanks to the
+ :class:`Symfony\\Component\\Messenger\\Transport\\Serialization\\Normalizer\\FlattenExceptionNormalizer`
+ which helps error reporting in the Messenger context.
+
+Instead of dealing directly with the messages in the middleware you receive the envelope.
+Hence you can inspect the envelope content and its stamps, or add any::
+
+ use App\Message\Stamp\AnotherStamp;
+ use Symfony\Component\Messenger\Envelope;
+ use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
+ use Symfony\Component\Messenger\Middleware\StackInterface;
+ use Symfony\Component\Messenger\Stamp\ReceivedStamp;
+
+ class MyOwnMiddleware implements MiddlewareInterface
+ {
+ public function handle(Envelope $envelope, StackInterface $stack): Envelope
+ {
+ if (null !== $envelope->last(ReceivedStamp::class)) {
+ // Message just has been received...
+
+ // You could for example add another stamp.
+ $envelope = $envelope->with(new AnotherStamp(/* ... */));
+ } else {
+ // Message was just originally dispatched
+ }
+
+ return $stack->next()->handle($envelope, $stack);
+ }
+ }
+
+The above example will forward the message to the next middleware with an
+additional stamp *if* the message has just been received (i.e. has at least one
+``ReceivedStamp`` stamp). You can create your own stamps by implementing
+:class:`Symfony\\Component\\Messenger\\Stamp\\StampInterface`.
+
+If you want to examine all stamps on an envelope, use the ``$envelope->all()``
+method, which returns all stamps grouped by type (FQCN). Alternatively, you can
+iterate through all stamps of a specific type by using the FQCN as first
+parameter of this method (e.g. ``$envelope->all(ReceivedStamp::class)``).
+
+.. note::
+
+ Any stamp must be serializable using the Symfony Serializer component
+ if going through transport using the :class:`Symfony\\Component\\Messenger\\Transport\\Serialization\\Serializer`
+ base serializer.
+
+Transports
+----------
+
+In order to send and receive messages, you will have to configure a transport. A
+transport will be responsible for communicating with your message broker or 3rd parties.
+
+Your own Sender
+~~~~~~~~~~~~~~~
+
+Imagine that you already have an ``ImportantAction`` message going through the
+message bus and being handled by a handler. Now, you also want to send this
+message as an email (using the :doc:`Mime ` and
+:doc:`Mailer ` components).
+
+Using the :class:`Symfony\\Component\\Messenger\\Transport\\Sender\\SenderInterface`,
+you can create your own message sender::
+
+ namespace App\MessageSender;
+
+ use App\Message\ImportantAction;
+ use Symfony\Component\Mailer\MailerInterface;
+ use Symfony\Component\Messenger\Envelope;
+ use Symfony\Component\Messenger\Transport\Sender\SenderInterface;
+ use Symfony\Component\Mime\Email;
+
+ class ImportantActionToEmailSender implements SenderInterface
+ {
+ public function __construct(
+ private MailerInterface $mailer,
+ private string $toEmail,
+ ) {
+ }
+
+ public function send(Envelope $envelope): Envelope
+ {
+ $message = $envelope->getMessage();
+
+ if (!$message instanceof ImportantAction) {
+ throw new \InvalidArgumentException(sprintf('This transport only supports "%s" messages.', ImportantAction::class));
+ }
+
+ $this->mailer->send(
+ (new Email())
+ ->to($this->toEmail)
+ ->subject('Important action made')
+ ->html('
Important action
Made by '.$message->getUsername().'
')
+ );
+
+ return $envelope;
+ }
+ }
+
+Your own Receiver
+~~~~~~~~~~~~~~~~~
+
+A receiver is responsible for getting messages from a source and dispatching
+them to the application.
+
+Imagine you already processed some "orders" in your application using a
+``NewOrder`` message. Now you want to integrate with a 3rd party or a legacy
+application but you can't use an API and need to use a shared CSV file with new
+orders.
+
+You will read this CSV file and dispatch a ``NewOrder`` message. All you need to
+do is to write your own CSV receiver::
+
+ namespace App\MessageReceiver;
+
+ use App\Message\NewOrder;
+ use Symfony\Component\Messenger\Envelope;
+ use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
+ use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
+ use Symfony\Component\Serializer\SerializerInterface;
+
+ class NewOrdersFromCsvFileReceiver implements ReceiverInterface
+ {
+ private $connection;
+
+ public function __construct(
+ private SerializerInterface $serializer,
+ private string $filePath,
+ ) {
+ // Available connection bundled with the Messenger component
+ // can be found in "Symfony\Component\Messenger\Bridge\*\Transport\Connection".
+ $this->connection = /* create your connection */;
+ }
+
+ public function get(): iterable
+ {
+ // Receive the envelope according to your transport ($yourEnvelope here),
+ // in most cases, using a connection is the easiest solution.
+ $yourEnvelope = $this->connection->get();
+ if (null === $yourEnvelope) {
+ return [];
+ }
+
+ try {
+ $envelope = $this->serializer->decode([
+ 'body' => $yourEnvelope['body'],
+ 'headers' => $yourEnvelope['headers'],
+ ]);
+ } catch (MessageDecodingFailedException $exception) {
+ $this->connection->reject($yourEnvelope['id']);
+ throw $exception;
+ }
+
+ return [$envelope->with(new CustomStamp($yourEnvelope['id']))];
+ }
+
+ public function ack(Envelope $envelope): void
+ {
+ // Add information about the handled message
+ }
+
+ public function reject(Envelope $envelope): void
+ {
+ // In the case of a custom connection
+ $id = /* get the message id thanks to information or stamps present in the envelope */;
+
+ $this->connection->reject($id);
+ }
+ }
+
+Receiver and Sender on the same Bus
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To allow sending and receiving messages on the same bus and prevent an infinite
+loop, the message bus will add a :class:`Symfony\\Component\\Messenger\\Stamp\\ReceivedStamp`
+stamp to the message envelopes and the :class:`Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware`
+middleware will know it should not route these messages again to a transport.
+
+Learn more
+----------
+
+.. toctree::
+ :maxdepth: 1
+ :glob:
+
+ /messenger
+ /messenger/*
+
+.. _`blog posts about command buses`: https://matthiasnoback.nl/tags/command%20bus/
+.. _`SimpleBus project`: https://docs.simplebus.io/en/latest/
diff --git a/components/mime.rst b/components/mime.rst
new file mode 100644
index 00000000000..c043b342ebc
--- /dev/null
+++ b/components/mime.rst
@@ -0,0 +1,298 @@
+The Mime Component
+==================
+
+ The Mime component allows manipulating the MIME messages used to send emails
+ and provides utilities related to MIME types.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/mime
+
+.. include:: /components/require_autoload.rst.inc
+
+Introduction
+------------
+
+`MIME`_ (Multipurpose Internet Mail Extensions) is an Internet standard that
+extends the original basic format of emails to support features like:
+
+* Headers and text contents using non-ASCII characters;
+* Message bodies with multiple parts (e.g. HTML and plain text contents);
+* Non-text attachments: audio, video, images, PDF, etc.
+
+The entire MIME standard is complex and huge, but Symfony abstracts all that
+complexity to provide two ways of creating MIME messages:
+
+* A high-level API based on the :class:`Symfony\\Component\\Mime\\Email` class
+ to quickly create email messages with all the common features;
+* A low-level API based on the :class:`Symfony\\Component\\Mime\\Message` class
+ to have absolute control over every single part of the email message.
+
+Usage
+-----
+
+Use the :class:`Symfony\\Component\\Mime\\Email` class and their *chainable*
+methods to compose the entire email message::
+
+ use Symfony\Component\Mime\Email;
+
+ $email = (new Email())
+ ->from('fabien@symfony.com')
+ ->to('foo@example.com')
+ ->cc('bar@example.com')
+ ->bcc('baz@example.com')
+ ->replyTo('fabien@symfony.com')
+ ->priority(Email::PRIORITY_HIGH)
+ ->subject('Important Notification')
+ ->text('Lorem ipsum...')
+ ->html('
Lorem ipsum
...
')
+ ;
+
+The only purpose of this component is to create the email messages. Use the
+:doc:`Mailer component ` to actually send them.
+
+Twig Integration
+----------------
+
+The Mime component comes with excellent integration with Twig, allowing you to
+create messages from Twig templates, embed images, inline CSS and more. Details
+on how to use those features can be found in the Mailer documentation:
+:ref:`Twig: HTML & CSS `.
+
+But if you're using the Mime component without the Symfony framework, you'll need
+to handle a few setup details.
+
+Twig Setup
+~~~~~~~~~~
+
+To integrate with Twig, use the :class:`Symfony\\Bridge\\Twig\\Mime\\BodyRenderer`
+class to render the template and update the email message contents with the results::
+
+ // ...
+ use Symfony\Bridge\Twig\Mime\BodyRenderer;
+ use Twig\Environment;
+ use Twig\Loader\FilesystemLoader;
+
+ // when using the Mime component inside a full-stack Symfony application, you
+ // don't need to do this Twig setup. You only have to inject the 'twig' service
+ $loader = new FilesystemLoader(__DIR__.'/templates');
+ $twig = new Environment($loader);
+
+ $renderer = new BodyRenderer($twig);
+ // this updates the $email object contents with the result of rendering
+ // the template defined earlier with the given context
+ $renderer->render($email);
+
+Inlining CSS Styles (and other Extensions)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To use the :ref:`inline_css ` filter, first install the Twig
+extension:
+
+.. code-block:: terminal
+
+ $ composer require twig/cssinliner-extra
+
+Now, enable the extension::
+
+ // ...
+ use Twig\Extra\CssInliner\CssInlinerExtension;
+
+ $loader = new FilesystemLoader(__DIR__.'/templates');
+ $twig = new Environment($loader);
+ $twig->addExtension(new CssInlinerExtension());
+
+The same process should be used for enabling other extensions, like the
+:ref:`MarkdownExtension ` and :ref:`InkyExtension `.
+
+Creating Raw Email Messages
+---------------------------
+
+This is useful for advanced applications that need absolute control over every
+email part. It's not recommended for applications with regular email
+requirements because it adds complexity for no real gain.
+
+Before continuing, it's important to have a look at the low level structure of
+an email message. Consider a message which includes some content as both text
+and HTML, a single PNG image embedded in those contents and a PDF file attached
+to it. The MIME standard allows structuring this message in different ways, but
+the following tree is the one that works on most email clients:
+
+.. code-block:: text
+
+ multipart/mixed
+ ├── multipart/related
+ │ ├── multipart/alternative
+ │ │ ├── text/plain
+ │ │ └── text/html
+ │ └── image/png
+ └── application/pdf
+
+This is the purpose of each MIME message part:
+
+* ``multipart/alternative``: used when two or more parts are alternatives of the
+ same (or very similar) content. The preferred format must be added last.
+* ``multipart/mixed``: used to send different content types in the same message,
+ such as when attaching files.
+* ``multipart/related``: used to indicate that each message part is a component
+ of an aggregate whole. The most common usage is to display images embedded
+ in the message contents.
+
+When using the low-level :class:`Symfony\\Component\\Mime\\Message` class to
+create the email message, you must keep all the above in mind to define the
+different parts of the email by hand::
+
+ use Symfony\Component\Mime\Header\Headers;
+ use Symfony\Component\Mime\Message;
+ use Symfony\Component\Mime\Part\Multipart\AlternativePart;
+ use Symfony\Component\Mime\Part\TextPart;
+
+ $headers = (new Headers())
+ ->addMailboxListHeader('From', ['fabien@symfony.com'])
+ ->addMailboxListHeader('To', ['foo@example.com'])
+ ->addTextHeader('Subject', 'Important Notification')
+ ;
+
+ $textContent = new TextPart('Lorem ipsum...');
+ $htmlContent = new TextPart('
Lorem ipsum
...
', null, 'html');
+ $body = new AlternativePart($textContent, $htmlContent);
+
+ $email = new Message($headers, $body);
+
+Embedding images and attaching files is possible by creating the appropriate
+email multiparts::
+
+ // ...
+ use Symfony\Component\Mime\Part\DataPart;
+ use Symfony\Component\Mime\Part\Multipart\MixedPart;
+ use Symfony\Component\Mime\Part\Multipart\RelatedPart;
+
+ // ...
+ $embeddedImage = new DataPart(fopen('/path/to/images/logo.png', 'r'), null, 'image/png');
+ $imageCid = $embeddedImage->getContentId();
+
+ $attachedFile = new DataPart(fopen('/path/to/documents/terms-of-use.pdf', 'r'), null, 'application/pdf');
+
+ $textContent = new TextPart('Lorem ipsum...');
+ $htmlContent = new TextPart(sprintf(
+ '
Lorem ipsum
...
', $imageCid
+ ), null, 'html');
+ $bodyContent = new AlternativePart($textContent, $htmlContent);
+ $body = new RelatedPart($bodyContent, $embeddedImage);
+
+ $messageParts = new MixedPart($body, $attachedFile);
+
+ $email = new Message($headers, $messageParts);
+
+Serializing Email Messages
+--------------------------
+
+Email messages created with either the ``Email`` or ``Message`` classes can be
+serialized because they are simple data objects::
+
+ $email = (new Email())
+ ->from('fabien@symfony.com')
+ // ...
+ ;
+
+ $serializedEmail = serialize($email);
+
+A common use case is to store serialized email messages, include them in a
+message sent with the :doc:`Messenger component ` and
+recreate them later when sending them. Use the
+:class:`Symfony\\Component\\Mime\\RawMessage` class to recreate email messages
+from their serialized contents::
+
+ use Symfony\Component\Mime\RawMessage;
+
+ // ...
+ $serializedEmail = serialize($email);
+
+ // later, recreate the original message to actually send it
+ $message = new RawMessage(unserialize($serializedEmail));
+
+MIME Types Utilities
+--------------------
+
+Although MIME was designed mainly for creating emails, the content types (also
+known as `MIME types`_ and "media types") defined by MIME standards are also of
+importance in communication protocols outside of email, such as HTTP. That's
+why this component also provides utilities to work with MIME types.
+
+The :class:`Symfony\\Component\\Mime\\MimeTypes` class transforms between
+MIME types and file name extensions::
+
+ use Symfony\Component\Mime\MimeTypes;
+
+ $mimeTypes = new MimeTypes();
+ $exts = $mimeTypes->getExtensions('application/javascript');
+ // $exts = ['js', 'jsm', 'mjs']
+ $exts = $mimeTypes->getExtensions('image/jpeg');
+ // $exts = ['jpeg', 'jpg', 'jpe']
+
+ $types = $mimeTypes->getMimeTypes('js');
+ // $types = ['application/javascript', 'application/x-javascript', 'text/javascript']
+ $types = $mimeTypes->getMimeTypes('apk');
+ // $types = ['application/vnd.android.package-archive']
+
+These methods return arrays with one or more elements. The element position
+indicates its priority, so the first returned extension is the preferred one.
+
+.. _components-mime-type-guess:
+
+Guessing the MIME Type
+~~~~~~~~~~~~~~~~~~~~~~
+
+Another useful utility allows to guess the MIME type of any given file::
+
+ use Symfony\Component\Mime\MimeTypes;
+
+ $mimeTypes = new MimeTypes();
+ $mimeType = $mimeTypes->guessMimeType('/some/path/to/image.gif');
+ // Guessing is not based on the file name, so $mimeType will be 'image/gif'
+ // only if the given file is truly a GIF image
+
+Guessing the MIME type is a time-consuming process that requires inspecting
+part of the file contents. Symfony applies multiple guessing mechanisms, one
+of them based on the PHP `fileinfo extension`_. It's recommended to install
+that extension to improve the guessing performance.
+
+Adding a MIME Type Guesser
+..........................
+
+You can add your own MIME type guesser by creating a class that implements
+:class:`Symfony\\Component\\Mime\\MimeTypeGuesserInterface`::
+
+ namespace App;
+
+ use Symfony\Component\Mime\MimeTypeGuesserInterface;
+
+ class SomeMimeTypeGuesser implements MimeTypeGuesserInterface
+ {
+ public function isGuesserSupported(): bool
+ {
+ // return true when the guesser is supported (might depend on the OS for instance)
+ return true;
+ }
+
+ public function guessMimeType(string $path): ?string
+ {
+ // inspect the contents of the file stored in $path to guess its
+ // type and return a valid MIME type ... or null if unknown
+
+ return '...';
+ }
+ }
+
+MIME type guessers must be :ref:`registered as services `
+and :doc:`tagged ` with the ``mime.mime_type_guesser`` tag.
+If you're using the
+:ref:`default services.yaml configuration `,
+this is already done for you, thanks to :ref:`autoconfiguration `.
+
+.. _`MIME`: https://en.wikipedia.org/wiki/MIME
+.. _`MIME types`: https://en.wikipedia.org/wiki/Media_type
+.. _`fileinfo extension`: https://www.php.net/fileinfo
diff --git a/components/options_resolver.rst b/components/options_resolver.rst
index edee6f833e7..6f3a6751f28 100644
--- a/components/options_resolver.rst
+++ b/components/options_resolver.rst
@@ -1,300 +1,994 @@
-.. index::
- single: Options Resolver
- single: Components; OptionsResolver
-
The OptionsResolver Component
=============================
- The OptionsResolver Component helps you configure objects with option
- arrays. It supports default values, option constraints and lazy options.
+ The OptionsResolver component is an improved replacement for the
+ :phpfunction:`array_replace` PHP function. It allows you to create an
+ options system with required options, defaults, validation (type, value),
+ normalization and more.
Installation
------------
-You can install the component in several different ways:
+.. code-block:: terminal
+
+ $ composer require symfony/options-resolver
-* Use the official Git repository (https://github.com/symfony/OptionsResolver
-* :doc:`Install it via Composer` (``symfony/options-resolver`` on `Packagist`_)
+.. include:: /components/require_autoload.rst.inc
Usage
-----
-Imagine you have a ``Person`` class which has 2 options: ``firstName`` and
-``lastName``. These options are going to be handled by the OptionsResolver
-Component.
+Imagine you have a ``Mailer`` class which has four options: ``host``,
+``username``, ``password`` and ``port``::
-First, create the ``Person`` class::
+ class Mailer
+ {
+ protected array $options;
- class Person
+ public function __construct(array $options = [])
+ {
+ $this->options = $options;
+ }
+ }
+
+When accessing the ``$options``, you need to add some boilerplate code to
+check which options are set::
+
+ class Mailer
{
- protected $options;
+ // ...
+ public function sendMail($from, $to): void
+ {
+ $mail = ...;
+
+ $mail->setHost($this->options['host'] ?? 'smtp.example.org');
+ $mail->setUsername($this->options['username'] ?? 'user');
+ $mail->setPassword($this->options['password'] ?? 'pa$$word');
+ $mail->setPort($this->options['port'] ?? 25);
+
+ // ...
+ }
+ }
+
+Also, the default values of the options are buried in the business logic of your
+code. Use :phpfunction:`array_replace` to fix that::
- public function __construct(array $options = array())
+ class Mailer
+ {
+ // ...
+
+ public function __construct(array $options = [])
{
+ $this->options = array_replace([
+ 'host' => 'smtp.example.org',
+ 'username' => 'user',
+ 'password' => 'pa$$word',
+ 'port' => 25,
+ ], $options);
}
}
-You could of course set the ``$options`` value directly on the property. Instead,
-use the :class:`Symfony\\Component\\OptionsResolver\\OptionsResolver` class
-and let it resolve the options by calling
-:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::resolve`.
-The advantages of doing this will become more obvious as you continue::
+Now all four options are guaranteed to be set, but you could still make an error
+like the following when using the ``Mailer`` class::
+
+ $mailer = new Mailer([
+ 'usernme' => 'johndoe', // 'username' is wrongly spelled as 'usernme'
+ ]);
+
+No error will be shown. In the best case, the bug will appear during testing,
+but the developer will spend time looking for the problem. In the worst case,
+the bug might not appear until it's deployed to the live system.
+
+Fortunately, the :class:`Symfony\\Component\\OptionsResolver\\OptionsResolver`
+class helps you to fix this problem::
use Symfony\Component\OptionsResolver\OptionsResolver;
- // ...
- public function __construct(array $options = array())
+ class Mailer
{
- $resolver = new OptionsResolver();
+ // ...
+
+ public function __construct(array $options = [])
+ {
+ $resolver = new OptionsResolver();
+ $resolver->setDefaults([
+ 'host' => 'smtp.example.org',
+ 'username' => 'user',
+ 'password' => 'pa$$word',
+ 'port' => 25,
+ ]);
- $this->options = $resolver->resolve($options);
+ $this->options = $resolver->resolve($options);
+ }
}
-The ``$options`` property is an instance of
-:class:`Symfony\\Component\\OptionsResolver\\Options`, which implements
-:phpclass:`ArrayAccess`, :phpclass:`Iterator` and :phpclass:`Countable`. That
-means you can handle it just like a normal array::
+Like before, all options will be guaranteed to be set. Additionally, an
+:class:`Symfony\\Component\\OptionsResolver\\Exception\\UndefinedOptionsException`
+is thrown if an unknown option is passed::
+
+ $mailer = new Mailer([
+ 'usernme' => 'johndoe',
+ ]);
+
+ // UndefinedOptionsException: The option "usernme" does not exist.
+ // Defined options are: "host", "password", "port", "username"
+
+The rest of your code can access the values of the options without boilerplate
+code::
// ...
- public function getFirstName()
+ class Mailer
{
- return $this->options['firstName'];
+ // ...
+
+ public function sendMail($from, $to): void
+ {
+ $mail = ...;
+ $mail->setHost($this->options['host']);
+ $mail->setUsername($this->options['username']);
+ $mail->setPassword($this->options['password']);
+ $mail->setPort($this->options['port']);
+ // ...
+ }
}
- public function getFullName()
+It's a good practice to split the option configuration into a separate method::
+
+ // ...
+ class Mailer
{
- $name = $this->options['firstName'];
+ // ...
- if (isset($this->options['lastName'])) {
- $name .= ' '.$this->options['lastName'];
+ public function __construct(array $options = [])
+ {
+ $resolver = new OptionsResolver();
+ $this->configureOptions($resolver);
+
+ $this->options = $resolver->resolve($options);
}
- return $name;
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ $resolver->setDefaults([
+ 'host' => 'smtp.example.org',
+ 'username' => 'user',
+ 'password' => 'pa$$word',
+ 'port' => 25,
+ 'encryption' => null,
+ ]);
+ }
}
-Now, try to actually use the class::
+First, your code becomes easier to read, especially if the constructor does more
+than processing options. Second, sub-classes may now override the
+``configureOptions()`` method to adjust the configuration of the options::
- $person = new Person(array(
- 'firstName' => 'Wouter',
- 'lastName' => 'de Jong',
- ));
+ // ...
+ class GoogleMailer extends Mailer
+ {
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ parent::configureOptions($resolver);
- echo $person->getFirstName();
+ $resolver->setDefaults([
+ 'host' => 'smtp.google.com',
+ 'encryption' => 'ssl',
+ ]);
+ }
+ }
-Right now, you'll receive a
-:class:`Symfony\\Component\\OptionsResolver\\Exception\\InvalidOptionsException`,
-which tells you that the options ``firstName`` and ``lastName`` do not exist.
-This is because you need to configure the ``OptionsResolver`` first, so it
-knows which options should be resolved.
+Required Options
+~~~~~~~~~~~~~~~~
-.. tip::
+If an option must be set by the caller, pass that option to
+:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setRequired`.
+For example, to make the ``host`` option required, you can do::
- To check if an option exists, you can use the
- :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::isKnown`
- function.
+ // ...
+ class Mailer
+ {
+ // ...
-A best practice is to put the configuration in a method (e.g.
-``setDefaultOptions``). You call this method in the constructor to configure
-the ``OptionsResolver`` class::
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ // ...
+ $resolver->setRequired('host');
+ }
+ }
- use Symfony\Component\OptionsResolver\OptionsResolver;
- use Symfony\Component\OptionsResolver\OptionsResolverInterface;
+If you omit a required option, a
+:class:`Symfony\\Component\\OptionsResolver\\Exception\\MissingOptionsException`
+will be thrown::
+
+ $mailer = new Mailer();
+
+ // MissingOptionsException: The required option "host" is missing.
+
+The :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setRequired`
+method accepts a single name or an array of option names if you have more than
+one required option::
- class Person
+ // ...
+ class Mailer
{
- protected $options;
+ // ...
- public function __construct(array $options = array())
+ public function configureOptions(OptionsResolver $resolver): void
{
- $resolver = new OptionsResolver();
- $this->setDefaultOptions($resolver);
+ // ...
+ $resolver->setRequired(['host', 'username', 'password']);
+ }
+ }
- $this->options = $resolver->resolve($options);
+Use :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::isRequired` to find
+out if an option is required. You can use
+:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::getRequiredOptions` to
+retrieve the names of all required options::
+
+ // ...
+ class GoogleMailer extends Mailer
+ {
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ parent::configureOptions($resolver);
+
+ if ($resolver->isRequired('host')) {
+ // ...
+ }
+
+ $requiredOptions = $resolver->getRequiredOptions();
}
+ }
- protected function setDefaultOptions(OptionsResolverInterface $resolver)
+If you want to check whether a required option is still missing from the default
+options, you can use :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::isMissing`.
+The difference between this and :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::isRequired`
+is that this method will return false if a required option has already
+been set::
+
+ // ...
+ class Mailer
+ {
+ // ...
+
+ public function configureOptions(OptionsResolver $resolver): void
{
- // ... configure the resolver, you will learn this in the sections below
+ // ...
+ $resolver->setRequired('host');
}
}
-Required Options
-----------------
+ // ...
+ class GoogleMailer extends Mailer
+ {
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ parent::configureOptions($resolver);
+
+ $resolver->isRequired('host');
+ // => true
+
+ $resolver->isMissing('host');
+ // => true
+
+ $resolver->setDefault('host', 'smtp.google.com');
+
+ $resolver->isRequired('host');
+ // => true
+
+ $resolver->isMissing('host');
+ // => false
+ }
+ }
-Suppose the ``firstName`` option is required: the class can't work without
-it. You can set the required options by calling
-:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setRequired`::
+The :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::getMissingOptions` method
+lets you access the names of all missing options.
+
+Type Validation
+~~~~~~~~~~~~~~~
+
+You can run additional checks on the options to make sure they were passed
+correctly. To validate the types of the options, call
+:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setAllowedTypes`::
// ...
- protected function setDefaultOptions(OptionsResolverInterface $resolver)
+ class Mailer
{
- $resolver->setRequired(array('firstName'));
+ // ...
+
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ // ...
+
+ // specify one allowed type
+ $resolver->setAllowedTypes('host', 'string');
+
+ // specify multiple allowed types
+ $resolver->setAllowedTypes('port', ['null', 'int']);
+ // if you prefer, you can also use the following equivalent syntax
+ $resolver->setAllowedTypes('port', 'int|null');
+
+ // check all items in an array recursively for a type
+ $resolver->setAllowedTypes('dates', 'DateTime[]');
+ $resolver->setAllowedTypes('ports', 'int[]');
+ // the following syntax means "an array of integers or an array of strings"
+ $resolver->setAllowedTypes('endpoints', '(int|string)[]');
+ }
}
-You are now able to use the class without errors::
+.. versionadded:: 7.3
- $person = new Person(array(
- 'firstName' => 'Wouter',
- ));
+ Defining type unions with the ``|`` syntax was introduced in Symfony 7.3.
- echo $person->getFirstName(); // 'Wouter'
+You can pass any type for which an ``is_()`` function is defined in PHP.
+You may also pass fully qualified class or interface names (which is checked
+using ``instanceof``). Additionally, you can validate all items in an array
+recursively by suffixing the type with ``[]``.
-If you don't pass a required option, a
-:class:`Symfony\\Component\\OptionsResolver\\Exception\\MissingOptionsException`
-will be thrown.
+If you pass an invalid option now, an
+:class:`Symfony\\Component\\OptionsResolver\\Exception\\InvalidOptionsException`
+is thrown::
+
+ $mailer = new Mailer([
+ 'host' => 25,
+ ]);
-To determine if an option is required, you can use the
-:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::isRequired`
-method.
+ // InvalidOptionsException: The option "host" with value "25" is
+ // expected to be of type "string", but is of type "int"
-Optional Options
-----------------
+In sub-classes, you can use :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::addAllowedTypes`
+to add additional allowed types without erasing the ones already set.
-Sometimes, an option can be optional (e.g. the ``lastName`` option in the
-``Person`` class). You can configure these options by calling
-:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setOptional`::
+.. _optionsresolver-validate-value:
+
+Value Validation
+~~~~~~~~~~~~~~~~
+
+Some options can only take one of a fixed list of predefined values. For
+example, suppose the ``Mailer`` class has a ``transport`` option which can be
+one of ``sendmail``, ``mail`` and ``smtp``. Use the method
+:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setAllowedValues`
+to verify that the passed option contains one of these values::
// ...
- protected function setDefaultOptions(OptionsResolverInterface $resolver)
+ class Mailer
{
// ...
- $resolver->setOptional(array('lastName'));
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ // ...
+ $resolver->setDefault('transport', 'sendmail');
+ $resolver->setAllowedValues('transport', ['sendmail', 'mail', 'smtp']);
+ }
}
-Set Default Values
-------------------
+If you pass an invalid transport, an
+:class:`Symfony\\Component\\OptionsResolver\\Exception\\InvalidOptionsException`
+is thrown::
+
+ $mailer = new Mailer([
+ 'transport' => 'send-mail',
+ ]);
+
+ // InvalidOptionsException: The option "transport" with value "send-mail"
+ // is invalid. Accepted values are: "sendmail", "mail", "smtp"
+
+For options with more complicated validation schemes, pass a closure which
+returns ``true`` for acceptable values and ``false`` for invalid values::
+
+ // ...
+ $resolver->setAllowedValues('transport', function (string $value): bool {
+ // return true or false
+ });
+
+.. tip::
+
+ You can even use the :doc:`Validator ` component to validate the
+ input by using the :method:`Symfony\\Component\\Validator\\Validation::createIsValidCallable`
+ method::
-Most of the optional options have a default value. You can configure these
-options by calling
-:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setDefaults`::
+ use Symfony\Component\OptionsResolver\OptionsResolver;
+ use Symfony\Component\Validator\Constraints\Length;
+ use Symfony\Component\Validator\Validation;
+
+ // ...
+ $resolver->setAllowedValues('transport', Validation::createIsValidCallable(
+ new Length(min: 10)
+ ));
+
+In sub-classes, you can use :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::addAllowedValues`
+to add additional allowed values without erasing the ones already set.
+
+Option Normalization
+~~~~~~~~~~~~~~~~~~~~
+
+Sometimes, option values need to be normalized before you can use them. For
+instance, assume that the ``host`` should always start with ``http://``. To do
+that, you can write normalizers. Normalizers are executed after validating an
+option. You can configure a normalizer by calling
+:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setNormalizer`::
+
+ use Symfony\Component\OptionsResolver\Options;
// ...
- protected function setDefaultOptions(OptionsResolverInterface $resolver)
+ class Mailer
{
// ...
- $resolver->setDefaults(array(
- 'age' => 0,
- ));
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ // ...
+
+ $resolver->setNormalizer('host', function (Options $options, string $value): string {
+ if (!str_starts_with($value, 'http://')) {
+ $value = 'http://'.$value;
+ }
+
+ return $value;
+ });
+ }
}
-The default age will be ``0`` now. When the user specifies an age, it gets
-replaced. You don't need to configure ``age`` as an optional option. The
-``OptionsResolver`` already knows that options with a default value are
-optional.
+The normalizer receives the actual ``$value`` and returns the normalized form.
+You see that the closure also takes an ``$options`` parameter. This is useful
+if you need to use other options during normalization::
-The ``OptionsResolver`` component also has an
-:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::replaceDefaults`
-method. This can be used to override the previous default value. The closure
-that is passed has 2 parameters:
+ // ...
+ class Mailer
+ {
+ // ...
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ // ...
+ $resolver->setNormalizer('host', function (Options $options, string $value): string {
+ if (!str_starts_with($value, 'http://') && !str_starts_with($value, 'https://')) {
+ if ('ssl' === $options['encryption']) {
+ $value = 'https://'.$value;
+ } else {
+ $value = 'http://'.$value;
+ }
+ }
-* ``$options`` (an :class:`Symfony\\Component\\OptionsResolver\\Options`
- instance), with all the default options
-* ``$value``, the previous set default value
+ return $value;
+ });
+ }
+ }
-Default Values that depend on another Option
+To normalize a new allowed value in subclasses that are being normalized
+in parent classes, use :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::addNormalizer` method.
+This way, the ``$value`` argument will receive the previously normalized
+value, otherwise you can prepend the new normalizer by passing ``true`` as
+third argument.
+
+Default Values that Depend on another Option
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Suppose you add a ``gender`` option to the ``Person`` class, whose default
-value you guess based on the first name. You can do that easily by using a
-Closure as the default value::
+Suppose you want to set the default value of the ``port`` option based on the
+encryption chosen by the user of the ``Mailer`` class. More precisely, you want
+to set the port to ``465`` if SSL is used and to ``25`` otherwise.
+
+You can implement this feature by passing a closure as the default value of
+the ``port`` option. The closure receives the options as arguments. Based on
+these options, you can return the desired default value::
use Symfony\Component\OptionsResolver\Options;
// ...
- protected function setDefaultOptions(OptionsResolverInterface $resolver)
+ class Mailer
{
// ...
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ // ...
+ $resolver->setDefault('encryption', null);
- $resolver->setDefaults(array(
- 'gender' => function (Options $options) {
- if (GenderGuesser::isMale($options['firstName'])) {
- return 'male';
+ $resolver->setDefault('port', function (Options $options): int {
+ if ('ssl' === $options['encryption']) {
+ return 465;
}
-
- return 'female';
- },
- ));
+
+ return 25;
+ });
+ }
}
-.. caution::
+.. warning::
- The first argument of the Closure must be typehinted as ``Options``,
- otherwise it is considered as the value.
+ The argument of the callable must be type hinted as ``Options``. Otherwise,
+ the callable itself is considered as the default value of the option.
-Configure allowed Values
-------------------------
+.. note::
-Not all values are valid values for options. For instance, the ``gender``
-option can only be ``female`` or ``male``. You can configure these allowed
-values by calling
-:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setAllowedValues`::
+ The closure is only executed if the ``port`` option isn't set by the user
+ or overwritten in a subclass.
+
+A previously set default value can be accessed by adding a second argument to
+the closure::
// ...
- protected function setDefaultOptions(OptionsResolverInterface $resolver)
+ class Mailer
{
// ...
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ // ...
+ $resolver->setDefaults([
+ 'encryption' => null,
+ 'host' => 'example.org',
+ ]);
+ }
+ }
- $resolver->setAllowedValues(array(
- 'gender' => array('male', 'female'),
- ));
+ class GoogleMailer extends Mailer
+ {
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ parent::configureOptions($resolver);
+
+ $resolver->setDefault('host', function (Options $options, string $previousValue): string {
+ if ('ssl' === $options['encryption']) {
+ return 'secure.example.org';
+ }
+
+ // Take default value configured in the base class
+ return $previousValue;
+ });
+ }
}
-There is also an
-:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::addAllowedValues`
-method, which you can use if you want to add an allowed value to the previous
-set allowed values.
+As seen in the example, this feature is mostly useful if you want to reuse the
+default values set in parent classes in sub-classes.
-Configure allowed Types
-~~~~~~~~~~~~~~~~~~~~~~~
+Options without Default Values
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-You can also specify allowed types. For instance, the ``firstName`` option can
-be anything, but it must be a string. You can configure these types by calling
-:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setAllowedTypes`::
+In some cases, it is useful to define an option without setting a default value.
+This is useful if you need to know whether or not the user *actually* set
+an option or not. For example, if you set the default value for an option,
+it's not possible to know whether the user passed this value or if it comes
+from the default::
// ...
- protected function setDefaultOptions(OptionsResolverInterface $resolver)
+ class Mailer
{
// ...
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ // ...
+ $resolver->setDefault('port', 25);
+ }
- $resolver->setAllowedTypes(array(
- 'firstName' => 'string',
- ));
+ // ...
+ public function sendMail(string $from, string $to): void
+ {
+ // Is this the default value or did the caller of the class really
+ // set the port to 25?
+ if (25 === $this->options['port']) {
+ // ...
+ }
+ }
}
-Possible types are the one associated with the ``is_*`` php functions or a
-class name. You can also pass an array of types as the value. For instance,
-``array('null', 'string')`` allows ``firstName`` to be ``null`` or a
-``string``.
+You can use :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setDefined`
+to define an option without setting a default value. Then the option will only
+be included in the resolved options if it was actually passed to
+:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::resolve`::
-There is also an
-:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::addAllowedTypes`
-method, which you can use to add an allowed type to the previous allowed types.
+ // ...
+ class Mailer
+ {
+ // ...
-Normalize the Options
----------------------
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ // ...
+ $resolver->setDefined('port');
+ }
+
+ // ...
+ public function sendMail(string $from, string $to): void
+ {
+ if (array_key_exists('port', $this->options)) {
+ echo 'Set!';
+ } else {
+ echo 'Not Set!';
+ }
+ }
+ }
+
+ $mailer = new Mailer();
+ $mailer->sendMail($from, $to);
+ // => Not Set!
+
+ $mailer = new Mailer([
+ 'port' => 25,
+ ]);
+ $mailer->sendMail($from, $to);
+ // => Set!
-Some values need to be normalized before you can use them. For instance, the
-``firstName`` should always start with an uppercase letter. To do that, you can
-write normalizers. These Closures will be executed after all options are
-passed and return the normalized value. You can configure these normalizers by
-calling
-:method:`Symfony\\Components\\OptionsResolver\\OptionsResolver::setNormalizers`::
+You can also pass an array of option names if you want to define multiple
+options in one go::
// ...
- protected function setDefaultOptions(OptionsResolverInterface $resolver)
+ class Mailer
{
// ...
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ // ...
+ $resolver->setDefined(['port', 'encryption']);
+ }
+ }
- $resolver->setNormalizers(array(
- 'firstName' => function (Options $options, $value) {
- return ucfirst($value);
- },
- ));
+The methods :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::isDefined`
+and :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::getDefinedOptions`
+let you find out which options are defined::
+
+ // ...
+ class GoogleMailer extends Mailer
+ {
+ // ...
+
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ parent::configureOptions($resolver);
+
+ if ($resolver->isDefined('host')) {
+ // One of the following was called:
+
+ // $resolver->setDefault('host', ...);
+ // $resolver->setRequired('host');
+ // $resolver->setDefined('host');
+ }
+
+ $definedOptions = $resolver->getDefinedOptions();
+ }
+ }
+
+Nested Options
+~~~~~~~~~~~~~~
+
+Suppose you have an option named ``spool`` which has two sub-options ``type``
+and ``path``. Instead of defining it as a simple array of values, you can pass a
+closure as the default value of the ``spool`` option with a
+:class:`Symfony\\Component\\OptionsResolver\\OptionsResolver` argument. Based on
+this instance, you can define the options under ``spool`` and its desired
+default value::
+
+ class Mailer
+ {
+ // ...
+
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ $resolver->setOptions('spool', function (OptionsResolver $spoolResolver): void {
+ $spoolResolver->setDefaults([
+ 'type' => 'file',
+ 'path' => '/path/to/spool',
+ ]);
+ $spoolResolver->setAllowedValues('type', ['file', 'memory']);
+ $spoolResolver->setAllowedTypes('path', 'string');
+ });
+ }
+
+ public function sendMail(string $from, string $to): void
+ {
+ if ('memory' === $this->options['spool']['type']) {
+ // ...
+ }
+ }
}
-You see that the closure also get an ``$options`` parameter. Sometimes, you
-need to use the other options for normalizing.
+ $mailer = new Mailer([
+ 'spool' => [
+ 'type' => 'memory',
+ ],
+ ]);
+
+.. deprecated:: 7.3
+
+ Defining nested options via :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setDefault`
+ is deprecated since Symfony 7.3. Use the :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setOptions`
+ method instead, which also allows defining default values for prototyped options.
+
+.. versionadded:: 7.3
+
+ The ``setOptions()`` method was introduced in Symfony 7.3.
+
+Nested options also support required options, validation (type, value) and
+normalization of their values. If the default value of a nested option depends
+on another option defined in the parent level, add a second ``Options`` argument
+to the closure to access to them::
+
+ class Mailer
+ {
+ // ...
+
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ $resolver->setDefault('sandbox', false);
+ $resolver->setOptions('spool', function (OptionsResolver $spoolResolver, Options $parent): void {
+ $spoolResolver->setDefaults([
+ 'type' => $parent['sandbox'] ? 'memory' : 'file',
+ // ...
+ ]);
+ });
+ }
+ }
+
+.. warning::
+
+ The arguments of the closure must be type hinted as ``OptionsResolver`` and
+ ``Options`` respectively. Otherwise, the closure itself is considered as the
+ default value of the option.
+
+In same way, parent options can access to the nested options as normal arrays::
+
+ class Mailer
+ {
+ // ...
+
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ $resolver->setOptions('spool', function (OptionsResolver $spoolResolver): void {
+ $spoolResolver->setDefaults([
+ 'type' => 'file',
+ // ...
+ ]);
+ });
+ $resolver->setOptions('profiling', function (Options $options): void {
+ return 'file' === $options['spool']['type'];
+ });
+ }
+ }
+
+.. note::
+
+ The fact that an option is defined as nested means that you must pass
+ an array of values to resolve it at runtime.
+
+Prototype Options
+~~~~~~~~~~~~~~~~~
+
+There are situations where you will have to resolve and validate a set of
+options that may repeat many times within another option. Let's imagine a
+``connections`` option that will accept an array of database connections
+with ``host``, ``database``, ``user`` and ``password`` each.
+
+The best way to implement this is to define the ``connections`` option as prototype::
+
+ $resolver->setOptions('connections', function (OptionsResolver $connResolver): void {
+ $connResolver
+ ->setPrototype(true)
+ ->setRequired(['host', 'database'])
+ ->setDefaults(['user' => 'root', 'password' => null]);
+ });
+
+According to the prototype definition in the example above, it is possible
+to have multiple connection arrays like the following::
+
+ $resolver->resolve([
+ 'connections' => [
+ 'default' => [
+ 'host' => '127.0.0.1',
+ 'database' => 'symfony',
+ ],
+ 'test' => [
+ 'host' => '127.0.0.1',
+ 'database' => 'symfony_test',
+ 'user' => 'test',
+ 'password' => 'test',
+ ],
+ // ...
+ ],
+ ]);
+
+The array keys (``default``, ``test``, etc.) of this prototype option are
+validation-free and can be any arbitrary value that helps differentiate the
+connections.
+
+.. note::
+
+ A prototype option can only be defined inside a nested option and
+ during its resolution it will expect an array of arrays.
+
+Deprecating the Option
+~~~~~~~~~~~~~~~~~~~~~~
+
+Once an option is outdated or you decided not to maintain it anymore, you can
+deprecate it using the :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setDeprecated`
+method::
+
+ $resolver
+ ->setDefined(['hostname', 'host'])
+
+ // this outputs the following generic deprecation message:
+ // Since acme/package 1.2: The option "hostname" is deprecated.
+ ->setDeprecated('hostname', 'acme/package', '1.2')
+
+ // you can also pass a custom deprecation message (%name% placeholder is available)
+ // %name% placeholder will be replaced by the deprecated option.
+ // This outputs the following deprecation message:
+ // Since acme/package 1.2: The option "hostname" is deprecated, use "host" instead.
+ ->setDeprecated(
+ 'hostname',
+ 'acme/package',
+ '1.2',
+ 'The option "%name%" is deprecated, use "host" instead.'
+ )
+ ;
+
+.. note::
+
+ The deprecation message will be triggered only if the option is being used
+ somewhere, either its value is provided by the user or the option is evaluated
+ within closures of lazy options and normalizers.
+
+.. note::
+
+ When using an option deprecated by you in your own library, you can pass
+ ``false`` as the second argument of the
+ :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::offsetGet` method
+ to not trigger the deprecation warning.
+
+.. note::
+
+ All deprecation messages are displayed in the profiler logs in the "Deprecations" tab.
+
+Instead of passing the message, you may also pass a closure which returns
+a string (the deprecation message) or an empty string to ignore the deprecation.
+This closure is useful to only deprecate some of the allowed types or values of
+the option::
+
+ $resolver
+ ->setDefault('encryption', null)
+ ->setDefault('port', null)
+ ->setAllowedTypes('port', ['null', 'int'])
+ ->setDeprecated('port', 'acme/package', '1.2', function (Options $options, ?int $value): string {
+ if (null === $value) {
+ return 'Passing "null" to option "port" is deprecated, pass an integer instead.';
+ }
+
+ // deprecation may also depend on another option
+ if ('ssl' === $options['encryption'] && 456 !== $value) {
+ return 'Passing a different port than "456" when the "encryption" option is set to "ssl" is deprecated.';
+ }
+
+ return '';
+ })
+ ;
+
+.. note::
+
+ Deprecation based on the value is triggered only when the option is provided
+ by the user.
+
+This closure receives as argument the value of the option after validating it
+and before normalizing it when the option is being resolved.
+
+Ignore not defined Options
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, all options are resolved and validated, resulting in a
+:class:`Symfony\\Component\\OptionsResolver\\Exception\\UndefinedOptionsException`
+if an unknown option is passed. You can ignore not defined options by using the
+:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::ignoreUndefined` method::
+
+ // ...
+ $resolver
+ ->setDefined(['hostname'])
+ ->setIgnoreUndefined(true)
+ ;
+
+ // option "version" will be ignored
+ $resolver->resolve([
+ 'hostname' => 'acme/package',
+ 'version' => '1.2.3'
+ ]);
+
+Chaining Option Configurations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In many cases you may need to define multiple configurations for each option.
+For example, suppose the ``InvoiceMailer`` class has an ``host`` option that is required
+and a ``transport`` option which can be one of ``sendmail``, ``mail`` and ``smtp``.
+You can improve the readability of the code avoiding to duplicate option name for
+each configuration using the :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::define`
+method::
+
+ // ...
+ class InvoiceMailer
+ {
+ // ...
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ // ...
+ $resolver->define('host')
+ ->required()
+ ->default('smtp.example.org')
+ ->allowedTypes('string')
+ ->info('The IP address or hostname');
+
+ $resolver->define('transport')
+ ->required()
+ ->default('transport')
+ ->allowedValues('sendmail', 'mail', 'smtp');
+ }
+ }
+
+Performance Tweaks
+~~~~~~~~~~~~~~~~~~
+
+With the current implementation, the ``configureOptions()`` method will be
+called for every single instance of the ``Mailer`` class. Depending on the
+amount of option configuration and the number of created instances, this may add
+noticeable overhead to your application. If that overhead becomes a problem, you
+can change your code to do the configuration only once per class::
+
+ // ...
+ class Mailer
+ {
+ private static array $resolversByClass = [];
+
+ protected array $options;
+
+ public function __construct(array $options = [])
+ {
+ // What type of Mailer is this, a Mailer, a GoogleMailer, ... ?
+ $class = get_class($this);
+
+ // Was configureOptions() executed before for this class?
+ if (!isset(self::$resolversByClass[$class])) {
+ self::$resolversByClass[$class] = new OptionsResolver();
+ $this->configureOptions(self::$resolversByClass[$class]);
+ }
+
+ $this->options = self::$resolversByClass[$class]->resolve($options);
+ }
+
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ // ...
+ }
+ }
+
+Now the :class:`Symfony\\Component\\OptionsResolver\\OptionsResolver` instance
+will be created once per class and reused from that on. Be aware that this may
+lead to memory leaks in long-running applications, if the default options contain
+references to objects or object graphs. If that's the case for you, implement a
+method ``clearOptionsConfig()`` and call it periodically::
+
+ // ...
+ class Mailer
+ {
+ private static array $resolversByClass = [];
+
+ public static function clearOptionsConfig(): void
+ {
+ self::$resolversByClass = [];
+ }
+
+ // ...
+ }
+
+That's it! You now have all the tools and knowledge needed to process
+options in your code.
+
+Getting More Insights
+~~~~~~~~~~~~~~~~~~~~~
+
+Use the ``OptionsResolverIntrospector`` to inspect the options definitions
+inside an ``OptionsResolver`` instance::
+
+ use Symfony\Component\OptionsResolver\Debug\OptionsResolverIntrospector;
+ use Symfony\Component\OptionsResolver\OptionsResolver;
+
+ $resolver = new OptionsResolver();
+ $resolver->setDefaults([
+ 'host' => 'smtp.example.org',
+ 'port' => 25,
+ ]);
-.. _Packagist: https://packagist.org/packages/symfony/options-resolver
+ $introspector = new OptionsResolverIntrospector($resolver);
+ $introspector->getDefault('host'); // Retrieves "smtp.example.org"
diff --git a/components/phpunit_bridge.rst b/components/phpunit_bridge.rst
new file mode 100644
index 00000000000..5ce4c003a11
--- /dev/null
+++ b/components/phpunit_bridge.rst
@@ -0,0 +1,1087 @@
+The PHPUnit Bridge
+==================
+
+ The PHPUnit Bridge provides utilities to report legacy tests and usage of
+ deprecated code and helpers for mocking native functions related to time,
+ DNS and class existence.
+
+It comes with the following features:
+
+* Sets by default a consistent locale (``C``) for your tests (if you
+ create locale-sensitive tests, use PHPUnit's ``setLocale()`` method);
+
+* Auto-register ``class_exists`` to load Doctrine annotations (when used);
+
+* It displays the whole list of deprecated features used in the application;
+
+* Displays the stack trace of a deprecation on-demand;
+
+* Provides a ``ClockMock``, ``DnsMock`` and ``ClassExistsMock`` classes for tests
+ sensitive to time, network or class existence;
+
+* Provides a modified version of PHPUnit that allows:
+
+ #. separating the dependencies of your app from those of phpunit to prevent any unwanted constraints to apply;
+ #. running tests in parallel when a test suite is split in several phpunit.xml files;
+ #. recording and replaying skipped tests;
+
+* It allows to create tests that are compatible with multiple PHPUnit versions
+ (because it provides polyfills for missing methods, namespaced aliases for
+ non-namespaced classes, etc.).
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require --dev symfony/phpunit-bridge
+
+.. include:: /components/require_autoload.rst.inc
+
+.. note::
+
+ The PHPUnit bridge is designed to work with all maintained versions of
+ Symfony components, even across different major versions of them. You should
+ always use its very latest stable major version to get the most accurate
+ deprecation report.
+
+If you plan to :ref:`write assertions about deprecations ` and use the regular
+PHPUnit script (not the modified PHPUnit script provided by Symfony), you have
+to register a new `test listener`_ called ``SymfonyTestsListener``:
+
+.. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+Usage
+-----
+
+.. seealso::
+
+ This article explains how to use the PhpUnitBridge features as an independent
+ component in any PHP application. Read the :doc:`/testing` article to learn
+ about how to use it in Symfony applications.
+
+Once the component is installed, a ``simple-phpunit`` script is created in the
+``vendor/`` directory to run tests. This script wraps the original PHPUnit binary
+to provide more features:
+
+.. code-block:: terminal
+
+ $ cd my-project/
+ $ ./vendor/bin/simple-phpunit
+
+After running your PHPUnit tests, you will get a report similar to this one:
+
+.. code-block:: terminal
+
+ $ ./vendor/bin/simple-phpunit
+ PHPUnit by Sebastian Bergmann.
+
+ Configuration read from /phpunit.xml.dist
+ .................
+
+ Time: 1.77 seconds, Memory: 5.75Mb
+
+ OK (17 tests, 21 assertions)
+
+ Remaining deprecation notices (2)
+
+ getEntityManager is deprecated since Symfony 2.1. Use getManager instead: 2x
+ 1x in DefaultControllerTest::testPublicUrls from App\Tests\Controller
+ 1x in BlogControllerTest::testIndex from App\Tests\Controller
+
+The summary includes:
+
+**Unsilenced**
+ Reports deprecation notices that were triggered without the recommended
+ `@-silencing operator`_.
+
+**Legacy**
+ Deprecation notices denote tests that explicitly test some legacy features.
+
+**Remaining/Other**
+ Deprecation notices are all other (non-legacy) notices, grouped by message,
+ test class and method.
+
+.. note::
+
+ If you don't want to use the ``simple-phpunit`` script, register the following
+ `PHPUnit event listener`_ in your PHPUnit configuration file to get the same
+ report about deprecations (which is created by a `PHP error handler`_
+ called :class:`Symfony\\Bridge\\PhpUnit\\DeprecationErrorHandler`):
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+Running Tests in Parallel
+-------------------------
+
+The modified PHPUnit script allows running tests in parallel by providing
+a directory containing multiple test suites with their own ``phpunit.xml.dist``.
+
+.. code-block:: terminal
+
+ ├── tests/
+ │ ├── Functional/
+ │ │ ├── ...
+ │ │ └── phpunit.xml.dist
+ │ ├── Unit/
+ │ │ ├── ...
+ │ │ └── phpunit.xml.dist
+
+.. code-block:: terminal
+
+ $ ./vendor/bin/simple-phpunit tests/
+
+The modified PHPUnit script will recursively go through the provided directory,
+up to a depth of 3 subdirectories or the value specified by the environment variable
+``SYMFONY_PHPUNIT_MAX_DEPTH``, looking for ``phpunit.xml.dist`` files and then
+running each suite it finds in parallel, collecting their output and displaying
+each test suite's results in their own section.
+
+Trigger Deprecation Notices
+---------------------------
+
+Deprecation notices can be triggered by using ``trigger_deprecation`` from
+the ``symfony/deprecation-contracts`` package::
+
+ // indicates something is deprecated since version 1.3 of vendor-name/packagename
+ trigger_deprecation('vendor-name/package-name', '1.3', 'Your deprecation message');
+
+ // you can also use printf format (all arguments after the message will be used)
+ trigger_deprecation('...', '1.3', 'Value "%s" is deprecated, use ... instead.', $value);
+
+Mark Tests as Legacy
+--------------------
+
+There are three ways to mark a test as legacy:
+
+* (**Recommended**) Add the ``@group legacy`` annotation to its class or method;
+
+* Make its class name start with the ``Legacy`` prefix;
+
+* Make its method name start with ``testLegacy*()`` instead of ``test*()``.
+
+.. note::
+
+ If your data provider calls code that would usually trigger a deprecation,
+ you can prefix its name with ``provideLegacy`` or ``getLegacy`` to silence
+ these deprecations. If your data provider does not execute deprecated
+ code, it is not required to choose a special naming just because the
+ test being fed by the data provider is marked as legacy.
+
+ Also be aware that choosing one of the two legacy prefixes will not mark
+ tests as legacy that make use of this data provider. You still have to
+ mark them as legacy tests explicitly.
+
+Configuration
+-------------
+
+In case you need to inspect the stack trace of a particular deprecation
+triggered by your unit tests, you can set the ``SYMFONY_DEPRECATIONS_HELPER``
+`environment variable`_ to a regular expression that matches this deprecation's
+message, enclosed with ``/``. For example, with:
+
+.. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+`PHPUnit`_ will stop your test suite once a deprecation notice is triggered whose
+message contains the ``"foobar"`` string.
+
+.. _making-tests-fail:
+
+Making Tests Fail
+~~~~~~~~~~~~~~~~~
+
+By default, any non-legacy-tagged or any non-silenced (`@-silencing operator`_)
+deprecation notices will make tests fail. Alternatively, you can configure
+an arbitrary threshold by setting ``SYMFONY_DEPRECATIONS_HELPER`` to
+``max[total]=320`` for instance. It will make the tests fail only if a
+higher number of deprecation notices is reached (``0`` is the default
+value).
+
+You can have even finer-grained control by using other keys of the ``max``
+array, which are ``self``, ``direct``, and ``indirect``. The
+``SYMFONY_DEPRECATIONS_HELPER`` environment variable accepts a URL-encoded
+string, meaning you can combine thresholds and any other configuration setting,
+like this: ``SYMFONY_DEPRECATIONS_HELPER='max[total]=42&max[self]=0&verbose=0'``
+
+Internal deprecations
+.....................
+
+When you maintain a library, having the test suite fail as soon as a dependency
+introduces a new deprecation is not desirable, because it shifts the burden of
+fixing that deprecation to any contributor that happens to submit a pull request
+shortly after a new vendor release is made with that deprecation.
+
+To mitigate this, you can either use tighter requirements, in the hope that
+dependencies will not introduce deprecations in a patch version, or even commit
+the ``composer.lock`` file, which would create another class of issues.
+Libraries will often use ``SYMFONY_DEPRECATIONS_HELPER=max[total]=999999``
+because of this. This has the drawback of allowing contributions that introduce
+deprecations but:
+
+* forget to fix the deprecated calls if there are any;
+* forget to mark appropriate tests with the ``@group legacy`` annotations.
+
+By using ``SYMFONY_DEPRECATIONS_HELPER=max[self]=0``, deprecations that are
+triggered outside the ``vendor/`` directory will be accounted for separately,
+while deprecations triggered from a library inside it will not (unless you reach
+999999 of these), giving you the best of both worlds.
+
+Direct and Indirect Deprecations
+................................
+
+When working on a project, you might be more interested in ``max[direct]``.
+Let's say you want to fix deprecations as soon as they appear. A problem many
+developers experience is that some dependencies they have tend to lag behind
+their own dependencies, meaning they do not fix deprecations as soon as
+possible, which means you should create a pull request on the outdated vendor,
+and ignore these deprecations until your pull request is merged.
+
+The ``max[direct]`` config allows you to put a threshold on direct deprecations
+only, allowing you to notice when *your code* is using deprecated APIs, and to
+keep up with the changes. You can still use ``max[indirect]`` if you want to
+keep indirect deprecations under a given threshold.
+
+Here is a summary that should help you pick the right configuration:
+
++------------------------+-----------------------------------------------------+
+| Value | Recommended situation |
++========================+=====================================================+
+| max[total]=0 | Recommended for actively maintained projects |
+| | with robust/no dependencies |
++------------------------+-----------------------------------------------------+
+| max[direct]=0 | Recommended for projects with dependencies |
+| | that fail to keep up with new deprecations. |
++------------------------+-----------------------------------------------------+
+| max[self]=0 | Recommended for libraries that use |
+| | the deprecation system themselves and |
+| | cannot afford to use one of the modes above. |
++------------------------+-----------------------------------------------------+
+
+Ignoring Deprecations
+.....................
+
+If your application has some deprecations that you can't fix for some reasons,
+you can tell Symfony to ignore them.
+
+You need first to create a text file where each line is a deprecation to ignore
+defined as a regular expression. Lines beginning with a hash (``#``) are
+considered comments:
+
+.. code-block:: terminal
+
+ # This file contains patterns to be ignored while testing for use of
+ # deprecated code.
+
+ %The "Symfony\\Component\\Validator\\Context\\ExecutionContextInterface::.*\(\)" method is considered internal Used by the validator engine\. (Should not be called by user\W+code\. )?It may change without further notice\. You should not extend it from "[^"]+"\.%
+ %The "PHPUnit\\Framework\\TestCase::addWarning\(\)" method is considered internal%
+
+Then, you can run the following command to use that file and ignore those deprecations:
+
+.. code-block:: terminal
+
+ $ SYMFONY_DEPRECATIONS_HELPER='ignoreFile=./tests/baseline-ignore' ./vendor/bin/simple-phpunit
+
+Baseline Deprecations
+.....................
+
+You can also take a snapshot of deprecations currently triggered by your application
+code, and ignore those during your test runs, still reporting newly added ones.
+The trick is to create a file with the allowed deprecations and define it as the
+"deprecation baseline". Deprecations inside that file are ignored but the rest of
+deprecations are still reported.
+
+First, generate the file with the allowed deprecations (run the same command
+whenever you want to update the existing file):
+
+.. code-block:: terminal
+
+ $ SYMFONY_DEPRECATIONS_HELPER='generateBaseline=true&baselineFile=./tests/allowed.json' ./vendor/bin/simple-phpunit
+
+This command stores all the deprecations reported while running tests in the
+given file path and encoded in JSON.
+
+Then, you can run the following command to use that file and ignore those deprecations:
+
+.. code-block:: terminal
+
+ $ SYMFONY_DEPRECATIONS_HELPER='baselineFile=./tests/allowed.json' ./vendor/bin/simple-phpunit
+
+Disabling the Verbose Output
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, the bridge will display a detailed output with the number of
+deprecations and where they arise. If this is too much for you, you can use
+``SYMFONY_DEPRECATIONS_HELPER=verbose=0`` to turn the verbose output off.
+
+It's also possible to change verbosity per deprecation type. For example, using
+``quiet[]=indirect&quiet[]=other`` will hide details for deprecations of types
+"indirect" and "other".
+
+The ``quiet`` option hides details for the specified deprecation types, but will
+not change the outcome in terms of exit code. That's what :ref:`max `
+is for, and both settings are orthogonal.
+
+Disabling the Deprecation Helper
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Set the ``SYMFONY_DEPRECATIONS_HELPER`` environment variable to ``disabled=1``
+to completely disable the deprecation helper. This is useful to make use of the
+rest of features provided by this component without getting errors or messages
+related to deprecations.
+
+Deprecation Notices at Autoloading Time
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, the PHPUnit Bridge uses ``DebugClassLoader`` from the
+`ErrorHandler component`_ to throw deprecation notices at class autoloading
+time. This can be disabled with the ``debug-class-loader`` option.
+
+.. code-block:: xml
+
+
+
+
+
+
+
+
+ 0
+
+
+
+
+
+Compile-time Deprecations
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Use the ``debug:container`` command to list the deprecations generated during
+the compiling and warming up of the container:
+
+.. code-block:: terminal
+
+ $ php bin/console debug:container --deprecations
+
+Log Deprecations
+~~~~~~~~~~~~~~~~
+
+For turning the verbose output off and write it to a log file instead you can use
+``SYMFONY_DEPRECATIONS_HELPER='logFile=/path/deprecations.log'``.
+
+Setting The Locale For Tests
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, the PHPUnit Bridge forces the locale to ``C`` to avoid locale
+issues in tests. This behavior can be changed by setting the
+``SYMFONY_PHPUNIT_LOCALE`` environment variable to the desired locale:
+
+.. code-block:: bash
+
+ # .env.test
+ SYMFONY_PHPUNIT_LOCALE="fr_FR"
+
+Alternatively, you can set this environment variable in the PHPUnit
+configuration file:
+
+.. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+Finally, if you want to avoid the bridge to force any locale, you can set the
+``SYMFONY_PHPUNIT_LOCALE`` environment variable to ``0``.
+
+.. _write-assertions-about-deprecations:
+
+Write Assertions about Deprecations
+-----------------------------------
+
+When adding deprecations to your code, you might like writing tests that verify
+that they are triggered as required. To do so, the bridge provides the
+``expectDeprecation()`` method that you can use on your test methods.
+It requires you to pass the expected message, given in the same format as for
+the `PHPUnit's assertStringMatchesFormat()`_ method. If you expect more than one
+deprecation message for a given test method, you can use the method several
+times (order matters)::
+
+ use PHPUnit\Framework\TestCase;
+ use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
+
+ class MyTest extends TestCase
+ {
+ use ExpectDeprecationTrait;
+
+ /**
+ * @group legacy
+ */
+ public function testDeprecatedCode(): void
+ {
+ // test some code that triggers the following deprecation:
+ // trigger_deprecation('vendor-name/package-name', '5.1', 'This "Foo" method is deprecated.');
+ $this->expectDeprecation('Since vendor-name/package-name 5.1: This "%s" method is deprecated');
+
+ // ...
+
+ // test some code that triggers the following deprecation:
+ // trigger_deprecation('vendor-name/package-name', '4.4', 'The second argument of the "Bar" method is deprecated.');
+ $this->expectDeprecation('Since vendor-name/package-name 4.4: The second argument of the "%s" method is deprecated.');
+ }
+ }
+
+Display the Full Stack Trace
+----------------------------
+
+By default, the PHPUnit Bridge displays only deprecation messages.
+To show the full stack trace related to a deprecation, set the value of ``SYMFONY_DEPRECATIONS_HELPER``
+to a regular expression matching the deprecation message.
+
+For example, if the following deprecation notice is thrown:
+
+.. code-block:: bash
+
+ 1x: Doctrine\Common\ClassLoader is deprecated.
+ 1x in EntityTypeTest::setUp from Symfony\Bridge\Doctrine\Tests\Form\Type
+
+Running the following command will display the full stack trace:
+
+.. code-block:: terminal
+
+ $ SYMFONY_DEPRECATIONS_HELPER='/Doctrine\\Common\\ClassLoader is deprecated\./' ./vendor/bin/simple-phpunit
+
+Testing with Multiple PHPUnit Versions
+--------------------------------------
+
+When testing a library that has to be compatible with several versions of PHP,
+the test suite cannot use the latest versions of PHPUnit because:
+
+* PHPUnit 8 deprecated several methods in favor of other methods which are not
+ available in older versions (e.g. PHPUnit 4);
+* PHPUnit 8 added the ``void`` return type to the ``setUp()`` method, which is
+ not compatible with PHP 5.5;
+* PHPUnit switched to namespaced classes starting from PHPUnit 6, so tests must
+ work with and without namespaces.
+
+Polyfills for the Unavailable Methods
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When using the ``simple-phpunit`` script, PHPUnit Bridge injects polyfills for
+most methods of the ``TestCase`` and ``Assert`` classes (e.g. ``expectException()``,
+``expectExceptionMessage()``, ``assertContainsEquals()``, etc.). This allows writing
+test cases using the latest best practices while still remaining compatible with
+older PHPUnit versions.
+
+Removing the Void Return Type
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When running the ``simple-phpunit`` script with the ``SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT``
+environment variable set to ``1``, the PHPUnit bridge will alter the code of
+PHPUnit to remove the return type (introduced in PHPUnit 8) from ``setUp()``,
+``tearDown()``, ``setUpBeforeClass()`` and ``tearDownAfterClass()`` methods.
+This allows you to write a test compatible with both PHP 5 and PHPUnit 8.
+
+Using Namespaced PHPUnit Classes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The PHPUnit bridge adds namespaced class aliases for most of the PHPUnit classes
+declared without namespaces (e.g. ``PHPUnit_Framework_Assert``), allowing you to
+always use the namespaced class declaration even when the test is executed with
+PHPUnit 4.
+
+Time-sensitive Tests
+--------------------
+
+Use Case
+~~~~~~~~
+
+If you have this kind of time-related tests::
+
+ use PHPUnit\Framework\TestCase;
+ use Symfony\Component\Stopwatch\Stopwatch;
+
+ class MyTest extends TestCase
+ {
+ public function testSomething(): void
+ {
+ $stopwatch = new Stopwatch();
+
+ $stopwatch->start('event_name');
+ sleep(10);
+ $duration = $stopwatch->stop('event_name')->getDuration();
+
+ $this->assertEquals(10000, $duration);
+ }
+ }
+
+You calculated the duration time of your process using the Stopwatch utilities to
+:ref:`profile Symfony applications `. However, depending
+on the load of the server or the processes running on your local machine, the
+``$duration`` could for example be ``10.000023s`` instead of ``10s``.
+
+This kind of tests are called transient tests: they are failing randomly
+depending on spurious and external circumstances. They are often cause trouble
+when using public continuous integration services like `Travis CI`_.
+
+Clock Mocking
+~~~~~~~~~~~~~
+
+The :class:`Symfony\\Bridge\\PhpUnit\\ClockMock` class provided by this bridge
+allows you to mock the PHP's built-in time functions ``time()``, ``microtime()``,
+``sleep()``, ``usleep()``, ``gmdate()``, and ``hrtime()``. Additionally the
+function ``date()`` is mocked so it uses the mocked time if no timestamp is
+specified.
+
+Other functions with an optional timestamp parameter that defaults to ``time()``
+will still use the system time instead of the mocked time. This means that you
+may need to change some code in your tests. For example, instead of ``new DateTime()``,
+you should use ``DateTime::createFromFormat('U', (string) time())`` to use the mocked
+``time()`` function.
+
+To use the ``ClockMock`` class in your test, add the ``@group time-sensitive``
+annotation to its class or methods. This annotation only works when executing
+PHPUnit using the ``vendor/bin/simple-phpunit`` script or when registering the
+following listener in your PHPUnit configuration:
+
+.. code-block:: xml
+
+
+
+
+
+
+
+.. note::
+
+ If you don't want to use the ``@group time-sensitive`` annotation, you can
+ register the ``ClockMock`` class manually by calling
+ ``ClockMock::register(__CLASS__)`` and ``ClockMock::withClockMock(true)``
+ before the test and ``ClockMock::withClockMock(false)`` after the test.
+
+As a result, the following is guaranteed to work and is no longer a transient
+test::
+
+ use PHPUnit\Framework\TestCase;
+ use Symfony\Component\Stopwatch\Stopwatch;
+
+ /**
+ * @group time-sensitive
+ */
+ class MyTest extends TestCase
+ {
+ public function testSomething(): void
+ {
+ $stopwatch = new Stopwatch();
+
+ $stopwatch->start('event_name');
+ sleep(10);
+ $duration = $stopwatch->stop('event_name')->getDuration();
+
+ $this->assertEquals(10000, $duration);
+ }
+ }
+
+And that's all!
+
+.. warning::
+
+ Time-based function mocking follows the `PHP namespace resolutions rules`_
+ so "fully qualified function calls" (e.g ``\time()``) cannot be mocked.
+
+The ``@group time-sensitive`` annotation is equivalent to calling
+``ClockMock::register(MyTest::class)``. If you want to mock a function used in a
+different class, do it explicitly using ``ClockMock::register(MyClass::class)``::
+
+ // the class that uses the time() function to be mocked
+ namespace App;
+
+ class MyClass
+ {
+ public function getTimeInHours(): void
+ {
+ return time() / 3600;
+ }
+ }
+
+ // the test that mocks the external time() function explicitly
+ namespace App\Tests;
+
+ use App\MyClass;
+ use PHPUnit\Framework\TestCase;
+ use Symfony\Bridge\PhpUnit\ClockMock;
+
+ /**
+ * @group time-sensitive
+ */
+ class MyTest extends TestCase
+ {
+ public function testGetTimeInHours(): void
+ {
+ ClockMock::register(MyClass::class);
+
+ $my = new MyClass();
+ $result = $my->getTimeInHours();
+
+ $this->assertEquals(time() / 3600, $result);
+ }
+ }
+
+.. tip::
+
+ An added bonus of using the ``ClockMock`` class is that time passes
+ instantly. Using PHP's ``sleep(10)`` will make your test wait for 10
+ actual seconds (more or less). In contrast, the ``ClockMock`` class
+ advances the internal clock the given number of seconds without actually
+ waiting that time, so your test will execute 10 seconds faster.
+
+DNS-sensitive Tests
+-------------------
+
+Tests that make network connections, for example to check the validity of a DNS
+record, can be slow to execute and unreliable due to the conditions of the
+network. For that reason, this component also provides mocks for these PHP
+functions:
+
+* :phpfunction:`checkdnsrr`
+* :phpfunction:`dns_check_record`
+* :phpfunction:`getmxrr`
+* :phpfunction:`dns_get_mx`
+* :phpfunction:`gethostbyaddr`
+* :phpfunction:`gethostbyname`
+* :phpfunction:`gethostbynamel`
+* :phpfunction:`dns_get_record`
+
+Use Case
+~~~~~~~~
+
+Consider the following example that tests a custom class called ``DomainValidator``
+which defines a ``checkDnsRecord`` option to also validate that a domain is
+associated to a valid host::
+
+ use App\Validator\DomainValidator;
+ use PHPUnit\Framework\TestCase;
+
+ class MyTest extends TestCase
+ {
+ public function testEmail(): void
+ {
+ $validator = new DomainValidator(['checkDnsRecord' => true]);
+ $isValid = $validator->validate('example.com');
+
+ // ...
+ }
+ }
+
+In order to avoid making a real network connection, add the ``@group dns-sensitive``
+annotation to the class and use the ``DnsMock::withMockedHosts()`` to configure
+the data you expect to get for the given hosts::
+
+ use App\Validator\DomainValidator;
+ use PHPUnit\Framework\TestCase;
+ use Symfony\Bridge\PhpUnit\DnsMock;
+
+ /**
+ * @group dns-sensitive
+ */
+ class DomainValidatorTest extends TestCase
+ {
+ public function testEmails(): void
+ {
+ DnsMock::withMockedHosts([
+ 'example.com' => [['type' => 'A', 'ip' => '1.2.3.4']],
+ ]);
+
+ $validator = new DomainValidator(['checkDnsRecord' => true]);
+ $isValid = $validator->validate('example.com');
+
+ // ...
+ }
+ }
+
+The ``withMockedHosts()`` method configuration is defined as an array. The keys
+are the mocked hosts and the values are arrays of DNS records in the same format
+returned by :phpfunction:`dns_get_record`, so you can simulate diverse network
+conditions::
+
+ DnsMock::withMockedHosts([
+ 'example.com' => [
+ [
+ 'type' => 'A',
+ 'ip' => '1.2.3.4',
+ ],
+ [
+ 'type' => 'AAAA',
+ 'ipv6' => '::12',
+ ],
+ ],
+ ]);
+
+Class Existence Based Tests
+---------------------------
+
+Tests that behave differently depending on existing classes, for example Composer's
+development dependencies, are often hard to test for the alternate case. For that
+reason, this component also provides mocks for these PHP functions:
+
+* :phpfunction:`class_exists`
+* :phpfunction:`interface_exists`
+* :phpfunction:`trait_exists`
+* :phpfunction:`enum_exists`
+
+Use Case
+~~~~~~~~
+
+Consider the following example that relies on the ``Vendor\DependencyClass`` to
+toggle a behavior::
+
+ use Vendor\DependencyClass;
+
+ class MyClass
+ {
+ public function hello(): string
+ {
+ if (class_exists(DependencyClass::class)) {
+ return 'The dependency behavior.';
+ }
+
+ return 'The default behavior.';
+ }
+ }
+
+A regular test case for ``MyClass`` (assuming the development dependencies
+are installed during tests) would look like::
+
+ use MyClass;
+ use PHPUnit\Framework\TestCase;
+
+ class MyClassTest extends TestCase
+ {
+ public function testHello(): void
+ {
+ $class = new MyClass();
+ $result = $class->hello(); // "The dependency behavior."
+
+ // ...
+ }
+ }
+
+In order to test the default behavior instead use the
+``ClassExistsMock::withMockedClasses()`` to configure the expected
+classes, interfaces and/or traits for the code to run::
+
+ use MyClass;
+ use PHPUnit\Framework\TestCase;
+ use Vendor\DependencyClass;
+
+ class MyClassTest extends TestCase
+ {
+ // ...
+
+ public function testHelloDefault(): void
+ {
+ ClassExistsMock::register(MyClass::class);
+ ClassExistsMock::withMockedClasses([DependencyClass::class => false]);
+
+ $class = new MyClass();
+ $result = $class->hello(); // "The default behavior."
+
+ // ...
+ }
+ }
+
+Note that mocking a class with ``ClassExistsMock::withMockedClasses()``
+will make :phpfunction:`class_exists`, :phpfunction:`interface_exists`
+and :phpfunction:`trait_exists` return true.
+
+To register an enumeration and mock :phpfunction:`enum_exists`,
+``ClassExistsMock::withMockedEnums()`` must be used. Note that, like in
+PHP 8.1 and later, calling ``class_exists`` on a enum will return ``true``.
+That's why calling ``ClassExistsMock::withMockedEnums()`` will also register the enum
+as a mocked class.
+
+Troubleshooting
+---------------
+
+The ``@group time-sensitive`` and ``@group dns-sensitive`` annotations work
+"by convention" and assume that the namespace of the tested class can be
+obtained just by removing the ``Tests\`` part from the test namespace. I.e.
+if your test cases fully-qualified class name (FQCN) is
+``App\Tests\Watch\DummyWatchTest``, it assumes the tested class namespace
+is ``App\Watch``.
+
+If this convention doesn't work for your application, configure the mocked
+namespaces in the ``phpunit.xml`` file, as done for example in the
+:doc:`HttpKernel Component `:
+
+.. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+ Symfony\Component\HttpFoundation
+
+
+
+
+
+
+Under the hood, a PHPUnit listener injects the mocked functions in the tested
+classes' namespace. In order to work as expected, the listener has to run before
+the tested class ever runs.
+
+By default, the mocked functions are created when the annotation are found and
+the corresponding tests are run. Depending on how your tests are constructed,
+this might be too late.
+
+You can either:
+
+* Declare the namespaces of the tested classes in your ``phpunit.xml.dist``;
+* Register the namespaces at the end of the ``config/bootstrap.php`` file.
+
+.. code-block:: xml
+
+
+
+
+
+
+
+ Acme\MyClassTest
+
+
+
+
+
+::
+
+ // config/bootstrap.php
+ use Symfony\Bridge\PhpUnit\ClockMock;
+
+ // ...
+ if ('test' === $_SERVER['APP_ENV']) {
+ ClockMock::register('Acme\\MyClassTest\\');
+ }
+
+Modified PHPUnit script
+-----------------------
+
+This bridge provides a modified version of PHPUnit that you can call by using
+its ``bin/simple-phpunit`` command. It has the following features:
+
+* Works with a standalone vendor directory that doesn't conflict with yours;
+* Does not embed ``prophecy`` to prevent any conflicts with its dependencies;
+* Collects and replays skipped tests when the ``SYMFONY_PHPUNIT_SKIPPED_TESTS``
+ env var is defined: the env var should specify a file name that will be used for
+ storing skipped tests on a first run, and replay them on the second run;
+* Parallelizes test suites execution when given a directory as argument, scanning
+ this directory for ``phpunit.xml.dist`` files up to ``SYMFONY_PHPUNIT_MAX_DEPTH``
+ levels (specified as an env var, defaults to ``3``);
+
+The script writes the modified PHPUnit it builds in a directory that can be
+configured by the ``SYMFONY_PHPUNIT_DIR`` env var, or in the same directory as
+the ``simple-phpunit`` if it is not provided. It's also possible to set this
+env var in the ``phpunit.xml.dist`` file.
+
+If you have installed the bridge through Composer, you can run it by calling e.g.:
+
+.. code-block:: terminal
+
+ $ vendor/bin/simple-phpunit
+
+.. tip::
+
+ It's possible to change the PHPUnit version by setting the
+ ``SYMFONY_PHPUNIT_VERSION`` env var in the ``phpunit.xml.dist`` file (e.g.
+ ````). This is the
+ preferred method as it can be committed to your version control repository.
+
+ It's also possible to set ``SYMFONY_PHPUNIT_VERSION`` as a real env var
+ (not defined in a :ref:`dotenv file `).
+
+ In the same way, ``SYMFONY_MAX_PHPUNIT_VERSION`` will set the maximum version
+ of PHPUnit to be considered. This is useful when testing a framework that does
+ not support the latest version(s) of PHPUnit.
+
+.. tip::
+
+ If you still need to use ``prophecy`` (but not ``symfony/yaml``),
+ then set the ``SYMFONY_PHPUNIT_REMOVE`` env var to ``symfony/yaml``.
+
+ It's also possible to set this env var in the ``phpunit.xml.dist`` file.
+
+.. tip::
+
+ It is also possible to require additional packages that will be installed along
+ with the rest of the needed PHPUnit packages using the ``SYMFONY_PHPUNIT_REQUIRE``
+ env variable. This is specially useful for installing PHPUnit plugins without
+ having to add them to your main ``composer.json`` file. The required packages
+ need to be separated with a space.
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+Code Coverage Listener
+----------------------
+
+By default, the code coverage is computed with the following rule: if a line of
+code is executed, then it is marked as covered. The test which executes a
+line of code is therefore marked as "covering the line of code". This can be
+misleading.
+
+Consider the following example::
+
+ class Bar
+ {
+ public function barMethod(): string
+ {
+ return 'bar';
+ }
+ }
+
+ class Foo
+ {
+ public function __construct(
+ private Bar $bar,
+ ) {
+ }
+
+ public function fooMethod(): string
+ {
+ $this->bar->barMethod();
+
+ return 'bar';
+ }
+ }
+
+ class FooTest extends PHPUnit\Framework\TestCase
+ {
+ public function test(): void
+ {
+ $bar = new Bar();
+ $foo = new Foo($bar);
+
+ $this->assertSame('bar', $foo->fooMethod());
+ }
+ }
+
+The ``FooTest::test`` method executes every single line of code of both ``Foo``
+and ``Bar`` classes, but ``Bar`` is not truly tested. The ``CoverageListener``
+aims to fix this behavior by adding the appropriate `@covers`_ annotation on
+each test class.
+
+If a test class already defines the ``@covers`` annotation, this listener does
+nothing. Otherwise, it tries to find the code related to the test by removing
+the ``Test`` part of the classname: ``My\Namespace\Tests\FooTest`` ->
+``My\Namespace\Foo``.
+
+Installation
+~~~~~~~~~~~~
+
+Add the following configuration to the ``phpunit.xml.dist`` file:
+
+.. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+If the logic used to find the related code is too simple or doesn't work for
+your application, you can use your own SUT (System Under Test) solver:
+
+.. code-block:: xml
+
+
+
+
+ My\Namespace\SutSolver::solve
+
+
+
+
+The ``My\Namespace\SutSolver::solve`` can be any PHP callable and receives the
+current test as its first argument.
+
+Finally, the listener can also display warning messages when the SUT solver does
+not find the SUT:
+
+.. code-block:: xml
+
+
+
+
+
+ true
+
+
+
+
+.. _`PHPUnit`: https://phpunit.de
+.. _`PHPUnit event listener`: https://docs.phpunit.de/en/10.0/extending-phpunit.html#phpunit-s-event-system
+.. _`ErrorHandler component`: https://github.com/symfony/error-handler
+.. _`PHPUnit's assertStringMatchesFormat()`: https://docs.phpunit.de/en/9.6/assertions.html#assertstringmatchesformat
+.. _`PHP error handler`: https://www.php.net/manual/en/book.errorfunc.php
+.. _`environment variable`: https://docs.phpunit.de/en/9.6/configuration.html#the-env-element
+.. _`@-silencing operator`: https://www.php.net/manual/en/language.operators.errorcontrol.php
+.. _`Travis CI`: https://travis-ci.org/
+.. _`test listener`: https://docs.phpunit.de/en/9.6/configuration.html#the-extensions-element
+.. _`@covers`: https://docs.phpunit.de/en/9.6/annotations.html#covers
+.. _`PHP namespace resolutions rules`: https://www.php.net/manual/en/language.namespaces.rules.php
diff --git a/components/process.rst b/components/process.rst
index b109ea6d0d7..7552537e82e 100644
--- a/components/process.rst
+++ b/components/process.rst
@@ -1,137 +1,466 @@
-.. index::
- single: Process
- single: Components; Process
-
The Process Component
=====================
- The Process Component executes commands in sub-processes.
+ The Process component executes commands in sub-processes.
Installation
------------
-You can install the component in many different ways:
+.. code-block:: terminal
+
+ $ composer require symfony/process
-* Use the official Git repository (https://github.com/symfony/Process);
-* :doc:`Install it via Composer` (``symfony/process`` on `Packagist`_).
+.. include:: /components/require_autoload.rst.inc
Usage
-----
-The :class:`Symfony\\Component\\Process\\Process` class allows you to execute
-a command in a sub-process::
+The :class:`Symfony\\Component\\Process\\Process` class executes a command in a
+sub-process, taking care of the differences between operating system and
+escaping arguments to prevent security issues. It replaces PHP functions like
+:phpfunction:`exec`, :phpfunction:`passthru`, :phpfunction:`shell_exec` and
+:phpfunction:`system`::
+ use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;
- $process = new Process('ls -lsa');
+ $process = new Process(['ls', '-lsa']);
$process->run();
// executes after the command finishes
if (!$process->isSuccessful()) {
- throw new \RuntimeException($process->getErrorOutput());
+ throw new ProcessFailedException($process);
}
- print $process->getOutput();
-
-The component takes care of the subtle differences between the different platforms
-when executing the command.
-
-.. versionadded:: 2.2
- The ``getIncrementalOutput()`` and ``getIncrementalErrorOutput()`` methods were added in Symfony 2.2.
+ echo $process->getOutput();
-The ``getOutput()`` method always return the whole content of the standard
+The ``getOutput()`` method always returns the whole content of the standard
output of the command and ``getErrorOutput()`` the content of the error
output. Alternatively, the :method:`Symfony\\Component\\Process\\Process::getIncrementalOutput`
and :method:`Symfony\\Component\\Process\\Process::getIncrementalErrorOutput`
-methods returns the new outputs since the last call.
+methods return the new output since the last call.
+
+The :method:`Symfony\\Component\\Process\\Process::clearOutput` method clears
+the contents of the output and
+:method:`Symfony\\Component\\Process\\Process::clearErrorOutput` clears
+the contents of the error output.
+
+You can also use the :class:`Symfony\\Component\\Process\\Process` class with the
+for each construct to get the output while it is generated. By default, the loop waits
+for new output before going to the next iteration::
+
+ $process = new Process(['ls', '-lsa']);
+ $process->start();
+
+ foreach ($process as $type => $data) {
+ if ($process::OUT === $type) {
+ echo "\nRead from stdout: ".$data;
+ } else { // $process::ERR === $type
+ echo "\nRead from stderr: ".$data;
+ }
+ }
+
+.. tip::
+
+ The Process component internally uses a PHP iterator to get the output while
+ it is generated. That iterator is exposed via the ``getIterator()`` method
+ to allow customizing its behavior::
+
+ $process = new Process(['ls', '-lsa']);
+ $process->start();
+ $iterator = $process->getIterator($process::ITER_SKIP_ERR | $process::ITER_KEEP_OUTPUT);
+ foreach ($iterator as $data) {
+ echo $data."\n";
+ }
+
+The ``mustRun()`` method is identical to ``run()``, except that it will throw
+a :class:`Symfony\\Component\\Process\\Exception\\ProcessFailedException`
+if the process couldn't be executed successfully (i.e. the process exited
+with a non-zero code)::
+
+ use Symfony\Component\Process\Exception\ProcessFailedException;
+ use Symfony\Component\Process\Process;
+
+ $process = new Process(['ls', '-lsa']);
+
+ try {
+ $process->mustRun();
+
+ echo $process->getOutput();
+ } catch (ProcessFailedException $exception) {
+ echo $exception->getMessage();
+ }
+
+.. tip::
+
+ You can get the last output time in seconds by using the
+ :method:`Symfony\\Component\\Process\\Process::getLastOutputTime` method.
+ This method returns ``null`` if the process wasn't started!
+
+Configuring Process Options
+---------------------------
+
+Symfony uses the PHP :phpfunction:`proc_open` function to run the processes.
+You can configure the options passed to the ``other_options`` argument of
+``proc_open()`` using the ``setOptions()`` method::
+
+ $process = new Process(['...', '...', '...']);
+ // this option allows a subprocess to continue running after the main script exited
+ $process->setOptions(['create_new_console' => true]);
+
+.. warning::
+
+ Most of the options defined by ``proc_open()`` (such as ``create_new_console``
+ and ``suppress_errors``) are only supported on Windows operating systems.
+ Check out the `PHP documentation for proc_open()`_ before using them.
+
+.. _process-using-features-from-the-os-shell:
+
+Using Features From the OS Shell
+--------------------------------
+
+Using an array of arguments is the recommended way to define commands. This
+saves you from any escaping and allows sending signals seamlessly
+(e.g. to stop processes while they run)::
+
+ $process = new Process(['/path/command', '--option', 'argument', 'etc.']);
+ $process = new Process(['/path/to/php', '--define', 'memory_limit=1024M', '/path/to/script.php']);
+
+If you need to use stream redirections, conditional execution, or any other
+feature provided by the shell of your operating system, you can also define
+commands as strings using the
+:method:`Symfony\\Component\\Process\\Process::fromShellCommandline` static
+factory.
+
+Each operating system provides a different syntax for their command-lines,
+so it becomes your responsibility to deal with escaping and portability.
+
+When using strings to define commands, variable arguments are passed as
+environment variables using the second argument of the ``run()``,
+``mustRun()`` or ``start()`` methods. Referencing them is also OS-dependent::
+
+ // On Unix-like OSes (Linux, macOS)
+ $process = Process::fromShellCommandline('echo "$MESSAGE"');
+
+ // On Windows
+ $process = Process::fromShellCommandline('echo "!MESSAGE!"');
+
+ // On both Unix-like and Windows
+ $process->run(null, ['MESSAGE' => 'Something to output']);
+
+If you prefer to create portable commands that are independent from the
+operating system, you can write the above command as follows::
+
+ // works the same on Windows , Linux and macOS
+ $process = Process::fromShellCommandline('echo "${:MESSAGE}"');
+
+Portable commands require using a syntax that is specific to the component: when
+enclosing a variable name into ``"${:`` and ``}"`` exactly, the process object
+will replace it with its escaped value, or will fail if the variable is not
+found in the list of environment variables attached to the command.
+
+Setting Environment Variables for Processes
+-------------------------------------------
+
+The constructor of the :class:`Symfony\\Component\\Process\\Process` class and
+all of its methods related to executing processes (``run()``, ``mustRun()``,
+``start()``, etc.) allow passing an array of environment variables to set while
+running the process::
-When executing a long running command (like rsync-ing files to a remote
+ $process = new Process(['...'], null, ['ENV_VAR_NAME' => 'value']);
+ $process = Process::fromShellCommandline('...', null, ['ENV_VAR_NAME' => 'value']);
+ $process->run(null, ['ENV_VAR_NAME' => 'value']);
+
+In addition to the env vars passed explicitly, processes inherit all the env
+vars defined in your system. You can prevent this by setting to ``false`` the
+env vars you want to remove::
+
+ $process = new Process(['...'], null, [
+ 'APP_ENV' => false,
+ 'SYMFONY_DOTENV_VARS' => false,
+ ]);
+
+Getting real-time Process Output
+--------------------------------
+
+When executing a long running command (like ``rsync`` to a remote
server), you can give feedback to the end user in real-time by passing an
anonymous function to the
:method:`Symfony\\Component\\Process\\Process::run` method::
use Symfony\Component\Process\Process;
- $process = new Process('ls -lsa');
- $process->run(function ($type, $buffer) {
- if ('err' === $type) {
+ $process = new Process(['ls', '-lsa']);
+ $process->run(function ($type, $buffer): void {
+ if (Process::ERR === $type) {
echo 'ERR > '.$buffer;
} else {
echo 'OUT > '.$buffer;
}
});
-
-.. versionadded:: 2.1
- The non-blocking feature was added in 2.1.
-
+
+.. note::
+
+ This feature won't work as expected in servers using PHP output buffering.
+ In those cases, either disable the `output_buffering`_ PHP option or use the
+ :phpfunction:`ob_flush` PHP function to force sending the output buffer.
+
+Running Processes Asynchronously
+--------------------------------
+
You can also start the subprocess and then let it run asynchronously, retrieving
-output and the status in your main process whenever you need it. Use the
+output and the status in your main process whenever you need it. Use the
:method:`Symfony\\Component\\Process\\Process::start` method to start an asynchronous
process, the :method:`Symfony\\Component\\Process\\Process::isRunning` method
to check if the process is done and the
:method:`Symfony\\Component\\Process\\Process::getOutput` method to get the output::
- $process = new Process('ls -lsa');
+ $process = new Process(['ls', '-lsa']);
$process->start();
-
+
while ($process->isRunning()) {
// waiting for process to finish
}
echo $process->getOutput();
-
+
You can also wait for a process to end if you started it asynchronously and
are done doing other stuff::
- $process = new Process('ls -lsa');
+ $process = new Process(['ls', '-lsa']);
$process->start();
-
+
// ... do other things
-
- $process->wait(function ($type, $buffer) {
- if ('err' === $type) {
+
+ $process->wait();
+
+ // ... do things after the process has finished
+
+.. note::
+
+ The :method:`Symfony\\Component\\Process\\Process::wait` method is blocking,
+ which means that your code will halt at this line until the external
+ process is completed.
+
+.. note::
+
+ If a ``Response`` is sent **before** a child process had a chance to complete,
+ the server process will be killed (depending on your OS). It means that
+ your task will be stopped right away. Running an asynchronous process
+ is not the same as running a process that survives its parent process.
+
+ If you want your process to survive the request/response cycle, you can
+ take advantage of the ``kernel.terminate`` event, and run your command
+ **synchronously** inside this event. Be aware that ``kernel.terminate``
+ is called only if you use PHP-FPM.
+
+.. danger::
+
+ Beware also that if you do that, the said PHP-FPM process will not be
+ available to serve any new request until the subprocess is finished. This
+ means you can quickly block your FPM pool if you're not careful enough.
+ That is why it's generally way better not to do any fancy things even
+ after the request is sent, but to use a job queue instead.
+
+:method:`Symfony\\Component\\Process\\Process::wait` takes one optional argument:
+a callback that is called repeatedly whilst the process is still running, passing
+in the output and its type::
+
+ $process = new Process(['ls', '-lsa']);
+ $process->start();
+
+ $process->wait(function ($type, $buffer): void {
+ if (Process::ERR === $type) {
echo 'ERR > '.$buffer;
} else {
echo 'OUT > '.$buffer;
}
});
+Instead of waiting until the process has finished, you can use the
+:method:`Symfony\\Component\\Process\\Process::waitUntil` method to keep or stop
+waiting based on some PHP logic. The following example starts a long running
+process and checks its output to wait until its fully initialized::
+
+ $process = new Process(['/usr/bin/php', 'slow-starting-server.php']);
+ $process->start();
+
+ // ... do other things
+
+ // waits until the given anonymous function returns true
+ $process->waitUntil(function ($type, $output): bool {
+ return $output === 'Ready. Waiting for commands...';
+ });
+
+ // ... do things after the process is ready
+
+Streaming to the Standard Input of a Process
+--------------------------------------------
+
+Before a process is started, you can specify its standard input using either the
+:method:`Symfony\\Component\\Process\\Process::setInput` method or the 4th argument
+of the constructor. The provided input can be a string, a stream resource or a
+``Traversable`` object::
+
+ $process = new Process(['cat']);
+ $process->setInput('foobar');
+ $process->run();
+
+When this input is fully written to the subprocess standard input, the corresponding
+pipe is closed.
+
+In order to write to a subprocess standard input while it is running, the component
+provides the :class:`Symfony\\Component\\Process\\InputStream` class::
+
+ $input = new InputStream();
+ $input->write('foo');
+
+ $process = new Process(['cat']);
+ $process->setInput($input);
+ $process->start();
+
+ // ... read process output or do other things
+
+ $input->write('bar');
+ $input->close();
+
+ $process->wait();
+
+ // will echo: foobar
+ echo $process->getOutput();
+
+The :method:`Symfony\\Component\\Process\\InputStream::write` method accepts scalars,
+stream resources or ``Traversable`` objects as arguments. As shown in the above example,
+you need to explicitly call the :method:`Symfony\\Component\\Process\\InputStream::close`
+method when you are done writing to the standard input of the subprocess.
+
+Using PHP Streams as the Standard Input of a Process
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The input of a process can also be defined using `PHP streams`_::
+
+ $stream = fopen('php://temporary', 'w+');
+
+ $process = new Process(['cat']);
+ $process->setInput($stream);
+ $process->start();
+
+ fwrite($stream, 'foo');
+
+ // ... read process output or do other things
+
+ fwrite($stream, 'bar');
+ fclose($stream);
+
+ $process->wait();
+
+ // will echo: 'foobar'
+ echo $process->getOutput();
+
+Using TTY and PTY Modes
+-----------------------
+
+All examples above show that your program has control over the input of a
+process (using ``setInput()``) and the output from that process (using
+``getOutput()``). The Process component has two special modes that tweak
+the relationship between your program and the process: teletype (tty) and
+pseudo-teletype (pty).
+
+In TTY mode, you connect the input and output of the process to the input
+and output of your program. This allows for instance to open an editor like
+Vim or Nano as a process. You enable TTY mode by calling
+:method:`Symfony\\Component\\Process\\Process::setTty`::
+
+ $process = new Process(['vim']);
+ $process->setTty(true);
+ $process->run();
+
+ // As the output is connected to the terminal, it is no longer possible
+ // to read or modify the output from the process!
+ dump($process->getOutput()); // null
+
+In PTY mode, your program behaves as a terminal for the process instead of
+a plain input and output. Some programs behave differently when
+interacting with a real terminal instead of another program. For instance,
+some programs prompt for a password when talking with a terminal. Use
+:method:`Symfony\\Component\\Process\\Process::setPty` to enable this
+mode.
+
+Stopping a Process
+------------------
+
+Any asynchronous process can be stopped at any time with the
+:method:`Symfony\\Component\\Process\\Process::stop` method. This method takes
+two arguments: a timeout and a signal. Once the timeout is reached, the signal
+is sent to the running process. The default signal sent to a process is ``SIGKILL``.
+Please read the :ref:`signal documentation below `
+to find out more about signal handling in the Process component::
+
+ $process = new Process(['ls', '-lsa']);
+ $process->start();
+
+ // ... do other things
+
+ $process->stop(3, SIGINT);
+
+Executing PHP Code in Isolation
+-------------------------------
+
If you want to execute some PHP code in isolation, use the ``PhpProcess``
instead::
use Symfony\Component\Process\PhpProcess;
$process = new PhpProcess(<<
+ = 'Hello World' ?>
EOF
);
$process->run();
-.. versionadded:: 2.1
- The ``ProcessBuilder`` class was added in Symfony 2.1.
+Executing a PHP Child Process with the Same Configuration
+---------------------------------------------------------
-To make your code work better on all platforms, you might want to use the
-:class:`Symfony\\Component\\Process\\ProcessBuilder` class instead::
+When you start a PHP process, it uses the default configuration defined in
+your ``php.ini`` file. You can bypass these options with the ``-d`` command line
+option. For example, if ``memory_limit`` is set to ``256M``, you can disable this
+memory limit when running some command like this:
+``php -d memory_limit=-1 bin/console app:my-command``.
- use Symfony\Component\Process\ProcessBuilder;
+However, if you run the command via the Symfony ``Process`` class, PHP will use
+the settings defined in the ``php.ini`` file. You can solve this issue by using
+the :class:`Symfony\\Component\\Process\\PhpSubprocess` class to run the command::
- $builder = new ProcessBuilder(array('ls', '-lsa'));
- $builder->getProcess()->run();
+ use Symfony\Component\Process\Process;
+
+ class MyCommand extends Command
+ {
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ // the memory_limit (and any other config option) of this command is
+ // the one defined in php.ini instead of the new values (optionally)
+ // passed via the '-d' command option
+ $childProcess = new Process(['bin/console', 'cache:pool:prune']);
+
+ // the memory_limit (and any other config option) of this command takes
+ // into account the values (optionally) passed via the '-d' command option
+ $childProcess = new PhpSubprocess(['bin/console', 'cache:pool:prune']);
+ }
+ }
Process Timeout
---------------
-You can limit the amount of time a process takes to complete by setting a
-timeout (in seconds)::
+By default processes have a timeout of 60 seconds, but you can change it passing
+a different timeout (in seconds) to the ``setTimeout()`` method::
use Symfony\Component\Process\Process;
- $process = new Process('ls -lsa');
+ $process = new Process(['ls', '-lsa']);
$process->setTimeout(3600);
$process->run();
If the timeout is reached, a
-:class:`Symfony\\Process\\Exception\\RuntimeException` is thrown.
+:class:`Symfony\\Component\\Process\\Exception\\ProcessTimedOutException` is thrown.
For long running commands, it is your responsibility to perform the timeout
check regularly::
@@ -148,4 +477,141 @@ check regularly::
usleep(200000);
}
-.. _Packagist: https://packagist.org/packages/symfony/process
+.. tip::
+
+ You can get the process start time using the ``getStartTime()`` method.
+
+.. _reference-process-signal:
+
+Process Idle Timeout
+--------------------
+
+In contrast to the timeout of the previous paragraph, the idle timeout only
+considers the time since the last output was produced by the process::
+
+ use Symfony\Component\Process\Process;
+
+ $process = new Process(['something-with-variable-runtime']);
+ $process->setTimeout(3600);
+ $process->setIdleTimeout(60);
+ $process->run();
+
+In the case above, a process is considered timed out, when either the total runtime
+exceeds 3600 seconds, or the process does not produce any output for 60 seconds.
+
+Process Signals
+---------------
+
+When running a program asynchronously, you can send it POSIX signals with the
+:method:`Symfony\\Component\\Process\\Process::signal` method::
+
+ use Symfony\Component\Process\Process;
+
+ $process = new Process(['find', '/', '-name', 'rabbit']);
+ $process->start();
+
+ // will send a SIGKILL to the process
+ $process->signal(SIGKILL);
+
+You can make the process ignore signals by using the
+:method:`Symfony\\Component\\Process\\Process::setIgnoredSignals`
+method. The given signals won't be propagated to the child process::
+
+ use Symfony\Component\Process\Process;
+
+ $process = new Process(['find', '/', '-name', 'rabbit']);
+ $process->setIgnoredSignals([SIGKILL, SIGUSR1]);
+
+.. versionadded:: 7.1
+
+ The :method:`Symfony\\Component\\Process\\Process::setIgnoredSignals`
+ method was introduced in Symfony 7.1.
+
+Process Pid
+-----------
+
+You can access the `pid`_ of a running process with the
+:method:`Symfony\\Component\\Process\\Process::getPid` method::
+
+ use Symfony\Component\Process\Process;
+
+ $process = new Process(['/usr/bin/php', 'worker.php']);
+ $process->start();
+
+ $pid = $process->getPid();
+
+Disabling Output
+----------------
+
+As standard output and error output are always fetched from the underlying process,
+it might be convenient to disable output in some cases to save memory.
+Use :method:`Symfony\\Component\\Process\\Process::disableOutput` and
+:method:`Symfony\\Component\\Process\\Process::enableOutput` to toggle this feature::
+
+ use Symfony\Component\Process\Process;
+
+ $process = new Process(['/usr/bin/php', 'worker.php']);
+ $process->disableOutput();
+ $process->run();
+
+.. warning::
+
+ You cannot enable or disable the output while the process is running.
+
+ If you disable the output, you cannot access ``getOutput()``,
+ ``getIncrementalOutput()``, ``getErrorOutput()``, ``getIncrementalErrorOutput()`` or
+ ``setIdleTimeout()``.
+
+ However, it is possible to pass a callback to the ``start``, ``run`` or ``mustRun``
+ methods to handle process output in a streaming fashion.
+
+Finding an Executable
+---------------------
+
+The Process component provides a utility class called
+:class:`Symfony\\Component\\Process\\ExecutableFinder` which finds
+and returns the absolute path of an executable::
+
+ use Symfony\Component\Process\ExecutableFinder;
+
+ $executableFinder = new ExecutableFinder();
+ $chromedriverPath = $executableFinder->find('chromedriver');
+ // $chromedriverPath = '/usr/local/bin/chromedriver' (the result will be different on your computer)
+
+The :method:`Symfony\\Component\\Process\\ExecutableFinder::find` method also takes extra parameters to specify a default value
+to return and extra directories where to look for the executable::
+
+ use Symfony\Component\Process\ExecutableFinder;
+
+ $executableFinder = new ExecutableFinder();
+ $chromedriverPath = $executableFinder->find('chromedriver', '/path/to/chromedriver', ['local-bin/']);
+
+Finding the Executable PHP Binary
+---------------------------------
+
+This component also provides a special utility class called
+:class:`Symfony\\Component\\Process\\PhpExecutableFinder` which returns the
+absolute path of the executable PHP binary available on your server::
+
+ use Symfony\Component\Process\PhpExecutableFinder;
+
+ $phpBinaryFinder = new PhpExecutableFinder();
+ $phpBinaryPath = $phpBinaryFinder->find();
+ // $phpBinaryPath = '/usr/local/bin/php' (the result will be different on your computer)
+
+Checking for TTY Support
+------------------------
+
+Another utility provided by this component is a method called
+:method:`Symfony\\Component\\Process\\Process::isTtySupported` which returns
+whether `TTY`_ is supported on the current operating system::
+
+ use Symfony\Component\Process\Process;
+
+ $process = (new Process())->setTty(Process::isTtySupported());
+
+.. _`pid`: https://en.wikipedia.org/wiki/Process_identifier
+.. _`PHP streams`: https://www.php.net/manual/en/book.stream.php
+.. _`output_buffering`: https://www.php.net/manual/en/outcontrol.configuration.php
+.. _`TTY`: https://en.wikipedia.org/wiki/Tty_(unix)
+.. _`PHP documentation for proc_open()`: https://www.php.net/manual/en/function.proc-open.php
diff --git a/components/property_access.rst b/components/property_access.rst
new file mode 100644
index 00000000000..f608640fa9b
--- /dev/null
+++ b/components/property_access.rst
@@ -0,0 +1,589 @@
+The PropertyAccess Component
+============================
+
+ The PropertyAccess component provides functions to read and write from/to an
+ object or array using a simple string notation.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/property-access
+
+.. include:: /components/require_autoload.rst.inc
+
+Usage
+-----
+
+The entry point of this component is the
+:method:`Symfony\\Component\\PropertyAccess\\PropertyAccess::createPropertyAccessor`
+factory. This factory will create a new instance of the
+:class:`Symfony\\Component\\PropertyAccess\\PropertyAccessor` class with the
+default configuration::
+
+ use Symfony\Component\PropertyAccess\PropertyAccess;
+
+ $propertyAccessor = PropertyAccess::createPropertyAccessor();
+
+.. _property-access-reading-arrays:
+
+Reading from Arrays
+-------------------
+
+You can read an array with the
+:method:`Symfony\\Component\\PropertyAccess\\PropertyAccessor::getValue` method.
+This is done using the index notation that is used in PHP::
+
+ // ...
+ $person = [
+ 'first_name' => 'Wouter',
+ ];
+
+ var_dump($propertyAccessor->getValue($person, '[first_name]')); // 'Wouter'
+ var_dump($propertyAccessor->getValue($person, '[age]')); // null
+
+As you can see, the method will return ``null`` if the index does not exist.
+But you can change this behavior with the
+:method:`Symfony\\Component\\PropertyAccess\\PropertyAccessorBuilder::enableExceptionOnInvalidIndex`
+method::
+
+ // ...
+ $propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
+ ->enableExceptionOnInvalidIndex()
+ ->getPropertyAccessor();
+
+ $person = [
+ 'first_name' => 'Wouter',
+ ];
+
+ // instead of returning null, the code now throws an exception of type
+ // Symfony\Component\PropertyAccess\Exception\NoSuchIndexException
+ $value = $propertyAccessor->getValue($person, '[age]');
+
+ // You can avoid the exception by adding the nullsafe operator
+ $value = $propertyAccessor->getValue($person, '[age?]');
+
+You can also use multi dimensional arrays::
+
+ // ...
+ $persons = [
+ [
+ 'first_name' => 'Wouter',
+ ],
+ [
+ 'first_name' => 'Ryan',
+ ],
+ ];
+
+ var_dump($propertyAccessor->getValue($persons, '[0][first_name]')); // 'Wouter'
+ var_dump($propertyAccessor->getValue($persons, '[1][first_name]')); // 'Ryan'
+
+.. tip::
+
+ If the key of the array contains a dot ``.`` or a left square bracket ``[``,
+ you must escape those characters with a backslash. In the above example,
+ if the array key was ``first.name`` instead of ``first_name``, you should
+ access its value as follows::
+
+ var_dump($propertyAccessor->getValue($persons, '[0][first\.name]')); // 'Wouter'
+ var_dump($propertyAccessor->getValue($persons, '[1][first\.name]')); // 'Ryan'
+
+ Right square brackets ``]`` don't need to be escaped in array keys.
+
+Reading from Objects
+--------------------
+
+The ``getValue()`` method is a very robust method, and you can see all of its
+features when working with objects.
+
+Accessing public Properties
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To read from properties, use the "dot" notation::
+
+ // ...
+ $person = new Person();
+ $person->firstName = 'Wouter';
+
+ var_dump($propertyAccessor->getValue($person, 'firstName')); // 'Wouter'
+
+ $child = new Person();
+ $child->firstName = 'Bar';
+ $person->children = [$child];
+
+ var_dump($propertyAccessor->getValue($person, 'children[0].firstName')); // 'Bar'
+
+.. warning::
+
+ Accessing public properties is the last option used by ``PropertyAccessor``.
+ It tries to access the value using the below methods first before using
+ the property directly. For example, if you have a public property that
+ has a getter method, it will use the getter.
+
+Using Getters
+~~~~~~~~~~~~~
+
+The ``getValue()`` method also supports reading using getters. The method will
+be created using common naming conventions for getters. It transforms the
+property name to camelCase (``first_name`` becomes ``FirstName``) and prefixes
+it with ``get``. So the actual method becomes ``getFirstName()``::
+
+ // ...
+ class Person
+ {
+ private string $firstName = 'Wouter';
+
+ public function getFirstName(): string
+ {
+ return $this->firstName;
+ }
+ }
+
+ $person = new Person();
+
+ var_dump($propertyAccessor->getValue($person, 'first_name')); // 'Wouter'
+
+Using Hassers/Issers
+~~~~~~~~~~~~~~~~~~~~
+
+And it doesn't even stop there. If there is no getter found, the accessor will
+look for an isser or hasser. This method is created using the same way as
+getters, this means that you can do something like this::
+
+ // ...
+ class Person
+ {
+ private bool $author = true;
+ private array $children = [];
+
+ public function isAuthor(): bool
+ {
+ return $this->author;
+ }
+
+ public function hasChildren(): bool
+ {
+ return 0 !== count($this->children);
+ }
+ }
+
+ $person = new Person();
+
+ if ($propertyAccessor->getValue($person, 'author')) {
+ var_dump('This person is an author');
+ }
+ if ($propertyAccessor->getValue($person, 'children')) {
+ var_dump('This person has children');
+ }
+
+This will produce: ``This person is an author``
+
+Accessing a non Existing Property Path
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default a :class:`Symfony\\Component\\PropertyAccess\\Exception\\NoSuchPropertyException`
+is thrown if the property path passed to :method:`Symfony\\Component\\PropertyAccess\\PropertyAccessor::getValue`
+does not exist. You can change this behavior using the
+:method:`Symfony\\Component\\PropertyAccess\\PropertyAccessorBuilder::disableExceptionOnInvalidPropertyPath`
+method::
+
+ // ...
+ class Person
+ {
+ public string $name;
+ }
+
+ $person = new Person();
+
+ $propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
+ ->disableExceptionOnInvalidPropertyPath()
+ ->getPropertyAccessor();
+
+ // instead of throwing an exception the following code returns null
+ $value = $propertyAccessor->getValue($person, 'birthday');
+
+Accessing Nullable Property Paths
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Consider the following PHP code::
+
+ class Person
+ {
+ }
+
+ class Comment
+ {
+ public ?Person $person = null;
+ public string $message;
+ }
+
+ $comment = new Comment();
+ $comment->message = 'test';
+
+Given that ``$person`` is nullable, an object graph like ``comment.person.profile``
+will trigger an exception when the ``$person`` property is ``null``. The solution
+is to mark all nullable properties with the nullsafe operator (``?``)::
+
+ // This code throws an exception of type
+ // Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException
+ var_dump($propertyAccessor->getValue($comment, 'person.firstname'));
+
+ // If a property marked with the nullsafe operator is null, the expression is
+ // no longer evaluated and null is returned immediately without throwing an exception
+ var_dump($propertyAccessor->getValue($comment, 'person?.firstname')); // null
+
+.. _components-property-access-magic-get:
+
+Magic ``__get()`` Method
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``getValue()`` method can also use the magic ``__get()`` method::
+
+ // ...
+ class Person
+ {
+ private array $children = [
+ 'Wouter' => [...],
+ ];
+
+ public function __get($id): mixed
+ {
+ return $this->children[$id];
+ }
+
+ public function __isset($id): bool
+ {
+ return isset($this->children[$id]);
+ }
+ }
+
+ $person = new Person();
+
+ var_dump($propertyAccessor->getValue($person, 'Wouter')); // [...]
+
+.. warning::
+
+ When implementing the magic ``__get()`` method, you also need to implement
+ ``__isset()``.
+
+.. _components-property-access-magic-call:
+
+Magic ``__call()`` Method
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Lastly, ``getValue()`` can use the magic ``__call()`` method, but you need to
+enable this feature by using :class:`Symfony\\Component\\PropertyAccess\\PropertyAccessorBuilder`::
+
+ // ...
+ class Person
+ {
+ private array $children = [
+ 'wouter' => [...],
+ ];
+
+ public function __call($name, $args): mixed
+ {
+ $property = lcfirst(substr($name, 3));
+ if ('get' === substr($name, 0, 3)) {
+ return $this->children[$property] ?? null;
+ } elseif ('set' === substr($name, 0, 3)) {
+ $value = 1 == count($args) ? $args[0] : null;
+ $this->children[$property] = $value;
+ }
+ }
+ }
+
+ $person = new Person();
+
+ // enables PHP __call() magic method
+ $propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
+ ->enableMagicCall()
+ ->getPropertyAccessor();
+
+ var_dump($propertyAccessor->getValue($person, 'wouter')); // [...]
+
+.. warning::
+
+ The ``__call()`` feature is disabled by default, you can enable it by calling
+ :method:`Symfony\\Component\\PropertyAccess\\PropertyAccessorBuilder::enableMagicCall`
+ see `Enable other Features`_.
+
+Writing to Arrays
+-----------------
+
+The ``PropertyAccessor`` class can do more than just read an array, it can
+also write to an array. This can be achieved using the
+:method:`Symfony\\Component\\PropertyAccess\\PropertyAccessor::setValue` method::
+
+ // ...
+ $person = [];
+
+ $propertyAccessor->setValue($person, '[first_name]', 'Wouter');
+
+ var_dump($propertyAccessor->getValue($person, '[first_name]')); // 'Wouter'
+ // or
+ // var_dump($person['first_name']); // 'Wouter'
+
+.. _components-property-access-writing-to-objects:
+
+Writing to Objects
+------------------
+
+The ``setValue()`` method has the same features as the ``getValue()`` method. You
+can use setters, the magic ``__set()`` method or properties to set values::
+
+ // ...
+ class Person
+ {
+ public string $firstName;
+ private string $lastName;
+ private array $children = [];
+
+ public function setLastName($name): void
+ {
+ $this->lastName = $name;
+ }
+
+ public function getLastName(): string
+ {
+ return $this->lastName;
+ }
+
+ public function getChildren(): array
+ {
+ return $this->children;
+ }
+
+ public function __set($property, $value): void
+ {
+ $this->$property = $value;
+ }
+ }
+
+ $person = new Person();
+
+ $propertyAccessor->setValue($person, 'firstName', 'Wouter');
+ $propertyAccessor->setValue($person, 'lastName', 'de Jong'); // setLastName is called
+ $propertyAccessor->setValue($person, 'children', [new Person()]); // __set is called
+
+ var_dump($person->firstName); // 'Wouter'
+ var_dump($person->getLastName()); // 'de Jong'
+ var_dump($person->getChildren()); // [Person()];
+
+You can also use ``__call()`` to set values but you need to enable the feature,
+see `Enable other Features`_::
+
+ // ...
+ class Person
+ {
+ private array $children = [];
+
+ public function __call($name, $args): mixed
+ {
+ $property = lcfirst(substr($name, 3));
+ if ('get' === substr($name, 0, 3)) {
+ return $this->children[$property] ?? null;
+ } elseif ('set' === substr($name, 0, 3)) {
+ $value = 1 == count($args) ? $args[0] : null;
+ $this->children[$property] = $value;
+ }
+ }
+
+ }
+
+ $person = new Person();
+
+ // Enable magic __call
+ $propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
+ ->enableMagicCall()
+ ->getPropertyAccessor();
+
+ $propertyAccessor->setValue($person, 'wouter', [...]);
+
+ var_dump($person->getWouter()); // [...]
+
+.. note::
+
+ The ``__set()`` method support is enabled by default.
+ See `Enable other Features`_ if you want to disable it.
+
+Writing to Array Properties
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``PropertyAccessor`` class allows to update the content of arrays stored in
+properties through *adder* and *remover* methods::
+
+ // ...
+ class Person
+ {
+ /**
+ * @var string[]
+ */
+ private array $children = [];
+
+ public function getChildren(): array
+ {
+ return $this->children;
+ }
+
+ public function addChild(string $name): void
+ {
+ $this->children[$name] = $name;
+ }
+
+ public function removeChild(string $name): void
+ {
+ unset($this->children[$name]);
+ }
+ }
+
+ $person = new Person();
+ $propertyAccessor->setValue($person, 'children', ['kevin', 'wouter']);
+
+ var_dump($person->getChildren()); // ['kevin', 'wouter']
+
+The PropertyAccess component checks for methods called ``add()``
+and ``remove()``. Both methods must be defined.
+For instance, in the previous example, the component looks for the ``addChild()``
+and ``removeChild()`` methods to access the ``children`` property.
+`The String component`_ inflector is used to find the singular of a property name.
+
+If available, *adder* and *remover* methods have priority over a *setter* method.
+
+Using non-standard adder/remover methods
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Sometimes, adder and remover methods don't use the standard ``add`` or ``remove`` prefix, like in this example::
+
+ // ...
+ class Team
+ {
+ // ...
+
+ public function joinTeam(string $person): void
+ {
+ $this->team[] = $person;
+ }
+
+ public function leaveTeam(string $person): void
+ {
+ foreach ($this->team as $id => $item) {
+ if ($person === $item) {
+ unset($this->team[$id]);
+
+ break;
+ }
+ }
+ }
+ }
+
+ use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
+ use Symfony\Component\PropertyAccess\PropertyAccessor;
+
+ $list = new Team();
+ $reflectionExtractor = new ReflectionExtractor(null, null, ['join', 'leave']);
+ $propertyAccessor = new PropertyAccessor(PropertyAccessor::DISALLOW_MAGIC_METHODS, PropertyAccessor::THROW_ON_INVALID_PROPERTY_PATH, null, $reflectionExtractor, $reflectionExtractor);
+ $propertyAccessor->setValue($person, 'team', ['kevin', 'wouter']);
+
+ var_dump($person->getTeam()); // ['kevin', 'wouter']
+
+Instead of calling ``add()`` and ``remove()``, the PropertyAccess
+component will call ``join()`` and ``leave()`` methods.
+
+Checking Property Paths
+-----------------------
+
+When you want to check whether
+:method:`Symfony\\Component\\PropertyAccess\\PropertyAccessor::getValue` can
+safely be called without actually calling that method, you can use
+:method:`Symfony\\Component\\PropertyAccess\\PropertyAccessor::isReadable` instead::
+
+ $person = new Person();
+
+ if ($propertyAccessor->isReadable($person, 'firstName')) {
+ // ...
+ }
+
+The same is possible for :method:`Symfony\\Component\\PropertyAccess\\PropertyAccessor::setValue`:
+Call the :method:`Symfony\\Component\\PropertyAccess\\PropertyAccessor::isWritable`
+method to find out whether a property path can be updated::
+
+ $person = new Person();
+
+ if ($propertyAccessor->isWritable($person, 'firstName')) {
+ // ...
+ }
+
+Mixing Objects and Arrays
+-------------------------
+
+You can also mix objects and arrays::
+
+ // ...
+ class Person
+ {
+ public string $firstName;
+ private array $children = [];
+
+ public function setChildren($children): void
+ {
+ $this->children = $children;
+ }
+
+ public function getChildren(): array
+ {
+ return $this->children;
+ }
+ }
+
+ $person = new Person();
+
+ $propertyAccessor->setValue($person, 'children[0]', new Person);
+ // equal to $person->getChildren()[0] = new Person()
+
+ $propertyAccessor->setValue($person, 'children[0].firstName', 'Wouter');
+ // equal to $person->getChildren()[0]->firstName = 'Wouter'
+
+ var_dump('Hello '.$propertyAccessor->getValue($person, 'children[0].firstName')); // 'Wouter'
+ // equal to $person->getChildren()[0]->firstName
+
+Enable other Features
+~~~~~~~~~~~~~~~~~~~~~
+
+The :class:`Symfony\\Component\\PropertyAccess\\PropertyAccessor` can be
+configured to enable extra features. To do that you could use the
+:class:`Symfony\\Component\\PropertyAccess\\PropertyAccessorBuilder`::
+
+ // ...
+ $propertyAccessorBuilder = PropertyAccess::createPropertyAccessorBuilder();
+
+ $propertyAccessorBuilder->enableMagicCall(); // enables magic __call
+ $propertyAccessorBuilder->enableMagicGet(); // enables magic __get
+ $propertyAccessorBuilder->enableMagicSet(); // enables magic __set
+ $propertyAccessorBuilder->enableMagicMethods(); // enables magic __get, __set and __call
+
+ $propertyAccessorBuilder->disableMagicCall(); // disables magic __call
+ $propertyAccessorBuilder->disableMagicGet(); // disables magic __get
+ $propertyAccessorBuilder->disableMagicSet(); // disables magic __set
+ $propertyAccessorBuilder->disableMagicMethods(); // disables magic __get, __set and __call
+
+ // checks if magic __call, __get or __set handling are enabled
+ $propertyAccessorBuilder->isMagicCallEnabled(); // true or false
+ $propertyAccessorBuilder->isMagicGetEnabled(); // true or false
+ $propertyAccessorBuilder->isMagicSetEnabled(); // true or false
+
+ // At the end get the configured property accessor
+ $propertyAccessor = $propertyAccessorBuilder->getPropertyAccessor();
+
+ // Or all in one
+ $propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
+ ->enableMagicCall()
+ ->getPropertyAccessor();
+
+Or you can pass parameters directly to the constructor (not the recommended way)::
+
+ // enable handling of magic __call, __set but not __get:
+ $propertyAccessor = new PropertyAccessor(PropertyAccessor::MAGIC_CALL | PropertyAccessor::MAGIC_SET);
+
+.. _`The String component`: https://github.com/symfony/string
diff --git a/components/property_access/index.rst b/components/property_access/index.rst
deleted file mode 100644
index c40373aaac1..00000000000
--- a/components/property_access/index.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-Property Access
-===============
-
-.. toctree::
- :maxdepth: 2
-
- introduction
diff --git a/components/property_access/introduction.rst b/components/property_access/introduction.rst
deleted file mode 100644
index f4d4de5cb23..00000000000
--- a/components/property_access/introduction.rst
+++ /dev/null
@@ -1,264 +0,0 @@
-.. index::
- single: PropertyAccess
- single: Components; PropertyAccess
-
-The PropertyAccess Component
-============================
-
- The PropertyAccess component provides function to read and write from/to an
- object or array using a simple string notation.
-
-.. versionadded:: 2.2
- The PropertyAccess Component is new to Symfony 2.2. Previously, the
- ``PropertyPath`` class was located in the ``Form`` component.
-
-Installation
-------------
-
-You can install the component in two different ways:
-
-* Use the official Git repository (https://github.com/symfony/PropertyAccess);
-* :doc:`Install it via Composer` (``symfony/property-access`` on `Packagist`_).
-
-Usage
------
-
-The entry point of this component is the
-:method:`PropertyAccess::getPropertyAccessor`
-factory. This factory will create a new instance of the
-:class:`Symfony\\Component\\PropertyAccess\\PropertyAccessor` class with the
-default configuration::
-
- use Symfony\Component\PropertyAccess\PropertyAccess;
-
- $accessor = PropertyAccess::getPropertyAccessor();
-
-Reading from Arrays
--------------------
-
-You can read an array with the
-:method:`PropertyAccessor::getValue`
-method. This is done using the index notation that is used in PHP::
-
- // ...
- $person = array(
- 'first_name' => 'Wouter',
- );
-
- echo $accessor->getValue($person, '[first_name]'); // 'Wouter'
- echo $accessor->getValue($person, '[age]'); // null
-
-As you can see, the method will return ``null`` if the index does not exists.
-
-You can also use multi dimensional arrays::
-
- // ...
- $persons = array(
- array(
- 'first_name' => 'Wouter',
- ),
- array(
- 'first_name' => 'Ryan',
- )
- );
-
- echo $accessor->getValue($persons, '[0][first_name]'); // 'Wouter'
- echo $accessor->getValue($persons, '[1][first_name]'); // 'Ryan'
-
-Reading from Objects
---------------------
-
-The ``getValue`` method is a very robust method, and you can see all of its
-features when working with objects.
-
-Accessing public Properties
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To read from properties, use the "dot" notation::
-
- // ...
- $person = new Person();
- $person->firstName = 'Wouter';
-
- echo $accessor->getValue($person, 'firstName'); // 'Wouter'
-
- $child = new Person();
- $child->firstName = 'Bar';
- $person->children = array($child);
-
- echo $accessor->getValue($person, 'children[0].firstName'); // 'Bar'
-
-.. caution::
-
- Accessing public properties is the last option used by ``PropertyAccessor``.
- It tries to access the value using the below methods first before using
- the property directly. For example, if you have a public property that
- has a getter method, it will use the getter.
-
-Using Getters
-~~~~~~~~~~~~~
-
-The ``getValue`` method also supports reading using getters. The method will
-be created using common naming conventions for getters. It camelizes the
-property name (``first_name`` becomes ``FirstName``) and prefixes it with
-``get``. So the actual method becomes ``getFirstName``::
-
- // ...
- class Person
- {
- private $firstName = 'Wouter';
-
- public function getFirstName()
- {
- return $this->firstName;
- }
- }
-
- $person = new Person();
-
- echo $accessor->getValue($person, 'first_name'); // 'Wouter'
-
-Using Hassers/Issers
-~~~~~~~~~~~~~~~~~~~~
-
-And it doesn't even stop there. If there is no getter found, the accessor will
-look for an isser or hasser. This method is created using the same way as
-getters, this means that you can do something like this::
-
- // ...
- class Person
- {
- private $author = true;
- private $children = array();
-
- public function isAuthor()
- {
- return $this->author;
- }
-
- public function hasChildren()
- {
- return 0 !== count($this->children);
- }
- }
-
- $person = new Person();
-
- if ($accessor->getValue($person, 'author')) {
- echo 'He is an author';
- }
- if ($accessor->getValue($person, 'children')) {
- echo 'He has children';
- }
-
-This will produce: ``He is an author``
-
-Magic Methods
-~~~~~~~~~~~~~
-
-At last, ``getValue`` can use the magic ``__get`` method too::
-
- // ...
- class Person
- {
- private $children = array(
- 'wouter' => array(...),
- );
-
- public function __get($id)
- {
- return $this->children[$id];
- }
- }
-
- $person = new Person();
-
- echo $accessor->getValue($person, 'Wouter'); // array(...)
-
-Writing to Arrays
------------------
-
-The ``PropertyAccessor`` class can do more than just read an array, it can
-also write to an array. This can be achieved using the
-:method:`PropertyAccessor::setValue`
-method::
-
- // ...
- $person = array();
-
- $accessor->setValue($person, '[first_name]', 'Wouter');
-
- echo $accessor->getValue($person, '[first_name]'); // 'Wouter'
- // or
- // echo $person['first_name']; // 'Wouter'
-
-Writing to Objects
-------------------
-
-The ``setValue`` method has the same features as the ``getValue`` method. You
-can use setters, the magic ``__set`` or properties to set values::
-
- // ...
- class Person
- {
- public $firstName;
- private $lastName;
- private $children = array();
-
- public function setLastName($name)
- {
- $this->lastName = $name;
- }
-
- public function __set($property, $value)
- {
- $this->$property = $value;
- }
-
- // ...
- }
-
- $person = new Person();
-
- $accessor->setValue($person, 'firstName', 'Wouter');
- $accessor->setValue($person, 'lastName', 'de Jong');
- $accessor->setValue($person, 'children', array(new Person()));
-
- echo $person->firstName; // 'Wouter'
- echo $person->getLastName(); // 'de Jong'
- echo $person->children; // array(Person());
-
-Mixing Objects and Arrays
--------------------------
-
-You can also mix objects and arrays::
-
- // ...
- class Person
- {
- public $firstName;
- private $children = array();
-
- public function setChildren($children)
- {
- return $this->children;
- }
-
- public function getChildren()
- {
- return $this->children;
- }
- }
-
- $person = new Person();
-
- $accessor->setValue($person, 'children[0]', new Person);
- // equal to $person->getChildren()[0] = new Person()
-
- $accessor->setValue($person, 'children[0].firstName', 'Wouter');
- // equal to $person->getChildren()[0]->firstName = 'Wouter'
-
- echo 'Hello '.$accessor->getValue($person, 'children[0].firstName'); // 'Wouter'
- // equal to $person->getChildren()[0]->firstName
-
-.. _Packagist: https://packagist.org/packages/symfony/property-access
diff --git a/components/property_info.rst b/components/property_info.rst
new file mode 100644
index 00000000000..39019657ced
--- /dev/null
+++ b/components/property_info.rst
@@ -0,0 +1,607 @@
+The PropertyInfo Component
+==========================
+
+ The PropertyInfo component allows you to get information
+ about class properties by using different sources of metadata.
+
+While the :doc:`PropertyAccess component `
+allows you to read and write values to/from objects and arrays, the PropertyInfo
+component works solely with class definitions to provide information about the
+data type and visibility - including via getter or setter methods - of the properties
+within that class.
+
+.. _`components-property-information-installation`:
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/property-info
+
+.. include:: /components/require_autoload.rst.inc
+
+Additional dependencies may be required for some of the
+:ref:`extractors provided with this component `.
+
+.. _`components-property-information-usage`:
+
+Usage
+-----
+
+To use this component, create a new
+:class:`Symfony\\Component\\PropertyInfo\\PropertyInfoExtractor` instance and
+provide it with a set of information extractors::
+
+ use Example\Namespace\YourAwesomeCoolClass;
+ use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
+ use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
+ use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
+
+ // a full list of extractors is shown further below
+ $phpDocExtractor = new PhpDocExtractor();
+ $reflectionExtractor = new ReflectionExtractor();
+
+ // list of PropertyListExtractorInterface (any iterable)
+ $listExtractors = [$reflectionExtractor];
+
+ // list of PropertyTypeExtractorInterface (any iterable)
+ $typeExtractors = [$phpDocExtractor, $reflectionExtractor];
+
+ // list of PropertyDescriptionExtractorInterface (any iterable)
+ $descriptionExtractors = [$phpDocExtractor];
+
+ // list of PropertyAccessExtractorInterface (any iterable)
+ $accessExtractors = [$reflectionExtractor];
+
+ // list of PropertyInitializableExtractorInterface (any iterable)
+ $propertyInitializableExtractors = [$reflectionExtractor];
+
+ $propertyInfo = new PropertyInfoExtractor(
+ $listExtractors,
+ $typeExtractors,
+ $descriptionExtractors,
+ $accessExtractors,
+ $propertyInitializableExtractors
+ );
+
+ // see below for more examples
+ $class = YourAwesomeCoolClass::class;
+ $properties = $propertyInfo->getProperties($class);
+
+Extractor Ordering
+~~~~~~~~~~~~~~~~~~
+
+The order of extractor instances within an array matters: the first non-null
+result will be returned. That is why you must provide each category of extractors
+as a separate array, even if an extractor provides information for more than
+one category.
+
+For example, while the :class:`Symfony\\Component\\PropertyInfo\\Extractor\\ReflectionExtractor`
+and :class:`Symfony\\Bridge\\Doctrine\\PropertyInfo\\DoctrineExtractor`
+both provide list and type information it is probably better that:
+
+* The :class:`Symfony\\Component\\PropertyInfo\\Extractor\\ReflectionExtractor`
+ has priority for list information so that all properties in a class (not
+ just mapped properties) are returned.
+* The :class:`Symfony\\Bridge\\Doctrine\\PropertyInfo\\DoctrineExtractor`
+ has priority for type information so that entity metadata is used instead
+ of type-hinting to provide more accurate type information::
+
+ use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor;
+ use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
+ use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
+
+ $reflectionExtractor = new ReflectionExtractor();
+ $doctrineExtractor = new DoctrineExtractor(/* ... */);
+
+ $propertyInfo = new PropertyInfoExtractor(
+ // List extractors
+ [
+ $reflectionExtractor,
+ $doctrineExtractor
+ ],
+ // Type extractors
+ [
+ $doctrineExtractor,
+ $reflectionExtractor
+ ]
+ );
+
+.. _`components-property-information-extractable-information`:
+
+Extractable Information
+-----------------------
+
+The :class:`Symfony\\Component\\PropertyInfo\\PropertyInfoExtractor`
+class exposes public methods to extract several types of information:
+
+* :ref:`List of properties `: :method:`Symfony\\Component\\PropertyInfo\\PropertyListExtractorInterface::getProperties`
+* :ref:`Property type `: :method:`Symfony\\Component\\PropertyInfo\\PropertyTypeExtractorInterface::getTypes`
+ (including typed properties)
+* :ref:`Property description `: :method:`Symfony\\Component\\PropertyInfo\\PropertyDescriptionExtractorInterface::getShortDescription` and :method:`Symfony\\Component\\PropertyInfo\\PropertyDescriptionExtractorInterface::getLongDescription`
+* :ref:`Property access details `: :method:`Symfony\\Component\\PropertyInfo\\PropertyAccessExtractorInterface::isReadable` and :method:`Symfony\\Component\\PropertyInfo\\PropertyAccessExtractorInterface::isWritable`
+* :ref:`Property initializable through the constructor `: :method:`Symfony\\Component\\PropertyInfo\\PropertyInitializableExtractorInterface::isInitializable`
+
+.. note::
+
+ Be sure to pass a *class* name, not an object to the extractor methods::
+
+ // bad! It may work, but not with all extractors
+ $propertyInfo->getProperties($awesomeObject);
+
+ // Good!
+ $propertyInfo->getProperties(get_class($awesomeObject));
+ $propertyInfo->getProperties('Example\Namespace\YourAwesomeClass');
+ $propertyInfo->getProperties(YourAwesomeClass::class);
+
+.. _property-info-list:
+
+List Information
+~~~~~~~~~~~~~~~~
+
+Extractors that implement :class:`Symfony\\Component\\PropertyInfo\\PropertyListExtractorInterface`
+provide the list of properties that are available on a class as an array
+containing each property name as a string::
+
+ $properties = $propertyInfo->getProperties($class);
+ /*
+ Example Result
+ --------------
+ array(3) {
+ [0] => string(8) "username"
+ [1] => string(8) "password"
+ [2] => string(6) "active"
+ }
+ */
+
+.. _property-info-type:
+
+Type Information
+~~~~~~~~~~~~~~~~
+
+Extractors that implement :class:`Symfony\\Component\\PropertyInfo\\PropertyTypeExtractorInterface`
+provide :ref:`extensive data type information `
+for a property::
+
+ $types = $propertyInfo->getTypes($class, $property);
+ /*
+ Example Result
+ --------------
+ array(1) {
+ [0] =>
+ class Symfony\Component\PropertyInfo\Type (6) {
+ private $builtinType => string(6) "string"
+ private $nullable => bool(false)
+ private $class => NULL
+ private $collection => bool(false)
+ private $collectionKeyType => NULL
+ private $collectionValueType => NULL
+ }
+ }
+ */
+
+See :ref:`components-property-info-type` for info about the ``Type`` class.
+
+Documentation Block
+~~~~~~~~~~~~~~~~~~~
+
+Extractors that implement :class:`Symfony\\Component\\PropertyInfo\\PropertyDocBlockExtractorInterface`
+can provide the full documentation block for a property as a string::
+
+ $docBlock = $propertyInfo->getDocBlock($class, $property);
+ /*
+ Example Result
+ --------------
+ string(79):
+ This is the subsequent paragraph in the DocComment.
+ It can span multiple lines.
+ */
+
+.. versionadded:: 7.1
+
+ The :class:`Symfony\\Component\\PropertyInfo\\PropertyDocBlockExtractorInterface`
+ interface was introduced in Symfony 7.1.
+
+.. _property-info-description:
+
+Description Information
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Extractors that implement :class:`Symfony\\Component\\PropertyInfo\\PropertyDescriptionExtractorInterface`
+provide long and short descriptions from a properties annotations as
+strings::
+
+ $title = $propertyInfo->getShortDescription($class, $property);
+ /*
+ Example Result
+ --------------
+ string(41) "This is the first line of the DocComment."
+ */
+
+ $paragraph = $propertyInfo->getLongDescription($class, $property);
+ /*
+ Example Result
+ --------------
+ string(79):
+ This is the subsequent paragraph in the DocComment.
+ It can span multiple lines.
+ */
+
+.. _property-info-access:
+
+Access Information
+~~~~~~~~~~~~~~~~~~
+
+Extractors that implement :class:`Symfony\\Component\\PropertyInfo\\PropertyAccessExtractorInterface`
+provide whether properties are readable or writable as booleans::
+
+ $propertyInfo->isReadable($class, $property);
+ // Example Result: bool(true)
+
+ $propertyInfo->isWritable($class, $property);
+ // Example Result: bool(false)
+
+The :class:`Symfony\\Component\\PropertyInfo\\Extractor\\ReflectionExtractor` looks
+for getter/isser/setter/hasser method in addition to whether or not a property is public
+to determine if it's accessible. This based on how the :doc:`PropertyAccess `
+works. It assumes camel case style method names following `PSR-1`_. For example,
+both ``myProperty`` and ``my_property`` properties are readable if there's a
+``getMyProperty()`` method and writable if there's a ``setMyProperty()`` method.
+
+.. _property-info-initializable:
+
+Property Initializable Information
+----------------------------------
+
+Extractors that implement :class:`Symfony\\Component\\PropertyInfo\\PropertyInitializableExtractorInterface`
+provide whether properties are initializable through the class's constructor as booleans::
+
+ $propertyInfo->isInitializable($class, $property);
+ // Example Result: bool(true)
+
+:method:`Symfony\\Component\\PropertyInfo\\Extractor\\ReflectionExtractor::isInitializable`
+returns ``true`` if a constructor's parameter of the given class matches the
+given property name.
+
+.. tip::
+
+ The main :class:`Symfony\\Component\\PropertyInfo\\PropertyInfoExtractor`
+ class implements all interfaces, delegating the extraction of property
+ information to the extractors that have been registered with it.
+
+ This means that any method available on each of the extractors is also
+ available on the main :class:`Symfony\\Component\\PropertyInfo\\PropertyInfoExtractor`
+ class.
+
+.. _`components-property-info-type`:
+
+Type Objects
+------------
+
+Compared to the other extractors, type information extractors provide much
+more information than can be represented as simple scalar values. Because
+of this, type extractors return an array of :class:`Symfony\\Component\\PropertyInfo\\Type`
+objects for each type that the property supports.
+
+For example, if a property supports both ``integer`` and ``string`` (via
+the ``@return int|string`` annotation),
+:method:`PropertyInfoExtractor::getTypes() `
+will return an array containing **two** instances of the :class:`Symfony\\Component\\PropertyInfo\\Type`
+class.
+
+.. note::
+
+ Most extractors will return only one :class:`Symfony\\Component\\PropertyInfo\\Type`
+ instance. The :class:`Symfony\\Component\\PropertyInfo\\Extractor\\PhpDocExtractor`
+ is currently the only extractor that returns multiple instances in the array.
+
+Each object will provide 6 attributes, available in the 6 methods:
+
+.. _`components-property-info-type-builtin`:
+
+``Type::getBuiltInType()``
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The :method:`Type::getBuiltinType() `
+method returns the built-in PHP data type, which can be one of these
+string values: ``array``, ``bool``, ``callable``, ``float``, ``int``,
+``iterable``, ``null``, ``object``, ``resource`` or ``string``.
+
+Constants inside the :class:`Symfony\\Component\\PropertyInfo\\Type`
+class, in the form ``Type::BUILTIN_TYPE_*``, are provided for convenience.
+
+``Type::isNullable()``
+~~~~~~~~~~~~~~~~~~~~~~
+
+The :method:`Type::isNullable() `
+method will return a boolean value indicating whether the property parameter
+can be set to ``null``.
+
+``Type::getClassName()``
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+If the :ref:`built-in PHP data type `
+is ``object``, the :method:`Type::getClassName() `
+method will return the fully-qualified class or interface name accepted.
+
+``Type::isCollection()``
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+The :method:`Type::isCollection() `
+method will return a boolean value indicating if the property parameter is
+a collection - a non-scalar value capable of containing other values. Currently
+this returns ``true`` if:
+
+* The :ref:`built-in PHP data type `
+ is ``array``;
+* The mutator method the property is derived from has a prefix of ``add``
+ or ``remove`` (which are defined as the list of array mutator prefixes);
+* The `phpDocumentor`_ annotation is of type "collection" (e.g.
+ ``@var SomeClass``, ``@var SomeClass``,
+ ``@var Doctrine\Common\Collections\Collection``, etc.)
+
+``Type::getCollectionKeyTypes()`` & ``Type::getCollectionValueTypes()``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If the property is a collection, additional type objects may be returned
+for both the key and value types of the collection (if the information is
+available), via the :method:`Type::getCollectionKeyTypes() `
+and :method:`Type::getCollectionValueTypes() `
+methods.
+
+.. note::
+
+ The ``list`` pseudo type is returned by the PropertyInfo component as an
+ array with integer as the key type.
+
+.. _`components-property-info-extractors`:
+
+Extractors
+----------
+
+The extraction of property information is performed by *extractor classes*.
+An extraction class can provide one or more types of property information
+by implementing the correct interface(s).
+
+The :class:`Symfony\\Component\\PropertyInfo\\PropertyInfoExtractor` will
+iterate over the relevant extractor classes in the order they were set, call
+the appropriate method and return the first result that is not ``null``.
+
+.. _`components-property-information-extractors-available`:
+
+While you can create your own extractors, the following are already available
+to cover most use-cases:
+
+ReflectionExtractor
+~~~~~~~~~~~~~~~~~~~
+
+Using PHP reflection, the :class:`Symfony\\Component\\PropertyInfo\\Extractor\\ReflectionExtractor`
+provides list, type and access information from setter and accessor methods.
+It can also give the type of a property (even extracting it from the constructor
+arguments), and if it is initializable through the constructor. It supports
+return and scalar types::
+
+ use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
+
+ $reflectionExtractor = new ReflectionExtractor();
+
+ // List information.
+ $reflectionExtractor->getProperties($class);
+
+ // Type information.
+ $reflectionExtractor->getTypes($class, $property);
+
+ // Access information.
+ $reflectionExtractor->isReadable($class, $property);
+ $reflectionExtractor->isWritable($class, $property);
+
+ // Initializable information
+ $reflectionExtractor->isInitializable($class, $property);
+
+.. note::
+
+ When using the Symfony framework, this service is automatically registered
+ when the ``property_info`` feature is enabled:
+
+ .. code-block:: yaml
+
+ # config/packages/framework.yaml
+ framework:
+ property_info:
+ enabled: true
+
+PhpDocExtractor
+~~~~~~~~~~~~~~~
+
+.. note::
+
+ This extractor depends on the `phpdocumentor/reflection-docblock`_ library.
+
+Using `phpDocumentor Reflection`_ to parse property and method annotations,
+the :class:`Symfony\\Component\\PropertyInfo\\Extractor\\PhpDocExtractor`
+provides type and description information. This extractor is automatically
+registered with the ``property_info`` in the Symfony Framework *if* the dependent
+library is present::
+
+ use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
+
+ $phpDocExtractor = new PhpDocExtractor();
+
+ // Type information.
+ $phpDocExtractor->getTypes($class, $property);
+ // Description information.
+ $phpDocExtractor->getShortDescription($class, $property);
+ $phpDocExtractor->getLongDescription($class, $property);
+ $phpDocExtractor->getDocBlock($class, $property);
+
+.. versionadded:: 7.1
+
+ The :method:`Symfony\\Component\\PropertyInfo\\Extractor\\PhpDocExtractor::getDocBlock`
+ method was introduced in Symfony 7.1.
+
+PhpStanExtractor
+~~~~~~~~~~~~~~~~
+
+.. note::
+
+ This extractor depends on the `phpstan/phpdoc-parser`_ and
+ `phpdocumentor/reflection-docblock`_ libraries.
+
+This extractor fetches information thanks to the PHPStan parser. It gathers
+information from annotations of properties and methods, such as ``@var``,
+``@param`` or ``@return``::
+
+ // src/Domain/Foo.php
+ class Foo
+ {
+ /**
+ * @param string $bar
+ */
+ public function __construct(
+ private string $bar,
+ ) {
+ }
+ }
+
+ // Extraction.php
+ use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor;
+ use App\Domain\Foo;
+
+ $phpStanExtractor = new PhpStanExtractor();
+
+ // Type information.
+ $phpStanExtractor->getTypesFromConstructor(Foo::class, 'bar');
+ // Description information.
+ $phpStanExtractor->getShortDescription($class, 'bar');
+ $phpStanExtractor->getLongDescription($class, 'bar');
+
+.. versionadded:: 7.3
+
+ The :method:`Symfony\\Component\\PropertyInfo\\Extractor\\PhpStanExtractor::getShortDescription`
+ and :method:`Symfony\\Component\\PropertyInfo\\Extractor\\PhpStanExtractor::getLongDescription`
+ methods were introduced in Symfony 7.3.
+
+SerializerExtractor
+~~~~~~~~~~~~~~~~~~~
+
+.. note::
+
+ This extractor depends on the `symfony/serializer`_ library.
+
+Using :ref:`groups metadata ` from the
+:doc:`Serializer component `, the
+:class:`Symfony\\Component\\PropertyInfo\\Extractor\\SerializerExtractor`
+provides list information. This extractor is *not* registered automatically
+with the ``property_info`` service in the Symfony Framework::
+
+ use Symfony\Component\PropertyInfo\Extractor\SerializerExtractor;
+ use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
+ use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
+
+ $serializerClassMetadataFactory = new ClassMetadataFactory(new AttributeLoader());
+ $serializerExtractor = new SerializerExtractor($serializerClassMetadataFactory);
+
+ // the `serializer_groups` option must be configured (may be set to null)
+ $serializerExtractor->getProperties($class, ['serializer_groups' => ['mygroup']]);
+
+If ``serializer_groups`` is set to ``null``, serializer groups metadata won't be
+checked but you will get only the properties considered by the Serializer
+Component (notably the ``#[Ignore]`` attribute is taken into account).
+
+DoctrineExtractor
+~~~~~~~~~~~~~~~~~
+
+.. note::
+
+ This extractor depends on the `symfony/doctrine-bridge`_ and `doctrine/orm`_
+ libraries.
+
+Using entity mapping data from `Doctrine ORM`_, the
+:class:`Symfony\\Bridge\\Doctrine\\PropertyInfo\\DoctrineExtractor`
+provides list and type information. This extractor is not registered automatically
+with the ``property_info`` service in the Symfony Framework::
+
+ use Doctrine\ORM\EntityManager;
+ use Doctrine\ORM\Tools\Setup;
+ use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor;
+
+ $config = Setup::createAnnotationMetadataConfiguration([__DIR__], true);
+ $entityManager = EntityManager::create([
+ 'driver' => 'pdo_sqlite',
+ // ...
+ ], $config);
+ $doctrineExtractor = new DoctrineExtractor($entityManager);
+
+ // List information.
+ $doctrineExtractor->getProperties($class);
+ // Type information.
+ $doctrineExtractor->getTypes($class, $property);
+
+.. _components-property-information-constructor-extractor:
+
+ConstructorExtractor
+~~~~~~~~~~~~~~~~~~~~
+
+The :class:`Symfony\\Component\\PropertyInfo\\Extractor\\ConstructorExtractor`
+tries to extract properties information by using either the
+:class:`Symfony\\Component\\PropertyInfo\\Extractor\\PhpStanExtractor` or
+the :class:`Symfony\\Component\\PropertyInfo\\Extractor\\ReflectionExtractor`
+on the constructor arguments::
+
+ // src/Domain/Foo.php
+ class Foo
+ {
+ public function __construct(
+ private string $bar,
+ ) {
+ }
+ }
+
+ // Extraction.php
+ use App\Domain\Foo;
+ use Symfony\Component\PropertyInfo\Extractor\ConstructorExtractor;
+
+ $constructorExtractor = new ConstructorExtractor([new ReflectionExtractor()]);
+ $constructorExtractor->getTypes(Foo::class, 'bar')[0]->getBuiltinType(); // returns 'string'
+
+.. _`components-property-information-extractors-creation`:
+
+Creating Your Own Extractors
+----------------------------
+
+You can create your own property information extractors by creating a
+class that implements one or more of the following interfaces:
+:class:`Symfony\\Component\\PropertyInfo\\Extractor\\ConstructorArgumentTypeExtractorInterface`,
+:class:`Symfony\\Component\\PropertyInfo\\PropertyAccessExtractorInterface`,
+:class:`Symfony\\Component\\PropertyInfo\\PropertyDescriptionExtractorInterface`,
+:class:`Symfony\\Component\\PropertyInfo\\PropertyListExtractorInterface`,
+:class:`Symfony\\Component\\PropertyInfo\\PropertyTypeExtractorInterface` and
+:class:`Symfony\\Component\\PropertyInfo\\PropertyInitializableExtractorInterface`.
+
+If you have enabled the PropertyInfo component with the FrameworkBundle,
+you can automatically register your extractor class with the ``property_info``
+service by defining it as a service with one or more of the following
+:doc:`tags `:
+
+* ``property_info.list_extractor`` if it provides list information.
+* ``property_info.type_extractor`` if it provides type information.
+* ``property_info.description_extractor`` if it provides description information.
+* ``property_info.access_extractor`` if it provides access information.
+* ``property_info.initializable_extractor`` if it provides initializable information
+ (it checks if a property can be initialized through the constructor).
+* ``property_info.constructor_extractor`` if it provides type information from the constructor argument.
+
+ .. versionadded:: 7.3
+
+ The ``property_info.constructor_extractor`` tag was introduced in Symfony 7.3.
+
+.. _`PSR-1`: https://www.php-fig.org/psr/psr-1/
+.. _`phpDocumentor Reflection`: https://github.com/phpDocumentor/ReflectionDocBlock
+.. _`phpdocumentor/reflection-docblock`: https://packagist.org/packages/phpdocumentor/reflection-docblock
+.. _`phpstan/phpdoc-parser`: https://packagist.org/packages/phpstan/phpdoc-parser
+.. _`Doctrine ORM`: https://www.doctrine-project.org/projects/orm.html
+.. _`symfony/serializer`: https://packagist.org/packages/symfony/serializer
+.. _`symfony/doctrine-bridge`: https://packagist.org/packages/symfony/doctrine-bridge
+.. _`doctrine/orm`: https://packagist.org/packages/doctrine/orm
+.. _`phpDocumentor`: https://www.phpdoc.org/
diff --git a/components/psr7.rst b/components/psr7.rst
new file mode 100644
index 00000000000..04a3b9148b5
--- /dev/null
+++ b/components/psr7.rst
@@ -0,0 +1,97 @@
+The PSR-7 Bridge
+================
+
+ The PSR-7 bridge converts :doc:`HttpFoundation `
+ objects from and to objects implementing HTTP message interfaces defined
+ by the `PSR-7`_.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/psr-http-message-bridge
+
+.. include:: /components/require_autoload.rst.inc
+
+The bridge also needs a PSR-7 and `PSR-17`_ implementation to convert
+HttpFoundation objects to PSR-7 objects. The following command installs the
+``nyholm/psr7`` library, a lightweight and fast PSR-7 implementation, but you
+can use any of the `libraries that implement psr/http-factory-implementation`_:
+
+.. code-block:: terminal
+
+ $ composer require nyholm/psr7
+
+Usage
+-----
+
+Converting from HttpFoundation Objects to PSR-7
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The bridge provides an interface of a factory called
+`HttpMessageFactoryInterface`_ that builds objects implementing PSR-7
+interfaces from HttpFoundation objects.
+
+The following code snippet explains how to convert a :class:`Symfony\\Component\\HttpFoundation\\Request`
+to a ``Nyholm\Psr7\ServerRequest`` class implementing the
+``Psr\Http\Message\ServerRequestInterface`` interface::
+
+ use Nyholm\Psr7\Factory\Psr17Factory;
+ use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
+ use Symfony\Component\HttpFoundation\Request;
+
+ $symfonyRequest = new Request([], [], [], [], [], ['HTTP_HOST' => 'dunglas.fr'], 'Content');
+ // The HTTP_HOST server key must be set to avoid an unexpected error
+
+ $psr17Factory = new Psr17Factory();
+ $psrHttpFactory = new PsrHttpFactory($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory);
+ $psrRequest = $psrHttpFactory->createRequest($symfonyRequest);
+
+And now from a :class:`Symfony\\Component\\HttpFoundation\\Response` to a
+``Nyholm\Psr7\Response`` class implementing the
+``Psr\Http\Message\ResponseInterface`` interface::
+
+ use Nyholm\Psr7\Factory\Psr17Factory;
+ use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
+ use Symfony\Component\HttpFoundation\Response;
+
+ $symfonyResponse = new Response('Content');
+
+ $psr17Factory = new Psr17Factory();
+ $psrHttpFactory = new PsrHttpFactory($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory);
+ $psrResponse = $psrHttpFactory->createResponse($symfonyResponse);
+
+Converting Objects implementing PSR-7 Interfaces to HttpFoundation
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+On the other hand, the bridge provide a factory interface called
+`HttpFoundationFactoryInterface`_ that builds HttpFoundation objects from
+objects implementing PSR-7 interfaces.
+
+The next snippet explain how to convert an object implementing the
+``Psr\Http\Message\ServerRequestInterface`` interface to a
+:class:`Symfony\\Component\\HttpFoundation\\Request` instance::
+
+ use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
+
+ // $psrRequest is an instance of Psr\Http\Message\ServerRequestInterface
+
+ $httpFoundationFactory = new HttpFoundationFactory();
+ $symfonyRequest = $httpFoundationFactory->createRequest($psrRequest);
+
+From an object implementing the ``Psr\Http\Message\ResponseInterface``
+to a :class:`Symfony\\Component\\HttpFoundation\\Response` instance::
+
+ use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
+
+ // $psrResponse is an instance of Psr\Http\Message\ResponseInterface
+
+ $httpFoundationFactory = new HttpFoundationFactory();
+ $symfonyResponse = $httpFoundationFactory->createResponse($psrResponse);
+
+.. _`PSR-7`: https://www.php-fig.org/psr/psr-7/
+.. _`PSR-17`: https://www.php-fig.org/psr/psr-17/
+.. _`libraries that implement psr/http-factory-implementation`: https://packagist.org/providers/psr/http-factory-implementation
+.. _`HttpMessageFactoryInterface`: https://github.com/symfony/psr-http-message-bridge/blob/main/HttpMessageFactoryInterface.php
+.. _`HttpFoundationFactoryInterface`: https://github.com/symfony/psr-http-message-bridge/blob/main/HttpFoundationFactoryInterface.php
diff --git a/components/require_autoload.rst.inc b/components/require_autoload.rst.inc
new file mode 100644
index 00000000000..9d47bd7ffca
--- /dev/null
+++ b/components/require_autoload.rst.inc
@@ -0,0 +1,6 @@
+.. note::
+
+ If you install this component outside of a Symfony application, you must
+ require the ``vendor/autoload.php`` file in your code to enable the class
+ autoloading mechanism provided by Composer. Read
+ :doc:`this article ` for more details.
diff --git a/components/routing/hostname_pattern.rst b/components/routing/hostname_pattern.rst
deleted file mode 100644
index 38bc0f143eb..00000000000
--- a/components/routing/hostname_pattern.rst
+++ /dev/null
@@ -1,165 +0,0 @@
-.. index::
- single: Routing; Matching on Hostname
-
-How to match a route based on the Host
-======================================
-
-.. versionadded:: 2.2
- Host matching support was added in Symfony 2.2
-
-You can also match on the HTTP *host* of the incoming request.
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- mobile_homepage:
- path: /
- host: m.example.com
- defaults: { _controller: AcmeDemoBundle:Main:mobileHomepage }
-
- homepage:
- path: /
- defaults: { _controller: AcmeDemoBundle:Main:homepage }
-
- .. code-block:: xml
-
-
-
-
-
-
- AcmeDemoBundle:Main:mobileHomepage
-
-
-
- AcmeDemoBundle:Main:homepage
-
-
-
- .. code-block:: php
-
- use Symfony\Component\Routing\RouteCollection;
- use Symfony\Component\Routing\Route;
-
- $collection = new RouteCollection();
- $collection->add('mobile_homepage', new Route('/', array(
- '_controller' => 'AcmeDemoBundle:Main:mobileHomepage',
- ), array(), array(), 'm.example.com'));
-
- $collection->add('homepage', new Route('/', array(
- '_controller' => 'AcmeDemoBundle:Main:homepage',
- )));
-
- return $collection;
-
-Both routes match the same path ``/``, however the first one will match
-only if the host is ``m.example.com``.
-
-Placeholders and Requirements in Hostname Patterns
---------------------------------------------------
-
-If you're using the :doc:`DependencyInjection Component`
-(or the full Symfony2 Framework), then you can use
-:ref:`service container parameters` as
-variables anywhere in your routes.
-
-You can avoid hardcoding the domain name by using a placeholder and a requirement.
-The ``%domain%`` in requirements is replaced by the value of the ``domain``
-dependency injection container parameter.
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- mobile_homepage:
- path: /
- host: m.{domain}
- defaults: { _controller: AcmeDemoBundle:Main:mobileHomepage }
- requirements:
- domain: %domain%
-
- homepage:
- path: /
- defaults: { _controller: AcmeDemoBundle:Main:homepage }
-
- .. code-block:: xml
-
-
-
-
-
-
- AcmeDemoBundle:Main:mobileHomepage
- %domain%
-
-
-
- AcmeDemoBundle:Main:homepage
-
-
-
- .. code-block:: php
-
- use Symfony\Component\Routing\RouteCollection;
- use Symfony\Component\Routing\Route;
-
- $collection = new RouteCollection();
- $collection->add('mobile_homepage', new Route('/', array(
- '_controller' => 'AcmeDemoBundle:Main:mobileHomepage',
- ), array(
- 'domain' => '%domain%',
- ), array(), 'm.{domain}'));
-
- $collection->add('homepage', new Route('/', array(
- '_controller' => 'AcmeDemoBundle:Main:homepage',
- )));
-
- return $collection;
-
-.. _component-routing-host-imported:
-
-Adding a Host Regex to Imported Routes
---------------------------------------------
-
-You can set a host regex on imported routes:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/routing.yml
- acme_hello:
- resource: "@AcmeHelloBundle/Resources/config/routing.yml"
- host: "hello.example.com"
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/routing.php
- use Symfony\Component\Routing\RouteCollection;
-
- $collection = new RouteCollection();
- $collection->addCollection($loader->import("@AcmeHelloBundle/Resources/config/routing.php"), '', array(), array(), array(), 'hello.example.com');
-
- return $collection;
-
-The host ``hello.example.com`` will be set on each route loaded from the new
-routing resource.
diff --git a/components/routing/index.rst b/components/routing/index.rst
deleted file mode 100644
index b7f4d40386b..00000000000
--- a/components/routing/index.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-Routing
-=======
-
-.. toctree::
- :maxdepth: 2
-
- introduction
- hostname_pattern
diff --git a/components/routing/introduction.rst b/components/routing/introduction.rst
deleted file mode 100644
index 8be2af7af92..00000000000
--- a/components/routing/introduction.rst
+++ /dev/null
@@ -1,344 +0,0 @@
-.. index::
- single: Routing
- single: Components; Routing
-
-The Routing Component
-=====================
-
- The Routing Component maps an HTTP request to a set of configuration
- variables.
-
-Installation
-------------
-
-You can install the component in many different ways:
-
-* Use the official Git repository (https://github.com/symfony/Routing);
-* :doc:`Install it via Composer` (``symfony/routing`` on `Packagist`_).
-
-Usage
------
-
-In order to set up a basic routing system you need three parts:
-
-* A :class:`Symfony\\Component\\Routing\\RouteCollection`, which contains the route definitions (instances of the class :class:`Symfony\\Component\\Routing\\Route`)
-* A :class:`Symfony\\Component\\Routing\\RequestContext`, which has information about the request
-* A :class:`Symfony\\Component\\Routing\\Matcher\\UrlMatcher`, which performs the mapping of the request to a single route
-
-Let's see a quick example. Notice that this assumes that you've already configured
-your autoloader to load the Routing component::
-
- use Symfony\Component\Routing\Matcher\UrlMatcher;
- use Symfony\Component\Routing\RequestContext;
- use Symfony\Component\Routing\RouteCollection;
- use Symfony\Component\Routing\Route;
-
- $route = new Route('/foo', array('controller' => 'MyController'));
- $routes = new RouteCollection();
- $routes->add('route_name', $route);
-
- $context = new RequestContext($_SERVER['REQUEST_URI']);
-
- $matcher = new UrlMatcher($routes, $context);
-
- $parameters = $matcher->match('/foo');
- // array('controller' => 'MyController', '_route' => 'route_name')
-
-.. note::
-
- Be careful when using ``$_SERVER['REQUEST_URI']``, as it may include
- any query parameters on the URL, which will cause problems with route
- matching. An easy way to solve this is to use the HttpFoundation component
- as explained :ref:`below`.
-
-You can add as many routes as you like to a
-:class:`Symfony\\Component\\Routing\\RouteCollection`.
-
-The :method:`RouteCollection::add()`
-method takes two arguments. The first is the name of the route. The second
-is a :class:`Symfony\\Component\\Routing\\Route` object, which expects a
-URL path and some array of custom variables in its constructor. This array
-of custom variables can be *anything* that's significant to your application,
-and is returned when that route is matched.
-
-If no matching route can be found a
-:class:`Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException` will be thrown.
-
-In addition to your array of custom variables, a ``_route`` key is added,
-which holds the name of the matched route.
-
-Defining routes
-~~~~~~~~~~~~~~~
-
-A full route definition can contain up to seven parts:
-
-1. The URL path route. This is matched against the URL passed to the `RequestContext`,
-and can contain named wildcard placeholders (e.g. ``{placeholders}``)
-to match dynamic parts in the URL.
-
-2. An array of default values. This contains an array of arbitrary values
-that will be returned when the request matches the route.
-
-3. An array of requirements. These define constraints for the values of the
-placeholders as regular expressions.
-
-4. An array of options. These contain internal settings for the route and
-are the least commonly needed.
-
-5. A host. This is matched against the host of the request. See
- :doc:`/components/routing/hostname_pattern` for more details.
-
-6. An array of schemes. These enforce a certain HTTP scheme (``http``, ``https``).
-
-7. An array of methods. These enforce a certain HTTP request method (``HEAD``,
- ``GET``, ``POST``, ...).
-
-.. versionadded:: 2.2
- Host matching support was added in Symfony 2.2
-
-Take the following route, which combines several of these ideas::
-
- $route = new Route(
- '/archive/{month}', // path
- array('controller' => 'showArchive'), // default values
- array('month' => '[0-9]{4}-[0-9]{2}', 'subdomain' => 'www|m'), // requirements
- array(), // options
- '{subdomain}.example.com', // host
- array(), // schemes
- array() // methods
- );
-
- // ...
-
- $parameters = $matcher->match('/archive/2012-01');
- // array(
- // 'controller' => 'showArchive',
- // 'month' => '2012-01',
- // 'subdomain' => 'www',
- // '_route' => ...
- // )
-
- $parameters = $matcher->match('/archive/foo');
- // throws ResourceNotFoundException
-
-In this case, the route is matched by ``/archive/2012-01``, because the ``{month}``
-wildcard matches the regular expression wildcard given. However, ``/archive/foo``
-does *not* match, because "foo" fails the month wildcard.
-
-.. tip::
-
- If you want to match all urls which start with a certain path and end in an
- arbitrary suffix you can use the following route definition::
-
- $route = new Route(
- '/start/{suffix}',
- array('suffix' => ''),
- array('suffix' => '.*')
- );
-
-Using Prefixes
-~~~~~~~~~~~~~~
-
-You can add routes or other instances of
-:class:`Symfony\\Component\\Routing\\RouteCollection` to *another* collection.
-This way you can build a tree of routes. Additionally you can define a prefix,
-default requirements, default options and host to all routes of a subtree with
-the :method:`Symfony\\Component\\Routing\\RouteCollection::addPrefix` method::
-
- $rootCollection = new RouteCollection();
-
- $subCollection = new RouteCollection();
- $subCollection->add(...);
- $subCollection->add(...);
- $subCollection->addPrefix(
- '/prefix', // prefix
- array(), // requirements
- array(), // options
- 'admin.example.com', // host
- array('https') // schemes
- );
-
- $rootCollection->addCollection($subCollection);
-
-.. versionadded:: 2.2
- The ``addPrefix`` method is added in Symfony2.2. This was part of the
- ``addCollection`` method in older versions.
-
-Set the Request Parameters
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The :class:`Symfony\\Component\\Routing\\RequestContext` provides information
-about the current request. You can define all parameters of an HTTP request
-with this class via its constructor::
-
- public function __construct(
- $baseUrl = '',
- $method = 'GET',
- $host = 'localhost',
- $scheme = 'http',
- $httpPort = 80,
- $httpsPort = 443
- )
-
-.. _components-routing-http-foundation:
-
-Normally you can pass the values from the ``$_SERVER`` variable to populate the
-:class:`Symfony\\Component\\Routing\\RequestContext`. But If you use the
-:doc:`HttpFoundation` component, you can use its
-:class:`Symfony\\Component\\HttpFoundation\\Request` class to feed the
-:class:`Symfony\\Component\\Routing\\RequestContext` in a shortcut::
-
- use Symfony\Component\HttpFoundation\Request;
-
- $context = new RequestContext();
- $context->fromRequest(Request::createFromGlobals());
-
-Generate a URL
-~~~~~~~~~~~~~~
-
-While the :class:`Symfony\\Component\\Routing\\Matcher\\UrlMatcher` tries
-to find a route that fits the given request you can also build a URL from
-a certain route::
-
- use Symfony\Component\Routing\Generator\UrlGenerator;
-
- $routes = new RouteCollection();
- $routes->add('show_post', new Route('/show/{slug}'));
-
- $context = new RequestContext($_SERVER['REQUEST_URI']);
-
- $generator = new UrlGenerator($routes, $context);
-
- $url = $generator->generate('show_post', array(
- 'slug' => 'my-blog-post',
- ));
- // /show/my-blog-post
-
-.. note::
-
- If you have defined a scheme, an absolute URL is generated if the scheme
- of the current :class:`Symfony\\Component\\Routing\\RequestContext` does
- not match the requirement.
-
-Load Routes from a File
-~~~~~~~~~~~~~~~~~~~~~~~
-
-You've already seen how you can easily add routes to a collection right inside
-PHP. But you can also load routes from a number of different files.
-
-The Routing component comes with a number of loader classes, each giving
-you the ability to load a collection of route definitions from an external
-file of some format.
-Each loader expects a :class:`Symfony\\Component\\Config\\FileLocator` instance
-as the constructor argument. You can use the :class:`Symfony\\Component\\Config\\FileLocator`
-to define an array of paths in which the loader will look for the requested files.
-If the file is found, the loader returns a :class:`Symfony\\Component\\Routing\\RouteCollection`.
-
-If you're using the ``YamlFileLoader``, then route definitions look like this:
-
-.. code-block:: yaml
-
- # routes.yml
- route1:
- path: /foo
- defaults: { _controller: 'MyController::fooAction' }
-
- route2:
- path: /foo/bar
- defaults: { _controller: 'MyController::foobarAction' }
-
-To load this file, you can use the following code. This assumes that your
-``routes.yml`` file is in the same directory as the below code::
-
- use Symfony\Component\Config\FileLocator;
- use Symfony\Component\Routing\Loader\YamlFileLoader;
-
- // look inside *this* directory
- $locator = new FileLocator(array(__DIR__));
- $loader = new YamlFileLoader($locator);
- $collection = $loader->load('routes.yml');
-
-Besides :class:`Symfony\\Component\\Routing\\Loader\\YamlFileLoader` there are two
-other loaders that work the same way:
-
-* :class:`Symfony\\Component\\Routing\\Loader\\XmlFileLoader`
-* :class:`Symfony\\Component\\Routing\\Loader\\PhpFileLoader`
-
-If you use the :class:`Symfony\\Component\\Routing\\Loader\\PhpFileLoader` you
-have to provide the name of a php file which returns a :class:`Symfony\\Component\\Routing\\RouteCollection`::
-
- // RouteProvider.php
- use Symfony\Component\Routing\RouteCollection;
- use Symfony\Component\Routing\Route;
-
- $collection = new RouteCollection();
- $collection->add(
- 'route_name',
- new Route('/foo', array('controller' => 'ExampleController'))
- );
- // ...
-
- return $collection;
-
-Routes as Closures
-..................
-
-There is also the :class:`Symfony\\Component\\Routing\\Loader\\ClosureLoader`, which
-calls a closure and uses the result as a :class:`Symfony\\Component\\Routing\\RouteCollection`::
-
- use Symfony\Component\Routing\Loader\ClosureLoader;
-
- $closure = function() {
- return new RouteCollection();
- };
-
- $loader = new ClosureLoader();
- $collection = $loader->load($closure);
-
-Routes as Annotations
-.....................
-
-Last but not least there are
-:class:`Symfony\\Component\\Routing\\Loader\\AnnotationDirectoryLoader` and
-:class:`Symfony\\Component\\Routing\\Loader\\AnnotationFileLoader` to load
-route definitions from class annotations. The specific details are left
-out here.
-
-The all-in-one Router
-~~~~~~~~~~~~~~~~~~~~~
-
-The :class:`Symfony\\Component\\Routing\\Router` class is a all-in-one package
-to quickly use the Routing component. The constructor expects a loader instance,
-a path to the main route definition and some other settings::
-
- public function __construct(
- LoaderInterface $loader,
- $resource,
- array $options = array(),
- RequestContext $context = null,
- array $defaults = array()
- );
-
-With the ``cache_dir`` option you can enable route caching (if you provide a
-path) or disable caching (if it's set to ``null``). The caching is done
-automatically in the background if you want to use it. A basic example of the
-:class:`Symfony\\Component\\Routing\\Router` class would look like::
-
- $locator = new FileLocator(array(__DIR__));
- $requestContext = new RequestContext($_SERVER['REQUEST_URI']);
-
- $router = new Router(
- new YamlFileLoader($locator),
- 'routes.yml',
- array('cache_dir' => __DIR__.'/cache'),
- $requestContext
- );
- $router->match('/foo/bar');
-
-.. note::
-
- If you use caching, the Routing component will compile new classes which
- are saved in the ``cache_dir``. This means your script must have write
- permissions for that location.
-
-.. _Packagist: https://packagist.org/packages/symfony/routing
diff --git a/components/runtime.rst b/components/runtime.rst
new file mode 100644
index 00000000000..4eb75de2a75
--- /dev/null
+++ b/components/runtime.rst
@@ -0,0 +1,493 @@
+The Runtime Component
+=====================
+
+ The Runtime Component decouples the bootstrapping logic from any global state
+ to make sure the application can run with runtimes like `PHP-PM`_, `ReactPHP`_,
+ `Swoole`_, `FrankenPHP`_ etc. without any changes.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/runtime
+
+.. include:: /components/require_autoload.rst.inc
+
+Usage
+-----
+
+The Runtime component abstracts most bootstrapping logic as so-called
+*runtimes*, allowing you to write front-controllers in a generic way.
+For instance, the Runtime component allows Symfony's ``public/index.php``
+to look like this::
+
+ // public/index.php
+ use App\Kernel;
+
+ require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
+
+ return function (array $context): Kernel {
+ return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
+ };
+
+So how does this front-controller work? At first, the special
+``autoload_runtime.php`` file is automatically created by the Composer plugin in
+the component. This file runs the following logic:
+
+#. It instantiates a :class:`Symfony\\Component\\Runtime\\RuntimeInterface`;
+#. The callable (returned by ``public/index.php``) is passed to the Runtime, whose job
+ is to resolve the arguments (in this example: ``array $context``);
+#. Then, this callable is called to get the application (``App\Kernel``);
+#. At last, the Runtime is used to run the application (i.e. calling
+ ``$kernel->handle(Request::createFromGlobals())->send()``).
+
+.. warning::
+
+ If you use the Composer ``--no-plugins`` option, the ``autoload_runtime.php``
+ file won't be created.
+
+ If you use the Composer ``--no-scripts`` option, make sure your Composer version
+ is ``>=2.1.3``; otherwise the ``autoload_runtime.php`` file won't be created.
+
+To make a console application, the bootstrap code would look like::
+
+ #!/usr/bin/env php
+ setCode(static function (InputInterface $input, OutputInterface $output): void {
+ $output->write('Hello World');
+ });
+
+ return $command;
+ };
+
+:class:`Symfony\\Component\\Console\\Application`
+ Useful with console applications with more than one command. This will use the
+ :class:`Symfony\\Component\\Runtime\\Runner\\Symfony\\ConsoleApplicationRunner`::
+
+ use Symfony\Component\Console\Application;
+ use Symfony\Component\Console\Command\Command;
+ use Symfony\Component\Console\Input\InputInterface;
+ use Symfony\Component\Console\Output\OutputInterface;
+
+ require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
+
+ return static function (array $context): Application {
+ $command = new Command('hello');
+ $command->setCode(static function (InputInterface $input, OutputInterface $output): void {
+ $output->write('Hello World');
+ });
+
+ $app = new Application();
+ $app->add($command);
+ $app->setDefaultCommand('hello', true);
+
+ return $app;
+ };
+
+The ``GenericRuntime`` and ``SymfonyRuntime`` also support these generic
+applications:
+
+:class:`Symfony\\Component\\Runtime\\RunnerInterface`
+ The ``RunnerInterface`` is a way to use a custom application with the
+ generic Runtime::
+
+ // public/index.php
+ use Symfony\Component\Runtime\RunnerInterface;
+
+ require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
+
+ return static function (): RunnerInterface {
+ return new class implements RunnerInterface {
+ public function run(): int
+ {
+ echo 'Hello World';
+
+ return 0;
+ }
+ };
+ };
+
+``callable``
+ Your "application" can also be a ``callable``. The first callable will return
+ the "application" and the second callable is the "application" itself::
+
+ // public/index.php
+ require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
+
+ return static function (): callable {
+ $app = static function(): int {
+ echo 'Hello World';
+
+ return 0;
+ };
+
+ return $app;
+ };
+
+``void``
+ If the callable doesn't return anything, the ``SymfonyRuntime`` will assume
+ everything is fine::
+
+ require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
+
+ return function (): void {
+ echo 'Hello world';
+ };
+
+Using Options
+~~~~~~~~~~~~~
+
+Some behavior of the Runtimes can be modified through runtime options. They
+can be set using the ``APP_RUNTIME_OPTIONS`` environment variable::
+
+ $_SERVER['APP_RUNTIME_OPTIONS'] = [
+ 'project_dir' => '/var/task',
+ ];
+
+ require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
+
+ // ...
+
+You can also configure ``extra.runtime`` in ``composer.json``:
+
+.. code-block:: json
+
+ {
+ "require": {
+ "...": "..."
+ },
+ "extra": {
+ "runtime": {
+ "project_dir": "/var/task"
+ }
+ }
+ }
+
+Then, update your Composer files (running ``composer dump-autoload``, for instance),
+so that the ``vendor/autoload_runtime.php`` files gets regenerated with the new option.
+
+The following options are supported by the ``SymfonyRuntime``:
+
+``env`` (default: ``APP_ENV`` environment variable, or ``"dev"``)
+ To define the name of the environment the app runs in.
+``disable_dotenv`` (default: ``false``)
+ To disable looking for ``.env`` files.
+``dotenv_path`` (default: ``.env``)
+ To define the path of dot-env files.
+``dotenv_overload`` (default: ``false``)
+ To tell Dotenv whether to override ``.env`` vars with ``.env.local`` (or other ``.env.*`` files)
+``use_putenv``
+ To tell Dotenv to set env vars using ``putenv()`` (NOT RECOMMENDED).
+``prod_envs`` (default: ``["prod"]``)
+ To define the names of the production envs.
+``test_envs`` (default: ``["test"]``)
+ To define the names of the test envs.
+
+Besides these, the ``GenericRuntime`` and ``SymfonyRuntime`` also support
+these options:
+
+``debug`` (default: the value of the env var defined by ``debug_var_name`` option
+ (usually, ``APP_DEBUG``), or ``true`` if such env var is not defined)
+ Toggles the :ref:`debug mode ` of Symfony applications (e.g. to
+ display errors)
+``runtimes``
+ Maps "application types" to a ``GenericRuntime`` implementation that
+ knows how to deal with each of them.
+``error_handler`` (default: :class:`Symfony\\Component\\Runtime\\Internal\\BasicErrorHandler` or :class:`Symfony\\Component\\Runtime\\Internal\\SymfonyErrorHandler` for ``SymfonyRuntime``)
+ Defines the class to use to handle PHP errors.
+``env_var_name`` (default: ``"APP_ENV"``)
+ Defines the name of the env var that stores the name of the
+ :ref:`configuration environment `
+ to use when running the application.
+``debug_var_name`` (default: ``"APP_DEBUG"``)
+ Defines the name of the env var that stores the value of the
+ :ref:`debug mode ` flag to use when running the application.
+
+Create Your Own Runtime
+-----------------------
+
+This is an advanced topic that describes the internals of the Runtime component.
+
+Using the Runtime component will benefit maintainers because the bootstrap
+logic could be versioned as a part of a normal package. If the application
+author decides to use this component, the package maintainer of the Runtime
+class will have more control and can fix bugs and add features.
+
+The Runtime component is designed to be totally generic and able to run any
+application outside of the global state in 6 steps:
+
+#. The main entry point returns a *callable* (the "app") that wraps the application;
+#. The *app callable* is passed to ``RuntimeInterface::getResolver()``, which returns
+ a :class:`Symfony\\Component\\Runtime\\ResolverInterface`. This resolver returns
+ an array with the app callable (or something that decorates this callable) at
+ index 0 and all its resolved arguments at index 1.
+#. The *app callable* is invoked with its arguments, it will return an object that
+ represents the application.
+#. This *application object* is passed to ``RuntimeInterface::getRunner()``, which
+ returns a :class:`Symfony\\Component\\Runtime\\RunnerInterface`: an instance
+ that knows how to "run" the application object.
+#. The ``RunnerInterface::run(object $application)`` is called and it returns the
+ exit status code as ``int``.
+#. The PHP engine is terminated with this status code.
+
+When creating a new runtime, there are two things to consider: First, what arguments
+will the end user use? Second, what will the user's application look like?
+
+For instance, imagine you want to create a runtime for `ReactPHP`_:
+
+**What arguments will the end user use?**
+
+For a generic ReactPHP application, no special arguments are
+typically required. This means that you can use the
+:class:`Symfony\\Component\\Runtime\\GenericRuntime`.
+
+**What will the user's application look like?**
+
+There is also no typical React application, so you might want to rely on
+the `PSR-15`_ interfaces for HTTP request handling.
+
+However, a ReactPHP application will need some special logic to *run*. That logic
+is added in a new class implementing :class:`Symfony\\Component\\Runtime\\RunnerInterface`::
+
+ use Psr\Http\Message\ResponseInterface;
+ use Psr\Http\Message\ServerRequestInterface;
+ use Psr\Http\Server\RequestHandlerInterface;
+ use React\EventLoop\Factory as ReactFactory;
+ use React\Http\Server as ReactHttpServer;
+ use React\Socket\Server as ReactSocketServer;
+ use Symfony\Component\Runtime\RunnerInterface;
+
+ class ReactPHPRunner implements RunnerInterface
+ {
+ public function __construct(
+ private RequestHandlerInterface $application,
+ private int $port,
+ ) {
+ }
+
+ public function run(): int
+ {
+ $application = $this->application;
+ $loop = ReactFactory::create();
+
+ // configure ReactPHP to correctly handle the PSR-15 application
+ $server = new ReactHttpServer(
+ $loop,
+ function (ServerRequestInterface $request) use ($application): ResponseInterface {
+ return $application->handle($request);
+ }
+ );
+
+ // start the ReactPHP server
+ $socket = new ReactSocketServer($this->port, $loop);
+ $server->listen($socket);
+
+ $loop->run();
+
+ return 0;
+ }
+ }
+
+By extending the ``GenericRuntime``, you make sure that the application is
+always using this ``ReactPHPRunner``::
+
+ use Symfony\Component\Runtime\GenericRuntime;
+ use Symfony\Component\Runtime\RunnerInterface;
+
+ class ReactPHPRuntime extends GenericRuntime
+ {
+ private int $port;
+
+ public function __construct(array $options)
+ {
+ $this->port = $options['port'] ?? 8080;
+ parent::__construct($options);
+ }
+
+ public function getRunner(?object $application): RunnerInterface
+ {
+ if ($application instanceof RequestHandlerInterface) {
+ return new ReactPHPRunner($application, $this->port);
+ }
+
+ // if it's not a PSR-15 application, use the GenericRuntime to
+ // run the application (see "Resolvable Applications" above)
+ return parent::getRunner($application);
+ }
+ }
+
+The end user will now be able to create front controller like::
+
+ require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
+
+ return function (array $context): SomeCustomPsr15Application {
+ return new SomeCustomPsr15Application();
+ };
+
+.. _PHP-PM: https://github.com/php-pm/php-pm
+.. _Swoole: https://openswoole.com/
+.. _FrankenPHP: https://frankenphp.dev/
+.. _ReactPHP: https://reactphp.org/
+.. _`PSR-15`: https://www.php-fig.org/psr/psr-15/
+.. _`runtime template file`: https://github.com/symfony/symfony/blob/{version}/src/Symfony/Component/Runtime/Internal/autoload_runtime.template
diff --git a/components/security/authentication.rst b/components/security/authentication.rst
deleted file mode 100644
index 48154ebb5c5..00000000000
--- a/components/security/authentication.rst
+++ /dev/null
@@ -1,215 +0,0 @@
-.. index::
- single: Security, Authentication
-
-Authentication
-==============
-
-When a request points to a secured area, and one of the listeners from the
-firewall map is able to extract the user's credentials from the current
-:class:`Symfony\\Component\\HttpFoundation\\Request` object, it should create
-a token, containing these credentials. The next thing the listener should
-do is ask the authentication manager to validate the given token, and return
-an *authenticated* token if the supplied credentials were found to be valid.
-The listener should then store the authenticated token in the security context::
-
- use Symfony\Component\Security\Http\Firewall\ListenerInterface;
- use Symfony\Component\Security\Core\SecurityContextInterface;
- use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
- use Symfony\Component\HttpKernel\Event\GetResponseEvent;
- use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
-
- class SomeAuthenticationListener implements ListenerInterface
- {
- /**
- * @var SecurityContextInterface
- */
- private $securityContext;
-
- /**
- * @var AuthenticationManagerInterface
- */
- private $authenticationManager;
-
- /**
- * @var string Uniquely identifies the secured area
- */
- private $providerKey;
-
- // ...
-
- public function handle(GetResponseEvent $event)
- {
- $request = $event->getRequest();
-
- $username = ...;
- $password = ...;
-
- $unauthenticatedToken = new UsernamePasswordToken(
- $username,
- $password,
- $this->providerKey
- );
-
- $authenticatedToken = $this
- ->authenticationManager
- ->authenticate($unauthenticatedToken);
-
- $this->securityContext->setToken($authenticatedToken);
- }
- }
-
-.. note::
-
- A token can be of any class, as long as it implements
- :class:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface`.
-
-The Authentication Manager
---------------------------
-
-The default authentication manager is an instance of
-:class:`Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationProviderManager`::
-
- use Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager;
-
- // instances of Symfony\Component\Security\Core\Authentication\AuthenticationProviderInterface
- $providers = array(...);
-
- $authenticationManager = new AuthenticationProviderManager($providers);
-
- try {
- $authenticatedToken = $authenticationManager
- ->authenticate($unauthenticatedToken);
- } catch (AuthenticationException $failed) {
- // authentication failed
- }
-
-The ``AuthenticationProviderManager``, when instantiated, receives several
-authentication providers, each supporting a different type of token.
-
-.. note::
-
- You may of course write your own authentication manager, it only has
- to implement :class:`Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationManagerInterface`.
-
-.. _authentication_providers:
-
-Authentication providers
-------------------------
-
-Each provider (since it implements
-:class:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\AuthenticationProviderInterface`)
-has a method :method:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\AuthenticationProviderInterface::supports`
-by which the ``AuthenticationProviderManager``
-can determine if it supports the given token. If this is the case, the
-manager then calls the provider's method :class:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\AuthenticationProviderInterface::authenticate`.
-This method should return an authenticated token or throw an
-:class:`Symfony\\Component\\Security\\Core\\Exception\\AuthenticationException`
-(or any other exception extending it).
-
-Authenticating Users by their Username and Password
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-An authentication provider will attempt to authenticate a user based on
-the credentials he provided. Usually these are a username and a password.
-Most web applications store their user's username and a hash of the user's
-password combined with a randomly generated salt. This means that the average
-authentication would consist of fetching the salt and the hashed password
-from the user data storage, hash the password the user has just provided
-(e.g. using a login form) with the salt and compare both to determine if
-the given password is valid.
-
-This functionality is offered by the :class:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\DaoAuthenticationProvider`.
-It fetches the user's data from a :class:`Symfony\\Component\\Security\\Core\\User\\UserProviderInterface``,
-uses a :class:`Symfony\\Component\\Security\\Core\\Encoder\\PasswordEncoderInterface`
-to create a hash of the password and returns an authenticated token if the
-password was valid::
-
- use Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider;
- use Symfony\Component\Security\Core\User\UserChecker;
- use Symfony\Component\Security\Core\User\InMemoryUserProvider;
- use Symfony\Component\Security\Core\Encoder\EncoderFactory;
-
- $userProvider = new InMemoryUserProvider(
- array(
- 'admin' => array(
- // password is "foo"
- 'password' => '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg==',
- 'roles' => array('ROLE_ADMIN'),
- ),
- )
- );
-
- // for some extra checks: is account enabled, locked, expired, etc.?
- $userChecker = new UserChecker();
-
- // an array of password encoders (see below)
- $encoderFactory = new EncoderFactory(...);
-
- $provider = new DaoAuthenticationProvider(
- $userProvider,
- $userChecker,
- 'secured_area',
- $encoderFactory
- );
-
- $provider->authenticate($unauthenticatedToken);
-
-.. note::
-
- The example above demonstrates the use of the "in-memory" user provider,
- but you may use any user provider, as long as it implements
- :class:`Symfony\\Component\\Security\\Core\\User\\UserProviderInterface`.
- It is also possible to let multiple user providers try to find the user's
- data, using the :class:`Symfony\\Component\\Security\\Core\\User\\ChainUserProvider`.
-
-The Password encoder Factory
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The :class:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\DaoAuthenticationProvider`
-uses an encoder factory to create a password encoder for a given type of
-user. This allows you to use different encoding strategies for different
-types of users. The default :class:`Symfony\\Component\\Security\\Core\\Encoder\\EncoderFactory`
-receives an array of encoders::
-
- use Symfony\Component\Security\Core\Encoder\EncoderFactory;
- use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder;
-
- $defaultEncoder = new MessageDigestPasswordEncoder('sha512', true, 5000);
- $weakEncoder = new MessageDigestPasswordEncoder('md5', true, 1);
-
- $encoders = array(
- 'Symfony\\Component\\Security\\Core\\User\\User' => $defaultEncoder,
- 'Acme\\Entity\\LegacyUser' => $weakEncoder,
-
- // ...
- );
-
- $encoderFactory = new EncoderFactory($encoders);
-
-Each encoder should implement :class:`Symfony\\Component\\Security\\Core\\Encoder\\PasswordEncoderInterface`
-or be an array with a ``class`` and an ``arguments`` key, which allows the
-encoder factory to construct the encoder only when it is needed.
-
-Password Encoders
-~~~~~~~~~~~~~~~~~
-
-When the :method:`Symfony\\Component\\Security\\Core\\Encoder\\EncoderFactory::getEncoder`
-method of the password encoder factory is called with the user object as
-its first argument, it will return an encoder of type :class:`Symfony\\Component\\Security\\Core\\Encoder\\PasswordEncoderInterface`
-which should be used to encode this user's password::
-
- // fetch a user of type Acme\Entity\LegacyUser
- $user = ...
-
- $encoder = $encoderFactory->getEncoder($user);
-
- // will return $weakEncoder (see above)
-
- $encodedPassword = $encoder->encodePassword($password, $user->getSalt());
-
- // check if the password is valid:
-
- $validPassword = $encoder->isPasswordValid(
- $user->getPassword(),
- $password,
- $user->getSalt());
diff --git a/components/security/authorization.rst b/components/security/authorization.rst
deleted file mode 100644
index 7dc0433fd8e..00000000000
--- a/components/security/authorization.rst
+++ /dev/null
@@ -1,242 +0,0 @@
-.. index::
- single: Security, Authorization
-
-Authorization
-=============
-
-When any of the authentication providers (see :ref:`authentication_providers`)
-has verified the still-unauthenticated token, an authenticated token will
-be returned. The authentication listener should set this token directly
-in the :class:`Symfony\\Component\\Security\\Core\\SecurityContextInterface`
-using its :method:`Symfony\\Component\\Security\\Core\\SecurityContextInterface::setToken`
-method.
-
-From then on, the user is authenticated, i.e. identified. Now, other parts
-of the application can use the token to decide whether or not the user may
-request a certain URI, or modify a certain object. This decision will be made
-by an instance of :class:`Symfony\\Component\\Security\\Core\\Authorization\\AccessDecisionManagerInterface`.
-
-An authorization decision will always be based on a few things:
-
-* The current token
- For instance, the token's :method:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface::getRoles`
- method may be used to retrieve the roles of the current user (e.g.
- ``ROLE_SUPER_ADMIN``), or a decision may be based on the class of the token.
-* A set of attributes
- Each attribute stands for a certain right the user should have, e.g.
- ``ROLE_ADMIN`` to make sure the user is an administrator.
-* An object (optional)
- Any object on which for which access control needs to be checked, like
- an article or a comment object.
-
-Access Decision Manager
------------------------
-
-Since deciding whether or not a user is authorized to perform a certain
-action can be a complicated process, the standard :class:`Symfony\\Component\\Security\\Core\\Authorization\\AccessDecisionManager`
-itself depends on multiple voters, and makes a final verdict based on all
-the votes (either positive, negative or neutral) it has received. It
-recognizes several strategies:
-
-* ``affirmative`` (default)
- grant access as soon as any voter returns an affirmative response;
-
-* ``consensus``
- grant access if there are more voters granting access than there are denying;
-
-* ``unanimous``
- only grant access if none of the voters has denied access;
-
-.. code-block:: php
-
- use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;
-
- // instances of Symfony\Component\Security\Core\Authorization\Voter\VoterInterface
- $voters = array(...);
-
- // one of "affirmative", "consensus", "unanimous"
- $strategy = ...;
-
- // whether or not to grant access when all voters abstain
- $allowIfAllAbstainDecisions = ...;
-
- // whether or not to grant access when there is no majority (applies only to the "consensus" strategy)
- $allowIfEqualGrantedDeniedDecisions = ...;
-
- $accessDecisionManager = new AccessDecisionManager(
- $voters,
- $strategy,
- $allowIfAllAbstainDecisions,
- $allowIfEqualGrantedDeniedDecisions
- );
-
-Voters
-------
-
-Voters are instances
-of :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`,
-which means they have to implement a few methods which allows the decision
-manager to use them:
-
-* ``supportsAttribute($attribute)``
- will be used to check if the voter knows how to handle the given attribute;
-
-* ``supportsClass($class)``
- will be used to check if the voter is able to grant or deny access for
- an object of the given class;
-
-* ``vote(TokenInterface $token, $object, array $attributes)``
- this method will do the actual voting and return a value equal to one
- of the class constants of :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`,
- i.e. ``VoterInterface::ACCESS_GRANTED``, ``VoterInterface::ACCESS_DENIED``
- or ``VoterInterface::ACCESS_ABSTAIN``;
-
-The security component contains some standard voters which cover many use
-cases:
-
-AuthenticatedVoter
-~~~~~~~~~~~~~~~~~~
-
-The :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AuthenticatedVoter`
-voter supports the attributes ``IS_AUTHENTICATED_FULLY``, ``IS_AUTHENTICATED_REMEMBERED``,
-and ``IS_AUTHENTICATED_ANONYMOUSLY`` and grants access based on the current
-level of authentication, i.e. is the user fully authenticated, or only based
-on a "remember-me" cookie, or even authenticated anonymously?
-
-.. code-block:: php
-
- use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver;
-
- $anonymousClass = 'Symfony\Component\Security\Core\Authentication\Token\AnonymousToken';
- $rememberMeClass = 'Symfony\Component\Security\Core\Authentication\Token\RememberMeToken';
-
- $trustResolver = new AuthenticationTrustResolver($anonymousClass, $rememberMeClass);
-
- $authenticatedVoter = new AuthenticatedVoter($trustResolver);
-
- // instance of Symfony\Component\Security\Core\Authentication\Token\TokenInterface
- $token = ...;
-
- // any object
- $object = ...;
-
- $vote = $authenticatedVoter->vote($token, $object, array('IS_AUTHENTICATED_FULLY');
-
-RoleVoter
-~~~~~~~~~
-
-The :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\RoleVoter`
-supports attributes starting with ``ROLE_`` and grants access to the user
-when the required ``ROLE_*`` attributes can all be found in the array of
-roles returned by the token's :method:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface::getRoles`
-method::
-
- use Symfony\Component\Security\Core\Authorization\Voter\RoleVoter;
-
- $roleVoter = new RoleVoter('ROLE_');
-
- $roleVoter->vote($token, $object, 'ROLE_ADMIN');
-
-RoleHierarchyVoter
-~~~~~~~~~~~~~~~~~~
-
-The :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\RoleHierarchyVoter`
-extends :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\RoleVoter`
-and provides some additional functionality: it knows how to handle a
-hierarchy of roles. For instance, a ``ROLE_SUPER_ADMIN`` role may have subroles
-``ROLE_ADMIN`` and ``ROLE_USER``, so that when a certain object requires the
-user to have the ``ROLE_ADMIN`` role, it grants access to users who in fact
-have the ``ROLE_ADMIN`` role, but also to users having the ``ROLE_SUPER_ADMIN``
-role::
-
- use Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter;
- use Symfony\Component\Security\Core\Role\RoleHierarchy;
-
- $hierarchy = array(
- 'ROLE_SUPER_ADMIN' => array('ROLE_ADMIN', 'ROLE_USER'),
- );
-
- $roleHierarchy = new RoleHierarchy($hierarchy);
-
- $roleHierarchyVoter = new RoleHierarchyVoter($roleHierarchy);
-
-.. note::
-
- When you make your own voter, you may of course use its constructor
- to inject any dependencies it needs to come to a decision.
-
-Roles
------
-
-Roles are objects that give expression to a certain right the user has.
-The only requirement is that they implement :class:`Symfony\\Component\\Security\\Core\\Role\\RoleInterface`,
-which means they should also have a :method:`Symfony\\Component\\Security\\Core\\Role\\Role\\RoleInterface::getRole`
-method that returns a string representation of the role itself. The default
-:class:`Symfony\\Component\\Security\\Core\\Role\\Role` simply returns its
-first constructor argument::
-
- use Symfony\Component\Security\Core\Role\Role;
-
- $role = new Role('ROLE_ADMIN');
-
- // will echo 'ROLE_ADMIN'
- echo $role->getRole();
-
-.. note::
-
- Most authentication tokens extend from :class:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\AbstractToken`,
- which means that the roles given to its constructor will be
- automatically converted from strings to these simple ``Role`` objects.
-
-Using the decision manager
---------------------------
-
-The Access Listener
-~~~~~~~~~~~~~~~~~~~
-
-The access decision manager can be used at any point in a request to decide whether
-or not the current user is entitled to access a given resource. One optional,
-but useful, method for restricting access based on a URL pattern is the
-:class:`Symfony\\Component\\Security\\Http\\Firewall\\AccessListener`,
-which is one of the firewall listeners (see :ref:`firewall_listeners`) that
-is triggered for each request matching the firewall map (see :ref:`firewall`).
-
-It uses an access map (which should be an instance of :class:`Symfony\\Component\\Security\\Http\\AccessMapInterface`)
-which contains request matchers and a corresponding set of attributes that
-are required for the current user to get access to the application::
-
- use Symfony\Component\Security\Http\AccessMap;
- use Symfony\Component\HttpFoundation\RequestMatcher;
- use Symfony\Component\Security\Http\Firewall\AccessListener;
-
- $accessMap = new AccessMap();
- $requestMatcher = new RequestMatcher('^/admin');
- $accessMap->add($requestMatcher, array('ROLE_ADMIN'));
-
- $accessListener = new AccessListener(
- $securityContext,
- $accessDecisionManager,
- $accessMap,
- $authenticationManager
- );
-
-Security context
-~~~~~~~~~~~~~~~~
-
-The access decision manager is also available to other parts of the application
-via the :method:`Symfony\\Component\\Security\\Core\\SecurityContext::isGranted`
-method of the :class:`Symfony\\Component\\Security\\Core\\SecurityContext`.
-A call to this method will directly delegate the question to the access
-decision manager::
-
- use Symfony\Component\Security\SecurityContext;
- use Symfony\Component\Security\Core\Exception\AccessDeniedException;
-
- $securityContext = new SecurityContext(
- $authenticationManager,
- $accessDecisionManager
- );
-
- if (!$securityContext->isGranted('ROLE_ADMIN')) {
- throw new AccessDeniedException();
- }
diff --git a/components/security/firewall.rst b/components/security/firewall.rst
deleted file mode 100644
index 1fce747905e..00000000000
--- a/components/security/firewall.rst
+++ /dev/null
@@ -1,131 +0,0 @@
-.. index::
- single: Security, Firewall
-
-The Firewall and Security Context
-=================================
-
-Central to the Security Component is the security context, which is an instance
-of :class:`Symfony\\Component\\Security\\Core\\SecurityContextInterface`. When all
-steps in the process of authenticating the user have been taken successfully,
-you can ask the security context if the authenticated user has access to a
-certain action or resource of the application::
-
- use Symfony\Component\Security\SecurityContext;
- use Symfony\Component\Security\Core\Exception\AccessDeniedException;
-
- $securityContext = new SecurityContext();
-
- // ... authenticate the user
-
- if (!$securityContext->isGranted('ROLE_ADMIN')) {
- throw new AccessDeniedException();
- }
-
-.. _firewall:
-
-A Firewall for HTTP Requests
-----------------------------
-
-Authenticating a user is done by the firewall. An application may have
-multiple secured areas, so the firewall is configured using a map of these
-secured areas. For each of these areas, the map contains a request matcher
-and a collection of listeners. The request matcher gives the firewall the
-ability to find out if the current request points to a secured area.
-The listeners are then asked if the current request can be used to authenticate
-the user::
-
- use Symfony\Component\Security\Http\FirewallMap;
- use Symfony\Component\HttpFoundation\RequestMatcher;
- use Symfony\Component\Security\Http\Firewall\ExceptionListener;
-
- $map = new FirewallMap();
-
- $requestMatcher = new RequestMatcher('^/secured-area/');
-
- // instances of Symfony\Component\Security\Http\Firewall\ListenerInterface
- $listeners = array(...);
-
- $exceptionListener = new ExceptionListener(...);
-
- $map->add($requestMatcher, $listeners, $exceptionListener);
-
-The firewall map will be given to the firewall as its first argument, together
-with the event dispatcher that is used by the :class:`Symfony\\Component\\HttpKernel\\HttpKernel`::
-
- use Symfony\Component\Security\Http\Firewall;
- use Symfony\Component\HttpKernel\KernelEvents;
-
- // the EventDispatcher used by the HttpKernel
- $dispatcher = ...;
-
- $firewall = new Firewall($map, $dispatcher);
-
- $dispatcher->addListener(KernelEvents::REQUEST, array($firewall, 'onKernelRequest');
-
-The firewall is registered to listen to the ``kernel.request`` event that
-will be dispatched by the ``HttpKernel`` at the beginning of each request
-it processes. This way, the firewall may prevent the user from going any
-further than allowed.
-
-.. _firewall_listeners:
-
-Firewall listeners
-~~~~~~~~~~~~~~~~~~
-
-When the firewall gets notified of the ``kernel.request`` event, it asks
-the firewall map if the request matches one of the secured areas. The first
-secured area that matches the request will return a set of corresponding
-firewall listeners (which each implement :class:`Symfony\\Component\\Security\\Http\\Firewall\\ListenerInterface`).
-These listeners will all be asked to handle the current request. This basically
-means: find out if the current request contains any information by which
-the user might be authenticated (for instance the Basic HTTP authentication
-listener checks if the request has a header called ``PHP_AUTH_USER``).
-
-Exception listener
-~~~~~~~~~~~~~~~~~~
-
-If any of the listeners throws an :class:`Symfony\\Component\\Security\\Core\\Exception\\AuthenticationException`,
-the exception listener that was provided when adding secured areas to the
-firewall map will jump in.
-
-The exception listener determines what happens next, based on the arguments
-it received when it was created. It may start the authentication procedure,
-perhaps ask the user to supply his credentials again (when he has only been
-authenticated based on a "remember-me" cookie), or transform the exception
-into an :class:`Symfony\\Component\\HttpKernel\\Exception\\AccessDeniedHttpException`,
-which will eventually result in an "HTTP/1.1 403: Access Denied" response.
-
-Entry points
-~~~~~~~~~~~~
-
-When the user is not authenticated at all (i.e. when the security context
-has no token yet), the firewall's entry point will be called to "start"
-the authentication process. An entry point should implement
-:class:`Symfony\\Component\\Security\\Http\\EntryPoint\\AuthenticationEntryPointInterface`,
-which has only one method: :method:`Symfony\\Component\\Security\\Http\\EntryPoint\\AuthenticationEntryPointInterface::start`.
-This method receives the current :class:`Symfony\\Component\\HttpFoundation\\Request`
-object and the exception by which the exception listener was triggered.
-The method should return a :class:`Symfony\\Component\\HttpFoundation\\Response`
-object. This could be, for instance, the page containing the login form or,
-in the case of Basic HTTP authentication, a response with a ``WWW-Authenticate``
-header, which will prompt the user to supply his username and password.
-
-Flow: Firewall, Authentication, Authorization
----------------------------------------------
-
-Hopefully you can now see a little bit about how the "flow" of the security
-context works:
-
-#. the Firewall is registered as a listener on the ``kernel.request`` event;
-#. at the beginning of the request, the Firewall checks the firewall map
- to see if any firewall should be active for this URL;
-#. If a firewall is found in the map for this URL, its listeners are notified
-#. each listener checks to see if the current request contains any authentication
- information - a listener may (a) authenticate a user, (b) throw an
- ``AuthenticationException``, or (c) do nothing (because there is no
- authentication information on the request);
-#. Once a user is authenticated, you'll use :doc:`/components/security/authorization`
- to deny access to certain resources.
-
-Read the next sections to find out more about :doc:`/components/security/authentication`
-and :doc:`/components/security/authorization`.
\ No newline at end of file
diff --git a/components/security/index.rst b/components/security/index.rst
deleted file mode 100644
index c735740690d..00000000000
--- a/components/security/index.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-Security
-========
-
-.. toctree::
- :maxdepth: 2
-
- introduction
- firewall
- authentication
- authorization
\ No newline at end of file
diff --git a/components/security/introduction.rst b/components/security/introduction.rst
deleted file mode 100644
index a6849f15c4f..00000000000
--- a/components/security/introduction.rst
+++ /dev/null
@@ -1,32 +0,0 @@
-.. index::
- single: Security
-
-The Security Component
-======================
-
-Introduction
-------------
-
-The Security Component provides a complete security system for your web
-application. It ships with facilities for authenticating using HTTP basic
-or digest authentication, interactive form login or X.509 certificate login,
-but also allows you to implement your own authentication strategies.
-Furthermore, the component provides ways to authorize authenticated users
-based on their roles, and it contains an advanced ACL system.
-
-Installation
-------------
-
-You can install the component in many different ways:
-
-* Use the official Git repository (https://github.com/symfony/Security);
-* :doc:`Install it via Composer` (``symfony/security`` on Packagist_).
-
-Sections
---------
-
-* :doc:`/components/security/firewall`
-* :doc:`/components/security/authentication`
-* :doc:`/components/security/authorization`
-
-.. _Packagist: https://packagist.org/packages/symfony/security
\ No newline at end of file
diff --git a/components/semaphore.rst b/components/semaphore.rst
new file mode 100644
index 00000000000..5715b426053
--- /dev/null
+++ b/components/semaphore.rst
@@ -0,0 +1,73 @@
+The Semaphore Component
+=======================
+
+ The Semaphore Component manages `semaphores`_, a mechanism to provide
+ exclusive access to a shared resource.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/semaphore
+
+.. include:: /components/require_autoload.rst.inc
+
+Usage
+-----
+
+In computer science, a semaphore is a variable or abstract data type used to
+control access to a common resource by multiple processes in a concurrent
+system such as a multitasking operating system. The main difference
+with :doc:`locks ` is that semaphores allow more than one process to
+access a resource, whereas locks only allow one process.
+
+Create semaphores with the :class:`Symfony\\Component\\Semaphore\\SemaphoreFactory`
+class, which in turn requires another class to manage the storage::
+
+ use Symfony\Component\Semaphore\SemaphoreFactory;
+ use Symfony\Component\Semaphore\Store\RedisStore;
+
+ $redis = new Redis();
+ $redis->connect('172.17.0.2');
+
+ $store = new RedisStore($redis);
+ $factory = new SemaphoreFactory($store);
+
+The semaphore is created by calling the
+:method:`Symfony\\Component\\Semaphore\\SemaphoreFactory::createSemaphore`
+method. Its first argument is an arbitrary string that represents the locked
+resource. Its second argument is the maximum number of processes allowed. Then, a
+call to the :method:`Symfony\\Component\\Semaphore\\SemaphoreInterface::acquire`
+method will try to acquire the semaphore::
+
+ // ...
+ $semaphore = $factory->createSemaphore('pdf-invoice-generation', 2);
+
+ if ($semaphore->acquire()) {
+ // The resource "pdf-invoice-generation" is locked.
+ // Here you can safely compute and generate the invoice.
+
+ $semaphore->release();
+ }
+
+If the semaphore can not be acquired, the method returns ``false``. The
+``acquire()`` method can be safely called repeatedly, even if the semaphore is
+already acquired.
+
+.. note::
+
+ Unlike other implementations, the Semaphore component distinguishes
+ semaphores instances even when they are created for the same resource. If a
+ semaphore has to be used by several services, they should share the same
+ ``Semaphore`` instance returned by the ``SemaphoreFactory::createSemaphore``
+ method.
+
+.. tip::
+
+ If you don't release the semaphore explicitly, it will be released
+ automatically on instance destruction. In some cases, it can be useful to
+ lock a resource across several requests. To disable the automatic release
+ behavior, set the fifth argument of the ``createSemaphore()`` method to ``false``.
+
+.. _`semaphores`: https://en.wikipedia.org/wiki/Semaphore_(programming)
diff --git a/components/serializer.rst b/components/serializer.rst
deleted file mode 100644
index 877bc396639..00000000000
--- a/components/serializer.rst
+++ /dev/null
@@ -1,194 +0,0 @@
-.. index::
- single: Serializer
- single: Components; Serializer
-
-The Serializer Component
-========================
-
- The Serializer Component is meant to be used to turn objects into a
- specific format (XML, JSON, Yaml, ...) and the other way around.
-
-In order to do so, the Serializer Component follows the following
-simple schema.
-
-.. _component-serializer-encoders:
-.. _component-serializer-normalizers:
-
-.. image:: /images/components/serializer/serializer_workflow.png
-
-As you can see in the picture above, an array is used as a man in
-the middle. This way, Encoders will only deal with turning specific
-**formats** into **arrays** and vice versa. The same way, Normalizers
-will deal with turning specific **objects** into **arrays** and vice versa.
-
-Serialization is a complicated topic, and while this component may not work
-in all cases, it can be a useful tool while developing tools to serialize
-and deserialize your objects.
-
-Installation
-------------
-
-You can install the component in many different ways:
-
-* Use the official Git repository (https://github.com/symfony/Serializer);
-* :doc:`Install it via Composer` (``symfony/serializer`` on `Packagist`_).
-
-Usage
------
-
-Using the Serializer component is really simple. You just need to set up
-the :class:`Symfony\\Component\\Serializer\\Serializer` specifying
-which Encoders and Normalizer are going to be available::
-
- use Symfony\Component\Serializer\Serializer;
- use Symfony\Component\Serializer\Encoder\XmlEncoder;
- use Symfony\Component\Serializer\Encoder\JsonEncoder;
- use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
-
- $encoders = array(new XmlEncoder(), new JsonEncoder());
- $normalizers = array(new GetSetMethodNormalizer());
-
- $serializer = new Serializer($normalizers, $encoders);
-
-Serializing an Object
----------------------
-
-For the sake of this example, assume the following class already
-exists in your project::
-
- namespace Acme;
-
- class Person
- {
- private $age;
- private $name;
-
- // Getters
- public function getName()
- {
- return $this->name;
- }
-
- public function getAge()
- {
- return $this->age;
- }
-
- // Setters
- public function setName($name)
- {
- $this->name = $name;
- }
-
- public function setAge($age)
- {
- $this->age = $age;
- }
- }
-
-Now, if you want to serialize this object into JSON, you only need to
-use the Serializer service created before::
-
- $person = new Acme\Person();
- $person->setName('foo');
- $person->setAge(99);
-
- $jsonContent = $serializer->serialize($person, 'json');
-
- // $jsonContent contains {"name":"foo","age":99}
-
- echo $jsonContent; // or return it in a Response
-
-The first parameter of the :method:`Symfony\\Component\\Serializer\\Serializer::serialize`
-is the object to be serialized and the second is used to choose the proper encoder,
-in this case :class:`Symfony\\Component\\Serializer\\Encoder\\JsonEncoder`.
-
-Ignoring Attributes when Serializing
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. versionadded:: 2.3
- The :method:`GetSetMethodNormalizer::setIgnoredAttributes`
- method was added in Symfony 2.3.
-
-As an option, there's a way to ignore attributes from the origin object when
-serializing. To remove those attributes use the
-:method:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer::setIgnoredAttributes`
-method on the normalizer definition::
-
- use Symfony\Component\Serializer\Serializer;
- use Symfony\Component\Serializer\Encoder\JsonEncoder;
- use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
-
- $normalizer = new GetSetMethodNormalizer();
- $normalizer->setIgnoredAttributes(array('age'));
- $encoder = new JsonEncoder();
-
- $serializer = new Serializer(array($normalizer), array($encoder));
- $serializer->serialize($person, 'json'); // Output: {"name":"foo"}
-
-Deserializing an Object
------------------------
-
-Let's see now how to do the exactly the opposite. This time, the information
-of the `People` class would be encoded in XML format::
-
- $data = <<
- foo
- 99
-
- EOF;
-
- $person = $serializer->deserialize($data,'Acme\Person','xml');
-
-In this case, :method:`Symfony\\Component\\Serializer\\Serializer::deserialize`
-needs three parameters:
-
-1. The information to be decoded
-2. The name of the class this information will be decoded to
-3. The encoder used to convert that information into an array
-
-Using Camelized Method Names for Underscored Attributes
--------------------------------------------------------
-
-.. versionadded:: 2.3
- The :method:`GetSetMethodNormalizer::setCamelizedAttributes`
- method was added in Symfony 2.3.
-
-Sometimes property names from the serialized content are underscored (e.g.
-``first_name``). Normally, these attributes will use get/set methods like
-``getFirst_name``, when ``getFirstName`` method is what you really want. To
-change that behavior use the
-:method:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer::setCamelizedAttributes`
-method on the normalizer definition::
-
- $encoder = new JsonEncoder();
- $normalizer = new GetSetMethodNormalizer();
- $normalizer->setCamelizedAttributes(array('first_name'));
-
- $serializer = new Serializer(array($normalizer), array($encoder));
-
- $json = <<deserialize($json, 'Acme\Person', 'json');
-
-As a final result, the deserializer uses the ``first_name`` attribute as if
-it were ``firstName`` and uses the ``getFirstName`` and ``setFirstName`` methods.
-
-JMSSerializer
--------------
-
-A popular third-party library, `JMS serializer`_, provides a more
-sophisticated albeit more complex solution. This library includes the
-ability to configure how your objects should be serialize/deserialized via
-annotations (as well as YML, XML and PHP), integration with the Doctrine ORM,
-and handling of other complex cases (e.g. circular references).
-
-.. _`JMS serializer`: https://github.com/schmittjoh/serializer
-.. _Packagist: https://packagist.org/packages/symfony/serializer
diff --git a/components/stopwatch.rst b/components/stopwatch.rst
deleted file mode 100644
index af5f98e823b..00000000000
--- a/components/stopwatch.rst
+++ /dev/null
@@ -1,101 +0,0 @@
-.. index::
- single: Stopwatch
- single: Components; Stopwatch
-
-The Stopwatch Component
-=======================
-
- Stopwatch component provides a way to profile code.
-
-.. versionadded:: 2.2
- The Stopwatch Component is new to Symfony 2.2. Previously, the ``Stopwatch``
- class was located in the ``HttpKernel`` component (and was new in 2.1).
-
-Installation
-------------
-
-You can install the component in two different ways:
-
-* Use the official Git repository (https://github.com/symfony/Stopwatch);
-* :doc:`Install it via Composer` (``symfony/stopwatch`` on `Packagist`_).
-
-Usage
------
-
-The Stopwatch component provides an easy and consistent way to measure execution
-time of certain parts of code so that you don't constantly have to parse
-microtime by yourself. Instead, use the simple
-:class:`Symfony\\Component\\Stopwatch\\Stopwatch` class::
-
- use Symfony\Component\Stopwatch\Stopwatch;
-
- $stopwatch = new Stopwatch();
- // Start event named 'eventName'
- $stopwatch->start('eventName');
- // ... some code goes here
- $event = $stopwatch->stop('eventName');
-
-You can also provide a category name to an event::
-
- $stopwatch->start('eventName', 'categoryName');
-
-You can consider categories as a way of tagging events. For example, the
-Symfony Profiler tool uses categories to nicely color-code different events.
-
-Periods
--------
-
-As you know from the real world, all stopwatches come with two buttons:
-one to start and stop the stopwatch, and another to measure the lap time.
-This is exactly what the :method:`Symfony\\Component\\Stopwatch\\Stopwatch::lap``
-method does::
-
- $stopwatch = new Stopwatch();
- // Start event named 'foo'
- $stopwatch->start('foo');
- // ... some code goes here
- $stopwatch->lap('foo');
- // ... some code goes here
- $stopwatch->lap('foo');
- // ... some other code goes here
- $event = $stopwatch->stop('foo');
-
-Lap information is stored as "periods" within the event. To get lap information
-call::
-
- $event->getPeriods();
-
-In addition to periods, you can get other useful information from the event object.
-For example::
-
- $event->getCategory(); // Returns the category the event was started in
- $event->getOrigin(); // Returns the event start time in milliseconds
- $event->ensureStopped(); // Stops all periods not already stopped
- $event->getStartTime(); // Returns the start time of the very first period
- $event->getEndTime(); // Returns the end time of the very last period
- $event->getDuration(); // Returns the event duration, including all periods
- $event->getMemory(); // Returns the max memory usage of all periods
-
-Sections
---------
-
-Sections are a way to logically split the timeline into groups. You can see
-how Symfony uses sections to nicely visualize the framework lifecycle in the
-Symfony Profiler tool. Here is a basic usage example using sections::
-
- $stopwatch = new Stopwatch();
-
- $stopwatch->openSection();
- $stopwatch->start('parsing_config_file', 'filesystem_operations');
- $stopwatch->stopSection('routing');
-
- $events = $stopwatch->getSectionEvents('routing');
-
-You can reopen a closed section by calling the :method:`Symfony\\Component\\Stopwatch\\Stopwatch::openSection``
-method and specifying the id of the section to be reopened::
-
- $stopwatch->openSection('routing');
- $stopwatch->start('building_config_tree');
- $stopwatch->stopSection('routing');
-
-.. _Packagist: https://packagist.org/packages/symfony/stopwatch
diff --git a/components/templating.rst b/components/templating.rst
deleted file mode 100644
index cd4b8a0bf0a..00000000000
--- a/components/templating.rst
+++ /dev/null
@@ -1,113 +0,0 @@
-.. index::
- single: Templating
- single: Components; Templating
-
-The Templating Component
-========================
-
- Templating provides all the tools needed to build any kind of template
- system.
-
- It provides an infrastructure to load template files and optionally monitor
- them for changes. It also provides a concrete template engine implementation
- using PHP with additional tools for escaping and separating templates into
- blocks and layouts.
-
-Installation
-------------
-
-You can install the component in many different ways:
-
-* Use the official Git repository (https://github.com/symfony/Templating);
-* :doc:`Install it via Composer` (``symfony/templating`` on `Packagist`_).
-
-Usage
------
-
-The :class:`Symfony\\Component\\Templating\\PhpEngine` class is the entry point
-of the component. It needs a template name parser
-(:class:`Symfony\\Component\\Templating\\TemplateNameParserInterface`) to
-convert a template name to a template reference and template loader
-(:class:`Symfony\\Component\\Templating\\Loader\\LoaderInterface`) to find the
-template associated to a reference::
-
- use Symfony\Component\Templating\PhpEngine;
- use Symfony\Component\Templating\TemplateNameParser;
- use Symfony\Component\Templating\Loader\FilesystemLoader;
-
- $loader = new FilesystemLoader(__DIR__ . '/views/%name%');
-
- $view = new PhpEngine(new TemplateNameParser(), $loader);
-
- echo $view->render('hello.php', array('firstname' => 'Fabien'));
-
-The :method:`Symfony\\Component\\Templating\\PhpEngine::render` method executes
-the file `views/hello.php` and returns the output text.
-
-.. code-block:: html+php
-
-
- Hello, !
-
-Template Inheritance with Slots
--------------------------------
-
-The template inheritance is designed to share layouts with many templates.
-
-.. code-block:: html+php
-
-
-
-
- output('title', 'Default title') ?>
-
-
- output('_content') ?>
-
-
-
-The :method:`Symfony\\Component\\Templating\\PhpEngine::extend` method is called in the
-sub-template to set its parent template.
-
-.. code-block:: html+php
-
-
- extend('layout.php') ?>
-
- set('title', $page->title) ?>
-
-
- title ?>
-
-
- body ?>
-
-
-To use template inheritance, the :class:`Symfony\\Component\\Templating\\Helper\\SlotsHelper`
-helper must be registered::
-
- use Symfony\Component\Templating\Helper\SlotsHelper;
-
- $view->set(new SlotsHelper());
-
- // Retrieve page object
- $page = ...;
-
- echo $view->render('page.php', array('page' => $page));
-
-.. note::
-
- Multiple levels of inheritance is possible: a layout can extend an other
- layout.
-
-Output Escaping
----------------
-
-This documentation is still being written.
-
-The Asset Helper
-----------------
-
-This documentation is still being written.
-
-.. _Packagist: https://packagist.org/packages/symfony/templating
\ No newline at end of file
diff --git a/components/type_info.rst b/components/type_info.rst
new file mode 100644
index 00000000000..817c7f1d61a
--- /dev/null
+++ b/components/type_info.rst
@@ -0,0 +1,202 @@
+The TypeInfo Component
+======================
+
+The TypeInfo component extracts type information from PHP elements like properties,
+arguments and return types.
+
+This component provides:
+
+* A powerful ``Type`` definition that can handle unions, intersections, and generics
+ (and can be extended to support more types in the future);
+* A way to get types from PHP elements such as properties, method arguments,
+ return types, and raw strings.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/type-info
+
+.. include:: /components/require_autoload.rst.inc
+
+Usage
+-----
+
+This component gives you a :class:`Symfony\\Component\\TypeInfo\\Type` object that
+represents the PHP type of anything you built or asked to resolve.
+
+There are two ways to use this component. First one is to create a type manually thanks
+to the :class:`Symfony\\Component\\TypeInfo\\Type` static methods as following::
+
+ use Symfony\Component\TypeInfo\Type;
+
+ Type::int();
+ Type::nullable(Type::string());
+ Type::generic(Type::object(Collection::class), Type::int());
+ Type::list(Type::bool());
+ Type::intersection(Type::object(\Stringable::class), Type::object(\Iterator::class));
+
+Many others methods are available and can be found
+in :class:`Symfony\\Component\\TypeInfo\\TypeFactoryTrait`.
+
+You can also use a generic method that detects the type automatically::
+
+ Type::fromValue(1.1); // same as Type::float()
+ Type::fromValue('...'); // same as Type::string()
+ Type::fromValue(false); // same as Type::false()
+
+.. versionadded:: 7.3
+
+ The ``fromValue()`` method was introduced in Symfony 7.3.
+
+Resolvers
+~~~~~~~~~
+
+The second way to use the component is by using ``TypeInfo`` to resolve a type
+based on reflection or a simple string. This approach is designed for libraries
+that need a simple way to describe a class or anything with a type::
+
+ use Symfony\Component\TypeInfo\Type;
+ use Symfony\Component\TypeInfo\TypeResolver\TypeResolver;
+
+ class Dummy
+ {
+ public function __construct(
+ public int $id,
+ ) {
+ }
+ }
+
+ // Instantiate a new resolver
+ $typeResolver = TypeResolver::create();
+
+ // Then resolve types for any subject
+ $typeResolver->resolve(new \ReflectionProperty(Dummy::class, 'id')); // returns an "int" Type instance
+ $typeResolver->resolve('bool'); // returns a "bool" Type instance
+
+ // Types can be instantiated thanks to static factories
+ $type = Type::list(Type::nullable(Type::bool()));
+
+ // Type instances have several helper methods
+
+ // for collections, it returns the type of the item used as the key;
+ // in this example, the collection is a list, so it returns an "int" Type instance
+ $keyType = $type->getCollectionKeyType();
+
+ // you can chain the utility methods (e.g. to introspect the values of the collection)
+ // the following code will return true
+ $isValueNullable = $type->getCollectionValueType()->isNullable();
+
+Each of these calls will return you a ``Type`` instance that corresponds to the
+static method used. You can also resolve types from a string (as shown in the
+``bool`` parameter of the previous example)
+
+PHPDoc Parsing
+~~~~~~~~~~~~~~
+
+In many cases, you may not have cleanly typed properties or may need more precise
+type definitions provided by advanced PHPDoc. To achieve this, you can use a string
+resolver based on the PHPDoc annotations.
+
+First, run the command ``composer require phpstan/phpdoc-parser`` to install the
+PHP package required for string resolving. Then, follow these steps::
+
+ use Symfony\Component\TypeInfo\TypeResolver\TypeResolver;
+
+ class Dummy
+ {
+ public function __construct(
+ public int $id,
+ /** @var string[] $tags */
+ public array $tags,
+ ) {
+ }
+ }
+
+ $typeResolver = TypeResolver::create();
+ $typeResolver->resolve(new \ReflectionProperty(Dummy::class, 'id')); // returns an "int" Type
+ $typeResolver->resolve(new \ReflectionProperty(Dummy::class, 'tags')); // returns a collection with "int" as key and "string" as values Type
+
+Advanced Usages
+~~~~~~~~~~~~~~~
+
+The TypeInfo component provides various methods to manipulate and check types,
+depending on your needs.
+
+**Identify** a type::
+
+ // define a simple integer type
+ $type = Type::int();
+ // check if the type matches a specific identifier
+ $type->isIdentifiedBy(TypeIdentifier::INT); // true
+ $type->isIdentifiedBy(TypeIdentifier::STRING); // false
+
+ // define a union type (equivalent to PHP's int|string)
+ $type = Type::union(Type::string(), Type::int());
+ // now the second check is true because the union type contains the string type
+ $type->isIdentifiedBy(TypeIdentifier::INT); // true
+ $type->isIdentifiedBy(TypeIdentifier::STRING); // true
+
+ class DummyParent {}
+ class Dummy extends DummyParent implements DummyInterface {}
+
+ // define an object type
+ $type = Type::object(Dummy::class);
+
+ // check if the type is an object or matches a specific class
+ $type->isIdentifiedBy(TypeIdentifier::OBJECT); // true
+ $type->isIdentifiedBy(Dummy::class); // true
+ // check if it inherits/implements something
+ $type->isIdentifiedBy(DummyParent::class); // true
+ $type->isIdentifiedBy(DummyInterface::class); // true
+
+Checking if a type **accepts a value**::
+
+ $type = Type::int();
+ // check if the type accepts a given value
+ $type->accepts(123); // true
+ $type->accepts('z'); // false
+
+ $type = Type::union(Type::string(), Type::int());
+ // now the second check is true because the union type accepts either an int or a string value
+ $type->accepts(123); // true
+ $type->accepts('z'); // true
+
+.. versionadded:: 7.3
+
+ The :method:`Symfony\\Component\\TypeInfo\\Type::accepts`
+ method was introduced in Symfony 7.3.
+
+Using callables for **complex checks**::
+
+ class Foo
+ {
+ private int $integer;
+ private string $string;
+ private ?float $float;
+ }
+
+ $reflClass = new \ReflectionClass(Foo::class);
+
+ $resolver = TypeResolver::create();
+ $integerType = $resolver->resolve($reflClass->getProperty('integer'));
+ $stringType = $resolver->resolve($reflClass->getProperty('string'));
+ $floatType = $resolver->resolve($reflClass->getProperty('float'));
+
+ // define a callable to validate non-nullable number types
+ $isNonNullableNumber = function (Type $type): bool {
+ if ($type->isNullable()) {
+ return false;
+ }
+
+ if ($type->isIdentifiedBy(TypeIdentifier::INT) || $type->isIdentifiedBy(TypeIdentifier::FLOAT)) {
+ return true;
+ }
+
+ return false;
+ };
+
+ $integerType->isSatisfiedBy($isNonNullableNumber); // true
+ $stringType->isSatisfiedBy($isNonNullableNumber); // false
+ $floatType->isSatisfiedBy($isNonNullableNumber); // false
diff --git a/components/uid.rst b/components/uid.rst
new file mode 100644
index 00000000000..6c92fff0af9
--- /dev/null
+++ b/components/uid.rst
@@ -0,0 +1,723 @@
+The UID Component
+=================
+
+ The UID component provides utilities to work with `unique identifiers`_ (UIDs)
+ such as UUIDs and ULIDs.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/uid
+
+.. include:: /components/require_autoload.rst.inc
+
+.. _uuid:
+
+UUIDs
+-----
+
+`UUIDs`_ (*universally unique identifiers*) are one of the most popular UIDs in
+the software industry. UUIDs are 128-bit numbers usually represented as five
+groups of hexadecimal characters: ``xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx``
+(the ``M`` digit is the UUID version and the ``N`` digit is the UUID variant).
+
+Generating UUIDs
+~~~~~~~~~~~~~~~~
+
+Use the named constructors of the ``Uuid`` class or any of the specific classes
+to create each type of UUID:
+
+**UUID v1** (time-based)
+
+Generates the UUID using a timestamp and the MAC address of your device
+(`read UUIDv1 spec `__).
+Both are obtained automatically, so you don't have to pass any constructor argument::
+
+ use Symfony\Component\Uid\Uuid;
+
+ // $uuid is an instance of Symfony\Component\Uid\UuidV1
+ $uuid = Uuid::v1();
+
+.. tip::
+
+ It's recommended to use UUIDv7 instead of UUIDv1 because it provides
+ better entropy.
+
+**UUID v2** (DCE security)
+
+Similar to UUIDv1 but with a very high likelihood of ID collision
+(`read UUIDv2 spec `__).
+It's part of the authentication mechanism of DCE (Distributed Computing Environment)
+and the UUID includes the POSIX UIDs (user/group ID) of the user who generated it.
+This UUID variant is **not implemented** by the Uid component.
+
+**UUID v3** (name-based, MD5)
+
+Generates UUIDs from names that belong, and are unique within, some given namespace
+(`read UUIDv3 spec `__).
+This variant is useful to generate deterministic UUIDs from arbitrary strings.
+It works by populating the UUID contents with the``md5`` hash of concatenating
+the namespace and the name::
+
+ use Symfony\Component\Uid\Uuid;
+
+ // you can use any of the predefined namespaces...
+ $namespace = Uuid::fromString(Uuid::NAMESPACE_OID);
+ // ...or use a random namespace:
+ // $namespace = Uuid::v4();
+
+ // $name can be any arbitrary string
+ // $uuid is an instance of Symfony\Component\Uid\UuidV3
+ $uuid = Uuid::v3($namespace, $name);
+
+These are the default namespaces defined by the standard:
+
+* ``Uuid::NAMESPACE_DNS`` if you are generating UUIDs for `DNS entries `__
+* ``Uuid::NAMESPACE_URL`` if you are generating UUIDs for `URLs `__
+* ``Uuid::NAMESPACE_OID`` if you are generating UUIDs for `OIDs (object identifiers) `__
+* ``Uuid::NAMESPACE_X500`` if you are generating UUIDs for `X500 DNs (distinguished names) `__
+
+**UUID v4** (random)
+
+Generates a random UUID (`read UUIDv4 spec `__).
+Because of its randomness, it ensures uniqueness across distributed systems
+without the need for a central coordinating entity. It's privacy-friendly
+because it doesn't contain any information about where and when it was generated::
+
+ use Symfony\Component\Uid\Uuid;
+
+ // $uuid is an instance of Symfony\Component\Uid\UuidV4
+ $uuid = Uuid::v4();
+
+**UUID v5** (name-based, SHA-1)
+
+It's the same as UUIDv3 (explained above) but it uses ``sha1`` instead of
+``md5`` to hash the given namespace and name (`read UUIDv5 spec `__).
+This makes it more secure and less prone to hash collisions.
+
+.. _uid-uuid-v6:
+
+**UUID v6** (reordered time-based)
+
+It rearranges the time-based fields of the UUIDv1 to make it lexicographically
+sortable (like :ref:`ULIDs `). It's more efficient for database indexing
+(`read UUIDv6 spec `__)::
+
+ use Symfony\Component\Uid\Uuid;
+
+ // $uuid is an instance of Symfony\Component\Uid\UuidV6
+ $uuid = Uuid::v6();
+
+.. tip::
+
+ It's recommended to use UUIDv7 instead of UUIDv6 because it provides
+ better entropy.
+
+.. _uid-uuid-v7:
+
+**UUID v7** (UNIX timestamp)
+
+Generates time-ordered UUIDs based on a high-resolution Unix Epoch timestamp
+source (the number of milliseconds since midnight 1 Jan 1970 UTC, leap seconds excluded)
+(`read UUIDv7 spec `__).
+It's recommended to use this version over UUIDv1 and UUIDv6 because it provides
+better entropy (and a more strict chronological order of UUID generation)::
+
+ use Symfony\Component\Uid\Uuid;
+
+ // $uuid is an instance of Symfony\Component\Uid\UuidV7
+ $uuid = Uuid::v7();
+
+**UUID v8** (custom)
+
+Provides an RFC-compatible format for experimental or vendor-specific use cases
+(`read UUIDv8 spec `__).
+The only requirement is to set the variant and version bits of the UUID. The rest
+of the UUID value is specific to each implementation and no format should be assumed::
+
+ use Symfony\Component\Uid\Uuid;
+
+ // $uuid is an instance of Symfony\Component\Uid\UuidV8
+ $uuid = Uuid::v8();
+
+If your UUID value is already generated in another format, use any of the
+following methods to create a ``Uuid`` object from it::
+
+ // all the following examples would generate the same Uuid object
+ $uuid = Uuid::fromString('d9e7a184-5d5b-11ea-a62a-3499710062d0');
+ $uuid = Uuid::fromBinary("\xd9\xe7\xa1\x84\x5d\x5b\x11\xea\xa6\x2a\x34\x99\x71\x00\x62\xd0");
+ $uuid = Uuid::fromBase32('6SWYGR8QAV27NACAHMK5RG0RPG');
+ $uuid = Uuid::fromBase58('TuetYWNHhmuSQ3xPoVLv9M');
+ $uuid = Uuid::fromRfc4122('d9e7a184-5d5b-11ea-a62a-3499710062d0');
+
+You can also use the ``UuidFactory`` to generate UUIDs. First, you may
+configure the behavior of the factory using configuration files::
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/uid.yaml
+ framework:
+ uid:
+ default_uuid_version: 7
+ name_based_uuid_version: 5
+ name_based_uuid_namespace: 6ba7b810-9dad-11d1-80b4-00c04fd430c8
+ time_based_uuid_version: 7
+ time_based_uuid_node: 121212121212
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/uid.php
+ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+ return static function (ContainerConfigurator $container): void {
+ $services = $container->services()
+ ->defaults()
+ ->autowire()
+ ->autoconfigure();
+
+ $container->extension('framework', [
+ 'uid' => [
+ 'default_uuid_version' => 7,
+ 'name_based_uuid_version' => 5,
+ 'name_based_uuid_namespace' => '6ba7b810-9dad-11d1-80b4-00c04fd430c8',
+ 'time_based_uuid_version' => 7,
+ 'time_based_uuid_node' => 121212121212,
+ ],
+ ]);
+ };
+
+Then, you can inject the factory in your services and use it to generate UUIDs based
+on the configuration you defined::
+
+ namespace App\Service;
+
+ use Symfony\Component\Uid\Factory\UuidFactory;
+
+ class FooService
+ {
+ public function __construct(
+ private UuidFactory $uuidFactory,
+ ) {
+ }
+
+ public function generate(): void
+ {
+ // This creates a UUID of the version given in the configuration file (v7 by default)
+ $uuid = $this->uuidFactory->create();
+
+ $nameBasedUuid = $this->uuidFactory->nameBased(/** ... */);
+ $randomBasedUuid = $this->uuidFactory->randomBased();
+ $timestampBased = $this->uuidFactory->timeBased();
+
+ // ...
+ }
+ }
+
+Converting UUIDs
+~~~~~~~~~~~~~~~~
+
+Use these methods to transform the UUID object into different bases::
+
+ $uuid = Uuid::fromString('d9e7a184-5d5b-11ea-a62a-3499710062d0');
+
+ $uuid->toBinary(); // string(16) "\xd9\xe7\xa1\x84\x5d\x5b\x11\xea\xa6\x2a\x34\x99\x71\x00\x62\xd0"
+ $uuid->toBase32(); // string(26) "6SWYGR8QAV27NACAHMK5RG0RPG"
+ $uuid->toBase58(); // string(22) "TuetYWNHhmuSQ3xPoVLv9M"
+ $uuid->toRfc4122(); // string(36) "d9e7a184-5d5b-11ea-a62a-3499710062d0"
+ $uuid->toHex(); // string(34) "0xd9e7a1845d5b11eaa62a3499710062d0"
+ $uuid->toString(); // string(36) "d9e7a184-5d5b-11ea-a62a-3499710062d0"
+
+.. versionadded:: 7.1
+
+ The ``toString()`` method was introduced in Symfony 7.1.
+
+You can also convert some UUID versions to others::
+
+ // convert V1 to V6 or V7
+ $uuid = Uuid::v1();
+
+ $uuid->toV6(); // returns a Symfony\Component\Uid\UuidV6 instance
+ $uuid->toV7(); // returns a Symfony\Component\Uid\UuidV7 instance
+
+ // convert V6 to V7
+ $uuid = Uuid::v6();
+
+ $uuid->toV7(); // returns a Symfony\Component\Uid\UuidV7 instance
+
+.. versionadded:: 7.1
+
+ The :method:`Symfony\\Component\\Uid\\UuidV1::toV6`,
+ :method:`Symfony\\Component\\Uid\\UuidV1::toV7` and
+ :method:`Symfony\\Component\\Uid\\UuidV6::toV7`
+ methods were introduced in Symfony 7.1.
+
+Working with UUIDs
+~~~~~~~~~~~~~~~~~~
+
+UUID objects created with the ``Uuid`` class can use the following methods
+(which are equivalent to the ``uuid_*()`` method of the PHP extension)::
+
+ use Symfony\Component\Uid\NilUuid;
+ use Symfony\Component\Uid\Uuid;
+
+ // checking if the UUID is null (note that the class is called
+ // NilUuid instead of NullUuid to follow the UUID standard notation)
+ $uuid = Uuid::v4();
+ $uuid instanceof NilUuid; // false
+
+ // checking the type of UUID
+ use Symfony\Component\Uid\UuidV4;
+ $uuid = Uuid::v4();
+ $uuid instanceof UuidV4; // true
+
+ // getting the UUID datetime (it's only available in certain UUID types)
+ $uuid = Uuid::v1();
+ $uuid->getDateTime(); // returns a \DateTimeImmutable instance
+
+ // checking if a given value is valid as UUID
+ $isValid = Uuid::isValid($uuid); // true or false
+
+ // comparing UUIDs and checking for equality
+ $uuid1 = Uuid::v1();
+ $uuid4 = Uuid::v4();
+ $uuid1->equals($uuid4); // false
+
+ // this method returns:
+ // * int(0) if $uuid1 and $uuid4 are equal
+ // * int > 0 if $uuid1 is greater than $uuid4
+ // * int < 0 if $uuid1 is less than $uuid4
+ $uuid1->compare($uuid4); // e.g. int(4)
+
+If you're working with different UUIDs format and want to validate them,
+you can use the ``$format`` parameter of the :method:`Symfony\\Component\\Uid\\Uuid::isValid`
+method to specify the UUID format you're expecting::
+
+ use Symfony\Component\Uid\Uuid;
+
+ $isValid = Uuid::isValid('90067ce4-f083-47d2-a0f4-c47359de0f97', Uuid::FORMAT_RFC_4122); // accept only RFC 4122 UUIDs
+ $isValid = Uuid::isValid('3aJ7CNpDMfXPZrCsn4Cgey', Uuid::FORMAT_BASE_32 | Uuid::FORMAT_BASE_58); // accept multiple formats
+
+The following constants are available:
+
+* ``Uuid::FORMAT_BINARY``
+* ``Uuid::FORMAT_BASE_32``
+* ``Uuid::FORMAT_BASE_58``
+* ``Uuid::FORMAT_RFC_4122``
+* ``Uuid::FORMAT_RFC_9562`` (equivalent to ``Uuid::FORMAT_RFC_4122``)
+
+You can also use the ``Uuid::FORMAT_ALL`` constant to accept any UUID format.
+By default, only the RFC 4122 format is accepted.
+
+.. versionadded:: 7.2
+
+ The ``$format`` parameter of the :method:`Symfony\\Component\\Uid\\Uuid::isValid`
+ method and the related constants were introduced in Symfony 7.2.
+
+Storing UUIDs in Databases
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you :doc:`use Doctrine `, consider using the ``uuid`` Doctrine
+type, which converts to/from UUID objects automatically::
+
+ // src/Entity/Product.php
+ namespace App\Entity;
+
+ use Doctrine\ORM\Mapping as ORM;
+ use Symfony\Bridge\Doctrine\Types\UuidType;
+ use Symfony\Component\Uid\Uuid;
+
+ #[ORM\Entity(repositoryClass: ProductRepository::class)]
+ class Product
+ {
+ #[ORM\Column(type: UuidType::NAME)]
+ private Uuid $someProperty;
+
+ // ...
+ }
+
+There's also a Doctrine generator to help auto-generate UUID values for the
+entity primary keys::
+
+ namespace App\Entity;
+
+ use Doctrine\ORM\Mapping as ORM;
+ use Symfony\Bridge\Doctrine\Types\UuidType;
+ use Symfony\Component\Uid\Uuid;
+
+ class User implements UserInterface
+ {
+ #[ORM\Id]
+ #[ORM\Column(type: UuidType::NAME, unique: true)]
+ #[ORM\GeneratedValue(strategy: 'CUSTOM')]
+ #[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
+ private ?Uuid $id;
+
+ public function getId(): ?Uuid
+ {
+ return $this->id;
+ }
+
+ // ...
+ }
+
+.. warning::
+
+ Using UUIDs as primary keys is usually not recommended for performance reasons:
+ indexes are slower and take more space (because UUIDs in binary format take
+ 128 bits instead of 32/64 bits for auto-incremental integers) and the non-sequential
+ nature of UUIDs fragments indexes. :ref:`UUID v6 ` and :ref:`UUID v7 `
+ are the only variants that solve the fragmentation issue (but the index size issue remains).
+
+When using built-in Doctrine repository methods (e.g. ``findOneBy()``), Doctrine
+knows how to convert these UUID types to build the SQL query
+(e.g. ``->findOneBy(['user' => $user->getUuid()])``). However, when using DQL
+queries or building the query yourself, you'll need to set ``uuid`` as the type
+of the UUID parameters::
+
+ // src/Repository/ProductRepository.php
+
+ // ...
+ use Doctrine\DBAL\ParameterType;
+ use Symfony\Bridge\Doctrine\Types\UuidType;
+
+ class ProductRepository extends ServiceEntityRepository
+ {
+ // ...
+
+ public function findUserProducts(User $user): array
+ {
+ $qb = $this->createQueryBuilder('p')
+ // ...
+ // add UuidType::NAME as the third argument to tell Doctrine that this is a UUID
+ ->setParameter('user', $user->getUuid(), UuidType::NAME)
+
+ // alternatively, you can convert it to a value compatible with
+ // the type inferred by Doctrine
+ ->setParameter('user', $user->getUuid()->toBinary(), ParameterType::BINARY)
+ ;
+
+ // ...
+ }
+ }
+
+.. _ulid:
+
+ULIDs
+-----
+
+`ULIDs`_ (*Universally Unique Lexicographically Sortable Identifier*) are 128-bit
+numbers usually represented as a 26-character string: ``TTTTTTTTTTRRRRRRRRRRRRRRRR``
+(where ``T`` represents a timestamp and ``R`` represents the random bits).
+
+ULIDs are an alternative to UUIDs when using those is impractical. They provide
+128-bit compatibility with UUID, they are lexicographically sortable and they
+are encoded as 26-character strings (vs 36-character UUIDs).
+
+.. note::
+
+ If you generate more than one ULID during the same millisecond in the
+ same process then the random portion is incremented by one bit in order
+ to provide monotonicity for sorting. The random portion is not random
+ compared to the previous ULID in this case.
+
+Generating ULIDs
+~~~~~~~~~~~~~~~~
+
+Instantiate the ``Ulid`` class to generate a random ULID value::
+
+ use Symfony\Component\Uid\Ulid;
+
+ $ulid = new Ulid(); // e.g. 01AN4Z07BY79KA1307SR9X4MV3
+
+If your ULID value is already generated in another format, use any of the
+following methods to create a ``Ulid`` object from it::
+
+ // all the following examples would generate the same Ulid object
+ $ulid = Ulid::fromString('01E439TP9XJZ9RPFH3T1PYBCR8');
+ $ulid = Ulid::fromBinary("\x01\x71\x06\x9d\x59\x3d\x97\xd3\x8b\x3e\x23\xd0\x6d\xe5\xb3\x08");
+ $ulid = Ulid::fromBase32('01E439TP9XJZ9RPFH3T1PYBCR8');
+ $ulid = Ulid::fromBase58('1BKocMc5BnrVcuq2ti4Eqm');
+ $ulid = Ulid::fromRfc4122('0171069d-593d-97d3-8b3e-23d06de5b308');
+
+Like UUIDs, ULIDs have their own factory, ``UlidFactory``, that can be used to generate them::
+
+ namespace App\Service;
+
+ use Symfony\Component\Uid\Factory\UlidFactory;
+
+ class FooService
+ {
+ public function __construct(
+ private UlidFactory $ulidFactory,
+ ) {
+ }
+
+ public function generate(): void
+ {
+ $ulid = $this->ulidFactory->create();
+
+ // ...
+ }
+ }
+
+There's also a special ``NilUlid`` class to represent ULID ``null`` values::
+
+ use Symfony\Component\Uid\NilUlid;
+
+ $ulid = new NilUlid();
+ // equivalent to $ulid = new Ulid('00000000000000000000000000');
+
+Converting ULIDs
+~~~~~~~~~~~~~~~~
+
+Use these methods to transform the ULID object into different bases::
+
+ $ulid = Ulid::fromString('01E439TP9XJZ9RPFH3T1PYBCR8');
+
+ $ulid->toBinary(); // string(16) "\x01\x71\x06\x9d\x59\x3d\x97\xd3\x8b\x3e\x23\xd0\x6d\xe5\xb3\x08"
+ $ulid->toBase32(); // string(26) "01E439TP9XJZ9RPFH3T1PYBCR8"
+ $ulid->toBase58(); // string(22) "1BKocMc5BnrVcuq2ti4Eqm"
+ $ulid->toRfc4122(); // string(36) "0171069d-593d-97d3-8b3e-23d06de5b308"
+ $ulid->toHex(); // string(34) "0x0171069d593d97d38b3e23d06de5b308"
+
+Working with ULIDs
+~~~~~~~~~~~~~~~~~~
+
+ULID objects created with the ``Ulid`` class can use the following methods::
+
+ use Symfony\Component\Uid\Ulid;
+
+ $ulid1 = new Ulid();
+ $ulid2 = new Ulid();
+
+ // checking if a given value is valid as ULID
+ $isValid = Ulid::isValid($ulidValue); // true or false
+
+ // getting the ULID datetime
+ $ulid1->getDateTime(); // returns a \DateTimeImmutable instance
+
+ // comparing ULIDs and checking for equality
+ $ulid1->equals($ulid2); // false
+ // this method returns $ulid1 <=> $ulid2
+ $ulid1->compare($ulid2); // e.g. int(-1)
+
+Storing ULIDs in Databases
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you :doc:`use Doctrine `, consider using the ``ulid`` Doctrine
+type, which converts to/from ULID objects automatically::
+
+ // src/Entity/Product.php
+ namespace App\Entity;
+
+ use Doctrine\ORM\Mapping as ORM;
+ use Symfony\Bridge\Doctrine\Types\UlidType;
+ use Symfony\Component\Uid\Ulid;
+
+ #[ORM\Entity(repositoryClass: ProductRepository::class)]
+ class Product
+ {
+ #[ORM\Column(type: UlidType::NAME)]
+ private Ulid $someProperty;
+
+ // ...
+ }
+
+There's also a Doctrine generator to help auto-generate ULID values for the
+entity primary keys::
+
+ namespace App\Entity;
+
+ use Doctrine\ORM\Mapping as ORM;
+ use Symfony\Bridge\Doctrine\Types\UlidType;
+ use Symfony\Component\Uid\Ulid;
+
+ class Product
+ {
+ #[ORM\Id]
+ #[ORM\Column(type: UlidType::NAME, unique: true)]
+ #[ORM\GeneratedValue(strategy: 'CUSTOM')]
+ #[ORM\CustomIdGenerator(class: 'doctrine.ulid_generator')]
+ private ?Ulid $id;
+
+ public function getId(): ?Ulid
+ {
+ return $this->id;
+ }
+
+ // ...
+ }
+
+.. warning::
+
+ Using ULIDs as primary keys is usually not recommended for performance reasons.
+ Although ULIDs don't suffer from index fragmentation issues (because the values
+ are sequential), their indexes are slower and take more space (because ULIDs
+ in binary format take 128 bits instead of 32/64 bits for auto-incremental integers).
+
+When using built-in Doctrine repository methods (e.g. ``findOneBy()``), Doctrine
+knows how to convert these ULID types to build the SQL query
+(e.g. ``->findOneBy(['user' => $user->getUlid()])``). However, when using DQL
+queries or building the query yourself, you'll need to set ``ulid`` as the type
+of the ULID parameters::
+
+ // src/Repository/ProductRepository.php
+
+ // ...
+ use Symfony\Bridge\Doctrine\Types\UlidType;
+
+ class ProductRepository extends ServiceEntityRepository
+ {
+ // ...
+
+ public function findUserProducts(User $user): array
+ {
+ $qb = $this->createQueryBuilder('p')
+ // ...
+ // add UlidType::NAME as the third argument to tell Doctrine that this is a ULID
+ ->setParameter('user', $user->getUlid(), UlidType::NAME)
+
+ // alternatively, you can convert it to a value compatible with
+ // the type inferred by Doctrine
+ ->setParameter('user', $user->getUlid()->toBinary())
+ ;
+
+ // ...
+ }
+ }
+
+Generating and Inspecting UUIDs/ULIDs in the Console
+----------------------------------------------------
+
+This component provides several commands to generate and inspect UUIDs/ULIDs in
+the console. They are not enabled by default, so you must add the following
+configuration in your application before using these commands:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/services.yaml
+ services:
+ Symfony\Component\Uid\Command\GenerateUlidCommand: ~
+ Symfony\Component\Uid\Command\GenerateUuidCommand: ~
+ Symfony\Component\Uid\Command\InspectUlidCommand: ~
+ Symfony\Component\Uid\Command\InspectUuidCommand: ~
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/services.php
+ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+ use Symfony\Component\Uid\Command\GenerateUlidCommand;
+ use Symfony\Component\Uid\Command\GenerateUuidCommand;
+ use Symfony\Component\Uid\Command\InspectUlidCommand;
+ use Symfony\Component\Uid\Command\InspectUuidCommand;
+
+ return static function (ContainerConfigurator $container): void {
+ // ...
+
+ $services
+ ->set(GenerateUlidCommand::class)
+ ->set(GenerateUuidCommand::class)
+ ->set(InspectUlidCommand::class)
+ ->set(InspectUuidCommand::class);
+ };
+
+Now you can generate UUIDs/ULIDs as follows (add the ``--help`` option to the
+commands to learn about all their options):
+
+.. code-block:: terminal
+
+ # generate 1 random-based UUID
+ $ php bin/console uuid:generate --random-based
+
+ # generate 1 time-based UUID with a specific node
+ $ php bin/console uuid:generate --time-based=now --node=fb3502dc-137e-4849-8886-ac90d07f64a7
+
+ # generate 2 UUIDs and output them in base58 format
+ $ php bin/console uuid:generate --count=2 --format=base58
+
+ # generate 1 ULID with the current time as the timestamp
+ $ php bin/console ulid:generate
+
+ # generate 1 ULID with a specific timestamp
+ $ php bin/console ulid:generate --time="2021-02-02 14:00:00"
+
+ # generate 2 ULIDs and output them in RFC4122 format
+ $ php bin/console ulid:generate --count=2 --format=rfc4122
+
+In addition to generating new UIDs, you can also inspect them with the following
+commands to show all the information for a given UID:
+
+.. code-block:: terminal
+
+ $ php bin/console uuid:inspect d0a3a023-f515-4fe0-915c-575e63693998
+ ---------------------- --------------------------------------
+ Label Value
+ ---------------------- --------------------------------------
+ Version 4
+ Canonical (RFC 4122) d0a3a023-f515-4fe0-915c-575e63693998
+ Base 58 SmHvuofV4GCF7QW543rDD9
+ Base 32 6GMEG27X8N9ZG92Q2QBSHPJECR
+ ---------------------- --------------------------------------
+
+ $ php bin/console ulid:inspect 01F2TTCSYK1PDRH73Z41BN1C4X
+ --------------------- --------------------------------------
+ Label Value
+ --------------------- --------------------------------------
+ Canonical (Base 32) 01F2TTCSYK1PDRH73Z41BN1C4X
+ Base 58 1BYGm16jS4kX3VYCysKKq6
+ RFC 4122 0178b5a6-67d3-0d9b-889c-7f205750b09d
+ --------------------- --------------------------------------
+ Timestamp 2021-04-09 08:01:24.947
+ --------------------- --------------------------------------
+
+.. _`unique identifiers`: https://en.wikipedia.org/wiki/UID
+.. _`UUIDs`: https://en.wikipedia.org/wiki/Universally_unique_identifier
+.. _`ULIDs`: https://github.com/ulid/spec
diff --git a/components/using_components.rst b/components/using_components.rst
index 174b041facc..f975be7e1b2 100644
--- a/components/using_components.rst
+++ b/components/using_components.rst
@@ -1,16 +1,14 @@
-.. index::
- single: Components; Installation
- single: Components; Usage
+.. _how-to-install-and-use-the-symfony2-components:
-How to Install and Use the Symfony2 Components
-==============================================
+How to Install and Use the Symfony Components
+=============================================
If you're starting a new project (or already have a project) that will use
one or more components, the easiest way to integrate everything is with `Composer`_.
Composer is smart enough to download the component(s) that you need and take
care of autoloading so that you can begin using the libraries immediately.
-This article will take you through using the :doc:`/components/finder`, though
+This article will take you through using :doc:`/components/finder`, though
this applies to using any component.
Using the Finder Component
@@ -18,84 +16,57 @@ Using the Finder Component
**1.** If you're creating a new project, create a new empty directory for it.
-**2.** Create a new file called ``composer.json`` and paste the following into it:
+**2.** Open a terminal, step into this directory and use Composer to grab the library.
-.. code-block:: json
+.. code-block:: terminal
- {
- "require": {
- "symfony/finder": "2.2.*"
- }
- }
+ $ composer require symfony/finder
-If you already have a ``composer.json`` file, just add this line to it. You
-may also need to adjust the version (e.g. ``2.1.1`` or ``2.2.*``).
+The name ``symfony/finder`` is written at the top of the documentation for
+whatever component you want.
-You can research the component names and versions at `packagist.org`_.
-
-**3.** `Install composer`_ if you don't already have it present on your system:
-
-**4.** Download the vendor libraries and generate the ``vendor/autoload.php`` file:
-
-.. code-block:: bash
+.. tip::
- $ php composer.phar install
+ `Install Composer`_ if you don't have it already present on your system.
+ Depending on how you install, you may end up with a ``composer.phar``
+ file in your directory. In that case, no worries! Your command line in that
+ case is ``php composer.phar require symfony/finder``.
-**5.** Write your code:
+**3.** Write your code!
Once Composer has downloaded the component(s), all you need to do is include
the ``vendor/autoload.php`` file that was generated by Composer. This file
takes care of autoloading all of the libraries so that you can use them
immediately::
- // File: src/script.php
-
- // update this to the path to the "vendor/" directory, relative to this file
- require_once '../vendor/autoload.php';
-
- use Symfony\Component\Finder\Finder;
-
- $finder = new Finder();
- $finder->in('../data/');
-
- // ...
-
-.. tip::
-
- If you want to use all of the Symfony2 Components, then instead of adding
- them one by one:
-
- .. code-block:: json
-
- {
- "require": {
- "symfony/finder": "2.2.*",
- "symfony/dom-crawler": "2.2.*",
- "symfony/css-selector": "2.2.*"
- }
- }
+ // Project structure example:
+ // my_project/
+ // data/
+ // ... # Some project data
+ // src/
+ // my_script.php # Main entry point
+ // vendor/
+ // autoload.php # Autoloader generated by Composer
+ // ... # Packages downloaded by Composer
- you can use:
+ // File example: src/my_script.php
+ // Autoloader relative path to this PHP file
+ require_once __DIR__.'/../vendor/autoload.php';
- .. code-block:: json
+ use Symfony\Component\Finder\Finder;
- {
- "require": {
- "symfony/symfony": "2.2.*"
- }
- }
+ $finder = new Finder();
+ $finder->in('../data/');
- This will include the Bundle and Bridge libraries, which you may not
- actually need.
+ // rest of your PHP code...
-Now What?
+Now what?
---------
-Now that the component is installed and autoloaded, read the specific component's
+Now, the component is installed and autoloaded. Read the specific component's
documentation to find out more about how to use it.
And have fun!
-.. _Composer: http://getcomposer.org
-.. _Install composer: http://getcomposer.org/download/
-.. _packagist.org: https://packagist.org/
\ No newline at end of file
+.. _Composer: https://getcomposer.org
+.. _Install Composer: https://getcomposer.org/download/
diff --git a/components/validator.rst b/components/validator.rst
new file mode 100644
index 00000000000..12c61507257
--- /dev/null
+++ b/components/validator.rst
@@ -0,0 +1,87 @@
+The Validator Component
+=======================
+
+ The Validator component provides tools to validate values following the
+ `JSR-303 Bean Validation specification`_.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/validator
+
+.. include:: /components/require_autoload.rst.inc
+
+Usage
+-----
+
+.. seealso::
+
+ This article explains how to use the Validator features as an independent
+ component in any PHP application. Read the :doc:`/validation` article to
+ learn about how to validate data and entities in Symfony applications.
+
+The Validator component behavior is based on two concepts:
+
+* Constraints, which define the rules to be validated;
+* Validators, which are the classes that contain the actual validation logic.
+
+The following example shows how to validate that a string is at least 10
+characters long::
+
+ use Symfony\Component\Validator\Constraints\Length;
+ use Symfony\Component\Validator\Constraints\NotBlank;
+ use Symfony\Component\Validator\Validation;
+
+ $validator = Validation::createValidator();
+ $violations = $validator->validate('Bernhard', [
+ new Length(min: 10),
+ new NotBlank(),
+ ]);
+
+ if (0 !== count($violations)) {
+ // there are errors, now you can show them
+ foreach ($violations as $violation) {
+ echo $violation->getMessage().' ';
+ }
+ }
+
+The ``validate()`` method returns the list of violations as an object that
+implements :class:`Symfony\\Component\\Validator\\ConstraintViolationListInterface`.
+If you have lots of validation errors, you can filter them by error code::
+
+ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
+
+ $violations = $validator->validate(/* ... */);
+ if (0 !== count($violations->findByCodes(UniqueEntity::NOT_UNIQUE_ERROR))) {
+ // handle this specific error (display some message, send an email, etc.)
+ }
+
+Retrieving a Validator Instance
+-------------------------------
+
+The Validator object (that implements :class:`Symfony\\Component\\Validator\\Validator\\ValidatorInterface`) is the main access
+point of the Validator component. To create a new instance of it, it's
+recommended to use the :class:`Symfony\\Component\\Validator\\Validation` class::
+
+ use Symfony\Component\Validator\Validation;
+
+ $validator = Validation::createValidator();
+
+This ``$validator`` object can validate simple variables such as strings, numbers
+and arrays, but it can't validate objects. To do so, configure the
+``Validator`` as explained in the next sections.
+
+Learn More
+----------
+
+.. toctree::
+ :maxdepth: 1
+ :glob:
+
+ /components/validator/*
+ /validation
+ /validation/*
+
+.. _`JSR-303 Bean Validation specification`: https://jcp.org/en/jsr/detail?id=303
diff --git a/components/validator/metadata.rst b/components/validator/metadata.rst
new file mode 100755
index 00000000000..782e1ee216f
--- /dev/null
+++ b/components/validator/metadata.rst
@@ -0,0 +1,94 @@
+Metadata
+========
+
+The :class:`Symfony\\Component\\Validator\\Mapping\\ClassMetadata` class
+represents and manages all the configured constraints on a given class.
+
+Properties
+----------
+
+The Validator component can validate public, protected or private properties.
+The following example shows how to validate that the ``$firstName`` property of
+the ``Author`` class has at least 3 characters::
+
+ // ...
+ use Symfony\Component\Validator\Constraints as Assert;
+ use Symfony\Component\Validator\Mapping\ClassMetadata;
+
+ class Author
+ {
+ private string $firstName;
+
+ public static function loadValidatorMetadata(ClassMetadata $metadata): void
+ {
+ $metadata->addPropertyConstraint('firstName', new Assert\NotBlank());
+ $metadata->addPropertyConstraint(
+ 'firstName',
+ new Assert\Length(min: 3)
+ );
+ }
+ }
+
+Getters
+-------
+
+Constraints can also be applied to the value returned by any public *getter*
+method, which are the methods whose names start with ``get``, ``has`` or ``is``.
+This feature allows validating your objects dynamically.
+
+Suppose that, for security reasons, you want to validate that a password field
+doesn't match the first name of the user. First, create a public method called
+``isPasswordSafe()`` to define this custom validation logic::
+
+ public function isPasswordSafe(): bool
+ {
+ return $this->firstName !== $this->password;
+ }
+
+Then, add the Validator component configuration to the class::
+
+ // ...
+ use Symfony\Component\Validator\Constraints as Assert;
+ use Symfony\Component\Validator\Mapping\ClassMetadata;
+
+ class Author
+ {
+ public static function loadValidatorMetadata(ClassMetadata $metadata): void
+ {
+ $metadata->addGetterConstraint('passwordSafe', new Assert\IsTrue(
+ message: 'The password cannot match your first name',
+ ));
+ }
+ }
+
+Classes
+-------
+
+Some constraints allow validating the entire object. For example, the
+:doc:`Callback ` constraint is a generic
+constraint that's applied to the class itself.
+
+Suppose that the class defines a ``validate()`` method to hold its custom
+validation logic::
+
+ // ...
+ use Symfony\Component\Validator\Context\ExecutionContextInterface;
+
+ public function validate(ExecutionContextInterface $context): void
+ {
+ // ...
+ }
+
+Then, add the Validator component configuration to the class::
+
+ // ...
+ use Symfony\Component\Validator\Constraints as Assert;
+ use Symfony\Component\Validator\Mapping\ClassMetadata;
+
+ class Author
+ {
+ public static function loadValidatorMetadata(ClassMetadata $metadata): void
+ {
+ $metadata->addConstraint(new Assert\Callback('validate'));
+ }
+ }
diff --git a/components/validator/resources.rst b/components/validator/resources.rst
new file mode 100644
index 00000000000..7d6cd0e8e5d
--- /dev/null
+++ b/components/validator/resources.rst
@@ -0,0 +1,178 @@
+Loading Resources
+=================
+
+The Validator component uses metadata to validate a value. This metadata defines
+how a class, array or any other value should be validated. When validating a
+class, the metadata is defined by the class itself. When validating simple values,
+the metadata must be passed to the validation methods.
+
+Class metadata can be defined in a configuration file or in the class itself.
+The Validator component collects that metadata using a set of loaders.
+
+.. seealso::
+
+ You'll learn how to define the metadata in :doc:`metadata`.
+
+The StaticMethodLoader
+----------------------
+
+The most basic loader is the
+:class:`Symfony\\Component\\Validator\\Mapping\\Loader\\StaticMethodLoader`.
+This loader gets the metadata by calling a static method of the class. The name
+of the method is configured using the
+:method:`Symfony\\Component\\Validator\\ValidatorBuilder::addMethodMapping`
+method of the validator builder::
+
+ use Symfony\Component\Validator\Validation;
+
+ $validator = Validation::createValidatorBuilder()
+ ->addMethodMapping('loadValidatorMetadata')
+ ->getValidator();
+
+In this example, the validation metadata is retrieved executing the
+``loadValidatorMetadata()`` method of the class::
+
+ use Symfony\Component\Validator\Constraints as Assert;
+ use Symfony\Component\Validator\Mapping\ClassMetadata;
+
+ class User
+ {
+ protected string $name;
+
+ public static function loadValidatorMetadata(ClassMetadata $metadata): void
+ {
+ $metadata->addPropertyConstraint('name', new Assert\NotBlank());
+ $metadata->addPropertyConstraint('name', new Assert\Length(
+ min: 5,
+ max: 20,
+ ));
+ }
+ }
+
+.. tip::
+
+ Instead of calling ``addMethodMapping()`` multiple times to add several
+ method names, you can also use
+ :method:`Symfony\\Component\\Validator\\ValidatorBuilder::addMethodMappings`
+ to set an array of supported method names.
+
+The File Loaders
+----------------
+
+The component also provides two file loaders, one to load YAML files and one to
+load XML files. Use
+:method:`Symfony\\Component\\Validator\\ValidatorBuilder::addYamlMapping` or
+:method:`Symfony\\Component\\Validator\\ValidatorBuilder::addXmlMapping` to
+configure the locations of these files::
+
+ use Symfony\Component\Validator\Validation;
+
+ $validator = Validation::createValidatorBuilder()
+ ->addYamlMapping('validator/validation.yaml')
+ ->getValidator();
+
+.. note::
+
+ If you want to load YAML mapping files, then you will also need to install
+ :doc:`the Yaml component `.
+
+.. tip::
+
+ Just like with the method mappings, you can also use
+ :method:`Symfony\\Component\\Validator\\ValidatorBuilder::addYamlMappings` and
+ :method:`Symfony\\Component\\Validator\\ValidatorBuilder::addXmlMappings`
+ to configure an array of file paths.
+
+The AttributeLoader
+-------------------
+
+The component provides an
+:class:`Symfony\\Component\\Validator\\Mapping\\Loader\\AttributeLoader` to get
+the metadata from the attributes of the class. For example::
+
+ use Symfony\Component\Validator\Constraints as Assert;
+ // ...
+
+ class User
+ {
+ #[Assert\NotBlank]
+ protected string $name;
+ }
+
+To enable the attribute loader, call the
+:method:`Symfony\\Component\\Validator\\ValidatorBuilder::enableAttributeMapping` method.
+
+To disable the attribute loader after it was enabled, call
+:method:`Symfony\\Component\\Validator\\ValidatorBuilder::disableAttributeMapping`.
+
+Using Multiple Loaders
+----------------------
+
+The component provides a
+:class:`Symfony\\Component\\Validator\\Mapping\\Loader\\LoaderChain` class to
+execute several loaders sequentially in the same order they were defined:
+
+The ``ValidatorBuilder`` will already take care of this when you configure
+multiple mappings::
+
+ use Symfony\Component\Validator\Validation;
+
+ $validator = Validation::createValidatorBuilder()
+ ->enableAttributeMapping()
+ ->addMethodMapping('loadValidatorMetadata')
+ ->addXmlMapping('validator/validation.xml')
+ ->getValidator();
+
+Caching
+-------
+
+Using many loaders to load metadata from different places is convenient, but it
+can slow down your application because each file needs to be parsed, validated
+and converted into a :class:`Symfony\\Component\\Validator\\Mapping\\ClassMetadata`
+instance.
+
+To solve this problem, call the :method:`Symfony\\Component\\Validator\\ValidatorBuilder::setMappingCache`
+method of the Validator builder and pass your own caching class (which must
+implement the PSR-6 interface ``Psr\Cache\CacheItemPoolInterface``)::
+
+ use Symfony\Component\Validator\Validation;
+
+ $validator = Validation::createValidatorBuilder()
+ // ... add loaders
+ ->setMappingCache(new SomePsr6Cache())
+ ->getValidator();
+
+.. note::
+
+ The loaders already use a singleton load mechanism. That means that the
+ loaders will only load and parse a file once and put that in a property,
+ which will then be used the next time it is asked for metadata. However,
+ the Validator still needs to merge all metadata of one class from every
+ loader when it is requested.
+
+Using a Custom MetadataFactory
+------------------------------
+
+All the loaders and the cache are passed to an instance of
+:class:`Symfony\\Component\\Validator\\Mapping\\Factory\\LazyLoadingMetadataFactory`.
+This class is responsible for creating a ``ClassMetadata`` instance from all the
+configured resources.
+
+You can also use a custom metadata factory implementation by creating a class
+which implements
+:class:`Symfony\\Component\\Validator\\Mapping\\Factory\\MetadataFactoryInterface`.
+You can set this custom implementation using
+:method:`Symfony\\Component\\Validator\\ValidatorBuilder::setMetadataFactory`::
+
+ use Acme\Validation\CustomMetadataFactory;
+ use Symfony\Component\Validator\Validation;
+
+ $validator = Validation::createValidatorBuilder()
+ ->setMetadataFactory(new CustomMetadataFactory(...))
+ ->getValidator();
+
+.. warning::
+
+ Since you are using a custom metadata factory, you can't configure loaders
+ and caches using the ``add*Mapping()`` methods anymore. You now have to
+ inject them into your custom metadata factory yourself.
diff --git a/components/var_dumper.rst b/components/var_dumper.rst
new file mode 100644
index 00000000000..c6966a692af
--- /dev/null
+++ b/components/var_dumper.rst
@@ -0,0 +1,893 @@
+The VarDumper Component
+=======================
+
+ The VarDumper component provides mechanisms for extracting the state out of
+ any PHP variables. Built on top, it provides a better ``dump()`` function
+ that you can use instead of :phpfunction:`var_dump`.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require --dev symfony/var-dumper
+
+.. include:: /components/require_autoload.rst.inc
+
+.. note::
+
+ If using it inside a Symfony application, make sure that the DebugBundle has
+ been installed (or run ``composer require --dev symfony/debug-bundle`` to install it).
+
+.. _components-var-dumper-dump:
+
+The dump() Function
+-------------------
+
+The VarDumper component creates a global ``dump()`` function that you can
+use instead of e.g. :phpfunction:`var_dump`. By using it, you'll gain:
+
+* Per object and resource types specialized view to e.g. filter out
+ Doctrine internals while dumping a single proxy entity, or get more
+ insight on opened files with :phpfunction:`stream_get_meta_data`;
+* Configurable output formats: HTML or colored command line output;
+* Ability to dump internal references, either soft ones (objects or
+ resources) or hard ones (``=&`` on arrays or objects properties).
+ Repeated occurrences of the same object/array/resource won't appear
+ again and again anymore. Moreover, you'll be able to inspect the
+ reference structure of your data;
+* Ability to operate in the context of an output buffering handler.
+
+For example::
+
+ require __DIR__.'/vendor/autoload.php';
+
+ // create a variable, which could be anything!
+ $someVar = ...;
+
+ dump($someVar);
+
+ // dump() returns the passed value, so you can dump an object and keep using it
+ dump($someObject)->someMethod();
+
+By default, the output format and destination are selected based on your
+current PHP SAPI:
+
+* On the command line (CLI SAPI), the output is written on ``STDOUT``. This
+ can be surprising to some because this bypasses PHP's output buffering
+ mechanism;
+* On other SAPIs, dumps are written as HTML in the regular output.
+
+.. tip::
+
+ You can also select the output format explicitly defining the
+ ``VAR_DUMPER_FORMAT`` environment variable and setting its value to either
+ ``html``, ``cli`` or :ref:`server `.
+
+.. note::
+
+ If you want to catch the dump output as a string, please read the
+ :ref:`advanced section ` which contains examples of
+ it.
+ You'll also learn how to change the format or redirect the output to
+ wherever you want.
+
+.. tip::
+
+ In order to have the ``dump()`` function always available when running
+ any PHP code, you can install it globally on your computer:
+
+ #. Run ``composer global require symfony/var-dumper``;
+ #. Add ``auto_prepend_file = ${HOME}/.composer/vendor/autoload.php``
+ to your ``php.ini`` file;
+ #. From time to time, run ``composer global update symfony/var-dumper``
+ to have the latest bug fixes.
+
+.. tip::
+
+ The VarDumper component also provides a ``dd()`` ("dump and die") helper
+ function. This function dumps the variables using ``dump()`` and
+ immediately ends the execution of the script (using :phpfunction:`exit`).
+
+.. _var-dumper-dump-server:
+
+The Dump Server
+---------------
+
+The ``dump()`` function outputs its contents in the same browser window or
+console terminal as your own application. Sometimes mixing the real output
+with the debug output can be confusing. That's why this component provides a
+server to collect all the dumped data.
+
+Start the server with the ``server:dump`` command and whenever you call to
+``dump()``, the dumped data won't be displayed in the output but sent to that
+server, which outputs it to its own console or to an HTML file:
+
+.. code-block:: terminal
+
+ # displays the dumped data in the console:
+ $ php bin/console server:dump
+ [OK] Server listening on tcp://0.0.0.0:9912
+
+ # stores the dumped data in a file using the HTML format:
+ $ php bin/console server:dump --format=html > dump.html
+
+Inside a Symfony application, the output of the dump server is configured with
+the :ref:`dump_destination option ` of the
+``debug`` package:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/debug.yaml
+ debug:
+ dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%"
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/debug.php
+ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+ return static function (ContainerConfigurator $container): void {
+ $container->extension('debug', [
+ 'dump_destination' => 'tcp://%env(VAR_DUMPER_SERVER)%',
+ ]);
+ };
+
+Outside a Symfony application, use the :class:`Symfony\\Component\\VarDumper\\Dumper\\ServerDumper` class::
+
+ require __DIR__.'/vendor/autoload.php';
+
+ use Symfony\Component\VarDumper\Cloner\VarCloner;
+ use Symfony\Component\VarDumper\Dumper\CliDumper;
+ use Symfony\Component\VarDumper\Dumper\ContextProvider\CliContextProvider;
+ use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider;
+ use Symfony\Component\VarDumper\Dumper\HtmlDumper;
+ use Symfony\Component\VarDumper\Dumper\ServerDumper;
+ use Symfony\Component\VarDumper\VarDumper;
+
+ $cloner = new VarCloner();
+ $fallbackDumper = \in_array(\PHP_SAPI, ['cli', 'phpdbg']) ? new CliDumper() : new HtmlDumper();
+ $dumper = new ServerDumper('tcp://127.0.0.1:9912', $fallbackDumper, [
+ 'cli' => new CliContextProvider(),
+ 'source' => new SourceContextProvider(),
+ ]);
+
+ VarDumper::setHandler(function (mixed $var) use ($cloner, $dumper): ?string {
+ return $dumper->dump($cloner->cloneVar($var));
+ });
+
+.. note::
+
+ The second argument of :class:`Symfony\\Component\\VarDumper\\Dumper\\ServerDumper`
+ is a :class:`Symfony\\Component\\VarDumper\\Dumper\\DataDumperInterface` instance
+ used as a fallback when the server is unreachable. The third argument are the
+ context providers, which allow to gather some info about the context in which the
+ data was dumped. The built-in context providers are: ``cli``, ``request`` and ``source``.
+
+Then you can use the following command to start a server out-of-the-box:
+
+.. code-block:: terminal
+
+ $ ./vendor/bin/var-dump-server
+ [OK] Server listening on tcp://127.0.0.1:9912
+
+.. _var-dumper-dump-server-format:
+
+Configuring the Dump Server with Environment Variables
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you prefer to not modify the application configuration (e.g. to quickly debug
+a project given to you) use the ``VAR_DUMPER_FORMAT`` env var.
+
+First, start the server as usual:
+
+.. code-block:: terminal
+
+ $ ./vendor/bin/var-dump-server
+
+Then, run your code with the ``VAR_DUMPER_FORMAT=server`` env var by configuring
+this value in the :ref:`.env file of your application `. For
+console commands, you can also define this env var as follows:
+
+.. code-block:: terminal
+
+ $ VAR_DUMPER_FORMAT=server [your-cli-command]
+
+.. note::
+
+ The host used by the ``server`` format is the one configured in the
+ ``VAR_DUMPER_SERVER`` env var or ``127.0.0.1:9912`` if none is defined.
+ If you prefer, you can also configure the host in the ``VAR_DUMPER_FORMAT``
+ env var like this: ``VAR_DUMPER_FORMAT=tcp://127.0.0.1:1234``.
+
+DebugBundle and Twig Integration
+--------------------------------
+
+The DebugBundle allows greater integration of this component into Symfony
+applications.
+
+Since generating (even debug) output in the controller or in the model
+of your application may just break it by e.g. sending HTTP headers or
+corrupting your view, the bundle configures the ``dump()`` function so that
+variables are dumped in the web debug toolbar.
+
+But if the toolbar cannot be displayed because you e.g. called
+``die()``/``exit()``/``dd()`` or a fatal error occurred, then dumps are written
+on the regular output.
+
+In a Twig template, two constructs are available for dumping a variable.
+Choosing between both is mostly a matter of personal taste, still:
+
+* ``{% dump foo.bar %}`` is the way to go when the original template output
+ shall not be modified: variables are not dumped inline, but in the web
+ debug toolbar;
+* on the contrary, ``{{ dump(foo.bar) }}`` dumps inline and thus may or not
+ be suited to your use case (e.g. you shouldn't use it in an HTML
+ attribute or a ``';
+ $urlValidator = new Constraints\UrlValidator();
+ $urlConstraint = new Constraints\Url();
+
+ // The URL is wrong, so var_dump() should display an error, but it displays
+ // "null" instead because there is no context to build a validator violation
+ var_dump($urlValidator->validate($wrongUrl, $urlConstraint));
+
+Reproducing Complex Bugs
+------------------------
+
+If the bug is related to the Symfony Framework or if it's too complex to create
+a PHP script, it's better to reproduce the bug by creating a new project. To do so:
+
+#. Create a new project:
+
+.. code-block:: terminal
+
+ $ composer create-project symfony/skeleton bug_app
+
+#. Add and commit the changes generated by Symfony.
+#. Now you must add the minimum amount of code to reproduce the bug. This is the
+ trickiest part and it's explained a bit more later.
+#. Add and commit your changes.
+#. Create a `new repository`_ on GitHub (give it any name).
+#. Follow the instructions on GitHub to add the ``origin`` remote to your local project
+ and push it.
+#. Add a comment in your original issue report to share the URL of your forked
+ project (e.g. ``https://github.com/YOUR-GITHUB-USERNAME/symfony_issue_23567``)
+ and, if necessary, explain the steps to reproduce (e.g. "browse this URL",
+ "fill in this data in the form and submit it", etc.)
+
+Adding the Minimum Amount of Code Possible
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The key to create a bug reproducer is to solely focus on the feature that you
+suspect is failing. For example, imagine that you suspect that the bug is related
+to a route definition. Then, after creating your project:
+
+#. Don't edit any of the default Symfony configuration options.
+#. Don't copy your original application code and don't use the same structure
+ of controllers, actions, etc. as in your original application.
+#. Create a small controller and add your routing definition that shows the bug.
+#. Don't create or modify any other file.
+#. Install the :doc:`local web server ` provided by Symfony
+ and use the ``symfony server:start`` command to browse to the new route and
+ see if the bug appears or not.
+#. If you can see the bug, you're done and you can already share the code with us.
+#. If you can't see the bug, you must keep making small changes. For example, if
+ your original route was defined using XML, forget about the previous route
+ and define the route using XML instead. Or maybe your application
+ registers some event listeners and that's where the real bug is. In that case,
+ add an event listener that's similar to your real app to see if you can find
+ the bug.
+
+In short, the idea is to keep adding small and incremental changes to a new project
+until you can reproduce the bug.
+
+.. _`new repository`: https://github.com/new
diff --git a/contributing/code/security.rst b/contributing/code/security.rst
index 5d895101380..ba8949971a4 100644
--- a/contributing/code/security.rst
+++ b/contributing/code/security.rst
@@ -1,28 +1,54 @@
Security Issues
===============
-This document explains how Symfony security issues are handled by the Symfony
-core team (Symfony being the code hosted on the main ``symfony/symfony`` `Git
-repository`_).
+This document explains how Symfony security issues are handled by the
+Symfony core team (Symfony being the code hosted on the main ``symfony/symfony``
+`Git repository`_).
Reporting a Security Issue
--------------------------
If you think that you have found a security issue in Symfony, don't use the
-mailing-list or the bug tracker and don't publish it publicly. Instead, all
-security issues must be sent to **security [at] symfony.com**. Emails sent to
-this address are forwarded to the Symfony core-team private mailing-list.
+bug tracker and don't publish it publicly. Instead, all security issues must
+be sent to **security [at] symfony.com**. Emails sent to this address are
+forwarded to the Symfony core team private mailing-list.
+
+The following issues are not considered security issues and should be handled
+as regular bug fixes (if you have any doubts, don't hesitate to send us an
+email for confirmation):
+
+* Any security issues found in debug tools that must never be enabled in
+ production (including the web profiler or anything enabled when ``APP_DEBUG``
+ is set to ``true`` or ``APP_ENV`` set to anything but ``prod``);
+
+* Any security issues found in classes provided to help for testing that should
+ never be used in production (like for instance mock classes that contain
+ ``Mock`` in their name or classes in the ``Test`` namespace);
+
+* Any fix that can be classified as **security hardening** like route
+ enumeration, login throttling bypasses, denial of service attacks, timing
+ attacks, or lack of ``SensitiveParameter`` attributes.
+
+In any case, the core team has the final decision on which issues are
+considered security vulnerabilities.
+
+Security Bug Bounties
+---------------------
+
+Symfony is an Open-Source project where most of the work is done by volunteers.
+We appreciate that developers are trying to find security issues in Symfony and
+report them responsibly, but we are currently unable to pay bug bounties.
Resolving Process
-----------------
For each report, we first try to confirm the vulnerability. When it is
-confirmed, the core-team works on a solution following these steps:
+confirmed, the core team works on a solution following these steps:
-1. Send an acknowledgement to the reporter;
-2. Work on a patch;
-3. Get a CVE identifier from mitre.org;
-4. Write a security announcement for the official Symfony `blog`_ about the
+#. Send an acknowledgment to the reporter;
+#. Work on a patch;
+#. Get a CVE identifier from `mitre.org`_;
+#. Write a security announcement for the official Symfony `blog`_ about the
vulnerability. This post should contain the following information:
* a title that always include the "Security release" string;
@@ -32,12 +58,14 @@ confirmed, the core-team works on a solution following these steps:
* how to patch/upgrade/workaround affected applications;
* the CVE identifier;
* credits.
-5. Send the patch and the announcement to the reporter for review;
-6. Apply the patch to all maintained versions of Symfony;
-7. Package new versions for all affected versions;
-8. Publish the post on the official Symfony `blog`_ (it must also be added to
+#. Send the patch and the announcement to the reporter for review;
+#. Apply the patch to all maintained versions of Symfony;
+#. Package new versions for all affected versions;
+#. Publish the post on the official Symfony `blog`_ (it must also be added to
the "`Security Advisories`_" category);
-9. Update the security advisory list (see below).
+#. Update the public `security advisories database`_ maintained by the
+ FriendsOfPHP organization and which is used by
+ :ref:`the check:security command `.
.. note::
@@ -48,32 +76,134 @@ confirmed, the core-team works on a solution following these steps:
While we are working on a patch, please do not reveal the issue publicly.
+.. note::
+
+ The resolution takes anywhere between a couple of days to a month depending
+ on its complexity and the coordination with the downstream projects (see
+ next paragraph).
+
+Collaborating with Downstream Open-Source Projects
+--------------------------------------------------
+
+As Symfony is used by many large Open-Source projects, we standardized the way
+the Symfony security team collaborates on security issues with downstream
+projects. The process works as follows:
+
+#. After the Symfony security team has acknowledged a security issue, it
+ immediately sends an email to the downstream project security teams to
+ inform them of the issue;
+
+#. The Symfony security team creates a private Git repository to ease the
+ collaboration on the issue and access to this repository is given to the
+ Symfony security team, to the Symfony contributors that are impacted by
+ the issue, and to one representative of each downstream projects;
+
+#. All people with access to the private repository work on a solution to
+ solve the issue via pull requests, code reviews, and comments;
+
+#. Once the fix is found, all involved projects collaborate to find the best
+ date for a joint release (there is no guarantee that all releases will
+ be at the same time but we will try hard to make them at about the same
+ time). When the issue is not known to be exploited in the wild, a period
+ of two weeks is considered a reasonable amount of time.
+
+The list of downstream projects participating in this process is kept as small
+as possible in order to better manage the flow of confidential information
+prior to disclosure. As such, projects are included at the sole discretion of
+the Symfony security team.
+
+As of today, the following projects have validated this process and are part
+of the downstream projects included in this process:
+
+* Drupal (releases typically happen on Wednesdays)
+* eZPublish
+
+Issue Severity
+--------------
+
+In order to determine the severity of a security issue we take into account
+the complexity of any potential attack, the impact of the vulnerability and
+also how many projects it is likely to affect. This score out of 15 is then
+converted into a level of: Low, Medium, High, Critical, or Exceptional.
+
+Attack Complexity
+~~~~~~~~~~~~~~~~~
+
+*Score of between 1 and 5 depending on how complex it is to exploit the
+vulnerability*
+
+* 4 - 5 Basic: attacker must follow a set of simple steps
+* 2 - 3 Complex: attacker must follow non-intuitive steps with a high level
+ of dependencies
+* 1 - 2 High: A successful attack depends on conditions beyond the attacker's
+ control. That is, a successful attack cannot be accomplished at will, but
+ requires the attacker to invest in some measurable amount of effort in
+ preparation or execution against the vulnerable component before a successful
+ attack can be expected.
+
+Impact
+~~~~~~
+
+*Scores from the following areas are added together to produce a score. The
+score for Impact is capped at 6. Each area is scored between 0 and 4.*
+
+* Integrity: Does this vulnerability cause non-public data to be accessible?
+ If so, does the attacker have control over the data disclosed? (0-4)
+* Disclosure: Can this exploit allow system data (or data handled by the
+ system) to be compromised? If so, does the attacker have control over
+ modification? (0-4)
+* Code Execution: Does the vulnerability allow arbitrary code to be executed
+ on an end-users system, or the server that it runs on? (0-4)
+* Availability: Is the availability of a service or application affected? Is
+ it reduced availability or total loss of availability of a service /
+ application? Availability includes networked services (e.g. databases) or
+ resources such as consumption of network bandwidth, processor cycles, or
+ disk space. (0-4)
+
+Affected Projects
+~~~~~~~~~~~~~~~~~
+
+*Scores from the following areas are added together to produce a score. The
+score for Affected Projects is capped at 4.*
+
+* Will it affect some or all using a component? (1-2)
+* Is the usage of the component that would cause such a thing already
+ considered bad practice? (0-1)
+* How common/popular is the component (e.g. Console vs HttpFoundation vs
+ Lock)? (0-2)
+* Are a number of well-known open source projects using Symfony affected
+ that requires coordinated releases? (0-1)
+
+Score Totals
+~~~~~~~~~~~~
+
+* Attack Complexity: 1 - 5
+* Impact: 1 - 6
+* Affected Projects: 1 - 4
+
+Severity levels
+~~~~~~~~~~~~~~~
+
+* Low: 1 - 5
+* Medium: 6 - 10
+* High: 11 - 12
+* Critical: 13 - 14
+* Exceptional: 15
+
Security Advisories
-------------------
-This section indexes security vulnerabilities that were fixed in Symfony
-releases, starting from Symfony 1.0.0:
-
-* January 17, 2013: `Security release: Symfony 2.0.22 and 2.1.7 released `_ (`CVE-2013-1348 `_ and `CVE-2013-1397 `_)
-* December 20, 2012: `Security release: Symfony 2.0.20 and 2.1.5 `_ (`CVE-2012-6431 `_ and `CVE-2012-6432 `_)
-* November 29, 2012: `Security release: Symfony 2.0.19 and 2.1.4 `_
-* November 25, 2012: `Security release: symfony 1.4.20 released `_ (`CVE-2012-5574 `_)
-* August 28, 2012: `Security Release: Symfony 2.0.17 released `_
-* May 30, 2012: `Security Release: symfony 1.4.18 released `_ (`CVE-2012-2667 `_)
-* February 24, 2012: `Security Release: Symfony 2.0.11 released `_
-* November 16, 2011: `Security Release: Symfony 2.0.6 `_
-* March 21, 2011: `symfony 1.3.10 and 1.4.10: security releases `_
-* June 29, 2010: `Security Release: symfony 1.3.6 and 1.4.6 `_
-* May 31, 2010: `symfony 1.3.5 and 1.4.5 `_
-* February 25, 2010: `Security Release: 1.2.12, 1.3.3 and 1.4.3 `_
-* February 13, 2010: `symfony 1.3.2 and 1.4.2 `_
-* April 27, 2009: `symfony 1.2.6: Security fix `_
-* October 03, 2008: `symfony 1.1.4 released: Security fix `_
-* May 14, 2008: `symfony 1.0.16 is out `_
-* April 01, 2008: `symfony 1.0.13 is out `_
-* March 21, 2008: `symfony 1.0.12 is (finally) out ! `_
-* June 25, 2007: `symfony 1.0.5 released (security fix) `_
-
-.. _Git repository: https://github.com/symfony/symfony
-.. _blog: http://symfony.com/blog/
-.. _Security Advisories: http://symfony.com/blog/category/security-advisories
+.. tip::
+
+ You can check your Symfony application for known security vulnerabilities
+ using :ref:`the check:security command `.
+
+Check the `Security Advisories`_ blog category for a list of all security
+vulnerabilities that were fixed in Symfony releases, starting from Symfony
+1.0.0.
+
+.. _`Git repository`: https://github.com/symfony/symfony
+.. _blog: https://symfony.com/blog/
+.. _`security advisories database`: https://github.com/FriendsOfPHP/security-advisories
+.. _`mitre.org`: https://cveform.mitre.org/
+.. _`Security Advisories`: https://symfony.com/blog/category/security-advisories
diff --git a/contributing/code/stack_trace.rst b/contributing/code/stack_trace.rst
new file mode 100644
index 00000000000..6fd6987d4e3
--- /dev/null
+++ b/contributing/code/stack_trace.rst
@@ -0,0 +1,189 @@
+Getting a Stack Trace
+=====================
+
+When :doc:`reporting a bug ` for an
+exception or a wrong behavior in code, it is crucial that you provide
+one or several stack traces. To understand why, you first have to
+understand what a stack trace is, and how it can be useful to you as a
+developer, and also to library maintainers.
+
+Anatomy of a Stack Trace
+------------------------
+
+A stack trace is called that way because it allows one to see a trail of
+function calls leading to a point in code since the beginning of the
+program. That point is not necessarily an exception. For instance, you
+can use the native PHP function ``debug_print_backtrace()`` to get such
+a trace. For each line in the trace, you get a file and a function or
+method call, and the line number for that call. This is often of great
+help for understanding the flow of your program and how it can end up in
+unexpected places, such as lines of code where exceptions are thrown.
+
+Stack Traces and Exceptions
+---------------------------
+
+In PHP, every exception comes with its own stack trace, which is
+displayed by default if the exception is not caught. When using Symfony,
+such exceptions go through a custom exception handler, which enhances
+them in various ways before displaying them according to the current
+Server API (CLI or not).
+This means a better way to get a stack trace when you do not need the
+program to continue is to throw an exception, as follows:
+``throw new \Exception();``
+
+Nested Exceptions
+-----------------
+
+When applications get bigger, complexity is often tackled with layers of
+architecture that need to be kept separate. For instance, if you have a
+web application that makes a call to a remote API, it might be good to
+wrap exceptions thrown when making that call with exceptions that have
+special meaning in your domain, and to build appropriate HTTP exceptions
+from those. Exceptions can be nested by using the ``$previous``
+argument that appears in the signature of the ``Exception`` class:
+``public __construct ([ string $message = "" [, int $code = 0 [, Throwable $previous = NULL ]]] )``
+This means that sometimes, when you get an exception from an
+application, you might actually get several of them.
+
+What to look for in a Stack Trace
+---------------------------------
+
+When using a library, you will call code that you did not write. When
+using a framework, it is the opposite: because you follow the
+conventions of the framework, `the framework finds your code and calls
+it `_, and does
+things for you beforehand, like routing or access control.
+Symfony being both a framework and library of components, it calls your
+code and then your code might call it. This means you will always have
+at least 2 parts, very often 3 in your stack traces when using Symfony:
+a part that starts in one of the entry points of the framework
+(``bin/console`` or ``public/index.php`` in most cases), and ends when
+reaching your code, most times in a command or in a controller found under
+``src``. Then, either the exception is thrown in your code or in
+libraries you call. If it is the latter, there should be a third part in
+the stack trace with calls made in files under ``vendor``. Before
+landing in that directory, code goes through numerous review processes
+and CI pipelines, which means it should be less likely to be the source
+of the issue than code from your application, so it is important that
+you focus first on lines starting with ``src``, and look for anything
+suspicious or unexpected, like method calls that are not supposed to
+happen.
+
+Next, you can have a look at what packages are involved. Files under
+``vendor`` are organized by Composer in the following way:
+``vendor/acme/router`` where ``acme`` is the vendor, ``router`` the
+library and ``acme/router`` the Composer package. If you plan on
+reporting the bug, make sure to report it to the library throwing the
+exception. ``composer home acme/router`` should lead you to the right
+place for that. As Symfony is a mono-repository, use ``composer home
+symfony/symfony`` when reporting a bug for any component.
+
+Getting Stack Traces with Symfony
+---------------------------------
+
+Now that we have all this in mind, let us see how to get a stack trace
+with Symfony.
+
+Stack Traces in your Web Browser
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Several things need to be paid attention to when picking a stack trace
+from your development environment through a web browser:
+
+1. Are there several exceptions? If yes, the most interesting one is
+ often exception 1/n which, is shown *last* in the default exception page
+ (it is the one marked as ``exception [1/2]`` in the below example).
+2. Under the "Stack Traces" tab, you will find exceptions in plain
+ text, so that you can easily share them in e.g. bug reports. Make
+ sure to **remove any sensitive information** before doing so.
+3. You may notice there is a logs tab too; this tab does not have to do
+ with stack traces, it only contains logs produced in arbitrary places
+ in your application. They may or may not relate to the exception you
+ are getting, but are not what the term "stack trace" refers to.
+
+.. image:: /_images/contributing/code/stack-trace.gif
+ :alt: The default Symfony exception page with the "Exceptions", "Logs" and "Stack Traces" tabs.
+ :class: with-browser
+
+Since stack traces may contain sensitive data, they should not be
+exposed in production. Getting a stack trace from your production
+environment, although more involving, is still possible with solutions
+that include but are not limited to sending them to an email address
+with Monolog.
+
+Stack Traces in the CLI
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Exceptions might occur when running a Symfony command. By default, only
+the message is shown because it is often enough to understand what is
+going on:
+
+.. code-block:: terminal
+
+ $ php bin/console debug:exception
+
+
+ Command "debug:exception" is not defined.
+
+ Did you mean one of these?
+ debug:autowiring
+ debug:config
+ debug:container
+ debug:event-dispatcher
+ debug:form
+ debug:router
+ debug:translation
+ debug:twig
+
+
+If that is not the case, you can obtain a stack trace by increasing the
+:doc:`verbosity level ` with ``--verbose``:
+
+.. code-block:: terminal
+
+ $ php bin/console --verbose debug:exception
+
+ In Application.php line 644:
+
+ [Symfony\Component\Console\Exception\CommandNotFoundException]
+ Command "debug:exception" is not defined.
+
+ Did you mean one of these?
+ debug:autowiring
+ debug:config
+ debug:container
+ debug:event-dispatcher
+ debug:form
+ debug:router
+ debug:translation
+ debug:twig
+
+
+ Exception trace:
+ at /app/vendor/symfony/console/Application.php:644
+ Symfony\Component\Console\Application->find() at /app/vendor/symfony/framework-bundle/Console/Application.php:116
+ Symfony\Bundle\FrameworkBundle\Console\Application->find() at /app/vendor/symfony/console/Application.php:228
+ Symfony\Component\Console\Application->doRun() at /app/vendor/symfony/framework-bundle/Console/Application.php:82
+ Symfony\Bundle\FrameworkBundle\Console\Application->doRun() at /app/vendor/symfony/console/Application.php:140
+ Symfony\Component\Console\Application->run() at /app/bin/console:42
+
+Stack Traces and API Calls
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When getting an exception from an API, you might not get a stack trace,
+or it might be displayed in a way that is not suitable for sharing.
+Luckily, when in the dev environment, you can obtain a plain text stack
+trace by using the profiler. To find the profile, you can have a look
+at the ``X-Debug-Token-Link`` response headers:
+
+.. code-block:: terminal
+
+ $ curl --head http://localhost:8000/api/posts/1
+ … more headers
+ X-Debug-Token: 110e1e
+ X-Debug-Token-Link: http://localhost:8000/_profiler/110e1e
+ X-Robots-Tag: noindex
+ X-Previous-Debug-Token: 209101
+
+Following that link will lead you to a page very similar to the one
+described above in `Stack Traces in your Web Browser`_.
diff --git a/contributing/code/standards.rst b/contributing/code/standards.rst
index 61a2ed6b8df..ebfde7dfab4 100644
--- a/contributing/code/standards.rst
+++ b/contributing/code/standards.rst
@@ -1,23 +1,33 @@
Coding Standards
================
-When contributing code to Symfony2, you must follow its coding standards. To
-make a long story short, here is the golden rule: **Imitate the existing
-Symfony2 code**. Most open-source Bundles and libraries used by Symfony2 also
-follow the same guidelines, and you should too.
+Symfony code is contributed by thousands of developers around the world. To make
+every piece of code look and feel familiar, Symfony defines some coding standards
+that all contributions must follow.
-Remember that the main advantage of standards is that every piece of code
-looks and feels familiar, it's not about this or that being more readable.
+These Symfony coding standards are based on the `PSR-1`_, `PSR-2`_, `PSR-4`_
+and `PSR-12`_ standards, so you may already know most of them.
-Symfony follows the standards defined in the `PSR-0`_, `PSR-1`_ and `PSR-2`_
-documents.
+Making your Code Follow the Coding Standards
+--------------------------------------------
-Since a picture - or some code - is worth a thousand words, here's a short
-example containing most features described below:
+Instead of reviewing your code manually, Symfony makes it simple to ensure that
+your contributed code matches the expected code syntax. First, install the
+`PHP CS Fixer tool`_ and then, run this command to fix any problem:
-.. code-block:: html+php
+.. code-block:: terminal
- fooBar = $this->transformText($dummy);
}
/**
- * @param string $dummy Some argument description
- * @param array $options
+ * @deprecated
+ */
+ public function someDeprecatedMethod(): string
+ {
+ trigger_deprecation('symfony/package-name', '5.1', 'The %s() method is deprecated, use Acme\Baz::someMethod() instead.', __METHOD__);
+
+ return Baz::someMethod();
+ }
+
+ /**
+ * Transforms the input given as the first argument.
+ *
+ * @param $options an options collection to be used within the transformation
*
- * @return string|null Transformed input
+ * @throws \RuntimeException when an invalid option is provided
*/
- private function transformText($dummy, $options = array())
+ private function transformText(bool|string $dummy, array $options = []): ?string
{
- $mergedOptions = array_merge(
- $options,
- array(
- 'some_default' => 'values',
- 'another_default' => 'more values',
- )
- );
+ $defaultOptions = [
+ 'some_default' => 'values',
+ 'another_default' => 'more values',
+ ];
+
+ foreach ($options as $name => $value) {
+ if (!array_key_exists($name, $defaultOptions)) {
+ throw new \RuntimeException(sprintf('Unrecognized option "%s"', $name));
+ }
+ }
+
+ $mergedOptions = array_merge($defaultOptions, $options);
if (true === $dummy) {
- return;
+ return 'something';
}
- if ('string' === $dummy) {
+
+ if (\is_string($dummy)) {
if ('values' === $mergedOptions['some_default']) {
- $dummy = substr($dummy, 0, 5);
- } else {
- $dummy = ucwords($dummy);
+ return substr($dummy, 0, 5);
}
- } else {
- throw new \RuntimeException(sprintf('Unrecognized dummy option "%s"', $dummy));
+
+ return ucwords($dummy);
}
- return $dummy;
+ return null;
+ }
+
+ /**
+ * Performs some basic operations for a given value.
+ */
+ private function performOperations(mixed $value = null, bool $theSwitch = false): void
+ {
+ if (!$theSwitch) {
+ return;
+ }
+
+ $this->qux->doFoo($value);
+ $this->qux->doBar($value);
}
}
Structure
----------
+~~~~~~~~~
* Add a single space after each comma delimiter;
-* Add a single space around operators (``==``, ``&&``, ...);
+* Add a single space around binary operators (``==``, ``&&``, ...), with
+ the exception of the concatenation (``.``) operator;
+
+* Place unary operators (``!``, ``--``, ...) adjacent to the affected variable;
-* Add a comma after each array item in a multi-line array, even after the
+* Always use `identical comparison`_ unless you need type juggling;
+
+* Use `Yoda conditions`_ when checking a variable against an expression to avoid
+ an accidental assignment inside the condition statement (this applies to ``==``,
+ ``!=``, ``===``, and ``!==``);
+
+* Add a comma after each array item in a multi-line array, even after the
last one;
* Add a blank line before ``return`` statements, unless the return is alone
inside a statement-group (like an ``if`` statement);
+* Use ``return null;`` when a function explicitly returns ``null`` values and
+ use ``return;`` when the function returns ``void`` values;
+
+* Do not add the ``void`` return type to methods in tests;
+
* Use braces to indicate control structure body regardless of the number of
statements it contains;
* Define one class per file - this does not apply to private helper classes
that are not intended to be instantiated from the outside and thus are not
- concerned by the `PSR-0`_ standard;
+ concerned by the `PSR-0`_ and `PSR-4`_ autoload standards;
+
+* Declare the class inheritance and all the implemented interfaces on the same
+ line as the class name;
* Declare class properties before methods;
-* Declare public methods first, then protected ones and finally private ones;
+* Declare public methods first, then protected ones and finally private ones.
+ The exceptions to this rule are the class constructor and the ``setUp()`` and
+ ``tearDown()`` methods of PHPUnit tests, which must always be the first methods
+ to increase readability;
+
+* Declare all the arguments on the same line as the method/function name, no
+ matter how many arguments there are. The only exception are constructor methods
+ using `constructor property promotion`_, where each parameter must be on a new
+ line with `trailing comma`_;
* Use parentheses when instantiating classes regardless of the number of
arguments the constructor has;
-* Exception message strings should be concatenated using :phpfunction:`sprintf`.
+* Exception and error message strings must be concatenated using :phpfunction:`sprintf`;
+
+* Exception and error messages must not contain backticks,
+ even when referring to a technical element (such as a method or variable name).
+ Double quotes must be used at all time:
+
+ .. code-block:: diff
+
+ - Expected `foo` option to be one of ...
+ + Expected "foo" option to be one of ...
+
+* Exception and error messages must start with a capital letter and finish with a dot ``.``;
+
+* Exception, error and deprecation messages containing a class name must
+ use ``get_debug_type()`` instead of ``::class`` to retrieve it:
+
+ .. code-block:: diff
+
+ - throw new \Exception(sprintf('Command "%s" failed.', $command::class));
+ + throw new \Exception(sprintf('Command "%s" failed.', get_debug_type($command)));
+
+* Do not use ``else``, ``elseif``, ``break`` after ``if`` and ``case`` conditions
+ which return or throw something;
+
+* Do not use spaces around ``[`` offset accessor and before ``]`` offset accessor;
+
+* Add a ``use`` statement for every class that is not part of the global namespace;
+
+* When PHPDoc tags like ``@param`` or ``@return`` include ``null`` and other
+ types, always place ``null`` at the end of the list of types.
Naming Conventions
-------------------
+~~~~~~~~~~~~~~~~~~
+
+* Use `camelCase`_ for PHP variables, function and method names, arguments
+ (e.g. ``$acceptableContentTypes``, ``hasSession()``);
-* Use camelCase, not underscores, for variable, function and method
- names, arguments;
+* Use `snake_case`_ for configuration parameters, route names and Twig template
+ variables (e.g. ``framework.csrf_protection``, ``http_status_code``);
-* Use underscores for option names and parameter names;
+* Use SCREAMING_SNAKE_CASE for constants (e.g. ``InputArgument::IS_ARRAY``);
-* Use namespaces for all classes;
+* Use `UpperCamelCase`_ for enumeration cases (e.g. ``InputArgumentMode::IsArray``);
-* Prefix abstract classes with ``Abstract``. Please note some early Symfony2 classes
- do not follow this convention and have not been renamed for backward compatibility
- reasons. However all new abstract classes must follow this naming convention;
+* Use namespaces for all PHP classes, interfaces, traits and enums and
+ `UpperCamelCase`_ for their names (e.g. ``ConsoleLogger``);
+
+* Prefix all abstract classes with ``Abstract`` except PHPUnit ``*TestCase``.
+ Please note some early Symfony classes do not follow this convention and
+ have not been renamed for backward compatibility reasons. However, all new
+ abstract classes must follow this naming convention;
* Suffix interfaces with ``Interface``;
* Suffix traits with ``Trait``;
+* Don't use a dedicated suffix for classes or enumerations (e.g. like ``Class``
+ or ``Enum``), except for the cases listed below.
+
* Suffix exceptions with ``Exception``;
-* Use alphanumeric characters and underscores for file names;
+* Prefix PHP attributes that relate to service configuration with ``As``
+ (e.g. ``#[AsCommand]``, ``#[AsEventListener]``, etc.);
+
+* Prefix PHP attributes that relate to controller arguments with ``Map``
+ (e.g. ``#[MapEntity]``, ``#[MapCurrentUser]``, etc.);
+
+* Use UpperCamelCase for naming PHP files (e.g. ``EnvVarProcessor.php``) and
+ snake case for naming Twig templates and web assets (``section_layout.html.twig``,
+ ``index.scss``);
+
+* For type-hinting in PHPDocs and casting, use ``bool`` (instead of ``boolean``
+ or ``Boolean``), ``int`` (instead of ``integer``), ``float`` (instead of
+ ``double`` or ``real``);
* Don't forget to look at the more verbose :doc:`conventions` document for
more subjective naming considerations.
+.. _service-naming-conventions:
+
+Service Naming Conventions
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+* A service name must be the same as the fully qualified class name (FQCN) of
+ its class (e.g. ``App\EventSubscriber\UserSubscriber``);
+
+* If there are multiple services for the same class, use the FQCN for the main
+ service and use lowercase and underscored names for the rest of services.
+ Optionally divide them in groups separated with dots (e.g.
+ ``something.service_name``, ``fos_user.something.service_name``);
+
+* Use lowercase letters for parameter names (except when referring
+ to environment variables with the ``%env(VARIABLE_NAME)%`` syntax);
+
+* Add class aliases for public services (e.g. alias ``Symfony\Component\Something\ClassName``
+ to ``something.service_name``).
+
Documentation
--------------
+~~~~~~~~~~~~~
+
+* Add PHPDoc blocks for classes, methods, and functions only when they add
+ relevant information that does not duplicate the name, native type
+ declaration or context (e.g. ``instanceof`` checks);
+
+* Only use annotations and types defined in `the PHPDoc reference`_. In
+ order to improve types for static analysis, the following annotations are
+ also allowed:
+
+ * `Generics`_, with the exception of ``@template-covariant``.
+ * `Conditional return types`_ using the vendor-prefixed ``@psalm-return``;
+ * `Class constants`_;
+ * `Callable types`_;
+
+* Group annotations together so that annotations of the same type immediately
+ follow each other, and annotations of a different type are separated by a
+ single blank line;
-* Add PHPDoc blocks for all classes, methods, and functions;
+* Omit the ``@return`` annotation if the method does not return anything;
-* Omit the ``@return`` tag if the method does not return anything;
+* Don't use one-line PHPDoc blocks on classes, methods and functions, even
+ when they contain just one annotation (e.g. don't put ``/** {@inheritdoc} */``
+ in a single line);
-* The ``@package`` and ``@subpackage`` annotations are not used.
+* When adding a new class or when making significant changes to an existing class,
+ an ``@author`` tag with personal contact information may be added, or expanded.
+ Please note it is possible to have the personal contact information updated or
+ removed per request to the :doc:`core team `.
License
--------
+~~~~~~~
* Symfony is released under the MIT license, and the license block has to be
present at the top of every PHP file, before the namespace.
-.. _`PSR-0`: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
-.. _`PSR-1`: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md
-.. _`PSR-2`: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md
+.. _`PHP CS Fixer tool`: https://cs.symfony.com/
+.. _`PSR-0`: https://www.php-fig.org/psr/psr-0/
+.. _`PSR-1`: https://www.php-fig.org/psr/psr-1/
+.. _`PSR-2`: https://www.php-fig.org/psr/psr-2/
+.. _`PSR-4`: https://www.php-fig.org/psr/psr-4/
+.. _`PSR-12`: https://www.php-fig.org/psr/psr-12/
+.. _`identical comparison`: https://www.php.net/manual/en/language.operators.comparison.php
+.. _`Yoda conditions`: https://en.wikipedia.org/wiki/Yoda_conditions
+.. _`camelCase`: https://en.wikipedia.org/wiki/Camel_case
+.. _`UpperCamelCase`: https://en.wikipedia.org/wiki/Camel_case
+.. _`snake_case`: https://en.wikipedia.org/wiki/Snake_case
+.. _`constructor property promotion`: https://www.php.net/manual/en/language.oop5.decon.php#language.oop5.decon.constructor.promotion
+.. _`trailing comma`: https://wiki.php.net/rfc/trailing_comma_in_parameter_list
+.. _`the PHPDoc reference`: https://docs.phpdoc.org/3.0/guide/references/phpdoc/index.html
+.. _`Conditional return types`: https://psalm.dev/docs/annotating_code/type_syntax/conditional_types/
+.. _`Class constants`: https://psalm.dev/docs/annotating_code/type_syntax/value_types/#regular-class-constants
+.. _`Callable types`: https://psalm.dev/docs/annotating_code/type_syntax/callable_types/
+.. _`Generics`: https://psalm.dev/docs/annotating_code/templated_annotations/
diff --git a/contributing/code/tests.rst b/contributing/code/tests.rst
index 873f3c382eb..060e3eda02b 100644
--- a/contributing/code/tests.rst
+++ b/contributing/code/tests.rst
@@ -1,119 +1,71 @@
-Running Symfony2 Tests
-======================
+.. _running-symfony2-tests:
-Before submitting a :doc:`patch ` for inclusion, you need to run the
-Symfony2 test suite to check that you have not broken anything.
+Running Symfony Tests
+=====================
-PHPUnit
--------
+The Symfony project uses a CI (Continuous Integration) service which automatically runs tests
+for any submitted :doc:`patch `. If the new code breaks any test,
+the pull request will show an error message with a link to the full error details.
-To run the Symfony2 test suite, `install`_ PHPUnit 3.6.4 or later first:
+In any case, it's a good practice to run tests locally before submitting a
+:doc:`patch ` for inclusion, to check that you have not broken anything.
-.. code-block:: bash
+.. _phpunit:
+.. _dependencies_optional:
- $ pear config-set auto_discover 1
- $ pear install pear.phpunit.de/PHPUnit
+Before Running the Tests
+------------------------
-Dependencies (optional)
------------------------
+To run the Symfony test suite, install the external dependencies used during the
+tests, such as Doctrine, Twig and Monolog. To do so,
+`install Composer`_ and execute the following:
-To run the entire test suite, including tests that depend on external
-dependencies, Symfony2 needs to be able to autoload them. By default, they are
-autoloaded from `vendor/` under the main root directory (see
-`autoload.php.dist`).
+.. code-block:: terminal
-The test suite needs the following third-party libraries:
+ $ composer update
-* Doctrine
-* Swiftmailer
-* Twig
-* Monolog
-
-To install them all, use `Composer`_:
-
-Step 1: Get `Composer`_
-
-.. code-block:: bash
-
- curl -s http://getcomposer.org/installer | php
-
-Make sure you download ``composer.phar`` in the same folder where
-the ``composer.json`` file is located.
-
-Step 2: Install vendors
-
-.. code-block:: bash
-
- $ php composer.phar --dev install
-
-.. note::
-
- Note that the script takes some time to finish.
-
-.. note::
-
- If you don't have ``curl`` installed, you can also just download the ``installer``
- file manually at http://getcomposer.org/installer. Place this file into your
- project and then run:
-
- .. code-block:: bash
-
- $ php installer
- $ php composer.phar --dev install
+.. tip::
-After installation, you can update the vendors to their latest version with
-the follow command:
+ Dependencies might fail to update and in this case Composer might need you to
+ tell it what Symfony version you are working on.
+ To do so set ``COMPOSER_ROOT_VERSION`` variable, e.g.:
-.. code-block:: bash
+ .. code-block:: terminal
- $ php composer.phar --dev update
+ $ COMPOSER_ROOT_VERSION=7.2.x-dev composer update
-Running
--------
+.. _running:
-First, update the vendors (see above).
+Running the Tests
+-----------------
-Then, run the test suite from the Symfony2 root directory with the following
+Then, run the test suite from the Symfony root directory with the following
command:
-.. code-block:: bash
+.. code-block:: terminal
- $ phpunit
+ $ php ./phpunit symfony
-The output should display `OK`. If not, you need to figure out what's going on
-and if the tests are broken because of your modifications.
+The output should display ``OK``. If not, read the reported errors to figure out
+what's going on and if the tests are broken because of the new code.
.. tip::
- If you want to test a single component type its path after the `phpunit`
- command, e.g.:
-
- .. code-block:: bash
-
- $ phpunit src/Symfony/Component/Finder/
-
-.. tip::
-
- Run the test suite before applying your modifications to check that they
- run fine on your configuration.
-
-Code Coverage
--------------
-
-If you add a new feature, you also need to check the code coverage by using
-the `coverage-html` option:
-
-.. code-block:: bash
+ The entire Symfony suite can take up to several minutes to complete. If you
+ want to test a single component, type its path after the ``phpunit`` command,
+ e.g.:
- $ phpunit --coverage-html=cov/
+ .. code-block:: terminal
-Check the code coverage by opening the generated `cov/index.html` page in a
-browser.
+ $ php ./phpunit src/Symfony/Component/Finder/
.. tip::
- The code coverage only works if you have XDebug enabled and all
- dependencies installed.
+ On Windows, install the `Cmder`_, `ConEmu`_, `ANSICON`_ or `Mintty`_ free applications
+ to see colored test results.
-.. _install: http://www.phpunit.de/manual/current/en/installation.html
-.. _`Composer`: http://getcomposer.org/
+.. _`install Composer`: https://getcomposer.org/download/
+.. _Cmder: https://cmder.app/
+.. _ConEmu: https://conemu.github.io/
+.. _ANSICON: https://github.com/adoxa/ansicon/releases
+.. _Mintty: https://mintty.github.io/
diff --git a/contributing/code_of_conduct/care_team.rst b/contributing/code_of_conduct/care_team.rst
new file mode 100644
index 00000000000..1b15850da39
--- /dev/null
+++ b/contributing/code_of_conduct/care_team.rst
@@ -0,0 +1,60 @@
+CARE Team
+=========
+
+Our Pledge
+----------
+
+In the interest of fostering an open and welcoming environment, the "Code of
+Conduct Active Response Ensurers", or CARE team, pledge to ensure that the
+spirit of the :doc:`Code of Conduct `
+is respected. Our main priority is to ensure the safety of our community members.
+The second goal is to help educate the community as a whole to be aware of the
+Code of Conduct and how to help implement its spirit throughout the community.
+In case these goals conflict, we will prioritize safety of community members
+over all other goals.
+
+If you think there is or has been a violation to the Code of Conduct please contact
+the CARE team or if you prefer contact only individual members of the CARE team.
+
+Members
+-------
+
+Here are all the members of the CARE team (sorted alphabetically by surname).
+You can contact any of them directly using the contact details below or you can
+also contact all of them at once by emailing ** care@symfony.com **.
+
+* **Timo Bakx**
+
+ * *E-mail*: timobakx [at] gmail.com
+ * *Twitter*: `@TimoBakx `_
+ * *SymfonyConnect*: `timobakx `_
+ * *SymfonySlack*: `@Timo Bakx `_
+
+* **Zan Baldwin**
+
+ * *E-mail*: hello [at] zanbaldwin.com
+ * *Twitter*: `@ZanBaldwin `_
+ * *SymfonyConnect*: `zanbaldwin `_
+ * *SymfonySlack*: `@Zan `_
+
+* **Valentine Boineau**
+
+ * *E-mail*: valentine.boineau [at] gmail.com
+ * *Twitter*: `@BoineauV `_
+ * *SymfonyConnect*: `valentineboineau `_
+ * *SymfonySlack*: `@Valentine `_
+
+* **Tobias Nyholm**
+
+ * *E-mail*: tobias.nyholm [at] gmail.com
+ * *Twitter*: `@tobiasnyholm `_
+ * *SymfonyConnect*: `tobias `_
+ * *SymfonySlack*: `@Tobias Nyholm `_
+
+About the CARE Team
+-------------------
+
+The :doc:`Symfony project leader ` appoints the CARE
+team with candidates they see fit. The CARE team will consist of at least
+3 people. The team should be representing as many demographics as possible,
+ideally from different employers.
diff --git a/contributing/code_of_conduct/code_of_conduct.rst b/contributing/code_of_conduct/code_of_conduct.rst
new file mode 100644
index 00000000000..6202fdad424
--- /dev/null
+++ b/contributing/code_of_conduct/code_of_conduct.rst
@@ -0,0 +1,144 @@
+Code of Conduct
+===============
+
+Our Pledge
+----------
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, caste, color, religion, or sexual
+identity and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+Our Standards
+-------------
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the overall
+ community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or advances of
+ any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others’ private information, such as a physical or email address,
+ without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+Our Responsibilities
+--------------------
+
+:doc:`CoC Active Response Ensurers (CARE) team members `
+are responsible for clarifying and enforcing our standards of acceptable
+behavior and will take appropriate and fair corrective action in response to any
+behavior that they deem inappropriate, threatening, offensive, or harmful.
+
+CARE team members have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+Scope
+-----
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+Enforcement
+-----------
+
+Instances of abusive, harassing, or otherwise unacceptable behavior
+:doc:`may be reported ` by
+contacting the :doc:`CARE team members `.
+All complaints will be reviewed and investigated promptly and fairly.
+
+CARE team members are obligated to respect the privacy and security of the
+reporter of any incident.
+
+Enforcement Guidelines
+----------------------
+
+The :doc:`CARE team members ` will
+follow these Community Impact Guidelines in determining the consequences for any
+action they deem in violation of this Code of Conduct:
+
+1. Correction
+~~~~~~~~~~~~~
+
+Community Impact: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+Consequence: A private, written warning from a CARE team member, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+2. Warning
+~~~~~~~~~~
+
+Community Impact: A violation through a single incident or series of actions.
+
+Consequence: A warning with consequences for continued behavior. No interaction
+with the people involved, including unsolicited interaction with those enforcing
+the Code of Conduct, for a specified period of time. This includes avoiding
+interactions in community spaces as well as external channels like social media.
+Violating these terms may lead to a temporary or permanent ban.
+
+3. Temporary Ban
+~~~~~~~~~~~~~~~~
+
+Community Impact: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+Consequence: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+4. Permanent Ban
+~~~~~~~~~~~~~~~~
+
+Community Impact: Demonstrating a pattern of violation of community standards,
+including sustained inappropriate behavior, harassment of an individual, or
+aggression toward or disparagement of classes of individuals.
+
+Consequence: A permanent ban from any sort of public interaction within the
+community.
+
+Attribution
+-----------
+
+This Code of Conduct is adapted from the `Contributor Covenant`_, version 2.1,
+available at https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
+
+Community Impact Guidelines were inspired by `Mozilla’s code of conduct enforcement ladder`_.
+
+Related Documents
+-----------------
+
+.. toctree::
+ :maxdepth: 1
+
+ reporting_guidelines
+ care_team
+ concrete_example_document
+
+.. _Contributor Covenant: https://www.contributor-covenant.org
+.. _Mozilla’s code of conduct enforcement ladder: https://github.com/mozilla/diversity
diff --git a/contributing/code_of_conduct/concrete_example_document.rst b/contributing/code_of_conduct/concrete_example_document.rst
new file mode 100644
index 00000000000..60ffe2527db
--- /dev/null
+++ b/contributing/code_of_conduct/concrete_example_document.rst
@@ -0,0 +1,32 @@
+Code of Conduct: Concrete Example Document
+==========================================
+
+This is a living document that serves to give concrete examples of
+unwanted behavior. These examples have all taken place somewhere in the
+PHP community in the past, and are clear code of conduct violations
+according to the Symfony code of conduct.
+
+Concrete Examples
+-----------------
+
+* Unwelcome comments regarding a person’s lifestyle choices and practices,
+ including those related to food, health, parenting, drugs, and employment;
+* Deliberate misgendering or use of `dead names`_ (The birth name
+ of a person who has since changed their name, often a transgender person);
+* Threats of violence like "The person that created this PR should be
+ punched in the face";
+* Incitement of violence towards any individual, including encouraging a
+ person to commit suicide or to engage in self-harm (even as a joke);
+* Sustained disruption of discussion;
+* Pattern of inappropriate social contact, such as requesting/assuming
+ inappropriate levels of intimacy with others;
+* Continued one-on-one communication after requests to cease;
+* Putting down people based on their technology choices or their work;
+* Taking photographs of a conference attendee or speaker in the foreground and
+ publishing them without their permission.
+
+The original list is inspired and modified from `geek feminism`_ and
+confirmed by experiences from PHPWomen.
+
+.. _dead names: https://en.wiktionary.org/wiki/deadname
+.. _geek feminism: https://geekfeminism.org/about/code-of-conduct
diff --git a/contributing/code_of_conduct/index.rst b/contributing/code_of_conduct/index.rst
new file mode 100644
index 00000000000..5a2beff23a9
--- /dev/null
+++ b/contributing/code_of_conduct/index.rst
@@ -0,0 +1,10 @@
+Code of Conduct
+===============
+
+.. toctree::
+ :maxdepth: 2
+
+ code_of_conduct
+ reporting_guidelines
+ care_team
+ concrete_example_document
diff --git a/contributing/code_of_conduct/reporting_guidelines.rst b/contributing/code_of_conduct/reporting_guidelines.rst
new file mode 100644
index 00000000000..a00394bce65
--- /dev/null
+++ b/contributing/code_of_conduct/reporting_guidelines.rst
@@ -0,0 +1,98 @@
+Reporting Guidelines
+====================
+
+If you believe someone is violating the Code of Conduct we ask that you report
+it to the :doc:`CARE team `
+by emailing, Twitter, in person or any way you see fit.
+
+**All reports will be kept confidential.** The privacy of everyone included in
+the report is of our highest concern. Second to privacy there is transparency.
+After every report we will determine if a public statement should be made. If
+that's the case, the identities of all victims, reporters, and the accused will
+remain confidential unless those individuals instruct us otherwise. The details
+of the incident may also be generalized.
+
+If you believe anyone is in physical danger or doing something that is against
+the law, please notify appropriate emergency services first by calling the relevant
+local authorities. If you are unsure what service or agency is appropriate to
+contact, include this in your report and we will attempt to notify them.
+
+In your report please include:
+
+* Your contact info for follow-up contact.
+* Names (legal, nicknames, or pseudonyms) of any individuals involved.
+* If there were other witnesses besides you, please try to include them as well.
+* When and where the incident occurred. Please be as specific as possible.
+* Your description of what occurred.
+* If there is a publicly available record (e.g. a mailing list archive or a
+ public IRC or Slack log), please include a link and a screenshot.
+* If you believe this incident is ongoing.
+* Any other information you believe we should have.
+
+What happens after you file a report?
+-------------------------------------
+
+You will receive a reply from the :doc:`CARE team `
+acknowledging receipt as soon as possible, but within 24 hours.
+
+The team member receiving the report will immediately contact all or some other
+CARE team members to review the incident and determine:
+
+* What happened.
+* Whether this event constitutes a Code of Conduct violation.
+* What kind of response is appropriate.
+
+If this is determined to be an ongoing incident or a threat to physical safety,
+the team's immediate priority will be to protect everyone involved. This means
+we may delay an "official" response until we believe that the situation has ended
+and that everyone is physically safe.
+
+Once the team has a complete account of the events, they will make a decision as
+to how to respond. Responses may include:
+
+* Nothing (if we determine no Code of Conduct violation occurred).
+* A private reprimand from the Code of Conduct response team to the individual(s)
+ involved.
+* An imposed vacation (i.e. asking someone to "take a week off" from a mailing
+ list or Slack).
+* A permanent or temporary ban from some or all Symfony conference/community
+ spaces (events, meetings, mailing lists, IRC, Slack, etc.)
+* A request to engage in mediation and/or an accountability plan.
+* On a case by case basis, other actions may be possible but will usually be
+ coordinated with the core team and the Symfony company.
+
+We'll respond within one week to the person who filed the report with either a
+resolution or an explanation of why the situation is not yet resolved.
+
+Once we've determined our final actions, we'll contact the original reporter to
+let them know what action (if any) we'll be taking. We'll take into account feedback
+from the reporter on the appropriateness of our response, but our response will be
+determined by what will be best for community safety.
+
+The CARE team keeps a private record of all incidents. By default, all reports
+are shared with the entire CARE team unless the reporter specifically asks
+to exclude specific CARE team members, in which case these CARE team
+members will not be included in any communication on the incidents as well as records
+created related to the incidents.
+
+CARE team members are expected to inform the CARE team and the reporters
+in case of a conflict of interest, and recuse themselves if this is deemed to be a problem.
+
+Appealing the response
+----------------------
+
+Only permanent resolutions (such as bans) may be appealed. To appeal a decision
+of the working group, contact the :doc:`CARE team `
+with your appeal and they will review the case.
+
+Document origin
+---------------
+
+Reporting Guidelines derived from those of the `Stumptown Syndicate`_ and the
+`Django Software Foundation`_.
+
+Adopted by `Symfony`_ organizers on 21 February 2018.
+
+.. _`Stumptown Syndicate`: https://github.com/stumpsyn/policies/blob/master/reporting_guidelines.md/
+.. _`Django Software Foundation`: https://www.djangoproject.com/conduct/reporting/
+.. _`Symfony`: https://symfony.com
diff --git a/contributing/community/index.rst b/contributing/community/index.rst
index e6b51e3fb99..4a5aab91265 100644
--- a/contributing/community/index.rst
+++ b/contributing/community/index.rst
@@ -5,5 +5,7 @@ Community
:maxdepth: 2
releases
- irc
- other
+ review-comments
+ reviews
+ mentoring
+ speaker-mentoring
diff --git a/contributing/community/irc.rst b/contributing/community/irc.rst
deleted file mode 100644
index cc5bdb21ddf..00000000000
--- a/contributing/community/irc.rst
+++ /dev/null
@@ -1,60 +0,0 @@
-IRC Meetings
-============
-
-The purpose of this meeting is to discuss topics in real time with many of the
-Symfony2 devs.
-
-Anyone may propose topics on the `symfony-dev`_ mailing-list until 24 hours
-before the meeting, ideally including well prepared relevant information via
-some URL. 24 hours before the meeting a link to a `doodle`_ will be posted
-including a list of all proposed topics. Anyone can vote on the topics until
-the beginning of the meeting to define the order in the agenda. Each topic
-will be timeboxed to 15mins and the meeting lasts one hour, leaving enough
-time for at least 4 topics.
-
-.. caution::
-
- Note that it's not the expected goal of the meeting to find final
- solutions, but more to ensure that there is a common understanding of the
- issue at hand and move the discussion forward in ways which are hard to
- achieve with less real time communication tools.
-
-Meetings will happen each Thursday at 17:00 CET (+01:00) on the #symfony-dev
-channel on the Freenode IRC server.
-
-The IRC `logs`_ will later be published on the trac wiki, which will include a
-short summary for each of the topics. Tickets will be created for any tasks or
-issues identified during the meeting and referenced in the summary.
-
-Some simple guidelines and pointers for participation:
-
-* It's possible to change votes until the beginning of the meeting by clicking
- on "Edit an entry";
-* The doodle will be closed for voting at the beginning of the meeting;
-* Agenda is defined by which topics got the most votes in the doodle, or
- whichever was proposed first in case of a tie;
-* At the beginning of the meeting one person will identify him/herself as the
- moderator;
-* The moderator is essentially responsible for ensuring the 15min timebox and
- ensuring that tasks are clearly identified;
-* Usually the moderator will also handle writing the summary and creating trac
- tickets unless someone else steps up;
-* Anyone can join and is explicitly invited to participate;
-* Ideally one should familiarize oneself with the proposed topic before the
- meeting;
-* When starting on a new topic the proposer is invited to start things off
- with a few words;
-* Anyone can then comment as they see fit;
-* Depending on how many people participate one should potentially retrain
- oneself from pushing a specific argument too hard;
-* Remember the IRC `logs`_ will be published later on, so people have the
- chance to review comments later on once more;
-* People are encouraged to raise their hand to take on tasks defined during
- the meeting.
-
-Here is an `example`_ doodle.
-
-.. _symfony-dev: http://groups.google.com/group/symfony-devs
-.. _doodle: http://doodle.com
-.. _logs: http://trac.symfony-project.org/wiki/Symfony2IRCMeetingLogs
-.. _example: http://doodle.com/4cnzme7xys3ay53w
diff --git a/contributing/community/mentoring.rst b/contributing/community/mentoring.rst
new file mode 100644
index 00000000000..511a61e6e82
--- /dev/null
+++ b/contributing/community/mentoring.rst
@@ -0,0 +1,13 @@
+Mentoring
+=========
+
+Reading the :doc:`contributing ` is already a great way
+to get started on becoming a Symfony contributor. However, sometimes
+it might still seem overwhelming - contributing can be complex! For this
+purpose we created a dedicated `Symfony Slack`_ channel called `#mentoring`_
+to connect new contributors to long-time contributors. This is a great way
+to get one-on-one advice on the entire process. These long-time contributors
+truly want to help new contributors - so feel free to ask anything!
+
+.. _`Symfony Slack`: https://symfony.com/slack-invite
+.. _`#mentoring`: https://symfony-devs.slack.com/messages/mentoring
diff --git a/contributing/community/other.rst b/contributing/community/other.rst
deleted file mode 100644
index 3869aa2a4e0..00000000000
--- a/contributing/community/other.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-Other Resources
-===============
-
-In order to follow what is happening in the community you might find helpful
-these additional resources:
-
-* List of open `pull requests`_
-* List of recent `commits`_
-* List of open `bugs and enhancements`_
-* List of open source `bundles`_
-
-.. _pull requests: https://github.com/symfony/symfony/pulls
-.. _commits: https://github.com/symfony/symfony/commits/master
-.. _bugs and enhancements: https://github.com/symfony/symfony/issues
-.. _bundles: http://knpbundles.com/
diff --git a/contributing/community/releases.rst b/contributing/community/releases.rst
index 3a43ba425d2..2c5a796e9b5 100644
--- a/contributing/community/releases.rst
+++ b/contributing/community/releases.rst
@@ -1,27 +1,46 @@
The Release Process
===================
-This document explains the Symfony release process (Symfony being the code
-hosted on the main ``symfony/symfony`` `Git repository`_).
+This document explains the process followed by the Symfony project to develop,
+release and maintain its different versions.
-Symfony manages its releases through a *time-based model*; a new Symfony
-release comes out every *six months*: one in *May* and one in *November*.
+Symfony releases follow the `semantic versioning`_ strategy and they are
+published through a *time-based model*:
-.. note::
+* A new **Symfony patch version** (e.g. 5.4.12, 6.1.9) comes out roughly every
+ month. It only contains bug fixes, so you can safely upgrade your applications;
+* A new **Symfony minor version** (e.g. 5.4, 6.0, 6.1) comes out every *six months*:
+ one in *May* and one in *November*. It contains bug fixes and new features,
+ can contain new deprecations but it doesn't include any breaking change,
+ so you can safely upgrade your applications;
+* A new **Symfony major version** (e.g. 5.0, 6.0, 7.0) comes out every *two years*
+ in November of odd years (e.g. 2019, 2021, 2023). It can contain breaking changes,
+ so you may need to do some changes in your applications before upgrading.
+
+.. tip::
+
+ `Subscribe to Symfony Release notifications`_ to receive an email when a new
+ Symfony version is published or when a Symfony version reaches its end of life.
- This release process has been adopted as of Symfony 2.2, and all the
- "rules" explained in this document must be strictly followed as of Symfony
- 2.4.
+.. _contributing-release-development:
Development
-----------
-The six-months period is divided into two phases:
+.. note::
+
+ The Symfony project is an open-source community-driven development framework.
+ There is no roadmap written or defined in advance. Every feature request
+ may or may not be developed in future versions based on the community.
+ Symfony Core Team members can help move things forward if there's enough interest.
+
+The full development period for any major or minor version lasts six months and
+is divided into two phases:
-* *Development*: *Four months* to add new features and to enhance existing
+* **Development**: *Four months* to add new features and to enhance existing
ones;
-* *Stabilisation*: *Two months* to fix bugs, prepare the release, and wait
+* **Stabilization**: *Two months* to fix bugs, prepare the release, and wait
for the whole Symfony ecosystem (third-party libraries, bundles, and
projects using Symfony) to catch up.
@@ -29,69 +48,88 @@ During the development phase, any new feature can be reverted if it won't be
finished in time or if it won't be stable enough to be included in the current
final release.
-Maintenance
------------
+.. tip::
-Each Symfony version is maintained for a fixed period of time, depending on
-the type of the release.
+ Check out the `Symfony Release`_ to learn more about any specific version.
-Standard Releases
-~~~~~~~~~~~~~~~~~
+.. _contributing-release-maintenance:
+.. _symfony-versions:
+.. _releases-lts:
-A standard release is maintained for an *eight month* period.
+Maintenance
+-----------
-Long Term Support Releases
-~~~~~~~~~~~~~~~~~~~~~~~~~~
+Starting from the Symfony 3.x branch, the number of minor versions is limited to
+five per branch (X.0, X.1, X.2, X.3 and X.4). The last minor version of a branch
+(e.g. 5.4, 6.4) is considered a **long-term support version** and the other
+ones are considered **standard versions**:
-Every two years, a new Long Term Support Release (aka LTS release) is
-published. Each LTS release is supported for a *three year* period.
+======================= ===================== ================================
+Version Type Bugs are fixed for... Security issues are fixed for...
+======================= ===================== ================================
+Standard 8 months 8 months
+Long-Term Support (LTS) 3 years 4 years
+======================= ===================== ================================
.. note::
- Paid support after the three year support provided by the community can
- also be bought from `SensioLabs`_.
+ After the active maintenance of a Symfony version has ended, you can get
+ `professional Symfony support`_ from SensioLabs, the company which sponsors
+ the Symfony project.
-Schedule
---------
+.. _deprecations:
-Below is the schedule for the first few versions that use this release model:
+Backward Compatibility
+----------------------
-.. image:: /images/release-process.jpg
- :align: center
+Our :doc:`Backward Compatibility Promise ` is very
+strict and allows developers to upgrade with confidence from one minor version
+of Symfony to the next one.
-* **Yellow** represents the Development phase
-* **Blue** represents the Stabilisation phase
-* **Green** represents the Maintenance period
+When a feature implementation cannot be replaced with a better one without
+breaking backward compatibility, Symfony deprecates the old implementation and
+adds a new preferred one alongside. Read the
+:ref:`conventions ` document to
+learn more about how deprecations are handled in Symfony.
-This results in very predictable dates and maintenance periods.
+.. _major-version-development:
-* *(special)* Symfony 2.2 will be released at the end of February 2013;
-* *(special)* Symfony 2.3 (the first LTS) will be released at the end of May
- 2013;
-* Symfony 2.4 will be released at the end of November 2013;
-* Symfony 2.5 will be released at the end of May 2014;
-* ...
+This deprecation policy also requires a custom development process for major
+versions (6.0, 7.0, etc.) In those cases, Symfony develops at the same time
+two versions: the new major one (e.g. 6.0) and the latest version of the
+previous branch (e.g. 5.4).
-Backward Compatibility
-----------------------
+Both versions have the same new features, but they differ in the deprecated
+features. The oldest version (5.4 in this example) contains all the deprecated
+features whereas the new version (6.0 in this example) removes all of them.
-After the release of Symfony 2.3, backward compatibility will be kept at all
-cost. If it is not possible, the feature, the enhancement, or the bug fix will
-be scheduled for the next major version: Symfony 3.0.
+This allows you to upgrade your projects to the latest minor version (e.g. 5.4),
+see all the deprecation messages and fix them. Once you have fixed all those
+deprecations, you can upgrade to the new major version (e.g. 6.0) without
+effort, because it contains the same features (the only difference are the
+deprecated features, which your project no longer uses).
-.. note::
+PHP Compatibility
+-----------------
- The work on Symfony 3.0 will start whenever enough major features breaking
- backward compatibility are waiting on the todo-list.
+The **minimum** PHP version is decided for each **major** Symfony version by consensus
+amongst the :doc:`core team ` and documented as
+part of the :ref:`technical requirements for running Symfony applications
+`.
-Deprecations
-------------
+Throughout each Symfony release's support lifetime, all released versions of PHP
+including new major versions will be supported. In this way, the **maximum** supported
+version of PHP for a maintained Symfony release is the latest released
+one that is publicly available.
-When a feature implementation cannot be replaced with a better one without
-breaking backward compatibility, there is still the possibility to deprecate
-the old implementation and add a new preferred one along side. Read the
-:ref:`conventions` document to
-learn more about how deprecations are handled in Symfony.
+For out-of-support releases of Symfony, the latest PHP version at time of EOL is the last
+supported PHP version. Newer versions of PHP may or may not function.
+
+.. note::
+
+ By exception to the rule, bumping the minimum **minor** version of PHP is
+ possible for a **minor** Symfony version when this helps fix important
+ issues.
Rationale
---------
@@ -108,7 +146,9 @@ This release process was adopted to give more *predictability* and
* Coordinate the Symfony timeline with popular PHP projects that work well
with Symfony and with projects using Symfony;
* Give time to the Symfony ecosystem to catch up with the new versions
- (bundle authors, documentation writers, translators, ...).
+ (bundle authors, documentation writers, translators, ...);
+* Give companies a strict and predictable timeline they can rely on to plan
+ their own projects development.
The six month period was chosen as two releases fit in a year. It also allows
for plenty of time to work on new features and it allows for non-ready
@@ -117,10 +157,11 @@ for the next cycle.
The dual maintenance mode was adopted to make every Symfony user happy. Fast
movers, who want to work with the latest and the greatest, use the standard
-releases: a new version is published every six months, and there is a two
-months period to upgrade. Companies wanting more stability use the LTS
-releases: a new version is published every two years and there is a year to
-upgrade.
-
-.. _Git repository: https://github.com/symfony/symfony
-.. _SensioLabs: http://sensiolabs.com/
+version: a new version is published every six months, and there is a two months
+period to upgrade. Companies wanting more stability use the LTS versions: a new
+version is published every two years and there is a year to upgrade.
+
+.. _`semantic versioning`: https://semver.org/
+.. _`Subscribe to Symfony Release notifications`: https://symfony.com/account/notifications
+.. _`Symfony Release`: https://symfony.com/releases
+.. _`professional Symfony support`: https://sensiolabs.com/
diff --git a/contributing/community/review-comments.rst b/contributing/community/review-comments.rst
new file mode 100644
index 00000000000..5b9bc932205
--- /dev/null
+++ b/contributing/community/review-comments.rst
@@ -0,0 +1,190 @@
+Respectful Review Comments
+==========================
+
+:doc:`Reviewing issues and pull requests `
+is a great way to get started with contributing to the Symfony community.
+Anyone can do it! But before you give a comment, take a step back and think,
+is what you are about to say actually what you intend?
+
+Communicating over the Internet with nothing but text can pose a
+big challenge, especially if you remember that the Symfony community
+is world-wide and is composed of a wide variety of people with differing
+ideas and opinions.
+
+Not everyone speaks English or is able to use a keyboard. Some might
+have dyslexia or similar conditions that affect their writing.
+
+Not to mention that some might have a bad experience from previous
+contributions (to other projects).
+
+You're not alone in this. This guide will try to help you write
+constructive, respectful and helpful reviews and replies.
+
+.. tip::
+
+ This guide is not about lecturing you to "conform" or give-up
+ your ideas and opinions but helping you to better communicate,
+ prevent possible confusion, and keeping the Symfony community a
+ welcoming place for everyone. **You are free to disagree with
+ someone's opinions, but don't be disrespectful.**
+
+It’s important to accept that many programming decisions are opinions.
+Discuss trade-offs, which you prefer, and reach a resolution quickly.
+It's not about being right or wrong, but using what works.
+
+Tone of Voice
+-------------
+
+We don't expect you to be completely formal, or to even write error-free
+English. Just remember this: don't swear, and be respectful to others.
+
+Don't reply in anger or with an aggressive tone. If you're angry, we understand
+that, but swearing/cursing and name calling doesn't really encourage anyone to
+help you. Take a deep breath, count to 10 and try to *clearly* explain what problems
+you encounter.
+
+Inclusive Language
+------------------
+
+In an effort to be inclusive to a wide group of people, it's recommended to
+use personal pronouns that don't suggest a particular gender. Unless someone
+has stated their pronouns, use "they", "them" instead of "he", "she", "his",
+"hers", "his/hers", "he/she", etc.
+
+Try to avoid using wording that may be considered excluding, needlessly gendered
+(e.g. words that have a male or female base), racially motivated or singles out
+a particular group in society. For example, it's recommended to use words like
+"folks", "team", "everyone" instead of "guys", "ladies", "yanks", etc.
+
+Giving Positive Feedback
+------------------------
+
+While reviewing issues and pull requests you may run into some suggestions
+(including patches) that don't reflect your ideas, are not good, or downright wrong.
+
+Now, when you prepare your comment, consider the amount of work and time the author
+has spent on their idea and how your response would make them feel.
+
+Did you correctly understand their intention? Or are you making assumptions?
+Whatever your response, be explicit. Remember people don't always understand your
+intentions online.
+
+Avoid using terms that could be seen as referring to personal traits ("dumb", "stupid").
+Assume everyone is intelligent and well-meaning.
+
+.. tip::
+
+ Good questions avoid judgment and avoid assumptions about the author's perspective.
+
+ Maybe you can ask for clarification? Suggest an alternative?
+ Or provide a simple explanation *why* you disagree with their proposal.
+
+ * ``This looks wrong. Are you sure it's correct?`` (e.g. typo/syntax error)
+
+ * ``What do you think of "RequestFactory" instead of RequestCreator?``
+
+Even if something *is* really wrong or "a bad idea", stay respectful and
+don't get into endless you-are-wrong discussions or "flame wars".
+
+Don't use hyperbole ("always", "never", "endlessly", "nothing", "worst", "horrible", "terrible").
+
+**Don't:** *"I don't like how you wrote this code"* - there is no clear explanation why you
+don't like how it's written.
+
+**Better:** *"I find it hard to read this code as there are many nested if statements, can you make it more
+readable? By encapsulating some of the details or maybe adding some comments to explain the overall logic."* -
+You explain why you find the code hard to read *and* give some suggestions for improvement.
+
+If a piece of code is in fact wrong, explain why:
+
+* "This code doesn't comply with Symfony's CS rules. Please see [...] for details."
+
+* "Symfony 3 still uses PHP 5 and doesn't allow the usage of scalar type-hints."
+
+* "I think the code is less readable now." - careful here, be sure explain why you think
+ the code is less readable, and maybe give some suggestions?
+
+**Examples of valid reasons to reject:**
+
+* "We tried that in the past (link to the relevant PR) but we needed to revert it for XXX reason."
+
+* "That change would introduce too many merge conflicts when merging up Symfony branches.
+ In the past we've always rejected changes like this."
+
+* "I profiled this change and it hurts performance significantly" - if you don't profile, it's an opinion, so we can ignore
+
+* "Code doesn't match Symfony's CS rules (e.g. use ``[]`` instead of ``array()``)"
+
+* "We only provide integration with very popular projects (e.g. we integrate Bootstrap but not your own CSS framework)"
+
+* "This would require adding lots of code and making lots of changes for a feature that doesn't look so important.
+ That could hurt maintenance in the future."
+
+Asking for Changes
+------------------
+
+Rarely something is perfect from the start, while the code itself is good.
+It may not be optimal or conform to the Symfony coding style.
+
+Again, understand the author already spent time on the issue and asking
+for (small) changes may be misinterpreted or seen as a personal attack.
+
+Be thankful for their work (so far), stay positive and really help them
+to make the contribution a great one. *Especially if they are a first
+time contributor.*
+
+Use words like "Please", "Thank you" and "Could you" instead of making demands;
+
+* "Thank you for your work so far. I left some suggestions for improvement
+ to make the code more readable."
+
+* "Your code contains some coding-style problems, can you fix these before
+ we merge? Thank you"
+
+* "Please use 4 spaces instead of tabs", "This needs be on the previous line";
+
+During a pull request review you can usually leave more than one comment,
+you don't have to use "Please" all the time. But it wouldn't hurt.
+
+It may not seem like much, but saying "Thank you" does make others feel
+more welcome.
+
+Preventing Escalations
+----------------------
+
+Sometimes when people receive feedback they may get defensive.
+In that case, it is better to try to approach the discussion in
+a different way, to not escalate further.
+
+If you want someone to mediate, please join the ``#contribs`` channel on `Symfony Slack`_,
+to have a safe environment and keep working together on common goals.
+
+Using Humor
+-----------
+
+In short: Extreme misbehavior will not be tolerated and may even get you banned;
+Keep it real and friendly.
+
+**Don't use sarcasm for a serious topic, that's not something that belongs
+to the Symfony community.** And don't marginalize someone's problems;
+``Well I guess that's not supposed to happen? 😆``.
+
+Even if someone's explanation is "inviting to joke about it", it's a real
+problem to them. Making jokes about this doesn't help with solving their
+problem and only makes them *feel stupid*. Instead, try to discover the
+actual problem.
+
+Final Words
+-----------
+
+Don't feel bad if you "failed" to follow these tips. As long as your
+intentions were good and you didn't really offend or insult anyone;
+you can explain you misunderstood, you didn't mean to marginalize or
+simply failed.
+
+But don't say it "just because", if your apology is not really meant
+you *will* lose credibility and respect from other developers.
+
+*Do unto others as you would have them do unto you.*
+
+.. _`Symfony Slack`: https://symfony.com/slack-invite
diff --git a/contributing/community/reviews.rst b/contributing/community/reviews.rst
new file mode 100644
index 00000000000..06426c03985
--- /dev/null
+++ b/contributing/community/reviews.rst
@@ -0,0 +1,220 @@
+Community Reviews
+=================
+
+Symfony is an open-source project driven by a large community. If you don't feel
+ready to contribute code or patches, reviewing issues and pull requests (PRs)
+can be a great start to get involved and give back. In fact, people who "triage"
+issues are the backbone to Symfony's success!
+
+.. note::
+
+ Communicating in a way where your words come across as intended can be
+ difficult. Please read through the
+ :doc:`Respectful Review Comments `
+ guidelines.
+
+Why Reviewing Is Important
+--------------------------
+
+Community reviews are essential for the development of the Symfony framework,
+since there are many more pull requests and bug reports than there are members
+in the Symfony core team to review, fix and merge them.
+
+On the `Symfony issue tracker`_, you can find many items in a `Needs Review`_
+status:
+
+* **Bug Reports**: Bug reports need to be checked for completeness.
+ Is any important information missing? Can the bug be reproduced?
+
+* **Pull Requests**: Pull requests contain code that fixes a bug or implements
+ new functionality. Reviews of pull requests ensure that they are implemented
+ properly, are covered by test cases, don't introduce new bugs and maintain
+ backward compatibility.
+
+Note that **anyone who has some basic familiarity with Symfony and PHP can
+review bug reports and pull requests**. You don't need to be an expert to help.
+
+Be Constructive
+---------------
+
+Before you begin, remember that you are looking at the result of someone else's
+hard work. A good review comment thanks the contributor for their work,
+identifies what was done well, identifies what should be improved and suggests a
+next step.
+
+Create a GitHub Account
+-----------------------
+
+Symfony uses `GitHub`_ to manage bug reports and pull requests. If you want to
+do reviews, you need to `create a GitHub account`_ and log in.
+
+The Bug Report Review Process
+-----------------------------
+
+A good way to get started with reviewing is to pick a bug report from the
+`bug reports in need of review`_.
+
+The steps for the review are:
+
+#. **Is the Report Complete?**
+
+ Good bug reports contain a link to a project (the "reproduction project")
+ created with the `Symfony skeleton`_ that reproduces the bug. If it
+ doesn't, the report should at least contain enough information and code
+ samples to reproduce the bug.
+
+#. **Reproduce the Bug**
+
+ Download the reproduction project and test whether the bug can be reproduced
+ on your system. If the reporter did not provide a reproduction project,
+ create one based on one `Symfony skeleton`_.
+
+#. **Update the Issue Status**
+
+ At last, add a comment to the bug report. **Thank the reporter for reporting
+ the bug**. Include the line ``Status: `` in your comment to trigger
+ our `Carson Bot`_ which updates the status label of the issue. You can set
+ the status to one of the following:
+
+ **Needs Work** If the bug *does not* contain enough information to be
+ reproduced, explain what information is missing and move the report to this
+ status.
+
+ **Works for me** If the bug *does* contain enough information to be
+ reproduced but works on your system, or if the reported bug is a feature and
+ not a bug, provide a short explanation and move the report to this status.
+
+ **Reviewed** If you can reproduce the bug, move the report to this status.
+ If you created a reproduction project, include the link to the project in
+ your comment.
+
+.. topic:: Example
+
+ Here is a sample comment for a bug report that could be reproduced:
+
+ .. code-block:: text
+
+ Thank you @weaverryan for creating this bug report! This indeed looks
+ like a bug. I reproduced the bug in the "kernel-bug" branch of
+ https://github.com/webmozart/some-project.
+
+ Status: Reviewed
+
+The Pull Request Review Process
+-------------------------------
+
+The process for reviewing pull requests (PRs) is similar to the one for bug
+reports. Reviews of pull requests usually take a little longer since you need
+to understand the functionality that has been fixed or added and find out
+whether the implementation is complete.
+
+It is okay to do partial reviews! If you do a partial review, comment how far
+you got and leave the PR in the "Needs Review" state.
+
+Pick a pull request from the `PRs in need of review`_ and follow these steps:
+
+#. **Is the PR Complete**?
+
+ Every pull request must contain a header that gives some basic information
+ about the PR. You can find the template for that header in the
+ :ref:`Contribution Guidelines `.
+
+#. **Is the Base Branch Correct?**
+
+ GitHub displays the branch that a PR is based on below the title of the
+ pull request. Is that branch correct?
+
+ * Bugs should be fixed in the oldest, maintained version that contains the
+ bug. Check :doc:`Symfony's Release Schedule ` to find the oldest
+ currently supported version.
+
+ * New features should always be added to the current development version.
+ Check the `Symfony Roadmap`_ to find the current development version.
+
+#. **Reproduce the Problem**
+
+ Read the issue that the pull request is supposed to fix. Reproduce the
+ problem on a new project created with the `Symfony skeleton`_ and try to
+ understand why it exists. If the linked issue already contains such a
+ project, install it and run it on your system.
+
+#. **Review the Code**
+
+ Read the code of the pull request and check it against some common criteria:
+
+ * Does the code address the issue the PR is intended to fix/implement?
+ * Does the PR stay within scope to address *only* that issue?
+ * Does the PR contain automated tests? Do those tests cover all relevant
+ edge cases?
+ * Does the PR contain sufficient comments to understand its code?
+ * Does the code break backward compatibility? If yes, does the PR header say
+ so?
+ * Does the PR contain deprecations? If yes, does the PR header say so? Does
+ the code contain ``trigger_deprecation()`` statements for all deprecated
+ features?
+ * Are all deprecations and backward compatibility breaks documented in the
+ latest UPGRADE-X.X.md file? Do those explanations contain "Before"/"After"
+ examples with clear upgrade instructions?
+
+ .. note::
+
+ Eventually, some of these aspects will be checked automatically.
+
+#. **Test the Code**
+
+ Take your project from step 3 and test whether the PR works properly.
+ Replace the Symfony project in the ``vendor`` directory by the code in the
+ PR by running the following Git commands. Insert the PR ID (that's the number
+ after the ``#`` in the PR title) for the ```` placeholders:
+
+ .. code-block:: terminal
+
+ $ cd vendor/symfony/symfony
+ $ git fetch origin pull//head:pr
+ $ git checkout pr
+
+ For example:
+
+ .. code-block:: terminal
+
+ $ git fetch origin pull/15723/head:pr15723
+ $ git checkout pr15723
+
+ Now you can :doc:`test the project ` against
+ the code in the PR.
+
+#. **Update the PR Status**
+
+ At last, add a comment to the PR. **Thank the contributor for working on the
+ PR**. Include the line ``Status: `` in your comment to trigger our
+ `Carson Bot`_ which updates the status label of the issue. You can set the
+ status to one of the following:
+
+ **Needs Work** If the PR is not yet ready to be merged, explain the issues
+ that you found and move it to this status.
+
+ **Reviewed** If the PR satisfies all the checks above, move it to this
+ status. A core contributor will soon look at the PR and decide whether it can
+ be merged or needs further work.
+
+.. topic:: Example
+
+ Here is a sample comment for a PR that is not yet ready for merge:
+
+ .. code-block:: text
+
+ Thank you @weaverryan for working on this! It seems that your test
+ cases don't cover the cases when the counter is zero or smaller.
+ Could you please add some tests for that?
+
+ Status: Needs Work
+
+.. _GitHub: https://github.com
+.. _Symfony issue tracker: https://github.com/symfony/symfony/issues
+.. _`Symfony skeleton`: https://github.com/symfony/skeleton
+.. _create a GitHub account: https://help.github.com/github/getting-started-with-github/signing-up-for-a-new-github-account
+.. _bug reports in need of review: https://github.com/symfony/symfony/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue+label%3A%22Bug%22+label%3A%22Status%3A+Needs+Review%22+
+.. _PRs in need of review: https://github.com/symfony/symfony/pulls?q=is%3Aopen+is%3Apr+label%3A%22Status%3A+Needs+Review%22
+.. _Symfony Roadmap: https://symfony.com/releases
+.. _Carson Bot: https://github.com/carsonbot/carsonbot
+.. _`Needs Review`: https://github.com/symfony/symfony/labels/Status%3A%20Needs%20Review
diff --git a/contributing/community/speaker-mentoring.rst b/contributing/community/speaker-mentoring.rst
new file mode 100644
index 00000000000..82b25c61f57
--- /dev/null
+++ b/contributing/community/speaker-mentoring.rst
@@ -0,0 +1,44 @@
+Speaker Mentoring
+=================
+
+The Symfony community benefits greatly when as many people as possible
+share their knowledge and experience with others. Every different
+point of view adds to our collective understanding of how to best use
+and evolve the code, design patterns and architecture provided within
+the Symfony community. Because of this, we specifically want to hear
+from long-time contributors and new users, who often come across entirely
+different challenges with a totally fresh new look and perspective.
+
+How to get started
+------------------
+
+Giving a first talk at a conference can seem quite intimidating. But
+don't worry! At one time, every speaker went through the same process.
+And so, we want to make sure that as many people as possible are empowered
+to take this path if they are motivated. We have collected a few resources
+with advice to get started. More importantly, we can connect experienced
+speakers with people who are just taking their first steps in this area:
+
+.. tip::
+
+ A good first step might be to give a talk at a local user group to a
+ smaller crowd that one knows more intimately. A next step could be to
+ give a talk at a conference in your first language.
+
+The best way to find people that can review your talk idea or slides is
+the `#speaker-mentoring`_ channel on `Symfony Slack`_. There are many
+seasoned speakers with knowledge in various parts of Symfony that are
+motivated to help you get started on your path towards becoming a
+public speaker. They can even do practice runs via video chat!
+Furthermore, they can also be an ally when it comes to the day of
+giving the talk at a conference!
+
+A great resource with advice on everything related to `public speaking`_
+is a collection of links maintained by VM (Vicky) Brasseur. It covers
+everything from finding a conference call for proposals, how to
+refine a proposal, to how to put together slide decks to practical
+tips for preparation and talk delivery.
+
+.. _`#speaker-mentoring`: https://symfony-devs.slack.com/messages/speaker-mentoring
+.. _`Symfony Slack`: https://symfony.com/slack-invite
+.. _`public speaking`: https://github.com/vmbrasseur/Public_Speaking
diff --git a/contributing/core_team.rst b/contributing/core_team.rst
new file mode 100644
index 00000000000..f895dcd00d8
--- /dev/null
+++ b/contributing/core_team.rst
@@ -0,0 +1,381 @@
+Symfony Core Team
+=================
+
+The **Symfony Core** team is the group of developers that determine the
+direction and evolution of the Symfony project. Their votes rule if the
+features and patches proposed by the community are approved or rejected.
+
+All the Symfony Core members are long-time contributors with solid technical
+expertise and they have demonstrated a strong commitment to drive the project
+forward.
+
+This document states the rules that govern the Symfony core team. These rules
+are effective upon publication of this document and all Symfony Core members
+must adhere to said rules and protocol.
+
+Core Team Member Role
+---------------------
+
+In addition to being a regular contributor, core team members are expected to:
+
+* Review, approve, and merge pull requests;
+* Help enforce, improve, and implement Symfony :doc:`processes and policies `;
+* Participate in the Symfony Core Team discussions (on Slack and GitHub).
+
+Core Team Member Responsibilities
+---------------------------------
+
+Core Team members are unpaid volunteers and as such, they are not expected to
+dedicate any specific amount of time on Symfony. They are expected to help the
+project in any way they can. From reviewing pull requests and writing documentation,
+to participating in discussions and helping the community in general. However,
+their involvement is completely voluntary and can be as much or as little as
+they want.
+
+Core Team Communication
+~~~~~~~~~~~~~~~~~~~~~~~
+
+As an open source project, public discussions and documentation is favored
+over private ones. All communication in the Symfony community conforms to
+the :doc:`/contributing/code_of_conduct/code_of_conduct`. Request
+assistance from other Core and CARE team members when getting in situations
+not following the Code of Conduct.
+
+Core Team members are invited in a private Slack channel, for quick
+interactions and private processes (e.g. security issues). Each member
+should feel free to ask for assistance for anything they may encounter.
+Expect no judgement from other team members.
+
+Core Organization
+-----------------
+
+Symfony Core members are divided into groups. Each member can only belong to one
+group at a time. The privileges granted to a group are automatically granted to
+all higher priority groups.
+
+The Symfony Core groups, in descending order of priority, are as follows:
+
+1. **Project Leader**
+
+ * Elects members in any other group;
+ * Merges pull requests in all Symfony repositories.
+
+2. **Mergers Team**
+
+ * Merge pull requests on the main Symfony repository.
+
+In addition, there are other groups created to manage specific topics:
+
+* **Security Team**: manages the whole security process (triaging reported vulnerabilities,
+ fixing the reported issues, coordinating the release of security fixes, etc.);
+* **Symfony UX Team**: manages the `UX repositories`_;
+* **Symfony CLI Team**: manages the `CLI repositories`_;
+* **Documentation Team**: manages the whole `symfony-docs repository`_.
+
+Active Core Members
+~~~~~~~~~~~~~~~~~~~
+
+* **Project Leader**:
+
+ * **Fabien Potencier** (`fabpot`_).
+
+* **Mergers Team** (``@symfony/mergers`` on GitHub):
+
+ * **Nicolas Grekas** (`nicolas-grekas`_);
+ * **Christophe Coevoet** (`stof`_);
+ * **Christian Flothmann** (`xabbuh`_);
+ * **Kévin Dunglas** (`dunglas`_);
+ * **Javier Eguiluz** (`javiereguiluz`_);
+ * **Grégoire Pineau** (`lyrixx`_);
+ * **Ryan Weaver** (`weaverryan`_);
+ * **Robin Chalas** (`chalasr`_);
+ * **Yonel Ceruto** (`yceruto`_);
+ * **Tobias Nyholm** (`Nyholm`_);
+ * **Wouter De Jong** (`wouterj`_);
+ * **Alexander M. Turek** (`derrabus`_);
+ * **Jérémy Derussé** (`jderusse`_);
+ * **Oskar Stark** (`OskarStark`_);
+ * **Mathieu Santostefano** (`welcomattic`_);
+ * **Kevin Bond** (`kbond`_);
+ * **Jérôme Tamarelle** (`gromnan`_);
+ * **Berislav Balogović** (`hypemc`_);
+ * **Mathias Arlaud** (`mtarld`_);
+ * **Florent Morselli** (`spomky`_);
+ * **Alexandre Daubois** (`alexandre-daubois`_).
+
+* **Security Team** (``@symfony/security`` on GitHub):
+
+ * **Fabien Potencier** (`fabpot`_);
+ * **Jérémy Derussé** (`jderusse`_).
+
+* **Symfony UX Team** (``@symfony/ux`` on GitHub):
+
+ * **Ryan Weaver** (`weaverryan`_);
+ * **Kevin Bond** (`kbond`_);
+ * **Simon André** (`smnandre`_);
+ * **Hugo Alliaume** (`kocal`_);
+ * **Matheo Daninos** (`webmamba`_).
+
+* **Symfony CLI Team** (``@symfony-cli/core`` on GitHub):
+
+ * **Fabien Potencier** (`fabpot`_);
+ * **Tugdual Saunier** (`tucksaun`_).
+
+* **Documentation Team** (``@symfony/team-symfony-docs`` on GitHub):
+
+ * **Fabien Potencier** (`fabpot`_);
+ * **Ryan Weaver** (`weaverryan`_);
+ * **Christian Flothmann** (`xabbuh`_);
+ * **Wouter De Jong** (`wouterj`_);
+ * **Javier Eguiluz** (`javiereguiluz`_).
+ * **Oskar Stark** (`OskarStark`_).
+
+Former Core Members
+~~~~~~~~~~~~~~~~~~~
+
+They are no longer part of the core team, but we are very grateful for all their
+Symfony contributions:
+
+* **Bernhard Schussek** (`webmozart`_);
+* **Abdellatif AitBoudad** (`aitboudad`_);
+* **Romain Neutron** (`romainneutron`_);
+* **Jordi Boggiano** (`Seldaek`_);
+* **Lukas Kahwe Smith** (`lsmith77`_);
+* **Jules Pietri** (`HeahDude`_);
+* **Jakub Zalas** (`jakzal`_);
+* **Samuel Rozé** (`sroze`_);
+* **Tobias Schultze** (`Tobion`_);
+* **Maxime Steinhausser** (`ogizanagi`_);
+* **Titouan Galopin** (`tgalopin`_);
+* **Michael Cullum** (`michaelcullum`_);
+* **Thomas Calvet** (`fancyweb`_).
+
+Core Membership Application
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+About once a year, the core team discusses the opportunity to invite new members.
+
+Core Membership Revocation
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A Symfony Core membership can be revoked for any of the following reasons:
+
+* Refusal to follow the rules and policies stated in this document;
+* Lack of activity for the past six months;
+* Willful negligence or intent to harm the Symfony project;
+* Upon decision of the **Project Leader**.
+
+Core Membership Compensation
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Core Team members work on Symfony on a purely voluntary basis. In return
+for their work for the Symfony project, members can get free access to
+Symfony conferences. Personal vouchers for Symfony conferences are handed out
+on request by the **Project Leader**.
+
+Code Development Rules
+----------------------
+
+Symfony project development is based on pull requests proposed by any member
+of the Symfony community. Pull request acceptance or rejection is decided based
+on the votes cast by the Symfony Core members.
+
+Pull Request Voting Policy
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+* ``-1`` votes must always be justified by technical and objective reasons;
+
+* ``+1`` votes do not require justification, unless there is at least one
+ ``-1`` vote;
+
+* Core members can change their votes as many times as they desire
+ during the course of a pull request discussion;
+* Core members are not allowed to vote on their own pull requests.
+
+Pull Request Merging Policy
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A pull request **can be merged** if:
+
+* It is a :ref:`unsubstantial change `;
+* Enough time was given for peer reviews;
+* It is a bug fix and at least two **Mergers Team** members voted ``+1``
+ (only one if the submitter is part of the Mergers team) and no Core
+ member voted ``-1`` (via GitHub reviews or as comments).
+* It is a new feature and at least two **Mergers Team** members voted
+ ``+1`` (if the submitter is part of the Mergers team, two *other* members)
+ and no Core member voted ``-1`` (via GitHub reviews or as comments).
+
+.. _core-team_unsubstantial-changes:
+
+.. note::
+
+ Unsubstantial changes comprise typos, DocBlock fixes, code standards
+ fixes, comment, exception message tweaks, and minor CSS, JavaScript and
+ HTML modifications.
+
+Pull Request Merging Process
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+All code must be committed to the repository through pull requests, except
+for :ref:`unsubstantial change ` which can be
+committed directly to the repository.
+
+**Mergers** must always use the command-line ``gh`` tool provided by the
+**Project Leader** to merge pull requests.
+
+When merging a pull request, the tool asks for a category that should be chosen
+following these rules:
+
+* **Feature**: For new features and deprecations; Pull requests must be merged
+ in the development branch.
+* **Bug**: Only for bug fixes; We are very conservative when it comes to
+ merging older, but still maintained, branches. Read the :doc:`maintenance`
+ document for more information.
+* **Minor**: For everything that does not change the code or when they don't
+ need to be listed in the CHANGELOG files: typos, Markdown files, test files,
+ new or missing translations, etc.
+* **Security**: It's the category used for security fixes and should never be
+ used except by the security team.
+
+Getting the right category is important as it is used by automated tools to
+generate the CHANGELOG files when releasing new versions.
+
+.. tip::
+
+ Core team members are part of the ``mergers`` group on the ``symfony``
+ Github organization. This gives them write-access to many repositories,
+ including the main ``symfony/symfony`` mono-repository.
+
+ To avoid unintentional pushes to the main project (which in turn creates
+ new versions on Packagist), Core team members are encouraged to have
+ two clones of the project locally:
+
+ #. A clone for their own contributions, which they use to push to their
+ fork on GitHub. Clear out the push URL for the Symfony repository using
+ ``git remote set-url --push origin dev://null`` (change ``origin``
+ to the Git remote poiting to the Symfony repository);
+ #. A clone for merging, which they use in combination with ``gh`` and
+ allows them to push to the main repository.
+
+Upmerging Version Branches
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To synchronize changes in all versions, version branches are regularly
+merged from oldest to latest, called "upmerging". This is a manual process.
+There is no strict policy on when this occurs, but usually not more than
+once a day and at least once before monthly releases.
+
+Before starting the upmerge, Git must be configured to provide a merge
+summary by running:
+
+.. code-block:: terminal
+
+ # Run command in the "symfony" repository
+ $ git config merge.stat true
+
+The upmerge should always be done on all maintained versions at the same
+time. Refer to `the releases page`_ to find all actively maintained
+versions (indicated by a green color).
+
+The process follows these steps:
+
+#. Start on the oldest version and make sure it's up to date with the
+ upstream repository;
+#. Check-out the second oldest version, update from upstream and merge the
+ previous version from the local branch;
+#. Continue this process until you reached the latest version;
+#. Push the branches to the repository and monitor the test suite. Failure
+ might indicate hidden/missed merge conflicts.
+
+.. code-block:: terminal
+
+ # 'origin' is refered to as the main upstream project
+ $ git fetch origin
+
+ # update the local branches
+ $ git checkout 6.4
+ $ git reset --hard origin/6.4
+ $ git checkout 7.2
+ $ git reset --hard origin/7.2
+ $ git checkout 7.3
+ $ git reset --hard origin/7.3
+
+ # upmerge 6.4 into 7.2
+ $ git checkout 7.2
+ $ git merge --no-ff 6.4
+ # ... resolve conflicts
+ $ git commit
+
+ # upmerge 7.2 into 7.3
+ $ git checkout 7.3
+ $ git merge --no-ff 7.2
+ # ... resolve conflicts
+ $ git commit
+
+ $ git push origin 7.3 7.2 6.4
+
+.. warning::
+
+ Upmerges must be explicit, i.e. no fast-forward merges.
+
+.. tip::
+
+ Solving merge conflicts can be challenging. You can always ping other
+ Core team members to help you in the process (e.g. members that merged
+ a specific conflicting change).
+
+Release Policy
+~~~~~~~~~~~~~~
+
+The **Project Leader** is also the release manager for every Symfony version.
+
+Symfony Core Rules and Protocol Amendments
+------------------------------------------
+
+The rules described in this document may be amended at any time at the
+discretion of the **Project Leader**.
+
+.. _`symfony-docs repository`: https://github.com/symfony/symfony-docs
+.. _`UX repositories`: https://github.com/symfony/ux
+.. _`CLI repositories`: https://github.com/symfony-cli
+.. _`fabpot`: https://github.com/fabpot/
+.. _`webmozart`: https://github.com/webmozart/
+.. _`Tobion`: https://github.com/Tobion/
+.. _`nicolas-grekas`: https://github.com/nicolas-grekas/
+.. _`stof`: https://github.com/stof/
+.. _`dunglas`: https://github.com/dunglas/
+.. _`jakzal`: https://github.com/jakzal/
+.. _`Seldaek`: https://github.com/Seldaek/
+.. _`weaverryan`: https://github.com/weaverryan/
+.. _`aitboudad`: https://github.com/aitboudad/
+.. _`xabbuh`: https://github.com/xabbuh/
+.. _`javiereguiluz`: https://github.com/javiereguiluz/
+.. _`lyrixx`: https://github.com/lyrixx/
+.. _`chalasr`: https://github.com/chalasr/
+.. _`ogizanagi`: https://github.com/ogizanagi/
+.. _`Nyholm`: https://github.com/Nyholm
+.. _`sroze`: https://github.com/sroze
+.. _`yceruto`: https://github.com/yceruto
+.. _`michaelcullum`: https://github.com/michaelcullum
+.. _`wouterj`: https://github.com/wouterj
+.. _`HeahDude`: https://github.com/HeahDude
+.. _`OskarStark`: https://github.com/OskarStark
+.. _`romainneutron`: https://github.com/romainneutron
+.. _`lsmith77`: https://github.com/lsmith77/
+.. _`derrabus`: https://github.com/derrabus/
+.. _`jderusse`: https://github.com/jderusse/
+.. _`tgalopin`: https://github.com/tgalopin/
+.. _`fancyweb`: https://github.com/fancyweb/
+.. _`welcomattic`: https://github.com/welcomattic/
+.. _`kbond`: https://github.com/kbond/
+.. _`gromnan`: https://github.com/gromnan/
+.. _`smnandre`: https://github.com/smnandre/
+.. _`kocal`: https://github.com/kocal/
+.. _`webmamba`: https://github.com/webmamba/
+.. _`hypemc`: https://github.com/hypemc/
+.. _`mtarld`: https://github.com/mtarld/
+.. _`spomky`: https://github.com/spomky/
+.. _`alexandre-daubois`: https://github.com/alexandre-daubois/
+.. _`tucksaun`: https://github.com/tucksaun/
+.. _`the releases page`: https://symfony.com/releases
diff --git a/contributing/diversity/further_reading.rst b/contributing/diversity/further_reading.rst
new file mode 100644
index 00000000000..8bb07c39c97
--- /dev/null
+++ b/contributing/diversity/further_reading.rst
@@ -0,0 +1,56 @@
+Further Reading / Viewing
+=========================
+
+This is a non-exhaustive list of further reading on the topic of diversity.
+
+Diversity in Open Source
+------------------------
+
+`Sage Sharp - What makes a good community? `_
+`Ashe Dryden - The Ethics of Unpaid Labor and the OSS Community `_
+`Model View Culture - The Dehumanizing Myth of the Meritocracy `_
+`Annalee - How “Good Intent” Undermines Diversity and Inclusion `_
+`Karolina Szczur - Building Inclusive Communities `_
+
+Code of Conduct
+---------------
+
+`Karolina Szczur - When a Code of Conduct becomes harmful `_
+`Ashe Dryden - Codes of Conduct 101 + FAQ `_
+`Phil Sturgeon - Codes of Conduct: Maybe They're Not So Bad? `_
+
+Inclusive language
+------------------
+
+`Jenée Desmond-Harris - Why I’m finally convinced it's time to stop saying "you guys" `_
+`inclusive language presentations `_
+
+Other talks and Blog Posts
+--------------------------
+
+`Lena Reinhard – A Talk About Nothing `_
+`Lena Reinhard - A Talk about Everything `_
+`Sage Sharp - SCALE: Improving Diversity with Maslow’s hierarchy `_
+`UCSF - Unconscious Bias