diff --git a/.doctor-rst.yaml b/.doctor-rst.yaml
new file mode 100644
index 00000000000..c4d6b09029a
--- /dev/null
+++ b/.doctor-rst.yaml
@@ -0,0 +1,82 @@
+rules:
+ no_inheritdoc: ~
+ avoid_repetetive_words: ~
+ blank_line_after_directive: ~
+ short_array_syntax: ~
+ no_app_console: ~
+ typo: ~
+ replacement: ~
+ composer_dev_option_not_at_the_end: ~
+ yarn_dev_option_at_the_end: ~
+ versionadded_directive_should_have_version: ~
+ deprecated_directive_should_have_version: ~
+ no_composer_req: ~
+ no_php_open_tag_in_code_block_php_directive: ~
+ no_blank_line_after_filepath_in_php_code_block: ~
+ no_blank_line_after_filepath_in_yaml_code_block: ~
+ no_blank_line_after_filepath_in_xml_code_block: ~
+ no_blank_line_after_filepath_in_twig_code_block: ~
+ php_prefix_before_bin_console: ~
+ use_deprecated_directive_instead_of_versionadded: ~
+ no_space_before_self_xml_closing_tag: ~
+ no_explicit_use_of_code_block_php: ~
+ ensure_order_of_code_blocks_in_configuration_block: ~
+ american_english: ~
+ valid_use_statements: ~
+ lowercase_as_in_use_statements: ~
+ ordered_use_statements: ~
+ no_namespace_after_use_statements: ~
+ correct_code_block_directive_based_on_the_content: ~
+ max_blank_lines:
+ max: 2
+ replace_code_block_types: ~
+ use_https_xsd_urls: ~
+ blank_line_before_directive: ~
+ extension_xlf_instead_of_xliff: ~
+ valid_inline_highlighted_namespaces: ~
+ indention: ~
+ unused_links: ~
+ yaml_instead_of_yml_suffix: ~
+ extend_abstract_controller: ~
+# no_app_bundle: ~
+
+ # 4.x
+ versionadded_directive_major_version:
+ major_version: 4
+
+ versionadded_directive_min_version:
+ min_version: '4.0'
+
+ deprecated_directive_major_version:
+ major_version: 4
+
+ deprecated_directive_min_version:
+ min_version: '4.0'
+
+# do not report as violation
+whitelist:
+ regex:
+ - '/FOSUserBundle(.*)\.yml/'
+ - '/``.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'
+ - 'code in production without a proxy, it becomes trivially easy to abuse your'
+ - '.. _`EasyDeployBundle`: https://github.com/EasyCorp/easy-deploy-bundle'
+ - 'The bin/console Command'
+ - '# username is your full Gmail or Google Apps email address'
+ - '.. _`LDAP injection`: http://projects.webappsec.org/w/page/13246947/LDAP%20Injection'
+ - '.. versionadded:: 0.21.0' # Encore
+ - '.. versionadded:: 2.4.0' # SwiftMailer
+ - '.. versionadded:: 1.26' # Twig
+ - '.. versionadded:: 1.30' # Twig
+ - '.. versionadded:: 1.2' # MakerBundle
+ - '.. versionadded:: 1.11' # MakerBundle
+ - '.. versionadded:: 1.3' # MakerBundle
+ - '.. versionadded:: 1.8' # MakerBundle
+ - '0 => 123' # assertion for var_dumper - components/var_dumper.rst
+ - '1 => "foo"' # assertion for var_dumper - components/var_dumper.rst
+ - '$var .= "Because of this `\xE9` octet (\\xE9),\n";'
+ - "`Deploying Symfony 4 Apps on Heroku`_."
+ - ".. _`Deploying Symfony 4 Apps on Heroku`: https://devcenter.heroku.com/articles/deploying-symfony4"
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 00000000000..d917bbee7ac
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,27 @@
+# Console
+/console* @chalasr
+/components/console* @chalasr
+
+# Form
+/forms.rst @xabbuh
+/components/form* @xabbuh
+/reference/forms* @xabbuh
+
+# PropertyInfo
+/components/property_info* @dunglas
+
+# Security
+/security* @chalasr
+/components/security* @chalasr
+
+# Validator
+/validation/*
+/components/validator* @xabbuh
+/reference/constraints* @xabbuh
+
+# Workflow
+/workflow* @lyrixx
+/components/workflow* @lyrixx
+
+# Yaml
+/components/yaml* @xabbuh
diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml
new file mode 100644
index 00000000000..ada570e342c
--- /dev/null
+++ b/.github/workflows/lint.yaml
@@ -0,0 +1,12 @@
+on: [push, pull_request]
+name: Lint
+jobs:
+ doctor-rst:
+ name: DOCtor-RST
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@master
+ - name: DOCtor-RST
+ uses: docker://oskarstark/doctor-rst
+ with:
+ args: --short
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 00000000000..d211dd419d0
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,83 @@
+Code of Conduct
+===============
+
+Our Pledge
+----------
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnic origin, gender identity and expression, level of
+experience, education, socio-economic status, nationality, personal appearance,
+religion, or sexual identity and orientation.
+
+Our Standards
+-------------
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+Our Responsibilities
+--------------------
+
+[CoC Active Response Ensurers, or CARE][1], are responsible for clarifying the
+standards of acceptable behavior and are expected to take appropriate and fair
+corrective action in response to any instances of unacceptable behavior.
+
+CARE team members have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, or to ban temporarily or permanently any
+contributor for other behaviors that they deem inappropriate, threatening,
+offensive, or harmful.
+
+Scope
+-----
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by CARE team members.
+
+Enforcement
+-----------
+
+Instances of abusive, harassing, or otherwise unacceptable behavior
+[may be reported][2] by contacting the [CARE team members][1].
+All complaints will be reviewed and investigated and will result in a response
+that is deemed necessary and appropriate to the circumstances. The CARE team is
+obligated to maintain confidentiality with regard to the reporter of an
+incident. Further details of specific enforcement policies may be posted
+separately.
+
+CARE team members who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by the
+[core team][3].
+
+Attribution
+-----------
+
+This Code of Conduct is adapted from the [Contributor Covenant version 1.4][4].
+
+[1]: https://symfony.com/doc/current/contributing/code_of_conduct/care_team.html
+[2]: https://symfony.com/doc/current/contributing/code_of_conduct/reporting_guidelines.html
+[3]: https://symfony.com/doc/current/contributing/code/core_team.html
+[4]: https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 00000000000..01524e6ec84
--- /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
index 218c3354128..c1c1000be1a 100644
--- a/README.markdown
+++ b/README.markdown
@@ -6,15 +6,15 @@ This documentation is rendered online at https://symfony.com/doc/current/
Contributing
------------
->**Note**
->Unless you're documenting a feature that was introduced *after* Symfony 3.4
->(e.g. in Symfony 4.2), all pull requests must be based off of the **3.4** branch,
->**not** the master or older branches.
-
We love contributors! For more information on how you can contribute to the
Symfony documentation, please read
[Contributing to the Documentation](https://symfony.com/doc/current/contributing/documentation/overview.html)
+> **Note**
+> Unless you're documenting a feature that was introduced *after* Symfony 3.4
+> (e.g. in Symfony 4.2), all pull requests must be based off of the **3.4** branch,
+> **not** the master or older branches.
+
SymfonyCloud
------------
@@ -29,7 +29,10 @@ You can build the doc locally with these commands:
# build the image...
$ docker build . -t symfony-docs
-# ...and serve it locally on http//:127.0.0.1:8080
+# ...and start the local web server
# (if it's already in use, change the '8080' port by any other port)
$ docker run --rm -p 8080:80 symfony-docs
```
+
+You can now read the docs at http://127.0.0.1:8080 (if you use a virtual
+machine, browse its IP instead of localhost; e.g. `http://192.168.99.100:8080`).
diff --git a/_build/.requirements.txt b/_build/.requirements.txt
index 430bce090b0..53c6c3ddff0 100644
--- a/_build/.requirements.txt
+++ b/_build/.requirements.txt
@@ -2,7 +2,7 @@ alabaster==0.7.10
Babel==2.4.0
docutils==0.13.1
imagesize==0.7.1
-Jinja2==2.9.6
+Jinja2==2.10.1
MarkupSafe==1.0
Pygments==2.2.0
pytz==2017.2
diff --git a/_build/_themes/_exts/symfonycom/sphinx/lexer.py b/_build/_themes/_exts/symfonycom/sphinx/lexer.py
index 4100b66d283..f1e87066236 100644
--- a/_build/_themes/_exts/symfonycom/sphinx/lexer.py
+++ b/_build/_themes/_exts/symfonycom/sphinx/lexer.py
@@ -10,7 +10,7 @@ class TerminalLexer(RegexLexer):
tokens = {
'root': [
('^\$', Generic.Prompt, 'bash-prompt'),
- ('^[^\n>]+>', Generic.Prompt, 'dos-prompt'),
+ ('^>', Generic.Prompt, 'dos-prompt'),
('^#.+$', Comment.Single),
('^.+$', Generic.Output),
],
diff --git a/_build/maintainer_guide.rst b/_build/maintainer_guide.rst
new file mode 100644
index 00000000000..fd3de4f3f3b
--- /dev/null
+++ b/_build/maintainer_guide.rst
@@ -0,0 +1,341 @@
+Symfony Docs Maintainer Guide
+=============================
+
+The `symfony/symfony-docs`_ repository stores the Symfony project documentation
+and is managed by the `Symfony Docs team`_. This article explains in detail some
+of those management tasks, so it's only useful for maintainers and not regular
+readers or Symfony developers.
+
+Reviewing Pull Requests
+-----------------------
+
+All the recommendations of the `Symfony's respectful review comments`_ apply,
+but there are extra things to keep in mind for maintainers:
+
+* Always be nice in all interactions with all contributors.
+* Be extra-patient with new contributors (GitHub shows a special badge for them).
+* Don't assume that contributors know what you think is obvious (e.g. lots of
+ them don't know what to "squash commits" means).
+* Don't use acronyms like IMO, IIRC, etc. or complex English words (most
+ contributors are not native in English and it's intimidating for them).
+* Never engage in a heated discussion. Lock it right away using GitHub.
+* Never discuss non-tech issues. Some PRs are related to our Diversity initiative
+ and some people always try to drag you into politics. Never engage in that and
+ lock the issue/PR as off-topic on GitHub.
+
+Fixing Minor Issues Yourself
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+It's common for new contributors to make lots of minor mistakes in the syntax
+of the RST format used in the docs. It's also common for non English speakers to
+make minor typos.
+
+Even if your intention is good, if you add lots of comments when reviewing a
+first contribution, that person will probably not contribute again. It's better
+to fix the minor errors and typos yourself while merging. If that person
+contributes again, it's OK to mention some of the minor issues to educate them.
+
+.. code-block:: terminal
+
+ $ gh merge 11059
+
+ Working on symfony/symfony-docs (branch master)
+ Merging Pull Request 11059: dmaicher/patch-3
+
+ ...
+
+ # This is important!! Say NO to push the changes now
+ Push the changes now? (Y/n) n
+ Now, push with: git push gh "master" refs/notes/github-comments
+
+ # Now, open your editor and make the needed changes ...
+
+ $ git commit -a
+ # Use "Minor reword", "Minor tweak", etc. as the commit message
+
+ # now run the 'push' command shown above by 'gh' (it's different each time)
+ $ git push gh "master" refs/notes/github-comments
+
+Merging Pull Requests
+---------------------
+
+Technical Requirements
+~~~~~~~~~~~~~~~~~~~~~~
+
+* `Git`_ installed and properly configured.
+* ``gh`` tool fully installed according to its installation instructions
+ (GitHub token configured, Git remote configured, etc.)
+ This is a proprietary CLI tool which only Symfony team members have access to.
+* Some previous Git experience, specially merging pull requests.
+
+First Setup
+~~~~~~~~~~~
+
+First, fork the using the GitHub web
+interface. Then:
+
+.. code-block:: terminal
+
+ # Clone your fork
+ $ git clone https://github.com//symfony-docs.git
+
+ $ cd symfony-docs/
+
+ # Add the original repo as 'upstream' remote
+ $ git remote add upstream https://github.com/symfony/symfony-docs
+
+ # Add the original repo as 'gh' remote (needed for the 'gh' tool)
+ $ git remote add gh https://github.com/symfony/symfony-docs
+
+ # Configure 'gh' in Git as the remote used by the 'gh' tool
+ $ git config gh.remote gh
+
+Merging Process
+~~~~~~~~~~~~~~~
+
+At first, it's common to make mistakes and merge things badly. Don't worry. This
+has happened to all of us and we've always been able to recover from any mistake.
+
+Step 1: Select the right branch to merge
+........................................
+
+PRs must be merged in the oldest maintained branch where they are applicable:
+
+* Here you can find the currently maintained branches: https://symfony.com/roadmap.
+* Typos and old undocumented features are merged into the oldest maintained branch.
+* New features are merged into the branch where they were introduced. This
+ usually means ``master``. And don't forget to check that new feature includes
+ the ``versionadded`` directive.
+
+It's very common for contributors (specially newcomers) to select the wrong
+branch for their PRs, so we must always check if the change should go to the
+proposed branch or not.
+
+If the branch is wrong, there's no need to ask the contributor to rebase. The
+``gh`` tool can do that for us.
+
+Step 2: Merge the pull request
+..............................
+
+Never use GitHub's web interface (or desktop clients) to merge PRs or to solve
+merge conflicts. Always use the ``gh`` tool for anything related to merges.
+
+We require two approval votes from team members before merging a PR, except if
+it's a typo, a small change or clearly an error.
+
+If a PR contains lots of commits, there's no need to ask the contributor to
+squash them. The ``gh`` tool does that automatically. The only exceptions are
+when commits are made by more than one person and when there's a merge commit.
+``gh`` can't squash commits in those cases, so it's better to ask to the
+original contributor.
+
+.. code-block:: terminal
+
+ $ cd symfony-docs/
+
+ # make sure that your local branch is updated
+ $ git checkout 3.4
+ $ git fetch upstream
+ $ git merge upstream/3.4
+
+ # merge any PR passing its GitHub number as argument
+ $ gh merge 11159
+
+ # the gh tool will ask you some questions...
+
+ # push your changes (you can merge several PRs and push once at the end)
+ $ git push origin
+ $ git push upstream
+
+It's common to have to change the branch where a PR is merged. Instead of asking
+the contributors to rebase their PRs, the "gh" tool can change the branch with
+the ``-s`` option:
+
+.. code-block:: terminal
+
+ # e.g. this PR was sent against 'master', but it's merged in '3.4'
+ $ gh merge 11160 -s 3.4
+
+Sometimes, when changing the branch, you may face rebase issues, but they are
+usually simple to fix:
+
+.. code-block:: terminal
+
+ $ gh merge 11160 -s 3.4
+
+ ...
+
+ Unable to rebase the patch for pull/11183
+ The command "'git' 'rebase' '--onto' '3.4' '4.2' 'pull/11160'" failed.
+ Exit Code: 128(Invalid exit argument)
+
+ [...]
+ Auto-merging reference/forms/types/entity.rst
+ CONFLICT (content): Merge conflict in reference/forms/types/entity.rst
+ Patch failed at 0001 Update entity.rst
+ The copy of the patch that failed is found in: .git/rebase-apply/patch
+
+ # Now, fix all the conflicts using your editor
+
+ # Add the modified files and continue the rebase
+ $ git add reference/forms/types/entity.rst ...
+ $ git rebase --continue
+
+ # Lastly, re-run the exact same original command that resulted in a conflict
+ # There's no need to change the branch or do anything else.
+ $ gh merge 11160 -s 3.4
+
+ The previous run had some conflicts. Do you want to resume the merge? (Y/n)
+
+Later in this article you can find a troubleshooting section for the errors that
+you will usually face while merging.
+
+Step 3: Merge it into the other branches
+........................................
+
+If a PR has not been merged in ``master``, you must merge it up into all the
+maintained branches until ``master``. Imagine that you are merging a PR against
+``3.4`` and the maintained branches are ``3.4``, ``4.2`` and ``master``:
+
+.. code-block:: terminal
+
+ $ git fetch upstream
+
+ $ git checkout 3.4
+ $ git merge upstream/3.4
+
+ $ gh merge 11159
+ $ git push origin
+ $ git push upstream
+
+ $ git checkout 4.2
+ $ git merge upstream/4.2
+ $ git merge --log 3.4
+ # here you can face several errors explained later
+ $ git push origin
+ $ git push upstream
+
+ $ git checkout master
+ $ git merge upstream/master
+ $ git merge --log 4.2
+ $ git push origin
+ $ git push upstream
+
+.. tip::
+
+ If you followed the full ``gh`` installation instructions you can remove the
+ ``--log`` option in the above commands.
+
+.. tip::
+
+ When the support of a Symfony branch ends, it's recommended to delete your
+ local branch to avoid merging in it unawarely:
+
+ .. code-block:: terminal
+
+ # if Symfony 3.3 goes out of maintenance today, delete your local branch
+ $ git branch -D 3.3
+
+Troubleshooting
+~~~~~~~~~~~~~~~
+
+Wrong merge of your local branch
+................................
+
+When updating your local branches before merging:
+
+.. code-block:: terminal
+
+ $ git fetch upstream
+ $ git checkout 3.4
+ $ git merge upstream/3.4
+
+It's possible that you merge a wrong upstream branch unawarely. It's usually
+easy to spot because you'll see lots of conflicts:
+
+.. code-block:: terminal
+
+ # DON'T DO THIS! It's a wrong branch merge
+ $ git checkout 3.4
+ $ git merge upstream/4.2
+
+As long as you don't push this wrong merge, there's no problem. Delete your
+local branch and check it out again:
+
+.. code-block:: terminal
+
+ $ git checkout master
+ $ git branch -D 3.4
+ $ git checkout 3.4 upstream/3.4
+
+If you did push the wrong branch merge, ask for help in the documentation
+mergers chat and we'll help solve the problem.
+
+Solving merge conflicts
+.......................
+
+When merging things to upper branches, most of the times you'll see conflicts:
+
+.. code-block:: terminal
+
+ $ git checkout 4.2
+ $ git merge upstream/4.2
+ $ git merge --log 3.4
+
+ Auto-merging security/entity_provider.rst
+ Auto-merging logging/monolog_console.rst
+ Auto-merging form/dynamic_form_modification.rst
+ Auto-merging components/phpunit_bridge.rst
+ CONFLICT (content): Merge conflict in components/phpunit_bridge.rst
+ Automatic merge failed; fix conflicts and then commit the result.
+
+Solve the conflicts with your editor (look for occurrences of ``<<<<``, which is
+the marker used by Git for conflicts) and then do this:
+
+.. code-block:: terminal
+
+ # add all the conflicting files that you fixed
+ $ git add components/phpunit_bridge.rst
+ $ git commit -a
+ $ git push origin
+ $ git push upstream
+
+.. tip::
+
+ When there are lots of conflicts, look for ``<<<<<`` with your editor in all
+ docs before committing the changes. It's common to forget about some of them.
+ If you prefer, you can run this too: ``git grep --cached "<<<<<"``.
+
+Merging deleted files
+.....................
+
+A common cause of conflict when merging PRs into upper branches are files which
+were modified by the PR but no longer exist in newer branches:
+
+.. code-block:: terminal
+
+ $ git checkout 4.2
+ $ git merge upstream/4.2
+ $ git merge --log 3.4
+
+ Auto-merging translation/debug.rst
+ CONFLICT (modify/delete): service_container/scopes.rst deleted in HEAD and
+ modified in 3.4. Version 3.4 of service_container/scopes.rst left in tree.
+ Auto-merging service_container.rst
+
+If the contents of the deleted file were moved to a different file in newer
+branches, redo the changes in the new file. Then, delete the file that Git left
+in the tree as follows:
+
+.. code-block:: terminal
+
+ # delete all the conflicting files that no longer exist in this branch
+ $ git rm service_container/scopes.rst
+ $ git commit -a
+ $ git push origin
+ $ git push upstream
+
+.. _`symfony/symfony-docs`: https://github.com/symfony/symfony-docs
+.. _`Symfony Docs team`: https://github.com/orgs/symfony/teams/team-symfony-docs
+.. _`Symfony's respectful review comments`: https://symfony.com/doc/current/contributing/community/review-comments.html
+.. _`Git`: https://git-scm.com/
diff --git a/_build/redirection_map b/_build/redirection_map
index 5bc5ef3a0df..f9ec4237331 100644
--- a/_build/redirection_map
+++ b/_build/redirection_map
@@ -405,3 +405,44 @@
/console/logging /console
/reference/forms/twig_reference /form/form_customization
/form/rendering /form/form_customization
+/profiler/matchers /profiler
+/profiler/profiling_data /profiler
+/profiler/wdt_follow_ajax /profiler
+/security/entity_provider /security/user_provider
+/session/avoid_session_start /session
+/session/sessions_directory /session
+/frontend/encore/legacy-apps /frontend/encore/legacy-applications
+/configuration/external_parameters /configuration/environment_variables
+/contributing/code/patches /contributing/code/pull_requests
+/workflow/state-machines /workflow/introduction
+/workflow/usage /workflow
+/introduction/from_flat_php_to_symfony2 /introduction/from_flat_php_to_symfony
+/configuration/environment_variables /configuration/env_var_processors
+/configuration/configuration_organization /configuration
+/configuration/environments /configuration
+/configuration/configuration_organization /configuration
+/email/dev_environment /mailer
+/email/spool /mailer
+/email/testing /mailer
+/contributing/community/other /contributing/community
+/profiler/storage /profiler
+/setup/composer /setup
+/security/security_checker /setup
+/service_container/parameters /configuration
+/routing/generate_url_javascript /routing
+/routing/slash_in_parameter /routing
+/routing/scheme /routing
+/routing/optional_placeholders /routing
+/routing/conditions /routing
+/routing/requirements /routing
+/routing/redirect_trailing_slash /routing
+/routing/debug /routing
+/routing/service_container_parameters /routing
+/routing/redirect_in_config /routing
+/routing/external_resources /routing
+/routing/hostname_pattern /routing
+/routing/extra_information /routing
+/console/request_context /routing
+/form/action_method /forms
+/reference/requirements /setup
+ /bundles/inheritance /bundles/override
diff --git a/_images/components/workflow/pull_request.png b/_images/components/workflow/pull_request.png
index 1aa8886728c..692a95345ae 100644
Binary files a/_images/components/workflow/pull_request.png and b/_images/components/workflow/pull_request.png differ
diff --git a/_images/components/workflow/pull_request_puml_styled.png b/_images/components/workflow/pull_request_puml_styled.png
new file mode 100644
index 00000000000..cda9233d731
Binary files /dev/null and b/_images/components/workflow/pull_request_puml_styled.png differ
diff --git a/_images/form/form-custom-type-postal-address-fragment-names.svg b/_images/form/form-custom-type-postal-address-fragment-names.svg
new file mode 100644
index 00000000000..9b6092c9808
--- /dev/null
+++ b/_images/form/form-custom-type-postal-address-fragment-names.svg
@@ -0,0 +1 @@
+
diff --git a/_images/form/form-custom-type-postal-address.svg b/_images/form/form-custom-type-postal-address.svg
new file mode 100644
index 00000000000..ab0fde8af3a
--- /dev/null
+++ b/_images/form/form-custom-type-postal-address.svg
@@ -0,0 +1 @@
+
diff --git a/_images/mercure/chrome.png b/_images/mercure/chrome.png
new file mode 100644
index 00000000000..8ccc55a0a88
Binary files /dev/null and b/_images/mercure/chrome.png differ
diff --git a/_images/mercure/discovery.png b/_images/mercure/discovery.png
new file mode 100644
index 00000000000..0ef38271de6
Binary files /dev/null and b/_images/mercure/discovery.png differ
diff --git a/_images/mercure/schema.png b/_images/mercure/schema.png
new file mode 100644
index 00000000000..4616046e5cc
Binary files /dev/null and b/_images/mercure/schema.png differ
diff --git a/_images/profiler/web-interface.png b/_images/profiler/web-interface.png
new file mode 100644
index 00000000000..2e6c6061892
Binary files /dev/null and b/_images/profiler/web-interface.png differ
diff --git a/_images/sources/README.md b/_images/sources/README.md
new file mode 100644
index 00000000000..9e40e0ac884
--- /dev/null
+++ b/_images/sources/README.md
@@ -0,0 +1,58 @@
+How to Create Symfony Diagrams
+==============================
+
+Creating the Diagram
+--------------------
+
+* Use [Dia][1] as the diagramming application;
+* Use [PT Sans Narrow][2] as the only font in all diagrams (if possible, use
+ only the "normal" weight for all contents);
+* Use 36pt as the base font size;
+* Use 0.10 cm width for lines and shape borders;
+* Use the following color palette:
+ * Text, lines and shape borders: black (#000000)
+ * Shape backgrounds:
+ * Grays: dark (#4d4d4d), medium (#b3b3b3), light (#f2f2f2)
+ * Blue: #b2d4eb
+ * Red: #ecbec0
+ * Green: #b2dec7
+ * Orange: #fddfbb
+
+In case of doubt, check the existing diagrams or ask to the
+[Symfony Documentation Team][3].
+
+Saving and Exporting the Diagram
+--------------------------------
+
+* Save the original diagram in `*.dia` format in `_images/sources/`;
+* Export the diagram to SVG format and save it in `_images/`.
+
+Including the Diagram in the Symfony Docs
+-----------------------------------------
+
+Use the following snippet to embed the diagram in the docs:
+
+```
+.. raw:: html
+
+
+```
+
+Reasoning
+---------
+
+* Dia was chosen because it's one of the few applications which are free, open
+ source and compatible with Linux, macOS and Windows.
+* Font, colors and line widths were chosen to be similar to the diagrams used
+ in the best tech books.
+
+Troubleshooting
+---------------
+
+* On some macOS systems, Dia cannot be executed as a regular application and
+ you must run the following console command instead:
+ `export DISPLAY=:0 && /Applications/Dia.app/Contents/Resources/bin/dia`
+
+[1]: http://dia-installer.de/
+[2]: https://fonts.google.com/specimen/PT+Sans+Narrow
+[3]: https://symfony.com/doc/current/contributing/code/core_team.html
diff --git a/_images/sources/form/form-custom-type-postal-address-fragment-names.dia b/_images/sources/form/form-custom-type-postal-address-fragment-names.dia
new file mode 100644
index 00000000000..aebdadb4170
Binary files /dev/null and b/_images/sources/form/form-custom-type-postal-address-fragment-names.dia differ
diff --git a/_images/sources/form/form-custom-type-postal-address.dia b/_images/sources/form/form-custom-type-postal-address.dia
new file mode 100644
index 00000000000..35a1eaebfd6
Binary files /dev/null and b/_images/sources/form/form-custom-type-postal-address.dia differ
diff --git a/_includes/_annotation_loader_tip.rst.inc b/_includes/_annotation_loader_tip.rst.inc
index ed43b8f51d8..0f4267b07f5 100644
--- a/_includes/_annotation_loader_tip.rst.inc
+++ b/_includes/_annotation_loader_tip.rst.inc
@@ -8,7 +8,7 @@
Annotation classes aren't loaded automatically, so you must load them
using a class loader like this::
- use Composer\Autoload\ClassLoader;
+ use Composer\Autoload\ClassLoader;
use Doctrine\Common\Annotations\AnnotationRegistry;
/** @var ClassLoader $loader */
diff --git a/_includes/service_container/_my_mailer.rst.inc b/_includes/service_container/_my_mailer.rst.inc
index decb1c1a23b..01eafdfe87a 100644
--- a/_includes/service_container/_my_mailer.rst.inc
+++ b/_includes/service_container/_my_mailer.rst.inc
@@ -15,7 +15,7 @@
+ https://symfony.com/schema/dic/services/services-1.0.xsd">
diff --git a/best_practices/business-logic.rst b/best_practices/business-logic.rst
index 417f57c004a..81b724a8fd8 100644
--- a/best_practices/business-logic.rst
+++ b/best_practices/business-logic.rst
@@ -57,8 +57,8 @@ post URL. Let's create a new ``Slugger`` class inside ``src/Utils/``::
}
If you're using the :ref:`default services.yaml configuration `,
-this class is auto-registered as a service whose ID is ``App\Utils\Slugger`` (or
-simply ``Slugger::class`` if the class is already imported in your code).
+this class is auto-registered as a service with the ID ``App\Utils\Slugger`` (to
+prevent against typos, import the class and write ``Slugger::class`` in your code).
.. best-practice::
@@ -109,8 +109,8 @@ distributed among developers, with a slight preference towards YAML.
Both formats have the same performance, so this is ultimately a matter of
personal taste.
-We recommend YAML because it's friendly to newcomers and concise. You can
-use any of the other formats if you prefer another format.
+We recommend YAML because it's friendly to newcomers and concise, but you can
+use whatever format you like.
Using a Persistence Layer
-------------------------
@@ -122,6 +122,15 @@ library or strategy you want for this.
In practice, many Symfony applications rely on the independent
`Doctrine project`_ to define their model using entities and repositories.
+
+Doctrine support is not enabled by default in Symfony. So to use Doctrine
+as shown in the examples below you will need to install :doc:`Doctrine ORM support `
+by executing the following command:
+
+.. code-block:: terminal
+
+ $ composer require symfony/orm-pack
+
Just like with business logic, we recommend storing Doctrine entities in the
``src/Entity/`` directory.
@@ -154,8 +163,8 @@ looking for mapping information::
namespace App\Entity;
- use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
+ use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
@@ -226,7 +235,7 @@ the following command to install the Doctrine fixtures bundle:
.. code-block:: terminal
- $ composer require "doctrine/doctrine-fixtures-bundle"
+ $ composer require --dev doctrine/doctrine-fixtures-bundle
Then, this bundle is enabled automatically, but only for the ``dev`` and
``test`` environments::
diff --git a/best_practices/configuration.rst b/best_practices/configuration.rst
index 33a781edef3..80d4273c6a1 100644
--- a/best_practices/configuration.rst
+++ b/best_practices/configuration.rst
@@ -6,8 +6,6 @@ and security credentials) and different environments (development, production).
That's why Symfony recommends that you split the application configuration into
three parts.
-.. _config-parameters.yml:
-
Infrastructure-Related Configuration
------------------------------------
@@ -18,9 +16,8 @@ application behavior.
.. best-practice::
Define the infrastructure-related configuration options as
- :doc:`environment variables `. During
- development, use the ``.env`` and ``.env.local`` files at the root of your project
- to set these.
+ :ref:`environment variables `. During development, use the
+ ``.env`` and ``.env.local`` files at the root of your project to set these.
By default, Symfony adds these types of options to the ``.env`` file when
installing new dependencies in the app:
@@ -53,8 +50,6 @@ To override these variables with machine-specific or sensitive values, create a
environment variables, exposing sensitive information such as the database
credentials.
-.. _best-practices-canonical-parameters:
-
Canonical Parameters
~~~~~~~~~~~~~~~~~~~~
@@ -88,7 +83,7 @@ layer of configuration that's not needed because you don't need or want these
configuration values to change on each server.
The configuration options defined in the ``services.yaml`` may vary from one
-:doc:`environment ` to another. That's why Symfony
+:ref:`environment ` to another. That's why Symfony
supports defining ``config/services_dev.yaml`` and ``config/services_prod.yaml``
files so that you can override specific values for each environment.
@@ -104,8 +99,8 @@ paginated results.
Use constants to define configuration options that rarely change.
The traditional approach for defining configuration options has caused many
-Symfony apps to include an option like the following, which would be used
-to control the number of posts to display on the blog homepage:
+Symfony applications to include an option like the following, which would be
+used to control the number of posts to display on the blog homepage:
.. code-block:: yaml
@@ -142,8 +137,8 @@ Constants can be used for example in your Twig templates thanks to the
Displaying the {{ constant('NUMBER_OF_ITEMS', post) }} most recent results.
-And Doctrine entities and repositories can now easily access these values,
-whereas they cannot access the container parameters::
+And Doctrine entities and repositories can access these values too, whereas they
+cannot access the container parameters::
namespace App\Repository;
@@ -159,7 +154,7 @@ whereas they cannot access the container parameters::
}
The only notable disadvantage of using constants for this kind of configuration
-values is that you cannot redefine them easily in your tests.
+values is that it's complicated to redefine their values in your tests.
Parameter Naming
----------------
diff --git a/best_practices/controllers.rst b/best_practices/controllers.rst
index 64538972c26..c2e9827b32e 100644
--- a/best_practices/controllers.rst
+++ b/best_practices/controllers.rst
@@ -89,10 +89,10 @@ The ``@Template`` annotation is useful, but also involves some magic. We
don't think its benefit is worth the magic, and so recommend against using
it.
-Most of the time, ``@Template`` is used without any parameters, which makes
-it more difficult to know which template is being rendered. It also makes
-it less obvious to beginners that a controller should always return a Response
-object (unless you're using a view layer).
+Most of the time, ``@Template`` is used without any parameters, which makes it
+more difficult to know which template is being rendered. It also hides the fact
+that a controller should always return a Response object (unless you're using a
+view layer).
What does the Controller look like
----------------------------------
@@ -128,7 +128,7 @@ Fetching Services
If you extend the base ``AbstractController`` class, you can't access services
directly from the container via ``$this->container->get()`` or ``$this->get()``.
-Instead, you must use dependency injection to fetch services: most easily done by
+Instead, you must use dependency injection to fetch services by
:ref:`type-hinting action method arguments `:
.. best-practice::
@@ -218,9 +218,9 @@ flexible::
// ...
}
-The point is this: the ParamConverter shortcut is great for simple situations.
-But you shouldn't forget that querying for entities directly is still very
-easy.
+The point is this: the ParamConverter shortcut is great for most situations.
+However, there is nothing wrong with querying for entities directly if the
+ParamConverter would get complicated.
Pre and Post Hooks
------------------
diff --git a/best_practices/creating-the-project.rst b/best_practices/creating-the-project.rst
index 6ea123c0b6f..2c5ea8a5d55 100644
--- a/best_practices/creating-the-project.rst
+++ b/best_practices/creating-the-project.rst
@@ -39,10 +39,9 @@ create files and execute the following commands:
This command creates a new directory called ``blog`` that contains a fresh new
project based on the most recent stable Symfony version available.
-.. tip::
+.. seealso::
- The technical requirements to run Symfony are simple. If you want to check
- if your system meets those requirements, read :doc:`/reference/requirements`.
+ Check out the :ref:`technical requirements for running Symfony applications `.
Structuring the Application
---------------------------
@@ -76,13 +75,13 @@ Application Bundles
When Symfony 2.0 was released, most developers naturally adopted the symfony
1.x way of dividing applications into logical modules. That's why many Symfony
-apps used bundles to divide their code into logical features: UserBundle,
+applications used bundles to divide their code into logical features: UserBundle,
ProductBundle, InvoiceBundle, etc.
But a bundle is *meant* to be something that can be reused as a stand-alone
piece of software. If UserBundle cannot be used *"as is"* in other Symfony
-apps, then it shouldn't be its own bundle. Moreover, if InvoiceBundle depends on
-ProductBundle, then there's no advantage to having two separate bundles.
+applications, then it shouldn't be its own bundle. Moreover, if InvoiceBundle
+depends on ProductBundle, then there's no advantage to having two separate bundles.
.. best-practice::
diff --git a/best_practices/forms.rst b/best_practices/forms.rst
index 31ffa4d5707..07596e73e54 100644
--- a/best_practices/forms.rst
+++ b/best_practices/forms.rst
@@ -21,11 +21,11 @@ PHP class::
use App\Entity\Post;
use Symfony\Component\Form\AbstractType;
+ use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
+ use Symfony\Component\Form\Extension\Core\Type\EmailType;
+ use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
- use Symfony\Component\Form\Extension\Core\Type\TextareaType;
- use Symfony\Component\Form\Extension\Core\Type\EmailType;
- use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
class PostType extends AbstractType
{
@@ -103,9 +103,9 @@ some developers configure form buttons in the controller::
use App\Entity\Post;
use App\Form\PostType;
- use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
+ use Symfony\Component\HttpFoundation\Request;
class PostController extends AbstractController
{
@@ -134,7 +134,7 @@ view layer:
{{ form_start(form) }}
{{ form_widget(form) }}
-
+
{{ form_end(form) }}
Validation
@@ -177,7 +177,7 @@ One of the simplest ways - which is especially useful during development -
is to render the form tags and use the ``form_widget()`` function to render
all of the fields:
-.. code-block:: html+twig
+.. code-block:: twig
{{ form_start(form, {attr: {class: 'my-form-class'} }) }}
{{ form_widget(form) }}
@@ -212,6 +212,8 @@ Handling a form submit usually follows a similar template::
// render the template
}
+.. _best-practice-handle-form:
+
We recommend that you use a single action for both rendering the form and
handling the form submit. For example, you *could* have a ``new()`` action that
*only* renders the form and a ``create()`` action that *only* processes the form
diff --git a/best_practices/introduction.rst b/best_practices/introduction.rst
index 3bce1615273..7575eb8c408 100644
--- a/best_practices/introduction.rst
+++ b/best_practices/introduction.rst
@@ -19,13 +19,13 @@ What is this Guide About?
-------------------------
This guide aims to fix that by describing the **best practices for developing
-web apps with the Symfony full-stack Framework**. These are best practices that
-fit the philosophy of the framework as envisioned by its original creator
+web applications with the Symfony full-stack Framework**. These are best practices
+that fit the philosophy of the framework as envisioned by its original creator
`Fabien Potencier`_.
.. note::
- **Best practice** is a noun that means *"a well defined procedure that is
+ **Best practice** is a noun that means *"a well-defined procedure that is
known to produce near-optimum results"*. And that's exactly what this
guide aims to provide. Even if you don't agree with every recommendation,
we believe these will help you build great applications with less complexity.
@@ -44,8 +44,8 @@ then **extend and fit to your specific needs**:
We know that old habits die hard and some of you will be shocked by some
of these best practices. But by following these, you'll be able to develop
-apps faster, with less complexity and with the same or even higher quality.
-It's also a moving target that will continue to improve.
+applications faster, with less complexity and with the same or even higher
+quality. It's also a moving target that will continue to improve.
Keep in mind that these are **optional recommendations** that you and your
team may or may not follow to develop Symfony applications. If you want to
diff --git a/best_practices/security.rst b/best_practices/security.rst
index 957d8bedfdd..bd68fb21b4d 100644
--- a/best_practices/security.rst
+++ b/best_practices/security.rst
@@ -18,10 +18,10 @@ primarily under the ``firewalls`` key.
API only), we recommend having only *one* firewall entry with the ``anonymous``
key enabled.
-Most applications only have one authentication system and one set of users.
-For this reason, you only need *one* firewall entry. There are exceptions
-of course, especially if you have separated web and API sections on your
-site. But the point is to keep things simple.
+Most applications only have one authentication system and one set of users. For
+this reason, you only need *one* firewall entry. If you have separated web and
+API sections on your site, you will need more firewall entries. But the point is
+to keep things simple.
Additionally, you should use the ``anonymous`` key under your firewall. If
you need to require users to be logged in for different sections of your
@@ -39,7 +39,7 @@ remain resistant to brute-force search attacks.
.. note::
- :ref:`Argon2i ` is the hashing algorithm as
+ :ref:`Sodium ` is the hashing algorithm as
recommended by industry standards, but this won't be available to you unless
you are using PHP 7.2+ or have the `libsodium`_ extension installed.
``bcrypt`` is sufficient for most applications.
@@ -105,13 +105,10 @@ The @Security Annotation
------------------------
For controlling access on a controller-by-controller basis, use the ``@Security``
-annotation whenever possible. It's easy to read and is placed consistently
-above each action.
+annotation whenever possible. Placing it above each action makes it consistent and readable.
In our application, you need the ``ROLE_ADMIN`` in order to create a new post.
-Using ``@Security``, this looks like:
-
-.. code-block:: php
+Using ``@Security``, this looks like::
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\Routing\Annotation\Route;
@@ -121,7 +118,7 @@ Using ``@Security``, this looks like:
* Displays a form to create a new Post entity.
*
* @Route("/new", name="admin_post_new")
- * @Security("has_role('ROLE_ADMIN')")
+ * @Security("is_granted('ROLE_ADMIN')")
*/
public function new()
{
@@ -153,18 +150,18 @@ Notice that this requires the use of the `ParamConverter`_, which automatically
queries for the ``Post`` object and puts it on the ``$post`` argument. This
is what makes it possible to use the ``post`` variable in the expression.
-This has one major drawback: an expression in an annotation cannot easily
+This has one major drawback: an expression in an annotation cannot
be reused in other parts of the application. Imagine that you want to add
a link in a template that will only be seen by authors. Right now you'll
need to repeat the expression code using Twig syntax:
-.. code-block:: html+jinja
+.. code-block:: html+twig
{% if app.user and app.user.email == post.authorEmail %}
...
{% endif %}
-The easiest solution - if your logic is simple enough - is to add a new method
+A good solution - if your logic is simple enough - can be to add a new method
to the ``Post`` entity that checks if a given user is its author::
// src/Entity/Post.php
@@ -200,7 +197,7 @@ Now you can reuse this method both in the template and in the security expressio
// ...
}
-.. code-block:: html+jinja
+.. code-block:: html+twig
{% if post.isAuthor(app.user) %}
...
@@ -359,7 +356,7 @@ via the even easier shortcut in a controller::
$this->denyAccessUnlessGranted('edit', $post);
// use Symfony\Component\Security\Core\Exception\AccessDeniedException;
- // use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface
+ // use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
//
// ...
//
@@ -379,6 +376,4 @@ via the even easier shortcut in a controller::
Next: :doc:`/best_practices/web-assets`
.. _`ParamConverter`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
-.. _`@Security annotation`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/security.html
-.. _`FOSUserBundle`: https://github.com/FriendsOfSymfony/FOSUserBundle
.. _`libsodium`: https://pecl.php.net/package/libsodium
diff --git a/best_practices/templates.rst b/best_practices/templates.rst
index 79633868ecf..6570690dc56 100644
--- a/best_practices/templates.rst
+++ b/best_practices/templates.rst
@@ -44,7 +44,7 @@ and ``edit_form.html.twig`` instead of ``EditForm.html.twig``).
Use a prefixed underscore for partial templates in template names.
You often want to reuse template code using the ``include`` function to avoid
-redundant code. To determine those partials easily in the filesystem you should
+redundant code. To determine those partials in the filesystem you should
prefix partials and any other template without HTML body or ``extends`` tag
with a single underscore.
@@ -119,4 +119,3 @@ be used as a Twig extension.
Next: :doc:`/best_practices/forms`
.. _`Twig`: https://twig.symfony.com/
-.. _`Parsedown`: http://parsedown.org/
diff --git a/best_practices/tests.rst b/best_practices/tests.rst
index 2c1230cc452..856ec055271 100644
--- a/best_practices/tests.rst
+++ b/best_practices/tests.rst
@@ -106,8 +106,8 @@ The built-in functional testing client is great, but it can't be used to
test any JavaScript behavior on your pages. If you need to test this, consider
using the `Mink`_ library from within PHPUnit.
-Of course, if you have a heavy JavaScript front-end, you should consider using
-pure JavaScript-based testing tools.
+If you have a heavy JavaScript frontend, you should consider using pure
+JavaScript-based testing tools.
Learn More about Functional Tests
---------------------------------
diff --git a/bundles.rst b/bundles.rst
index 2f0fb608c04..cdef74b5c87 100644
--- a/bundles.rst
+++ b/bundles.rst
@@ -18,7 +18,7 @@ SecurityBundle, DebugBundle, etc.) They are also used to add new features in
your application via `third-party bundles`_.
Bundles used in your applications must be enabled per
-:doc:`environment ` in the ``config/bundles.php``
+:ref:`environment ` in the ``config/bundles.php``
file::
// config/bundles.php
@@ -37,7 +37,7 @@ file::
.. tip::
- In a default Symfony application that uses :doc:`Symfony Flex `,
+ In a default Symfony application that uses :ref:`Symfony Flex `,
bundles are enabled/disabled automatically for you when installing/removing
them, so you don't need to look at or edit this ``bundles.php`` file.
diff --git a/bundles/best_practices.rst b/bundles/best_practices.rst
index c7572451c4f..431a267db97 100644
--- a/bundles/best_practices.rst
+++ b/bundles/best_practices.rst
@@ -4,10 +4,10 @@
Best Practices for Reusable Bundles
===================================
-This article is all about how to structure your **reusable bundles** so that
-they're easy to configure and extend. Reusable bundles are those meant to be
-shared privately across many company projects or publicly so any Symfony project
-can install them.
+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.
.. index::
pair: Bundle; Naming conventions
@@ -197,9 +197,9 @@ of Symfony and the latest beta release:
include:
# Minimum supported dependencies with the latest and oldest PHP version
- php: 7.2
- env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" SYMFONY_DEPRECATIONS_HELPER="weak_vendors"
+ env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" SYMFONY_DEPRECATIONS_HELPER="max[self]=0"
- php: 7.0
- env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" SYMFONY_DEPRECATIONS_HELPER="weak_vendors"
+ env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" SYMFONY_DEPRECATIONS_HELPER="max[self]=0"
# Test the latest stable release
- php: 7.0
@@ -246,11 +246,11 @@ Installation
------------
Bundles should set ``"type": "symfony-bundle"`` in their ``composer.json`` file.
-With this, :doc:`Symfony Flex ` will be able to automatically
+With this, :ref:`Symfony Flex ` will be able to automatically
enable your bundle when it's installed.
If your bundle requires any setup (e.g. configuration, new files, changes to
-`.gitignore`, etc), then you should create a `Symfony Flex recipe`_.
+``.gitignore``, etc), then you should create a `Symfony Flex recipe`_.
Documentation
-------------
@@ -306,27 +306,15 @@ following standardized instructions in your ``README.md`` file.
### Step 2: Enable the Bundle
Then, enable the bundle by adding it to the list of registered bundles
- in the `app/AppKernel.php` file of your project:
+ in the `config/bundles.php` file of your project:
```php
- \\(),
- ];
-
- // ...
- }
+ // config/bundles.php
+ return [
// ...
- }
+ \\::class => ['all' => true],
+ ];
```
.. code-block:: rst
@@ -363,29 +351,13 @@ following standardized instructions in your ``README.md`` file.
~~~~~~~~~~~~~~~~~~~~~~~~~
Then, enable the bundle by adding it to the list of registered bundles
- in the ``app/AppKernel.php`` file of your project:
-
- .. code-block:: php
-
- \\(),
- ];
-
- // ...
- }
+ 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
@@ -450,7 +422,7 @@ The end user can provide values in any configuration file:
+ https://symfony.com/schema/dic/services/services-1.0.xsd">
fabien@example.com
@@ -521,7 +493,12 @@ The ``composer.json`` file should include at least the following metadata:
``autoload``
This information is used by Symfony to load the classes of the bundle. It's
- recommended to use the `PSR-4`_ autoload standard.
+ recommended to use the `PSR-4`_ autoload standard: use the namespace as key,
+ and the location of the bundle's main class (relative to ``composer.json``)
+ as value. For example, if the main class is located in the bundle root
+ directory: ``"autoload": { "psr-4": { "SomeVendor\\BlogBundle\\": "" } }``.
+ If the main class is located in the ``src/`` directory of the bundle:
+ ``"autoload": { "psr-4": { "SomeVendor\\BlogBundle\\": "src/" } }``.
In order to make it easier for developers to find your bundle, register it on
`Packagist`_, the official repository for Composer packages.
@@ -554,4 +531,4 @@ Learn more
.. _`choose any license`: https://choosealicense.com/
.. _`valid license identifier`: https://spdx.org/licenses/
.. _`Travis CI`: https://travis-ci.org/
-.. _`Travis Cron`: https://docs.travis-ci.com/user/cron-jobs/
+.. _`Travis cron`: https://docs.travis-ci.com/user/cron-jobs/
diff --git a/bundles/configuration.rst b/bundles/configuration.rst
index a61cbe8a6e1..f8927103123 100644
--- a/bundles/configuration.rst
+++ b/bundles/configuration.rst
@@ -29,12 +29,12 @@ as integration of other related components:
+ https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
-
+
@@ -48,8 +48,8 @@ Using the Bundle Extension
--------------------------
Imagine you are creating a new bundle - AcmeSocialBundle - which provides
-integration with Twitter, etc. To make your bundle easy to use, you want to
-allow users to configure it with some configuration that looks like this:
+integration with Twitter. To make your bundle configurable to the user, you
+can add some configuration that looks like this:
.. configuration-block::
@@ -69,13 +69,13 @@ allow users to configure it with some configuration that looks like this:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:acme-social="http://example.org/schema/dic/acme_social"
xsi:schemaLocation="http://symfony.com/schema/dic/services
- http://symfony.com/schema/dic/services/services-1.0.xsd">
+ https://symfony.com/schema/dic/services/services-1.0.xsd">
-
-
-
+
+
+
-
+
.. code-block:: php
@@ -180,10 +180,9 @@ The ``Configuration`` class to handle the sample configuration looks like::
{
public function getConfigTreeBuilder()
{
- $treeBuilder = new TreeBuilder();
- $rootNode = $treeBuilder->root('acme_social');
+ $treeBuilder = new TreeBuilder('acme_social');
- $rootNode
+ $treeBuilder->getRootNode()
->children()
->arrayNode('twitter')
->children()
@@ -198,6 +197,10 @@ The ``Configuration`` class to handle the sample configuration looks like::
}
}
+.. deprecated:: 4.2
+
+ Not passing the root node name to ``TreeBuilder`` was deprecated in Symfony 4.2.
+
.. seealso::
The ``Configuration`` class can be much more complicated than shown here,
@@ -237,7 +240,7 @@ For example, imagine your bundle has the following example config:
+ https://symfony.com/schema/dic/services/services-1.0.xsd">
diff --git a/cache.rst b/cache.rst
new file mode 100644
index 00000000000..52d615d541d
--- /dev/null
+++ b/cache.rst
@@ -0,0 +1,652 @@
+.. index::
+ single: Cache
+
+Cache
+=====
+
+Using cache is a great way of making your application run quicker. The Symfony cache
+component is shipped with many adapters to different storages. Every adapter is
+developed for high performance.
+
+Basic uses of the cache looks like this::
+
+ use Symfony\Contracts\Cache\ItemInterface;
+
+ // The callable will only be executed on a cache miss.
+ $value = $pool->get('my_cache_key', function (ItemInterface $item) {
+ $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 the Cache Contracts, PSR-6/16 and Doctrine Cache interfaces.
+You can read more about these at the :doc:`component documentation `.
+
+.. versionadded:: 4.2
+
+ The cache contracts were introduced in Symfony 4.2.
+
+.. _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 are using to connect to the storage.
+ Redis and Memcached are example of such adapters. If a DSN is used as the
+ provider then a service is automatically created.
+
+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
+ $container->loadFromExtension('framework', [
+ 'cache' => [
+ 'app' => 'cache.adapter.filesystem',
+ 'system' => 'cache.adapter.system',
+ ],
+ ]);
+
+The Cache component comes with a series of adapters pre-configured:
+
+* :doc:`cache.adapter.apcu `
+* :doc:`cache.adapter.array `
+* :doc:`cache.adapter.doctrine `
+* :doc:`cache.adapter.filesystem `
+* :doc:`cache.adapter.memcached `
+* :doc:`cache.adapter.pdo `
+* :doc:`cache.adapter.psr6 `
+* :doc:`cache.adapter.redis `
+
+Some of these adapters could be configured via shortcuts. Using these shortcuts
+will create pool with service id of ``cache.[type]``
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/cache.yaml
+ framework:
+ cache:
+ directory: '%kernel.cache_dir%/pools' # Only used with cache.adapter.filesystem
+
+ # service: cache.doctrine
+ default_doctrine_provider: 'app.doctrine_cache'
+ # service: cache.psr6
+ default_psr6_provider: 'app.my_psr6_service'
+ # service: cache.redis
+ default_redis_provider: 'redis://localhost'
+ # service: cache.memcached
+ default_memcached_provider: 'memcached://localhost'
+ # service: cache.pdo
+ default_pdo_provider: 'doctrine.dbal.default_connection'
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/cache.php
+ $container->loadFromExtension('framework', [
+ 'cache' => [
+ // Only used with cache.adapter.filesystem
+ 'directory' => '%kernel.cache_dir%/pools',
+
+ // Service: cache.doctrine
+ 'default_doctrine_provider' => 'app.doctrine_cache',
+ // Service: cache.psr6
+ 'default_psr6_provider' => 'app.my_psr6_service',
+ // Service: cache.redis
+ 'default_redis_provider' => 'redis://localhost',
+ // Service: cache.memcached
+ 'default_memcached_provider' => 'memcached://localhost',
+ // Service: cache.pdo
+ 'default_pdo_provider' => 'doctrine.dbal.default_connection',
+ ],
+ ]);
+
+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.array
+
+ # 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
+ $container->loadFromExtension('framework', [
+ 'cache' => [
+ 'default_memcached_provider' => 'memcached://localhost',
+ 'pools' => [
+ 'custom_thing.cache' => [
+ 'adapter' => 'cache.app',
+ ],
+ 'my_cache_pool' => [
+ 'adapter' => 'cache.adapter.array',
+ ],
+ 'acme.cache' => [
+ 'adapter' => 'cache.adapter.memcached',
+ ],
+ 'foobar.cache' => [
+ 'adapter' => 'cache.adapter.memcached',
+ 'provider' => 'memcached://user:password@example.com',
+ ],
+ 'short_cache' => [
+ 'adapter' => 'foobar.cache',
+ 'default_lifetime' => 60,
+ ],
+ ],
+ ],
+ ]);
+
+Each pool manages a set of independent cache keys: keys of 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 compiled container class and a :ref:`configurable seed`
+that defaults to the project directory.
+
+Each custom pool becomes a service where the 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)
+ {
+ // ...
+ }
+
+ // in a service
+ public function __construct(CacheInterface $customThingCache)
+ {
+ // ...
+ }
+
+Custom Provider Options
+-----------------------
+
+Some providers have specific options that can be configured. The
+:doc:`RedisAdapter ` allows you to
+create providers with option ``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
+ $container->loadFromExtension('framework', [
+ 'cache' => [
+ 'pools' => [
+ 'cache.my_redis' => [
+ 'adapter' => 'cache.adapter.redis',
+ 'provider' => 'app.my_custom_redis_provider',
+ ],
+ ],
+ ],
+ ]);
+
+ $container->getDefinition('app.my_custom_redis_provider', \Redis::class)
+ ->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 small 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. The idea is to
+first look at the quick adapter and then move on to slower adapters. In the worst
+case the value needs to be recalculated.
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/cache.yaml
+ framework:
+ cache:
+ pools:
+ my_cache_pool:
+ adapter: cache.adapter.psr6
+ provider: app.my_cache_chain_adapter
+ cache.my_redis:
+ adapter: cache.adapter.redis
+ provider: 'redis://user:password@example.com'
+ cache.apcu:
+ adapter: cache.adapter.apcu
+ cache.array:
+ adapter: cache.adapter.array
+
+
+ services:
+ app.my_cache_chain_adapter:
+ class: Symfony\Component\Cache\Adapter\ChainAdapter
+ arguments:
+ - ['@cache.array', '@cache.apcu', '@cache.my_redis']
+ - 31536000 # One year
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 31536000
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/cache.php
+ $container->loadFromExtension('framework', [
+ 'cache' => [
+ 'pools' => [
+ 'my_cache_pool' => [
+ 'adapter' => 'cache.adapter.psr6',
+ 'provider' => 'app.my_cache_chain_adapter',
+ ],
+ 'cache.my_redis' => [
+ 'adapter' => 'cache.adapter.redis',
+ 'provider' => 'redis://user:password@example.com',
+ ],
+ 'cache.apcu' => [
+ 'adapter' => 'cache.adapter.apcu',
+ ],
+ 'cache.array' => [
+ 'adapter' => 'cache.adapter.array',
+ ],
+ ],
+ ],
+ ]);
+
+ $container->getDefinition('app.my_cache_chain_adapter', \Symfony\Component\Cache\Adapter\ChainAdapter::class)
+ ->addArgument([
+ new Reference('cache.array'),
+ new Reference('cache.apcu'),
+ new Reference('cache.my_redis'),
+ ])
+ ->addArgument(31536000);
+
+.. note::
+
+ In this configuration the ``my_cache_pool`` pool is using the ``cache.adapter.psr6``
+ adapter and the ``app.my_cache_chain_adapter`` service as a provider. That is
+ because ``ChainAdapter`` does not support the ``cache.pool`` tag. So it is decorated
+ with the ``ProxyAdapter``.
+
+
+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 efficient. One way to achieve that is to
+use cache tags. One or more tags could be added to the cache item. All items with
+the same key could be invalidate with one function call::
+
+ use Symfony\Contracts\Cache\ItemInterface;
+
+ $value0 = $pool->get('item_0', function (ItemInterface $item) {
+ $item->tag(['foo', 'bar']);
+
+ return 'debug';
+ });
+
+ $value1 = $pool->get('item_1', function (ItemInterface $item) {
+ $item->tag('foo');
+
+ return 'debug';
+ });
+
+ // Remove all cache keys tagged with "bar"
+ $pool->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
+ tags: true
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/cache.php
+ $container->loadFromExtension('framework', [
+ 'cache' => [
+ 'pools' => [
+ 'my_cache_pool' => [
+ 'adapter' => 'cache.adapter.redis',
+ 'tags' => true,
+ ],
+ ],
+ ],
+ ]);
+
+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
+ $container->loadFromExtension('framework', [
+ 'cache' => [
+ 'pools' => [
+ 'my_cache_pool' => [
+ 'adapter' => 'cache.adapter.redis',
+ 'tags' => 'tag_pool',
+ ],
+ 'tag_pool' => [
+ 'adapter' => '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 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 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
+
+.. versionadded:: 4.3
+
+ The ``cache:pool:list`` command was introduced in Symfony 4.3.
+
+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 caches everywhere:
+
+.. code-block:: terminal
+
+ $ php bin/console cache:pool:clear cache.global_clearer
diff --git a/components/asset.rst b/components/asset.rst
index f911c5cad86..625a3ea9f2f 100644
--- a/components/asset.rst
+++ b/components/asset.rst
@@ -30,7 +30,7 @@ simple. Hardcoding URLs can be a disadvantage because:
is essential for some applications because it allows you to control how
the assets are cached. The Asset component allows you to define different
versioning strategies for each package;
-* **Moving assets location** is cumbersome and error-prone: it requires you to
+* **Moving assets' location** is cumbersome and error-prone: it requires you to
carefully update the URLs of all assets included in all templates. The Asset
component allows to move assets effortlessly just by changing the base path
value associated with the package of assets;
@@ -46,8 +46,6 @@ Installation
$ composer require symfony/asset
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
@@ -225,8 +223,8 @@ If you are also using the :doc:`HttpFoundation `
component in your project (for instance, in a Symfony application), the ``PathPackage``
class can take into account the context of the current request::
- use Symfony\Component\Asset\PathPackage;
use Symfony\Component\Asset\Context\RequestStackContext;
+ use Symfony\Component\Asset\PathPackage;
// ...
$pathPackage = new PathPackage(
@@ -304,7 +302,7 @@ constructor::
// result: http://static2.example.com/images/icon.png?v1
For each asset, one of the URLs will be randomly used. But, the selection
-is deterministic, meaning that each asset will be always served by the same
+is deterministic, meaning that each asset will always be served by the same
domain. This behavior simplifies the management of HTTP cache.
Request Context Aware Assets
@@ -315,8 +313,8 @@ account the context of the current request. In this case, only the request
scheme is considered, in order to select the appropriate base URL (HTTPs or
protocol-relative URLs for HTTPs requests, any base URL for HTTP requests)::
- use Symfony\Component\Asset\UrlPackage;
use Symfony\Component\Asset\Context\RequestStackContext;
+ use Symfony\Component\Asset\UrlPackage;
// ...
$urlPackage = new UrlPackage(
@@ -341,9 +339,9 @@ In the following example, all packages use the same versioning strategy, but
they all have different base paths::
use Symfony\Component\Asset\Package;
+ use Symfony\Component\Asset\Packages;
use Symfony\Component\Asset\PathPackage;
use Symfony\Component\Asset\UrlPackage;
- use Symfony\Component\Asset\Packages;
// ...
$versionStrategy = new StaticVersionStrategy('v1');
@@ -372,8 +370,36 @@ document inside a template::
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
----------
-.. _Packagist: https://packagist.org/packages/symfony/asset
+* :doc:`How to manage CSS and JavaScript assets in Symfony applications `
+* :doc:`WebLink component ` to preload assets using HTTP/2.
+
.. _`Webpack`: https://webpack.js.org/
diff --git a/components/browser_kit.rst b/components/browser_kit.rst
index e9693042650..1fa554a5c0f 100644
--- a/components/browser_kit.rst
+++ b/components/browser_kit.rst
@@ -10,9 +10,10 @@ The BrowserKit Component
.. note::
- The BrowserKit component can only make internal requests to your application.
- If you need to make requests to external sites and applications, consider
- using `Goutte`_, a simple web scraper based on Symfony Components.
+ In Symfony versions prior to 4.3, the BrowserKit component could only make
+ internal requests to your application. Starting from Symfony 4.3, this
+ component can also :ref:`make HTTP requests to any public site `
+ when using it in combination with the :doc:`HttpClient component `.
Installation
------------
@@ -21,8 +22,6 @@ Installation
$ composer require symfony/browser-kit
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Basic Usage
@@ -91,20 +90,25 @@ make AJAX requests::
// the required HTTP_X_REQUESTED_WITH header is added automatically
$crawler = $client->xmlHttpRequest('GET', '/');
-.. versionadded:: 4.1
- The ``xmlHttpRequest()`` method was introduced in Symfony 4.1.
-
Clicking Links
~~~~~~~~~~~~~~
-The ``Crawler`` object is capable of simulating link clicks. First, pass the
-text content of the link to the ``selectLink()`` method, which returns a
-``Link`` object. Then, pass this object to the ``click()`` method, which
-performs the needed HTTP GET request to simulate the link click::
+The ``Client`` object 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);
@@ -112,27 +116,48 @@ performs the needed HTTP GET request to simulate the link click::
Submitting Forms
~~~~~~~~~~~~~~~~
-The ``Crawler`` object is also capable of selecting forms. First, select any of
-the form's buttons with the ``selectButton()`` method. Then, use the ``form()``
-method to select the form which the button belongs to.
-
-After selecting the form, fill in its data and send it using the ``submit()``
-method (which makes the needed HTTP POST request to submit the form contents)::
+The ``Client`` object 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;
- // make a real request to an external site
$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 or
+ $client->submitForm('Log in');
+
+ // the second optional argument lets you override the default form field values
+ $client->submitForm('Log in', [
+ 'login' => 'my_user',
+ 'password' => 'my_pass',
+ // to upload a file, the value must be the absolute file path
+ 'file' => __FILE__,
+ ]);
+
+ // you can override other form options too
+ $client->submitForm(
+ 'Log in',
+ ['login' => 'my_user', 'password' => 'my_pass'],
+ // override the default form HTTP method
+ 'PUT',
+ // override some $_SERVER parameters (e.g. HTTP headers)
+ ['HTTP_ACCEPT_LANGUAGE' => 'es']
+ );
+
+If you need the :class:`Symfony\\Component\\DomCrawler\\Form` object that
+provides access to the form properties (e.g. ``$form->getUri()``,
+``$form->getValues()``, ``$form->getFields()``), use this other method::
+
+ // ...
+
// select the form and fill in some values
$form = $crawler->selectButton('Log in')->form();
$form['login'] = 'symfonyfan';
$form['password'] = 'anypass';
- // To upload a file, the value should be the absolute file path
- $form['file'] = __FILE__;
-
// submit that form
$crawler = $client->submit($form);
@@ -255,6 +280,41 @@ also delete all the cookies::
// reset the client (history and cookies are cleared too)
$client->restart();
+.. _component-browserkit-external-requests:
+
+Making External HTTP Requests
+-----------------------------
+
+So far, all the examples in this article have assumed that you are making
+internal requests to your own application. However, you can run the exact same
+examples when making HTTP requests to external web sites and applications.
+
+First, install and configure the :doc:`HttpClient component `.
+Then, use the :class:`Symfony\\Component\\BrowserKit\\HttpBrowser` to create
+the client that will make the external HTTP requests::
+
+ use Symfony\Component\BrowserKit\HttpBrowser;
+ use Symfony\Component\HttpClient\HttpClient;
+
+ $browser = new HttpBrowser(HttpClient::create());
+
+You can now use any of the methods shown in this article to extract information,
+click links, submit forms, etc. This means that you no longer need to use a
+dedicated web crawler or scraper such as `Goutte`_::
+
+ $browser = new HttpBrowser(HttpClient::create());
+
+ $browser->request('GET', 'https://github.com');
+ $browser->clickLink('Sign in');
+ $browser->submitForm('Sign in', ['login' => '...', 'password' => '...']);
+ $openPullRequests = trim($browser->clickLink('Pull requests')->filter(
+ '.table-list-header-toggle a:nth-child(1)'
+ )->text());
+
+.. versionadded:: 4.3
+
+ The feature to make external HTTP requests was introduced in Symfony 4.3.
+
Learn more
----------
@@ -262,5 +322,4 @@ Learn more
* :doc:`/components/css_selector`
* :doc:`/components/dom_crawler`
-.. _`Packagist`: https://packagist.org/packages/symfony/browser-kit
.. _`Goutte`: https://github.com/FriendsOfPHP/Goutte
diff --git a/components/cache.rst b/components/cache.rst
index c8f9174a1fe..d323113e9bd 100644
--- a/components/cache.rst
+++ b/components/cache.rst
@@ -8,11 +8,17 @@
The Cache Component
===================
- The Cache component provides an extended `PSR-6`_ implementation as well as
- a `PSR-16`_ "Simple Cache" implementation for adding cache to your applications.
- It is designed for performance and resiliency, and ships with ready to use
- adapters for the most common caching backends, including proxies for adapting
- from/to `Doctrine Cache`_.
+ The Cache component provides features covering simple to advanced caching needs.
+ It natively implements `PSR-6`_ and the `Cache Contracts`_ for greatest
+ interoperability. It is designed for performance and resiliency, ships with
+ ready to use adapters for the most common caching backends. It enables tag-based
+ invalidation and cache stampede protection via locking and early expiration.
+
+.. tip::
+
+ The component also contains adapters to convert between PSR-6, PSR-16 and
+ Doctrine caches. See :doc:`/components/cache/psr6_psr16_adapters` and
+ :doc:`/components/cache/adapters/doctrine_adapter`.
Installation
------------
@@ -21,121 +27,128 @@ Installation
$ composer require symfony/cache
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
-Cache (PSR-6) Versus Simple Cache (PSR-16)
-------------------------------------------
+Cache Contracts versus PSR-6
+----------------------------
This component includes *two* different approaches to caching:
:ref:`PSR-6 Caching `:
- A fully-featured cache system, which includes cache "pools", more advanced
- cache "items", and :ref:`cache tagging for invalidation `.
-
-:ref:`PSR-16 Simple Caching `:
- A simple way to store, fetch and remove items from a cache.
+ A generic cache system, which involves cache "pools" and cache "items".
-Both methods support the *same* cache adapters and will give you very similar performance.
+:ref:`Cache Contracts `:
+ A simpler yet more powerful way to cache values based on recomputation callbacks.
.. tip::
- The component also contains adapters to convert between PSR-6 and PSR-16 caches.
- See :doc:`/components/cache/psr6_psr16_adapters`.
+ Using the Cache Contracts approach is recommended: it requires less
+ code boilerplate and provides cache stampede protection by default.
-.. _cache-component-psr16-caching:
+.. _cache-component-contracts:
-Simple Caching (PSR-16)
------------------------
+Cache Contracts
+---------------
-This part of the component is an implementation of `PSR-16`_, which means that its
-basic API is the same as defined in the standard. First, create a cache object from
-one of the built-in cache classes. For example, to create a filesystem-based cache,
-instantiate :class:`Symfony\\Component\\Cache\\Simple\\FilesystemCache`::
+All adapters support the Cache Contracts. They contain only two methods:
+``get()`` and ``delete()``. There's no ``set()`` method because the ``get()``
+method both gets and sets the cache values.
- use Symfony\Component\Cache\Simple\FilesystemCache;
+The first thing you need is to instantiate a cache adapter. The
+:class:`Symfony\\Component\\Cache\\Adapter\\FilesystemAdapter` is used in this
+example::
- $cache = new FilesystemCache();
+ use Symfony\Component\Cache\Adapter\FilesystemAdapter;
-Now you can create, retrieve, update and delete items using this object::
+ $cache = new FilesystemAdapter();
- // save a new item in the cache
- $cache->set('stats.products_count', 4711);
+Now you can retrieve and delete cached data using this object. The first
+argument of the ``get()`` method is a key, an arbitrary string that you
+associate to the cached value so you can retrieve it later. The second argument
+is a PHP callable which is executed when the key is not found in the cache to
+generate and return the value::
- // or set it with a custom ttl
- // $cache->set('stats.products_count', 4711, 3600);
+ use Symfony\Contracts\Cache\ItemInterface;
- // retrieve the cache item
- if (!$cache->has('stats.products_count')) {
- // ... item does not exists in the cache
- }
+ // The callable will only be executed on a cache miss.
+ $value = $cache->get('my_cache_key', function (ItemInterface $item) {
+ $item->expiresAfter(3600);
- // retrieve the value stored by the item
- $productsCount = $cache->get('stats.products_count');
+ // ... do some HTTP request or heavy computations
+ $computedValue = 'foobar';
- // or specify a default value, if the key doesn't exist
- // $productsCount = $cache->get('stats.products_count', 100);
+ return $computedValue;
+ });
- // remove the cache key
- $cache->delete('stats.products_count');
+ echo $value; // 'foobar'
- // clear *all* cache keys
- $cache->clear();
+ // ... and to remove the cache key
+ $cache->delete('my_cache_key');
-You can also work with multiple items at once::
+.. note::
- $cache->setMultiple([
- 'stats.products_count' => 4711,
- 'stats.users_count' => 1356,
- ]);
+ Use cache tags to delete more than one key at the time. Read more at
+ :doc:`/components/cache/cache_invalidation`.
- $stats = $cache->getMultiple([
- 'stats.products_count',
- 'stats.users_count',
- ]);
+The Cache Contracts also comes with built in `Stampede prevention`_. This will
+remove CPU spikes at the moments when the cache is cold. If an example application
+spends 5 seconds to compute data that is cached for 1 hour and this data is accessed
+10 times every second, this means that you mostly have cache hits and everything
+is fine. But after 1 hour, we get 10 new requests to a cold cache. So the data
+is computed again. The next second the same thing happens. So the data is computed
+about 50 times before the cache is warm again. This is where you need stampede
+prevention
- $cache->deleteMultiple([
- 'stats.products_count',
- 'stats.users_count',
- ]);
+The first solution is to use locking: only allow one PHP process (on a per-host basis)
+to compute a specific key at a time. Locking is built-in by default, so
+you don't need to do anything beyond leveraging the Cache Contracts.
-Available Simple Cache (PSR-16) Classes
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+The second solution is also built-in when using the Cache Contracts: instead of
+waiting for the full delay before expiring a value, recompute it ahead of its
+expiration date. The `Probabilistic early expiration`_ algorithm randomly fakes a
+cache miss for one user while others are still served the cached value. You can
+control its behavior with the third optional parameter of
+:method:`Symfony\\Contracts\\Cache\\CacheInterface::get`,
+which is a float value called "beta".
+
+By default the beta is ``1.0`` and higher values mean earlier recompute. Set it
+to ``0`` to disable early recompute and set it to ``INF`` to force an immediate
+recompute::
+
+ use Symfony\Contracts\Cache\ItemInterface;
+
+ $beta = 1.0;
+ $value = $cache->get('my_cache_key', function (ItemInterface $item) {
+ $item->expiresAfter(3600);
+ $item->tag(['tag_0', 'tag_1']);
+
+ return '...';
+ }, $beta);
+
+Available Cache Adapters
+~~~~~~~~~~~~~~~~~~~~~~~~
The following cache adapters are available:
-.. tip::
+.. toctree::
+ :glob:
+ :maxdepth: 1
+
+ cache/adapters/*
- To find out more about each of these classes, you can read the
- :doc:`PSR-6 Cache Pool ` page. These "Simple"
- (PSR-16) cache classes aren't identical to the PSR-6 Adapters on that page, but
- each share constructor arguments and use-cases.
-
-* :class:`Symfony\\Component\\Cache\\Simple\\ApcuCache`
-* :class:`Symfony\\Component\\Cache\\Simple\\ArrayCache`
-* :class:`Symfony\\Component\\Cache\\Simple\\ChainCache`
-* :class:`Symfony\\Component\\Cache\\Simple\\DoctrineCache`
-* :class:`Symfony\\Component\\Cache\\Simple\\FilesystemCache`
-* :class:`Symfony\\Component\\Cache\\Simple\\MemcachedCache`
-* :class:`Symfony\\Component\\Cache\\Simple\\NullCache`
-* :class:`Symfony\\Component\\Cache\\Simple\\PdoCache`
-* :class:`Symfony\\Component\\Cache\\Simple\\PhpArrayCache`
-* :class:`Symfony\\Component\\Cache\\Simple\\PhpFilesCache`
-* :class:`Symfony\\Component\\Cache\\Simple\\RedisCache`
-* :class:`Symfony\\Component\\Cache\\Simple\\TraceableCache`
.. _cache-component-psr6-caching:
-More Advanced Caching (PSR-6)
------------------------------
+Generic Caching (PSR-6)
+-----------------------
-To use the more-advanced, PSR-6 Caching abilities, you'll need to learn its key
+To use the generic PSR-6 Caching abilities, you'll need to learn its key
concepts:
**Item**
A single unit of information stored as a key/value pair, where the key is
the unique identifier of the information and the value is its contents;
+ see the :doc:`/components/cache/cache_items` article for more details.
**Pool**
A logical repository of cache items. All cache operations (saving items,
looking for items, etc.) are performed through the pool. Applications can
@@ -149,7 +162,7 @@ Basic Usage (PSR-6)
-------------------
This part of the component is an implementation of `PSR-6`_, which means that its
-basic API is the same as defined in the standard. Before starting to cache information,
+basic API is the same as defined in the document. Before starting to cache information,
create the cache pool using any of the built-in adapters. For example, to create
a filesystem-based cache, instantiate :class:`Symfony\\Component\\Cache\\Adapter\\FilesystemAdapter`::
@@ -169,7 +182,7 @@ Now you can create, retrieve, update and delete items using this cache pool::
// retrieve the cache item
$productsCount = $cache->getItem('stats.products_count');
if (!$productsCount->isHit()) {
- // ... item does not exists in the cache
+ // ... item does not exist in the cache
}
// retrieve the value stored by the item
$total = $productsCount->get();
@@ -179,8 +192,8 @@ Now you can create, retrieve, update and delete items using this cache pool::
For a list of all of the supported adapters, see :doc:`/components/cache/cache_pools`.
-Advanced Usage (PSR-6)
-----------------------
+Advanced Usage
+--------------
.. toctree::
:glob:
@@ -189,5 +202,6 @@ Advanced Usage (PSR-6)
cache/*
.. _`PSR-6`: http://www.php-fig.org/psr/psr-6/
-.. _`PSR-16`: http://www.php-fig.org/psr/psr-16/
-.. _Doctrine Cache: https://www.doctrine-project.org/projects/cache.html
+.. _`Cache Contracts`: https://github.com/symfony/contracts/blob/master/Cache/CacheInterface.php
+.. _`Stampede prevention`: https://en.wikipedia.org/wiki/Cache_stampede
+.. _Probabilistic early expiration: https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration
diff --git a/components/cache/adapters/apcu_adapter.rst b/components/cache/adapters/apcu_adapter.rst
index 54507ccfc66..17ecd4058e6 100644
--- a/components/cache/adapters/apcu_adapter.rst
+++ b/components/cache/adapters/apcu_adapter.rst
@@ -1,6 +1,6 @@
.. index::
single: Cache Pool
- single: APC Cache, APCu Cache
+ single: APCu Cache
.. _apcu-adapter:
diff --git a/components/cache/adapters/chain_adapter.rst b/components/cache/adapters/chain_adapter.rst
index c1ad7630565..de7555029d2 100644
--- a/components/cache/adapters/chain_adapter.rst
+++ b/components/cache/adapters/chain_adapter.rst
@@ -15,16 +15,15 @@ given adapters. This exposes a simple and efficient method for creating a layere
The ChainAdapter must be provided an array of adapters and optionally a maximum cache
lifetime as its constructor arguments::
- use Symfony\Component\Cache\Adapter\ApcuAdapter;
-
- $cache = new ChainAdapter([
+ use Symfony\Component\Cache\Adapter\ChainAdapter;
+ $cache = new ChainAdapter(
// The ordered list of adapters used to fetch cached items
array $adapters,
// The max lifetime of items propagated from lower adapters to upper ones
$maxLifetime = 0
- ]);
+ );
.. note::
@@ -59,11 +58,5 @@ incompatible adapters are silently ignored::
new FilesystemAdapter(), // DOES implement PruneableInterface
]);
- // prune will proxy the call to FilesystemAdapter while silently skipping ApcuAdapter
+ // prune will proxy the call to FilesystemAdapter while silently skip ApcuAdapter
$cache->prune();
-
-.. note::
-
- Since Symfony 3.4, this adapter implements :class:`Symfony\\Component\\Cache\\PruneableInterface`,
- allowing for manual :ref:`pruning of expired cache entries ` by
- calling its ``prune()`` method.
diff --git a/components/cache/adapters/doctrine_adapter.rst b/components/cache/adapters/doctrine_adapter.rst
index f8f95126ae9..198ae19338c 100644
--- a/components/cache/adapters/doctrine_adapter.rst
+++ b/components/cache/adapters/doctrine_adapter.rst
@@ -34,4 +34,9 @@ third parameters::
$defaultLifetime = 0
);
+.. tip::
+
+ A :class:`Symfony\\Component\\Cache\\DoctrineProvider` class is also provided by the
+ component to use any PSR6-compatible implementations with Doctrine-compatible classes.
+
.. _`Doctrine Cache`: https://github.com/doctrine/cache
diff --git a/components/cache/adapters/memcached_adapter.rst b/components/cache/adapters/memcached_adapter.rst
index 4e60abd8115..69d7afa48be 100644
--- a/components/cache/adapters/memcached_adapter.rst
+++ b/components/cache/adapters/memcached_adapter.rst
@@ -64,6 +64,16 @@ helper method allows creating and configuring a `Memcached`_ class instance usin
// etc...
]);
+ // a single DSN can define multiple servers using the following syntax:
+ // host[hostname-or-IP:port] (where port is optional). Sockets must include a trailing ':'
+ $client = MemcachedAdapter::createConnection(
+ 'memcached:?host[localhost]&host[localhost:12345]&host[/some/memcached.sock:]=3'
+ );
+
+.. versionadded:: 4.2
+
+ The option to define multiple servers in a single DSN was introduced in Symfony 4.2.
+
The `Data Source Name (DSN)`_ for this adapter must use the following format:
.. code-block:: text
@@ -231,7 +241,7 @@ Available Options
``server_failure_limit`` (type: ``int``, default: ``0``)
Specifies the failure limit for server connection attempts before marking
- the server as "dead". The server will remaining in the server pool unless
+ the server as "dead". The server will remain in the server pool unless
``auto_eject_hosts`` is enabled.
Valid option values include *any positive integer*.
@@ -291,4 +301,3 @@ Available Options
.. _`Memcached server`: https://memcached.org/
.. _`Memcached`: http://php.net/manual/en/class.memcached.php
.. _`Data Source Name (DSN)`: https://en.wikipedia.org/wiki/Data_source_name
-.. _`Domain Name System (DNS)`: https://en.wikipedia.org/wiki/Domain_Name_System
diff --git a/components/cache/adapters/pdo_doctrine_dbal_adapter.rst b/components/cache/adapters/pdo_doctrine_dbal_adapter.rst
index 2ac27a83e24..cc8c43976c5 100644
--- a/components/cache/adapters/pdo_doctrine_dbal_adapter.rst
+++ b/components/cache/adapters/pdo_doctrine_dbal_adapter.rst
@@ -31,6 +31,12 @@ third, and forth parameters::
$options = []
);
+The table where values are stored is created automatically on the first call to
+the :method:`Symfony\\Component\\Cache\\Adapter\\PdoAdapter::save` method.
+You can also create this table explicitly by calling the
+:method:`Symfony\\Component\\Cache\\Adapter\\PdoAdapter::createTable` method in
+your code.
+
.. tip::
When passed a `Data Source Name (DSN)`_ string (instead of a database connection
diff --git a/components/cache/adapters/php_array_cache_adapter.rst b/components/cache/adapters/php_array_cache_adapter.rst
index dfc29f62002..6fc5729705d 100644
--- a/components/cache/adapters/php_array_cache_adapter.rst
+++ b/components/cache/adapters/php_array_cache_adapter.rst
@@ -6,10 +6,11 @@ Php Array Cache Adapter
=======================
This adapter is a high performance cache for static data (e.g. application configuration)
-that is optimized and preloaded into OPcache memory storage::
+that is optimized and preloaded into OPcache memory storage. It is suited for any data that
+is mostly read-only after warmup::
- use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+ use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
// somehow, decide it's time to warm up the cache!
if ($needsWarmup) {
@@ -34,5 +35,4 @@ that is optimized and preloaded into OPcache memory storage::
.. note::
- This adapter requires PHP 7.x and should be used with the php.ini setting
- ``opcache.enable`` on.
+ This adapter requires turning on the ``opcache.enable`` php.ini setting.
diff --git a/components/cache/adapters/php_files_adapter.rst b/components/cache/adapters/php_files_adapter.rst
index 078394aeaf0..c6e78cf52c0 100644
--- a/components/cache/adapters/php_files_adapter.rst
+++ b/components/cache/adapters/php_files_adapter.rst
@@ -29,15 +29,16 @@ file similar to the following::
.. note::
+ This adapter requires turning on the ``opcache.enable`` php.ini setting.
As cache items are included and parsed as native PHP code and due to the way `OPcache`_
handles file includes, this adapter has the potential to be much faster than other
filesystem-based caches.
.. caution::
- If you have configured OPcache to
- :ref:`not check the file timestamps `
- the cached items will not not be invalidated unless you clear OPcache.
+ While it supports updates and because it is using OPcache as a backend, this adapter is
+ better suited for append-mostly needs. Using it in other scenarios might lead to
+ periodical reset of the OPcache memory, potentially leading to degraded performance.
The PhpFilesAdapter can optionally be provided a namespace, default cache lifetime, and cache
directory path as constructor arguments::
diff --git a/components/cache/adapters/proxy_adapter.rst b/components/cache/adapters/proxy_adapter.rst
index 86b24ead50f..85d41d2c6d1 100644
--- a/components/cache/adapters/proxy_adapter.rst
+++ b/components/cache/adapters/proxy_adapter.rst
@@ -9,14 +9,18 @@ This adapter wraps a `PSR-6`_ compliant `cache item pool interface`_. It is used
your application's cache item pool implementation with the Symfony :ref:`Cache Component `
by consuming any implementation of ``Psr\Cache\CacheItemPoolInterface``.
+It can also be used to prefix all keys automatically before storing items in the decorated pool,
+effectively allowing the creation of several namespaced pools out of a single one.
+
This adapter expects a ``Psr\Cache\CacheItemPoolInterface`` instance as its first parameter,
and optionally a namespace and default cache lifetime as its second and third parameters::
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\Adapter\ProxyAdapter;
- $psr6CachePool = \\ create your own cache pool instance that implements the PSR-6
- \\ interface `CacheItemPoolInterface`
+ // create your own cache pool instance that implements
+ // the PSR-6 CacheItemPoolInterface
+ $psr6CachePool = ...
$cache = new ProxyAdapter(
diff --git a/components/cache/adapters/redis_adapter.rst b/components/cache/adapters/redis_adapter.rst
index f85fe2081ea..53b28c58466 100644
--- a/components/cache/adapters/redis_adapter.rst
+++ b/components/cache/adapters/redis_adapter.rst
@@ -7,7 +7,15 @@
Redis Cache Adapter
===================
-This adapter stores the values in-memory using one (or more) `Redis server`_ instances.
+.. seealso::
+
+ This article explains how to configure the Redis adapter when using the
+ Cache as an independent component in any PHP application. Read the
+ :ref:`Symfony Cache configuration `
+ article if you are using it in a Symfony application.
+
+This adapter stores the values in-memory using one (or more) `Redis server`_ instances.
+
Unlike the :ref:`APCu adapter `, and similarly to the
:ref:`Memcached adapter `, it is not limited to the current server's
shared memory; you can store contents independent of your PHP environment. The ability
@@ -53,8 +61,8 @@ helper method allows creating and configuring the Redis client class instance us
'redis://localhost'
);
-The DSN can specify either an IP/host (and an optional port) or a socket path, as well as a user
-and password and a database index.
+The DSN can specify either an IP/host (and an optional port) or a socket path, as well as a
+password and a database index.
.. note::
@@ -62,7 +70,7 @@ and password and a database index.
.. code-block:: text
- redis://[user:pass@][ip|host|socket[:port]][/db-index]
+ redis://[pass@][ip|host|socket[:port]][/db-index]
Below are common examples of valid DSNs showing a combination of available values::
@@ -74,11 +82,38 @@ Below are common examples of valid DSNs showing a combination of available value
// host "my.server.com" and port "6379" and database index "20"
RedisAdapter::createConnection('redis://my.server.com:6379/20');
- // host "localhost" and SASL use "rmf" and pass "abcdef"
- RedisAdapter::createConnection('redis://rmf:abcdef@localhost');
+ // host "localhost", auth "abcdef" and timeout 5 seconds
+ RedisAdapter::createConnection('redis://abcdef@localhost?timeout=5');
+
+ // socket "/var/run/redis.sock" and auth "bad-pass"
+ RedisAdapter::createConnection('redis://bad-pass@/var/run/redis.sock');
+
+ // a single DSN can define multiple servers using the following syntax:
+ // host[hostname-or-IP:port] (where port is optional). Sockets must include a trailing ':'
+ RedisAdapter::createConnection(
+ 'redis:?host[localhost]&host[localhost:6379]&host[/var/run/redis.sock:]&auth=my-password&redis_cluster=1'
+ );
+
+`Redis Sentinel`_, which provides high availability for Redis, is also supported
+when using the Predis library. Use the ``redis_sentinel`` parameter to set the
+name of your service group::
+
+ RedisAdapter::createConnection(
+ 'redis:?host[redis1:26379]&host[redis2:26379]&host[redis3:26379]&redis_sentinel=mymaster'
+ );
- // socket "/var/run/redis.sock" and SASL user "user1" and pass "bad-pass"
- RedisAdapter::createConnection('redis://user1:bad-pass@/var/run/redis.sock');
+.. versionadded:: 4.2
+
+ The option to define multiple servers in a single DSN was introduced in Symfony 4.2.
+
+.. versionadded:: 4.4
+
+ Redis Sentinel support was introduced in Symfony 4.4.
+
+.. note::
+
+ See the :class:`Symfony\\Component\\Cache\\Traits\\RedisTrait` for more options
+ you can pass as DSN parameters.
Configure the Options
---------------------
@@ -96,9 +131,11 @@ array of ``key => value`` pairs representing option names and their respective v
// associative array of configuration options
[
+ 'compression' => true,
'lazy' => false,
'persistent' => 0,
'persistent_id' => null,
+ 'tcp_keepalive' => 0,
'timeout' => 30,
'read_timeout' => 0,
'retry_interval' => 0,
@@ -114,6 +151,10 @@ Available Options
If none is specified, it will return ``\Redis`` if the ``redis`` extension is
available, and ``\Predis\Client`` otherwise.
+``compression`` (type: ``bool``, default: ``true``)
+ Enables or disables compression of items. This requires phpredis v4 or higher with
+ LZF support enabled.
+
``lazy`` (type: ``bool``, default: ``false``)
Enables or disables lazy connections to the backend. It's ``false`` by
default when using this as a stand-alone component and ``true`` by default
@@ -134,6 +175,10 @@ Available Options
Specifies the delay (in milliseconds) between reconnection attempts in case the client
loses connection with the server.
+``tcp_keepalive`` (type: ``int``, default: ``0``)
+ Specifies the `TCP-keepalive`_ timeout (in seconds) of the connection. This
+ requires phpredis v4 or higher and a TCP-keepalive enabled server.
+
``timeout`` (type: ``int``, default: ``30``)
Specifies the time (in seconds) used to connect to a Redis server before the
connection attempt times out.
@@ -150,3 +195,5 @@ Available Options
.. _`RedisCluster`: https://github.com/phpredis/phpredis/blob/master/cluster.markdown#readme
.. _`Predis`: https://packagist.org/packages/predis/predis
.. _`Predis Connection Parameters`: https://github.com/nrk/predis/wiki/Connection-Parameters#list-of-connection-parameters
+.. _`TCP-keepalive`: https://redis.io/topics/clients#tcp-keepalive
+.. _`Redis Sentinel`: https://redis.io/topics/sentinel
diff --git a/components/cache/cache_invalidation.rst b/components/cache/cache_invalidation.rst
index c0b570b21d5..e56153b9ce1 100644
--- a/components/cache/cache_invalidation.rst
+++ b/components/cache/cache_invalidation.rst
@@ -10,10 +10,10 @@ change in the state of your model. The most basic kind of invalidation is direct
items deletion. But when the state of a primary resource has spread across
several cached items, keeping them in sync can be difficult.
-The Symfony Cache component provides two mechanisms to help solving this problem:
+The Symfony Cache component provides two mechanisms to help solve this problem:
* :ref:`Tags-based invalidation ` for managing data dependencies;
-* :ref:`Expiration based invalidation ` for time related dependencies.
+* :ref:`Expiration based invalidation ` for time-related dependencies.
.. _cache-component-tags:
@@ -25,28 +25,27 @@ each cached item. Each tag is a plain string identifier that you can use at any
time to trigger the removal of all items associated with this tag.
To attach tags to cached items, you need to use the
-:method:`Symfony\\Component\\Cache\\CacheItem::tag` method that is implemented by
-cache items, as returned by cache adapters::
+:method:`Symfony\\Contracts\\Cache\\ItemInterface::tag` method that is implemented by
+cache items::
- $item = $cache->getItem('cache_key');
- // ...
- // add one or more tags
- $item->tag('tag_1');
- $item->tag(['tag_2', 'tag_3']);
- $cache->save($item);
+ $item = $cache->get('cache_key', function (ItemInterface $item) {
+ // [...]
+ // add one or more tags
+ $item->tag('tag_1');
+ $item->tag(['tag_2', 'tag_3']);
-If ``$cache`` implements :class:`Symfony\\Component\\Cache\\Adapter\\TagAwareAdapterInterface`,
+ return $cachedValue;
+ });
+
+If ``$cache`` implements :class:`Symfony\\Contracts\\Cache\\TagAwareCacheInterface`,
you can invalidate the cached items by calling
-:method:`Symfony\\Component\\Cache\\Adapter\\TagAwareAdapterInterface::invalidateTags`::
+:method:`Symfony\\Contracts\\Cache\\TagAwareCacheInterface::invalidateTags`::
// invalidate all items related to `tag_1` or `tag_3`
$cache->invalidateTags(['tag_1', 'tag_3']);
// if you know the cache key, you can also delete the item directly
- $cache->deleteItem('cache_key');
-
- // If you don't remember the item key, you can use the getKey() method
- $cache->deleteItem($item->getKey());
+ $cache->delete('cache_key');
Using tags invalidation is very useful when tracking cache keys becomes difficult.
@@ -55,7 +54,7 @@ Tag Aware Adapters
To store tags, you need to wrap a cache adapter with the
:class:`Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter` class or implement
-:class:`Symfony\\Component\\Cache\\Adapter\\TagAwareAdapterInterface` and its only
+:class:`Symfony\\Contracts\\Cache\\TagAwareCacheInterface` and its
:method:`Symfony\\Component\\Cache\\Adapter\\TagAwareAdapterInterface::invalidateTags`
method.
@@ -69,9 +68,9 @@ in the same place. By using two adapters, you can e.g. store some big cached ite
on the filesystem or in the database and keep tags in a Redis database to sync all
your fronts and have very fast invalidation checks::
- use Symfony\Component\Cache\Adapter\TagAwareAdapter;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Adapter\RedisAdapter;
+ use Symfony\Component\Cache\Adapter\TagAwareAdapter;
$cache = new TagAwareAdapter(
// Adapter for cached items
diff --git a/components/cache/cache_items.rst b/components/cache/cache_items.rst
index 9cc126e45de..027bb59f4a9 100644
--- a/components/cache/cache_items.rst
+++ b/components/cache/cache_items.rst
@@ -9,6 +9,7 @@ Cache Items
Cache items are the information units stored in the cache as a key/value pair.
In the Cache component they are represented by the
:class:`Symfony\\Component\\Cache\\CacheItem` class.
+They are used in both the Cache Contracts and the PSR-6 interfaces.
Cache Item Keys and Values
--------------------------
@@ -27,14 +28,22 @@ arrays and objects.
Creating Cache Items
--------------------
-Cache items are created with the ``getItem($key)`` method of the cache pool. The
-argument is the key of the item::
+The only way to create cache items is via cache pools. When using the Cache
+Contracts, they are passed as arguments to the recomputation callback::
+
+ // $cache pool object was created before
+ $productsCount = $cache->get('stats.products_count', function (ItemInterface $item) {
+ // [...]
+ });
+
+When using PSR-6, they are created with the ``getItem($key)`` method of the cache
+pool::
// $cache pool object was created before
$productsCount = $cache->getItem('stats.products_count');
-Then, use the ``Psr\\Cache\\CacheItemInterface::set`` method to set
-the data stored in the cache item::
+Then, use the ``Psr\Cache\CacheItemInterface::set`` method to set the data stored
+in the cache item (this step is done automatically when using the Cache Contracts)::
// storing a simple integer
$productsCount->set(4711);
@@ -58,7 +67,7 @@ corresponding *getter* methods::
Cache Item Expiration
~~~~~~~~~~~~~~~~~~~~~
-By default cache items are stored permanently. In practice, this "permanent
+By default, cache items are stored permanently. In practice, this "permanent
storage" can vary greatly depending on the type of cache being used, as
explained in the :doc:`/components/cache/cache_pools` article.
@@ -67,7 +76,6 @@ lifespan. Consider for example an application which caches the latest news just
for one minute. In those cases, use the ``expiresAfter()`` method to set the
number of seconds to cache the item::
- $latestNews = $cache->getItem('latest_news');
$latestNews->expiresAfter(60); // 60 seconds = 1 minute
// this method also accepts \DateInterval instances
@@ -76,23 +84,22 @@ number of seconds to cache the item::
Cache items define another related method called ``expiresAt()`` to set the
exact date and time when the item will expire::
- $mostPopularNews = $cache->getItem('popular_news');
$mostPopularNews->expiresAt(new \DateTime('tomorrow'));
Cache Item Hits and Misses
--------------------------
Using a cache mechanism is important to improve the application performance, but
-it should not be required to make the application work. In fact, the PSR-6
-standard states that caching errors should not result in application failures.
+it should not be required to make the application work. In fact, the PSR-6 document
+wisely states that caching errors should not result in application failures.
-In practice this means that the ``getItem()`` method always returns an object
-which implements the ``Psr\Cache\CacheItemInterface`` interface, even when the
-cache item doesn't exist. Therefore, you don't have to deal with ``null`` return
+In practice with PSR-6, this means that the ``getItem()`` method always returns an
+object which implements the ``Psr\Cache\CacheItemInterface`` interface, even when
+the cache item doesn't exist. Therefore, you don't have to deal with ``null`` return
values and you can safely store in the cache values such as ``false`` and ``null``.
-In order to decide if the returned object is correct or not, caches use the
-concept of hits and misses:
+In order to decide if the returned object represents a value coming from the storage
+or not, caches use the concept of hits and misses:
* **Cache Hits** occur when the requested item is found in the cache, its value
is not corrupted or invalid and it hasn't expired;
diff --git a/components/cache/cache_pools.rst b/components/cache/cache_pools.rst
index 8917b40ef61..15afe615000 100644
--- a/components/cache/cache_pools.rst
+++ b/components/cache/cache_pools.rst
@@ -1,6 +1,6 @@
.. index::
single: Cache Pool
- single: APC Cache, APCu Cache
+ single: APCu Cache
single: Array Cache
single: Chain Cache
single: Doctrine Cache
@@ -16,7 +16,7 @@ Cache Pools and Supported Adapters
Cache Pools are the logical repositories of cache items. They perform all the
common operations on items, such as saving them or looking for them. Cache pools
-are independent from the actual cache implementation. Therefore, applications
+are independent of the actual cache implementation. Therefore, applications
can keep using the same cache pool even if the underlying cache mechanism
changes from a file system based cache to a Redis or database based cache.
@@ -26,8 +26,9 @@ Creating Cache Pools
--------------------
Cache Pools are created through the **cache adapters**, which are classes that
-implement :class:`Symfony\\Component\\Cache\\Adapter\\AdapterInterface`. This
-component provides several adapters ready to use in your applications.
+implement both :class:`Symfony\\Contracts\\Cache\\CacheInterface` and
+``Psr\Cache\CacheItemPoolInterface``. This component provides several adapters
+ready to use in your applications.
.. toctree::
:glob:
@@ -35,8 +36,53 @@ component provides several adapters ready to use in your applications.
adapters/*
+
+Using the Cache Contracts
+-------------------------
+
+The :class:`Symfony\\Contracts\\Cache\\CacheInterface` allows fetching, storing
+and deleting cache items using only two methods and a callback::
+
+ use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+ use Symfony\Contracts\Cache\ItemInterface;
+
+ $cache = new FilesystemAdapter();
+
+ // The callable will only be executed on a cache miss.
+ $value = $cache->get('my_cache_key', function (ItemInterface $item) {
+ $item->expiresAfter(3600);
+
+ // ... do some HTTP request or heavy computations
+ $computedValue = 'foobar';
+
+ return $computedValue;
+ });
+
+ echo $value; // 'foobar'
+
+ // ... and to remove the cache key
+ $cache->delete('my_cache_key');
+
+Out of the box, using this interface provides stampede protection via locking
+and early expiration. Early expiration can be controlled via the third "beta"
+argument of the :method:`Symfony\\Contracts\\Cache\\CacheInterface::get()` method.
+See the :doc:`/components/cache` article for more information.
+
+Early expiration can be detected inside the callback by calling the
+:method:`Symfony\\Contracts\\Cache\\ItemInterface::isHit()` method: if this
+returns ``true``, it means we are currently recomputing a value ahead of its
+expiration date.
+
+For advanced use cases, the callback can accept a second ``bool &$save``
+argument passed by reference. By setting ``$save`` to ``false`` inside the
+callback, you can instruct the cache pool that the returned value *should not*
+be stored in the backend.
+
+Using PSR-6
+-----------
+
Looking for Cache Items
------------------------
+~~~~~~~~~~~~~~~~~~~~~~~
Cache Pools define three methods to look for cache items. The most common method
is ``getItem($key)``, which returns the cache item identified by the given key::
@@ -66,10 +112,10 @@ returns ``true`` if there is a cache item identified by the given key::
$hasBadges = $cache->hasItem('user_'.$userId.'_badges');
Saving Cache Items
-------------------
+~~~~~~~~~~~~~~~~~~
The most common method to save cache items is
-``Psr\\Cache\\CacheItemPoolInterface::save``, which stores the
+``Psr\Cache\CacheItemPoolInterface::save``, which stores the
item in the cache immediately (it returns ``true`` if the item was saved or
``false`` if some error occurred)::
@@ -80,9 +126,9 @@ item in the cache immediately (it returns ``true`` if the item was saved or
Sometimes you may prefer to not save the objects immediately in order to
increase the application performance. In those cases, use the
-``Psr\\Cache\\CacheItemPoolInterface::saveDeferred`` method to mark cache
+``Psr\Cache\CacheItemPoolInterface::saveDeferred`` method to mark cache
items as "ready to be persisted" and then call to
-``Psr\\Cache\\CacheItemPoolInterface::commit`` method when you are ready
+``Psr\Cache\CacheItemPoolInterface::commit`` method when you are ready
to persist them all::
// ...
@@ -100,17 +146,17 @@ method returns ``true`` when all the pending items are successfully saved or
``false`` otherwise.
Removing Cache Items
---------------------
+~~~~~~~~~~~~~~~~~~~~
Cache Pools include methods to delete a cache item, some of them or all of them.
-The most common is ``Psr\\Cache\\CacheItemPoolInterface::deleteItem``,
+The most common is ``Psr\Cache\CacheItemPoolInterface::deleteItem``,
which deletes the cache item identified by the given key (it returns ``true``
when the item is successfully deleted or doesn't exist and ``false`` otherwise)::
// ...
$isDeleted = $cache->deleteItem('user_'.$userId);
-Use the ``Psr\\Cache\\CacheItemPoolInterface::deleteItems`` method to
+Use the ``Psr\Cache\CacheItemPoolInterface::deleteItems`` method to
delete several cache items simultaneously (it returns ``true`` only if all the
items have been deleted, even when any or some of them don't exist)::
@@ -118,7 +164,7 @@ items have been deleted, even when any or some of them don't exist)::
$areDeleted = $cache->deleteItems(['category1', 'category2']);
Finally, to remove all the cache items stored in the pool, use the
-``Psr\\Cache\\CacheItemPoolInterface::clear`` method (which returns ``true``
+``Psr\Cache\CacheItemPoolInterface::clear`` method (which returns ``true``
when all items are successfully deleted)::
// ...
@@ -151,10 +197,6 @@ when all items are successfully deleted)::
# clears the "cache.validation" and "cache.app" pool
$ php bin/console cache:pool:clear cache.validation cache.app
-.. versionadded:: 4.1
-
- The ``cache:pool:delete`` command was introduced in Symfony 4.1.
-
.. _component-cache-cache-pool-prune:
Pruning Cache Items
@@ -163,7 +205,7 @@ Pruning Cache Items
Some cache pools do not include an automated mechanism for pruning expired cache items.
For example, the :ref:`FilesystemAdapter ` cache
does not remove expired cache items *until an item is explicitly requested and determined to
-be expired*, for example, via a call to ``Psr\\Cache\\CacheItemPoolInterface::getItem``.
+be expired*, for example, via a call to ``Psr\Cache\CacheItemPoolInterface::getItem``.
Under certain workloads, this can cause stale cache entries to persist well past their
expiration, resulting in a sizable consumption of wasted disk or memory space from excess,
expired cache items.
diff --git a/components/cache/psr6_psr16_adapters.rst b/components/cache/psr6_psr16_adapters.rst
index 88f1e7c93e2..3c1e683b278 100644
--- a/components/cache/psr6_psr16_adapters.rst
+++ b/components/cache/psr6_psr16_adapters.rst
@@ -6,7 +6,7 @@
Adapters For Interoperability between PSR-6 and PSR-16 Cache
============================================================
-Sometimes, you may have a Cache object that implements the :ref:`PSR-16 `
+Sometimes, you may have a Cache object that implements the `PSR-16`_
standard, but need to pass it to an object that expects a :ref:`PSR-6 `
cache adapter. Or, you might have the opposite situation. The cache component contains
two classes for bidirectional interoperability between PSR-6 and PSR-16 caches.
@@ -33,21 +33,23 @@ example::
But, you already have a PSR-16 cache object, and you'd like to pass this to the class
instead. No problem! The Cache component provides the
-:class:`Symfony\\Component\\Cache\\Adapter\\SimpleCacheAdapter` class for exactly
+:class:`Symfony\\Component\\Cache\\Adapter\\Psr16Adapter` class for exactly
this use-case::
- use Symfony\Component\Cache\Simple\FilesystemCache;
- use Symfony\Component\Cache\Adapter\SimpleCacheAdapter;
+ use Symfony\Component\Cache\Adapter\Psr16Adapter;
- // the PSR-16 cache object that you want to use
- $psr16Cache = new FilesystemCache();
+ // $psr16Cache is the PSR-16 object that you want to use as a PSR-6 one
// a PSR-6 cache that uses your cache internally!
- $psr6Cache = new SimpleCacheAdapter($psr16Cache);
+ $psr6Cache = new Psr16Adapter($psr16Cache);
// now use this wherever you want
$githubApiClient = new GitHubApiClient($psr6Cache);
+.. versionadded:: 4.3
+
+ The ``Psr16Adapter`` class was introduced in Symfony 4.3.
+
Using a PSR-6 Cache Object as a PSR-16 Cache
--------------------------------------------
@@ -70,17 +72,23 @@ example::
But, you already have a PSR-6 cache pool object, and you'd like to pass this to
the class instead. No problem! The Cache component provides the
-:class:`Symfony\\Component\\Cache\\Simple\\Psr6Cache` class for exactly
+:class:`Symfony\\Component\\Cache\\Psr16Cache` class for exactly
this use-case::
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
- use Symfony\Component\Cache\Simple\Psr6Cache;
+ use Symfony\Component\Cache\Psr16Cache;
// the PSR-6 cache object that you want to use
$psr6Cache = new FilesystemAdapter();
// a PSR-16 cache that uses your cache internally!
- $psr16Cache = new Psr6Cache($psr6Cache);
+ $psr16Cache = new Psr16Cache($psr6Cache);
// now use this wherever you want
$githubApiClient = new GitHubApiClient($psr16Cache);
+
+.. versionadded:: 4.3
+
+ The ``Psr16Cache`` class was introduced in Symfony 4.3.
+
+.. _`PSR-16`: http://www.php-fig.org/psr/psr-16/
diff --git a/components/config.rst b/components/config.rst
index 8134007103d..c41b1a54a81 100644
--- a/components/config.rst
+++ b/components/config.rst
@@ -16,8 +16,6 @@ Installation
$ composer require symfony/config
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Learn More
@@ -31,5 +29,3 @@ Learn More
/bundles/configuration
/bundles/extension
/bundles/prepend_extension
-
-.. _Packagist: https://packagist.org/packages/symfony/config
diff --git a/components/config/definition.rst b/components/config/definition.rst
index e313f9bc019..6cc72276e7c 100644
--- a/components/config/definition.rst
+++ b/components/config/definition.rst
@@ -52,22 +52,26 @@ implements the :class:`Symfony\\Component\\Config\\Definition\\ConfigurationInte
namespace Acme\DatabaseConfiguration;
- use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
+ use Symfony\Component\Config\Definition\ConfigurationInterface;
class DatabaseConfiguration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
- $treeBuilder = new TreeBuilder();
- $rootNode = $treeBuilder->root('database');
+ $treeBuilder = new TreeBuilder('database');
// ... add node definitions to the root of the tree
+ // $treeBuilder->getRootNode()->...
return $treeBuilder;
}
}
+.. deprecated:: 4.2
+
+ Not passing the root node name to ``TreeBuilder`` was deprecated in Symfony 4.2.
+
Adding Node Definitions to the Tree
-----------------------------------
@@ -284,8 +288,8 @@ Or the following XML configuration:
.. code-block:: xml
-
-
+
+
The processed configuration is::
@@ -353,9 +357,9 @@ same YAML configuration shown before or the following XML configuration:
.. code-block:: xml
+ table="symfony" user="root" password="null"/>
+ table="foo" user="root" password="pa$$"/>
In both cases, the processed configuration maintains the ``sf_connection`` and
``default`` keys::
@@ -392,7 +396,7 @@ has a certain value:
(``null``, ``true``, ``false``), provide a replacement value in case
the value is ``*.``
-.. code-block:: php
+The following example shows these methods in practice::
$rootNode
->children()
@@ -472,14 +476,14 @@ In YAML you may have:
.. code-block:: yaml
# This value is only used for the search results page.
- entries_per_page: 25
+ entries_per_page: 25
and in XML:
.. code-block:: xml
-
+
Optional Sections
-----------------
@@ -527,17 +531,16 @@ For all nodes:
Appending Sections
------------------
-If you have a complex configuration to validate then the tree can grow to
+If you have a complex configuration to validate, then the tree can grow to
be large and you may want to split it up into sections. You can do this
by making a section a separate node and then appending it into the main
tree with ``append()``::
public function getConfigTreeBuilder()
{
- $treeBuilder = new TreeBuilder();
- $rootNode = $treeBuilder->root('database');
+ $treeBuilder = new TreeBuilder('database');
- $rootNode
+ $treeBuilder->getRootNode()
->children()
->arrayNode('connection')
->children()
@@ -564,10 +567,9 @@ tree with ``append()``::
public function addParametersNode()
{
- $treeBuilder = new TreeBuilder();
- $node = $treeBuilder->root('parameters');
+ $treeBuilder = new TreeBuilder('parameters');
- $node
+ $node = $treeBuilder->getRootNode()
->isRequired()
->requiresAtLeastOneElement()
->useAttributeAsKey('name')
@@ -700,8 +702,8 @@ and sometimes only:
default
-By default ``connection`` would be an array in the first case and a string
-in the second making it difficult to validate. You can ensure it is always
+By default, ``connection`` would be an array in the first case and a string
+in the second, making it difficult to validate. You can ensure it is always
an array with ``fixXmlConfig()``.
You can further control the normalization process if you need to. For example,
@@ -790,15 +792,11 @@ for the node, instead of the node's original value.
Configuring the Node Path Separator
-----------------------------------
-.. versionadded:: 4.1
- The option to configure the node path separator was introduced in Symfony 4.1.
-
Consider the following config builder example::
- $treeBuilder = new TreeBuilder();
- $rootNode = $treeBuilder->root('database');
+ $treeBuilder = new TreeBuilder('database');
- $rootNode
+ $treeBuilder->getRootNode()
->children()
->arrayNode('connection')
->children()
@@ -840,9 +838,9 @@ any value is not of the expected type, is mandatory and yet undefined, or
could not be validated in some other way, an exception will be thrown.
Otherwise the result is a clean array of configuration values::
- use Symfony\Component\Yaml\Yaml;
- use Symfony\Component\Config\Definition\Processor;
use Acme\DatabaseConfiguration;
+ use Symfony\Component\Config\Definition\Processor;
+ use Symfony\Component\Yaml\Yaml;
$config = Yaml::parse(
file_get_contents(__DIR__.'/src/Matthias/config/config.yaml')
diff --git a/components/config/resources.rst b/components/config/resources.rst
index 5c72a23bf36..3ee380a9828 100644
--- a/components/config/resources.rst
+++ b/components/config/resources.rst
@@ -10,6 +10,8 @@ Loading Resources
:phpfunction:`parse_ini_file` function. Therefore, you can only set
parameters to string values. To set parameters to other data types
(e.g. boolean, integer, etc), the other loaders are recommended.
+
+Loaders populate the application's configuration from different sources like YAML files. The Config component defines the interface for such loaders. The :doc:`Dependency Injection ` and :doc:`Routing ` components come with specialized loaders for different file formats.
Locating Resources
------------------
@@ -40,6 +42,8 @@ defined. Each loader should implement
abstract :class:`Symfony\\Component\\Config\\Loader\\FileLoader` class,
which allows for recursively importing other resources::
+ namespace Acme\Config\Loader;
+
use Symfony\Component\Config\Loader\FileLoader;
use Symfony\Component\Yaml\Yaml;
@@ -81,8 +85,9 @@ When it is asked to load a resource, it delegates this question to the
resolver has found a suitable loader, this loader will be asked to load
the resource::
- use Symfony\Component\Config\Loader\LoaderResolver;
+ use Acme\Config\Loader\YamlUserLoader;
use Symfony\Component\Config\Loader\DelegatingLoader;
+ use Symfony\Component\Config\Loader\LoaderResolver;
$loaderResolver = new LoaderResolver([new YamlUserLoader($fileLocator)]);
$delegatingLoader = new DelegatingLoader($loaderResolver);
diff --git a/components/console.rst b/components/console.rst
index 3c516e3e3ce..6a2abe2366e 100644
--- a/components/console.rst
+++ b/components/console.rst
@@ -19,8 +19,6 @@ Installation
$ composer require symfony/console
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Creating a Console Application
@@ -67,5 +65,3 @@ Learn more
/components/console/*
/components/console/helpers/index
/console/*
-
-.. _Packagist: https://packagist.org/packages/symfony/console
diff --git a/components/console/changing_default_command.rst b/components/console/changing_default_command.rst
index 77c40efacec..6eb9f2b5227 100644
--- a/components/console/changing_default_command.rst
+++ b/components/console/changing_default_command.rst
@@ -16,10 +16,11 @@ name to the ``setDefaultCommand()`` method::
class HelloWorldCommand extends Command
{
+ protected static $defaultName = 'hello:world';
+
protected function configure()
{
- $this->setName('hello:world')
- ->setDescription('Outputs \'Hello World\'');
+ $this->setDescription('Outputs "Hello World"');
}
protected function execute(InputInterface $input, OutputInterface $output)
@@ -31,7 +32,6 @@ name to the ``setDefaultCommand()`` method::
Executing the application and changing the default command::
// application.php
-
use Acme\Console\Command\HelloWorldCommand;
use Symfony\Component\Console\Application;
diff --git a/components/console/console_arguments.rst b/components/console/console_arguments.rst
index d49b34701d7..79f5c6c1f4c 100644
--- a/components/console/console_arguments.rst
+++ b/components/console/console_arguments.rst
@@ -23,10 +23,11 @@ Have a look at the following command that has three options::
class DemoArgsCommand extends Command
{
+ protected static $defaultName = 'demo:args';
+
protected function configure()
{
$this
- ->setName('demo:args')
->setDescription('Describe args behaviors')
->setDefinition(
new InputDefinition([
@@ -39,7 +40,7 @@ Have a look at the following command that has three options::
protected function execute(InputInterface $input, OutputInterface $output)
{
- // ...
+ // ...
}
}
diff --git a/components/console/events.rst b/components/console/events.rst
index c61db7baa78..93f6d1a3791 100644
--- a/components/console/events.rst
+++ b/components/console/events.rst
@@ -33,8 +33,8 @@ Just before executing any command, the ``ConsoleEvents::COMMAND`` event is
dispatched. Listeners receive a
:class:`Symfony\\Component\\Console\\Event\\ConsoleCommandEvent` event::
- use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\ConsoleEvents;
+ use Symfony\Component\Console\Event\ConsoleCommandEvent;
$dispatcher->addListener(ConsoleEvents::COMMAND, function (ConsoleCommandEvent $event) {
// gets the input instance
@@ -62,10 +62,10 @@ method, you can disable a command inside a listener. The application
will then *not* execute the command, but instead will return the code ``113``
(defined in ``ConsoleCommandEvent::RETURN_CODE_DISABLED``). This code is one
of the `reserved exit codes`_ for console commands that conform with the
-C/C++ standard.::
+C/C++ standard::
- use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\ConsoleEvents;
+ use Symfony\Component\Console\Event\ConsoleCommandEvent;
$dispatcher->addListener(ConsoleEvents::COMMAND, function (ConsoleCommandEvent $event) {
// gets the command to be executed
@@ -97,8 +97,8 @@ thrown by the application.
Listeners receive a
:class:`Symfony\\Component\\Console\\Event\\ConsoleErrorEvent` event::
- use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\ConsoleEvents;
+ use Symfony\Component\Console\Event\ConsoleErrorEvent;
$dispatcher->addListener(ConsoleEvents::ERROR, function (ConsoleErrorEvent $event) {
$output = $event->getOutput();
@@ -131,8 +131,8 @@ listener (like sending logs, closing a database connection, sending emails,
Listeners receive a
:class:`Symfony\\Component\\Console\\Event\\ConsoleTerminateEvent` event::
- use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\Console\ConsoleEvents;
+ use Symfony\Component\Console\Event\ConsoleTerminateEvent;
$dispatcher->addListener(ConsoleEvents::TERMINATE, function (ConsoleTerminateEvent $event) {
// gets the output
@@ -154,4 +154,4 @@ Listeners receive a
It is then dispatched just after the ``ConsoleEvents::ERROR`` event.
The exit code received in this case is the exception code.
-.. _`reserved exit codes`: http://www.tldp.org/LDP/abs/html/exitcodes.html
+.. _`reserved exit codes`: https://www.tldp.org/LDP/abs/html/exitcodes.html
diff --git a/components/console/helpers/formatterhelper.rst b/components/console/helpers/formatterhelper.rst
index 853890a00cd..ba3c2743d24 100644
--- a/components/console/helpers/formatterhelper.rst
+++ b/components/console/helpers/formatterhelper.rst
@@ -108,6 +108,7 @@ If you don't want to use suffix at all, pass an empty string::
$truncatedMessage = $formatter->truncate($message, 7, '!!'); // result: This is!!
$truncatedMessage = $formatter->truncate($message, 7, ''); // result: This is
- $truncatedMessage = $formatter->truncate('test', 10));
- /* result: test
- because length of the "test..." string is shorter than 10 */
+
+ $truncatedMessage = $formatter->truncate('test', 10);
+ // result: test
+ // because length of the "test..." string is shorter than 10
diff --git a/components/console/helpers/progressbar.rst b/components/console/helpers/progressbar.rst
index 1c99e067636..51c33738049 100644
--- a/components/console/helpers/progressbar.rst
+++ b/components/console/helpers/progressbar.rst
@@ -64,9 +64,6 @@ value and then call the ``setMaxSteps()`` method to update it as needed::
// a complex task has just been created: increase the progressbar to 200 units
$progressBar->setMaxSteps(200);
-.. versionadded:: 4.1
- The ``setMaxSteps()`` method was introduced in Symfony 4.1.
-
Another solution is to omit the steps argument when creating the
:class:`Symfony\\Component\\Console\\Helper\\ProgressBar` instance::
@@ -98,6 +95,33 @@ that the progress bar display is refreshed with a 100% completion.
:method:`Symfony\\Component\\Console\\Helper\\ProgressBar::display`
to show the progress bar again.
+If the progress information is stored in an iterable variable (such as an array
+or a PHP generator) you can use the
+:method:`Symfony\\Component\\Console\\Helper\\ProgressBar::iterate` method,
+which starts, advances and finishes the progress bar automatically::
+
+ use Symfony\Component\Console\Helper\ProgressBar;
+
+ $progressBar = new ProgressBar($output);
+
+ // $iterable can be for example an array ([1, 2, 3, ...]) or a generator
+ // $iterable = function () { yield 1; yield 2; ... };
+ foreach ($progressBar->iterate($iterable) as $value) {
+ // ... do some work
+ }
+
+If ``$iterable = [1, 2]``, the previous code will output the following:
+
+.. code-block:: console
+
+ 0/2 [>---------------------------] 0%
+ 1/2 [==============>-------------] 50%
+ 2/2 [============================] 100%
+
+.. versionadded:: 4.3
+
+ The ``iterate()`` method was introduced in Symfony 4.3.
+
Customizing the Progress Bar
----------------------------
@@ -348,10 +372,6 @@ of the custom placeholders::
Displaying Multiple Progress Bars
---------------------------------
-.. versionadded:: 4.1
- The feature to display multiple progress bars using output sections was
- introduced in Symfony 4.1.
-
When using :ref:`Console output sections ` it's
possible to display multiple progress bars at the same time and change their
progress independently::
diff --git a/components/console/helpers/questionhelper.rst b/components/console/helpers/questionhelper.rst
index 60651b83e5f..aeee9dbe30b 100644
--- a/components/console/helpers/questionhelper.rst
+++ b/components/console/helpers/questionhelper.rst
@@ -179,6 +179,45 @@ will be autocompleted as the user types::
$bundleName = $helper->ask($input, $output, $question);
}
+In more complex use cases, it may be necessary to generate suggestions on the
+fly, for instance if you wish to autocomplete a file path. In that case, you can
+provide a callback function to dynamically generate suggestions::
+
+ use Symfony\Component\Console\Question\Question;
+
+ // ...
+ public function execute(InputInterface $input, OutputInterface $output)
+ {
+ $helper = $this->getHelper('question');
+
+ // This function is called whenever the input changes and new
+ // suggestions are needed.
+ $callback = function (string $userInput): array {
+ // Strip any characters from the last slash to the end of the string
+ // to keep only the last directory and generate suggestions for it
+ $inputPath = preg_replace('%(/|^)[^/]*$%', '$1', $userInput);
+ $inputPath = '' === $inputPath ? '.' : $inputPath;
+
+ // CAUTION - this example code allows unrestricted access to the
+ // entire filesystem. In real applications, restrict the directories
+ // where files and dirs can be found
+ $foundFilesAndDirs = @scandir($inputPath) ?: [];
+
+ return array_map(function ($dirOrFile) use ($inputPath) {
+ return $inputPath.$dirOrFile;
+ }, $foundFilesAndDirs);
+ };
+
+ $question = new Question('Please provide the full path of a file to parse');
+ $question->setAutocompleterCallback($callback);
+
+ $filePath = $helper->ask($input, $output, $question);
+ }
+
+.. versionadded:: 4.3
+
+ The ``setAutocompleterCallback()`` method was introduced in Symfony 4.3.
+
Hiding the User's Response
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -340,8 +379,8 @@ Testing a Command that Expects Input
If you want to write a unit test for a command which expects some kind of input
from the command line, you need to set the inputs that the command expects::
- use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Helper\HelperSet;
+ use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Tester\CommandTester;
// ...
diff --git a/components/console/helpers/table.rst b/components/console/helpers/table.rst
index 0a50a147c83..bc680cc5ad0 100644
--- a/components/console/helpers/table.rst
+++ b/components/console/helpers/table.rst
@@ -69,7 +69,26 @@ You can add a table separator anywhere in the output by passing an instance of
| 80-902734-1-6 | And Then There Were None | Agatha Christie |
+---------------+--------------------------+------------------+
-By default the width of the columns is calculated automatically based on their
+You can optionally display titles at the top and the bottom of the table::
+
+ // ...
+ $table->setHeaderTitle('Books')
+ $table->setFooterTitle('Page 1/2')
+ $table->render();
+
+.. code-block:: terminal
+
+ +---------------+----------- Books --------+------------------+
+ | ISBN | Title | Author |
+ +---------------+--------------------------+------------------+
+ | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
+ | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
+ +---------------+--------------------------+------------------+
+ | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
+ | 80-902734-1-6 | And Then There Were None | Agatha Christie |
+ +---------------+--------- Page 1/2 -------+------------------+
+
+By default, the width of the columns is calculated automatically based on their
contents. Use the :method:`Symfony\\Component\\Console\\Helper\\Table::setColumnWidths`
method to set the column widths explicitly::
@@ -79,7 +98,19 @@ method to set the column widths explicitly::
In this example, the first column width will be ``10``, the last column width
will be ``30`` and the second column width will be calculated automatically
-because of the ``0`` value. The output of this command will be:
+because of the ``0`` value.
+
+You can also set the width individually for each column with the
+:method:`Symfony\\Component\\Console\\Helper\\Table::setColumnWidth` method.
+Its first argument is the column index (starting from ``0``) and the second
+argument is the column width::
+
+ // ...
+ $table->setColumnWidth(0, 10);
+ $table->setColumnWidth(2, 30);
+ $table->render();
+
+The output of this command will be:
.. code-block:: terminal
@@ -98,16 +129,27 @@ widths. If the contents don't fit, the given column width is increased up to the
longest content length. That's why in the previous example the first column has
a ``13`` character length although the user defined ``10`` as its width.
-You can also set the width individually for each column with the
-:method:`Symfony\\Component\\Console\\Helper\\Table::setColumnWidth` method.
-Its first argument is the column index (starting from ``0``) and the second
-argument is the column width::
+If you prefer to wrap long contents in multiple rows, use the
+:method:`Symfony\\Component\\Console\\Helper\\Table::setColumnMaxWidth` method::
// ...
- $table->setColumnWidth(0, 10);
- $table->setColumnWidth(2, 30);
+ $table->setColumnMaxWidth(0, 5);
+ $table->setColumnMaxWidth(1, 10);
$table->render();
+The output of this command will be:
+
+.. code-block:: terminal
+
+ +-------+------------+--------------------------------+
+ | ISBN | Title | Author |
+ +-------+------------+--------------------------------+
+ | 99921 | Divine Com | Dante Alighieri |
+ | -58-1 | edy | |
+ | 0-7 | | |
+ | (the rest of rows...) |
+ +-------+------------+--------------------------------+
+
The table style can be changed to any built-in styles via
:method:`Symfony\\Component\\Console\\Helper\\Table::setStyle`::
@@ -164,9 +206,6 @@ which outputs:
│ 80-902734-1-6 │ And Then There Were None │ Agatha Christie │
└───────────────┴──────────────────────────┴──────────────────┘
-.. versionadded:: 4.1
- The ``box`` style was introduced in Symfony 4.1.
-
You can also set the style to ``box-double``::
$table->setStyle('box-double');
@@ -185,9 +224,6 @@ which outputs:
║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
╚═══════════════╧══════════════════════════╧══════════════════╝
-.. versionadded:: 4.1
- The ``box-double`` style was introduced in Symfony 4.1.
-
If the built-in styles do not fit your need, define your own::
use Symfony\Component\Console\Helper\TableStyle;
@@ -217,17 +253,6 @@ Here is a full list of things you can customize:
* :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setBorderFormat`
* :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setPadType`
-.. versionadded:: 4.1
- The ``setDefaultCrossingChars`` method was introduced in Symfony 4.1.
- It replaces the deprecated ``setHorizontalBorderChar`` method.
-
- Also, the ``setVerticalBorderChars`` method was introduced. Use this instead
- of the deprecated ``setVerticalBorderChar`` method.
-
- The ``setCrossingChars()`` and ``setDefaultCrossingChar()`` methods are also
- new. Previously you could only use the now deprecated ``setCrossingChar()``
- method.
-
.. tip::
You can also register a style globally::
@@ -246,8 +271,8 @@ Spanning Multiple Columns and Rows
To make a table cell that spans multiple columns you can use a :class:`Symfony\\Component\\Console\\Helper\\TableCell`::
use Symfony\Component\Console\Helper\Table;
- use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Console\Helper\TableCell;
+ use Symfony\Component\Console\Helper\TableSeparator;
$table = new Table($output);
$table
@@ -280,7 +305,7 @@ This results in:
$table->setHeaders([
[new TableCell('Main table title', ['colspan' => 3])],
['ISBN', 'Title', 'Author'],
- ))
+ ])
// ...
This generates:
@@ -333,9 +358,6 @@ you to create any table layout you may wish.
Modifying Rendered Tables
-------------------------
-.. versionadded:: 4.1
- The feature to modify rendered tables was introduced in Symfony 4.1.
-
The ``render()`` method requires passing the entire table contents. However,
sometimes that information is not available beforehand because it's generated
dynamically. In those cases, use the
diff --git a/components/console/logger.rst b/components/console/logger.rst
index d221d4c98eb..8f029e47002 100644
--- a/components/console/logger.rst
+++ b/components/console/logger.rst
@@ -39,15 +39,16 @@ You can rely on the logger to use this dependency inside a command::
use Acme\MyDependency;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
- use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Logger\ConsoleLogger;
+ use Symfony\Component\Console\Output\OutputInterface;
class MyCommand extends Command
{
+ protected static $defaultName = 'my:command';
+
protected function configure()
{
$this
- ->setName('my:command')
->setDescription(
'Use an external dependency requiring a PSR-3 logger'
)
diff --git a/components/console/single_command_tool.rst b/components/console/single_command_tool.rst
index b1fce6b9746..74c554c46ef 100644
--- a/components/console/single_command_tool.rst
+++ b/components/console/single_command_tool.rst
@@ -8,26 +8,26 @@ When building a command line tool, you may not need to provide several commands.
In such case, having to pass the command name each time is tedious. Fortunately,
it is possible to remove this need by declaring a single command application::
- #!/usr/bin/env php
- register('echo')
- ->addArgument('foo', InputArgument::OPTIONAL, 'The directory')
- ->addOption('bar', null, InputOption::VALUE_REQUIRED)
- ->setCode(function(InputInterface $input, OutputInterface $output) {
- // output arguments and options
- })
- ->getApplication()
- ->setDefaultCommand('echo', true) // Single command application
- ->run();
+ #!/usr/bin/env php
+ register('echo')
+ ->addArgument('foo', InputArgument::OPTIONAL, 'The directory')
+ ->addOption('bar', null, InputOption::VALUE_REQUIRED)
+ ->setCode(function(InputInterface $input, OutputInterface $output) {
+ // output arguments and options
+ })
+ ->getApplication()
+ ->setDefaultCommand('echo', true) // Single command application
+ ->run();
The method :method:`Symfony\\Component\\Console\\Application::setDefaultCommand`
accepts a boolean as second parameter. If true, the command ``echo`` will then
@@ -35,17 +35,17 @@ always be used, without having to pass its name.
You can still register a command as usual::
- #!/usr/bin/env php
- add($command);
+ $application->add($command);
- $application->setDefaultCommand($command->getName(), true);
- $application->run();
+ $application->setDefaultCommand($command->getName(), true);
+ $application->run();
diff --git a/components/contracts.rst b/components/contracts.rst
new file mode 100644
index 00000000000..4f84c5f2935
--- /dev/null
+++ b/components/contracts.rst
@@ -0,0 +1,78 @@
+.. index::
+ single: Contracts
+ single: Components; Contracts
+
+The Contracts Component
+=======================
+
+ The Contracts component provides a set of abstractions extracted out of the
+ Symfony components. They can be used to build on semantics that the Symfony
+ components proved useful - and that already have battle-tested implementations.
+
+Installation
+------------
+
+Contracts are provided as separate packages, so you can install only the ones
+your projects really need:
+
+.. code-block:: terminal
+
+ $ composer require symfony/cache-contracts
+ $ composer require symfony/event-dispatcher-contracts
+ $ composer require symfony/http-client-contracts
+ $ composer require symfony/service-contracts
+ $ composer require symfony/translation-contracts
+
+.. include:: /components/require_autoload.rst.inc
+
+Usage
+-----
+
+The abstractions in this package are useful to achieve loose coupling and
+interoperability. By using the provided interfaces as type hints, you are able
+to reuse any implementations that match their contracts. It could be a Symfony
+component, or another package provided by the PHP community at large.
+
+Depending on their semantics, some interfaces can be combined with
+:doc:`autowiring ` to seamlessly inject a service
+in your classes.
+
+Others might be useful as labeling interfaces, to hint about a specific behavior
+that can be enabled when using :ref:`autoconfiguration `
+or manual :doc:`service tagging ` (or any other means
+provided by your framework.)
+
+Design Principles
+-----------------
+
+* Contracts are split by domain, each into their own sub-namespaces;
+* Contracts are small and consistent sets of PHP interfaces, traits, normative
+ docblocks and reference test suites when applicable, ...;
+* Contracts must have a proven implementation to enter this repository;
+* Contracts must be backward compatible with existing Symfony components.
+
+Packages that implement specific contracts should list them in the ``provide``
+section of their ``composer.json`` file, using the ``symfony/*-implementation``
+convention. For example:
+
+.. code-block:: javascript
+
+ {
+ "...": "...",
+ "provide": {
+ "symfony/cache-implementation": "1.0"
+ }
+ }
+
+Frequently Asked Questions
+--------------------------
+
+How Is this Different From PHP-FIG's PSRs?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When applicable, the provided contracts are built on top of `PHP-FIG`_'s PSRs.
+However, PHP-FIG has different goals and different processes. Symfony Contracts
+focuses on providing abstractions that are useful on their own while still
+compatible with implementations provided by Symfony.
+
+.. _`PHP-FIG`: https://www.php-fig.org/
diff --git a/components/css_selector.rst b/components/css_selector.rst
index ca664e07aaf..2c15ef432dd 100644
--- a/components/css_selector.rst
+++ b/components/css_selector.rst
@@ -14,8 +14,6 @@ Installation
$ composer require symfony/css-selector
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
@@ -97,9 +95,7 @@ Several pseudo-classes are not yet supported:
* ``*:first-of-type``, ``*:last-of-type``, ``*:nth-of-type``,
``*:nth-last-of-type``, ``*:only-of-type``. (These work with an element
- name (e.g. ``li:first-of-type``) but not with ``*``.
-
-.. _Packagist: https://packagist.org/packages/symfony/css-selector
+ name (e.g. ``li:first-of-type``) but not with ``*``).
Learn more
----------
diff --git a/components/debug.rst b/components/debug.rst
index 2b8910b56e1..6f116e25af6 100644
--- a/components/debug.rst
+++ b/components/debug.rst
@@ -14,15 +14,13 @@ Installation
$ composer require symfony/debug
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
-----
The Debug component provides several tools to help you debug PHP code.
-Enabling them all can be done by calling the static method ``Debug::enable()``::
+Enable all of them by calling this method::
use Symfony\Component\Debug\Debug;
@@ -83,11 +81,9 @@ throw more helpful exceptions when a class isn't found by the registered
autoloaders. All autoloaders that implement a ``findFile()`` method are replaced
with a ``DebugClassLoader`` wrapper.
-To activate the ``DebugClassLoader``, call its static
+Using the ``DebugClassLoader`` is done by calling its static
:method:`Symfony\\Component\\Debug\\DebugClassLoader::enable` method::
use Symfony\Component\Debug\DebugClassLoader;
DebugClassLoader::enable();
-
-.. _Packagist: https://packagist.org/packages/symfony/debug
diff --git a/components/dependency_injection.rst b/components/dependency_injection.rst
index eb9a85bcc28..af13905456c 100644
--- a/components/dependency_injection.rst
+++ b/components/dependency_injection.rst
@@ -19,8 +19,6 @@ Installation
$ composer require symfony/dependency-injection
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Basic Usage
@@ -200,8 +198,8 @@ files. To do this you also need to install
Loading an XML config file::
- use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
+ use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
$containerBuilder = new ContainerBuilder();
@@ -210,8 +208,8 @@ Loading an XML config file::
Loading a YAML config file::
- use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
+ use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
$containerBuilder = new ContainerBuilder();
@@ -235,8 +233,8 @@ Loading a YAML config file::
If you *do* want to use PHP to create the services then you can move this
into a separate config file and load it in a similar way::
- use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
+ use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
$containerBuilder = new ContainerBuilder();
@@ -268,7 +266,7 @@ config files:
+ xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
@@ -282,7 +280,7 @@ config files:
-
+
@@ -313,4 +311,3 @@ Learn More
/service_container/*
.. _`PSR-11`: http://www.php-fig.org/psr/psr-11/
-.. _Packagist: https://packagist.org/packages/symfony/dependency-injection
diff --git a/components/dependency_injection/_imports-parameters-note.rst.inc b/components/dependency_injection/_imports-parameters-note.rst.inc
index eb68a4dbdcc..92868df1985 100644
--- a/components/dependency_injection/_imports-parameters-note.rst.inc
+++ b/components/dependency_injection/_imports-parameters-note.rst.inc
@@ -19,10 +19,10 @@
+ https://symfony.com/schema/dic/services/services-1.0.xsd">
-
+
diff --git a/components/dependency_injection/compilation.rst b/components/dependency_injection/compilation.rst
index 83fc81d57aa..369de563625 100644
--- a/components/dependency_injection/compilation.rst
+++ b/components/dependency_injection/compilation.rst
@@ -57,10 +57,10 @@ added but are processed when the container's ``compile()`` method is called.
A very simple extension may just load configuration files into the container::
+ use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
- use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
- use Symfony\Component\Config\FileLocator;
+ use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
class AcmeDemoExtension implements ExtensionInterface
{
@@ -113,8 +113,8 @@ If this file is loaded into the configuration then the values in it are
only processed when the container is compiled at which point the Extensions
are loaded::
- use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
+ use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
$containerBuilder = new ContainerBuilder();
@@ -201,7 +201,7 @@ The XML version of the config would then look like this:
+ xsi:schemaLocation="http://www.example.com/symfony/schema/ https://www.example.com/symfony/schema/hello-1.0.xsd">
fooValue
@@ -325,7 +325,7 @@ compilation::
{
public function process(ContainerBuilder $container)
{
- // ... do something during the compilation
+ // ... do something during the compilation
}
// ...
@@ -379,7 +379,7 @@ class implementing the ``CompilerPassInterface``::
{
public function process(ContainerBuilder $container)
{
- // ... do something during the compilation
+ // ... do something during the compilation
}
}
@@ -469,7 +469,7 @@ serves at dumping the compiled container::
}
``ProjectServiceContainer`` is the default name given to the dumped container
-class. However you can change this with the ``class`` option when you
+class. However, you can change this with the ``class`` option when you
dump it::
// ...
diff --git a/components/dom_crawler.rst b/components/dom_crawler.rst
index 5ddaf8ca0a9..855fd707446 100644
--- a/components/dom_crawler.rst
+++ b/components/dom_crawler.rst
@@ -19,8 +19,6 @@ Installation
$ composer require symfony/dom-crawler
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
@@ -36,7 +34,7 @@ The :class:`Symfony\\Component\\DomCrawler\\Crawler` class provides methods
to query and manipulate HTML and XML documents.
An instance of the Crawler represents a set of :phpclass:`DOMElement` objects,
-which are basically nodes that you can traverse easily::
+which are nodes that can be traversed as follows::
use Symfony\Component\DomCrawler\Crawler;
@@ -72,6 +70,17 @@ tree.
isn't meant to dump content, you can see the "fixed" version of your HTML
by :ref:`dumping it `.
+.. note::
+
+ If you need better support for HTML5 contents or want to get rid of the
+ inconsistencies of PHP's DOM extension, install the `html5-php library`_.
+ The DomCrawler component will use it automatically when the content has
+ an HTML5 doctype.
+
+ .. versionadded:: 4.3
+
+ The automatic support of the html5-php library was introduced in Symfony 4.3.
+
Node Filtering
~~~~~~~~~~~~~~
@@ -182,6 +191,10 @@ Get all the child or parent nodes::
$crawler->filter('body')->children();
$crawler->filter('body > p')->parents();
+Get all the direct child nodes matching a CSS selector::
+
+ $crawler->filter('body')->children('p.lorem');
+
.. note::
All the traversal methods return a new :class:`Symfony\\Component\\DomCrawler\\Crawler`
@@ -197,8 +210,16 @@ Access the node name (HTML tag name) of the first node of the current selection
Access the value of the first node of the current selection::
+ // if the node does not exist, calling to text() will result in an exception
$message = $crawler->filterXPath('//body/p')->text();
+ // avoid the exception passing an argument that text() returns when node does not exist
+ $message = $crawler->filterXPath('//body/p')->text('Default text content');
+
+.. versionadded:: 4.3
+
+ The default argument of ``text()`` was introduced in Symfony 4.3.
+
Access the attribute value of the first node of the current selection::
$class = $crawler->filterXPath('//body/p')->attr('class');
@@ -207,12 +228,17 @@ Extract attribute and/or node values from the list of nodes::
$attributes = $crawler
->filterXpath('//body/p')
- ->extract(['_text', 'class'])
+ ->extract(['_name', '_text', 'class'])
;
.. note::
- Special attribute ``_text`` represents a node value.
+ Special attribute ``_text`` represents a node value, while ``_name``
+ represents the element name (the HTML tag name).
+
+ .. versionadded:: 4.3
+
+ The special attribute ``_name`` was introduced in Symfony 4.3.
Call an anonymous function on each node of the list::
@@ -226,21 +252,33 @@ Call an anonymous function on each node of the list::
The anonymous function receives the node (as a Crawler) and the position as arguments.
The result is an array of values returned by the anonymous function calls.
+When using nested crawler, beware that ``filterXPath()`` is evaluated in the
+context of the crawler::
+
+ $crawler->filterXPath('parent')->each(function (Crawler $parentCrawler, $i) {
+ // DON'T DO THIS: direct child can not be found
+ $subCrawler = $parentCrawler->filterXPath('sub-tag/sub-child-tag');
+
+ // DO THIS: specify the parent tag too
+ $subCrawler = $parentCrawler->filterXPath('parent/sub-tag/sub-child-tag');
+ $subCrawler = $parentCrawler->filterXPath('node()/sub-tag/sub-child-tag');
+ });
+
Adding the Content
~~~~~~~~~~~~~~~~~~
The crawler supports multiple ways of adding the content::
- $crawler = new Crawler(' ');
+ $crawler = new Crawler(' ');
- $crawler->addHtmlContent(' ');
- $crawler->addXmlContent(' ');
+ $crawler->addHtmlContent(' ');
+ $crawler->addXmlContent(' ');
- $crawler->addContent(' ');
- $crawler->addContent(' ', 'text/xml');
+ $crawler->addContent(' ');
+ $crawler->addContent(' ', 'text/xml');
- $crawler->add(' ');
- $crawler->add(' ');
+ $crawler->add(' ');
+ $crawler->add(' ');
.. note::
@@ -258,7 +296,7 @@ to interact with native :phpclass:`DOMDocument`, :phpclass:`DOMNodeList`
and :phpclass:`DOMNode` objects::
$domDocument = new \DOMDocument();
- $domDocument->loadXml(' ');
+ $domDocument->loadXml(' ');
$nodeList = $domDocument->getElementsByTagName('node');
$node = $domDocument->getElementsByTagName('node')->item(0);
@@ -289,8 +327,16 @@ and :phpclass:`DOMNode` objects::
Or you can get the HTML of the first node using
:method:`Symfony\\Component\\DomCrawler\\Crawler::html`::
+ // if the node does not exist, calling to html() will result in an exception
$html = $crawler->html();
+ // avoid the exception passing an argument that html() returns when node does not exist
+ $html = $crawler->html('Default HTML content');
+
+ .. versionadded:: 4.3
+
+ The default argument of ``html()`` was introduced in Symfony 4.3.
+
Expression Evaluation
~~~~~~~~~~~~~~~~~~~~~
@@ -316,32 +362,36 @@ This behavior is best illustrated with examples::
$crawler->addHtmlContent($html);
$crawler->filterXPath('//span[contains(@id, "article-")]')->evaluate('substring-after(@id, "-")');
- /* array:3 [
- 0 => "100"
- 1 => "101"
- 2 => "102"
- ]
- */
+ /* Result:
+ [
+ 0 => '100',
+ 1 => '101',
+ 2 => '102',
+ ];
+ */
$crawler->evaluate('substring-after(//span[contains(@id, "article-")]/@id, "-")');
- /* array:1 [
- 0 => "100"
- ]
- */
+ /* Result:
+ [
+ 0 => '100',
+ ]
+ */
$crawler->filterXPath('//span[@class="article"]')->evaluate('count(@id)');
- /* array:3 [
- 0 => 1.0
- 1 => 1.0
- 2 => 1.0
- ]
- */
+ /* Result:
+ [
+ 0 => 1.0,
+ 1 => 1.0,
+ 2 => 1.0,
+ ]
+ */
$crawler->evaluate('count(//span[@class="article"])');
- /* array:1 [
- 0 => 3.0
- ]
- */
+ /* Result:
+ [
+ 0 => 3.0,
+ ]
+ */
$crawler->evaluate('//span[1]');
// A Symfony\Component\DomCrawler\Crawler instance
@@ -458,9 +508,12 @@ You can virtually set and get values on the form::
To work with multi-dimensional fields::
Pass an array of values::
@@ -474,6 +527,11 @@ Pass an array of values::
'dimensional' => 'an other value',
]]);
+ // tick multiple checkboxes at once
+ $form->setValues(['multi' => [
+ 'dimensional' => [1, 3] // it uses the input value to determine which checkbox to tick
+ ]]);
+
This is great, but it gets better! The ``Form`` object allows you to interact
with your form like a browser, selecting radio values, ticking checkboxes,
and uploading files::
@@ -548,11 +606,11 @@ the whole form or specific field(s)::
$form->disableValidation();
$form['country']->select('Invalid value');
-.. _`Goutte`: https://github.com/FriendsOfPHP/Goutte
-.. _Packagist: https://packagist.org/packages/symfony/dom-crawler
-
Learn more
----------
* :doc:`/testing`
* :doc:`/components/css_selector`
+
+.. _`Goutte`: https://github.com/FriendsOfPHP/Goutte
+.. _`html5-php library`: https://github.com/Masterminds/html5-php
diff --git a/components/dotenv.rst b/components/dotenv.rst
index 017a2614b0d..acd67399a9c 100644
--- a/components/dotenv.rst
+++ b/components/dotenv.rst
@@ -6,16 +6,14 @@ The Dotenv Component
====================
The Dotenv Component parses ``.env`` files to make environment variables
- stored in them accessible via ``getenv()``, ``$_ENV`` or ``$_SERVER``.
+ stored in them accessible via ``$_ENV`` or ``$_SERVER``.
Installation
------------
.. code-block:: terminal
- $ composer require --dev symfony/dotenv
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require symfony/dotenv
.. include:: /components/require_autoload.rst.inc
@@ -47,24 +45,72 @@ Load a ``.env`` file in your PHP application via ``Dotenv::load()``::
Given the following ``.env`` file content:
-.. code-block:: bash
+.. code-block:: terminal
# .env
DB_USER=root
DB_PASS=pass
-Access the value with ``getenv()`` in your code::
+Access the value with ``$_ENV`` in your code::
+
+ $dbUser = $_ENV['DB_USER'];
+ // you can also use ``$_SERVER``
+
+The ``load()`` method never overwrites existing environment variables. Use the
+``overload()`` method if you need to overwrite them::
+
+ // ...
+ $dotenv->overload(__DIR__.'/.env');
- $dbUser = getenv('DB_USER');
- // you can also use ``$_ENV`` or ``$_SERVER``
+As you're working with the Dotenv component you'll notice that you might want
+to have different files depending on the environment you're working in. Typically
+this happens for local development or Continuous Integration where you might
+want to have different files for your ``test`` and ``dev`` environments.
+
+You can use ``Dotenv::loadEnv()`` to ease this process::
+
+ use Symfony\Component\Dotenv\Dotenv;
+
+ $dotenv = new Dotenv();
+ $dotenv->loadEnv(__DIR__.'/.env');
+
+The Dotenv component will then look for the correct ``.env`` file to load
+in the following order whereas the files loaded later override the variables
+defined in previously loaded files:
+
+#. If ``.env`` exists, it is loaded first. In case there's no ``.env`` file but a
+ ``.env.dist``, this one will be loaded instead.
+#. If one of the previously mentioned files contains the ``APP_ENV`` variable, the
+ variable is populated and used to load environment-specific files hereafter. If
+ ``APP_ENV`` is not defined in either of the previously mentioned files, ``dev`` is
+ assumed for ``APP_ENV`` and populated by default.
+#. If there's a ``.env.local`` representing general local environment variables it's loaded now.
+#. If there's a ``.env.$env.local`` file, this one is loaded. Otherwise, it falls
+ back to ``.env.$env``.
+
+This might look complicated at first glance but it gives you the opportunity to
+commit multiple environment-specific files that can then be adjusted to your
+local environment. Given you commit ``.env``, ``.env.test`` and ``.env.dev`` to
+represent different configuration settings for your environments, each of them
+can be adjusted by using ``.env.local``, ``.env.test.local`` and
+``.env.dev.local`` respectively.
.. note::
- Symfony Dotenv never overwrites existing environment variables.
+ ``.env.local`` is always ignored in ``test`` environment because tests should produce the
+ same results for everyone.
+
+You can adjust the variable defining the environment, default environment and test
+environments by passing them as additional arguments to ``Dotenv::loadEnv()``
+(see :method:`Symfony\\Component\\Dotenv\\Dotenv::loadEnv` for details).
+
+.. versionadded:: 4.2
+
+ The ``Dotenv::loadEnv()`` method was introduced in Symfony 4.2.
You should never store a ``.env`` file in your code repository as it might
-contain sensitive information; create a ``.env.dist`` file with sensible
-defaults instead.
+contain sensitive information; create a ``.env.dist`` file (or multiple
+environment-specific ones as shown above) with sensible defaults instead.
.. note::
@@ -82,7 +128,7 @@ shell scripts:
Add comments by prefixing them with ``#``:
-.. code-block:: bash
+.. code-block:: terminal
# Database credentials
DB_USER=root
@@ -90,14 +136,22 @@ Add comments by prefixing them with ``#``:
Use environment variables in values by prefixing variables with ``$``:
-.. code-block:: bash
+.. code-block:: terminal
DB_USER=root
DB_PASS=${DB_USER}pass # Include the user as a password prefix
+.. note::
+
+ The order is important when some env var depends on the value of other env
+ vars. In the above example, ``DB_PASS`` must be defined after ``DB_USER``.
+ Moreover, if you define multiple ``.env`` files and put ``DB_PASS`` first,
+ its value will depend on the ``DB_USER`` value defined in other files
+ instead of the value defined in this file.
+
Embed commands via ``$()`` (not supported on Windows):
-.. code-block:: bash
+.. code-block:: terminal
START_TIME=$(date)
@@ -105,5 +159,4 @@ Embed commands via ``$()`` (not supported on Windows):
Note that using ``$()`` might not work depending on your shell.
-.. _Packagist: https://packagist.org/packages/symfony/dotenv
.. _twelve-factor applications: http://www.12factor.net/
diff --git a/components/event_dispatcher.rst b/components/event_dispatcher.rst
index 658de8c358f..89853d22941 100644
--- a/components/event_dispatcher.rst
+++ b/components/event_dispatcher.rst
@@ -13,7 +13,7 @@ Introduction
------------
Object-oriented code has gone a long way to ensuring code extensibility.
-By creating classes that have well defined responsibilities, your code becomes
+By creating classes that have well-defined responsibilities, your code becomes
more flexible and a developer can extend them with subclasses to modify
their behaviors. But if they want to share the changes with other developers
who have also made their own subclasses, code inheritance is no longer the
@@ -56,8 +56,6 @@ Installation
$ composer require symfony/event-dispatcher
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
@@ -74,7 +72,7 @@ Events
When an event is dispatched, it's identified by a unique name (e.g.
``kernel.response``), which any number of listeners might be listening to.
-An :class:`Symfony\\Component\\EventDispatcher\\Event` instance is also
+An :class:`Symfony\\Contracts\\EventDispatcher\\Event` instance is also
created and passed to all of the listeners. As you'll see later, the ``Event``
object itself often contains data about the event being dispatched.
@@ -88,7 +86,7 @@ The unique event name can be any string, but optionally follows a few
naming conventions:
* Use only lowercase letters, numbers, dots (``.``) and underscores (``_``);
-* Prefix names with a namespace followed by a dot (e.g. ``order.``, ``user.*``);
+* Prefix names with a namespace followed by a dot (e.g. ``order.*``, ``user.*``);
* End names with a verb that indicates what action has been taken (e.g.
``order.placed``).
@@ -113,7 +111,7 @@ Often times, data about a specific event needs to be passed along with the
case, a special subclass that has additional methods for retrieving and
overriding information can be passed when dispatching an event. For example,
the ``kernel.response`` event uses a
-:class:`Symfony\\Component\\HttpKernel\\Event\\FilterResponseEvent`, which
+:class:`Symfony\\Component\\HttpKernel\\Event\\ResponseEvent`, which
contains methods to get and even replace the ``Response`` object.
The Dispatcher
@@ -163,7 +161,7 @@ The ``addListener()`` method takes up to three arguments:
So far, you've seen how PHP objects can be registered as listeners.
You can also register PHP `Closures`_ as event listeners::
- use Symfony\Component\EventDispatcher\Event;
+ use Symfony\Contracts\EventDispatcher\Event;
$dispatcher->addListener('acme.foo.action', function (Event $event) {
// will be executed when the acme.foo.action event is dispatched
@@ -174,7 +172,7 @@ is notified. In the above example, when the ``acme.foo.action`` event is dispatc
the dispatcher calls the ``AcmeListener::onFooAction()`` method and passes
the ``Event`` object as the single argument::
- use Symfony\Component\EventDispatcher\Event;
+ use Symfony\Contracts\EventDispatcher\Event;
class AcmeListener
{
@@ -201,8 +199,8 @@ determine which instance is passed.
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\Reference;
- use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
+ use Symfony\Component\EventDispatcher\EventDispatcher;
$containerBuilder = new ContainerBuilder(new ParameterBag());
// register the compiler pass that handles the 'kernel.event_listener'
@@ -253,8 +251,8 @@ order. Start by creating this custom event class and documenting it::
namespace Acme\Store\Event;
- use Symfony\Component\EventDispatcher\Event;
use Acme\Store\Order;
+ use Symfony\Contracts\EventDispatcher\Event;
/**
* The order.placed event is dispatched each time an order is created
@@ -262,7 +260,7 @@ order. Start by creating this custom event class and documenting it::
*/
class OrderPlacedEvent extends Event
{
- const NAME = 'order.placed';
+ public const NAME = 'order.placed';
protected $order;
@@ -283,7 +281,7 @@ Each listener now has access to the order via the ``getOrder()`` method.
If you don't need to pass any additional data to the event listeners, you
can also use the default
- :class:`Symfony\\Component\\EventDispatcher\\Event` class. In such case,
+ :class:`Symfony\\Contracts\\EventDispatcher\\Event` class. In such case,
you can document the event and its name in a generic ``StoreEvents`` class,
similar to the :class:`Symfony\\Component\\HttpKernel\\KernelEvents`
class.
@@ -293,11 +291,11 @@ Dispatch the Event
The :method:`Symfony\\Component\\EventDispatcher\\EventDispatcher::dispatch`
method notifies all listeners of the given event. It takes two arguments:
-the name of the event to dispatch and the ``Event`` instance to pass to
-each listener of that event::
+the ``Event`` instance to pass to each listener of that event and the name
+of the event to dispatch and ::
- use Acme\Store\Order;
use Acme\Store\Event\OrderPlacedEvent;
+ use Acme\Store\Order;
// the order is somehow created or retrieved
$order = new Order();
@@ -305,7 +303,7 @@ each listener of that event::
// creates the OrderPlacedEvent and dispatches it
$event = new OrderPlacedEvent($order);
- $dispatcher->dispatch(OrderPlacedEvent::NAME, $event);
+ $dispatcher->dispatch($event, OrderPlacedEvent::NAME);
Notice that the special ``OrderPlacedEvent`` object is created and passed to
the ``dispatch()`` method. Now, any listener to the ``order.placed``
@@ -334,10 +332,10 @@ Take the following example of a subscriber that subscribes to the
namespace Acme\Store\Event;
+ use Acme\Store\Event\OrderPlacedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
- use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
+ use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
- use Acme\Store\Event\OrderPlacedEvent;
class StoreSubscriber implements EventSubscriberInterface
{
@@ -352,12 +350,12 @@ Take the following example of a subscriber that subscribes to the
];
}
- public function onKernelResponsePre(FilterResponseEvent $event)
+ public function onKernelResponsePre(ResponseEvent $event)
{
// ...
}
- public function onKernelResponsePost(FilterResponseEvent $event)
+ public function onKernelResponsePost(ResponseEvent $event)
{
// ...
}
@@ -421,11 +419,11 @@ Now, any listeners to ``order.placed`` that have not yet been called will
*not* be called.
It is possible to detect if an event was stopped by using the
-:method:`Symfony\\Component\\EventDispatcher\\Event::isPropagationStopped`
+:method:`Symfony\\Contracts\\EventDispatcher\\Event::isPropagationStopped`
method which returns a boolean value::
// ...
- $dispatcher->dispatch('foo.event', $event);
+ $dispatcher->dispatch($event, 'foo.event');
if ($event->isPropagationStopped()) {
// ...
}
@@ -443,36 +441,6 @@ name and a reference to itself to the listeners. This can lead to some advanced
applications of the ``EventDispatcher`` including dispatching other events inside
listeners, chaining events or even lazy loading listeners into the dispatcher object.
-.. index::
- single: EventDispatcher; Dispatcher shortcuts
-
-.. _event_dispatcher-shortcuts:
-
-Dispatcher Shortcuts
-~~~~~~~~~~~~~~~~~~~~
-
-If you do not need a custom event object, you can rely on a plain
-:class:`Symfony\\Component\\EventDispatcher\\Event` object. You do not even
-need to pass this to the dispatcher as it will create one by default unless you
-specifically pass one::
-
- $dispatcher->dispatch('order.placed');
-
-Moreover, the event dispatcher always returns whichever event object that
-was dispatched, i.e. either the event that was passed or the event that
-was created internally by the dispatcher. This allows for nice shortcuts::
-
- if (!$dispatcher->dispatch('foo.event')->isPropagationStopped()) {
- // ...
- }
-
-Or::
-
- $event = new OrderPlacedEvent($order);
- $order = $dispatcher->dispatch('bar.event', $event)->getOrder();
-
-and so on.
-
.. index::
single: EventDispatcher; Event name introspection
@@ -484,8 +452,8 @@ Event Name Introspection
The ``EventDispatcher`` instance, as well as the name of the event that
is dispatched, are passed as arguments to the listener::
- use Symfony\Component\EventDispatcher\Event;
- use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+ use Symfony\Contracts\EventDispatcher\Event;
+ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
class Foo
{
@@ -522,4 +490,3 @@ Learn More
.. _Observer: https://en.wikipedia.org/wiki/Observer_pattern
.. _Closures: https://php.net/manual/en/functions.anonymous.php
.. _PHP callable: https://php.net/manual/en/language.pseudo-types.php#language.types.callback
-.. _Packagist: https://packagist.org/packages/symfony/event-dispatcher
diff --git a/components/event_dispatcher/generic_event.rst b/components/event_dispatcher/generic_event.rst
index cc00d26ac20..1f9be477151 100644
--- a/components/event_dispatcher/generic_event.rst
+++ b/components/event_dispatcher/generic_event.rst
@@ -4,7 +4,7 @@
The Generic Event Object
========================
-The base :class:`Symfony\\Component\\EventDispatcher\\Event` class provided
+The base :class:`Symfony\\Contracts\\EventDispatcher\\Event` class provided
by the EventDispatcher component is deliberately sparse to allow the creation
of API specific event objects by inheritance using OOP. This allows for
elegant and readable code in complex applications.
@@ -18,7 +18,7 @@ arguments.
:class:`Symfony\\Component\\EventDispatcher\\GenericEvent` adds some more
methods in addition to the base class
-:class:`Symfony\\Component\\EventDispatcher\\Event`
+:class:`Symfony\\Contracts\\EventDispatcher\\Event`
* :method:`Symfony\\Component\\EventDispatcher\\GenericEvent::__construct`:
Constructor takes the event subject and any arguments;
@@ -53,7 +53,7 @@ Passing a subject::
use Symfony\Component\EventDispatcher\GenericEvent;
$event = new GenericEvent($subject);
- $dispatcher->dispatch('foo', $event);
+ $dispatcher->dispatch($event, 'foo');
class FooListener
{
@@ -74,7 +74,7 @@ access the event arguments::
$subject,
['type' => 'foo', 'counter' => 0]
);
- $dispatcher->dispatch('foo', $event);
+ $dispatcher->dispatch($event, 'foo');
class FooListener
{
@@ -93,7 +93,7 @@ Filtering data::
use Symfony\Component\EventDispatcher\GenericEvent;
$event = new GenericEvent($subject, ['data' => 'Foo']);
- $dispatcher->dispatch('foo', $event);
+ $dispatcher->dispatch($event, 'foo');
class FooListener
{
diff --git a/components/event_dispatcher/traceable_dispatcher.rst b/components/event_dispatcher/traceable_dispatcher.rst
index 57f05ba6e0d..87d58023445 100644
--- a/components/event_dispatcher/traceable_dispatcher.rst
+++ b/components/event_dispatcher/traceable_dispatcher.rst
@@ -38,7 +38,7 @@ to register event listeners and dispatch events::
// dispatches an event
$event = ...;
- $traceableEventDispatcher->dispatch('event.the_name', $event);
+ $traceableEventDispatcher->dispatch($event, 'event.the_name');
After your application has been processed, you can use the
:method:`Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcherInterface::getCalledListeners`
diff --git a/components/expression_language.rst b/components/expression_language.rst
index 1e75bdeb78f..7e955a8b891 100644
--- a/components/expression_language.rst
+++ b/components/expression_language.rst
@@ -16,8 +16,6 @@ Installation
$ composer require symfony/expression-language
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
How can the Expression Engine Help Me?
@@ -131,5 +129,3 @@ Learn More
/components/expression_language/*
/service_container/expression_language
/reference/constraints/Expression
-
-.. _Packagist: https://packagist.org/packages/symfony/expression-language
diff --git a/components/expression_language/caching.rst b/components/expression_language/caching.rst
index 5bfaf15133a..770c2768ca5 100644
--- a/components/expression_language/caching.rst
+++ b/components/expression_language/caching.rst
@@ -31,8 +31,8 @@ uses an :class:`Symfony\\Component\\Cache\\Adapter\\ArrayAdapter`). You can
customize this by creating a custom cache pool or using one of the available
ones and injecting this using the constructor::
- use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\Cache\Adapter\RedisAdapter;
+ use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
$cache = new RedisAdapter(...);
$expressionLanguage = new ExpressionLanguage($cache);
diff --git a/components/expression_language/extending.rst b/components/expression_language/extending.rst
index 9f055d84156..e3ec448a495 100644
--- a/components/expression_language/extending.rst
+++ b/components/expression_language/extending.rst
@@ -29,7 +29,7 @@ This method has 3 arguments:
function;
* **evaluator** - A function executed when the expression is evaluated.
-.. code-block:: php
+Example::
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
@@ -127,7 +127,7 @@ or by using the second argument of the constructor::
{
public function __construct(CacheItemPoolInterface $parser = null, array $providers = [])
{
- // prepends the default provider to let users override it easily
+ // prepends the default provider to let users override it
array_unshift($providers, new StringExpressionLanguageProvider());
parent::__construct($parser, $providers);
diff --git a/components/filesystem.rst b/components/filesystem.rst
index 5aee48f7c23..10406ce68c9 100644
--- a/components/filesystem.rst
+++ b/components/filesystem.rst
@@ -13,8 +13,6 @@ Installation
$ composer require symfony/filesystem
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
@@ -23,13 +21,13 @@ Usage
The :class:`Symfony\\Component\\Filesystem\\Filesystem` class is the unique
endpoint for filesystem operations::
- use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Filesystem\Exception\IOExceptionInterface;
+ use Symfony\Component\Filesystem\Filesystem;
- $fileSystem = new Filesystem();
+ $filesystem = new Filesystem();
try {
- $fileSystem->mkdir(sys_get_temp_dir().'/'.random_int(0, 1000));
+ $filesystem->mkdir(sys_get_temp_dir().'/'.random_int(0, 1000));
} catch (IOExceptionInterface $exception) {
echo "An error occurred while creating your directory at ".$exception->getPath();
}
@@ -53,7 +51,7 @@ mkdir
On POSIX filesystems, directories are created with a default mode value
`0777`. You can use the second argument to set your own mode::
- $fileSystem->mkdir('/tmp/photos', 0700);
+ $filesystem->mkdir('/tmp/photos', 0700);
.. note::
@@ -79,11 +77,11 @@ presence of one or more files or directories and returns ``false`` if any of
them is missing::
// if this absolute directory exists, returns true
- $fileSystem->exists('/tmp/photos');
+ $filesystem->exists('/tmp/photos');
// if rabbit.jpg exists and bottle.png does not exist, returns false
// non-absolute paths are relative to the directory where the running PHP script is stored
- $fileSystem->exists(['rabbit.jpg', 'bottle.png']);
+ $filesystem->exists(['rabbit.jpg', 'bottle.png']);
.. note::
@@ -100,10 +98,10 @@ source modification date is later than the target. This behavior can be overridd
by the third boolean argument::
// works only if image-ICC has been modified after image.jpg
- $fileSystem->copy('image-ICC.jpg', 'image.jpg');
+ $filesystem->copy('image-ICC.jpg', 'image.jpg');
// image.jpg will be overridden
- $fileSystem->copy('image-ICC.jpg', 'image.jpg', true);
+ $filesystem->copy('image-ICC.jpg', 'image.jpg', true);
touch
~~~~~
@@ -113,11 +111,11 @@ modification time for a file. The current time is used by default. You can set
your own with the second argument. The third argument is the access time::
// sets modification time to the current timestamp
- $fileSystem->touch('file.txt');
+ $filesystem->touch('file.txt');
// sets modification time 10 seconds in the future
- $fileSystem->touch('file.txt', time() + 10);
+ $filesystem->touch('file.txt', time() + 10);
// sets access time 10 seconds in the past
- $fileSystem->touch('file.txt', time(), time() - 10);
+ $filesystem->touch('file.txt', time(), time() - 10);
.. note::
@@ -131,9 +129,9 @@ chown
a file. The third argument is a boolean recursive option::
// sets the owner of the lolcat video to www-data
- $fileSystem->chown('lolcat.mp4', 'www-data');
+ $filesystem->chown('lolcat.mp4', 'www-data');
// changes the owner of the video directory recursively
- $fileSystem->chown('/video', 'www-data', true);
+ $filesystem->chown('/video', 'www-data', true);
.. note::
@@ -147,9 +145,9 @@ chgrp
a file. The third argument is a boolean recursive option::
// sets the group of the lolcat video to nginx
- $fileSystem->chgrp('lolcat.mp4', 'nginx');
+ $filesystem->chgrp('lolcat.mp4', 'nginx');
// changes the group of the video directory recursively
- $fileSystem->chgrp('/video', 'nginx', true);
+ $filesystem->chgrp('/video', 'nginx', true);
.. note::
@@ -163,9 +161,9 @@ chmod
permissions of a file. The fourth argument is a boolean recursive option::
// sets the mode of the video to 0600
- $fileSystem->chmod('video.ogg', 0600);
+ $filesystem->chmod('video.ogg', 0600);
// changes the mod of the src directory recursively
- $fileSystem->chmod('src', 0700, 0000, true);
+ $filesystem->chmod('src', 0700, 0000, true);
.. note::
@@ -178,7 +176,7 @@ remove
:method:`Symfony\\Component\\Filesystem\\Filesystem::remove` deletes files,
directories and symlinks::
- $fileSystem->remove(['symlink', '/path/to/directory', 'activity.log']);
+ $filesystem->remove(['symlink', '/path/to/directory', 'activity.log']);
.. note::
@@ -192,9 +190,9 @@ rename
of a single file or directory::
// renames a file
- $fileSystem->rename('/tmp/processed_video.ogg', '/path/to/store/video_647.ogg');
+ $filesystem->rename('/tmp/processed_video.ogg', '/path/to/store/video_647.ogg');
// renames a directory
- $fileSystem->rename('/tmp/files', '/path/to/store/files');
+ $filesystem->rename('/tmp/files', '/path/to/store/files');
symlink
~~~~~~~
@@ -204,10 +202,10 @@ symbolic link from the target to the destination. If the filesystem does not
support symbolic links, a third boolean argument is available::
// creates a symbolic link
- $fileSystem->symlink('/path/to/source', '/path/to/destination');
+ $filesystem->symlink('/path/to/source', '/path/to/destination');
// duplicates the source directory if the filesystem
// does not support symbolic links
- $fileSystem->symlink('/path/to/source', '/path/to/destination', true);
+ $filesystem->symlink('/path/to/source', '/path/to/destination', true);
readlink
~~~~~~~~
@@ -223,10 +221,10 @@ The :method:`Symfony\\Component\\Filesystem\\Filesystem::readlink` method provid
by the Filesystem component always behaves in the same way::
// returns the next direct target of the link without considering the existence of the target
- $fileSystem->readlink('/path/to/link');
+ $filesystem->readlink('/path/to/link');
// returns its absolute fully resolved final version of the target (if there are nested links, they are resolved)
- $fileSystem->readlink('/path/to/link', true);
+ $filesystem->readlink('/path/to/link', true);
Its behavior is the following::
@@ -247,12 +245,12 @@ makePathRelative
absolute paths and returns the relative path from the second path to the first one::
// returns '../'
- $fileSystem->makePathRelative(
+ $filesystem->makePathRelative(
'/var/lib/symfony/src/Symfony/',
'/var/lib/symfony/src/Symfony/Component'
);
// returns 'videos/'
- $fileSystem->makePathRelative('/tmp/videos', '/tmp')
+ $filesystem->makePathRelative('/tmp/videos', '/tmp')
mirror
~~~~~~
@@ -262,7 +260,7 @@ contents of the source directory into the target one (use the
:method:`Symfony\\Component\\Filesystem\\Filesystem::copy` method to copy single
files)::
- $fileSystem->mirror('/path/to/source', '/path/to/target');
+ $filesystem->mirror('/path/to/source', '/path/to/target');
isAbsolutePath
~~~~~~~~~~~~~~
@@ -271,13 +269,13 @@ isAbsolutePath
``true`` if the given path is absolute, ``false`` otherwise::
// returns true
- $fileSystem->isAbsolutePath('/tmp');
+ $filesystem->isAbsolutePath('/tmp');
// returns true
- $fileSystem->isAbsolutePath('c:\\Windows');
+ $filesystem->isAbsolutePath('c:\\Windows');
// returns false
- $fileSystem->isAbsolutePath('tmp');
+ $filesystem->isAbsolutePath('tmp');
// returns false
- $fileSystem->isAbsolutePath('../dir');
+ $filesystem->isAbsolutePath('../dir');
tempnam
~~~~~~~
@@ -296,7 +294,7 @@ file first and then moves it to the new file location when it's finished.
This means that the user will always see either the complete old file or
complete new file (but never a partially-written file)::
- $fileSystem->dumpFile('file.txt', 'Hello World');
+ $filesystem->dumpFile('file.txt', 'Hello World');
The ``file.txt`` file contains ``Hello World`` now.
@@ -306,7 +304,7 @@ appendToFile
:method:`Symfony\\Component\\Filesystem\\Filesystem::appendToFile` adds new
contents at the end of some file::
- $fileSystem->appendToFile('logs.txt', 'Email sent to user@example.com');
+ $filesystem->appendToFile('logs.txt', 'Email sent to user@example.com');
If either the file or its containing directory doesn't exist, this method
creates them before appending the contents.
@@ -323,5 +321,4 @@ Whenever something wrong happens, an exception implementing
An :class:`Symfony\\Component\\Filesystem\\Exception\\IOException` is
thrown if directory creation fails.
-.. _`Packagist`: https://packagist.org/packages/symfony/filesystem
.. _`umask`: https://en.wikipedia.org/wiki/Umask
diff --git a/components/finder.rst b/components/finder.rst
index ab47dbd09db..2201150ffa1 100644
--- a/components/finder.rst
+++ b/components/finder.rst
@@ -5,8 +5,8 @@
The Finder Component
====================
- The Finder component finds files and directories via an intuitive fluent
- interface.
+ The Finder component finds files and directories based on different criteria
+ (name, file size, modification time, etc.) via an intuitive fluent interface.
Installation
------------
@@ -15,8 +15,6 @@ Installation
$ composer require symfony/finder
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
@@ -28,57 +26,36 @@ directories::
use Symfony\Component\Finder\Finder;
$finder = new Finder();
+ // find all files in the current directory
$finder->files()->in(__DIR__);
- foreach ($finder as $file) {
- // dumps the absolute path
- var_dump($file->getRealPath());
-
- // dumps the relative path to the file, omitting the filename
- var_dump($file->getRelativePath());
-
- // dumps the relative path to the file
- var_dump($file->getRelativePathname());
+ // check if there are any search results
+ if ($finder->hasResults()) {
+ // ...
}
-The ``$file`` is an instance of :class:`Symfony\\Component\\Finder\\SplFileInfo`
-which extends PHP's own :phpclass:`SplFileInfo` to provide methods to work with relative
-paths.
-
-The above code prints the names of all the files in the current directory
-recursively. The Finder class uses a fluent interface, so all methods return
-the Finder instance.
+ foreach ($finder as $file) {
+ $absoluteFilePath = $file->getRealPath();
+ $fileNameWithExtension = $file->getRelativePathname();
-.. tip::
+ // ...
+ }
- A Finder instance is a PHP :phpclass:`IteratorAggregate`. So, in addition to iterating over the
- Finder with ``foreach``, you can also convert it to an array with the
- :phpfunction:`iterator_to_array` function, or get the number of items with
- :phpfunction:`iterator_count`.
+The ``$file`` variable is an instance of
+:class:`Symfony\\Component\\Finder\\SplFileInfo` which extends PHP's own
+:phpclass:`SplFileInfo` to provide methods to work with relative paths.
.. caution::
The ``Finder`` object doesn't reset its internal state automatically.
This means that you need to create a new instance if you do not want
- get mixed results.
+ to get mixed results.
-.. caution::
+Searching for Files and Directories
+-----------------------------------
- When searching through multiple locations passed to the
- :method:`Symfony\\Component\\Finder\\Finder::in` method, a separate iterator
- is created internally for every location. This means we have multiple result
- sets aggregated into one.
- Since :phpfunction:`iterator_to_array` uses keys of result sets by default,
- when converting to an array, some keys might be duplicated and their values
- overwritten. This can be avoided by passing ``false`` as a second parameter
- to :phpfunction:`iterator_to_array`.
-
-Criteria
---------
-
-There are lots of ways to filter and sort your results. You can also use the
-:method:`Symfony\\Component\\Finder\\Finder::hasResults` method to check if
-there's any file or directory matching the search criteria.
+The component provides lots of methods to define the search criteria. They all
+can be chained because they implement a `fluent interface`_.
Location
~~~~~~~~
@@ -97,12 +74,11 @@ Search in several locations by chaining calls to
// same as above
$finder->in(__DIR__)->in('/elsewhere');
-Use wildcard characters to search in the directories matching a pattern::
+Use ``*`` as a wildcard character to search in the directories matching a
+pattern (each pattern has to resolve to at least one directory path)::
$finder->in('src/Symfony/*/*/Resources');
-Each pattern has to resolve to at least one directory path.
-
Exclude directories from matching with the
:method:`Symfony\\Component\\Finder\\Finder::exclude` method::
@@ -114,7 +90,7 @@ It's also possible to ignore directories that you don't have permission to read:
$finder->ignoreUnreadableDirs()->in(__DIR__);
As the Finder uses PHP iterators, you can pass any URL with a supported
-`protocol`_::
+`PHP wrapper for URL-style protocols`_ (``ftp://``, ``zlib://``, etc.)::
// always add a trailing slash when looking for in the FTP root dir
$finder->in('ftp://example.com/');
@@ -126,8 +102,9 @@ And it also works with user-defined streams::
use Symfony\Component\Finder\Finder;
- $s3 = new \Zend_Service_Amazon_S3($key, $secret);
- $s3->registerStreamWrapper('s3');
+ // register a 's3://' wrapper with the official AWS SDK
+ $s3Client = new Aws\S3\S3Client([/* config options */]);
+ $s3Client->registerStreamWrapper();
$finder = new Finder();
$finder->name('photos*')->size('< 100K')->date('since 1 hour ago');
@@ -135,9 +112,9 @@ And it also works with user-defined streams::
// ... do something with the file
}
-.. note::
+.. seealso::
- Read the `Streams`_ documentation to learn how to create your own streams.
+ Read the `PHP streams`_ documentation to learn how to create your own streams.
Files or Directories
~~~~~~~~~~~~~~~~~~~~
@@ -146,77 +123,72 @@ By default, the Finder returns files and directories; but the
:method:`Symfony\\Component\\Finder\\Finder::files` and
:method:`Symfony\\Component\\Finder\\Finder::directories` methods control that::
+ // look for files only; ignore directories
$finder->files();
+ // look for directories only; ignore files
$finder->directories();
-If you want to follow links, use the ``followLinks()`` method::
+If you want to follow `symbolic links`_, use the ``followLinks()`` method::
$finder->files()->followLinks();
-By default, the iterator ignores popular VCS files. This can be changed with
-the ``ignoreVCS()`` method::
-
- $finder->ignoreVCS(false);
-
-Sorting
-~~~~~~~
-
-Sort the result by name or by type (directories first, then files)::
-
- $finder->sortByName();
-
- $finder->sortByType();
+Version Control Files
+~~~~~~~~~~~~~~~~~~~~~
-.. tip::
+`Version Control Systems`_ (or "VCS" for short), such as Git and Mercurial,
+create some special files to store their metadata. Those files are ignored by
+default when looking for files and directories, but you can change this with the
+``ignoreVCS()`` method::
- By default, the ``sortByName()`` method uses the :phpfunction:`strcmp` PHP
- function (e.g. ``file1.txt``, ``file10.txt``, ``file2.txt``). Pass ``true``
- as its argument to use PHP's `natural sort order`_ algorithm instead (e.g.
- ``file1.txt``, ``file2.txt``, ``file10.txt``).
-
- .. versionadded:: 4.2
- The option to use the natural sort order was introduced in Symfony 4.2.
-
-Sort the files and directories by the last accessed, changed or modified time::
-
- $finder->sortByAccessedTime();
-
- $finder->sortByChangedTime();
+ $finder->ignoreVCS(false);
- $finder->sortByModifiedTime();
+If the search directory contains a ``.gitignore`` file, you can reuse those
+rules to exclude files and directories from the results with the
+:method:`Symfony\\Component\\Finder\\Finder::ignoreVCSIgnored` method::
-You can also define your own sorting algorithm with ``sort()`` method::
+ // excludes files/directories matching the .gitignore patterns
+ $finder->ignoreVCSIgnored(true);
- $finder->sort(function (\SplFileInfo $a, \SplFileInfo $b) {
- return strcmp($a->getRealPath(), $b->getRealPath());
- });
+.. versionadded:: 4.3
-.. note::
-
- Notice that the ``sort*`` methods need to get all matching elements to do
- their jobs. For large iterators, it is slow.
+ The ``ignoreVCSIgnored()`` method was introduced in Symfony 4.3.
File Name
~~~~~~~~~
-Restrict files by name with the
+Find files by name with the
:method:`Symfony\\Component\\Finder\\Finder::name` method::
$finder->files()->name('*.php');
-The ``name()`` method accepts globs, strings, or regexes::
+The ``name()`` method accepts globs, strings, regexes or an array of globs,
+strings or regexes::
$finder->files()->name('/\.php$/');
+Multiple filenames can be defined by chaining calls or passing an array::
+
+ $finder->files()->name('*.php')->name('*.twig');
+
+ // same as above
+ $finder->files()->name(['*.php', '*.twig']);
+
The ``notName()`` method excludes files matching a pattern::
$finder->files()->notName('*.rb');
+Multiple filenames can be excluded by chaining calls or passing an array::
+
+ $finder->files()->notName('*.rb')->notName('*.py');
+
+ // same as above
+ $finder->files()->notName(['*.rb', '*.py']);
+
File Contents
~~~~~~~~~~~~~
-Restrict files by contents with the
+Find files by content with the
:method:`Symfony\\Component\\Finder\\Finder::contains` method::
$finder->files()->contains('lorem ipsum');
@@ -232,7 +204,7 @@ The ``notContains()`` method excludes files containing given pattern::
Path
~~~~
-Restrict files and directories by path with the
+Find files and directories by path with the
:method:`Symfony\\Component\\Finder\\Finder::path` method::
// matches files that contain "data" anywhere in their paths (files or directories)
@@ -240,39 +212,66 @@ Restrict files and directories by path with the
// for example this will match data/*.xml and data.xml if they exist
$finder->path('data')->name('*.xml');
-On all platforms slash (i.e. ``/``) should be used as the directory separator.
+Use the forward slash (i.e. ``/``) as the directory separator on all platforms,
+including Windows. The component makes the necessary conversion internally.
-The ``path()`` method accepts a string or a regular expression::
+The ``path()`` method accepts a string, a regular expression or an array of
+strings or regulars expressions::
$finder->path('foo/bar');
$finder->path('/^foo\/bar/');
+Multiple paths can be defined by chaining calls or passing an array::
+
+ $finder->path('data')->path('foo/bar');
+
+ // same as above
+ $finder->path(['data', 'foo/bar']);
+
Internally, strings are converted into regular expressions by escaping slashes
and adding delimiters:
-.. code-block:: text
-
- dirname ===> /dirname/
- a/b/c ===> /a\/b\/c/
+===================== =======================
+Original Given String Regular Expression Used
+===================== =======================
+``dirname`` ``/dirname/``
+``a/b/c`` ``/a\/b\/c/``
+===================== =======================
-The :method:`Symfony\\Component\\Finder\\Finder::notPath` method excludes files by path::
+The :method:`Symfony\\Component\\Finder\\Finder::notPath` method excludes files
+by path::
$finder->notPath('other/dir');
+Multiple paths can be excluded by chaining calls or passing an array::
+
+ $finder->notPath('first/dir')->notPath('other/dir');
+
+ // same as above
+ $finder->notPath(['first/dir', 'other/dir']);
+
+.. versionadded:: 4.2
+
+ Support for passing arrays to ``notPath()`` was introduced in Symfony
+ 4.2
+
File Size
~~~~~~~~~
-Restrict files by size with the
+Find files by size with the
:method:`Symfony\\Component\\Finder\\Finder::size` method::
$finder->files()->size('< 1.5K');
-Restrict by a size range by chaining calls::
+Restrict by a size range by chaining calls or passing an array::
$finder->files()->size('>= 1K')->size('<= 2K');
-The comparison operator can be any of the following: ``>``, ``>=``, ``<``, ``<=``,
-``==``, ``!=``.
+ // same as above
+ $finder->files()->size(['>= 1K', '<= 2K']);
+
+The comparison operator can be any of the following: ``>``, ``>=``, ``<``,
+``<=``, ``==``, ``!=``.
The target value may use magnitudes of kilobytes (``k``, ``ki``), megabytes
(``m``, ``mi``), or gigabytes (``g``, ``gi``). Those suffixed with an ``i`` use
@@ -281,30 +280,44 @@ the appropriate ``2**n`` version in accordance with the `IEC standard`_.
File Date
~~~~~~~~~
-Restrict files by last modified dates with the
+Find files by last modified dates with the
:method:`Symfony\\Component\\Finder\\Finder::date` method::
$finder->date('since yesterday');
-The comparison operator can be any of the following: ``>``, ``>=``, ``<``, ``<=``,
-``==``. You can also use ``since`` or ``after`` as an alias for ``>``, and
-``until`` or ``before`` as an alias for ``<``.
+Restrict by a date range by chaining calls or passing an array::
+
+ $finder->date('>= 2018-01-01')->date('<= 2018-12-31');
+
+ // same as above
+ $finder->date(['>= 2018-01-01', '<= 2018-12-31']);
+
+The comparison operator can be any of the following: ``>``, ``>=``, ``<``,
+``<=``, ``==``. You can also use ``since`` or ``after`` as an alias for ``>``,
+and ``until`` or ``before`` as an alias for ``<``.
-The target value can be any date supported by the `strtotime`_ function.
+The target value can be any date supported by :phpfunction:`strtotime`.
Directory Depth
~~~~~~~~~~~~~~~
-By default, the Finder recursively traverse directories. Restrict the depth of
+By default, the Finder recursively traverses directories. Restrict the depth of
traversing with :method:`Symfony\\Component\\Finder\\Finder::depth`::
$finder->depth('== 0');
$finder->depth('< 3');
+Restrict by a depth range by chaining calls or passing an array::
+
+ $finder->depth('> 2')->depth('< 5');
+
+ // same as above
+ $finder->depth(['> 2', '< 5']);
+
Custom Filtering
~~~~~~~~~~~~~~~~
-To restrict the matching file with your own strategy, use
+To filter results with your own strategy, use
:method:`Symfony\\Component\\Finder\\Finder::filter`::
$filter = function (\SplFileInfo $file)
@@ -321,8 +334,63 @@ it is called with the file as a :class:`Symfony\\Component\\Finder\\SplFileInfo`
instance. The file is excluded from the result set if the Closure returns
``false``.
+Sorting Results
+---------------
+
+Sort the results by name or by type (directories first, then files)::
+
+ $finder->sortByName();
+
+ $finder->sortByType();
+
+.. tip::
+
+ By default, the ``sortByName()`` method uses the :phpfunction:`strcmp` PHP
+ function (e.g. ``file1.txt``, ``file10.txt``, ``file2.txt``). Pass ``true``
+ as its argument to use PHP's `natural sort order`_ algorithm instead (e.g.
+ ``file1.txt``, ``file2.txt``, ``file10.txt``).
+
+Sort the files and directories by the last accessed, changed or modified time::
+
+ $finder->sortByAccessedTime();
+
+ $finder->sortByChangedTime();
+
+ $finder->sortByModifiedTime();
+
+You can also define your own sorting algorithm with the ``sort()`` method::
+
+ $finder->sort(function (\SplFileInfo $a, \SplFileInfo $b) {
+ return strcmp($a->getRealPath(), $b->getRealPath());
+ });
+
+You can reverse any sorting by using the ``reverseSorting()`` method::
+
+ // results will be sorted "Z to A" instead of the default "A to Z"
+ $finder->sortByName()->reverseSorting();
+
+.. note::
+
+ Notice that the ``sort*`` methods need to get all matching elements to do
+ their jobs. For large iterators, it is slow.
+
+Transforming Results into Arrays
+--------------------------------
+
+A Finder instance is an :phpclass:`IteratorAggregate` PHP class. So, in addition
+to iterating over the Finder results with ``foreach``, you can also convert it
+to an array with the :phpfunction:`iterator_to_array` function, or get the
+number of items with :phpfunction:`iterator_count`.
+
+If you call to the :method:`Symfony\\Component\\Finder\\Finder::in` method more
+than once to search through multiple locations, pass ``false`` as a second
+parameter to :phpfunction:`iterator_to_array` to avoid issues (a separate
+iterator is created for each location and, if you don't pass ``false`` to
+:phpfunction:`iterator_to_array`, keys of result sets are used and some of them
+might be duplicated and their values overwritten).
+
Reading Contents of Returned Files
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+----------------------------------
The contents of returned files can be read with
:method:`Symfony\\Component\\Finder\\SplFileInfo::getContents`::
@@ -338,9 +406,10 @@ The contents of returned files can be read with
// ...
}
-.. _strtotime: https://php.net/manual/en/datetime.formats.php
-.. _protocol: https://php.net/manual/en/wrappers.php
-.. _Streams: https://php.net/streams
-.. _IEC standard: https://physics.nist.gov/cuu/Units/binary.html
-.. _Packagist: https://packagist.org/packages/symfony/finder
+.. _`fluent interface`: https://en.wikipedia.org/wiki/Fluent_interface
+.. _`symbolic links`: https://en.wikipedia.org/wiki/Symbolic_link
+.. _`Version Control Systems`: https://en.wikipedia.org/wiki/Version_control
+.. _`PHP wrapper for URL-style protocols`: https://php.net/manual/en/wrappers.php
+.. _`PHP streams`: https://php.net/streams
+.. _`IEC standard`: https://physics.nist.gov/cuu/Units/binary.html
.. _`natural sort order`: https://en.wikipedia.org/wiki/Natural_sort_order
diff --git a/components/form.rst b/components/form.rst
index 9d143766f86..bcfce8939d7 100644
--- a/components/form.rst
+++ b/components/form.rst
@@ -20,8 +20,6 @@ Installation
$ composer require symfony/form
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Configuration
@@ -82,8 +80,8 @@ object to read data off of the correct PHP superglobals (i.e. ``$_POST`` or
.. seealso::
If you need more control over exactly when your form is submitted or which
- data is passed to it, you can use the :method:`Symfony\\Component\\Form\\FormInterface::submit`
- for this. Read more about it :ref:`form-call-submit-directly`.
+ data is passed to it,
+ :doc:`use the submit() method to handle form submissions `.
.. sidebar:: Integration with the HttpFoundation Component
@@ -91,8 +89,8 @@ object to read data off of the correct PHP superglobals (i.e. ``$_POST`` or
:class:`Symfony\\Component\\Form\\Extension\\HttpFoundation\\HttpFoundationExtension`
to your form factory::
- use Symfony\Component\Form\Forms;
use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationExtension;
+ use Symfony\Component\Form\Forms;
$formFactory = Forms::createFormFactoryBuilder()
->addExtension(new HttpFoundationExtension())
@@ -121,12 +119,12 @@ use the built-in support, first install the Security CSRF component:
The following snippet adds CSRF protection to the form factory::
+ use Symfony\Component\Form\Extension\Csrf\CsrfExtension;
use Symfony\Component\Form\Forms;
use Symfony\Component\HttpFoundation\Session\Session;
- use Symfony\Component\Form\Extension\Csrf\CsrfExtension;
- use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage;
- use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator;
use Symfony\Component\Security\Csrf\CsrfTokenManager;
+ use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator;
+ use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage;
// creates a Session object from the HttpFoundation component
$session = new Session();
@@ -157,7 +155,7 @@ the CSRF generator and validated when binding the form.
You can disable CSRF protection per form using the ``csrf_protection`` option::
- use Symfony\\Component\\Form\\Extension\\Core\\Type\\FormType
+ use Symfony\Component\Form\Extension\Core\Type\FormType;
$form = $formFactory->createBuilder(FormType::class, null, ['csrf_protection' => false])
->getForm();
@@ -165,10 +163,10 @@ You can disable CSRF protection per form using the ``csrf_protection`` option::
Twig Templating
~~~~~~~~~~~~~~~
-If you're using the Form component to process HTML forms, you'll need a way
-to render your form as HTML form fields (complete with field values,
-errors, and labels). If you use `Twig`_ as your template engine, the Form
-component offers a rich integration.
+If you're using the Form component to process HTML forms, you'll need a way to
+render your form as HTML form fields (complete with field values, errors, and
+labels). If you use `Twig`_ as your template engine, the Form component offers a
+rich integration.
To use the integration, you'll need the twig bridge, which provides integration
between Twig and several Symfony components:
@@ -183,10 +181,10 @@ that help you render the HTML widget, label, help and errors for each field
(as well as a few other things). To configure the integration, you'll need
to bootstrap or access Twig and add the :class:`Symfony\\Bridge\\Twig\\Extension\\FormExtension`::
- use Symfony\Component\Form\Forms;
use Symfony\Bridge\Twig\Extension\FormExtension;
- use Symfony\Component\Form\FormRenderer;
use Symfony\Bridge\Twig\Form\TwigRendererEngine;
+ use Symfony\Component\Form\FormRenderer;
+ use Symfony\Component\Form\Forms;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
use Twig\RuntimeLoader\FactoryRuntimeLoader;
@@ -226,7 +224,7 @@ to bootstrap or access Twig and add the :class:`Symfony\\Bridge\\Twig\\Extension
.. versionadded:: 1.30
- The ``Twig\\RuntimeLoader\\FactoryRuntimeLoader`` was introduced in Twig 1.30.
+ The ``Twig\RuntimeLoader\FactoryRuntimeLoader`` was introduced in Twig 1.30.
The exact details of your `Twig Configuration`_ will vary, but the goal is
always to add the :class:`Symfony\\Bridge\\Twig\\Extension\\FormExtension`
@@ -266,12 +264,12 @@ installed:
$ composer require symfony/translation symfony/config
Next, add the :class:`Symfony\\Bridge\\Twig\\Extension\\TranslationExtension`
-to your ``Twig\\Environment`` instance::
+to your ``Twig\Environment`` instance::
+ use Symfony\Bridge\Twig\Extension\TranslationExtension;
use Symfony\Component\Form\Forms;
- use Symfony\Component\Translation\Translator;
use Symfony\Component\Translation\Loader\XliffFileLoader;
- use Symfony\Bridge\Twig\Extension\TranslationExtension;
+ use Symfony\Component\Translation\Translator;
// creates the Translator
$translator = new Translator('en');
@@ -318,8 +316,8 @@ errors are then mapped to the correct field and rendered.
Your integration with the Validation component will look something like this::
- use Symfony\Component\Form\Forms;
use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
+ use Symfony\Component\Form\Forms;
use Symfony\Component\Validator\Validation;
$vendorDirectory = realpath(__DIR__.'/../vendor');
@@ -519,7 +517,7 @@ done by passing a special form "view" object to your template (notice the
{{ form_start(form) }}
{{ form_widget(form) }}
-
+
{{ form_end(form) }}
.. image:: /_images/form/simple-form.png
@@ -756,6 +754,18 @@ method to access the list of errors. It returns a
// a FormErrorIterator instance representing the form tree structure
$errors = $form->getErrors(true, false);
+Clearing Form Errors
+~~~~~~~~~~~~~~~~~~~~
+
+Any errors can be manually cleared using the
+:method:`Symfony\\Component\\Form\\ClearableErrorsInterface::clearErrors`
+method. This is useful when you'd like to validate the form without showing
+validation errors to the user (i.e. during a partial AJAX submission or
+:doc:`dynamic form modification `).
+
+Because clearing the errors makes the form valid, ``clearErrors()`` should only
+be called after testing whether the form is valid.
+
Learn more
----------
@@ -765,6 +775,5 @@ Learn more
/form/*
-.. _Packagist: https://packagist.org/packages/symfony/form
.. _Twig: https://twig.symfony.com
.. _`Twig Configuration`: https://twig.symfony.com/doc/2.x/intro.html
diff --git a/components/http_client.rst b/components/http_client.rst
new file mode 100644
index 00000000000..f3ed12a74ef
--- /dev/null
+++ b/components/http_client.rst
@@ -0,0 +1,836 @@
+.. index::
+ single: HttpClient
+ single: Components; HttpClient
+
+The HttpClient Component
+========================
+
+ The HttpClient component is a low-level HTTP client with support for both
+ PHP stream wrappers and cURL. It provides utilities to consume APIs and
+ supports synchronous and asynchronous operations.
+
+.. versionadded:: 4.3
+
+ The HttpClient component was introduced in Symfony 4.3 and it's still
+ considered an :doc:`experimental feature `.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/http-client
+
+.. include:: /components/require_autoload.rst.inc
+
+Basic Usage
+-----------
+
+Use the :class:`Symfony\\Component\\HttpClient\\HttpClient` class to create the
+low-level HTTP client that makes requests, like the following ``GET`` request::
+
+ use Symfony\Component\HttpClient\HttpClient;
+
+ $client = HttpClient::create();
+ $response = $client->request('GET', 'https://api.github.com/repos/symfony/symfony-docs');
+
+ $statusCode = $response->getStatusCode();
+ // $statusCode = 200
+ $contentType = $response->getHeaders()['content-type'][0];
+ // $contentType = 'application/json'
+ $content = $response->getContent();
+ // $content = '{"id":521583, "name":"symfony-docs", ...}'
+ $content = $response->toArray();
+ // $content = ['id' => 521583, 'name' => 'symfony-docs', ...]
+
+Performance
+-----------
+
+The component is built for maximum HTTP performance. By design, it is compatible
+with HTTP/2 and with doing concurrent asynchronous streamed and multiplexed
+requests/responses. Even when doing regular synchronous calls, this design
+allows keeping connections to remote hosts open between requests, improving
+performance by saving repetitive DNS resolution, SSL negotiation, etc.
+To leverage all these design benefits, the cURL extension is needed.
+
+Enabling cURL Support
+~~~~~~~~~~~~~~~~~~~~~
+
+This component supports both the native PHP streams and cURL to make the HTTP
+requests. Although both are interchangeable and provide the same features,
+including concurrent requests, HTTP/2 is only supported when using cURL.
+
+``HttpClient::create()`` selects the cURL transport if the `cURL PHP extension`_
+is enabled and falls back to PHP streams otherwise. If you prefer to select
+the transport explicitly, use the following classes to create the client::
+
+ use Symfony\Component\HttpClient\CurlHttpClient;
+ use Symfony\Component\HttpClient\NativeHttpClient;
+
+ // uses native PHP streams
+ $client = new NativeHttpClient();
+
+ // uses the cURL PHP extension
+ $client = new CurlHttpClient();
+
+When using this component in a full-stack Symfony application, this behavior is
+not configurable and cURL will be used automatically if the cURL PHP extension
+is installed and enabled. Otherwise, the native PHP streams will be used.
+
+HTTP/2 Support
+~~~~~~~~~~~~~~
+
+When requesting an ``https`` URL, HTTP/2 is enabled by default if libcurl >= 7.36
+is used. To force HTTP/2 for ``http`` URLs, you need to enable it explicitly via
+the ``http_version`` option::
+
+ $client = HttpClient::create(['http_version' => '2.0']);
+
+Support for HTTP/2 PUSH works out of the box when libcurl >= 7.61 is used with
+PHP >= 7.2.17 / 7.3.4: pushed responses are put into a temporary cache and are
+used when a subsequent request is triggered for the corresponding URLs.
+
+Making Requests
+---------------
+
+The client created with the ``HttpClient`` class provides a single ``request()``
+method to perform all kinds of HTTP requests::
+
+ $response = $client->request('GET', 'https://...');
+ $response = $client->request('POST', 'https://...');
+ $response = $client->request('PUT', 'https://...');
+ // ...
+
+Responses are always asynchronous, so that the call to the method returns
+immediately instead of waiting to receive the response::
+
+ // code execution continues immediately; it doesn't wait to receive the response
+ $response = $client->request('GET', 'http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso');
+
+ // getting the response headers waits until they arrive
+ $contentType = $response->getHeaders()['content-type'][0];
+
+ // trying to get the response contents will block the execution until
+ // the full response contents are received
+ $contents = $response->getContent();
+
+This component also supports :ref:`streaming responses `
+for full asynchronous applications.
+
+.. note::
+
+ HTTP compression and chunked transfer encoding are automatically enabled when
+ both your PHP runtime and the remote server support them.
+
+Authentication
+~~~~~~~~~~~~~~
+
+The HTTP client supports different authentication mechanisms. They can be
+defined globally when creating the client (to apply it to all requests) and to
+each request (which overrides any global authentication)::
+
+ // Use the same authentication for all requests
+ $client = HttpClient::create([
+ // HTTP Basic authentication with only the username and not a password
+ 'auth_basic' => ['the-username'],
+
+ // HTTP Basic authentication with a username and a password
+ 'auth_basic' => ['the-username', 'the-password'],
+
+ // HTTP Bearer authentication (also called token authentication)
+ 'auth_bearer' => 'the-bearer-token',
+ ]);
+
+ $response = $client->request('GET', 'https://...', [
+ // use a different HTTP Basic authentication only for this request
+ 'auth_basic' => ['the-username', 'the-password'],
+
+ // ...
+ ]);
+
+Query String Parameters
+~~~~~~~~~~~~~~~~~~~~~~~
+
+You can either append them manually to the requested URL, or define them as an
+associative array via the ``query`` option, that will be merged with the URL::
+
+ // it makes an HTTP GET request to https://httpbin.org/get?token=...&name=...
+ $response = $client->request('GET', 'https://httpbin.org/get', [
+ // these values are automatically encoded before including them in the URL
+ 'query' => [
+ 'token' => '...',
+ 'name' => '...',
+ ],
+ ]);
+
+Headers
+~~~~~~~
+
+Use the ``headers`` option to define both the default headers added to all
+requests and the specific headers for each request::
+
+ // this header is added to all requests made by this client
+ $client = HttpClient::create(['headers' => [
+ 'User-Agent' => 'My Fancy App',
+ ]]);
+
+ // this header is only included in this request and overrides the value
+ // of the same header if defined globally by the HTTP client
+ $response = $client->request('POST', 'https://...', [
+ 'headers' => [
+ 'Content-Type' => 'text/plain',
+ ],
+ ]);
+
+Uploading Data
+~~~~~~~~~~~~~~
+
+This component provides several methods for uploading data using the ``body``
+option. You can use regular strings, closures, iterables and resources and they'll be
+processed automatically when making the requests::
+
+ $response = $client->request('POST', 'https://...', [
+ // defining data using a regular string
+ 'body' => 'raw data',
+
+ // defining data using an array of parameters
+ 'body' => ['parameter1' => 'value1', '...'],
+
+ // using a closure to generate the uploaded data
+ 'body' => function (int $size): string {
+ // ...
+ },
+
+ // using a resource to get the data from it
+ 'body' => fopen('/path/to/file', 'r'),
+ ]);
+
+When uploading data with the ``POST`` method, if you don't define the
+``Content-Type`` HTTP header explicitly, Symfony assumes that you're uploading
+form data and adds the required
+``'Content-Type: application/x-www-form-urlencoded'`` header for you.
+
+When the ``body`` option is set as a closure, it will be called several times until
+it returns the empty string, which signals the end of the body. Each time, the
+closure should return a string smaller than the amount requested as argument.
+
+A generator or any ``Traversable`` can also be used instead of a closure.
+
+.. tip::
+
+ When uploading JSON payloads, use the ``json`` option instead of ``body``. The
+ given content will be JSON-encoded automatically and the request will add the
+ ``Content-Type: application/json`` automatically too::
+
+ $response = $client->request('POST', 'https://...', [
+ 'json' => ['param1' => 'value1', '...'],
+ ]);
+
+ $decodedPayload = $response->toArray();
+
+To submit a form with file uploads, it is your responsibility to encode the body
+according to the ``multipart/form-data`` content-type. The
+:doc:`Symfony Mime ` component makes it a few lines of code::
+
+ use Symfony\Component\Mime\Part\DataPart;
+ use Symfony\Component\Mime\Part\Multipart\FormDataPart;
+
+ $formFields = [
+ 'regular_field' => 'some value',
+ 'file_field' => DataPart::fromPath('/path/to/uploaded/file'),
+ ];
+ $formData = new FormDataPart($formFields);
+ $client->request('POST', 'https://...', [
+ 'headers' => $formData->getPreparedHeaders()->toArray(),
+ 'body' => $formData->bodyToIterable(),
+ ]);
+
+Cookies
+~~~~~~~
+
+The HTTP client provided by this component is stateless but handling cookies
+requires a stateful storage (because responses can update cookies and they must
+be used for subsequent requests). That's why this component doesn't handle
+cookies automatically.
+
+You can either handle cookies yourself using the ``Cookie`` HTTP header or use
+the :doc:`BrowserKit component ` which provides this
+feature and integrates seamlessly with the HttpClient component.
+
+Redirects
+~~~~~~~~~
+
+By default, the HTTP client follows redirects, up to a maximum of 20, when
+making a request. Use the ``max_redirects`` setting to configure this behavior
+(if the number of redirects is higher than the configured value, you'll get a
+:class:`Symfony\\Component\\HttpClient\\Exception\\RedirectionException`)::
+
+ $response = $client->request('GET', 'https://...', [
+ // 0 means to not follow any redirect
+ 'max_redirects' => 0,
+ ]);
+
+HTTP Proxies
+~~~~~~~~~~~~
+
+By default, this component honors the standard environment variables that your
+Operating System defines to direct the HTTP traffic through your local proxy.
+This means there is usually nothing to configure to have the client work with
+proxies, provided these env vars are properly configured.
+
+You can still set or override these settings using the ``proxy`` and ``no_proxy``
+options:
+
+* ``proxy`` should be set to the ``http://...`` URL of the proxy to get through
+
+* ``no_proxy`` disables the proxy for a comma-separated list of hosts that do not
+ require it to get reached.
+
+Progress Callback
+~~~~~~~~~~~~~~~~~
+
+By providing a callable to the ``on_progress`` option, one can track
+uploads/downloads as they complete. This callback is guaranteed to be called on
+DNS resolution, on arrival of headers and on completion; additionally it is
+called when new data is uploaded or downloaded and at least once per second::
+
+ $response = $client->request('GET', 'https://...', [
+ 'on_progress' => function (int $dlNow, int $dlSize, array $info): void {
+ // $dlNow is the number of bytes downloaded so far
+ // $dlSize is the total size to be downloaded or -1 if it is unknown
+ // $info is what $response->getInfo() would return at this very time
+ },
+ ]);
+
+Any exceptions thrown from the callback will be wrapped in an instance of
+``TransportExceptionInterface`` and will abort the request.
+
+Advanced Options
+~~~~~~~~~~~~~~~~
+
+The :class:`Symfony\\Contracts\\HttpClient\\HttpClientInterface` defines all the
+options you might need to take full control of the way the request is performed,
+including DNS pre-resolution, SSL parameters, public key pinning, etc.
+
+Processing Responses
+--------------------
+
+The response returned by all HTTP clients is an object of type
+:class:`Symfony\\Contracts\\HttpClient\\ResponseInterface` which provides the
+following methods::
+
+ $response = $client->request('GET', 'https://...');
+
+ // gets the HTTP status code of the response
+ $statusCode = $response->getStatusCode();
+
+ // gets the HTTP headers as string[][] with the header names lower-cased
+ $headers = $response->getHeaders();
+
+ // gets the response body as a string
+ $content = $response->getContent();
+
+ // cancels the request/response
+ $response->cancel();
+
+ // returns info coming from the transport layer, such as "response_headers",
+ // "redirect_count", "start_time", "redirect_url", etc.
+ $httpInfo = $response->getInfo();
+ // you can get individual info too
+ $startTime = $response->getInfo('start_time');
+
+.. note::
+
+ ``$response->getInfo()`` is non-blocking: it returns *live* information
+ about the response. Some of them might not be known yet (e.g. ``http_code``)
+ when you'll call it.
+
+.. tip::
+
+ Call ``$response->getInfo('debug')`` to get detailed logs about the HTTP transaction.
+
+.. _http-client-streaming-responses:
+
+Streaming Responses
+~~~~~~~~~~~~~~~~~~~
+
+Call the ``stream()`` method of the HTTP client to get *chunks* of the
+response sequentially instead of waiting for the entire response::
+
+ $url = 'https://releases.ubuntu.com/18.04.1/ubuntu-18.04.1-desktop-amd64.iso';
+ $response = $client->request('GET', $url, [
+ // optional: if you don't want to buffer the response in memory
+ 'buffer' => false,
+ ]);
+
+ // Responses are lazy: this code is executed as soon as headers are received
+ if (200 !== $response->getStatusCode()) {
+ throw new \Exception('...');
+ }
+
+ // get the response contents in chunk and save them in a file
+ // response chunks implement Symfony\Contracts\HttpClient\ChunkInterface
+ $fileHandler = fopen('/ubuntu.iso', 'w');
+ foreach ($client->stream($response) as $chunk) {
+ fwrite($fileHandler, $chunk->getContent());
+ }
+
+Canceling Responses
+~~~~~~~~~~~~~~~~~~~
+
+To abort a request (e.g. because it didn't complete in due time, or you want to
+fetch only the first bytes of the response, etc.), you can either use the
+``cancel()`` method of ``ResponseInterface``::
+
+ $response->cancel()
+
+Or throw an exception from a progress callback::
+
+ $response = $client->request('GET', 'https://...', [
+ 'on_progress' => function (int $dlNow, int $dlSize, array $info): void {
+ // ...
+
+ throw new \MyException();
+ },
+ ]);
+
+The exception will be wrapped in an instance of ``TransportExceptionInterface``
+and will abort the request.
+
+Handling Exceptions
+~~~~~~~~~~~~~~~~~~~
+
+When the HTTP status code of the response is in the 300-599 range (i.e. 3xx,
+4xx or 5xx) your code is expected to handle it. If you don't do that, the
+``getHeaders()`` and ``getContent()`` methods throw an appropriate exception::
+
+ // the response of this request will be a 403 HTTP error
+ $response = $client->request('GET', 'https://httpbin.org/status/403');
+
+ // this code results in a Symfony\Component\HttpClient\Exception\ClientException
+ // because it doesn't check the status code of the response
+ $content = $response->getContent();
+
+ // pass FALSE as the optional argument to not throw an exception and return
+ // instead the original response content (even if it's an error message)
+ $content = $response->getContent(false);
+
+Concurrent Requests
+-------------------
+
+Thanks to responses being lazy, requests are always managed concurrently.
+On a fast enough network, the following code makes 379 requests in less than
+half a second when cURL is used::
+
+ use Symfony\Component\HttpClient\CurlHttpClient;
+
+ $client = new CurlHttpClient();
+
+ $responses = [];
+
+ for ($i = 0; $i < 379; ++$i) {
+ $uri = "https://http2.akamai.com/demo/tile-$i.png";
+ $responses[] = $client->request('GET', $uri);
+ }
+
+ foreach ($responses as $response) {
+ $content = $response->getContent();
+ // ...
+ }
+
+As you can read in the first "for" loop, requests are issued but are not consumed
+yet. That's the trick when concurrency is desired: requests should be sent
+first and be read later on. This will allow the client to monitor all pending
+requests while your code waits for a specific one, as done in each iteration of
+the above "foreach" loop.
+
+Multiplexing Responses
+~~~~~~~~~~~~~~~~~~~~~~
+
+If you look again at the snippet above, responses are read in requests' order.
+But maybe the 2nd response came back before the 1st? Fully asynchronous operations
+require being able to deal with the responses in whatever order they come back.
+
+In order to do so, the ``stream()`` method of HTTP clients accepts a list of
+responses to monitor. As mentioned :ref:`previously `,
+this method yields response chunks as they arrive from the network. By replacing
+the "foreach" in the snippet with this one, the code becomes fully async::
+
+ foreach ($client->stream($responses) as $response => $chunk) {
+ if ($chunk->isFirst()) {
+ // headers of $response just arrived
+ // $response->getHeaders() is now a non-blocking call
+ } elseif ($chunk->isLast()) {
+ // the full content of $response just completed
+ // $response->getContent() is now a non-blocking call
+ } else {
+ // $chunk->getContent() will return a piece
+ // of the response body that just arrived
+ }
+ }
+
+.. tip::
+
+ Use the ``user_data`` option combined with ``$response->getInfo('user_data')``
+ to track the identity of the responses in your foreach loops.
+
+Dealing with Network Timeouts
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This component allows dealing with both request and response timeouts.
+
+A timeout can happen when e.g. DNS resolution takes too much time, when the TCP
+connection cannot be opened in the given time budget, or when the response
+content pauses for too long. This can be configured with the ``timeout`` request
+option::
+
+ // A TransportExceptionInterface will be issued if nothing
+ // happens for 2.5 seconds when accessing from the $response
+ $response = $client->request('GET', 'https://...', ['timeout' => 2.5]);
+
+The ``default_socket_timeout`` PHP ini setting is used if the option is not set.
+
+The option can be overridden by using the 2nd argument of the ``stream()`` method.
+This allows monitoring several responses at once and applying the timeout to all
+of them in a group. If all responses become inactive for the given duration, the
+method will yield a special chunk whose ``isTimeout()`` will return ``true``::
+
+ foreach ($client->stream($responses, 1.5) as $response => $chunk) {
+ if ($chunk->isTimeout()) {
+ // $response staled for more than 1.5 seconds
+ }
+ }
+
+A timeout is not necessarily an error: you can decide to stream again the
+response and get remaining contents that might come back in a new timeout, etc.
+
+.. tip::
+
+ Passing ``0`` as timeout allows monitoring responses in a non-blocking way.
+
+.. note::
+
+ Timeouts control how long one is willing to wait *while the HTTP transaction
+ is idle*. Big responses can last as long as needed to complete, provided they
+ remain active during the transfer and never pause for longer than specified.
+
+Dealing with Network Errors
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Network errors (broken pipe, failed DNS resolution, etc.) are thrown as instances
+of :class:`Symfony\\Contracts\\HttpClient\\Exception\\TransportExceptionInterface`.
+
+First of all, you don't *have* to deal with them: letting errors bubble to your
+generic exception-handling stack might be really fine in most use cases.
+
+If you want to handle them, here is what you need to know:
+
+To catch errors, you need to wrap calls to ``$client->request()`` but also calls
+to any methods of the returned responses. This is because responses are lazy, so
+that network errors can happen when calling e.g. ``getStatusCode()`` too::
+
+ try {
+ // both lines can potentially throw
+ $response = $client->request(...);
+ $headers = $response->getHeaders();
+ // ...
+ } catch (TransportExceptionInterface $e) {
+ // ...
+ }
+
+.. note::
+
+ Because ``$response->getInfo()`` is non-blocking, it shouldn't throw by design.
+
+When multiplexing responses, you can deal with errors for individual streams by
+catching ``TransportExceptionInterface`` in the foreach loop::
+
+ foreach ($client->stream($responses) as $response => $chunk) {
+ try {
+ if ($chunk->isLast()) {
+ // ... do something with $response
+ }
+ } catch (TransportExceptionInterface $e) {
+ // ...
+ }
+ }
+
+Caching Requests and Responses
+------------------------------
+
+This component provides a :class:`Symfony\\Component\\HttpClient\\CachingHttpClient`
+decorator that allows caching responses and serving them from the local storage
+for next requests. The implementation leverages the
+:class:`Symfony\\Component\\HttpKernel\\HttpCache\\HttpCache` class under the hood
+so that the :doc:`HttpKernel component ` needs to be
+installed in your application::
+
+ use Symfony\Component\HttpClient\CachingHttpClient;
+ use Symfony\Component\HttpClient\HttpClient;
+ use Symfony\Component\HttpKernel\HttpCache\Store;
+
+ $store = new Store('/path/to/cache/storage/');
+ $client = HttpClient::create();
+ $client = new CachingHttpClient($client, $store);
+
+ // this won't hit the network if the resource is already in the cache
+ $response = $client->request('GET', 'https://example.com/cacheable-resource');
+
+``CachingHttpClient`` accepts a third argument to set the options of the ``HttpCache``.
+
+Scoping Client
+--------------
+
+It's common that some of the HTTP client options depend on the URL of the
+request (e.g. you must set some headers when making requests to GitHub API but
+not for other hosts). If that's your case, this component provides a special
+HTTP client via the :class:`Symfony\\Component\\HttpClient\\ScopingHttpClient`
+class to autoconfigure the HTTP client based on the requested URL::
+
+ use Symfony\Component\HttpClient\HttpClient;
+ use Symfony\Component\HttpClient\ScopingHttpClient;
+
+ $client = HttpClient::create();
+ $client = new ScopingHttpClient($client, [
+ // the options defined as values apply only to the URLs matching
+ // the regular expressions defined as keys
+ 'https://api\.github\.com/' => [
+ 'headers' => [
+ 'Accept' => 'application/vnd.github.v3+json',
+ 'Authorization' => 'token '.$githubToken,
+ ],
+ ],
+ // ...
+ ]);
+
+You can define several scopes, so that each set of options is added only if a
+requested URL matches one of the regular expressions provided as keys.
+
+If the request URL is relative (because you use the ``base_uri`` option), the
+scoping HTTP client can't make a match. That's why you can define a third
+optional argument in its constructor which will be considered the default
+regular expression applied to relative URLs::
+
+ // ...
+
+ $client = new ScopingHttpClient($client,
+ [
+ 'https://api\.github\.com/' => [
+ 'base_uri' => 'https://api.github.com/',
+ // ...
+ ],
+ ],
+ // this is the index in the previous array that defines
+ // the base URI that shoud be used to resolve relative URLs
+ 'https://api\.github\.com/'
+ );
+
+The above example can be reduced to a simpler call::
+
+ // ...
+
+ $client = ScopingHttpClient::forBaseUri($client, 'https://api.github.com/', [
+ // ...
+ ]);
+
+This way, the provided options will be used only if the requested URL is relative
+or if it matches the ``https://api.github.com/`` base URI.
+
+Interoperability
+----------------
+
+The component is interoperable with two different abstractions for HTTP clients:
+`Symfony Contracts`_ and `PSR-18`_. If your application uses libraries that need
+any of them, the component is compatible with both. They also benefit from
+:ref:`autowiring aliases ` when the
+:ref:`framework bundle ` is used.
+
+If you are writing or maintaining a library that makes HTTP requests, you can
+decouple it from any specific HTTP client implementations by coding against
+either Symfony Contracts (recommended) or PSR-18.
+
+Symfony Contracts
+~~~~~~~~~~~~~~~~~
+
+The interfaces found in the ``symfony/http-client-contracts`` package define
+the primary abstractions implemented by the component. Its entry point is the
+:class:`Symfony\\Contracts\\HttpClient\\HttpClientInterface`. That's the
+interface you need to code against when a client is needed::
+
+ use Symfony\Contracts\HttpClient\HttpClientInterface;
+
+ class MyApiLayer
+ {
+ private $client;
+
+ public function __construct(HttpClientInterface $client)
+ {
+ $this->client = $client
+ }
+
+ // [...]
+ }
+
+All request options mentioned above (e.g. timeout management) are also defined
+in the wordings of the interface, so that any compliant implementations (like
+this component) is guaranteed to provide them. That's a major difference with
+the PSR-18 abstraction, which provides none related to the transport itself.
+
+Another major feature covered by the Symfony Contracts is async/multiplexing,
+as described in the previous sections.
+
+PSR-18
+~~~~~~
+
+This component implements the `PSR-18`_ (HTTP Client) specifications via the
+:class:`Symfony\\Component\\HttpClient\\Psr18Client` class, which is an adapter
+to turn a Symfony ``HttpClientInterface`` into a PSR-18 ``ClientInterface``.
+
+To use it, you need the ``psr/http-client`` package and a `PSR-17`_ implementation:
+
+.. code-block:: terminal
+
+ # installs the PSR-18 ClientInterface
+ $ composer require psr/http-client
+
+ # installs an efficient implementation of response and stream factories
+ # with autowiring aliases provided by Symfony Flex
+ $ composer require nyholm/psr7
+
+Now you can make HTTP requests with the PSR-18 client as follows::
+
+ use Nyholm\Psr7\Factory\Psr17Factory;
+ use Symfony\Component\HttpClient\Psr18Client;
+
+ $psr17Factory = new Psr17Factory();
+ $psr18Client = new Psr18Client();
+
+ $url = 'https://symfony.com/versions.json';
+ $request = $psr17Factory->createRequest('GET', $url);
+ $response = $psr18Client->sendRequest($request);
+
+ $content = json_decode($response->getBody()->getContents(), true);
+
+Symfony Framework Integration
+-----------------------------
+
+When using this component in a full-stack Symfony application, you can configure
+multiple clients with different configurations and inject them into your services.
+
+Configuration
+~~~~~~~~~~~~~
+
+Use the ``framework.http_client`` key to configure the default HTTP client used
+in the application. Check out the full
+:ref:`http_client config reference ` to learn about all
+the available config options:
+
+.. code-block:: yaml
+
+ # config/packages/framework.yaml
+ framework:
+ # ...
+ http_client:
+ max_host_connections: 10
+ default_options:
+ max_redirects: 7
+
+If you want to define multiple HTTP clients, use this other expanded configuration:
+
+.. code-block:: yaml
+
+ # config/packages/framework.yaml
+ framework:
+ # ...
+ http_client:
+ scoped_clients:
+ crawler.client:
+ headers: { 'X-Powered-By': 'ACME App' }
+ http_version: '1.0'
+ some_api.client:
+ max_redirects: 5
+
+Injecting the HTTP Client into Services
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If your application only needs one HTTP client, you can inject the default one
+into any services by type-hinting a constructor argument with the
+:class:`Symfony\\Contracts\\HttpClient\\HttpClientInterface`::
+
+ use Symfony\Contracts\HttpClient\HttpClientInterface;
+
+ class SomeService
+ {
+ private $client;
+
+ public function __construct(HttpClientInterface $client)
+ {
+ $this->client = $client;
+ }
+ }
+
+If you have several clients, you must use any of the methods defined by Symfony
+to :ref:`choose a specific service `. Each client
+has a unique service named after its configuration.
+
+Each scoped client also defines a corresponding named autowiring alias.
+If you use for example
+``Symfony\Contracts\HttpClient\HttpClientInterface $myApiClient``
+as the type and name of an argument, autowiring will inject the ``my_api.client``
+service into your autowired classes.
+
+Testing HTTP Clients and Responses
+----------------------------------
+
+This component includes the ``MockHttpClient`` and ``MockResponse`` classes to
+use them in tests that need an HTTP client which doesn't make actual HTTP
+requests.
+
+The first way of using ``MockHttpClient`` is to pass a list of responses to its
+constructor. These will be yielded in order when requests are made::
+
+ use Symfony\Component\HttpClient\MockHttpClient;
+ use Symfony\Component\HttpClient\Response\MockResponse;
+
+ $responses = [
+ new MockResponse($body1, $info1),
+ new MockResponse($body2, $info2),
+ ];
+
+ $client = new MockHttpClient($responses);
+ // responses are returned in the same order as passed to MockHttpClient
+ $response1 = $client->request('...'); // returns $responses[0]
+ $response2 = $client->request('...'); // returns $responses[1]
+
+Another way of using ``MockHttpClient`` is to pass a callback that generates the
+responses dynamically when it's called::
+
+ use Symfony\Component\HttpClient\MockHttpClient;
+ use Symfony\Component\HttpClient\Response\MockResponse;
+
+ $callback = function ($method, $url, $options) {
+ return new MockResponse('...');
+ };
+
+ $client = new MockHttpClient($callback);
+ $response = $client->request('...'); // calls $callback to get the response
+
+The responses provided to the mock client don't have to be instances of
+``MockResponse``. Any class implementing ``ResponseInterface`` will work (e.g.
+``$this->createMock(ResponseInterface::class)``).
+
+However, using ``MockResponse`` allows simulating chunked responses and timeouts::
+
+ $body = function () {
+ yield 'hello';
+ // empty strings are turned into timeouts so that they are easy to test
+ yield '';
+ yield 'world';
+ };
+
+ $mockResponse = new MockResponse($body());
+
+.. _`cURL PHP extension`: https://php.net/curl
+.. _`PSR-17`: https://www.php-fig.org/psr/psr-17/
+.. _`PSR-18`: https://www.php-fig.org/psr/psr-18/
+.. _`Symfony Contracts`: https://github.com/symfony/contracts
diff --git a/components/http_foundation.rst b/components/http_foundation.rst
index aede539a852..3a728812b07 100644
--- a/components/http_foundation.rst
+++ b/components/http_foundation.rst
@@ -23,8 +23,6 @@ Installation
$ composer require symfony/http-foundation
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
.. seealso::
@@ -242,18 +240,9 @@ the
method tells you if the request contains a session which was started in one of
the previous requests.
-.. versionadded:: 4.1
- Using :method:`Symfony\\Component\\HttpFoundation\\Request::getSession`
- when no session has been set was deprecated in Symfony 4.1. It will throw
- an exception in Symfony 5.0 when the session is ``null``. Check for an existing session
- first by calling :method:`Symfony\\Component\\HttpFoundation\\Request::hasSession`.
-
Processing HTTP Headers
~~~~~~~~~~~~~~~~~~~~~~~
-.. versionadded:: 4.1
- The ``HeaderUtils`` class was introduced in Symfony 4.1.
-
Processing HTTP headers is not a trivial task because of the escaping and white
space handling of their contents. Symfony provides a
:class:`Symfony\\Component\\HttpFoundation\\HeaderUtils` class that abstracts
@@ -325,10 +314,6 @@ are also supported::
$quality = $accept->get('text/xml')->getQuality(); // $quality = 0.8
$quality = $accept->get('application/xml')->getQuality(); // $quality = 0.3
-.. versionadded:: 4.1
- The support of default values in the ``Accept-*`` headers was introduced in
- Symfony 4.1.
-
Accessing other Data
~~~~~~~~~~~~~~~~~~~~
@@ -428,7 +413,7 @@ attribute::
use Symfony\Component\HttpFoundation\Cookie;
- $response->headers->setCookie(new Cookie('foo', 'bar'));
+ $response->headers->setCookie(Cookie::create('foo', 'bar'));
The
:method:`Symfony\\Component\\HttpFoundation\\ResponseHeaderBag::setCookie`
@@ -547,17 +532,18 @@ Serving Files
When sending a file, you must add a ``Content-Disposition`` header to your
response. While creating this header for basic file downloads is straightforward,
using non-ASCII filenames is more involving. The
-:method:`Symfony\\Component\\HttpFoundation\\ResponseHeaderBag::makeDisposition`
+:method:`Symfony\\Component\\HttpFoundation\\HeaderUtils::makeDisposition`
abstracts the hard work behind a simple API::
+ use Symfony\Component\HttpFoundation\HeaderUtils;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
$fileContent = ...; // the generated file content
$response = new Response($fileContent);
- $disposition = $response->headers->makeDisposition(
- ResponseHeaderBag::DISPOSITION_ATTACHMENT,
+ $disposition = HeaderUtils::makeDisposition(
+ HeaderUtils::DISPOSITION_ATTACHMENT,
'foo.pdf'
);
@@ -580,6 +566,26 @@ if it should::
BinaryFileResponse::trustXSendfileTypeHeader();
+.. note::
+
+ The ``BinaryFileResponse`` will only handle ``X-Sendfile`` if the particular header is present.
+ For Apache, this is not the default case.
+
+ To add the header use the ``mod_headers`` Apache module and add the following to the Apache configuration:
+
+ .. code-block:: apache
+
+
+ # This is already present somewhere...
+ XSendFile on
+ XSendFilePath ...some path...
+
+ # This needs to be added:
+
+ RequestHeader set X-Sendfile-Type X-Sendfile
+
+
+
With the ``BinaryFileResponse``, you can still set the ``Content-Type`` of the sent file,
or change its ``Content-Disposition``::
@@ -692,7 +698,6 @@ Learn More
/session/*
/http_cache/*
-.. _Packagist: https://packagist.org/packages/symfony/http-foundation
.. _Nginx: https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile/
.. _Apache: https://tn123.org/mod_xsendfile/
.. _`JSON Hijacking`: http://haacked.com/archive/2009/06/25/json-hijacking.aspx
diff --git a/components/http_foundation/session_configuration.rst b/components/http_foundation/session_configuration.rst
index 327000a6c0c..547a98ad917 100644
--- a/components/http_foundation/session_configuration.rst
+++ b/components/http_foundation/session_configuration.rst
@@ -42,8 +42,8 @@ Symfony provides drivers for the following native save handler as an example:
Example usage::
use Symfony\Component\HttpFoundation\Session\Session;
- use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler;
+ use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
$sessionStorage = new NativeSessionStorage([], new NativeFileSessionHandler());
$session = new Session($sessionStorage);
@@ -81,8 +81,8 @@ serve as examples if you wish to write your own.
Example usage::
use Symfony\Component\HttpFoundation\Session\Session;
- use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
+ use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
$pdo = new \PDO(...);
$sessionStorage = new NativeSessionStorage([], new PdoSessionHandler($pdo));
@@ -91,9 +91,6 @@ Example usage::
Migrating Between Save Handlers
-------------------------------
-.. versionadded:: 4.1
- The ``MigratingSessionHandler`` class was introduced in Symfony 4.1.
-
If your application changes the way sessions are stored, use the
:class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MigratingSessionHandler`
to migrate between old and new save handlers without losing session data.
diff --git a/components/http_foundation/session_php_bridge.rst b/components/http_foundation/session_php_bridge.rst
index 295c0976854..00f57e59e4f 100644
--- a/components/http_foundation/session_php_bridge.rst
+++ b/components/http_foundation/session_php_bridge.rst
@@ -12,7 +12,7 @@ As stated elsewhere, Symfony Sessions are designed to replace the use of
PHP's native ``session_*()`` functions and use of the ``$_SESSION``
superglobal. Additionally, it is mandatory for Symfony to start the session.
-However when there really are circumstances where this is not possible, you
+However, when there really are circumstances where this is not possible, you
can use a special storage bridge
:class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\PhpBridgeSessionStorage`
which is designed to allow Symfony to work with a session started outside of
@@ -46,4 +46,3 @@ of your application to Symfony sessions.
cannot access arbitrary keys in ``$_SESSION`` that may be set by the legacy
application, although all the ``$_SESSION`` contents will be saved when
the session is saved.
-
diff --git a/components/http_foundation/session_testing.rst b/components/http_foundation/session_testing.rst
index 54a3363a4d2..7d8a570c17e 100644
--- a/components/http_foundation/session_testing.rst
+++ b/components/http_foundation/session_testing.rst
@@ -6,8 +6,8 @@ Testing with Sessions
=====================
Symfony is designed from the ground up with code-testability in mind. In order
-to make your code which utilizes session easily testable, we provide two separate
-mock storage mechanisms for both unit testing and functional testing.
+to test your code which utilizes sessions, we provide two separate mock storage
+mechanisms for both unit testing and functional testing.
Testing code using real sessions is tricky because PHP's workflow state is global
and it is not possible to have multiple concurrent sessions in the same PHP
@@ -40,8 +40,8 @@ For unit testing where it is not necessary to persist the session, you should
swap out the default storage engine with
:class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\MockArraySessionStorage`::
- use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
use Symfony\Component\HttpFoundation\Session\Session;
+ use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
$session = new Session(new MockArraySessionStorage());
diff --git a/components/http_foundation/sessions.rst b/components/http_foundation/sessions.rst
index e15d44411f6..7cb56159d71 100644
--- a/components/http_foundation/sessions.rst
+++ b/components/http_foundation/sessions.rst
@@ -207,6 +207,8 @@ Example::
// ...
$session->clear();
+.. _namespaced-attributes:
+
Namespaced Attributes
.....................
@@ -226,8 +228,7 @@ store it again::
],
];
-So any processing of this might quickly get ugly, even simply adding a token to
-the array::
+So any processing of this might quickly get ugly, even adding a token to the array::
$tokens = $session->get('tokens');
$tokens['c'] = $value;
diff --git a/components/http_kernel.rst b/components/http_kernel.rst
index e5c36fd4283..2b2ebe3b5fc 100644
--- a/components/http_kernel.rst
+++ b/components/http_kernel.rst
@@ -18,8 +18,6 @@ Installation
$ composer require symfony/http-kernel
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
The Workflow of a Request
@@ -47,12 +45,12 @@ This is a simplified overview of the request workflow in Symfony applications:
#. The **browser** displays the **resource** to the **user**.
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::
+tasks (e.g. routing, security, etc) so that a developer can 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;
@@ -103,12 +101,12 @@ not take many steps. You create an
(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\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
+ use Symfony\Component\HttpKernel\HttpKernel;
// create the Request object
$request = Request::createFromGlobals();
@@ -174,7 +172,7 @@ 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.
-Other listeners simply initialize things or add more information to the request.
+Other listeners initialize things or add more information to the request.
For example, a listener might determine and set the locale on the ``Request``
object.
@@ -294,7 +292,7 @@ have been determined (e.g. the controller, routing information) but before
the controller is executed. For some examples, see the Symfony section below.
Listeners to this event can also change the controller callable completely
-by calling :method:`FilterControllerEvent::setController `
+by calling :method:`ControllerEvent::setController `
on the event object that's passed to listeners on this event.
.. sidebar:: ``kernel.controller`` in the Symfony Framework
@@ -526,9 +524,9 @@ to the exception.
-Each listener to this event is passed a :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent`
+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\\GetResponseForExceptionEvent::getException`
+:method:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent::getException`
method. A typical listener on this event will check for a certain type of
exception and create an appropriate error ``Response``.
@@ -604,17 +602,31 @@ 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.finish_request ``KernelEvents::FINISH_REQUEST`` :class:`Symfony\\Component\\HttpKernel\\Event\\FinishRequestEvent`
-kernel.terminate ``KernelEvents::TERMINATE`` :class:`Symfony\\Component\\HttpKernel\\Event\\PostResponseEvent`
-kernel.exception ``KernelEvents::EXCEPTION`` :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent`
-===================== ================================ ===================================================================================
+=========================== ====================================== ========================================================================
+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`
+=========================== ====================================== ========================================================================
+
+.. deprecated:: 4.3
+
+ Since Symfony 4.3, most of the event classes were renamed.
+ The following old classes were deprecated:
+
+ * `GetResponseEvent` renamed to :class:`Symfony\\Component\\HttpKernel\\Event\\RequestEvent`
+ * `FilterControllerEvent` renamed to :class:`Symfony\\Component\\HttpKernel\\Event\\ControllerEvent`
+ * `FilterControllerArgumentsEvent` renamed to :class:`Symfony\\Component\\HttpKernel\\Event\\ControllerArgumentsEvent`
+ * `GetResponseForControllerResultEvent` renamed to :class:`Symfony\\Component\\HttpKernel\\Event\\ViewEvent`
+ * `FilterResponseEvent` renamed to :class:`Symfony\\Component\\HttpKernel\\Event\\ResponseEvent`
+ * `PostResponseEvent` renamed to :class:`Symfony\\Component\\HttpKernel\\Event\\TerminateEvent`
+ * `GetResponseForExceptionEvent` renamed to :class:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent`
.. _http-kernel-working-example:
@@ -710,10 +722,10 @@ can be used to check 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\Event\GetResponseEvent;
+ use Symfony\Component\HttpKernel\Event\RequestEvent;
// ...
- public function onKernelRequest(GetResponseEvent $event)
+ public function onKernelRequest(RequestEvent $event)
{
if (!$event->isMasterRequest()) {
return;
@@ -757,10 +769,8 @@ Learn more
/reference/events
-.. _Packagist: https://packagist.org/packages/symfony/http-kernel
.. _reflection: https://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
.. _`PHP FPM`: https://php.net/manual/en/install.fpm.php
.. _`SensioFrameworkExtraBundle`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html
.. _`@ParamConverter`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
diff --git a/components/inflector.rst b/components/inflector.rst
new file mode 100644
index 00000000000..5e9f4325884
--- /dev/null
+++ b/components/inflector.rst
@@ -0,0 +1,64 @@
+.. index::
+ single: Inflector
+ single: Components; Inflector
+
+The Inflector Component
+=======================
+
+ The Inflector component converts English words between their singular and
+ plural forms.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/inflector
+
+.. include:: /components/require_autoload.rst.inc
+
+When you May Need an Inflector
+------------------------------
+
+In some scenarios such as code generation and code introspection, it's usually
+required to convert words from/to singular/plural. For example, if you need to
+know which property is associated with an *adder* method, you must convert from
+plural to singular (``addStories()`` method -> ``$story`` property).
+
+Although most human languages define simple pluralization rules, they also
+define lots of exceptions. For example, the general rule in English is to add an
+``s`` at the end of the word (``book`` -> ``books``) but there are lots of
+exceptions even for common words (``woman`` -> ``women``, ``life`` -> ``lives``,
+``news`` -> ``news``, ``radius`` -> ``radii``, etc.)
+
+This component abstracts all those pluralization rules so you can convert
+from/to singular/plural with confidence. However, due to the complexity of the
+human languages, this component only provides support for the English language.
+
+Usage
+-----
+
+The Inflector component provides two static methods to convert from/to
+singular/plural::
+
+ use Symfony\Component\Inflector\Inflector;
+
+ Inflector::singularize('alumni'); // 'alumnus'
+ Inflector::singularize('knives'); // 'knife'
+ Inflector::singularize('mice'); // 'mouse'
+
+ Inflector::pluralize('grandchild'); // 'grandchildren'
+ Inflector::pluralize('news'); // 'news'
+ Inflector::pluralize('bacterium'); // 'bacteria'
+
+Sometimes it's not possible to determine a unique singular/plural form for the
+given word. In those cases, the methods return an array with all the possible
+forms::
+
+ use Symfony\Component\Inflector\Inflector;
+
+ Inflector::singularize('indices'); // ['index', 'indix', 'indice']
+ Inflector::singularize('leaves'); // ['leaf', 'leave', 'leaff']
+
+ Inflector::pluralize('matrix'); // ['matricies', 'matrixes']
+ Inflector::pluralize('person'); // ['persons', 'people']
diff --git a/components/intl.rst b/components/intl.rst
index 6a5fc5e3d7f..94ee45ff018 100644
--- a/components/intl.rst
+++ b/components/intl.rst
@@ -5,8 +5,8 @@
The Intl Component
==================
- A PHP replacement layer for the C `intl extension`_ that also provides
- access to the localization data of the `ICU library`_.
+ This component provides access to the localization data of the `ICU library`_.
+ It also provides a PHP replacement layer for the C `intl extension`_.
.. caution::
@@ -26,8 +26,6 @@ Installation
$ composer require symfony/intl
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
If you install the component via Composer, the following classes and functions
@@ -54,282 +52,290 @@ replace the intl classes:
Composer automatically exposes these classes in the global namespace.
-Writing and Reading Resource Bundles
-------------------------------------
+Accessing ICU Data
+------------------
-The :phpclass:`ResourceBundle` class is not currently supported 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:
+This component provides the following ICU data:
-* `TextBundleWriter`_
-* `PhpBundleWriter`_
-* `BinaryBundleReader`_
-* `PhpBundleReader`_
-* `BufferedBundleReader`_
-* `StructuredBundleReader`_
+* `Language and Script Names`_
+* `Country Names`_
+* `Locales`_
+* `Currencies`_
+* `Timezones`_
-Continue reading if you are interested in how to use these classes. Otherwise
-skip this section and jump to `Accessing ICU Data`_.
+Language and Script Names
+~~~~~~~~~~~~~~~~~~~~~~~~~
-TextBundleWriter
-~~~~~~~~~~~~~~~~
+The ``Languages`` class provides access to the name of all languages
+according to the `ISO 639-1 alpha-2`_ list and the `ISO 639-2 alpha-3`_ list::
-The :class:`Symfony\\Component\\Intl\\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::
+ use Symfony\Component\Intl\Languages;
- use Symfony\Component\Intl\ResourceBundle\Writer\TextBundleWriter;
- use Symfony\Component\Intl\ResourceBundle\Compiler\BundleCompiler;
+ \Locale::setDefault('en');
- $writer = new TextBundleWriter();
- $writer->write('/path/to/bundle', 'en', [
- 'Data' => [
- 'entry1',
- 'entry2',
- // ...
- ],
- ]);
+ $languages = Languages::getNames();
+ // ('languageCode' => 'languageName')
+ // => ['ab' => 'Abkhazian', 'ace' => 'Achinese', ...]
- $compiler = new BundleCompiler();
- $compiler->compile('/path/to/bundle', '/path/to/binary/bundle');
+ $language = Languages::getName('fr');
+ // => 'French'
-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.
+All methods accept the translation locale as the last, optional parameter,
+which defaults to the current default locale::
-PhpBundleWriter
-~~~~~~~~~~~~~~~
+ $languages = Languages::getNames('de');
+ // => ['ab' => 'Abchasisch', 'ace' => 'Aceh', ...]
-The :class:`Symfony\\Component\\Intl\\ResourceBundle\\Writer\\PhpBundleWriter`
-writes an array or an array-like object to a .php resource bundle::
+ $language = Languages::getName('fr', 'de');
+ // => 'Französisch'
- use Symfony\Component\Intl\ResourceBundle\Writer\PhpBundleWriter;
+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::
- $writer = new PhpBundleWriter();
- $writer->write('/path/to/bundle', 'en', [
- 'Data' => [
- 'entry1',
- 'entry2',
- // ...
- ],
- ]);
+ $isValidLanguage = Languages::exists($languageCode);
-BinaryBundleReader
-~~~~~~~~~~~~~~~~~~
+.. versionadded:: 4.3
-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::
+ The ``Languages`` class was introduced in Symfony 4.3.
- use Symfony\Component\Intl\ResourceBundle\Reader\BinaryBundleReader;
+The ``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)::
- $reader = new BinaryBundleReader();
- $data = $reader->read('/path/to/bundle', 'en');
+ use Symfony\Component\Intl\Scripts;
- var_dump($data['Data']['entry1']);
+ \Locale::setDefault('en');
-PhpBundleReader
-~~~~~~~~~~~~~~~
+ $scripts = Scripts::getNames();
+ // ('scriptCode' => 'scriptName')
+ // => ['Adlm' => 'Adlam', 'Afak' => 'Afaka', ...]
-The :class:`Symfony\\Component\\Intl\\ResourceBundle\\Reader\\PhpBundleReader`
-reads resource bundles from .php files and returns an array or an array-like
-object::
+ $script = Scripts::getName('Hans');
+ // => 'Simplified'
- use Symfony\Component\Intl\ResourceBundle\Reader\PhpBundleReader;
+All methods accept the translation locale as the last, optional parameter,
+which defaults to the current default locale::
- $reader = new PhpBundleReader();
- $data = $reader->read('/path/to/bundle', 'en');
+ $scripts = Scripts::getNames('de');
+ // => ['Adlm' => 'Adlam', 'Afak' => 'Afaka', ...]
- var_dump($data['Data']['entry1']);
+ $script = Scripts::getName('Hans', 'de');
+ // => 'Vereinfacht'
-BufferedBundleReader
-~~~~~~~~~~~~~~~~~~~~
+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::
-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::
+ $isValidScript = Scripts::exists($scriptCode);
- use Symfony\Component\Intl\ResourceBundle\Reader\BinaryBundleReader;
- use Symfony\Component\Intl\ResourceBundle\Reader\BufferedBundleReader;
+.. versionadded:: 4.3
- $reader = new BufferedBundleReader(new BinaryBundleReader(), 10);
+ The ``Scripts`` class was introduced in Symfony 4.3.
- // actually reads the file
- $data = $reader->read('/path/to/bundle', 'en');
+Country Names
+~~~~~~~~~~~~~
- // returns data from the buffer
- $data = $reader->read('/path/to/bundle', 'en');
+The ``Countries`` class provides access to the name of all countries according
+to the `ISO 3166-1 alpha-2`_ list of officially recognized countries and
+territories::
- // actually reads the file
- $data = $reader->read('/path/to/bundle', 'fr');
+ use Symfony\Component\Intl\Countries;
-StructuredBundleReader
-~~~~~~~~~~~~~~~~~~~~~~
+ \Locale::setDefault('en');
-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::
+ $countries = Countries::getNames();
+ // ('countryCode' => 'countryName')
+ // => ['AF' => 'Afghanistan', 'AX' => 'Åland Islands', ...]
- use Symfony\Component\Intl\ResourceBundle\Reader\BinaryBundleReader;
- use Symfony\Component\Intl\ResourceBundle\Reader\StructuredBundleReader;
+ $country = Countries::getName('GB');
+ // => 'United Kingdom'
- $reader = new StructuredBundleReader(new BinaryBundleReader());
+All methods accept the translation locale as the last, optional parameter,
+which defaults to the current default locale::
- $data = $reader->read('/path/to/bundle', 'en');
+ $countries = Countries::getNames('de');
+ // => ['AF' => 'Afghanistan', 'EG' => 'Ägypten', ...]
- // produces an error if the key "Data" does not exist
- var_dump($data['Data']['entry1']);
+ $country = Countries::getName('GB', 'de');
+ // => 'Vereinigtes Königreich'
- // returns null if the key "Data" does not exist
- var_dump($reader->readEntry('/path/to/bundle', 'en', ['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::
-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``::
+ $isValidCountry = Countries::exists($countryCode);
- var_dump($reader->readEntry(
- '/path/to/bundle',
- 'en',
- ['Data', 'entry1'],
- false
- ));
+.. versionadded:: 4.3
-Accessing ICU Data
-------------------
+ The ``Countries`` class was introduced in Symfony 4.3.
-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:
+Locales
+~~~~~~~
-* `Language and Script Names`_
-* `Country Names`_
-* `Locales`_
-* `Currencies`_
+A locale is the combination of a language and a region. For example, "Chinese"
+is the language and ``zh_Hans_MO`` is the locale for "Chinese" (language) +
+"Simplified" (script) + "Macau SAR China" (region). The ``Locales`` class
+provides access to the name of all locales::
-Language and Script Names
-~~~~~~~~~~~~~~~~~~~~~~~~~
+ use Symfony\Component\Intl\Locales;
-The translations of language and script names can be found in the language
-bundle::
+ \Locale::setDefault('en');
- use Symfony\Component\Intl\Intl;
+ $locales = Locales::getNames();
+ // ('localeCode' => 'localeName')
+ // => ['af' => 'Afrikaans', 'af_NA' => 'Afrikaans (Namibia)', ...]
- \Locale::setDefault('en');
+ $locale = Locales::getName('zh_Hans_MO');
+ // => 'Chinese (Simplified, Macau SAR China)'
- $languages = Intl::getLanguageBundle()->getLanguageNames();
- // => ['ab' => 'Abkhazian', ...]
+All methods accept the translation locale as the last, optional parameter,
+which defaults to the current default locale::
- $language = Intl::getLanguageBundle()->getLanguageName('de');
- // => 'German'
+ $locales = Locales::getNames('de');
+ // => ['af' => 'Afrikaans', 'af_NA' => 'Afrikaans (Namibia)', ...]
- $language = Intl::getLanguageBundle()->getLanguageName('de', 'AT');
- // => 'Austrian German'
+ $locale = Locales::getName('zh_Hans_MO', 'de');
+ // => 'Chinesisch (Vereinfacht, Sonderverwaltungsregion Macau)'
- $scripts = Intl::getLanguageBundle()->getScriptNames();
- // => ['Arab' => 'Arabic', ...]
+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::
- $script = Intl::getLanguageBundle()->getScriptName('Hans');
- // => 'Simplified'
+ $isValidLocale = Locales::exists($localeCode);
-All methods accept the translation locale as the last, optional parameter,
-which defaults to the current default locale::
+.. versionadded:: 4.3
- $languages = Intl::getLanguageBundle()->getLanguageNames('de');
- // => ['ab' => 'Abchasisch', ...]
+ The ``Locales`` class was introduced in Symfony 4.3.
-Country Names
-~~~~~~~~~~~~~
+Currencies
+~~~~~~~~~~
-The translations of country names can be found in the region bundle::
+The ``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');
- $countries = Intl::getRegionBundle()->getCountryNames();
- // => ['AF' => 'Afghanistan', ...]
+ $currencies = Currencies::getNames();
+ // ('currencyCode' => 'currencyName')
+ // => ['AFN' => 'Afghan Afghani', 'ALL' => 'Albanian Lek', ...]
- $country = Intl::getRegionBundle()->getCountryName('GB');
- // => 'United Kingdom'
+ $currency = Currencies::getName('INR');
+ // => 'Indian Rupee'
-All methods accept the translation locale as the last, optional parameter,
-which defaults to the current default locale::
+ $symbol = Currencies::getSymbol('INR');
+ // => '₹'
- $countries = Intl::getRegionBundle()->getCountryNames('de');
- // => ['AF' => 'Afghanistan', ...]
+ $fractionDigits = Currencies::getFractionDigits('INR');
+ // => 2
-Locales
-~~~~~~~
+ $roundingIncrement = Currencies::getRoundingIncrement('INR');
+ // => 0
+
+All methods (except for ``getFractionDigits()`` and ``getRoundingIncrement()``)
+accept the translation locale as the last, optional parameter, which defaults to
+the current default locale::
+
+ $currencies = Currencies::getNames('de');
+ // => ['AFN' => 'Afghanischer Afghani', 'EGP' => 'Ägyptisches Pfund', ...]
+
+ $currency = Currencies::getName('INR', 'de');
+ // => 'Indische Rupie'
-The translations of locale names can be found in the locale bundle::
+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::
- use Symfony\Component\Intl\Intl;
+ $isValidCurrency = Currencies::exists($currencyCode);
+
+.. versionadded:: 4.3
+
+ The ``Currencies`` class was introduced in Symfony 4.3.
+
+.. _component-intl-timezones:
+
+Timezones
+~~~~~~~~~
+
+The ``Timezones`` class provides several utilities related to timezones. First,
+you can get the name and values of all timezones in all languages::
+
+ use Symfony\Component\Intl\Timezones;
\Locale::setDefault('en');
- $locales = Intl::getLocaleBundle()->getLocaleNames();
- // => ['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');
- // => ['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();
- // => ['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
- $fractionDigits = Intl::getCurrencyBundle()->getFractionDigits('INR');
- // => 2
+ $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'
- $roundingIncrement = Intl::getCurrencyBundle()->getRoundingIncrement('INR');
- // => 0
+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::
-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::
+ $isValidTimezone = Timezones::exists($timezoneId);
- $currencies = Intl::getCurrencyBundle()->getCurrencyNames('de');
- // => ['AFN' => 'Afghanische Afghani', ...]
+.. versionadded:: 4.3
-That's all you need to know for now. Have fun coding!
+ The ``Timezones`` class was introduced in Symfony 4.3.
Learn more
----------
@@ -342,9 +348,14 @@ Learn more
/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: https://php.net/manual/en/book.intl.php
.. _install the intl extension: https://php.net/manual/en/intl.setup.php
.. _ICU library: http://site.icu-project.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
+.. _`UTC/GMT time offsets`: https://en.wikipedia.org/wiki/List_of_UTC_time_offsets
+.. _`daylight saving time (DST)`: https://en.wikipedia.org/wiki/Daylight_saving_time
+.. _`ISO 639-1 alpha-2`: https://en.wikipedia.org/wiki/ISO_639-1
+.. _`ISO 639-2 alpha-3`: https://en.wikipedia.org/wiki/ISO_639-2
diff --git a/components/ldap.rst b/components/ldap.rst
index 4747529b1e7..50b7eb5b917 100644
--- a/components/ldap.rst
+++ b/components/ldap.rst
@@ -14,8 +14,6 @@ Installation
$ composer require symfony/ldap
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
@@ -42,7 +40,7 @@ following options:
The encryption protocol: ``ssl``, ``tls`` or ``none`` (default)
``connection_string``
- You may use this option instead of ``host`` and ``port`` to connect to the
+ You may use this option instead of ``host`` and ``port`` to connect to the
LDAP server
``optReferrals``
@@ -103,14 +101,24 @@ array, you may use the
// Do something with the results array
+By default, LDAP queries use the ``Symfony\Component\Ldap\Adapter::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;
+
+ $query = $ldap->query('dc=symfony,dc=com', '...', ['scope' => Adapter::SCOPE_ONE]);
+
Creating or Updating Entries
----------------------------
The Ldap component provides means to create new LDAP entries, update or even
delete existing ones::
- use Symfony\Component\Ldap\Ldap;
use Symfony\Component\Ldap\Entry;
+ use Symfony\Component\Ldap\Ldap;
// ...
$entry = new Entry('cn=Fabien Potencier,dc=symfony,dc=com', [
@@ -137,15 +145,14 @@ delete existing ones::
// 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\Ldap;
use Symfony\Component\Ldap\Entry;
+ use Symfony\Component\Ldap\Ldap;
// ...
$entry = new Entry('cn=Fabien Potencier,dc=symfony,dc=com', [
@@ -165,12 +172,3 @@ Possible operation types are ``LDAP_MODIFY_BATCH_ADD``, ``LDAP_MODIFY_BATCH_REMO
``LDAP_MODIFY_BATCH_REMOVE_ALL``, ``LDAP_MODIFY_BATCH_REPLACE``. Parameter
``$values`` must be ``NULL`` when using ``LDAP_MODIFY_BATCH_REMOVE_ALL``
operation type.
-
-.. versionadded:: 4.2
- The ``applyOperations()`` method was introduced in Symfony 4.2.
-
-.. versionadded:: 4.1
- The ``addAttributeValues()`` and ``removeAttributeValues()`` methods
- were introduced in Symfony 4.1.
-
-.. _Packagist: https://packagist.org/packages/symfony/ldap
diff --git a/components/lock.rst b/components/lock.rst
index e49164b6c59..d6e1220f2b7 100644
--- a/components/lock.rst
+++ b/components/lock.rst
@@ -15,8 +15,6 @@ Installation
$ composer require symfony/lock
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
@@ -157,9 +155,54 @@ to reset the TTL to its original value::
// refresh the lock for 600 seconds (next refresh() call will be 30 seconds again)
$lock->refresh(600);
- .. versionadded:: 4.1
- The feature to pass a custom TTL as an argument of the ``refresh()``
- method was introduced in Symfony 4.1.
+This component also provides two useful methods related to expiring locks:
+``getExpiringDate()`` (which returns ``null`` or a ``\DateTimeImmutable``
+object) and ``isExpired()`` (which returns a boolean).
+
+The Owner of The Lock
+---------------------
+
+Locks that are acquired for the first time are owned[1]_ 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 of the fact that some lock stores have expiring locks (as seen and explained
+above), it is possible for an instance to lose the lock it acquired automatically::
+
+ // 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');
+ }
+
+.. caution::
+
+ 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!
+
+.. [1] 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.
Available Stores
----------------
@@ -173,8 +216,10 @@ Store Scope Blocking Expiring
============================================ ====== ======== ========
:ref:`FlockStore ` local yes no
:ref:`MemcachedStore ` remote no yes
+:ref:`PdoStore ` remote no yes
:ref:`RedisStore ` remote no yes
:ref:`SemaphoreStore ` local yes no
+:ref:`ZookeeperStore ` remote no no
============================================ ====== ======== ========
.. _lock-store-flock:
@@ -195,7 +240,7 @@ PHP process is terminated::
Beware that some file systems (such as some types of NFS) do not support
locking. In those cases, it's better to use a directory on a local disk
- drive or a remote store based on Redis or Memcached.
+ drive or a remote store based on PDO, Redis or Memcached.
.. _lock-store-memcached:
@@ -217,6 +262,45 @@ support blocking, and expects a TTL to avoid stalled locks::
Memcached does not support TTL lower than 1 second.
+.. _lock-store-pdo:
+
+PdoStore
+~~~~~~~~
+
+The PdoStore saves locks in an SQL database. It requires a `PDO`_ connection, a
+`Doctrine DBAL Connection`_, or a `Data Source Name (DSN)`_. This store does not
+support blocking, and expects a TTL to avoid stalled locks::
+
+ use Symfony\Component\Lock\Store\PdoStore;
+
+ // a PDO, a Doctrine DBAL connection or DSN for lazy connecting through PDO
+ $databaseConnectionOrDSN = 'mysql:host=127.0.0.1;dbname=lock';
+ $store = new PdoStore($databaseConnectionOrDSN, ['db_username' => 'myuser', 'db_password' => 'mypassword']);
+
+.. note::
+
+ This store does not support TTL lower than 1 second.
+
+Before storing locks in the database, you must create the table that stores
+the information. The store provides a method called
+:method:`Symfony\\Component\\Lock\\Store\\PdoStore::createTable`
+to set up this table for you according to the database engine used::
+
+ try {
+ $store->createTable();
+ } catch (\PDOException $exception) {
+ // the table could not be created for some reason
+ }
+
+A great way to set up the table in production is to call the ``createTable()``
+method in your local computer and then generate a
+:ref:`database migration `:
+
+.. code-block:: terminal
+
+ $ php bin/console doctrine:migrations:diff
+ $ php bin/console doctrine:migrations:migrate
+
.. _lock-store-redis:
RedisStore
@@ -256,9 +340,9 @@ is being acquired, it forwards the call to all the managed stores, and it
collects their responses. If a simple majority of stores have acquired the lock,
then the lock is considered as acquired; otherwise as not acquired::
- use Symfony\Component\Lock\Strategy\ConsensusStrategy;
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) {
@@ -281,6 +365,29 @@ the stores.
working when a single server fails (because this strategy requires that the
lock is acquired in 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
-----------
@@ -290,11 +397,13 @@ the component is used in the following way.
Remote Stores
~~~~~~~~~~~~~
-Remote stores (:ref:`MemcachedStore ` and
-:ref:`RedisStore `) use an unique token to recognize the true
-owner of the lock. This token is stored in the
-:class:`Symfony\\Component\\Lock\\Key` object and is used internally by the
-``Lock``, therefore this key must not be shared between processes (session,
+Remote stores (:ref:`MemcachedStore `,
+:ref:`PdoStore `,
+:ref:`RedisStore ` and
+:ref:`ZookeeperStore `) use a unique token to recognize
+the true owner of the lock. This token is stored in the
+:class:`Symfony\\Component\\Lock\\Key` object and is used internally by
+the ``Lock``, therefore this key must not be shared between processes (session,
caching, fork, ...).
.. caution::
@@ -313,11 +422,12 @@ different machines may allow two different processes to acquire the same ``Lock`
Expiring Stores
~~~~~~~~~~~~~~~
-Expiring stores (:ref:`MemcachedStore ` 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.
+Expiring stores (:ref:`MemcachedStore `,
+: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 it lifetime is over and the ``getRemainingLifetime()``
@@ -431,6 +541,30 @@ method uses the Memcached's ``flush()`` method which purges and removes everythi
The method ``flush()`` must not be called, or locks should be stored in a
dedicated Memcached service away from Cache.
+PdoStore
+~~~~~~~~~~
+
+The PdoStore relies on the `ACID`_ properties of the SQL engine.
+
+.. caution::
+
+ In a cluster configured with multiple primaries, ensure writes are
+ synchronously propagated to every nodes, or always use the same node.
+
+.. caution::
+
+ 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.
+
+.. caution::
+
+ To ensure locks don't expire prematurely; the TTLs should be set with
+ enough extra time to account for any clock drift between nodes.
+
RedisStore
~~~~~~~~~~
@@ -493,6 +627,30 @@ can be two running containers in parallel.
concurrent process on a new machine, check that other process are stopped
on the old one.
+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
~~~~~~~
@@ -501,6 +659,10 @@ 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.
+.. _`ACID`: https://en.wikipedia.org/wiki/ACID
.. _`locks`: https://en.wikipedia.org/wiki/Lock_(computer_science)
-.. _Packagist: https://packagist.org/packages/symfony/lock
-.. _`PHP semaphore functions`: http://php.net/manual/en/book.sem.php
+.. _`PHP semaphore functions`: https://php.net/manual/en/book.sem.php
+.. _`PDO`: https://php.net/pdo
+.. _`Doctrine DBAL Connection`: https://github.com/doctrine/dbal/blob/master/lib/Doctrine/DBAL/Connection.php
+.. _`Data Source Name (DSN)`: https://en.wikipedia.org/wiki/Data_source_name
+.. _`ZooKeeper`: https://zookeeper.apache.org/
diff --git a/components/mailer.rst b/components/mailer.rst
new file mode 100644
index 00000000000..36caa78c972
--- /dev/null
+++ b/components/mailer.rst
@@ -0,0 +1,169 @@
+.. index::
+ single: Mailer
+ single: Components; Mailer
+
+The Mailer Component
+====================
+
+ The Mailer component helps sending emails.
+
+If you're using the Symfony Framework, read the
+:doc:`Symfony Framework Mailer documentation `.
+
+.. versionadded:: 4.3
+
+ The Mailer component was introduced in Symfony 4.3 and it's still
+ considered an :doc:`experimental feature `.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/mailer
+
+.. include:: /components/require_autoload.rst.inc
+
+Usage
+-----
+
+The Mailer component has two main classes: a ``Transport`` and the ``Mailer`` itself::
+
+ use Symfony\Component\Mailer\Mailer;
+ use Symfony\Component\Mailer\Transport\Smtp\SmtpTransport;
+
+ $transport = new SmtpTransport('localhost');
+ $mailer = new Mailer($transport);
+ $mailer->send($email);
+
+The ``$email`` object is created via the :doc:`Mime component `.
+
+Transport
+---------
+
+The only transport that comes pre-installed is SMTP.
+
+Below is the list of other popular providers with built-in support:
+
+================== =============================================
+Service Install with
+================== =============================================
+Amazon SES ``composer require symfony/amazon-mailer``
+Gmail ``composer require symfony/google-mailer``
+MailChimp ``composer require symfony/mailchimp-mailer``
+Mailgun ``composer require symfony/mailgun-mailer``
+Postmark ``composer require symfony/postmark-mailer``
+SendGrid ``composer require symfony/sendgrid-mailer``
+================== =============================================
+
+For example, suppose you want to use Google's Gmail SMTP server. First, install
+it:
+
+.. code-block:: terminal
+
+ $ composer require symfony/google-mailer
+
+Then, use the SMTP Gmail transport::
+
+ use Symfony\Component\Mailer\Bridge\Google\Smtp\GmailTransport;
+
+ $transport = new GmailTransport('user', 'pass');
+ $mailer = new Mailer($transport);
+ $mailer->send($email);
+
+Each provider provides up to 3 transports: standard SMTP, HTTP (it uses the
+provider's API but the body is created by the mailer component), API (it uses
+the full API of the provider with no control over the body creation -- features
+might be limited as well).
+
+.. _mailer_dsn:
+
+The mailer component provides a convenient way to create a transport from a
+DSN::
+
+ use Symfony\Component\Mailer\Transport;
+
+ $transport = Transport::fromDsn($dsn);
+
+Where ``$dsn`` depends on the provider you want to use. For plain SMTP, use
+``smtp://user:pass@example.com`` or ``smtp://sendmail`` to use the ``sendmail``
+binary. For third-party providers, refers to the following table:
+
+==================== ================================== ================================== ================================
+ Provider SMTP HTTP API
+==================== ================================== ================================== ================================
+ Amazon SES smtp://ACCESS_KEY:SECRET_KEY@ses http://ACCESS_KEY:SECRET_KEY@ses api://ACCESS_KEY:SECRET_KEY@ses
+ Google Gmail smtp://USERNAME:PASSWORD@gmail n/a n/a
+ Mailchimp Mandrill smtp://USERNAME:PASSWORD@mandrill http://KEY@mandrill api://KEY@mandrill
+ Mailgun smtp://USERNAME:PASSWORD@mailgun http://KEY:DOMAIN@mailgun api://KEY:DOMAIN@mailgun
+ Postmark smtp://ID:ID@postmark n/a api://KEY@postmark
+ Sendgrid smtp://apikey:KEY@sendgrid n/a api://KEY@sendgrid
+==================== ================================== ================================== ================================
+
+Failover Transport
+------------------
+
+You can create failover transport with the help of `||` operator::
+
+ $dsn = 'api://id@postmark || smtp://key@sendgrid';
+
+So if the first transport fails, the mailer will attempt to send through the
+second transport.
+
+Round Robin
+-----------
+
+If you want to send emails by using multiple transports in a round-robin fashion,
+you can use the ``&&`` operator between the transports::
+
+ $dsn = 'api://id@postmark && smtp://key@sendgrid'
+
+Sending emails asynchronously
+-----------------------------
+
+If you want to send emails asynchronously, install the :doc:`Messenger component
+`.
+
+.. code-block:: terminal
+
+ $ composer require symfony/messenger
+
+Then, instantiate and pass a ``MessageBus`` as a second argument to ``Mailer``::
+
+ use Symfony\Component\Mailer\Mailer;
+ use Symfony\Component\Mailer\Messenger\MessageHandler;
+ use Symfony\Component\Mailer\Messenger\SendEmailMessage;
+ use Symfony\Component\Mailer\SmtpEnvelope;
+ use Symfony\Component\Mailer\Transport;
+ use Symfony\Component\Messenger\Handler\HandlersLocator;
+ use Symfony\Component\Messenger\MessageBus;
+ use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware;
+ use Symfony\Component\Mime\Address;
+
+ $dsn = 'change-dsn-accordingly';
+
+ $transport = Transport::fromDsn($dsn);
+ $handler = new MessageHandler($transport);
+
+ $bus = new MessageBus([
+ new HandleMessageMiddleware(new HandlersLocator([
+ SendEmailMessage::class => [$handler],
+ ])),
+ ]);
+
+ $mailer = new Mailer($transport, $bus);
+ $mailer->send($email);
+
+ // you can pass an optional Envelope
+ $mailer->send($email, new SmtpEnvelope(
+ new Address('sender@example.com'),
+ [
+ new Address('recipient@example.com'),
+ ]
+ ));
+
+Learn More
+-----------
+
+To learn more about how to use the mailer component, refer to the
+:doc:`Symfony Framework Mailer documentation `.
diff --git a/components/mercure.rst b/components/mercure.rst
new file mode 100644
index 00000000000..adfd7a38f5f
--- /dev/null
+++ b/components/mercure.rst
@@ -0,0 +1,46 @@
+.. index::
+ single: Mercure
+ single: Components; Mercure
+
+The Mercure Component
+=====================
+
+ `Mercure`_ is an open protocol allowing to push data updates to web
+ browsers and other HTTP clients in a convenient, fast, reliable
+ and battery-friendly way.
+ It is especially useful to publish real-time updates of resources served
+ through web APIs, to reactive web and mobile applications.
+
+The Mercure Component implements the "publisher" part of the Mercure Protocol.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/mercure
+
+.. include:: /components/require_autoload.rst.inc
+
+Usage
+-----
+
+The following example shows the component in action::
+
+ // change these values accordingly to your hub installation
+ define('HUB_URL', 'https://demo.mercure.rocks/hub');
+ define('JWT', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXJjdXJlIjp7InN1YnNjcmliZSI6WyJmb28iLCJiYXIiXSwicHVibGlzaCI6WyJmb28iXX19.LRLvirgONK13JgacQ_VbcjySbVhkSmHy3IznH3tA9PM');
+
+ use Symfony\Component\Mercure\Jwt\StaticJwtProvider;
+ use Symfony\Component\Mercure\Publisher;
+ use Symfony\Component\Mercure\Update;
+
+ $publisher = new Publisher(HUB_URL, new StaticJwtProvider(JWT));
+ // Serialize the update, and dispatch it to the hub, that will broadcast it to the clients
+ $id = $publisher(new Update('https://example.com/books/1.jsonld', 'Hi from Symfony!', ['target1', 'target2']));
+
+Read the full :doc:`Mercure integration documentation ` to learn
+about all the features of this component and its integration with the Symfony
+framework.
+
+.. _`Mercure`: https://mercure.rocks
diff --git a/components/messenger.rst b/components/messenger.rst
index faa03e8e875..bbe6a55c135 100644
--- a/components/messenger.rst
+++ b/components/messenger.rst
@@ -8,8 +8,8 @@ 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`_.
+ The component is greatly inspired by Matthias Noback's series of
+ `blog posts about command buses`_ and the `SimpleBus project`_.
.. seealso::
@@ -24,8 +24,6 @@ Installation
$ composer require symfony/messenger
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Concepts
@@ -55,15 +53,15 @@ Concepts
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 items to it or even
+ which means they can tweak the envelope, by adding stamps to it or even
replacing it, as well as interrupt the middleware chain.
**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 items*.
+ through *envelope stamps*.
-**Envelope Items**
+**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.
@@ -78,19 +76,24 @@ When using the message bus with Symfony's FrameworkBundle, the following middlew
are configured for you:
#. :class:`Symfony\\Component\\Messenger\\Middleware\\LoggingMiddleware` (logs the processing of your messages)
-#. :class:`Symfony\\Component\\Messenger\\Asynchronous\\Middleware\\SendMessageMiddleware` (enables asynchronous processing)
+#. :class:`Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware` (enables asynchronous processing)
#. :class:`Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware` (calls the registered handler(s))
+.. deprecated:: 4.3
+
+ The ``LoggingMiddleware`` is deprecated since Symfony 4.3 and will be
+ removed in 5.0. Pass a logger to ``SendMessageMiddleware`` instead.
+
Example::
use App\Message\MyMessage;
+ use Symfony\Component\Messenger\Handler\HandlersLocator;
use Symfony\Component\Messenger\MessageBus;
- use Symfony\Component\Messenger\Handler\Locator\HandlerLocator;
use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware;
$bus = new MessageBus([
- new HandleMessageMiddleware(new HandlerLocator([
- MyMessage::class => $handler,
+ new HandleMessageMiddleware(new HandlersLocator([
+ MyMessage::class => ['dummy' => $handler],
])),
]);
@@ -113,72 +116,83 @@ that will do the required processing for your message::
class MyMessageHandler
{
- public function __invoke(MyMessage $message)
- {
- // Message processing...
- }
+ public function __invoke(MyMessage $message)
+ {
+ // Message processing...
+ }
}
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. For example, to set the
-serialization groups used when the message goes through the transport layer, use
-the ``SerializerConfiguration`` envelope::
+: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\Transport\Serialization\SerializerConfiguration;
+ use Symfony\Component\Messenger\Stamp\SerializerStamp;
$bus->dispatch(
- (new Envelope($message))->with(new SerializerConfiguration([
+ (new Envelope($message))->with(new SerializerStamp([
'groups' => ['my_serialization_groups'],
]))
);
-At the moment, the Symfony Messenger has the following built-in envelope items:
+At the moment, the Symfony Messenger has the following built-in envelope stamps:
-#. :class:`Symfony\\Component\\Messenger\\Transport\\Serialization\\SerializerConfiguration`,
+#. :class:`Symfony\\Component\\Messenger\\Stamp\\SerializerStamp`,
to configure the serialization groups used by the transport.
-#. :class:`Symfony\\Component\\Messenger\\Middleware\\Configuration\\ValidationConfiguration`,
+#. :class:`Symfony\\Component\\Messenger\\Stamp\\ValidationStamp`,
to configure the validation groups used when the validation middleware is enabled.
-#. :class:`Symfony\\Component\\Messenger\\Asynchronous\\Transport\\ReceivedMessage`,
- an internal item that marks the message as received from a transport.
-
-Instead of dealing directly with the messages in the middleware you can receive the
-envelope by implementing the :class:`Symfony\\Component\\Messenger\\EnvelopeAwareInterface`
-marker, like this::
-
- use Symfony\Component\Messenger\Asynchronous\Transport\ReceivedMessage;
+#. :class:`Symfony\\Component\\Messenger\\Stamp\\ReceivedStamp`,
+ an internal stamp that marks the message as received from a transport.
+#. :class:`Symfony\\Component\\Messenger\\Stamp\\SentStamp`,
+ a stamp that marks the message as sent by a specific sender.
+ Allows accessing the sender FQCN and the alias if available from the
+ :class:`Symfony\\Component\\Messenger\\Transport\\Sender\\SendersLocator`.
+#. :class:`Symfony\\Component\\Messenger\\Stamp\\HandledStamp`,
+ a stamp that marks the message as handled by a specific handler.
+ Allows accessing the handler returned value, the handler callable name
+ and its alias if available from the :class:`Symfony\\Component\\Messenger\\Handler\\HandlersLocator`.
+
+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\Middleware\MiddlewareInterface;
- use Symfony\Component\Messenger\EnvelopeAwareInterface;
+ use Symfony\Component\Messenger\Middleware\StackInterface;
+ use Symfony\Component\Messenger\Stamp\ReceivedStamp;
- class MyOwnMiddleware implements MiddlewareInterface, EnvelopeAwareInterface
+ class MyOwnMiddleware implements MiddlewareInterface
{
- public function handle($envelope, callable $next)
+ public function handle(Envelope $envelope, StackInterface $stack): Envelope
{
- // $envelope here is an `Envelope` object, because this middleware
- // implements the EnvelopeAwareInterface interface.
-
- if (null !== $envelope->get(ReceivedMessage::class)) {
+ if (null !== $envelope->last(ReceivedStamp::class)) {
// Message just has been received...
- // You could for example add another item.
- $envelope = $envelope->with(new AnotherEnvelopeItem(/* ... */));
+ // You could for example add another stamp.
+ $envelope = $envelope->with(new AnotherStamp(/* ... */));
}
- return $next($envelope);
+ return $stack->next()->handle($envelope, $stack);
}
}
-The above example will forward the message to the next middleware with an additional
-envelope item *if* the message has just been received (i.e. has the `ReceivedMessage` item).
-You can create your own items by implementing :class:`Symfony\\Component\\Messenger\\EnvelopeAwareInterface`.
+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 envelope item must be php serializable if going through transport using
- the :class:`Symfony\\Component\\Messenger\\Transport\\Serialization\\Serializer`
+ 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
@@ -190,48 +204,50 @@ transport will be responsible for communicating with your message broker or 3rd
Your own Sender
~~~~~~~~~~~~~~~
-Using the :class:`Symfony\\Component\\Messenger\\Transport\\SenderInterface`,
-you can create your own message 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.
+message as an email (using the :doc:`Mime ` and
+:doc:`Mailer ` components).
-First, create your sender::
+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\Messenger\Transport\SenderInterface;
+ 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
{
- private $mailer;
- private $toEmail;
-
- public function __construct(\Swift_Mailer $mailer, string $toEmail)
- {
- $this->mailer = $mailer;
- $this->toEmail = $toEmail;
- }
-
- public function send(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 \Swift_Message('Important action made'))
- ->setTo($this->toEmail)
- ->setBody(
- 'Important action Made by '.$message->getUsername().'
',
- 'text/html'
- )
- );
- }
+ private $mailer;
+ private $toEmail;
+
+ public function __construct(MailerInterface $mailer, string $toEmail)
+ {
+ $this->mailer = $mailer;
+ $this->toEmail = $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
@@ -246,55 +262,69 @@ 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 custom CSV receiver and Symfony will do the rest.
-
-First, create your receiver::
+do is to write your own CSV receiver::
namespace App\MessageReceiver;
use App\Message\NewOrder;
- use Symfony\Component\Messenger\Transport\ReceiverInterface;
- use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Messenger\Envelope;
+ use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
+ use Symfony\Component\Serializer\SerializerInterface;
class NewOrdersFromCsvFileReceiver implements ReceiverInterface
{
- private $serializer;
- private $filePath;
-
- public function __construct(SerializerInterface $serializer, string $filePath)
- {
- $this->serializer = $serializer;
- $this->filePath = $filePath;
- }
-
- public function receive(callable $handler): void
- {
- $ordersFromCsv = $this->serializer->deserialize(file_get_contents($this->filePath), 'csv');
-
- foreach ($ordersFromCsv as $orderFromCsv) {
- $order = new NewOrder($orderFromCsv['id'], $orderFromCsv['account_id'], $orderFromCsv['amount']);
-
- $handler(new Envelope($order));
- }
- }
-
- public function stop(): void
- {
- // noop
- }
+ private $serializer;
+ private $filePath;
+
+ public function __construct(SerializerInterface $serializer, string $filePath)
+ {
+ $this->serializer = $serializer;
+ $this->filePath = $filePath;
+ }
+
+ public function get(): iterable
+ {
+ $ordersFromCsv = $this->serializer->deserialize(file_get_contents($this->filePath), 'csv');
+
+ foreach ($ordersFromCsv as $orderFromCsv) {
+ $order = new NewOrder($orderFromCsv['id'], $orderFromCsv['account_id'], $orderFromCsv['amount']);
+
+ $envelope = new Envelope($order);
+
+ $handler($envelope);
+ }
+
+ return [$envelope];
+ }
+
+ public function ack(Envelope $envelope): void
+ {
+ // Add information about the handled message
+ }
+
+ public function reject(Envelope $envelope): void
+ {
+ // Reject the message if needed
+ }
}
+.. versionadded:: 4.3
+
+ In Symfony 4.3, the ``ReceiverInterface`` has changed its methods as shown
+ in the example above. You may need to update your code if you used this
+ interface in previous Symfony versions.
+
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\\Asynchronous\\Transport\\ReceivedMessage`
-envelope item to the message envelopes and the :class:`Symfony\\Component\\Messenger\\Asynchronous\\Middleware\\SendMessageMiddleware`
+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:
@@ -302,5 +332,5 @@ Learn more
/messenger
/messenger/*
-.. _blog posts about command buses: https://matthiasnoback.nl/tags/command%20bus/
-.. _SimpleBus project: http://simplebus.io
+.. _`blog posts about command buses`: https://matthiasnoback.nl/tags/command%20bus/
+.. _`SimpleBus project`: http://docs.simplebus.io/en/latest/
diff --git a/components/mime.rst b/components/mime.rst
new file mode 100644
index 00000000000..501b10a8dc5
--- /dev/null
+++ b/components/mime.rst
@@ -0,0 +1,306 @@
+.. index::
+ single: MIME
+ single: MIME Messages
+ single: Components; MIME
+
+The Mime Component
+==================
+
+ The Mime component allows manipulating the MIME messages used to send emails
+ and provides utilities related to MIME types.
+
+.. versionadded:: 4.3
+
+ The Mime component was introduced in Symfony 4.3 and it's still
+ considered an :doc:`experimental feature `.
+
+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 an 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 ...
')
+ ;
+
+This only purpose of this component is to create the email messages. Use the
+:doc:`Mailer component ` to actually send them. In Symfony
+applications, it's easier to use the :doc:`Mailer integration `.
+
+Most of the details about how to create Email objects, including Twig integration,
+can be found in the :doc:`Mailer documentation `.
+
+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-extension
+
+Now, enable the extension::
+
+ // ...
+ use Twig\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 ...
', '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
+ ), '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']
+
+ $mimeTypes = $mimeTypes->getMimeTypes('js');
+ // $mimeTypes = ['application/javascript', 'application/x-javascript', 'text/javascript']
+ $mimeTypes = $mimeTypes->getMimeTypes('apk');
+ // $mimeTypes = ['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 register 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`: https://en.wikipedia.org/wiki/MIME
+.. _`MIME types`: https://en.wikipedia.org/wiki/Media_type
+.. _`fileinfo extension`: https://php.net/fileinfo
diff --git a/components/options_resolver.rst b/components/options_resolver.rst
index bcebfcf5fe7..4c2ef3c1254 100644
--- a/components/options_resolver.rst
+++ b/components/options_resolver.rst
@@ -5,9 +5,10 @@
The OptionsResolver Component
=============================
- The OptionsResolver component is :phpfunction:`array_replace` on steroids.
- It allows you to create an options system with required options, defaults,
- validation (type, value), normalization and more.
+ 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
------------
@@ -16,8 +17,6 @@ Installation
$ composer require symfony/options-resolver
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
@@ -36,7 +35,7 @@ Imagine you have a ``Mailer`` class which has four options: ``host``,
}
}
-When accessing the ``$options``, you need to add a lot of boilerplate code to
+When accessing the ``$options``, you need to add some boilerplate code to
check which options are set::
class Mailer
@@ -46,29 +45,17 @@ check which options are set::
{
$mail = ...;
- $mail->setHost(isset($this->options['host'])
- ? $this->options['host']
- : 'smtp.example.org');
-
- $mail->setUsername(isset($this->options['username'])
- ? $this->options['username']
- : 'user');
-
- $mail->setPassword(isset($this->options['password'])
- ? $this->options['password']
- : 'pa$$word');
-
- $mail->setPort(isset($this->options['port'])
- ? $this->options['port']
- : 25);
+ $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);
// ...
}
}
-This boilerplate is hard to read and repetitive. Also, the default values of the
-options are buried in the business logic of your code. Use the
-:phpfunction:`array_replace` to fix that::
+Also, the default values of the options are buried in the business logic of your
+code. Use the :phpfunction:`array_replace` to fix that::
class Mailer
{
@@ -85,13 +72,11 @@ options are buried in the business logic of your code. Use the
}
}
-Now all four options are guaranteed to be set. But what happens if the user of
-the ``Mailer`` class makes a mistake?
-
-.. code-block:: php
+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', // usernme misspelled (instead of username)
+ 'usernme' => 'johndoe', // 'username' is wrongly spelled as 'usernme'
]);
No error will be shown. In the best case, the bug will appear during testing,
@@ -449,6 +434,16 @@ if you need to use other options during normalization::
}
}
+To normalize a new allowed value in sub-classes that are being normalized
+in parent classes use :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::addNormalizer`.
+This way, the ``$value`` argument will receive the previously normalized
+value, otherwise you can prepend the new normalizer by passing ``true`` as
+third argument.
+
+.. versionadded:: 4.3
+
+ The ``addNormalizer()`` method was introduced in Symfony 4.3.
+
Default Values that Depend on another Option
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -534,8 +529,8 @@ Options without Default Values
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 simply
-comes from the default::
+it's not possible to know whether the user passed this value or if it comes
+from the default::
// ...
class Mailer
@@ -634,6 +629,159 @@ let you find out which options are defined::
}
}
+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)
+ {
+ $resolver->setDefault('spool', function (OptionsResolver $spoolResolver) {
+ $spoolResolver->setDefaults([
+ 'type' => 'file',
+ 'path' => '/path/to/spool',
+ ]);
+ $spoolResolver->setAllowedValues('type', ['file', 'memory']);
+ $spoolResolver->setAllowedTypes('path', 'string');
+ });
+ }
+
+ public function sendMail($from, $to)
+ {
+ if ('memory' === $this->options['spool']['type']) {
+ // ...
+ }
+ }
+ }
+
+ $mailer = new Mailer([
+ 'spool' => [
+ 'type' => 'memory',
+ ],
+ ]);
+
+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)
+ {
+ $resolver->setDefault('sandbox', false);
+ $resolver->setDefault('spool', function (OptionsResolver $spoolResolver, Options $parent) {
+ $spoolResolver->setDefaults([
+ 'type' => $parent['sandbox'] ? 'memory' : 'file',
+ // ...
+ ]);
+ });
+ }
+ }
+
+.. caution::
+
+ 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)
+ {
+ $resolver->setDefault('spool', function (OptionsResolver $spoolResolver) {
+ $spoolResolver->setDefaults([
+ 'type' => 'file',
+ // ...
+ ]);
+ });
+ $resolver->setDefault('profiling', function (Options $options) {
+ 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.
+
+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:
+ // The option "hostname" is deprecated.
+ ->setDeprecated('hostname')
+
+ // you can also pass a custom deprecation message
+ ->setDeprecated('hostname', 'The option "hostname" 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\\Options::offsetGet()` method
+ to not trigger the deprecation warning.
+
+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', function (Options $options, $value) {
+ 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.
+
Performance Tweaks
~~~~~~~~~~~~~~~~~~
@@ -691,6 +839,3 @@ method ``clearOptionsConfig()`` and call it periodically::
That's it! You now have all the tools and knowledge needed to process
options in your code.
-
-.. _Packagist: https://packagist.org/packages/symfony/options-resolver
-.. _CHANGELOG: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/OptionsResolver/CHANGELOG.md#260
diff --git a/components/phpunit_bridge.rst b/components/phpunit_bridge.rst
index 9a55e5e936f..cf1a19727fd 100644
--- a/components/phpunit_bridge.rst
+++ b/components/phpunit_bridge.rst
@@ -6,11 +6,13 @@ The PHPUnit Bridge
==================
The PHPUnit Bridge provides utilities to report legacy tests and usage of
- deprecated code and a helper for time-sensitive tests.
+ deprecated code and helpers for mocking native functions related to time,
+ DNS and class existence.
It comes with the following features:
-* Forces the tests to use a consistent locale (``C``);
+* Forces the tests to use a consistent locale (``C``) (if you create
+ locale-sensitive tests, use PHPUnit's ``setLocale()`` method);
* Auto-register ``class_exists`` to load Doctrine annotations (when used);
@@ -18,19 +20,20 @@ It comes with the following features:
* Displays the stack trace of a deprecation on-demand;
-* Provides a ``ClockMock`` and ``DnsMock`` helper classes for time or network-sensitive tests.
+* Provides a ``ClockMock``, ``DnsMock`` and ``ClassExistsMock`` classes for tests
+ sensitive to time, network or class existence;
-* Provides a modified version of PHPUnit that does not embed ``symfony/yaml`` nor
- ``prophecy`` to prevent any conflicts with these dependencies.
+* Provides a modified version of PHPUnit that allows 1. separating the
+ dependencies of your app from those of phpunit to prevent any unwanted
+ constraints to apply; 2. running tests in parallel when a test suite is split
+ in several phpunit.xml files; 3. recording and replaying skipped tests;
Installation
------------
.. code-block:: terminal
- $ composer require --dev "symfony/phpunit-bridge:*"
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require --dev symfony/phpunit-bridge
.. include:: /components/require_autoload.rst.inc
@@ -49,13 +52,13 @@ to register a new `test listener`_ called ``SymfonyTestsListener``:
-
+
@@ -122,9 +125,35 @@ The summary includes:
-
+
+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 subfolders 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
---------------------------
@@ -173,59 +202,133 @@ message, enclosed with ``/``. For example, with:
-
-
+
+
-PHPUnit_ will stop your test suite once a deprecation notice is triggered whose
+`PHPUnit`_ will stop your test suite once a deprecation notice is triggered whose
message contains the ``"foobar"`` string.
Making Tests Fail
~~~~~~~~~~~~~~~~~
-By default, any non-legacy-tagged or any non-`@-silenced`_ deprecation notices
-will make tests fail. Alternatively, setting ``SYMFONY_DEPRECATIONS_HELPER`` to
-an arbitrary value (ex: ``320``) will make the tests fails only if a higher
-number of deprecation notices is reached (``0`` is the default value). You can
-also set the value ``"weak"`` which will make the bridge ignore any deprecation
-notices. This is useful to projects that must use deprecated interfaces for
-backward compatibility reasons.
+By default, any non-legacy-tagged or any non-`@-silenced`_ deprecation
+notices will make tests fail. Alternatively, you can configure an
+arbitrary threshold by setting ``SYMFONY_DEPRECATIONS_HELPER`` to
+``max[total]=320`` for instance. It will make the tests fails only if a
+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 an 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
+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=weak`` because of this. This has
-the drawback of allowing contributions that introduce deprecations but:
+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 the ``"weak_vendors"`` value, deprecations that are triggered outside
-the ``vendors`` directory will make the test suite fail, while deprecations
-triggered from a library inside it will not, giving you the best of both
-worlds.
+By using ``SYMFONY_DEPRECATIONS_HELPER=max[self]=0``, deprecations that are
+triggered outside the ``vendors`` directory will be accounted for seperately,
+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. |
++------------------------+-----------------------------------------------------+
+
+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.
Disabling the Deprecation Helper
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Set the ``SYMFONY_DEPRECATIONS_HELPER`` environment variable to ``disabled`` to
-completely disable the deprecation helper. This is useful to make use of the
+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.
.. _write-assertions-about-deprecations:
+Deprecation Notices at Autoloading Time
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, the PHPUnit Bridge uses ``DebugClassLoader`` from the
+:doc:`Debug component ` to throw deprecation notices at
+class autoloading time. This can be disabled with the ``debug-class-loader`` option.
+
+.. code-block:: xml
+
+
+
+
+
+
+
+
+ 0
+
+
+
+
+
+.. versionadded:: 4.2
+
+ The ``DebugClassLoader`` integration was introduced in Symfony 4.2.
+
Write Assertions about Deprecations
-----------------------------------
@@ -255,7 +358,9 @@ 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::
+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
@@ -305,7 +410,10 @@ 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()`` and ``usleep()``.
+``microtime()``, ``sleep()``, ``usleep()`` and ``gmdate()``. 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.
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
@@ -317,7 +425,7 @@ following listener in your PHPUnit configuration:
-
+
.. note::
@@ -377,6 +485,7 @@ different class, do it explicitly using ``ClockMock::register(MyClass::class)``:
use App\MyClass;
use PHPUnit\Framework\TestCase;
+ use Symfony\Bridge\PhpUnit\ClockMock;
/**
* @group time-sensitive
@@ -438,6 +547,7 @@ constraint to test the validity of the email domain::
$result = $validator->validate('foo@example.com', $constraint);
// ...
+ }
}
In order to avoid making a real network connection, add the ``@dns-sensitive``
@@ -462,6 +572,7 @@ the data you expect to get for the given hosts::
$result = $validator->validate('foo@example.com', $constraint);
// ...
+ }
}
The ``withMockedHosts()`` method configuration is defined as an array. The keys
@@ -482,6 +593,78 @@ conditions::
],
]);
+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`
+
+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 bahavior.';
+ }
+
+ 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()
+ {
+ $class = new MyClass();
+ $result = $class->hello(); // "The dependency bahavior."
+
+ // ...
+ }
+ }
+
+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()
+ {
+ ClassExistsMock::register(MyClass::class);
+ ClassExistsMock::withMockedClasses([DependencyClass::class => false]);
+
+ $class = new MyClass();
+ $result = $class->hello(); // "The default bahavior."
+
+ // ...
+ }
+ }
+
Troubleshooting
---------------
@@ -500,7 +683,7 @@ namespaces in the ``phpunit.xml`` file, as done for example in the
@@ -543,8 +726,8 @@ 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:
-* Does not embed ``symfony/yaml`` nor ``prophecy`` to prevent any conflicts with
- these dependencies;
+* Works with a standalone vendor directory that doesn't conflict with yours;
+* Does not embed ``prophecy`` to prevent any conflicts with its dependencies;
* Uses PHPUnit 4.8 when run with PHP <=5.5, PHPUnit 5.7 when run with PHP >=5.6
and PHPUnit 6.5 when run with PHP >=7.2;
* Collects and replays skipped tests when the ``SYMFONY_PHPUNIT_SKIPPED_TESTS``
@@ -562,16 +745,19 @@ 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:: bash
+.. code-block:: terminal
$ vendor/bin/simple-phpunit
.. tip::
- Set the ``SYMFONY_PHPUNIT_VERSION`` env var to e.g. ``5.5`` to change the
- base version of PHPUnit to ``5.5`` instead of the default ``5.3``.
+ It's possible to change the base version of PHPUnit 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 this env var in the ``phpunit.xml.dist`` file.
+ It's also possible to set ``SYMFONY_PHPUNIT_VERSION`` as a real env var
+ (not defined in a :ref:`dotenv file `).
.. tip::
@@ -580,11 +766,11 @@ If you have installed the bridge through Composer, you can run it by calling e.g
It's also possible to set this env var in the ``phpunit.xml.dist`` file.
-Code coverage listener
+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. And the test which executes a
+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.
@@ -639,19 +825,19 @@ the ``Test`` part of the classname: ``My\Namespace\Tests\FooTest`` ->
Installation
~~~~~~~~~~~~
-Add the following configuration to the ``phpunit.xml.dist`` file
+Add the following configuration to the ``phpunit.xml.dist`` file:
.. code-block:: xml
-
+
@@ -685,12 +871,11 @@ not find the SUT:
-.. _PHPUnit: https://phpunit.de
+.. _`PHPUnit`: https://phpunit.de
.. _`PHPUnit event listener`: https://phpunit.de/manual/current/en/extending-phpunit.html#extending-phpunit.PHPUnit_Framework_TestListener
.. _`PHPUnit's assertStringMatchesFormat()`: https://phpunit.de/manual/current/en/appendixes.assertions.html#appendixes.assertions.assertStringMatchesFormat
.. _`PHP error handler`: https://php.net/manual/en/book.errorfunc.php
.. _`environment variable`: https://phpunit.de/manual/current/en/appendixes.configuration.html#appendixes.configuration.php-ini-constants-variables
-.. _Packagist: https://packagist.org/packages/symfony/phpunit-bridge
.. _`@-silencing operator`: https://php.net/manual/en/language.operators.errorcontrol.php
.. _`@-silenced`: https://php.net/manual/en/language.operators.errorcontrol.php
.. _`Travis CI`: https://travis-ci.org/
diff --git a/components/polyfill_apcu.rst b/components/polyfill_apcu.rst
index b3b60e95566..80f7aeef809 100644
--- a/components/polyfill_apcu.rst
+++ b/components/polyfill_apcu.rst
@@ -16,8 +16,6 @@ Installation
$ composer require symfony/polyfill-apcu
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
diff --git a/components/polyfill_ctype.rst b/components/polyfill_ctype.rst
index c77af0bc874..f685a8563a2 100644
--- a/components/polyfill_ctype.rst
+++ b/components/polyfill_ctype.rst
@@ -16,8 +16,6 @@ Installation
$ composer require symfony/polyfill-ctype
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
diff --git a/components/polyfill_iconv.rst b/components/polyfill_iconv.rst
index 56346186c60..e50b926e6bd 100644
--- a/components/polyfill_iconv.rst
+++ b/components/polyfill_iconv.rst
@@ -16,8 +16,6 @@ Installation
$ composer require symfony/polyfill-iconv
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
@@ -57,5 +55,3 @@ extension are installed:
* :phpfunction:`iconv_mime_decode`
.. _`PHP iconv extension`: https://secure.php.net/manual/en/book.iconv.php
-.. _`mbstring`: https://secure.php.net/manual/en/book.mbstring.php
-.. _`xml`: https://secure.php.net/manual/en/book.xml.php
diff --git a/components/polyfill_intl_grapheme.rst b/components/polyfill_intl_grapheme.rst
index bad42f179fc..cb5b020c59b 100644
--- a/components/polyfill_intl_grapheme.rst
+++ b/components/polyfill_intl_grapheme.rst
@@ -17,8 +17,6 @@ Installation
$ composer require symfony/polyfill-intl-grapheme
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
@@ -50,9 +48,11 @@ Provided Functions
.. seealso::
- The :doc:`polyfill-intl-icu ` and
- :doc:`polyfill-intl-normalizer `
- components provide polyfills for other classes and functions related to the
- Intl PHP extension.
+ Symfony provides more polyfills for other classes and functions related to
+ the Intl PHP extension:
+ :doc:`polyfill-intl-icu `,
+ :doc:`polyfill-intl-idn `,
+ :doc:`polyfill-intl-messageformatter `,
+ and :doc:`polyfill-intl-normalizer `.
.. _`PHP intl extension`: https://secure.php.net/manual/en/book.intl.php
diff --git a/components/polyfill_intl_icu.rst b/components/polyfill_intl_icu.rst
index 4dc7c503afc..a00fb7ed35c 100644
--- a/components/polyfill_intl_icu.rst
+++ b/components/polyfill_intl_icu.rst
@@ -17,8 +17,6 @@ Installation
$ composer require symfony/polyfill-intl-icu
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
@@ -46,9 +44,11 @@ Provided Functions
.. seealso::
- The :doc:`polyfill-intl-grapheme ` and
- :doc:`polyfill-intl-normalizer `
- components provide polyfills for other classes and functions related to the
- Intl PHP extension.
+ Symfony provides more polyfills for other classes and functions related to
+ the Intl PHP extension:
+ :doc:`polyfill-intl-grapheme `,
+ :doc:`polyfill-intl-idn `,
+ :doc:`polyfill-intl-messageformatter `,
+ and :doc:`polyfill-intl-normalizer `.
.. _`PHP intl extension`: https://secure.php.net/manual/en/book.intl.php
diff --git a/components/polyfill_intl_idn.rst b/components/polyfill_intl_idn.rst
new file mode 100644
index 00000000000..9314f0af35b
--- /dev/null
+++ b/components/polyfill_intl_idn.rst
@@ -0,0 +1,43 @@
+.. index::
+ single: Polyfill
+ single: IDN
+ single: Components; Polyfill
+
+The Symfony Polyfill / Intl IDN Component
+=========================================
+
+ This component provides a collection of functions related to IDN when the
+ Intl extension is not installed.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/polyfill-intl-idn
+
+.. include:: /components/require_autoload.rst.inc
+
+Usage
+-----
+
+Once this component is installed in your application, you can use the following
+functions, no matter if the `PHP intl extension`_ is installed or not in your
+server.
+
+Provided Functions
+~~~~~~~~~~~~~~~~~~
+
+* :phpfunction:`idn_to_ascii`
+* :phpfunction:`idn_to_utf8`
+
+.. seealso::
+
+ Symfony provides more polyfills for other classes and functions related to
+ the Intl PHP extension:
+ :doc:`polyfill-intl-grapheme `,
+ :doc:`polyfill-intl-icu `,
+ :doc:`polyfill-intl-messageformatter `,
+ and :doc:`polyfill-intl-normalizer `.
+
+.. _`PHP intl extension`: https://secure.php.net/manual/en/book.intl.php
diff --git a/components/polyfill_intl_messageformatter.rst b/components/polyfill_intl_messageformatter.rst
new file mode 100644
index 00000000000..b07533d1e16
--- /dev/null
+++ b/components/polyfill_intl_messageformatter.rst
@@ -0,0 +1,48 @@
+.. index::
+ single: Polyfill
+ single: MessageFormatter
+ single: Components; Polyfill
+
+The Symfony Polyfill / Intl MessageFormatter Component
+======================================================
+
+ This component provides a fallback implementation for the ``MessageFormatter``
+ class to users who run PHP versions without the ``intl`` extension.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/polyfill-intl-messageformatter
+
+.. include:: /components/require_autoload.rst.inc
+
+Usage
+-----
+
+Once this component is installed in your application, you can use the following
+classes and functions, no matter if the `PHP intl extension`_ is installed or
+not in your server.
+
+Provided Classes
+~~~~~~~~~~~~~~~~
+
+* :phpclass:`IntlException`
+* :phpclass:`MessageFormatter`
+
+Provided Functions
+~~~~~~~~~~~~~~~~~~
+
+* :phpfunction:`msgfmt_format_message`
+
+.. seealso::
+
+ Symfony provides more polyfills for other classes and functions related to
+ the Intl PHP extension:
+ :doc:`polyfill-intl-grapheme `,
+ :doc:`polyfill-intl-idn `,
+ :doc:`polyfill-intl-icu `,
+ and :doc:`polyfill-intl-normalizer `.
+
+.. _`PHP intl extension`: https://secure.php.net/manual/en/book.intl.php
diff --git a/components/polyfill_intl_normalizer.rst b/components/polyfill_intl_normalizer.rst
index 4604f29a259..a9c4ae01a32 100644
--- a/components/polyfill_intl_normalizer.rst
+++ b/components/polyfill_intl_normalizer.rst
@@ -16,8 +16,6 @@ Installation
$ composer require symfony/polyfill-intl-normalizer
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
@@ -40,8 +38,11 @@ Provided Functions
.. seealso::
- The :doc:`polyfill-intl-grapheme ` and
- :doc:`polyfill-intl-icu ` components provide
- polyfills for other classes and functions related to the Intl PHP extension.
+ Symfony provides more polyfills for other classes and functions related to
+ the Intl PHP extension:
+ :doc:`polyfill-intl-grapheme `,
+ :doc:`polyfill-intl-idn `,
+ :doc:`polyfill-intl-icu `,
+ and :doc:`polyfill-intl-messageformatter `.
.. _`PHP intl extension`: https://secure.php.net/manual/en/book.intl.php
diff --git a/components/polyfill_mbstring.rst b/components/polyfill_mbstring.rst
index 934301bf432..322f1e338fc 100644
--- a/components/polyfill_mbstring.rst
+++ b/components/polyfill_mbstring.rst
@@ -16,8 +16,6 @@ Installation
$ composer require symfony/polyfill-mbstring
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
diff --git a/components/polyfill_php54.rst b/components/polyfill_php54.rst
index e32a00f6f62..7c2019a0dba 100644
--- a/components/polyfill_php54.rst
+++ b/components/polyfill_php54.rst
@@ -16,8 +16,6 @@ Installation
$ composer require symfony/polyfill-php54
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
diff --git a/components/polyfill_php55.rst b/components/polyfill_php55.rst
index 9ac1577dea1..99650499763 100644
--- a/components/polyfill_php55.rst
+++ b/components/polyfill_php55.rst
@@ -16,8 +16,6 @@ Installation
$ composer require symfony/polyfill-php55
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
diff --git a/components/polyfill_php56.rst b/components/polyfill_php56.rst
index 44b8f69598b..c68e8d46fd8 100644
--- a/components/polyfill_php56.rst
+++ b/components/polyfill_php56.rst
@@ -16,8 +16,6 @@ Installation
$ composer require symfony/polyfill-php56
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
diff --git a/components/polyfill_php70.rst b/components/polyfill_php70.rst
index e95ada92dcc..2169173faf0 100644
--- a/components/polyfill_php70.rst
+++ b/components/polyfill_php70.rst
@@ -16,8 +16,6 @@ Installation
$ composer require symfony/polyfill-php70
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
diff --git a/components/polyfill_php71.rst b/components/polyfill_php71.rst
index 6e7f116403e..1cec61b4fa6 100644
--- a/components/polyfill_php71.rst
+++ b/components/polyfill_php71.rst
@@ -16,8 +16,6 @@ Installation
$ composer require symfony/polyfill-php71
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
diff --git a/components/polyfill_php72.rst b/components/polyfill_php72.rst
index b3c6c86198e..1a0d0bf87ca 100644
--- a/components/polyfill_php72.rst
+++ b/components/polyfill_php72.rst
@@ -16,8 +16,6 @@ Installation
$ composer require symfony/polyfill-php72
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
diff --git a/components/polyfill_php73.rst b/components/polyfill_php73.rst
index 5f1dac15414..be04e57cb63 100644
--- a/components/polyfill_php73.rst
+++ b/components/polyfill_php73.rst
@@ -16,8 +16,6 @@ Installation
$ composer require symfony/polyfill-php73
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
diff --git a/components/process.rst b/components/process.rst
index 9674e1af96a..7aecb26302d 100644
--- a/components/process.rst
+++ b/components/process.rst
@@ -14,7 +14,6 @@ Installation
$ composer require symfony/process
-Alternatively, you can clone the ``_ repository.
.. include:: /components/require_autoload.rst.inc
@@ -27,8 +26,8 @@ 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\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;
+ use Symfony\Component\Process\Process;
$process = new Process(['ls', '-lsa']);
$process->run();
@@ -40,19 +39,6 @@ escaping arguments to prevent security issues. It replaces PHP functions like
echo $process->getOutput();
-.. tip::
-
- In addition to passing the command binary and its arguments as a string, you
- can also pass them as an array, which is useful when building a complex
- command programmatically::
-
- // traditional string based commands
- $builder = new Process('ls -lsa');
- // same example but using an array
- $builder = new Process(['ls', '-lsa']);
- // the array can contain any number of arguments and options
- $builder = new Process(['ls', '-l', '-s', '-a']);
-
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`
@@ -110,33 +96,57 @@ with a non-zero code)::
echo $exception->getMessage();
}
-.. tip::
+Using Features From the OS Shell
+--------------------------------
+
+Using 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 before completion)::
+
+ $process = new Process(['/path/command', '--flag', 'arg 1', 'etc.']);
+
+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.
- Using 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 before completion.)::
+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::
- $process = new Process(['/path/command', '--flag', 'arg 1', 'etc.']);
+ // On Unix-like OSes (Linux, macOS)
+ $process = Process::fromShellCommandline('echo "$MESSAGE"');
- 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.
+ // On Windows
+ $process = Process::fromShellCommandline('echo "!MESSAGE!"');
- Please note that each OS provides a different syntax for their command-lines
- so that it becomes your responsibility to deal with escaping and portability.
+ // On both Unix-like and Windows
+ $process->run(null, ['MESSAGE' => 'Something to output']);
- To provide any variable arguments to command-line string, pass them as
- environment variables using the second argument of the ``run()``,
- ``mustRun()`` or ``start()`` methods. Referencing them is also OS-dependent::
+Setting Environment Variables for Processes
+-------------------------------------------
- // On Unix-like OSes (Linux, macOS)
- $process = new Process('echo "$MESSAGE"');
+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::
- // On Windows
- $process = new Process('echo "!MESSAGE!"');
+ $process = new Process(['...'], null, ['ENV_VAR_NAME' => 'value']);
+ $process = Process::fromShellCommandline('...', null, ['ENV_VAR_NAME' => 'value']);
+ $process->run(null, ['ENV_VAR_NAME' => 'value']);
- // On both Unix-like and Windows
- $process->run(null, ['MESSAGE' => 'Something to output']);
+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
--------------------------------
@@ -229,6 +239,23 @@ in the output and its type::
}
});
+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) {
+ return $output === 'Ready. Waiting for commands...';
+ });
+
+ // ... do things after the process is ready
+
Streaming to the Standard Input of a Process
--------------------------------------------
@@ -237,7 +264,7 @@ Before a process is started, you can specify its standard input using either the
of the constructor. The provided input can be a string, a stream resource or a
Traversable object::
- $process = new Process('cat');
+ $process = new Process(['cat']);
$process->setInput('foobar');
$process->run();
@@ -336,7 +363,7 @@ a different timeout (in seconds) to the ``setTimeout()`` method::
$process->run();
If the timeout is reached, a
-:class:`Symfony\\Component\\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::
@@ -447,14 +474,6 @@ whether `TTY`_ is supported on the current operating system::
$process = (new Process())->setTty(Process::isTtySupported());
-.. versionadded:: 4.1
- The ``isTtySupported()`` method was introduced in Symfony 4.1.
-
-.. _`Symfony Issue#5759`: https://github.com/symfony/symfony/issues/5759
-.. _`PHP Bug#39992`: https://bugs.php.net/bug.php?id=39992
-.. _`exec`: https://en.wikipedia.org/wiki/Exec_(operating_system)
.. _`pid`: https://en.wikipedia.org/wiki/Process_identifier
-.. _`PHP Documentation`: https://php.net/manual/en/pcntl.constants.php
-.. _Packagist: https://packagist.org/packages/symfony/process
-.. _`PHP streams`: http://www.php.net/manual/en/book.stream.php
+.. _`PHP streams`: https://www.php.net/manual/en/book.stream.php
.. _`TTY`: https://en.wikipedia.org/wiki/Tty_(unix)
diff --git a/components/property_access.rst b/components/property_access.rst
index 1181f8ff3a7..81aed5ab2c2 100644
--- a/components/property_access.rst
+++ b/components/property_access.rst
@@ -15,8 +15,6 @@ Installation
$ composer require symfony/property-access
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
@@ -168,6 +166,36 @@ getters, this means that you can do something like this::
This will produce: ``He is an author``
+Accessing a non Existing Property Path
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 4.3
+
+ The ``disableExceptionOnInvalidPropertyPath()`` method was introduced in
+ Symfony 4.3.
+
+By default a :class:`Symfony\\Component\\PropertyAccess\\Exception\\NoSuchPropertyException`
+is thrown if the property path passed to :method:`PropertyAccessor::getValue`
+does not exist. You can change this behavior using the
+:method:`Symfony\\Component\\PropertyAccess\\PropertyAccessorBuilder::disableExceptionOnInvalidPropertyPath`
+method::
+
+ // ...
+ class Person
+ {
+ public $name;
+ }
+
+ $person = new Person();
+
+ $propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
+ ->disableExceptionOnInvalidPropertyPath()
+ ->getPropertyAccessor();
+
+ // instead of throwing an exception the following code returns null
+ $value = $propertyAccessor->getValue($person, 'birthday');
+
+
Magic ``__get()`` Method
~~~~~~~~~~~~~~~~~~~~~~~~
@@ -296,9 +324,7 @@ can use setters, the magic ``__set()`` method or properties to set values::
var_dump($person->getChildren()); // [Person()];
You can also use ``__call()`` to set values but you need to enable the feature,
-see `Enable other Features`_.
-
-.. code-block:: php
+see `Enable other Features`_::
// ...
class Person
@@ -335,9 +361,7 @@ Writing to Array Properties
~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``PropertyAccessor`` class allows to update the content of arrays stored in
-properties through *adder* and *remover* methods.
-
-.. code-block:: php
+properties through *adder* and *remover* methods::
// ...
class Person
@@ -467,5 +491,4 @@ Or you can pass parameters directly to the constructor (not the recommended way)
// ...
$propertyAccessor = new PropertyAccessor(true); // this enables handling of magic __call
-.. _Packagist: https://packagist.org/packages/symfony/property-access
.. _The Inflector component: https://github.com/symfony/inflector
diff --git a/components/property_info.rst b/components/property_info.rst
index 21ded9a48d1..5f1c56826d6 100644
--- a/components/property_info.rst
+++ b/components/property_info.rst
@@ -23,8 +23,6 @@ Installation
$ composer require symfony/property-info
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Additional dependencies may be required for some of the
@@ -37,36 +35,38 @@ Usage
To use this component, create a new
:class:`Symfony\\Component\\PropertyInfo\\PropertyInfoExtractor` instance and
-provide it with a set of information extractors.
-
-.. code-block:: php
+provide it with a set of information extractors::
- use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
+ use Example\Namespace\YourAwesomeCoolClass;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
- use Example\Namespace\YourAwesomeCoolClass;
+ use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
// a full list of extractors is shown further below
$phpDocExtractor = new PhpDocExtractor();
$reflectionExtractor = new ReflectionExtractor();
- // array of PropertyListExtractorInterface
+ // list of PropertyListExtractorInterface (any iterable)
$listExtractors = [$reflectionExtractor];
- // array of PropertyTypeExtractorInterface
+ // list of PropertyTypeExtractorInterface (any iterable)
$typeExtractors = [$phpDocExtractor, $reflectionExtractor];
- // array of PropertyDescriptionExtractorInterface
+ // list of PropertyDescriptionExtractorInterface (any iterable)
$descriptionExtractors = [$phpDocExtractor];
- // array of PropertyAccessExtractorInterface
+ // list of PropertyAccessExtractorInterface (any iterable)
$accessExtractors = [$reflectionExtractor];
+ // list of PropertyInitializableExtractorInterface (any iterable)
+ $propertyInitializableExtractors = [$reflectionExtractor];
+
$propertyInfo = new PropertyInfoExtractor(
$listExtractors,
$typeExtractors,
$descriptionExtractors,
- $accessExtractors
+ $accessExtractors,
+ $propertyInitializableExtractors
);
// see below for more examples
@@ -90,9 +90,7 @@ both provide list and type information it is probably better that:
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.
-
-.. code-block:: php
+ of type-hinting to provide more accurate type information::
use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
@@ -120,12 +118,13 @@ Extractable Information
-----------------------
The :class:`Symfony\\Component\\PropertyInfo\\PropertyInfoExtractor`
-class exposes public methods to extract four types of information:
+class exposes public methods to extract several types of information:
-* :ref:`List of properties `: `getProperties()`
-* :ref:`Property type `: `getTypes()`
-* :ref:`Property description `: `getShortDescription()` and `getLongDescription()`
-* :ref:`Property access details `: `isReadable()` and `isWritable()`
+* :ref:`List of properties `: :method:`Symfony\\Component\\PropertyInfo\\PropertyListExtractorInterface::getProperties()`
+* :ref:`Property type `: :method:`Symfony\\Component\\PropertyInfo\\PropertyTypeExtractorInterface::getTypes()`
+* :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::
@@ -146,19 +145,17 @@ 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.
-
-.. code-block:: php
+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"
- }
+ Example Result
+ --------------
+ array(3) {
+ [0] => string(8) "username"
+ [1] => string(8) "password"
+ [2] => string(6) "active"
+ }
*/
.. _property-info-type:
@@ -168,26 +165,23 @@ Type Information
Extractors that implement :class:`Symfony\\Component\\PropertyInfo\\PropertyTypeExtractorInterface`
provide :ref:`extensive data type information `
-for a property.
-
-.. code-block:: php
+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
+ 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.
@@ -199,24 +193,22 @@ Description Information
Extractors that implement :class:`Symfony\\Component\\PropertyInfo\\PropertyDescriptionExtractorInterface`
provide long and short descriptions from a properties annotations as
-strings.
-
-.. code-block:: php
+strings::
$title = $propertyInfo->getShortDescription($class, $property);
/*
- Example Result
- --------------
- string(41) "This is the first line of the DocComment."
+ Example Result
+ --------------
+ string(41) "This is the first line of the DocComment."
*/
$paragraph = $propertyInfo->getLongDescription($class, $property);
/*
- Example Result
- --------------
- string(79):
- These is the subsequent paragraph in the DocComment.
- It can span multiple lines.
+ Example Result
+ --------------
+ string(79):
+ These is the subsequent paragraph in the DocComment.
+ It can span multiple lines.
*/
.. _property-info-access:
@@ -225,9 +217,7 @@ Access Information
~~~~~~~~~~~~~~~~~~
Extractors that implement :class:`Symfony\\Component\\PropertyInfo\\PropertyAccessExtractorInterface`
-provide whether properties are readable or writable as booleans.
-
-.. code-block:: php
+provide whether properties are readable or writable as booleans::
$propertyInfo->isReadable($class, $property);
// Example Result: bool(true)
@@ -240,15 +230,25 @@ for getter/isser/setter/hasser method in addition to whether or not a property i
to determine if it's accessible. This based on how the :doc:`PropertyAccess `
works.
-.. versionadded:: 4.1
+.. _property-info-initializable:
- The support of hasser methods in the ``ReflectionExtractor`` class was
- introduced in Symfony 4.1.
+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 four interfaces, delegating the extraction of property
+ 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
@@ -315,9 +315,16 @@ 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``, or
+ 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).
+ 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.)
+
+.. versionadded:: 4.2
+
+ The support of phpDocumentor collection types was introduced in Symfony 4.2.
Type::getCollectionKeyType() & Type::getCollectionValueType()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -351,21 +358,9 @@ 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 provide return and scalar types for PHP 7+.
-
-.. 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
-
-.. code-block:: php
+It can also give the type of a property (even extracting it from the constructor
+arguments), and if it is initializable through the constructor. It supports
+return and scalar types for PHP 7::
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
@@ -373,12 +368,34 @@ It can also provide return and scalar types for PHP 7+.
// 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);
+
+.. versionadded:: 4.1
+
+ The feature to extract the property types from constructor arguments was
+ introduced in Symfony 4.1.
+
+.. 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
~~~~~~~~~~~~~~~
@@ -390,9 +407,7 @@ 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.
-
-.. code-block:: php
+library is present::
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
@@ -415,9 +430,7 @@ 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.
-
-.. code-block:: php
+with the ``property_info`` service in the Symfony Framework::
use Doctrine\Common\Annotations\AnnotationReader;
use Symfony\Component\PropertyInfo\Extractor\SerializerExtractor;
@@ -443,9 +456,7 @@ DoctrineExtractor
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.
-
-.. code-block:: php
+with the ``property_info`` service in the Symfony Framework::
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Tools\Setup;
@@ -456,7 +467,7 @@ with the ``property_info`` service in the Symfony Framework.
'driver' => 'pdo_sqlite',
// ...
], $config);
- $doctrineExtractor = new DoctrineExtractor($entityManager->getMetadataFactory());
+ $doctrineExtractor = new DoctrineExtractor($entityManager);
// List information.
$doctrineExtractor->getProperties($class);
@@ -472,8 +483,9 @@ 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\\PropertyAccessExtractorInterface`,
:class:`Symfony\\Component\\PropertyInfo\\PropertyDescriptionExtractorInterface`,
-:class:`Symfony\\Component\\PropertyInfo\\PropertyListExtractorInterface`
-and :class:`Symfony\\Component\\PropertyInfo\\PropertyTypeExtractorInterface`.
+: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``
@@ -485,10 +497,10 @@ service by defining it as a service with one or more of the following
* ``property_info.description_extractor`` if it provides description information.
* ``property_info.access_extractor`` if it provides access information.
-.. _Packagist: https://packagist.org/packages/symfony/property-info
.. _`phpDocumentor Reflection`: https://github.com/phpDocumentor/ReflectionDocBlock
.. _`phpdocumentor/reflection-docblock`: https://packagist.org/packages/phpdocumentor/reflection-docblock
-.. _`Doctrine ORM`: http://www.doctrine-project.org/projects/orm.html
+.. _`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
index 61b2d1b35e3..2df3c6fc3af 100644
--- a/components/psr7.rst
+++ b/components/psr7.rst
@@ -15,14 +15,16 @@ Installation
$ composer require symfony/psr-http-message-bridge
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
-The bridge also needs a PSR-7 implementation to allow converting HttpFoundation
-objects to PSR-7 objects. It provides native support for `Zend Diactoros`_.
-Use Composer (`zendframework/zend-diactoros on Packagist `_)
-or refer to the project documentation to install it.
+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
-----
@@ -33,32 +35,35 @@ Converting from HttpFoundation Objects to PSR-7
The bridge provides an interface of a factory called
:class:`Symfony\\Bridge\\PsrHttpMessage\\HttpMessageFactoryInterface`
that builds objects implementing PSR-7 interfaces from HttpFoundation objects.
-It also provide a default implementation using Zend Diactoros internally.
The following code snippet explains how to convert a :class:`Symfony\\Component\\HttpFoundation\\Request`
-to a ``Zend\Diactoros\ServerRequest`` class implementing the
+to a ``Nyholm\Psr7\ServerRequest`` class implementing the
``Psr\Http\Message\ServerRequestInterface`` interface::
- use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
+ 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
- $psr7Factory = new DiactorosFactory();
- $psrRequest = $psr7Factory->createRequest($symfonyRequest);
+ $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
-``Zend\Diactoros\Response`` class implementing the
+``Nyholm\Psr7\Response`` class implementing the
``Psr\Http\Message\ResponseInterface`` interface::
- use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
+ use Nyholm\Psr7\Factory\Psr17Factory;
+ use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
use Symfony\Component\HttpFoundation\Response;
$symfonyResponse = new Response('Content');
- $psr7Factory = new DiactorosFactory();
- $psrResponse = $psr7Factory->createResponse($symfonyResponse);
+ $psr17Factory = new Psr17Factory();
+ $psrHttpFactory = new PsrHttpFactory($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory);
+ $psrResponse = $psrHttpFactory->createResponse($symfonyResponse);
Converting Objects implementing PSR-7 Interfaces to HttpFoundation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -89,5 +94,5 @@ to a :class:`Symfony\\Component\\HttpFoundation\\Response` instance::
$symfonyResponse = $httpFoundationFactory->createResponse($psrResponse);
.. _`PSR-7`: https://www.php-fig.org/psr/psr-7/
-.. _`Zend Diactoros`: https://github.com/zendframework/zend-diactoros
-.. _`symfony/psr-http-message-bridge on Packagist`: https://packagist.org/packages/symfony/psr-http-message-bridge
+.. _`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
diff --git a/components/routing.rst b/components/routing.rst
index bad49425614..22b9ea9f342 100644
--- a/components/routing.rst
+++ b/components/routing.rst
@@ -6,7 +6,8 @@ The Routing Component
=====================
The Routing component maps an HTTP request to a set of configuration
- variables.
+ variables. It's used to build routing systems for web applications where
+ each URL is associated with some code to execute.
Installation
------------
@@ -15,52 +16,56 @@ Installation
$ composer require symfony/routing
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
-----
-.. seealso::
+The main :doc:`Symfony routing ` article explains all the features of
+this component when used inside a Symfony application. This article only
+explains the things you need to do to use it in a non-Symfony PHP application.
- This article explains how to use the Routing features as an independent
- component in any PHP application. Read the :doc:`/routing` article to learn
- about how to use it in Symfony applications.
+Routing System Setup
+--------------------
-In order to set up a basic routing system you need three parts:
+A routing system has 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
+* 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 path to a single route.
-Here is a quick example. Notice that this assumes that you've already configured
-your autoloader to load the Routing component::
+Here is a quick example::
+ use App\Controller\BlogController;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
- use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
+ use Symfony\Component\Routing\RouteCollection;
- $route = new Route('/foo', ['_controller' => 'MyController']);
+ $route = new Route('/blog/{slug}', ['_controller' => BlogController::class])
$routes = new RouteCollection();
- $routes->add('route_name', $route);
+ $routes->add('blog_show', $route);
$context = new RequestContext('/');
+ // Routing can match routes with incoming requests
$matcher = new UrlMatcher($routes, $context);
+ $parameters = $matcher->match('/blog/lorem-ipsum');
+ // $parameters = [
+ // '_controller' => 'App\Controller\BlogController',
+ // 'slug' => 'lorem-ipsum',
+ // '_route' => 'blog_show'
+ // ]
- $parameters = $matcher->match('/foo');
- // ['_controller' => 'MyController', '_route' => 'route_name']
-
-.. note::
-
- The :class:`Symfony\\Component\\Routing\\RequestContext` parameters can be populated
- with the values stored in ``$_SERVER``, but it's easier 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`.
+ // Routing can also generate URLs for a given route
+ $generator = new UrlGenerator($routes, $context);
+ $url = $generator->generate('blog_show', [
+ 'slug' => 'my-blog-post',
+ ]);
+ // $url = '/blog/my-blog-post'
The :method:`RouteCollection::add() `
method takes two arguments. The first is the name of the route. The second
@@ -70,42 +75,19 @@ of custom variables can be *anything* that's significant to your application,
and is returned when that route is matched.
The :method:`UrlMatcher::match() `
-returns the variables you set on the route as well as the wildcard placeholders
-(see below). Your application can now use this information to continue
-processing the request. In addition to the configured variables, a ``_route``
-key is added, which holds the name of the matched route.
+returns the variables you set on the route as well as the route parameters.
+Your application can now use this information to continue processing the request.
+In addition to the configured variables, a ``_route`` key is added, which holds
+the name of the matched route.
If no matching route can be found, a
:class:`Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException` will
be thrown.
Defining Routes
-~~~~~~~~~~~~~~~
-
-A full route definition can contain up to seven parts:
-
-#. 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.
-
-#. An array of default values. This contains an array of arbitrary values
- that will be returned when the request matches the route.
-
-#. An array of requirements. These define constraints for the values of the
- placeholders as regular expressions.
-
-#. An array of options. These contain internal settings for the route and
- are the least commonly needed.
-
-#. A host. This is matched against the host of the request. See
- :doc:`/routing/hostname_pattern` for more details.
-
-#. An array of schemes. These enforce a certain HTTP scheme (``http``, ``https``).
+---------------
-#. An array of methods. These enforce a certain HTTP request method (``HEAD``,
- ``GET``, ``POST``, ...).
-
-Take the following route, which combines several of these ideas::
+A full route definition can contain up to eight parts::
$route = new Route(
'/archive/{month}', // path
@@ -114,7 +96,8 @@ Take the following route, which combines several of these ideas::
[], // options
'{subdomain}.example.com', // host
[], // schemes
- [] // methods
+ [], // methods
+ 'context.getHost() matches "/(secure|admin).example.com/"' // condition
);
// ...
@@ -130,33 +113,13 @@ Take the following route, which combines several of these ideas::
$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.
-
-When using wildcards, these are returned in the array result when calling
-``match``. The part of the path that the wildcard matched (e.g. ``2012-01``) is used
-as value.
-
-.. 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}',
- ['suffix' => ''],
- ['suffix' => '.*']
- );
-
-Using Prefixes
-~~~~~~~~~~~~~~
+Route Collections
+-----------------
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
-and default values for the parameters, requirements, options, schemes and the
-host to all routes of a subtree using methods provided by the
+This way you can build a tree of routes. Additionally you can define common
+options for all routes of a subtree using methods provided by the
``RouteCollection`` class::
$rootCollection = new RouteCollection();
@@ -166,16 +129,17 @@ host to all routes of a subtree using methods provided by the
$subCollection->add(...);
$subCollection->addPrefix('/prefix');
$subCollection->addDefaults([...]);
- $subCollection->addRequirements([]);
- $subCollection->addOptions([]);
- $subCollection->setHost('admin.example.com');
+ $subCollection->addRequirements([...]);
+ $subCollection->addOptions([...]);
+ $subCollection->setHost('{subdomain}.example.com');
$subCollection->setMethods(['POST']);
$subCollection->setSchemes(['https']);
+ $subCollection->setCondition('context.getHost() matches "/(secure|admin).example.com/"');
$rootCollection->addCollection($subCollection);
-Set the Request Parameters
-~~~~~~~~~~~~~~~~~~~~~~~~~~
+Setting the Request Parameters
+------------------------------
The :class:`Symfony\\Component\\Routing\\RequestContext` provides information
about the current request. You can define all parameters of an HTTP request
@@ -205,67 +169,15 @@ Normally you can pass the values from the ``$_SERVER`` variable to populate the
$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;
- use Symfony\Component\Routing\RequestContext;
- use Symfony\Component\Routing\Route;
- use Symfony\Component\Routing\RouteCollection;
-
- $routes = new RouteCollection();
- $routes->add('show_post', new Route('/show/{slug}'));
-
- $context = new RequestContext('/');
-
- $generator = new UrlGenerator($routes, $context);
-
- $url = $generator->generate('show_post', [
- '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.
-
-Check if a Route Exists
-~~~~~~~~~~~~~~~~~~~~~~~
-
-In highly dynamic applications, it may be necessary to check whether a route
-exists before using it to generate a URL. In those cases, don't use the
-:method:`Symfony\\Component\\Routing\\Router::getRouteCollection` method because
-that regenerates the routing cache and slows down the application.
+Loading Routes
+--------------
-Instead, try to generate the URL and catch the
-:class:`Symfony\\Component\\Routing\\Exception\\RouteNotFoundException` thrown
-when the route doesn't exist::
+The Routing component comes with a number of loader classes, each giving you the
+ability to load a collection of route definitions from external resources.
- use Symfony\Component\Routing\Exception\RouteNotFoundException;
+File Routing Loaders
+~~~~~~~~~~~~~~~~~~~~
- // ...
-
- try {
- $url = $generator->generate($dynamicRouteName, $parameters);
- } catch (RouteNotFoundException $e) {
- // the route is not defined...
- }
-
-Load Routes from a File
-~~~~~~~~~~~~~~~~~~~~~~~
-
-You've already seen how you can 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.
@@ -277,12 +189,13 @@ If you're using the ``YamlFileLoader``, then route definitions look like this:
# routes.yaml
route1:
- path: /foo
- defaults: { _controller: 'MyController::fooAction' }
-
+ path: /foo
+ controller: MyController::fooAction
+ methods: GET|HEAD
route2:
- path: /foo/bar
- defaults: { _controller: 'MyController::foobarAction' }
+ path: /foo/bar
+ controller: FooBarInvokableController
+ methods: PUT
To load this file, you can use the following code. This assumes that your
``routes.yaml`` file is in the same directory as the below code::
@@ -302,23 +215,22 @@ other loaders that work the same way:
* :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`::
+have to provide the name of a PHP file which returns a callable handling a
+:class:`Symfony\\Component\\Routing\\Loader\\Configurator\\RoutingConfigurator`.
+This class allows to chain imports, collections or simple route definition calls::
// RouteProvider.php
- use Symfony\Component\Routing\RouteCollection;
- use Symfony\Component\Routing\Route;
+ use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
- $routes = new RouteCollection();
- $routes->add(
- 'route_name',
- new Route('/foo', ['_controller' => 'ExampleController'])
- );
- // ...
-
- return $routes;
+ return function (RoutingConfigurator $routes) {
+ $routes->add('route_name', '/foo')
+ ->controller('ExampleController')
+ // ...
+ ;
+ };
-Routes as Closures
-..................
+Closure Routing Loaders
+~~~~~~~~~~~~~~~~~~~~~~~
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`::
@@ -332,14 +244,28 @@ calls a closure and uses the result as a :class:`Symfony\\Component\\Routing\\Ro
$loader = new ClosureLoader();
$routes = $loader->load($closure);
-Routes as Annotations
-.....................
+Annotation Routing Loaders
+~~~~~~~~~~~~~~~~~~~~~~~~~~
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.
+route definitions from class annotations::
+
+ use Doctrine\Common\Annotations\AnnotationReader;
+ use Symfony\Bundle\FrameworkBundle\Routing\AnnotatedRouteControllerLoader;
+ use Symfony\Component\Config\FileLocator;
+ use Symfony\Component\Routing\Loader\AnnotationDirectoryLoader;
+
+ $loader = new AnnotationDirectoryLoader(
+ new FileLocator(__DIR__.'/app/controllers/'),
+ new AnnotatedRouteControllerLoader(
+ new AnnotationReader()
+ )
+ );
+
+ $routes = $loader->load(__DIR__.'/app/controllers/');
+ // ...
.. include:: /_includes/_annotation_loader_tip.rst.inc
@@ -372,7 +298,8 @@ automatically in the background if you want to use it. A basic example of the
['cache_dir' => __DIR__.'/cache'],
$requestContext
);
- $router->match('/foo/bar');
+ $parameters = $router->match('/foo/bar');
+ $url = $router->generate('some_route', ['parameter' => 'value']);
.. note::
@@ -380,161 +307,6 @@ automatically in the background if you want to use it. A basic example of the
are saved in the ``cache_dir``. This means your script must have write
permissions for that location.
-Unicode Routing Support
-~~~~~~~~~~~~~~~~~~~~~~~
-
-The Routing component supports UTF-8 characters in route paths and requirements.
-Thanks to the ``utf8`` route option, you can make Symfony match and generate
-routes with UTF-8 characters:
-
-.. configuration-block::
-
- .. code-block:: php-annotations
-
- namespace App\Controller;
-
- use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
- use Symfony\Component\Routing\Annotation\Route;
-
- class DefaultController extends AbstractController
- {
- /**
- * @Route("/category/{name}", name="route1", options={"utf8": true})
- */
- public function category()
- {
- // ...
- }
-
- .. code-block:: yaml
-
- route1:
- path: /category/{name}
- defaults: { _controller: 'App\Controller\DefaultController::category' }
- options:
- utf8: true
-
- .. code-block:: xml
-
-
-
-
-
- App\Controller\DefaultController::category
- true
-
-
-
- .. code-block:: php
-
- use Symfony\Component\Routing\RouteCollection;
- use Symfony\Component\Routing\Route;
-
- $routes = new RouteCollection();
- $routes->add('route1', new Route('/category/{name}',
- [
- '_controller' => 'App\Controller\DefaultController::category',
- ],
- [],
- [
- 'utf8' => true,
- ]
- ));
-
- // ...
-
- return $routes;
-
-In this route, the ``utf8`` option set to ``true`` makes Symfony consider the
-``.`` requirement to match any UTF-8 characters instead of just a single
-byte character. This means that so the following URLs would match:
-``/category/日本語``, ``/category/فارسی``, ``/category/한국어``, etc. In case you
-are wondering, this option also allows to include and match emojis in URLs.
-
-You can also include UTF-8 strings as routing requirements:
-
-.. configuration-block::
-
- .. code-block:: php-annotations
-
- namespace App\Controller;
-
- use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
- use Symfony\Component\Routing\Annotation\Route;
-
- class DefaultController extends AbstractController
- {
- /**
- * @Route(
- * "/category/{name}",
- * name="route2",
- * requirements={"default"="한국어"},
- * options={"utf8": true}
- * )
- */
- public function default()
- {
- // ...
- }
-
- .. code-block:: yaml
-
- route2:
- path: /default/{default}
- defaults: { _controller: 'App\Controller\DefaultController::default' }
- requirements:
- default: "한국어"
- options:
- utf8: true
-
- .. code-block:: xml
-
-
-
-
-
- App\Controller\DefaultController::default
- 한국어
- true
-
-
-
- .. code-block:: php
-
- use Symfony\Component\Routing\RouteCollection;
- use Symfony\Component\Routing\Route;
-
- $routes = new RouteCollection();
- $routes->add('route2', new Route('/default/{default}',
- [
- '_controller' => 'App\Controller\DefaultController::default',
- ],
- [
- 'default' => '한국어',
- ],
- [
- 'utf8' => true,
- ]
- ));
-
- // ...
-
- return $routes;
-
-.. tip::
-
- In addition to UTF-8 characters, the Routing component also supports all
- the `PCRE Unicode properties`_, which are escape sequences that match
- generic character types. For example, ``\p{Lu}`` matches any uppercase
- character in any language, ``\p{Greek}`` matches any Greek character,
- ``\P{Han}`` matches any character not included in the Chinese Han script.
-
Learn more
----------
@@ -546,6 +318,3 @@ Learn more
/routing/*
/controller
/controller/*
-
-.. _Packagist: https://packagist.org/packages/symfony/routing
-.. _PCRE Unicode properties: http://php.net/manual/en/regexp.reference.unicode.php
diff --git a/components/security.rst b/components/security.rst
index 98808c33c52..ee6c1580472 100644
--- a/components/security.rst
+++ b/components/security.rst
@@ -18,12 +18,10 @@ Installation
$ composer require symfony/security
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
-The Security component is divided into four smaller sub-components which can be
-used separately:
+The Security component is divided into several smaller sub-components which can
+be used separately:
``symfony/security-core``
It provides all the common security features, from authentication to
@@ -36,6 +34,10 @@ used separately:
``symfony/security-csrf``
It provides protection against `CSRF attacks`_.
+``symfony/security-guard``
+ It brings many layers of authentication together, allowing the creation
+ of complex authentication systems.
+
.. seealso::
This article explains how to use the Security features as an independent
@@ -55,5 +57,4 @@ Learn More
/reference/configuration/security
/reference/constraints/UserPassword
-.. _Packagist: https://packagist.org/packages/symfony/security
.. _`CSRF attacks`: https://en.wikipedia.org/wiki/Cross-site_request_forgery
diff --git a/components/security/authentication.rst b/components/security/authentication.rst
index ee4e99b6529..849ea53a992 100644
--- a/components/security/authentication.rst
+++ b/components/security/authentication.rst
@@ -13,11 +13,11 @@ an *authenticated* token if the supplied credentials were found to be valid.
The listener should then store the authenticated token using
:class:`the token storage `::
- use Symfony\Component\Security\Http\Firewall\ListenerInterface;
- use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
+ use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
- use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+ use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
+ use Symfony\Component\Security\Http\Firewall\ListenerInterface;
class SomeAuthenticationListener implements ListenerInterface
{
@@ -38,7 +38,7 @@ The listener should then store the authenticated token using
// ...
- public function handle(GetResponseEvent $event)
+ public function handle(RequestEvent $event)
{
$request = $event->getRequest();
@@ -127,9 +127,9 @@ 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;
+ use Symfony\Component\Security\Core\User\InMemoryUserProvider;
+ use Symfony\Component\Security\Core\User\UserChecker;
$userProvider = new InMemoryUserProvider(
[
@@ -276,14 +276,15 @@ Authentication Events
The security component provides 4 related authentication events:
-=============================== ================================================ ==============================================================================
-Name Event Constant Argument Passed to the Listener
-=============================== ================================================ ==============================================================================
-security.authentication.success ``AuthenticationEvents::AUTHENTICATION_SUCCESS`` :class:`Symfony\\Component\\Security\\Core\\Event\\AuthenticationEvent`
-security.authentication.failure ``AuthenticationEvents::AUTHENTICATION_FAILURE`` :class:`Symfony\\Component\\Security\\Core\\Event\\AuthenticationFailureEvent`
-security.interactive_login ``SecurityEvents::INTERACTIVE_LOGIN`` :class:`Symfony\\Component\\Security\\Http\\Event\\InteractiveLoginEvent`
-security.switch_user ``SecurityEvents::SWITCH_USER`` :class:`Symfony\\Component\\Security\\Http\\Event\\SwitchUserEvent`
-=============================== ================================================ ==============================================================================
+=============================== ================================================================= ==============================================================================
+Name Event Constant Argument Passed to the Listener
+=============================== ================================================================= ==============================================================================
+security.authentication.success ``AuthenticationEvents::AUTHENTICATION_SUCCESS`` :class:`Symfony\\Component\\Security\\Core\\Event\\AuthenticationEvent`
+security.authentication.failure ``AuthenticationEvents::AUTHENTICATION_FAILURE`` :class:`Symfony\\Component\\Security\\Core\\Event\\AuthenticationFailureEvent`
+security.interactive_login ``SecurityEvents::INTERACTIVE_LOGIN`` :class:`Symfony\\Component\\Security\\Http\\Event\\InteractiveLoginEvent`
+security.switch_user ``SecurityEvents::SWITCH_USER`` :class:`Symfony\\Component\\Security\\Http\\Event\\SwitchUserEvent`
+security.logout_on_change ``Symfony\Component\Security\Http\Event\DeauthenticatedEvent`` :class:`Symfony\\Component\\Security\\Http\\Event\\DeauthenticatedEvent`
+=============================== ================================================================= ==============================================================================
Authentication Success and Failure Events
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -314,10 +315,16 @@ order to give your user a welcome flash message every time they log in.
The ``security.switch_user`` event is triggered every time you activate
the ``switch_user`` firewall listener.
+The ``Symfony\Component\Security\Http\Event\DeauthenticatedEvent`` event is triggered when a token has been deauthenticated
+because of a user change, it can help you doing some clean-up task when a logout has been triggered.
+
+.. versionadded:: 4.3
+
+ The ``Symfony\Component\Security\Http\Event\DeauthenticatedEvent`` event was introduced in Symfony 4.3.
+
.. seealso::
For more information on switching users, see
:doc:`/security/impersonating_user`.
.. _`CVE-2013-5750`: https://symfony.com/blog/cve-2013-5750-security-issue-in-fosuserbundle-login-form
-.. _`BasePasswordEncoder::checkPasswordLength`: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Security/Core/Encoder/BasePasswordEncoder.php
diff --git a/components/security/authorization.rst b/components/security/authorization.rst
index 4bac0b42c74..52f9b8bacd4 100644
--- a/components/security/authorization.rst
+++ b/components/security/authorization.rst
@@ -19,7 +19,7 @@ by an instance of :class:`Symfony\\Component\\Security\\Core\\Authorization\\Acc
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`
+ For instance, the token's :method:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface::getRoleNames`
method may be used to retrieve the roles of the current user (e.g.
``ROLE_SUPER_ADMIN``), or a decision may be based on the class of the token.
* A set of attributes
@@ -49,7 +49,7 @@ recognizes several strategies:
``unanimous``
only grant access if none of the voters has denied access;
-.. code-block:: php
+Usage of the available options in detail::
use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;
@@ -101,9 +101,7 @@ The :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\Authentica
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
+on a "remember-me" cookie, or even authenticated anonymously?::
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver;
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
@@ -127,7 +125,7 @@ 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`
+roles returned by the token's :method:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface::getRoleNames`
method::
use Symfony\Component\Security\Core\Authorization\Voter\RoleVoter;
@@ -159,32 +157,53 @@ role::
$roleHierarchyVoter = new RoleHierarchyVoter($roleHierarchy);
-.. note::
+ExpressionVoter
+~~~~~~~~~~~~~~~
- When you make your own voter, you can use its constructor
- to inject any dependencies it needs to come to a decision.
+The :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\ExpressionVoter`
+grants access based on the evaluation of expressions created with the
+:doc:`ExpressionLanguage component `. These
+expressions have access to a number of
+:ref:`special security variables `::
-Roles
------
+ use Symfony\Component\ExpressionLanguage\Expression;
+ use Symfony\Component\Security\Core\Authorization\Voter\ExpressionVoter;
+
+ // Symfony\Component\Security\Core\Authorization\ExpressionLanguage;
+ $expressionLanguage = ...;
-Roles are objects that give expression to a certain right the user has. The only
-requirement is that they must define a ``getRole()`` method that returns a
-string representation of the role itself. To do so, you can optionally extend
-from the default :class:`Symfony\\Component\\Security\\Core\\Role\\Role` class,
-which returns its first constructor argument in this method::
+ // instance of Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface
+ $trustResolver = ...;
+
+ // Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface
+ $authorizationChecker = ...;
+
+ $expressionVoter = new ExpressionVoter($expressionLanguage, $trustResolver, $authorizationChecker);
+
+ // instance of Symfony\Component\Security\Core\Authentication\Token\TokenInterface
+ $token = ...;
- use Symfony\Component\Security\Core\Role\Role;
+ // any object
+ $object = ...;
- $role = new Role('ROLE_ADMIN');
+ $expression = new Expression(
+ '"ROLE_ADMIN" in roles or (not is_anonymous() and user.isSuperAdmin())'
+ )
- // shows 'ROLE_ADMIN'
- var_dump($role->getRole());
+ $vote = $expressionVoter->vote($token, $object, [$expression]);
.. 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.
+ When you make your own voter, you can use its constructor to inject any
+ dependencies it needs to come to a decision.
+
+Roles
+-----
+
+Roles are strings that give expression to a certain right the user has (e.g.
+*"edit a blog post"*, *"create an invoice"*). You can freely choose those
+strings. The only requirement is that they must start with the ``ROLE_`` prefix
+(e.g. ``ROLE_POST_EDIT``, ``ROLE_INVOICE_CREATE``).
Using the Decision Manager
--------------------------
@@ -203,8 +222,8 @@ It uses an access map (which should be an instance of :class:`Symfony\\Component
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\AccessMap;
use Symfony\Component\Security\Http\Firewall\AccessListener;
$accessMap = new AccessMap();
diff --git a/components/security/firewall.rst b/components/security/firewall.rst
index eca13908727..17b7d03e0d2 100644
--- a/components/security/firewall.rst
+++ b/components/security/firewall.rst
@@ -52,9 +52,9 @@ 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;
+ use Symfony\Component\Security\Http\FirewallMap;
$firewallMap = new FirewallMap();
@@ -70,8 +70,8 @@ the user::
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;
+ use Symfony\Component\Security\Http\Firewall;
// the EventDispatcher used by the HttpKernel
$dispatcher = ...;
diff --git a/components/serializer.rst b/components/serializer.rst
index 3a2de500aa6..4eba1055433 100644
--- a/components/serializer.rst
+++ b/components/serializer.rst
@@ -29,8 +29,6 @@ Installation
$ composer require symfony/serializer
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
To use the ``ObjectNormalizer``, the :doc:`PropertyAccess component `
@@ -41,18 +39,20 @@ Usage
.. seealso::
- This article explains how to use the Serializer features as an independent
- component in any PHP application. Read the :doc:`/serializer` article to
- learn about how to use it in Symfony applications.
+ This article explains the philosophy of the Serializer and gets you familiar
+ with the concepts of normalizers and encoders. The code examples assume
+ that you use the Serializer as an independent component. If you are using
+ the Serializer in a Symfony application, read :doc:`/serializer` after you
+ finish this article.
To use the Serializer component, set up the
:class:`Symfony\\Component\\Serializer\\Serializer` specifying which encoders
and normalizer are going to be available::
- use Symfony\Component\Serializer\Serializer;
- use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
+ use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+ use Symfony\Component\Serializer\Serializer;
$encoders = [new XmlEncoder(), new JsonEncoder()];
$normalizers = [new ObjectNormalizer()];
@@ -212,6 +212,22 @@ The serializer can also be used to update an existing object::
This is a common need when working with an ORM.
+The ``OBJECT_TO_POPULATE`` is only used for the top level object. If that object
+is the root of a tree structure, all child elements that exist in the
+normalized data will be re-created with new instances.
+
+When the ``AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE`` option is set to
+true, existing children of the root ``OBJECT_TO_POPULATE`` are updated from the
+normalized data, instead of the denormalizer re-creating them. Note that
+``DEEP_OBJECT_TO_POPULATE`` only works for single child objects, but not for
+arrays of objects. Those will still be replaced when present in the normalized
+data.
+
+.. versionadded:: 4.3
+
+ The ``AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE`` option was
+ introduced in Symfony 4.3.
+
.. _component-serializer-attributes-groups:
Attributes Groups
@@ -245,23 +261,30 @@ The definition of serialization can be specified using annotations, XML
or YAML. The :class:`Symfony\\Component\\Serializer\\Mapping\\Factory\\ClassMetadataFactory`
that will be used by the normalizer must be aware of the format to use.
-Initialize the :class:`Symfony\\Component\\Serializer\\Mapping\\Factory\\ClassMetadataFactory`
-like the following::
+The following code shows how to initialize the :class:`Symfony\\Component\\Serializer\\Mapping\\Factory\\ClassMetadataFactory`
+for each format:
+
+* Annotations in PHP files::
- use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
- // For annotations
use Doctrine\Common\Annotations\AnnotationReader;
+ use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
- // For XML
- // use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
- // For YAML
- // use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
- // For XML
- // $classMetadataFactory = new ClassMetadataFactory(new XmlFileLoader('/path/to/your/definition.xml'));
- // For YAML
- // $classMetadataFactory = new ClassMetadataFactory(new YamlFileLoader('/path/to/your/definition.yaml'));
+
+* YAML files::
+
+ use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
+ use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;
+
+ $classMetadataFactory = new ClassMetadataFactory(new YamlFileLoader('/path/to/your/definition.yaml'));
+
+* XML files::
+
+ use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
+ use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
+
+ $classMetadataFactory = new ClassMetadataFactory(new XmlFileLoader('/path/to/your/definition.xml'));
.. _component-serializer-attributes-groups-annotations:
@@ -308,7 +331,7 @@ Then, create your groups definition:
@@ -324,8 +347,8 @@ Then, create your groups definition:
You are now able to serialize only attributes in the groups you want::
- use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+ use Symfony\Component\Serializer\Serializer;
$obj = new MyObj();
$obj->foo = 'foo';
@@ -334,7 +357,7 @@ You are now able to serialize only attributes in the groups you want::
$normalizer = new ObjectNormalizer($classMetadataFactory);
$serializer = new Serializer([$normalizer]);
- $data = $serializer->normalize($obj, null, ['groups' => ['group1']]);
+ $data = $serializer->normalize($obj, null, ['groups' => 'group1']);
// $data = ['foo' => 'foo'];
$obj2 = $serializer->denormalize(
@@ -354,8 +377,8 @@ Selecting Specific Attributes
It is also possible to serialize only a set of specific attributes::
- use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+ use Symfony\Component\Serializer\Serializer;
class User
{
@@ -392,26 +415,30 @@ As for groups, attributes can be selected during both the serialization and dese
Ignoring Attributes
-------------------
-.. note::
-
- Using attribute groups instead of the :method:`Symfony\\Component\\Serializer\\Normalizer\\AbstractNormalizer::setIgnoredAttributes`
- method is considered best practice.
-
-As an option, there's a way to ignore attributes from the origin object. To remove
-those attributes use the
-:method:`Symfony\\Component\\Serializer\\Normalizer\\AbstractNormalizer::setIgnoredAttributes`
-method on the normalizer definition::
+As an option, there's a way to ignore attributes from the origin object.
+To remove those attributes provide an array via the ``ignored_attributes``
+key in the ``context`` parameter of the desired serializer method::
- use Symfony\Component\Serializer\Serializer;
+ use Acme\Person;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+ use Symfony\Component\Serializer\Serializer;
+
+ $person = new Person();
+ $person->setName('foo');
+ $person->setAge(99);
$normalizer = new ObjectNormalizer();
- $normalizer->setIgnoredAttributes(['age']);
$encoder = new JsonEncoder();
$serializer = new Serializer([$normalizer], [$encoder]);
- $serializer->serialize($person, 'json'); // Output: {"name":"foo","sportsperson":false}
+ $serializer->serialize($person, 'json', ['ignored_attributes' => ['age']]); // Output: {"name":"foo"}
+
+.. deprecated:: 4.2
+
+ The :method:`Symfony\\Component\\Serializer\\Normalizer\\AbstractNormalizer::setIgnoredAttributes`
+ method that was used as an alternative to the ``ignored_attributes`` option
+ was deprecated in Symfony 4.2.
.. _component-serializer-converting-property-names-when-serializing-and-deserializing:
@@ -478,6 +505,12 @@ and :class:`Symfony\\Component\\Serializer\\Normalizer\\PropertyNormalizer`::
$companyCopy = $serializer->deserialize($json, Company::class, 'json');
// Same data as $company
+.. note::
+
+ You can also implement
+ :class:`Symfony\\Component\\Serializer\\NameConverter\\AdvancedNameConverterInterface`
+ to access to the current class name, format and context.
+
.. _using-camelized-method-names-for-underscored-attributes:
CamelCase to snake_case
@@ -519,6 +552,80 @@ processes::
$anne = $normalizer->denormalize(['first_name' => 'Anne'], 'Person');
// Person object with firstName: 'Anne'
+Configure name conversion using metadata
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When using this component inside a Symfony application and the class metadata
+factory is enabled as explained in the :ref:`Attributes Groups section `,
+this is already set up and you only need to provide the configuration. Otherwise::
+
+ // ...
+ use Symfony\Component\Serializer\Encoder\JsonEncoder;
+ use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter;
+ use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+ use Symfony\Component\Serializer\Serializer;
+
+ $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
+
+ $metadataAwareNameConverter = new MetadataAwareNameConverter($classMetadataFactory);
+
+ $serializer = new Serializer(
+ [new ObjectNormalizer($classMetadataFactory, $metadataAwareNameConverter)],
+ ['json' => new JsonEncoder()]
+ );
+
+Now configure your name conversion mapping. Consider an application that
+defines a ``Person`` entity with a ``firstName`` property:
+
+.. configuration-block::
+
+ .. code-block:: php-annotations
+
+ namespace App\Entity;
+
+ use Symfony\Component\Serializer\Annotation\SerializedName;
+
+ class Person
+ {
+ /**
+ * @SerializedName("customer_name")
+ */
+ private $firstName;
+
+ public function __construct($firstName)
+ {
+ $this->firstName = $firstName;
+ }
+
+ // ...
+ }
+
+ .. code-block:: yaml
+
+ App\Entity\Person:
+ attributes:
+ firstName:
+ serialized_name: customer_name
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+This custom mapping is used to convert property names when serializing and
+deserializing objects::
+
+ $serialized = $serializer->serialize(new Person("Kévin"));
+ // {"customer_name": "Kévin"}
+
Serializing Boolean Attributes
------------------------------
@@ -532,6 +639,12 @@ and ``remove``.
Using Callbacks to Serialize Properties with Object Instances
-------------------------------------------------------------
+.. deprecated:: 4.2
+
+ The :method:`Symfony\\Component\\Serializer\\Normalizer\\AbstractNormalizer::setCallbacks`
+ method is deprecated since Symfony 4.2. Use the ``callbacks``
+ key of the context instead.
+
When serializing, you can set a callback to format a specific object property::
use App\Model\Person;
@@ -540,15 +653,19 @@ When serializing, you can set a callback to format a specific object property::
use Symfony\Component\Serializer\Serializer;
$encoder = new JsonEncoder();
- $normalizer = new GetSetMethodNormalizer();
- $callback = function ($dateTime) {
- return $dateTime instanceof \DateTime
- ? $dateTime->format(\DateTime::ISO8601)
- : '';
+ // all callback parameters are optional (you can omit the ones you don't use)
+ $dateCallback = function ($innerObject, $outerObject, string $attributeName, string $format = null, array $context = []) {
+ return $innerObject instanceof \DateTime ? $innerObject->format(\DateTime::ISO8601) : '';
};
- $normalizer->setCallbacks(['createdAt' => $callback]);
+ $defaultContext = [
+ AbstractNormalizer::CALLBACKS => [
+ 'createdAt' => $dateCallback,
+ ],
+ ];
+
+ $normalizer = new GetSetMethodNormalizer(null, null, null, null, null, $defaultContext);
$serializer = new Serializer([$normalizer], [$encoder]);
@@ -560,6 +677,11 @@ When serializing, you can set a callback to format a specific object property::
$serializer->serialize($person, 'json');
// Output: {"name":"cordoval", "age": 34, "createdAt": "2014-03-22T09:43:12-0500"}
+.. deprecated:: 4.2
+
+ The :method:`Symfony\\Component\\Serializer\\Normalizer\\AbstractNormalizer::setCallbacks` is deprecated since
+ Symfony 4.2, use the "callbacks" key of the context instead.
+
.. _component-serializer-normalizers:
Normalizers
@@ -574,7 +696,7 @@ There are several types of normalizers available:
calling the constructor during the denormalization process.
Objects are normalized to a map of property names and values (names are
- generated removing the ``get``, ``set``, ``has`` or ``remove`` prefix from
+ generated removing the ``get``, ``set``, ``has``, ``is`` or ``remove`` prefix from
the method name and lowercasing the first letter; e.g. ``getFirstName()`` ->
``firstName``).
@@ -613,7 +735,7 @@ There are several types of normalizers available:
:class:`Symfony\\Component\\Serializer\\Normalizer\\DateTimeNormalizer`
This normalizer converts :phpclass:`DateTimeInterface` objects (e.g.
:phpclass:`DateTime` and :phpclass:`DateTimeImmutable`) into strings.
- By default it uses the RFC3339_ format.
+ By default, it uses the `RFC3339`_ format.
:class:`Symfony\\Component\\Serializer\\Normalizer\\DataUriNormalizer`
This normalizer converts :phpclass:`SplFileInfo` objects into a data URI
@@ -621,17 +743,13 @@ There are several types of normalizers available:
:class:`Symfony\\Component\\Serializer\\Normalizer\\DateIntervalNormalizer`
This normalizer converts :phpclass:`DateInterval` objects into strings.
- By default it uses the ``P%yY%mM%dDT%hH%iM%sS`` format.
+ By default, it uses the ``P%yY%mM%dDT%hH%iM%sS`` format.
:class:`Symfony\\Component\\Serializer\\Normalizer\\ConstraintViolationListNormalizer`
This normalizer converts objects that implement
:class:`Symfony\\Component\\Validator\\ConstraintViolationListInterface`
into a list of errors according to the `RFC 7807`_ standard.
- .. versionadded:: 4.1
-
- The ``ConstraintViolationListNormalizer`` was introduced in Symfony 4.1.
-
.. _component-serializer-encoders:
Encoders
@@ -645,9 +763,9 @@ for encoding (array to format) and
You can add new encoders to a Serializer instance by using its second constructor argument::
- use Symfony\Component\Serializer\Serializer;
- use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
+ use Symfony\Component\Serializer\Encoder\XmlEncoder;
+ use Symfony\Component\Serializer\Serializer;
$encoders = [new XmlEncoder(), new JsonEncoder()];
$serializer = new Serializer([], $encoders);
@@ -658,17 +776,17 @@ Built-in Encoders
The Serializer component provides several built-in encoders:
:class:`Symfony\\Component\\Serializer\\Encoder\\JsonEncoder`
- This class encodes and decodes data in JSON_.
+ This class encodes and decodes data in `JSON`_.
:class:`Symfony\\Component\\Serializer\\Encoder\\XmlEncoder`
- This class encodes and decodes data in XML_.
+ This class encodes and decodes data in `XML`_.
:class:`Symfony\\Component\\Serializer\\Encoder\\YamlEncoder`
- This encoder encodes and decodes data in YAML_. This encoder requires the
+ This encoder encodes and decodes data in `YAML`_. This encoder requires the
:doc:`Yaml Component `.
:class:`Symfony\\Component\\Serializer\\Encoder\\CsvEncoder`
- This encoder encodes and decodes data in CSV_.
+ This encoder encodes and decodes data in `CSV`_.
All these encoders are enabled by default when using the Serializer component
in a Symfony application.
@@ -687,8 +805,9 @@ The ``CsvEncoder`` encodes to and decodes from CSV.
You can pass the context key ``as_collection`` in order to have the results
always as a collection.
-.. versionadded:: 4.1
- The ``as_collection`` option was introduced in Symfony 4.1.
+.. deprecated:: 4.2
+
+ Relying on the default value ``false`` is deprecated since Symfony 4.2.
The ``XmlEncoder``
~~~~~~~~~~~~~~~~~~
@@ -708,30 +827,33 @@ The ``XmlEncoder`` will encode this object like that::
1
-Be aware that this encoder will consider keys beginning with ``@`` as attributes::
+Be aware that this encoder will consider keys beginning with ``@`` as attributes, and will use
+the key ``#comment`` for encoding XML comments::
$encoder = new XmlEncoder();
- $encoder->encode(['foo' => ['@bar' => 'value']], 'xml');
+ $encoder->encode([
+ 'foo' => ['@bar' => 'value'],
+ 'qux' => ['#comment' => 'A comment'],
+ ], 'xml');
// will return:
//
//
- //
+ //
+ //
//
You can pass the context key ``as_collection`` in order to have the results
always as a collection.
-.. versionadded:: 4.1
- The ``as_collection`` option was introduced in Symfony 4.1.
-
.. tip::
XML comments are ignored by default when decoding contents, but this
- behavior can be changed with the optional ``$ignoredNodeTypes`` argument of
+ behavior can be changed with the optional ``$decoderIgnoredNodeTypes`` argument of
the ``XmlEncoder`` class constructor.
- .. versionadded:: 4.1
- XML comments are ignored by default starting from Symfony 4.1.
+ Data with ``#comment`` keys are encoded to XML comments by default. This can be
+ changed with the optional ``$encoderIgnoredNodeTypes`` argument of the
+ ``XmlEncoder`` class constructor.
The ``YamlEncoder``
~~~~~~~~~~~~~~~~~~~
@@ -739,6 +861,22 @@ The ``YamlEncoder``
This encoder requires the :doc:`Yaml Component ` and
transforms from and to Yaml.
+Skipping ``null`` Values
+------------------------
+
+By default, the Serializer will preserve properties containing a ``null`` value.
+You can change this behavior by setting the ``skip_null_values`` context option
+to ``true``::
+
+ $dummy = new class {
+ public $foo;
+ public $bar = 'notNull';
+ };
+
+ $normalizer = new ObjectNormalizer();
+ $result = $normalizer->normalize($dummy, 'json', ['skip_null_values' => true]);
+ // ['bar' => 'notNull']
+
.. _component-serializer-handling-circular-references:
Handling Circular References
@@ -814,25 +952,32 @@ when such a case is encountered::
echo $serializer->serialize($organization, 'json'); // Throws a CircularReferenceException
-The ``setCircularReferenceLimit()`` method of this normalizer sets the number
-of times it will serialize the same object before considering it a circular
-reference. Its default value is ``1``.
+The key ``circular_reference_limit`` in the default context sets the number of
+times it will serialize the same object before considering it a circular
+reference. The default value is ``1``.
Instead of throwing an exception, circular references can also be handled
by custom callables. This is especially useful when serializing entities
having unique identifiers::
$encoder = new JsonEncoder();
- $normalizer = new ObjectNormalizer();
-
- $normalizer->setCircularReferenceHandler(function ($object) {
- return $object->getName();
- });
+ $defaultContext = [
+ AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object, $format, $context) {
+ return $object->getName();
+ },
+ ];
+ $normalizer = new ObjectNormalizer(null, null, null, null, null, null, $defaultContext);
$serializer = new Serializer([$normalizer], [$encoder]);
var_dump($serializer->serialize($org, 'json'));
// {"name":"Les-Tilleuls.coop","members":[{"name":"K\u00e9vin", organization: "Les-Tilleuls.coop"}]}
+.. deprecated:: 4.2
+
+ The :method:`Symfony\\Component\\Serializer\\Normalizer\\AbstractNormalizer::setCircularReferenceHandler`
+ method is deprecated since Symfony 4.2. Use the ``circular_reference_handler``
+ key of the context instead.
+
Handling Serialization Depth
----------------------------
@@ -870,10 +1015,10 @@ Here, we set it to 2 for the ``$child`` property:
.. code-block:: php-annotations
- use Symfony\Component\Serializer\Annotation\MaxDepth;
-
namespace Acme;
+ use Symfony\Component\Serializer\Annotation\MaxDepth;
+
class MyObj
{
/**
@@ -897,10 +1042,10 @@ Here, we set it to 2 for the ``$child`` property:
-
+
@@ -919,11 +1064,11 @@ because it is deeper than the configured maximum depth of 2::
$result = [
'foo' => 'level1',
'child' => [
- 'foo' => 'level2',
- 'child' => [
- 'child' => null,
- ],
+ 'foo' => 'level2',
+ 'child' => [
+ 'child' => null,
],
+ ],
];
*/
@@ -932,11 +1077,11 @@ maximum depth is reached. This is especially useful when serializing entities
having unique identifiers::
use Doctrine\Common\Annotations\AnnotationReader;
- use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Annotation\MaxDepth;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+ use Symfony\Component\Serializer\Serializer;
class Foo
{
@@ -960,10 +1105,16 @@ having unique identifiers::
$level2->child = $level3;
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
- $normalizer = new ObjectNormalizer($classMetadataFactory);
- $normalizer->setMaxDepthHandler(function ($foo) {
- return '/foos/'.$foo->id;
- });
+
+ // all callback parameters are optional (you can omit the ones you don't use)
+ $maxDepthHandler = function ($innerObject, $outerObject, string $attributeName, string $format = null, array $context = []) {
+ return '/foos/'.$innerObject->id;
+ };
+
+ $defaultContext = [
+ AbstractObjectNormalizer::MAX_DEPTH_HANDLER => $maxDepthHandler,
+ ];
+ $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, null, null, null, $defaultContext);
$serializer = new Serializer([$normalizer]);
@@ -978,8 +1129,11 @@ having unique identifiers::
];
*/
-.. versionadded:: 4.1
- The ``setMaxDepthHandler()`` method was introduced in Symfony 4.1.
+.. deprecated:: 4.2
+
+ The :method:`Symfony\\Component\\Serializer\\Normalizer\\AbstractNormalizer::setMaxDepthHandler`
+ method is deprecated since Symfony 4.2. Use the ``max_depth_handler``
+ key of the context instead.
Handling Arrays
---------------
@@ -1008,9 +1162,7 @@ If you want to deserialize such a structure, you need to add the
:class:`Symfony\\Component\\Serializer\\Normalizer\\ArrayDenormalizer`
to the set of normalizers. By appending ``[]`` to the type parameter of the
:method:`Symfony\\Component\\Serializer\\Serializer::deserialize` method,
-you indicate that you're expecting an array instead of a single object.
-
-.. code-block:: php
+you indicate that you're expecting an array instead of a single object::
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
@@ -1051,7 +1203,7 @@ The array keys beginning with ``@`` are considered XML attributes::
// is encoded as follows:
//
//
- //
+ //
//
Use the special ``#`` key to define the data of a node::
@@ -1097,16 +1249,13 @@ These are the options available:
Handling Constructor Arguments
------------------------------
-.. versionadded:: 4.1
- The ``default_constructor_arguments`` option was introduced in Symfony 4.1.
-
If the class constructor defines arguments, as usually happens with
`Value Objects`_, the serializer won't be able to create the object if some
arguments are missing. In those cases, use the ``default_constructor_arguments``
context option::
- use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+ use Symfony\Component\Serializer\Serializer;
class MyObj
{
@@ -1144,12 +1293,12 @@ When using the component standalone, an implementation of :class:`Symfony\\Compo
(usually an instance of :class:`Symfony\\Component\\PropertyInfo\\PropertyInfoExtractor`) must be passed as the 4th
parameter of the ``ObjectNormalizer``::
+ namespace Acme;
+
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
- use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
-
- namespace Acme;
+ use Symfony\Component\Serializer\Serializer;
class ObjectOuter
{
@@ -1188,7 +1337,7 @@ parameter of the ``ObjectNormalizer``::
$obj = $serializer->denormalize(
['inner' => ['foo' => 'foo', 'bar' => 'bar'], 'date' => '1988/01/21'],
- 'Acme\ObjectOuter'
+ 'Acme\ObjectOuter'
);
dump($obj->getInner()->foo); // 'foo'
@@ -1225,8 +1374,8 @@ this is already set up and you only need to provide the configuration. Otherwise
// ...
use Symfony\Component\Serializer\Encoder\JsonEncoder;
- use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping;
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata;
+ use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
@@ -1277,12 +1426,12 @@ and ``BitBucketCodeRepository`` classes:
-
-
+
+
@@ -1316,7 +1465,7 @@ and return ``true`` when
:method:`Symfony\\Component\\Serializer\\Normalizer\\CacheableSupportsMethodInterface::hasCacheableSupportsMethod`
is called.
- .. note::
+.. note::
All built-in :ref:`normalizers and denormalizers `
as well the ones included in `API Platform`_ natively implement this interface.
@@ -1333,7 +1482,7 @@ Learn more
.. seealso::
Normalizers for the Symfony Serializer Component supporting popular web API formats
- (JSON-LD, GraphQL, HAL and JSONAPI) are available as part of the `API Platform`_ project.
+ (JSON-LD, GraphQL, OpenAPI, HAL, JSON:API) are available as part of the `API Platform`_ project.
.. seealso::
@@ -1343,7 +1492,6 @@ Learn more
.. _`PSR-1 standard`: https://www.php-fig.org/psr/psr-1/
.. _`JMS serializer`: https://github.com/schmittjoh/serializer
-.. _Packagist: https://packagist.org/packages/symfony/serializer
.. _RFC3339: https://tools.ietf.org/html/rfc3339#section-5.8
.. _JSON: http://www.json.org/
.. _XML: https://www.w3.org/XML/
diff --git a/components/stopwatch.rst b/components/stopwatch.rst
index c792b0eb90f..a000bca1be8 100644
--- a/components/stopwatch.rst
+++ b/components/stopwatch.rst
@@ -14,14 +14,12 @@ Installation
$ composer require symfony/stopwatch
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
-----
-The Stopwatch component provides an easy and consistent way to measure execution
+The Stopwatch component provides a consistent way to measure execution
time of certain parts of code so that you don't constantly have to parse
microtime by yourself. Instead, use the
:class:`Symfony\\Component\\Stopwatch\\Stopwatch` class::
@@ -63,9 +61,8 @@ Symfony Profiler tool uses categories to nicely color-code different events.
.. tip::
- When you want to show events in the Symfony profiler, autowire
- ``Symfony\Component\Stopwatch\Stopwatch`` into your service. Each category
- is shown on a separate line.
+ Read :ref:`this article ` to learn more about
+ integrating the Stopwatch component into the Symfony profiler.
Periods
-------
@@ -122,5 +119,3 @@ 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
index 9639eb39719..ea6b8d11e83 100644
--- a/components/templating.rst
+++ b/components/templating.rst
@@ -20,8 +20,6 @@ Installation
$ composer require symfony/templating
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
@@ -41,9 +39,9 @@ template reference (:class:`Symfony\\Component\\Templating\\TemplateReferenceInt
It also needs a template loader (:class:`Symfony\\Component\\Templating\\Loader\\LoaderInterface`)
which uses the template reference to actually find and load the template::
+ use Symfony\Component\Templating\Loader\FilesystemLoader;
use Symfony\Component\Templating\PhpEngine;
use Symfony\Component\Templating\TemplateNameParser;
- use Symfony\Component\Templating\Loader\FilesystemLoader;
$filesystemLoader = new FilesystemLoader(__DIR__.'/views/%name%');
@@ -197,8 +195,8 @@ choose which one to use for the template, the
method is used::
use Acme\Templating\CustomEngine;
- use Symfony\Component\Templating\PhpEngine;
use Symfony\Component\Templating\DelegatingEngine;
+ use Symfony\Component\Templating\PhpEngine;
$templating = new DelegatingEngine([
new PhpEngine(...),
@@ -215,5 +213,3 @@ Learn More
/components/templating/*
/templating
/templating/*
-
-.. _Packagist: https://packagist.org/packages/symfony/templating
diff --git a/components/translation.rst b/components/translation.rst
index 7b99002535e..fe7ab4c3d79 100644
--- a/components/translation.rst
+++ b/components/translation.rst
@@ -15,8 +15,6 @@ Installation
$ composer require symfony/translation
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
.. seealso::
@@ -162,12 +160,17 @@ Fallback Locales
If the message is not located in the catalog of the specific locale, the
translator will look into the catalog of one or more fallback locales. For
-example, assume you're trying to translate into the ``fr_FR`` locale:
+example, assume you're trying to translate into the ``es_AR`` locale:
+
+#. First, the translator looks for the translation in the ``es_AR``
+ (Argentinean Spanish) locale;
-#. First, the translator looks for the translation in the ``fr_FR`` locale;
+#. If it wasn't found, the translator looks for the translation in the parent
+ locale, which is automatically defined only for some locales. In this
+ example, the parent locale is ``es_419`` (Latin American Spanish);
-#. If it wasn't found, the translator looks for the translation in the ``fr``
- locale;
+#. If it wasn't found, the translator looks for the translation in the ``es``
+ (Spanish) locale;
#. If the translation still isn't found, the translator uses the one or more
fallback locales set explicitly on the translator.
@@ -229,6 +232,5 @@ Learn More
/translation/*
/validation/translations
-.. _Packagist: https://packagist.org/packages/symfony/translation
.. _`ISO 3166-1 alpha-2`: https://en.wikipedia.org/wiki/ISO_3166-1#Current_codes
.. _`ISO 639-1`: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
diff --git a/components/translation/custom_formats.rst b/components/translation/custom_formats.rst
index 3d90d5861f9..98721a17dec 100644
--- a/components/translation/custom_formats.rst
+++ b/components/translation/custom_formats.rst
@@ -30,8 +30,8 @@ new class that implements the
method will get a filename and parse it into an array. Then, it will
create the catalog that will be returned::
- use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Loader\LoaderInterface;
+ use Symfony\Component\Translation\MessageCatalogue;
class MyFormatLoader implements LoaderInterface
{
@@ -80,8 +80,8 @@ must be created. To write the dump contents into a file, extending the
:class:`Symfony\\Component\\Translation\\Dumper\\FileDumper` class
will save a few lines::
- use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Dumper\FileDumper;
+ use Symfony\Component\Translation\MessageCatalogue;
class MyFormatDumper extends FileDumper
{
diff --git a/components/translation/usage.rst b/components/translation/usage.rst
index 6391a9db1a0..e9fa2a6bdcb 100644
--- a/components/translation/usage.rst
+++ b/components/translation/usage.rst
@@ -6,8 +6,8 @@ Using the Translator
Imagine you want to translate the string *"Symfony is great"* into French::
- use Symfony\Component\Translation\Translator;
use Symfony\Component\Translation\Loader\ArrayLoader;
+ use Symfony\Component\Translation\Translator;
$translator = new Translator('fr_FR');
$translator->addLoader('array', new ArrayLoader());
@@ -21,79 +21,6 @@ In this example, the message *"Symfony is great!"* will be translated into
the locale set in the constructor (``fr_FR``) if the message exists in one of
the message catalogs.
-.. _component-translation-placeholders:
-
-Message Placeholders
---------------------
-
-Sometimes, a message containing a variable needs to be translated::
-
- // ...
- $translated = $translator->trans('Hello '.$name);
-
- var_dump($translated);
-
-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"::
-
- // ...
- $translated = $translator->trans(
- 'Hello %name%',
- ['%name%' => $name]
- );
-
- var_dump($translated);
-
-Symfony 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
-
- return [
- 'Hello %name%' => 'Bonjour %name%',
- ];
-
- .. code-block:: yaml
-
- 'Hello %name%': Bonjour %name%
-
-.. note::
-
- The placeholders can take on any form as the full message is reconstructed
- using the PHP :phpfunction:`strtr function`. But the ``%...%`` form
- is recommended, to avoid problems when using Twig.
-
-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 catalogs that define the translations
-for any number of different locales.
-
Creating Translations
---------------------
@@ -104,7 +31,7 @@ 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.
``symfony.great`` - see the sidebar below).
-Translation files can be created in several different formats, XLIFF being the
+Translation files can be created in several formats, XLIFF being the
recommended format. These files are parsed by one of the loader classes.
.. configuration-block::
@@ -194,7 +121,7 @@ recommended format. These files are parsed by one of the loader classes.
'has' => [
'bundles' => 'Symfony has bundles',
],
- ),
+ ],
'user' => [
'login' => 'Login',
],
@@ -222,141 +149,6 @@ recommended format. These files are parsed by one of the loader classes.
'user.login' => 'Login',
];
-.. _component-translation-pluralization:
-
-Pluralization
--------------
-
-Message pluralization is a tough topic as the rules can be quite complex. For
-instance, here is the mathematical 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::
-
- // the %count% placeholder is assigned to the second argument...
- $translator->transChoice(
- 'There is one apple|There are %count% apples',
- 10
- );
-
- // ...but you can define more placeholders if needed
- $translator->transChoice(
- 'Hurry up %name%! There is one apple left.|There are %count% apples left.',
- 10,
- // no need to include %count% here; Symfony does that for you
- ['%name%' => $user->getName()]
- );
-
-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:
-
-.. code-block:: text
-
- '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:
-
-.. code-block:: text
-
- '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 the Translator 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:
-
-.. code-block:: text
-
- '{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:
-
-.. code-block:: text
-
- '{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:
-
-.. code-block:: text
-
- {1,2,3,4}
-
-Or numbers between two other numbers:
-
-.. code-block:: text
-
- [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.
-
Forcing the Translator Locale
-----------------------------
@@ -431,30 +223,30 @@ loaded/dumped when using this component inside a Symfony application:
.. code-block:: xml
-
+
-
-
-
- new
- true
- user login
-
-
- original-content
- translated-content
-
-
-
+ srcLang="fr-FR" trgLang="en-US">
+
+
+
+ new
+ true
+ user login
+
+
+ original-content
+ translated-content
+
+
+
When using the standalone Translation component, call the ``setMetadata()``
method of the catalogue and pass the notes as arrays. This is for example the
code needed to generate the previous XLIFF file::
- use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Dumper\XliffFileDumper;
+ use Symfony\Component\Translation\MessageCatalogue;
$catalogue = new MessageCatalogue('en_US');
$catalogue->add([
@@ -473,4 +265,3 @@ code needed to generate the previous XLIFF file::
]);
.. _`L10n`: https://en.wikipedia.org/wiki/Internationalization_and_localization
-.. _`ISO 31-11`: https://en.wikipedia.org/wiki/Interval_(mathematics)#Notations_for_intervals
diff --git a/components/using_components.rst b/components/using_components.rst
index d63a0313363..0b77e4f6a4e 100644
--- a/components/using_components.rst
+++ b/components/using_components.rst
@@ -59,7 +59,7 @@ immediately::
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!
diff --git a/components/validator.rst b/components/validator.rst
index 77b531fc5f8..9028f8d6874 100644
--- a/components/validator.rst
+++ b/components/validator.rst
@@ -15,8 +15,6 @@ Installation
$ composer require symfony/validator
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
@@ -36,9 +34,9 @@ The Validator component behavior is based on two concepts:
The following example shows how to validate that a string is at least 10
characters long::
- use Symfony\Component\Validator\Validation;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
+ use Symfony\Component\Validator\Validation;
$validator = Validation::createValidator();
$violations = $validator->validate('Bernhard', [
@@ -91,4 +89,3 @@ Learn More
/validation/*
.. _`JSR-303 Bean Validation specification`: http://jcp.org/en/jsr/detail?id=303
-.. _Packagist: https://packagist.org/packages/symfony/validator
diff --git a/components/validator/metadata.rst b/components/validator/metadata.rst
index 77c4037cc48..f5df3fa68de 100755
--- a/components/validator/metadata.rst
+++ b/components/validator/metadata.rst
@@ -15,8 +15,8 @@ The following example shows how to validate that the ``$firstName`` property of
the ``Author`` class has at least 3 characters::
// ...
- use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints as Assert;
+ use Symfony\Component\Validator\Mapping\ClassMetadata;
class Author
{
@@ -51,8 +51,8 @@ doesn't match the first name of the user. First, create a public method called
Then, add the Validator component configuration to the class::
// ...
- use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints as Assert;
+ use Symfony\Component\Validator\Mapping\ClassMetadata;
class Author
{
@@ -85,8 +85,8 @@ validation logic::
Then, add the Validator component configuration to the class::
// ...
- use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints as Assert;
+ use Symfony\Component\Validator\Mapping\ClassMetadata;
class Author
{
diff --git a/components/validator/resources.rst b/components/validator/resources.rst
index 69feb66243c..a7cf3678c9d 100644
--- a/components/validator/resources.rst
+++ b/components/validator/resources.rst
@@ -35,8 +35,8 @@ method of the validator builder::
In this example, the validation metadata is retrieved executing the
``loadValidatorMetadata()`` method of the class::
- use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints as Assert;
+ use Symfony\Component\Validator\Mapping\ClassMetadata;
class User
{
@@ -71,7 +71,7 @@ configure the locations of these files::
use Symfony\Component\Validator\Validation;
$validator = Validation::createValidatorBuilder()
- ->addYamlMapping('config/validation.yaml')
+ ->addYamlMapping('validator/validation.yaml')
->getValidator();
.. note::
@@ -100,8 +100,8 @@ prefixed classes included in doc block comments (``/** ... */``). For example::
class User
{
/**
- * @Assert\NotBlank
- */
+ * @Assert\NotBlank
+ */
protected $name;
}
@@ -136,7 +136,7 @@ multiple mappings::
$validator = Validation::createValidatorBuilder()
->enableAnnotationMapping()
->addMethodMapping('loadValidatorMetadata')
- ->addXmlMapping('config/validation.xml')
+ ->addXmlMapping('validator/validation.xml')
->getValidator();
Caching
@@ -192,5 +192,3 @@ You can set this custom implementation using
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.
-
-.. _`Packagist`: https://packagist.org
diff --git a/components/var_dumper.rst b/components/var_dumper.rst
index 2378d6da7ec..1c48a021855 100644
--- a/components/var_dumper.rst
+++ b/components/var_dumper.rst
@@ -16,14 +16,12 @@ Installation
$ composer require --dev symfony/var-dumper
-Alternatively, you can clone the ``_ repository.
-
.. 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 symfony/debug-bundle`` to install it).
+ been installed (or run ``composer require --dev symfony/debug-bundle`` to install it).
.. _components-var-dumper-dump:
@@ -64,6 +62,12 @@ current PHP SAPI:
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`` or ``cli``.
+
.. note::
If you want to catch the dump output as a string, please read the
@@ -88,17 +92,11 @@ current PHP SAPI:
function. This function dumps the variables using ``dump()`` and
immediately ends the execution of the script (using :phpfunction:`exit`).
- .. versionadded:: 4.1
- The ``dd()`` helper method was introduced in Symfony 4.1.
-
.. _var-dumper-dump-server:
The Dump Server
---------------
-.. versionadded:: 4.1
- The dump server was introduced in Symfony 4.1.
-
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
@@ -111,11 +109,11 @@ server, which outputs it to its own console or to an HTML file:
.. code-block:: terminal
# displays the dumped data in the console:
- $ ./bin/console server:dump
+ $ 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:
- $ ./bin/console server:dump --format=html > dump.html
+ $ 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
@@ -137,10 +135,10 @@ the :ref:`dump_destination option ` of the
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:debug="http://symfony.com/schema/dic/debug"
xsi:schemaLocation="http://symfony.com/schema/dic/services
- http://symfony.com/schema/dic/services/services-1.0.xsd
- http://symfony.com/schema/dic/debug http://symfony.com/schema/dic/debug/debug-1.0.xsd">
+ https://symfony.com/schema/dic/services/services-1.0.xsd
+ http://symfony.com/schema/dic/debug https://symfony.com/schema/dic/debug/debug-1.0.xsd">
-
+
.. code-block:: php
@@ -154,13 +152,13 @@ Outside a Symfony application, use the :class:`Symfony\\Component\\VarDumper\\Du
require __DIR__.'/vendor/autoload.php';
- use Symfony\Component\VarDumper\VarDumper;
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();
@@ -221,10 +219,10 @@ option. Read more about this and other options in
If the dumped contents are complex, consider using the local search box to
look for specific variables or values. First, click anywhere on the dumped
- contents and then press :kbd:`Ctrl. + F` or :kbd:`Cmd. + F` to make the local
+ contents and then press ``Ctrl. + F`` or ``Cmd. + F`` to make the local
search box appear. All the common shortcuts to navigate the search results
- are supported (:kbd:`Ctrl. + G` or :kbd:`Cmd. + G`, :kbd:`F3`, etc.) When
- finished, press :kbd:`Esc.` to hide the box again.
+ are supported (``Ctrl. + G`` or ``Cmd. + G``, ``F3``, etc.) When
+ finished, press ``Esc.`` to hide the box again.
Using the VarDumper Component in your PHPUnit Test Suite
--------------------------------------------------------
@@ -246,10 +244,11 @@ This will provide you with two new assertions:
Example::
use PHPUnit\Framework\TestCase;
+ use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
class ExampleTest extends TestCase
{
- use \Symfony\Component\VarDumper\Test\VarDumperTestTrait;
+ use VarDumperTestTrait;
public function testWithDumpEquals()
{
@@ -271,10 +270,6 @@ Example::
}
}
-.. versionadded:: 4.1
- The possibility of passing non-string variables as the first argument of
- ``assertDumpEquals()`` was introduced in Symfony 4.1.
-
Dump Examples and Output
------------------------
@@ -411,5 +406,3 @@ Learn More
:glob:
var_dumper/*
-
-.. _Packagist: https://packagist.org/packages/symfony/var-dumper
diff --git a/components/var_dumper/advanced.rst b/components/var_dumper/advanced.rst
index 34355da2f4c..c90d94e6cdb 100644
--- a/components/var_dumper/advanced.rst
+++ b/components/var_dumper/advanced.rst
@@ -15,10 +15,10 @@ By adding a handler, you can customize the `Cloners`_, `Dumpers`_ and `Casters`_
as explained below. A simple implementation of a handler function might look
like this::
- use Symfony\Component\VarDumper\VarDumper;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
+ use Symfony\Component\VarDumper\VarDumper;
VarDumper::setHandler(function ($var) {
$cloner = new VarCloner();
@@ -179,9 +179,16 @@ method. They also typically implement the
them from re-implementing the logic required to walk through a
:class:`Symfony\\Component\\VarDumper\\Cloner\\Data` object's internal structure.
+The :class:`Symfony\\Component\\VarDumper\\Dumper\\HtmlDumper` uses a dark
+theme by default. Use the :method:`Symfony\\Component\\VarDumper\\Dumper\\HtmlDumper::setTheme`
+method to use a light theme::
+
+ // ...
+ $htmlDumper->setTheme('light');
+
The :class:`Symfony\\Component\\VarDumper\\Dumper\\HtmlDumper` limits string
length and nesting depth of the output to make it more readable. These options
-can be overriden by the third optional parameter of the
+can be overridden by the third optional parameter of the
:method:`dump(Data $data) `
method::
@@ -208,23 +215,24 @@ bit field of ``Caster::EXCLUDE_*`` constants and influences the expected
output produced by the different casters.
If ``DUMP_STRING_LENGTH`` is set, then the length of a string is displayed
-next to its content:
-
-.. code-block:: php
+next to its content::
+ use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\AbstractDumper;
use Symfony\Component\VarDumper\Dumper\CliDumper;
+ $varCloner = new VarCloner();
$var = ['test'];
+
$dumper = new CliDumper();
- echo $dumper->dump($var, true);
+ echo $dumper->dump($varCloner->cloneVar($var), true);
// array:1 [
// 0 => "test"
// ]
$dumper = new CliDumper(null, null, AbstractDumper::DUMP_STRING_LENGTH);
- echo $dumper->dump($var, true);
+ echo $dumper->dump($varCloner->cloneVar($var), true);
// (added string length before the string)
// array:1 [
@@ -232,23 +240,24 @@ next to its content:
// ]
If ``DUMP_LIGHT_ARRAY`` is set, then arrays are dumped in a shortened format
-similar to PHP's short array notation:
-
-.. code-block:: php
+similar to PHP's short array notation::
+ use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\AbstractDumper;
use Symfony\Component\VarDumper\Dumper\CliDumper;
+ $varCloner = new VarCloner();
$var = ['test'];
+
$dumper = new CliDumper();
- echo $dumper->dump($var, true);
+ echo $dumper->dump($varCloner->cloneVar($var), true);
// array:1 [
// 0 => "test"
// ]
$dumper = new CliDumper(null, null, AbstractDumper::DUMP_LIGHT_ARRAY);
- echo $dumper->dump($var, true);
+ echo $dumper->dump($varCloner->cloneVar($var), true);
// (no more array:1 prefix)
// [
@@ -256,16 +265,17 @@ similar to PHP's short array notation:
// ]
If you would like to use both options, then you can combine them by
-using a the logical OR operator ``|``:
-
-.. code-block:: php
+using the logical OR operator ``|``::
+ use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\AbstractDumper;
use Symfony\Component\VarDumper\Dumper\CliDumper;
+ $varCloner = new VarCloner();
$var = ['test'];
+
$dumper = new CliDumper(null, null, AbstractDumper::DUMP_STRING_LENGTH | AbstractDumper::DUMP_LIGHT_ARRAY);
- echo $dumper->dump($var, true);
+ echo $dumper->dump($varCloner->cloneVar($var), true);
// [
// 0 => (4) "test"
diff --git a/components/var_exporter.rst b/components/var_exporter.rst
new file mode 100644
index 00000000000..6b403f79263
--- /dev/null
+++ b/components/var_exporter.rst
@@ -0,0 +1,132 @@
+.. index::
+ single: VarExporter
+ single: Components; VarExporter
+
+The VarExporter Component
+=========================
+
+ The VarExporter component exports any serializable PHP data structure to
+ plain PHP code and allows to instantiate and populate objects without
+ calling their constructors.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require --dev symfony/var-exporter
+
+.. include:: /components/require_autoload.rst.inc
+
+Exporting/Serializing Variables
+-------------------------------
+
+The main feature of this component is to serialize PHP data structures to plain
+PHP code, similar to PHP's :phpfunction:`var_export` function::
+
+ use Symfony\Component\VarExporter\VarExporter;
+
+ $exported = VarExporter::export($someVariable);
+ // store the $exported data in some file or cache system for later reuse
+ $data = file_put_contents('exported.php', $exported);
+
+ // later, regenerate the original variable when you need it
+ $regeneratedVariable = require 'exported.php';
+
+The reason to use this component instead of ``serialize()`` or ``igbinary`` is
+performance: thanks to `OPcache`_, the resulting code is significantly faster
+and more memory efficient than using ``unserialize()`` or ``igbinary_unserialize()``.
+
+In addition, there are some minor differences:
+
+* If the original variable defines them, all the semantics associated with
+ ``serialize()`` (such as ``__wakeup()``, ``__sleep()``, and ``Serializable``)
+ are preserved (``var_export()`` ignores them);
+* References involving ``SplObjectStorage``, ``ArrayObject`` or ``ArrayIterator``
+ instances are preserved;
+* Missing classes throw a ``ClassNotFoundException`` instead of being
+ unserialized to ``PHP_Incomplete_Class`` objects;
+* ``Reflection*``, ``IteratorIterator`` and ``RecursiveIteratorIterator``
+ classes throw an exception when being serialized.
+
+The exported data is a `PSR-2`_ compatible PHP file. Consider for example the
+following class hierarchy::
+
+ abstract class AbstractClass
+ {
+ protected $foo;
+ private $bar;
+
+ protected function setBar($bar)
+ {
+ $this->bar = $bar;
+ }
+ }
+
+ class ConcreteClass extends AbstractClass
+ {
+ public function __construct()
+ {
+ $this->foo = 123;
+ $this->setBar(234);
+ }
+ }
+
+When exporting the ``ConcreteClass`` data with VarExporter, the generated PHP
+file looks like this::
+
+ [
+ 'foo' => [
+ 123,
+ ],
+ 'bar' => [
+ 234,
+ ],
+ ],
+ ],
+ $o[0],
+ []
+ );
+
+Instantiating PHP Classes
+-------------------------
+
+The other main feature provided by this component is an instantiator which can
+create objects and set their properties without calling their constructors or
+any other methods::
+
+ use Symfony\Component\VarExporter\Instantiator;
+
+ // creates an empty instance of Foo
+ $fooObject = Instantiator::instantiate(Foo::class);
+
+ // creates a Foo instance and sets one of its properties
+ $fooObject = Instantiator::instantiate(Foo::class, ['propertyName' => $propertyValue]);
+
+ // creates a Foo instance and sets a private property defined on its parent Bar class
+ $fooObject = Instantiator::instantiate(Foo::class, [], [
+ Bar::class => ['privateBarProperty' => $propertyValue],
+ ]);
+
+Instances of ``ArrayObject``, ``ArrayIterator`` and ``SplObjectHash`` can be
+created by using the special ``"\0"`` property name to define their internal value::
+
+ // Creates an SplObjectHash where $info1 is associated to $object1, etc.
+ $theObject = Instantiator::instantiate(SplObjectStorage::class, [
+ "\0" => [$object1, $info1, $object2, $info2...]
+ ]);
+
+ // creates an ArrayObject populated with $inputArray
+ $theObject = Instantiator::instantiate(ArrayObject::class, [
+ "\0" => [$inputArray]
+ ]);
+
+.. _`OPcache`: https://php.net/opcache
+.. _`PSR-2`: https://www.php-fig.org/psr/psr-2/
diff --git a/components/web_link.rst b/components/web_link.rst
index 9bc6e945a1d..634da8b81b7 100644
--- a/components/web_link.rst
+++ b/components/web_link.rst
@@ -5,8 +5,8 @@
The WebLink Component
======================
- The WebLink component provides tools to manage the ``Link`` HTTP header needed
- for `Web Linking`_ when using `HTTP/2 Server Push`_ as well as `Resource Hints`_.
+ The WebLink component provides tools to manage the ``Link`` HTTP header needed
+ for `Web Linking`_ when using `HTTP/2 Server Push`_ as well as `Resource Hints`_.
Installation
------------
@@ -15,8 +15,6 @@ Installation
$ composer require symfony/web-link
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Usage
@@ -24,16 +22,16 @@ Usage
The following example shows the component in action::
- use Fig\Link\GenericLinkProvider;
- use Fig\Link\Link;
- use Symfony\Component\WebLink\HttpHeaderSerializer;
+ use Fig\Link\GenericLinkProvider;
+ use Fig\Link\Link;
+ use Symfony\Component\WebLink\HttpHeaderSerializer;
- $linkProvider = (new GenericLinkProvider())
- ->withLink(new Link('preload', '/bootstrap.min.css'));
+ $linkProvider = (new GenericLinkProvider())
+ ->withLink(new Link('preload', '/bootstrap.min.css'));
- header('Link: '.(new HttpHeaderSerializer())->serialize($linkProvider->getLinks()));
+ header('Link: '.(new HttpHeaderSerializer())->serialize($linkProvider->getLinks()));
- echo 'Hello';
+ echo 'Hello';
Read the full :doc:`WebLink documentation ` to learn about all the
features of the component and its integration with the Symfony framework.
diff --git a/components/workflow.rst b/components/workflow.rst
index e1fcbc145c9..f04571d19b4 100644
--- a/components/workflow.rst
+++ b/components/workflow.rst
@@ -15,8 +15,6 @@ Installation
$ composer require symfony/workflow
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Creating a Workflow
@@ -34,28 +32,30 @@ a ``Definition`` and a way to write the states to the objects (i.e. an
instance of a :class:`Symfony\\Component\\Workflow\\MarkingStore\\MarkingStoreInterface`).
Consider the following example for a blog post. A post can have one of a number
-of predefined statuses (`draft`, `review`, `rejected`, `published`). In a workflow,
+of predefined statuses (`draft`, `reviewed`, `rejected`, `published`). In a workflow,
these statuses are called **places**. You can define the workflow like this::
use Symfony\Component\Workflow\DefinitionBuilder;
+ use Symfony\Component\Workflow\MarkingStore\MethodMarkingStore;
use Symfony\Component\Workflow\Transition;
use Symfony\Component\Workflow\Workflow;
- use Symfony\Component\Workflow\MarkingStore\SingleStateMarkingStore;
$definitionBuilder = new DefinitionBuilder();
- $definition = $definitionBuilder->addPlaces(['draft', 'review', 'rejected', 'published'])
+ $definition = $definitionBuilder->addPlaces(['draft', 'reviewed', 'rejected', 'published'])
// Transitions are defined with a unique name, an origin place and a destination place
- ->addTransition(new Transition('to_review', 'draft', 'review'))
- ->addTransition(new Transition('publish', 'review', 'published'))
- ->addTransition(new Transition('reject', 'review', 'rejected'))
+ ->addTransition(new Transition('to_review', 'draft', 'reviewed'))
+ ->addTransition(new Transition('publish', 'reviewed', 'published'))
+ ->addTransition(new Transition('reject', 'reviewed', 'rejected'))
->build()
;
- $marking = new SingleStateMarkingStore('currentState');
+ $singleState = true; // true if the subject can be in only one state at a given time
+ $property = 'currentState'; // subject property name where the state is stored
+ $marking = new MethodMarkingStore($singleState, $property);
$workflow = new Workflow($definition, $marking);
-The ``Workflow`` can now help you to decide what actions are allowed
-on a blog post depending on what *place* it is in. This will keep your domain
+The ``Workflow`` can now help you to decide what *transitions* (actions) are allowed
+on a blog post depending on what *place* (state) it is in. This will keep your domain
logic in one place and not spread all over your application.
When you define multiple workflows you should consider using a ``Registry``,
@@ -63,37 +63,36 @@ which is an object that stores and provides access to different workflows.
A registry will also help you to decide if a workflow supports the object you
are trying to use it with::
- use Symfony\Component\Workflow\Registry;
- use Symfony\Component\Workflow\SupportStrategy\InstanceOfSupportStrategy;
use Acme\Entity\BlogPost;
use Acme\Entity\Newsletter;
+ use Symfony\Component\Workflow\Registry;
+ use Symfony\Component\Workflow\SupportStrategy\InstanceOfSupportStrategy;
- $blogWorkflow = ...
+ $blogPostWorkflow = ...
$newsletterWorkflow = ...
$registry = new Registry();
- $registry->addWorkflow($blogWorkflow, new InstanceOfSupportStrategy(BlogPost::class));
+ $registry->addWorkflow($blogPostWorkflow, new InstanceOfSupportStrategy(BlogPost::class));
$registry->addWorkflow($newsletterWorkflow, new InstanceOfSupportStrategy(Newsletter::class));
-.. versionadded:: 4.1
- The ``addWorkflow()`` method was introduced in Symfony 4.1. In previous
- Symfony versions it was called ``add()``.
-
Usage
-----
-When you have configured a ``Registry`` with your workflows, you may use it as follows::
+When you have configured a ``Registry`` with your workflows,
+you can retrieve a workflow from it and use it as follows::
// ...
- $post = new BlogPost();
- $workflow = $registry->get($post);
+ // Consider that $blogPost is in place "draft" by default
+ $blogPost = new BlogPost();
+ $workflow = $registry->get($blogPost);
- $workflow->can($post, 'publish'); // False
- $workflow->can($post, 'to_review'); // True
+ $workflow->can($blogPost, 'publish'); // False
+ $workflow->can($blogPost, 'to_review'); // True
- $workflow->apply($post, 'to_review');
- $workflow->can($post, 'publish'); // True
- $workflow->getEnabledTransitions($post); // ['publish', 'reject']
+ $workflow->apply($blogPost, 'to_review'); // $blogPost is now in place "reviewed"
+
+ $workflow->can($blogPost, 'publish'); // True
+ $workflow->getEnabledTransitions($blogPost); // $blogPost can perform transition "publish" or "reject"
Learn more
----------
@@ -103,5 +102,3 @@ Learn more
:glob:
/workflow/*
-
-.. _Packagist: https://packagist.org/packages/symfony/workflow
diff --git a/components/yaml.rst b/components/yaml.rst
index bbbd1fbba03..93e9f30e0fd 100644
--- a/components/yaml.rst
+++ b/components/yaml.rst
@@ -33,8 +33,6 @@ Installation
$ composer require symfony/yaml
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
Why?
@@ -190,7 +188,7 @@ representation to the inline one::
Indentation
...........
-By default the YAML component will use 4 spaces for indentation. This can be
+By default, the YAML component will use 4 spaces for indentation. This can be
changed using the third argument as follows::
// uses 8 spaces for indentation
@@ -279,7 +277,7 @@ representation of the object as a map.
Handling Invalid Types
~~~~~~~~~~~~~~~~~~~~~~
-By default the parser will encode invalid types as ``null``. You can make the
+By default, the parser will encode invalid types as ``null``. You can make the
parser throw exceptions by using the ``PARSE_EXCEPTION_ON_INVALID_TYPE``
flag::
@@ -291,14 +289,12 @@ Similarly you can use ``DUMP_EXCEPTION_ON_INVALID_TYPE`` when dumping::
$data = new \stdClass(); // by default objects are invalid.
Yaml::dump($data, 2, 4, Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE); // throws an exception
- echo $yaml; // { foo: bar }
-
Date Handling
~~~~~~~~~~~~~
-By default the YAML parser will convert unquoted strings which look like a
+By default, the YAML parser will convert unquoted strings which look like a
date or a date-time into a Unix timestamp; for example ``2016-05-27`` or
-``2016-05-27T02:59:43.1Z`` (ISO-8601_)::
+``2016-05-27T02:59:43.1Z`` (`ISO-8601`_)::
Yaml::parse('2016-05-27'); // 1464307200
@@ -311,7 +307,7 @@ flag::
Dumping Multi-line Literal Blocks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-In YAML multiple lines can be represented as literal blocks, by default the
+In YAML, multiple lines can be represented as literal blocks. By default, the
dumper will encode multiple lines as an inline string::
$string = ["string" => "Multiple\nLine\nString"];
@@ -394,7 +390,6 @@ First, install the Console component:
Create a console application with ``lint:yaml`` as its only command::
// lint.php
-
use Symfony\Component\Console\Application;
use Symfony\Component\Yaml\Command\LintCommand;
@@ -411,9 +406,15 @@ Then, execute the script for validating contents:
# validates a single file
$ php lint.php path/to/file.yaml
+ # or validates multiple files
+ $ php lint.php path/to/file1.yaml path/to/file2.yaml
+
# or all the files in a directory
$ php lint.php path/to/directory
+ # or all the files in multiple directories
+ $ php lint.php path/to/directory1 path/to/directory2
+
# or contents passed to STDIN
$ cat path/to/file.yaml | php lint.php
@@ -439,7 +440,6 @@ Learn More
yaml/*
-.. _YAML: http://yaml.org/
-.. _Packagist: https://packagist.org/packages/symfony/yaml
+.. _`YAML`: http://yaml.org/
.. _`YAML 1.2 version specification`: http://yaml.org/spec/1.2/spec.html
-.. _ISO-8601: http://www.iso.org/iso/iso8601
+.. _`ISO-8601`: http://www.iso.org/iso/iso8601
diff --git a/components/yaml/yaml_format.rst b/components/yaml/yaml_format.rst
index 78802213765..d5f29b3937a 100644
--- a/components/yaml/yaml_format.rst
+++ b/components/yaml/yaml_format.rst
@@ -149,7 +149,7 @@ Booleans in YAML are expressed with ``true`` and ``false``.
Dates
~~~~~
-YAML uses the ISO-8601 standard to express dates:
+YAML uses the `ISO-8601`_ standard to express dates:
.. code-block:: yaml
@@ -286,8 +286,8 @@ Comments can be added in YAML by prefixing them with a hash mark (``#``):
.. note::
- Comments are simply ignored by the YAML parser and do not need to be
- indented according to the current level of nesting in a collection.
+ Comments are ignored by the YAML parser and do not need to be indented
+ according to the current level of nesting in a collection.
Explicit Typing
---------------
@@ -310,8 +310,6 @@ The YAML specification defines some tags to set the type of any data explicitly:
Pz7Y6OjuDg4J+fn5OTk6enp
56enmleECcgggoBADs=
-.. _YAML: http://yaml.org/
-
Unsupported YAML Features
-------------------------
@@ -320,12 +318,13 @@ The following YAML features are not supported by the Symfony Yaml component:
* Multi-documents (``---`` and ``...`` markers);
* Complex mapping keys and complex values starting with ``?``;
* Tagged values as keys;
-* The following tags and types: `!!set`, `!!omap`, `!!pairs`, `!!set`, `!!seq`,
- `!!bool`, `!!int`, `!!merge`, `!!null`, `!!timestamp`, `!!value`, `!!yaml`;
+* The following tags and types: `!!set`, `!!omap`, `!!pairs`, `!!seq`,
+ `!!bool`, `!!int`, `!!merge`, `!!null`, `!!timestamp`, `!!value`, `!!yaml`;
* Tags (``TAG`` directive; example: ``%TAG ! tag:example.com,2000:app/``)
and tag references (example: ``!``);
* Using sequence-like syntax for mapping elements (example: ``{foo, bar}``; use
``{foo: ~, bar: ~}`` instead).
+.. _`ISO-8601`: http://www.iso.org/iso/iso8601
.. _`YAML website`: http://yaml.org/
.. _`YAML specification`: http://www.yaml.org/spec/1.2/spec.html
diff --git a/configuration.rst b/configuration.rst
index b1fab7982c9..6314d0dbfb3 100644
--- a/configuration.rst
+++ b/configuration.rst
@@ -1,128 +1,145 @@
.. index::
single: Configuration
-Configuring Symfony (and Environments)
-======================================
+Configuring Symfony
+===================
-Symfony applications can install third-party packages (bundles, libraries, etc.)
-to bring in new features (:doc:`services `) to your project.
-Each package can be customized via configuration files that live - by default -
-in the ``config/`` directory.
+Configuration Files
+-------------------
-Configuration: config/packages/
--------------------------------
+Symfony applications are configured with the files stored in the ``config/``
+directory, which has this default structure:
-The configuration for each package can be found in ``config/packages/``. For
-instance, the framework bundle is configured in ``config/packages/framework.yaml``:
+.. code-block:: text
-.. configuration-block::
+ your-project/
+ ├─ config/
+ │ ├─ packages/
+ │ ├─ bundles.php
+ │ ├─ routes.yaml
+ │ └─ services.yaml
+ ├─ ...
- .. code-block:: yaml
+The ``routes.yaml`` file defines the :doc:`routing configuration `;
+the ``services.yaml`` file configures the services of the
+:doc:`service container `; the ``bundles.php`` file enables/
+disables packages in your application.
- # config/packages/framework.yaml
- framework:
- secret: '%env(APP_SECRET)%'
- #default_locale: en
- #csrf_protection: true
- #http_method_override: true
+You'll be working most in the ``config/packages/`` directory. This directory
+stores the configuration of every package installed in your application.
+Packages (also called "bundles" in Symfony and "plugins/modules" in other
+projects) add ready-to-use features to your projects.
- # Enables session support. Note that the session will ONLY be started if you read or write from it.
- # Remove or comment this section to explicitly disable session support.
- session:
- handler_id: ~
+When using :ref:`Symfony Flex `, which is enabled by default in
+Symfony applications, packages update the ``bundles.php`` file and create new
+files in ``config/packages/`` automatically during their installation. For
+example, this is the default file created by the "API Platform" package:
- #esi: true
- #fragments: true
- php_errors:
- log: true
+.. code-block:: yaml
- .. code-block:: xml
+ # config/packages/api_platform.yaml
+ api_platform:
+ mapping:
+ paths: ['%kernel.project_dir%/src/Entity']
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+Splitting the configuration into lots of small files is intimidating for some
+Symfony newcomers. However, you'll get used to them quickly and you rarely need
+to change these files after package installation
- .. code-block:: php
+.. tip::
- // config/packages/framework.php
- $container->loadFromExtension('framework', [
- 'secret' => '%env(APP_SECRET)%',
- //'default_locale' => 'en',
- //'csrf_protection' => true,
- //'http_method_override' => true,
-
- // Enables session support. Note that the session will ONLY be started if you read or write from it.
- // Remove or comment this section to explicitly disable session support.
- 'session' => [
- 'handler_id' => null,
- ],
- //'esi' => true,
- //'fragments' => true,
- 'php_errors' => [
- 'log' => true,
- ],
- ]);
+ To learn about all the available configuration options, check out the
+ :doc:`Symfony Configuration Reference ` or run the
+ ``config:dump-reference`` command.
-The top-level key (here ``framework``) references configuration for a specific
-bundle (:doc:`FrameworkBundle ` in this case).
+Configuration Formats
+~~~~~~~~~~~~~~~~~~~~~
-.. sidebar:: Configuration Formats
+Unlike other frameworks, Symfony doesn't impose you a specific format to
+configure your applications. Symfony lets you choose between YAML, XML and PHP
+and throughout the Symfony documentation, all configuration examples will be
+shown in these three formats.
- Throughout the documentation, all configuration examples will be shown in
- three formats (YAML, XML and PHP). YAML is used by default, but you can
- choose whatever you like best. There is no performance difference:
+There isn't any practical difference between formats. In fact, Symfony
+transforms and caches all of them into PHP before running the application, so
+there's not even any performance difference between them.
- * :doc:`/components/yaml/yaml_format`: Simple, clean and readable;
- * *XML*: More powerful than YAML at times & supports IDE autocompletion;
- * *PHP*: Very powerful but less readable than standard configuration formats.
+YAML is used by default when installing packages because it's concise and very
+readable. These are the main advantages and disadvantages of each format:
-Configuration Reference & Dumping
----------------------------------
+* **YAML**: simple, clean and readable, but not all IDEs support autocompletion
+ and validation for it. :doc:`Learn the YAML syntax `;
+* **XML**:autocompleted/validated by most IDEs and is parsed natively by PHP,
+ but sometimes it generates too verbose configuration. `Learn the XML syntax`_;
+* **PHP**: very powerful and it allows to create dynamic configuration, but the
+ resulting configuration is less readable than the other formats.
-There are *two* ways to know *what* keys you can configure:
+Importing Configuration Files
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-#. Use the :doc:`Reference Section `;
-#. Use the ``config:dump-reference`` command.
+Symfony loads configuration files using the :doc:`Config component
+`, which provides advanced features such as importing other
+configuration files, even if they use a different format:
-For example, if you want to configure something related to the framework bundle,
-you can see an example dump of all available configuration options by running:
+.. configuration-block::
-.. code-block:: terminal
+ .. code-block:: yaml
- $ php bin/console config:dump-reference framework
+ # config/services.yaml
+ imports:
+ - { resource: 'legacy_config.php' }
+ # ignore_errors silently discards errors if the loaded file doesn't exist
+ - { resource: 'my_config_file.xml', ignore_errors: true }
+ # glob expressions are also supported to load multiple files
+ - { resource: '/etc/myapp/*.yaml' }
-.. index::
- single: Environments; Introduction
+ # ...
-.. _page-creation-environments:
-.. _page-creation-prod-cache-clear:
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/services.php
+ $loader->import('legacy_config.xml');
+ // the third optional argument of import() is 'ignore_errors', which
+ // silently discards errors if the loaded file doesn't exist
+ $loader->import('my_config_file.yaml', null, true);
+ // glob expressions are also supported to load multiple files
+ $loader->import('/etc/myapp/*.yaml');
+
+ // ...
.. _config-parameter-intro:
+.. _config-parameters-yml:
+.. _configuration-parameters:
-The parameters Key: Parameters (Variables)
-------------------------------------------
+Configuration Parameters
+------------------------
-The configuration has some special top-level keys. One of them is called
-``parameters``: it's used to define *variables* that can be referenced in *any*
-other configuration file. For example, when you install the *translation*
-package, a ``locale`` parameter is added to ``config/services.yaml``:
+Sometimes the same configuration value is used in several configuration files.
+Instead of repeating it, you can define it as a "parameter", which is like a
+reusable configuration value. By convention, parameters are defined under the
+``parameters`` key in the ``config/services.yaml`` file:
.. configuration-block::
@@ -130,7 +147,22 @@ package, a ``locale`` parameter is added to ``config/services.yaml``:
# config/services.yaml
parameters:
- locale: en
+ # the parameter name is an arbitrary string (the 'app.' prefix is recommended
+ # to better differentiate your parameters from Symfony parameters).
+ app.admin_email: 'something@example.com'
+
+ # boolean parameters
+ app.enable_v2_protocol: true
+
+ # array/collection parameters
+ app.supported_locales: ['en', 'es', 'fr']
+
+ # binary content parameters (encode the contents with base64_encode())
+ app.some_parameter: !!binary VGhpcyBpcyBhIEJlbGwgY2hhciAH
+
+ # PHP constants as parameter values
+ app.some_constant: !php/const GLOBAL_CONSTANT
+ app.another_constant: !php/const App\Entity\BlogPost::MAX_ITEMS
# ...
@@ -142,12 +174,33 @@ package, a ``locale`` parameter is added to ``config/services.yaml``:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:framework="http://symfony.com/schema/dic/symfony"
xsi:schemaLocation="http://symfony.com/schema/dic/services
- http://symfony.com/schema/dic/services/services-1.0.xsd
+ https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony
- http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+ https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
- en
+
+ something@example.com
+
+
+ true
+
+ true
+
+
+
+ en
+ es
+ fr
+
+
+
+ VGhpcyBpcyBhIEJlbGwgY2hhciAH
+
+
+ GLOBAL_CONSTANT
+ App\Entity\BlogPost::MAX_ITEMS
@@ -156,92 +209,432 @@ package, a ``locale`` parameter is added to ``config/services.yaml``:
.. code-block:: php
// config/services.php
- $container->setParameter('locale', 'en');
+ // the parameter name is an arbitrary string (the 'app.' prefix is recommended
+ // to better differentiate your parameters from Symfony parameters).
+ $container->setParameter('app.admin_email', 'something@example.com');
+
+ // boolean parameters
+ $container->setParameter('app.enable_v2_protocol', true);
+
+ // array/collection parameters
+ $container->setParameter('app.supported_locales', ['en', 'es', 'fr']);
+
+ // binary content parameters (use the PHP escape sequences)
+ $container->setParameter('app.some_parameter', 'This is a Bell char: \x07');
+
+ // PHP constants as parameter values
+ use App\Entity\BlogPost;
+
+ $container->setParameter('app.some_constant', GLOBAL_CONSTANT);
+ $container->setParameter('app.another_constant', BlogPost::MAX_ITEMS);
+
// ...
-This parameter is then referenced in the framework config in
-``config/packages/translation.yaml``:
+.. caution::
+
+ When using XML configuration, the values between ```` tags are
+ not trimmed. This means that the value of the following parameter will be
+ ``'\n something@example.com\n'``:
+
+ .. code-block:: xml
+
+
+ something@example.com
+
+
+Once defined, you can reference this parameter value from any other
+configuration file using a special syntax: wrap the parameter name in two ``%``
+(e.g. ``%app.admin_email%``):
.. configuration-block::
.. code-block:: yaml
- # config/packages/translation.yaml
- framework:
+ # config/packages/some_package.yaml
+ some_package:
# any string surrounded by two % is replaced by that parameter value
- default_locale: '%locale%'
+ email_address: '%app.admin_email%'
# ...
.. code-block:: xml
-
+
+ https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
-
+
-
+
.. code-block:: php
- // config/packages/translation.php
- $container->loadFromExtension('framework', [
+ // config/packages/some_package.php
+ $container->loadFromExtension('some_package', [
// any string surrounded by two % is replaced by that parameter value
- 'default_locale' => '%locale%',
+ 'email_address' => '%app.admin_email%',
// ...
]);
-You can define whatever parameter names you want under the ``parameters`` key of
-any configuration file. To reference a parameter, surround its name with two
-percent signs - e.g. ``%locale%``.
+.. note::
+
+ If some parameter value includes the ``%`` character, you need to escape it
+ by adding another ``%`` so Symfony doesn't consider it a reference to a
+ parameter name:
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/services.yaml
+ parameters:
+ # Parsed as 'https://symfony.com/?foo=%s&bar=%d'
+ url_pattern: 'https://symfony.com/?foo=%%s&bar=%%d'
+
+ .. code-block:: xml
+
+
+
+ http://symfony.com/?foo=%%s&bar=%%d
+
+
+ .. code-block:: php
+
+ // config/services.php
+ $container->setParameter('url_pattern', 'http://symfony.com/?foo=%%s&bar=%%d');
+
+.. include:: /components/dependency_injection/_imports-parameters-note.rst.inc
+
+Configuration parameters are very common in Symfony applications. Some packages
+even define their own parameters (e.g. when installing the translation package,
+a new ``locale`` parameter is added to the ``config/services.yaml`` file).
+
+.. seealso::
+
+ Read the `Accessing Configuration Values`_ section of this article to learn
+ about how to use these configuration parameters in services and controllers.
+
+.. index::
+ single: Environments; Introduction
+
+.. _page-creation-environments:
+.. _page-creation-prod-cache-clear:
+.. _configuration-environments:
+
+Configuration Environments
+--------------------------
+
+You have just one application, but whether you realize it or not, you need it
+to behave differently at different times:
+
+* While **developing**, you want to log everything and expose nice debugging tools;
+* After deploying to **production**, you want that same application to be
+ optimized for speed and only log errors.
+
+The files stored in ``config/packages/`` are used by Symfony to configure the
+:doc:`application services `. In other words, you can change
+the application behavior by changing which configuration files are loaded.
+That's the idea of Symfony's **configuration environments**.
+
+A typical Symfony application begins with three environments: ``dev`` (for local
+development), ``prod`` (for production servers) and ``test`` (for
+:doc:`automated tests `). When running the application, Symfony loads
+the configuration files in this order (the last files can override the values
+set in the previous ones):
+
+#. ``config/packages/*.yaml`` (and ``.xml`` and ``*.php`` files too);
+#. ``config/packages//*.yaml`` (and ``.xml`` and ``*.php`` files too);
+#. ``config/packages/services.yaml`` (and ``services.xml`` and ``services.php`` files too);
+
+Take the ``framework`` package, installed by default, as an example:
+
+* First, ``config/packages/framework.yaml`` is loaded in all environments and
+ it configures the framework with some options;
+* In the **prod** environment, nothing extra will be set as there is no
+ ``config/packages/prod/framework.yaml`` file;
+* In the **dev** environment, there is no file either (
+ ``config/packages/dev/framework.yaml`` does not exist).
+* In the **test** environment, the ``config/packages/test/framework.yaml`` file
+ is loaded to override some of the settings previously configured in
+ ``config/packages/framework.yaml``.
+
+In reality, each environment differs only somewhat from others. This means that
+all environments share a large base of common configurations, which is put in
+files directly in the ``config/packages/`` directory.
.. seealso::
- You can also set parameters dynamically, like from environment variables.
- See :doc:`/configuration/external_parameters`.
+ See the ``configureContainer()`` method of
+ :doc:`the Kernel class ` to
+ learn everything about the loading order of configuration files.
+
+.. _selecting-the-active-environment:
+
+Selecting the Active Environment
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Symfony applications come with a file called ``.env`` located at the project
+root directory. This file is used to define the value of environment variables
+and it's explained in detail :ref:`later in this article `.
+
+Open the ``.env`` file (or better, the ``.env.local`` file if you created one)
+and edit the value of the ``APP_ENV`` variable to change the environment in
+which the application runs. For example, to run the application in production:
+
+.. code-block:: bash
+
+ # .env (or .env.local)
+ APP_ENV=prod
+
+This value is used both for the web and for the console commands. However, you
+can override it for commands by setting the ``APP_ENV`` value before running them:
+
+.. code-block:: terminal
+
+ # Use the environment defined in the .env file
+ $ php bin/console command_name
+
+ # Ignore the .env file and run this command in production
+ $ APP_ENV=prod php bin/console command_name
+
+Creating a New Environment
+~~~~~~~~~~~~~~~~~~~~~~~~~~
-For more information about parameters - including how to reference them from inside
-a controller - see :ref:`service-container-parameters`.
+The default three environments provided by Symfony are enough for most projects,
+but you can define your own environments too. For example, this is how you can
+define a ``staging`` environment where the client can test the project before
+going to production:
+#. Create a configuration directory with the same name as the environment (in
+ this case, ``config/packages/staging/``);
+#. Add the needed configuration files in ``config/packages/staging/`` to
+ define the behavior of the new environment. Symfony loads first the files in
+ ``config/packages/*.yaml``, so you must only configure the differences with
+ those files;
+#. Select the ``staging`` environment using the ``APP_ENV`` env var as explained
+ in the previous section.
+
+.. tip::
+
+ It's common for environments to be similar between each other, so you can
+ use `symbolic links`_ between ``config/packages//``
+ directories to reuse the same configuration.
+
+.. _config-env-vars:
+
+Configuration Based on Environment Variables
+--------------------------------------------
+
+Using `environment variables`_ (or "env vars" for short) is a common practice to
+configure options that depend on where the application is run (e.g. the database
+credentials are usually different in production and in your local machine).
+
+Instead of defining those as regular options, you can define them as environment
+variables and reference them in the configuration files using the special syntax
+``%env(ENV_VAR_NAME)%``. The values of these options are resolved at runtime
+(only once per request, to not impact performance).
+
+This example shows how to configure the database connection using an env var:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/doctrine.yaml
+ doctrine:
+ dbal:
+ # by convention the env var names are always uppercase
+ url: '%env(DATABASE_URL)%'
+ # ...
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/doctrine.php
+ $container->loadFromExtension('doctrine', [
+ 'dbal' => [
+ // by convention the env var names are always uppercase
+ 'url' => '%env(DATABASE_URL)%',
+ ]
+ ]);
+
+The next step is to define the value of those env vars in your shell, your web
+server, etc. This is explained in the following sections, but to protect your
+application from undefined env vars, you can give them a default value using the
+``.env`` file:
+
+.. code-block:: bash
+
+ # .env
+ DATABASE_URL=sqlite:///%kernel.project_dir%/var/data.db
+
+.. seealso::
+
+ The values of env vars can only be strings, but Symfony includes some
+ :doc:`env var processors ` to transform
+ their contents (e.g. to turn a string value into an integer).
+
+In order to define the actual values of env vars, Symfony proposes different
+solutions depending if the application is running in production or in your local
+development machine.
+
+Independent from the way you set environmnet variables, you may need to run the
+``debug:container`` command with the ``--env-vars`` option to verify that they
+are defined and have the expected values:
+
+.. code-block:: terminal
+
+ $ php bin/console debug:container --env-vars
+
+ ---------------- ----------------- ---------------------------------------------
+ Name Default value Real value
+ ---------------- ----------------- ---------------------------------------------
+ APP_SECRET n/a "471a62e2d601a8952deb186e44186cb3"
+ FOO "[1, "2.5", 3]" n/a
+ BAR null n/a
+ ---------------- ----------------- ---------------------------------------------
+
+ # you can also filter the list of env vars by name:
+ $ php bin/console debug:container --env-vars foo
+
+ # run this command to show all the details for a specific env var:
+ $ php bin/console debug:container --env-var=FOO
+
+.. versionadded:: 4.3
+
+ The option to debug environment variables was introduced in Symfony 4.3.
+
+.. _configuration-env-var-in-dev:
.. _config-dot-env:
-.. _config-parameters-yml:
-The .env File & Environment Variables
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Configuring Environment Variables in Development
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-There is also a ``.env`` file which is loaded and its contents become environment
-variables. This is useful during development, or if setting environment variables
-is difficult for your deployment.
+Instead of defining env vars in your shell or your web server, Symfony proposes
+a convenient way of defining them in your local machine based on a file called
+``.env`` (with a leading dot) located at the root of your project.
-When you install packages, more environment variables are added to this file. But
-you can also add your own.
+The ``.env`` file is read and parsed on every request and its env vars are added
+to the ``$_ENV`` PHP variable. The existing env vars are never overwritten by
+the values defined in ``.env``, so you can combine both.
-Environment variables can be referenced in any other configuration files by using
-a special syntax. For example, if you install the ``doctrine`` package, then you
-will have an environment variable called ``DATABASE_URL`` in your ``.env`` file.
-This is referenced inside ``config/packages/doctrine.yaml``:
+This is for example the content of the ``.env`` file to define the value of the
+``DATABASE_URL`` env var shown earlier in this article:
-.. code-block:: yaml
+.. code-block:: bash
+
+ # .env
+ DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name"
+
+In addition to your own env vars, this ``.env`` file also contains the env vars
+defined by the third-party packages installed in your application (they are
+added automatically by :ref:`Symfony Flex ` when installing packages).
+
+.. _configuration-env-var-in-prod:
+
+Configuring Environment Variables in Production
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In production, the ``.env`` files are also parsed and loaded on each request so
+you can override the env vars already defined in the server. In order to improve
+performance, you can run the ``dump-env`` command (available when using
+:ref:`Symfony Flex ` 1.2 or later).
+
+This command parses all the ``.env`` files once and compiles their contents into
+a new PHP-optimized file called ``.env.local.php``. From that moment, Symfony
+will load the parsed file instead of parsing the ``.env`` files again:
- # config/packages/doctrine.yaml
- doctrine:
- dbal:
- url: '%env(DATABASE_URL)%'
+.. code-block:: terminal
+
+ $ composer dump-env prod
+
+.. tip::
+
+ Update your deployment tools/workflow to run the ``dump-env`` command after
+ each deploy to improve the application performance.
- # The `resolve:` prefix replaces container params by their values inside the env variable:
- # url: '%env(resolve:DATABASE_URL)%'
+.. _configuration-env-var-web-server:
-For more details about environment variables, see :ref:`config-env-vars`.
+Creating ``.env`` files is the easiest way of using env vars in Symfony
+applications. However, you can also configure real env vars in your servers and
+operating systems.
+
+.. tip::
+
+ SymfonyCloud, the cloud service optimized for Symfony applications, defines
+ some `utilities to manage env vars`_ in production.
+
+.. caution::
+
+ Beware that dumping the contents of the ``$_SERVER`` and ``$_ENV`` variables
+ or outputting the ``phpinfo()`` contents will display the values of the
+ environment variables, exposing sensitive information such as the database
+ credentials.
+
+ The values of the env vars are also exposed in the web interface of the
+ :doc:`Symfony profiler `. In practice this shouldn't be a
+ problem because the web profiler must **never** be enabled in production.
+
+Managing Multiple .env Files
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``.env`` file defines the default values for all env vars. However, it's
+common to override some of those values depending on the environment (e.g. to
+use a different database for tests) or depending on the machine (e.g. to use a
+different OAuth token on your local machine while developing).
+
+That's why you can define multiple ``.env`` files to override env vars. The
+following list shows the files loaded in all environments. The ``.env`` file is
+the only mandatory file and each file content overrides the previous one:
+
+* ``.env``: defines the default values of the env vars needed by the application;
+* ``.env.local``: defines machine-specific overrides for env vars on all
+ environments. This file is not committed to the repository, so these overrides
+ only apply to the machine which contains the file (your local computer,
+ production server, etc.);
+* ``.env.`` (e.g. ``.env.test``): overrides env vars only for some
+ environment but for all machines;
+* ``.env..local`` (e.g. ``.env.test.local``): defines machine-specific
+ env vars overrides only for some environment. It's similar to ``.env.local``,
+ but the overrides only apply to some particular environment.
+
+.. note::
+
+ The real environment variables defined in the server always win over the
+ env vars created by the ``.env`` files.
+
+The ``.env`` and ``.env.`` files should be committed to the shared
+repository because they are the same for all developers and machines. However,
+the env files ending in ``.local`` (``.env.local`` and ``.env..local``)
+**should not be committed** because only you will use them. In fact, the
+``.gitignore`` file that comes with Symfony prevents them from being committed.
.. caution::
@@ -249,48 +642,154 @@ For more details about environment variables, see :ref:`config-env-vars`.
involving a ``.env.dist`` file. For information about upgrading, see:
:doc:`configuration/dot-env-changes`.
-The ``.env`` file is special, because it defines the values that usually change
-on each server. For example, the database credentials on your local development
-machine might be different from your workmates. The ``.env`` file should contain
-sensible, non-secret *default* values for all of your environment variables and
-*should* be commited to your repository.
+Accessing Configuration Values
+------------------------------
+
+Controllers and services can access all the configuration parameters. This
+includes both the :ref:`parameters defined by yourself `
+and the parameters created by packages/bundles. Run the following command to see
+all the parameters that exist in your application:
+
+.. code-block:: terminal
+
+ $ php bin/console debug:container --parameters
-To override these variables with machine-specific or sensitive values, create a
-``.env.local`` file. This file is **not committed to the shared repository** and
-is only stored on your machine. In fact, the ``.gitignore`` file that comes with
-Symfony prevents it from being committed.
+Parameters are injected in services as arguments to their constructors.
+:doc:`Service autowiring ` doesn't work for
+parameters. Instead, inject them explicitly:
-You can also create a few other ``.env`` files that will be loaded:
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/services.yaml
+ parameters:
+ app.contents_dir: '...'
+
+ services:
+ App\Service\MessageGenerator:
+ arguments:
+ $contentsDir: '%app.contents_dir%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ ...
+
+
+
+
+ %app.contents_dir%
+
+
+
+
+ .. code-block:: php
+
+ // config/services.php
+ use App\Service\MessageGenerator;
+ use Symfony\Component\DependencyInjection\Reference;
+
+ $container->setParameter('app.contents_dir', '...');
+
+ $container->getDefinition(MessageGenerator::class)
+ ->setArgument('$contentsDir', '%app.contents_dir%');
-* ``.env.{environment}``: e.g. ``.env.test`` will be loaded in the ``test`` environment
- and committed to your repository.
+If you inject the same parameters over and over again, use instead the
+``services._defaults.bind`` option. The arguments defined in that option are
+injected automatically whenever a service constructor or controller action
+define an argument with that exact name. For example, to inject the value of the
+:ref:`kernel.project_dir parameter `
+whenever a service/controller defines a ``$projectDir`` argument, use this:
-* ``.env.{environment}.local``: e.g. ``.env.prod.local`` will be loaded in the
- ``prod`` environment but will *not* be committed to your repository.
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/services.yaml
+ services:
+ _defaults:
+ bind:
+ # pass this value to any $projectDir argument for any service
+ # that's created in this file (including controller arguments)
+ $projectDir: '%kernel.project_dir%'
+
+ # ...
+
+ .. code-block:: xml
+
+
+
+
-If you decide to set real environment variables on production, the ``.env`` files
-*are* still loaded, but your real environment variables will override those values.
+
+
+
+ %kernel.project_dir%
+
-Environments & the Other Config Files
--------------------------------------
+
+
+
-You have just *one* app, but whether you realize it or not, you need it to
-behave *differently* at different times:
+ .. code-block:: php
-* While **developing**, you want your app to log everything and expose nice
- debugging tools;
+ // config/services.php
+ use App\Controller\LuckyController;
+ use Psr\Log\LoggerInterface;
+ use Symfony\Component\DependencyInjection\Reference;
+
+ $container->register(LuckyController::class)
+ ->setPublic(true)
+ ->setBindings([
+ // pass this value to any $projectDir argument for any service
+ // that's created in this file (including controller arguments)
+ '$projectDir' => '%kernel.project_dir%',
+ ])
+ ;
-* After deploying to **production**, you want that *same* app to be optimized
- for speed and only log errors.
+.. seealso::
-How can you make *one* application behave in two different ways? With
-*environments*.
+ Read the article about :ref:`binding arguments by name and/or type `
+ to learn more about this powerful feature.
-You've probably already been using the ``dev`` environment without even knowing
-it. After you deploy, you'll use the ``prod`` environment.
+Finally, if some service needs to access to lots of parameters, instead of
+injecting each of them individually, you can inject all the application
+parameters at once by type-hinting any of its constructor arguments with the
+:class:`Symfony\\Component\\DependencyInjection\\ParameterBag\\ContainerBagInterface`::
-To learn more about *how* to execute and control each environment, see
-:doc:`/configuration/environments`.
+ // src/Service/MessageGenerator.php
+ // ...
+
+ use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface;
+
+ class MessageGenerator
+ {
+ private $params;
+
+ public function __construct(ContainerBagInterface $params)
+ {
+ $this->params = $params;
+ }
+
+ public function someMethod()
+ {
+ // get any container parameter from $this->params, which stores all of them
+ $sender = $this->params->get('mailer_sender');
+ // ...
+ }
+ }
Keep Going!
-----------
@@ -305,10 +804,7 @@ part of Symfony individually by following the guides. Check out:
* :doc:`/email`
* :doc:`/logging`
-And the many other topics.
-
-Learn more
-----------
+And all the other topics related to configuration:
.. toctree::
:maxdepth: 1
@@ -316,4 +812,7 @@ Learn more
configuration/*
-.. _`Incenteev Parameter Handler`: https://github.com/Incenteev/ParameterHandler
+.. _`Learn the XML syntax`: https://en.wikipedia.org/wiki/XML
+.. _`environment variables`: https://en.wikipedia.org/wiki/Environment_variable
+.. _`symbolic links`: https://en.wikipedia.org/wiki/Symbolic_link
+.. _`utilities to manage env vars`: https://symfony.com/doc/master/cloud/cookbooks/env.html
diff --git a/configuration/configuration_organization.rst b/configuration/configuration_organization.rst
deleted file mode 100644
index b3a06a89aae..00000000000
--- a/configuration/configuration_organization.rst
+++ /dev/null
@@ -1,180 +0,0 @@
-.. index::
- single: Configuration
-
-How to Organize Configuration Files
-===================================
-
-The Symfony skeleton defines three :doc:`execution environments `
-called ``dev``, ``prod`` and ``test``. An environment represents a way
-to execute the same codebase with different configurations.
-
-In order to select the configuration file to load for each environment, Symfony
-executes the ``configureContainer()`` method of the ``Kernel`` class::
-
- // src/Kernel.php
- use Symfony\Component\Config\Loader\LoaderInterface;
- use Symfony\Component\DependencyInjection\ContainerBuilder;
- use Symfony\Component\HttpKernel\Kernel as BaseKernel;
-
- class Kernel extends BaseKernel
- {
- const CONFIG_EXTS = '.{php,xml,yaml,yml}';
-
- // ...
-
- public function configureContainer(ContainerBuilder $container, LoaderInterface $loader)
- {
- $confDir = $this->getProjectDir().'/config';
- $loader->load($confDir.'/packages/*'.self::CONFIG_EXTS, 'glob');
- if (is_dir($confDir.'/packages/'.$this->environment)) {
- $loader->load($confDir.'/packages/'.$this->environment.'/**/*'.self::CONFIG_EXTS, 'glob');
- }
- $loader->load($confDir.'/services'.self::CONFIG_EXTS, 'glob');
- $loader->load($confDir.'/services_'.$this->environment.self::CONFIG_EXTS, 'glob');
- }
- }
-
-For the ``dev`` environment, Symfony loads the following config files and
-directories and in this order:
-
-#. ``config/packages/*``
-#. ``config/packages/dev/*``
-#. ``config/services.yaml``
-#. ``config/services_dev.yaml``
-
-Therefore, the configuration files of the default Symfony applications follow
-this structure:
-
-.. code-block:: text
-
- your-project/
- ├─ config/
- │ ├─ packages/
- │ │ ├─ dev/
- │ │ │ ├─ framework.yaml
- │ │ │ └─ ...
- │ │ ├─ prod/
- │ │ │ └─ ...
- │ │ ├─ test/
- │ │ │ └─ ...
- │ │ ├─ framework.yaml
- │ │ └─ ...
- │ ├─ services.yaml
- │ └─ services_dev.yaml
- ├─ ...
-
-This default structure was chosen for its simplicity — one file per package and
-environment. But as any other Symfony feature, you can customize it to better
-suit your needs.
-
-Advanced Techniques
--------------------
-
-Symfony loads configuration files using the
-:doc:`Config component `, which provides some
-advanced features.
-
-Mix and Match Configuration Formats
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Configuration files can import files defined with any other built-in configuration
-format (``.yaml`` or ``.yml``, ``.xml``, ``.php``, ``.ini``):
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # config/services.yaml
- imports:
- - { resource: 'my_config_file.xml' }
- - { resource: 'legacy.php' }
-
- # ...
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // config/services.php
- $loader->import('my_config_file.yaml');
- $loader->import('legacy.xml');
-
- // ...
-
-If you use any other configuration format, you have to define your own loader
-class extending it from :class:`Symfony\\Component\\DependencyInjection\\Loader\\FileLoader`.
-When the configuration values are dynamic, you can use the PHP configuration
-file to execute your own logic. In addition, you can define your own services
-to load configurations from databases or web services.
-
-Global Configuration Files
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Some system administrators may prefer to store sensitive parameters in files
-outside the project directory. Imagine that the database credentials for your
-website are stored in the ``/etc/sites/mysite.com/parameters.yaml`` file. You
-can load files from outside the project folder by indicating the full file path
-when importing it from any other configuration file:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # config/services.yaml
- imports:
- - { resource: '/etc/sites/mysite.com/parameters.yaml', ignore_errors: true }
-
- # ...
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // config/services.php
- $loader->import('/etc/sites/mysite.com/parameters.yaml', null, true);
-
- // ...
-
-.. tip::
-
- The ``ignore_errors`` option (which is the third optional argument in the
- loader's ``import()`` method) silently discards errors when the loaded file
- doesn't exist. This is needed in this case because most of the time, local
- developers won't have the same files that exist on the production servers.
-
-As you've seen, there are lots of ways to organize your configuration files. You
-can choose one of these or even create your own custom way of organizing the
-files. For even more customization, see ":doc:`/configuration/override_dir_structure`".
diff --git a/configuration/dot-env-changes.rst b/configuration/dot-env-changes.rst
index c0e1a3917d1..280a40c8d63 100644
--- a/configuration/dot-env-changes.rst
+++ b/configuration/dot-env-changes.rst
@@ -18,12 +18,12 @@ important changes:
* A) The ``.env.dist`` file no longer exists. Its contents should be moved to your
``.env`` file (see the next point).
-* B) The ``.env`` file **is** now commited to your repository. It was previously ignored
+* B) The ``.env`` file **is** now committed to your repository. It was previously ignored
via the ``.gitignore`` file (the updated recipe does not ignore this file). Because
this file is committed, it should contain non-sensitive, default values. Basically,
the ``.env.dist`` file was moved to ``.env``.
-* C) A ``.env.local`` file can now be created to *override* environment variables for
+* C) A ``.env.local`` file can now be created to *override* values in ``.env`` for
your machine. This file is ignored in the new ``.gitignore``.
* D) When testing, your ``.env`` file is now read, making it consistent with all
@@ -67,7 +67,7 @@ changes can be made to any Symfony 3.4 or higher app:
+ /.env.local
+ /.env.local.php
+ /.env.*.local
-
+
# ...
#. Rename ``.env`` to ``.env.local`` and ``.env.dist`` to ``.env``:
@@ -79,18 +79,18 @@ changes can be made to any Symfony 3.4 or higher app:
$ git mv .env.dist .env
# Windows
- $ mv .env .env.local
- $ git mv .env.dist .env
+ C:\> move .env .env.local
+ C:\> git mv .env.dist .env
- You can also update the `comment on the top of .env`_ to reflect the new changes.
+ You can also update the `comment on the top of .env`_ to reflect the new changes.
#. If you're using PHPUnit, you will also need to `create a new .env.test`_ file
and update your `phpunit.xml.dist file`_ so it loads the ``config/bootstrap.php``
file.
-.. _`config/bootstrap.php`: https://github.com/symfony/recipes/blob/master/symfony/framework-bundle/3.3/config/bootstrap.php
-.. _`public/index.php`: https://github.com/symfony/recipes/blob/master/symfony/framework-bundle/3.3/public/index.php
-.. _`index.php diff`: https://github.com/symfony/recipes/compare/8a4e5555e30d5dff64275e2788a901f31a214e79...f54d6a468405d0d8d27b0e790dc09a01e337777a#diff-473fca613b5bda15d87731036cb31586
+.. _`config/bootstrap.php`: https://github.com/symfony/recipes/blob/master/symfony/framework-bundle/4.2/config/bootstrap.php
+.. _`public/index.php`: https://github.com/symfony/recipes/blob/master/symfony/framework-bundle/4.2/public/index.php
+.. _`index.php diff`: https://github.com/symfony/recipes/compare/8a4e5555e30d5dff64275e2788a901f31a214e79...86e2b6795c455f026e5ab0cba2aff2c7a18511f7#diff-7d73eabd1e5eb7d969ddf9a7ce94f954
.. _`bin/console`: https://github.com/symfony/recipes/blob/master/symfony/console/3.3/bin/console
.. _`comment on the top of .env`: https://github.com/symfony/recipes/blob/master/symfony/flex/1.0/.env
.. _`create a new .env.test`: https://github.com/symfony/recipes/blob/master/symfony/phpunit-bridge/3.3/.env.test
diff --git a/configuration/env_var_processors.rst b/configuration/env_var_processors.rst
new file mode 100644
index 00000000000..67d9bca422e
--- /dev/null
+++ b/configuration/env_var_processors.rst
@@ -0,0 +1,670 @@
+.. index::
+ single: Environment Variable Processors; env vars
+
+.. _env-var-processors:
+
+Environment Variable Processors
+===============================
+
+:ref:`Using env vars to configure Symfony applications ` is a
+common practice to make your applications truly dynamic.
+
+The main issue of env vars is that their values can only be strings and your
+application may need other data types (integer, boolean, etc.). Symfony solves
+this problem with "env var processors", which transform the original contents of
+the given environment variables. The following example uses the integer
+processor to turn the value of the ``HTTP_PORT`` env var into an integer:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/framework.yaml
+ framework:
+ router:
+ http_port: '%env(int:HTTP_PORT)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/framework.php
+ $container->loadFromExtension('framework', [
+ 'router' => [
+ 'http_port' => '%env(int:HTTP_PORT)%',
+ ],
+ ]);
+
+Built-In Environment Variable Processors
+----------------------------------------
+
+Symfony provides the following env var processors:
+
+``env(string:FOO)``
+ Casts ``FOO`` to a string:
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/framework.yaml
+ parameters:
+ env(SECRET): 'some_secret'
+ framework:
+ secret: '%env(string:SECRET)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ some_secret
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/framework.php
+ $container->setParameter('env(SECRET)', 'some_secret');
+ $container->loadFromExtension('framework', [
+ 'secret' => '%env(string:SECRET)%',
+ ]);
+
+``env(bool:FOO)``
+ Casts ``FOO`` to a bool:
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/framework.yaml
+ parameters:
+ env(HTTP_METHOD_OVERRIDE): 'true'
+ framework:
+ http_method_override: '%env(bool:HTTP_METHOD_OVERRIDE)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ true
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/framework.php
+ $container->setParameter('env(HTTP_METHOD_OVERRIDE)', 'true');
+ $container->loadFromExtension('framework', [
+ 'http_method_override' => '%env(bool:HTTP_METHOD_OVERRIDE)%',
+ ]);
+
+``env(int:FOO)``
+ Casts ``FOO`` to an int.
+
+``env(float:FOO)``
+ Casts ``FOO`` to a float.
+
+``env(const:FOO)``
+ Finds the const value named in ``FOO``:
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/security.yaml
+ parameters:
+ env(HEALTH_CHECK_METHOD): 'Symfony\Component\HttpFoundation\Request::METHOD_HEAD'
+ security:
+ access_control:
+ - { path: '^/health-check$', methods: '%env(const:HEALTH_CHECK_METHOD)%' }
+
+ .. code-block:: xml
+
+
+
+
+
+
+ Symfony\Component\HttpFoundation\Request::METHOD_HEAD
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/security.php
+ $container->setParameter('env(HEALTH_CHECK_METHOD)', 'Symfony\Component\HttpFoundation\Request::METHOD_HEAD');
+ $container->loadFromExtension('security', [
+ 'access_control' => [
+ [
+ 'path' => '^/health-check$',
+ 'methods' => '%env(const:HEALTH_CHECK_METHOD)%',
+ ],
+ ],
+ ]);
+
+``env(base64:FOO)``
+ Decodes the content of ``FOO``, which is a base64 encoded string.
+
+``env(json:FOO)``
+ Decodes the content of ``FOO``, which is a JSON encoded string. It returns
+ either an array or ``null``:
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/framework.yaml
+ parameters:
+ env(TRUSTED_HOSTS): '["10.0.0.1", "10.0.0.2"]'
+ framework:
+ trusted_hosts: '%env(json:TRUSTED_HOSTS)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ ["10.0.0.1", "10.0.0.2"]
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/framework.php
+ $container->setParameter('env(TRUSTED_HOSTS)', '["10.0.0.1", "10.0.0.2"]');
+ $container->loadFromExtension('framework', [
+ 'trusted_hosts' => '%env(json:TRUSTED_HOSTS)%',
+ ]);
+
+``env(resolve:FOO)``
+ Replaces the string ``FOO`` by the value of a config parameter with the
+ same name:
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/sentry.yaml
+ parameters:
+ env(HOST): '10.0.0.1'
+ sentry_host: '%env(HOST)%'
+ env(SENTRY_DSN): 'http://%sentry_host%/project'
+ sentry:
+ dsn: '%env(resolve:SENTRY_DSN)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ 10.0.0.1
+ %env(HOST)%
+ http://%sentry_host%/project
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/sentry.php
+ $container->setParameter('env(HOST)', '10.0.0.1');
+ $container->setParameter('sentry_host', '%env(HOST)%');
+ $container->setParameter('env(SENTRY_DSN)', 'http://%sentry_host%/project');
+ $container->loadFromExtension('sentry', [
+ 'dsn' => '%env(resolve:SENTRY_DSN)%',
+ ]);
+
+``env(csv:FOO)``
+ Decodes the content of ``FOO``, which is a CSV-encoded string:
+
+ .. code-block:: yaml
+
+ parameters:
+ env(TRUSTED_HOSTS): "10.0.0.1, 10.0.0.2"
+ framework:
+ trusted_hosts: '%env(csv:TRUSTED_HOSTS)%'
+
+``env(file:FOO)``
+ Returns the contents of a file whose path is the value of the ``FOO`` env var:
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/framework.yaml
+ parameters:
+ env(AUTH_FILE): '../config/auth.json'
+ google:
+ auth: '%env(file:AUTH_FILE)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ ../config/auth.json
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/framework.php
+ $container->setParameter('env(AUTH_FILE)', '../config/auth.json');
+ $container->loadFromExtension('google', [
+ 'auth' => '%env(file:AUTH_FILE)%',
+ ]);
+
+``env(require:FOO)``
+ ``require()`` the PHP file whose path is the value of the ``FOO``
+ env var and return the value returned from it.
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/framework.yaml
+ parameters:
+ env(PHP_FILE): '../config/.runtime-evaluated.php'
+ app:
+ auth: '%env(require:PHP_FILE)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ ../config/.runtime-evaluated.php
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/framework.php
+ $container->setParameter('env(PHP_FILE)', '../config/.runtime-evaluated.php');
+ $container->loadFromExtension('app', [
+ 'auth' => '%env(require:AUTH_FILE)%',
+ ]);
+
+ .. versionadded:: 4.3
+
+ The ``require`` processor was introduced in Symfony 4.3.
+
+``env(trim:FOO)``
+ Trims the content of ``FOO`` env var, removing whitespaces from the beginning
+ and end of the string. This is especially useful in combination with the
+ ``file`` processor, as it'll remove newlines at the end of a file.
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/framework.yaml
+ parameters:
+ env(AUTH_FILE): '../config/auth.json'
+ google:
+ auth: '%env(trim:file:AUTH_FILE)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ ../config/auth.json
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/framework.php
+ $container->setParameter('env(AUTH_FILE)', '../config/auth.json');
+ $container->loadFromExtension('google', [
+ 'auth' => '%env(trim:file:AUTH_FILE)%',
+ ]);
+
+ .. versionadded:: 4.3
+
+ The ``trim`` processor was introduced in Symfony 4.3.
+
+``env(key:FOO:BAR)``
+ Retrieves the value associated with the key ``FOO`` from the array whose
+ contents are stored in the ``BAR`` env var:
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/services.yaml
+ parameters:
+ env(SECRETS_FILE): '/opt/application/.secrets.json'
+ database_password: '%env(key:database_password:json:file:SECRETS_FILE)%'
+ # if SECRETS_FILE contents are: {"database_password": "secret"} it returns "secret"
+
+ .. code-block:: xml
+
+
+
+
+
+
+ /opt/application/.secrets.json
+ %env(key:database_password:json:file:SECRETS_FILE)%
+
+
+
+ .. code-block:: php
+
+ // config/services.php
+ $container->setParameter('env(SECRETS_FILE)', '/opt/application/.secrets.json');
+ $container->setParameter('database_password', '%env(key:database_password:json:file:SECRETS_FILE)%');
+
+``env(default:fallback_param:BAR)``
+ Retrieves the value of the parameter ``fallback_param`` when the ``BAR`` env
+ var is not available:
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/services.yaml
+ parameters:
+ # if PRIVATE_KEY is not a valid file path, the content of raw_key is returned
+ private_key: '%env(default:raw_key:file:PRIVATE_KEY)%'
+ raw_key: '%env(PRIVATE_KEY)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ %env(default:raw_key:file:PRIVATE_KEY)%
+ %env(PRIVATE_KEY)%
+
+
+
+ .. code-block:: php
+
+ // config/services.php
+
+ // if PRIVATE_KEY is not a valid file path, the content of raw_key is returned
+ $container->setParameter('private_key', '%env(default:raw_key:file:PRIVATE_KEY)%');
+ $container->setParameter('raw_key', '%env(PRIVATE_KEY)%');
+
+ When the fallback parameter is omitted (e.g. ``env(default::API_KEY)``), the
+ value returned is ``null``.
+
+ .. versionadded:: 4.3
+
+ The ``default`` processor was introduced in Symfony 4.3.
+
+``env(url:FOO)``
+ Parses an absolute URL and returns its components as an associative array.
+
+ .. code-block:: bash
+
+ # .env
+ MONGODB_URL="mongodb://db_user:db_password@127.0.0.1:27017/db_name"
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/mongodb.yaml
+ mongo_db_bundle:
+ clients:
+ default:
+ hosts:
+ - { host: '%env(key:host:url:MONGODB_URL)%', port: '%env(key:port:url:MONGODB_URL)%' }
+ username: '%env(key:user:url:MONGODB_URL)%'
+ password: '%env(key:pass:url:MONGODB_URL)%'
+ connections:
+ default:
+ database_name: '%env(key:path:url:MONGODB_URL)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/mongodb.php
+ $container->loadFromExtension('mongodb', [
+ 'clients' => [
+ 'default' => [
+ 'hosts' => [
+ [
+ 'host' => '%env(key:host:url:MONGODB_URL)%',
+ 'port' => '%env(key:port:url:MONGODB_URL)%',
+ ],
+ ],
+ 'username' => '%env(key:user:url:MONGODB_URL)%',
+ 'password' => '%env(key:pass:url:MONGODB_URL)%',
+ ],
+ ],
+ 'connections' => [
+ 'default' => [
+ 'database_name' => '%env(key:path:url:MONGODB_URL)%',
+ ],
+ ],
+ ]);
+
+ .. caution::
+
+ In order to ease extraction of the resource from the URL, the leading
+ ``/`` is trimmed from the ``path`` component.
+
+ .. versionadded:: 4.3
+
+ The ``url`` processor was introduced in Symfony 4.3.
+
+``env(query_string:FOO)``
+ Parses the query string part of the given URL and returns its components as
+ an associative array.
+
+ .. code-block:: bash
+
+ # .env
+ MONGODB_URL="mongodb://db_user:db_password@127.0.0.1:27017/db_name?timeout=3000"
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/mongodb.yaml
+ mongo_db_bundle:
+ clients:
+ default:
+ # ...
+ connectTimeoutMS: '%env(int:key:timeout:query_string:MONGODB_URL)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/mongodb.php
+ $container->loadFromExtension('mongodb', [
+ 'clients' => [
+ 'default' => [
+ // ...
+ 'connectTimeoutMS' => '%env(int:key:timeout:query_string:MONGODB_URL)%',
+ ],
+ ],
+ ]);
+
+ .. versionadded:: 4.3
+
+ The ``query_string`` processor was introduced in Symfony 4.3.
+
+It is also possible to combine any number of processors:
+
+.. code-block:: yaml
+
+ parameters:
+ env(AUTH_FILE): "%kernel.project_dir%/config/auth.json"
+ google:
+ # 1. gets the value of the AUTH_FILE env var
+ # 2. replaces the values of any config param to get the config path
+ # 3. gets the content of the file stored in that path
+ # 4. JSON-decodes the content of the file and returns it
+ auth: '%env(json:file:resolve:AUTH_FILE)%'
+
+Custom Environment Variable Processors
+--------------------------------------
+
+It's also possible to add your own processors for environment variables. First,
+create a class that implements
+:class:`Symfony\\Component\\DependencyInjection\\EnvVarProcessorInterface`::
+
+ use Symfony\Component\DependencyInjection\EnvVarProcessorInterface;
+
+ class LowercasingEnvVarProcessor implements EnvVarProcessorInterface
+ {
+ public function getEnv($prefix, $name, \Closure $getEnv)
+ {
+ $env = $getEnv($name);
+
+ return strtolower($env);
+ }
+
+ public static function getProvidedTypes()
+ {
+ return [
+ 'lowercase' => 'string',
+ ];
+ }
+ }
+
+To enable the new processor in the app, register it as a service and
+:doc:`tag it ` with the ``container.env_var_processor``
+tag. If you're using the
+:ref:`default services.yaml configuration `,
+this is already done for you, thanks to :ref:`autoconfiguration `.
diff --git a/configuration/environments.rst b/configuration/environments.rst
deleted file mode 100644
index 70b7d9aaa3c..00000000000
--- a/configuration/environments.rst
+++ /dev/null
@@ -1,360 +0,0 @@
-.. index::
- single: Environments
-
-How to Master and Create new Environments
-=========================================
-
-Every application is the combination of code and a set of configuration that
-dictates how that code should function. The configuration may define the database
-being used, if something should be cached or how verbose logging should be.
-
-In Symfony, the idea of "environments" is the idea that the same codebase can be
-run using multiple different configurations. For example, the ``dev`` environment
-should use configuration that makes development easy and friendly, while the
-``prod`` environment should use a set of configuration optimized for speed.
-
-.. index::
- single: Environments; Configuration files
-
-Different Environments, different Configuration Files
------------------------------------------------------
-
-A typical Symfony application begins with three environments: ``dev``,
-``prod`` and ``test``. As mentioned, each environment represents a way to
-execute the same codebase with different configuration. It should be no
-surprise then that each environment loads its own individual configuration
-files. These different files are organized by environment:
-
-* for the ``dev`` environment: ``config/packages/dev/``
-* for the ``prod`` environment: ``config/packages/prod/``
-* for the ``test`` environment: ``config/packages/test/``
-
-In reality, each environment differs only somewhat from others. This means that
-all environments share a large base of common configurations. This configuration
-is put in files directly in the ``config/packages/`` directory.
-
-The location of these files is defined by the application's kernel::
-
- // src/Kernel.php
-
- // ...
- class Kernel extends BaseKernel
- {
- // ...
-
- protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader)
- {
- // ...
- $confDir = $this->getProjectDir().'/config';
-
- // always load all files in /config/packages/
- $loader->load($confDir.'/packages/*'.self::CONFIG_EXTS, 'glob');
-
- // then, if available, load the files in the specific environment directory
- if (is_dir($confDir.'/packages/'.$this->environment)) {
- $loader->load($confDir.'/packages/'.$this->environment.'/**/*'.self::CONFIG_EXTS, 'glob');
- }
-
- // load a special services.(yaml/xml/php) and, if available, services_ENVIRONMENT.(yaml/xml/php) file
- $loader->load($confDir.'/services'.self::CONFIG_EXTS, 'glob');
- $loader->load($confDir.'/services_'.$this->environment.self::CONFIG_EXTS, 'glob');
- }
- }
-
-Take the framework package, installed by default, as an example:
-
-* Loaded in all environments, ``config/packages/framework.yaml`` configures the
- framework with some ``secret`` setting;
-* In the **prod** environment, nothing extra will be set as there is no
- ``config/packages/prod/`` directory;
-* The same applies to **dev**, as there is no
- ``config/packages/dev/framework.yaml``. There are however other packages (e.g.
- ``routing.yaml``) with special dev settings;
-* At last, during the **test** environment, the framework's test features are
- enabled in ``config/packages/test/framework.yaml``.
-
-.. index::
- single: Environments; Executing different environments
-
-Executing an Application in different Environments
---------------------------------------------------
-
-To execute the application in each environment, change the ``APP_ENV``
-environment variable. During development, this is done in ``.env`` or in ``.env.local``:
-
-.. code-block:: bash
-
- # .env or .env.local
- APP_ENV=dev
-
- # or for test:
- #APP_ENV=test
-
-Visit the ``http://localhost:8000/index.php`` page in your web browser to see
-your application in the configured environment.
-
-.. tip::
-
- In production, you can use real environment variables via
- your :ref:`web server configuration `.
-
-.. note::
-
- The given URLs assume that your web server is configured to use the ``public/``
- directory of the application as its root. Read more in :doc:`Installing Symfony `.
-
-If you open the file you just visited (``public/index.php``), you'll see that
-the environment variable is passed to the kernel::
-
- // public/index.php
-
- // ...
- $kernel = new Kernel($_SERVER['APP_ENV'], $_SERVER['APP_DEBUG']);
-
- // ...
-
-.. note::
-
- The ``test`` environment is used when writing functional tests and is
- usually not accessed in the browser directly via a front controller.
-
-.. index::
- single: Configuration; Debug mode
-
-.. sidebar:: *Debug* Mode
-
- Important, but unrelated to the topic of *environments* is the second
- argument to the ``Kernel`` constructor. This specifies if the application
- should run in "debug mode". Regardless of the environment, a Symfony
- application can be run with debug mode set to ``true`` or ``false``
- (respectively ``1`` or ``0`` for the ``APP_DEBUG`` variable defined in
- ``.env``). This affects many things in the application, such as displaying
- stacktraces on error pages or if cache files are dynamically rebuilt on
- each request. Though not a requirement, debug mode is generally set to
- ``true`` for the ``dev`` and ``test`` environments and ``false`` for the
- ``prod`` environment.
-
- Internally, the value of the debug mode becomes the ``kernel.debug``
- parameter used inside the :doc:`service container `.
- If you look inside the application configuration file, you'll see the
- parameter used, for example, to turn Twig's debug mode on:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- # config/packages/twig.yaml
- twig:
- debug: '%kernel.debug%'
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- $container->loadFromExtension('twig', [
- 'debug' => '%kernel.debug%',
- // ...
- ]);
-
-Selecting the Environment for Console Commands
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-By default, Symfony commands are executed in whatever environment is defined by
-the ``APP_ENV`` environment variable (usually configured in your ``.env`` file).
-
-Use the ``--env`` and ``--no-debug`` options to modify this behavior:
-
-.. code-block:: terminal
-
- # 'dev' environment and debug enabled
- $ php bin/console command_name
-
- # 'prod' environment (debug is always disabled for 'prod')
- $ php bin/console command_name --env=prod
-
- # 'test' environment and debug disabled
- $ php bin/console command_name --env=test --no-debug
-
-.. index::
- single: Environments; Creating a new environment
-
-Creating a new Environment
---------------------------
-
-Since an environment is nothing more than a string that corresponds to a set of
-configuration, you can also create your own environments for specific purposes.
-
-Suppose, for example, that before deployment, you need to benchmark your
-application. One way to benchmark the application is to use near-production
-settings, but with Symfony's ``web_profiler`` enabled. This allows Symfony
-to record information about your application while benchmarking.
-
-The best way to accomplish this is via a new environment called, for example,
-``benchmark``. Start by creating a new configuration directory and a
-configuration file:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # config/packages/benchmark/web_profiler.yaml
- framework:
- profiler: { only_exceptions: false }
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // config/packages/benchmark/web_profiler.php
- $container->loadFromExtension('framework', [
- 'profiler' => ['only_exceptions' => false],
- ]);
-
-And... you're finished! The application now supports a new environment called
-``benchmark``.
-
-Change the ``APP_ENV`` variable to ``benchmark`` to be able to access the new
-environment through your browser:
-
-.. code-block:: bash
-
- # .env or .env.local
- APP_ENV=benchmark
-
-.. sidebar:: Importing configuration
-
- Besides loading files in the Kernel, you can also import files in the
- configuration directly. For instance, to make sure the benchmark
- environment is identical to the prod environment, you might want to load
- all its configuration as well.
-
- You can achieve this by using a special ``imports`` key:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- # config/packages/benchmark/other.yaml
- imports:
- - { resource: '../prod/' }
-
- # other resources are possible as well, like importing other
- # files or using globs:
- #- { resource: '/etc/myapp/some_special_config.xml' }
- #- { resource: '/etc/myapp/*.yaml' }
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // config/packages/benchmark/other.php
- $loader->import('../prod/');
-
- // other resources are possible as well, like importing other
- // files or using globs:
- //$loader->import('/etc/myapp/some_special_config.yaml');
- //$loader->import('/etc/myapp/*.php');
-
-.. index::
- single: Environments; Cache directory
-
-Environments and the Cache Directory
-------------------------------------
-
-Symfony takes advantage of caching in many ways: the application configuration,
-routing configuration, Twig templates and more are cached to PHP objects
-stored in files on the filesystem.
-
-By default, these cached files are largely stored in the ``var/cache/`` directory.
-However, each environment caches its own set of files:
-
-.. code-block:: text
-
- your-project/
- ├─ var/
- │ ├─ cache/
- │ │ ├─ dev/ # cache directory for the *dev* environment
- │ │ └─ prod/ # cache directory for the *prod* environment
- │ ├─ ...
-
-Sometimes, when debugging, it may be helpful to inspect a cached file to
-understand how something is working. When doing so, remember to look in
-the directory of the environment you're using (most commonly ``dev/`` while
-developing and debugging). While it can vary, the ``var/cache/dev/`` directory
-includes the following:
-
-``appDevDebugProjectContainer.php``
- The cached "service container" that represents the cached application
- configuration.
-
-``appDevUrlGenerator.php``
- The PHP class generated from the routing configuration and used when
- generating URLs.
-
-``appDevUrlMatcher.php``
- The PHP class used for route matching - look here to see the compiled regular
- expression logic used to match incoming URLs to different routes.
-
-``twig/``
- This directory contains all the cached Twig templates.
-
-.. note::
-
- You can change the directory location and name. For more information
- read the article :doc:`/configuration/override_dir_structure`.
-
-Going further
--------------
-
-Read the article on :doc:`/configuration/external_parameters`.
diff --git a/configuration/external_parameters.rst b/configuration/external_parameters.rst
deleted file mode 100644
index 362f1e1b302..00000000000
--- a/configuration/external_parameters.rst
+++ /dev/null
@@ -1,522 +0,0 @@
-.. index::
- single: Environments; External parameters
-
-How to Set external Parameters in the Service Container
-=======================================================
-
-In :doc:`/configuration`, you learned how to manage your application
-configuration. At times, it may benefit your application to store certain
-credentials outside of your project code. Database configuration is one such
-example. The flexibility of the Symfony service container allows you to easily
-do this.
-
-.. _config-env-vars:
-
-Environment Variables
----------------------
-
-You can reference environment variables by using special parameters named after
-the variables you want to use enclosed between ``env()``. Their actual values
-will be resolved at runtime (once per request), so that dumped containers can be
-reconfigured dynamically even after being compiled.
-
-For example, when installing the ``doctrine`` recipe, database configuration is
-put in a ``DATABASE_URL`` environment variable:
-
-.. code-block:: bash
-
- # .env
- DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name"
-
-This variable is referenced in the service container configuration using
-``%env(DATABASE_URL)%``:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # config/packages/doctrine.yaml
- doctrine:
- dbal:
- url: '%env(DATABASE_URL)%'
- # ...
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // config/packages/doctrine.php
- $container->loadFromExtension('doctrine', [
- 'dbal' => [
- 'url' => '%env(DATABASE_URL)%',
- ]
- ]);
-
-You can also give the ``env()`` parameters a default value: the default value
-will be used whenever the corresponding environment variable is *not* found:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # config/services.yaml
- parameters:
- env(DATABASE_HOST): localhost
-
- .. code-block:: xml
-
-
-
-
-
-
- localhost
-
-
-
- .. code-block:: php
-
- // config/services.php
- $container->setParameter('env(DATABASE_HOST)', 'localhost');
-
-.. _configuration-env-var-in-prod:
-
-Configuring Environment Variables in Production
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-During development, you'll use the ``.env`` file to configure your environment
-variables. On your production server, it is recommended to configure these at
-the web server level. If you're using Apache or Nginx, you can use e.g. one of
-the following:
-
-.. configuration-block::
-
- .. code-block:: apache
-
-
- # ...
-
- SetEnv DATABASE_URL "mysql://db_user:db_password@127.0.0.1:3306/db_name"
-
-
- .. code-block:: nginx
-
- fastcgi_param DATABASE_URL "mysql://db_user:db_password@127.0.0.1:3306/db_name";
-
-.. caution::
-
- Beware that dumping the contents of the ``$_SERVER`` and ``$_ENV`` variables
- or outputting the ``phpinfo()`` contents will display the values of the
- environment variables, exposing sensitive information such as the database
- credentials.
-
- The values of the env vars are also exposed in the web interface of the
- :doc:`Symfony profiler `. In practice this shouldn't be a
- problem because the web profiler must **never** be enabled in production.
-
-Environment Variable Processors
--------------------------------
-
-The values of environment variables are considered strings by default.
-However, your code may expect other data types, like integers or booleans.
-Symfony solves this problem with *processors*, which modify the contents of the
-given environment variables. The following example uses the integer processor to
-turn the value of the ``HTTP_PORT`` env var into an integer:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # config/packages/framework.yaml
- framework:
- router:
- http_port: env(int:HTTP_PORT)
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // config/packages/framework.php
- $container->loadFromExtension('framework', [
- 'router' => [
- 'http_port' => '%env(int:HTTP_PORT)%',
- ],
- ]);
-
-Symfony provides the following env var processors:
-
-``env(string:FOO)``
- Casts ``FOO`` to a string:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- # config/packages/framework.yaml
- parameters:
- env(SECRET): 'some_secret'
- framework:
- secret: '%env(string:SECRET)%'
-
- .. code-block:: xml
-
-
-
-
-
-
- some_secret
-
-
-
-
-
- .. code-block:: php
-
- // config/packages/framework.php
- $container->setParameter('env(SECRET)', 'some_secret');
- $container->loadFromExtension('framework', [
- 'secret' => '%env(string:SECRET)%',
- ]);
-
-``env(bool:FOO)``
- Casts ``FOO`` to a bool:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- # config/packages/framework.yaml
- parameters:
- env(HTTP_METHOD_OVERRIDE): 'true'
- framework:
- http_method_override: '%env(bool:HTTP_METHOD_OVERRIDE)%'
-
- .. code-block:: xml
-
-
-
-
-
-
- true
-
-
-
-
-
- .. code-block:: php
-
- // config/packages/framework.php
- $container->setParameter('env(HTTP_METHOD_OVERRIDE)', 'true');
- $container->loadFromExtension('framework', [
- 'http_method_override' => '%env(bool:HTTP_METHOD_OVERRIDE)%',
- ]);
-
-``env(int:FOO)``
- Casts ``FOO`` to an int.
-
-``env(float:FOO)``
- Casts ``FOO`` to a float.
-
-``env(const:FOO)``
- Finds the const value named in ``FOO``:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- # config/packages/security.yaml
- parameters:
- env(HEALTH_CHECK_METHOD): 'Symfony\Component\HttpFoundation\Request::METHOD_HEAD'
- security:
- access_control:
- - { path: '^/health-check$', methods: '%env(const:HEALTH_CHECK_METHOD)%' }
-
- .. code-block:: xml
-
-
-
-
-
-
- Symfony\Component\HttpFoundation\Request::METHOD_HEAD
-
-
-
-
-
-
-
- .. code-block:: php
-
- // config/packages/security.php
- $container->setParameter('env(HEALTH_CHECK_METHOD)', 'Symfony\Component\HttpFoundation\Request::METHOD_HEAD');
- $container->loadFromExtension('security', [
- 'access_control' => [
- [
- 'path' => '^/health-check$',
- 'methods' => '%env(const:HEALTH_CHECK_METHOD)%',
- ],
- ],
- ]);
-
-``env(base64:FOO)``
- Decodes the content of ``FOO``, which is a base64 encoded string.
-
-``env(json:FOO)``
- Decodes the content of ``FOO``, which is a JSON encoded string. It returns
- either an array or ``null``:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- # config/packages/framework.yaml
- parameters:
- env(TRUSTED_HOSTS): '["10.0.0.1", "10.0.0.2"]'
- framework:
- trusted_hosts: '%env(json:TRUSTED_HOSTS)%'
-
- .. code-block:: xml
-
-
-
-
-
-
- ["10.0.0.1", "10.0.0.2"]
-
-
-
-
-
- .. code-block:: php
-
- // config/packages/framework.php
- $container->setParameter('env(TRUSTED_HOSTS)', '["10.0.0.1", "10.0.0.2"]');
- $container->loadFromExtension('framework', [
- 'trusted_hosts' => '%env(json:TRUSTED_HOSTS)%',
- ]);
-
-``env(resolve:FOO)``
- Replaces the string ``FOO`` by the value of a config parameter with the
- same name:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- # config/packages/sentry.yaml
- parameters:
- env(HOST): '10.0.0.1'
- env(SENTRY_DSN): 'http://%env(HOST)%/project'
- sentry:
- dsn: '%env(resolve:SENTRY_DSN)%'
-
- .. code-block:: xml
-
-
-
-
-
-
- 10.0.0.1
- http://%env(HOST)%/project
-
-
-
-
-
- .. code-block:: php
-
- // config/packages/sentry.php
- $container->setParameter('env(HOST)', '10.0.0.1');
- $container->setParameter('env(SENTRY_DSN)', 'http://%env(HOST)%/project');
- $container->loadFromExtension('sentry', [
- 'dsn' => '%env(resolve:SENTRY_DSN)%',
- ]);
-
-``env(csv:FOO)``
- Decodes the content of ``FOO``, which is a CSV-encoded string:
-
- .. code-block:: yaml
-
- parameters:
- env(TRUSTED_HOSTS): "10.0.0.1, 10.0.0.2"
- framework:
- trusted_hosts: '%env(csv:TRUSTED_HOSTS)%'
-
- .. versionadded:: 4.1
- The ``csv`` processor was introduced in Symfony 4.1.
-
-``env(file:FOO)``
- Returns the contents of a file whose path is the value of the ``FOO`` env var:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- # config/packages/framework.yaml
- parameters:
- env(AUTH_FILE): '../config/auth.json'
- google:
- auth: '%env(file:AUTH_FILE)%'
-
- .. code-block:: xml
-
-
-
-
-
-
- ../config/auth.json
-
-
-
-
-
- .. code-block:: php
-
- // config/packages/framework.php
- $container->setParameter('env(AUTH_FILE)', '../config/auth.json');
- $container->loadFromExtension('google', [
- 'auth' => '%env(file:AUTH_FILE)%',
- ]);
-
-It is also possible to combine any number of processors:
-
-.. code-block:: yaml
-
- parameters:
- env(AUTH_FILE): "%kernel.project_dir%/config/auth.json"
- google:
- # 1. gets the value of the AUTH_FILE env var
- # 2. replaces the values of any config param to get the config path
- # 3. gets the content of the file stored in that path
- # 4. JSON-decodes the content of the file and returns it
- auth: '%env(json:file:resolve:AUTH_FILE)%'
-
-Custom Environment Variable Processors
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-It's also possible to add your own processors for environment variables. First,
-create a class that implements
-:class:`Symfony\\Component\\DependencyInjection\\EnvVarProcessorInterface`::
-
- use Symfony\Component\DependencyInjection\EnvVarProcessorInterface;
-
- class LowercasingEnvVarProcessor implements EnvVarProcessorInterface
- {
- public function getEnv($prefix, $name, \Closure $getEnv)
- {
- $env = $getEnv($name);
-
- return strtolower($env);
- }
-
- public static function getProvidedTypes()
- {
- return [
- 'lowercase' => 'string',
- ];
- }
- }
-
-To enable the new processor in the app, register it as a service and
-:doc:`tag it ` with the ``container.env_var_processor``
-tag. If you're using the
-:ref:`default services.yaml configuration `,
-this is already done for you, thanks to :ref:`autoconfiguration `.
-
-Constants
----------
-
-The container also has support for setting PHP constants as parameters.
-See :ref:`component-di-parameters-constants` for more details.
-
-Miscellaneous Configuration
----------------------------
-
-You can mix whatever configuration format you like (YAML, XML and PHP) in
-``config/packages/``. Importing a PHP file gives you the flexibility to add
-whatever is needed in the container. For instance, you can create a
-``drupal.php`` file in which you set a database URL based on Drupal's database
-configuration::
-
- // config/packages/drupal.php
-
- // import Drupal's configuration
- include_once('/path/to/drupal/sites/default/settings.php');
-
- // set a app.database_url parameter
- $container->setParameter('app.database_url', $db_url);
-
-.. _`SetEnv`: http://httpd.apache.org/docs/current/env.html
-.. _`fastcgi_param`: http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_param
diff --git a/configuration/front_controllers_and_kernel.rst b/configuration/front_controllers_and_kernel.rst
index 61ded2eb384..d986d7471b7 100644
--- a/configuration/front_controllers_and_kernel.rst
+++ b/configuration/front_controllers_and_kernel.rst
@@ -5,11 +5,11 @@
Understanding how the Front Controller, Kernel and Environments Work together
=============================================================================
-The section :doc:`/configuration/environments` explained the basics on how
-Symfony uses environments to run your application with different configuration
-settings. This section will explain a bit more in-depth what happens when your
-application is bootstrapped. To hook into this process, you need to understand
-three parts that work together:
+The :ref:`configuration environments ` section
+explained the basics on how Symfony uses environments to run your application
+with different configuration settings. This section will explain a bit more
+in-depth what happens when your application is bootstrapped. To hook into this
+process, you need to understand three parts that work together:
* `The Front Controller`_
* `The Kernel Class`_
@@ -74,7 +74,7 @@ It then creates the service container before serving requests in its
:method:`Symfony\\Component\\HttpKernel\\HttpKernelInterface::handle`
method.
-The kernel used in Symfony apps extends from :class:`Symfony\\Component\\HttpKernel\\Kernel`
+The kernel used in Symfony applications extends from :class:`Symfony\\Component\\HttpKernel\\Kernel`
and uses the :class:`Symfony\\Bundle\\FrameworkBundle\\Kernel\\MicroKernelTrait`.
The ``Kernel`` class leaves some methods from :class:`Symfony\\Component\\HttpKernel\\KernelInterface`
unimplemented and the ``MicroKernelTrait`` defines several abstract methods, so
@@ -101,9 +101,9 @@ This class uses the name of the environment - which is passed to the Kernel's
method and is available via :method:`Symfony\\Component\\HttpKernel\\Kernel::getEnvironment` -
to decide which bundles to enable. The logic for that is in ``registerBundles()``.
-You are, of course, free to create your own, alternative or additional
-``Kernel`` variants. All you need is to adapt your (or add a new) front
-controller to make use of the new kernel.
+You are free to create your own, alternative or additional ``Kernel`` variants.
+All you need is to adapt your (or add a new) front controller to make use of the
+new kernel.
.. note::
@@ -120,6 +120,77 @@ controller to make use of the new kernel.
But odds are high that you don't need to change things like this on the
fly by having several ``Kernel`` implementations.
+.. index::
+ single: Configuration; Debug mode
+
+Debug Mode
+~~~~~~~~~~
+
+The second argument to the ``Kernel`` constructor specifies if the application
+should run in "debug mode". Regardless of the
+:ref:`configuration environment `, a Symfony
+application can be run with debug mode set to ``true`` or ``false``.
+
+This affects many things in the application, such as displaying stacktraces on
+error pages or if cache files are dynamically rebuilt on each request. Though
+not a requirement, debug mode is generally set to ``true`` for the ``dev`` and
+``test`` environments and ``false`` for the ``prod`` environment.
+
+Similar to :ref:`configuring the environment `
+you can also enable/disable the debug mode using :ref:`the .env file `:
+
+.. code-block:: bash
+
+ # .env
+ # set it to 1 to enable the debug mode
+ APP_DEBUG=0
+
+This value can be overridden for commands by passing the ``APP_DEBUG`` value
+before running them:
+
+.. code-block:: terminal
+
+ # Use the debug mode defined in the .env file
+ $ php bin/console command_name
+
+ # Ignore the .env file and enable the debug mode for this command
+ $ APP_DEBUG=1 php bin/console command_name
+
+Internally, the value of the debug mode becomes the ``kernel.debug``
+parameter used inside the :doc:`service container `.
+If you look inside the application configuration file, you'll see the
+parameter used, for example, to turn Twig's debug mode on:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/twig.yaml
+ twig:
+ debug: '%kernel.debug%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ $container->loadFromExtension('twig', [
+ 'debug' => '%kernel.debug%',
+ // ...
+ ]);
+
The Environments
----------------
@@ -128,9 +199,9 @@ As mentioned above, the ``Kernel`` has to implement another method -
This method is responsible for loading the application's configuration from the
right *environment*.
-Environments have been covered extensively :doc:`in the previous article
-`, and you probably remember that the Symfony uses
-by default three of them - ``dev``, ``prod`` and ``test``.
+:ref:`Configuration environments ` allow to execute
+the same code using different configuration. Symfony provides three environments
+by default called ``dev``, ``prod`` and ``test``.
More technically, these names are nothing more than strings passed from the
front controller to the ``Kernel``'s constructor. This name can then be used in
@@ -138,9 +209,56 @@ the ``configureContainer()`` method to decide which configuration files to load.
Symfony's default ``Kernel`` class implements this method by loading first the
config files found on ``config/packages/*`` and then, the files found on
-``config/packages/ENVIRONMENT_NAME/``. You are, of course, free to implement
-this method differently if you need a more sophisticated way of loading your
-configuration.
+``config/packages/ENVIRONMENT_NAME/``. You are free to implement this method
+differently if you need a more sophisticated way of loading your configuration.
+
+.. index::
+ single: Environments; Cache directory
+
+Environments and the Cache Directory
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Symfony takes advantage of caching in many ways: the application configuration,
+routing configuration, Twig templates and more are cached to PHP objects
+stored in files on the filesystem.
+
+By default, these cached files are largely stored in the ``var/cache/`` directory.
+However, each environment caches its own set of files:
+
+.. code-block:: text
+
+ your-project/
+ ├─ var/
+ │ ├─ cache/
+ │ │ ├─ dev/ # cache directory for the *dev* environment
+ │ │ └─ prod/ # cache directory for the *prod* environment
+ │ ├─ ...
+
+Sometimes, when debugging, it may be helpful to inspect a cached file to
+understand how something is working. When doing so, remember to look in
+the directory of the environment you're using (most commonly ``dev/`` while
+developing and debugging). While it can vary, the ``var/cache/dev/`` directory
+includes the following:
+
+``appDevDebugProjectContainer.php``
+ The cached "service container" that represents the cached application
+ configuration.
+
+``appDevUrlGenerator.php``
+ The PHP class generated from the routing configuration and used when
+ generating URLs.
+
+``appDevUrlMatcher.php``
+ The PHP class used for route matching - look here to see the compiled regular
+ expression logic used to match incoming URLs to different routes.
+
+``twig/``
+ This directory contains all the cached Twig templates.
+
+.. note::
+
+ You can change the cache directory location and name. For more information
+ read the article :doc:`/configuration/override_dir_structure`.
-.. _front controller: https://en.wikipedia.org/wiki/Front_Controller_pattern
-.. _decorate: https://en.wikipedia.org/wiki/Decorator_pattern
+.. _`front controller`: https://en.wikipedia.org/wiki/Front_Controller_pattern
+.. _`decorate`: https://en.wikipedia.org/wiki/Decorator_pattern
diff --git a/configuration/micro_kernel_trait.rst b/configuration/micro_kernel_trait.rst
index f6b5eedf1f6..4676c9a27e4 100644
--- a/configuration/micro_kernel_trait.rst
+++ b/configuration/micro_kernel_trait.rst
@@ -14,7 +14,7 @@ A Single-File Symfony Application
Start with a completely empty directory and install these Symfony components
via Composer:
-.. code-block:: bash
+.. code-block:: terminal
$ composer require symfony/config symfony/http-kernel \
symfony/http-foundation symfony/routing \
@@ -72,11 +72,12 @@ Next, create an ``index.php`` file that defines the kernel class and executes it
$response->send();
$kernel->terminate($request, $response);
-That's it! To test it, you can start the built-in web server:
+That's it! To test it, start the :doc:`Symfony Local Web Server
+`:
.. code-block:: terminal
- $ php -S localhost:8000
+ $ symfony server:start
Then see the JSON response in your browser:
@@ -220,11 +221,11 @@ because the configuration started to get bigger:
+ xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
+ http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
-
+
@@ -282,7 +283,6 @@ Finally, you need a front controller to boot and run the application. Create a
``public/index.php``::
// public/index.php
-
use App\Kernel;
use Doctrine\Common\Annotations\AnnotationRegistry;
use Symfony\Component\HttpFoundation\Request;
@@ -323,12 +323,13 @@ this:
├─ composer.json
└─ composer.lock
-As before you can use PHP built-in server:
+As before you can use the :doc:`Symfony Local Web Server
+`:
.. code-block:: terminal
cd public/
- $ php -S localhost:8000 -t public/
+ $ symfony server:start
Then see webpage in browser:
diff --git a/configuration/multiple_kernels.rst b/configuration/multiple_kernels.rst
index d5999b96c20..235305ce090 100644
--- a/configuration/multiple_kernels.rst
+++ b/configuration/multiple_kernels.rst
@@ -16,7 +16,7 @@ request to generate the response.
This single kernel approach is a convenient default, but Symfony applications
can define any number of kernels. Whereas
-:doc:`environments ` execute the same application
+:ref:`environments ` execute the same application
with different configurations, kernels can execute different parts of the same
application.
@@ -82,9 +82,9 @@ Kernel. Be sure to also change the location of the cache, logs and configuration
files so they don't collide with the files from ``src/Kernel.php``::
// src/ApiKernel.php
- use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
+ use Symfony\Component\HttpKernel\Kernel;
class ApiKernel extends Kernel
{
diff --git a/configuration/override_dir_structure.rst b/configuration/override_dir_structure.rst
index 2e6bb5d3e82..fbfa119cc14 100644
--- a/configuration/override_dir_structure.rst
+++ b/configuration/override_dir_structure.rst
@@ -4,9 +4,8 @@
How to Override Symfony's default Directory Structure
=====================================================
-Symfony automatically ships with a default directory structure. You can
-override this directory structure to create your own. The default
-directory structure is:
+Symfony applications have the following default directory structure, but you can
+override it to create your own structure:
.. code-block:: text
@@ -28,10 +27,19 @@ directory structure is:
│ └─ ...
└─ vendor/
+.. _override-config-dir:
+
+Override the Configuration Directory
+------------------------------------
+
+The configuration directory is the only one which cannot be overridden in a
+Symfony application. Its location is hardcoded as the ``config/`` directory
+at your project root directory.
+
.. _override-cache-dir:
-Override the ``cache`` Directory
---------------------------------
+Override the Cache Directory
+----------------------------
You can change the default cache directory by overriding the ``getCacheDir()``
method in the ``Kernel`` class of your application::
@@ -51,21 +59,21 @@ method in the ``Kernel`` class of your application::
In this code, ``$this->environment`` is the current environment (i.e. ``dev``).
In this case you have changed the location of the cache directory to
-``var/{environment}/cache``.
+``var/{environment}/cache/``.
.. caution::
- You should keep the ``cache`` directory different for each environment,
+ You should keep the cache directory different for each environment,
otherwise some unexpected behavior may happen. Each environment generates
its own cached configuration files, and so each needs its own directory to
store those cache files.
.. _override-logs-dir:
-Override the ``logs`` Directory
--------------------------------
+Override the Log Directory
+--------------------------
-Overriding the ``logs`` directory is the same as overriding the ``cache``
+Overriding the ``var/log/`` directory is the same as overriding the ``var/cache/``
directory. The only difference is that you need to override the ``getLogDir()``
method::
@@ -82,7 +90,7 @@ method::
}
}
-Here you have changed the location of the directory to ``var/{environment}/log``.
+Here you have changed the location of the directory to ``var/{environment}/log/``.
.. _override-templates-dir:
@@ -110,9 +118,9 @@ own templates directory (or directories):
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:twig="http://symfony.com/schema/dic/twig"
xsi:schemaLocation="http://symfony.com/schema/dic/services
- http://symfony.com/schema/dic/services/services-1.0.xsd
+ https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/twig
- http://symfony.com/schema/dic/twig/twig-1.0.xsd">
+ https://symfony.com/schema/dic/twig/twig-1.0.xsd">
%kernel.project_dir%/resources/views
@@ -154,9 +162,9 @@ configuration option to define your own translations directory (or directories):
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:twig="http://symfony.com/schema/dic/twig"
xsi:schemaLocation="http://symfony.com/schema/dic/services
- http://symfony.com/schema/dic/services/services-1.0.xsd
+ https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/twig
- http://symfony.com/schema/dic/twig/twig-1.0.xsd">
+ https://symfony.com/schema/dic/twig/twig-1.0.xsd">
@@ -180,19 +188,19 @@ configuration option to define your own translations directory (or directories):
.. _override-web-dir:
.. _override-the-web-directory:
-Override the ``public`` Directory
----------------------------------
+Override the Public Directory
+-----------------------------
-If you need to rename or move your ``public`` directory, the only thing you need
-to guarantee is that the path to the ``var`` directory is still correct in your
-``index.php`` front controller. If you simply renamed the directory, you're
-fine. But if you moved it in some way, you may need to modify these paths inside
-those files::
+If you need to rename or move your ``public/`` directory, the only thing you
+need to guarantee is that the path to the ``var/`` directory is still correct in
+your ``index.php`` front controller. If you renamed the directory, you're fine.
+But if you moved it in some way, you may need to modify these paths inside those
+files::
require_once __DIR__.'/../path/to/vendor/autoload.php';
-You also need to change the ``extra.public-dir`` option in the
-``composer.json`` file:
+You also need to change the ``extra.public-dir`` option in the ``composer.json``
+file:
.. code-block:: json
@@ -206,17 +214,17 @@ You also need to change the ``extra.public-dir`` option in the
.. tip::
- Some shared hosts have a ``public_html`` web directory root. Renaming
- your web directory from ``public`` to ``public_html`` is one way to make
+ Some shared hosts have a ``public_html/`` web directory root. Renaming
+ your web directory from ``public/`` to ``public_html/`` is one way to make
your Symfony project work on your shared host. Another way is to deploy
your application to a directory outside of your web root, delete your
- ``public_html`` directory, and then replace it with a symbolic link to
- the ``public`` dir in your project.
+ ``public_html/`` directory, and then replace it with a symbolic link to
+ the ``public/`` dir in your project.
-Override the ``vendor`` Directory
----------------------------------
+Override the Vendor Directory
+-----------------------------
-To override the ``vendor`` directory, you need to define the ``vendor-dir``
+To override the ``vendor/`` directory, you need to define the ``vendor-dir``
option in your ``composer.json`` file like this:
.. code-block:: json
@@ -230,6 +238,6 @@ option in your ``composer.json`` file like this:
.. tip::
- This modification can be of interest if you are working in a virtual environment
- and cannot use NFS - for example, if you're running a Symfony application using
- Vagrant/VirtualBox in a guest operating system.
+ This modification can be of interest if you are working in a virtual
+ environment and cannot use NFS - for example, if you're running a Symfony
+ application using Vagrant/VirtualBox in a guest operating system.
diff --git a/configuration/using_parameters_in_dic.rst b/configuration/using_parameters_in_dic.rst
index 9a45d0e4eca..758bb68d39a 100644
--- a/configuration/using_parameters_in_dic.rst
+++ b/configuration/using_parameters_in_dic.rst
@@ -53,15 +53,15 @@ Now, examine the results to see this closely:
-
+
-
+
-
+
-
-
-
-
- example.org
- https
- my/path
- %router.request_context.base_url%
- true
-
-
-
-
- .. code-block:: php
-
- // config/services.php
- $container->setParameter('router.request_context.host', 'example.org');
- $container->setParameter('router.request_context.scheme', 'https');
- $container->setParameter('router.request_context.base_url', 'my/path');
- $container->setParameter('asset.request_context.base_path', $container->getParameter('router.request_context.base_url'));
- $container->setParameter('asset.request_context.secure', true);
-
-Configuring the Request Context per Command
--------------------------------------------
-
-To change it only in one command you can fetch the Request Context from the
-router service and override its settings::
-
- // src/Command/DemoCommand.php
- use Symfony\Component\Routing\RouterInterface;
- // ...
-
- class DemoCommand extends Command
- {
- private $router;
-
- public function __construct(RouterInterface $router)
- {
- parent::__construct();
-
- $this->router = $router;
- }
-
- protected function execute(InputInterface $input, OutputInterface $output)
- {
- $context = $this->router->getContext();
- $context->setHost('example.com');
- $context->setScheme('https');
- $context->setBaseUrl('my/path');
-
- $url = $this->router->generate('route-name', ['param-name' => 'param-value']);
- // ...
- }
- }
diff --git a/console/style.rst b/console/style.rst
index e866ecc1e16..88e605f0146 100644
--- a/console/style.rst
+++ b/console/style.rst
@@ -13,11 +13,11 @@ Consider for example the code used to display the title of the following command
// src/Command/GreetCommand.php
namespace App\Command;
- use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
+ use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
- class GreetCommand extends ContainerAwareCommand
+ class GreetCommand extends Command
{
// ...
@@ -53,12 +53,12 @@ title of the command::
// src/Command/GreetCommand.php
namespace App\Command;
- use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
- use Symfony\Component\Console\Style\SymfonyStyle;
+ use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
+ use Symfony\Component\Console\Style\SymfonyStyle;
- class GreetCommand extends ContainerAwareCommand
+ class GreetCommand extends Command
{
// ...
@@ -226,7 +226,7 @@ User Input Methods
$io->ask('What is your name?');
- You can pass the default value as the second argument so the user can simply
+ You can pass the default value as the second argument so the user can
hit the key to select that value::
$io->ask('Where are you from?', 'United States');
@@ -248,7 +248,9 @@ User Input Methods
$io->askHidden('What is your password?');
- // validates the given answer
+ In case you need to validate the given value, pass a callback validator as
+ the second argument::
+
$io->askHidden('What is your password?', function ($password) {
if (empty($password)) {
throw new \RuntimeException('Password cannot be empty.');
@@ -262,7 +264,7 @@ User Input Methods
$io->confirm('Restart the web server?');
- You can pass the default value as the second argument so the user can simply
+ You can pass the default value as the second argument so the user can
hit the key to select that value::
$io->confirm('Restart the web server?', true);
@@ -273,7 +275,7 @@ User Input Methods
$io->choice('Select the queue to analyze', ['queue1', 'queue2', 'queue3']);
- You can pass the default value as the third argument so the user can simply
+ You can pass the default value as the third argument so the user can
hit the key to select that value::
$io->choice('Select the queue to analyze', ['queue1', 'queue2', 'queue3'], 'queue1');
@@ -352,23 +354,26 @@ Then, instantiate this custom class instead of the default ``SymfonyStyle`` in
your commands. Thanks to the ``StyleInterface`` you won't need to change the code
of your commands to change their appearance::
+ // src/Command/GreetCommand.php
namespace App\Console;
use App\Console\CustomStyle;
+ use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
- class GreetCommand extends ContainerAwareCommand
+ class GreetCommand extends Command
{
// ...
protected function execute(InputInterface $input, OutputInterface $output)
{
// Before
- // $io = new SymfonyStyle($input, $output);
+ $io = new SymfonyStyle($input, $output);
// After
$io = new CustomStyle($input, $output);
+
// ...
}
}
diff --git a/contributing/code/bc.rst b/contributing/code/bc.rst
index dbfe6b0e52c..a631073afec 100644
--- a/contributing/code/bc.rst
+++ b/contributing/code/bc.rst
@@ -22,7 +22,7 @@ method signature.
Also, not every BC break has the same impact on application code. While some BC
breaks require you to make significant changes to your classes or your
-architecture, others are fixed as easily as changing the name of a method.
+architecture, others are fixed by changing the name of a method.
That's why we created this page for you. The section "Using Symfony Code" will
tell you how you can ensure that your application won't break completely when
@@ -94,7 +94,7 @@ public methods and properties.
.. caution::
Classes, properties and methods that bear the tag ``@internal`` as well as
- the classes located in the various ``*\\Tests\\`` namespaces are an
+ the classes located in the various ``*\Tests\`` namespaces are an
exception to this rule. They are meant for internal use only and should
not be accessed by your own code.
@@ -445,9 +445,4 @@ Turn static into non static No
.. [9] Allowed for the ``void`` return type.
-.. _Semantic Versioning: https://semver.org/
-.. _scalar type: https://php.net/manual/en/function.is-scalar.php
-.. _boolean values: https://php.net/manual/en/function.boolval.php
-.. _string values: https://php.net/manual/en/function.strval.php
-.. _integer values: https://php.net/manual/en/function.intval.php
-.. _float values: https://php.net/manual/en/function.floatval.php
+.. _`Semantic Versioning`: https://semver.org/
diff --git a/contributing/code/bugs.rst b/contributing/code/bugs.rst
index 5f0ac3f585d..3b024945271 100644
--- a/contributing/code/bugs.rst
+++ b/contributing/code/bugs.rst
@@ -26,7 +26,7 @@ If your problem definitely looks like a bug, report it using the official bug
* Describe the steps needed to reproduce the bug with short code examples
(providing a unit test that illustrates the bug is best);
-* If the bug you experienced is not obvious or affects more than one layer,
+* If the bug you experienced is not simple or affects more than one layer,
providing a simple failing unit test may not be sufficient. In this case,
please :doc:`provide a reproducer `;
@@ -42,11 +42,10 @@ If your problem definitely looks like a bug, report it using the official bug
**Be wary that stack traces may contain sensitive information, and if it is
the case, be sure to redact them prior to posting your stack trace.**
-* *(optional)* Attach a :doc:`patch `.
+* *(optional)* Attach a :doc:`patch `.
.. _`Stack Overflow`: https://stackoverflow.com/questions/tagged/symfony
.. _IRC channel: https://symfony.com/irc
.. _the Symfony Slack: https://symfony.com/slack-invite
.. _tracker: https://github.com/symfony/symfony/issues
-.. _Symfony Standard Edition: https://github.com/symfony/symfony-standard/
.. _ HTML tag: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details
diff --git a/contributing/code/conventions.rst b/contributing/code/conventions.rst
index b90cbb565da..d3622b57ade 100644
--- a/contributing/code/conventions.rst
+++ b/contributing/code/conventions.rst
@@ -79,31 +79,46 @@ must be used instead (where ``XXX`` is the name of the related thing):
.. _contributing-code-conventions-deprecations:
-Deprecations
-------------
+Deprecating Code
+----------------
From time to time, some classes and/or methods are deprecated in the
framework; that happens when a feature implementation cannot be changed
because of backward compatibility issues, but we still want to propose a
-"better" alternative. In that case, the old implementation can simply be
-**deprecated**.
+"better" alternative. In that case, the old implementation can be **deprecated**.
+
+Deprecations must only be introduced on the next minor version of the impacted
+component (or bundle, or bridge, or contract).
+They can exceptionally be introduced on previous supported versions if they are critical.
+
+A new class (or interface, or trait) cannot be introduced as deprecated, or
+contain deprecated methods.
+
+A new method cannot be introduced as deprecated.
A feature is marked as deprecated by adding a ``@deprecated`` phpdoc to
relevant classes, methods, properties, ...::
/**
- * @deprecated since version 2.8, to be removed in 3.0. Use XXX instead.
+ * @deprecated since Symfony 2.8.
+ */
+
+The deprecation message must indicate the version in which the feature was deprecated,
+and whenever possible, how it was replaced::
+
+ /**
+ * @deprecated since Symfony 2.8, use Replacement instead.
*/
-The deprecation message should indicate the version when the class/method was
-deprecated, the version when it will be removed, and whenever possible, how
-the feature was replaced.
+When the replacement is in another namespace than the deprecated class, its FQCN must be used::
+
+ /**
+ * @deprecated since Symfony 2.8, use A\B\Replacement instead.
+ */
-A PHP ``E_USER_DEPRECATED`` error must also be triggered to help people with
-the migration starting one or two minor versions before the version where the
-feature will be removed (depending on the criticality of the removal)::
+A PHP ``E_USER_DEPRECATED`` error must also be triggered to help people with the migration::
- @trigger_error('XXX() is deprecated since version 2.8 and will be removed in 3.0. Use XXX instead.', E_USER_DEPRECATED);
+ @trigger_error(sprintf('The "%s" class is deprecated since Symfony 2.8, use "%s" instead.', Deprecated::class, Replacement::class), E_USER_DEPRECATED);
Without the `@-silencing operator`_, users would need to opt-out from deprecation
notices. Silencing swaps this behavior and allows users to opt-in when they are
@@ -114,19 +129,58 @@ the Web Debug Toolbar or by the PHPUnit bridge).
When deprecating a whole class the ``trigger_error()`` call should be placed
between the namespace and the use declarations, like in this example from
-`ArrayParserCache`_::
+`ServiceRouterLoader`_::
- namespace Symfony\Component\ExpressionLanguage\ParserCache;
+ namespace Symfony\Component\Routing\Loader\DependencyInjection;
- @trigger_error('The '.__NAMESPACE__.'\ArrayParserCache class is deprecated since version 3.2 and will be removed in 4.0. Use the Symfony\Component\Cache\Adapter\ArrayAdapter class instead.', E_USER_DEPRECATED);
+ use Symfony\Component\Routing\Loader\ContainerLoader;
- use Symfony\Component\ExpressionLanguage\ParsedExpression;
+ @trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', ServiceRouterLoader::class, ContainerLoader::class), E_USER_DEPRECATED);
/**
- * @author Adrien Brault
- *
- * @deprecated ArrayParserCache class is deprecated since version 3.2 and will be removed in 4.0. Use the Symfony\Component\Cache\Adapter\ArrayAdapter class instead.
+ * @deprecated since Symfony 4.4, use Symfony\Component\Routing\Loader\ContainerLoader instead.
*/
- class ArrayParserCache implements ParserCacheInterface
+ class ServiceRouterLoader extends ObjectRouteLoader
+
+.. _`ServiceRouterLoader`: https://github.com/symfony/symfony/blob/4.4/src/Symfony/Component/Routing/Loader/DependencyInjection/ServiceRouterLoader.php
+
+The deprecation must be added to the ``CHANGELOG.md`` file of the impacted component::
+
+ 4.4.0
+ -----
+
+ * Deprecated the `Deprecated` class, use `Replacement` instead.
+
+It must also be added to the ``UPGRADE.md`` file of the targeted minor version
+(``UPGRADE-4.4.md`` in our example)::
+
+ DependencyInjection
+ -------------------
+
+ * Deprecated the `Deprecated` class, use `Replacement` instead.
+
+Finally, its consequences must be added to the ``UPGRADE.md`` file of the next major version
+(``UPGRADE-5.0.md`` in our example)::
+
+ DependencyInjection
+ -------------------
+
+ * Removed the `Deprecated` class, use `Replacement` instead.
+
+All these tasks are mandatory and must be done in the same pull request.
+
+Removing Deprecated Code
+------------------------
+
+Removing deprecated code can only be done once every 2 years, on the next major version of the
+impacted component (``master`` branch).
+
+When removing deprecated code, the consequences of the deprecation must be added to the ``CHANGELOG.md`` file
+of the impacted component::
+
+ 5.0.0
+ -----
+
+ * Removed the `Deprecated` class, use `Replacement` instead.
-.. _`ArrayParserCache`: https://github.com/symfony/symfony/blob/3.2/src/Symfony/Component/ExpressionLanguage/ParserCache/ArrayParserCache.php
+This task is mandatory and must be done in the same pull request.
diff --git a/contributing/code/core_team.rst b/contributing/code/core_team.rst
index 0c5c37427f2..5a9596fc80f 100644
--- a/contributing/code/core_team.rst
+++ b/contributing/code/core_team.rst
@@ -29,12 +29,7 @@ The Symfony Core groups, in descending order of priority, are as follows:
2. **Mergers Team**
-* Merge pull requests for the component or components on which they have been
- granted privileges.
-
-3. **Deciders Team**
-
-* Decide to merge or reject a pull request.
+* Merge pull requests on the main Symfony repository.
In addition, there are other groups created to manage specific topics:
@@ -43,6 +38,10 @@ In addition, there are other groups created to manage specific topics:
* Manage the whole security process (triaging reported vulnerabilities, fixing
the reported issues, coordinating the release of security fixes, etc.)
+**Recipes Team**
+
+* Manage the recipes in the main and contrib recipe repositories.
+
**Documentation Team**
* Manage the whole `symfony-docs repository`_.
@@ -50,62 +49,36 @@ In addition, there are other groups created to manage specific topics:
Active Core Members
~~~~~~~~~~~~~~~~~~~
-.. role:: leader
-.. role:: merger
-.. role:: decider
-
* **Project Leader**:
* **Fabien Potencier** (`fabpot`_).
* **Mergers Team** (``@symfony/mergers`` on GitHub):
- * **Tobias Schultze** (`Tobion`_) can merge into the Routing_,
- OptionsResolver_ and PropertyAccess_ components;
-
- * **Nicolas Grekas** (`nicolas-grekas`_) can merge into the Cache_, Debug_,
- Process_, PropertyAccess_, VarDumper_ components, PhpUnitBridge_ and
- the DebugBundle_;
-
- * **Christophe Coevoet** (`stof`_) can merge into all components, bridges and
- bundles;
-
- * **Kévin Dunglas** (`dunglas`_) can merge into the PropertyInfo_, the Serializer_
- and the WebLink_ components;
-
- * **Jakub Zalas** (`jakzal`_) can merge into the DomCrawler_ and Intl_
- components;
-
- * **Christian Flothmann** (`xabbuh`_) can merge into the Yaml_ component;
-
- * **Javier Eguiluz** (`javiereguiluz`_) can merge into the WebProfilerBundle_;
-
- * **Grégoire Pineau** (`lyrixx`_) can merge into the Workflow_ component;
-
- * **Ryan Weaver** (`weaverryan`_) can merge into the Security_ component and
- the SecurityBundle_;
-
- * **Robin Chalas** (`chalasr`_) can merge into the Console_ and Security_
- components and the SecurityBundle_;
-
- * **Maxime Steinhausser** (`ogizanagi`_) can merge into Config_, Console_,
- Form_, Serializer_, DependencyInjection_, and HttpKernel_ components;
-
- * **Tobias Nyholm** (`Nyholm`_) manages the official and contrib recipes
- repositories;
-
- * **Samuel Rozé** (`sroze`_) can merge into the Messenger_ component.
-
-* **Deciders Team** (``@symfony/deciders`` on GitHub):
-
- * **Jordi Boggiano** (`seldaek`_);
- * **Lukas Kahwe Smith** (`lsmith77`_).
+ * **Nicolas Grekas** (`nicolas-grekas`_);
+ * **Christophe Coevoet** (`stof`_);
+ * **Christian Flothmann** (`xabbuh`_);
+ * **Tobias Schultze** (`Tobion`_);
+ * **Kévin Dunglas** (`dunglas`_);
+ * **Jakub Zalas** (`jakzal`_);
+ * **Javier Eguiluz** (`javiereguiluz`_);
+ * **Grégoire Pineau** (`lyrixx`_);
+ * **Ryan Weaver** (`weaverryan`_);
+ * **Robin Chalas** (`chalasr`_);
+ * **Maxime Steinhausser** (`ogizanagi`_);
+ * **Samuel Rozé** (`sroze`_);
+ * **Yonel Ceruto** (`yceruto`_).
* **Security Team** (``@symfony/security`` on GitHub):
* **Fabien Potencier** (`fabpot`_);
* **Michael Cullum** (`michaelcullum`_).
+* **Recipes Team**:
+
+ * **Fabien Potencier** (`fabpot`_);
+ * **Tobias Nyholm** (`Nyholm`_).
+
* **Documentation Team** (``@symfony/team-symfony-docs`` on GitHub):
* **Fabien Potencier** (`fabpot`_);
@@ -114,6 +87,7 @@ Active Core Members
* **Wouter De Jong** (`wouterj`_);
* **Jules Pietri** (`HeahDude`_);
* **Javier Eguiluz** (`javiereguiluz`_).
+ * **Oskar Stark** (`OskarStark`_).
Former Core Members
~~~~~~~~~~~~~~~~~~~
@@ -123,7 +97,8 @@ Symfony contributions:
* **Bernhard Schussek** (`webmozart`_);
* **Abdellatif AitBoudad** (`aitboudad`_);
-* **Romain Neutron**.
+* **Romain Neutron**;
+* **Jordi Boggiano** (`Seldaek`_).
Core Membership Application
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -170,11 +145,11 @@ A pull request **can be merged** if:
* It is a minor change [1]_;
-* Enough time was given for peer reviews (at least 2 days for "regular"
- pull requests, and 4 days for pull requests with "a significant impact");
+* Enough time was given for peer reviews;
-* At least the component's **Merger** or two other Core members voted ``+1``
- and no Core member voted ``-1``.
+* At least two **Merger Team** members voted ``+1`` (only one if the submitter
+ is part of the Merger team) and no Core member voted ``-1`` (via Github
+ reviews or as comments).
Pull Request Merging Process
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -199,54 +174,15 @@ discretion of the **Project Leader**.
.. [1] Minor changes comprise typos, DocBlock fixes, code standards
violations, and minor CSS, JavaScript and HTML modifications.
-.. _PhpUnitBridge: https://github.com/symfony/phpunit-bridge
-.. _BrowserKit: https://github.com/symfony/browser-kit
-.. _Cache: https://github.com/symfony/cache
-.. _Config: https://github.com/symfony/config
-.. _Console: https://github.com/symfony/console
-.. _Debug: https://github.com/symfony/debug
-.. _DebugBundle: https://github.com/symfony/debug-bundle
-.. _DependencyInjection: https://github.com/symfony/dependency-injection
-.. _DoctrineBridge: https://github.com/symfony/doctrine-bridge
-.. _EventDispatcher: https://github.com/symfony/event-dispatcher
-.. _DomCrawler: https://github.com/symfony/dom-crawler
-.. _Form: https://github.com/symfony/form
-.. _HttpFoundation: https://github.com/symfony/http-foundation
-.. _HttpKernel: https://github.com/symfony/http-kernel
-.. _Icu: https://github.com/symfony/icu
-.. _Intl: https://github.com/symfony/intl
-.. _LDAP: https://github.com/symfony/ldap
-.. _Locale: https://github.com/symfony/locale
-.. _Messenger: https://github.com/symfony/messenger
-.. _MonologBridge: https://github.com/symfony/monolog-bridge
-.. _OptionsResolver: https://github.com/symfony/options-resolver
-.. _Process: https://github.com/symfony/process
-.. _PropertyAccess: https://github.com/symfony/property-access
-.. _PropertyInfo: https://github.com/symfony/property-info
-.. _Routing: https://github.com/symfony/routing
-.. _Serializer: https://github.com/symfony/serializer
-.. _Translation: https://github.com/symfony/translation
-.. _Security: https://github.com/symfony/security
-.. _SecurityBundle: https://github.com/symfony/security-bundle
-.. _Stopwatch: https://github.com/symfony/stopwatch
-.. _TwigBridge: https://github.com/symfony/twig-bridge
-.. _Validator: https://github.com/symfony/validator
-.. _VarDumper: https://github.com/symfony/var-dumper
-.. _Workflow: https://github.com/symfony/workflow
-.. _Yaml: https://github.com/symfony/yaml
-.. _WebProfilerBundle: https://github.com/symfony/web-profiler-bundle
-.. _WebLink: https://github.com/symfony/web-link
.. _`symfony-docs repository`: https://github.com/symfony/symfony-docs
.. _`fabpot`: https://github.com/fabpot/
.. _`webmozart`: https://github.com/webmozart/
.. _`Tobion`: https://github.com/Tobion/
-.. _`romainneutron`: https://github.com/romainneutron/
.. _`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/
-.. _`lsmith77`: https://github.com/lsmith77/
.. _`weaverryan`: https://github.com/weaverryan/
.. _`aitboudad`: https://github.com/aitboudad/
.. _`xabbuh`: https://github.com/xabbuh/
@@ -256,6 +192,8 @@ discretion of the **Project Leader**.
.. _`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
diff --git a/contributing/code/experimental.rst b/contributing/code/experimental.rst
index 9081cf5184c..998f112ba7b 100644
--- a/contributing/code/experimental.rst
+++ b/contributing/code/experimental.rst
@@ -5,8 +5,8 @@ All Symfony features benefit from our :doc:`Backward Compatibility Promise
` to give developers the confidence to upgrade to new
versions safely and more often.
-But sometimes, a new feature is controversial. Or finding a good API is not
-easy. In such cases, we prefer to gather feedback from real-world usage, adapt
+But sometimes, a new feature is controversial or you cannot find a convincing API.
+In such cases, we prefer to gather feedback from real-world usage, adapt
the API, or remove it altogether. Doing so is not possible with a no BC-break
approach.
diff --git a/contributing/code/index.rst b/contributing/code/index.rst
index 49608caeeb6..0bb29d6abc4 100644
--- a/contributing/code/index.rst
+++ b/contributing/code/index.rst
@@ -6,7 +6,7 @@ Contributing Code
bugs
reproducer
- patches
+ pull_requests
maintenance
core_team
security
diff --git a/contributing/code/maintenance.rst b/contributing/code/maintenance.rst
index bc16a6cf805..de643e98af6 100644
--- a/contributing/code/maintenance.rst
+++ b/contributing/code/maintenance.rst
@@ -23,8 +23,8 @@ Besides bug fixes, other minor changes can be accepted in a patch version:
issues (any such patches must come with numbers that show a significant
improvement on real-world code);
-* **Newer versions of PHP/HHVM**: Fixes that add support for newer versions of
- PHP or HHVM are acceptable if they don't break the unit test suite;
+* **Newer versions of PHP**: Fixes that add support for newer versions of
+ PHP are acceptable if they don't break the unit test suite;
* **Newer versions of popular OSes**: Fixes that add support for newer versions
of popular OSes (Linux, MacOS and Windows) are acceptable if they don't break
diff --git a/contributing/code/patches.rst b/contributing/code/pull_requests.rst
similarity index 72%
rename from contributing/code/patches.rst
rename to contributing/code/pull_requests.rst
index 9eb76d4b570..6957c5d043d 100644
--- a/contributing/code/patches.rst
+++ b/contributing/code/pull_requests.rst
@@ -1,10 +1,21 @@
-Submitting a Patch
+Proposing a Change
==================
-Patches are the best way to provide a bug fix or to propose enhancements to
-Symfony.
+A pull request, "PR" for short, is the best way to provide a bug fix or to
+propose enhancements to Symfony.
-Step 1: Setup your Environment
+Step 1: Check existing Issues and Pull Requests
+-----------------------------------------------
+
+Before working on a change, check to see if someone else also raised the topic
+or maybe even started working on a PR by `searching on GitHub`_.
+
+If you are unsure or if you have any questions during this entire process,
+please ask your questions on the #contribs channel on `Symfony Slack`_.
+
+.. _step-1-setup-your-environment:
+
+Step 2: Setup your Environment
------------------------------
Install the Software Stack
@@ -90,20 +101,27 @@ Check that the current Tests Pass
Now that Symfony is installed, check that all unit tests pass for your
environment as explained in the dedicated :doc:`document `.
-Step 2: Work on your Patch
---------------------------
+.. tip::
+
+ If tests are failing, check on `Travis-CI`_ if the same test is
+ failing there as well. In that case you do not need to be concerned
+ about the test failing locally.
+
+.. _step-2-work-on-your-patch:
+
+Step 3: Work on your Pull Request
+---------------------------------
The License
~~~~~~~~~~~
-Before you start, you must know that all the patches you are going to submit
-must be released under the *MIT license*, unless explicitly specified in your
-commits.
+Before you start, you should be aware that all the code you are going to submit
+must be released under the *MIT license*.
Choose the right Branch
~~~~~~~~~~~~~~~~~~~~~~~
-Before working on a patch, you must determine on which branch you need to
+Before working on a PR, you must determine on which branch you need to
work:
* ``3.4``, if you are fixing a bug for an existing feature or want to make a
@@ -111,33 +129,39 @@ work:
` (you may have to choose a higher branch if
the feature you are fixing was introduced in a later version);
- * ``master``, if you are adding a new feature.
+* ``master``, if you are adding a new feature.
+
+ The only exception is when a new :doc:`major Symfony version `
+ (4.0, 5.0, etc.) comes out every two years. Because of the
+ :ref:`special development process ` of those versions,
+ you need to use the previous minor version for the features (e.g. use ``3.4``
+ instead of ``4.0``, use ``4.4`` instead of ``5.0``, etc.)
.. note::
All bug fixes merged into maintenance branches are also merged into more
- recent branches on a regular basis. For instance, if you submit a patch
- for the ``3.4`` branch, the patch will also be applied by the core team on
+ recent branches on a regular basis. For instance, if you submit a PR
+ for the ``3.4`` branch, the PR will also be applied by the core team on
the ``master`` branch.
Create a Topic Branch
~~~~~~~~~~~~~~~~~~~~~
-Each time you want to work on a patch for a bug or on an enhancement, create a
+Each time you want to work on a PR for a bug or on an enhancement, create a
topic branch:
.. code-block:: terminal
$ git checkout -b BRANCH_NAME master
-Or, if you want to provide a bugfix for the ``3.4`` branch, first track the remote
+Or, if you want to provide a bug fix for the ``3.4`` branch, first track the remote
``3.4`` branch locally:
.. code-block:: terminal
$ git checkout -t origin/3.4
-Then create a new branch off the ``3.4`` branch to work on the bugfix:
+Then create a new branch off the ``3.4`` branch to work on the bug fix:
.. code-block:: terminal
@@ -167,8 +191,10 @@ uses, and replaces them by symbolic links to the ones in the Git repository.
Before running the ``link`` command, be sure that the dependencies of the project you
want to debug are installed by running ``composer install`` inside it.
-Work on your Patch
-~~~~~~~~~~~~~~~~~~
+.. _work-on-your-patch:
+
+Work on your Pull Request
+~~~~~~~~~~~~~~~~~~~~~~~~~
Work on the code as much as you want and commit as much as you want; but keep
in mind the following:
@@ -181,7 +207,7 @@ in mind the following:
actually works;
* Try hard to not break backward compatibility (if you must do so, try to
- provide a compatibility layer to support the old way) -- patches that break
+ provide a compatibility layer to support the old way) -- PRs that break
backward compatibility have less chance to be merged;
* Do atomic and logically separate commits (use the power of ``git rebase`` to
@@ -199,7 +225,7 @@ in mind the following:
as defined in `PSR-1`_ and `PSR-2`_.
A status is posted below the pull request description with a summary
- of any problems it detects or any Travis CI build failures.
+ of any problems it detects or any `Travis-CI`_ build failures.
.. tip::
@@ -210,10 +236,12 @@ in mind the following:
verb (``fixed ...``, ``added ...``, ...) to start the summary and don't
add a period at the end.
-Prepare your Patch for Submission
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.. _prepare-your-patch-for-submission:
+
+Prepare your Pull Request for Submission
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-When your patch is not about a bug fix (when you add a new feature or change
+When your PR is not about a bug fix (when you add a new feature or change
an existing one for instance), it must also include the following:
* An explanation of the changes in the relevant ``CHANGELOG`` file(s) (the
@@ -223,16 +251,20 @@ an existing one for instance), it must also include the following:
``UPGRADE`` file(s) if the changes break backward compatibility or if you
deprecate something that will ultimately break backward compatibility.
-Step 3: Submit your Patch
--------------------------
+.. _step-4-submit-your-patch:
+
+Step 4: Submit your Pull Request
+--------------------------------
-Whenever you feel that your patch is ready for submission, follow the
+Whenever you feel that your PR is ready for submission, follow the
following steps.
-Rebase your Patch
-~~~~~~~~~~~~~~~~~
+.. _rebase-your-patch:
-Before submitting your patch, update your branch (needed if it takes you a
+Rebase your Pull Request
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Before submitting your PR, update your branch (needed if it takes you a
while to finish your changes):
.. code-block:: terminal
@@ -246,7 +278,7 @@ while to finish your changes):
.. tip::
Replace ``master`` with the branch you selected previously (e.g. ``3.4``)
- if you are working on a bugfix
+ if you are working on a bug fix.
When doing the ``rebase`` command, you might have to fix merge conflicts.
``git status`` will show you the *unmerged* files. Resolve all the conflicts,
@@ -273,7 +305,7 @@ You can now make a pull request on the ``symfony/symfony`` GitHub repository.
.. tip::
Take care to point your pull request towards ``symfony:3.4`` if you want
- the core team to pull a bugfix based on the ``3.4`` branch.
+ the core team to pull a bug fix based on the ``3.4`` branch.
To ease the core team work, always include the modified components in your
pull request message, like in:
@@ -296,10 +328,10 @@ Some answers to the questions trigger some more requirements:
* If you answer yes to "New feature?", you must submit a pull request to the
documentation and reference it under the "Doc PR" section;
-* If you answer yes to "BC breaks?", the patch must contain updates to the
+* If you answer yes to "BC breaks?", the PR must contain updates to the
relevant ``CHANGELOG`` and ``UPGRADE`` files;
-* If you answer yes to "Deprecations?", the patch must contain updates to the
+* If you answer yes to "Deprecations?", the PR must contain updates to the
relevant ``CHANGELOG`` and ``UPGRADE`` files;
* If you answer no to "Tests pass", you must add an item to a todo-list with
@@ -326,9 +358,10 @@ because you want early feedback on your work, add an item to todo-list:
- [ ] gather feedback for my changes
As long as you have items in the todo-list, please prefix the pull request
-title with "[WIP]".
+title with "[WIP]". If you do not yet want to trigger the automated tests,
+you can also set the PR to `draft status`_.
-In the pull request description, give as much details as possible about your
+In the pull request description, give as much detail as possible about your
changes (don't hesitate to give code examples to illustrate your points). If
your pull request is about adding a new feature or modifying an existing one,
explain the rationale for the changes. The pull request description helps the
@@ -339,11 +372,29 @@ commit message).
In addition to this "code" pull request, you must also send a pull request to
the `documentation repository`_ to update the documentation when appropriate.
-Rework your Patch
-~~~~~~~~~~~~~~~~~
+Step 5: Receiving Feedback
+--------------------------
+
+We ask all contributors to follow some
+:doc:`best practices `
+to ensure a constructive feedback process.
+
+If you think someone fails to keep this advice in mind and you want another
+perspective, please join the #contribs channel on `Symfony Slack`_. If you
+receive feedback you find abusive please contact the
+:doc:`CARE team `.
+
+The :doc:`core team ` is responsible for deciding
+which PR gets merged, so their feedback is the most relevant. So do not feel
+pressured to refactor your code immediately when someone provides feedback.
+
+.. _rework-your-patch:
+
+Rework your Pull Request
+~~~~~~~~~~~~~~~~~~~~~~~~
Based on the feedback on the pull request, you might need to rework your
-patch. Before re-submitting the patch, rebase with ``upstream/master`` or
+PR. Before re-submitting the PR, rebase with ``upstream/master`` or
``upstream/3.4``, don't merge; and force the push to the origin:
.. code-block:: terminal
@@ -364,13 +415,13 @@ before merging.
.. _ProGit: https://git-scm.com/book
.. _GitHub: https://github.com/join
-.. _`GitHub's Documentation`: https://help.github.com/articles/ignoring-files
+.. _`GitHub's documentation`: https://help.github.com/articles/ignoring-files
.. _Symfony repository: https://github.com/symfony/symfony
-.. _dev mailing-list: https://groups.google.com/group/symfony-devs
-.. _travis-ci.org: https://travis-ci.org/
-.. _`travis-ci.org status icon`: https://about.travis-ci.com/docs/user/status-images/
-.. _`travis-ci.org Getting Started Guide`: https://about.travis-ci.com/docs/user/getting-started/
.. _`documentation repository`: https://github.com/symfony/symfony-docs
.. _`fabbot`: https://fabbot.io
.. _`PSR-1`: https://www.php-fig.org/psr/psr-1/
.. _`PSR-2`: https://www.php-fig.org/psr/psr-2/
+.. _`searching on GitHub`: https://github.com/symfony/symfony/issues?q=+is%3Aopen+
+.. _`Symfony Slack`: https://symfony.com/slack-invite
+.. _`Travis-CI`: https://travis-ci.org/symfony/symfony
+.. _`draft status`: https://help.github.com/en/articles/about-pull-requests#draft-pull-requests
diff --git a/contributing/code/reproducer.rst b/contributing/code/reproducer.rst
index be6514c842c..771bd69eeac 100644
--- a/contributing/code/reproducer.rst
+++ b/contributing/code/reproducer.rst
@@ -2,11 +2,11 @@ Creating a Bug Reproducer
=========================
The main Symfony code repository receives thousands of issues reports per year.
-Some of those issues are so obvious or easy to understand, that Symfony Core
-developers can fix them without any other information. However, other issues are
-much harder to understand because developers can't easily reproduce them in their
-computers. That's when we'll ask you to create a "bug reproducer", which is the
-minimum amount of code needed to make the bug appear when executed.
+Some of those issues are easy to understand and the Symfony Core developers can
+fix them without any other information. However, other issues are much harder to
+understand because developers can't reproduce them in their computers. That's
+when we'll ask you to create a "bug reproducer", which is the minimum amount of
+code needed to make the bug appear when executed.
Reproducing Simple Bugs
-----------------------
diff --git a/contributing/code/security.rst b/contributing/code/security.rst
index a751763c8c9..e9f4dcdabf1 100644
--- a/contributing/code/security.rst
+++ b/contributing/code/security.rst
@@ -1,9 +1,9 @@
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
--------------------------
@@ -38,7 +38,8 @@ confirmed, the core team works on a solution following these steps:
#. Publish the post on the official Symfony `blog`_ (it must also be added to
the "`Security Advisories`_" category);
#. Update the public `security advisories database`_ maintained by the
- FriendsOfPHP organization and which is used by the ``security:check`` command.
+ FriendsOfPHP organization and which is used by
+ :ref:`the check:security command `.
.. note::
@@ -78,7 +79,7 @@ projects. The process works as follows:
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 seems like a reasonable amount of time.
+ 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
@@ -150,7 +151,7 @@ score for Affected Projects is capped at 4.*
Score Totals
~~~~~~~~~~~~
-* Attack Complexity: 1 - 4
+* Attack Complexity: 1 - 5
* Impact: 1 - 6
* Affected Projects: 1 - 4
@@ -169,15 +170,14 @@ Security Advisories
.. tip::
You can check your Symfony application for known security vulnerabilities
- using the ``security:check`` command (see :doc:`/security/security_checker`).
+ 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
+.. _`Git repository`: https://github.com/symfony/symfony
.. _blog: https://symfony.com/blog/
-.. _Security Advisories: https://symfony.com/blog/category/security-advisories
.. _`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/standards.rst b/contributing/code/standards.rst
index 8b28f2d64a7..e81d195ab71 100644
--- a/contributing/code/standards.rst
+++ b/contributing/code/standards.rst
@@ -27,11 +27,7 @@ Symfony Coding Standards in Detail
----------------------------------
If you want to learn about the Symfony coding standards in detail, here's a
-short example containing most features described below:
-
-.. code-block:: html+php
-
- `.
+ removed per request to the :doc:`core team `.
License
~~~~~~~
@@ -280,7 +276,7 @@ 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.
-.. _`PHP CS Fixer tool`: http://cs.sensiolabs.org/
+.. _`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/
diff --git a/contributing/code/tests.rst b/contributing/code/tests.rst
index 968ad1c95cf..75c72e0128d 100644
--- a/contributing/code/tests.rst
+++ b/contributing/code/tests.rst
@@ -4,11 +4,11 @@ Running Symfony Tests
=====================
The Symfony project uses a third-party service which automatically runs tests
-for any submitted :doc:`patch `. If the new code breaks any test,
+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.
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.
+:doc:`patch ` for inclusion, to check that you have not broken anything.
.. _phpunit:
.. _dependencies_optional:
@@ -18,7 +18,7 @@ Before Running the Tests
To run the Symfony test suite, install the external dependencies used during the
tests, such as Doctrine, Twig and Monolog. To do so,
-:doc:`install Composer ` and execute the following:
+`install Composer`_ and execute the following:
.. code-block:: terminal
@@ -54,6 +54,7 @@ what's going on and if the tests are broken because of the new code.
On Windows, install the `Cmder`_, `ConEmu`_, `ANSICON`_ or `Mintty`_ free applications
to see colored test results.
+.. _`install Composer`: https://getcomposer.org/download/
.. _Cmder: http://cmder.net/
.. _ConEmu: https://conemu.github.io/
.. _ANSICON: https://github.com/adoxa/ansicon/releases
diff --git a/contributing/code_of_conduct/care_team.rst b/contributing/code_of_conduct/care_team.rst
index 04b7fdbad82..b452de35895 100644
--- a/contributing/code_of_conduct/care_team.rst
+++ b/contributing/code_of_conduct/care_team.rst
@@ -4,22 +4,24 @@ CARE Team
Our Pledge
----------
-In the interest of fostering an open and welcoming environment, the CoC Active Response Ensurers, or CARE,
-pledge to ensure that the spirit of the :doc:`Code of Conduct `
+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 CoC
-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.
+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
+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 Code of Conduct CARE team (in alphabetic order).
-You can contact any of them directly using the contact details below or you can
-also contact all of them at once by emailing **coc@symfony.com**:
+Here are all the members of the CARE team (in alphabetic order). You can contact
+any of them directly using the contact details below or you can also contact all
+of them at once by emailing **care@symfony.com**:
* **Emilie Lorenzo**
@@ -46,3 +48,12 @@ The :doc:`Symfony project leader ` appoints the CA
team with candidates they see fit. The CARE team will consist of at least
3 people. The team should be representing as many demographics as possible,
ideally from different employers.
+
+CARE Team Transparency Reports
+------------------------------
+
+The CARE team publishes a transparency report at the end of each year:
+
+* `Symfony Code of Conduct Transparency Report 2018`_.
+
+.. _`Symfony Code of Conduct Transparency Report 2018`: https://symfony.com/blog/symfony-code-of-conduct-transparency-report-2018
diff --git a/contributing/code_of_conduct/concrete_example_document.rst b/contributing/code_of_conduct/concrete_example_document.rst
index 6f1277a625a..ddd1c9b84c8 100644
--- a/contributing/code_of_conduct/concrete_example_document.rst
+++ b/contributing/code_of_conduct/concrete_example_document.rst
@@ -2,7 +2,7 @@ Code of Conduct: Concrete Example Document
==========================================
This is a living document that serves to give concrete examples of
-unwanted behaviour. These examples have all taken place somewhere in the
+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.
diff --git a/contributing/code_of_conduct/reporting_guidelines.rst b/contributing/code_of_conduct/reporting_guidelines.rst
index 1766d025e4d..63c4e820ce6 100644
--- a/contributing/code_of_conduct/reporting_guidelines.rst
+++ b/contributing/code_of_conduct/reporting_guidelines.rst
@@ -69,7 +69,7 @@ let them know what action (if any) we'll be taking. We'll take into account feed
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
+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
diff --git a/contributing/community/index.rst b/contributing/community/index.rst
index 70cb60c740e..4a5aab91265 100644
--- a/contributing/community/index.rst
+++ b/contributing/community/index.rst
@@ -9,4 +9,3 @@ Community
reviews
mentoring
speaker-mentoring
- other
diff --git a/contributing/community/other.rst b/contributing/community/other.rst
deleted file mode 100644
index 2196ccb925c..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: https://github.com/search?q=topic%3Asymfony-bundle&type=Repositories
diff --git a/contributing/community/releases.rst b/contributing/community/releases.rst
index 74e6d963d3b..d2336e21e86 100644
--- a/contributing/community/releases.rst
+++ b/contributing/community/releases.rst
@@ -8,13 +8,13 @@ Symfony releases follow the `semantic versioning`_ strategy and they are
published through a *time-based model*:
* A new **Symfony patch version** (e.g. 2.8.15, 4.1.7) comes out roughly every
- month. It only contains bug fixes, so you can safely upgrade your apps;
+ month. It only contains bug fixes, so you can safely upgrade your applications;
* A new **Symfony minor version** (e.g. 2.8, 3.2, 4.1) comes out every *six months*:
one in *May* and one in *November*. It contains bug fixes and new features, but
- it doesn't include any breaking change, so you can safely upgrade your apps;
+ it doesn't include any breaking change, so you can safely upgrade your applications;
* A new **Symfony major version** (e.g. 3.0, 4.0) comes out every *two years*.
It can contain breaking changes, so you may need to do some changes in your
- apps before upgrading.
+ applications before upgrading.
.. tip::
@@ -84,6 +84,23 @@ adds a new preferred one along side. Read the
:ref:`conventions ` document to
learn more about how deprecations are handled in Symfony.
+.. _major-version-development:
+
+This deprecation policy also requires a custom development process for major
+versions (4.0, 5.0, 6.0, etc.) In those cases, Symfony develops at the same time
+two versions: the new major one (e.g. 4.0) and the latest version of the
+previous branch (e.g. 3.4).
+
+Both versions have the same new features, but they differ in the deprecated
+features. The oldest version (3.4 in this example) contains all the deprecated
+features whereas the new version (4.0 in this example) removes all of them.
+
+This allows you to upgrade your projects to the latest minor version (e.g. 3.4),
+see all the deprecation messages and fix them. Once you have fixed all those
+deprecations, you can upgrade to the new major version (e.g. 4.0) without
+effort, because it contains the same features (the only difference are the
+deprecated features, which your project no longer uses).
+
Rationale
---------
diff --git a/contributing/community/review-comments.rst b/contributing/community/review-comments.rst
index fa1f26dcf2d..cb5b1c7084f 100644
--- a/contributing/community/review-comments.rst
+++ b/contributing/community/review-comments.rst
@@ -38,7 +38,7 @@ 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. You're angry, we understand
+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.
@@ -91,34 +91,34 @@ Don't use hyperbole ("always", "never", "endlessly", "nothing", "worst", "horrib
**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 many nested if statements, can you make it more
-readable? By encapsulating some of it's details or maybe adding some comments to explain the overall logic."* -
+**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``.
+* "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 scalar type-hints.``.
+* "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
+* "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.
+* "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.
+* "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)
+* "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()``)
+* "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)
+* "We only provide integration with very popular projects (e.g. we integrate Bootstrap but not your own CSS framework)"
- * This would require adding lots of code and making lots of changes for a feature that doesn't look so important.
- That could hurt maintaining in the future.
+* "This would require adding lots of code and making lots of changes for a feature that doesn't look so important.
+ That could hurt maintaining in the future."
Asking for Changes
------------------
@@ -149,6 +149,17 @@ 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 the common goals.
+
Using Humor
-----------
@@ -176,3 +187,5 @@ 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
index 2d4a5f125fa..06cc404d821 100644
--- a/contributing/community/reviews.rst
+++ b/contributing/community/reviews.rst
@@ -6,6 +6,13 @@ 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
--------------------------
@@ -17,7 +24,7 @@ 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 *easily* reproduced?
+ 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
@@ -38,7 +45,7 @@ next step.
Create a GitHub Account
-----------------------
-Symfony uses GitHub_ to manage bug reports and pull requests. If you want to
+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
@@ -51,16 +58,16 @@ The steps for the review are:
#. **Is the Report Complete?**
- Good bug reports contain a link to a fork of the `Symfony Standard Edition`_
- (the "reproduction project") that reproduces the bug. If it doesn't, the
- report should at least contain enough information and code samples to
- reproduce the bug.
+ Good bug reports contain a link to a project (the "reproduction project")
+ created with the `Symfony skeleton`_ or the `Symfony website skeleton`_
+ that reproduces the bug. If it doesn't, the report should at least contain
+ enough information and code samples to reproduce the bug.
#. **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 by forking_ the `Symfony Standard Edition`_.
+ create one based on one `Symfony skeleton`_ (or the `Symfony website skeleton`_).
#. **Update the Issue Status**
@@ -89,7 +96,7 @@ The steps for the review are:
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/symfony-standard.
+ https://github.com/webmozart/some-project.
Status: Reviewed
@@ -127,9 +134,9 @@ Pick a pull request from the `PRs in need of review`_ and follow these steps:
#. **Reproduce the Problem**
Read the issue that the pull request is supposed to fix. Reproduce the
- problem on a clean `Symfony Standard Edition`_ project and try to understand
- why it exists. If the linked issue already contains such a project, install
- it and run it on your system.
+ problem on a new project created with the `Symfony skeleton`_ (or the
+ `Symfony website skeleton`_) and try to understand why it exists. If the
+ linked issue already contains such a project, install it and run it on your system.
#. **Review the Code**
@@ -139,7 +146,7 @@ Pick a pull request from the `PRs in need of review`_ and follow these steps:
* 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 easily understand its code?
+ * 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
@@ -204,13 +211,11 @@ Pick a pull request from the `PRs in need of review`_ and follow these steps:
.. _GitHub: https://github.com
.. _Symfony issue tracker: https://github.com/symfony/symfony/issues
-.. _Symfony Standard Edition: https://github.com/symfony/symfony-standard
+.. _`Symfony skeleton`: https://github.com/symfony/skeleton
+.. _`Symfony website skeleton`: https://github.com/symfony/website-skeleton
.. _create a GitHub account: https://help.github.com/articles/signing-up-for-a-new-github-account/
-.. _forking: https://help.github.com/articles/fork-a-repo/
.. _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/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Apr+label%3A%22Status%3A+Needs+Review%22+
-.. _Contribution Guidelines: https://github.com/symfony/symfony/blob/master/CONTRIBUTING.md
-.. _Symfony's Release Schedule: https://symfony.com/doc/current/contributing/community/releases.html#schedule
.. _Symfony Roadmap: https://symfony.com/roadmap
.. _Carson Bot: https://github.com/carsonbot/carsonbot
.. _`Needs Review`: https://github.com/symfony/symfony/labels/Status%3A%20Needs%20Review
diff --git a/contributing/diversity/governance.rst b/contributing/diversity/governance.rst
index 0a58e564669..7e27f139b05 100644
--- a/contributing/diversity/governance.rst
+++ b/contributing/diversity/governance.rst
@@ -13,9 +13,9 @@ Guidance
--------
The project leader, Fabien Potencier, is responsible for publicly appointing
-five (5) key players of the initiative to provide guidance and drive it forward,
-but also retains the right to revoke any of the key players at any time. These
-guiding key players should:
+five (5) members of the initiative to provide guidance and drive it forward,
+but also retains the right to revoke any of the appointed members at any time.
+This guidance team should:
* Be committed to the initiative's cause and have joined because they want to
help the initiative to deliver its purpose most effectively for the
@@ -24,21 +24,30 @@ guiding key players should:
* Be committed to good governance and want to contribute to the initiative's
continued improvement.
+The current guidance team is composed of the following people (in alphabetical
+order):
+
+* **Lukas Kahwe Smith** (`lsmith77`_);
+* **Michelle Sanver** (`michellesanver`_);
+* **Nicolas Grekas** (`nicolas-grekas`_);
+* **Timo Bakx** (`TimoBakx`_);
+* **Zan Baldwin** (`zanbaldwin`_).
+
Veto
~~~~
The project leader (Fabien Potencier) will have the right to veto any actionable
-item, regardless of the vote of the initiative's key players. The project leader
-may, at their discretion, also appoint other people from among the initiative's
-key players to also have the right to veto - in such a case these people are
-expected to use appropriate judgement to know when to use a "no" vote or a veto.
-Any single veto will reject an actionable item.
+item, regardless of the vote of the initiative's guidance team. The project
+leader may, at their discretion, also appoint other people from among the
+initiative's guidance team to also have the right to veto - in such a case these
+people are expected to use appropriate judgement to know when to use a "no" vote
+or a veto. Any single veto will reject an actionable item.
The purpose of having members with the right to veto is to prevent a "people's
majority" from overruling the core interests of the Symfony project. This will
-encourage communication between proposing members, the initiative's key players
-and the Core Team to create realistic proposals, and in return any veto will
-come with a full explanation (not just a justification).
+encourage communication between proposing members, the initiative's guidance
+team and the Core Team to create realistic proposals, and in return any veto
+will come with a full explanation (not just a justification).
Advice Process
~~~~~~~~~~~~~~
@@ -48,20 +57,37 @@ the community (advice, general consensus, or non-binding poll) should be
requested from the wider community - this will aim to include both those who
will be meaningfully affected and those with meaningful expertise in the matter
at hand.
-This feedback will enable the key players to have the confidence to vote for the
-best possible decision according to the information they have available, knowing
-that the responsibility they accept for said vote is justified.
+This feedback will enable the guidance team to have the confidence to vote for
+the best possible decision according to the information they have available,
+knowing that the responsibility they accept for said vote is justified.
Voting
~~~~~~
-Key players have the right to vote on proposals for actionable items.
+The guidance team have the right to vote on proposals for actionable items.
The quorum of "yes" or "no" votes required for a decision to be considered valid
-is at least 75% of active, appointed key players - to abstain from voting means
-that vote will not be counted towards the quorum.
-For an actionable item to pass, approval from greater than 50% of the voting key
-players is required. Use or management of finances/donations require at least a
-two-thirds majority to pass.
+is at least 75% of active, appointed members of the guidance team - to abstain
+from voting means that vote will not be counted towards the quorum.
+For an actionable item to pass, approval from greater than 50% of the voting
+guidance team members is required. Use or management of finances/donations
+require at least a two-thirds majority to pass.
+
+For transparency and ease-of-understanding, this means only the following
+combinations of votes will result in an actionable item passing:
+
++-----+---------+---------+
+| For | Against | Abstain |
++=====+=========+=========+
+| 5 | 0 | 0 |
++-----+---------+---------+
+| 4 | 1 | 0 |
++-----+---------+---------+
+| 3 | 2 | 0 |
++-----+---------+---------+
+| 4 | 0 | 1 |
++-----+---------+---------+
+| 3 | 1 | 1 |
++-----+---------+---------+
Guidance Principles
-------------------
@@ -69,37 +95,37 @@ Guidance Principles
Purpose
~~~~~~~
-The initiative should be led by an effective team of key players that provide
+The initiative should be led by an effective guidance team that provides
strategic guidance in line with the initiative's aims and values, including a
-shared understanding with fellow key players to ensure that these are being
-delivered effectively and sustainably.
+shared understanding with fellow initiative members to ensure that these are
+being delivered effectively and sustainably.
Integrity
~~~~~~~~~
-Key players should act with integrity: adopting values which help achieve the
-initiative's purposes, even where difficult or unpopular decisions are required.
-Key players should undertake their duties, aware of the importance of confidence
-and trust in the initiative from the wider community, and ultimately
-acknowledges shared responsibility for the reputation of the Symfony project
-like the Core Team.
+The guidance team should act with integrity: adopting values which help achieve
+the initiative's purposes, even where difficult or unpopular decisions are
+required. Guidance team members should undertake their duties, aware of the
+importance of confidence and trust in the initiative from the wider community,
+and ultimately acknowledges shared responsibility for the reputation of the
+Symfony project like the Core Team.
Decision-making Effectiveness
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Key players should work as an effective team, using the appropriate balance of
-skills, experience, backgrounds and knowledge to make sure its decision-making
-processes are informed and equitable. Risk assessment and management systems
-should be set up and monitored.
+Guidance members should work as an effective team, using the appropriate balance
+of skills, experience, backgrounds and knowledge to make sure its
+decision-making processes are informed and equitable. Risk assessment and
+management systems should be set up and monitored.
Openness and Accountability
~~~~~~~~~~~~~~~~~~~~~~~~~~~
-The behaviour and conduct of the initiative's key players sets the tone for the
-rest of the community. Key players should lead by example to create a culture
-that enables members to feel it is safe to suggest, question and challenge -
-rather than avoid - difficult ideas and topics. Key players guide the initiative
-in being transparent, accountable and open.
+The behavior and conduct of the initiative's guidance team sets the tone for
+the rest of the community. The guidance team should lead by example to create a
+culture that enables members to feel it is safe to suggest, question and
+challenge - rather than avoid - difficult ideas and topics. The team should
+guide the initiative in being transparent, accountable and open.
Adaptability
~~~~~~~~~~~~
@@ -107,5 +133,11 @@ Adaptability
The initiative should establish processes that do not require any one person to
hold specific positions while being adaptable to accommodate unforeseen needs of
the community, especially as membership and involvement grows over time (changes
-to key player appointment will have to be approved by the current system, which
-is Fabien Potencier).
+to guidance team member appointment will have to be approved by the current
+system, which is Fabien Potencier).
+
+.. _`lsmith77`: https://github.com/lsmith77/
+.. _`michellesanver`: https://github.com/michellesanver/
+.. _`nicolas-grekas`: https://github.com/nicolas-grekas/
+.. _`TimoBakx`: https://github.com/TimoBakx/
+.. _`zanbaldwin`: https://github.com/zanbaldwin/
diff --git a/contributing/documentation/format.rst b/contributing/documentation/format.rst
index 2464b5b9e66..d7d19a193c3 100644
--- a/contributing/documentation/format.rst
+++ b/contributing/documentation/format.rst
@@ -131,7 +131,7 @@ If you want to modify that title, use this alternative syntax:
.. code-block:: rst
- :doc:`Spooling Email `
+ :doc:`Doctrine Associations `
.. note::
@@ -168,42 +168,51 @@ of the linked resource (``namespace``, ``class`` or ``method``):
:phpfunction:`iterator_to_array`
-New Features or Behavior Changes
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+New Features, Behavior Changes or Deprecations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-If you're documenting a brand new feature or a change that's been made in
-Symfony, you should precede your description of the change with a
-``.. versionadded:: 2.X`` directive and a short description:
+If you are documenting a brand new feature, a change or a deprecation that's
+been made in Symfony, you should precede your description of the change with
+the corresponding directive and a short description:
+
+For a new feature or a behavior change use the ``.. versionadded:: 4.x``
+directive:
.. code-block:: rst
- .. versionadded:: 2.7
+ .. versionadded:: 4.2
+
+ Named autowiring aliases have been introduced in Symfony 4.2.
+
+If you are documenting a behavior change, it may be helpful to *briefly*
+describe how the behavior has changed:
+
+.. code-block:: rst
- The ``askHiddenResponse()`` method was introduced in Symfony 2.7.
+ .. versionadded:: 4.2
- You can also ask a question and hide the response. This is particularly [...]
+ Support for ICU MessageFormat was introduced in Symfony 4.2. Prior to this,
+ pluralization was managed by the ``transChoice`` method.
-If you're documenting a behavior change, it may be helpful to *briefly* describe
-how the behavior has changed:
+For a deprecation use the ``.. deprecated:: 4.x`` directive:
.. code-block:: rst
- .. versionadded:: 2.7
+ .. deprecated:: 4.2
- The ``include()`` function is a new Twig feature that's available in
- Symfony 2.7. Prior, the ``{% include %}`` tag was used.
+ Not passing the root node name to ``TreeBuilder`` was deprecated in Symfony 4.2.
-Whenever a new minor version of Symfony is released (e.g. 2.4, 2.5, etc),
+Whenever a new major version of Symfony is released (e.g. 5.0, 6.0, etc),
a new branch of the documentation is created from the ``master`` branch.
-At this point, all the ``versionadded`` tags for Symfony versions that have
-reached end-of-maintenance will be removed. For example, if Symfony 2.5 were
-released today, and 2.2 had recently reached its end-of-maintenance, the 2.2
-``versionadded`` tags would be removed from the new ``2.5`` branch.
+At this point, all the ``versionadded`` and ``deprecated`` tags for Symfony
+versions that have a lower major version will be removed. For example, if
+Symfony 5.0 were released today, 4.0 to 4.4 ``versionadded`` and ``deprecated``
+tags would be removed from the new ``5.0`` branch.
.. _reStructuredText: http://docutils.sourceforge.net/rst.html
-.. _Sphinx: http://sphinx-doc.org/
+.. _Sphinx: https://www.sphinx-doc.org/
.. _`Symfony documentation`: https://github.com/symfony/symfony-docs
-.. _`reStructuredText Primer`: http://sphinx-doc.org/rest.html
+.. _`reStructuredText Primer`: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
.. _`reStructuredText Reference`: http://docutils.sourceforge.net/docs/user/rst/quickref.html
-.. _`Sphinx Markup Constructs`: http://sphinx-doc.org/markup/
+.. _`Sphinx Markup Constructs`: https://www.sphinx-doc.org/en/1.7/markup/index.html
.. _`supported languages`: http://pygments.org/languages/
diff --git a/contributing/documentation/index.rst b/contributing/documentation/index.rst
index d4ccfe74310..f16f4e32cc7 100644
--- a/contributing/documentation/index.rst
+++ b/contributing/documentation/index.rst
@@ -5,21 +5,21 @@ These short articles explain everything you need to contribute to the Symfony
documentation:
:doc:`The Contribution Process `
- Explains the steps to follow to contribute fixes and new contents. It's the
- same contribution process followed by most open source projects, so you may
- already know everything that is needed.
+ Explains the steps to follow to contribute fixes and new contents. It's the
+ same contribution process followed by most open source projects, so you may
+ already know everything that is needed.
:doc:`Documentation Formats `
- Explains the technical details of the reStructuredText format that is used to
- write the docs. Skip it if you are already familiar with this format.
+ Explains the technical details of the reStructuredText format that is used to
+ write the docs. Skip it if you are already familiar with this format.
:doc:`Documentation Standards `
- Explains how to write docs and code examples to match the style and tone of
- the rest of the existing documentation.
+ Explains how to write docs and code examples to match the style and tone of
+ the rest of the existing documentation.
:doc:`License `
- Explains the details of the Creative Commons BY-SA 3.0 license used for the
- Symfony Documentation.
+ Explains the details of the Creative Commons BY-SA 3.0 license used for the
+ Symfony Documentation.
.. toctree::
:hidden:
diff --git a/contributing/documentation/overview.rst b/contributing/documentation/overview.rst
index e0fd5eac618..19dd6d030ce 100644
--- a/contributing/documentation/overview.rst
+++ b/contributing/documentation/overview.rst
@@ -65,9 +65,9 @@ Let's imagine that you want to improve the Setup guide. In order to make your
changes, follow these steps:
**Step 1.** Go to the official Symfony documentation repository located at
-`github.com/symfony/symfony-docs`_ and click on the **Fork** button to `fork the
-repository`_ to your personal account. This is only needed the first time you
-contribute to Symfony.
+`github.com/symfony/symfony-docs`_ and click on the **Fork** button to
+`fork the repository`_ to your personal account. This is only needed the first
+time you contribute to Symfony.
**Step 2.** **Clone** the forked repository to your local machine (this example
uses the ``projects/symfony-docs/`` directory to store the documentation; change
@@ -249,8 +249,22 @@ GitHub, click on the **Show all checks** link and finally, click on the
Build the Documentation Locally
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Alternatively you can build the documentation on your own computer for testing
-purposes following these steps:
+If you have Docker installed on your machine, run these commands to build the
+docs:
+
+.. code-block:: terminal
+
+ # build the image...
+ $ docker build . -t symfony-docs
+
+ # ...and start the local web server
+ # (if it's already in use, change the '8080' port by any other port)
+ $ docker run --rm -p 8080:80 symfony-docs
+
+You can now read the docs at ``http://127.0.0.1:8080`` (if you use a virtual
+machine, browse its IP instead of localhost; e.g. ``http://192.168.99.100:8080``).
+
+If you don't use Docker, follow these steps to build the docs locally:
#. Install `pip`_ as explained in the `pip installation`_ article;
@@ -325,7 +339,6 @@ definitely don't want you to waste your time!
.. _`Symfony Documentation Contributors`: https://symfony.com/contributors/doc
.. _`SymfonyConnect`: https://connect.symfony.com/
.. _`Symfony Documentation Badge`: https://connect.symfony.com/badge/36/symfony-documentation-contributor
-.. _`sync your fork`: https://help.github.com/articles/syncing-a-fork
.. _`SymfonyCloud`: https://symfony.com/cloud
.. _`roadmap`: https://symfony.com/roadmap
.. _`pip`: https://pip.pypa.io/en/stable/
diff --git a/contributing/documentation/standards.rst b/contributing/documentation/standards.rst
index 5fc84d4b5fa..0cfad8880ed 100644
--- a/contributing/documentation/standards.rst
+++ b/contributing/documentation/standards.rst
@@ -135,7 +135,7 @@ Files and Directories
* When referencing file extensions explicitly, you should include a leading dot
for every extension (e.g. "XML files use the ``.xml`` extension").
* When you list a Symfony file/directory hierarchy, use ``your-project/`` as the
- top level directory. E.g.
+ top-level directory. E.g.
.. code-block:: text
@@ -173,6 +173,24 @@ In addition, documentation follows these rules:
* his or hers, use theirs
* himself or herself, use themselves
+* **Avoid belittling words**: People read documentation because they know very
+ little about a specific topic or are even completely new to it. Things that
+ seem "obvious" or "simple" for the person documenting it, can be the exact
+ opposite for the reader. To make sure everybody feels comfortable when reading
+ the documentation, try to avoid words like:
+
+ * basically
+ * clearly
+ * easy/easily
+ * just
+ * logically
+ * merely
+ * obviously
+ * of course
+ * quick/quickly
+ * simply
+ * trivial
+
.. _`the Sphinx documentation`: http://sphinx-doc.org/rest.html#source-code
.. _`Twig Coding Standards`: https://twig.symfony.com/doc/2.x/coding_standards.html
.. _`reserved by the IANA`: http://tools.ietf.org/html/rfc2606#section-3
diff --git a/contributing/map.rst.inc b/contributing/map.rst.inc
index a0cdd23cdd0..b9c9311a6cb 100644
--- a/contributing/map.rst.inc
+++ b/contributing/map.rst.inc
@@ -8,8 +8,8 @@
* **Code**
* :doc:`Bugs `
- * :doc:`Patches `
- * :doc:`Reviewing Issues and Patches `
+ * :doc:`Pull Requests `
+ * :doc:`Reviewing Issues and Pull Requests `
* :doc:`Maintenance `
* :doc:`The Core Team `
* :doc:`Security `
@@ -32,7 +32,6 @@
* :doc:`Release Process `
* :doc:`Respectful Review comments `
* :doc:`Community Reviews `
- * :doc:`Other Resources `
* **Diversity**
diff --git a/controller.rst b/controller.rst
index bd15d13a7eb..2513b5d3faf 100644
--- a/controller.rst
+++ b/controller.rst
@@ -7,8 +7,8 @@ Controller
A controller is a PHP function you create that reads information from the
``Request`` object and creates and returns a ``Response`` object. The response could
be an HTML page, JSON, XML, a file download, a redirect, a 404 error or anything
-else you can dream up. The controller executes whatever arbitrary logic
-*your application* needs to render the content of a page.
+else. The controller executes whatever arbitrary logic *your application* needs
+to render the content of a page.
.. tip::
@@ -21,7 +21,7 @@ else you can dream up. The controller executes whatever arbitrary logic
A Simple Controller
-------------------
-While a controller can be any PHP callable (a function, method on an object,
+While a controller can be any PHP callable (function, method on an object,
or a ``Closure``), a controller is usually a method inside a controller
class::
@@ -46,7 +46,7 @@ class::
}
}
-The controller is the ``number()`` method, which lives inside a
+The controller is the ``number()`` method, which lives inside the
controller class ``LuckyController``.
This controller is pretty straightforward:
@@ -91,9 +91,9 @@ For more information on routing, see :doc:`/routing`.
The Base Controller Class & Services
------------------------------------
-To make life nicer, Symfony comes with an optional base controller class called
+To aid development, Symfony comes with an optional base controller class called
:class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController`.
-You can extend it to get access to some `helper methods`_.
+It can be extended to gain access to helper methods.
Add the ``use`` statement atop your controller class and then modify
``LuckyController`` to extend it:
@@ -125,6 +125,8 @@ method is just a helper method that generates the URL for a given route::
$url = $this->generateUrl('app_lucky_number', ['max' => 10]);
+.. _controller-redirect:
+
Redirecting
~~~~~~~~~~~
@@ -195,7 +197,7 @@ any other "work" you can think of.
If you need a service in a controller, type-hint an argument with its class
(or interface) name. Symfony will automatically pass you the service you need::
- use Psr\Log\LoggerInterface
+ use Psr\Log\LoggerInterface;
// ...
/**
@@ -243,7 +245,7 @@ the argument by its name:
+ https://symfony.com/schema/dic/services/services-1.0.xsd">
@@ -276,10 +278,6 @@ the argument by its name:
Like with all services, you can also use regular :ref:`constructor injection `
in your controllers.
-.. versionadded:: 4.1
- The ability to bind scalar values to controller arguments was introduced in
- Symfony 4.1. Previously you could only bind services.
-
For more information about services, see the :doc:`/service_container` article.
Generating Controllers
@@ -293,6 +291,7 @@ new controller class:
$ php bin/console make:controller BrandNewController
created: src/Controller/BrandNewController.php
+ created: templates/brandnew/index.html.twig
If you want to generate an entire CRUD from a Doctrine :doc:`entity `,
use:
@@ -301,7 +300,17 @@ use:
$ php bin/console make:crud Product
+ created: src/Controller/ProductController.php
+ created: src/Form/ProductType.php
+ created: templates/product/_delete_form.html.twig
+ created: templates/product/_form.html.twig
+ created: templates/product/edit.html.twig
+ created: templates/product/index.html.twig
+ created: templates/product/new.html.twig
+ created: templates/product/show.html.twig
+
.. versionadded:: 1.2
+
The ``make:crud`` command was introduced in MakerBundle 1.2.
.. index::
@@ -357,8 +366,8 @@ The Request object as a Controller Argument
-------------------------------------------
What if you need to read query parameters, grab a request header or get access
-to an uploaded file? All of that information is stored in Symfony's ``Request``
-object. To get it in your controller, add it as an argument and
+to an uploaded file? That information is stored in Symfony's ``Request``
+object. To access it in your controller, add it as an argument and
**type-hint it with the Request class**::
use Symfony\Component\HttpFoundation\Request;
@@ -409,16 +418,13 @@ To get the session, add an argument and type-hint it with
Stored attributes remain in the session for the remainder of that user's session.
-.. tip::
-
- Every ``SessionInterface`` implementation is supported. If you have your
- own implementation, type-hint this in the argument instead.
-
For more info, see :doc:`/session`.
.. index::
single: Session; Flash messages
+.. _flash-messages:
+
Flash Messages
~~~~~~~~~~~~~~
@@ -461,14 +467,23 @@ read any flash messages from the session using ``app.flashes()``:
{# templates/base.html.twig #}
- {# you can read and display just one flash message type... #}
+ {# read and display just one flash message type #}
{% for message in app.flashes('notice') %}
{{ message }}
{% endfor %}
- {# ...or you can read and display every flash message available #}
+ {# read and display several types of flash messages #}
+ {% for label, messages in app.flashes(['success', 'warning']) %}
+ {% for message in messages %}
+
+ {{ message }}
+
+ {% endfor %}
+ {% endfor %}
+
+ {# read and display all flash messages #}
{% for label, messages in app.flashes %}
{% for message in messages %}
@@ -528,13 +543,13 @@ the ``Request`` class::
The ``Request`` class has several public properties and methods that return any
information you need about the request.
-Like the ``Request``, the ``Response`` object has also a public ``headers`` property.
-This is a :class:`Symfony\\Component\\HttpFoundation\\ResponseHeaderBag` that has
-some nice methods for getting and setting response headers. The header names are
-normalized so that using ``Content-Type`` is equivalent to ``content-type`` or even
-``content_type``.
+Like the ``Request``, the ``Response`` object has a public ``headers`` property.
+This object is of the type :class:`Symfony\\Component\\HttpFoundation\\ResponseHeaderBag`
+and provides methods for getting and setting response headers. The header names are
+normalized. As a result, the name ``Content-Type`` is equivalent to
+the name ``content-type`` or ``content_type``.
-The only requirement for a controller is to return a ``Response`` object::
+In Symfony, a controller is required to return a ``Response`` object::
use Symfony\Component\HttpFoundation\Response;
@@ -545,15 +560,29 @@ The only requirement for a controller is to return a ``Response`` object::
$response = new Response('');
$response->headers->set('Content-Type', 'text/css');
-There are special classes that make certain kinds of responses easier. Some of these
-are mentioned below. To learn more about the ``Request`` and ``Response`` (and special
-``Response`` classes), see the :ref:`HttpFoundation component documentation
`.
+To facilitate this, different response objects are included to address different
+response types. Some of these are mentioned below. To learn more about the
+``Request`` and ``Response`` (and different ``Response`` classes), see the
+:ref:`HttpFoundation component documentation `.
+
+Accessing Configuration Values
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To get the value of any :ref:`configuration parameter `
+from a controller, use the ``getParameter()`` helper method::
+
+ // ...
+ public function index()
+ {
+ $contentsDir = $this->getParameter('kernel.project_dir').'/contents';
+ // ...
+ }
Returning JSON Response
~~~~~~~~~~~~~~~~~~~~~~~
To return JSON from a controller, use the ``json()`` helper method. This returns a
-special ``JsonResponse`` object that encodes the data automatically::
+``JsonResponse`` object that encodes the data automatically::
// ...
public function index()
@@ -603,13 +632,16 @@ The ``file()`` helper provides some arguments to configure its behavior::
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 where you can do anything in order to return the
-final ``Response`` object that will be returned to the user.
+In Symfony, a controller is usually a class method which is used to accept
+requests, and return a ``Response`` object. When mapped with a URL, a controller
+becomes accessible and its response can be viewed.
-To make life easier, you'll probably extend the base ``AbstractController`` class because
-this gives access to shortcut methods (like ``render()`` and ``redirectToRoute()``).
+To facilitate the development of controllers, Symfony provides an
+``AbstractController``. It can be used to extend the controller class allowing
+access to some frequently used utilities such as ``render()`` and
+``redirectToRoute()``. The ``AbstractController`` also provides the
+``createNotFoundException()`` utility which is used to return a page not found
+response.
In other articles, you'll learn how to use specific services from inside your controller
that will help you persist and fetch objects from a database, process form submissions,
@@ -634,6 +666,5 @@ Learn more about Controllers
controller/*
-.. _`helper methods`: https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php
.. _`Symfony Maker`: https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html
.. _`unvalidated redirects security vulnerability`: https://www.owasp.org/index.php/Open_redirect
diff --git a/controller/argument_value_resolver.rst b/controller/argument_value_resolver.rst
index 5765cfb7469..0ac95a12ad6 100644
--- a/controller/argument_value_resolver.rst
+++ b/controller/argument_value_resolver.rst
@@ -12,10 +12,13 @@ in order to be recognized. This is done via the
creating and registering custom argument value resolvers, you can extend this
functionality.
-Functionality Shipped with the HttpKernel
------------------------------------------
+.. _functionality-shipped-with-the-httpkernel:
-Symfony ships with five value resolvers in the HttpKernel component:
+Built-In Value Resolvers
+------------------------
+
+Symfony ships with the following value resolvers in the
+:doc:`HttpKernel component `:
:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\RequestAttributeValueResolver`
Attempts to find a request attribute that matches the name of the argument.
@@ -42,6 +45,30 @@ Symfony ships with five value resolvers in the HttpKernel component:
argument list. When the action is called, the last (variadic) argument will
contain all the values of this array.
+In addition, some components and official bundles provide other value resolvers:
+
+:class:`Symfony\\Component\\Security\\Http\\Controller\\UserValueResolver`
+ Injects the object that represents the current logged in user if type-hinted
+ with ``UserInterface``. Default value can be set to ``null`` in case
+ the controller can be accessed by anonymous users. It requires installing
+ the :doc:`Security component `.
+
+:class:`Symfony\\Bundle\\SecurityBundle\\SecurityUserValueResolver`
+ Injects the object that represents the current logged in user if type-hinted
+ with ``UserInterface``. Default value can be set to ``null`` in case
+ the controller can be accessed by anonymous users. It requires installing
+ the `SecurityBundle`_.
+
+.. deprecated:: 4.1
+
+ The ``SecurityUserValueResolver`` was deprecated in Symfony 4.1 in favor of
+ :class:`Symfony\\Component\\Security\\Http\\Controller\\UserValueResolver`.
+
+``Psr7ServerRequestResolver``
+ Injects a `PSR-7`_ compliant version of the current request if type-hinted
+ with ``RequestInterface``, ``MessageInterface`` or ``ServerRequestInterface``.
+ It requires installing the `SensioFrameworkExtraBundle`_.
+
Adding a Custom Value Resolver
------------------------------
@@ -85,10 +112,10 @@ type-hinted method arguments:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:sensio-framework-extra="http://symfony.com/schema/dic/symfony_extra"
xsi:schemaLocation="http://symfony.com/schema/dic/services
- http://symfony.com/schema/dic/services/services-1.0.xsd">
+ https://symfony.com/schema/dic/services/services-1.0.xsd">
-
+
@@ -190,15 +217,15 @@ and adding a priority.
+ xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
-
+
-
+
@@ -233,3 +260,6 @@ subrequests.
.. _`@ParamConverter`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
.. _`yield`: http://php.net/manual/en/language.generators.syntax.php
+.. _`SecurityBundle`: https://github.com/symfony/security-bundle
+.. _`PSR-7`: https://www.php-fig.org/psr/psr-7/
+.. _`SensioFrameworkExtraBundle`: https://github.com/sensiolabs/SensioFrameworkExtraBundle
diff --git a/controller/error_pages.rst b/controller/error_pages.rst
index 7fa444003be..a4026b75a66 100644
--- a/controller/error_pages.rst
+++ b/controller/error_pages.rst
@@ -17,7 +17,7 @@ customize error pages, so run this command to make sure the bundle is installed:
$ composer require twig
-In the :doc:`development environment `,
+In the :ref:`development environment `,
Symfony catches all the exceptions and displays a special **exception page**
with lots of debug information to help you discover the root problem:
@@ -150,7 +150,7 @@ Fortunately, the default ``ExceptionController`` allows you to preview your
*error* pages during development.
To use this feature, you need to load some special routes provided by TwigBundle
-(if the application uses :doc:`Symfony Flex ` they are loaded
+(if the application uses :ref:`Symfony Flex ` they are loaded
automatically when installing Twig support):
.. configuration-block::
@@ -169,24 +169,21 @@ automatically when installing Twig support):
+ https://symfony.com/schema/routing/routing-1.0.xsd">
-
+
.. code-block:: php
// config/routes/dev/twig.php
- use Symfony\Component\Routing\RouteCollection;
+ use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
- $routes = new RouteCollection();
- $routes->addCollection(
- $loader->import('@TwigBundle/Resources/config/routing/errors.xml')
- );
- $routes->addPrefix("/_error");
-
- return $routes;
+ return function (RoutingConfigurator $routes) {
+ $routes->import('@TwigBundle/Resources/config/routing/errors.xml')
+ ->prefix('/_error')
+ ;
+ };
With this route added, you can use URLs like these to preview the *error* page
for a given status code as HTML or for a given status code and format.
@@ -216,7 +213,7 @@ configuration option to point to it:
# config/packages/twig.yaml
twig:
- exception_controller: App\Controller\ExceptionController::showException
+ exception_controller: App\Controller\ExceptionController::showAction
.. code-block:: xml
@@ -226,12 +223,12 @@ configuration option to point to it:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:twig="http://symfony.com/schema/dic/twig"
xsi:schemaLocation="http://symfony.com/schema/dic/services
- http://symfony.com/schema/dic/services/services-1.0.xsd
+ https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/twig
- http://symfony.com/schema/dic/twig/twig-1.0.xsd">
+ https://symfony.com/schema/dic/twig/twig-1.0.xsd">
- App\Controller\ExceptionController::showException
+ App\Controller\ExceptionController::showAction
@@ -240,7 +237,7 @@ configuration option to point to it:
// config/packages/twig.php
$container->loadFromExtension('twig', [
- 'exception_controller' => 'App\Controller\ExceptionController::showException',
+ 'exception_controller' => 'App\Controller\ExceptionController::showAction',
// ...
]);
@@ -292,11 +289,11 @@ In that case, you might want to override one or both of the ``showAction()`` and
+ https://symfony.com/schema/dic/services/services-1.0.xsd">
-
+
@@ -342,7 +339,7 @@ error pages.
.. note::
If your listener calls ``setResponse()`` on the
- :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent`,
+ :class:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent`,
event, propagation will be stopped and the response will be sent to
the client.
@@ -360,6 +357,3 @@ time and again, you can have just one (or several) listeners deal with them.
out and other things.
.. _`TwigBundle`: https://github.com/symfony/twig-bundle
-.. _`WebfactoryExceptionsBundle`: https://github.com/webfactory/exceptions-bundle
-.. _`Symfony Standard Edition`: https://github.com/symfony/symfony-standard/
-.. _`ExceptionListener`: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php
diff --git a/controller/forwarding.rst b/controller/forwarding.rst
index b8fb3bf8c63..74a6ffedeed 100644
--- a/controller/forwarding.rst
+++ b/controller/forwarding.rst
@@ -5,10 +5,13 @@ How to Forward Requests to another Controller
=============================================
Though not very common, you can also forward to another controller internally
-with the :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController::forward`
-method. Instead of redirecting the user's browser, this makes an "internal"
-sub-request and calls the defined controller. The ``forward()`` method returns
-the :class:`Symfony\\Component\\HttpFoundation\\Response` object that is returned
+with the ``forward()`` method provided by the
+:class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController`
+class.
+
+Instead of redirecting the user's browser, this makes an "internal" sub-request
+and calls the defined controller. The ``forward()`` method returns the
+:class:`Symfony\\Component\\HttpFoundation\\Response` object that is returned
from *that* controller::
public function index($name)
diff --git a/controller/service.rst b/controller/service.rst
index 8fbb6db9a2f..a9098e16b86 100644
--- a/controller/service.rst
+++ b/controller/service.rst
@@ -26,13 +26,12 @@ a service like: ``App\Controller\HelloController::index``:
.. code-block:: php-annotations
// src/Controller/HelloController.php
-
use Symfony\Component\Routing\Annotation\Route;
class HelloController
{
/**
- * @Route("/hello", name="hello")
+ * @Route("/hello", name="hello", methods={"GET"})
*/
public function index()
{
@@ -45,7 +44,8 @@ a service like: ``App\Controller\HelloController::index``:
# config/routes.yaml
hello:
path: /hello
- defaults: { _controller: App\Controller\HelloController::index }
+ controller: App\Controller\HelloController::index
+ methods: GET
.. code-block:: xml
@@ -54,29 +54,81 @@ a service like: ``App\Controller\HelloController::index``:
+ https://symfony.com/schema/routing/routing-1.0.xsd">
-
- App\Controller\HelloController::index
-
+
.. code-block:: php
// config/routes.php
- $collection->add('hello', new Route('/hello', [
- '_controller' => 'App\Controller\HelloController::index',
- ]));
+ use App\Controller\HelloController;
+ use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
+
+ return function (RoutingConfigurator $routes) {
+ $routes->add('hello', '/hello')
+ ->controller([HelloController::class, 'index'])
+ ->methods(['GET'])
+ ;
+ };
.. _controller-service-invoke:
Invokable Controllers
---------------------
-If your controller implements the ``__invoke()`` method - popular with the
-Action-Domain-Response (ADR) pattern, you can refer to the service id
-without the method (``App\Controller\HelloController`` for example).
+Controllers can also define a single action using the ``__invoke()`` method,
+which is a common practice when following the `ADR pattern`_
+(Action-Domain-Responder):
+
+.. configuration-block::
+
+ .. code-block:: php-annotations
+
+ // src/Controller/Hello.php
+ use Symfony\Component\HttpFoundation\Response;
+ use Symfony\Component\Routing\Annotation\Route;
+
+ /**
+ * @Route("/hello/{name}", name="hello")
+ */
+ class Hello
+ {
+ public function __invoke($name = 'World')
+ {
+ return new Response(sprintf('Hello %s!', $name));
+ }
+ }
+
+ .. code-block:: yaml
+
+ # config/routes.yaml
+ hello:
+ path: /hello/{name}
+ defaults: { _controller: app.hello_controller }
+
+ .. code-block:: xml
+
+
+
+
+
+
+ app.hello_controller
+
+
+
+
+ .. code-block:: php
+
+ // app/config/routing.php
+ $collection->add('hello', new Route('/hello', [
+ '_controller' => 'app.hello_controller',
+ ]));
Alternatives to base Controller Methods
---------------------------------------
@@ -132,6 +184,6 @@ If you want to know what type-hints to use for each service, see the
``getSubscribedServices()`` method in `AbstractController`_.
.. _`Controller class source code`: https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php
-.. _`base Controller class`: https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php
.. _`ControllerTrait`: https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php
.. _`AbstractController`: https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php
+.. _`ADR pattern`: https://en.wikipedia.org/wiki/Action%E2%80%93domain%E2%80%93responder
diff --git a/controller/soap_web_service.rst b/controller/soap_web_service.rst
index 902d3d9a796..42679a7ca14 100644
--- a/controller/soap_web_service.rst
+++ b/controller/soap_web_service.rst
@@ -59,10 +59,10 @@ can be retrieved via ``/soap?wsdl``::
namespace App\Controller;
+ use App\Service\HelloService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
- use App\Service\HelloService;
class HelloServiceController extends AbstractController
{
@@ -120,17 +120,17 @@ An example WSDL is below.
-
-
+
+
-
+
-
+
@@ -160,7 +160,7 @@ An example WSDL is below.
-
+
diff --git a/controller/upload_file.rst b/controller/upload_file.rst
index 6b0368d1b59..ce63d2bd449 100644
--- a/controller/upload_file.rst
+++ b/controller/upload_file.rst
@@ -12,14 +12,13 @@ How to Upload Files
integrated with Doctrine ORM, MongoDB ODM, PHPCR ODM and Propel.
Imagine that you have a ``Product`` entity in your application and you want to
-add a PDF brochure for each product. To do so, add a new property called ``brochure``
-in the ``Product`` entity::
+add a PDF brochure for each product. To do so, add a new property called
+``brochureFilename`` in the ``Product`` entity::
// src/Entity/Product.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
- use Symfony\Component\Validator\Constraints as Assert;
class Product
{
@@ -27,38 +26,40 @@ in the ``Product`` entity::
/**
* @ORM\Column(type="string")
- *
- * @Assert\NotBlank(message="Please, upload the product brochure as a PDF file.")
- * @Assert\File(mimeTypes={ "application/pdf" })
*/
- private $brochure;
+ private $brochureFilename;
- public function getBrochure()
+ public function getBrochureFilename()
{
- return $this->brochure;
+ return $this->brochureFilename;
}
- public function setBrochure($brochure)
+ public function setBrochureFilename($brochureFilename)
{
- $this->brochure = $brochure;
+ $this->brochureFilename = $brochureFilename;
return $this;
}
}
-Note that the type of the ``brochure`` column is ``string`` instead of ``binary``
-or ``blob`` because it just stores the PDF file name instead of the file contents.
+Note that the type of the ``brochureFilename`` column is ``string`` instead of
+``binary`` or ``blob`` because it only stores the PDF file name instead of the
+file contents.
-Then, add a new ``brochure`` field to the form that manages the ``Product`` entity::
+The next step is to add a new field to the form that manages the ``Product``
+entity. This must be a ``FileType`` field so the browsers can display the file
+upload widget. The trick to make it work is to add the form field as "unmapped",
+so Symfony doesn't try to get/set its value from the related entity::
// src/Form/ProductType.php
namespace App\Form;
use App\Entity\Product;
use Symfony\Component\Form\AbstractType;
+ use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
- use Symfony\Component\Form\Extension\Core\Type\FileType;
+ use Symfony\Component\Validator\Constraints\File;
class ProductType extends AbstractType
{
@@ -66,7 +67,29 @@ Then, add a new ``brochure`` field to the form that manages the ``Product`` enti
{
$builder
// ...
- ->add('brochure', FileType::class, ['label' => 'Brochure (PDF file)'])
+ ->add('brochure', FileType::class, [
+ 'label' => 'Brochure (PDF file)',
+
+ // unmapped means that this field is not associated to any entity property
+ 'mapped' => false,
+
+ // make it optional so you don't have to re-upload the PDF file
+ // everytime you edit the Product details
+ 'required' => false,
+
+ // unmapped fields can't define their validation using annotations
+ // in the associated entity, so you can use the PHP constraint classes
+ 'constraints' => [
+ new File([
+ 'maxSize' => '1024k',
+ 'mimeTypes' => [
+ 'application/pdf',
+ 'application/x-pdf',
+ ],
+ 'mimeTypesMessage' => 'Please upload a valid PDF document',
+ ])
+ ],
+ ])
// ...
;
}
@@ -99,12 +122,13 @@ Finally, you need to update the code of the controller that handles the form::
// src/Controller/ProductController.php
namespace App\Controller;
+ use App\Entity\Product;
+ use App\Form\ProductType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
+ use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
- use App\Entity\Product;
- use App\Form\ProductType;
class ProductController extends AbstractController
{
@@ -118,26 +142,32 @@ Finally, you need to update the code of the controller that handles the form::
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
- // $file stores the uploaded PDF file
- /** @var Symfony\Component\HttpFoundation\File\UploadedFile $file */
- $file = $product->getBrochure();
-
- $fileName = $this->generateUniqueFileName().'.'.$file->guessExtension();
-
- // Move the file to the directory where brochures are stored
- try {
- $file->move(
- $this->getParameter('brochures_directory'),
- $fileName
- );
- } catch (FileException $e) {
- // ... handle exception if something happens during file upload
+ /** @var UploadedFile $brochureFile */
+ $brochureFile = $form['brochure']->getData();
+
+ // this condition is needed because the 'brochure' field is not required
+ // so the PDF file must be processed only when a file is uploaded
+ if ($brochureFile) {
+ $originalFilename = pathinfo($brochureFile->getClientOriginalName(), PATHINFO_FILENAME);
+ // this is needed to safely include the file name as part of the URL
+ $safeFilename = transliterator_transliterate('Any-Latin; Latin-ASCII; [^A-Za-z0-9_] remove; Lower()', $originalFilename);
+ $newFilename = $safeFilename.'-'.uniqid().'.'.$brochureFile->guessExtension();
+
+ // Move the file to the directory where brochures are stored
+ try {
+ $brochureFile->move(
+ $this->getParameter('brochures_directory'),
+ $newFilename
+ );
+ } catch (FileException $e) {
+ // ... handle exception if something happens during file upload
+ }
+
+ // updates the 'brochureFilename' property to store the PDF file name
+ // instead of its contents
+ $product->setBrochureFilename($newFilename);
}
- // updates the 'brochure' property to store the PDF file name
- // instead of its contents
- $product->setBrochure($fileName);
-
// ... persist the $product variable or any other work
return $this->redirect($this->generateUrl('app_product_list'));
@@ -147,16 +177,6 @@ Finally, you need to update the code of the controller that handles the form::
'form' => $form->createView(),
]);
}
-
- /**
- * @return string
- */
- private function generateUniqueFileName()
- {
- // md5() reduces the similarity of the file names generated by
- // uniqid(), which is based on timestamps
- return md5(uniqid());
- }
}
Now, create the ``brochures_directory`` parameter that was used in the
@@ -172,9 +192,6 @@ controller to specify the directory in which the brochures should be stored:
There are some important things to consider in the code of the above controller:
-#. When the form is uploaded, the ``brochure`` property contains the whole PDF
- file contents. Since this property stores just the file name, you must set
- its new value before persisting the changes of the entity;
#. In Symfony applications, uploaded files are objects of the
:class:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile` class. This class
provides methods for the most common operations when dealing with uploaded files;
@@ -189,7 +206,8 @@ There are some important things to consider in the code of the above controller:
use the :method:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile::guessExtension`
method to let Symfony guess the right extension according to the file MIME type;
-.. versionadded:: 4.1
+.. deprecated:: 4.1
+
The :method:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile::getClientSize`
method was deprecated in Symfony 4.1 and will be removed in Symfony 5.0.
Use ``getSize()`` instead.
@@ -198,7 +216,7 @@ You can use the following code to link to the PDF brochure of a product:
.. code-block:: html+twig
- View brochure (PDF)
+ View brochure (PDF)
.. tip::
@@ -211,8 +229,8 @@ You can use the following code to link to the PDF brochure of a product:
use Symfony\Component\HttpFoundation\File\File;
// ...
- $product->setBrochure(
- new File($this->getParameter('brochures_directory').'/'.$product->getBrochure())
+ $product->setBrochureFilename(
+ new File($this->getParameter('brochures_directory').'/'.$product->getBrochureFilename())
);
Creating an Uploader Service
@@ -238,7 +256,9 @@ logic to a separate service::
public function upload(UploadedFile $file)
{
- $fileName = md5(uniqid()).'.'.$file->guessExtension();
+ $originalFilename = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
+ $safeFilename = transliterator_transliterate('Any-Latin; Latin-ASCII; [^A-Za-z0-9_] remove; Lower()', $originalFilename);
+ $fileName = $safeFilename.'-'.uniqid().'.'.$file->guessExtension();
try {
$file->move($this->getTargetDirectory(), $fileName);
@@ -267,9 +287,6 @@ logic to a separate service::
:class:`Symfony\\Component\\HttpFoundation\\File\\Exception\\NoTmpDirFileException`,
and :class:`Symfony\\Component\\HttpFoundation\\File\\Exception\\PartialFileException`.
- .. versionadded:: 4.1
- The detailed exception classes were introduced in Symfony 4.1.
-
Then, define a service for this class:
.. configuration-block::
@@ -291,7 +308,7 @@ Then, define a service for this class:
+ https://symfony.com/schema/dic/services/services-1.0.xsd">
@@ -310,8 +327,8 @@ Then, define a service for this class:
Now you're ready to use this service in the controller::
// src/Controller/ProductController.php
- use Symfony\Component\HttpFoundation\Request;
use App\Service\FileUploader;
+ use Symfony\Component\HttpFoundation\Request;
// ...
public function new(Request $request, FileUploader $fileUploader)
@@ -319,10 +336,12 @@ Now you're ready to use this service in the controller::
// ...
if ($form->isSubmitted() && $form->isValid()) {
- $file = $product->getBrochure();
- $fileName = $fileUploader->upload($file);
-
- $product->setBrochure($fileName);
+ /** @var UploadedFile $brochureFile */
+ $brochureFile = $form['brochure']->getData();
+ if ($brochureFile) {
+ $brochureFileName = $fileUploader->upload($brochureFile);
+ $product->setBrochureFilename($brochureFileName);
+ }
// ...
}
@@ -333,149 +352,16 @@ Now you're ready to use this service in the controller::
Using a Doctrine Listener
-------------------------
-If you are using Doctrine to store the Product entity, you can create a
-:doc:`Doctrine listener ` to
-automatically upload the file when persisting the entity::
-
- // src/EventListener/BrochureUploadListener.php
- namespace App\EventListener;
-
- use Symfony\Component\HttpFoundation\File\UploadedFile;
- use Symfony\Component\HttpFoundation\File\File;
- use Doctrine\ORM\Event\LifecycleEventArgs;
- use Doctrine\ORM\Event\PreUpdateEventArgs;
- use App\Entity\Product;
- use App\Service\FileUploader;
-
- class BrochureUploadListener
- {
- private $uploader;
-
- public function __construct(FileUploader $uploader)
- {
- $this->uploader = $uploader;
- }
-
- public function prePersist(LifecycleEventArgs $args)
- {
- $entity = $args->getEntity();
-
- $this->uploadFile($entity);
- }
-
- public function preUpdate(PreUpdateEventArgs $args)
- {
- $entity = $args->getEntity();
-
- $this->uploadFile($entity);
- }
-
- private function uploadFile($entity)
- {
- // upload only works for Product entities
- if (!$entity instanceof Product) {
- return;
- }
-
- $file = $entity->getBrochure();
-
- // only upload new files
- if ($file instanceof UploadedFile) {
- $fileName = $this->uploader->upload($file);
- $entity->setBrochure($fileName);
- } elseif ($file instanceof File) {
- // prevents the full file path being saved on updates
- // as the path is set on the postLoad listener
- $entity->setBrochure($file->getFilename());
- }
- }
- }
-
-Now, register this class as a Doctrine listener:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # config/services.yaml
- services:
- _defaults:
- # ... be sure autowiring is enabled
- autowire: true
- # ...
-
- App\EventListener\BrochureUploadListener:
- tags:
- - { name: doctrine.event_listener, event: prePersist }
- - { name: doctrine.event_listener, event: preUpdate }
-
- .. code-block:: xml
+The previous versions of this article explained how to handle file uploads using
+:doc:`Doctrine listeners `. However, this
+is no longer recommended, because Doctrine events shouldn't be used for your
+domain logic.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // config/services.php
- use App\EventListener\BrochureUploaderListener;
-
- $container->autowire(BrochureUploaderListener::class)
- ->addTag('doctrine.event_listener', [
- 'event' => 'prePersist',
- ])
- ->addTag('doctrine.event_listener', [
- 'event' => 'preUpdate',
- ])
- ;
-
-This listener is now automatically executed when persisting a new Product
-entity. This way, you can remove everything related to uploading from the
-controller.
-
-.. tip::
-
- This listener can also create the ``File`` instance based on the path when
- fetching entities from the database::
-
- // ...
- use Symfony\Component\HttpFoundation\File\File;
-
- // ...
- class BrochureUploadListener
- {
- // ...
-
- public function postLoad(LifecycleEventArgs $args)
- {
- $entity = $args->getEntity();
-
- if (!$entity instanceof Product) {
- return;
- }
-
- if ($fileName = $entity->getBrochure()) {
- $entity->setBrochure(new File($this->uploader->getTargetDirectory().'/'.$fileName));
- }
- }
- }
+Moreover, Doctrine listeners are often dependent on internal Doctrine behavior
+which may change in future versions. Also, they can introduce performance issues
+unawarely (because your listener persists entities which cause other entities to
+be changed and persisted).
- After adding these lines, configure the listener to also listen for the
- ``postLoad`` event.
+As an alternative, you can use :doc:`Symfony events, listeners and subscribers `.
.. _`VichUploaderBundle`: https://github.com/dustin10/VichUploaderBundle
diff --git a/create_framework/dependency_injection.rst b/create_framework/dependency_injection.rst
index 2dbb759abed..27991a50915 100644
--- a/create_framework/dependency_injection.rst
+++ b/create_framework/dependency_injection.rst
@@ -66,15 +66,15 @@ framework more configurable, but at the same time, it introduces a lot of
issues:
* We are not able to register custom listeners anymore as the dispatcher is
- not available outside the Framework class (an easy workaround could be the
+ not available outside the Framework class (a workaround could be the
adding of a ``Framework::getEventDispatcher()`` method);
* We have lost the flexibility we had before; you cannot change the
implementation of the ``UrlMatcher`` or of the ``ControllerResolver``
anymore;
-* Related to the previous point, we cannot test our framework easily anymore
- as it's impossible to mock internal objects;
+* Related to the previous point, we cannot test our framework without much
+ effort anymore as it's impossible to mock internal objects;
* We cannot change the charset passed to ``ResponseListener`` anymore (a
workaround could be to pass it as a constructor argument).
@@ -97,13 +97,13 @@ container:
Create a new file to host the dependency injection container configuration::
// example.com/src/container.php
+ use Simplex\Framework;
use Symfony\Component\DependencyInjection;
use Symfony\Component\DependencyInjection\Reference;
+ use Symfony\Component\EventDispatcher;
use Symfony\Component\HttpFoundation;
use Symfony\Component\HttpKernel;
use Symfony\Component\Routing;
- use Symfony\Component\EventDispatcher;
- use Simplex\Framework;
$containerBuilder = new DependencyInjection\ContainerBuilder();
$containerBuilder->register('context', Routing\RequestContext::class);
@@ -234,7 +234,7 @@ And the related change in the front controller::
$container->setParameter('routes', include __DIR__.'/../src/app.php');
-We have obviously barely scratched the surface of what you can do with the
+We have barely scratched the surface of what you can do with the
container: from class names as parameters, to overriding existing object
definitions, from shared service support to dumping a container to a plain PHP class,
and much more. The Symfony dependency injection container is really powerful
@@ -253,4 +253,3 @@ internally.
Have fun!
.. _`Pimple`: https://github.com/silexphp/Pimple
-.. _`Application`: https://github.com/silexphp/Silex/blob/master/src/Silex/Application.php
diff --git a/create_framework/event_dispatcher.rst b/create_framework/event_dispatcher.rst
index b0d8aa99848..fd655a93ebf 100644
--- a/create_framework/event_dispatcher.rst
+++ b/create_framework/event_dispatcher.rst
@@ -3,8 +3,7 @@ The EventDispatcher Component
Our framework is still missing a major characteristic of any good framework:
*extensibility*. Being extensible means that the developer should be able to
-easily hook into the framework life cycle to modify the way the request is
-handled.
+hook into the framework life cycle to modify the way the request is handled.
What kind of hooks are we talking about? Authentication or caching for
instance. To be flexible, hooks must be plug-and-play; the ones you "register"
@@ -77,7 +76,7 @@ the Response instance::
}
// dispatch a response event
- $this->dispatcher->dispatch('response', new ResponseEvent($response, $request));
+ $this->dispatcher->dispatch(new ResponseEvent($response, $request), 'response');
return $response;
}
@@ -91,7 +90,7 @@ now dispatched::
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
- use Symfony\Component\EventDispatcher\Event;
+ use Symfony\Contracts\EventDispatcher\Event;
class ResponseEvent extends Event
{
@@ -302,4 +301,4 @@ is not about creating a generic framework, but one that is tailored to your
needs. Stop whenever you see fit, and further evolve the code from there.
.. _`WSGI`: https://www.python.org/dev/peps/pep-0333/#middleware-components-that-play-both-sides
-.. _`Rack`: http://rack.rubyforge.org/
+.. _`Rack`: https://github.com/rack/rack
diff --git a/create_framework/front_controller.rst b/create_framework/front_controller.rst
index 23dc9b8c82b..39286ba8c16 100644
--- a/create_framework/front_controller.rst
+++ b/create_framework/front_controller.rst
@@ -61,8 +61,8 @@ which name is exposed to the end user via the URL
(``http://127.0.0.1:4321/bye.php``): there is a direct mapping between the PHP
script name and the client URL. This is because the dispatching of the request
is done by the web server directly. It might be a good idea to move this
-dispatching to our code for better flexibility. This can be achieved by
-routing all client requests to a single PHP script.
+dispatching to our code for better flexibility. This can be achieved by routing
+all client requests to a single PHP script.
.. tip::
@@ -153,12 +153,12 @@ web root directory:
Now, configure your web server root directory to point to ``web/`` and all
other files won't be accessible from the client anymore.
-To test your changes in a browser (``http://localhost:4321/hello?name=Fabien``), run
-the PHP built-in server:
+To test your changes in a browser (``http://localhost:4321/hello?name=Fabien``),
+run the :doc:`Symfony Local Web Server `:
.. code-block:: terminal
- $ php -S 127.0.0.1:4321 -t web/ web/front.php
+ $ symfony server:start --port=4321 --passthru=front.php
.. note::
@@ -220,7 +220,7 @@ We have the first version of our framework::
$response->send();
-Adding a new page is a two step process: add an entry in the map and create a
+Adding a new page is a two-step process: add an entry in the map and create a
PHP template in ``src/pages/``. From a template, get the Request data via the
``$request`` variable and tweak the Response headers via the ``$response``
variable.
diff --git a/create_framework/http_foundation.rst b/create_framework/http_foundation.rst
index 672f40599ce..28d3f33dec3 100644
--- a/create_framework/http_foundation.rst
+++ b/create_framework/http_foundation.rst
@@ -9,10 +9,9 @@ top of the Symfony components is better than creating a framework from scratch.
.. note::
- We won't talk about the obvious and traditional benefits of using a
- framework when working on big applications with more than a few
- developers; the Internet has already plenty of good resources on that
- topic.
+ We won't talk about the traditional benefits of using a framework when
+ working on big applications with more than a few developers; the Internet
+ has already plenty of good resources on that topic.
Even if the "application" we wrote in the previous chapter was simple enough,
it suffers from a few problems::
@@ -52,7 +51,7 @@ As you can see for yourself, the simple code we had written first is not that
simple anymore if we want to avoid PHP warnings/notices and make the code
more secure.
-Beyond security, this code is not even easily testable. Even if there is not
+Beyond security, this code can be complex to test. Even if there is not
much to test, it strikes me that writing unit tests for the simplest possible
snippet of PHP code is not natural and feels ugly. Here is a tentative PHPUnit
unit test for the above code::
@@ -78,7 +77,7 @@ unit test for the above code::
If our application were just slightly bigger, we would have been able to
find even more problems. If you are curious about them, read the
- :doc:`/introduction/from_flat_php_to_symfony2` chapter of the book.
+ :doc:`/introduction/from_flat_php_to_symfony` chapter of the book.
At this point, if you are not convinced that security and testing are indeed
two very good reasons to stop writing code the old way and adopt a framework
@@ -87,9 +86,9 @@ reading this book now and go back to whatever code you were working on before.
.. note::
- Of course, using a framework should give you more than just security and
- testability, but the more important thing to keep in mind is that the
- framework you choose must allow you to write better code faster.
+ Using a framework should give you more than just security and testability,
+ but the more important thing to keep in mind is that the framework you
+ choose must allow you to write better code faster.
Going OOP with the HttpFoundation Component
-------------------------------------------
@@ -126,10 +125,10 @@ containing the new requirement.
.. sidebar:: Class Autoloading
When installing a new dependency, Composer also generates a
- ``vendor/autoload.php`` file that allows any class to be easily
- `autoloaded`_. Without autoloading, you would need to require the file
- where a class is defined before being able to use it. But thanks to
- `PSR-4`_, we can just let Composer and PHP do the hard work for us.
+ ``vendor/autoload.php`` file that allows any class to be `autoloaded`_.
+ Without autoloading, you would need to require the file where a class
+ is defined before being able to use it. But thanks to `PSR-4`_,
+ we can just let Composer and PHP do the hard work for us.
Now, let's rewrite our application by using the ``Request`` and the
``Response`` classes::
@@ -285,8 +284,8 @@ the wheel.
I've almost forgot to talk about one added benefit: using the HttpFoundation
component is the start of better interoperability between all frameworks and
-`applications using it`_ (like `Symfony`_, `Drupal 8`_, `phpBB 3`_, `ezPublish
-5`_, `Laravel`_ and `more`_).
+`applications using it`_ (like `Symfony`_, `Drupal 8`_, `phpBB 3`_, `Laravel`_
+and `ezPublish 5`_, and `more`_).
.. _`Twig`: https://twig.symfony.com/
.. _`HTTP specification`: https://tools.ietf.org/wg/httpbis/
@@ -297,8 +296,6 @@ component is the start of better interoperability between all frameworks and
.. _`phpBB 3`: https://www.phpbb.com/
.. _`ezPublish 5`: https://ez.no/
.. _`Laravel`: https://laravel.com/
-.. _`Midgard CMS`: http://www.midgard-project.org/
-.. _`Zikula`: https://zikula.org/
.. _`autoloaded`: https://php.net/autoload
.. _`PSR-4`: https://www.php-fig.org/psr/psr-4/
.. _`more`: https://symfony.com/components/HttpFoundation
diff --git a/create_framework/http_kernel_controller_resolver.rst b/create_framework/http_kernel_controller_resolver.rst
index f9148c974c0..5b998ed2dab 100644
--- a/create_framework/http_kernel_controller_resolver.rst
+++ b/create_framework/http_kernel_controller_resolver.rst
@@ -25,10 +25,10 @@ Update the route definition accordingly::
$routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', [
'year' => null,
'_controller' => [new LeapYearController(), 'index'],
- )));
+ ]));
The move is pretty straightforward and makes a lot of sense as soon as you
-create more pages but you might have noticed a non-desirable side-effect...
+create more pages but you might have noticed a non-desirable side effect...
The ``LeapYearController`` class is *always* instantiated, even if the
requested URL does not match the ``leap_year`` route. This is bad for one main
reason: performance wise, all controllers for all routes must now be
@@ -162,8 +162,8 @@ Let's conclude with the new version of our framework::
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
- use Symfony\Component\Routing;
use Symfony\Component\HttpKernel;
+ use Symfony\Component\Routing;
function render_template(Request $request)
{
@@ -203,4 +203,3 @@ Think about it once more: our framework is more robust and more flexible than
ever and it still has less than 50 lines of code.
.. _`reflection`: https://php.net/reflection
-.. _`FrameworkExtraBundle`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
diff --git a/create_framework/http_kernel_httpkernel_class.rst b/create_framework/http_kernel_httpkernel_class.rst
index 717b66f4113..f9f8f16932f 100644
--- a/create_framework/http_kernel_httpkernel_class.rst
+++ b/create_framework/http_kernel_httpkernel_class.rst
@@ -4,7 +4,7 @@ The HttpKernel Component: The HttpKernel Class
If you were to use our framework right now, you would probably have to add
support for custom error messages. We do have 404 and 500 error support but
the responses are hardcoded in the framework itself. Making them customizable
-is easy enough though: dispatch a new event and listen to it. Doing it right
+is straightforward though: dispatch a new event and listen to it. Doing it right
means that the listener has to call a regular controller. But what if the
error controller throws an exception? You will end up in an infinite loop.
There should be an easier way, right?
@@ -91,8 +91,8 @@ The error controller reads as follows::
// example.com/src/Calendar/Controller/ErrorController.php
namespace Calendar\Controller;
- use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Debug\Exception\FlattenException;
+ use Symfony\Component\HttpFoundation\Response;
class ErrorController
{
@@ -104,8 +104,8 @@ The error controller reads as follows::
}
}
-Voilà! Clean and customizable error management without efforts. And
-if your ``ErrorController`` throws an exception, HttpKernel will handle it nicely.
+*Voilà!* Clean and customizable error management without efforts. And if your
+``ErrorController`` throws an exception, HttpKernel will handle it nicely.
In chapter two, we talked about the ``Response::prepare()`` method, which
ensures that a Response is compliant with the HTTP specification. It is
@@ -114,8 +114,8 @@ client; that's what the ``ResponseListener`` does::
$dispatcher->addSubscriber(new HttpKernel\EventListener\ResponseListener('UTF-8'));
-This one was easy too! Let's take another one: do you want out of the box
-support for streamed responses? Subscribe to ``StreamedResponseListener``::
+If you want out of the box support for streamed responses, subscribe
+to ``StreamedResponseListener``::
$dispatcher->addSubscriber(new HttpKernel\EventListener\StreamedResponseListener());
@@ -153,12 +153,12 @@ only if needed::
namespace Simplex;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
- use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpFoundation\Response;
+ use Symfony\Component\HttpKernel\Event\ViewEvent;
class StringResponseListener implements EventSubscriberInterface
{
- public function onView(GetResponseForControllerResultEvent $event)
+ public function onView(ViewEvent $event)
{
$response = $event->getControllerResult();
diff --git a/create_framework/http_kernel_httpkernelinterface.rst b/create_framework/http_kernel_httpkernelinterface.rst
index bf5fe5d3132..fccf5d408c0 100644
--- a/create_framework/http_kernel_httpkernelinterface.rst
+++ b/create_framework/http_kernel_httpkernelinterface.rst
@@ -46,7 +46,7 @@ Update your framework so that it implements this interface::
}
}
-Even if this change looks trivial, it brings us a lot! Let's talk about one of
+Even if this change looks not too complex, it brings us a lot! Let's talk about one of
the most impressive one: transparent :doc:`HTTP caching ` support.
The ``HttpCache`` class implements a fully-featured reverse proxy, written in
@@ -55,7 +55,7 @@ PHP; it implements ``HttpKernelInterface`` and wraps another
// example.com/web/front.php
- // ..
+ // ...
$framework = new Simplex\Framework($dispatcher, $matcher, $controllerResolver, $argumentResolver);
$framework = new HttpKernel\HttpCache\HttpCache(
@@ -90,11 +90,10 @@ to cache a response for 10 seconds, use the ``Response::setTtl()`` method::
.. tip::
- If, like me, you are running your framework from the command line by
- simulating requests (``Request::create('/is_leap_year/2012')``), you can
- debug Response instances by dumping their string representation
- (``echo $response;``) as it displays all headers as well as the response
- content.
+ If you are running your framework from the command line by simulating
+ requests (``Request::create('/is_leap_year/2012')``), you can debug Response
+ instances by dumping their string representation (``echo $response;``) as it
+ displays all headers as well as the response content.
To validate that it works correctly, add a random number to the response
content and check that the number only changes every 10 seconds::
@@ -113,9 +112,9 @@ expiration and the validation models of the HTTP specification. If you are not
comfortable with these concepts, read the `HTTP caching`_ chapter of the
Symfony documentation.
-The Response class contains methods that let you configure the
-HTTP cache. One of the most powerful is ``setCache()`` as it
-abstracts the most frequently used caching strategies into a single array::
+The Response class contains methods that let you configure the HTTP cache. One
+of the most powerful is ``setCache()`` as it abstracts the most frequently used
+caching strategies into a single array::
$date = date_create_from_format('Y-m-d H:i:s', '2005-10-15 10:00:00');
@@ -135,8 +134,8 @@ abstracts the most frequently used caching strategies into a single array::
$response->setSharedMaxAge(10);
When using the validation model, the ``isNotModified()`` method allows you to
-cut on the response time by short-circuiting the response generation as
-early as possible::
+cut on the response time by short-circuiting the response generation as early as
+possible::
$response->setETag('whatever_you_compute_as_an_etag');
@@ -158,7 +157,7 @@ page as being the content of a sub-request call:
This is the content of your page
- Is 2012 a leap year?
+ Is 2012 a leap year?
Some other content
diff --git a/create_framework/introduction.rst b/create_framework/introduction.rst
index 82aaf864ea3..363538529d8 100644
--- a/create_framework/introduction.rst
+++ b/create_framework/introduction.rst
@@ -38,8 +38,8 @@ you can use as is or as a start for your very own. It will start with a simple
framework and more features will be added with time. Eventually, you will have
a fully-featured full-stack web framework.
-And each step will be the occasion to learn more about some of the
-Symfony Components.
+And each step will be the occasion to learn more about some of the Symfony
+Components.
Many modern web frameworks advertize themselves as being MVC frameworks. This
tutorial won't talk about the MVC pattern, as the Symfony Components are able to
@@ -87,7 +87,7 @@ Dependency Management
To install the Symfony Components that you need for your framework, you are going
to use `Composer`_, a project dependency manager for PHP. If you don't have it
-yet, :doc:`download and install Composer ` now.
+yet, `download and install Composer`_ now.
Our Project
-----------
@@ -101,17 +101,17 @@ start with the simplest web application we can think of in PHP::
printf('Hello %s', $name);
-You can use the PHP built-in server to test this great application in a browser
-(``http://localhost:4321/index.php?name=Fabien``):
+You can use the :doc:`Symfony Local Web Server ` to test
+this great application in a browser
+(``http://localhost:8000/index.php?name=Fabien``):
.. code-block:: terminal
- $ php -S 127.0.0.1:4321
-
-Otherwise, you can always use your own server (Apache, Nginx, etc.).
+ $ symfony server:start
In the :doc:`next chapter `, we are going to
introduce the HttpFoundation Component and see what it brings us.
.. _`Symfony`: https://symfony.com/
.. _`Composer`: http://packagist.org/about-composer
+.. _`download and install Composer`: https://getcomposer.org/download/
diff --git a/create_framework/routing.rst b/create_framework/routing.rst
index 56fa2465d92..b196d2cb965 100644
--- a/create_framework/routing.rst
+++ b/create_framework/routing.rst
@@ -80,8 +80,8 @@ of default values for route attributes (``['name' => 'World']``).
Based on the information stored in the ``RouteCollection`` instance, a
``UrlMatcher`` instance can match URL paths::
- use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Matcher\UrlMatcher;
+ use Symfony\Component\Routing\RequestContext;
$context = new RequestContext();
$context->fromRequest($request);
@@ -93,27 +93,27 @@ The ``match()`` method takes a request path and returns an array of attributes
(notice that the matched route is automatically stored under the special
``_route`` attribute)::
- print_r($matcher->match('/bye'));
- /* Gives:
- array (
- '_route' => 'bye',
- );
+ $matcher->match('/bye');
+ /* Result:
+ [
+ '_route' => 'bye',
+ ];
*/
- print_r($matcher->match('/hello/Fabien'));
- /* Gives:
- array (
- 'name' => 'Fabien',
- '_route' => 'hello',
- );
+ $matcher->match('/hello/Fabien');
+ /* Result:
+ [
+ 'name' => 'Fabien',
+ '_route' => 'hello',
+ ];
*/
- print_r($matcher->match('/hello'));
- /* Gives:
- array (
- 'name' => 'World',
- '_route' => 'hello',
- );
+ $matcher->match('/hello');
+ /* Result:
+ [
+ 'name' => 'World',
+ '_route' => 'hello',
+ ];
*/
.. note::
@@ -165,23 +165,23 @@ There are a few new things in the code:
* Request attributes are extracted to keep our templates simple::
-
- Hello = htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?>
+ // example.com/src/pages/hello.php
+ Hello = htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?>
* Route configuration has been moved to its own file::
- // example.com/src/app.php
- use Symfony\Component\Routing;
+ // example.com/src/app.php
+ use Symfony\Component\Routing;
- $routes = new Routing\RouteCollection();
- $routes->add('hello', new Routing\Route('/hello/{name}', ['name' => 'World']));
- $routes->add('bye', new Routing\Route('/bye'));
+ $routes = new Routing\RouteCollection();
+ $routes->add('hello', new Routing\Route('/hello/{name}', ['name' => 'World']));
+ $routes->add('bye', new Routing\Route('/bye'));
- return $routes;
+ return $routes;
- We now have a clear separation between the configuration (everything
- specific to our application in ``app.php``) and the framework (the generic
- code that powers our application in ``front.php``).
+We now have a clear separation between the configuration (everything
+specific to our application in ``app.php``) and the framework (the generic
+code that powers our application in ``front.php``).
With less than 30 lines of code, we have a new framework, more powerful and
more flexible than the previous one. Enjoy!
@@ -189,8 +189,7 @@ more flexible than the previous one. Enjoy!
Using the Routing component has one big additional benefit: the ability to
generate URLs based on Route definitions. When using both URL matching and URL
generation in your code, changing the URL patterns should have no other
-impact. Want to know how to use the generator? Calling the ``generate`` method
-is all it takes::
+impact. You can use the generator this way::
use Symfony\Component\Routing;
@@ -217,6 +216,11 @@ generate absolute URLs::
highly optimized URL matcher class that can replace the default
``UrlMatcher``::
- $dumper = new Routing\Matcher\Dumper\PhpMatcherDumper($routes);
+ use Symfony\Component\Routing\Matcher\CompiledUrlMatcher;
+ use Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherDumper;
+
+ // $compiledRoutes is a plain PHP array that describes all routes in a performant data format
+ // you can (and should) cache it, typically by exporting it to a PHP file
+ $compiledRoutes = (new CompiledUrlMatcherDumper($routes))->getCompiledRoutes();
- echo $dumper->dump();
+ $matcher = new CompiledUrlMatcher($compiledRoutes, $context);
diff --git a/create_framework/separation_of_concerns.rst b/create_framework/separation_of_concerns.rst
index cf971869c73..e1e46f3ebe3 100644
--- a/create_framework/separation_of_concerns.rst
+++ b/create_framework/separation_of_concerns.rst
@@ -13,7 +13,7 @@ simple principle: the logic is about creating the Response associated with a
Request.
Let's create our very own namespace for our framework: ``Simplex``. Move the
-request handling logic into its own ``Simplex\\Framework`` class::
+request handling logic into its own ``Simplex\Framework`` class::
// example.com/src/Simplex/Framework.php
namespace Simplex;
@@ -100,9 +100,9 @@ Move the controller to ``Calendar\Controller\LeapYearController``::
// example.com/src/Calendar/Controller/LeapYearController.php
namespace Calendar\Controller;
+ use Calendar\Model\LeapYear;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
- use Calendar\Model\LeapYear;
class LeapYearController
{
@@ -138,7 +138,7 @@ Don't forget to update the ``example.com/src/app.php`` file accordingly::
$routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', [
'year' => null,
- '_controller' => 'Calendar\Controller\LeapYearController::indexAction',
+ '_controller' => 'Calendar\Controller\LeapYearController::index',
]));
To sum up, here is the new file layout:
@@ -163,7 +163,7 @@ To sum up, here is the new file layout:
└── front.php
That's it! Our application has now four different layers and each of them has
-a well defined goal:
+a well-defined goal:
* ``web/front.php``: The front controller; the only exposed PHP code that
makes the interface with the client (it gets the Request and sends the
@@ -171,7 +171,7 @@ a well defined goal:
our application;
* ``src/Simplex``: The reusable framework code that abstracts the handling of
- incoming Requests (by the way, it makes your controllers/templates easily
+ incoming Requests (by the way, it makes your controllers/templates better
testable -- more about that later on);
* ``src/Calendar``: Our application specific code (the controllers and the
diff --git a/create_framework/templating.rst b/create_framework/templating.rst
index f21564ce7b7..566779d5eaf 100644
--- a/create_framework/templating.rst
+++ b/create_framework/templating.rst
@@ -69,8 +69,8 @@ the ``_controller`` route attribute::
$response = new Response('An error occurred', 500);
}
-A route can now be associated with any controller and within a
-controller, you can still use the ``render_template()`` to render a template::
+A route can now be associated with any controller and within a controller, you
+can still use the ``render_template()`` to render a template::
$routes->add('hello', new Routing\Route('/hello/{name}', [
'name' => 'World',
@@ -142,8 +142,8 @@ framework does not need to be modified in any way, create a new
``app.php`` file::
// example.com/src/app.php
- use Symfony\Component\Routing;
use Symfony\Component\HttpFoundation\Response;
+ use Symfony\Component\Routing;
function is_leap_year($year = null) {
if (null === $year) {
diff --git a/create_framework/unit_testing.rst b/create_framework/unit_testing.rst
index 054dca776ef..69c235cfd3a 100644
--- a/create_framework/unit_testing.rst
+++ b/create_framework/unit_testing.rst
@@ -16,7 +16,7 @@ using `PHPUnit`_. Create a PHPUnit configuration file in
matcher = $matcher;
- $this->resolver = $resolver;
+ $this->controllerResolver = $resolver;
$this->argumentResolver = $argumentResolver;
}
@@ -135,8 +135,7 @@ Execute this test by running ``phpunit`` in the ``example.com`` directory:
After the test ran, you should see a green bar. If not, you have a bug
either in the test or in the framework code!
-Adding a unit test for any exception thrown in a controller means expecting a
-response code of 500::
+Adding a unit test for any exception thrown in a controller::
public function testErrorHandling()
{
@@ -151,8 +150,8 @@ Last, but not the least, let's write a test for when we actually have a proper
Response::
use Symfony\Component\HttpFoundation\Response;
- use Symfony\Component\HttpKernel\Controller\ControllerResolver;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver;
+ use Symfony\Component\HttpKernel\Controller\ControllerResolver;
// ...
public function testControllerResponse()
diff --git a/deployment.rst b/deployment.rst
index 9bfdc06e17b..b0ab569c87f 100644
--- a/deployment.rst
+++ b/deployment.rst
@@ -63,8 +63,8 @@ manually taking other steps (see `Common Post-Deployment Tasks`_).
Using Platforms as a Service
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Using a Platform as a Service (PaaS) can be a great way to deploy your Symfony app
-quickly and easily. There are many PaaS - below are a few that work well with Symfony:
+Using a Platform as a Service (PaaS) can be a great way to deploy your Symfony
+app quickly. There are many PaaS - below are a few that work well with Symfony:
* `Symfony Cloud`_
* `Heroku`_
@@ -118,8 +118,8 @@ you'll need to do:
A) Check Requirements
~~~~~~~~~~~~~~~~~~~~~
-Use the :doc:`Symfony Requirements Checker ` to check
-if your server meets the technical requirements to run Symfony applications.
+Use the ``check:requirements`` command to check if your server meets the
+:ref:`technical requirements for running Symfony applications `.
.. _b-configure-your-app-config-parameters-yml-file:
@@ -146,7 +146,6 @@ most natural in your hosting environment.
.. code-block:: terminal
- $ composer remove symfony/dotenv
$ composer require symfony/dotenv
C) Install/Update your Vendors
@@ -170,8 +169,8 @@ as you normally do:
.. caution::
If you get a "class not found" error during this step, you may need to
- run ``export APP_ENV=prod`` (or ``export SYMFONY_ENV=prod`` if you're not
- using :doc:`Symfony Flex `) before running this command so
+ run ``export APP_ENV=prod`` (or ``export SYMFONY_ENV=prod`` if you're not
+ using :ref:`Symfony Flex `) before running this command so
that the ``post-install-cmd`` scripts run in the ``prod`` environment.
D) Clear your Symfony Cache
@@ -181,7 +180,7 @@ Make sure you clear and warm-up your Symfony cache:
.. code-block:: terminal
- $ php bin/console cache:clear --env=prod --no-debug
+ $ APP_ENV=prod APP_DEBUG=0 php bin/console cache:clear
E) Other Things!
~~~~~~~~~~~~~~~~
@@ -190,7 +189,7 @@ There may be lots of other things that you need to do, depending on your
setup:
* Running any database migrations
-* Clearing your APC cache
+* Clearing your APCu cache
* Add/edit CRON jobs
* :ref:`Building and minifying your assets ` with Webpack Encore
* Pushing assets to a CDN
@@ -218,28 +217,15 @@ Troubleshooting
Deployments not Using the ``composer.json`` File
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Symfony applications provide a ``kernel.project_dir`` parameter and a related
-:method:`Symfony\\Component\\HttpKernel\\Kernel::getProjectDir` method.
-You can use this method to perform operations with file paths relative to your
-project's root directory. The logic to find that project root directory is based
-on the location of the main ``composer.json`` file.
-
-If your deployment method doesn't use Composer, you may have removed the
-``composer.json`` file and the application won't work on the production server.
-The solution is to override the ``getProjectDir()`` method in the application
-kernel and return your project's root directory::
-
- // src/Kernel.php
- // ...
- class Kernel extends BaseKernel
- {
- // ...
-
- public function getProjectDir()
- {
- return __DIR__.'/..';
- }
- }
+The :ref:`project root directory `
+(whose value is used via the ``kernel.project_dir`` parameter and the
+:method:`Symfony\\Component\\HttpKernel\\Kernel::getProjectDir` method) is
+calculated automatically by Symfony as the directory where the main
+``composer.json`` file is stored.
+
+In deployments not using the ``composer.json`` file, you'll need to override the
+:method:`Symfony\\Component\\HttpKernel\\Kernel::getProjectDir` method
+:ref:`as explained in this section `.
Learn More
----------
@@ -262,7 +248,7 @@ Learn More
.. _`Deployer`: http://deployer.org/
.. _`Git Tagging`: https://git-scm.com/book/en/v2/Git-Basics-Tagging
.. _`Heroku`: https://devcenter.heroku.com/articles/getting-started-with-symfony
-.. _`platform.sh`: https://docs.platform.sh/frameworks/symfony.html
+.. _`Platform.sh`: https://docs.platform.sh/frameworks/symfony.html
.. _`Azure`: https://azure.microsoft.com/en-us/develop/php/
.. _`fortrabbit`: https://help.fortrabbit.com/install-symfony
.. _`EasyDeployBundle`: https://github.com/EasyCorp/easy-deploy-bundle
diff --git a/deployment/heroku.rst b/deployment/heroku.rst
index a7527805bd2..1e2e1635c55 100644
--- a/deployment/heroku.rst
+++ b/deployment/heroku.rst
@@ -7,6 +7,6 @@ Deploying to Heroku
===================
To deploy to Heroku, see their official documentation:
-`Getting Started with Symfony on Heroku`_.
+`Deploying Symfony 4 Apps on Heroku`_.
-.. _`Getting Started with Symfony on Heroku`: https://devcenter.heroku.com/articles/getting-started-with-symfony
+.. _`Deploying Symfony 4 Apps on Heroku`: https://devcenter.heroku.com/articles/deploying-symfony4
diff --git a/deployment/proxies.rst b/deployment/proxies.rst
index d61d1c3a8d3..6e54ce5b612 100644
--- a/deployment/proxies.rst
+++ b/deployment/proxies.rst
@@ -22,9 +22,7 @@ Solution: setTrustedProxies()
-----------------------------
To fix this, you need to tell Symfony which reverse proxy IP addresses to trust
-and what headers your reverse proxy uses to send information:
-
-.. code-block:: php
+and what headers your reverse proxy uses to send information::
// public/index.php
@@ -61,9 +59,7 @@ In this case, you'll need to - *very carefully* - trust *all* proxies.
other than your load balancers. For AWS, this can be done with `security groups`_.
#. Once you've guaranteed that traffic will only come from your trusted reverse
- proxies, configure Symfony to *always* trust incoming request:
-
- .. code-block:: php
+ proxies, configure Symfony to *always* trust incoming request::
// public/index.php
@@ -80,5 +76,21 @@ That's it! It's critical that you prevent traffic from all non-trusted sources.
If you allow outside traffic, they could "spoof" their true IP address and
other information.
+Custom Headers When Using a Reverse Proxy
+-----------------------------------------
+
+Some reverse proxies (like `CloudFront`_ with ``CloudFront-Forwarded-Proto``) may force you to use a custom header.
+For instance you have ``Custom-Forwarded-Proto`` instead of ``X-Forwarded-Proto``.
+
+In this case, you'll need to set the header ``X-Forwarded-Proto`` with the value of
+``Custom-Forwarded-Proto`` early enough in your application, i.e. before handling the request::
+
+ // public/index.php
+
+ // ...
+ $_SERVER['HEADER_X_FORWARDED_PROTO'] = $_SERVER['HEADER_CUSTOM_FORWARDED_PROTO'];
+ // ...
+ $response = $kernel->handle($request);
+
.. _`security groups`: http://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-security-groups.html
-.. _`RFC 7239`: http://tools.ietf.org/html/rfc7239
+.. _`CloudFront`: https://en.wikipedia.org/wiki/Amazon_CloudFront
diff --git a/doctrine.rst b/doctrine.rst
index 0283fb9dfbd..9cedaa2c984 100644
--- a/doctrine.rst
+++ b/doctrine.rst
@@ -29,7 +29,7 @@ which will help generate some code:
.. code-block:: terminal
$ composer require symfony/orm-pack
- $ composer require symfony/maker-bundle --dev
+ $ composer require --dev symfony/maker-bundle
Configuring the Database
~~~~~~~~~~~~~~~~~~~~~~~~
@@ -50,10 +50,10 @@ The database connection information is stored as an environment variable called
.. caution::
If the username, password, host or database name contain any character considered
- special in a URI (such as ``!``, ``@``, ``$``, ``#``, ``/``), you must encode them.
- See `RFC 3986`_ for the full list of reserved characters or use the
- :phpfunction:`urlencode` function to encode them. In this case you need to remove
- the ``resolve:`` prefix in ``config/packages/doctrine.yaml`` to avoid errors:
+ special in a URI (such as ``+``, ``@``, ``$``, ``#``, ``/``, ``:``, ``*``, ``!``),
+ you must encode them. See `RFC 3986`_ for the full list of reserved characters or
+ use the :phpfunction:`urlencode` function to encode them. In this case you need to
+ remove the ``resolve:`` prefix in ``config/packages/doctrine.yaml`` to avoid errors:
``url: '%env(resolve:DATABASE_URL)%'``
Now that your connection parameters are setup, Doctrine can create the ``db_name``
@@ -117,6 +117,7 @@ need. The command will ask you some questions - answer them like done below:
(press enter again to finish)
.. versionadded:: 1.3
+
The interactive behavior of the ``make:entity`` command was introduced
in MakerBundle 1.3.
@@ -162,6 +163,13 @@ Woh! You now have a new ``src/Entity/Product.php`` file::
Confused why the price is an integer? Don't worry: this is just an example.
But, storing prices as integers (e.g. 100 = $1 USD) can avoid rounding issues.
+.. note::
+
+ If you are using an SQLite database, you'll see the following error:
+ *PDOException: SQLSTATE[HY000]: General error: 1 Cannot add a NOT NULL
+ column with default value NULL*. Add a ``nullable=true`` option to the
+ ``description`` property to fix the problem.
+
.. caution::
There is a `limit of 767 bytes for the index key prefix`_ when using
@@ -234,9 +242,9 @@ your production database up-to-date.
Migrations & Adding more Fields
-------------------------------
-But what if you need to add a new field property to ``Product``, like a ``description``?
-You can edit the class to add the new property. But, you can also use ``make:entity``
-again:
+But what if you need to add a new field property to ``Product``, like a
+``description``? You can edit the class to add the new property. But, you can
+also use ``make:entity`` again:
.. code-block:: terminal
@@ -332,25 +340,25 @@ to experiment:
$ php bin/console make:controller ProductController
Inside the controller, you can create a new ``Product`` object, set data on it,
-and save it!
-
-.. code-block:: php
+and save it::
// src/Controller/ProductController.php
namespace App\Controller;
// ...
use App\Entity\Product;
+ use Doctrine\ORM\EntityManagerInterface;
+ use Symfony\Component\HttpFoundation\Response;
class ProductController extends AbstractController
{
/**
- * @Route("/product", name="product")
+ * @Route("/product", name="create_product")
*/
- public function index()
+ public function createProduct(): Response
{
// you can fetch the EntityManager via $this->getDoctrine()
- // or you can add an argument to your action: index(EntityManagerInterface $entityManager)
+ // or you can add an argument to the action: createProduct(EntityManagerInterface $entityManager)
$entityManager = $this->getDoctrine()->getManager();
$product = new Product();
@@ -386,17 +394,17 @@ Take a look at the previous example in more detail:
.. _doctrine-entity-manager:
-* **line 16** The ``$this->getDoctrine()->getManager()`` method gets Doctrine's
+* **line 18** The ``$this->getDoctrine()->getManager()`` method gets Doctrine's
*entity manager* object, which is the most important object in Doctrine. It's
responsible for saving objects to, and fetching objects from, the database.
-* **lines 18-21** In this section, you instantiate and work with the ``$product``
+* **lines 20-23** In this section, you instantiate and work with the ``$product``
object like any other normal PHP object.
-* **line 24** The ``persist($product)`` call tells Doctrine to "manage" the
+* **line 26** The ``persist($product)`` call tells Doctrine to "manage" the
``$product`` object. This does **not** cause a query to be made to the database.
-* **line 27** When the ``flush()`` method is called, Doctrine looks through
+* **line 29** 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's data doesn't
exist in the database, so the entity manager executes an ``INSERT`` query,
@@ -410,6 +418,78 @@ Take a look at the previous example in more detail:
Whether you're creating or updating objects, the workflow is always the same: Doctrine
is smart enough to know if it should INSERT or UPDATE your entity.
+.. _automatic_object_validation:
+
+Validating Objects
+------------------
+
+:doc:`The Symfony validator ` reuses Doctrine metadata to perform
+some basic validation tasks::
+
+ // src/Controller/ProductController.php
+ namespace App\Controller;
+
+ use App\Entity\Product;
+ use Symfony\Component\HttpFoundation\Response;
+ use Symfony\Component\Validator\Validator\ValidatorInterface;
+ // ...
+
+ class ProductController extends AbstractController
+ {
+ /**
+ * @Route("/product", name="create_product")
+ */
+ public function createProduct(ValidatorInterface $validator): Response
+ {
+ $product = new Product();
+ // This will trigger an error: the column isn't nullable in the database
+ $product->setName(null);
+ // This will trigger a type mismatch error: an integer is expected
+ $product->setPrice('1999');
+
+ // ...
+
+ $errors = $validator->validate($product);
+ if (count($errors) > 0) {
+ return new Response((string) $errors, 400);
+ }
+
+ // ...
+ }
+ }
+
+Although the ``Product`` entity doesn't define any explicit
+:doc:`validation configuration `, Symfony introspects the Doctrine
+mapping configuration to infer some validation rules. For example, given that
+the ``name`` property can't be ``null`` in the database, a
+:doc:`NotNull constraint ` is added automatically
+to the property (if it doesn't contain that constraint already).
+
+The following table summarizes the mapping between Doctrine metadata and
+the corresponding validation constraints added automatically by Symfony:
+
+================== ========================================================= =====
+Doctrine attribute Validation constraint Notes
+================== ========================================================= =====
+``nullable=false`` :doc:`NotNull ` Requires installing the :doc:`PropertyInfo component `
+``type`` :doc:`Type ` Requires installing the :doc:`PropertyInfo component `
+``unique=true`` :doc:`UniqueEntity `
+``length`` :doc:`Length `
+================== ========================================================= =====
+
+Because :doc:`the Form component ` as well as `API Platform`_ internally
+use the Validator component, all your forms and web APIs will also automatically
+benefit from these automatic validation constraints.
+
+This automatic validation is a nice feature to improve your productivity, but it
+doesn't replace the validation configuration entirely. You still need to add
+some :doc:`validation constraints ` to ensure that data
+provided by the user is correct.
+
+.. versionadded:: 4.3
+
+ The automatic validation has been added in Symfony 4.3.
+
Fetching Objects from the Database
----------------------------------
@@ -504,9 +584,7 @@ for you automatically! First, install the bundle in case you don't have it:
Now, simplify your controller::
// src/Controller/ProductController.php
-
use App\Entity\Product;
- // ...
/**
* @Route("/product/{id}", name="product_show")
@@ -593,11 +671,11 @@ But what if you need a more complex query? When you generated your entity with
use App\Entity\Product;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
- use Symfony\Bridge\Doctrine\RegistryInterface;
+ use Doctrine\Common\Persistence\ManagerRegistry;
class ProductRepository extends ServiceEntityRepository
{
- public function __construct(RegistryInterface $registry)
+ public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Product::class);
}
@@ -615,7 +693,7 @@ a new method for this to your repository::
// ...
class ProductRepository extends ServiceEntityRepository
{
- public function __construct(RegistryInterface $registry)
+ public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Product::class);
}
@@ -729,7 +807,7 @@ data into your project (i.e. "fixture data"). Install it with:
.. code-block:: terminal
- $ composer require doctrine/doctrine-fixtures-bundle --dev
+ $ composer require --dev doctrine/doctrine-fixtures-bundle
Then, use the ``make:fixtures`` command to generate an empty fixture class:
@@ -799,7 +877,6 @@ Learn more
.. _`Doctrine's Mapping Types 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
-.. _`Mapping Types Documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#property-mapping
.. _`Reserved SQL keywords documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#quoting-reserved-words
.. _`DoctrineMongoDBBundle`: https://symfony.com/doc/current/bundles/DoctrineMongoDBBundle/index.html
.. _`DoctrineFixturesBundle`: https://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html
@@ -810,3 +887,4 @@ Learn more
.. _`ParamConverter`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
.. _`limit of 767 bytes for the index key prefix`: https://dev.mysql.com/doc/refman/5.6/en/innodb-restrictions.html
.. _`Doctrine screencast series`: https://symfonycasts.com/screencast/symfony-doctrine
+.. _`API Platform`: https://api-platform.com/docs/core/validation/
diff --git a/doctrine/associations.rst b/doctrine/associations.rst
index 24bad95a950..62eaec04955 100644
--- a/doctrine/associations.rst
+++ b/doctrine/associations.rst
@@ -188,7 +188,7 @@ the ``Product`` entity (and getter & setter methods):
+ https://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
@@ -196,7 +196,7 @@ the ``Product`` entity (and getter & setter methods):
field="category"
target-entity="App\Entity\Category"
inversed-by="products">
-
+
@@ -264,14 +264,14 @@ class that will hold these objects:
+ https://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
+ mapped-by="category"/>
-
+
-
+
-
+
@@ -95,9 +103,9 @@ a ``postPersist()`` method, which will be called when the event is dispatched::
// src/EventListener/SearchIndexer.php
namespace App\EventListener;
+ use App\Entity\Product;
// for Doctrine < 2.4: use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
- use App\Entity\Product;
class SearchIndexer
{
@@ -140,10 +148,10 @@ interface and have an event method for each event it subscribes to::
// src/EventListener/SearchIndexerSubscriber.php
namespace App\EventListener;
+ use App\Entity\Product;
use Doctrine\Common\EventSubscriber;
// for Doctrine < 2.4: use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
- use App\Entity\Product;
use Doctrine\ORM\Events;
class SearchIndexerSubscriber implements EventSubscriber
@@ -188,57 +196,16 @@ interface and have an event method for each event it subscribes to::
For a full reference, see chapter `The Event System`_ in the Doctrine documentation.
-Lazy loading for Event Listeners
---------------------------------
-
-One subtle difference between listeners and subscribers is that Symfony can load
-entity listeners lazily. This means that your listener class will only be fetched
-from the service container (and thus be instantiated) once the event it is linked
-to actually fires.
-
-Lazy loading might give you a slight performance improvement when your listener
-runs for events that rarely fire. Also, it can help you when you run into
-*circular dependency issues* that may occur when your listener service in turn
-depends on the DBAL connection.
-
-To mark a listener service as lazily loaded, just add the ``lazy`` attribute
-to the tag like so:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- services:
- App\EventListener\SearchIndexer:
- tags:
- - { name: doctrine.event_listener, event: postPersist, lazy: true }
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- use App\EventListener\SearchIndexer;
-
- $container
- ->autowire(SearchIndexer::class)
- ->addTag('doctrine.event_listener', ['event' => 'postPersist', 'lazy' => 'true'])
- ;
+Performance Considerations
+--------------------------
-.. note::
+One important difference between listeners and subscribers is that Symfony loads
+entity listeners lazily. This means that the listener classes are only fetched
+from the service container (and instantiated) if the related event is actually
+fired.
- Marking an event listener as ``lazy`` has nothing to do with lazy service
- definitions which are described :doc:`in their own article `
+That's why it is preferable to use entity listeners instead of subscribers
+whenever possible.
Priorities for Event Listeners
------------------------------
@@ -271,10 +238,10 @@ numbers mean that listeners are invoked earlier.
-
+
-
+
@@ -282,8 +249,8 @@ numbers mean that listeners are invoked earlier.
.. code-block:: php
// config/services.php
- use AppBundle\EventListener\MyHighPriorityListener;
- use AppBundle\EventListener\MyLowPriorityListener;
+ use App\EventListener\MyHighPriorityListener;
+ use App\EventListener\MyLowPriorityListener;
$container
->autowire(MyHighPriorityListener::class)
@@ -297,3 +264,4 @@ numbers mean that listeners are invoked earlier.
.. _`The Event System`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html
.. _`the DoctrineBundle documentation`: https://symfony.com/doc/current/bundles/DoctrineBundle/entity-listeners.html
+.. _`DoctrineMongoDBBundle documentation`: https://symfony.com/doc/current/bundles/DoctrineMongoDBBundle/index.html
diff --git a/doctrine/lifecycle_callbacks.rst b/doctrine/lifecycle_callbacks.rst
index 42344c8098d..9b62ae16f74 100644
--- a/doctrine/lifecycle_callbacks.rst
+++ b/doctrine/lifecycle_callbacks.rst
@@ -15,6 +15,9 @@ callbacks. This is not necessary if you're using YAML or XML for your mapping.
.. code-block:: php-annotations
+ // src/Entity/Product.php
+ use Doctrine\ORM\Mapping as ORM;
+
/**
* @ORM\Entity()
* @ORM\HasLifecycleCallbacks()
@@ -33,6 +36,7 @@ the current date, only when the entity is first persisted (i.e. inserted):
.. code-block:: php-annotations
// src/Entity/Product.php
+ use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\PrePersist
@@ -58,12 +62,12 @@ the current date, only when the entity is first persisted (i.e. inserted):
+ https://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
-
+
diff --git a/doctrine/multiple_entity_managers.rst b/doctrine/multiple_entity_managers.rst
index 84867cde69f..4c73ef6f741 100644
--- a/doctrine/multiple_entity_managers.rst
+++ b/doctrine/multiple_entity_managers.rst
@@ -76,9 +76,9 @@ The following configuration code shows how you can configure two entity managers
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:doctrine="http://symfony.com/schema/dic/doctrine"
xsi:schemaLocation="http://symfony.com/schema/dic/services
- http://symfony.com/schema/dic/services/services-1.0.xsd
+ https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/doctrine
- http://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd">
+ https://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd">
diff --git a/doctrine/pdo_session_storage.rst b/doctrine/pdo_session_storage.rst
index e53eb161804..ee6efc80de9 100644
--- a/doctrine/pdo_session_storage.rst
+++ b/doctrine/pdo_session_storage.rst
@@ -28,7 +28,7 @@ To use it, first register a new handler service:
# If you're using Doctrine & want to re-use that connection, then:
# comment-out the above 2 lines and uncomment the line below
- # - !service { class: PDO, factory: 'database_connection:getWrappedConnection' }
+ # - !service { class: PDO, factory: ['@database_connection', 'getWrappedConnection'] }
# If you get transaction issues (e.g. after login) uncomment the line below
# - { lock_mode: 1 }
@@ -40,8 +40,8 @@ To use it, first register a new handler service:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:framework="http://symfony.com/schema/dic/symfony"
xsi:schemaLocation="http://symfony.com/schema/dic/services
- http://symfony.com/schema/dic/services/services-1.0.xsd
- http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
+ https://symfony.com/schema/dic/services/services-1.0.xsd
+ https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
@@ -68,8 +68,8 @@ To use it, first register a new handler service:
.. tip::
- Configure the database credentials as
- :doc:`parameters defined with environment variables `
+ Configure the database credentials
+ :ref:`using environment variables in the config file `
to make your application more secure.
Next, tell Symfony to use your service as the session handler:
@@ -133,7 +133,7 @@ a second array argument to ``PdoSessionHandler``:
+ https://symfony.com/schema/dic/services/services-1.0.xsd">
@@ -150,7 +150,6 @@ a second array argument to ``PdoSessionHandler``:
.. code-block:: php
// config/services.php
-
use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
// ...
@@ -220,7 +219,7 @@ MySQL
`sess_data` BLOB NOT NULL,
`sess_time` INTEGER UNSIGNED NOT NULL,
`sess_lifetime` MEDIUMINT NOT NULL
- ) COLLATE utf8_bin, ENGINE = InnoDB;
+ ) COLLATE utf8mb4_bin, ENGINE = InnoDB;
.. note::
diff --git a/doctrine/registration_form.rst b/doctrine/registration_form.rst
index 9b6fbea28fb..ff3ee4d4be2 100644
--- a/doctrine/registration_form.rst
+++ b/doctrine/registration_form.rst
@@ -21,7 +21,7 @@ Make sure MakerBundle is installed:
.. code-block:: terminal
- $ composer require symfony/maker-bundle --dev
+ $ composer require --dev symfony/maker-bundle
If you need any other dependencies, MakerBundle will tell you when you run each
command.
@@ -50,6 +50,7 @@ To easiest way to build your registration form is by using the ``make:registrati
command:
.. versionadded:: 1.11
+
The ``make:registration-form`` was introduced in MakerBundle 1.11.0.
.. code-block:: terminal
@@ -76,8 +77,8 @@ The form class for the registration form will look something like this::
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
- use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Length;
+ use Symfony\Component\Validator\Constraints\NotBlank;
class RegistrationFormType extends AbstractType
{
@@ -96,7 +97,7 @@ The form class for the registration form will look something like this::
new Length([
'min' => 6,
'minMessage' => 'Your password should be at least {{ limit }} characters',
- 'max' => 4096
+ 'max' => 4096,
]),
],
])
@@ -170,7 +171,7 @@ saves the user::
// do anything else you need here, like send an email
- return $this->redirect('app_homepage');
+ return $this->redirectToRoute('app_homepage');
}
return $this->render('registration/register.html.twig', [
@@ -184,7 +185,7 @@ register.html.twig
The template renders the form:
-.. code-block:: twig
+.. code-block:: html+twig
{% extends 'base.html.twig' %}
@@ -214,22 +215,22 @@ To do this, add a ``termsAccepted`` field to your form, but set its
// src/Form/UserType.php
// ...
- use Symfony\Component\Validator\Constraints\IsTrue;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
+ use Symfony\Component\Validator\Constraints\IsTrue;
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
- ->add('email', EmailType::class);
+ ->add('email', EmailType::class)
// ...
->add('termsAccepted', CheckboxType::class, [
'mapped' => false,
'constraints' => new IsTrue(),
])
- );
+ ;
}
}
diff --git a/doctrine/resolve_target_entity.rst b/doctrine/resolve_target_entity.rst
index d6ec5c3ccac..a60dfe3e604 100644
--- a/doctrine/resolve_target_entity.rst
+++ b/doctrine/resolve_target_entity.rst
@@ -40,12 +40,11 @@ brevity) to explain how to set up and use the ``ResolveTargetEntityListener``.
A Customer entity::
// src/Entity/Customer.php
-
namespace App\Entity;
- use Doctrine\ORM\Mapping as ORM;
use Acme\CustomerBundle\Entity\Customer as BaseCustomer;
use Acme\InvoiceBundle\Model\InvoiceSubjectInterface;
+ use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
@@ -60,11 +59,10 @@ A Customer entity::
An Invoice entity::
// src/Acme/InvoiceBundle/Entity/Invoice.php
-
namespace Acme\InvoiceBundle\Entity;
- use Doctrine\ORM\Mapping AS ORM;
use Acme\InvoiceBundle\Model\InvoiceSubjectInterface;
+ use Doctrine\ORM\Mapping as ORM;
/**
* Represents an Invoice.
@@ -84,7 +82,6 @@ An Invoice entity::
An InvoiceSubjectInterface::
// src/Acme/InvoiceBundle/Model/InvoiceSubjectInterface.php
-
namespace Acme\InvoiceBundle\Model;
/**
@@ -128,9 +125,9 @@ about the replacement:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:doctrine="http://symfony.com/schema/dic/doctrine"
xsi:schemaLocation="http://symfony.com/schema/dic/services
- http://symfony.com/schema/dic/services/services-1.0.xsd
+ https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/doctrine
- http://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd">
+ https://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd">
diff --git a/doctrine/reverse_engineering.rst b/doctrine/reverse_engineering.rst
index ae446e33140..6b84cb17709 100644
--- a/doctrine/reverse_engineering.rst
+++ b/doctrine/reverse_engineering.rst
@@ -56,7 +56,7 @@ table fields.
.. code-block:: terminal
- $ php bin/console doctrine:mapping:import 'App\Entity' annotation --path=src/Entity
+ $ php bin/console doctrine:mapping:import "App\Entity" annotation --path=src/Entity
This command line tool asks Doctrine to introspect the database and generate
new PHP classes with annotation metadata into ``src/Entity``. This generates two
@@ -68,7 +68,7 @@ files: ``BlogPost.php`` and ``BlogComment.php``.
.. code-block:: terminal
- $ php bin/console doctrine:mapping:import 'App\Entity' xml --path=config/doctrine
+ $ php bin/console doctrine:mapping:import "App\Entity" xml --path=config/doctrine
In this case, make sure to adapt your mapping configuration accordingly:
@@ -94,7 +94,7 @@ The generated PHP classes now have properties and annotation metadata, but they
do *not* have any getter or setter methods. If you generated XML or YAML metadata,
you don't even have the PHP classes!
-To generate the missing getter/setter methods (or to *create* the classes if neceesary),
+To generate the missing getter/setter methods (or to *create* the classes if necessary),
run:
.. code-block:: terminal
@@ -111,5 +111,4 @@ run:
The generated entities are now ready to be used. Have fun!
-.. _`Doctrine tools documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/tools.html#reverse-engineering
-.. _`doctrine/doctrine#729`: https://github.com/doctrine/DoctrineBundle/issues/729
+.. _`Doctrine tools documentation`: https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/tools.html#reverse-engineering
diff --git a/email.rst b/email.rst
index f588eee0993..5cef4971914 100644
--- a/email.rst
+++ b/email.rst
@@ -1,8 +1,13 @@
.. index::
single: Emails
-How to Send an Email
-====================
+Swift Mailer
+============
+
+.. note::
+
+ In Symfony 4.3, the :doc:`Mailer ` component was introduced and can
+ be used instead of Swift Mailer.
Symfony provides a mailer feature based on the popular `Swift Mailer`_ library
via the `SwiftMailerBundle`_. This mailer supports sending messages with your
@@ -12,7 +17,7 @@ own mail servers as well as using popular email providers like `Mandrill`_,
Installation
------------
-In applications using :doc:`Symfony Flex `, run this command to
+In applications using :ref:`Symfony Flex `, run this command to
install the Swift Mailer based mailer before using it:
.. code-block:: terminal
@@ -39,10 +44,15 @@ environment variable in the ``.env`` file:
# use this to disable email delivery
MAILER_URL=null://localhost
- # use this to configure a traditional SMTP server (make sure to URL-encode the
- # values of the username and password if they contain non-alphanumeric characters
- # such as '+', '@', ':' and '*', which are reserved in URLs)
- MAILER_URL=smtp://localhost:25?encryption=ssl&auth_mode=login&username=&password=
+ # use this to configure a traditional SMTP server
+ MAILER_URL=smtp://localhost:465?encryption=ssl&auth_mode=login&username=&password=
+
+.. caution::
+
+ If the username, password or host contain any character considered special in a
+ URI (such as ``+``, ``@``, ``$``, ``#``, ``/``, ``:``, ``*``, ``!``), you must
+ encode them. See `RFC 3986`_ for the full list of reserved characters or use the
+ :phpfunction:`urlencode` function to encode them.
Refer to the :doc:`SwiftMailer configuration reference `
for the detailed explanation of all the available config options.
@@ -68,16 +78,16 @@ sending an email is pretty straightforward::
),
'text/html'
)
- /*
- * If you also want to include a plaintext version of the message
+
+ // you can remove the following code if you don't define a text version for your emails
->addPart(
$this->renderView(
+ // templates/emails/registration.txt.twig
'emails/registration.txt.twig',
['name' => $name]
),
'text/plain'
)
- */
;
$mailer->send($message);
@@ -89,7 +99,7 @@ To keep things decoupled, the email body has been stored in a template and
rendered with the ``renderView()`` method. The ``registration.html.twig``
template might look something like this:
-.. code-block:: html+jinja
+.. code-block:: html+twig
{# templates/emails/registration.html.twig #}
You did it! You registered!
@@ -122,9 +132,9 @@ setting up a regular SMTP server. To do that, update the ``MAILER_URL`` of your
# username is your full Gmail or Google Apps email address
MAILER_URL=gmail://username:password@localhost
-The ``gmail`` transport is simply a shortcut that uses the ``smtp`` transport,
-``ssl`` encryption, ``login`` auth mode and ``smtp.gmail.com`` host. If your app
-uses other encryption or auth mode, you must override those values
+The ``gmail`` transport is a shortcut that uses the ``smtp`` transport, ``ssl``
+encryption, ``login`` auth mode and ``smtp.gmail.com`` host. If your app uses
+other encryption or auth mode, you must override those values
(:doc:`see mailer config reference `):
.. code-block:: bash
@@ -134,7 +144,7 @@ uses other encryption or auth mode, you must override those values
If your Gmail account uses 2-Step-Verification, you must `generate an App password`_
and use it as the value of the mailer password. You must also ensure that you
-`allow less secure apps to access your Gmail account`_.
+`allow less secure applications to access your Gmail account`_.
Using Cloud Services to Send Emails
-----------------------------------
@@ -153,16 +163,499 @@ file. For example, for `Amazon SES`_ (Simple Email Service):
Use the same technique for other mail services, as most of the time there is
nothing more to it than configuring an SMTP endpoint.
-Learn more
-----------
+How to Work with Emails during Development
+------------------------------------------
+
+When developing an application which sends email, you will often
+not want to actually send the email to the specified recipient during
+development. If you are using the SwiftmailerBundle with Symfony, you
+can achieve this through configuration settings without having to make
+any changes to your application's code at all. There are two main choices
+when it comes to handling email during development: (a) disabling the
+sending of email altogether or (b) sending all email to a specific
+address (with optional exceptions).
+
+Disabling Sending
+~~~~~~~~~~~~~~~~~
+
+You can disable sending email by setting the ``disable_delivery`` option to
+``true``, which is the default value used by Symfony in the ``test`` environment
+(email messages will continue to be sent in the other environments):
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/test/swiftmailer.yaml
+ swiftmailer:
+ disable_delivery: true
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/test/swiftmailer.php
+ $container->loadFromExtension('swiftmailer', [
+ 'disable_delivery' => "true",
+ ]);
+
+.. _sending-to-a-specified-address:
+
+Sending to a Specified Address(es)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can also choose to have all email sent to a specific address or a list of addresses, instead
+of the address actually specified when sending the message. This can be done
+via the ``delivery_addresses`` option:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/dev/swiftmailer.yaml
+ swiftmailer:
+ delivery_addresses: ['dev@example.com']
+
+ .. code-block:: xml
+
+
+
+
+
+
+ dev@example.com
+
+
+
+ .. code-block:: php
+
+ // config/packages/dev/swiftmailer.php
+ $container->loadFromExtension('swiftmailer', [
+ 'delivery_addresses' => ['dev@example.com'],
+ ]);
+
+Now, suppose you're sending an email to ``recipient@example.com`` in a controller::
+
+ public function index($name, \Swift_Mailer $mailer)
+ {
+ $message = (new \Swift_Message('Hello Email'))
+ ->setFrom('send@example.com')
+ ->setTo('recipient@example.com')
+ ->setBody(
+ $this->renderView(
+ // templates/hello/email.txt.twig
+ 'hello/email.txt.twig',
+ ['name' => $name]
+ )
+ )
+ ;
+ $mailer->send($message);
+
+ return $this->render(...);
+ }
+
+In the ``dev`` environment, the email will instead be sent to ``dev@example.com``.
+Swift Mailer will add an extra header to the email, ``X-Swift-To``, containing
+the replaced address, so you can still see who it would have been sent to.
+
+.. note::
+
+ In addition to the ``to`` addresses, this will also stop the email being
+ sent to any ``CC`` and ``BCC`` addresses set for it. Swift Mailer will add
+ additional headers to the email with the overridden addresses in them.
+ These are ``X-Swift-Cc`` and ``X-Swift-Bcc`` for the ``CC`` and ``BCC``
+ addresses respectively.
+
+.. _sending-to-a-specified-address-but-with-exceptions:
+
+Sending to a Specified Address but with Exceptions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Suppose you want to have all email redirected to a specific address,
+(like in the above scenario to ``dev@example.com``). But then you may want
+email sent to some specific email addresses to go through after all, and
+not be redirected (even if it is in the dev environment). This can be done
+by adding the ``delivery_whitelist`` option:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/dev/swiftmailer.yaml
+ swiftmailer:
+ delivery_addresses: ['dev@example.com']
+ delivery_whitelist:
+ # all email addresses matching these regexes will be delivered
+ # like normal, as well as being sent to dev@example.com
+ - '/@specialdomain\.com$/'
+ - '/^admin@mydomain\.com$/'
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+ /@specialdomain\.com$/
+ /^admin@mydomain\.com$/
+ dev@example.com
+
+
+
+ .. code-block:: php
+
+ // config/packages/dev/swiftmailer.php
+ $container->loadFromExtension('swiftmailer', [
+ 'delivery_addresses' => ["dev@example.com"],
+ 'delivery_whitelist' => [
+ // all email addresses matching these regexes will be delivered
+ // like normal, as well as being sent to dev@example.com
+ '/@specialdomain\.com$/',
+ '/^admin@mydomain\.com$/',
+ ],
+ ]);
+
+In the above example all email messages will be redirected to ``dev@example.com``
+and messages sent to the ``admin@mydomain.com`` address or to any email address
+belonging to the domain ``specialdomain.com`` will also be delivered as normal.
+
+.. caution::
+
+ The ``delivery_whitelist`` option is ignored unless the ``delivery_addresses`` option is defined.
+
+Viewing from the Web Debug Toolbar
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can view any email sent during a single response when you are in the
+``dev`` environment using the web debug toolbar. The email icon in the toolbar
+will show how many emails were sent. If you click it, a report will open
+showing the details of the sent emails.
+
+If you're sending an email and then immediately redirecting to another page,
+the web debug toolbar will not display an email icon or a report on the next
+page.
+
+Instead, you can set the ``intercept_redirects`` option to ``true`` in the
+``dev`` environment, which will cause the redirect to stop and allow you to open
+the report with details of the sent emails.
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/dev/web_profiler.yaml
+ web_profiler:
+ intercept_redirects: true
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/dev/web_profiler.php
+ $container->loadFromExtension('web_profiler', [
+ 'intercept_redirects' => 'true',
+ ]);
+
+.. tip::
+
+ Alternatively, you can open the profiler after the redirect and search
+ by the submit URL used on the previous request (e.g. ``/contact/handle``).
+ The profiler's search feature allows you to load the profiler information
+ for any past requests.
+
+.. tip::
+
+ In addition to the features provided by Symfony, there are applications that
+ can help you test emails during application development, like `MailCatcher`_
+ and `MailHog`_.
+
+How to Spool Emails
+-------------------
+
+The default behavior of the Symfony mailer is to send the email messages
+immediately. You may, however, want to avoid the performance hit of the
+communication to the email server, which could cause the user to wait for the
+next page to load while the email is sending. This can be avoided by choosing to
+"spool" the emails instead of sending them directly.
+
+This makes the mailer to not attempt to send the email message but instead save
+it somewhere such as a file. Another process can then read from the spool and
+take care of sending the emails in the spool. Currently only spooling to file or
+memory is supported.
+
+.. _email-spool-memory:
+
+Spool Using Memory
+~~~~~~~~~~~~~~~~~~
+
+When you use spooling to store the emails to memory, they will get sent right
+before the kernel terminates. This means the email only gets sent if the whole
+request got executed without any unhandled exception or any errors. To configure
+this spool, use the following configuration:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/swiftmailer.yaml
+ swiftmailer:
+ # ...
+ spool: { type: memory }
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/swiftmailer.php
+ $container->loadFromExtension('swiftmailer', [
+ // ...
+ 'spool' => ['type' => 'memory'],
+ ]);
+
+.. _spool-using-a-file:
+
+Spool Using Files
+~~~~~~~~~~~~~~~~~
+
+When you use the filesystem for spooling, Symfony creates a folder in the given
+path for each mail service (e.g. "default" for the default service). This folder
+will contain files for each email in the spool. So make sure this directory is
+writable by Symfony (or your webserver/php)!
+
+In order to use the spool with files, use the following configuration:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/swiftmailer.yaml
+ swiftmailer:
+ # ...
+ spool:
+ type: file
+ path: /path/to/spooldir
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/swiftmailer.php
+ $container->loadFromExtension('swiftmailer', [
+ // ...
+
+ 'spool' => [
+ 'type' => 'file',
+ 'path' => '/path/to/spooldir',
+ ],
+ ]);
+
+.. tip::
+
+ If you want to store the spool somewhere with your project directory,
+ remember that you can use the ``%kernel.project_dir%`` parameter to reference
+ the project's root:
+
+ .. code-block:: yaml
+
+ path: '%kernel.project_dir%/var/spool'
+
+Now, when your app sends an email, it will not actually be sent but instead
+added to the spool. Sending the messages from the spool is done separately.
+There is a console command to send the messages in the spool:
+
+.. code-block:: terminal
+
+ $ APP_ENV=prod php bin/console swiftmailer:spool:send
+
+It has an option to limit the number of messages to be sent:
+
+.. code-block:: terminal
+
+ $ APP_ENV=prod php bin/console swiftmailer:spool:send --message-limit=10
+
+You can also set the time limit in seconds:
+
+.. code-block:: terminal
+
+ $ APP_ENV=prod php bin/console swiftmailer:spool:send --time-limit=10
+
+In practice you will not want to run this manually. Instead, the console command
+should be triggered by a cron job or scheduled task and run at a regular
+interval.
+
+.. caution::
+
+ When you create a message with SwiftMailer, it generates a ``Swift_Message``
+ class. If the ``swiftmailer`` service is lazy loaded, it generates instead a
+ proxy class named ``Swift_Message_``.
+
+ If you use the memory spool, this change is transparent and has no impact.
+ But when using the filesystem spool, the message class is serialized in
+ a file with the randomized class name. The problem is that this random
+ class name changes on every cache clear. So if you send a mail and then you
+ clear the cache, the message will not be unserializable.
+
+ On the next execution of ``swiftmailer:spool:send`` an error will raise because
+ the class ``Swift_Message_`` doesn't exist (anymore).
+
+ The solutions are either to use the memory spool or to load the
+ ``swiftmailer`` service without the ``lazy`` option (see :doc:`/service_container/lazy_services`).
+
+How to Test that an Email is Sent in a Functional Test
+------------------------------------------------------
+
+Sending emails with Symfony is pretty straightforward thanks to the
+SwiftmailerBundle, which leverages the power of the `Swift Mailer`_ library.
+
+To functionally test that an email was sent, and even assert the email subject,
+content or any other headers, you can use :doc:`the Symfony Profiler `.
+
+Start with a controller action that sends an email::
+
+ public function sendEmail($name, \Swift_Mailer $mailer)
+ {
+ $message = (new \Swift_Message('Hello Email'))
+ ->setFrom('send@example.com')
+ ->setTo('recipient@example.com')
+ ->setBody('You should see me from the profiler!')
+ ;
+
+ $mailer->send($message);
+
+ // ...
+ }
+
+In your functional test, use the ``swiftmailer`` collector on the profiler
+to get information about the messages sent on the previous request::
+
+ // tests/Controller/MailControllerTest.php
+ namespace App\Tests\Controller;
+
+ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
+
+ class MailControllerTest extends WebTestCase
+ {
+ public function testMailIsSentAndContentIsOk()
+ {
+ $client = static::createClient();
+
+ // enables the profiler for the next request (it does nothing if the profiler is not available)
+ $client->enableProfiler();
+
+ $crawler = $client->request('POST', '/path/to/above/action');
+
+ $mailCollector = $client->getProfile()->getCollector('swiftmailer');
+
+ // checks that an email was sent
+ $this->assertSame(1, $mailCollector->getMessageCount());
+
+ $collectedMessages = $mailCollector->getMessages();
+ $message = $collectedMessages[0];
+
+ // Asserting email data
+ $this->assertInstanceOf('Swift_Message', $message);
+ $this->assertSame('Hello Email', $message->getSubject());
+ $this->assertSame('send@example.com', key($message->getFrom()));
+ $this->assertSame('recipient@example.com', key($message->getTo()));
+ $this->assertSame(
+ 'You should see me from the profiler!',
+ $message->getBody()
+ );
+ }
+ }
+
+Troubleshooting
+~~~~~~~~~~~~~~~
+
+Problem: The Collector Object Is ``null``
+.........................................
+
+The email collector is only available when the profiler is enabled and collects
+information, as explained in :doc:`/testing/profiling`.
-.. toctree::
- :maxdepth: 1
+Problem: The Collector Doesn't Contain the Email
+................................................
- email/dev_environment
- email/spool
- email/testing
+If a redirection is performed after sending the email (for example when you send
+an email after a form is processed and before redirecting to another page), make
+sure that the test client doesn't follow the redirects, as explained in
+:doc:`/testing`. Otherwise, the collector will contain the information of the
+redirected page and the email won't be accessible.
+.. _`MailCatcher`: https://github.com/sj26/mailcatcher
+.. _`MailHog`: https://github.com/mailhog/MailHog
.. _`Swift Mailer`: http://swiftmailer.org/
.. _`SwiftMailerBundle`: https://github.com/symfony/swiftmailer-bundle
.. _`Creating Messages`: https://swiftmailer.symfony.com/docs/messages.html
@@ -170,4 +663,5 @@ Learn more
.. _`SendGrid`: https://sendgrid.com/
.. _`Amazon SES`: http://aws.amazon.com/ses/
.. _`generate an App password`: https://support.google.com/accounts/answer/185833
-.. _`allow less secure apps to access your Gmail account`: https://support.google.com/accounts/answer/6010255
+.. _`allow less secure applications to access your Gmail account`: https://support.google.com/accounts/answer/6010255
+.. _`RFC 3986`: https://www.ietf.org/rfc/rfc3986.txt
diff --git a/email/dev_environment.rst b/email/dev_environment.rst
deleted file mode 100644
index 15c440dbe22..00000000000
--- a/email/dev_environment.rst
+++ /dev/null
@@ -1,252 +0,0 @@
-.. index::
- single: Emails; In development
-
-How to Work with Emails during Development
-==========================================
-
-When developing an application which sends email, you will often
-not want to actually send the email to the specified recipient during
-development. If you are using the default Symfony mailer, you
-can achieve this through configuration settings without having to
-make any changes to your application's code at all. There are two main
-choices when it comes to handling email during development: (a) disabling the
-sending of email altogether or (b) sending all email to a specific
-address (with optional exceptions).
-
-Disabling Sending
------------------
-
-You can disable sending email by setting the ``disable_delivery`` option to
-``true``, which is the default value used by Symfony in the ``test`` environment
-(email messages will continue to be sent in the other environments):
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # config/packages/test/swiftmailer.yaml
- swiftmailer:
- disable_delivery: true
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // config/packages/test/swiftmailer.php
- $container->loadFromExtension('swiftmailer', [
- 'disable_delivery' => "true",
- ]);
-
-.. _sending-to-a-specified-address:
-
-Sending to a Specified Address(es)
-----------------------------------
-
-You can also choose to have all email sent to a specific address or a list of addresses, instead
-of the address actually specified when sending the message. This can be done
-via the ``delivery_addresses`` option:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # config/packages/dev/swiftmailer.yaml
- swiftmailer:
- delivery_addresses: ['dev@example.com']
-
- .. code-block:: xml
-
-
-
-
-
-
- dev@example.com
-
-
-
- .. code-block:: php
-
- // config/packages/dev/swiftmailer.php
- $container->loadFromExtension('swiftmailer', [
- 'delivery_addresses' => ['dev@example.com'],
- ]);
-
-Now, suppose you're sending an email to ``recipient@example.com`` in a controller::
-
- public function index($name, \Swift_Mailer $mailer)
- {
- $message = (new \Swift_Message('Hello Email'))
- ->setFrom('send@example.com')
- ->setTo('recipient@example.com')
- ->setBody(
- $this->renderView(
- 'HelloBundle:Hello:email.txt.twig',
- ['name' => $name]
- )
- )
- ;
- $mailer->send($message);
-
- return $this->render(...);
- }
-
-In the ``dev`` environment, the email will instead be sent to ``dev@example.com``.
-Swift Mailer will add an extra header to the email, ``X-Swift-To``, containing
-the replaced address, so you can still see who it would have been sent to.
-
-.. note::
-
- In addition to the ``to`` addresses, this will also stop the email being
- sent to any ``CC`` and ``BCC`` addresses set for it. Swift Mailer will add
- additional headers to the email with the overridden addresses in them.
- These are ``X-Swift-Cc`` and ``X-Swift-Bcc`` for the ``CC`` and ``BCC``
- addresses respectively.
-
-.. _sending-to-a-specified-address-but-with-exceptions:
-
-Sending to a Specified Address but with Exceptions
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Suppose you want to have all email redirected to a specific address,
-(like in the above scenario to ``dev@example.com``). But then you may want
-email sent to some specific email addresses to go through after all, and
-not be redirected (even if it is in the dev environment). This can be done
-by adding the ``delivery_whitelist`` option:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # config/packages/dev/swiftmailer.yaml
- swiftmailer:
- delivery_addresses: ['dev@example.com']
- delivery_whitelist:
- # all email addresses matching these regexes will be delivered
- # like normal, as well as being sent to dev@example.com
- - '/@specialdomain\.com$/'
- - '/^admin@mydomain\.com$/'
-
- .. code-block:: xml
-
-
-
-
-
-
-
- /@specialdomain\.com$/
- /^admin@mydomain\.com$/
- dev@example.com
-
-
-
- .. code-block:: php
-
- // config/packages/dev/swiftmailer.php
- $container->loadFromExtension('swiftmailer', [
- 'delivery_addresses' => ["dev@example.com"],
- 'delivery_whitelist' => [
- // all email addresses matching these regexes will be delivered
- // like normal, as well as being sent to dev@example.com
- '/@specialdomain\.com$/',
- '/^admin@mydomain\.com$/',
- ],
- ]);
-
-In the above example all email messages will be redirected to ``dev@example.com``
-and messages sent to the ``admin@mydomain.com`` address or to any email address
-belonging to the domain ``specialdomain.com`` will also be delivered as normal.
-
-.. caution::
-
- The ``delivery_whitelist`` option is ignored unless the ``delivery_addresses`` option is defined.
-
-Viewing from the Web Debug Toolbar
-----------------------------------
-
-You can view any email sent during a single response when you are in the
-``dev`` environment using the web debug toolbar. The email icon in the toolbar
-will show how many emails were sent. If you click it, a report will open
-showing the details of the sent emails.
-
-If you're sending an email and then immediately redirecting to another page,
-the web debug toolbar will not display an email icon or a report on the next
-page.
-
-Instead, you can set the ``intercept_redirects`` option to ``true`` in the
-``dev`` environment, which will cause the redirect to stop and allow you to open
-the report with details of the sent emails.
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # config/packages/dev/web_profiler.yaml
- web_profiler:
- intercept_redirects: true
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // config/packages/dev/web_profiler.php
- $container->loadFromExtension('web_profiler', [
- 'intercept_redirects' => 'true',
- ]);
-
-.. tip::
-
- Alternatively, you can open the profiler after the redirect and search
- by the submit URL used on the previous request (e.g. ``/contact/handle``).
- The profiler's search feature allows you to load the profiler information
- for any past requests.
-
-.. tip::
-
- In addition to the features provided by Symfony, there are applications that
- can help you test emails during application development, like `MailCatcher`_
- and `MailHog`_.
-
-.. _`MailCatcher`: https://github.com/sj26/mailcatcher
-.. _`MailHog`: https://github.com/mailhog/MailHog
diff --git a/email/spool.rst b/email/spool.rst
deleted file mode 100644
index 695242a1d52..00000000000
--- a/email/spool.rst
+++ /dev/null
@@ -1,164 +0,0 @@
-.. index::
- single: Emails; Spooling
-
-How to Spool Emails
-===================
-
-The default behavior of the Symfony mailer is to send the email messages
-immediately. You may, however, want to avoid the performance hit of the
-communication to the email server, which could cause the user to wait for the
-next page to load while the email is sending. This can be avoided by choosing to
-"spool" the emails instead of sending them directly.
-
-This makes the mailer to not attempt to send the email message but instead save
-it somewhere such as a file. Another process can then read from the spool and
-take care of sending the emails in the spool. Currently only spooling to file or
-memory is supported.
-
-.. _email-spool-memory:
-
-Spool Using Memory
-------------------
-
-When you use spooling to store the emails to memory, they will get sent right
-before the kernel terminates. This means the email only gets sent if the whole
-request got executed without any unhandled exception or any errors. To configure
-this spool, use the following configuration:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # config/packages/swiftmailer.yaml
- swiftmailer:
- # ...
- spool: { type: memory }
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // config/packages/swiftmailer.php
- $container->loadFromExtension('swiftmailer', [
- // ...
- 'spool' => ['type' => 'memory'],
- ]);
-
-.. _spool-using-a-file:
-
-Spool Using Files
-------------------
-
-When you use the filesystem for spooling, Symfony creates a folder in the given
-path for each mail service (e.g. "default" for the default service). This folder
-will contain files for each email in the spool. So make sure this directory is
-writable by Symfony (or your webserver/php)!
-
-In order to use the spool with files, use the following configuration:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # config/packages/swiftmailer.yaml
- swiftmailer:
- # ...
- spool:
- type: file
- path: /path/to/spooldir
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // config/packages/swiftmailer.php
- $container->loadFromExtension('swiftmailer', [
- // ...
-
- 'spool' => [
- 'type' => 'file',
- 'path' => '/path/to/spooldir',
- ],
- ]);
-
-.. tip::
-
- If you want to store the spool somewhere with your project directory,
- remember that you can use the ``%kernel.project_dir%`` parameter to reference
- the project's root:
-
- .. code-block:: yaml
-
- path: '%kernel.project_dir%/var/spool'
-
-Now, when your app sends an email, it will not actually be sent but instead
-added to the spool. Sending the messages from the spool is done separately.
-There is a console command to send the messages in the spool:
-
-.. code-block:: terminal
-
- $ php bin/console swiftmailer:spool:send --env=prod
-
-It has an option to limit the number of messages to be sent:
-
-.. code-block:: terminal
-
- $ php bin/console swiftmailer:spool:send --message-limit=10 --env=prod
-
-You can also set the time limit in seconds:
-
-.. code-block:: terminal
-
- $ php bin/console swiftmailer:spool:send --time-limit=10 --env=prod
-
-You will most likely not want to run this command manually in reality. Instead, the
-console command should be triggered by a cron job or scheduled task and run
-at a regular interval.
-
-.. caution::
-
- When you create a message with SwiftMailer, it generates a ``Swift_Message``
- class. If the ``swiftmailer`` service is lazy loaded, it generates instead a
- proxy class named ``Swift_Message_``.
-
- If you use the memory spool, this change is transparent and has no impact.
- But when using the filesystem spool, the message class is serialized in
- a file with the randomized class name. The problem is that this random
- class name changes on every cache clear. So if you send a mail and then you
- clear the cache, the message will not be unserializable.
-
- On the next execution of ``swiftmailer:spool:send`` an error will raise because
- the class ``Swift_Message_`` doesn't exist (anymore).
-
- The solutions are either to use the memory spool or to load the
- ``swiftmailer`` service without the ``lazy`` option (see :doc:`/service_container/lazy_services`).
diff --git a/email/testing.rst b/email/testing.rst
deleted file mode 100644
index 1da89c7315b..00000000000
--- a/email/testing.rst
+++ /dev/null
@@ -1,85 +0,0 @@
-.. index::
- single: Emails; Testing
-
-How to Test that an Email is Sent in a Functional Test
-======================================================
-
-Sending emails with Symfony is pretty straightforward thanks to the
-SwiftmailerBundle, which leverages the power of the `Swift Mailer`_ library.
-
-To functionally test that an email was sent, and even assert the email subject,
-content or any other headers, you can use :doc:`the Symfony Profiler `.
-
-Start with a simple controller action that sends an email::
-
- public function sendEmail($name, \Swift_Mailer $mailer)
- {
- $message = (new \Swift_Message('Hello Email'))
- ->setFrom('send@example.com')
- ->setTo('recipient@example.com')
- ->setBody('You should see me from the profiler!')
- ;
-
- $mailer->send($message);
-
- return $this->render(...);
- }
-
-In your functional test, use the ``swiftmailer`` collector on the profiler
-to get information about the messages sent on the previous request::
-
- // tests/Controller/MailControllerTest.php
- namespace App\Tests\Controller;
-
- use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
-
- class MailControllerTest extends WebTestCase
- {
- public function testMailIsSentAndContentIsOk()
- {
- $client = static::createClient();
-
- // enables the profiler for the next request (it does nothing if the profiler is not available)
- $client->enableProfiler();
-
- $crawler = $client->request('POST', '/path/to/above/action');
-
- $mailCollector = $client->getProfile()->getCollector('swiftmailer');
-
- // checks that an email was sent
- $this->assertSame(1, $mailCollector->getMessageCount());
-
- $collectedMessages = $mailCollector->getMessages();
- $message = $collectedMessages[0];
-
- // Asserting email data
- $this->assertInstanceOf('Swift_Message', $message);
- $this->assertSame('Hello Email', $message->getSubject());
- $this->assertSame('send@example.com', key($message->getFrom()));
- $this->assertSame('recipient@example.com', key($message->getTo()));
- $this->assertSame(
- 'You should see me from the profiler!',
- $message->getBody()
- );
- }
- }
-
-Troubleshooting
----------------
-
-Problem: The Collector Object Is ``null``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The email collector is only available when the profiler is enabled and collects
-information, as explained in :doc:`/testing/profiling`.
-
-Problem: The Collector Doesn't Contain the Email
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-If a redirection is performed after sending the email (for example when you send
-an email after a form is processed and before redirecting to another page), make
-sure that the test client doesn't follow the redirects, as explained in
-:doc:`/testing`. Otherwise, the collector will contain the information of the
-redirected page and the email won't be accessible.
-
-.. _`Swift Mailer`: http://swiftmailer.org/
diff --git a/event_dispatcher.rst b/event_dispatcher.rst
index 276f9d7fec4..9ba1850d58d 100644
--- a/event_dispatcher.rst
+++ b/event_dispatcher.rst
@@ -26,13 +26,13 @@ The most common way to listen to an event is to register an **event listener**::
// src/EventListener/ExceptionListener.php
namespace App\EventListener;
- use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpFoundation\Response;
+ use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
class ExceptionListener
{
- public function onKernelException(GetResponseForExceptionEvent $event)
+ public function onKernelException(ExceptionEvent $event)
{
// You get the exception object from the received event
$exception = $event->getException();
@@ -63,10 +63,16 @@ The most common way to listen to an event is to register an **event listener**::
.. tip::
Each event receives a slightly different type of ``$event`` object. For
- the ``kernel.exception`` event, it is :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent`.
+ the ``kernel.exception`` event, it is :class:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent`.
Check out the :doc:`Symfony events reference ` to see
what type of object each event provides.
+.. versionadded:: 4.3
+
+ The :class:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent` class was
+ introduced in Symfony 4.3. In previous versions it was called
+ ``Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent``.
+
Now that the class is created, you need to register it as a service and
notify Symfony that it is a "listener" on the ``kernel.exception`` event by
using a special "tag":
@@ -88,11 +94,11 @@ using a special "tag":
+ https://symfony.com/schema/dic/services/services-1.0.xsd">
-
+
@@ -119,10 +125,6 @@ listener class:
method (which makes event listeners invokable);
#. If the ``_invoke()`` method is not defined either, throw an exception.
-.. versionadded:: 4.1
- The support of the ``__invoke()`` method to create invokable event listeners
- was introduced in Symfony 4.1.
-
.. note::
There is an optional attribute for the ``kernel.event_listener`` tag called
@@ -155,7 +157,7 @@ listen to the same ``kernel.exception`` event::
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
- use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
+ use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class ExceptionSubscriber implements EventSubscriberInterface
@@ -164,25 +166,25 @@ listen to the same ``kernel.exception`` event::
{
// return the subscribed events, their methods and priorities
return [
- KernelEvents::EXCEPTION => [
- ['processException', 10],
- ['logException', 0],
- ['notifyException', -10],
- ]
+ KernelEvents::EXCEPTION => [
+ ['processException', 10],
+ ['logException', 0],
+ ['notifyException', -10],
+ ],
];
}
- public function processException(GetResponseForExceptionEvent $event)
+ public function processException(ExceptionEvent $event)
{
// ...
}
- public function logException(GetResponseForExceptionEvent $event)
+ public function logException(ExceptionEvent $event)
{
// ...
}
- public function notifyException(GetResponseForExceptionEvent $event)
+ public function notifyException(ExceptionEvent $event)
{
// ...
}
@@ -211,11 +213,11 @@ or a "sub request"::
// src/EventListener/RequestListener.php
namespace App\EventListener;
- use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+ use Symfony\Component\HttpKernel\Event\RequestEvent;
class RequestListener
{
- public function onKernelRequest(GetResponseEvent $event)
+ public function onKernelRequest(RequestEvent $event)
{
if (!$event->isMasterRequest()) {
// don't do anything if it's not the master request
diff --git a/event_dispatcher/before_after_filters.rst b/event_dispatcher/before_after_filters.rst
index 1353a2d8595..4202918ba92 100644
--- a/event_dispatcher/before_after_filters.rst
+++ b/event_dispatcher/before_after_filters.rst
@@ -52,7 +52,7 @@ First, define some token configuration as parameters:
+ https://symfony.com/schema/dic/services/services-1.0.xsd">
@@ -114,9 +114,9 @@ event subscribers, you can learn more about them at :doc:`/event_dispatcher`::
namespace App\EventSubscriber;
use App\Controller\TokenAuthenticatedController;
- use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
- use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+ use Symfony\Component\HttpKernel\Event\ControllerEvent;
+ use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
class TokenSubscriber implements EventSubscriberInterface
@@ -128,7 +128,7 @@ event subscribers, you can learn more about them at :doc:`/event_dispatcher`::
$this->tokens = $tokens;
}
- public function onKernelController(FilterControllerEvent $event)
+ public function onKernelController(ControllerEvent $event)
{
$controller = $event->getController();
@@ -188,7 +188,7 @@ For example, take the ``TokenSubscriber`` from the previous example and first
record the authentication token inside the request attributes. This will
serve as a basic flag that this request underwent token authentication::
- public function onKernelController(FilterControllerEvent $event)
+ public function onKernelController(ControllerEvent $event)
{
// ...
@@ -208,9 +208,9 @@ This will look for the ``auth_token`` flag on the request object and set a custo
header on the response if it's found::
// add the new use statement at the top of your file
- use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
+ use Symfony\Component\HttpKernel\Event\ResponseEvent;
- public function onKernelResponse(FilterResponseEvent $event)
+ public function onKernelResponse(ResponseEvent $event)
{
// check to see if onKernelController marked this as a token "auth'ed" request
if (!$token = $event->getRequest()->attributes->get('auth_token')) {
diff --git a/event_dispatcher/method_behavior.rst b/event_dispatcher/method_behavior.rst
index b7d16962bd9..7d93d074353 100644
--- a/event_dispatcher/method_behavior.rst
+++ b/event_dispatcher/method_behavior.rst
@@ -19,7 +19,7 @@ method::
{
// dispatch an event before the method
$event = new BeforeSendMailEvent($subject, $message);
- $this->dispatcher->dispatch('mailer.pre_send', $event);
+ $this->dispatcher->dispatch($event, 'mailer.pre_send');
// get $foo and $bar from the event, they may have been modified
$subject = $event->getSubject();
@@ -30,7 +30,7 @@ method::
// do something after the method
$event = new AfterSendMailEvent($returnValue);
- $this->dispatcher->dispatch('mailer.post_send', $event);
+ $this->dispatcher->dispatch($event, 'mailer.post_send');
return $event->getReturnValue();
}
@@ -44,7 +44,7 @@ events. For example, ``BeforeSendMailEvent`` might look like this::
// src/Event/BeforeSendMailEvent.php
namespace App\Event;
- use Symfony\Component\EventDispatcher\Event;
+ use Symfony\Contracts\EventDispatcher\Event;
class BeforeSendMailEvent extends Event
{
@@ -83,7 +83,7 @@ And the ``AfterSendMailEvent`` even like this::
// src/Event/AfterSendMailEvent.php
namespace App\Event;
- use Symfony\Component\EventDispatcher\Event;
+ use Symfony\Contracts\EventDispatcher\Event;
class AfterSendMailEvent extends Event
{
@@ -114,8 +114,8 @@ could listen to the ``mailer.post_send`` event and change the method's return va
// src/EventSubscriber/MailPostSendSubscriber.php
namespace App\EventSubscriber;
- use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use App\Event\AfterSendMailEvent;
+ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class MailPostSendSubscriber implements EventSubscriberInterface
{
diff --git a/form/action_method.rst b/form/action_method.rst
deleted file mode 100644
index 64067720898..00000000000
--- a/form/action_method.rst
+++ /dev/null
@@ -1,132 +0,0 @@
-.. index::
- single: Forms; Changing the action and method
-
-How to Change the Action and Method of a Form
-=============================================
-
-By default, a form will be submitted via an HTTP POST request to the same
-URL under which the form was rendered. Sometimes you want to change these
-parameters. You can do so in a few different ways.
-
-If you use the :class:`Symfony\\Component\\Form\\FormBuilder` to build your
-form, you can use ``setAction()`` and ``setMethod()``:
-
-.. configuration-block::
-
- .. code-block:: php-symfony
-
- // src/Controller/DefaultController.php
- namespace App\Controller;
-
- use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
- use Symfony\Component\Form\Extension\Core\Type\DateType;
- use Symfony\Component\Form\Extension\Core\Type\SubmitType;
- use Symfony\Component\Form\Extension\Core\Type\TextType;
-
- class DefaultController extends AbstractController
- {
- public function new()
- {
- // ...
-
- $form = $this->createFormBuilder($task)
- ->setAction($this->generateUrl('target_route'))
- ->setMethod('GET')
- ->add('task', TextType::class)
- ->add('dueDate', DateType::class)
- ->add('save', SubmitType::class)
- ->getForm();
-
- // ...
- }
- }
-
- .. code-block:: php-standalone
-
- use Symfony\Component\Form\Forms;
- use Symfony\Component\Form\Extension\Core\Type\DateType;
- use Symfony\Component\Form\Extension\Core\Type\FormType;
- use Symfony\Component\Form\Extension\Core\Type\SubmitType;
- use Symfony\Component\Form\Extension\Core\Type\TextType;
-
- // ...
-
- $formFactoryBuilder = Forms::createFormFactoryBuilder();
-
- // Form factory builder configuration ...
-
- $formFactory = $formFactoryBuilder->getFormFactory();
-
- $form = $formFactory->createBuilder(FormType::class, $task)
- ->setAction('...')
- ->setMethod('GET')
- ->add('task', TextType::class)
- ->add('dueDate', DateType::class)
- ->add('save', SubmitType::class)
- ->getForm();
-
-.. note::
-
- This example assumes that you've created a route called ``target_route``
- that points to the controller that processes the form.
-
-When using a form type class, you can pass the action and method as form
-options:
-
-.. configuration-block::
-
- .. code-block:: php-symfony
-
- // src/Controller/DefaultController.php
- namespace App\Controller;
-
- use App\Form\TaskType;
- use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
-
- class DefaultController extends AbstractController
- {
- public function new()
- {
- // ...
-
- $form = $this->createForm(TaskType::class, $task, [
- 'action' => $this->generateUrl('target_route'),
- 'method' => 'GET',
- ]);
-
- // ...
- }
- }
-
- .. code-block:: php-standalone
-
- use App\Form\TaskType;
- use Symfony\Component\Form\Forms;
-
- $formFactoryBuilder = Forms::createFormFactoryBuilder();
-
- // Form factory builder configuration ...
-
- $formFactory = $formFactoryBuilder->getFormFactory();
-
- $form = $formFactory->create(TaskType::class, $task, [
- 'action' => '...',
- 'method' => 'GET',
- ]);
-
-Finally, you can override the action and method in the template by passing them
-to the ``form()`` or the ``form_start()`` helper functions:
-
-.. code-block:: html+twig
-
- {# templates/default/new.html.twig #}
- {{ form_start(form, {'action': path('target_route'), 'method': 'GET'}) }}
-
-.. note::
-
- If the form's method is not GET or POST, but PUT, PATCH or DELETE, Symfony
- will insert a hidden field with the name ``_method`` that stores this method.
- The form will be submitted in a normal POST request, but Symfony's router
- is capable of detecting the ``_method`` parameter and will interpret it as
- a PUT, PATCH or DELETE request. See the :ref:`configuration-framework-http_method_override`
- option.
diff --git a/form/bootstrap4.rst b/form/bootstrap4.rst
index ce2e7ca860d..330ed2a165e 100644
--- a/form/bootstrap4.rst
+++ b/form/bootstrap4.rst
@@ -42,9 +42,9 @@ configuration:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:twig="http://symfony.com/schema/dic/twig"
xsi:schemaLocation="http://symfony.com/schema/dic/services
- http://symfony.com/schema/dic/services/services-1.0.xsd
+ https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/twig
- http://symfony.com/schema/dic/twig/twig-1.0.xsd">
+ https://symfony.com/schema/dic/twig/twig-1.0.xsd">
bootstrap_4_layout.html.twig
@@ -66,7 +66,7 @@ configuration:
If you prefer to apply the Bootstrap styles on a form to form basis, include the
``form_theme`` tag in the templates where those forms are used:
-.. code-block:: twig
+.. code-block:: html+twig
{# ... #}
{# this tag only applies to the forms defined in this template #}
@@ -95,7 +95,7 @@ Bootstrap 4 has a feature called "`custom forms`_". You can enable that on your
Symfony Form ``RadioType`` and ``CheckboxType`` by adding a class called ``radio-custom``
and ``checkbox-custom`` respectively.
-.. code-block:: html+twig
+.. code-block:: twig
{{ form_row(form.myRadio, {label_attr: {class: 'radio-custom'} }) }}
{{ form_row(form.myCheckbox, {label_attr: {class: 'checkbox-custom'} }) }}
@@ -111,6 +111,5 @@ Form errors are rendered **inside** the ```` element to make sure there
is a strong connection between the error and its `` ``, as required by the
`WCAG 2.0 standard`_.
-.. _`their documentation`: https://getbootstrap.com/docs/4.1/
.. _`WCAG 2.0 standard`: https://www.w3.org/TR/WCAG20/
.. _`custom forms`: https://getbootstrap.com/docs/4.1/components/forms/#custom-forms
diff --git a/form/create_custom_field_type.rst b/form/create_custom_field_type.rst
index cbe88040737..cc9ffe65df1 100644
--- a/form/create_custom_field_type.rst
+++ b/form/create_custom_field_type.rst
@@ -4,27 +4,37 @@
How to Create a Custom Form Field Type
======================================
-Symfony comes with a bunch of core field types available for building forms.
-However there are situations where you may want to create a custom form field
-type for a specific purpose. This article assumes you need a field definition
-that holds a shipping option, based on the existing choice field. This section
-explains how the field is defined and how you can customize its layout.
-
-Defining the Field Type
------------------------
-
-In order to create the custom field type, first you have to create the class
-representing the field. In this situation the class holding the field type
-will be called ``ShippingType`` and the file will be stored in the default location
-for form fields, which is ``App\Form\Type``. Make sure the field extends
-:class:`Symfony\\Component\\Form\\AbstractType`::
+Symfony comes with :doc:`tens of form types ` (called
+"form fields" in other projects) ready to use in your applications. However,
+it's common to create custom form types to solve specific purposes in your
+projects.
+
+Creating Form Types Based on Symfony Built-in Types
+---------------------------------------------------
+
+The easiest way to create a form type is to base it on one of the
+:doc:`existing form types `. Imagine that your project
+displays a list of "shipping options" as a ```` HTML element. This can
+be implemented with a :doc:`ChoiceType ` where the
+``choices`` option is set to the list of available shipping options.
+
+However, if you use the same form type in several forms, repeating the list of
+``choices`` everytime you use it quickly becomes boring. In this example, a
+better solution is to create a custom form type based on ``ChoiceType``. The
+custom type looks and behaves like a ``ChoiceType`` but the list of choices is
+already populated with the shipping options so you don't need to define them.
+
+Form types are PHP classes that implement :class:`Symfony\\Component\\Form\\FormTypeInterface`,
+but you should instead extend from :class:`Symfony\\Component\\Form\\AbstractType`,
+which already implements that interface and provides some utilities.
+By convention they are stored in the ``src/Form/Type/`` directory::
// src/Form/Type/ShippingType.php
namespace App\Form\Type;
use Symfony\Component\Form\AbstractType;
- use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
+ use Symfony\Component\OptionsResolver\OptionsResolver;
class ShippingType extends AbstractType
{
@@ -45,238 +55,408 @@ for form fields, which is ``App\Form\Type``. Make sure the field extends
}
}
-.. tip::
+The ``configureOptions()`` method, which is explained later in this article,
+defines the options that can be configured for the form type and sets the
+default value of those options.
- The location of this file is not important - the ``Form\Type`` directory
- is just a convention.
+The ``getParent()`` method defines which is the form type used as the base of
+this type. In this case, the type extends from ``ChoiceType`` to reuse all of
+the logic and rendering of that field type.
-Here, the return value of the ``getParent()`` function indicates that you're
-extending the ``ChoiceType`` field. This means that, by default, you inherit
-all of the logic and rendering of that field type. To see some of the logic,
-check out the `ChoiceType`_ class. There are three methods that are particularly
-important:
+.. note::
-.. _form-type-methods-explanation:
+ The PHP class extension mechanism and the Symfony form field extension
+ mechanism are not the same. The parent type returned in ``getParent()`` is
+ what Symfony uses to build and manage the field type. Making the PHP class
+ extend from ``AbstractType`` is only a convenience way of implementing the
+ required ``FormTypeInterface``.
-``buildForm()``
- Each field type has a ``buildForm()`` method, which is where
- you configure and build any field(s). Notice that this is the same method
- you use to setup *your* forms, and it works the same here.
+Now you can add this form type when :doc:`creating Symfony forms `::
-``buildView()``
- This method is used to set any extra variables you'll
- need when rendering your field in a template. For example, in `ChoiceType`_,
- a ``multiple`` variable is set and used in the template to set (or not
- set) the ``multiple`` attribute on the ``select`` field. See
- `Creating a Template for the Field`_ for more details.
+ // src/Form/Type/OrderType.php
+ namespace App\Form\Type;
-``configureOptions()``
- This defines options for your form type that
- can be used in ``buildForm()`` and ``buildView()``. There are a lot of
- options common to all fields (see :doc:`/reference/forms/types/form`),
- but you can create any others that you need here.
+ use App\Form\Type\ShippingType;
+ use Symfony\Component\Form\AbstractType;
+ use Symfony\Component\Form\FormBuilderInterface;
-.. tip::
+ class OrderType extends AbstractType
+ {
+ public function buildForm(FormBuilderInterface $builder, array $options)
+ {
+ $builder
+ // ...
+ ->add('shipping', ShippingType::class)
+ ;
+ }
- If you're creating a field that consists of many fields, then be sure
- to set your "parent" type as ``form`` or something that extends ``form``.
- Also, if you need to modify the "view" of any of your child types from
- your parent type, use the ``finishView()`` method.
+ // ...
+ }
-The goal of this field was to extend the choice type to enable selection of the
-shipping type. This is achieved by fixing the ``choices`` to a list of available
-shipping options.
+That's all. The ``shipping`` form field will be rendered correctly in any
+template because it reuses the templating logic defined by its parent type
+``ChoiceType``. If you prefer, you can also define a template for your custom
+types, as explained later in this article.
-Creating a Template for the Field
----------------------------------
+Creating Form Types Created From Scratch
+----------------------------------------
-Each field type is rendered by a template fragment whose name is determined in
-part by the class name of your type. Read the :ref:`from fragment naming `
-rules for more details.
+Some form types are so specific to your projects that they cannot be based on
+any :doc:`existing form types ` because they are too
+different. Consider an application that wants to reuse in different forms the
+following set of fields as the "postal address":
-.. note::
+.. raw:: html
- The first part of the prefix (e.g. ``shipping``) comes from the class name
- (``ShippingType`` -> ``shipping``). This can be controlled by overriding ``getBlockPrefix()``
- in ``ShippingType``.
+
-.. caution::
+As explained above, form types are PHP classes that implement
+:class:`Symfony\\Component\\Form\\FormTypeInterface`, although it's more
+convenient to extend instead from :class:`Symfony\\Component\\Form\\AbstractType`::
- When the name of your form class matches any of the built-in field types,
- your form might not be rendered correctly. A form type named
- ``App\Form\PasswordType`` will have the same block name as the
- built-in ``PasswordType`` and won't be rendered correctly. Override the
- ``getBlockPrefix()`` method to return a unique block prefix (e.g.
- ``app_password``) to avoid collisions.
+ // src/Form/Type/PostalAddressType.php
+ namespace App\Form\Type;
-In this case, since the parent field is ``ChoiceType``, you don't *need* to do
-any work as the custom field type will automatically be rendered like a ``ChoiceType``.
-But for the sake of this example, suppose that when your field is "expanded"
-(i.e. radio buttons or checkboxes, instead of a select field), you want to
-always render it in a ``ul`` element. In your form theme template (see above
-link for details), create a ``shipping_widget`` block to handle this:
+ use Symfony\Component\Form\AbstractType;
+ use Symfony\Component\Form\Extension\Core\Type\FormType;
+ use Symfony\Component\OptionsResolver\OptionsResolver;
-.. code-block:: html+twig
+ class PostalAddressType extends AbstractType
+ {
+ // ...
+ }
- {# templates/form/fields.html.twig #}
- {% block shipping_widget %}
- {% spaceless %}
- {% if expanded %}
-
- {% for child in form %}
-
- {{ form_widget(child) }}
- {{ form_label(child) }}
-
- {% endfor %}
-
- {% else %}
- {# let the choice widget render the select tag #}
- {{ block('choice_widget') }}
- {% endif %}
- {% endspaceless %}
- {% endblock %}
+When a form type doesn't extend from another specific type, there's no need to
+implement the ``getParent()`` method (Symfony will make the type extend from the
+generic :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\FormType`,
+which is the parent of all the other types).
-.. tip::
+These are the most important methods that a form type class can define:
- You can further customize the template used to render each children of the
- choice type. The block to override in that case is named "block name" +
- ``_entry`` + "element name" (``label``, ``errors`` or ``widget``) (e.g. to
- customize the labels of the children of the Shipping widget you'd need to
- define ``{% block shipping_entry_label %} ... {% endblock %}``).
+.. _form-type-methods-explanation:
-.. note::
+``buildForm()``
+ It adds and configures other types into this type. It's the same method used
+ when :ref:`creating Symfony form classes `.
- Make sure the correct widget prefix is used. In this example the name should
- be ``shipping_widget`` (see :ref:`form fragment naming `
- rules). Further, the main config file should point to the custom form template
- so that it's used when rendering all forms.
+``buildView()``
+ It sets any extra variables you'll need when rendering the field in a template.
- When using Twig this is:
+``configureOptions()``
+ It defines the options configurable when using the form type, which are also
+ the options that can be used in ``buildForm()`` and ``buildView()`` methods.
- .. configuration-block::
+``finishView()``
+ When creating a form type that consists of many fields, this method allows
+ to modify the "view" of any of those fields. For any other use case, it's
+ recommended to use instead the ``buildView()`` method.
- .. code-block:: yaml
+Defining the Form Type
+~~~~~~~~~~~~~~~~~~~~~~
- # config/packages/twig.yaml
- twig:
- form_themes:
- - 'form/fields.html.twig'
+Start by adding the ``buildForm()`` method to configure all the types included
+in the postal address. For the moment, all fields are of type ``TextType``::
- .. code-block:: xml
+ // src/Form/Type/PostalAddressType.php
+ namespace App\Form\Type;
-
-
-
+ use Symfony\Component\Form\AbstractType;
+ use Symfony\Component\Form\Extension\Core\Type\TextType;
+ use Symfony\Component\Form\FormBuilderInterface;
+
+ class PostalAddressType extends AbstractType
+ {
+ // ...
-
- form/fields.html.twig
-
-
+ public function buildForm(FormBuilderInterface $builder, array $options)
+ {
+ $builder
+ ->add('addressLine1', TextType::class, [
+ 'help' => 'Street address, P.O. box, company name',
+ ])
+ ->add('addressLine2', TextType::class, [
+ 'help' => 'Apartment, suite, unit, building, floor',
+ ])
+ ->add('city', TextType::class)
+ ->add('state', TextType::class, [
+ 'label' => 'State',
+ ])
+ ->add('zipCode', TextType::class, [
+ 'label' => 'ZIP Code',
+ ])
+ ;
+ }
+ }
- .. code-block:: php
+.. tip::
- // config/packages/twig.php
- $container->loadFromExtension('twig', [
- 'form_themes' => [
- 'form/fields.html.twig',
- ],
- ]);
+ Run the following command to verify that the form type was successfully
+ registered in the application:
- For the PHP templating engine, your configuration should look like this:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- # config/packages/framework.yaml
- framework:
- templating:
- form:
- resources:
- - ':form:fields.html.php'
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
- :form:fields.html.php
-
-
-
-
-
- .. code-block:: php
-
- // config/packages/framework.php
- $container->loadFromExtension('framework', [
- 'templating' => [
- 'form' => [
- 'resources' => [
- ':form:fields.html.php',
- ],
- ],
- ],
- ]);
+ .. code-block:: terminal
-Using the Field Type
---------------------
+ $ php bin/console debug:form
-You can now use your custom field type immediately, by creating a
-new instance of the type in one of your forms::
+This form type is ready to use it inside other forms and all its fields will be
+correctly rendered in any template::
// src/Form/Type/OrderType.php
namespace App\Form\Type;
+ use App\Form\Type\PostalAddressType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
- use App\Form\Type\ShippingType;
class OrderType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
- $builder->add('shipping_code', ShippingType::class, [
- 'placeholder' => 'Choose a delivery option',
- ]);
+ $builder
+ // ...
+ ->add('address', PostalAddressType::class)
+ ;
}
+
+ // ...
}
-But this only works because the ``ShippingType()`` is very simple. What if
-the shipping codes were stored in configuration or in a database? The next
-section explains how more complex field types solve this problem.
+However, the real power of custom form types is achieved with custom form
+options (to make them flexible) and with custom templates (to make them look
+better).
-.. _form-field-service:
-.. _creating-your-field-type-as-a-service:
+.. _form-type-config-options:
-Accessing Services and Config
------------------------------
+Adding Configuration Options for the Form Type
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-If you need to access :doc:`services ` from your form class,
-add a ``__construct()`` method like normal::
+Imagine that your project requires to make the ``PostalAddressType``
+configurable in two ways:
- // src/Form/Type/ShippingType.php
+* In addition to "address line 1" and "address line 2", some addresses should be
+ allowed to display an "address line 3" to store extended address information;
+* Instead of displaying a free text input, some addresses should be able to
+ restrict the possible states to a given list.
+
+This is solved with "form type options", which allow to configure the behavior
+of the form types. The options are defined in the ``configureOptions()`` method
+and you can use all the :doc:`OptionsResolver component features `
+to define, validate and process their values::
+
+ // src/Form/Type/PostalAddressType.php
namespace App\Form\Type;
+ use Symfony\Component\Form\AbstractType;
+ use Symfony\Component\Form\Extension\Core\Type\TextType;
+ use Symfony\Component\OptionsResolver\Options;
+ use Symfony\Component\OptionsResolver\OptionsResolver;
+
+ class PostalAddressType extends AbstractType
+ {
+ // ...
+
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ // this defines the available options and their default values when
+ // they are not configured explicitly when using the form type
+ $resolver->setDefaults([
+ 'allowed_states' => null,
+ 'is_extended_address' => false,
+ ]);
+
+ // optionally you can also restrict the options type or types (to get
+ // automatic type validation and useful error messages for end users)
+ $resolver->setAllowedTypes('allowed_states', ['null', 'string', 'array']);
+ $resolver->setAllowedTypes('is_extended_address', 'bool');
+
+ // optionally you can transform the given values for the options to
+ // simplify the further processing of those options
+ $resolver->setNormalizer('allowed_states', static function (Options $options, $states) {
+ if (null === $states) {
+ return $states;
+ }
+
+ if (is_string($states)) {
+ $states = (array) $states;
+ }
+
+ return array_combine(array_values($states), array_values($states));
+ });
+ }
+ }
+
+Now you can configure these options when using the form type::
+
+ // src/Form/Type/OrderType.php
+ // ...
+
+ class OrderType extends AbstractType
+ {
+ public function buildForm(FormBuilderInterface $builder, array $options)
+ {
+ $builder
+ // ...
+ ->add('address', PostalAddressType::class, [
+ 'is_extended_address' => true,
+ 'allowed_states' => ['CA', 'FL', 'TX'],
+ // in this example, this config would also be valid:
+ // 'allowed_states' => 'CA',
+ ])
+ ;
+ }
+
+ // ...
+ }
+
+The last step is to use these options when building the form::
+
+ // src/Form/Type/PostalAddressType.php
// ...
+
+ class PostalAddressType extends AbstractType
+ {
+ // ...
+
+ public function buildForm(FormBuilderInterface $builder, array $options)
+ {
+ // ...
+
+ if (true === $options['is_extended_address']) {
+ $builder->add('addressLine3', TextType::class, [
+ 'help' => 'Extended address info',
+ ]);
+ }
+
+ if (null !== $options['allowed_states']) {
+ $builder->add('state', ChoiceType::class, [
+ 'choices' => $options['allowed_states'],
+ ]);
+ } else {
+ $builder->add('state', TextType::class, [
+ 'label' => 'State/Province/Region',
+ ]);
+ }
+ }
+ }
+
+Creating the Form Type Template
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, custom form types will be rendered using the
+:doc:`form themes ` configured in the application. However,
+for some types you may prefer to create a custom template in order to customize
+how they look or their HTML structure.
+
+First, create a new Twig template anywhere in the application to store the
+fragments used to render the types:
+
+.. code-block:: twig
+
+ {# templates/form/custom_types.html.twig #}
+
+ {# ... here you will add the Twig code ... #}
+
+Then, update the :ref:`form_themes option ` to
+add this new template at the beginning of the list (the first one overrides the
+rest of files):
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/twig.yaml
+ twig:
+ form_themes:
+ - 'form/custom_types.html.twig'
+ - '...'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ form/custom_types.html.twig
+ ...
+
+
+
+ .. code-block:: php
+
+ // config/packages/twig.php
+ $container->loadFromExtension('twig', [
+ 'form_themes' => [
+ 'form/custom_types.html.twig',
+ '...',
+ ],
+ ]);
+
+The last step is to create the actual Twig template that will render the type.
+The template contents depend on which HTML, CSS and JavaScript frameworks and
+libraries are used in your application:
+
+.. code-block:: html+twig
+
+ {# templates/form/custom_types.html.twig #}
+ {% block postal_address_row %}
+ {% for child in form.children if not child.rendered %}
+
+ {{ form_label(child) }}
+ {{ form_widget(child) }}
+ {{ form_help(child) }}
+ {{ form_errors(child) }}
+
+ {% endfor %}
+ {% endblock %}
+
+.. note::
+
+ Symfony 4.2 deprecated calling ``FormRenderer::searchAndRenderBlock`` for
+ fields that have already been rendered. That's why the previous example
+ includes the ``... if not child.rendered`` statement.
+
+The first part of the Twig block name (e.g. ``postal_address``) comes from the
+class name (``PostalAddressType`` -> ``postal_address``). This can be controlled
+by overriding the ``getBlockPrefix()`` method in ``PostalAddressType``. The
+second part of the Twig block name (e.g. ``_row``) defines which form type part
+is being rendered (row, widget, help, errors, etc.)
+
+The article about form themes explains the
+:ref:`form fragment naming rules ` in detail. The
+following diagram shows some of the Twig block names defined in this example:
+
+.. raw:: html
+
+
+
+.. caution::
+
+ When the name of your form class matches any of the built-in field types,
+ your form might not be rendered correctly. A form type named
+ ``App\Form\PasswordType`` will have the same block name as the built-in
+ ``PasswordType`` and won't be rendered correctly. Override the
+ ``getBlockPrefix()`` method to return a unique block prefix (e.g.
+ ``app_password``) to avoid collisions.
+
+Passing Variables to the Form Type Template
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Symfony passes a series of variables to the template used to render the form
+type. You can also pass your own variables, which can be based on the options
+defined by the form or be completely independent::
+
+
+ // src/Form/Type/PostalAddressType.php
use Doctrine\ORM\EntityManagerInterface;
+ // ...
- class ShippingType extends AbstractType
+ class PostalAddressType extends AbstractType
{
private $entityManager;
@@ -285,19 +465,39 @@ add a ``__construct()`` method like normal::
$this->entityManager = $entityManager;
}
- // use $this->entityManager down anywhere you want ...
+ // ...
+
+ public function buildView(FormView $view, FormInterface $form, array $options)
+ {
+ // pass the form type option directly to the template
+ $view->vars['isExtendedAddress'] = $options['is_extended_address'];
+
+ // make a database query to find possible notifications related to postal addresses (e.g. to
+ // display dynamic messages such as 'Delivery to XX and YY states will be added next week!')
+ $view->vars['notification'] = $this->entityManager->find('...');
+ }
}
-If you're using the default ``services.yaml`` configuration (i.e. services from the
-``Form/`` are loaded and ``autoconfigure`` is enabled), this will already work!
-See :ref:`service-container-creating-service` for more details.
+If you're using the :ref:`default services.yaml configuration `,
+this example will already work! Otherwise, :ref:`create a service `
+for this form class and :doc:`tag it ` with ``form.type``.
-.. tip::
+The variables added in ``buildView()`` are available in the form type template
+as any other regular Twig variable:
+
+.. code-block:: html+twig
- If you're not using :ref:`autoconfigure `, make sure
- to :doc:`tag ` your service with ``form.type``.
+ {# templates/form/custom_types.html.twig #}
+ {% block postal_address_row %}
+ {# ... #}
-Have fun!
+ {% if isExtendedAddress %}
+ {# ... #}
+ {% endif %}
-.. _`ChoiceType`: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
-.. _`FieldType`: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Extension/Core/Type/FieldType.php
+ {% if notification is not empty %}
+
+ {{ notification }}
+
+ {% endif %}
+ {% endblock %}
diff --git a/form/create_form_type_extension.rst b/form/create_form_type_extension.rst
index 2bd6bd86980..fb1cd2e02b2 100644
--- a/form/create_form_type_extension.rst
+++ b/form/create_form_type_extension.rst
@@ -22,7 +22,9 @@ input.
Defining the Form Type Extension
--------------------------------
-First, create the form type extension class::
+First, create the form type extension class extending from
+:class:`Symfony\\Component\\Form\\AbstractTypeExtension` (you can implement
+:class:`Symfony\\Component\\Form\\FormTypeExtensionInterface` instead if you prefer)::
// src/Form/Extension/ImageTypeExtension.php
namespace App\Form\Extension;
@@ -33,29 +35,23 @@ First, create the form type extension class::
class ImageTypeExtension extends AbstractTypeExtension
{
/**
- * Returns the name of the type being extended.
- *
- * @return string The name of the type being extended
+ * Return the class of the type being extended.
*/
- public function getExtendedType()
+ public static function getExtendedTypes(): iterable
{
- // use FormType::class to modify (nearly) every field in the system
- return FileType::class;
+ // return FormType::class to modify (nearly) every field in the system
+ return [FileType::class];
}
}
-The only method you **must** implement is the ``getExtendedType()`` function.
-This is used to configure *which* field or field types you want to modify.
+The only method you **must** implement is ``getExtendedTypes()``, which is used
+to configure *which* field types you want to modify.
-In addition to the ``getExtendedType()`` function, you will probably want
-to override one of the following methods:
+Depending on your use case, you may need to override some of the following methods:
* ``buildForm()``
-
* ``buildView()``
-
* ``configureOptions()``
-
* ``finishView()``
For more information on what those methods do, see the
@@ -64,61 +60,33 @@ For more information on what those methods do, see the
Registering your Form Type Extension as a Service
-------------------------------------------------
-The next step is to make Symfony aware of your extension. Do this by registering
-your class as a service and using the ``form.type_extension`` tag:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # config/services.yaml
- services:
- # ...
-
- App\Form\Extension\ImageTypeExtension:
- tags:
- - { name: form.type_extension, extended_type: Symfony\Component\Form\Extension\Core\Type\FileType }
-
- .. code-block:: xml
+Form type extensions must be :ref:`registered as services `
+and :doc:`tagged ` with the ``form.type_extension`` tag.
+If you're using the
+:ref:`default services.yaml configuration `,
+this is already done for you, thanks to :ref:`autoconfiguration `.
-
-
-
-
-
-
-
-
-
-
+.. tip::
- .. code-block:: php
+ There is an optional tag attribute called ``priority``, which defaults to
+ ``0`` and controls the order in which the form type extensions are loaded
+ (the higher the priority, the earlier an extension is loaded). This is
+ useful when you need to guarantee that one extension is loaded before or
+ after another extension. Using this attribute requires you to add the
+ service configuration explicitly.
- // config/services.php
- use App\Form\Extension\ImageTypeExtension;
- use Symfony\Component\Form\Extension\Core\Type\FileType;
+Once the extension is registered, any method that you've overridden (e.g.
+``buildForm()``) will be called whenever *any* field of the given type
+(``FileType``) is built.
- $container->autowire(ImageTypeExtension::class)
- ->addTag('form.type_extension', [
- 'extended_type' => FileType::class
- ])
- ;
+.. tip::
-The ``extended_type`` key of the tag must match the class you're returning from
-the ``getExtendedType()`` method. As *soon* as you do this, any method that you've
-overridden (e.g. ``buildForm()``) will be called whenever *any* field of the given
-type (``FileType``) is built. Let's see an example next.
+ Run the following command to verify that the form type extension was
+ successfully registered in the application:
-.. tip::
+ .. code-block:: terminal
- There is an optional tag attribute called ``priority``, which
- defaults to ``0`` and controls the order in which the form
- type extensions are loaded (the higher the priority, the earlier
- an extension is loaded). This is useful when you need to guarantee
- that one extension is loaded before or after another extension.
+ $ php bin/console debug:form
Adding the extension Business Logic
-----------------------------------
@@ -167,17 +135,18 @@ For example::
namespace App\Form\Extension;
use Symfony\Component\Form\AbstractTypeExtension;
- use Symfony\Component\Form\FormView;
+ use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\FormInterface;
- use Symfony\Component\PropertyAccess\PropertyAccess;
+ use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
- use Symfony\Component\Form\Extension\Core\Type\FileType;
+ use Symfony\Component\PropertyAccess\PropertyAccess;
class ImageTypeExtension extends AbstractTypeExtension
{
- public function getExtendedType()
+ public static function getExtendedTypes(): iterable
{
- return FileType::class;
+ // return FormType::class to modify (nearly) every field in the system
+ return [FileType::class];
}
public function configureOptions(OptionsResolver $resolver)
@@ -246,9 +215,9 @@ next to the file field. For example::
namespace App\Form\Type;
use Symfony\Component\Form\AbstractType;
- use Symfony\Component\Form\FormBuilderInterface;
- use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
+ use Symfony\Component\Form\Extension\Core\Type\TextType;
+ use Symfony\Component\Form\FormBuilderInterface;
class MediaType extends AbstractType
{
@@ -278,3 +247,21 @@ would apply to all of these (notable exceptions are the ``ButtonType`` form
types). Also keep in mind that if you created (or are using) a *custom* form type,
it's possible that it does *not* extend ``FormType``, and so your form type extension
may not be applied to it.
+
+Another option is to return multiple form types in the ``getExtendedTypes()``
+method to extend all of them::
+
+ // ...
+ use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
+ use Symfony\Component\Form\Extension\Core\Type\DateType;
+ use Symfony\Component\Form\Extension\Core\Type\TimeType;
+
+ class DateTimeExtension extends AbstractTypeExtension
+ {
+ // ...
+
+ public static function getExtendedTypes(): iterable
+ {
+ return [DateTimeType::class, DateType::class, TimeType::class];
+ }
+ }
diff --git a/form/data_mappers.rst b/form/data_mappers.rst
index 23bb8939fa8..f25f1648a0b 100644
--- a/form/data_mappers.rst
+++ b/form/data_mappers.rst
@@ -33,7 +33,7 @@ Creating a Data Mapper
Suppose that you want to save a set of colors to the database. For this, you're
using an immutable color object::
- // src/App/Painting/Color.php
+ // src/Painting/Color.php
namespace App\Painting;
final class Color
@@ -78,45 +78,48 @@ one of the values is changed.
The red, green and blue form fields have to be mapped to the constructor
arguments and the ``Color`` instance has to be mapped to red, green and blue
-form fields. Recognize a familiar pattern? It's time for a data mapper!
+form fields. Recognize a familiar pattern? It's time for a data mapper. The
+easiest way to create one is by implementing :class:`Symfony\\Component\\Form\\DataMapperInterface`
+in your form type::
-.. code-block:: php
-
- // src/App/Form/DataMapper/ColorMapper.php
- namespace App\Form\DataMapper;
+ // src/Form/ColorType.php
+ namespace App\Form;
use App\Painting\Color;
+ use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\DataMapperInterface;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Form\FormInterface;
- final class ColorMapper implements DataMapperInterface
+ final class ColorType extends AbstractType implements DataMapperInterface
{
+ // ...
+
/**
- * @param Color|null $data
+ * @param Color|null $viewData
*/
- public function mapDataToForms($data, $forms)
+ public function mapDataToForms($viewData, $forms)
{
// there is no data yet, so nothing to prepopulate
- if (null === $data) {
+ if (null === $viewData) {
return;
}
// invalid data type
- if (!$data instanceof Color) {
- throw new UnexpectedTypeException($data, Color::class);
+ if (!$viewData instanceof Color) {
+ throw new UnexpectedTypeException($viewData, Color::class);
}
/** @var FormInterface[] $forms */
$forms = iterator_to_array($forms);
// initialize form field values
- $forms['red']->setData($data->getRed());
- $forms['green']->setData($data->getGreen());
- $forms['blue']->setData($data->getBlue());
+ $forms['red']->setData($viewData->getRed());
+ $forms['green']->setData($viewData->getGreen());
+ $forms['blue']->setData($viewData->getBlue());
}
- public function mapFormsToData($forms, &$data)
+ public function mapFormsToData($forms, &$viewData)
{
/** @var FormInterface[] $forms */
$forms = iterator_to_array($forms);
@@ -124,7 +127,7 @@ form fields. Recognize a familiar pattern? It's time for a data mapper!
// as data is passed by reference, overriding it will change it in
// the form object as well
// beware of type inconsistency, see caution below
- $data = new Color(
+ $viewData = new Color(
$forms['red']->getData(),
$forms['green']->getData(),
$forms['blue']->getData()
@@ -141,20 +144,19 @@ form fields. Recognize a familiar pattern? It's time for a data mapper!
Using the Mapper
----------------
-You're ready to use the data mapper for the ``ColorType`` form. Use the
-:method:`Symfony\\Component\\Form\\FormConfigBuilderInterface::setDataMapper`
-method to configure the data mapper::
+After creating the data mapper, you need to configure the form to use it. This is
+achieved using the :method:`Symfony\\Component\\Form\\FormConfigBuilderInterface::setDataMapper`
+method::
- // src/App/Form/Type/ColorType.php
+ // src/Form/Type/ColorType.php
namespace App\Form\Type;
- use App\Form\DataMapper\ColorMapper;
- use Symfony\Component\Form\AbstractType;
+ // ...
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
- final class ColorType extends AbstractType
+ final class ColorType extends AbstractType implements DataMapperInterface
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
@@ -170,7 +172,8 @@ method to configure the data mapper::
->add('blue', IntegerType::class, [
'empty_data' => '0',
])
- ->setDataMapper(new ColorMapper())
+ // configure the data mapper for this FormType
+ ->setDataMapper($this)
;
}
@@ -179,19 +182,41 @@ method to configure the data mapper::
// when creating a new color, the initial data should be null
$resolver->setDefault('empty_data', null);
}
+
+ // ...
}
-Cool! When using the ``ColorType`` form, the custom ``ColorMapper`` will create
-a new ``Color`` object now.
+Cool! When using the ``ColorType`` form, the custom data mapper methods will
+create a new ``Color`` object now.
.. caution::
When a form has the ``inherit_data`` option set to ``true``, it does not use the data mapper and
lets its parent map inner values.
-.. tip::
+.. sidebar:: Stateful Data Mappers
+
+ Sometimes, data mappers need to access services or need to maintain their
+ state. In this case, you cannot implement the methods in the form type
+ itself. Create a separate class implementing ``DataMapperInterface`` and
+ initialize it in your form type::
- You can also implement the ``DataMapperInterface`` in the ``ColorType`` and add
- the ``mapDataToForms()`` and ``mapFormsToData()`` in the form type directly
- to avoid creating a new class. You'll then have to call
- ``$builder->setDataMapper($this)``.
+ // src/Form/Type/ColorType.php
+
+ // ...
+ use App\Form\DataMapper\ColorMapper;
+
+ final class ColorType extends AbstractType
+ {
+ public function buildForm(FormBuilderInterface $builder, array $options)
+ {
+ $builder
+ // ...
+
+ // Initialize the data mapper class and e.g. pass some state
+ ->setDataMapper(new ColorMapper($options['opacity']))
+ ;
+ }
+
+ // ...
+ }
diff --git a/form/data_transformers.rst b/form/data_transformers.rst
index b19f7238ed2..589fedc053d 100644
--- a/form/data_transformers.rst
+++ b/form/data_transformers.rst
@@ -33,9 +33,9 @@ Suppose you have a Task form with a tags ``text`` type::
namespace App\Form\Type;
use App\Entity\Task;
+ use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
- use Symfony\Component\Form\Extension\Core\Type\TextType;
// ...
class TaskType extends AbstractType
@@ -66,8 +66,8 @@ class::
namespace App\Form\Type;
use Symfony\Component\Form\CallbackTransformer;
- use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\TextType;
+ use Symfony\Component\Form\FormBuilderInterface;
// ...
class TaskType extends AbstractType
@@ -252,7 +252,7 @@ that message with the ``invalid_message`` option (see below).
Using the Transformer
~~~~~~~~~~~~~~~~~~~~~
-Next, you need to use the ``IssueToNumberTransformer`` object inside if ``TaskType``
+Next, you need to use the ``IssueToNumberTransformer`` object inside of ``TaskType``
and add it to the ``issue`` field. No problem! Add a ``__construct()`` method
and type-hint the new class::
@@ -291,24 +291,62 @@ and type-hint the new class::
// ...
}
-That's it! As long as you're using :ref:`autowire ` and
-:ref:`autoconfigure `, Symfony will automatically
-know to pass your ``TaskType`` an instance of the ``IssueToNumberTransformer``.
+Whenever the transformer throws an exception, the ``invalid_message`` is shown
+to the user. Instead of showing the same message every time, you can set the
+end-user error message in the data transformer using the
+``setInvalidMessage()`` method. It also allows you to include user values::
-.. tip::
+ // src/Form/DataTransformer/IssueToNumberTransformer.php
+ namespace App\Form\DataTransformer;
+
+ use Symfony\Component\Form\DataTransformerInterface;
+ use Symfony\Component\Form\Exception\TransformationFailedException;
+
+ class IssueToNumberTransformer implements DataTransformerInterface
+ {
+ // ...
+
+ public function reverseTransform($issueNumber)
+ {
+ // ...
+
+ if (null === $issue) {
+ $privateErrorMessage = sprintf('An issue with number "%s" does not exist!', $issueNumber);
+ $publicErrorMessage = 'The given "{{ value }}" value is not a valid issue number.';
+
+ $failure = new TransformationFailedException($privateErrorMessage);
+ $failure->setInvalidMessage($publicErrorMessage, [
+ '{{ value }}' => $issueNumber,
+ ]);
+
+ throw $failure;
+ }
+
+ return $issue;
+ }
+ }
+
+.. versionadded:: 4.3
+
+ The ``setInvalidMessage()`` method was introduced in Symfony 4.3.
- For more information about defining form types as services, read
- :doc:`register your form type as a service `.
+That's it! If you're using the
+:ref:`default services.yaml configuration `,
+Symfony will automatically know to pass your ``TaskType`` an instance of the
+``IssueToNumberTransformer`` thanks to :ref:`autowire ` and
+:ref:`autoconfigure `.
+Otherwise, :ref:`register the form class as a service `
+and :doc:`tag it ` with the ``form.type`` tag.
Now, you can use your ``TaskType``::
- // e.g. in a controller somewhere
+ // e.g. somewhere in a controller
$form = $this->createForm(TaskType::class, $task);
// ...
Cool, you're done! Your user will be able to enter an issue number into the
-text field and it will be transformed back into an Issue object. This means
+text field, which will be transformed back into an Issue object. This means
that, after a successful submission, the Form component will pass a real
``Issue`` object to ``Task::setIssue()`` instead of the issue number.
diff --git a/form/direct_submit.rst b/form/direct_submit.rst
index 6edd70dc740..48388b100e5 100644
--- a/form/direct_submit.rst
+++ b/form/direct_submit.rst
@@ -4,54 +4,18 @@
How to Use the submit() Function to Handle Form Submissions
===========================================================
-With the ``handleRequest()`` method, you can handle form
-submissions::
+The recommended way of :ref:`processing Symfony forms ` is to
+use the :method:`Symfony\\Component\\Form\\FormInterface::handleRequest` method
+to detect when the form has been submitted. However, you can also use the
+:method:`Symfony\\Component\\Form\\FormInterface::submit` method to have better
+control over when exactly your form is submitted and what data is passed to it::
use Symfony\Component\HttpFoundation\Request;
// ...
public function new(Request $request)
{
- $form = $this->createFormBuilder()
- // ...
- ->getForm();
-
- $form->handleRequest($request);
-
- if ($form->isSubmitted() && $form->isValid()) {
- // perform some action...
-
- return $this->redirectToRoute('task_success');
- }
-
- return $this->render('product/new.html.twig', [
- 'form' => $form->createView(),
- ]);
- }
-
-.. tip::
-
- To see more about this method, read :ref:`form-handling-form-submissions`.
-
-.. _form-call-submit-directly:
-
-Calling Form::submit() manually
--------------------------------
-
-In some cases, you want better control over when exactly your form is submitted
-and what data is passed to it. Instead of using the
-:method:`Symfony\\Component\\Form\\FormInterface::handleRequest`
-method, pass the submitted data directly to
-:method:`Symfony\\Component\\Form\\FormInterface::submit`::
-
- use Symfony\Component\HttpFoundation\Request;
- // ...
-
- public function new(Request $request)
- {
- $form = $this->createFormBuilder()
- // ...
- ->getForm();
+ $form = $this->createForm(TaskType::class, $task);
if ($request->isMethod('POST')) {
$form->submit($request->request->get($form->getName()));
@@ -63,7 +27,7 @@ method, pass the submitted data directly to
}
}
- return $this->render('product/new.html.twig', [
+ return $this->render('task/new.html.twig', [
'form' => $form->createView(),
]);
}
diff --git a/form/dynamic_form_modification.rst b/form/dynamic_form_modification.rst
index 36619125cae..f2b37cb07c5 100644
--- a/form/dynamic_form_modification.rst
+++ b/form/dynamic_form_modification.rst
@@ -10,7 +10,7 @@ how to customize your form based on three common use-cases:
1) :ref:`form-events-underlying-data`
Example: you have a "Product" form and need to modify/add/remove a field
- based on the data on the underlying Product being edited.
+ based on the data on the underlying Product being edited.
2) :ref:`form-events-user-data`
@@ -144,10 +144,10 @@ you can also move the logic for creating the ``name`` field to an
// src/Form/EventListener/AddNameFieldSubscriber.php
namespace App\Form\EventListener;
- use Symfony\Component\Form\FormEvent;
- use Symfony\Component\Form\FormEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\Extension\Core\Type\TextType;
+ use Symfony\Component\Form\FormEvent;
+ use Symfony\Component\Form\FormEvents;
class AddNameFieldSubscriber implements EventSubscriberInterface
{
@@ -209,11 +209,11 @@ Using an event listener, your form might look like this::
namespace App\Form\Type;
use Symfony\Component\Form\AbstractType;
+ use Symfony\Component\Form\Extension\Core\Type\TextareaType;
+ use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
- use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
- use Symfony\Component\Form\Extension\Core\Type\TextType;
- use Symfony\Component\Form\Extension\Core\Type\TextareaType;
+ use Symfony\Component\Form\FormEvents;
class FriendMessageFormType extends AbstractType
{
@@ -236,11 +236,16 @@ service into the form type so you can get the current user object::
use Symfony\Component\Security\Core\Security;
// ...
- private $security;
-
- public function __construct(Security $security)
+ class FriendMessageFormType extends AbstractType
{
- $this->security = $security;
+ private $security;
+
+ public function __construct(Security $security)
+ {
+ $this->security = $security;
+ }
+
+ // ....
}
Customizing the Form Type
@@ -250,12 +255,11 @@ Now that you have all the basics in place you can use the features of the
security helper to fill in the listener logic::
// src/Form/Type/FriendMessageFormType.php
-
use App\Entity\User;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
- use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
+ use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Security\Core\Security;
// ...
@@ -322,10 +326,11 @@ security helper to fill in the listener logic::
Using the Form
~~~~~~~~~~~~~~
-If you're using :ref:`autowire ` and
-:ref:`autoconfigure `, your form is ready to be used!
-Otherwise, see :doc:`/form/form_dependencies` to learn how to register your form
-type as a service.
+If you're using the :ref:`default services.yaml configuration `,
+your form is ready to be used thanks to :ref:`autowire ` and
+:ref:`autoconfigure `.
+Otherwise, :ref:`register the form class as a service `
+and :doc:`tag it ` with the ``form.type`` tag.
In a controller, create the form like normal::
@@ -357,7 +362,7 @@ Dynamic Generation for Submitted Forms
Another case that can appear is that you want to customize the form specific to
the data that was submitted by the user. For example, imagine you have a registration
form for sports gatherings. Some events will allow you to specify your preferred
-position on the field. This would be a ``choice`` field for example. However the
+position on the field. This would be a ``choice`` field for example. However, the
possible choices will depend on each sport. Football will have attack, defense,
goalkeeper etc... Baseball will have a pitcher but will not have a goalkeeper. You
will need the correct options in order for validation to pass.
@@ -368,11 +373,11 @@ sport like this::
// src/Form/Type/SportMeetupType.php
namespace App\Form\Type;
+ use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
- use Symfony\Bridge\Doctrine\Form\Type\EntityType;
// ...
class SportMeetupType extends AbstractType
@@ -435,8 +440,8 @@ The type would now look like::
namespace App\Form\Type;
use App\Entity\Sport;
- use Symfony\Component\Form\FormInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
+ use Symfony\Component\Form\FormInterface;
// ...
class SportMeetupType extends AbstractType
@@ -447,7 +452,7 @@ The type would now look like::
->add('sport', EntityType::class, [
'class' => 'App\Entity\Sport',
'placeholder' => '',
- ]);
+ ])
;
$formModifier = function (FormInterface $form, Sport $sport = null) {
@@ -504,10 +509,10 @@ your application. Assume that you have a sport meetup creation controller::
// src/Controller/MeetupController.php
namespace App\Controller;
- use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
- use Symfony\Component\HttpFoundation\Request;
use App\Entity\SportMeetup;
use App\Form\Type\SportMeetupType;
+ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+ use Symfony\Component\HttpFoundation\Request;
// ...
class MeetupController extends AbstractController
diff --git a/form/embedded.rst b/form/embedded.rst
index fc43ef5336e..6568b09611f 100644
--- a/form/embedded.rst
+++ b/form/embedded.rst
@@ -6,8 +6,8 @@ How to Embed 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.
+a ``User`` object as well as many ``Address`` objects. Fortunately this can
+be achieved by the Form component.
.. _forms-embedding-single-object:
@@ -60,7 +60,7 @@ Next, add a new ``category`` property to the ``Task`` class::
.. tip::
The ``Valid`` Constraint has been added to the property ``category``. This
- cascades the validation to the corresponding entity. If you omit this constraint
+ cascades the validation to the corresponding entity. If you omit this constraint,
the child entity would not be validated.
Now that your application has been updated to reflect the new requirements,
@@ -94,8 +94,8 @@ 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::
- use Symfony\Component\Form\FormBuilderInterface;
use App\Form\CategoryType;
+ use Symfony\Component\Form\FormBuilderInterface;
public function buildForm(FormBuilderInterface $builder, array $options)
{
diff --git a/form/events.rst b/form/events.rst
index 76c37d125d5..5620ec96f46 100644
--- a/form/events.rst
+++ b/form/events.rst
@@ -263,15 +263,15 @@ method of the ``FormFactory``::
// ...
- use Symfony\Component\Form\FormEvent;
- use Symfony\Component\Form\FormEvents;
- use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
+ use Symfony\Component\Form\Extension\Core\Type\TextType;
+ use Symfony\Component\Form\FormEvent;
+ use Symfony\Component\Form\FormEvents;
$form = $formFactory->createBuilder()
->add('username', TextType::class)
- ->add('show_email', CheckboxType::class)
+ ->add('showEmail', CheckboxType::class)
->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$user = $event->getData();
$form = $event->getForm();
@@ -283,7 +283,7 @@ method of the ``FormFactory``::
// checks whether the user has chosen to display their email or not.
// If the data was submitted previously, the additional value that is
// included in the request variables needs to be removed.
- if (true === $user['show_email']) {
+ if (isset($user['showEmail']) && $user['showEmail']) {
$form->add('email', EmailType::class);
} else {
unset($user['email']);
@@ -300,8 +300,8 @@ callback for better readability::
// src/Form/SubscriptionType.php
namespace App\Form;
- use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
+ use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
@@ -312,7 +312,7 @@ callback for better readability::
{
$builder
->add('username', TextType::class)
- ->add('show_email', CheckboxType::class)
+ ->add('showEmail', CheckboxType::class)
->addEventListener(
FormEvents::PRE_SET_DATA,
[$this, 'onPreSetData']
@@ -335,15 +335,15 @@ Event subscribers have different uses:
* Listening to multiple events;
* Regrouping multiple listeners inside a single class.
-.. code-block:: php
+Consider the following example of a form event subscriber::
// src/Form/EventListener/AddEmailFieldListener.php
namespace App\Form\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+ use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
- use Symfony\Component\Form\Extension\Core\Type\EmailType;
class AddEmailFieldListener implements EventSubscriberInterface
{
@@ -379,7 +379,7 @@ Event subscribers have different uses:
// checks whether the user has chosen to display their email or not.
// If the data was submitted previously, the additional value that
// is included in the request variables needs to be removed.
- if (true === $user['show_email']) {
+ if (isset($user['showEmail']) && $user['showEmail']) {
$form->add('email', EmailType::class);
} else {
unset($user['email']);
@@ -391,14 +391,14 @@ Event subscribers have different uses:
To register the event subscriber, use the ``addEventSubscriber()`` method::
use App\Form\EventListener\AddEmailFieldListener;
- use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
+ use Symfony\Component\Form\Extension\Core\Type\TextType;
// ...
$form = $formFactory->createBuilder()
->add('username', TextType::class)
- ->add('show_email', CheckboxType::class)
+ ->add('showEmail', CheckboxType::class)
->addEventSubscriber(new AddEmailFieldListener())
->getForm();
diff --git a/form/form_collections.rst b/form/form_collections.rst
index 243b0f28334..28b6283a271 100644
--- a/form/form_collections.rst
+++ b/form/form_collections.rst
@@ -84,8 +84,8 @@ objects::
Then, create a form class so that a ``Tag`` object can be modified by the user::
- // src/Form/Type/TagType.php
- namespace App\Form\Type;
+ // src/Form/TagType.php
+ namespace App\Form;
use App\Entity\Tag;
use Symfony\Component\Form\AbstractType;
@@ -114,14 +114,14 @@ form itself, create a form for the ``Task`` class.
Notice that you embed a collection of ``TagType`` forms using the
:doc:`CollectionType ` field::
- // src/Form/Type/TaskType.php
- namespace App\Form\Type;
+ // src/Form/TaskType.php
+ namespace App\Form;
use App\Entity\Task;
use Symfony\Component\Form\AbstractType;
+ use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
- use Symfony\Component\Form\Extension\Core\Type\CollectionType;
class TaskType extends AbstractType
{
@@ -148,11 +148,11 @@ In your controller, you'll create a new form from the ``TaskType``::
// src/Controller/TaskController.php
namespace App\Controller;
- use App\Entity\Task;
use App\Entity\Tag;
- use App\Form\Type\TaskType;
- use Symfony\Component\HttpFoundation\Request;
+ use App\Entity\Task;
+ use App\Form\TaskType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+ use Symfony\Component\HttpFoundation\Request;
class TaskController extends AbstractController
{
@@ -254,7 +254,7 @@ type expects to receive exactly two, otherwise an error will be thrown:
``This form should not contain extra fields``. To make this flexible,
add the ``allow_add`` option to your collection field::
- // src/Form/Type/TaskType.php
+ // src/Form/TaskType.php
// ...
use Symfony\Component\Form\FormBuilderInterface;
@@ -295,7 +295,7 @@ new "tag" forms. To render it, make the following change to your template:
on it. You could even choose to render only one of its fields (e.g. the
``name`` field):
- .. code-block:: html+twig
+ .. code-block:: twig
{{ form_widget(form.tags.vars.prototype.name)|e }}
@@ -418,7 +418,7 @@ for the tags in the ``Task`` class::
Next, add a ``by_reference`` option to the ``tags`` field and set it to ``false``::
- // src/Form/Type/TaskType.php
+ // src/Form/TaskType.php
// ...
public function buildForm(FormBuilderInterface $builder, array $options)
@@ -444,6 +444,12 @@ you will learn about next!).
otherwise the form will still use ``setTag()`` even if ``by_reference`` is ``false``.
You'll learn more about the ``removeTag()`` method later in this article.
+.. caution::
+
+ Symfony can only make the plural-to-singular conversion (e.g. from the
+ ``tags`` property to the ``addTag()`` method) for English words. Code
+ written in any other language won't work as expected.
+
.. sidebar:: Doctrine: Cascading Relations and saving the "Inverse" side
To save the new tags with Doctrine, you need to consider a couple more
@@ -490,13 +496,13 @@ you will learn about next!).
+ https://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">