diff --git a/.doctor-rst.yaml b/.doctor-rst.yaml
new file mode 100644
index 00000000000..a243e6266b6
--- /dev/null
+++ b/.doctor-rst.yaml
@@ -0,0 +1,81 @@
+rules:
+ american_english: ~
+ avoid_repetetive_words: ~
+ blank_line_after_directive: ~
+ blank_line_before_directive: ~
+ composer_dev_option_not_at_the_end: ~
+ correct_code_block_directive_based_on_the_content: ~
+ deprecated_directive_should_have_version: ~
+ ensure_order_of_code_blocks_in_configuration_block: ~
+ extension_xlf_instead_of_xliff: ~
+ indention: ~
+ lowercase_as_in_use_statements: ~
+ max_blank_lines:
+ max: 2
+ no_app_console: ~
+ no_blank_line_after_filepath_in_php_code_block: ~
+ no_blank_line_after_filepath_in_twig_code_block: ~
+ no_blank_line_after_filepath_in_xml_code_block: ~
+ no_blank_line_after_filepath_in_yaml_code_block: ~
+ no_brackets_in_method_directive: ~
+ no_composer_req: ~
+ no_explicit_use_of_code_block_php: ~
+ no_inheritdoc: ~
+ no_namespace_after_use_statements: ~
+ no_php_open_tag_in_code_block_php_directive: ~
+ no_space_before_self_xml_closing_tag: ~
+ only_backslashes_in_namespace_in_php_code_block: ~
+ only_backslashes_in_use_statements_in_php_code_block: ~
+ ordered_use_statements: ~
+ php_prefix_before_bin_console: ~
+ replace_code_block_types: ~
+ replacement: ~
+ short_array_syntax: ~
+ space_between_label_and_link_in_doc: ~
+ space_between_label_and_link_in_ref: ~
+ typo: ~
+ unused_links: ~
+ use_deprecated_directive_instead_of_versionadded: ~
+ use_https_xsd_urls: ~
+ valid_inline_highlighted_namespaces: ~
+ valid_use_statements: ~
+ versionadded_directive_should_have_version: ~
+ yarn_dev_option_at_the_end: ~
+
+ # 3.4
+ versionadded_directive_major_version:
+ major_version: 3
+
+ versionadded_directive_min_version:
+ min_version: '3.0'
+
+# do not report as violation
+whitelist:
+ regex:
+ - '/FOSUserBundle(.*)\.yml/'
+ - '/``.yml``/'
+ - '/(.*)\.orm\.yml/' # currently DoctrineBundle only supports .yml
+ - '/rst-class/'
+ lines:
+ - 'in config files, so the old ``app/config/config_dev.yml`` goes to'
+ - '#. The most important config file is ``app/config/services.yml``, which now is'
+ - 'code in production without a proxy, it becomes trivially easy to abuse your'
+ - '.. _`EasyDeployBundle`: https://github.com/EasyCorp/easy-deploy-bundle'
+ - 'The bin/console Command'
+ - '# username is your full Gmail or Google Apps email address'
+ - '.. _`LDAP injection`: http://projects.webappsec.org/w/page/13246947/LDAP%20Injection'
+ - '.. versionadded:: 0.21.0' # Encore
+ - '.. versionadded:: 0.28.4' # Encore
+ - '.. versionadded:: 2.4.0' # SwiftMailer
+ - '.. versionadded:: 1.30' # Twig
+ - '.. versionadded:: 1.35' # Twig
+ - '.. versionadded:: 1.2' # MakerBundle
+ - '.. versionadded:: 1.11' # MakerBundle
+ - '.. versionadded:: 1.3' # MakerBundle
+ - '.. versionadded:: 1.8' # MakerBundle
+ - '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"
+ - '.. code-block:: twig'
diff --git a/.editorconfig b/.editorconfig
index 6f5286431d4..f9366facfb0 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,8 +1,8 @@
root = true
-[*.{rst,rst.inc}]
+[*]
indent_style = space
-indent_size = 2
+indent_size = 4
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 00000000000..51ce53a1a89
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,27 @@
+# Console
+/console* @chalasr
+/components/console* @chalasr
+
+# Form
+/forms.rst @xabbuh @HeahDude
+/components/form* @xabbuh @HeahDude
+/reference/forms* @xabbuh @HeahDude
+
+# PropertyInfo
+/components/property_info* @dunglas
+
+# Security
+/security* @chalasr
+/components/security* @chalasr
+
+# Validator
+/validation/* @xabbuh @HeahDude
+/components/validator* @xabbuh @HeahDude
+/reference/constraints* @xabbuh @HeahDude
+
+# Workflow
+/workflow* @lyrixx
+/components/workflow* @lyrixx
+
+# Yaml
+/components/yaml* @xabbuh
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index bc7d6a94182..0586345396f 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -4,6 +4,6 @@ If your pull request fixes a BUG, use the oldest maintained branch that contains
the bug (see https://symfony.com/roadmap for the list of maintained branches).
If your pull request documents a NEW FEATURE, use the same Symfony branch where
-the feature was introduced (and `master` for features of unreleased versions).
+the feature was introduced (and `5.x` for features of unreleased versions).
-->
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 00000000000..26f0e537118
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,71 @@
+on:
+ push:
+ branches-ignore:
+ - 'github-comments'
+ pull_request:
+ branches-ignore:
+ - 'github-comments'
+
+name: CI
+
+jobs:
+ build:
+ name: Build
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: "Checkout"
+ uses: actions/checkout@v2
+
+ - name: "Set up Python 3.7"
+ uses: actions/setup-python@v1
+ with:
+ python-version: '3.7' # Semantic version range syntax or exact version of a Python version
+
+ - name: "Display Python version"
+ run: python -c "import sys; print(sys.version)"
+
+ - name: "Install Sphinx dependencies"
+ run: sudo apt-get install python-dev build-essential
+
+ - name: "Cache pip"
+ uses: actions/cache@v2
+ with:
+ path: ~/.cache/pip
+ key: ${{ runner.os }}-pip-${{ hashFiles('_build/.requirements.txt') }}
+ restore-keys: |
+ ${{ runner.os }}-pip-
+
+ - name: "Install Sphinx + requirements via pip"
+ run: pip install -r _build/.requirements.txt
+
+ - name: "Build documentation"
+ run: make -C _build SPHINXOPTS="-nqW -j auto" html
+
+ doctor-rst:
+ name: DOCtor-RST
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: "Checkout"
+ uses: actions/checkout@v2
+
+ - name: "Create cache dir"
+ run: mkdir .cache
+
+ - name: "Extract base branch name"
+ run: echo "##[set-output name=branch;]$(echo ${GITHUB_BASE_REF:=${GITHUB_REF##*/}})"
+ id: extract_base_branch
+
+ - name: "Cache DOCtor-RST"
+ uses: actions/cache@v1
+ with:
+ path: .cache
+ key: ${{ runner.os }}-doctor-rst-${{ steps.extract_base_branch.outputs.branch }}
+
+ - name: "Run DOCtor-RST"
+ uses: docker://oskarstark/doctor-rst
+ with:
+ args: --short --error-format=github --cache-file=/github/workspace/.cache/doctor-rst.cache
diff --git a/.gitignore b/.gitignore
index a5eb433eea3..6a20088680a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
/_build/doctrees
+/_build/spelling
/_build/html
*.pyc
diff --git a/.platform/routes.yaml b/.platform/routes.yaml
deleted file mode 100644
index f99889ccec3..00000000000
--- a/.platform/routes.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-http://www.{default}/:
- to: http://{default}/
- type: redirect
-http://{default}/:
- cache:
- cookies:
- - '*'
- default_ttl: 0
- enabled: true
- headers:
- - Accept
- - Accept-Language
- ssi:
- enabled: false
- type: upstream
- upstream: symfonydocs:php
diff --git a/.platform.app.yaml b/.symfony.cloud.yaml
similarity index 93%
rename from .platform.app.yaml
rename to .symfony.cloud.yaml
index 66f6a036b73..faa3c24780e 100644
--- a/.platform.app.yaml
+++ b/.symfony.cloud.yaml
@@ -5,10 +5,7 @@
name: symfonydocs
# The toolstack used to build the application.
-type: "php"
-
-build:
- flavor: "composer"
+type: "python:3.7"
# The configuration of app when it is exposed to the web.
web:
@@ -53,7 +50,7 @@ hooks:
build: |
virtualenv .virtualenv
. .virtualenv/bin/activate
- # Platform.sh currently sets PIP_USER=1.
+ # SymfonyCloud currently sets PIP_USER=1.
export PIP_USER=
pip install pip==9.0.1 wheel==0.29.0
pip install -r _build/.requirements.txt
diff --git a/.symfony/routes.yaml b/.symfony/routes.yaml
new file mode 100644
index 00000000000..caf4875f732
--- /dev/null
+++ b/.symfony/routes.yaml
@@ -0,0 +1,11 @@
+https://{default}/:
+ cache:
+ cookies:
+ - '*'
+ default_ttl: 0
+ enabled: true
+ headers:
+ - Accept
+ - Accept-Language
+ type: upstream
+ upstream: symfonydocs:http
diff --git a/.platform/services.yaml b/.symfony/services.yaml
similarity index 100%
rename from .platform/services.yaml
rename to .symfony/services.yaml
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index cccb01eef30..00000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-language: python
-
-python: 2.7
-
-sudo: false
-cache:
- directories: [$HOME/.cache/pip]
-
-install: pip install -r _build/.requirements.txt
-
-script: make -C _build SPHINXOPTS=-nW html
-
-branches:
- except:
- - github-comments
diff --git a/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 557bd5e3a6d..8923f022322 100644
--- a/README.markdown
+++ b/README.markdown
@@ -6,19 +6,20 @@ This documentation is rendered online at https://symfony.com/doc/current/
Contributing
------------
->**Note**
->Unless you're documenting a feature that was introduced *after* Symfony 2.8
->(e.g. in Symfony 3.4), all pull requests must be based off of the **2.8** 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)
-Platform.sh
------------
+> **Note**
+> All pull requests must be based off of the **3.4** branch,
+> unless you're documenting a feature that was introduced *after* Symfony 3.4
+> (e.g. in Symfony 4.4), **not** the master or older branches.
+
+SymfonyCloud
+------------
-Pull requests are automatically built by [Platform.sh](https://platform.sh).
+Thanks to [SymfonyCloud](https://symfony.com/cloud) for providing an integration
+server where Pull Requests are built and can be reviewed by contributors.
Docker
------
@@ -29,7 +30,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 4a52e3fcb7e..47f076e9403 100644
--- a/_build/.requirements.txt
+++ b/_build/.requirements.txt
@@ -1,13 +1,6 @@
-alabaster==0.7.10
-Babel==2.4.0
docutils==0.13.1
-imagesize==0.7.1
-Jinja2==2.9.6
-MarkupSafe==1.0
Pygments==2.2.0
-pytz==2017.2
-requests==2.12.5
-six==1.10.0
-snowballstemmer==1.2.1
-Sphinx==1.3.6
-git+https://github.com/fabpot/sphinx-php.git@7312eccce9465640752e51373a480da700e02345#egg_name=sphinx-php
+sphinx==1.8.5
+git+https://github.com/fabpot/sphinx-php.git@v2.0.0#egg_name=sphinx-php
+jsx-lexer===0.0.8
+sphinx_rtd_theme==0.5.0
diff --git a/_build/_themes/_exts/symfonycom/__init__.py b/_build/_exts/symfonycom/__init__.py
similarity index 100%
rename from _build/_themes/_exts/symfonycom/__init__.py
rename to _build/_exts/symfonycom/__init__.py
diff --git a/_build/_themes/_exts/symfonycom/sphinx/__init__.py b/_build/_exts/symfonycom/sphinx/__init__.py
similarity index 54%
rename from _build/_themes/_exts/symfonycom/sphinx/__init__.py
rename to _build/_exts/symfonycom/sphinx/__init__.py
index 426d79826d7..4a61e711809 100644
--- a/_build/_themes/_exts/symfonycom/sphinx/__init__.py
+++ b/_build/_exts/symfonycom/sphinx/__init__.py
@@ -1,81 +1,7 @@
-from sphinx.highlighting import lexers, PygmentsBridge
from pygments.style import Style
-from pygments.formatters import HtmlFormatter
from pygments.token import Keyword, Name, Comment, String, Error, \
Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
-from sphinx.writers.html import HTMLTranslator
-from docutils import nodes
-from sphinx.locale import admonitionlabels, lazy_gettext
-
-customadmonitionlabels = admonitionlabels
-l_ = lazy_gettext
-customadmonitionlabels['best-practice'] = l_('Best Practice')
-
-def _getType(path):
- return path[:path.find('/')]
-
-def _isIndex(path):
- return 'index' in path
-
-class SensioHTMLTranslator(HTMLTranslator):
- def __init__(self, builder, *args, **kwds):
- HTMLTranslator.__init__(self, builder, *args, **kwds)
- builder.templates.environment.filters['get_type'] = _getType
- builder.templates.environment.tests['index'] = _isIndex
- self.highlightlinenothreshold = 0
-
- def visit_literal(self, node):
- self.body.append(self.starttag(node, 'code', '', CLASS='docutils literal notranslate'))
-
- def depart_literal(self, node):
- self.body.append('')
-
- def visit_admonition(self, node, name=''):
- self.body.append(self.starttag(node, 'div', CLASS=('admonition-wrapper')))
- self.body.append('
')
- if name and name != 'seealso':
- node.insert(0, nodes.title(name, customadmonitionlabels[name]))
- self.set_first_last(node)
-
- def depart_admonition(self, node=None):
- self.body.append('
-
- {% trans %}Free document hosting provided by Read the Docs.{% endtrans %}
-
-
-
-{% endif %}
-
diff --git a/_build/conf.py b/_build/conf.py
index 1d3adf29dce..ffa92d30604 100644
--- a/_build/conf.py
+++ b/_build/conf.py
@@ -16,11 +16,12 @@
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
-sys.path.append(os.path.abspath('_themes/_exts'))
+sys.path.append(os.path.abspath('_exts'))
# adding PhpLexer
from sphinx.highlighting import lexers
from pygments.lexers.compiled import CLexer
+from pygments.lexers.shell import BashLexer
from pygments.lexers.special import TextLexer
from pygments.lexers.text import RstLexer
from pygments.lexers.web import PhpLexer
@@ -29,16 +30,22 @@
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
+needs_sphinx = '1.8.5'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
- 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo',
- 'sensio.sphinx.refinclude', 'sensio.sphinx.configurationblock', 'sensio.sphinx.phpcode', 'sensio.sphinx.bestpractice', 'sensio.sphinx.codeblock',
- 'symfonycom.sphinx'
+ 'sphinx.ext.autodoc', 'sphinx.ext.doctest',
+ 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.ifconfig',
+ 'sphinx.ext.viewcode', 'sphinx.ext.extlinks',
+ 'sensio.sphinx.codeblock', 'sensio.sphinx.configurationblock', 'sensio.sphinx.phpcode', 'sensio.sphinx.bestpractice'
+ #,'sphinxcontrib.spelling'
]
+#spelling_show_sugestions=True
+#spelling_lang='en_US'
+#spelling_word_list_filename='_build/spelling_word_list.txt'
+
# Add any paths that contain templates here, relative to this directory.
# templates_path = ['_theme/_templates']
@@ -93,7 +100,7 @@
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = 'symfonycom.sphinx.SensioStyle'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
@@ -107,22 +114,27 @@
lexers['php-standalone'] = PhpLexer(startinline=True)
lexers['php-symfony'] = PhpLexer(startinline=True)
lexers['rst'] = RstLexer()
+lexers['varnish2'] = CLexer()
lexers['varnish3'] = CLexer()
lexers['varnish4'] = CLexer()
lexers['terminal'] = TerminalLexer()
+lexers['env'] = BashLexer()
config_block = {
+ 'apache': 'Apache',
'markdown': 'Markdown',
+ 'nginx': 'Nginx',
'rst': 'reStructuredText',
+ 'varnish2': 'Varnish 2',
'varnish3': 'Varnish 3',
'varnish4': 'Varnish 4'
}
-# use PHP as the primary domain
-primary_domain = 'php'
+# don't enable Sphinx Domains
+primary_domain = None
# set url for API links
-api_url = 'http://api.symfony.com/master/%s'
+api_url = 'https://github.com/symfony/symfony/blob/master/src/%s.php'
# -- Options for HTML output ---------------------------------------------------
@@ -130,12 +142,15 @@
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = "sphinx_rtd_theme"
-html_theme_path = ["_themes", ]
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
-#html_theme_options = {}
+html_theme_options = {
+ 'logo_only': True,
+ 'prev_next_buttons_location': None,
+ 'style_nav_header_background': '#f0f0f0'
+}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
@@ -149,7 +164,7 @@
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
-#html_logo = None
+html_logo = '_static/symfony-logo.svg'
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
@@ -159,7 +174,8 @@
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
-#html_static_path = ['_static']
+html_static_path = ['_static']
+html_css_files = ['rtd_custom.css']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
diff --git a/_build/maintainer_guide.rst b/_build/maintainer_guide.rst
new file mode 100644
index 00000000000..9788fd057f5
--- /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.4' '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.4`` 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.4
+ $ git merge upstream/4.4
+ $ 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.4
+ $ 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.4
+
+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.4
+ $ git merge upstream/4.4
+ $ 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.4
+ $ git merge upstream/4.4
+ $ 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 3ebfecff681..ccb41e8ada9 100644
--- a/_build/redirection_map
+++ b/_build/redirection_map
@@ -2,6 +2,7 @@
/cookbook/index /index
/book/stable_api /contributing/code/bc
/book/internals /reference/events
+/configuration/apache_router /routing
/cookbook/console/sending_emails /cookbook/console/request_context
/cookbook/deployment-tools /cookbook/deployment/tools
/cookbook/doctrine/migrations /bundles/DoctrineFixturesBundle/index
@@ -102,7 +103,7 @@
/cookbook/cache/form_csrf_caching /http_cache/form_csrf_caching
/cookbook/cache/varnish /http_cache/varnish
/cookbook/composer /setup/composer
-/cookbook/configuration/apache_router /configuration/apache_router
+/cookbook/configuration/apache_router /routing
/cookbook/configuration/configuration_organization /configuration/configuration_organization
/cookbook/configuration/environments /configuration/environments
/cookbook/configuration/external_parameters /configuration/external_parameters
@@ -116,7 +117,7 @@
/cookbook/console/commands_as_services /console/commands_as_services
/cookbook/console/console_command /console
/cookbook/console/index /console
-/cookbook/console/logging /console/logging
+/cookbook/console/logging /console
/cookbook/console/request_context /console/request_context
/cookbook/console/style /console/style
/cookbook/console/usage /console/usage
@@ -193,7 +194,7 @@
/cookbook/psr7 /components/psr7
/cookbook/request/index /request
/cookbook/request/load_balancer_reverse_proxy /deployment/proxies
-/cookbook/request/mime_type /reference/configuration/framework#formats
+/cookbook/request/mime_type /reference/configuration/framework
/cookbook/routing/conditions /routing/conditions
/cookbook/routing/custom_route_loader /routing/custom_route_loader
/cookbook/routing/debug /routing/debug
@@ -243,7 +244,8 @@
/cookbook/service_container/shared /service_container/shared
/cookbook/session/avoid_session_start /session/avoid_session_start
/cookbook/session/index /session
-/cookbook/session/limit_metadata_writes /session/limit_metadata_writes
+/cookbook/session/limit_metadata_writes /reference/configuration/framework
+/session/limit_metadata_writes /reference/configuration/framework
/cookbook/session/locale_sticky_session /session/locale_sticky_session
/cookbook/session/php_bridge /session/php_bridge
/cookbook/session/proxy_examples /session/proxy_examples
@@ -288,6 +290,8 @@
/components/class_loader/index /components/class_loader
/components/config/introduction /components/config
/components/config/index /components/config
+/components/console/helpers/tablehelper /components/console/helpers/table
+/components/console/helpers/progresshelper /components/console/helpers/progressbar
/components/console/introduction /components/console
/components/console/index /components/console
/components/debug/class_loader /components/debug
@@ -318,6 +322,7 @@
/components/form/type_guesser /form/type_guesser
/components/http_foundation/index /components/http_foundation
/components/http_foundation/introduction /components/http_foundation
+/request/load_balancer_reverse_proxy /deployment/proxies
/components/http_foundation/trusting_proxies /deployment/proxies
/components/http_kernel/introduction /components/http_kernel
/components/http_kernel/index /components/http_kernel
@@ -340,7 +345,31 @@
/components/yaml/index /components/yaml
/deployment/tools /deployment
/install/bundles /setup/bundles
+/event_dispatcher/class_extension /event_dispatcher
/form /forms
+/form/use_virtual_forms /form/inherit_data_option
+/security/target_path /security
+/service_container/service_locators /service_container/service_subscribers_locators
+/service_container/third_party /service_container
+/templating/templating_service /templates
/testing/simulating_authentication /testing/http_authentication
/validation/group_service_resolver /form/validation_group_service_resolver
-/request/load_balancer_reverse_proxy /deployment/proxies
\ No newline at end of file
+/request/load_balancer_reverse_proxy /deployment/proxies
+/service_container/service_locators /service_container/service_subscribers_locators
+/weblink /web_link
+/components/weblink /components/web_link
+/frontend/encore/installation-no-flex /frontend/encore/installation
+/console/logging /console
+/frontend/encore/legacy-apps /frontend/encore/legacy-applications
+/contributing/code/patches /contributing/code/pull_requests
+/workflow/state-machines /workflow/workflow-and-state-machine
+/workflow/introduction /workflow/workflow-and-state-machine
+/workflow/usage /workflow
+/introduction/from_flat_php_to_symfony2 /introduction/from_flat_php_to_symfony
+/contributing/community/other /contributing/community
+/setup/composer /setup
+/components/debug https://github.com/symfony/debug
+/components/translation https://github.com/symfony/translation
+/components/translation/usage /translation
+/components/translation/custom_formats https://github.com/symfony/translation
+/components/translation/custom_message_formatter https://github.com/symfony/translation
diff --git a/_build/spelling_word_list.txt b/_build/spelling_word_list.txt
new file mode 100644
index 00000000000..3b1d630fa11
--- /dev/null
+++ b/_build/spelling_word_list.txt
@@ -0,0 +1,346 @@
+accessor
+Akamai
+analytics
+Ansi
+Ansible
+Assetic
+async
+authenticator
+authenticators
+autocompleted
+autocompletion
+autoconfiguration
+autoconfigure
+autoconfigured
+autoconfigures
+autoconfiguring
+autoload
+autoloaded
+autoloader
+autoloaders
+autoloading
+autoprefixing
+autowire
+autowireable
+autowired
+autowiring
+backend
+backends
+balancer
+balancers
+bcrypt
+benchmarking
+Bitbucket
+bitmask
+bitmasks
+bitwise
+Blackfire
+boolean
+booleans
+Brasseur
+browserslist
+buildpack
+buildpacks
+bundler
+cacheable
+Caddy
+callables
+camelCase
+casted
+changelog
+changeset
+charset
+charsets
+checkboxes
+classmap
+classname
+clearers
+cloner
+cloners
+codebase
+config
+configs
+configurator
+configurators
+contrib
+cron
+cronjobs
+cryptographic
+cryptographically
+Ctrl
+ctype
+cURL
+customizable
+customizations
+Cygwin
+dataset
+datepicker
+decrypt
+denormalization
+denormalize
+denormalized
+denormalizing
+deprecations
+deserialization
+deserialize
+deserialized
+deserializing
+destructor
+dev
+dn
+DNS
+docblock
+Dotenv
+downloader
+Doxygen
+DSN
+Dunglas
+easter
+Eberlei
+emilie
+enctype
+entrypoints
+enum
+env
+escaper
+escpaer
+extensibility
+extractable
+eZPublish
+Fabien
+failover
+filesystem
+filesystems
+formatter
+formatters
+fortrabbit
+frontend
+getter
+getters
+GitHub
+gmail
+Gmail
+Goutte
+grapheme
+hardcode
+hardcoded
+hardcodes
+hardcoding
+hasser
+hassers
+headshot
+HInclude
+hostname
+https
+iconv
+igbinary
+incrementing
+ini
+inlined
+inlining
+installable
+instantiation
+interoperable
+intl
+Intl
+invokable
+IPv
+isser
+issers
+Jpegoptim
+jQuery
+js
+Karlton
+kb
+kB
+Kévin
+Ki
+KiB
+kibibyte
+Kubernetes
+Kudu
+labelled
+latin
+Ldap
+libketama
+licensor
+lifecycle
+liip
+linter
+localhost
+Loggly
+Logplex
+lookups
+loopback
+lorenzo
+Luhn
+macOS
+matcher
+matchers
+mbstring
+mebibyte
+memcache
+memcached
+MiB
+michelle
+minification
+minified
+minifier
+minifies
+minify
+minifying
+misconfiguration
+misconfigured
+misgendering
+Monolog
+mutator
+nagle
+namespace
+namespaced
+namespaces
+namespacing
+natively
+nd
+netmasks
+nginx
+normalizer
+normalizers
+npm
+nyholm
+OAuth
+OPcache
+overcomplicate
+Packagist
+parallelizes
+parsers
+PHP
+PHPUnit
+PID
+plaintext
+polyfill
+polyfills
+postcss
+Potencier
+pre
+preconfigured
+predefines
+Predis
+preload
+preloaded
+preloading
+prepend
+prepended
+prepending
+prepends
+preprocessed
+preprocessors
+Procfile
+profiler
+programmatically
+prototyped
+rebase
+reconfiguring
+reconnection
+redirections
+refactorization
+regexes
+renderer
+resolvers
+responder
+reStructuredText
+reusability
+runtime
+sandboxing
+schemas
+screencast
+semantical
+serializable
+serializer
+sexualized
+Silex
+sluggable
+socio
+specificities
+SQLite
+stacktrace
+stacktraces
+storages
+stringified
+stylesheet
+stylesheets
+subclasses
+subdirectories
+subdirectory
+sublcasses
+sublicense
+sublincense
+subrequests
+subtree
+superclass
+superglobal
+superglobals
+symfony
+Symfony
+symlink
+symlinks
+syntaxes
+templating
+testability
+th
+theming
+throbber
+timestampable
+timezones
+TLS
+tmpfs
+tobias
+todo
+Tomayko
+Toolbelt
+tooltip
+Traversable
+triaging
+UI
+uid
+unary
+unauthenticate
+uncacheable
+uncached
+uncomment
+uncommented
+undelete
+unhandled
+unicode
+Unix
+unmapped
+unminified
+unported
+unregister
+unrendered
+unserialize
+unserialized
+unserializing
+unsubmitted
+untracked
+uploader
+URI
+validator
+validators
+variadic
+VirtualBox
+Vue
+webpack
+webpacked
+webpackJsonp
+webserver
+whitespace
+whitespaces
+woh
+Wordpress
+Xdebug
+xkcd
+Xliff
+XML
+XPath
+yaml
+yay
diff --git a/_images/components/workflow/blogpost.png b/_images/components/workflow/blogpost.png
new file mode 100644
index 00000000000..38e29250eb1
Binary files /dev/null and b/_images/components/workflow/blogpost.png differ
diff --git a/_images/components/workflow/job_application.png b/_images/components/workflow/job_application.png
new file mode 100644
index 00000000000..9c5e6792ae9
Binary files /dev/null and b/_images/components/workflow/job_application.png differ
diff --git a/_images/components/workflow/pull_request.png b/_images/components/workflow/pull_request.png
new file mode 100644
index 00000000000..1aa8886728c
Binary files /dev/null and b/_images/components/workflow/pull_request.png differ
diff --git a/_images/components/workflow/simple.png b/_images/components/workflow/simple.png
new file mode 100644
index 00000000000..ed158d5cc7a
Binary files /dev/null and b/_images/components/workflow/simple.png differ
diff --git a/_images/components/workflow/states_transitions.png b/_images/components/workflow/states_transitions.png
new file mode 100644
index 00000000000..1e68f9ca597
Binary files /dev/null and b/_images/components/workflow/states_transitions.png differ
diff --git a/_images/contributing/docs-pull-request-platformsh.png b/_images/contributing/docs-pull-request-platformsh.png
deleted file mode 100644
index 30077cce94f..00000000000
Binary files a/_images/contributing/docs-pull-request-platformsh.png and /dev/null differ
diff --git a/_images/contributing/docs-pull-request-symfonycloud.png b/_images/contributing/docs-pull-request-symfonycloud.png
new file mode 100644
index 00000000000..0c485c1491c
Binary files /dev/null and b/_images/contributing/docs-pull-request-symfonycloud.png differ
diff --git a/_images/contributing/release-process.jpg b/_images/contributing/release-process.jpg
deleted file mode 100644
index 9868404b07f..00000000000
Binary files a/_images/contributing/release-process.jpg and /dev/null differ
diff --git a/_images/controller/error_pages/exceptions-in-dev-environment.png b/_images/controller/error_pages/exceptions-in-dev-environment.png
index 225dcdfa0dc..5e7da2cf6a1 100644
Binary files a/_images/controller/error_pages/exceptions-in-dev-environment.png and b/_images/controller/error_pages/exceptions-in-dev-environment.png differ
diff --git a/_images/http/xkcd-full.png b/_images/http/xkcd-full.png
index 58edf13f3f3..d5b01ea32b9 100644
Binary files a/_images/http/xkcd-full.png and b/_images/http/xkcd-full.png differ
diff --git a/_images/http/xkcd-request.png b/_images/http/xkcd-request.png
index 86e767db9b5..310713d304c 100644
Binary files a/_images/http/xkcd-request.png and b/_images/http/xkcd-request.png differ
diff --git a/_images/quick_tour/web_debug_toolbar.png b/_images/quick_tour/web_debug_toolbar.png
index 86249e862f3..72cd7483f2f 100644
Binary files a/_images/quick_tour/web_debug_toolbar.png and b/_images/quick_tour/web_debug_toolbar.png differ
diff --git a/_images/security/anonymous_wdt.png b/_images/security/anonymous_wdt.png
index 4803ed1b108..8dbf1cd8298 100644
Binary files a/_images/security/anonymous_wdt.png and b/_images/security/anonymous_wdt.png differ
diff --git a/_images/security/authentication-guard-methods.svg b/_images/security/authentication-guard-methods.svg
index b3a44007101..a18da9e66dc 100644
--- a/_images/security/authentication-guard-methods.svg
+++ b/_images/security/authentication-guard-methods.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/_images/security/symfony_loggedin_wdt.png b/_images/security/symfony_loggedin_wdt.png
index 6605c35edf7..ca182192c94 100644
Binary files a/_images/security/symfony_loggedin_wdt.png and b/_images/security/symfony_loggedin_wdt.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/security/authentication-guard-methods.dia b/_images/sources/security/authentication-guard-methods.dia
index 1acb7876337..283e74b2e05 100644
Binary files a/_images/sources/security/authentication-guard-methods.dia and b/_images/sources/security/authentication-guard-methods.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 37a20edf4a9..a944097f917 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 75a883e6539..3c329743eb0 100644
--- a/best_practices/business-logic.rst
+++ b/best_practices/business-logic.rst
@@ -21,6 +21,8 @@ Inside here, you can create whatever directories you want to organize things:
│ └─ AppBundle/
│ └─ Utils/
│ └─ MyClass.php
+ ├─ tests/
+ ├─ var/
├─ vendor/
└─ web/
@@ -40,6 +42,8 @@ and put things there:
│ │ └─ Utils/
│ │ └─ MyClass.php
│ └─ AppBundle/
+ ├─ tests/
+ ├─ var/
├─ vendor/
└─ web/
@@ -78,36 +82,58 @@ Next, define a new service for that class.
# app/config/services.yml
services:
- # keep your service names short
- app.slugger:
- class: AppBundle\Utils\Slugger
+ # ...
+
+ # use the fully-qualified class name as the service id
+ AppBundle\Utils\Slugger:
+ public: false
+
+.. note::
-Traditionally, the naming convention for a service involved following the
-class name and location to avoid name collisions. Thus, the service
-*would have been* called ``app.utils.slugger``. But by using short service names,
-your code will be easier to read and use.
+ If you're using the :ref:`default services.yml configuration `,
+ the class is auto-registered as a service.
+
+Traditionally, the naming convention for a service was a short, but unique
+snake case key - e.g. ``app.utils.slugger``. But for most services, you should now
+use the class name.
.. best-practice::
- The name of your application's services should be as short as possible,
- but unique enough that you can search your project for the service if
- you ever need to.
+ The id of your application's services should be equal to their class name,
+ except when you have multiple services configured for the same class (in that
+ case, use a snake case id).
Now you can use the custom slugger in any controller class, such as the
``AdminController``::
- public function createAction(Request $request)
+ use AppBundle\Utils\Slugger;
+
+ public function createAction(Request $request, Slugger $slugger)
{
// ...
+ // you can also fetch a public service like this
+ // but fetching services in this way is not considered a best practice
+ // $slugger = $this->get('app.slugger');
+
if ($form->isSubmitted() && $form->isValid()) {
- $slug = $this->get('app.slugger')->slugify($post->getTitle());
+ $slug = $slugger->slugify($post->getTitle());
$post->setSlug($slug);
// ...
}
}
+Services can also be :ref:`public or private `. If you use the
+:ref:`default services.yml configuration `,
+all services are private by default.
+
+.. best-practice::
+
+ Services should be ``private`` whenever possible. This will prevent you from
+ accessing that service via ``$container->get()``. Instead, you will need to use
+ dependency injection.
+
Service Format: YAML
--------------------
@@ -122,8 +148,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
-of course use whatever format you like.
+We recommend YAML because it's friendly to newcomers and concise, but you can
+use whatever format you like.
Service: No Class Parameter
---------------------------
@@ -150,7 +176,7 @@ This practice is cumbersome and completely unnecessary for your own services.
Don't define parameters for the classes of your services.
This practice was wrongly adopted from third-party bundles. When Symfony
-introduced its service container, some developers used this technique to easily
+introduced its service container, some developers used this technique to
allow overriding services. However, overriding a service by just changing its
class name is a very rare use case because, frequently, the new service has
different constructor arguments.
@@ -165,6 +191,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
AppBundle.
@@ -183,8 +218,7 @@ The three entities defined by our sample blog application are a good example:
.. tip::
- If you're more advanced, you can of course store them under your own
- namespace in ``src/``.
+ If you're more advanced, you can store them under your own namespace in ``src/``.
Doctrine Mapping Information
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -203,8 +237,8 @@ looking for mapping information::
namespace AppBundle\Entity;
- use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
+ use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
@@ -275,7 +309,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, enable the bundle in ``AppKernel.php``, but only for the ``dev`` and
``test`` environments::
@@ -286,11 +320,11 @@ Then, enable the bundle in ``AppKernel.php``, but only for the ``dev`` and
{
public function registerBundles()
{
- $bundles = array(
+ $bundles = [
// ...
- );
+ ];
- if (in_array($this->getEnvironment(), array('dev', 'test'))) {
+ if (in_array($this->getEnvironment(), ['dev', 'test'])) {
// ...
$bundles[] = new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle();
}
@@ -310,7 +344,7 @@ command:
.. code-block:: terminal
- $ php app/console doctrine:fixtures:load
+ $ php bin/console doctrine:fixtures:load
Careful, database will be purged. Do you want to continue Y/N ? Y
> purging database
diff --git a/best_practices/configuration.rst b/best_practices/configuration.rst
index 32851f132c3..d6e51c47451 100644
--- a/best_practices/configuration.rst
+++ b/best_practices/configuration.rst
@@ -52,8 +52,8 @@ Canonical Parameters
Define all your application's parameters in the
``app/config/parameters.yml.dist`` file.
-Since version 2.3, Symfony includes a configuration file called ``parameters.yml.dist``,
-which stores the canonical list of configuration parameters for the application.
+Symfony includes a configuration file called ``parameters.yml.dist``, which
+stores the canonical list of configuration parameters for the application.
Whenever a new configuration parameter is defined for the application, you
should also add it to this file and submit the changes to your version control
@@ -94,8 +94,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
@@ -132,13 +132,13 @@ 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 AppBundle\Repository;
- use Doctrine\ORM\EntityRepository;
use AppBundle\Entity\Post;
+ use Doctrine\ORM\EntityRepository;
class PostRepository extends EntityRepository
{
@@ -149,7 +149,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
----------------
@@ -198,8 +198,21 @@ Moving Sensitive Options Outside of Symfony Entirely
When dealing with sensitive options, like database credentials, we also recommend
that you store them outside the Symfony project and make them available
-through environment variables. Learn how to do it in the following article:
-:doc:`/configuration/external_parameters`.
+through environment variables:
+
+.. code-block:: yaml
+
+ # app/config/config.yml
+ doctrine:
+ dbal:
+ # ...
+ password: "%env(DB_PASSWORD)%"
+
+.. versionadded:: 3.2
+
+ Support for runtime environment variables via the ``%env(...)%`` syntax
+ was introduced in Symfony 3.2. Prior to version 3.2, you needed to use the
+ :doc:`special SYMFONY__ variables `.
----
diff --git a/best_practices/controllers.rst b/best_practices/controllers.rst
index 000293ae1dd..b96facacae3 100644
--- a/best_practices/controllers.rst
+++ b/best_practices/controllers.rst
@@ -6,8 +6,8 @@ means that controllers should hold just the thin layer of *glue-code*
needed to coordinate the different parts of the application.
As a rule of thumb, you should follow the 5-10-20 rule, where controllers should
-only define 5 variables or less, contain 10 actions or less and include 20 lines
-of code or less in each action. This isn't an exact science, but it should
+only define 5 variables or fewer, contain 10 actions or fewer and include 20 lines
+of code or fewer in each action. This isn't an exact science, but it should
help you realize when code should be refactored out of the controller and
into a service.
@@ -48,7 +48,7 @@ configuration to the main routing configuration file:
This configuration will load annotations from any controller stored inside the
``src/AppBundle/Controller/`` directory and even from its subdirectories.
-So if your application defines lots of controllers, it's perfectly ok to
+So if your application defines lots of controllers, it's perfectly OK to
reorganize them into subdirectories:
.. code-block:: text
@@ -80,10 +80,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
----------------------------------
@@ -95,7 +95,7 @@ for the homepage of our app::
use AppBundle\Entity\Post;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
- use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
+ use Symfony\Component\Routing\Annotation\Route;
class DefaultController extends Controller
{
@@ -108,12 +108,28 @@ for the homepage of our app::
->getRepository(Post::class)
->findLatest();
- return $this->render('default/index.html.twig', array(
+ return $this->render('default/index.html.twig', [
'posts' => $posts,
- ));
+ ]);
}
}
+Fetching Services
+-----------------
+
+If you extend the base ``Controller`` class, you can access services directly from
+the container via ``$this->container->get()`` or ``$this->get()``. But instead, you
+should use dependency injection to fetch services by
+:ref:`type-hinting action method arguments `:
+
+.. best-practice::
+
+ Don't use ``$this->get()`` or ``$this->container->get()`` to fetch services
+ from the container. Instead, use dependency injection.
+
+By not fetching services directly from the container, you can make your services
+*private*, which has :ref:`several advantages `.
+
.. _best-practices-paramconverter:
Using the ParamConverter
@@ -130,7 +146,7 @@ to automatically query for an entity and pass it as an argument to your controll
For example::
use AppBundle\Entity\Post;
- use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
+ use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/{id}", name="admin_post_show")
@@ -139,10 +155,10 @@ For example::
{
$deleteForm = $this->createDeleteForm($post);
- return $this->render('admin/post/show.html.twig', array(
+ return $this->render('admin/post/show.html.twig', [
'post' => $post,
'delete_form' => $deleteForm->createView(),
- ));
+ ]);
}
Normally, you'd expect a ``$id`` argument to ``showAction()``. Instead, by
@@ -166,7 +182,7 @@ manually. In our application, we have this situation in ``CommentController``::
{
$post = $this->getDoctrine()
->getRepository(Post::class)
- ->findOneBy(array('slug' => $postSlug));
+ ->findOneBy(['slug' => $postSlug]);
if (!$post) {
throw $this->createNotFoundException();
@@ -179,9 +195,9 @@ You can also use the ``@ParamConverter`` configuration, which is infinitely
flexible::
use AppBundle\Entity\Post;
- use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\HttpFoundation\Request;
+ use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/comment/{postSlug}/new", name="comment_new")
@@ -192,9 +208,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 6aa9cdbffc2..1b4c1f4a695 100644
--- a/best_practices/creating-the-project.rst
+++ b/best_practices/creating-the-project.rst
@@ -25,15 +25,21 @@ Now that everything is correctly set up, you can create a new project based on
Symfony. In your command console, browse to a directory where you have permission
to create files and execute the following commands:
+**Linux and macOS systems**:
+
+.. rst-class:: command-linux
.. code-block:: terminal
- # Linux, Mac OS X
$ cd projects/
- $ symfony new blog
+ $ symfony new blog --version=3.4
+
+**Windows systems**:
- # Windows
- c:\> cd projects/
- c:\projects\> php symfony new blog
+.. rst-class:: command-windows
+.. code-block:: terminal
+
+ > cd projects/
+ > php symfony new blog --version=3.4
.. note::
@@ -63,13 +69,18 @@ number of files and directories generated automatically:
blog/
├─ app/
- │ ├─ console
- │ ├─ cache/
│ ├─ config/
- │ ├─ logs/
│ └─ Resources/
+ ├─ bin
+ │ └─ console
├─ src/
│ └─ AppBundle/
+ ├─ var/
+ │ ├─ cache/
+ │ ├─ logs/
+ │ └─ sessions/
+ ├─ tests/
+ │ └─ AppBundle/
├─ vendor/
└─ web/
@@ -77,13 +88,16 @@ This file and directory hierarchy is the convention proposed by Symfony to
structure your applications. The recommended purpose of each directory is the
following:
-* ``app/cache/``, stores all the cache files generated by the application;
* ``app/config/``, stores all the configuration defined for any environment;
-* ``app/logs/``, stores all the log files generated by the application;
* ``app/Resources/``, stores all the templates and the translation files for the
application;
* ``src/AppBundle/``, stores the Symfony specific code (controllers and routes),
your domain code (e.g. Doctrine classes) and all your business logic;
+* ``var/cache/``, stores all the cache files generated by the application;
+* ``var/logs/``, stores all the log files generated by the application;
+* ``var/sessions/``, stores all the session files generated by the application;
+* ``tests/AppBundle/``, stores the automatic tests (e.g. Unit tests) of the
+ application.
* ``vendor/``, this is the directory where Composer installs the application's
dependencies and you should never modify any of its contents;
* ``web/``, stores all the front controller files and all the web assets, such
@@ -92,23 +106,22 @@ following:
Application Bundles
~~~~~~~~~~~~~~~~~~~
-When Symfony 2.0 was released, most developers naturally adopted the symfony
+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 use bundles to divide their code into logical features: UserBundle,
+applications use 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::
Create only one bundle called AppBundle for your application logic.
Implementing a single AppBundle bundle in your projects will make your code
-more concise and easier to understand. Starting in Symfony 2.6, the official
-Symfony documentation uses the AppBundle name.
+more concise and easier to understand.
.. note::
@@ -128,13 +141,18 @@ that follows these best practices:
blog/
├─ app/
- │ ├─ console
- │ ├─ cache/
│ ├─ config/
- │ ├─ logs/
│ └─ Resources/
+ ├─ bin/
+ │ └─ console
├─ src/
│ └─ AppBundle/
+ ├─ tests/
+ │ └─ AppBundle/
+ ├─ var/
+ │ ├─ cache/
+ │ ├─ logs/
+ └─ sessions/
├─ vendor/
└─ web/
├─ app.php
@@ -147,7 +165,7 @@ that follows these best practices:
.. code-block:: terminal
- $ php app/console generate:bundle --namespace=AppBundle --dir=src --format=annotation --no-interaction
+ $ php bin/console generate:bundle --namespace=AppBundle --dir=src --format=annotation --no-interaction
Extending the Directory Structure
---------------------------------
@@ -157,26 +175,6 @@ structure of Symfony, you can
:doc:`override the location of the main directories `:
``cache/``, ``logs/`` and ``web/``.
-In addition, Symfony3 uses a slightly different directory structure:
-
-.. code-block:: text
-
- blog-symfony3/
- ├─ app/
- │ ├─ config/
- │ └─ Resources/
- ├─ bin/
- │ └─ console
- ├─ src/
- ├─ var/
- │ ├─ cache/
- │ └─ logs/
- ├─ vendor/
- └─ web/
-
-The changes are pretty superficial, but for now, we recommend that you use
-the Symfony directory structure.
-
----
Next: :doc:`/best_practices/configuration`
diff --git a/best_practices/forms.rst b/best_practices/forms.rst
index 702f72679c6..0e556216eeb 100644
--- a/best_practices/forms.rst
+++ b/best_practices/forms.rst
@@ -21,11 +21,11 @@ form in its own PHP class::
use AppBundle\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
{
@@ -42,9 +42,9 @@ form in its own PHP class::
public function configureOptions(OptionsResolver $resolver)
{
- $resolver->setDefaults(array(
+ $resolver->setDefaults([
'data_class' => Post::class,
- ));
+ ]);
}
}
@@ -85,9 +85,10 @@ makes them easier to re-use later.
Add buttons in the templates, not in the form classes or the controllers.
-Since Symfony 2.3, you can add buttons as fields on your form. This is a nice
-way to simplify the template that renders your form. But if you add the buttons
-directly in your form class, this would effectively limit the scope of that form::
+The Symfony Form component allows you to add buttons as fields on your form.
+This is a nice way to simplify the template that renders your form. But if you
+add the buttons directly in your form class, this would effectively limit the
+scope of that form::
class PostType extends AbstractType
{
@@ -95,7 +96,7 @@ directly in your form class, this would effectively limit the scope of that form
{
$builder
// ...
- ->add('save', SubmitType::class, array('label' => 'Create Post'))
+ ->add('save', SubmitType::class, ['label' => 'Create Post'])
;
}
@@ -108,11 +109,11 @@ some developers configure form buttons in the controller::
namespace AppBundle\Controller\Admin;
- use Symfony\Component\HttpFoundation\Request;
- use Symfony\Bundle\FrameworkBundle\Controller\Controller;
- use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use AppBundle\Entity\Post;
use AppBundle\Form\PostType;
+ use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+ use Symfony\Component\Form\Extension\Core\Type\SubmitType;
+ use Symfony\Component\HttpFoundation\Request;
class PostController extends Controller
{
@@ -122,10 +123,10 @@ some developers configure form buttons in the controller::
{
$post = new Post();
$form = $this->createForm(PostType::class, $post);
- $form->add('submit', SubmitType::class, array(
+ $form->add('submit', SubmitType::class, [
'label' => 'Create',
- 'attr' => array('class' => 'btn btn-default pull-right'),
- ));
+ 'attr' => ['class' => 'btn btn-default pull-right'],
+ ]);
// ...
}
@@ -142,7 +143,7 @@ view layer:
{{ form_widget(form) }}
+ class="btn btn-default pull-right"/>
{{ form_end(form) }}
Validation
@@ -185,7 +186,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) }}
@@ -214,7 +215,7 @@ Handling a form submit usually follows a similar template::
return $this->redirect($this->generateUrl(
'admin_post_show',
- array('id' => $post->getId())
+ ['id' => $post->getId()]
));
}
@@ -228,10 +229,9 @@ and a ``createAction()`` that *only* processes the form submit. Both those
actions will be almost identical. So it's much simpler to let ``newAction()``
handle everything.
-Second, we recommend using ``$form->isSubmitted()`` in the ``if`` statement
-for clarity. This isn't technically needed, since ``isValid()`` first calls
-``isSubmitted()``. But without this, the flow doesn't read well as it *looks*
-like the form is *always* processed (even on the GET request).
+Second, is it required to call ``$form->isSubmitted()`` in the ``if`` statement
+before calling ``isValid()``. Calling ``isValid()`` with an unsubmitted form
+is deprecated since version 3.2 and will throw an exception in 4.0.
----
diff --git a/best_practices/i18n.rst b/best_practices/i18n.rst
index 1973d8652e6..57f3c1c94de 100644
--- a/best_practices/i18n.rst
+++ b/best_practices/i18n.rst
@@ -32,15 +32,14 @@ Of all the available translation formats, only XLIFF and gettext have broad
support in the tools used by professional translators. And since it's based
on XML, you can validate XLIFF file contents as you write them.
-Symfony 2.6 added support for notes inside XLIFF files, making them more
-user-friendly for translators. At the end, good translations are all about
-context, and these XLIFF notes allow you to define that context.
+Symfony supports notes in XLIFF files, making them more user-friendly for
+translators. At the end, good translations are all about context, and these
+XLIFF notes allow you to define that context.
.. tip::
- The Apache-licensed `JMSTranslationBundle`_ offers you a web interface for
- viewing and editing these translation files. It also has advanced extractors
- that can read your project and automatically update the XLIFF files.
+ The `PHP Translation Bundle`_ includes advanced extractors that can read
+ your project and automatically update the XLIFF files.
Translation Source File Location
--------------------------------
@@ -55,7 +54,7 @@ Traditionally, Symfony developers have created these files in the
``app/Resources/`` directory is considered the global location for the
application's resources, storing translations in ``app/Resources/translations/``
centralizes them *and* gives them priority over any other translation file.
-This let's you override translations defined in third-party bundles.
+This lets you override translations defined in third-party bundles.
Translation Keys
----------------
@@ -97,4 +96,4 @@ English in the application would be:
Next: :doc:`/best_practices/security`
-.. _`JMSTranslationBundle`: https://github.com/schmittjoh/JMSTranslationBundle
+.. _`PHP Translation Bundle`: https://github.com/php-translation/symfony-bundle
diff --git a/best_practices/introduction.rst b/best_practices/introduction.rst
index 48a6f395754..cb510c34462 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,14 +44,13 @@ 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
-continue using your own best practices and methodologies, you can of course
-do it. Symfony is flexible enough to adapt to your needs. That will never
-change.
+Keep in mind that these are **optional recommendations** that you and your team
+may or may not follow to develop Symfony applications. If you want to continue
+using your own best practices and methodologies, you can do it. Symfony is
+flexible enough to adapt to your needs. That will never change.
Who this Book Is for (Hint: It's not a Tutorial)
------------------------------------------------
@@ -76,12 +75,8 @@ installer and then execute this command to download the demo application:
.. code-block:: terminal
- # Linux and Mac OS X
$ symfony demo
- # Windows
- c:\> php symfony demo
-
**The demo application is a simple blog engine**, because that will allow us to
focus on the Symfony concepts and features without getting buried in difficult
implementation details. Instead of developing the application step by step in
diff --git a/best_practices/security.rst b/best_practices/security.rst
index 258f6667d63..63d95e27458 100644
--- a/best_practices/security.rst
+++ b/best_practices/security.rst
@@ -6,7 +6,7 @@ Authentication and Firewalls (i.e. Getting the User's Credentials)
You can configure Symfony to authenticate your users using any method you
want and to load user information from any source. This is a complex topic, but
-the :doc:`Security guide` has a lot of information about
+the :doc:`Security guide ` has a lot of information about
this.
Regardless of your needs, authentication is configured in ``security.yml``,
@@ -19,10 +19,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
@@ -38,6 +38,13 @@ of ``bcrypt`` are the inclusion of a *salt* value to protect against rainbow
table attacks, and its adaptive nature, which allows to make it slower to
remain resistant to brute-force search attacks.
+.. note::
+
+ :ref:`Argon2i ` is the hashing algorithm as
+ recommended by industry standards, but this won't be available to you unless
+ you are using PHP 7.2+ or have the `libsodium`_ extension installed.
+ ``bcrypt`` is sufficient for most applications.
+
With this in mind, here is the authentication setup from our application,
which uses a login form to load users from the database:
@@ -101,16 +108,13 @@ 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:
+Using ``@Security``, this looks like::
-.. code-block:: php
-
- use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
+ use Symfony\Component\Routing\Annotation\Route;
// ...
/**
@@ -133,8 +137,8 @@ controller if their email matches the value returned by the ``getAuthorEmail()``
method on the ``Post`` object::
use AppBundle\Entity\Post;
- use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
+ use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/{id}/edit", name="admin_post_edit")
@@ -149,18 +153,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/AppBundle/Entity/Post.php
@@ -185,6 +189,7 @@ Now you can reuse this method both in the template and in the security expressio
use AppBundle\Entity\Post;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
+ use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/{id}/edit", name="admin_post_edit")
@@ -195,7 +200,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) %}
...
@@ -218,7 +223,8 @@ more advanced use-case, you can always do the same security check in PHP::
*/
public function editAction($id)
{
- $post = $this->getDoctrine()->getRepository(Post::class)
+ $post = $this->getDoctrine()
+ ->getRepository(Post::class)
->find($id);
if (!$post) {
@@ -253,13 +259,12 @@ the same ``getAuthorEmail()`` logic you used above::
namespace AppBundle\Security;
+ use AppBundle\Entity\Post;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\User\UserInterface;
- use AppBundle\Entity\Post;
- // Voter class requires Symfony 2.8 or higher version
class PostVoter extends Voter
{
const CREATE = 'create';
@@ -277,7 +282,7 @@ the same ``getAuthorEmail()`` logic you used above::
protected function supports($attribute, $subject)
{
- if (!in_array($attribute, array(self::CREATE, self::EDIT))) {
+ if (!in_array($attribute, [self::CREATE, self::EDIT])) {
return false;
}
@@ -301,7 +306,7 @@ the same ``getAuthorEmail()`` logic you used above::
switch ($attribute) {
case self::CREATE:
// if the user is an admin, allow them to create new posts
- if ($this->decisionManager->decide($token, array('ROLE_ADMIN'))) {
+ if ($this->decisionManager->decide($token, ['ROLE_ADMIN'])) {
return true;
}
@@ -319,19 +324,10 @@ the same ``getAuthorEmail()`` logic you used above::
}
}
-To enable the security voter in the application, define a new service:
-
-.. code-block:: yaml
-
- # app/config/services.yml
- services:
- # ...
- app.post_voter:
- class: AppBundle\Security\PostVoter
- arguments: ['@security.access.decision_manager']
- public: false
- tags:
- - { name: security.voter }
+If you're using the :ref:`default services.yml configuration `,
+your application will :ref:`autoconfigure ` your security
+voter and inject an ``AccessDecisionManagerInterface`` instance into it thanks to
+:doc:`autowiring `.
Now, you can use the voter with the ``@Security`` annotation::
@@ -389,5 +385,5 @@ develop :doc:`your own user provider ` and
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 c51529560fa..b1327a72fdf 100644
--- a/best_practices/templates.rst
+++ b/best_practices/templates.rst
@@ -30,8 +30,8 @@ Template Locations
Store all your application's templates in ``app/Resources/views/`` directory.
Traditionally, Symfony developers stored the application templates in the
-``Resources/views/`` directory of each bundle. Then they used the Twig namespaced
-path to refer to them (e.g. ``@AcmeDemo/Default/index.html.twig``).
+``Resources/views/`` directory of each bundle. Then they used Twig namespaces
+to refer to them (e.g. ``@AcmeDemo/Default/index.html.twig``).
But for the templates used in your application, it's much more convenient
to store them in the ``app/Resources/views/`` directory. For starters, this
@@ -51,14 +51,14 @@ scattered through lots of bundles.
.. best-practice::
- Use lowercased snake_case for directory and template names.
+ Use lowercase snake_case for directory and template names.
.. best-practice::
Use a prefixed underscore for partial templates in template names.
You often want to reuse template code using the ``include`` function to avoid
-redundant code. To determine those partials 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.
@@ -67,8 +67,8 @@ Twig Extensions
.. best-practice::
- Define your Twig extensions in the ``AppBundle/Twig/`` directory and
- configure them using the ``app/config/services.yml`` file.
+ Define your Twig extensions in the ``AppBundle/Twig/`` directory. Your
+ application will automatically detect them and configure them.
Our application needs a custom ``md2html`` Twig filter so that we can transform
the Markdown contents of each post into HTML.
@@ -80,18 +80,8 @@ a new dependency of the project:
$ composer require erusev/parsedown
-Then, create a new ``Markdown`` service that will be used later by the Twig
-extension. The service definition only requires the path to the class:
-
-.. code-block:: yaml
-
- # app/config/services.yml
- services:
- # ...
- app.markdown:
- class: AppBundle\Utils\Markdown
-
-And the ``Markdown`` class just needs to define one single method to transform
+Then, create a new ``Markdown`` class that will be used later by the Twig
+extension. It just needs to define one single method to transform
Markdown content into HTML::
namespace AppBundle\Utils;
@@ -112,8 +102,8 @@ Markdown content into HTML::
}
Next, create a new Twig extension and define a new filter called ``md2html``
-using the ``Twig\TwigFilter`` class. Inject the newly defined ``markdown``
-service in the constructor of the Twig extension::
+using the ``Twig\TwigFilter`` class. Inject the newly defined ``Markdown``
+class in the constructor of the Twig extension::
namespace AppBundle\Twig;
@@ -132,13 +122,13 @@ service in the constructor of the Twig extension::
public function getFilters()
{
- return array(
+ return [
new TwigFilter(
'md2html',
- array($this, 'markdownToHtml'),
- array('is_safe' => array('html'), 'pre_escape' => 'html')
+ [$this, 'markdownToHtml'],
+ ['is_safe' => ['html'], 'pre_escape' => 'html']
),
- );
+ ];
}
public function markdownToHtml($content)
@@ -152,23 +142,15 @@ service in the constructor of the Twig extension::
}
}
-Lastly define a new service to enable this Twig extension in the app (the service
-name is irrelevant because you never use it in your own code):
-
-.. code-block:: yaml
+And that's it!
- # app/config/services.yml
- services:
- app.twig.app_extension:
- class: AppBundle\Twig\AppExtension
- arguments: ['@app.markdown']
- public: false
- tags:
- - { name: twig.extension }
+If you're using the :ref:`default services.yml configuration `,
+you're done! Symfony will automatically know about your new service and tag it to
+be used as a Twig extension.
----
Next: :doc:`/best_practices/forms`
.. _`Twig`: https://twig.symfony.com/
-.. _`Parsedown`: http://parsedown.org/
+.. _`Parsedown`: https://parsedown.org/
diff --git a/best_practices/tests.rst b/best_practices/tests.rst
index 42a40b1a7c9..f6f67685535 100644
--- a/best_practices/tests.rst
+++ b/best_practices/tests.rst
@@ -29,8 +29,8 @@ functional tests, you can quickly spot any big errors before you deploy them:
A functional test like this is simple to implement thanks to
:ref:`PHPUnit data providers `::
- // src/AppBundle/Tests/ApplicationAvailabilityFunctionalTest.php
- namespace AppBundle\Tests;
+ // tests/AppBundle/ApplicationAvailabilityFunctionalTest.php
+ namespace Tests\AppBundle;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
@@ -49,14 +49,14 @@ A functional test like this is simple to implement thanks to
public function urlProvider()
{
- return array(
- array('/'),
- array('/posts'),
- array('/post/fixture-post-1'),
- array('/blog/category/fixture-category'),
- array('/archives'),
+ return [
+ ['/'],
+ ['/posts'],
+ ['/post/fixture-post-1'],
+ ['/blog/category/fixture-category'],
+ ['/archives'],
// ...
- );
+ ];
}
}
@@ -104,8 +104,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 frontend, 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/best_practices/web-assets.rst b/best_practices/web-assets.rst
index 6bed1e11086..c567bf36e47 100644
--- a/best_practices/web-assets.rst
+++ b/best_practices/web-assets.rst
@@ -18,8 +18,8 @@ much more concise:
.. code-block:: html+twig
-
-
+
+
{# ... #}
@@ -37,11 +37,11 @@ Using Assetic
.. include:: /assetic/_standard_edition_warning.rst.inc
-These days, you probably can't simply create static CSS and JavaScript files
-and include them in your template. Instead, you'll probably want to combine
-and minify these to improve client-side performance. You may also want to
-use LESS or Sass (for example), which means you'll need some way to process
-these into CSS files.
+These days, you probably can't create static CSS and JavaScript files and
+include them in your template without much effort. Instead, you'll probably
+want to combine and minify these to improve client-side performance. You may
+also want to use LESS or Sass (for example), which means you'll need some way
+to process these into CSS files.
A lot of tools exist to solve these problems, including pure-frontend (non-PHP)
tools like GruntJS.
@@ -62,7 +62,7 @@ matter of wrapping all the assets with a single Twig tag:
'css/bootstrap.min.css'
'css/main.css'
filter='cssrewrite' output='css/compiled/app.css' %}
-
+
{% endstylesheets %}
{# ... #}
diff --git a/bundles.rst b/bundles.rst
index 8b81a117436..43ccbb10111 100644
--- a/bundles.rst
+++ b/bundles.rst
@@ -6,6 +6,12 @@
The Bundle System
=================
+.. caution::
+
+ In Symfony versions prior to 3.4, it was recommended to organize your own
+ application code using bundles. This is no longer recommended and bundles
+ should only be used to share code and features between multiple applications.
+
A bundle is similar to a plugin in other software, but even better. The key
difference is that *everything* is a bundle in Symfony, including both the
core framework functionality and the code written for your application.
@@ -33,7 +39,7 @@ the ``registerBundles()`` method of the ``AppKernel`` class::
// app/AppKernel.php
public function registerBundles()
{
- $bundles = array(
+ $bundles = [
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
new Symfony\Bundle\TwigBundle\TwigBundle(),
@@ -42,9 +48,9 @@ the ``registerBundles()`` method of the ``AppKernel`` class::
new Symfony\Bundle\DoctrineBundle\DoctrineBundle(),
new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
new AppBundle\AppBundle(),
- );
+ ];
- if (in_array($this->getEnvironment(), array('dev', 'test'))) {
+ if (in_array($this->getEnvironment(), ['dev', 'test'])) {
$bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
$bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();
$bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
@@ -101,11 +107,12 @@ Now that you've created the bundle, enable it via the ``AppKernel`` class::
// app/AppKernel.php
public function registerBundles()
{
- $bundles = array(
+ $bundles = [
// ...
+
// register your bundle
new Acme\TestBundle\AcmeTestBundle(),
- );
+ ];
// ...
return $bundles;
@@ -118,7 +125,7 @@ generating a basic bundle skeleton:
.. code-block:: terminal
- $ php app/console generate:bundle --namespace=Acme/TestBundle
+ $ php bin/console generate:bundle --namespace=Acme/TestBundle
The bundle skeleton generates a basic controller, template and routing
resource that can be customized. You'll learn more about Symfony's command-line
diff --git a/bundles/best_practices.rst b/bundles/best_practices.rst
index 3fa8ae25bb5..c9b68c1443b 100644
--- a/bundles/best_practices.rst
+++ b/bundles/best_practices.rst
@@ -9,8 +9,8 @@ There are two types of bundles:
* Application-specific bundles: only used to build your application;
* Reusable bundles: meant to be shared across many projects.
-This article is all about how to structure your **reusable bundles** so that
-they're easy to configure and extend. Many of these recommendations do not
+This article is all about how to structure your **reusable bundles** to be
+configurable and extendable. Many of these recommendations do not
apply to application bundles because you'll want to keep those as simple
as possible. For application bundles, just follow the practices shown throughout
the guides.
@@ -37,7 +37,7 @@ A namespace becomes a bundle as soon as you add a bundle class to it. The
bundle class name must follow these simple rules:
* Use only alphanumeric characters and underscores;
-* Use a StudlyCaps name (i.e. camelCase with the first letter uppercased);
+* Use a StudlyCaps name (i.e. camelCase with an uppercase first letter);
* Use a descriptive and short name (no more than two words);
* Prefix the name with the concatenation of the vendor (and optionally the
category namespaces);
@@ -188,7 +188,7 @@ The index file (for example ``Resources/doc/index.rst`` or
``Resources/doc/index.md``) is the only mandatory file and must be the entry
point for the documentation. The
:doc:`reStructuredText (rST) ` is the format
-used to render the documentation on symfony.com.
+used to render the documentation on the Symfony website.
Installation Instructions
~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -203,6 +203,10 @@ following standardized instructions in your ``README.md`` file.
Installation
============
+ Make sure Composer is installed globally, as explained in the
+ [installation chapter](https://getcomposer.org/doc/00-intro.md)
+ of the Composer documentation.
+
Step 1: Download the Bundle
---------------------------
@@ -210,13 +214,9 @@ following standardized instructions in your ``README.md`` file.
following command to download the latest stable version of this bundle:
```console
- $ composer require "~1"
+ $ composer require
```
- This command requires you to have Composer installed globally, as explained
- in the [installation chapter](https://getcomposer.org/doc/00-intro.md)
- of the Composer documentation.
-
Step 2: Enable the Bundle
-------------------------
@@ -224,7 +224,6 @@ following standardized instructions in your ``README.md`` file.
in the `app/AppKernel.php` file of your project:
```php
- \\(),
- );
+ ];
// ...
}
@@ -249,6 +248,9 @@ following standardized instructions in your ``README.md`` file.
Installation
============
+ Make sure Composer is installed globally, as explained in the
+ `installation chapter`_ of the Composer documentation.
+
Step 1: Download the Bundle
---------------------------
@@ -257,20 +259,14 @@ following standardized instructions in your ``README.md`` file.
.. code-block:: terminal
- $ composer require "~1"
-
- This command requires you to have Composer installed globally, as explained
- in the `installation chapter`_ of the Composer documentation.
+ $ composer require
Step 2: Enable the Bundle
-------------------------
Then, enable the bundle by adding it to the list of registered bundles
- in the ``app/AppKernel.php`` file of your project:
-
- .. code-block:: php
+ in the ``app/AppKernel.php`` file of your project::
- \\(),
- );
+ ];
// ...
}
@@ -353,7 +349,7 @@ The end user can provide values in any configuration file:
+ https://symfony.com/schema/dic/services/services-1.0.xsd">
fabien@example.com
@@ -385,7 +381,14 @@ If the bundle defines services, they must be prefixed with the bundle alias.
For example, AcmeBlogBundle services must be prefixed with ``acme_blog``.
In addition, services not meant to be used by the application directly, should
-be :ref:`defined as private `.
+be :ref:`defined as private `. For public services,
+:ref:`aliases should be created ` from the interface/class
+to the service id. For example, in MonologBundle, an alias is created from
+``Psr\Log\LoggerInterface`` to ``logger`` so that the ``LoggerInterface`` type-hint
+can be used for autowiring.
+
+Services should not use autowiring or autoconfiguration. Instead, all services should
+be defined explicitly.
.. seealso::
@@ -422,55 +425,6 @@ The ``composer.json`` file should include at least the following metadata:
In order to make it easier for developers to find your bundle, register it on
`Packagist`_, the official repository for Composer packages.
-Custom Validation Constraints
------------------------------
-
-Starting with Symfony 2.5, a new Validation API was introduced. In fact,
-there are 3 modes, which the user can configure in their project:
-
-* 2.4: the original 2.4 and earlier validation API;
-* 2.5: the new 2.5 and later validation API;
-* 2.5-BC: the new 2.5 API with a backwards-compatible layer so that the
- 2.4 API still works. This is only available in PHP 5.3.9+.
-
-.. note::
-
- Starting with Symfony 2.7, the support for the 2.4 API has been
- dropped and the minimal PHP version required for Symfony was
- increased to 5.3.9. If your bundles requires Symfony >=2.7, you
- don't need to take care about the 2.4 API anymore.
-
-As a bundle author, you'll want to support *both* API's, since some users
-may still be using the 2.4 API. Specifically, if your bundle adds a violation
-directly to the :class:`Symfony\\Component\\Validator\\Context\\ExecutionContext`
-(e.g. like in a custom validation constraint), you'll need to check for which
-API is being used. The following code, would work for *all* users::
-
- use Symfony\Component\Validator\ConstraintValidator;
- use Symfony\Component\Validator\Constraint;
- use Symfony\Component\Validator\Context\ExecutionContextInterface;
- // ...
-
- class ContainsAlphanumericValidator extends ConstraintValidator
- {
- public function validate($value, Constraint $constraint)
- {
- if ($this->context instanceof ExecutionContextInterface) {
- // the 2.5 API
- $this->context->buildViolation($constraint->message)
- ->setParameter('%string%', $value)
- ->addViolation()
- ;
- } else {
- // the 2.4 API
- $this->context->addViolation(
- $constraint->message,
- array('%string%' => $value)
- );
- }
- }
- }
-
Resources
---------
diff --git a/bundles/configuration.rst b/bundles/configuration.rst
index 2d5e830da5d..9b16f20dd89 100644
--- a/bundles/configuration.rst
+++ b/bundles/configuration.rst
@@ -28,20 +28,20 @@ as integration of other related components:
+ https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
-
+
.. code-block:: php
- $container->loadFromExtension('framework', array(
+ $container->loadFromExtension('framework', [
'form' => true,
- ));
+ ]);
.. sidebar:: Using Parameters to Configure your Bundle
@@ -79,22 +79,24 @@ 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
// app/config/config.php
- $container->loadFromExtension('acme_social', array(
- 'client_id' => 123,
- 'client_secret' => 'your_secret',
- ));
+ $container->loadFromExtension('acme_social', [
+ 'twitter' => [
+ 'client_id' => 123,
+ 'client_secret' => 'your_secret',
+ ],
+ ]);
The basic idea is that instead of having the user override individual
parameters, you let the user configure just a few, specifically created,
@@ -139,14 +141,14 @@ automatically converts XML and YAML to an array).
For the configuration example in the previous section, the array passed to your
``load()`` method will look like this::
- array(
- array(
- 'twitter' => array(
+ [
+ [
+ 'twitter' => [
'client_id' => 123,
'client_secret' => 'your_secret',
- ),
- ),
- )
+ ],
+ ],
+ ]
Notice that this is an *array of arrays*, not just a single flat array of the
configuration values. This is intentional, as it allows Symfony to parse
@@ -154,21 +156,21 @@ several configuration resources. For example, if ``acme_social`` appears in
another configuration file - say ``config_dev.yml`` - with different values
beneath it, the incoming array might look like this::
- array(
+ [
// values from config.yml
- array(
- 'twitter' => array(
+ [
+ 'twitter' => [
'client_id' => 123,
'client_secret' => 'your_secret',
- ),
- ),
+ ],
+ ],
// values from config_dev.yml
- array(
- 'twitter' => array(
+ [
+ 'twitter' => [
'client_id' => 456,
- ),
- ),
- )
+ ],
+ ],
+ ]
The order of the two arrays depends on which one is set first.
@@ -223,7 +225,6 @@ force validation (e.g. if an additional option was passed, an exception will be
thrown)::
// src/Acme/SocialBundle/DependencyInjection/AcmeSocialExtension.php
-
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
@@ -248,7 +249,7 @@ For example, imagine your bundle has the following example config:
+ https://symfony.com/schema/dic/services/services-1.0.xsd">
@@ -263,8 +264,8 @@ In your extension, you can load this and dynamically set its arguments::
// src/Acme/SocialBundle/DependencyInjection/AcmeSocialExtension.php
// ...
- use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\Config\FileLocator;
+ use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
public function load(array $configs, ContainerBuilder $container)
{
@@ -309,13 +310,11 @@ In your extension, you can load this and dynamically set its arguments::
Using the Config component is fully optional. The ``load()`` method gets an
array of configuration values. You can simply parse these arrays yourself
(e.g. by overriding configurations and using :phpfunction:`isset` to check
- for the existence of a value). Be aware that it'll be very hard to support XML.
-
- .. code-block:: php
+ for the existence of a value). Be aware that it'll be very hard to support XML::
public function load(array $configs, ContainerBuilder $container)
{
- $config = array();
+ $config = [];
// let resources override the previous set value
foreach ($configs as $subConfig) {
$config = array_merge($config, $subConfig);
@@ -371,7 +370,7 @@ In XML, the `XML namespace`_ is used to determine which elements belong to the
configuration of a specific bundle. The namespace is returned from the
:method:`Extension::getNamespace() `
method. By convention, the namespace is a URL (it doesn't have to be a valid
-URL nor does it need to exists). By default, the namespace for a bundle is
+URL nor does it need to exist). By default, the namespace for a bundle is
``http://example.org/schema/dic/DI_ALIAS``, where ``DI_ALIAS`` is the DI alias of
the extension. You might want to change this to a more professional URL::
@@ -393,7 +392,7 @@ Providing an XML Schema
XML has a very useful feature called `XML schema`_. This allows you to
describe all possible elements and attributes and their values in an XML Schema
-Definition (an xsd file). This XSD file is used by IDEs for auto completion and
+Definition (an XSD file). This XSD file is used by IDEs for auto completion and
it is used by the Config component to validate the elements.
In order to use the schema, the XML configuration file must provide an
@@ -421,7 +420,7 @@ can place it anywhere you like. You should return this path as the base path::
}
Assuming the XSD file is called ``hello-1.0.xsd``, the schema location will be
-``http://acme_company.com/schema/dic/hello/hello-1.0.xsd``:
+``https://acme_company.com/schema/dic/hello/hello-1.0.xsd``:
.. code-block:: xml
@@ -430,8 +429,10 @@ Assuming the XSD file is called ``hello-1.0.xsd``, the schema location will be
+ xsi:schemaLocation="http://symfony.com/schema/dic/services
+ https://symfony.com/schema/dic/services/services-1.0.xsd
+ http://acme_company.com/schema/dic/hello
+ https://acme_company.com/schema/dic/hello/hello-1.0.xsd">
diff --git a/bundles/extension.rst b/bundles/extension.rst
index bee1a7a5be8..c6cdabd05a6 100644
--- a/bundles/extension.rst
+++ b/bundles/extension.rst
@@ -27,10 +27,11 @@ following conventions:
``AppExtension`` and the one for AcmeHelloBundle would be called
``AcmeHelloExtension``).
-The Extension class should implement the
+The Extension class must implement the
:class:`Symfony\\Component\\DependencyInjection\\Extension\\ExtensionInterface`,
-but usually you would simply extend the
-:class:`Symfony\\Component\\DependencyInjection\\Extension\\Extension` class::
+but instead you should extend the
+:class:`Symfony\\Component\\DependencyInjection\\Extension\\Extension` class,
+which already implements the interface and provides some utilities::
// src/Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php
namespace Acme\HelloBundle\DependencyInjection;
@@ -92,8 +93,8 @@ the extension!
For instance, assume you have a file called ``services.xml`` in the
``Resources/config`` directory of your bundle, your ``load()`` method looks like::
- use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\Config\FileLocator;
+ use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
// ...
public function load(array $configs, ContainerBuilder $container)
@@ -129,27 +130,47 @@ read more about it, see the ":doc:`/bundles/configuration`" article.
Adding Classes to Compile
-------------------------
+.. deprecated:: 3.3
+
+ This technique is discouraged and the ``addClassesToCompile()`` method was
+ deprecated in Symfony 3.3 because modern PHP versions make it unnecessary.
+
Symfony creates a big ``classes.php`` file in the cache directory to aggregate
the contents of the PHP classes that are used in every request. This reduces the
I/O operations and increases the application performance.
+.. versionadded:: 3.2
+
+ The ``addAnnotatedClassesToCompile()`` method was introduced in Symfony 3.2.
+
Your bundles can also add their own classes into this file thanks to the
-``addClassesToCompile()`` method. Define the classes to compile as an array of
-their fully qualified class names::
+``addClassesToCompile()`` and ``addAnnotatedClassesToCompile()`` methods (both
+work in the same way, but the second one is for classes that contain PHP
+annotations). These methods are provied by the ``Extension`` class from the
+HttpKernel component. Define the classes to compile as an array of their
+fully qualified class names::
use AppBundle\Manager\UserManager;
use AppBundle\Utils\Slugger;
+ use Symfony\Component\HttpKernel\DependencyInjection\Extension;
// ...
public function load(array $configs, ContainerBuilder $container)
{
// ...
- $this->addClassesToCompile(array(
+ // this method can't compile classes that contain PHP annotations
+ $this->addClassesToCompile([
UserManager::class,
Slugger::class,
// ...
- ));
+ ]);
+
+ // add here only classes that contain PHP annotations
+ $this->addAnnotatedClassesToCompile([
+ 'AppBundle\\Controller\\DefaultController',
+ // ...
+ ]);
}
.. note::
@@ -157,10 +178,36 @@ their fully qualified class names::
If some class extends from other classes, all its parents are automatically
included in the list of classes to compile.
-Beware that this technique **can't be used in some cases**:
+.. versionadded:: 3.2
+
+ The option to add classes to compile using patterns was introduced in Symfony 3.2.
+
+The classes to compile can also be added using file path patterns::
+
+ use Symfony\Component\HttpKernel\DependencyInjection\Extension;
+
+ // ...
+ public function load(array $configs, ContainerBuilder $container)
+ {
+ // ...
+
+ $this->addClassesToCompile([
+ '**Bundle\\Manager\\',
+ // ...
+ ]);
+
+ $this->addAnnotatedClassesToCompile([
+ '**Bundle\\Controller\\',
+ // ...
+ ]);
+ }
+
+Patterns are transformed into the actual class namespaces using the classmap
+generated by Composer. Therefore, before using these patterns, you must generate
+the full classmap executing the ``dump-autoload`` command of Composer.
+
+.. caution::
-* When classes contain annotations, such as controllers with ``@Route``
- annotations and entities with ``@ORM`` or ``@Assert`` annotations, because
- the file location retrieved from PHP reflection changes;
-* When classes use the ``__DIR__`` and ``__FILE__`` constants, because their
- values will change when loading these classes from the ``classes.php`` file.
+ This technique can't be used when the classes to compile use the ``__DIR__``
+ or ``__FILE__`` constants, because their values will change when loading
+ these classes from the ``classes.php`` file.
diff --git a/bundles/inheritance.rst b/bundles/inheritance.rst
index f828ad90f45..5720a944b88 100644
--- a/bundles/inheritance.rst
+++ b/bundles/inheritance.rst
@@ -4,6 +4,12 @@
How to Use Bundle Inheritance to Override Parts of a Bundle
===========================================================
+.. deprecated:: 3.4
+
+ Bundle inheritance is deprecated since Symfony 3.4 and will be removed in
+ 4.0, but you can :doc:`override any part of a bundle `
+ without using bundle inheritance.
+
When working with third-party bundles, you'll probably come across a situation
where you want to override a file in that third-party bundle with a file
in one of your own bundles. Symfony gives you a very convenient way to override
@@ -30,8 +36,8 @@ Then, register the third-party FOSUserBundle as the "parent" of your bundle::
}
}
-By making this simple change, you can now override several parts of the FOSUserBundle
-simply by creating a file with the same name.
+By making this small change, you can now override several parts of the FOSUserBundle
+by creating a file with the same name.
.. note::
@@ -77,8 +83,8 @@ original method, and change its functionality::
Overriding Resources: Templates, Routing, etc
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Most resources can also be overridden, simply by creating a file in the same
-location as your parent bundle.
+Most resources can also be overridden by creating a file in the same location
+as your parent bundle.
For example, it's very common to need to override the FOSUserBundle's
``layout.html.twig`` template so that it uses your application's base layout.
diff --git a/bundles/installation.rst b/bundles/installation.rst
index a0e71df54e5..c8f3e4ec1d5 100644
--- a/bundles/installation.rst
+++ b/bundles/installation.rst
@@ -61,10 +61,10 @@ The only thing you need to do now is register the bundle in ``AppKernel``::
public function registerBundles()
{
- $bundles = array(
+ $bundles = [
// ...
new FOS\UserBundle\FOSUserBundle(),
- );
+ ];
// ...
}
@@ -85,11 +85,11 @@ and ``test`` environments, register the bundle in this way::
public function registerBundles()
{
- $bundles = array(
+ $bundles = [
// ...
- );
+ ];
- if (in_array($this->getEnvironment(), array('dev', 'test'))) {
+ if (in_array($this->getEnvironment(), ['dev', 'test'])) {
$bundles[] = new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle();
}
@@ -107,14 +107,14 @@ via the ``config:dump-reference`` command:
.. code-block:: terminal
- $ app/console config:dump-reference AsseticBundle
+ $ php bin/console config:dump-reference AsseticBundle
Instead of the full bundle name, you can also pass the short name used as the root
of the bundle's configuration:
.. code-block:: terminal
- $ app/console config:dump-reference assetic
+ $ php bin/console config:dump-reference assetic
The output will look like this:
@@ -125,13 +125,28 @@ The output will look like this:
use_controller:
enabled: '%kernel.debug%'
profiler: false
- read_from: '%kernel.root_dir%/../web'
+ read_from: '%kernel.project_dir%/web'
write_to: '%assetic.read_from%'
java: /usr/bin/java
node: /usr/local/bin/node
node_paths: []
# ...
+.. tip::
+
+ For complex bundles that define lots of configuration options, you can pass
+ a second optional argument to the ``config:dump-reference`` command to only
+ display a section of the entire configuration:
+
+ .. code-block:: terminal
+
+ $ php bin/console config:dump-reference AsseticBundle use_controller
+
+ # Default configuration for "AsseticBundle" at path "use_controller"
+ use_controller:
+ enabled: '%kernel.debug%'
+ profiler: false
+
Other Setup
-----------
diff --git a/bundles/override.rst b/bundles/override.rst
index 7dfd8f1689e..c583ffb5af9 100644
--- a/bundles/override.rst
+++ b/bundles/override.rst
@@ -5,7 +5,8 @@ How to Override any Part of a Bundle
====================================
This document is a quick reference for how to override different parts of
-third-party bundles.
+third-party bundles without using :doc:`/bundles/inheritance`, which is
+deprecated since Symfony 3.4.
.. tip::
@@ -18,10 +19,7 @@ third-party bundles.
Templates
---------
-For information on overriding templates, see
-
-* :doc:`/templating/overriding`.
-* :doc:`/bundles/inheritance`
+See :doc:`/templating/overriding`.
Routing
-------
@@ -31,16 +29,16 @@ the routes from any bundle, then they must be manually imported from somewhere
in your application (e.g. ``app/config/routing.yml``).
The easiest way to "override" a bundle's routing is to never import it at
-all. Instead of importing a third-party bundle's routing, simply copy
-that routing file into your application, modify it, and import it instead.
+all. Instead of importing a third-party bundle's routing, copy that
+routing file into your application, modify it, and import it instead.
Controllers
-----------
-Assuming the third-party bundle involved uses non-service controllers (which
-is almost always the case), you can easily override controllers via bundle
-inheritance. For more information, see :doc:`/bundles/inheritance`.
If the controller is a service, see the next section on how to override it.
+Otherwise, define a new route + controller with the same path associated to the
+controller you want to override (and make sure that the new route is loaded
+before the bundle one).
Services & Configuration
------------------------
@@ -55,14 +53,10 @@ inside a :doc:`compiler pass `.
Entities & Entity Mapping
-------------------------
-If a bundle defines its entity mapping in configuration files instead of
-annotations, you can override them as any other regular bundle configuration
-file. The only caveat is that you must override all those mapping configuration
-files and not just the ones you actually want to override.
-
-If a bundle provides a mapped superclass (such as the ``User`` entity in the
-FOSUserBundle) you can override its attributes and associations. Learn more
-about this feature and its limitations in `the Doctrine documentation`_.
+Overriding entity mapping is only possible if a bundle provides a mapped
+superclass (such as the ``User`` entity in the FOSUserBundle). It's possible to
+override attributes and associations in this way. Learn more about this feature
+and its limitations in `the Doctrine documentation`_.
Forms
-----
@@ -106,7 +100,7 @@ to a new validation group:
+ https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
@@ -137,15 +131,6 @@ Translations
Translations are not related to bundles, but to domains. That means that you
can override the translations from any translation file, as long as it is in
-:ref:`the correct domain `.
-
-.. caution::
-
- Translation files are not aware of :doc:`bundle inheritance `.
- If you want to override translations from the parent bundle or another bundle,
- make sure that the bundle containing *your* translations is loaded after any
- bundle whose translations you're overriding. This is done in ``AppKernel``.
+:ref:`the correct domain `.
- Finally, translations located in ``app/Resources/translations`` will override
- all the other translations since those files are always loaded last.
.. _`the Doctrine documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html#overrides
diff --git a/bundles/prepend_extension.rst b/bundles/prepend_extension.rst
index 28610a27154..5c32d206629 100644
--- a/bundles/prepend_extension.rst
+++ b/bundles/prepend_extension.rst
@@ -28,9 +28,9 @@ To give an Extension the power to do this, it needs to implement
// src/Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php
namespace Acme\HelloBundle\DependencyInjection;
- use Symfony\Component\HttpKernel\DependencyInjection\Extension;
- use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
+ use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
+ use Symfony\Component\HttpKernel\DependencyInjection\Extension;
class AcmeHelloExtension extends Extension implements PrependExtensionInterface
{
@@ -64,7 +64,7 @@ in case a specific other bundle is not registered::
// determine if AcmeGoodbyeBundle is registered
if (!isset($bundles['AcmeGoodbyeBundle'])) {
// disable AcmeGoodbyeBundle in bundles
- $config = array('use_acme_goodbye' => false);
+ $config = ['use_acme_goodbye' => false];
foreach ($container->getExtensions() as $name => $extension) {
switch ($name) {
case 'acme_something':
@@ -90,7 +90,7 @@ in case a specific other bundle is not registered::
// check if entity_manager_name is set in the "acme_hello" configuration
if (isset($config['entity_manager_name'])) {
// prepend the acme_something settings with the entity_manager_name
- $config = array('entity_manager_name' => $config['entity_manager_name']);
+ $config = ['entity_manager_name' => $config['entity_manager_name']];
$container->prependExtensionConfig('acme_something', $config);
}
}
@@ -122,28 +122,33 @@ The above would be the equivalent of writing the following into the
xmlns:acme-something="http://example.org/schema/dic/acme_something"
xmlns:acme-other="http://example.org/schema/dic/acme_other"
xsi:schemaLocation="http://symfony.com/schema/dic/services
- http://symfony.com/schema/dic/services/services-1.0.xsd">
+ https://symfony.com/schema/dic/services/services-1.0.xsd
+ http://example.org/schema/dic/acme_something
+ https://example.org/schema/dic/acme_something/acme_something-1.0.xsd
+ http://example.org/schema/dic/acme_other
+ https://example.org/schema/dic/acme_something/acme_other-1.0.xsd">
+
non_default
-
+
.. code-block:: php
// app/config/config.php
- $container->loadFromExtension('acme_something', array(
+ $container->loadFromExtension('acme_something', [
// ...
'use_acme_goodbye' => false,
'entity_manager_name' => 'non_default',
- ));
- $container->loadFromExtension('acme_other', array(
+ ]);
+ $container->loadFromExtension('acme_other', [
// ...
'use_acme_goodbye' => false,
- ));
+ ]);
More than one Bundle using PrependExtensionInterface
----------------------------------------------------
diff --git a/bundles/remove.rst b/bundles/remove.rst
index 644e8742310..0c27eacb68b 100644
--- a/bundles/remove.rst
+++ b/bundles/remove.rst
@@ -19,11 +19,11 @@ bundle is only registered in the development environment::
{
public function registerBundles()
{
- $bundles = array(
+ $bundles = [
new Acme\DemoBundle\AcmeDemoBundle(),
- );
+ ];
- if (in_array($this->getEnvironment(), array('dev', 'test'))) {
+ if (in_array($this->getEnvironment(), ['dev', 'test'])) {
// comment or remove this line:
// $bundles[] = new Acme\DemoBundle\AcmeDemoBundle();
// ...
diff --git a/cache.rst b/cache.rst
new file mode 100644
index 00000000000..77ccdca23af
--- /dev/null
+++ b/cache.rst
@@ -0,0 +1,567 @@
+.. 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.
+
+The following example shows a typical usage of the cache::
+
+ if (!$cache->has('my_cache_key')) {
+ // ... do some HTTP request or heavy computations
+ $cache->set('my_cache_key', 'foobar', 3600);
+ }
+
+ echo $cache->get('my_cache_key'); // 'foobar'
+
+ // ... and to remove the cache key
+ $cache->delete('my_cache_key');
+
+
+Symfony supports PSR-6 and PSR-16 cache interfaces. You can read more about
+these at the :doc:`component documentation `.
+
+.. _cache-configuration-with-frameworkbundle:
+
+Configuring Cache with FrameworkBundle
+--------------------------------------
+
+When configuring the cache component there are a few concepts you should know
+of:
+
+**Pool**
+ This is a service that you will interact with. Each pool will always have
+ its own namespace and cache items. There is never a conflict between pools.
+**Adapter**
+ An adapter is a *template* that you use to create Pools.
+**Provider**
+ A provider is a service that some adapters 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
+
+ # app/config/config.yml
+ framework:
+ cache:
+ app: cache.adapter.filesystem
+ system: cache.adapter.system
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // app/config/config.php
+ $container->loadFromExtension('framework', [
+ 'cache' => [
+ 'app' => 'cache.adapter.filesystem',
+ 'system' => 'cache.adapter.system',
+ ],
+ ]);
+
+The Cache component comes with a series of adapters already created:
+
+* :doc:`cache.adapter.apcu `
+* :doc:`cache.adapter.doctrine `
+* :doc:`cache.adapter.filesystem `
+* :doc:`cache.adapter.memcached `
+* :doc:`cache.adapter.pdo `
+* :doc:`cache.adapter.redis `
+* :doc:`PHPFileAdapter `
+* :doc:`PHPArrayAdapter `
+
+* :doc:`ChainAdapter `
+* :doc:`ProxyAdapter `
+* ``cache.adapter.psr6``
+
+* ``cache.adapter.system``
+* ``NullAdapter``
+
+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
+
+ # app/config/config.yml
+ 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
+
+ // app/config/config.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 Pools
+---------------------
+
+You can also create more customized pools. All you need is an adapter:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # app/config/config.yml
+ framework:
+ cache:
+ default_memcached_provider: 'memcached://localhost'
+ pools:
+ my_cache_pool:
+ provider: cache.adapter.filesystem
+ cache.acme:
+ adapter: cache.adapter.memcached
+ cache.foobar:
+ adapter: cache.adapter.memcached
+ provider: 'memcached://user:password@example.com'
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // app/config/config.php
+ $container->loadFromExtension('framework', [
+ 'cache' => [
+ 'default_memcached_provider' => 'memcached://localhost',
+ 'pools' => [
+ 'my_cache_pool' => [
+ 'adapter' => 'cache.adapter.filesystem',
+ ],
+ 'cache.acme' => [
+ 'adapter' => 'cache.adapter.memcached',
+ ],
+ 'cache.foobar' => [
+ 'adapter' => 'cache.adapter.memcached',
+ 'provider' => 'memcached://user:password@example.com',
+ ],
+ ],
+ ],
+ ]);
+
+The configuration above will create 3 services: ``my_cache_pool``, ``cache.acme``
+and ``cache.foobar``. The ``my_cache_pool`` pool is using the filesystem adapter
+and the other two are using the :doc:`MemcachedAdapter `.
+The ``cache.acme`` pool is using the Memcached server on localhost and ``cache.foobar``
+is using the Memcached server at example.com.
+
+For advanced configurations it could sometimes be useful to use a pool as an adapter.
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # app/config/config.yml
+ framework:
+ cache:
+ app: my_configured_app_cache
+ pools:
+ my_cache_pool:
+ adapter: cache.adapter.memcached
+ provider: 'memcached://user:password@example.com'
+ cache.short_cache:
+ adapter: my_cache_pool
+ default_lifetime: 60
+ cache.long_cache:
+ adapter: my_cache_pool
+ default_lifetime: 604800
+ my_configured_app_cache:
+ # "cache.adapter.filesystem" is the default for "cache.app"
+ adapter: cache.adapter.filesystem
+ default_lifetime: 3600
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // app/config/config.php
+ $container->loadFromExtension('framework', [
+ 'cache' => [
+ 'app' => 'my_configured_app_cache',
+ 'pools' => [
+ 'my_cache_pool' => [
+ 'adapter' => 'cache.adapter.memcached',
+ 'provider' => 'memcached://user:password@example.com',
+ ],
+ 'cache.short_cache' => [
+ 'adapter' => 'cache.adapter.memcached',
+ 'default_lifetime' => 60,
+ ],
+ 'cache.long_cache' => [
+ 'adapter' => 'cache.adapter.memcached',
+ 'default_lifetime' => 604800,
+ ],
+ 'my_configured_app_cache' => [
+ // "cache.adapter.filesystem" is the default for "cache.app"
+ 'adapter' => 'cache.adapter.filesystem',
+ 'default_lifetime' => 3600,
+ ],
+ ],
+ ],
+ ]);
+
+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
+
+ # app/config/config.yml
+ 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
+
+ // app/config/config.php
+ use Symfony\Component\Cache\Adapter\RedisAdapter;
+
+ $container->loadFromExtension('framework', [
+ 'cache' => [
+ 'pools' => [
+ 'cache.my_redis' => [
+ 'adapter' => 'cache.adapter.redis',
+ 'provider' => 'app.my_custom_redis_provider',
+ ],
+ ],
+ ],
+ ]);
+
+ $container->register('app.my_custom_redis_provider', \Redis::class)
+ ->setFactory([RedisAdapter::class, 'createConnection'])
+ ->addArgument('redis://localhost')
+ ->addArgument([
+ 'retry_interval' => 2,
+ 'timeout' => 10
+ ])
+ ;
+
+Creating a Cache Chain
+----------------------
+
+Different cache adapters have different strengths and weaknesses. Some might be
+really quick but optimized to store small items and some may be able to contain
+a lot of data but are quite slow. To get the best of both worlds you may use a
+chain of adapters.
+
+A cache chain combines several cache pools into a single one. When storing an
+item in a cache chain, Symfony stores it in all pools sequentially. When
+retrieving an item, Symfony tries to get it from the first pool. If it's not
+found, it tries the next pools until the item is found or an exception is thrown.
+Because of this behavior, it's recommended to define the adapters in the chain
+in order from the fastest to the slowest.
+
+If an error happens when storing an item in a pool, Symfony stores it in the
+other pools and no exception is thrown. Later, when the item is retrieved,
+Symfony stores the item automatically in all the missing pools.
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # app/config/config.yml
+ 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.filesystem
+
+
+ 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
+
+ // app/config/config.php
+ use Symfony\Component\Cache\Adapter\ChainAdapter;
+ use Symfony\Component\DependencyInjection\Reference;
+
+ $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.filesystem',
+ ],
+ ],
+ ],
+ ]);
+
+ $container->register('app.my_cache_chain_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``.
+
+
+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.
+
+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 8c13a331752..f3acca103b0 100644
--- a/components/asset.rst
+++ b/components/asset.rst
@@ -8,9 +8,6 @@ The Asset Component
The Asset component manages URL generation and versioning of web assets such
as CSS stylesheets, JavaScript files and image files.
-.. versionadded:: 2.7
- The Asset component was introduced in Symfony 2.7.
-
In the past, it was common for web applications to hardcode URLs of web assets.
For example:
@@ -20,7 +17,7 @@ For example:
-
+
This practice is no longer recommended unless the web application is extremely
simple. Hardcoding URLs can be a disadvantage because:
@@ -33,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;
@@ -47,9 +44,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/asset
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require symfony/asset:^3.4
.. include:: /components/require_autoload.rst.inc
@@ -69,9 +64,14 @@ any versioning::
$package = new Package(new EmptyVersionStrategy());
+ // Absolute path
echo $package->getUrl('/image.png');
// result: /image.png
+ // Relative path
+ echo $package->getUrl('image.png');
+ // result: image.png
+
Packages implement :class:`Symfony\\Component\\Asset\\PackageInterface`,
which defines the following two methods:
@@ -111,11 +111,17 @@ suffix to any asset path::
$package = new Package(new StaticVersionStrategy('v1'));
+ // Absolute path
echo $package->getUrl('/image.png');
// result: /image.png?v1
-In case you want to modify the version format, pass a sprintf-compatible format
-string as the second argument of the ``StaticVersionStrategy`` constructor::
+ // Relative path
+ echo $package->getUrl('image.png');
+ // result: image.png?v1
+
+In case you want to modify the version format, pass a ``sprintf``-compatible
+format string as the second argument of the ``StaticVersionStrategy``
+constructor::
// puts the 'version' word before the version value
$package = new Package(new StaticVersionStrategy('v1', '%s?version=%s'));
@@ -129,6 +135,36 @@ string as the second argument of the ``StaticVersionStrategy`` constructor::
echo $package->getUrl('/image.png');
// result: /v1/image.png
+ echo $package->getUrl('image.png');
+ // result: v1/image.png
+
+JSON File Manifest
+..................
+
+A popular strategy to manage asset versioning, which is used by tools such as
+`Webpack`_, is to generate a JSON file mapping all source file names to their
+corresponding output file:
+
+.. code-block:: json
+
+ {
+ "css/app.css": "build/css/app.b916426ea1d10021f3f17ce8031f93c2.css",
+ "js/app.js": "build/js/app.13630905267b809161e71d0f8a0c017b.js",
+ "...": "..."
+ }
+
+In those cases, use the
+:class:`Symfony\\Component\\Asset\\VersionStrategy\\JsonManifestVersionStrategy`::
+
+ use Symfony\Component\Asset\Package;
+ use Symfony\Component\Asset\VersionStrategy\JsonManifestVersionStrategy;
+
+ // assumes the JSON file above is called "rev-manifest.json"
+ $package = new Package(new JsonManifestVersionStrategy(__DIR__.'/rev-manifest.json'));
+
+ echo $package->getUrl('css/app.css');
+ // result: build/css/app.b916426ea1d10021f3f17ce8031f93c2.css
+
Custom Version Strategies
.........................
@@ -174,9 +210,13 @@ that path over and over again::
$pathPackage = new PathPackage('/static/images', new StaticVersionStrategy('v1'));
- echo $pathPackage->getUrl('/logo.png');
+ echo $pathPackage->getUrl('logo.png');
// result: /static/images/logo.png?v1
+ // Base path is ignored when using absolute paths
+ echo $pathPackage->getUrl('/logo.png');
+ // result: /logo.png?v1
+
Request Context Aware Assets
............................
@@ -184,8 +224,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(
@@ -194,9 +234,13 @@ class can take into account the context of the current request::
new RequestStackContext($requestStack)
);
- echo $pathPackage->getUrl('/logo.png');
+ echo $pathPackage->getUrl('logo.png');
// result: /somewhere/static/images/logo.png?v1
+ // Both "base path" and "base url" are ignored when using absolute path for asset
+ echo $pathPackage->getUrl('/logo.png');
+ // result: /logo.png?v1
+
Now that the request context is set, the ``PathPackage`` will prepend the
current request base URL. So, for example, if your entire site is hosted under
the ``/somewhere`` directory of your web server root directory and the configured
@@ -247,10 +291,10 @@ constructor::
use Symfony\Component\Asset\UrlPackage;
// ...
- $urls = array(
+ $urls = [
'//static1.example.com/images/',
'//static2.example.com/images/',
- );
+ ];
$urlPackage = new UrlPackage($urls, new StaticVersionStrategy('v1'));
echo $urlPackage->getUrl('/logo.png');
@@ -259,7 +303,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
@@ -270,12 +314,12 @@ 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(
- array('http://example.com/', 'https://example.com/'),
+ ['http://example.com/', 'https://example.com/'],
new StaticVersionStrategy('v1'),
new RequestStackContext($requestStack)
);
@@ -296,19 +340,19 @@ 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');
$defaultPackage = new Package($versionStrategy);
- $namedPackages = array(
+ $namedPackages = [
'img' => new UrlPackage('http://img.example.com/', $versionStrategy),
'doc' => new PathPackage('/somewhere/deep/for/documents', $versionStrategy),
- );
+ ];
$packages = new Packages($defaultPackage, $namedPackages);
@@ -324,10 +368,13 @@ document inside a template::
echo $packages->getUrl('/logo.png', 'img');
// result: http://img.example.com/logo.png?v1
- echo $packages->getUrl('/resume.pdf', 'doc');
+ echo $packages->getUrl('resume.pdf', 'doc');
// result: /somewhere/deep/for/documents/resume.pdf?v1
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 69d60a1612a..ba295db0eb4 100644
--- a/components/browser_kit.rst
+++ b/components/browser_kit.rst
@@ -19,9 +19,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/browser-kit
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require symfony/browser-kit:^3.4
.. include:: /components/require_autoload.rst.inc
@@ -155,6 +153,7 @@ retrieve any cookie while making requests with the client::
$expires = $cookie->getExpiresTime();
$path = $cookie->getPath();
$domain = $cookie->getDomain();
+ $sameSite = $cookie->getSameSite();
.. note::
@@ -206,7 +205,7 @@ into the client constructor::
$cookieJar->set($cookie);
// create a client and set the cookies
- $client = new Client(array(), null, $cookieJar);
+ $client = new Client([], null, $cookieJar);
// ...
History
@@ -248,5 +247,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
new file mode 100644
index 00000000000..217937a8ec5
--- /dev/null
+++ b/components/cache.rst
@@ -0,0 +1,195 @@
+.. index::
+ single: Cache
+ single: Performance
+ single: Components; Cache
+
+.. _`cache-component`:
+
+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`_.
+
+.. versionadded:: 3.3
+
+ The PSR-16 "Simple Cache" implementation was introduced in Symfony 3.3.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/cache:^3.4
+
+.. include:: /components/require_autoload.rst.inc
+
+Cache (PSR-6) Versus Simple Cache (PSR-16)
+------------------------------------------
+
+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.
+
+Both methods support the *same* cache adapters and will give you very similar performance.
+
+.. tip::
+
+ The component also contains adapters to convert between PSR-6 and PSR-16 caches.
+ See :doc:`/components/cache/psr6_psr16_adapters`.
+
+.. _cache-component-psr16-caching:
+
+Simple Caching (PSR-16)
+-----------------------
+
+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`::
+
+ use Symfony\Component\Cache\Simple\FilesystemCache;
+
+ $cache = new FilesystemCache();
+
+Now you can create, retrieve, update and delete items using this object::
+
+ // save a new item in the cache
+ $cache->set('stats.products_count', 4711);
+
+ // or set it with a custom ttl
+ // $cache->set('stats.products_count', 4711, 3600);
+
+ // retrieve the cache item
+ if (!$cache->has('stats.products_count')) {
+ // ... item does not exist in the cache
+ }
+
+ // retrieve the value stored by the item
+ $productsCount = $cache->get('stats.products_count');
+
+ // or specify a default value, if the key doesn't exist
+ // $productsCount = $cache->get('stats.products_count', 100);
+
+ // remove the cache key
+ $cache->delete('stats.products_count');
+
+ // clear *all* cache keys
+ $cache->clear();
+
+You can also work with multiple items at once::
+
+ $cache->setMultiple([
+ 'stats.products_count' => 4711,
+ 'stats.users_count' => 1356,
+ ]);
+
+ $stats = $cache->getMultiple([
+ 'stats.products_count',
+ 'stats.users_count',
+ ]);
+
+ $cache->deleteMultiple([
+ 'stats.products_count',
+ 'stats.users_count',
+ ]);
+
+Available Simple Cache (PSR-16) Classes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The following cache adapters are available:
+
+.. tip::
+
+ 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)
+-----------------------------
+
+To use the more-advanced, 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;
+**Pool**
+ A logical repository of cache items. All cache operations (saving items,
+ looking for items, etc.) are performed through the pool. Applications can
+ define as many pools as needed.
+**Adapter**
+ It implements the actual caching mechanism to store the information in the
+ filesystem, in a database, etc. The component provides several ready to use
+ adapters for common caching backends (Redis, APCu, Doctrine, PDO, etc.)
+
+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,
+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`::
+
+ use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+
+ $cache = new FilesystemAdapter();
+
+Now you can create, retrieve, update and delete items using this cache pool::
+
+ // create a new item by trying to get it from the cache
+ $productsCount = $cache->getItem('stats.products_count');
+
+ // assign a value to the item and save it
+ $productsCount->set(4711);
+ $cache->save($productsCount);
+
+ // retrieve the cache item
+ $productsCount = $cache->getItem('stats.products_count');
+ if (!$productsCount->isHit()) {
+ // ... item does not exist in the cache
+ }
+ // retrieve the value stored by the item
+ $total = $productsCount->get();
+
+ // remove the cache item
+ $cache->deleteItem('stats.products_count');
+
+For a list of all of the supported adapters, see :doc:`/components/cache/cache_pools`.
+
+Advanced Usage (PSR-6)
+----------------------
+
+.. toctree::
+ :glob:
+ :maxdepth: 1
+
+ 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
diff --git a/components/cache/adapters/apcu_adapter.rst b/components/cache/adapters/apcu_adapter.rst
new file mode 100644
index 00000000000..54507ccfc66
--- /dev/null
+++ b/components/cache/adapters/apcu_adapter.rst
@@ -0,0 +1,50 @@
+.. index::
+ single: Cache Pool
+ single: APC Cache, APCu Cache
+
+.. _apcu-adapter:
+
+APCu Cache Adapter
+==================
+
+This adapter is a high-performance, shared memory cache. It can *significantly*
+increase an application's performance, as its cache contents are stored in shared
+memory, a component appreciably faster than many others, such as the filesystem.
+
+.. caution::
+
+ **Requirement:** The `APCu extension`_ must be installed and active to use
+ this adapter.
+
+The ApcuAdapter can optionally be provided a namespace, default cache lifetime,
+and cache items version string as constructor arguments::
+
+ use Symfony\Component\Cache\Adapter\ApcuAdapter;
+
+ $cache = new ApcuAdapter(
+
+ // a string prefixed to the keys of the items stored in this cache
+ $namespace = '',
+
+ // the default lifetime (in seconds) for cache items that do not define their
+ // own lifetime, with a value 0 causing items to be stored indefinitely (i.e.
+ // until the APCu memory is cleared)
+ $defaultLifetime = 0,
+
+ // when set, all keys prefixed by $namespace can be invalidated by changing
+ // this $version string
+ $version = null
+ );
+
+.. caution::
+
+ Use of this adapter is discouraged in write/delete heavy workloads, as these
+ operations cause memory fragmentation that results in significantly degraded performance.
+
+.. tip::
+
+ This adapter's CRUD operations are specific to the PHP SAPI it is running under. This
+ means cache operations (such as additions, deletions, etc) using the CLI will not be
+ available under the FPM or CGI SAPIs.
+
+.. _`APCu extension`: https://pecl.php.net/package/APCu
diff --git a/components/cache/adapters/chain_adapter.rst b/components/cache/adapters/chain_adapter.rst
new file mode 100644
index 00000000000..18ba769ec31
--- /dev/null
+++ b/components/cache/adapters/chain_adapter.rst
@@ -0,0 +1,68 @@
+.. index::
+ single: Cache Pool
+ single: Chain Cache
+
+.. _component-cache-chain-adapter:
+
+Chain Cache Adapter
+===================
+
+This adapter allows combining any number of the other
+:ref:`available cache adapters `. Cache items are
+fetched from the first adapter containing them and cache items are saved to all the
+given adapters. This exposes a simple and efficient method for creating a layered cache.
+
+The ChainAdapter must be provided an array of adapters and optionally a maximum cache
+lifetime as its constructor arguments::
+
+ 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::
+
+ When an item is not found in the first adapter but is found in the next ones, this
+ adapter ensures that the fetched item is saved to all the adapters where it was
+ previously missing.
+
+The following example shows how to create a chain adapter instance using the fastest and
+slowest storage engines, :class:`Symfony\\Component\\Cache\\Adapter\\ApcuAdapter` and
+:class:`Symfony\\Component\\Cache\\Adapter\\FilesystemAdapter`, respectfully::
+
+ use Symfony\Component\Cache\Adapter\ApcuAdapter;
+ use Symfony\Component\Cache\Adapter\ChainAdapter;
+ use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+
+ $cache = new ChainAdapter([
+ new ApcuAdapter(),
+ new FilesystemAdapter(),
+ ]);
+
+When calling this adapter's :method:`Symfony\\Component\\Cache\\Adapter\\ChainAdapter::prune` method,
+the call is delegated to all its compatible cache adapters. It is safe to mix both adapters
+that *do* and do *not* implement :class:`Symfony\\Component\\Cache\\PruneableInterface`, as
+incompatible adapters are silently ignored::
+
+ use Symfony\Component\Cache\Adapter\ApcuAdapter;
+ use Symfony\Component\Cache\Adapter\ChainAdapter;
+ use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+
+ $cache = new ChainAdapter([
+ new ApcuAdapter(), // does NOT implement PruneableInterface
+ new FilesystemAdapter(), // DOES implement PruneableInterface
+ ]);
+
+ // prune will proxy the call to FilesystemAdapter while silently skip ApcuAdapter
+ $cache->prune();
+
+.. versionadded:: 3.4
+
+ 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
new file mode 100644
index 00000000000..f8f95126ae9
--- /dev/null
+++ b/components/cache/adapters/doctrine_adapter.rst
@@ -0,0 +1,37 @@
+.. index::
+ single: Cache Pool
+ single: Doctrine Cache
+
+.. _doctrine-adapter:
+
+Doctrine Cache Adapter
+======================
+
+This adapter wraps any class extending the `Doctrine Cache`_ abstract provider, allowing
+you to use these providers in your application as if they were Symfony Cache adapters.
+
+This adapter expects a ``\Doctrine\Common\Cache\CacheProvider`` instance as its first
+parameter, and optionally a namespace and default cache lifetime as its second and
+third parameters::
+
+ use Doctrine\Common\Cache\CacheProvider;
+ use Doctrine\Common\Cache\SQLite3Cache;
+ use Symfony\Component\Cache\Adapter\DoctrineAdapter;
+
+ $provider = new SQLite3Cache(new \SQLite3(__DIR__.'/cache/data.sqlite'), 'youTableName');
+
+ $cache = new DoctrineAdapter(
+
+ // a cache provider instance
+ CacheProvider $provider,
+
+ // a string prefixed to the keys of the items stored in this cache
+ $namespace = '',
+
+ // the default lifetime (in seconds) for cache items that do not define their
+ // own lifetime, with a value 0 causing items to be stored indefinitely (i.e.
+ // until the database table is truncated or its rows are otherwise deleted)
+ $defaultLifetime = 0
+ );
+
+.. _`Doctrine Cache`: https://github.com/doctrine/cache
diff --git a/components/cache/adapters/filesystem_adapter.rst b/components/cache/adapters/filesystem_adapter.rst
new file mode 100644
index 00000000000..33097fbd202
--- /dev/null
+++ b/components/cache/adapters/filesystem_adapter.rst
@@ -0,0 +1,59 @@
+.. index::
+ single: Cache Pool
+ single: Filesystem Cache
+
+.. _component-cache-filesystem-adapter:
+
+Filesystem Cache Adapter
+========================
+
+This adapter offers improved application performance for those who cannot install
+tools like :ref:`APCu ` or :ref:`Redis ` in their
+environment. It stores the cache item expiration and content as regular files in
+a collection of directories on a locally mounted filesystem.
+
+.. tip::
+
+ The performance of this adapter can be greatly increased by utilizing a
+ temporary, in-memory filesystem, such as `tmpfs`_ on Linux, or one of the
+ many other `RAM disk solutions`_ available.
+
+The FilesystemAdapter can optionally be provided a namespace, default cache lifetime,
+and cache root path as constructor parameters::
+
+ use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+
+ $cache = new FilesystemAdapter(
+
+ // a string used as the subdirectory of the root cache directory, where cache
+ // items will be stored
+ $namespace = '',
+
+ // the default lifetime (in seconds) for cache items that do not define their
+ // own lifetime, with a value 0 causing items to be stored indefinitely (i.e.
+ // until the files are deleted)
+ $defaultLifetime = 0,
+
+ // the main cache directory (the application needs read-write permissions on it)
+ // if none is specified, a directory is created inside the system temporary directory
+ $directory = null
+ );
+
+.. caution::
+
+ The overhead of filesystem IO often makes this adapter one of the *slower*
+ choices. If throughput is paramount, the in-memory adapters
+ (:ref:`Apcu `, :ref:`Memcached `, and
+ :ref:`Redis `) or the database adapters
+ (:ref:`Doctrine ` and :ref:`PDO `)
+ are recommended.
+
+.. note::
+
+ Since Symfony 3.4, this adapter implements
+ :class:`Symfony\\Component\\Cache\\PruneableInterface`, enabling manual
+ :ref:`pruning of expired cache items ` by
+ calling its ``prune()`` method.
+
+.. _`tmpfs`: https://wiki.archlinux.org/index.php/tmpfs
+.. _`RAM disk solutions`: https://en.wikipedia.org/wiki/List_of_RAM_drive_software
diff --git a/components/cache/adapters/memcached_adapter.rst b/components/cache/adapters/memcached_adapter.rst
new file mode 100644
index 00000000000..d51acc19aa0
--- /dev/null
+++ b/components/cache/adapters/memcached_adapter.rst
@@ -0,0 +1,302 @@
+.. index::
+ single: Cache Pool
+ single: Memcached Cache
+
+.. _memcached-adapter:
+
+Memcached Cache Adapter
+=======================
+
+.. versionadded:: 3.3
+
+ The Memcached adapter was introduced in Symfony 3.3.
+
+This adapter stores the values in-memory using one (or more) `Memcached server`_
+instances. Unlike the :ref:`APCu adapter `, and similarly to the
+:ref:`Redis adapter `, it is not limited to the current server's
+shared memory; you can store contents independent of your PHP environment.
+The ability to utilize a cluster of servers to provide redundancy and/or fail-over
+is also available.
+
+.. caution::
+
+ **Requirements:** The `Memcached PHP extension`_ as well as a `Memcached server`_
+ must be installed, active, and running to use this adapter. Version ``2.2`` or
+ greater of the `Memcached PHP extension`_ is required for this adapter.
+
+This adapter expects a `Memcached`_ instance to be passed as the first
+parameter. A namespace and default cache lifetime can optionally be passed as
+the second and third parameters::
+
+ use Symfony\Component\Cache\Adapter\MemcachedAdapter;
+
+ $cache = new MemcachedAdapter(
+ // the client object that sets options and adds the server instance(s)
+ \Memcached $client,
+
+ // a string prefixed to the keys of the items stored in this cache
+ $namespace = '',
+
+ // the default lifetime (in seconds) for cache items that do not define their
+ // own lifetime, with a value 0 causing items to be stored indefinitely (i.e.
+ // until MemcachedAdapter::clear() is invoked or the server(s) are restarted)
+ $defaultLifetime = 0
+ );
+
+Configure the Connection
+------------------------
+
+The :method:`Symfony\\Component\\Cache\\Adapter\\MemcachedAdapter::createConnection`
+helper method allows creating and configuring a `Memcached`_ class instance using a
+`Data Source Name (DSN)`_ or an array of DSNs::
+
+ use Symfony\Component\Cache\Adapter\MemcachedAdapter;
+
+ // pass a single DSN string to register a single server with the client
+ $client = MemcachedAdapter::createConnection(
+ 'memcached://localhost'
+ // the DSN can include config options (pass them as a query string):
+ // 'memcached://localhost:11222?retry_timeout=10'
+ // 'memcached://localhost:11222?socket_recv_size=1&socket_send_size=2'
+ );
+
+ // pass an array of DSN strings to register multiple servers with the client
+ $client = MemcachedAdapter::createConnection([
+ 'memcached://10.0.0.100',
+ 'memcached://10.0.0.101',
+ 'memcached://10.0.0.102',
+ // etc...
+ ]);
+
+.. versionadded:: 3.4
+
+ The feature to pass configuration options in the memcached DSN was
+ introduced in Symfony 3.4.
+
+The `Data Source Name (DSN)`_ for this adapter must use the following format:
+
+.. code-block:: text
+
+ memcached://[user:pass@][ip|host|socket[:port]][?weight=int]
+
+The DSN must include a IP/host (and an optional port) or a socket path, an
+optional username and password (for SASL authentication; it requires that the
+memcached extension was compiled with ``--enable-memcached-sasl``) and an
+optional weight (for prioritizing servers in a cluster; its value is an integer
+between ``0`` and ``100`` which defaults to ``null``; a higher value means more
+priority).
+
+Below are common examples of valid DSNs showing a combination of available values::
+
+ use Symfony\Component\Cache\Adapter\MemcachedAdapter;
+
+ $client = MemcachedAdapter::createConnection([
+ // hostname + port
+ 'memcached://my.server.com:11211'
+
+ // hostname without port + SASL username and password
+ 'memcached://rmf:abcdef@localhost'
+
+ // IP address instead of hostname + weight
+ 'memcached://127.0.0.1?weight=50'
+
+ // socket instead of hostname/IP + SASL username and password
+ 'memcached://janesmith:mypassword@/var/run/memcached.sock'
+
+ // socket instead of hostname/IP + weight
+ 'memcached:///var/run/memcached.sock?weight=20'
+ ]);
+
+Configure the Options
+---------------------
+
+The :method:`Symfony\\Component\\Cache\\Adapter\\MemcachedAdapter::createConnection`
+helper method also accepts an array of options as its second argument. The
+expected format is an associative array of ``key => value`` pairs representing
+option names and their respective values::
+
+ use Symfony\Component\Cache\Adapter\MemcachedAdapter;
+
+ $client = MemcachedAdapter::createConnection(
+ // a DSN string or an array of DSN strings
+ [],
+
+ // associative array of configuration options
+ [
+ 'compression' => true,
+ 'libketama_compatible' => true,
+ 'serializer' => 'igbinary',
+ ]
+ );
+
+Available Options
+~~~~~~~~~~~~~~~~~
+
+``auto_eject_hosts`` (type: ``bool``, default: ``false``)
+ Enables or disables a constant, automatic, re-balancing of the cluster by
+ auto-ejecting hosts that have exceeded the configured ``server_failure_limit``.
+
+``buffer_writes`` (type: ``bool``, default: ``false``)
+ Enables or disables buffered input/output operations, causing storage
+ commands to buffer instead of being immediately sent to the remote
+ server(s). Any action that retrieves data, quits the connection, or closes
+ down the connection will cause the buffer to be committed.
+
+``compression`` (type: ``bool``, default: ``true``)
+ Enables or disables payload compression, where item values longer than 100
+ bytes are compressed during storage and decompressed during retrieval.
+
+``compression_type`` (type: ``string``)
+ Specifies the compression method used on value payloads. when the
+ **compression** option is enabled.
+
+ Valid option values include ``fastlz`` and ``zlib``, with a default value
+ that *varies based on flags used at compilation*.
+
+``connect_timeout`` (type: ``int``, default: ``1000``)
+ Specifies the timeout (in milliseconds) of socket connection operations when
+ the ``no_block`` option is enabled.
+
+ Valid option values include *any positive integer*.
+
+``distribution`` (type: ``string``, default: ``consistent``)
+ Specifies the item key distribution method among the servers. Consistent
+ hashing delivers better distribution and allows servers to be added to the
+ cluster with minimal cache losses.
+
+ Valid option values include ``modula``, ``consistent``, and ``virtual_bucket``.
+
+``hash`` (type: ``string``, default: ``md5``)
+ Specifies the hashing algorithm used for item keys. Each hash algorithm has
+ its advantages and its disadvantages. The default is suggested for compatibility
+ with other clients.
+
+ Valid option values include ``default``, ``md5``, ``crc``, ``fnv1_64``,
+ ``fnv1a_64``, ``fnv1_32``, ``fnv1a_32``, ``hsieh``, and ``murmur``.
+
+``libketama_compatible`` (type: ``bool``, default: ``true``)
+ Enables or disables "libketama" compatible behavior, enabling other
+ libketama-based clients to access the keys stored by client instance
+ transparently (like Python and Ruby). Enabling this option sets the ``hash``
+ option to ``md5`` and the ``distribution`` option to ``consistent``.
+
+``no_block`` (type: ``bool``, default: ``true``)
+ Enables or disables asynchronous input and output operations. This is the
+ fastest transport option available for storage functions.
+
+``number_of_replicas`` (type: ``int``, default: ``0``)
+ Specifies the number of replicas that should be stored for each item (on
+ different servers). This does not dedicate certain memcached servers to
+ store the replicas in, but instead stores the replicas together with all of
+ the other objects (on the "n" next servers registered).
+
+ Valid option values include *any positive integer*.
+
+``prefix_key`` (type: ``string``, default: an empty string)
+ Specifies a "domain" (or "namespace") prepended to your keys. It cannot be
+ longer than 128 characters and reduces the maximum key size.
+
+ Valid option values include *any alphanumeric string*.
+
+``poll_timeout`` (type: ``int``, default: ``1000``)
+ Specifies the amount of time (in seconds) before timing out during a socket
+ polling operation.
+
+ Valid option values include *any positive integer*.
+
+``randomize_replica_read`` (type: ``bool``, type: ``false``)
+ Enables or disables randomization of the replica reads starting point.
+ Normally the read is done from primary server and in case of a miss the read
+ is done from "primary+1", then "primary+2", all the way to "n" replicas.
+ This option sets the replica reads as randomized between all available
+ servers; it allows distributing read load to multiple servers with the
+ expense of more write traffic.
+
+``recv_timeout`` (type: ``int``, default: ``0``)
+ Specifies the amount of time (in microseconds) before timing out during an outgoing socket (read) operation.
+ When the ``no_block`` option isn't enabled, this will allow you to still have timeouts on the reading of data.
+
+ Valid option values include ``0`` or *any positive integer*.
+
+``retry_timeout`` (type: ``int``, default: ``0``)
+ Specifies the amount of time (in seconds) before timing out and retrying a
+ connection attempt.
+
+ Valid option values include *any positive integer*.
+
+``send_timeout`` (type: ``int``, default: ``0``)
+ Specifies the amount of time (in microseconds) before timing out during an
+ incoming socket (send) operation. When the ``no_block`` option isn't enabled,
+ this will allow you to still have timeouts on the sending of data.
+
+ Valid option values include ``0`` or *any positive integer*.
+
+``serializer`` (type: ``string``, default: ``php``)
+ Specifies the serializer to use for serializing non-scalar values. The
+ ``igbinary`` options requires the igbinary PHP extension to be enabled, as
+ well as the memcached extension to have been compiled with support for it.
+
+ Valid option values include ``php`` and ``igbinary``.
+
+``server_failure_limit`` (type: ``int``, default: ``0``)
+ Specifies the failure limit for server connection attempts before marking
+ 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*.
+
+``socket_recv_size`` (type: ``int``)
+ Specified the maximum buffer size (in bytes) in the context of incoming
+ (receive) socket connection data.
+
+ Valid option values include *any positive integer*, with a default value
+ that *varies by platform and kernel configuration*.
+
+``socket_send_size`` (type: ``int``)
+ Specified the maximum buffer size (in bytes) in the context of outgoing (send)
+ socket connection data.
+
+ Valid option values include *any positive integer*, with a default value
+ that *varies by platform and kernel configuration*.
+
+``tcp_keepalive`` (type: ``bool``, default: ``false``)
+ Enables or disables the "`keep-alive`_" `Transmission Control Protocol (TCP)`_
+ feature, which is a feature that helps to determine whether the other end
+ has stopped responding by sending probes to the network peer after an idle
+ period and closing or persisting the socket based on the response (or lack thereof).
+
+``tcp_nodelay`` (type: ``bool``, default: ``false``)
+ Enables or disables the "`no-delay`_" (Nagle's algorithm) `Transmission Control Protocol (TCP)`_
+ algorithm, which is a mechanism intended to improve the efficiency of
+ networks by reducing the overhead of TCP headers by combining a number of
+ small outgoing messages and sending them all at once.
+
+``use_udp`` (type: ``bool``, default: ``false``)
+ Enables or disables the use of `User Datagram Protocol (UDP)`_ mode (instead
+ of `Transmission Control Protocol (TCP)`_ mode), where all operations are
+ executed in a "fire-and-forget" manner; no attempt to ensure the operation
+ has been received or acted on will be made once the client has executed it.
+
+ .. caution::
+
+ Not all library operations are tested in this mode. Mixed TCP and UDP
+ servers are not allowed.
+
+``verify_key`` (type: ``bool``, default: ``false``)
+ Enables or disables testing and verifying of all keys used to ensure they
+ are valid and fit within the design of the protocol being used.
+
+.. tip::
+
+ Reference the `Memcached`_ extension's `predefined constants`_ documentation
+ for additional information about the available options.
+
+.. _`Transmission Control Protocol (TCP)`: https://en.wikipedia.org/wiki/Transmission_Control_Protocol
+.. _`User Datagram Protocol (UDP)`: https://en.wikipedia.org/wiki/User_Datagram_Protocol
+.. _`no-delay`: https://en.wikipedia.org/wiki/TCP_NODELAY
+.. _`keep-alive`: https://en.wikipedia.org/wiki/Keepalive
+.. _`Memcached PHP extension`: http://php.net/manual/en/book.memcached.php
+.. _`predefined constants`: http://php.net/manual/en/memcached.constants.php
+.. _`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
diff --git a/components/cache/adapters/pdo_doctrine_dbal_adapter.rst b/components/cache/adapters/pdo_doctrine_dbal_adapter.rst
new file mode 100644
index 00000000000..047b88a0e66
--- /dev/null
+++ b/components/cache/adapters/pdo_doctrine_dbal_adapter.rst
@@ -0,0 +1,51 @@
+.. index::
+ single: Cache Pool
+ single: PDO Cache, Doctrine DBAL Cache
+
+.. _pdo-doctrine-adapter:
+
+PDO & Doctrine DBAL Cache Adapter
+=================================
+
+.. versionadded:: 3.2
+
+ The PDO & Doctrine DBAL adapter was introduced in Symfony 3.2.
+
+This adapter stores the cache items in an SQL database. It requires a `PDO`_,
+`Doctrine DBAL Connection`_, or `Data Source Name (DSN)`_ as its first parameter, and
+optionally a namespace, default cache lifetime, and options array as its second,
+third, and forth parameters::
+
+ use Symfony\Component\Cache\Adapter\PdoAdapter;
+
+ $cache = new PdoAdapter(
+
+ // a PDO, a Doctrine DBAL connection or DSN for lazy connecting through PDO
+ $databaseConnectionOrDSN,
+
+ // the string prefixed to the keys of the items stored in this cache
+ $namespace = '',
+
+ // the default lifetime (in seconds) for cache items that do not define their
+ // own lifetime, with a value 0 causing items to be stored indefinitely (i.e.
+ // until the database table is truncated or its rows are otherwise deleted)
+ $defaultLifetime = 0,
+
+ // an array of options for configuring the database table and connection
+ $options = []
+ );
+
+.. tip::
+
+ When passed a `Data Source Name (DSN)`_ string (instead of a database connection
+ class instance), the connection will be lazy-loaded when needed.
+
+.. note::
+
+ Since Symfony 3.4, this adapter implements :class:`Symfony\\Component\\Cache\\PruneableInterface`,
+ allowing for manual :ref:`pruning of expired cache entries ` by
+ calling its ``prune()`` method.
+
+.. _`PDO`: http://php.net/manual/en/class.pdo.php
+.. _`Doctrine DBAL Connection`: https://github.com/doctrine/dbal/blob/master/src/Connection.php
+.. _`Data Source Name (DSN)`: https://en.wikipedia.org/wiki/Data_source_name
diff --git a/components/cache/adapters/php_array_cache_adapter.rst b/components/cache/adapters/php_array_cache_adapter.rst
new file mode 100644
index 00000000000..c27e4fd2ecf
--- /dev/null
+++ b/components/cache/adapters/php_array_cache_adapter.rst
@@ -0,0 +1,38 @@
+.. index::
+ single: Cache Pool
+ single: PHP Array Cache
+
+PHP Array Cache Adapter
+=======================
+
+This adapter is a high performance cache for static data (e.g. application configuration)
+that is optimized and preloaded into OPcache memory storage::
+
+ use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+ use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
+
+ // somehow, decide it's time to warm up the cache!
+ if ($needsWarmup) {
+ // some static values
+ $values = [
+ 'stats.products_count' => 4711,
+ 'stats.users_count' => 1356,
+ ];
+
+ $cache = new PhpArrayAdapter(
+ // single file where values are cached
+ __DIR__ . '/somefile.cache',
+ // a backup adapter, if you set values after warmup
+ new FilesystemAdapter()
+ );
+ $cache->warmUp($values);
+ }
+
+ // ... then, use the cache!
+ $cacheItem = $cache->getItem('stats.users_count');
+ echo $cacheItem->get();
+
+.. note::
+
+ This adapter requires PHP 7.x and should be used with the ``php.ini``
+ setting ``opcache.enable=On``.
diff --git a/components/cache/adapters/php_files_adapter.rst b/components/cache/adapters/php_files_adapter.rst
new file mode 100644
index 00000000000..8e693be63ec
--- /dev/null
+++ b/components/cache/adapters/php_files_adapter.rst
@@ -0,0 +1,69 @@
+.. index::
+ single: Cache Pool
+ single: PHP Files Cache
+
+.. _component-cache-files-adapter:
+
+PHP Files Cache Adapter
+=======================
+
+Similarly to :ref:`Filesystem Adapter `, this cache
+implementation writes cache entries out to disk, but unlike the Filesystem cache adapter,
+the PHP Files cache adapter writes and reads back these cache files *as native PHP code*.
+For example, caching the value ``['my', 'cached', 'array']`` will write out a cache
+file similar to the following::
+
+ 9223372036854775807,
+
+ // the cache item contents
+ 1 => [
+ 0 => 'my',
+ 1 => 'cached',
+ 2 => 'array',
+ ],
+
+ ];
+
+.. note::
+
+ 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 be invalidated unless you clear OPcache.
+
+The PhpFilesAdapter can optionally be provided a namespace, default cache lifetime, and cache
+directory path as constructor arguments::
+
+ use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
+
+ $cache = new PhpFilesAdapter(
+
+ // a string used as the subdirectory of the root cache directory, where cache
+ // items will be stored
+ $namespace = '',
+
+ // the default lifetime (in seconds) for cache items that do not define their
+ // own lifetime, with a value 0 causing items to be stored indefinitely (i.e.
+ // until the files are deleted)
+ $defaultLifetime = 0,
+
+ // the main cache directory (the application needs read-write permissions on it)
+ // if none is specified, a directory is created inside the system temporary directory
+ $directory = null
+ );
+
+.. 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.
+
+.. _`OPcache`: http://php.net/manual/en/book.opcache.php
diff --git a/components/cache/adapters/proxy_adapter.rst b/components/cache/adapters/proxy_adapter.rst
new file mode 100644
index 00000000000..a0959781bca
--- /dev/null
+++ b/components/cache/adapters/proxy_adapter.rst
@@ -0,0 +1,37 @@
+.. index::
+ single: Cache Pool
+ single: Proxy Cache
+
+Proxy Cache Adapter
+===================
+
+This adapter wraps a `PSR-6`_ compliant `cache item pool interface`_. It is used to integrate
+your application's cache item pool implementation with the Symfony :ref:`Cache Component `
+by consuming any implementation of ``Psr\Cache\CacheItemPoolInterface``.
+
+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;
+
+ // create your own cache pool instance that implements
+ // the PSR-6 CacheItemPoolInterface
+ $psr6CachePool = ...
+
+ $cache = new ProxyAdapter(
+
+ // a cache pool instance
+ CacheItemPoolInterface $psr6CachePool,
+
+ // a string prefixed to the keys of the items stored in this cache
+ $namespace = '',
+
+ // the default lifetime (in seconds) for cache items that do not define their
+ // own lifetime, with a value 0 causing items to be stored indefinitely (i.e.
+ // until the cache is cleared)
+ $defaultLifetime = 0
+ );
+
+.. _`PSR-6`: http://www.php-fig.org/psr/psr-6/
+.. _`cache item pool interface`: http://www.php-fig.org/psr/psr-6/#cacheitempoolinterface
diff --git a/components/cache/adapters/redis_adapter.rst b/components/cache/adapters/redis_adapter.rst
new file mode 100644
index 00000000000..6850f079b2c
--- /dev/null
+++ b/components/cache/adapters/redis_adapter.rst
@@ -0,0 +1,159 @@
+.. index::
+ single: Cache Pool
+ single: Redis Cache
+
+.. _redis-adapter:
+
+Redis Cache Adapter
+===================
+
+.. 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
+to utilize a cluster of servers to provide redundancy and/or fail-over is also available.
+
+.. caution::
+
+ **Requirements:** At least one `Redis server`_ must be installed and running to use this
+ adapter. Additionally, this adapter requires a compatible extension or library that implements
+ ``\Redis``, ``\RedisArray``, ``RedisCluster``, or ``\Predis``.
+
+This adapter expects a `Redis`_, `RedisArray`_, `RedisCluster`_, or `Predis`_ instance to be
+passed as the first parameter. A namespace and default cache lifetime can optionally be passed
+as the second and third parameters::
+
+ use Symfony\Component\Cache\Adapter\RedisAdapter;
+
+ $cache = new RedisAdapter(
+
+ // the object that stores a valid connection to your Redis system
+ \Redis $redisConnection,
+
+ // the string prefixed to the keys of the items stored in this cache
+ $namespace = '',
+
+ // the default lifetime (in seconds) for cache items that do not define their
+ // own lifetime, with a value 0 causing items to be stored indefinitely (i.e.
+ // until RedisAdapter::clear() is invoked or the server(s) are purged)
+ $defaultLifetime = 0
+ );
+
+Configure the Connection
+------------------------
+
+The :method:`Symfony\\Component\\Cache\\Adapter\\RedisAdapter::createConnection`
+helper method allows creating and configuring the Redis client class instance using a
+`Data Source Name (DSN)`_::
+
+ use Symfony\Component\Cache\Adapter\RedisAdapter;
+
+ // pass a single DSN string to register a single server with the client
+ $client = RedisAdapter::createConnection(
+ '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.
+
+.. note::
+
+ A `Data Source Name (DSN)`_ for this adapter must use the following format.
+
+ .. code-block:: text
+
+ redis://[user:pass@][ip|host|socket[:port]][/db-index]
+
+Below are common examples of valid DSNs showing a combination of available values::
+
+ use Symfony\Component\Cache\Adapter\RedisAdapter;
+
+ // host "my.server.com" and port "6379"
+ RedisAdapter::createConnection('redis://my.server.com:6379');
+
+ // 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');
+
+ // socket "/var/run/redis.sock" and SASL user "user1" and pass "bad-pass"
+ RedisAdapter::createConnection('redis://user1:bad-pass@/var/run/redis.sock');
+
+Configure the Options
+---------------------
+
+The :method:`Symfony\\Component\\Cache\\Adapter\\RedisAdapter::createConnection` helper method
+also accepts an array of options as its second argument. The expected format is an associative
+array of ``key => value`` pairs representing option names and their respective values::
+
+ use Symfony\Component\Cache\Adapter\RedisAdapter;
+
+ $client = RedisAdapter::createConnection(
+
+ // provide a string dsn
+ 'redis://localhost:6379',
+
+ // associative array of configuration options
+ [
+ 'lazy' => false,
+ 'persistent' => 0,
+ 'persistent_id' => null,
+ 'timeout' => 30,
+ 'read_timeout' => 0,
+ 'retry_interval' => 0,
+ ]
+
+ );
+
+Available Options
+~~~~~~~~~~~~~~~~~
+
+``class`` (type: ``string``)
+ Specifies the connection library to return, either ``\Redis`` or ``\Predis\Client``.
+ If none is specified, it will return ``\Redis`` if the ``redis`` extension is
+ available, and ``\Predis\Client`` otherwise.
+
+``lazy`` (type: ``bool``, default: ``false``)
+ Enables or disables lazy connections to the backend. It's ``false`` by
+ default when using this as a stand-alone component and ``true`` by default
+ when using it inside a Symfony application.
+
+``persistent`` (type: ``int``, default: ``0``)
+ Enables or disables use of persistent connections. A value of ``0`` disables persistent
+ connections, and a value of ``1`` enables them.
+
+``persistent_id`` (type: ``string|null``, default: ``null``)
+ Specifies the persistent id string to use for a persistent connection.
+
+``read_timeout`` (type: ``int``, default: ``0``)
+ Specifies the time (in seconds) used when performing read operations on the underlying
+ network resource before the operation times out.
+
+``retry_interval`` (type: ``int``, default: ``0``)
+ Specifies the delay (in milliseconds) between reconnection attempts in case the client
+ loses connection with the server.
+
+``timeout`` (type: ``int``, default: ``30``)
+ Specifies the time (in seconds) used to connect to a Redis server before the
+ connection attempt times out.
+
+.. note::
+
+ When using the `Predis`_ library some additional Predis-specific options are available.
+ Reference the `Predis Connection Parameters`_ documentation for more information.
+
+.. _`Data Source Name (DSN)`: https://en.wikipedia.org/wiki/Data_source_name
+.. _`Redis server`: https://redis.io/
+.. _`Redis`: https://github.com/phpredis/phpredis
+.. _`RedisArray`: https://github.com/phpredis/phpredis/blob/master/arrays.markdown#readme
+.. _`RedisCluster`: https://github.com/phpredis/phpredis/blob/master/cluster.markdown#readme
+.. _`Predis`: https://packagist.org/packages/predis/predis
+.. _`Predis Connection Parameters`: https://github.com/nrk/predis/wiki/Connection-Parameters#list-of-connection-parameters
diff --git a/components/cache/cache_invalidation.rst b/components/cache/cache_invalidation.rst
new file mode 100644
index 00000000000..9deefa6851f
--- /dev/null
+++ b/components/cache/cache_invalidation.rst
@@ -0,0 +1,104 @@
+.. index::
+ single: Cache; Invalidation
+ single: Cache; Tags
+
+Cache Invalidation
+==================
+
+Cache invalidation is the process of removing all cached items related to a
+change in the state of your model. The most basic kind of invalidation is direct
+items deletion. But when the state of a primary resource has spread across
+several cached items, keeping them in sync can be difficult.
+
+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.
+
+.. _cache-component-tags:
+
+Using Cache Tags
+----------------
+
+.. versionadded:: 3.2
+
+ Tags-based invalidation was introduced in Symfony 3.2.
+
+To benefit from tags-based invalidation, you need to attach the proper tags to
+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::
+
+ $item = $cache->getItem('cache_key');
+ // ...
+ // add one or more tags
+ $item->tag('tag_1');
+ $item->tag(['tag_2', 'tag_3']);
+ $cache->save($item);
+
+If ``$cache`` implements :class:`Symfony\\Component\\Cache\\Adapter\\TagAwareAdapterInterface`,
+you can invalidate the cached items by calling
+:method:`Symfony\\Component\\Cache\\Adapter\\TagAwareAdapterInterface::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());
+
+Using tags invalidation is very useful when tracking cache keys becomes difficult.
+
+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
+:method:`Symfony\\Component\\Cache\\Adapter\\TagAwareAdapterInterface::invalidateTags`
+method.
+
+The :class:`Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter` class implements
+instantaneous invalidation (time complexity is ``O(N)`` where ``N`` is the number
+of invalidated tags). It needs one or two cache adapters: the first required
+one is used to store cached items; the second optional one is used to store tags
+and their invalidation version number (conceptually similar to their latest
+invalidation date). When only one adapter is used, items and tags are all stored
+in the same place. By using two adapters, you can e.g. store some big cached items
+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\FilesystemAdapter;
+ use Symfony\Component\Cache\Adapter\RedisAdapter;
+ use Symfony\Component\Cache\Adapter\TagAwareAdapter;
+
+ $cache = new TagAwareAdapter(
+ // Adapter for cached items
+ new FilesystemAdapter(),
+ // Adapter for tags
+ new RedisAdapter('redis://localhost')
+ );
+
+.. note::
+
+ Since Symfony 3.4, :class:`Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter`
+ implements :class:`Symfony\\Component\\Cache\\PruneableInterface`,
+ enabling manual
+ :ref:`pruning of expired cache entries ` by
+ calling its :method:`Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter::prune`
+ method (assuming the wrapped adapter itself implements
+ :class:`Symfony\\Component\\Cache\\PruneableInterface`).
+
+.. _cache-component-expiration:
+
+Using Cache Expiration
+----------------------
+
+If your data is valid only for a limited period of time, you can specify their
+lifetime or their expiration date with the PSR-6 interface, as explained in the
+:doc:`/components/cache/cache_items` article.
diff --git a/components/cache/cache_items.rst b/components/cache/cache_items.rst
new file mode 100644
index 00000000000..16eec288afe
--- /dev/null
+++ b/components/cache/cache_items.rst
@@ -0,0 +1,114 @@
+.. index::
+ single: Cache Item
+ single: Cache Expiration
+ single: Cache Exceptions
+
+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.
+
+Cache Item Keys and Values
+--------------------------
+
+The **key** of a cache item is a plain string which acts as its
+identifier, so it must be unique for each cache pool. You can freely choose the
+keys, but they should only contain letters (A-Z, a-z), numbers (0-9) and the
+``_`` and ``.`` symbols. Other common symbols (such as ``{``, ``}``, ``(``,
+``)``, ``/``, ``\``, ``@`` and ``:``) are reserved by the PSR-6 standard for future
+uses.
+
+The **value** of a cache item can be any data represented by a type which is
+serializable by PHP, such as basic types (string, integer, float, boolean, null),
+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::
+
+ // $cache pool object was created before
+ $productsCount = $cache->getItem('stats.products_count');
+
+Then, use the :method:`Psr\\Cache\\CacheItemInterface::set` method to set
+the data stored in the cache item::
+
+ // storing a simple integer
+ $productsCount->set(4711);
+ $cache->save($productsCount);
+
+ // storing an array
+ $productsCount->set([
+ 'category1' => 4711,
+ 'category2' => 2387,
+ ]);
+ $cache->save($productsCount);
+
+The key and the value of any given cache item can be obtained with the
+corresponding *getter* methods::
+
+ $cacheItem = $cache->getItem('exchange_rate');
+ // ...
+ $key = $cacheItem->getKey();
+ $value = $cacheItem->get();
+
+Cache Item Expiration
+~~~~~~~~~~~~~~~~~~~~~
+
+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.
+
+However, in some applications it's common to use cache items with a shorter
+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
+ $latestNews->expiresAfter(DateInterval::createFromDateString('1 hour'));
+
+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.
+
+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
+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:
+
+* **Cache Hits** occur when the requested item is found in the cache, its value
+ is not corrupted or invalid and it hasn't expired;
+* **Cache Misses** are the opposite of hits, so they occur when the item is not
+ found in the cache, its value is corrupted or invalid for any reason or the
+ item has expired.
+
+Cache item objects define a boolean ``isHit()`` method which returns ``true``
+for cache hits::
+
+ $latestNews = $cache->getItem('latest_news');
+
+ if (!$latestNews->isHit()) {
+ // do some heavy computation
+ $news = ...;
+ $cache->save($latestNews->set($news));
+ } else {
+ $news = $latestNews->get();
+ }
diff --git a/components/cache/cache_pools.rst b/components/cache/cache_pools.rst
new file mode 100644
index 00000000000..dd200eaf674
--- /dev/null
+++ b/components/cache/cache_pools.rst
@@ -0,0 +1,213 @@
+.. index::
+ single: Cache Pool
+ single: APC Cache, APCu Cache
+ single: Array Cache
+ single: Chain Cache
+ single: Doctrine Cache
+ single: Filesystem Cache
+ single: Memcached Cache
+ single: PDO Cache, Doctrine DBAL Cache
+ single: Redis Cache
+
+.. _component-cache-cache-pools:
+
+Cache Pools and Supported Adapters
+==================================
+
+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 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.
+
+.. _component-cache-creating-cache-pools:
+
+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.
+
+.. toctree::
+ :glob:
+ :maxdepth: 1
+
+ adapters/*
+
+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::
+
+ use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+
+ $cache = new FilesystemAdapter('app.cache');
+ $latestNews = $cache->getItem('latest_news');
+
+If no item is defined for the given key, the method doesn't return a ``null``
+value but an empty object which implements the :class:`Symfony\\Component\\Cache\\CacheItem`
+class.
+
+If you need to fetch several cache items simultaneously, use instead the
+``getItems([$key1, $key2, ...])`` method::
+
+ // ...
+ $stocks = $cache->getItems(['AAPL', 'FB', 'GOOGL', 'MSFT']);
+
+Again, if any of the keys doesn't represent a valid cache item, you won't get
+a ``null`` value but an empty ``CacheItem`` object.
+
+The last method related to fetching cache items is ``hasItem($key)``, which
+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
+item in the cache immediately (it returns ``true`` if the item was saved or
+``false`` if some error occurred)::
+
+ // ...
+ $userFriends = $cache->getItem('user_'.$userId.'_friends');
+ $userFriends->set($user->getFriends());
+ $isSaved = $cache->save($userFriends);
+
+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
+items as "ready to be persisted" and then call to
+``Psr\Cache\CacheItemPoolInterface::commit`` method when you are ready
+to persist them all::
+
+ // ...
+ $isQueued = $cache->saveDeferred($userFriends);
+ // ...
+ $isQueued = $cache->saveDeferred($userPreferences);
+ // ...
+ $isQueued = $cache->saveDeferred($userRecentProducts);
+ // ...
+ $isSaved = $cache->commit();
+
+The ``saveDeferred()`` method returns ``true`` when the cache item has been
+successfully added to the "persist queue" and ``false`` otherwise. The ``commit()``
+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``,
+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
+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)::
+
+ // ...
+ $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``
+when all items are successfully deleted)::
+
+ // ...
+ $cacheIsEmpty = $cache->clear();
+
+.. tip::
+
+ If the cache component is used inside a Symfony application, you can remove
+ *all items* from the *given pool(s)* using the following command (which resides within
+ the :ref:`framework bundle `):
+
+ .. code-block:: terminal
+
+ $ php bin/console cache:pool:clear
+
+ # clears the "cache.app" pool
+ $ php bin/console cache:pool:clear cache.app
+
+ # clears the "cache.validation" and "cache.app" pool
+ $ php bin/console cache:pool:clear cache.validation cache.app
+
+ .. versionadded:: 3.4
+
+ Starting from Symfony 3.4, the ``cache:clear`` command no longer clears
+ the cache pools, so you must use the ``cache:pool:clear`` command to
+ delete them.
+
+.. _component-cache-cache-pool-prune:
+
+Pruning Cache Items
+-------------------
+
+.. versionadded:: 3.4
+
+ Cache adapter pruning functionality was introduced in Symfony 3.4.
+
+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``.
+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.
+
+This shortcoming has been solved through the introduction of
+:class:`Symfony\\Component\\Cache\\PruneableInterface`, which defines the abstract method
+:method:`Symfony\\Component\\Cache\\PruneableInterface::prune`. The
+:ref:`ChainAdapter `,
+:ref:`FilesystemAdapter `,
+:ref:`PdoAdapter `, and
+:ref:`PhpFilesAdapter ` all implement this new interface,
+allowing manual removal of stale cache items::
+
+ use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+
+ $cache = new FilesystemAdapter('app.cache');
+ // ... do some set and get operations
+ $cache->prune();
+
+The :ref:`ChainAdapter ` implementation does not directly
+contain any pruning logic itself. Instead, when calling the chain adapter's
+:method:`Symfony\\Component\\Cache\\Adapter\\ChainAdapter::prune` method, the call is delegated to all
+its compatible cache adapters (and those that do not implement ``PruneableInterface`` are
+silently ignored)::
+
+ use Symfony\Component\Cache\Adapter\ApcuAdapter;
+ use Symfony\Component\Cache\Adapter\ChainAdapter;
+ use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+ use Symfony\Component\Cache\Adapter\PdoAdapter;
+ use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
+
+ $cache = new ChainAdapter([
+ new ApcuAdapter(), // does NOT implement PruneableInterface
+ new FilesystemAdapter(), // DOES implement PruneableInterface
+ new PdoAdapter(), // DOES implement PruneableInterface
+ new PhpFilesAdapter(), // DOES implement PruneableInterface
+ // ...
+ ]);
+
+ // prune will proxy the call to PdoAdapter, FilesystemAdapter and PhpFilesAdapter,
+ // while silently skipping ApcuAdapter
+ $cache->prune();
+
+.. tip::
+
+ If the cache component is used inside a Symfony application, you can prune
+ *all items* from *all pools* using the following command (which resides within
+ the :ref:`framework bundle `):
+
+ .. code-block:: terminal
+
+ $ php bin/console cache:pool:prune
diff --git a/components/cache/psr6_psr16_adapters.rst b/components/cache/psr6_psr16_adapters.rst
new file mode 100644
index 00000000000..55014b9faba
--- /dev/null
+++ b/components/cache/psr6_psr16_adapters.rst
@@ -0,0 +1,86 @@
+.. index::
+ single: Cache
+ single: Performance
+ single: Components; Cache
+
+Adapters For Interoperability between PSR-6 and PSR-16 Cache
+============================================================
+
+Sometimes, you may have a Cache object that implements the :ref:`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.
+
+Using a PSR-16 Cache Object as a PSR-6 Cache
+--------------------------------------------
+
+Suppose you want to work with a class that requires a PSR-6 Cache pool object. For
+example::
+
+ use Psr\Cache\CacheItemPoolInterface;
+
+ // just a made-up class for the example
+ class GitHubApiClient
+ {
+ // ...
+
+ // this requires a PSR-6 cache object
+ public function __construct(CacheItemPoolInterface $cachePool)
+ {
+ // ...
+ }
+ }
+
+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
+this use-case::
+
+ use Symfony\Component\Cache\Adapter\SimpleCacheAdapter;
+ use Symfony\Component\Cache\Simple\FilesystemCache;
+
+ // the PSR-16 cache object that you want to use
+ $psr16Cache = new FilesystemCache();
+
+ // a PSR-6 cache that uses your cache internally!
+ $psr6Cache = new SimpleCacheAdapter($psr16Cache);
+
+ // now use this wherever you want
+ $githubApiClient = new GitHubApiClient($psr6Cache);
+
+Using a PSR-6 Cache Object as a PSR-16 Cache
+--------------------------------------------
+
+Suppose you want to work with a class that requires a PSR-16 Cache object. For
+example::
+
+ use Psr\SimpleCache\CacheInterface;
+
+ // just a made-up class for the example
+ class GitHubApiClient
+ {
+ // ...
+
+ // this requires a PSR-16 cache object
+ public function __construct(CacheInterface $cache)
+ {
+ // ...
+ }
+ }
+
+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
+this use-case::
+
+ use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+ use Symfony\Component\Cache\Simple\Psr6Cache;
+
+ // 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);
+
+ // now use this wherever you want
+ $githubApiClient = new GitHubApiClient($psr16Cache);
diff --git a/components/class_loader.rst b/components/class_loader.rst
index 3a7021e8738..ca27db3663b 100644
--- a/components/class_loader.rst
+++ b/components/class_loader.rst
@@ -9,8 +9,9 @@ The ClassLoader Component
.. caution::
- The ClassLoader component was deprecated in Symfony 3.3 and it will be
- removed in 4.0. As an alternative, use Composer's class loading mechanism.
+ The ClassLoader component was deprecated in Symfony 3.3 and will be
+ removed in 4.0. As an alternative, use any of the `class loading optimizations`_
+ provided by Composer.
Usage
-----
@@ -33,17 +34,17 @@ Additionally, the Symfony ClassLoader component ships with a wrapper class
which makes it possible
:doc:`to cache the results of a class loader `.
-When using the :doc:`Debug component `, you
-can also use a special :ref:`DebugClassLoader `
-that eases debugging by throwing more helpful exceptions when a class could
-not be found by a class loader.
+When using the `Debug component`_, you can also use a special
+:class:`Symfony\\Component\\Debug\\DebugClassLoader` that eases debugging by
+throwing more helpful exceptions when a class could not be found by a class
+loader.
Installation
------------
.. code-block:: terminal
- $ composer require symfony/class-loader
+ $ composer require symfony/class-loader:^3.4
Alternatively, you can clone the ``_ repository.
@@ -57,9 +58,18 @@ Learn More
:maxdepth: 1
class_loader/class_loader
- class_loader/*
+ class_loader/class_map_generator
+ class_loader/debug_class_loader
+ class_loader/map_class_loader
+ class_loader/psr4_class_loader
+.. toctree::
+ :hidden:
+
+ class_loader/cache_class_loader
+
+.. _Debug component: https://github.com/symfony/debug
.. _PSR-0: https://www.php-fig.org/psr/psr-0/
.. _PSR-4: https://www.php-fig.org/psr/psr-4/
.. _`autoloading mechanism`: https://php.net/manual/en/language.oop5.autoload.php
-.. _Packagist: https://packagist.org/packages/symfony/class-loader
+.. _`class loading optimizations`: https://getcomposer.org/doc/articles/autoloader-optimization.md
diff --git a/components/class_loader/cache_class_loader.rst b/components/class_loader/cache_class_loader.rst
index cb0d1dd37d1..a75f743826c 100644
--- a/components/class_loader/cache_class_loader.rst
+++ b/components/class_loader/cache_class_loader.rst
@@ -8,58 +8,8 @@
Cache a Class Loader
====================
-Finding the file for a particular class can be an expensive task. Luckily,
-the ClassLoader component comes with two classes to cache the mapping
-from a class to its containing file. Both the :class:`Symfony\\Component\\ClassLoader\\ApcClassLoader`
-and the :class:`Symfony\\Component\\ClassLoader\\XcacheClassLoader` wrap
-around an object which implements a ``findFile()`` method to find the file
-for a class.
+The ``ApcClassLoader``, the ``WinCacheClassLoader`` and the ``XcacheClassLoader``
+are deprecated since Symfony 3.3. As an alternative, use any of the
+`class loading optimizations`_ provided by Composer.
-.. note::
-
- Both the ``ApcClassLoader`` and the ``XcacheClassLoader`` can be used
- to cache Composer's `autoloader`_.
-
-ApcClassLoader
---------------
-
-``ApcClassLoader`` wraps an existing class loader and caches calls to its
-``findFile()`` method using `APC`_::
-
- require_once '/path/to/src/Symfony/Component/ClassLoader/ApcClassLoader.php';
-
- // instance of a class that implements a findFile() method, like the ClassLoader
- $loader = ...;
-
- // sha1(__FILE__) generates an APC namespace prefix
- $cachedLoader = new ApcClassLoader(sha1(__FILE__), $loader);
-
- // registers the cached class loader
- $cachedLoader->register();
-
- // deactivates the original, non-cached loader if it was registered previously
- $loader->unregister();
-
-XcacheClassLoader
------------------
-
-``XcacheClassLoader`` uses `XCache`_ to cache a class loader. Registering
-it is straightforward::
-
- require_once '/path/to/src/Symfony/Component/ClassLoader/XcacheClassLoader.php';
-
- // instance of a class that implements a findFile() method, like the ClassLoader
- $loader = ...;
-
- // sha1(__FILE__) generates an XCache namespace prefix
- $cachedLoader = new XcacheClassLoader(sha1(__FILE__), $loader);
-
- // registers the cached class loader
- $cachedLoader->register();
-
- // deactivates the original, non-cached loader if it was registered previously
- $loader->unregister();
-
-.. _APC: https://php.net/manual/en/book.apc.php
-.. _autoloader: https://getcomposer.org/doc/01-basic-usage.md#autoloading
-.. _XCache: https://xcache.lighttpd.net
+.. _`class loading optimizations`: https://getcomposer.org/doc/articles/autoloader-optimization.md
diff --git a/components/class_loader/class_loader.rst b/components/class_loader/class_loader.rst
index c6d37661a84..3f5f6f5ac42 100644
--- a/components/class_loader/class_loader.rst
+++ b/components/class_loader/class_loader.rst
@@ -26,7 +26,7 @@ is straightforward::
$loader = new ClassLoader();
- // to enable searching the include path (eg. for PEAR packages)
+ // to enable searching the include path (e.g. for PEAR packages)
$loader->setUseIncludePath(true);
// ... register namespaces and prefixes here - see below
@@ -41,29 +41,29 @@ your classes::
$loader->addPrefix('Symfony', __DIR__.'/vendor/symfony/symfony/src');
// registers several namespaces at once
- $loader->addPrefixes(array(
+ $loader->addPrefixes([
'Symfony' => __DIR__.'/../vendor/symfony/symfony/src',
'Monolog' => __DIR__.'/../vendor/monolog/monolog/src',
- ));
+ ]);
// registers a prefix for a class following the PEAR naming conventions
$loader->addPrefix('Twig_', __DIR__.'/vendor/twig/twig/lib');
- $loader->addPrefixes(array(
+ $loader->addPrefixes([
'Swift_' => __DIR__.'/vendor/swiftmailer/swiftmailer/lib/classes',
'Twig_' => __DIR__.'/vendor/twig/twig/lib',
- ));
+ ]);
Classes from a sub-namespace or a sub-hierarchy of `PEAR`_ classes can be
-looked for in a location list to ease the vendoring of a sub-set of classes
-for large projects::
+looked for in a location list to ease the splitting a sub-set of classes into
+another package for large projects::
- $loader->addPrefixes(array(
+ $loader->addPrefixes([
'Doctrine\Common' => __DIR__.'/vendor/doctrine/common/lib',
'Doctrine\DBAL\Migrations' => __DIR__.'/vendor/doctrine/migrations/lib',
'Doctrine\DBAL' => __DIR__.'/vendor/doctrine/dbal/lib',
'Doctrine' => __DIR__.'/vendor/doctrine/orm/lib',
- ));
+ ]);
In this example, if you try to use a class in the ``Doctrine\Common`` namespace
or one of its children, the autoloader will first look for the class under
diff --git a/components/class_loader/class_map_generator.rst b/components/class_loader/class_map_generator.rst
index c060cc71a72..f7ceab4ebcd 100644
--- a/components/class_loader/class_map_generator.rst
+++ b/components/class_loader/class_map_generator.rst
@@ -19,12 +19,12 @@ manually. For example, imagine a library with the following directory structure:
library/
├── bar/
- │ ├── baz/
- │ │ └── Boo.php
- │ └── Foo.php
+ │ ├── baz/
+ │ │ └── Boo.php
+ │ └── Foo.php
└── foo/
├── bar/
- │ └── Foo.php
+ │ └── Foo.php
└── Bar.php
These files contain the following classes:
@@ -110,7 +110,7 @@ generated class map with, for example, the
The example assumes that you already have autoloading working (e.g.
through `Composer`_ or one of the other class loaders from the ClassLoader
- component.
+ component).
Besides dumping the class map for one directory, you can also pass an array
of directories for which to generate the class map (the result actually
@@ -119,7 +119,7 @@ is the same as in the example above)::
use Symfony\Component\ClassLoader\ClassMapGenerator;
ClassMapGenerator::dump(
- array(__DIR__.'/library/bar', __DIR__.'/library/foo'),
+ [__DIR__.'/library/bar', __DIR__.'/library/foo'],
__DIR__.'/class_map.php'
);
diff --git a/components/class_loader/debug_class_loader.rst b/components/class_loader/debug_class_loader.rst
index e316eeb8084..f60a726b3a4 100644
--- a/components/class_loader/debug_class_loader.rst
+++ b/components/class_loader/debug_class_loader.rst
@@ -3,6 +3,7 @@ Debugging a Class Loader
.. caution::
- The ``DebugClassLoader`` from the ClassLoader component was deprecated
- in Symfony 2.5 and will be removed in Symfony 3.0. Use the
- :ref:`DebugClassLoader provided by the Debug component `.
+ The ``DebugClassLoader`` from the ClassLoader component was deprecated in
+ Symfony 2.5 and removed in Symfony 3.0. Use the
+ :class:`Symfony\\Component\\Debug\\DebugClassLoader` provided by the Debug
+ component.
diff --git a/components/class_loader/map_class_loader.rst b/components/class_loader/map_class_loader.rst
index e6e464e771e..5f2e8cfab7a 100644
--- a/components/class_loader/map_class_loader.rst
+++ b/components/class_loader/map_class_loader.rst
@@ -29,10 +29,10 @@ an instance of the ``MapClassLoader`` class::
require_once '/path/to/src/Symfony/Component/ClassLoader/MapClassLoader.php';
- $mapping = array(
+ $mapping = [
'Foo' => '/path/to/Foo',
'Bar' => '/path/to/Bar',
- );
+ ];
$loader = new MapClassLoader($mapping);
diff --git a/components/class_loader/psr4_class_loader.rst b/components/class_loader/psr4_class_loader.rst
index 09d0af057e9..0093d8ba0c8 100644
--- a/components/class_loader/psr4_class_loader.rst
+++ b/components/class_loader/psr4_class_loader.rst
@@ -22,12 +22,12 @@ Usage
The following example demonstrates how you can use the
:class:`Symfony\\Component\\ClassLoader\\Psr4ClassLoader` autoloader to use
Symfony's Yaml component. Imagine, you downloaded both the ClassLoader and
-Yaml component as ZIP packages and unpacked them to a ``libs`` directory.
+Yaml component as ZIP packages and unpacked them to a ``lib/`` directory.
The directory structure will look like this:
.. code-block:: text
- libs/
+ lib/
ClassLoader/
Psr4ClassLoader.php
...
diff --git a/components/config.rst b/components/config.rst
index 8134007103d..ce58f64fc7e 100644
--- a/components/config.rst
+++ b/components/config.rst
@@ -6,7 +6,7 @@ The Config Component
====================
The Config component provides several classes to help you find, load,
- combine, autofill and validate configuration values of any kind, whatever
+ combine, fill and validate configuration values of any kind, whatever
their source may be (YAML, XML, INI files, or for instance a database).
Installation
@@ -14,9 +14,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/config
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require symfony/config:^3.4
.. include:: /components/require_autoload.rst.inc
@@ -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/caching.rst b/components/config/caching.rst
index 4984a59d755..9ef987a3165 100644
--- a/components/config/caching.rst
+++ b/components/config/caching.rst
@@ -35,11 +35,11 @@ should be regenerated::
// fill this with an array of 'users.yml' file paths
$yamlUserFiles = ...;
- $resources = array();
+ $resources = [];
foreach ($yamlUserFiles as $yamlUserFile) {
- // see the previous article "Loading resources" to
- // see where $delegatingLoader comes from
+ // see the article "Loading resources" to
+ // know where $delegatingLoader comes from
$delegatingLoader->load($yamlUserFile);
$resources[] = new FileResource($yamlUserFile);
}
diff --git a/components/config/definition.rst b/components/config/definition.rst
index 0c95e62642e..b6be37a9695 100644
--- a/components/config/definition.rst
+++ b/components/config/definition.rst
@@ -52,8 +52,8 @@ 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
{
@@ -143,7 +143,7 @@ values::
$rootNode
->children()
->enumNode('delivery')
- ->values(array('standard', 'expedited', 'priority'))
+ ->values(['standard', 'expedited', 'priority'])
->end()
->end()
;
@@ -175,7 +175,7 @@ Or you may define a prototype for each node inside an array node::
$rootNode
->children()
->arrayNode('connections')
- ->prototype('array')
+ ->arrayPrototype()
->children()
->scalarNode('driver')->end()
->scalarNode('host')->end()
@@ -187,11 +187,31 @@ Or you may define a prototype for each node inside an array node::
->end()
;
+.. versionadded:: 3.3
+
+ The ``arrayPrototype()`` method (and the related ``booleanPrototype()``
+ ``integerPrototype()``, ``floatPrototype()``, ``scalarPrototype()`` and
+ ``enumPrototype()``) was introduced in Symfony 3.3. In previous versions,
+ you needed to use ``prototype('array')``, ``prototype('boolean')``, etc.
+
A prototype can be used to add a definition which may be repeated many times
inside the current node. According to the prototype definition in the example
above, it is possible to have multiple connection arrays (containing a ``driver``,
``host``, etc.).
+.. versionadded:: 3.3
+
+ The ``castToArray()`` helper was introduced in Symfony 3.3.
+
+Sometimes, to improve the user experience of your application or bundle, you may
+allow to use a simple string or numeric value where an array value is required.
+Use the ``castToArray()`` helper to turn those variables into arrays::
+
+ ->arrayNode('hosts')
+ ->beforeNormalization()->castToArray()->end()
+ // ...
+ ->end()
+
Array Node Options
~~~~~~~~~~~~~~~~~~
@@ -221,7 +241,7 @@ A basic prototyped array configuration can be defined as follows::
->fixXmlConfig('driver')
->children()
->arrayNode('drivers')
- ->prototype('scalar')->end()
+ ->scalarPrototype()->end()
->end()
->end()
;
@@ -252,7 +272,7 @@ A more complex example would be to define a prototyped array with children::
->fixXmlConfig('connection')
->children()
->arrayNode('connections')
- ->prototype('array')
+ ->arrayPrototype()
->children()
->scalarNode('table')->end()
->scalarNode('user')->end()
@@ -275,8 +295,8 @@ Or the following XML configuration:
.. code-block:: xml
-
-
+
+
The processed configuration is::
@@ -326,7 +346,7 @@ In order to maintain the array keys use the ``useAttributeAsKey()`` method::
->children()
->arrayNode('connections')
->useAttributeAsKey('name')
- ->prototype('array')
+ ->arrayPrototype()
->children()
->scalarNode('table')->end()
->scalarNode('user')->end()
@@ -337,6 +357,13 @@ In order to maintain the array keys use the ``useAttributeAsKey()`` method::
->end()
;
+.. note::
+
+ In YAML, the ``'name'`` argument of ``useAttributeAsKey()`` has a special
+ meaning and refers to the key of the map (``sf_connection`` and ``default``
+ in this example). If a child node was defined for the ``connections`` node
+ with the key ``name``, then that key of the map would be lost.
+
The argument of this method (``name`` in the example above) defines the name of
the attribute added to each XML node to differentiate them. Now you can use the
same YAML configuration shown before or the following XML configuration:
@@ -344,9 +371,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::
@@ -383,7 +410,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()
@@ -416,6 +443,29 @@ has a certain value:
->end()
;
+Deprecating the Option
+----------------------
+
+You can deprecate options using the
+:method:`Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition::setDeprecated`
+method::
+
+ $rootNode
+ ->children()
+ ->integerNode('old_option')
+ // this outputs the following generic deprecation message:
+ // The child node "old_option" at path "..." is deprecated.
+ ->setDeprecated()
+
+ // you can also pass a custom deprecation message (%node% and %path% placeholders are available):
+ ->setDeprecated('The "%node%" option is deprecated. Use "new_config_option" instead.')
+ ->end()
+ ->end()
+ ;
+
+If you use the Web Debug Toolbar, these deprecation notices are shown when the
+configuration is rebuilt.
+
Documenting the Option
----------------------
@@ -440,18 +490,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
-
-
-.. versionadded:: 2.6
- Since Symfony 2.6, the info will also be added to the exception message
- when an invalid type is given.
+
Optional Sections
-----------------
@@ -470,9 +516,9 @@ methods::
// is equivalent to
$arrayNode
- ->treatFalseLike(array('enabled' => false))
- ->treatTrueLike(array('enabled' => true))
- ->treatNullLike(array('enabled' => true))
+ ->treatFalseLike(['enabled' => false])
+ ->treatTrueLike(['enabled' => true])
+ ->treatNullLike(['enabled' => true])
->children()
->booleanNode('enabled')
->defaultFalse()
@@ -499,7 +545,7 @@ 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()``::
@@ -543,7 +589,7 @@ tree with ``append()``::
->isRequired()
->requiresAtLeastOneElement()
->useAttributeAsKey('name')
- ->prototype('array')
+ ->arrayPrototype()
->children()
->scalarNode('value')->isRequired()->end()
->end()
@@ -641,7 +687,7 @@ with ``fixXmlConfig()``::
->fixXmlConfig('extension')
->children()
->arrayNode('extensions')
- ->prototype('scalar')->end()
+ ->scalarPrototype()->end()
->end()
->end()
;
@@ -672,8 +718,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,
@@ -703,10 +749,10 @@ By changing a string value into an associative array with ``name`` as the key::
->arrayNode('connection')
->beforeNormalization()
->ifString()
- ->then(function ($v) { return array('name' => $v); })
+ ->then(function ($v) { return ['name' => $v]; })
->end()
->children()
- ->scalarNode('name')->isRequired()
+ ->scalarNode('name')->isRequired()->end()
// ...
->end()
->end()
@@ -728,7 +774,7 @@ The builder is used for adding advanced validation rules to node definitions, li
->scalarNode('driver')
->isRequired()
->validate()
- ->ifNotInArray(array('mysql', 'sqlite', 'mssql'))
+ ->ifNotInArray(['mysql', 'sqlite', 'mssql'])
->thenInvalid('Invalid database driver %s')
->end()
->end()
@@ -743,6 +789,7 @@ the following ways:
- ``ifTrue()``
- ``ifString()``
- ``ifNull()``
+- ``ifEmpty()`` (since Symfony 3.2)
- ``ifArray()``
- ``ifInArray()``
- ``ifNotInArray()``
@@ -756,8 +803,7 @@ A validation rule also requires a "then" part:
- ``thenUnset()``
Usually, "then" is a closure. Its return value will be used as a new value
-for the node, instead
-of the node's original value.
+for the node, instead of the node's original value.
Processing Configuration Values
-------------------------------
@@ -770,9 +816,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.yml')
@@ -781,7 +827,7 @@ Otherwise the result is a clean array of configuration values::
file_get_contents(__DIR__.'/src/Matthias/config/config_extra.yml')
);
- $configs = array($config, $extraConfig);
+ $configs = [$config, $extraConfig];
$processor = new Processor();
$databaseConfiguration = new DatabaseConfiguration();
diff --git a/components/config/resources.rst b/components/config/resources.rst
index 1f56aa69ada..bee2bd7b438 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
------------------
@@ -19,7 +21,7 @@ files. This can be done with the :class:`Symfony\\Component\\Config\\FileLocator
use Symfony\Component\Config\FileLocator;
- $configDirectories = array(__DIR__.'/app/config');
+ $configDirectories = [__DIR__.'/app/config'];
$fileLocator = new FileLocator($configDirectories);
$yamlUserFiles = $fileLocator->locate('users.yml', null, false);
@@ -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,14 +85,13 @@ 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(array(new YamlUserLoader($fileLocator)));
+ $loaderResolver = new LoaderResolver([new YamlUserLoader($fileLocator)]);
$delegatingLoader = new DelegatingLoader($loaderResolver);
+ // YamlUserLoader is used to load this resource because it supports
+ // files with the '.yml' extension
$delegatingLoader->load(__DIR__.'/users.yml');
- /*
- The YamlUserLoader will be used to load this resource,
- since it supports files with a "yml" extension
- */
diff --git a/components/console.rst b/components/console.rst
index 3c516e3e3ce..fb377baa1a0 100644
--- a/components/console.rst
+++ b/components/console.rst
@@ -17,9 +17,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/console
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require symfony/console:^3.4
.. include:: /components/require_autoload.rst.inc
@@ -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 29534ba7d23..b5baf00c022 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 0757cf051fb..79f5c6c1f4c 100644
--- a/components/console/console_arguments.rst
+++ b/components/console/console_arguments.rst
@@ -23,23 +23,24 @@ 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(array(
+ new InputDefinition([
new InputOption('foo', 'f'),
new InputOption('bar', 'b', InputOption::VALUE_REQUIRED),
new InputOption('cat', 'c', InputOption::VALUE_OPTIONAL),
- ))
+ ])
);
}
protected function execute(InputInterface $input, OutputInterface $output)
{
- // ...
+ // ...
}
}
@@ -69,10 +70,10 @@ argument::
// ...
- new InputDefinition(array(
+ new InputDefinition([
// ...
new InputArgument('arg', InputArgument::OPTIONAL),
- ));
+ ]);
You might have to use the special ``--`` separator to separate options from
arguments. Have a look at the fifth example in the following table where it
diff --git a/components/console/events.rst b/components/console/events.rst
index eeee126e709..ae8aa494c29 100644
--- a/components/console/events.rst
+++ b/components/console/events.rst
@@ -4,9 +4,6 @@
Using Events
============
-.. versionadded:: 2.3
- Console events were introduced in Symfony 2.3.
-
The Application class of the Console component allows you to optionally hook
into the lifecycle of a console application via events. Instead of reinventing
the wheel, it uses the Symfony EventDispatcher component to do the work::
@@ -36,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
@@ -65,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
@@ -89,6 +86,11 @@ C/C++ standard.::
The ``ConsoleEvents::EXCEPTION`` Event
--------------------------------------
+.. deprecated:: 3.3
+
+ The ``ConsoleEvents::EXCEPTION`` event was deprecated in Symfony 3.3. Use
+ the ``ConsoleEvents::ERROR`` event instead.
+
**Typical Purposes**: Handle exceptions thrown during the execution of a
command.
@@ -96,26 +98,39 @@ Whenever an exception is thrown by a command, the ``ConsoleEvents::EXCEPTION``
event is dispatched. A listener can wrap or change the exception or do
anything useful before the exception is thrown by the application.
+The ``ConsoleEvents::ERROR`` Event
+----------------------------------
+
+**Typical Purposes**: Handle exceptions thrown during the execution of a
+command.
+
+Whenever an exception is thrown by a command, including those triggered from
+event listeners, the ``ConsoleEvents::ERROR`` event is dispatched. A listener
+can wrap or change the exception or do anything useful before the exception is
+thrown by the application.
+
Listeners receive a
-:class:`Symfony\\Component\\Console\\Event\\ConsoleExceptionEvent` event::
+:class:`Symfony\\Component\\Console\\Event\\ConsoleErrorEvent` event::
- use Symfony\Component\Console\Event\ConsoleExceptionEvent;
use Symfony\Component\Console\ConsoleEvents;
+ use Symfony\Component\Console\Event\ConsoleErrorEvent;
- $dispatcher->addListener(ConsoleEvents::EXCEPTION, function (ConsoleExceptionEvent $event) {
+ $dispatcher->addListener(ConsoleEvents::ERROR, function (ConsoleErrorEvent $event) {
$output = $event->getOutput();
$command = $event->getCommand();
$output->writeln(sprintf('Oops, exception thrown while running command %s', $command->getName()));
- // gets the current exit code (the exception code or the exit code set by a ConsoleEvents::TERMINATE event)
+ // gets the current exit code (the exception code)
$exitCode = $event->getExitCode();
// changes the exception to another one
- $event->setException(new \LogicException('Caught exception', $exitCode, $event->getException()));
+ $event->setError(new \LogicException('Caught exception', $exitCode, $event->getError()));
});
+.. _console-events-terminate:
+
The ``ConsoleEvents::TERMINATE`` Event
--------------------------------------
@@ -131,8 +146,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
@@ -151,7 +166,7 @@ Listeners receive a
.. tip::
This event is also dispatched when an exception is thrown by the command.
- It is then dispatched just after the ``ConsoleEvents::EXCEPTION`` event.
+ 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/dialoghelper.rst b/components/console/helpers/dialoghelper.rst
index cccf89d42cd..8618ffcba17 100644
--- a/components/console/helpers/dialoghelper.rst
+++ b/components/console/helpers/dialoghelper.rst
@@ -6,290 +6,7 @@ Dialog Helper
.. caution::
- The Dialog Helper was deprecated in Symfony 2.5 and will be removed in
+ The Dialog Helper was deprecated in Symfony 2.5 and removed in
Symfony 3.0. You should now use the
:doc:`Question Helper ` instead,
which is simpler to use.
-
-The :class:`Symfony\\Component\\Console\\Helper\\DialogHelper` provides
-functions to ask the user for more information. It is included in the default
-helper set, which you can get by calling
-:method:`Symfony\\Component\\Console\\Command\\Command::getHelperSet`::
-
- $dialog = $this->getHelper('dialog');
-
-All the methods inside the Dialog Helper have an
-:class:`Symfony\\Component\\Console\\Output\\OutputInterface` as the first
-argument, the question as the second argument and the default value as the last
-argument.
-
-Asking the User for Confirmation
---------------------------------
-
-Suppose you want to confirm an action before actually executing it. Add
-the following to your command::
-
- // ...
- if (!$dialog->askConfirmation(
- $output,
- 'Continue with this action?',
- false
- )) {
- return;
- }
-
-In this case, the user will be asked "Continue with this action?", and will
-return ``true`` if the user answers with ``y`` or ``false`` if the user answers
-with ``n``. The third argument to
-:method:`Symfony\\Component\\Console\\Helper\\DialogHelper::askConfirmation`
-is the default value to return if the user doesn't enter any input. Any other
-input will ask the same question again.
-
-Asking the User for Information
--------------------------------
-
-You can also ask question with more than a simple yes/no answer. For instance,
-if you want to know a bundle name, you can add this to your command::
-
- // ...
- $bundleName = $dialog->ask(
- $output,
- 'Please enter the name of the bundle',
- 'AcmeDemoBundle'
- );
-
-The user will be asked "Please enter the name of the bundle". They can type
-some name which will be returned by the
-:method:`Symfony\\Component\\Console\\Helper\\DialogHelper::ask` method.
-If they leave it empty, the default value (AcmeDemoBundle here) is returned.
-
-Autocompletion
-~~~~~~~~~~~~~~
-
-You can also specify an array of potential answers for a given question. These
-will be autocompleted as the user types::
-
- $dialog = $this->getHelper('dialog');
- $bundleNames = array('AcmeDemoBundle', 'AcmeBlogBundle', 'AcmeStoreBundle');
- $bundleName = $dialog->ask(
- $output,
- 'Please enter the name of a bundle',
- 'FooBundle',
- $bundleNames
- );
-
-Hiding the User's Response
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You can also ask a question and hide the response. This is particularly
-convenient for passwords::
-
- $dialog = $this->getHelper('dialog');
- $password = $dialog->askHiddenResponse(
- $output,
- 'What is the database password?',
- false
- );
-
-.. caution::
-
- When you ask for a hidden response, Symfony will use either a binary, change
- stty mode or use another trick to hide the response. If none is available,
- it will fallback and allow the response to be visible unless you pass ``false``
- as the third argument like in the example above. In this case, a ``RuntimeException``
- would be thrown.
-
-Validating the Answer
----------------------
-
-You can even validate the answer. For instance, in the last example you asked
-for the bundle name. Following the Symfony naming conventions, it should
-be suffixed with ``Bundle``. You can validate that by using the
-:method:`Symfony\\Component\\Console\\Helper\\DialogHelper::askAndValidate`
-method::
-
- // ...
- $bundleName = $dialog->askAndValidate(
- $output,
- 'Please enter the name of the bundle',
- function ($answer) {
- if ('Bundle' !== substr($answer, -6)) {
- throw new \RuntimeException(
- 'The name of the bundle should be suffixed with \'Bundle\''
- );
- }
-
- return $answer;
- },
- false,
- 'AcmeDemoBundle'
- );
-
-This method has 2 new arguments, the full signature is::
-
- askAndValidate(
- OutputInterface $output,
- string|array $question,
- callback $validator,
- integer $attempts = false,
- string $default = null,
- array $autocomplete = null
- )
-
-The ``$validator`` is a callback which handles the validation. It should
-throw an exception if there is something wrong. The exception message is displayed
-in the console, so it is a good practice to put some useful information in it. The callback
-function should also return the value of the user's input if the validation was successful.
-
-You can set the max number of times to ask in the ``$attempts`` argument.
-Using ``false`` means the amount of attempts is infinite.
-The user will be asked as long as they provide an invalid answer and will only
-be able to proceed if their input is valid.
-
-Each time the user is asked the question, the default one is used if no answer
-is supplied (and validated with the ``$validator`` callback). If the last
-attempt is reached, the application will throw an exception and ends its
-execution.
-
-Validating a Hidden Response
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You can also ask and validate a hidden response::
-
- $dialog = $this->getHelper('dialog');
-
- $validator = function ($value) {
- if ('' === trim($value)) {
- throw new \Exception('The password cannot be empty');
- }
-
- return $value;
- };
-
- $password = $dialog->askHiddenResponseAndValidate(
- $output,
- 'Please enter your password',
- $validator,
- 20,
- false
- );
-
-If you want to allow the response to be visible if it cannot be hidden for
-some reason, pass true as the fifth argument.
-
-Let the User Choose from a List of Answers
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-If you have a predefined set of answers the user can choose from, you
-could use the ``ask()`` method described above or, to make sure the user
-provided a correct answer, the ``askAndValidate()`` method. Both have
-the disadvantage that you need to handle incorrect values yourself.
-
-Instead, you can use the
-:method:`Symfony\\Component\\Console\\Helper\\DialogHelper::select`
-method, which makes sure that the user can only enter a valid string
-from a predefined list::
-
- $dialog = $this->getHelper('dialog');
- $colors = array('red', 'blue', 'yellow');
-
- $color = $dialog->select(
- $output,
- 'Please select your favorite color (default to red)',
- $colors,
- 0
- );
- $output->writeln('You have just selected: ' . $colors[$color]);
-
- // ... do something with the color
-
-The option which should be selected by default is provided with the fourth
-argument. The default is ``null``, which means that no option is the default one.
-
-If the user enters an invalid string, an error message is shown and the user
-is asked to provide the answer another time, until they enter a valid string
-or the maximum attempts is reached (which you can define in the fifth
-argument). The default value for the attempts is ``false``, which means infinite
-attempts. You can define your own error message in the sixth argument.
-
-.. versionadded:: 2.3
- Multiselect support was introduced in Symfony 2.3.
-
-Multiple Choices
-................
-
-Sometimes, multiple answers can be given. The DialogHelper provides this
-feature using comma separated values. This is disabled by default, to enable
-this set the seventh argument to ``true``::
-
- // ...
-
- $selected = $dialog->select(
- $output,
- 'Please select your favorite color (default to red)',
- $colors,
- 0,
- false,
- 'Value "%s" is invalid',
- true // enable multiselect
- );
-
- $selectedColors = array_map(function ($c) use ($colors) {
- return $colors[$c];
- }, $selected);
-
- $output->writeln(
- 'You have just selected: ' . implode(', ', $selectedColors)
- );
-
-Now, when the user enters ``1,2``, the result will be:
-``You have just selected: blue, yellow``.
-
-Testing a Command which 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 overwrite the HelperSet used by the command::
-
- use Symfony\Component\Console\Application;
- use Symfony\Component\Console\Helper\DialogHelper;
- use Symfony\Component\Console\Helper\HelperSet;
- use Symfony\Component\Console\Tester\CommandTester;
-
- // ...
- public function testExecute()
- {
- // ...
- $application = new Application();
- $application->add(new MyCommand());
- $command = $application->find('my:command:name');
- $commandTester = new CommandTester($command);
-
- $dialog = $command->getHelper('dialog');
- $dialog->setInputStream($this->getInputStream("Test\n"));
- // Equals to a user inputting "Test" and hitting ENTER
- // If you need to enter a confirmation, "yes\n" will work
-
- $commandTester->execute(array('command' => $command->getName()));
-
- // $this->assertRegExp('/.../', $commandTester->getDisplay());
- }
-
- protected function getInputStream($input)
- {
- $stream = fopen('php://memory', 'r+', false);
- fputs($stream, $input);
- rewind($stream);
-
- return $stream;
- }
-
-By setting the input stream of the ``DialogHelper``, you imitate what the
-console would do internally with all user input through the cli. This way
-you can test any user interaction (even complex ones) by passing an appropriate
-input stream.
-
-.. seealso::
-
- You find more information about testing commands in the console component
- docs about :ref:`testing console commands `.
diff --git a/components/console/helpers/formatterhelper.rst b/components/console/helpers/formatterhelper.rst
index 18982bb0fb1..25be9423202 100644
--- a/components/console/helpers/formatterhelper.rst
+++ b/components/console/helpers/formatterhelper.rst
@@ -50,7 +50,7 @@ notice that the background is only as long as each individual line. Use the
:method:`Symfony\\Component\\Console\\Helper\\FormatterHelper::formatBlock`
to generate a block output::
- $errorMessages = array('Error!', 'Something went wrong');
+ $errorMessages = ['Error!', 'Something went wrong'];
$formattedBlock = $formatter->formatBlock($errorMessages, 'error');
$output->writeln($formattedBlock);
@@ -63,3 +63,52 @@ messages and 2 spaces on the left and right).
The exact "style" you use in the block is up to you. In this case, you're using
the pre-defined ``error`` style, but there are other styles, or you can create
your own. See :doc:`/console/coloring`.
+
+Print Truncated Messages
+------------------------
+
+Sometimes you want to print a message truncated to an explicit character length.
+This is possible with the
+:method:`Symfony\\Component\\Console\\Helper\\FormatterHelper::truncate` method.
+
+If you would like to truncate a very long message, for example, to 7 characters,
+you can write::
+
+ $message = "This is a very long message, which should be truncated";
+ $truncatedMessage = $formatter->truncate($message, 7);
+ $output->writeln($truncatedMessage);
+
+And the output will be::
+
+ This is...
+
+The message is truncated to the given length, then the suffix is appended to end
+of that string.
+
+Negative String Length
+~~~~~~~~~~~~~~~~~~~~~~
+
+If the length is negative, the number of characters to truncate is counted
+from the end of the string::
+
+ $truncatedMessage = $formatter->truncate($message, -5);
+
+This will result in::
+
+ This is a very long message, which should be trun...
+
+Custom Suffix
+~~~~~~~~~~~~~
+
+By default, the ``...`` suffix is used. If you wish to use a different suffix,
+simply pass it as the third argument to the method.
+The suffix is always appended, unless truncate length is longer than a message
+and a suffix length.
+If you don't want to use suffix at all, just 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
diff --git a/components/console/helpers/index.rst b/components/console/helpers/index.rst
index bf51dc40c92..38a8c2a7939 100644
--- a/components/console/helpers/index.rst
+++ b/components/console/helpers/index.rst
@@ -11,10 +11,8 @@ The Console Helpers
formatterhelper
processhelper
progressbar
- progresshelper
questionhelper
table
- tablehelper
debug_formatter
The Console component comes with some useful helpers. These helpers contain
diff --git a/components/console/helpers/map.rst.inc b/components/console/helpers/map.rst.inc
index d0913db4033..68e1e722a87 100644
--- a/components/console/helpers/map.rst.inc
+++ b/components/console/helpers/map.rst.inc
@@ -1,9 +1,6 @@
-* :doc:`/components/console/helpers/dialoghelper` (deprecated as of 2.5)
* :doc:`/components/console/helpers/formatterhelper`
* :doc:`/components/console/helpers/processhelper`
* :doc:`/components/console/helpers/progressbar`
-* :doc:`/components/console/helpers/progresshelper` (deprecated as of 2.5)
* :doc:`/components/console/helpers/questionhelper`
* :doc:`/components/console/helpers/table`
-* :doc:`/components/console/helpers/tablehelper` (deprecated as of 2.5)
-* :doc:`/components/console/helpers/debug_formatter` (new in 2.6)
+* :doc:`/components/console/helpers/debug_formatter`
diff --git a/components/console/helpers/processhelper.rst b/components/console/helpers/processhelper.rst
index e517aab7b72..45572d90a66 100644
--- a/components/console/helpers/processhelper.rst
+++ b/components/console/helpers/processhelper.rst
@@ -9,12 +9,12 @@ useful information about process status.
To display process details, use the :class:`Symfony\\Component\\Console\\Helper\\ProcessHelper`
and run your command with verbosity. For example, running the following code with
-a very verbose verbosity (e.g. -vv)::
+a very verbose verbosity (e.g. ``-vv``)::
- use Symfony\Component\Process\ProcessBuilder;
+ use Symfony\Component\Process\Process;
$helper = $this->getHelper('process');
- $process = ProcessBuilder::create(array('figlet', 'Symfony'))->getProcess();
+ $process = new Process(['figlet', 'Symfony']);
$helper->run($output, $process);
@@ -43,7 +43,7 @@ There are three ways to use the process helper:
* An array of arguments::
// ...
- $helper->run($output, array('figlet', 'Symfony'));
+ $helper->run($output, ['figlet', 'Symfony']);
.. note::
@@ -52,10 +52,10 @@ There are three ways to use the process helper:
* Passing a :class:`Symfony\\Component\\Process\\Process` instance::
- use Symfony\Component\Process\ProcessBuilder;
+ use Symfony\Component\Process\Process;
// ...
- $process = ProcessBuilder::create(array('figlet', 'Symfony'))->getProcess();
+ $process = new Process(['figlet', 'Symfony']);
$helper->run($output, $process);
diff --git a/components/console/helpers/progressbar.rst b/components/console/helpers/progressbar.rst
index f21fa4ace42..923a2bf9349 100644
--- a/components/console/helpers/progressbar.rst
+++ b/components/console/helpers/progressbar.rst
@@ -35,6 +35,12 @@ number of units, and advance the progress as the command executes::
// ensures that the progress bar is at 100%
$progressBar->finish();
+.. tip::
+
+ You can also regress the progress bar (i.e. step backwards) by calling
+ ``$progress->advance()`` with a negative value. For example, if you call
+ ``$progress->advance(-2)`` then it will regress the progress bar 2 steps.
+
Instead of advancing the bar by a number of steps (with the
:method:`Symfony\\Component\\Console\\Helper\\ProgressBar::advance` method),
you can also set the current progress by calling the
@@ -103,9 +109,9 @@ level of verbosity of the ``OutputInterface`` instance:
3/3 [============================] 100% 1 sec
# OutputInterface::VERBOSITY_VERY_VERBOSE (-vv)
- 0/3 [>---------------------------] 0% 1 sec
- 1/3 [=========>------------------] 33% 1 sec
- 3/3 [============================] 100% 1 sec
+ 0/3 [>---------------------------] 0% 1 sec/1 sec
+ 1/3 [=========>------------------] 33% 1 sec/1 sec
+ 3/3 [============================] 100% 1 sec/1 sec
# OutputInterface::VERBOSITY_DEBUG (-vvv)
0/3 [>---------------------------] 0% 1 sec/1 sec 1.0 MB
@@ -231,7 +237,7 @@ the example above.
Bar Settings
~~~~~~~~~~~~
-Amongst the placeholders, ``bar`` is a bit special as all the characters used
+Among the placeholders, ``bar`` is a bit special as all the characters used
to display it can be customized::
// the finished part of the bar
@@ -318,7 +324,7 @@ The ``setMessage()`` method accepts a second optional argument to set the value
of the custom placeholders::
// ...
- // $files = array('client-001/invoices.xml', '...');
+ // $files = ['client-001/invoices.xml', '...'];
foreach ($files as $filename) {
$progressBar->setMessage('Importing invoices...');
$progressBar->setMessage($filename, 'filename');
diff --git a/components/console/helpers/progresshelper.rst b/components/console/helpers/progresshelper.rst
deleted file mode 100644
index 7f0f86a23ac..00000000000
--- a/components/console/helpers/progresshelper.rst
+++ /dev/null
@@ -1,94 +0,0 @@
-.. index::
- single: Console Helpers; Progress Helper
-
-Progress Helper
-===============
-
-.. versionadded:: 2.3
- The ``setCurrent()`` method was introduced in Symfony 2.3.
-
-.. caution::
-
- The Progress Helper was deprecated in Symfony 2.5 and will be removed in
- Symfony 3.0. You should now use the
- :doc:`Progress Bar ` instead which
- is more powerful.
-
-When executing longer-running commands, it may be helpful to show progress
-information, which updates as your command runs:
-
-.. image:: /_images/components/console/progress.png
-
-To display progress details, use the :class:`Symfony\\Component\\Console\\Helper\\ProgressHelper`,
-pass it a total number of units, and advance the progress as your command executes::
-
- $progress = $this->getHelper('progress');
-
- $progress->start($output, 50);
- $i = 0;
- while ($i++ < 50) {
- // ... do some work
-
- // advances the progress bar 1 unit
- $progress->advance();
- }
-
- $progress->finish();
-
-.. tip::
-
- You can also set the current progress by calling the
- :method:`Symfony\\Component\\Console\\Helper\\ProgressHelper::setCurrent`
- method.
-
-If you want to output something while the progress bar is running,
-call :method:`Symfony\\Component\\Console\\Helper\\ProgressHelper::clear` first.
-After you're done, call
-:method:`Symfony\\Component\\Console\\Helper\\ProgressHelper::display`
-to show the progress bar again.
-
-The appearance of the progress output can be customized as well, with a number
-of different levels of verbosity. Each of these displays different possible
-items - like percentage completion, a moving progress bar, or current/total
-information (e.g. 10/50)::
-
- $progress->setFormat(ProgressHelper::FORMAT_QUIET);
- $progress->setFormat(ProgressHelper::FORMAT_NORMAL);
- $progress->setFormat(ProgressHelper::FORMAT_VERBOSE);
- $progress->setFormat(ProgressHelper::FORMAT_QUIET_NOMAX);
- // the default value
- $progress->setFormat(ProgressHelper::FORMAT_NORMAL_NOMAX);
- $progress->setFormat(ProgressHelper::FORMAT_VERBOSE_NOMAX);
-
-You can also control the different characters and the width used for the
-progress bar::
-
- // the finished part of the bar
- $progress->setBarCharacter('=');
- // the unfinished part of the bar
- $progress->setEmptyBarCharacter(' ');
- $progress->setProgressCharacter('|');
- $progress->setBarWidth(50);
-
-To see other available options, check the API documentation for
-:class:`Symfony\\Component\\Console\\Helper\\ProgressHelper`.
-
-.. caution::
-
- For performance reasons, be careful if you set the total number of steps
- to a high number. For example, if you're iterating over a large number of
- items, consider setting the redraw frequency to a higher value by calling
- :method:`Symfony\\Component\\Console\\Helper\\ProgressHelper::setRedrawFrequency`,
- so it updates on only some iterations::
-
- $progress->start($output, 50000);
-
- // updates every 100 iterations
- $progress->setRedrawFrequency(100);
-
- $i = 0;
- while ($i++ < 50000) {
- // ... do some work
-
- $progress->advance();
- }
diff --git a/components/console/helpers/questionhelper.rst b/components/console/helpers/questionhelper.rst
index db7c44c9f1d..8e907bf04bd 100644
--- a/components/console/helpers/questionhelper.rst
+++ b/components/console/helpers/questionhelper.rst
@@ -25,6 +25,7 @@ Suppose you want to confirm an action before actually executing it. Add
the following to your command::
// ...
+ use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
@@ -65,10 +66,6 @@ the second argument is not provided, ``true`` is assumed.
The regex defaults to ``/^y/i``.
- .. versionadded:: 2.7
- The regex argument was introduced in Symfony 2.7. Before, only answers
- starting with ``y`` were considered as "yes".
-
Asking the User for Information
-------------------------------
@@ -108,7 +105,7 @@ from a predefined list::
$helper = $this->getHelper('question');
$question = new ChoiceQuestion(
'Please select your favorite color (defaults to red)',
- array('red', 'blue', 'yellow'),
+ ['red', 'blue', 'yellow'],
0
);
$question->setErrorMessage('Color %s is invalid.');
@@ -146,7 +143,7 @@ this use :method:`Symfony\\Component\\Console\\Question\\ChoiceQuestion::setMult
$helper = $this->getHelper('question');
$question = new ChoiceQuestion(
'Please select your favorite colors (defaults to red and blue)',
- array('red', 'blue', 'yellow'),
+ ['red', 'blue', 'yellow'],
'0,1'
);
$question->setMultiselect(true);
@@ -175,7 +172,7 @@ will be autocompleted as the user types::
// ...
$helper = $this->getHelper('question');
- $bundles = array('AcmeDemoBundle', 'AcmeBlogBundle', 'AcmeStoreBundle');
+ $bundles = ['AcmeDemoBundle', 'AcmeBlogBundle', 'AcmeStoreBundle'];
$question = new Question('Please enter the name of a bundle', 'FooBundle');
$question->setAutocompleterValues($bundles);
@@ -206,13 +203,37 @@ convenient for passwords::
.. caution::
When you ask for a hidden response, Symfony will use either a binary, change
- stty mode or use another trick to hide the response. If none is available,
+ ``stty`` mode or use another trick to hide the response. If none is available,
it will fallback and allow the response to be visible unless you set this
behavior to ``false`` using
:method:`Symfony\\Component\\Console\\Question\\Question::setHiddenFallback`
like in the example above. In this case, a ``RuntimeException``
would be thrown.
+.. note::
+
+ The ``stty`` command is used to get and set properties of the command line
+ (such as getting the number of rows and columns or hiding the input text).
+ On Windows systems, this ``stty`` command may generate gibberish output and
+ mangle the input text. If that's your case, disable it with this command::
+
+ use Symfony\Component\Console\Helper\QuestionHelper;
+ use Symfony\Component\Console\Question\ChoiceQuestion;
+
+ // ...
+ public function execute(InputInterface $input, OutputInterface $output)
+ {
+ // ...
+ $helper = $this->getHelper('question');
+ QuestionHelper::disableStty();
+
+ // ...
+ }
+
+ .. versionadded:: 3.3
+
+ The ``QuestionHelper::disableStty()`` method was introduced in Symfony 3.3.
+
Normalizing the Answer
----------------------
@@ -321,10 +342,10 @@ Testing a Command that Expects Input
------------------------------------
If you want to write a unit test for a command which expects some kind of input
-from the command line, you need to set the helper input stream::
+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;
// ...
@@ -333,29 +354,37 @@ from the command line, you need to set the helper input stream::
// ...
$commandTester = new CommandTester($command);
- $helper = $command->getHelper('question');
- $helper->setInputStream($this->getInputStream("Test\n"));
// Equals to a user inputting "Test" and hitting ENTER
- // If you need to enter a confirmation, "yes\n" will work
+ $commandTester->setInputs(['Test']);
+
+ // Equals to a user inputting "This", "That" and hitting ENTER
+ // This can be used for answering two separated questions for instance
+ $commandTester->setInputs(['This', 'That']);
- $commandTester->execute(array('command' => $command->getName()));
+ // For simulating a positive answer to a confirmation question, adding an
+ // additional input saying "yes" will work
+ $commandTester->setInputs(['yes']);
+
+ $commandTester->execute(['command' => $command->getName()]);
// $this->assertRegExp('/.../', $commandTester->getDisplay());
}
- protected function getInputStream($input)
- {
- $stream = fopen('php://memory', 'r+', false);
- fputs($stream, $input);
- rewind($stream);
+.. versionadded:: 3.2
- return $stream;
- }
+ The ``CommandTester::setInputs()`` method was introduced in Symfony 3.2.
+
+By calling :method:`Symfony\\Component\\Console\\Tester\\CommandTester::setInputs`,
+you imitate what the console would do internally with all user input through the CLI.
+This method takes an array as only argument with, for each input that the command expects,
+a string representing what the user would have typed.
+This way you can test any user interaction (even complex ones) by passing the appropriate inputs.
+
+.. note::
-By setting the input stream of the ``QuestionHelper``, you imitate what the
-console would do internally with all user input through the CLI. This way
-you can test any user interaction (even complex ones) by passing an appropriate
-input stream.
+ The :class:`Symfony\\Component\\Console\\Tester\\CommandTester` automatically
+ simulates a user hitting ``ENTER`` after each input, no need for passing
+ an additional input.
.. caution::
diff --git a/components/console/helpers/table.rst b/components/console/helpers/table.rst
index f3487aa04fe..0694a5a468a 100644
--- a/components/console/helpers/table.rst
+++ b/components/console/helpers/table.rst
@@ -20,7 +20,10 @@ When building a console application it may be useful to display tabular data:
To display a table, use :class:`Symfony\\Component\\Console\\Helper\\Table`,
set the headers, set the rows and then render the table::
+ use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Table;
+ use Symfony\Component\Console\Input\InputInterface;
+ use Symfony\Component\Console\Output\OutputInterface;
// ...
class SomeCommand extends Command
@@ -29,13 +32,13 @@ set the headers, set the rows and then render the table::
{
$table = new Table($output);
$table
- ->setHeaders(array('ISBN', 'Title', 'Author'))
- ->setRows(array(
- array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'),
- array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'),
- array('960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'),
- array('80-902734-1-6', 'And Then There Were None', 'Agatha Christie'),
- ))
+ ->setHeaders(['ISBN', 'Title', 'Author'])
+ ->setRows([
+ ['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'],
+ ])
;
$table->render();
}
@@ -46,13 +49,13 @@ You can add a table separator anywhere in the output by passing an instance of
use Symfony\Component\Console\Helper\TableSeparator;
- $table->setRows(array(
- array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'),
- array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'),
+ $table->setRows([
+ ['99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'],
+ ['9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'],
new TableSeparator(),
- array('960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'),
- array('80-902734-1-6', 'And Then There Were None', 'Agatha Christie'),
- ));
+ ['960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'],
+ ['80-902734-1-6', 'And Then There Were None', 'Agatha Christie'],
+ ]);
.. code-block:: terminal
@@ -66,6 +69,45 @@ 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
+contents. Use the :method:`Symfony\\Component\\Console\\Helper\\Table::setColumnWidths`
+method to set the column widths explicitly::
+
+ // ...
+ $table->setColumnWidths([10, 0, 30]);
+ $table->render();
+
+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:
+
+.. code-block:: terminal
+
+ +---------------+--------------------------+--------------------------------+
+ | ISBN | Title | Author |
+ +---------------+--------------------------+--------------------------------+
+ | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
+ | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
+ +---------------+--------------------------+--------------------------------+
+ | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
+ | 80-902734-1-6 | And Then There Were None | Agatha Christie |
+ +---------------+--------------------------+--------------------------------+
+
+Note that the defined column widths are always considered as the minimum column
+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::
+
+ // ...
+ $table->setColumnWidth(0, 10);
+ $table->setColumnWidth(2, 30);
+ $table->render();
+
The table style can be changed to any built-in styles via
:method:`Symfony\\Component\\Console\\Helper\\Table::setStyle`::
@@ -147,23 +189,20 @@ Here is a full list of things you can customize:
Spanning Multiple Columns and Rows
----------------------------------
-.. versionadded:: 2.7
- Spanning multiple columns and rows was introduced in Symfony 2.7.
-
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
- ->setHeaders(array('ISBN', 'Title', 'Author'))
- ->setRows(array(
- array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'),
+ ->setHeaders(['ISBN', 'Title', 'Author'])
+ ->setRows([
+ ['99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'],
new TableSeparator(),
- array(new TableCell('This value spans 3 columns.', array('colspan' => 3))),
- ))
+ [new TableCell('This value spans 3 columns.', ['colspan' => 3])],
+ ])
;
$table->render();
@@ -184,10 +223,10 @@ This results in:
You can create a multiple-line page title using a header cell that spans
the entire table width::
- $table->setHeaders(array(
- array(new TableCell('Main table title', array('colspan' => 3))),
- array('ISBN', 'Title', 'Author'),
- ))
+ $table->setHeaders([
+ [new TableCell('Main table title', ['colspan' => 3])],
+ ['ISBN', 'Title', 'Author'],
+ ])
// ...
This generates:
@@ -209,15 +248,15 @@ In a similar way you can span multiple rows::
$table = new Table($output);
$table
- ->setHeaders(array('ISBN', 'Title', 'Author'))
- ->setRows(array(
- array(
+ ->setHeaders(['ISBN', 'Title', 'Author'])
+ ->setRows([
+ [
'978-0521567817',
'De Monarchia',
- new TableCell("Dante Alighieri\nspans multiple rows", array('rowspan' => 2)),
- ),
- array('978-0804169127', 'Divine Comedy'),
- ))
+ new TableCell("Dante Alighieri\nspans multiple rows", ['rowspan' => 2]),
+ ],
+ ['978-0804169127', 'Divine Comedy'],
+ ])
;
$table->render();
diff --git a/components/console/helpers/tablehelper.rst b/components/console/helpers/tablehelper.rst
deleted file mode 100644
index f7502dc09b8..00000000000
--- a/components/console/helpers/tablehelper.rst
+++ /dev/null
@@ -1,73 +0,0 @@
-.. index::
- single: Console Helpers; Table Helper
-
-Table Helper
-============
-
-.. versionadded:: 2.3
- The ``table`` helper was introduced in Symfony 2.3.
-
-.. caution::
-
- The Table Helper was deprecated in Symfony 2.5 and will be removed in
- Symfony 3.0. You should now use the
- :doc:`Table ` class instead which is
- more powerful.
-
-When building a console application it may be useful to display tabular data:
-
-.. code-block:: terminal
-
- +---------------+--------------------------+------------------+
- | ISBN | Title | Author |
- +---------------+--------------------------+------------------+
- | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
- | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
- | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
- | 80-902734-1-6 | And Then There Were None | Agatha Christie |
- +---------------+--------------------------+------------------+
-
-To display a table, use the :class:`Symfony\\Component\\Console\\Helper\\TableHelper`,
-set headers, rows and render::
-
- $table = $this->getHelper('table');
- $table
- ->setHeaders(array('ISBN', 'Title', 'Author'))
- ->setRows(array(
- array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'),
- array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'),
- array('960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'),
- array('80-902734-1-6', 'And Then There Were None', 'Agatha Christie'),
- ))
- ;
- $table->render($output);
-
-The table layout can be customized as well. There are two ways to customize
-table rendering: using named layouts or by customizing rendering options.
-
-Customize Table Layout using Named Layouts
-------------------------------------------
-
-The Table helper ships with three preconfigured table layouts:
-
-* ``TableHelper::LAYOUT_DEFAULT``
-
-* ``TableHelper::LAYOUT_BORDERLESS``
-
-* ``TableHelper::LAYOUT_COMPACT``
-
-Layout can be set using :method:`Symfony\\Component\\Console\\Helper\\TableHelper::setLayout` method.
-
-Customize Table Layout using Rendering Options
-----------------------------------------------
-
-You can also control table rendering by setting custom rendering option values:
-
-* :method:`Symfony\\Component\\Console\\Helper\\TableHelper::setPaddingChar`
-* :method:`Symfony\\Component\\Console\\Helper\\TableHelper::setHorizontalBorderChar`
-* :method:`Symfony\\Component\\Console\\Helper\\TableHelper::setVerticalBorderChar`
-* :method:`Symfony\\Component\\Console\\Helper\\TableHelper::setCrossingChar`
-* :method:`Symfony\\Component\\Console\\Helper\\TableHelper::setCellHeaderFormat`
-* :method:`Symfony\\Component\\Console\\Helper\\TableHelper::setCellRowFormat`
-* :method:`Symfony\\Component\\Console\\Helper\\TableHelper::setBorderFormat`
-* :method:`Symfony\\Component\\Console\\Helper\\TableHelper::setPadType`
diff --git a/components/console/logger.rst b/components/console/logger.rst
index 2c44ee75970..528b7df3f2c 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'
)
@@ -83,10 +84,11 @@ constructor::
use Psr\Log\LogLevel;
// ...
- $verbosityLevelMap = array(
+ $verbosityLevelMap = [
LogLevel::NOTICE => OutputInterface::VERBOSITY_NORMAL,
LogLevel::INFO => OutputInterface::VERBOSITY_NORMAL,
- );
+ ];
+
$logger = new ConsoleLogger($output, $verbosityLevelMap);
Color
@@ -97,10 +99,23 @@ level. This behavior is configurable through the third parameter of the
constructor::
// ...
- $formatLevelMap = array(
+ $formatLevelMap = [
LogLevel::CRITICAL => ConsoleLogger::ERROR,
LogLevel::DEBUG => ConsoleLogger::INFO,
- );
- $logger = new ConsoleLogger($output, array(), $formatLevelMap);
+ ];
+
+ $logger = new ConsoleLogger($output, [], $formatLevelMap);
+
+Errors
+------
+
+.. versionadded:: 3.2
+
+ The ``hasErrored()`` method was introduced in Symfony 3.2.
+
+The Console logger includes a ``hasErrored()`` method which returns ``true`` as
+soon as any error message has been logged during the execution of the command.
+This is useful to decide which status code to return as the result of executing
+the command.
.. _PSR-3: https://www.php-fig.org/psr/psr-3/
diff --git a/components/console/single_command_tool.rst b/components/console/single_command_tool.rst
index a41f7f479d4..457efb48dae 100644
--- a/components/console/single_command_tool.rst
+++ b/components/console/single_command_tool.rst
@@ -6,68 +6,46 @@ Building a single Command Application
When building a command line tool, you may not need to provide several commands.
In such case, having to pass the command name each time is tedious. Fortunately,
-it is possible to remove this need by extending the application::
+it is possible to remove this need by declaring a single command application::
- namespace Acme\Tool;
+ #!/usr/bin/env php
+ setArguments();
-
- return $inputDefinition;
- }
- }
-
-When calling your console script, the command ``MyCommand`` will then always
-be used, without having to pass its name.
-
-You can also simplify how you execute the application::
+ use Symfony\Component\Console\Input\InputOption;
+ use Symfony\Component\Console\Output\OutputInterface;
+
+ (new Application('echo', '1.0.0'))
+ ->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:`Symfony\\Component\\Console\\Application::setDefaultCommand` method
+accepts a boolean as second parameter. If true, the command ``echo`` will then
+always be used, without having to pass its name.
+
+You can still register a command as usual::
#!/usr/bin/env php
add($command);
- $application = new MyApplication();
+ $application->setDefaultCommand($command->getName(), true);
$application->run();
diff --git a/components/console/usage.rst b/components/console/usage.rst
index d93c53647db..b51716280af 100644
--- a/components/console/usage.rst
+++ b/components/console/usage.rst
@@ -10,7 +10,7 @@ built-in options as well as a couple of built-in commands for the Console compon
.. note::
These examples assume you have added a file ``application.php`` to run at
- the cli::
+ the CLI::
#!/usr/bin/env php
`_ repository.
+ $ composer require symfony/css-selector:^3.4
.. include:: /components/require_autoload.rst.inc
@@ -31,7 +29,7 @@ Why to Use CSS selectors?
~~~~~~~~~~~~~~~~~~~~~~~~~
When you're parsing an HTML or an XML document, by far the most powerful
-method is XPath.
+method is `XPath`_.
XPath expressions are incredibly flexible, so there is almost always an
XPath expression that will find the element you need. Unfortunately, they
@@ -53,10 +51,6 @@ document.
The CssSelector Component
~~~~~~~~~~~~~~~~~~~~~~~~~
-.. versionadded:: 2.8
- The ``CssSelectorConverter`` class was introduced in Symfony 2.8. Previously,
- the ``CssSelector`` class was used and ``toXPath`` was a static method.
-
The component's only goal is to convert CSS selectors to their XPath
equivalents, using :method:`Symfony\\Component\\CssSelector\\CssSelectorConverter::toXPath`::
@@ -83,7 +77,7 @@ You can use this expression with, for instance, :phpclass:`DOMXPath` or
Limitations of the CssSelector Component
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Not all CSS selectors can be converted to XPath equivalents.
+Not all CSS selectors can be converted to `XPath`_ equivalents.
There are several CSS selectors that only make sense in the context of a
web-browser.
@@ -101,12 +95,12 @@ 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
----------
* :doc:`/testing`
* :doc:`/components/dom_crawler`
+
+.. _`XPath`: https://en.wikipedia.org/wiki/XPath
diff --git a/components/debug.rst b/components/debug.rst
deleted file mode 100644
index e42e3d3423a..00000000000
--- a/components/debug.rst
+++ /dev/null
@@ -1,94 +0,0 @@
-.. index::
- single: Debug
- single: Components; Debug
-
-The Debug Component
-===================
-
- The Debug component provides tools to ease debugging PHP code.
-
-.. versionadded:: 2.3
- The Debug component was introduced in Symfony 2.3. Previously, the classes
- were located in the HttpKernel component.
-
-Installation
-------------
-
-.. code-block:: terminal
-
- $ 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 is as easy as it can get::
-
- use Symfony\Component\Debug\Debug;
-
- Debug::enable();
-
-The :method:`Symfony\\Component\\Debug\\Debug::enable` method registers an
-error handler, an exception handler and
-:ref:`a special class loader `.
-
-Read the following sections for more information about the different available
-tools.
-
-.. caution::
-
- You should never enable the debug tools in a production environment as
- they might disclose sensitive information to the user.
-
-Enabling the Error Handler
---------------------------
-
-The :class:`Symfony\\Component\\Debug\\ErrorHandler` class catches PHP errors
-and converts them to exceptions (of class :phpclass:`ErrorException` or
-:class:`Symfony\\Component\\Debug\\Exception\\FatalErrorException` for PHP
-fatal errors)::
-
- use Symfony\Component\Debug\ErrorHandler;
-
- ErrorHandler::register();
-
-Enabling the Exception Handler
-------------------------------
-
-The :class:`Symfony\\Component\\Debug\\ExceptionHandler` class catches
-uncaught PHP exceptions and converts them to a nice PHP response. It is useful
-in debug mode to replace the default PHP/XDebug output with something prettier
-and more useful::
-
- use Symfony\Component\Debug\ExceptionHandler;
-
- ExceptionHandler::register();
-
-.. note::
-
- If the :doc:`HttpFoundation component ` is
- available, the handler uses a Symfony Response object; if not, it falls
- back to a regular PHP response.
-
-.. _component-debug-class-loader:
-
-Debugging a Class Loader
-------------------------
-
-The :class:`Symfony\\Component\\Debug\\DebugClassLoader` attempts to
-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.
-
-Using the ``DebugClassLoader`` is as easy as 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 dd3827b85fe..ae7f2c2258d 100644
--- a/components/dependency_injection.rst
+++ b/components/dependency_injection.rst
@@ -5,8 +5,9 @@
The DependencyInjection Component
=================================
- The DependencyInjection component allows you to standardize and centralize
- the way objects are constructed in your application.
+ The DependencyInjection component implements a `PSR-11`_ compatible service
+ container that allows you to standardize and centralize the way objects are
+ constructed in your application.
For an introduction to Dependency Injection and service containers see
:doc:`/service_container`.
@@ -16,9 +17,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/dependency-injection
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require symfony/dependency-injection:^3.4
.. include:: /components/require_autoload.rst.inc
@@ -158,7 +157,7 @@ If you do want to though then the container can call the setter method::
$containerBuilder
->register('newsletter_manager', 'NewsletterManager')
- ->addMethodCall('setMailer', array(new Reference('mailer')));
+ ->addMethodCall('setMailer', [new Reference('mailer')]);
You could then get your ``newsletter_manager`` service from the container
like this::
@@ -199,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();
@@ -209,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();
@@ -222,11 +221,20 @@ Loading a YAML config file::
If you want to load YAML config files then you will also need to install
:doc:`the Yaml component `.
+.. tip::
+
+ If your application uses unconventional file extensions (for example, your
+ XML files have a ``.config`` extension) you can pass the file type as the
+ second optional parameter of the ``load()`` method::
+
+ // ...
+ $loader->load('services.config', 'xml');
+
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();
@@ -258,7 +266,7 @@ config files:
+ xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
@@ -272,7 +280,7 @@ config files:
-
+
@@ -290,7 +298,7 @@ config files:
$container
->register('newsletter_manager', 'NewsletterManager')
- ->addMethodCall('setMailer', array(new Reference('mailer')));
+ ->addMethodCall('setMailer', [new Reference('mailer')]);
Learn More
----------
@@ -302,4 +310,4 @@ Learn More
/components/dependency_injection/*
/service_container/*
-.. _Packagist: https://packagist.org/packages/symfony/dependency-injection
+.. _`PSR-11`: http://www.php-fig.org/psr/psr-11/
diff --git a/components/dependency_injection/_imports-parameters-note.rst.inc b/components/dependency_injection/_imports-parameters-note.rst.inc
index dc9917d370e..18c35870ef3 100644
--- a/components/dependency_injection/_imports-parameters-note.rst.inc
+++ b/components/dependency_injection/_imports-parameters-note.rst.inc
@@ -10,7 +10,7 @@
# app/config/config.yml
imports:
- - { resource: '%kernel.root_dir%/parameters.yml' }
+ - { resource: '%kernel.project_dir%/app/parameters.yml' }
.. code-block:: xml
@@ -19,14 +19,14 @@
+ https://symfony.com/schema/dic/services/services-1.0.xsd">
-
+
.. code-block:: php
// app/config/config.php
- $loader->import('%kernel.root_dir%/parameters.yml');
+ $loader->import('%kernel.project_dir%/app/parameters.yml');
diff --git a/components/dependency_injection/compilation.rst b/components/dependency_injection/compilation.rst
index 1e6765bea86..82d51f42cf6 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
{
@@ -78,7 +78,7 @@ A very simple extension may just load configuration files into the container::
This does not gain very much compared to loading the file directly into
the overall container being built. It just allows the files to be split
-up amongst the modules/bundles. Being able to affect the configuration
+up among the modules/bundles. Being able to affect the configuration
of a module from configuration files outside of the module/bundle is needed
to make a complex application configurable. This can be done by specifying
sections of config files loaded directly into the container as being for
@@ -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();
@@ -146,12 +146,12 @@ that was loaded into the container. You are only loading a single config
file in the above example but it will still be within an array. The array
will look like this::
- array(
- array(
+ [
+ [
'foo' => 'fooValue',
'bar' => 'barValue',
- ),
- )
+ ],
+ ]
Whilst you can manually manage merging the different files, it is much better
to use :doc:`the Config component ` to
@@ -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,17 +325,12 @@ compilation::
{
public function process(ContainerBuilder $container)
{
- // ... do something during the compilation
+ // ... do something during the compilation
}
// ...
}
-.. versionadded:: 2.8
- Prior to Symfony 2.8, extensions implementing ``CompilerPassInterface``
- were not automatically registered. You needed to register them as explained
- in :ref:`the next section `.
-
As ``process()`` is called *after* all extensions are loaded, it allows you to
edit service definitions of other extensions as well as retrieving information
about service definitions.
@@ -346,7 +341,7 @@ methods described in :doc:`/service_container/definitions`.
.. note::
Please note that the ``process()`` method in the extension class is
- called during the optimization step. You can read
+ called during the ``PassConfig::TYPE_BEFORE_OPTIMIZATION`` step. You can read
:ref:`the next section ` if you
need to edit the container during another step.
@@ -384,7 +379,7 @@ class implementing the ``CompilerPassInterface``::
{
public function process(ContainerBuilder $container)
{
- // ... do something during the compilation
+ // ... do something during the compilation
}
}
@@ -428,6 +423,24 @@ been run, use::
PassConfig::TYPE_AFTER_REMOVING
);
+.. versionadded:: 3.2
+
+ The option to prioritize compiler passes was introduced in Symfony 3.2.
+
+You can also control the order in which compiler passes are run for each
+compilation phase. Use the optional third argument of ``addCompilerPass()`` to
+set the priority as an integer number. The default priority is ``0`` and the higher
+its value, the earlier it's executed::
+
+ // ...
+ // FirstPass is executed after SecondPass because its priority is lower
+ $container->addCompilerPass(
+ new FirstPass(), PassConfig::TYPE_AFTER_REMOVING, 10
+ );
+ $container->addCompilerPass(
+ new SecondPass(), PassConfig::TYPE_AFTER_REMOVING, 30
+ );
+
.. _components-dependency-injection-dumping:
Dumping the Configuration for Performance
@@ -460,7 +473,7 @@ makes dumping the compiled container easy::
}
``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::
// ...
@@ -477,7 +490,7 @@ dump it::
$dumper = new PhpDumper($containerBuilder);
file_put_contents(
$file,
- $dumper->dump(array('class' => 'MyCachedContainer'))
+ $dumper->dump(['class' => 'MyCachedContainer'])
);
}
@@ -510,7 +523,7 @@ application::
$dumper = new PhpDumper($containerBuilder);
file_put_contents(
$file,
- $dumper->dump(array('class' => 'MyCachedContainer'))
+ $dumper->dump(['class' => 'MyCachedContainer'])
);
}
}
@@ -543,7 +556,7 @@ for these resources and use them as metadata for the cache::
$dumper = new PhpDumper($containerBuilder);
$containerConfigCache->write(
- $dumper->dump(array('class' => 'MyCachedContainer')),
+ $dumper->dump(['class' => 'MyCachedContainer']),
$containerBuilder->getResources()
);
}
diff --git a/components/dom_crawler.rst b/components/dom_crawler.rst
index 169681ae205..b1624c8f9de 100644
--- a/components/dom_crawler.rst
+++ b/components/dom_crawler.rst
@@ -17,9 +17,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/dom-crawler
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require symfony/dom-crawler:^3.4
.. include:: /components/require_autoload.rst.inc
@@ -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;
@@ -56,9 +54,11 @@ which are basically nodes that you can traverse easily::
var_dump($domElement->nodeName);
}
-Specialized :class:`Symfony\\Component\\DomCrawler\\Link` and
+Specialized :class:`Symfony\\Component\\DomCrawler\\Link`,
+:class:`Symfony\\Component\\DomCrawler\\Image` and
:class:`Symfony\\Component\\DomCrawler\\Form` classes are useful for
-interacting with html links and forms as you traverse through the HTML tree.
+interacting with html links, images and forms as you traverse through the HTML
+tree.
.. note::
@@ -146,7 +146,7 @@ and :method:`Symfony\\Component\\DomCrawler\\Crawler::filter`::
method.
The default namespace is removed when loading the content if it's the only
- namespace in the document. It's done to simplify the xpath queries.
+ namespace in the document. It's done to simplify the XPath queries.
Namespaces can be explicitly registered with the
:method:`Symfony\\Component\\DomCrawler\\Crawler::registerNamespace` method::
@@ -188,7 +188,7 @@ Get all the child or parent nodes::
Accessing Node Values
~~~~~~~~~~~~~~~~~~~~~
-Access the node name (HTML tag name) of the first node of the current selection (eg. "p" or "div")::
+Access the node name (HTML tag name) of the first node of the current selection (e.g. "p" or "div")::
// returns the node name (HTML tag name) of the first child element under
$tag = $crawler->filterXPath('//body/*')->nodeName();
@@ -205,7 +205,7 @@ Extract attribute and/or node values from the list of nodes::
$attributes = $crawler
->filterXpath('//body/p')
- ->extract(array('_text', 'class'))
+ ->extract(['_text', 'class'])
;
.. note::
@@ -221,49 +221,66 @@ Call an anonymous function on each node of the list::
return $node->text();
});
-.. versionadded:: 2.3
- As seen here, in Symfony 2.3, the ``each`` and ``reduce`` Closure functions
- are passed a ``Crawler`` as the first argument. Previously, that argument
- was a :phpclass:`DOMNode`.
-
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::
- When dealing with character sets other than ISO-8859-1, always add HTML
- content using the :method:`Symfony\\Component\\DomCrawler\\Crawler::addHtmlContent`
- method where you can specify the second parameter to be your target character
- set.
+ The :method:`Symfony\\Component\\DomCrawler\\Crawler::addHtmlContent` and
+ :method:`Symfony\\Component\\DomCrawler\\Crawler::addXmlContent` methods
+ default to UTF-8 encoding but you can change this behavior with their second
+ optional argument.
+
+ The :method:`Symfony\\Component\\DomCrawler\\Crawler::addContent` method
+ guesses the best charset according to the given contents and defaults to
+ ``ISO-8859-1`` in case no charset can be guessed.
+
+ .. versionadded:: 3.4
+
+ The charset guessing mechanism of the ``addContent()`` method was
+ introduced in Symfony 3.4. In previous Symfony versions, the ``ISO-8859-1``
+ charset was always used.
As the Crawler's implementation is based on the DOM extension, it is also able
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);
$crawler->addDocument($domDocument);
$crawler->addNodeList($nodeList);
- $crawler->addNodes(array($node));
+ $crawler->addNodes([$node]);
$crawler->addNode($node);
$crawler->add($domDocument);
@@ -290,21 +307,89 @@ and :phpclass:`DOMNode` objects::
$html = $crawler->html();
- The ``html()`` method is new in Symfony 2.3.
+Expression Evaluation
+~~~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 3.2
+
+ The :method:`Symfony\\Component\\DomCrawler\\Crawler::evaluate` method was
+ introduced in Symfony 3.2.
+
+The ``evaluate()`` method evaluates the given XPath expression. The return
+value depends on the XPath expression. If the expression evaluates to a scalar
+value (e.g. HTML attributes), an array of results will be returned. If the
+expression evaluates to a DOM document, a new ``Crawler`` instance will be
+returned.
+
+This behavior is best illustrated with examples::
+
+ use Symfony\Component\DomCrawler\Crawler;
+
+ $html = '
+
+ Article 1
+ Article 2
+ Article 3
+
+ ';
+
+ $crawler = new Crawler();
+ $crawler->addHtmlContent($html);
+
+ $crawler->filterXPath('//span[contains(@id, "article-")]')->evaluate('substring-after(@id, "-")');
+ /* array:3 [
+ 0 => "100"
+ 1 => "101"
+ 2 => "102"
+ ]
+ */
+
+ $crawler->evaluate('substring-after(//span[contains(@id, "article-")]/@id, "-")');
+ /* array:1 [
+ 0 => "100"
+ ]
+ */
+
+ $crawler->filterXPath('//span[@class="article"]')->evaluate('count(@id)');
+ /* array:3 [
+ 0 => 1.0
+ 1 => 1.0
+ 2 => 1.0
+ ]
+ */
+
+ $crawler->evaluate('count(//span[@class="article"])');
+ /* array:1 [
+ 0 => 3.0
+ ]
+ */
+
+ $crawler->evaluate('//span[1]');
+ // A Symfony\Component\DomCrawler\Crawler instance
Links
~~~~~
-To find a link by name (or a clickable image by its ``alt`` attribute), use
-the ``selectLink()`` method on an existing crawler. This returns a Crawler
-instance with just the selected link(s). Calling ``link()`` gives you a special
-:class:`Symfony\\Component\\DomCrawler\\Link` object::
+Use the ``filter()`` method to find links by their ``id`` or ``class``
+attributes and use the ``selectLink()`` method to find links by their content
+(it also finds clickable images with that content in its ``alt`` attribute).
- $linksCrawler = $crawler->selectLink('Go elsewhere...');
- $link = $linksCrawler->link();
+Both methods return a ``Crawler`` instance with just the selected link. Use the
+``link()`` method to get the :class:`Symfony\\Component\\DomCrawler\\Link` object
+that represents the link::
- // or do this all at once
- $link = $crawler->selectLink('Go elsewhere...')->link();
+ // first, select the link by id, class or content...
+ $linkCrawler = $crawler->filter('#sign-up');
+ $linkCrawler = $crawler->filter('.user-profile');
+ $linkCrawler = $crawler->selectLink('Log in');
+
+ // ...then, get the Link object:
+ $link = $linkCrawler->link();
+
+ // or do all this at once:
+ $link = $crawler->filter('#sign-up')->link();
+ $link = $crawler->filter('.user-profile')->link();
+ $link = $crawler->selectLink('Log in')->link();
The :class:`Symfony\\Component\\DomCrawler\\Link` object has several useful
methods to get more information about the selected link itself::
@@ -320,6 +405,23 @@ methods to get more information about the selected link itself::
page suffixed with ``#foo``. The return from ``getUri()`` is always a full
URI that you can act on.
+Images
+~~~~~~
+
+To find an image by its ``alt`` attribute, use the ``selectImage`` method on an
+existing crawler. This returns a ``Crawler`` instance with just the selected
+image(s). Calling ``image()`` gives you a special
+:class:`Symfony\\Component\\DomCrawler\\Image` object::
+
+ $imagesCrawler = $crawler->selectImage('Kitten');
+ $image = $imagesCrawler->image();
+
+ // or do this all at once
+ $image = $crawler->selectImage('Kitten')->image();
+
+The :class:`Symfony\\Component\\DomCrawler\\Image` object has the same
+``getUri()`` method as :class:`Symfony\\Component\\DomCrawler\\Link`.
+
Forms
~~~~~
@@ -346,9 +448,9 @@ form that the button lives in::
$crawler->filter('.form-vertical')->form();
// or "fill" the form fields with data
- $form = $crawler->selectButton('my-super-button')->form(array(
+ $form = $crawler->selectButton('my-super-button')->form([
'name' => 'Ryan',
- ));
+ ]);
The :class:`Symfony\\Component\\DomCrawler\\Form` object has lots of very
useful methods for working with forms::
@@ -362,13 +464,20 @@ than just return the ``action`` attribute of the form. If the form method
is GET, then it mimics the browser's behavior and returns the ``action``
attribute followed by a query string of all of the form's values.
+.. versionadded:: 3.3
+
+ Starting from Symfony 3.3, the optional ``formaction`` and ``formmethod``
+ button attributes are supported. The ``getUri()`` and ``getMethod()``
+ methods take into account those attributes to always return the right action
+ and method depending on the button used to get the form.
+
You can virtually set and get values on the form::
// sets values on the form internally
- $form->setValues(array(
+ $form->setValues([
'registration[username]' => 'symfonyfan',
'registration[terms]' => 1,
- ));
+ ]);
// gets back an array of values - in the "flat" array like above
$values = $form->getValues();
@@ -380,21 +489,29 @@ You can virtually set and get values on the form::
To work with multi-dimensional fields::
Pass an array of values::
// sets a single field
- $form->setValues(array('multi' => array('value')));
+ $form->setValues(['multi' => ['value']]);
// sets multiple fields at once
- $form->setValues(array('multi' => array(
+ $form->setValues(['multi' => [
1 => 'value',
'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,
@@ -410,7 +527,7 @@ and uploading files::
$form['registration[birthday][year]']->select(1984);
// selects many options from a "multiple" select
- $form['registration[interests]']->select(array('symfony', 'cookies'));
+ $form['registration[interests]']->select(['symfony', 'cookies']);
// fakes a file upload
$form['registration[photo]']->upload('/path/to/lucas.jpg');
@@ -471,7 +588,6 @@ the whole form or specific field(s)::
$form['country']->select('Invalid value');
.. _`Goutte`: https://github.com/FriendsOfPHP/Goutte
-.. _Packagist: https://packagist.org/packages/symfony/dom-crawler
Learn more
----------
diff --git a/components/dotenv.rst b/components/dotenv.rst
new file mode 100644
index 00000000000..85e2b0bccfd
--- /dev/null
+++ b/components/dotenv.rst
@@ -0,0 +1,118 @@
+.. index::
+ single: Dotenv
+ single: Components; Dotenv
+
+The Dotenv Component
+====================
+
+ The Dotenv Component parses ``.env`` files to make environment variables
+ stored in them accessible via ``$_ENV`` or ``$_SERVER``.
+
+.. versionadded:: 3.3
+
+ The Dotenv component was introduced in Symfony 3.3.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/dotenv:^3.4
+
+.. include:: /components/require_autoload.rst.inc
+
+Usage
+-----
+
+Sensitive information and environment-dependent settings should be defined as
+environment variables (as recommended for `twelve-factor applications`_). Using
+a ``.env`` file to store those environment variables eases development and CI
+management by keeping them in one "standard" place and agnostic of the
+technology stack you are using (nginx vs PHP built-in server for instance).
+
+.. note::
+
+ PHP has a lot of different implementations of this "pattern". This
+ implementation's goal is to replicate what ``source .env`` would do. It
+ tries to be as similar as possible with the standard shell's behavior (so
+ no value validation for instance).
+
+Load a ``.env`` file in your PHP application via ``Dotenv::load()``::
+
+ use Symfony\Component\Dotenv\Dotenv;
+
+ $dotenv = new Dotenv();
+ $dotenv->load(__DIR__.'/.env');
+
+ // You can also load several files
+ $dotenv->load(__DIR__.'/.env', __DIR__.'/.env.dev');
+
+Given the following ``.env`` file content:
+
+.. code-block:: terminal
+
+ # .env
+ DB_USER=root
+ DB_PASS=pass
+
+Access the value with ``$_ENV`` in your code::
+
+ $dbUser = $_ENV['DB_USER'];
+ // you can also use ``$_SERVER``
+
+.. note::
+
+ Symfony Dotenv never overwrites existing environment variables.
+
+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.
+
+.. note::
+
+ Symfony Dotenv can be used in any environment of your application:
+ development, testing, staging and even production. However, in production
+ it's recommended to configure real environment variables to avoid the
+ performance overhead of parsing the ``.env`` file for every request.
+
+As a ``.env`` file is a regular shell script, you can ``source`` it in your own
+shell scripts:
+
+.. code-block:: terminal
+
+ source .env
+
+Add comments by prefixing them with ``#``:
+
+.. code-block:: terminal
+
+ # Database credentials
+ DB_USER=root
+ DB_PASS=pass # This is the secret password
+
+Use environment variables in values by prefixing variables with ``$``:
+
+.. 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:: terminal
+
+ START_TIME=$(date)
+
+.. note::
+
+ Note that using ``$()`` might not work depending on your shell.
+
+.. _twelve-factor applications: http://www.12factor.net/
diff --git a/components/event_dispatcher.rst b/components/event_dispatcher.rst
index cc5fe8716ea..33c6177b16d 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
@@ -54,9 +54,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/event-dispatcher
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require symfony/event-dispatcher:^3.4
.. include:: /components/require_autoload.rst.inc
@@ -88,7 +86,7 @@ The unique event name can be any string, but optionally follows a few simple
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``).
@@ -141,7 +139,7 @@ A call to the dispatcher's ``addListener()`` method associates any valid
PHP callable to an event::
$listener = new AcmeListener();
- $dispatcher->addListener('acme.foo.action', array($listener, 'onFooAction'));
+ $dispatcher->addListener('acme.foo.action', [$listener, 'onFooAction']);
The ``addListener()`` method takes up to three arguments:
@@ -192,35 +190,32 @@ event. In many cases, a special event subclass is passed with extra
information. You can check the documentation or implementation of each event to
determine which instance is passed.
-.. sidebar:: Registering Event Listeners in the Service Container
+.. sidebar:: Registering Event Listeners and Subscribers in the Service Container
- When you are using the
- :class:`Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher`
- and the
- :doc:`DependencyInjection component `,
- you can use the
- :class:`Symfony\\Component\\EventDispatcher\\DependencyInjection\\RegisterListenersPass`
- to tag services as event listeners::
+ Registering service definitions and tagging them with the
+ ``kernel.event_listener`` and ``kernel.event_subscriber`` tags is not enough
+ to enable the event listeners and event subscribers. You must also register
+ a compiler pass called ``RegisterListenersPass()`` in the container builder::
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\Reference;
- use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher;
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'
+ // and 'kernel.event_subscriber' service tags
$containerBuilder->addCompilerPass(new RegisterListenersPass());
- // registers the event dispatcher service
- $containerBuilder->register('event_dispatcher', ContainerAwareEventDispatcher::class)
- ->addArgument(new Reference('service_container'));
+ $containerBuilder->register('event_dispatcher', EventDispatcher::class);
- // registers your event listener service
+ // registers an event listener
$containerBuilder->register('listener_service_id', \AcmeListener::class)
- ->addTag('kernel.event_listener', array(
+ ->addTag('kernel.event_listener', [
'event' => 'acme.foo.action',
'method' => 'onFooAction',
- ));
+ ]);
// registers an event subscriber
$containerBuilder->register('subscriber_service_id', \AcmeSubscriber::class)
@@ -257,8 +252,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\Component\EventDispatcher\Event;
/**
* The order.placed event is dispatched each time an order is created
@@ -300,8 +295,8 @@ 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::
- use Acme\Store\Order;
use Acme\Store\Event\OrderPlacedEvent;
+ use Acme\Store\Order;
// the order is somehow created or retrieved
$order = new Order();
@@ -338,22 +333,22 @@ 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\KernelEvents;
- use Acme\Store\Event\OrderPlacedEvent;
class StoreSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
- return array(
- KernelEvents::RESPONSE => array(
- array('onKernelResponsePre', 10),
- array('onKernelResponsePost', -10),
- ),
+ return [
+ KernelEvents::RESPONSE => [
+ ['onKernelResponsePre', 10],
+ ['onKernelResponsePost', -10],
+ ],
OrderPlacedEvent::NAME => 'onStoreOrder',
- );
+ ];
}
public function onKernelResponsePre(FilterResponseEvent $event)
@@ -507,8 +502,7 @@ with some other dispatchers:
* :doc:`/components/event_dispatcher/container_aware_dispatcher`
* :doc:`/components/event_dispatcher/immutable_dispatcher`
-* :doc:`/components/event_dispatcher/traceable_dispatcher` (provided by the
- :doc:`HttpKernel component `)
+* :doc:`/components/event_dispatcher/traceable_dispatcher`
Learn More
----------
@@ -527,4 +521,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/container_aware_dispatcher.rst b/components/event_dispatcher/container_aware_dispatcher.rst
index ce330df6be9..a2d5e29a96a 100644
--- a/components/event_dispatcher/container_aware_dispatcher.rst
+++ b/components/event_dispatcher/container_aware_dispatcher.rst
@@ -4,6 +4,12 @@
The Container Aware Event Dispatcher
====================================
+.. deprecated:: 3.3
+
+ The ``ContainerAwareEventDispatcher`` class has been deprecated in Symfony 3.3
+ and will be removed in Symfony 4.0. Use ``EventDispatcher`` with
+ closure-proxy injection instead.
+
Introduction
------------
@@ -47,9 +53,9 @@ Adding Services
To connect existing service definitions, use the
:method:`Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher::addListenerService`
-method where the ``$callback`` is an array of ``array($serviceId, $methodName)``::
+method where the ``$callback`` is an array of ``[$serviceId, $methodName]``::
- $dispatcher->addListenerService($eventName, array('foo', 'logListener'));
+ $dispatcher->addListenerService($eventName, ['foo', 'logListener']);
Adding Subscriber Services
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -75,12 +81,12 @@ The ``EventSubscriberInterface`` is exactly as you would expect::
{
public static function getSubscribedEvents()
{
- return array(
- KernelEvents::RESPONSE => array(
- array('onKernelResponsePre', 10),
- array('onKernelResponsePost', 0),
+ return [
+ KernelEvents::RESPONSE => [
+ ['onKernelResponsePre', 10],
+ ['onKernelResponsePost', 0],
),
- 'store.order' => array('onStoreOrder', 0),
+ 'store.order' => ['onStoreOrder', 0],
);
}
diff --git a/components/event_dispatcher/generic_event.rst b/components/event_dispatcher/generic_event.rst
index 23d37f4de9c..42efd41b902 100644
--- a/components/event_dispatcher/generic_event.rst
+++ b/components/event_dispatcher/generic_event.rst
@@ -72,7 +72,7 @@ access the event arguments::
$event = new GenericEvent(
$subject,
- array('type' => 'foo', 'counter' => 0)
+ ['type' => 'foo', 'counter' => 0]
);
$dispatcher->dispatch('foo', $event);
@@ -92,7 +92,7 @@ Filtering data::
use Symfony\Component\EventDispatcher\GenericEvent;
- $event = new GenericEvent($subject, array('data' => 'Foo'));
+ $event = new GenericEvent($subject, ['data' => 'Foo']);
$dispatcher->dispatch('foo', $event);
class FooListener
diff --git a/components/event_dispatcher/immutable_dispatcher.rst b/components/event_dispatcher/immutable_dispatcher.rst
index 905d1db5b01..25940825065 100644
--- a/components/event_dispatcher/immutable_dispatcher.rst
+++ b/components/event_dispatcher/immutable_dispatcher.rst
@@ -12,9 +12,8 @@ The ``ImmutableEventDispatcher`` takes another event dispatcher with all
the listeners and subscribers. The immutable dispatcher is just a proxy
of this original dispatcher.
-To use it, first create a normal dispatcher (``EventDispatcher`` or
-``ContainerAwareEventDispatcher``) and register some listeners or
-subscribers::
+To use it, first create a normal ``EventDispatcher`` dispatcher and register
+some listeners or subscribers::
use Symfony\Component\EventDispatcher\EventDispatcher;
diff --git a/components/expression_language.rst b/components/expression_language.rst
index 9a55f3ed066..7bac54dba05 100644
--- a/components/expression_language.rst
+++ b/components/expression_language.rst
@@ -14,9 +14,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/expression-language
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require symfony/expression-language:^3.4
.. include:: /components/require_autoload.rst.inc
@@ -101,20 +99,33 @@ PHP type (including objects)::
var_dump($expressionLanguage->evaluate(
'fruit.variety',
- array(
+ [
'fruit' => $apple,
- )
- ));
+ ]
+ )); // displays "Honeycrisp"
-This will print "Honeycrisp". For more information, see the :doc:`/components/expression_language/syntax`
+For more information, see the :doc:`/components/expression_language/syntax`
entry, especially :ref:`component-expression-objects` and :ref:`component-expression-arrays`.
+.. caution::
+
+ When using variables in expressions, avoid passing untrusted data into the
+ array of variables. If you can't avoid that, sanitize non-alphanumeric
+ characters in untrusted data to prevent malicious users from injecting
+ control characters and altering the expression.
+
Caching
-------
The component provides some different caching strategies, read more about them
in :doc:`/components/expression_language/caching`.
+AST Dumping and Editing
+-----------------------
+
+The AST (*Abstract Syntax Tree*) of expressions can be dumped and manipulated
+as explained in :doc:`/components/expression_language/ast`.
+
Learn More
----------
@@ -125,5 +136,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/ast.rst b/components/expression_language/ast.rst
new file mode 100644
index 00000000000..0f15c20647a
--- /dev/null
+++ b/components/expression_language/ast.rst
@@ -0,0 +1,49 @@
+.. index::
+ single: AST; ExpressionLanguage
+ single: AST; Abstract Syntax Tree
+
+Dumping and Manipulating the AST of Expressions
+===============================================
+
+Manipulating or inspecting the expressions created with the ExpressionLanguage
+component is difficult because they are plain strings. A better approach is to
+turn those expressions into an AST. In computer science, `AST`_ (*Abstract
+Syntax Tree*) is *"a tree representation of the structure of source code written
+in a programming language"*. In Symfony, a ExpressionLanguage AST is a set of
+nodes that contain PHP classes representing the given expression.
+
+Dumping the AST
+---------------
+
+Call the :method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::getNodes`
+method after parsing any expression to get its AST::
+
+ use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+
+ $ast = (new ExpressionLanguage())
+ ->parse('1 + 2', [])
+ ->getNodes()
+ ;
+
+ // dump the AST nodes for inspection
+ var_dump($ast);
+
+ // dump the AST nodes as a string representation
+ $astAsString = $ast->dump();
+
+Manipulating the AST
+--------------------
+
+The nodes of the AST can also be dumped into a PHP array of nodes to allow
+manipulating them. Call the :method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::toArray`
+method to turn the AST into an array::
+
+ // ...
+
+ $astAsArray = (new ExpressionLanguage())
+ ->parse('1 + 2', [])
+ ->getNodes()
+ ->toArray()
+ ;
+
+.. _`AST`: https://en.wikipedia.org/wiki/Abstract_syntax_tree
diff --git a/components/expression_language/caching.rst b/components/expression_language/caching.rst
index dbb5a1310b3..4f24942a559 100644
--- a/components/expression_language/caching.rst
+++ b/components/expression_language/caching.rst
@@ -25,25 +25,29 @@ The ``evaluate()`` method needs to loop through the "nodes" (pieces of an
expression saved in the ``ParsedExpression``) and evaluate them on the fly.
To save time, the ``ExpressionLanguage`` caches the ``ParsedExpression`` so
-it can skip the tokenize and parse steps with duplicate expressions.
-The caching is done by a
-:class:`Symfony\\Component\\ExpressionLanguage\\ParserCache\\ParserCacheInterface`
-instance (by default, it uses an
-:class:`Symfony\\Component\\ExpressionLanguage\\ParserCache\\ArrayParserCache`).
-You can customize this by creating a custom ``ParserCache`` and injecting this
-in the object using the constructor::
+it can skip the tokenize and parse steps with duplicate expressions. The
+caching is done by a PSR-6 `CacheItemPoolInterface`_ instance (by default, it
+uses an :class:`Symfony\\Component\\Cache\\Adapter\\ArrayAdapter`). You can
+customize this by creating a custom cache pool or using one of the available
+ones and injecting this using the constructor::
+ use Symfony\Component\Cache\Adapter\RedisAdapter;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
- use Acme\ExpressionLanguage\ParserCache\MyDatabaseParserCache;
- $databaseParserCache = new MyDatabaseParserCache(...);
- $expressionLanguage = new ExpressionLanguage($databaseParserCache);
+ $cache = new RedisAdapter(...);
+ $expressionLanguage = new ExpressionLanguage($cache);
-.. note::
+.. versionadded:: 3.2
- The `DoctrineBridge`_ provides a Parser Cache implementation using the
- `doctrine cache library`_, which gives you caching for all sorts of cache
- strategies, like Apc, Filesystem and Memcached.
+ PSR-6 caching support was introduced in Symfony 3.2. Prior to version 3.2,
+ a
+ :class:`Symfony\\Component\\ExpressionLanguage\\ParserCache\\ParserCacheInterface`
+ instance had to be injected.
+
+.. seealso::
+
+ See the :doc:`/components/cache` documentation for more information about
+ available cache adapters.
Using Parsed and Serialized Expressions
---------------------------------------
@@ -54,7 +58,7 @@ Both ``evaluate()`` and ``compile()`` can handle ``ParsedExpression`` and
// ...
// the parse() method returns a ParsedExpression
- $expression = $expressionLanguage->parse('1 + 4', array());
+ $expression = $expressionLanguage->parse('1 + 4', []);
var_dump($expressionLanguage->evaluate($expression)); // prints 5
@@ -65,10 +69,9 @@ Both ``evaluate()`` and ``compile()`` can handle ``ParsedExpression`` and
$expression = new SerializedParsedExpression(
'1 + 4',
- serialize($expressionLanguage->parse('1 + 4', array())->getNodes())
+ serialize($expressionLanguage->parse('1 + 4', [])->getNodes())
);
var_dump($expressionLanguage->evaluate($expression)); // prints 5
-.. _DoctrineBridge: https://github.com/symfony/doctrine-bridge
-.. _`doctrine cache library`: http://docs.doctrine-project.org/projects/doctrine-common/en/latest/reference/caching.html
+.. _`CacheItemPoolInterface`: https://github.com/php-fig/cache/blob/master/src/CacheItemPoolInterface.php
diff --git a/components/expression_language/extending.rst b/components/expression_language/extending.rst
index 04cc0ba573f..b34af3e2c30 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;
@@ -49,7 +49,7 @@ This method has 3 arguments:
In addition to the custom function arguments, the **evaluator** is passed an
``arguments`` variable as its first argument, which is equal to the second
-argument of ``compile()`` (e.g. the "values" when evaluating an expression).
+argument of ``evaluate()`` (e.g. the "values" when evaluating an expression).
.. _components-expression-language-provider:
@@ -74,7 +74,7 @@ register::
{
public function getFunctions()
{
- return array(
+ return [
new ExpressionFunction('lowercase', function ($str) {
return sprintf('(is_string(%1$s) ? strtolower(%1$s) : %1$s)', $str);
}, function ($arguments, $str) {
@@ -84,10 +84,26 @@ register::
return strtolower($str);
}),
- );
+ ];
}
}
+.. tip::
+
+ To create an expression function from a PHP function with the
+ :method:`Symfony\\Component\\ExpressionLanguage\\ExpressionFunction::fromPhp` static method::
+
+ ExpressionFunction::fromPhp('strtoupper');
+
+ Namespaced functions are supported, but they require a second argument to
+ define the name of the expression::
+
+ ExpressionFunction::fromPhp('My\strtoupper', 'my_strtoupper');
+
+ .. versionadded:: 3.3
+
+ The ``ExpressionFunction::fromPhp()`` method was introduced in Symfony 3.3.
+
You can register providers using
:method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::registerProvider`
or by using the second argument of the constructor::
@@ -95,10 +111,10 @@ or by using the second argument of the constructor::
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
// using the constructor
- $expressionLanguage = new ExpressionLanguage(null, array(
+ $expressionLanguage = new ExpressionLanguage(null, [
new StringExpressionLanguageProvider(),
// ...
- ));
+ ]);
// using registerProvider()
$expressionLanguage->registerProvider(new StringExpressionLanguageProvider());
@@ -113,9 +129,9 @@ or by using the second argument of the constructor::
class ExpressionLanguage extends BaseExpressionLanguage
{
- public function __construct(ParserCacheInterface $parser = null, array $providers = array())
+ public function __construct(ParserCacheInterface $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/expression_language/syntax.rst b/components/expression_language/syntax.rst
index dae2c8c517a..ca220061b36 100644
--- a/components/expression_language/syntax.rst
+++ b/components/expression_language/syntax.rst
@@ -56,9 +56,9 @@ to JavaScript::
var_dump($expressionLanguage->evaluate(
'fruit.variety',
- array(
+ [
'fruit' => $apple,
- )
+ ]
));
This will print out ``Honeycrisp``.
@@ -73,7 +73,7 @@ JavaScript::
{
public function sayHi($times)
{
- $greetings = array();
+ $greetings = [];
for ($i = 0; $i < $times; $i++) {
$greetings[] = 'Hi';
}
@@ -86,9 +86,9 @@ JavaScript::
var_dump($expressionLanguage->evaluate(
'robot.sayHi(3)',
- array(
+ [
'robot' => $robot,
- )
+ ]
));
This will print out ``Hi Hi Hi!``.
@@ -124,13 +124,13 @@ Working with Arrays
If you pass an array into an expression, use the ``[]`` syntax to access
array keys, similar to JavaScript::
- $data = array('life' => 10, 'universe' => 10, 'everything' => 22);
+ $data = ['life' => 10, 'universe' => 10, 'everything' => 22];
var_dump($expressionLanguage->evaluate(
'data["life"] + data["universe"] + data["everything"]',
- array(
+ [
'data' => $data,
- )
+ ]
));
This will print out ``42``.
@@ -154,11 +154,11 @@ For example::
var_dump($expressionLanguage->evaluate(
'life + universe + everything',
- array(
+ [
'life' => 10,
'universe' => 10,
'everything' => 22,
- )
+ ]
));
This will print out ``42``.
@@ -197,20 +197,20 @@ Examples::
$ret1 = $expressionLanguage->evaluate(
'life == everything',
- array(
+ [
'life' => 10,
'universe' => 10,
'everything' => 22,
- )
+ ]
);
$ret2 = $expressionLanguage->evaluate(
'life > everything',
- array(
+ [
'life' => 10,
'universe' => 10,
'everything' => 22,
- )
+ ]
);
Both variables would be set to ``false``.
@@ -226,11 +226,11 @@ For example::
$ret = $expressionLanguage->evaluate(
'life < universe or life < everything',
- array(
+ [
'life' => 10,
'universe' => 10,
'everything' => 22,
- )
+ ]
);
This ``$ret`` variable will be set to ``true``.
@@ -244,10 +244,10 @@ For example::
var_dump($expressionLanguage->evaluate(
'firstName~" "~lastName',
- array(
+ [
'firstName' => 'Arthur',
'lastName' => 'Dent',
- )
+ ]
));
This would print out ``Arthur Dent``.
@@ -270,9 +270,9 @@ For example::
$inGroup = $expressionLanguage->evaluate(
'user.group in ["human_resources", "marketing"]',
- array(
+ [
'user' => $user,
- )
+ ]
);
The ``$inGroup`` would evaluate to ``true``.
@@ -294,9 +294,9 @@ For example::
$expressionLanguage->evaluate(
'user.age in 18..45',
- array(
+ [
'user' => $user,
- )
+ ]
);
This will evaluate to ``true``, because ``user.age`` is in the range from
@@ -308,3 +308,14 @@ Ternary Operators
* ``foo ? 'yes' : 'no'``
* ``foo ?: 'no'`` (equal to ``foo ? foo : 'no'``)
* ``foo ? 'yes'`` (equal to ``foo ? 'yes' : ''``)
+
+Built-in Objects and Variables
+------------------------------
+
+When using this component inside a Symfony application, certain objects and
+variables are automatically injected by Symfony so you can use them in your
+expressions (e.g. the request, the current user, etc.):
+
+* :doc:`Variables available in security expressions `;
+* :doc:`Variables available in service container expressions `;
+* :doc:`Variables available in routing expressions `.
diff --git a/components/filesystem.rst b/components/filesystem.rst
index a9deacfc2be..e27a0b64684 100644
--- a/components/filesystem.rst
+++ b/components/filesystem.rst
@@ -6,19 +6,12 @@ The Filesystem Component
The Filesystem component provides basic utilities for the filesystem.
-.. tip::
-
- The lock handler feature was introduced in symfony 2.6.
- :doc:`See the documentation for more information `.
-
Installation
------------
.. code-block:: terminal
- $ composer require symfony/filesystem
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require symfony/filesystem:^3.4
.. include:: /components/require_autoload.rst.inc
@@ -28,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();
}
@@ -51,14 +44,14 @@ endpoint for filesystem operations::
string, an array or any object implementing :phpclass:`Traversable` as
the target argument.
-mkdir
-~~~~~
+``mkdir``
+~~~~~~~~~
:method:`Symfony\\Component\\Filesystem\\Filesystem::mkdir` creates a directory recursively.
On POSIX filesystems, directories are created with a default mode value
`0777`. You can use the second argument to set your own mode::
- $fileSystem->mkdir('/tmp/photos', 0700);
+ $filesystem->mkdir('/tmp/photos', 0700);
.. note::
@@ -72,31 +65,31 @@ On POSIX filesystems, directories are created with a default mode value
.. note::
The directory permissions are affected by the current `umask`_.
- Set the umask for your webserver, use PHP's :phpfunction:`umask`
+ Set the ``umask`` for your webserver, use PHP's :phpfunction:`umask`
function or use the :phpfunction:`chmod` function after the
directory has been created.
-exists
-~~~~~~
+``exists``
+~~~~~~~~~~
:method:`Symfony\\Component\\Filesystem\\Filesystem::exists` checks for the
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(array('rabbit.jpg', 'bottle.png'));
+ $filesystem->exists(['rabbit.jpg', 'bottle.png']);
.. note::
You can pass an array or any :phpclass:`Traversable` object as the first
argument.
-copy
-~~~~
+``copy``
+~~~~~~~~
:method:`Symfony\\Component\\Filesystem\\Filesystem::copy` makes a copy of a
single file (use :method:`Symfony\\Component\\Filesystem\\Filesystem::mirror` to
@@ -105,159 +98,203 @@ 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
-~~~~~
+``touch``
+~~~~~~~~~
:method:`Symfony\\Component\\Filesystem\\Filesystem::touch` sets access and
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::
You can pass an array or any :phpclass:`Traversable` object as the first
argument.
-chown
-~~~~~
+``chown``
+~~~~~~~~~
:method:`Symfony\\Component\\Filesystem\\Filesystem::chown` changes the owner of
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::
You can pass an array or any :phpclass:`Traversable` object as the first
argument.
-chgrp
-~~~~~
+``chgrp``
+~~~~~~~~~
:method:`Symfony\\Component\\Filesystem\\Filesystem::chgrp` changes the group of
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::
You can pass an array or any :phpclass:`Traversable` object as the first
argument.
-chmod
-~~~~~
+``chmod``
+~~~~~~~~~
:method:`Symfony\\Component\\Filesystem\\Filesystem::chmod` changes the mode or
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::
You can pass an array or any :phpclass:`Traversable` object as the first
argument.
-remove
-~~~~~~
+``remove``
+~~~~~~~~~~
:method:`Symfony\\Component\\Filesystem\\Filesystem::remove` deletes files,
directories and symlinks::
- $fileSystem->remove(array('symlink', '/path/to/directory', 'activity.log'));
+ $filesystem->remove(['symlink', '/path/to/directory', 'activity.log']);
.. note::
You can pass an array or any :phpclass:`Traversable` object as the first
argument.
-rename
-~~~~~~
+``rename``
+~~~~~~~~~~
:method:`Symfony\\Component\\Filesystem\\Filesystem::rename` changes the name
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');
+ // if the target already exists, a third boolean argument is available to overwrite.
+ $filesystem->rename('/tmp/processed_video2.ogg', '/path/to/store/video_647.ogg', true);
-symlink
-~~~~~~~
+``symlink``
+~~~~~~~~~~~
:method:`Symfony\\Component\\Filesystem\\Filesystem::symlink` creates a
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);
-makePathRelative
-~~~~~~~~~~~~~~~~
+``readlink``
+~~~~~~~~~~~~
+
+.. versionadded:: 3.2
+
+ The :method:`Symfony\\Component\\Filesystem\\Filesystem::readlink` method was introduced in Symfony 3.2.
+
+:method:`Symfony\\Component\\Filesystem\\Filesystem::readlink` read links targets.
+
+PHP's :phpfunction:`readlink` function returns the target of a symbolic link. However, its behavior
+is completely different under Windows and Unix. On Windows systems, ``readlink()``
+resolves recursively the children links of a link until a final target is found. On
+Unix-based systems ``readlink()`` only resolves the next link.
+
+The :method:`Symfony\\Component\\Filesystem\\Filesystem::readlink` method provided
+by the Filesystem component always behaves in the same way::
+
+ // returns the next direct target of the link without considering the existence of the target
+ $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);
+
+Its behavior is the following::
+
+ public function readlink($path, $canonicalize = false)
+
+* When ``$canonicalize`` is ``false``:
+ * if ``$path`` does not exist or is not a link, it returns ``null``.
+ * if ``$path`` is a link, it returns the next direct target of the link without considering the existence of the target.
+
+* When ``$canonicalize`` is ``true``:
+ * if ``$path`` does not exist, it returns null.
+ * if ``$path`` exists, it returns its absolute fully resolved final version.
+
+``makePathRelative``
+~~~~~~~~~~~~~~~~~~~~
:method:`Symfony\\Component\\Filesystem\\Filesystem::makePathRelative` takes two
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
-~~~~~~
+``mirror``
+~~~~~~~~~~
:method:`Symfony\\Component\\Filesystem\\Filesystem::mirror` copies all the
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
-~~~~~~~~~~~~~~
+``isAbsolutePath``
+~~~~~~~~~~~~~~~~~~
:method:`Symfony\\Component\\Filesystem\\Filesystem::isAbsolutePath` returns
``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');
-dumpFile
-~~~~~~~~
+``tempnam``
+~~~~~~~~~~~
+
+:method:`Symfony\\Component\\Filesystem\\Filesystem::tempnam` creates a
+temporary file with a unique filename, and returns its path, or throw an
+exception on failure::
+
+ // returns a path like : /tmp/prefix_wyjgtF
+ $filesystem->tempnam('/tmp', 'prefix_');
-.. versionadded:: 2.3
- The ``dumpFile()`` was introduced in Symfony 2.3.
+``dumpFile``
+~~~~~~~~~~~~
:method:`Symfony\\Component\\Filesystem\\Filesystem::dumpFile` saves the given
contents into a file. It does this in an atomic manner: it writes a temporary
@@ -265,10 +302,26 @@ 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.
+``appendToFile``
+~~~~~~~~~~~~~~~~
+
+.. versionadded:: 3.3
+
+ The :method:`Symfony\\Component\\Filesystem\\Filesystem::appendToFile`
+ method was introduced in Symfony 3.3.
+
+: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');
+
+If either the file or its containing directory doesn't exist, this method
+creates them before appending the contents.
+
Error Handling
--------------
@@ -290,5 +343,4 @@ Learn More
filesystem/*
-.. _`Packagist`: https://packagist.org/packages/symfony/filesystem
.. _`umask`: https://en.wikipedia.org/wiki/Umask
diff --git a/components/filesystem/lock_handler.rst b/components/filesystem/lock_handler.rst
index 812c82d0dd0..28eaf3a6ba8 100644
--- a/components/filesystem/lock_handler.rst
+++ b/components/filesystem/lock_handler.rst
@@ -1,6 +1,12 @@
LockHandler
===========
+.. deprecated:: 3.4
+
+ The ``LockHandler`` class was deprecated in Symfony 3.4 and will be
+ removed in Symfony 4.0. Use :ref:`SemaphoreStore `
+ or :ref:`FlockStore ` from the Lock component instead.
+
What is a Lock?
---------------
@@ -76,7 +82,7 @@ PHP code will wait indefinitely until the lock is released by another process.
use Symfony\Component\Filesystem\LockHandler;
- if (!(new LockHandler('hello.lock'))->lock()) {
+ if (!(new LockHandler('hello.lock'))->lock()) {
// the resource "hello" is already locked by another process
return 0;
diff --git a/components/finder.rst b/components/finder.rst
index d54c476f286..d502086b4d6 100644
--- a/components/finder.rst
+++ b/components/finder.rst
@@ -13,9 +13,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/finder
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require symfony/finder:^3.4
.. include:: /components/require_autoload.rst.inc
@@ -60,7 +58,7 @@ the Finder instance.
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::
@@ -76,7 +74,13 @@ the Finder instance.
Criteria
--------
-There are lots of ways to filter and sort your results.
+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.
+
+.. versionadded:: 3.4
+
+ The ``hasResults()`` method was introduced in Symfony 3.4.
Location
~~~~~~~~
@@ -90,7 +94,7 @@ Search in several locations by chaining calls to
:method:`Symfony\\Component\\Finder\\Finder::in`::
// search inside *both* directories
- $finder->in(array(__DIR__, '/elsewhere'));
+ $finder->in([__DIR__, '/elsewhere']);
// same as above
$finder->in(__DIR__)->in('/elsewhere');
@@ -107,10 +111,6 @@ Exclude directories from matching with the
// directories passed as argument must be relative to the ones defined with the in() method
$finder->in(__DIR__)->exclude('ruby');
-.. versionadded:: 2.3
- The :method:`Symfony\\Component\\Finder\\Finder::ignoreUnreadableDirs`
- method was introduced in Symfony 2.3.
-
It's also possible to ignore directories that you don't have permission to read::
$finder->ignoreUnreadableDirs()->in(__DIR__);
@@ -144,9 +144,7 @@ And it also works with user-defined streams::
Files or Directories
~~~~~~~~~~~~~~~~~~~~
-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::
+By default, the Finder returns both files and directories. If you need to find either files or directories only, use the :method:`Symfony\\Component\\Finder\\Finder::files` and :method:`Symfony\\Component\\Finder\\Finder::directories` methods::
$finder->files();
@@ -334,4 +332,3 @@ The contents of returned files can be read with
.. _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
diff --git a/components/form.rst b/components/form.rst
index 304429568d8..9821381c32b 100644
--- a/components/form.rst
+++ b/components/form.rst
@@ -5,7 +5,7 @@
The Form Component
==================
- The Form component allows you to easily create, process and reuse forms.
+ The Form component allows you to create, process and reuse forms.
The Form component is a tool to help you solve the problem of allowing end-users
to interact with the data and modify the data in your application. And though
@@ -18,9 +18,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/form
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require symfony/form:^3.4
.. include:: /components/require_autoload.rst.inc
@@ -56,8 +54,8 @@ support for very important features:
The Symfony Form component relies on other libraries to solve these problems.
Most of the time you will use Twig and the Symfony
:doc:`HttpFoundation `,
-Translation and Validator components, but you can replace any of these with
-a different library of your choice.
+:doc:`Translation ` and :doc:`Validator `
+components, but you can replace any of these with a different library of your choice.
The following sections explain how to plug these libraries into the form
factory.
@@ -69,9 +67,6 @@ factory.
Request Handling
~~~~~~~~~~~~~~~~
-.. versionadded:: 2.3
- The ``handleRequest()`` method was introduced in Symfony 2.3.
-
To process form data, you'll need to call the :method:`Symfony\\Component\\Form\\Form::handleRequest`
method::
@@ -93,8 +88,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())
@@ -119,16 +114,16 @@ use the built-in support, first install the Security CSRF component:
.. code-block:: terminal
- $ composer require symfony/security-csrf
+ $ composer require symfony/security-csrf:^3.4
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();
@@ -159,7 +154,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();
@@ -167,29 +162,31 @@ 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 easily 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:
.. code-block:: terminal
- $ composer require symfony/twig-bridge
+ $ composer require symfony/twig-bridge:^3.4
The TwigBridge integration provides you with several :doc:`Twig Functions `
that help you render the HTML widget, label and error 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\Bridge\Twig\Form\TwigRenderer;
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;
// the Twig file that holds all the default markup for rendering forms
// this file comes with TwigBridge
@@ -203,25 +200,31 @@ to bootstrap or access Twig and add the :class:`Symfony\\Bridge\\Twig\\Extension
// the path to your other templates
$viewsDirectory = realpath(__DIR__.'/../views');
- $twig = new Environment(new FilesystemLoader(array(
+ $twig = new Environment(new FilesystemLoader([
$viewsDirectory,
$vendorTwigBridgeDirectory.'/Resources/views/Form',
- )));
- $formEngine = new TwigRendererEngine(array($defaultFormTheme));
- $formEngine->setEnvironment($twig);
+ ]));
+ $formEngine = new TwigRendererEngine([$defaultFormTheme], $twig);
+ $twig->addRuntimeLoader(new FactoryRuntimeLoader([
+ FormRenderer::class => function () use ($formEngine, $csrfManager) {
+ return new FormRenderer($formEngine, $csrfManager);
+ },
+ ]));
// ... (see the previous CSRF Protection section for more information)
// adds the FormExtension to Twig
- $twig->addExtension(
- new FormExtension(new TwigRenderer($formEngine, $csrfManager))
- );
+ $twig->addExtension(new FormExtension());
// creates a form factory
$formFactory = Forms::createFormFactoryBuilder()
// ...
->getFormFactory();
+.. versionadded:: 1.30
+
+ The ``Twig\RuntimeLoader\FactoryRuntimeLoader`` was introduced in Twig 1.30.
+
The exact details of your `Twig Configuration`_ will vary, but the goal is
always to add the :class:`Symfony\\Bridge\\Twig\\Extension\\FormExtension`
to Twig, which gives you access to the Twig functions for rendering forms.
@@ -257,15 +260,15 @@ installed:
.. code-block:: terminal
- $ composer require symfony/translation symfony/config
+ $ composer require symfony/translation symfony/config:^3.4
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');
@@ -302,7 +305,7 @@ it's installed in your application:
.. code-block:: terminal
- $ composer require symfony/validator
+ $ composer require symfony/validator:^3.4
If you're not familiar with Symfony's Validator component, read more about
it: :doc:`/validation`. The Form component comes with a
@@ -312,8 +315,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');
@@ -398,14 +401,12 @@ is created from the form factory.
$form = $formFactory->createBuilder()
->add('task', TextType::class)
- // If you use PHP 5.3 or 5.4, you must use
- // ->add('task', 'Symfony\Component\Form\Extension\Core\Type\TextType')
->add('dueDate', DateType::class)
->getForm();
- var_dump($twig->render('new.html.twig', array(
+ var_dump($twig->render('new.html.twig', [
'form' => $form->createView(),
- )));
+ ]));
.. code-block:: php-symfony
@@ -426,21 +427,18 @@ is created from the form factory.
$form = $this->createFormBuilder()
->add('task', TextType::class)
- // If you use PHP 5.3 or 5.4, you must use
- // ->add('task', 'Symfony\Component\Form\Extension\Core\Type\TextType')
->add('dueDate', DateType::class)
->getForm();
- return $this->render('@AcmeTask/Default/new.html.twig', array(
+ return $this->render('@AcmeTask/Default/new.html.twig', [
'form' => $form->createView(),
- ));
+ ]);
}
}
As you can see, creating a form is like writing a recipe: you call ``add()``
for each new field you want to create. The first argument to ``add()`` is the
-name of your field, and the second is the fully qualified class name. If you
-use PHP 5.5 or above, you can use ``::class`` constant of a form type. The Form
+name of your field, and the second is the fully qualified class name. The Form
component comes with a lot of :doc:`built-in types `.
Now that you've built your form, learn how to :ref:`render `
@@ -463,9 +461,9 @@ builder:
// ...
- $defaults = array(
+ $defaults = [
'dueDate' => new \DateTime('tomorrow'),
- );
+ ];
$form = $formFactory->createBuilder(FormType::class, $defaults)
->add('task', TextType::class)
@@ -485,9 +483,9 @@ builder:
{
public function newAction(Request $request)
{
- $defaults = array(
+ $defaults = [
'dueDate' => new \DateTime('tomorrow'),
- );
+ ];
$form = $this->createFormBuilder($defaults)
->add('task', TextType::class)
@@ -519,7 +517,7 @@ helper functions:
{{ form_start(form) }}
{{ form_widget(form) }}
-
+
{{ form_end(form) }}
.. image:: /_images/form/simple-form.png
@@ -534,14 +532,10 @@ to do that in the ":doc:`/form/rendering`" section.
Changing a Form's Method and Action
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.. versionadded:: 2.3
- The ability to configure the form method and action was introduced in
- Symfony 2.3.
-
By default, a form is submitted to the same URI that rendered the form with
an HTTP POST request. This behavior can be changed using the :ref:`form-option-action`
and :ref:`form-option-method` options (the ``method`` option is also used
-by ``handleRequest()`` to determine whether a form has been submitted):
+by :method:`Symfony\\Component\\Form\\Form::handleRequest` to determine whether a form has been submitted):
.. configuration-block::
@@ -551,10 +545,10 @@ by ``handleRequest()`` to determine whether a form has been submitted):
// ...
- $formBuilder = $formFactory->createBuilder(FormType::class, null, array(
+ $formBuilder = $formFactory->createBuilder(FormType::class, null, [
'action' => '/search',
'method' => 'GET',
- ));
+ ]);
// ...
@@ -570,10 +564,10 @@ by ``handleRequest()`` to determine whether a form has been submitted):
{
public function searchAction()
{
- $formBuilder = $this->createFormBuilder(null, array(
+ $formBuilder = $this->createFormBuilder(null, [
'action' => '/search',
'method' => 'GET',
- ));
+ ]);
// ...
}
@@ -657,15 +651,15 @@ This defines a common form "workflow", which contains 3 different possibilities:
1) On the initial GET request (i.e. when the user "surfs" to your page),
build your form and render it;
-If the request is a POST, process the submitted data (via ``handleRequest()``).
+If the request is a POST, process the submitted data (via :method:`Symfony\\Component\\Form\\Form::handleRequest`).
Then:
2) if the form is invalid, re-render the form (which will now contain errors);
3) if the form is valid, perform some action and redirect.
Luckily, you don't need to decide whether or not a form has been submitted.
-Just pass the current request to the ``handleRequest()`` method. Then, the Form
-component will do all the necessary work for you.
+Just pass the current request to the :method:`Symfony\\Component\\Form\\Form::handleRequest`
+method. Then, the Form component will do all the necessary work for you.
.. _component-form-intro-validation:
@@ -685,15 +679,15 @@ option when building each field:
use Symfony\Component\Form\Extension\Core\Type\DateType;
$form = $formFactory->createBuilder()
- ->add('task', TextType::class, array(
+ ->add('task', TextType::class, [
'constraints' => new NotBlank(),
- ))
- ->add('dueDate', DateType::class, array(
- 'constraints' => array(
+ ])
+ ->add('dueDate', DateType::class, [
+ 'constraints' => [
new NotBlank(),
new Type(\DateTime::class),
- )
- ))
+ ]
+ ])
->getForm();
.. code-block:: php-symfony
@@ -712,15 +706,15 @@ option when building each field:
public function newAction(Request $request)
{
$form = $this->createFormBuilder()
- ->add('task', TextType::class, array(
+ ->add('task', TextType::class, [
'constraints' => new NotBlank(),
- ))
- ->add('dueDate', DateType::class, array(
- 'constraints' => array(
+ ])
+ ->add('dueDate', DateType::class, [
+ 'constraints' => [
new NotBlank(),
new Type(\DateTime::class),
- )
- ))
+ ]
+ ])
->getForm();
// ...
}
@@ -760,17 +754,6 @@ method to access the list of errors. It returns a
// a FormErrorIterator instance representing the form tree structure
$errors = $form->getErrors(true, false);
-.. tip::
-
- In older Symfony versions, ``getErrors()`` returned an array. To use the
- errors the same way in Symfony 2.5 or newer, you have to pass them to
- PHP's :phpfunction:`iterator_to_array` function::
-
- $errorsAsArray = iterator_to_array($form->getErrors());
-
- This is useful, for example, if you want to use PHP's ``array_*()`` function
- on the form errors.
-
Learn more
----------
@@ -780,6 +763,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_foundation.rst b/components/http_foundation.rst
index a37025b148d..b2ebc92e1ea 100644
--- a/components/http_foundation.rst
+++ b/components/http_foundation.rst
@@ -21,9 +21,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/http-foundation
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require symfony/http-foundation:^3.4
.. include:: /components/require_autoload.rst.inc
@@ -53,7 +51,7 @@ which is almost equivalent to the more verbose, but also more flexible,
$request = new Request(
$_GET,
$_POST,
- array(),
+ [],
$_COOKIE,
$_FILES,
$_SERVER
@@ -136,9 +134,6 @@ has some methods to filter the input values:
:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::getBoolean`
Returns the parameter value converted to boolean;
- .. versionadded:: 2.6
- The ``getBoolean()`` method was introduced in Symfony 2.6.
-
:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::getDigits`
Returns the digits of the parameter value;
@@ -148,7 +143,7 @@ has some methods to filter the input values:
:method:`Symfony\\Component\\HttpFoundation\\ParameterBag::filter`
Filters the parameter by using the PHP :phpfunction:`filter_var` function.
-All getters take up to three arguments: the first one is the parameter name
+All getters take up to two arguments: the first one is the parameter name
and the second one is the default value to return if the parameter does not
exist::
@@ -170,7 +165,7 @@ When PHP imports the request query, it handles request parameters like
// the query string is '?foo[bar]=baz'
$request->query->get('foo');
- // returns array('bar' => 'baz')
+ // returns ['bar' => 'baz']
$request->query->get('foo[bar]');
// returns null
@@ -214,7 +209,7 @@ a request::
$request = Request::create(
'/hello-world',
'GET',
- array('name' => 'Fabien')
+ ['name' => 'Fabien']
);
The :method:`Symfony\\Component\\HttpFoundation\\Request::create` method
@@ -248,7 +243,7 @@ the previous requests.
Accessing ``Accept-*`` Headers Data
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-You can easily access basic data extracted from ``Accept-*`` headers
+You can access basic data extracted from ``Accept-*`` headers
by using the following methods:
:method:`Symfony\\Component\\HttpFoundation\\Request::getAcceptableContentTypes`
@@ -300,12 +295,12 @@ PHP callable that is able to create an instance of your ``Request`` class::
use Symfony\Component\HttpFoundation\Request;
Request::setFactory(function (
- array $query = array(),
- array $request = array(),
- array $attributes = array(),
- array $cookies = array(),
- array $files = array(),
- array $server = array(),
+ array $query = [],
+ array $request = [],
+ array $attributes = [],
+ array $cookies = [],
+ array $files = [],
+ array $server = [],
$content = null
) {
return new SpecialRequest(
@@ -336,7 +331,7 @@ code, and an array of HTTP headers::
$response = new Response(
'Content',
Response::HTTP_OK,
- array('content-type' => 'text/html')
+ ['content-type' => 'text/html']
);
This information can also be manipulated after the Response object creation::
@@ -389,36 +384,45 @@ method takes an instance of
You can clear a cookie via the
:method:`Symfony\\Component\\HttpFoundation\\ResponseHeaderBag::clearCookie` method.
+Note you can create a
+:class:`Symfony\\Component\\HttpFoundation\\Cookie` object from a raw header
+value using :method:`Symfony\\Component\\HttpFoundation\\Cookie::fromString`.
+
+.. versionadded:: 3.3
+
+ The ``Cookie::fromString()`` method was introduced in Symfony 3.3.
+
Managing the HTTP Cache
~~~~~~~~~~~~~~~~~~~~~~~
The :class:`Symfony\\Component\\HttpFoundation\\Response` class has a rich set
of methods to manipulate the HTTP headers related to the cache:
-* :method:`Symfony\\Component\\HttpFoundation\\Response::setPublic`;
-* :method:`Symfony\\Component\\HttpFoundation\\Response::setPrivate`;
-* :method:`Symfony\\Component\\HttpFoundation\\Response::expire`;
-* :method:`Symfony\\Component\\HttpFoundation\\Response::setExpires`;
-* :method:`Symfony\\Component\\HttpFoundation\\Response::setMaxAge`;
-* :method:`Symfony\\Component\\HttpFoundation\\Response::setSharedMaxAge`;
-* :method:`Symfony\\Component\\HttpFoundation\\Response::setTtl`;
-* :method:`Symfony\\Component\\HttpFoundation\\Response::setClientTtl`;
-* :method:`Symfony\\Component\\HttpFoundation\\Response::setLastModified`;
-* :method:`Symfony\\Component\\HttpFoundation\\Response::setEtag`;
-* :method:`Symfony\\Component\\HttpFoundation\\Response::setVary`;
+* :method:`Symfony\\Component\\HttpFoundation\\Response::setPublic`
+* :method:`Symfony\\Component\\HttpFoundation\\Response::setPrivate`
+* :method:`Symfony\\Component\\HttpFoundation\\Response::expire`
+* :method:`Symfony\\Component\\HttpFoundation\\Response::setExpires`
+* :method:`Symfony\\Component\\HttpFoundation\\Response::setMaxAge`
+* :method:`Symfony\\Component\\HttpFoundation\\Response::setSharedMaxAge`
+* :method:`Symfony\\Component\\HttpFoundation\\Response::setTtl`
+* :method:`Symfony\\Component\\HttpFoundation\\Response::setClientTtl`
+* :method:`Symfony\\Component\\HttpFoundation\\Response::setLastModified`
+* :method:`Symfony\\Component\\HttpFoundation\\Response::setEtag`
+* :method:`Symfony\\Component\\HttpFoundation\\Response::setVary`
The :method:`Symfony\\Component\\HttpFoundation\\Response::setCache` method
can be used to set the most commonly used cache information in one method
call::
- $response->setCache(array(
+ $response->setCache([
'etag' => 'abcdef',
'last_modified' => new \DateTime(),
'max_age' => 600,
's_maxage' => 600,
'private' => false,
'public' => true,
- ));
+ 'immutable' => true,
+ ]);
To check if the Response validators (``ETag``, ``Last-Modified``) match a
conditional value specified in the client Request, use the
@@ -473,10 +477,10 @@ represented by a PHP callable instead of a string::
Additionally, PHP isn't the only layer that can buffer output. Your web
server might also buffer based on its configuration. Some servers, such as
- Nginx, let you disable buffering at the config level or by adding a special HTTP
+ nginx, let you disable buffering at the config level or by adding a special HTTP
header in the response::
- // disables FastCGI buffering in Nginx only for this response
+ // disables FastCGI buffering in nginx only for this response
$response->headers->set('X-Accel-Buffering', 'no')
.. _component-http-foundation-serving-files:
@@ -485,8 +489,8 @@ 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 easy, using
-non-ASCII filenames is more involving. The
+response. While creating this header for basic file downloads is straightforward,
+using non-ASCII filenames is more involved. The
:method:`Symfony\\Component\\HttpFoundation\\ResponseHeaderBag::makeDisposition`
abstracts the hard work behind a simple API::
@@ -513,13 +517,33 @@ Alternatively, if you are serving a static file, you can use a
The ``BinaryFileResponse`` will automatically handle ``Range`` and
``If-Range`` headers from the request. It also supports ``X-Sendfile``
-(see for `Nginx`_ and `Apache`_). To make use of it, you need to determine
+(see for `nginx`_ and `Apache`_). To make use of it, you need to determine
whether or not the ``X-Sendfile-Type`` header should be trusted and call
:method:`Symfony\\Component\\HttpFoundation\\BinaryFileResponse::trustXSendfileTypeHeader`
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``::
@@ -530,10 +554,25 @@ or change its ``Content-Disposition``::
'filename.txt'
);
-It is possible to delete the file after the request is sent with the
+It is possible to delete the file after the response is sent with the
:method:`Symfony\\Component\\HttpFoundation\\BinaryFileResponse::deleteFileAfterSend` method.
Please note that this will not work when the ``X-Sendfile`` header is set.
+.. versionadded:: 3.3
+
+ The ``Stream`` class was introduced in Symfony 3.3.
+
+If the size of the served file is unknown (e.g. because it's being generated on the fly,
+or because a PHP stream filter is registered on it, etc.), you can pass a ``Stream``
+instance to ``BinaryFileResponse``. This will disable ``Range`` and ``Content-Length``
+handling, switching to chunked encoding instead::
+
+ use Symfony\Component\HttpFoundation\BinaryFileResponse;
+ use Symfony\Component\HttpFoundation\File\Stream;
+
+ $stream = new Stream('path/to/stream');
+ $response = new BinaryFileResponse($stream);
+
.. note::
If you *just* created the file during this same request, the file *may* be sent
@@ -553,9 +592,9 @@ right content and headers. A JSON response might look like this::
use Symfony\Component\HttpFoundation\Response;
$response = new Response();
- $response->setContent(json_encode(array(
+ $response->setContent(json_encode([
'data' => 123,
- )));
+ ]));
$response->headers->set('Content-Type', 'application/json');
There is also a helpful :class:`Symfony\\Component\\HttpFoundation\\JsonResponse`
@@ -563,13 +602,24 @@ class, which can make this even easier::
use Symfony\Component\HttpFoundation\JsonResponse;
+ // if you know the data to send when creating the response
+ $response = new JsonResponse(['data' => 123]);
+
+ // if you don't know the data to send when creating the response
$response = new JsonResponse();
- $response->setData(array(
- 'data' => 123,
- ));
+ // ...
+ $response->setData(['data' => 123]);
+
+ // if the data to send is already encoded in JSON
+ $response = JsonResponse::fromJsonString('{ "data": 123 }');
+
+.. versionadded:: 3.2
+
+ The :method:`Symfony\\Component\\HttpFoundation\\JsonResponse::fromJsonString`
+ method was introduced in Symfony 3.2.
-This encodes your array of data to JSON and sets the ``Content-Type`` header
-to ``application/json``.
+The ``JsonResponse`` class sets the ``Content-Type`` header to
+``application/json`` and encodes your data to JSON when needed.
.. caution::
@@ -615,8 +665,7 @@ Learn More
/session/*
/http_cache/*
-.. _Packagist: https://packagist.org/packages/symfony/http-foundation
-.. _Nginx: https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile/
+.. _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
-.. _OWASP guidelines: https://www.owasp.org/index.php/OWASP_AJAX_Security_Guidelines#Always_return_JSON_with_an_Object_on_the_outside
+.. _OWASP guidelines: https://cheatsheetseries.owasp.org/cheatsheets/AJAX_Security_Cheat_Sheet.html#always-return-json-with-an-object-on-the-outside
diff --git a/components/http_foundation/session_configuration.rst b/components/http_foundation/session_configuration.rst
index 5ad54f7f132..8f8ce6e8ce7 100644
--- a/components/http_foundation/session_configuration.rst
+++ b/components/http_foundation/session_configuration.rst
@@ -23,7 +23,7 @@ Native PHP Save Handlers
------------------------
So-called native handlers, are save handlers which are either compiled into
-PHP or provided by PHP extensions, such as PHP-Sqlite, PHP-Memcached and so on.
+PHP or provided by PHP extensions, such as PHP-SQLite, PHP-Memcached and so on.
All native save handlers are internal to PHP and as such, have no public facing API.
They must be configured by ``php.ini`` directives, usually ``session.save_path`` and
@@ -42,10 +42,10 @@ 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(array(), new NativeFileSessionHandler());
+ $sessionStorage = new NativeSessionStorage([], new NativeFileSessionHandler());
$session = new Session($sessionStorage);
.. note::
@@ -69,7 +69,7 @@ handlers by providing six callback functions which PHP calls internally at
various points in the session workflow.
The Symfony HttpFoundation component provides some by default and these can
-easily serve as examples if you wish to write your own.
+serve as examples if you wish to write your own.
* :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\PdoSessionHandler`
* :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MemcacheSessionHandler`
@@ -80,11 +80,11 @@ easily 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(array(), new PdoSessionHandler($pdo));
+ $sessionStorage = new NativeSessionStorage([], new PdoSessionHandler($pdo));
$session = new Session($sessionStorage);
Configuring PHP Sessions
@@ -175,7 +175,7 @@ calculated by adding the PHP runtime configuration value in
using the ``migrate()`` or ``invalidate()`` methods of the ``Session`` class.
The initial cookie lifetime can be set by configuring ``NativeSessionStorage``
- using the ``setOptions(array('cookie_lifetime' => 1234))`` method.
+ using the ``setOptions(['cookie_lifetime' => 1234])`` method.
.. note::
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 f6c290fa132..95ee18ad395 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 make your code which utilizes session testable 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
simply 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 609bee58191..b1107bb3ab0 100644
--- a/components/http_foundation/sessions.rst
+++ b/components/http_foundation/sessions.rst
@@ -33,7 +33,7 @@ Quick example::
$session->getFlashBag()->add('notice', 'Profile updated');
// retrieve messages
- foreach ($session->getFlashBag()->get('notice', array()) as $message) {
+ foreach ($session->getFlashBag()->get('notice', []) as $message) {
echo '
'.$message.'
';
}
@@ -151,6 +151,9 @@ the following API which is intended mainly for internal purposes:
:method:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface::getName`
Returns the name of the session bag.
+:method:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface::clear`
+ Clears out data from bag.
+
.. _attribute-bag-interface:
Attributes
@@ -219,12 +222,12 @@ data is an array, for example a set of tokens. In this case, managing the array
becomes a burden because you have to retrieve the array then process it and
store it again::
- $tokens = array(
- 'tokens' => array(
+ $tokens = [
+ 'tokens' => [
'a' => 'a6c1e0b6',
'b' => 'f4a7b1f3',
- ),
- );
+ ],
+ ];
So any processing of this might quickly get ugly, even simply adding a token to
the array::
@@ -278,7 +281,7 @@ has a simple API
Gets flashes by type and clears those flashes from the bag.
:method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::setAll`
- Sets all flashes, accepts a keyed array of arrays ``type => array(messages)``.
+ Sets all flashes, accepts a keyed array of arrays ``type => [messages]``.
:method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::all`
Gets all flashes (as a keyed array of arrays) and clears the flashes from the bag.
@@ -324,12 +327,12 @@ Displaying the flash messages might look as follows.
Simple, display one type of message::
// display warnings
- foreach ($session->getFlashBag()->get('warning', array()) as $message) {
+ foreach ($session->getFlashBag()->get('warning', []) as $message) {
echo '
';
}
diff --git a/components/http_foundation/trusting_proxies.rst b/components/http_foundation/trusting_proxies.rst
deleted file mode 100644
index 8c9687aeb2a..00000000000
--- a/components/http_foundation/trusting_proxies.rst
+++ /dev/null
@@ -1,67 +0,0 @@
-.. index::
- single: Request; Trusted Proxies
-
-Trusting Proxies
-================
-
-.. tip::
-
- If you're using the Symfony Framework, start by reading
- :doc:`/deployment/proxies`.
-
-If you find yourself behind some sort of proxy - like a load balancer - then
-certain header information may be sent to you using special ``X-Forwarded-*``
-headers or the ``Forwarded`` header. For example, the ``Host`` HTTP header is
-usually used to return the requested host. But when you're behind a proxy,
-the actual host may be stored in an ``X-Forwarded-Host`` header.
-
-Since HTTP headers can be spoofed, Symfony does *not* trust these proxy
-headers by default. If you are behind a proxy, you should manually whitelist
-your proxy as follows::
-
- use Symfony\Component\HttpFoundation\Request;
-
- // put this code as early as possible in your application (e.g. in your
- // front controller) to only trust proxy headers coming from these IP addresses
- Request::setTrustedProxies(array('192.0.0.1', '10.0.0.0/8'));
-
-.. versionadded:: 2.3
- CIDR notation support was introduced in Symfony 2.3, so you can whitelist whole
- subnets (e.g. ``10.0.0.0/8``, ``fc00::/7``).
-
-You should also make sure that your proxy filters unauthorized use of these
-headers, e.g. if a proxy natively uses the ``X-Forwarded-For`` header, it
-should not allow clients to send ``Forwarded`` headers to Symfony.
-
-If your proxy does not filter headers appropriately, you need to configure
-Symfony not to trust the headers your proxy does not filter (see below).
-
-Configuring Header Names
-------------------------
-
-By default, the following proxy headers are trusted:
-
-* ``Forwarded`` Used in :method:`Symfony\\Component\\HttpFoundation\\Request::getClientIp`;
-* ``X-Forwarded-For`` Used in :method:`Symfony\\Component\\HttpFoundation\\Request::getClientIp`;
-* ``X-Forwarded-Host`` Used in :method:`Symfony\\Component\\HttpFoundation\\Request::getHost`;
-* ``X-Forwarded-Port`` Used in :method:`Symfony\\Component\\HttpFoundation\\Request::getPort`;
-* ``X-Forwarded-Proto`` Used in :method:`Symfony\\Component\\HttpFoundation\\Request::getScheme` and :method:`Symfony\\Component\\HttpFoundation\\Request::isSecure`;
-
-If your reverse proxy uses a different header name for any of these, you
-can configure that header name via :method:`Symfony\\Component\\HttpFoundation\\Request::setTrustedHeaderName`::
-
- Request::setTrustedHeaderName(Request::HEADER_FORWARDED, 'X-Forwarded');
- Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'X-Proxy-For');
- Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, 'X-Proxy-Host');
- Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, 'X-Proxy-Port');
- Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'X-Proxy-Proto');
-
-Not Trusting certain Headers
-----------------------------
-
-By default, if you whitelist your proxy's IP address, then all five headers
-listed above are trusted. If you need to trust some of these headers but
-not others, you can do that as well::
-
- // disables trusting the ``Forwarded`` header
- Request::setTrustedHeaderName(Request::HEADER_FORWARDED, null);
diff --git a/components/http_kernel.rst b/components/http_kernel.rst
index a266da33433..5970157680b 100644
--- a/components/http_kernel.rst
+++ b/components/http_kernel.rst
@@ -16,9 +16,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/http-kernel
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require symfony/http-kernel:^3.4
.. include:: /components/require_autoload.rst.inc
@@ -47,7 +45,7 @@ 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
+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.
@@ -99,14 +97,16 @@ Framework - works.
Initially, using the :class:`Symfony\\Component\\HttpKernel\\HttpKernel`
is really simple and involves creating an
:doc:`event dispatcher ` and a
-:ref:`controller resolver ` (explained
-below). To complete your working kernel, you'll add more event listeners
-to the events discussed below::
+:ref:`controller and argument resolver `
+(explained below). To complete your working kernel, you'll add more event
+listeners to the events discussed below::
- use Symfony\Component\HttpFoundation\Request;
- use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\EventDispatcher\EventDispatcher;
+ use Symfony\Component\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();
@@ -114,10 +114,12 @@ to the events discussed below::
$dispatcher = new EventDispatcher();
// ... add some event listeners
- // create your controller resolver
- $resolver = new ControllerResolver();
+ // create your controller and argument resolvers
+ $controllerResolver = new ControllerResolver();
+ $argumentResolver = new ArgumentResolver();
+
// instantiate the kernel
- $kernel = new HttpKernel($dispatcher, $resolver);
+ $kernel = new HttpKernel($dispatcher, $controllerResolver, new RequestStack(), $argumentResolver);
// actually execute the kernel, which turns the request into a response
// by dispatching events, calling a controller, and returning the response
@@ -134,6 +136,13 @@ See ":ref:`http-kernel-working-example`" for a more concrete implementation.
For general information on adding listeners to the events below, see
:ref:`http-kernel-creating-listener`.
+.. caution::
+
+ As of 3.1 the :class:`Symfony\\Component\\HttpKernel\\HttpKernel` accepts a
+ fourth argument, which must be an instance of
+ :class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolverInterface`.
+ In 4.0 this argument will become mandatory.
+
.. seealso::
There is a wonderful tutorial series on using the HttpKernel component and
@@ -163,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.
@@ -208,7 +217,7 @@ the next step in HttpKernel is to determine and prepare (i.e. resolve) the
controller. The controller is the part of the end-application's code that
is responsible for creating and returning the ``Response`` for a specific page.
The only requirement is that it is a PHP callable - i.e. a function, method
-on an object, or a ``Closure``.
+on an object or a ``Closure``.
But *how* you determine the exact controller for a request is entirely up
to your application. This is the job of the "controller resolver" - a class
@@ -232,13 +241,24 @@ This implementation is explained more in the sidebar below::
public function getArguments(Request $request, $controller);
}
+.. caution::
+
+ The ``getArguments()`` method in the
+ :class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver` and
+ respective interface
+ :class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface`
+ are deprecated as of 3.1 and will be removed in 4.0. You can use the
+ :class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver` which
+ uses the :class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolverInterface`
+ instead.
+
Internally, the ``HttpKernel::handle()`` method first calls
:method:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface::getController`
on the controller resolver. This method is passed the ``Request`` and is responsible
for somehow determining and returning a PHP callable (the controller) based
on the request's information.
-The second method, :method:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface::getArguments`,
+The second method, :method:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolverInterface::getArguments`,
will be called after another event - ``kernel.controller`` - is dispatched.
.. sidebar:: Resolving the Controller in the Symfony Framework
@@ -311,11 +331,11 @@ on the event object that's passed to listeners on this event.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Next, ``HttpKernel::handle()`` calls
-:method:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface::getArguments`.
+:method:`ArgumentResolverInterface::getArguments() `.
Remember that the controller returned in ``getController()`` is a callable.
The purpose of ``getArguments()`` is to return the array of arguments that
should be passed to that controller. Exactly how this is done is completely
-up to your design, though the built-in :class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver`
+up to your design, though the built-in :class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver`
is a good example.
At this point the kernel has a PHP callable (the controller) and an array
@@ -324,21 +344,32 @@ of arguments that should be passed when executing that callable.
.. sidebar:: Getting the Controller Arguments in the Symfony Framework
Now that you know exactly what the controller callable (usually a method
- inside a controller object) is, the ``ControllerResolver`` uses `reflection`_
+ inside a controller object) is, the ``ArgumentResolver`` uses `reflection`_
on the callable to return an array of the *names* of each of the arguments.
It then iterates over each of these arguments and uses the following tricks
to determine which value should be passed for each argument:
a) If the ``Request`` attributes bag contains a key that matches the name
of the argument, that value is used. For example, if the first argument
- to a controller is ``$slug``, and there is a ``slug`` key in the ``Request``
+ to a controller is ``$slug`` and there is a ``slug`` key in the ``Request``
``attributes`` bag, that value is used (and typically this value came
from the ``RouterListener``).
b) If the argument in the controller is type-hinted with Symfony's
- :class:`Symfony\\Component\\HttpFoundation\\Request` object, then the
+ :class:`Symfony\\Component\\HttpFoundation\\Request` object, the
``Request`` is passed in as the value.
+ c) If the function or method argument is `variadic`_ and the ``Request``
+ ``attributes`` bag contains an array for that argument, they will all be
+ available through the `variadic`_ argument.
+
+ This functionality is provided by resolvers implementing the
+ :class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolverInterface`.
+ There are four implementations which provide the default behavior of
+ Symfony but customization is the key here. By implementing the
+ ``ArgumentValueResolverInterface`` yourself and passing this to the
+ ``ArgumentResolver``, you can extend this functionality.
+
.. _component-http-kernel-calling-controller:
5) Calling the Controller
@@ -534,7 +565,7 @@ below for more details).
There are two main listeners to ``kernel.exception`` when using the
Symfony Framework.
- **ExceptionListener in HttpKernel**
+ **ExceptionListener in the HttpKernel Component**
The first comes core to the HttpKernel component
and is called :class:`Symfony\\Component\\HttpKernel\\EventListener\\ExceptionListener`.
@@ -550,13 +581,20 @@ below for more details).
then ``getStatusCode()`` and ``getHeaders()`` are called on the exception
and used to populate the headers and status code of the ``FlattenException``
object. The idea is that these are used in the next step when creating
- the final response.
+ the final response. If you want to set custom HTTP headers, you can always
+ use the ``setHeaders()`` method on exceptions derived from the
+ :class:`Symfony\\Component\\HttpKernel\\Exception\\HttpException` class.
+
+ 3) If the original exception implements
+ :class:`Symfony\\Component\\HttpFoundation\\Exception\\RequestExceptionInterface`,
+ then the status code of the ``FlattenException`` object is populated with
+ ``400`` and no other headers are modified.
- 3) A controller is executed and passed the flattened exception. The exact
+ 4) A controller is executed and passed the flattened exception. The exact
controller to render is passed as a constructor argument to this listener.
This controller will return the final ``Response`` for this error page.
- **ExceptionListener in Security**
+ **ExceptionListener in the Security Component**
The other important listener is the
:class:`Symfony\\Component\\Security\\Http\\Firewall\\ExceptionListener`.
@@ -582,17 +620,18 @@ 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\\GetResponseEvent`
+kernel.controller ``KernelEvents::CONTROLLER`` :class:`Symfony\\Component\\HttpKernel\\Event\\FilterControllerEvent`
+kernel.controller_arguments ``KernelEvents::CONTROLLER_ARGUMENTS`` :class:`Symfony\\Component\\HttpKernel\\Event\\FilterControllerArgumentsEvent`
+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`
+=========================== ====================================== ===================================================================================
.. _http-kernel-working-example:
@@ -600,31 +639,33 @@ A full Working Example
----------------------
When using the HttpKernel component, you're free to attach any listeners
-to the core events and use any controller resolver that implements the
-:class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface`.
-However, the HttpKernel component comes with some built-in listeners and
-a built-in ControllerResolver that can be used to create a working example::
+to the core events, use any controller resolver that implements the
+:class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface` and
+use any argument resolver that implements the
+:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolverInterface`.
+However, the HttpKernel component comes with some built-in listeners and everything
+else that can be used to create a working example::
+ use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
- use Symfony\Component\HttpKernel\HttpKernel;
- use Symfony\Component\EventDispatcher\EventDispatcher;
+ use Symfony\Component\HttpKernel\Controller\ArgumentResolver;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
use Symfony\Component\HttpKernel\EventListener\RouterListener;
- use Symfony\Component\Routing\RouteCollection;
- use Symfony\Component\Routing\Route;
+ use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
+ use Symfony\Component\Routing\Route;
+ use Symfony\Component\Routing\RouteCollection;
$routes = new RouteCollection();
- $routes->add('hello', new Route('/hello/{name}', array(
- '_controller' => function (Request $request) {
- return new Response(
- sprintf("Hello %s", $request->get('name'))
- );
- }
- )
+ $routes->add('hello', new Route('/hello/{name}', [
+ '_controller' => function (Request $request) {
+ return new Response(
+ sprintf("Hello %s", $request->get('name'))
+ );
+ }]
));
$request = Request::createFromGlobals();
@@ -634,8 +675,10 @@ a built-in ControllerResolver that can be used to create a working example::
$dispatcher = new EventDispatcher();
$dispatcher->addSubscriber(new RouterListener($matcher, new RequestStack()));
- $resolver = new ControllerResolver();
- $kernel = new HttpKernel($dispatcher, $resolver);
+ $controllerResolver = new ControllerResolver();
+ $argumentResolver = new ArgumentResolver();
+
+ $kernel = new HttpKernel($dispatcher, $controllerResolver, new RequestStack(), $argumentResolver);
$response = $kernel->handle($request);
$response->send();
@@ -716,10 +759,6 @@ directory of AppBundle.
The HttpKernel component provides a method called :method:`Symfony\\Component\\HttpKernel\\Kernel::locateResource`
which can be used to transform logical paths into physical paths::
- use Symfony\Component\HttpKernel\HttpKernel;
-
- // ...
- $kernel = new HttpKernel($dispatcher, $resolver);
$path = $kernel->locateResource('@AppBundle/Resources/config/services.xml');
Learn more
@@ -731,12 +770,11 @@ 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
.. _`@Template`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/view.html
.. _`EmailSenderListener`: https://github.com/symfony/swiftmailer-bundle/blob/master/EventListener/EmailSenderListener.php
+.. _variadic: http://php.net/manual/en/functions.arguments.php
diff --git a/components/intl.rst b/components/intl.rst
index 12ebcff4c0d..ae4634ffe30 100644
--- a/components/intl.rst
+++ b/components/intl.rst
@@ -8,10 +8,6 @@ The Intl Component
A PHP replacement layer for the C `intl extension`_ that also provides
access to the localization data of the `ICU library`_.
-.. versionadded:: 2.3
- The Intl component was introduced in Symfony 2.3. In earlier versions of Symfony,
- you should use the Locale component instead.
-
.. caution::
The replacement layer is limited to the locale "en". If you want to use
@@ -28,9 +24,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/intl
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require symfony/intl:^3.4
.. include:: /components/require_autoload.rst.inc
@@ -92,26 +86,26 @@ TextBundleWriter
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
+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\ResourceBundle\Writer\TextBundleWriter;
use Symfony\Component\Intl\ResourceBundle\Compiler\BundleCompiler;
+ use Symfony\Component\Intl\ResourceBundle\Writer\TextBundleWriter;
$writer = new TextBundleWriter();
- $writer->write('/path/to/bundle', 'en', array(
- 'Data' => array(
+ $writer->write('/path/to/bundle', 'en', [
+ 'Data' => [
'entry1',
'entry2',
// ...
- ),
- ));
+ ],
+ ]);
$compiler = new BundleCompiler();
$compiler->compile('/path/to/bundle', '/path/to/binary/bundle');
-The command "genrb" must be available for the
+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
@@ -122,18 +116,18 @@ PhpBundleWriter
~~~~~~~~~~~~~~~
The :class:`Symfony\\Component\\Intl\\ResourceBundle\\Writer\\PhpBundleWriter`
-writes an array or an array-like object to a .php resource bundle::
+writes an array or an array-like object to a ``.php`` resource bundle::
use Symfony\Component\Intl\ResourceBundle\Writer\PhpBundleWriter;
$writer = new PhpBundleWriter();
- $writer->write('/path/to/bundle', 'en', array(
- 'Data' => array(
+ $writer->write('/path/to/bundle', 'en', [
+ 'Data' => [
'entry1',
'entry2',
// ...
- ),
- ));
+ ],
+ ]);
BinaryBundleReader
~~~~~~~~~~~~~~~~~~
@@ -153,7 +147,7 @@ PhpBundleReader
~~~~~~~~~~~~~~~
The :class:`Symfony\\Component\\Intl\\ResourceBundle\\Reader\\PhpBundleReader`
-reads resource bundles from .php files and returns an array or an array-like
+reads resource bundles from ``.php`` files and returns an array or an array-like
object::
use Symfony\Component\Intl\ResourceBundle\Reader\PhpBundleReader;
@@ -205,7 +199,7 @@ returned::
var_dump($data['Data']['entry1']);
// returns null if the key "Data" does not exist
- var_dump($reader->readEntry('/path/to/bundle', 'en', array('Data', 'entry1')));
+ var_dump($reader->readEntry('/path/to/bundle', 'en', ['Data', 'entry1']));
Additionally, the
:method:`Symfony\\Component\\Intl\\ResourceBundle\\Reader\\StructuredBundleReaderInterface::readEntry`
@@ -219,7 +213,7 @@ locale will be merged. In order to suppress this behavior, the last parameter
var_dump($reader->readEntry(
'/path/to/bundle',
'en',
- array('Data', 'entry1'),
+ ['Data', 'entry1'],
false
));
@@ -247,7 +241,7 @@ bundle::
\Locale::setDefault('en');
$languages = Intl::getLanguageBundle()->getLanguageNames();
- // => array('ab' => 'Abkhazian', ...)
+ // => ['ab' => 'Abkhazian', ...]
$language = Intl::getLanguageBundle()->getLanguageName('de');
// => 'German'
@@ -256,7 +250,7 @@ bundle::
// => 'Austrian German'
$scripts = Intl::getLanguageBundle()->getScriptNames();
- // => array('Arab' => 'Arabic', ...)
+ // => ['Arab' => 'Arabic', ...]
$script = Intl::getLanguageBundle()->getScriptName('Hans');
// => 'Simplified'
@@ -265,7 +259,7 @@ All methods accept the translation locale as the last, optional parameter,
which defaults to the current default locale::
$languages = Intl::getLanguageBundle()->getLanguageNames('de');
- // => array('ab' => 'Abchasisch', ...)
+ // => ['ab' => 'Abchasisch', ...]
Country Names
~~~~~~~~~~~~~
@@ -277,7 +271,7 @@ The translations of country names can be found in the region bundle::
\Locale::setDefault('en');
$countries = Intl::getRegionBundle()->getCountryNames();
- // => array('AF' => 'Afghanistan', ...)
+ // => ['AF' => 'Afghanistan', ...]
$country = Intl::getRegionBundle()->getCountryName('GB');
// => 'United Kingdom'
@@ -286,7 +280,7 @@ All methods accept the translation locale as the last, optional parameter,
which defaults to the current default locale::
$countries = Intl::getRegionBundle()->getCountryNames('de');
- // => array('AF' => 'Afghanistan', ...)
+ // => ['AF' => 'Afghanistan', ...]
Locales
~~~~~~~
@@ -298,7 +292,7 @@ The translations of locale names can be found in the locale bundle::
\Locale::setDefault('en');
$locales = Intl::getLocaleBundle()->getLocaleNames();
- // => array('af' => 'Afrikaans', ...)
+ // => ['af' => 'Afrikaans', ...]
$locale = Intl::getLocaleBundle()->getLocaleName('zh_Hans_MO');
// => 'Chinese (Simplified, Macau SAR China)'
@@ -307,7 +301,7 @@ All methods accept the translation locale as the last, optional parameter,
which defaults to the current default locale::
$locales = Intl::getLocaleBundle()->getLocaleNames('de');
- // => array('af' => 'Afrikaans', ...)
+ // => ['af' => 'Afrikaans', ...]
Currencies
~~~~~~~~~~
@@ -320,7 +314,7 @@ be found in the currency bundle::
\Locale::setDefault('en');
$currencies = Intl::getCurrencyBundle()->getCurrencyNames();
- // => array('AFN' => 'Afghan Afghani', ...)
+ // => ['AFN' => 'Afghan Afghani', ...]
$currency = Intl::getCurrencyBundle()->getCurrencyName('INR');
// => 'Indian Rupee'
@@ -342,7 +336,7 @@ accept the translation locale as the last, optional parameter, which defaults
to the current default locale::
$currencies = Intl::getCurrencyBundle()->getCurrencyNames('de');
- // => array('AFN' => 'Afghanische Afghani', ...)
+ // => ['AFN' => 'Afghanische Afghani', ...]
That's all you need to know for now. Have fun coding!
@@ -358,8 +352,6 @@ Learn more
/reference/forms/types/language
/reference/forms/types/locale
-.. _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/
diff --git a/components/ldap.rst b/components/ldap.rst
index 8e62686691e..a576344b208 100644
--- a/components/ldap.rst
+++ b/components/ldap.rst
@@ -7,28 +7,25 @@ The Ldap Component
The Ldap component provides a means to connect to an LDAP server (OpenLDAP or Active Directory).
-.. versionadded:: 2.8
- The Ldap component was introduced in Symfony 2.8.
-
Installation
------------
.. code-block:: terminal
- $ composer require symfony/ldap
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require symfony/ldap:^3.4
.. include:: /components/require_autoload.rst.inc
Usage
-----
-The :class:`Symfony\\Component\\Ldap\\LdapClient` class provides methods
-to authenticate and query against an LDAP server.
+The :class:`Symfony\\Component\\Ldap\\Ldap` class provides methods to authenticate
+and query against an LDAP server.
-The :class:`Symfony\\Component\\Ldap\\LdapClient` class can be configured
-using the following options:
+The ``Ldap`` class uses an :class:`Symfony\\Component\\Ldap\\Adapter\\AdapterInterface`
+to communicate with an LDAP server. The :class:`adapter `
+for PHP's built-in LDAP extension, for example, can be configured using the
+following options:
``host``
IP or hostname of the LDAP server
@@ -39,38 +36,107 @@ using the following options:
``version``
The version of the LDAP protocol to use
-``useSsl``
- Whether or not to secure the connection using SSL
+``encryption``
+ The encryption protocol: ``ssl``, ``tls`` or ``none`` (default)
-``useStartTls``
- Whether or not to secure the connection using StartTLS
+``connection_string``
+ You may use this option instead of ``host`` and ``port`` to connect to the
+ LDAP server
``optReferrals``
- Specifies whether to automatically follow referrals
- returned by the LDAP server
+ Specifies whether to automatically follow referrals returned by the LDAP server
+
+``options``
+ LDAP server's options as defined in
+ :class:`ConnectionOptions `
For example, to connect to a start-TLS secured LDAP server::
- use Symfony\Component\Ldap\LdapClient;
+ use Symfony\Component\Ldap\Ldap;
+
+ $ldap = Ldap::create('ext_ldap', [
+ 'host' => 'my-server',
+ 'encryption' => 'ssl',
+ ]);
+
+Or you could directly specify a connection string::
+
+ use Symfony\Component\Ldap\Ldap;
- $ldap = new LdapClient('my-server', 389, 3, false, true);
+ $ldap = Ldap::create('ext_ldap', ['connection_string' => 'ldaps://my-server:636']);
-The :method:`Symfony\\Component\\Ldap\\LdapClient::bind` method
+The :method:`Symfony\\Component\\Ldap\\Ldap::bind` method
authenticates a previously configured connection using both the
distinguished name (DN) and the password of a user::
- use Symfony\Component\Ldap\LdapClient;
+ use Symfony\Component\Ldap\Ldap;
// ...
$ldap->bind($dn, $password);
Once bound (or if you enabled anonymous authentication on your
LDAP server), you may query the LDAP server using the
-:method:`Symfony\\Component\\Ldap\\LdapClient::find` method::
+:method:`Symfony\\Component\\Ldap\\Ldap::query` method::
- use Symfony\Component\Ldap\LdapClient;
+ use Symfony\Component\Ldap\Ldap;
// ...
- $ldap->find('dc=symfony,dc=com', '(&(objectclass=person)(ou=Maintainers))');
+ $query = $ldap->query('dc=symfony,dc=com', '(&(objectclass=person)(ou=Maintainers))');
+ $results = $query->execute();
+
+ foreach ($results as $entry) {
+ // Do something with the results
+ }
+
+By default, LDAP entries are lazy-loaded. If you wish to fetch
+all entries in a single call and do something with the results'
+array, you may use the
+:method:`Symfony\\Component\\Ldap\\Adapter\\ExtLdap\\Collection::toArray` method::
+
+ use Symfony\Component\Ldap\Ldap;
+ // ...
+
+ $query = $ldap->query('dc=symfony,dc=com', '(&(objectclass=person)(ou=Maintainers))');
+ $results = $query->execute()->toArray();
+
+ // Do something with the results array
+
+By default, LDAP queries use the ``Symfony\Component\Ldap\Adapter::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\Entry;
+ use Symfony\Component\Ldap\Ldap;
+ // ...
+
+ $entry = new Entry('cn=Fabien Potencier,dc=symfony,dc=com', [
+ 'sn' => ['fabpot'],
+ 'objectClass' => ['inetOrgPerson'],
+ ]);
+
+ $entryManager = $ldap->getEntryManager();
+
+ // Creating a new entry
+ $entryManager->add($entry);
+
+ // Finding and updating an existing entry
+ $query = $ldap->query('dc=symfony,dc=com', '(&(objectclass=person)(ou=Maintainers))');
+ $result = $query->execute();
+ $entry = $result[0];
+ $entry->setAttribute('email', ['fabpot@symfony.com']);
+ $entryManager->update($entry);
-.. _Packagist: https://packagist.org/packages/symfony/ldap
+ // Removing an existing entry
+ $entryManager->remove(new Entry('cn=Test User,dc=symfony,dc=com'));
diff --git a/components/lock.rst b/components/lock.rst
new file mode 100644
index 00000000000..fdaf3b7686a
--- /dev/null
+++ b/components/lock.rst
@@ -0,0 +1,557 @@
+.. index::
+ single: Lock
+ single: Components; Lock
+
+The Lock Component
+==================
+
+ The Lock Component creates and manages `locks`_, a mechanism to provide
+ exclusive access to a shared resource.
+
+.. versionadded:: 3.4
+
+ The Lock component was introduced in Symfony 3.4.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/lock:^3.4
+
+.. include:: /components/require_autoload.rst.inc
+
+Usage
+-----
+
+Locks are used to guarantee exclusive access to some shared resource. In
+Symfony applications, you can use locks for example to ensure that a command is
+not executed more than once at the same time (on the same or different servers).
+
+Locks are created using a :class:`Symfony\\Component\\Lock\\Factory` class,
+which in turn requires another class to manage the storage of locks::
+
+ use Symfony\Component\Lock\Factory;
+ use Symfony\Component\Lock\Store\SemaphoreStore;
+
+ $store = new SemaphoreStore();
+ $factory = new Factory($store);
+
+The lock is created by calling the :method:`Symfony\\Component\\Lock\\Factory::createLock`
+method. Its first argument is an arbitrary string that represents the locked
+resource. Then, a call to the :method:`Symfony\\Component\\Lock\\LockInterface::acquire`
+method will try to acquire the lock::
+
+ // ...
+ $lock = $factory->createLock('pdf-invoice-generation');
+
+ if ($lock->acquire()) {
+ // The resource "pdf-invoice-generation" is locked.
+ // You can compute and generate invoice safely here.
+
+ $lock->release();
+ }
+
+If the lock can not be acquired, the method returns ``false``. The ``acquire()``
+method can be safely called repeatedly, even if the lock is already acquired.
+
+.. note::
+
+ Unlike other implementations, the Lock Component distinguishes locks
+ instances even when they are created for the same resource. If a lock has
+ to be used by several services, they should share the same ``Lock`` instance
+ returned by the ``Factory::createLock`` method.
+
+.. tip::
+
+ If you don't release the lock explicitly, it will be released automatically
+ on instance destruction. In some cases, it can be useful to lock a resource
+ across several requests. To disable the automatic release behavior, set the
+ third argument of the ``createLock()`` method to ``false``.
+
+.. _lock-blocking-locks:
+
+Blocking Locks
+--------------
+
+By default, when a lock cannot be acquired, the ``acquire`` method returns
+``false`` immediately. To wait (indefinitely) until the lock
+can be created, pass ``true`` as the argument of the ``acquire()`` method. This
+is called a **blocking lock** because the execution of your application stops
+until the lock is acquired.
+
+Some of the built-in ``Store`` classes support this feature. When they don't,
+they can be decorated with the ``RetryTillSaveStore`` class::
+
+ use Symfony\Component\Lock\Factory;
+ use Symfony\Component\Lock\Store\RedisStore;
+ use Symfony\Component\Lock\Store\RetryTillSaveStore;
+
+ $store = new RedisStore(new \Predis\Client('tcp://localhost:6379'));
+ $store = new RetryTillSaveStore($store);
+ $factory = new Factory($store);
+
+ $lock = $factory->createLock('notification-flush');
+ $lock->acquire(true);
+
+Expiring Locks
+--------------
+
+Locks created remotely are difficult to manage because there is no way for the
+remote ``Store`` to know if the locker process is still alive. Due to bugs,
+fatal errors or segmentation faults, it cannot be guaranteed that ``release()``
+method will be called, which would cause the resource to be locked infinitely.
+
+The best solution in those cases is to create **expiring locks**, which are
+released automatically after some amount of time has passed (called TTL for
+*Time To Live*). This time, in seconds, is configured as the second argument of
+the ``createLock()`` method. If needed, these locks can also be released early
+with the ``release()`` method.
+
+The trickiest part when working with expiring locks is choosing the right TTL.
+If it's too short, other processes could acquire the lock before finishing the
+job; if it's too long and the process crashes before calling the ``release()``
+method, the resource will stay locked until the timeout::
+
+ // ...
+ // create an expiring lock that lasts 30 seconds
+ $lock = $factory->createLock('charts-generation', 30);
+
+ if (!$lock->acquire()) {
+ return;
+ }
+ try {
+ // perform a job during less than 30 seconds
+ } finally {
+ $lock->release();
+ }
+
+.. tip::
+
+ To avoid letting the lock in a locking state, it's recommended to wrap the
+ job in a try/catch/finally block to always try to release the expiring lock.
+
+In case of long-running tasks, it's better to start with a not too long TTL and
+then use the :method:`Symfony\\Component\\Lock\\LockInterface::refresh` method
+to reset the TTL to its original value::
+
+ // ...
+ $lock = $factory->createLock('charts-generation', 30);
+
+ if (!$lock->acquire()) {
+ return;
+ }
+ try {
+ while (!$finished) {
+ // perform a small part of the job.
+
+ // renew the lock for 30 more seconds.
+ $lock->refresh();
+ }
+ } finally {
+ $lock->release();
+ }
+
+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
+----------------
+
+Locks are created and managed in ``Stores``, which are classes that implement
+:class:`Symfony\\Component\\Lock\\StoreInterface`. The component includes the
+following built-in store types:
+
+============================================ ====== ======== ========
+Store Scope Blocking Expiring
+============================================ ====== ======== ========
+:ref:`FlockStore ` local yes no
+:ref:`MemcachedStore ` remote no yes
+:ref:`RedisStore ` remote no yes
+:ref:`SemaphoreStore ` local yes no
+============================================ ====== ======== ========
+
+.. _lock-store-flock:
+
+FlockStore
+~~~~~~~~~~
+
+The FlockStore uses the file system on the local computer to create the locks.
+It does not support expiration, but the lock is automatically released when the
+lock object goes out of scope and is freed by the garbage collector (for example
+when the PHP process ends)::
+
+ use Symfony\Component\Lock\Store\FlockStore;
+
+ // the argument is the path of the directory where the locks are created
+ // if none is given, sys_get_temp_dir() is used internally.
+ $store = new FlockStore('/var/stores');
+
+.. caution::
+
+ 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.
+
+.. _lock-store-memcached:
+
+MemcachedStore
+~~~~~~~~~~~~~~
+
+The MemcachedStore saves locks on a Memcached server, it requires a Memcached
+connection implementing the ``\Memcached`` class. This store does not
+support blocking, and expects a TTL to avoid stalled locks::
+
+ use Symfony\Component\Lock\Store\MemcachedStore;
+
+ $memcached = new \Memcached();
+ $memcached->addServer('localhost', 11211);
+
+ $store = new MemcachedStore($memcached);
+
+.. note::
+
+ Memcached does not support TTL lower than 1 second.
+
+.. _lock-store-redis:
+
+RedisStore
+~~~~~~~~~~
+
+The RedisStore saves locks on a Redis server, it requires a Redis connection
+implementing the ``\Redis``, ``\RedisArray``, ``\RedisCluster`` or
+``\Predis`` classes. This store does not support blocking, and expects a TTL to
+avoid stalled locks::
+
+ use Symfony\Component\Lock\Store\RedisStore;
+
+ $redis = new \Redis();
+ $redis->connect('localhost');
+
+ $store = new RedisStore($redis);
+
+.. _lock-store-semaphore:
+
+SemaphoreStore
+~~~~~~~~~~~~~~
+
+The SemaphoreStore uses the `PHP semaphore functions`_ to create the locks::
+
+ use Symfony\Component\Lock\Store\SemaphoreStore;
+
+ $store = new SemaphoreStore();
+
+.. _lock-store-combined:
+
+CombinedStore
+~~~~~~~~~~~~~
+
+The CombinedStore is designed for High Availability applications because it
+manages several stores in sync (for example, several Redis servers). When a lock
+is 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\Store\CombinedStore;
+ use Symfony\Component\Lock\Store\RedisStore;
+ use Symfony\Component\Lock\Strategy\ConsensusStrategy;
+
+ $stores = [];
+ foreach (['server1', 'server2', 'server3'] as $server) {
+ $redis = new \Redis();
+ $redis->connect($server);
+
+ $stores[] = new RedisStore($redis);
+ }
+
+ $store = new CombinedStore($stores, new ConsensusStrategy());
+
+Instead of the simple majority strategy (``ConsensusStrategy``) an
+``UnanimousStrategy`` can be used to require the lock to be acquired in all
+the stores.
+
+.. caution::
+
+ In order to get high availability when using the ``ConsensusStrategy``, the
+ minimum cluster size must be three servers. This allows the cluster to keep
+ working when a single server fails (because this strategy requires that the
+ lock is acquired in more than half of the servers).
+
+Reliability
+-----------
+
+The component guarantees that the same resource can't be lock twice as long as
+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,
+caching, fork, ...).
+
+.. caution::
+
+ Do not share a key between processes.
+
+Every concurrent process must store the ``Lock`` in the same server. Otherwise two
+different machines may allow two different processes to acquire the same ``Lock``.
+
+.. caution::
+
+ To guarantee that the same server will always be safe, do not use Memcached
+ behind a LoadBalancer, a cluster or round-robin DNS. Even if the main server
+ is down, the calls must not be forwarded to a backup or failover server.
+
+Expiring Stores
+~~~~~~~~~~~~~~~
+
+Expiring stores (:ref:`MemcachedStore ` and
+:ref:`RedisStore `) guarantee that the lock is acquired
+only for the defined duration of time. If the task takes longer to be
+accomplished, then the lock can be released by the store and acquired by
+someone else.
+
+The ``Lock`` provides several methods to check its health. The ``isExpired()``
+method checks whether or not its lifetime is over and the ``getRemainingLifetime()``
+method returns its time to live in seconds.
+
+Using the above methods, a more robust code would be::
+
+ // ...
+ $lock = $factory->createLock('invoice-publication', 30);
+
+ if (!$lock->acquire()) {
+ return;
+ }
+ while (!$finished) {
+ if ($lock->getRemainingLifetime() <= 5) {
+ if ($lock->isExpired()) {
+ // lock was lost, perform a rollback or send a notification
+ throw new \RuntimeException('Lock lost during the overall process');
+ }
+
+ $lock->refresh();
+ }
+
+ // Perform the task whose duration MUST be less than 5 minutes
+ }
+
+.. caution::
+
+ Choose wisely the lifetime of the ``Lock`` and check whether its remaining
+ time to leave is enough to perform the task.
+
+.. caution::
+
+ Storing a ``Lock`` usually takes a few milliseconds, but network conditions
+ may increase that time a lot (up to a few seconds). Take that into account
+ when choosing the right TTL.
+
+By design, locks are stored in servers with a defined lifetime. If the date or
+time of the machine changes, a lock could be released sooner than expected.
+
+.. caution::
+
+ To guarantee that date won't change, the NTP service should be disabled
+ and the date should be updated when the service is stopped.
+
+FlockStore
+~~~~~~~~~~
+
+By using the file system, this ``Store`` is reliable as long as concurrent
+processes use the same physical directory to stores locks.
+
+Processes must run on the same machine, virtual machine or container.
+Be careful when updating a Kubernetes or Swarm service because for a short
+period of time, there can be two running containers in parallel.
+
+The absolute path to the directory must remain the same. Be careful of symlinks
+that could change at anytime: Capistrano and blue/green deployment often use
+that trick. Be careful when the path to that directory changes between two
+deployments.
+
+Some file systems (such as some types of NFS) do not support locking.
+
+.. caution::
+
+ All concurrent processes must use the same physical file system by running
+ on the same machine and using the same absolute path to locks directory.
+
+ By definition, usage of ``FlockStore`` in an HTTP context is incompatible
+ with multiple front servers, unless to ensure that the same resource will
+ always be locked on the same machine or to use a well configured shared file
+ system.
+
+Files on the file system can be removed during a maintenance operation. For instance,
+to clean up the ``/tmp`` directory or after a reboot of the machine when a directory
+uses tmpfs. It's not an issue if the lock is released when the process ended, but
+it is in case of ``Lock`` reused between requests.
+
+.. caution::
+
+ Do not store locks on a volatile file system if they have to be reused in
+ several requests.
+
+MemcachedStore
+~~~~~~~~~~~~~~
+
+The way Memcached works is to store items in memory. That means that by using
+the :ref:`MemcachedStore ` the locks are not persisted
+and may disappear by mistake at anytime.
+
+If the Memcached service or the machine hosting it restarts, every lock would
+be lost without notifying the running processes.
+
+.. caution::
+
+ To avoid that someone else acquires a lock after a restart, it's recommended
+ to delay service start and wait at least as long as the longest lock TTL.
+
+By default Memcached uses a LRU mechanism to remove old entries when the service
+needs space to add new items.
+
+.. caution::
+
+ The number of items stored in Memcached must be under control. If it's not
+ possible, LRU should be disabled and Lock should be stored in a dedicated
+ Memcached service away from Cache.
+
+When the Memcached service is shared and used for multiple usage, Locks could be
+removed by mistake. For instance some implementation of the PSR-6 ``clear()``
+method uses the Memcached's ``flush()`` method which purges and removes everything.
+
+.. caution::
+
+ The method ``flush()`` must not be called, or locks should be stored in a
+ dedicated Memcached service away from Cache.
+
+RedisStore
+~~~~~~~~~~
+
+The way Redis works is to store items in memory. That means that by using
+the :ref:`RedisStore ` the locks are not persisted
+and may disappear by mistake at anytime.
+
+If the Redis service or the machine hosting it restarts, every locks would
+be lost without notifying the running processes.
+
+.. caution::
+
+ To avoid that someone else acquires a lock after a restart, it's recommended
+ to delay service start and wait at least as long as the longest lock TTL.
+
+.. tip::
+
+ Redis can be configured to persist items on disk, but this option would
+ slow down writes on the service. This could go against other uses of the
+ server.
+
+When the Redis service is shared and used for multiple usages, locks could be
+removed by mistake.
+
+.. caution::
+
+ The command ``FLUSHDB`` must not be called, or locks should be stored in a
+ dedicated Redis service away from Cache.
+
+CombinedStore
+~~~~~~~~~~~~~
+
+Combined stores allow to store locks across several backends. It's a common
+mistake to think that the lock mechanism will be more reliable. This is wrong.
+The ``CombinedStore`` will be, at best, as reliable as the least reliable of
+all managed stores. As soon as one managed store returns erroneous information,
+the ``CombinedStore`` won't be reliable.
+
+.. caution::
+
+ All concurrent processes must use the same configuration, with the same
+ amount of managed stored and the same endpoint.
+
+.. tip::
+
+ Instead of using a cluster of Redis or Memcached servers, it's better to use
+ a ``CombinedStore`` with a single server per managed store.
+
+SemaphoreStore
+~~~~~~~~~~~~~~
+
+Semaphores are handled by the Kernel level. In order to be reliable, processes
+must run on the same machine, virtual machine or container. Be careful when
+updating a Kubernetes or Swarm service because for a short period of time, there
+can be two running containers in parallel.
+
+.. caution::
+
+ All concurrent processes must use the same machine. Before starting a
+ concurrent process on a new machine, check that other process are stopped
+ on the old one.
+
+.. caution::
+
+ When running on systemd with non-system user and option ``RemoveIPC=yes``
+ (default value), locks are deleted by systemd when that user logs out.
+ Check that process is run with a system user (UID <= SYS_UID_MAX) with
+ ``SYS_UID_MAX`` defined in ``/etc/login.defs``, or set the option
+ ``RemoveIPC=off`` in ``/etc/systemd/logind.conf``.
+
+Overall
+~~~~~~~
+
+Changing the configuration of stores should be done very carefully. For
+instance, during the deployment of a new version. Processes with new
+configuration must not be started while old processes with old configuration
+are still running.
+
+.. _`locks`: https://en.wikipedia.org/wiki/Lock_(computer_science)
+.. _`PHP semaphore functions`: https://php.net/manual/en/book.sem.php
diff --git a/components/options_resolver.rst b/components/options_resolver.rst
index f557936aecb..817bada6731 100644
--- a/components/options_resolver.rst
+++ b/components/options_resolver.rst
@@ -5,18 +5,17 @@
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
------------
.. code-block:: terminal
- $ composer require symfony/options-resolver
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require symfony/options-resolver:^3.4
.. include:: /components/require_autoload.rst.inc
@@ -30,7 +29,7 @@ Imagine you have a ``Mailer`` class which has four options: ``host``,
{
protected $options;
- public function __construct(array $options = array())
+ public function __construct(array $options = [])
{
$this->options = $options;
}
@@ -74,25 +73,23 @@ options are buried in the business logic of your code. Use the
{
// ...
- public function __construct(array $options = array())
+ public function __construct(array $options = [])
{
- $this->options = array_replace(array(
+ $this->options = array_replace([
'host' => 'smtp.example.org',
'username' => 'user',
'password' => 'pa$$word',
'port' => 25,
- ), $options);
+ ], $options);
}
}
-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(array(
- 'usernme' => 'johndoe', // usernme misspelled (instead of username)
- ));
+ $mailer = new Mailer([
+ 'usernme' => 'johndoe', // 'username' is wrongly spelled as 'usernme'
+ ]);
No error will be shown. In the best case, the bug will appear during testing,
but the developer will spend time looking for the problem. In the worst case,
@@ -107,15 +104,15 @@ class helps you to fix this problem::
{
// ...
- public function __construct(array $options = array())
+ public function __construct(array $options = [])
{
$resolver = new OptionsResolver();
- $resolver->setDefaults(array(
+ $resolver->setDefaults([
'host' => 'smtp.example.org',
'username' => 'user',
'password' => 'pa$$word',
'port' => 25,
- ));
+ ]);
$this->options = $resolver->resolve($options);
}
@@ -125,12 +122,12 @@ Like before, all options will be guaranteed to be set. Additionally, an
:class:`Symfony\\Component\\OptionsResolver\\Exception\\UndefinedOptionsException`
is thrown if an unknown option is passed::
- $mailer = new Mailer(array(
+ $mailer = new Mailer([
'usernme' => 'johndoe',
- ));
+ ]);
// UndefinedOptionsException: The option "usernme" does not exist.
- // Known options are: "host", "password", "port", "username"
+ // Defined options are: "host", "password", "port", "username"
The rest of your code can access the values of the options without boilerplate
code::
@@ -158,7 +155,7 @@ It's a good practice to split the option configuration into a separate method::
{
// ...
- public function __construct(array $options = array())
+ public function __construct(array $options = [])
{
$resolver = new OptionsResolver();
$this->configureOptions($resolver);
@@ -168,13 +165,13 @@ It's a good practice to split the option configuration into a separate method::
public function configureOptions(OptionsResolver $resolver)
{
- $resolver->setDefaults(array(
+ $resolver->setDefaults([
'host' => 'smtp.example.org',
'username' => 'user',
'password' => 'pa$$word',
'port' => 25,
'encryption' => null,
- ));
+ ]);
}
}
@@ -189,10 +186,10 @@ than processing options. Second, sub-classes may now override the
{
parent::configureOptions($resolver);
- $resolver->setDefaults(array(
+ $resolver->setDefaults([
'host' => 'smtp.google.com',
'encryption' => 'ssl',
- ));
+ ]);
}
}
@@ -235,7 +232,7 @@ one required option::
public function configureOptions(OptionsResolver $resolver)
{
// ...
- $resolver->setRequired(array('host', 'username', 'password'));
+ $resolver->setRequired(['host', 'username', 'password']);
}
}
@@ -300,7 +297,7 @@ been set::
}
}
-The method :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::getMissingOptions`
+The :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::getMissingOptions` method
lets you access the names of all missing options.
Type Validation
@@ -318,25 +315,39 @@ correctly. To validate the types of the options, call
public function configureOptions(OptionsResolver $resolver)
{
// ...
+
+ // specify one allowed type
$resolver->setAllowedTypes('host', 'string');
- $resolver->setAllowedTypes('port', array('null', 'int'));
+
+ // specify multiple allowed types
+ $resolver->setAllowedTypes('port', ['null', 'int']);
+
+ // check all items in an array recursively for a type
+ $resolver->setAllowedTypes('dates', 'DateTime[]');
+ $resolver->setAllowedTypes('ports', 'int[]');
}
}
-For each option, you can define either just one type or an array of acceptable
-types. You can pass any type for which an ``is_()`` function is defined
-in PHP. Additionally, you may pass fully qualified class or interface names.
+You can pass any type for which an ``is_()`` function is defined in PHP.
+You may also pass fully qualified class or interface names (which is checked
+using ``instanceof``). Additionally, you can validate all items in an array
+recursively by suffixing the type with ``[]``.
+
+.. versionadded:: 3.4
+
+ Validating types of array items recursively was introduced in Symfony 3.4.
+ Prior to Symfony 3.4, only scalar values could be validated.
If you pass an invalid option now, an
:class:`Symfony\\Component\\OptionsResolver\\Exception\\InvalidOptionsException`
is thrown::
- $mailer = new Mailer(array(
+ $mailer = new Mailer([
'host' => 25,
- ));
+ ]);
// InvalidOptionsException: The option "host" with value "25" is
- // expected to be of type "string"
+ // expected to be of type "string", but is of type "int"
In sub-classes, you can use :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::addAllowedTypes`
to add additional allowed types without erasing the ones already set.
@@ -359,7 +370,7 @@ to verify that the passed option contains one of these values::
{
// ...
$resolver->setDefault('transport', 'sendmail');
- $resolver->setAllowedValues('transport', array('sendmail', 'mail', 'smtp'));
+ $resolver->setAllowedValues('transport', ['sendmail', 'mail', 'smtp']);
}
}
@@ -367,12 +378,12 @@ If you pass an invalid transport, an
:class:`Symfony\\Component\\OptionsResolver\\Exception\\InvalidOptionsException`
is thrown::
- $mailer = new Mailer(array(
+ $mailer = new Mailer([
'transport' => 'send-mail',
- ));
+ ]);
- // InvalidOptionsException: The option "transport" has the value
- // "send-mail", but is expected to be one of "sendmail", "mail", "smtp"
+ // InvalidOptionsException: The option "transport" with value "send-mail"
+ // is invalid. Accepted values are: "sendmail", "mail", "smtp"
For options with more complicated validation schemes, pass a closure which
returns ``true`` for acceptable values and ``false`` for invalid values::
@@ -492,10 +503,10 @@ the closure::
public function configureOptions(OptionsResolver $resolver)
{
// ...
- $resolver->setDefaults(array(
+ $resolver->setDefaults([
'encryption' => null,
'host' => 'example.org',
- ));
+ ]);
}
}
@@ -580,9 +591,9 @@ be included in the resolved options if it was actually passed to
$mailer->sendMail($from, $to);
// => Not Set!
- $mailer = new Mailer(array(
+ $mailer = new Mailer([
'port' => 25,
- ));
+ ]);
$mailer->sendMail($from, $to);
// => Set!
@@ -596,7 +607,7 @@ options in one go::
public function configureOptions(OptionsResolver $resolver)
{
// ...
- $resolver->setDefined(array('port', 'encryption'));
+ $resolver->setDefined(['port', 'encryption']);
}
}
@@ -637,11 +648,11 @@ can change your code to do the configuration only once per class::
// ...
class Mailer
{
- private static $resolversByClass = array();
+ private static $resolversByClass = [];
protected $options;
- public function __construct(array $options = array())
+ public function __construct(array $options = [])
{
// What type of Mailer is this, a Mailer, a GoogleMailer, ... ?
$class = get_class($this);
@@ -670,19 +681,15 @@ method ``clearOptionsConfig()`` and call it periodically::
// ...
class Mailer
{
- private static $resolversByClass = array();
+ private static $resolversByClass = [];
public static function clearOptionsConfig()
{
- self::$resolversByClass = array();
+ self::$resolversByClass = [];
}
// ...
}
-That's it! You now have all the tools and knowledge needed to easily process
+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
-.. _`read the Symfony 2.5 documentation`: https://symfony.com/doc/2.5/components/options_resolver.html
diff --git a/components/phpunit_bridge.rst b/components/phpunit_bridge.rst
index 9eede230564..3ed9cdc5e5a 100644
--- a/components/phpunit_bridge.rst
+++ b/components/phpunit_bridge.rst
@@ -6,11 +6,12 @@ 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 a helper for time and network-sensitive tests.
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,11 +19,10 @@ It comes with the following features:
* Displays the stack trace of a deprecation on-demand;
-* Provides a ``ClockMock`` helper class for time-sensitive tests.
+* Provides a ``ClockMock`` and ``DnsMock`` helper classes for time and network-sensitive tests;
-.. versionadded:: 2.7
- The PHPUnit Bridge was introduced in Symfony 2.7. It is however possible to
- install the bridge in any Symfony application (even 2.3).
+* Provides a modified version of PHPUnit that does not embed ``symfony/yaml`` nor
+ ``prophecy`` to prevent any conflicts with these dependencies;
Installation
------------
@@ -31,8 +31,6 @@ Installation
$ composer require --dev "symfony/phpunit-bridge:*"
-Alternatively, you can clone the ``_ repository.
-
.. include:: /components/require_autoload.rst.inc
.. note::
@@ -42,6 +40,24 @@ Alternatively, you can clone the ``_
always use its very latest stable major version to get the most accurate
deprecation report.
+If you plan to :ref:`write-assertions-about-deprecations` and use the regular
+PHPUnit script (not the modified PHPUnit script provided by Symfony), you have
+to register a new `test listener`_ called ``SymfonyTestsListener``:
+
+.. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
Usage
-----
@@ -105,9 +121,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 subdirectories or the value specified by the environment variable
+``SYMFONY_PHPUNIT_MAX_DEPTH``, looking for ``phpunit.xml.dist`` files and then
+running each suite it finds in parallel, collecting their output and displaying
+each test suite's results in their own section.
+
Trigger Deprecation Notices
---------------------------
@@ -154,32 +196,82 @@ message, enclosed with ``/``. For example, with:
.. code-block:: xml
-
+
-
-
+
+
-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.
+
+When you maintain a library, having the test suite fail as soon as a dependency
+introduces a new deprecation is not desirable, because it shifts the burden of
+fixing that deprecation to any contributor that happens to submit a pull
+request shortly after a new vendor release is made with that deprecation. To
+mitigate this, you can either use tighter requirements, in the hope that
+dependencies will not introduce deprecations in a patch version, or even commit
+the Composer lock file, which would create another class of issues. Libraries
+will often use ``SYMFONY_DEPRECATIONS_HELPER=weak`` 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.
+
+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
+rest of features provided by this component without getting errors or messages
+related to deprecations.
+
+.. _write-assertions-about-deprecations:
+
+Write Assertions about Deprecations
+-----------------------------------
+
+When adding deprecations to your code, you might like writing tests that verify
+that they are triggered as required. To do so, the bridge provides the
+``@expectedDeprecation`` annotation that you can use on your test methods.
+It requires you to pass the expected message, given in the same format as for
+the `PHPUnit's assertStringMatchesFormat()`_ method. If you expect more than one
+deprecation message for a given test method, you can use the annotation several
+times (order matters)::
-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.
+ /**
+ * @group legacy
+ * @expectedDeprecation This "%s" method is deprecated.
+ * @expectedDeprecation The second argument of the "%s" method is deprecated.
+ */
+ public function testDeprecatedCode()
+ {
+ @trigger_error('This "Foo" method is deprecated.', E_USER_DEPRECATED);
+ @trigger_error('The second argument of the "Bar" method is deprecated.', E_USER_DEPRECATED);
+ }
Display the Full Stack Trace
----------------------------
@@ -188,7 +280,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
@@ -200,10 +294,7 @@ Running the following command will display the full stack trace:
$ SYMFONY_DEPRECATIONS_HELPER='/Doctrine\\Common\\ClassLoader is deprecated\./' ./vendor/bin/simple-phpunit
Time-sensitive Tests
----------------------
-
-.. versionadded:: 2.8
- Support for clock mocking was introduced in Symfony 2.8.
+--------------------
Use Case
~~~~~~~~
@@ -240,8 +331,15 @@ 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()``.
+allows you to mock the PHP's built-in time functions ``time()``, ``microtime()``,
+``sleep()``, ``usleep()`` and ``gmdate()``. Additionally the function ``date()``
+is mocked so it uses the mocked time if no timestamp is specified.
+
+Other functions with an optional timestamp parameter that defaults to ``time()``
+will still use the system time instead of the mocked time. This means that you
+may need to change some code in your tests. For example, instead of ``new DateTime()``,
+you should use ``DateTime::createFromFormat('U', time())`` to use the mocked
+``time()`` function.
To use the ``ClockMock`` class in your test, add the ``@group time-sensitive``
annotation to its class or methods. This annotation only works when executing
@@ -253,7 +351,7 @@ following listener in your PHPUnit configuration:
-
+
.. note::
@@ -313,6 +411,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
@@ -338,24 +437,107 @@ different class, do it explicitly using ``ClockMock::register(MyClass::class)``:
advances the internal clock the given number of seconds without actually
waiting that time, so your test will execute 10 seconds faster.
+DNS-sensitive Tests
+-------------------
+
+Tests that make network connections, for example to check the validity of a DNS
+record, can be slow to execute and unreliable due to the conditions of the
+network. For that reason, this component also provides mocks for these PHP
+functions:
+
+* :phpfunction:`checkdnsrr`
+* :phpfunction:`dns_check_record`
+* :phpfunction:`getmxrr`
+* :phpfunction:`dns_get_mx`
+* :phpfunction:`gethostbyaddr`
+* :phpfunction:`gethostbyname`
+* :phpfunction:`gethostbynamel`
+* :phpfunction:`dns_get_record`
+
+Use Case
+~~~~~~~~
+
+Consider the following example that uses the ``checkMX`` option of the ``Email``
+constraint to test the validity of the email domain::
+
+ use PHPUnit\Framework\TestCase;
+ use Symfony\Component\Validator\Constraints\Email;
+
+ class MyTest extends TestCase
+ {
+ public function testEmail()
+ {
+ $validator = ...
+ $constraint = new Email(['checkMX' => true]);
+
+ $result = $validator->validate('foo@example.com', $constraint);
+
+ // ...
+ }
+ }
+
+In order to avoid making a real network connection, add the ``@dns-sensitive``
+annotation to the class and use the ``DnsMock::withMockedHosts()`` to configure
+the data you expect to get for the given hosts::
+
+ use PHPUnit\Framework\TestCase;
+ use Symfony\Component\Validator\Constraints\Email;
+
+ /**
+ * @group dns-sensitive
+ */
+ class MyTest extends TestCase
+ {
+ public function testEmails()
+ {
+ DnsMock::withMockedHosts(['example.com' => [['type' => 'MX']]]);
+
+ $validator = ...
+ $constraint = new Email(['checkMX' => true]);
+
+ $result = $validator->validate('foo@example.com', $constraint);
+
+ // ...
+ }
+ }
+
+The ``withMockedHosts()`` method configuration is defined as an array. The keys
+are the mocked hosts and the values are arrays of DNS records in the same format
+returned by :phpfunction:`dns_get_record`, so you can simulate diverse network
+conditions::
+
+ DnsMock::withMockedHosts([
+ 'example.com' => [
+ [
+ 'type' => 'A',
+ 'ip' => '1.2.3.4',
+ ],
+ [
+ 'type' => 'AAAA',
+ 'ipv6' => '::12',
+ ],
+ ],
+ ]);
+
Troubleshooting
-~~~~~~~~~~~~~~~
+---------------
-The ``@group time-sensitive`` works "by convention" and assumes that the
-namespace of the tested class can be obtained just by removing the ``Tests\``
-part from the test namespace. I.e. that if the your test case fully-qualified
-class name (FQCN) is ``App\Tests\Watch\DummyWatchTest``, it assumes the tested
-class namespace is ``App\Watch``.
+The ``@group time-sensitive`` and ``@group dns-sensitive`` annotations work
+"by convention" and assume that the namespace of the tested class can be
+obtained just by removing the ``Tests\`` part from the test namespace. I.e.
+if your test cases fully-qualified class name (FQCN) is
+``App\Tests\Watch\DummyWatchTest``, it assumes the tested class namespace
+is ``App\Watch``.
-If this convention doesn't work for your application, you can also configure
-the mocked namespaces in the ``phpunit.xml`` file, as done for example in the
+If this convention doesn't work for your application, configure the mocked
+namespaces in the ``phpunit.xml`` file, as done for example in the
:doc:`HttpKernel Component `:
.. code-block:: xml
@@ -371,12 +553,187 @@ the mocked namespaces in the ``phpunit.xml`` file, as done for example in the
-.. _PHPUnit: https://phpunit.de
+Under the hood, a PHPUnit listener injects the mocked functions in the tested
+classes' namespace. In order to work as expected, the listener has to run before
+the tested class ever runs. By default, the mocked functions are created when the
+annotation are found and the corresponding tests are run. Depending on how your
+tests are constructed, this might be too late. In this case, you will need to declare
+the namespaces of the tested classes in your ``phpunit.xml.dist``.
+
+.. code-block:: xml
+
+
+
+
+
+
+
+ Acme\MyClassTest
+
+
+
+
+
+Modified PHPUnit script
+-----------------------
+
+.. versionadded:: 3.2
+
+ This modified PHPUnit script was introduced in the 3.2 version of
+ this component.
+
+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;
+* 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``
+ env var is defined: the env var should specify a file name that will be used for
+ storing skipped tests on a first run, and replay them on the second run;
+* Parallelizes test suites execution when given a directory as argument, scanning
+ this directory for ``phpunit.xml.dist`` files up to ``SYMFONY_PHPUNIT_MAX_DEPTH``
+ levels (specified as an env var, defaults to ``3``);
+
+The script writes the modified PHPUnit it builds in a directory that can be
+configured by the ``SYMFONY_PHPUNIT_DIR`` env var, or in the same directory as
+the ``simple-phpunit`` if it is not provided.
+
+If you have installed the bridge through Composer, you can run it by calling e.g.:
+
+.. code-block:: terminal
+
+ $ vendor/bin/simple-phpunit
+
+.. tip::
+
+ It's possible to change the 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 ``SYMFONY_PHPUNIT_VERSION`` as a real env var
+ (not defined in a :doc:`dotenv ` file).
+
+.. tip::
+
+ If you still need to use ``prophecy`` (but not ``symfony/yaml``),
+ then set the ``SYMFONY_PHPUNIT_REMOVE`` env var to ``symfony/yaml``.
+
+Code Coverage Listener
+----------------------
+
+By default, the code coverage is computed with the following rule: if a line of
+code is executed, then it is marked as covered. The test which executes a
+line of code is therefore marked as "covering the line of code". This can be
+misleading.
+
+Consider the following example::
+
+ class Bar
+ {
+ public function barMethod()
+ {
+ return 'bar';
+ }
+ }
+
+ class Foo
+ {
+ private $bar;
+
+ public function __construct(Bar $bar)
+ {
+ $this->bar = $bar;
+ }
+
+ public function fooMethod()
+ {
+ $this->bar->barMethod();
+
+ return 'bar';
+ }
+ }
+
+ class FooTest extends PHPUnit\Framework\TestCase
+ {
+ public function test()
+ {
+ $bar = new Bar();
+ $foo = new Foo($bar);
+
+ $this->assertSame('bar', $foo->fooMethod());
+ }
+ }
+
+The ``FooTest::test`` method executes every single line of code of both ``Foo``
+and ``Bar`` classes, but ``Bar`` is not truly tested. The ``CoverageListener``
+aims to fix this behavior by adding the appropriate `@covers`_ annotation on
+each test class.
+
+If a test class already defines the ``@covers`` annotation, this listener does
+nothing. Otherwise, it tries to find the code related to the test by removing
+the ``Test`` part of the classname: ``My\Namespace\Tests\FooTest`` ->
+``My\Namespace\Foo``.
+
+Installation
+~~~~~~~~~~~~
+
+Add the following configuration to the ``phpunit.xml.dist`` file:
+
+.. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+If the logic used to find the related code is too simple or doesn't work for
+your application, you can use your own SUT (System Under Test) solver:
+
+.. code-block:: xml
+
+
+
+
+ My\Namespace\SutSolver::solve
+
+
+
+
+The ``My\Namespace\SutSolver::solve`` can be any PHP callable and receives the
+current test as its first argument.
+
+Finally, the listener can also display warning messages when the SUT solver does
+not find the SUT:
+
+.. code-block:: xml
+
+
+
+
+
+ true
+
+
+
+
+.. _`PHPUnit`: https://phpunit.de
.. _`PHPUnit event listener`: https://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/
+.. _`test listener`: https://phpunit.de/manual/current/en/appendixes.configuration.html#appendixes.configuration.test-listeners
+.. _`@covers`: https://phpunit.de/manual/current/en/appendixes.annotations.html#appendixes.annotations.covers
.. _`PHP namespace resolutions rules`: https://php.net/manual/en/language.namespaces.rules.php
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 c8969b9ff5e..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
@@ -30,3 +28,6 @@ Provided Functions
~~~~~~~~~~~~~~~~~~
* :phpfunction:`is_countable`
+* :phpfunction:`hrtime`
+* :phpfunction:`array_key_first`
+* :phpfunction:`array_key_last`
diff --git a/components/polyfill_uuid.rst b/components/polyfill_uuid.rst
new file mode 100644
index 00000000000..0fd080d39d4
--- /dev/null
+++ b/components/polyfill_uuid.rst
@@ -0,0 +1,57 @@
+.. index::
+ single: Polyfill
+ single: PHP
+ single: Components; Polyfill
+
+The Symfony Polyfill / UUID Component
+=====================================
+
+ This component provides ``uuid_*`` functions to users who run PHP versions
+ without the UUID extension.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/polyfill-uuid
+
+.. 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 UUID extension`_ is installed or not in your
+server.
+
+Provided Constants
+~~~~~~~~~~~~~~~~~~
+
+* ``UUID_VARIANT_NCS`` (value = 0)
+* ``UUID_VARIANT_DCE`` (value = 1)
+* ``UUID_VARIANT_MICROSOFT`` (value = 2)
+* ``UUID_VARIANT_OTHER`` (value = 3)
+* ``UUID_TYPE_DEFAULT`` (value = 0)
+* ``UUID_TYPE_TIME`` (value = 1)
+* ``UUID_TYPE_DCE`` (value = 4)
+* ``UUID_TYPE_NAME`` (value = 1)
+* ``UUID_TYPE_RANDOM`` (value = 4)
+* ``UUID_TYPE_NULL`` (value = -1)
+* ``UUID_TYPE_INVALID`` (value = -42)
+
+Provided Functions
+~~~~~~~~~~~~~~~~~~
+
+* :phpfunction:`uuid_create`
+* :phpfunction:`uuid_is_valid`
+* :phpfunction:`uuid_compare`
+* :phpfunction:`uuid_is_null`
+* :phpfunction:`uuid_type`
+* :phpfunction:`uuid_variant`
+* :phpfunction:`uuid_time`
+* :phpfunction:`uuid_mac`
+* :phpfunction:`uuid_parse`
+* :phpfunction:`uuid_unparse`
+
+.. _`PHP UUID extension`: https://pecl.php.net/package/uuid
diff --git a/components/process.rst b/components/process.rst
index 41130de11cd..0c3098400d4 100644
--- a/components/process.rst
+++ b/components/process.rst
@@ -12,9 +12,8 @@ Installation
.. code-block:: terminal
- $ composer require symfony/process
+ $ composer require symfony/process:^3.4
-Alternatively, you can clone the ``_ repository.
.. include:: /components/require_autoload.rst.inc
@@ -24,10 +23,10 @@ Usage
The :class:`Symfony\\Component\\Process\\Process` class allows you to execute
a command in a sub-process::
- use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;
+ use Symfony\Component\Process\Process;
- $process = new Process('ls -lsa');
+ $process = new Process(['ls', '-lsa']);
$process->run();
// executes after the command finishes
@@ -51,6 +50,38 @@ the contents of the output and
:method:`Symfony\\Component\\Process\\Process::clearErrorOutput` clears
the contents of the error output.
+You can also use the :class:`Symfony\\Component\\Process\\Process` class with the
+for each construct to get the output while it is generated. By default, the loop waits
+for new output before going to the next iteration::
+
+ $process = new Process(['ls', '-lsa']);
+ $process->start();
+
+ foreach ($process as $type => $data) {
+ if ($process::OUT === $type) {
+ echo "\nRead from stdout: ".$data;
+ } else { // $process::ERR === $type
+ echo "\nRead from stderr: ".$data;
+ }
+ }
+
+.. tip::
+
+ The Process component internally uses a PHP iterator to get the output while
+ it is generated. That iterator is exposed via the ``getIterator()`` method
+ to allow customizing its behavior::
+
+ $process = new Process(['ls', '-lsa']);
+ $process->start();
+ $iterator = $process->getIterator($process::ITER_SKIP_ERR | $process::ITER_KEEP_OUTPUT);
+ foreach ($iterator as $data) {
+ echo $data."\n";
+ }
+
+ .. versionadded:: 3.2
+
+ The ``getIterator()`` method was introduced in Symfony 3.2.
+
The ``mustRun()`` method is identical to ``run()``, except that it will throw
a :class:`Symfony\\Component\\Process\\Exception\\ProcessFailedException`
if the process couldn't be executed successfully (i.e. the process exited
@@ -59,7 +90,7 @@ with a non-zero code)::
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;
- $process = new Process('ls -lsa');
+ $process = new Process(['ls', '-lsa']);
try {
$process->mustRun();
@@ -69,17 +100,51 @@ with a non-zero code)::
echo $exception->getMessage();
}
+.. tip::
+
+ .. versionadded:: 3.3
+
+ The ability to define commands as arrays of arguments was introduced in
+ Symfony 3.3.
+
+ 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', '--option', 'argument', 'etc.']);
+ $process = new Process(['/path/to/php', '--define', 'memory_limit=1024M', '/path/to/script.php']);
+
+ If you need to use stream redirections, conditional execution, or any other
+ feature provided by the shell of your operating system, you can also define
+ commands as strings.
+
+ 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.
+
+ 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::
+
+ // On Unix-like OSes (Linux, macOS)
+ $process = new Process('echo "$MESSAGE"');
+
+ // On Windows
+ $process = new Process('echo "!MESSAGE!"');
+
+ // On both Unix-like and Windows
+ $process->run(null, ['MESSAGE' => 'Something to output']);
+
Getting real-time Process Output
--------------------------------
-When executing a long running command (like rsync-ing files to a remote
+When executing a long running command (like ``rsync`` to a remote
server), you can give feedback to the end user in real-time by passing an
anonymous function to the
:method:`Symfony\\Component\\Process\\Process::run` method::
use Symfony\Component\Process\Process;
- $process = new Process('ls -lsa');
+ $process = new Process(['ls', '-lsa']);
$process->run(function ($type, $buffer) {
if (Process::ERR === $type) {
echo 'ERR > '.$buffer;
@@ -88,6 +153,12 @@ anonymous function to the
}
});
+.. note::
+
+ This feature won't work as expected in servers using PHP output buffering.
+ In those cases, either disable the `output_buffering`_ PHP option or use the
+ :phpfunction:`ob_flush` PHP function to force sending the output buffer.
+
Running Processes Asynchronously
--------------------------------
@@ -98,7 +169,7 @@ process, the :method:`Symfony\\Component\\Process\\Process::isRunning` method
to check if the process is done and the
:method:`Symfony\\Component\\Process\\Process::getOutput` method to get the output::
- $process = new Process('ls -lsa');
+ $process = new Process(['ls', '-lsa']);
$process->start();
while ($process->isRunning()) {
@@ -110,7 +181,7 @@ to check if the process is done and the
You can also wait for a process to end if you started it asynchronously and
are done doing other stuff::
- $process = new Process('ls -lsa');
+ $process = new Process(['ls', '-lsa']);
$process->start();
// ... do other things
@@ -149,7 +220,7 @@ are done doing other stuff::
a callback that is called repeatedly whilst the process is still running, passing
in the output and its type::
- $process = new Process('ls -lsa');
+ $process = new Process(['ls', '-lsa']);
$process->start();
$process->wait(function ($type, $buffer) {
@@ -160,20 +231,80 @@ in the output and its type::
}
});
+Streaming to the Standard Input of a Process
+--------------------------------------------
+
+Before a process is started, you can specify its standard input using either the
+:method:`Symfony\\Component\\Process\\Process::setInput` method or the 4th argument
+of the constructor. The provided input can be a string, a stream resource or a
+``Traversable`` object::
+
+ $process = new Process('cat');
+ $process->setInput('foobar');
+ $process->run();
+
+When this input is fully written to the subprocess standard input, the corresponding
+pipe is closed.
+
+In order to write to a subprocess standard input while it is running, the component
+provides the :class:`Symfony\\Component\\Process\\InputStream` class::
+
+ $input = new InputStream();
+ $input->write('foo');
+
+ $process = new Process(['cat']);
+ $process->setInput($input);
+ $process->start();
+
+ // ... read process output or do other things
+
+ $input->write('bar');
+ $input->close();
+
+ $process->wait();
+
+ // will echo: foobar
+ echo $process->getOutput();
+
+The :method:`Symfony\\Component\\Process\\InputStream::write` method accepts scalars,
+stream resources or ``Traversable`` objects as argument. As shown in the above example,
+you need to explicitly call the :method:`Symfony\\Component\\Process\\InputStream::close`
+method when you are done writing to the standard input of the subprocess.
+
+Using PHP Streams as the Standard Input of a Process
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The input of a process can also be defined using `PHP streams`_::
+
+ $stream = fopen('php://temporary', 'w+');
+
+ $process = new Process(['cat']);
+ $process->setInput($stream);
+ $process->start();
+
+ fwrite($stream, 'foo');
+
+ // ... read process output or do other things
+
+ fwrite($stream, 'bar');
+ fclose($stream);
+
+ $process->wait();
+
+ // will echo: 'foobar'
+ echo $process->getOutput();
+
Stopping a Process
------------------
-.. versionadded:: 2.3
- The ``signal`` parameter of the ``stop()`` method was introduced in Symfony 2.3.
-
Any asynchronous process can be stopped at any time with the
:method:`Symfony\\Component\\Process\\Process::stop` method. This method takes
two arguments: a timeout and a signal. Once the timeout is reached, the signal
is sent to the running process. The default signal sent to a process is ``SIGKILL``.
-Please read the :ref:`signal documentation below`
+Please read the :ref:`signal documentation below `
to find out more about signal handling in the Process component::
- $process = new Process('ls -lsa');
+ $process = new Process(['ls', '-lsa']);
$process->start();
// ... do other things
@@ -189,47 +320,11 @@ instead::
use Symfony\Component\Process\PhpProcess;
$process = new PhpProcess(<<
+ = 'Hello World' ?>
EOF
);
$process->run();
-To make your code work better on all platforms, you might want to use the
-:class:`Symfony\\Component\\Process\\ProcessBuilder` class instead::
-
- use Symfony\Component\Process\ProcessBuilder;
-
- $processBuilder = new ProcessBuilder(array('ls', '-lsa'));
- $processBuilder->getProcess()->run();
-
-.. versionadded:: 2.3
- The :method:`ProcessBuilder::setPrefix`
- method was introduced in Symfony 2.3.
-
-In case you are building a binary driver, you can use the
-:method:`Symfony\\Component\\Process\\ProcessBuilder::setPrefix` method to prefix all
-the generated process commands.
-
-The following example will generate two process commands for a tar binary
-adapter::
-
- use Symfony\Component\Process\ProcessBuilder;
-
- $processBuilder = new ProcessBuilder();
- $processBuilder->setPrefix('/usr/bin/tar');
-
- // '/usr/bin/tar' '--list' '--file=archive.tar.gz'
- echo $processBuilder
- ->setArguments(array('--list', '--file=archive.tar.gz'))
- ->getProcess()
- ->getCommandLine();
-
- // '/usr/bin/tar' '-xzf' 'archive.tar.gz'
- echo $processBuilder
- ->setArguments(array('-xzf', 'archive.tar.gz'))
- ->getProcess()
- ->getCommandLine();
-
Process Timeout
---------------
@@ -238,12 +333,12 @@ a different timeout (in seconds) to the ``setTimeout()`` method::
use Symfony\Component\Process\Process;
- $process = new Process('ls -lsa');
+ $process = new Process(['ls', '-lsa']);
$process->setTimeout(3600);
$process->run();
If the timeout is reached, a
-:class:`Symfony\\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::
@@ -270,7 +365,7 @@ considers the time since the last output was produced by the process::
use Symfony\Component\Process\Process;
- $process = new Process('something-with-variable-runtime');
+ $process = new Process(['something-with-variable-runtime']);
$process->setTimeout(3600);
$process->setIdleTimeout(60);
$process->run();
@@ -281,51 +376,30 @@ exceeds 3600 seconds, or the process does not produce any output for 60 seconds.
Process Signals
---------------
-.. versionadded:: 2.3
- The ``signal()`` method was introduced in Symfony 2.3.
-
When running a program asynchronously, you can send it POSIX signals with the
:method:`Symfony\\Component\\Process\\Process::signal` method::
use Symfony\Component\Process\Process;
- $process = new Process('find / -name "rabbit"');
+ $process = new Process(['find', '/', '-name', 'rabbit']);
$process->start();
// will send a SIGKILL to the process
$process->signal(SIGKILL);
-.. caution::
-
- Due to some limitations in PHP, if you're using signals with the Process
- component, you may have to prefix your commands with `exec`_. Please read
- `Symfony Issue#5759`_ and `PHP Bug#39992`_ to understand why this is happening.
-
- POSIX signals are not available on Windows platforms, please refer to the
- `PHP documentation`_ for available signals.
-
-Process Pid
+Process PID
-----------
-.. versionadded:: 2.3
- The ``getPid()`` method was introduced in Symfony 2.3.
-
-You can access the `pid`_ of a running process with the
+You can access the `PID`_ of a running process with the
:method:`Symfony\\Component\\Process\\Process::getPid` method::
use Symfony\Component\Process\Process;
- $process = new Process('/usr/bin/php worker.php');
+ $process = new Process(['/usr/bin/php', 'worker.php']);
$process->start();
$pid = $process->getPid();
-.. caution::
-
- Due to some limitations in PHP, if you want to get the pid of a symfony Process,
- you may have to prefix your commands with `exec`_. Please read
- `Symfony Issue#5759`_ to understand why this is happening.
-
Disabling Output
----------------
@@ -336,7 +410,7 @@ Use :method:`Symfony\\Component\\Process\\Process::disableOutput` and
use Symfony\Component\Process\Process;
- $process = new Process('/usr/bin/php worker.php');
+ $process = new Process(['/usr/bin/php', 'worker.php']);
$process->disableOutput();
$process->run();
@@ -345,9 +419,16 @@ Use :method:`Symfony\\Component\\Process\\Process::disableOutput` and
You cannot enable or disable the output while the process is running.
If you disable the output, you cannot access ``getOutput()``,
- ``getIncrementalOutput()``, ``getErrorOutput()`` or ``getIncrementalErrorOutput()``.
- Moreover, you could not pass a callback to the ``start()``, ``run()`` or ``mustRun()``
- methods or use ``setIdleTimeout()``.
+ ``getIncrementalOutput()``, ``getErrorOutput()``, ``getIncrementalErrorOutput()`` or
+ ``setIdleTimeout()``.
+
+ However, it is possible to pass a callback to the ``start``, ``run`` or ``mustRun``
+ methods to handle process output in a streaming fashion.
+
+ .. versionadded:: 3.1
+
+ The ability to pass a callback to these methods when output is disabled
+ was introduced in Symfony 3.1.
Finding the Executable PHP Binary
---------------------------------
@@ -362,9 +443,6 @@ absolute path of the executable PHP binary available on your server::
$phpBinaryPath = $phpBinaryFinder->find();
// $phpBinaryPath = '/usr/local/bin/php' (the result will be different on your computer)
-.. _`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
+.. _`PID`: https://en.wikipedia.org/wiki/Process_identifier
+.. _`PHP streams`: https://www.php.net/manual/en/book.stream.php
+.. _`output_buffering`: https://www.php.net/manual/en/outcontrol.configuration.php
diff --git a/components/property_access.rst b/components/property_access.rst
index 037f9cc0394..d1732b29893 100644
--- a/components/property_access.rst
+++ b/components/property_access.rst
@@ -13,9 +13,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/property-access
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require symfony/property-access:^3.4
.. include:: /components/require_autoload.rst.inc
@@ -32,10 +30,6 @@ default configuration::
$propertyAccessor = PropertyAccess::createPropertyAccessor();
-.. versionadded:: 2.3
- The :method:`Symfony\\Component\\PropertyAccess\\PropertyAccess::createPropertyAccessor`
- method was introduced in Symfony 2.3. Previously, it was called ``getPropertyAccessor()``.
-
Reading from Arrays
-------------------
@@ -44,9 +38,9 @@ You can read an array with the
method. This is done using the index notation that is used in PHP::
// ...
- $person = array(
+ $person = [
'first_name' => 'Wouter',
- );
+ ];
var_dump($propertyAccessor->getValue($person, '[first_name]')); // 'Wouter'
var_dump($propertyAccessor->getValue($person, '[age]')); // null
@@ -61,9 +55,9 @@ method::
->enableExceptionOnInvalidIndex()
->getPropertyAccessor();
- $person = array(
+ $person = [
'first_name' => 'Wouter',
- );
+ ];
// instead of returning null, the code now throws an exception of type
// Symfony\Component\PropertyAccess\Exception\NoSuchIndexException
@@ -72,14 +66,14 @@ method::
You can also use multi dimensional arrays::
// ...
- $persons = array(
- array(
+ $persons = [
+ [
'first_name' => 'Wouter',
- ),
- array(
+ ],
+ [
'first_name' => 'Ryan',
- )
- );
+ ]
+ ];
var_dump($propertyAccessor->getValue($persons, '[0][first_name]')); // 'Wouter'
var_dump($propertyAccessor->getValue($persons, '[1][first_name]')); // 'Ryan'
@@ -103,7 +97,7 @@ To read from properties, use the "dot" notation::
$child = new Person();
$child->firstName = 'Bar';
- $person->children = array($child);
+ $person->children = [$child];
var_dump($propertyAccessor->getValue($person, 'children[0].firstName')); // 'Bar'
@@ -118,9 +112,9 @@ Using Getters
~~~~~~~~~~~~~
The ``getValue()`` method also supports reading using getters. The method will
-be created using common naming conventions for getters. It camelizes the
-property name (``first_name`` becomes ``FirstName``) and prefixes it with
-``get``. So the actual method becomes ``getFirstName()``::
+be created using common naming conventions for getters. It transforms the
+property name to camelCase (``first_name`` becomes ``FirstName``) and prefixes
+it with ``get``. So the actual method becomes ``getFirstName()``::
// ...
class Person
@@ -148,7 +142,7 @@ getters, this means that you can do something like this::
class Person
{
private $author = true;
- private $children = array();
+ private $children = [];
public function isAuthor()
{
@@ -164,13 +158,13 @@ getters, this means that you can do something like this::
$person = new Person();
if ($propertyAccessor->getValue($person, 'author')) {
- var_dump('He is an author');
+ var_dump('This person is an author');
}
if ($propertyAccessor->getValue($person, 'children')) {
- var_dump('He has children');
+ var_dump('This person has children');
}
-This will produce: ``He is an author``
+This will produce: ``This person is an author``
Magic ``__get()`` Method
~~~~~~~~~~~~~~~~~~~~~~~~
@@ -180,9 +174,9 @@ The ``getValue()`` method can also use the magic ``__get()`` method::
// ...
class Person
{
- private $children = array(
- 'Wouter' => array(...),
- );
+ private $children = [
+ 'Wouter' => [...],
+ ];
public function __get($id)
{
@@ -192,7 +186,7 @@ The ``getValue()`` method can also use the magic ``__get()`` method::
$person = new Person();
- var_dump($propertyAccessor->getValue($person, 'Wouter')); // array(...)
+ var_dump($propertyAccessor->getValue($person, 'Wouter')); // [...]
.. _components-property-access-magic-call:
@@ -205,9 +199,9 @@ enable this feature by using :class:`Symfony\\Component\\PropertyAccess\\Propert
// ...
class Person
{
- private $children = array(
- 'wouter' => array(...),
- );
+ private $children = [
+ 'wouter' => [...],
+ ];
public function __call($name, $args)
{
@@ -230,10 +224,7 @@ enable this feature by using :class:`Symfony\\Component\\PropertyAccess\\Propert
->enableMagicCall()
->getPropertyAccessor();
- var_dump($propertyAccessor->getValue($person, 'wouter')); // array(...)
-
-.. versionadded:: 2.3
- The use of magic ``__call()`` method was introduced in Symfony 2.3.
+ var_dump($propertyAccessor->getValue($person, 'wouter')); // [...]
.. caution::
@@ -250,7 +241,7 @@ also write to an array. This can be achieved using the
method::
// ...
- $person = array();
+ $person = [];
$propertyAccessor->setValue($person, '[first_name]', 'Wouter');
@@ -269,7 +260,7 @@ can use setters, the magic ``__set()`` method or properties to set values::
{
public $firstName;
private $lastName;
- private $children = array();
+ private $children = [];
public function setLastName($name)
{
@@ -296,21 +287,19 @@ can use setters, the magic ``__set()`` method or properties to set values::
$propertyAccessor->setValue($person, 'firstName', 'Wouter');
$propertyAccessor->setValue($person, 'lastName', 'de Jong'); // setLastName is called
- $propertyAccessor->setValue($person, 'children', array(new Person())); // __set is called
+ $propertyAccessor->setValue($person, 'children', [new Person()]); // __set is called
var_dump($person->firstName); // 'Wouter'
var_dump($person->getLastName()); // 'de Jong'
- var_dump($person->getChildren()); // array(Person());
+ 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
{
- private $children = array();
+ private $children = [];
public function __call($name, $args)
{
@@ -334,17 +323,15 @@ see `Enable other Features`_.
->enableMagicCall()
->getPropertyAccessor();
- $propertyAccessor->setValue($person, 'wouter', array(...));
+ $propertyAccessor->setValue($person, 'wouter', [...]);
- var_dump($person->getWouter()); // array(...)
+ var_dump($person->getWouter()); // [...]
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
@@ -352,7 +339,7 @@ properties through *adder* and *remover* methods.
/**
* @var string[]
*/
- private $children = array();
+ private $children = [];
public function getChildren(): array
{
@@ -371,9 +358,9 @@ properties through *adder* and *remover* methods.
}
$person = new Person();
- $propertyAccessor->setValue($person, 'children', array('kevin', 'wouter'));
+ $propertyAccessor->setValue($person, 'children', ['kevin', 'wouter']);
- var_dump($person->getChildren()); // array('kevin', 'wouter')
+ var_dump($person->getChildren()); // ['kevin', 'wouter']
The PropertyAccess component checks for methods called ``add()``
and ``remove()``. Both methods must be defined.
@@ -418,7 +405,7 @@ You can also mix objects and arrays::
class Person
{
public $firstName;
- private $children = array();
+ private $children = [];
public function setChildren($children)
{
@@ -474,5 +461,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 4e18845315a..05814ddb884 100644
--- a/components/property_info.rst
+++ b/components/property_info.rst
@@ -14,9 +14,6 @@ component works solely with class definitions to provide information about the
data type and visibility - including via getter or setter methods - of the properties
within that class.
-.. versionadded:: 2.8
- The PropertyInfo component was introduced in Symfony 2.8.
-
.. _`components-property-information-installation`:
Installation
@@ -24,9 +21,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/property-info
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require symfony/property-info:^3.4
.. include:: /components/require_autoload.rst.inc
@@ -40,30 +35,28 @@ Usage
To use this component, create a new
:class:`Symfony\\Component\\PropertyInfo\\PropertyInfoExtractor` instance and
-provide it with a set of information extractors.
+provide it with a set of information extractors::
-.. code-block:: php
-
- 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
- $listExtractors = array($reflectionExtractor);
+ $listExtractors = [$reflectionExtractor];
// array of PropertyTypeExtractorInterface
- $typeExtractors = array($phpDocExtractor, $reflectionExtractor);
+ $typeExtractors = [$phpDocExtractor, $reflectionExtractor];
// array of PropertyDescriptionExtractorInterface
- $descriptionExtractors = array($phpDocExtractor);
+ $descriptionExtractors = [$phpDocExtractor];
// array of PropertyAccessExtractorInterface
- $accessExtractors = array($reflectionExtractor);
+ $accessExtractors = [$reflectionExtractor];
$propertyInfo = new PropertyInfoExtractor(
$listExtractors,
@@ -93,9 +86,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;
@@ -106,15 +97,15 @@ both provide list and type information it is probably better that:
$propertyInfo = new PropertyInfoExtractor(
// List extractors
- array(
+ [
$reflectionExtractor,
$doctrineExtractor
- ),
+ ],
// Type extractors
- array(
+ [
$doctrineExtractor,
$reflectionExtractor
- )
+ ]
);
.. _`components-property-information-extractable-information`:
@@ -149,19 +140,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:
@@ -171,26 +160,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.
@@ -202,24 +188,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:
@@ -228,9 +212,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,8 +222,35 @@ provide whether properties are readable or writable as booleans.
The :class:`Symfony\\Component\\PropertyInfo\\Extractor\\ReflectionExtractor` looks
for getter/isser/setter method in addition to whether or not a property is public
-to determine if it's accessible. This based on how the :doc:`PropertyAccess `
-works.
+to determine if it's accessible.
+
+This is based on how :doc:`PropertyAccess ` works,
+so it even looks for adder/remover methods and can transform between singular
+and plural property names::
+
+ use Acme\Entity\Analysis;
+
+ class SomeClass
+ {
+ private $analyses;
+
+ public function addAnalysis(Analysis $analysis)
+ {
+ // ...
+ }
+
+ public function removeAnalysis(Analysis $analysis)
+ {
+ // ...
+ }
+ }
+
+ // to be writable, both the adder and the remover methods must be defined
+ $propertyInfo->isWritable(SomeClass::class, 'analyses'); // returns true
+
+.. versionadded:: 3.2
+
+ The support of adder/remover methods was introduced in Symfony 3.2.
.. tip::
@@ -279,8 +288,8 @@ Each object will provide 6 attributes, available in the 6 methods:
.. _`components-property-info-type-builtin`:
-Type::getBuiltInType()
-~~~~~~~~~~~~~~~~~~~~~~
+``Type::getBuiltInType()``
+~~~~~~~~~~~~~~~~~~~~~~~~~~
The :method:`Type::getBuiltinType() `
method returns the built-in PHP data type, which can be one of these
@@ -290,22 +299,22 @@ string values: ``array``, ``bool``, ``callable``, ``float``, ``int``,
Constants inside the :class:`Symfony\\Component\\PropertyInfo\\Type`
class, in the form ``Type::BUILTIN_TYPE_*``, are provided for convenience.
-Type::isNullable()
-~~~~~~~~~~~~~~~~~~
+``Type::isNullable()``
+~~~~~~~~~~~~~~~~~~~~~~
The :method:`Type::isNullable() `
method will return a boolean value indicating whether the property parameter
can be set to ``null``.
-Type::getClassName()
-~~~~~~~~~~~~~~~~~~~~
+``Type::getClassName()``
+~~~~~~~~~~~~~~~~~~~~~~~~
If the :ref:`built-in PHP data type `
is ``object``, the :method:`Type::getClassName() `
method will return the fully-qualified class or interface name accepted.
-Type::isCollection()
-~~~~~~~~~~~~~~~~~~~~
+``Type::isCollection()``
+~~~~~~~~~~~~~~~~~~~~~~~~
The :method:`Type::isCollection() `
method will return a boolean value indicating if the property parameter is
@@ -317,8 +326,8 @@ this returns ``true`` if:
* 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).
-Type::getCollectionKeyType() & Type::getCollectionValueType()
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+``Type::getCollectionKeyType()`` & ``Type::getCollectionValueType()``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If the property is a collection, additional type objects may be returned
for both the key and value types of the collection (if the information is
@@ -349,21 +358,7 @@ 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
-
- # app/config/config.yml
- framework:
- property_info:
- enabled: true
-
-.. code-block:: php
+It can also provide return and scalar types for PHP 7+::
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
@@ -377,6 +372,18 @@ It can also provide return and scalar types for PHP 7+.
$reflectionExtractor->isReadable($class, $property);
$reflectionExtractor->isWritable($class, $property);
+.. note::
+
+ When using the Symfony framework, this service is automatically registered
+ when the ``property_info`` feature is enabled:
+
+ .. code-block:: yaml
+
+ # app/config/config.yml
+ framework:
+ property_info:
+ enabled: true
+
PhpDocExtractor
~~~~~~~~~~~~~~~
@@ -388,9 +395,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;
@@ -413,9 +418,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;
@@ -441,9 +444,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;
@@ -483,10 +484,9 @@ 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
diff --git a/components/psr7.rst b/components/psr7.rst
index 26f9fd0dbbb..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(array(), array(), array(), array(), array(), array('HTTP_HOST' => 'dunglas.fr'), 'Content');
+ $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 3fb4b2c4e8a..d081f381bc6 100644
--- a/components/routing.rst
+++ b/components/routing.rst
@@ -13,9 +13,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/routing
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require symfony/routing:^3.4
.. include:: /components/require_autoload.rst.inc
@@ -30,7 +28,7 @@ Usage
In order to set up a basic routing system you need three parts:
-* A :class:`Symfony\\Component\\Routing\\RouteCollection`, which contains the route definitions (instances of the class :class:`Symfony\\Component\\Routing\\Route`)
+* A :class:`Symfony\\Component\\Routing\\RouteCollection`, which contains the route definitions (instances of the :class:`Symfony\\Component\\Routing\\Route` class)
* 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
@@ -39,10 +37,10 @@ your autoloader to load the Routing component::
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
- use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
+ use Symfony\Component\Routing\RouteCollection;
- $route = new Route('/foo', array('_controller' => 'MyController'));
+ $route = new Route('/foo', ['_controller' => 'MyController']);
$routes = new RouteCollection();
$routes->add('route_name', $route);
@@ -51,7 +49,7 @@ your autoloader to load the Routing component::
$matcher = new UrlMatcher($routes, $context);
$parameters = $matcher->match('/foo');
- // array('_controller' => 'MyController', '_route' => 'route_name')
+ // ['_controller' => 'MyController', '_route' => 'route_name']
.. note::
@@ -82,20 +80,24 @@ be thrown.
Defining Routes
~~~~~~~~~~~~~~~
-A full route definition can contain up to seven parts:
+A full route definition can contain up to eight 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.
+#. The URL pattern. This is matched against the URL passed to the
+ ``RequestContext``. It is not a regular expression, but can contain named
+ wildcard placeholders (e.g. ``{slug}``) to match dynamic parts in the URL.
+ The component will create the regular expression from it.
-#. 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 default parameters. This contains an array of arbitrary values
+ that will be returned when the request matches the route. It is used by
+ convention to map a controller to the route.
#. An array of requirements. These define constraints for the values of the
- placeholders as regular expressions.
+ placeholders in the pattern as regular expressions.
-#. An array of options. These contain internal settings for the route and
- are the least commonly needed.
+#. An array of options. These contain advanced settings for the route and
+ can be used to control encoding or customize compilation.
+ See :ref:`routing-unicode-support` below. You can learn more about them by
+ reading :method:`Symfony\\Component\\Routing\\Route::setOptions` implementation.
#. A host. This is matched against the host of the request. See
:doc:`/routing/hostname_pattern` for more details.
@@ -105,27 +107,32 @@ A full route definition can contain up to seven parts:
#. An array of methods. These enforce a certain HTTP request method (``HEAD``,
``GET``, ``POST``, ...).
+#. A condition, using the :doc:`/components/expression_language/syntax`.
+ A string that must evaluate to ``true`` so the route matches. See
+ :doc:`/routing/conditions` for more details.
+
Take the following route, which combines several of these ideas::
$route = new Route(
'/archive/{month}', // path
- array('_controller' => 'showArchive'), // default values
- array('month' => '[0-9]{4}-[0-9]{2}', 'subdomain' => 'www|m'), // requirements
- array(), // options
+ ['_controller' => 'showArchive'], // default values
+ ['month' => '[0-9]{4}-[0-9]{2}', 'subdomain' => 'www|m'], // requirements
+ [], // options
'{subdomain}.example.com', // host
- array(), // schemes
- array() // methods
+ [], // schemes
+ [], // methods
+ 'context.getHost() matches "/(secure|admin).example.com/"' // condition
);
// ...
$parameters = $matcher->match('/archive/2012-01');
- // array(
+ // [
// '_controller' => 'showArchive',
// 'month' => '2012-01',
// 'subdomain' => 'www',
// '_route' => ...
- // )
+ // ]
$parameters = $matcher->match('/archive/foo');
// throws ResourceNotFoundException
@@ -138,19 +145,22 @@ 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::
+A placeholder matches any character except slashes ``/`` by default, unless you define
+a specific requirement for it.
+The reason is that they are used by convention to separate different placeholders.
+
+If you want a placeholder to match anything, it must be the last of the route::
- 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/{required}/{anything}',
+ ['required' => 'default'], // should always be defined
+ ['anything' => '.*'] // explicit requirement to allow "/"
+ );
- $route = new Route(
- '/start/{suffix}',
- array('suffix' => ''),
- array('suffix' => '.*')
- );
+Learn more about it by reading :ref:`routing/slash_in_parameter`.
-Using Prefixes
-~~~~~~~~~~~~~~
+Using Prefixes and Collection Settings
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can add routes or other instances of
:class:`Symfony\\Component\\Routing\\RouteCollection` to *another* collection.
@@ -165,12 +175,13 @@ host to all routes of a subtree using methods provided by the
$subCollection->add(...);
$subCollection->add(...);
$subCollection->addPrefix('/prefix');
- $subCollection->addDefaults(array(...));
- $subCollection->addRequirements(array(...));
- $subCollection->addOptions(array(...));
- $subCollection->setHost('admin.example.com');
- $subCollection->setMethods(array('POST'));
- $subCollection->setSchemes(array('https'));
+ $subCollection->addDefaults([...]);
+ $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);
@@ -210,7 +221,7 @@ 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::
+a certain route with the :class:`Symfony\\Component\\Routing\\Generator\\UrlGenerator`::
use Symfony\Component\Routing\Generator\UrlGenerator;
use Symfony\Component\Routing\RequestContext;
@@ -224,9 +235,9 @@ a certain route::
$generator = new UrlGenerator($routes, $context);
- $url = $generator->generate('show_post', array(
+ $url = $generator->generate('show_post', [
'slug' => 'my-blog-post',
- ));
+ ]);
// /show/my-blog-post
.. note::
@@ -260,7 +271,7 @@ when the route doesn't exist::
Load Routes from a File
~~~~~~~~~~~~~~~~~~~~~~~
-You've already seen how you can easily add routes to a collection right inside
+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
@@ -291,7 +302,7 @@ To load this file, you can use the following code. This assumes that your
use Symfony\Component\Routing\Loader\YamlFileLoader;
// looks inside *this* directory
- $fileLocator = new FileLocator(array(__DIR__));
+ $fileLocator = new FileLocator([__DIR__]);
$loader = new YamlFileLoader($fileLocator);
$routes = $loader->load('routes.yml');
@@ -305,13 +316,13 @@ If you use the :class:`Symfony\\Component\\Routing\\Loader\\PhpFileLoader` you
have to provide the name of a PHP file which returns a :class:`Symfony\\Component\\Routing\\RouteCollection`::
// RouteProvider.php
- use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
+ use Symfony\Component\Routing\RouteCollection;
$routes = new RouteCollection();
$routes->add(
'route_name',
- new Route('/foo', array('_controller' => 'ExampleController'))
+ new Route('/foo', ['_controller' => 'ExampleController'])
);
// ...
@@ -347,13 +358,13 @@ The all-in-one Router
~~~~~~~~~~~~~~~~~~~~~
The :class:`Symfony\\Component\\Routing\\Router` class is an all-in-one package
-to quickly use the Routing component. The constructor expects a loader instance,
+to use the Routing component. The constructor expects a loader instance,
a path to the main route definition and some other settings::
public function __construct(
LoaderInterface $loader,
$resource,
- array $options = array(),
+ array $options = [],
RequestContext $context = null,
LoggerInterface $logger = null
);
@@ -363,16 +374,17 @@ path) or disable caching (if it's set to ``null``). The caching is done
automatically in the background if you want to use it. A basic example of the
:class:`Symfony\\Component\\Routing\\Router` class would look like::
- $fileLocator = new FileLocator(array(__DIR__));
+ $fileLocator = new FileLocator([__DIR__]);
$requestContext = new RequestContext('/');
$router = new Router(
new YamlFileLoader($fileLocator),
'routes.yml',
- array('cache_dir' => __DIR__.'/cache'),
+ ['cache_dir' => __DIR__.'/cache'],
$requestContext
);
- $router->match('/foo/bar');
+ $parameters = $router->match('/foo/bar');
+ $url = $router->generate('some_route', ['parameter' => 'value']);
.. note::
@@ -380,6 +392,182 @@ 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.
+.. _routing-unicode-support:
+
+Unicode Routing Support
+~~~~~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 3.2
+
+ UTF-8 support for route paths and requirements was introduced in
+ Symfony 3.2.
+
+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
+
+ // src/AppBundle/Controller/DefaultController.php
+ namespace AppBundle\Controller;
+
+ use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+ use Symfony\Component\Routing\Annotation\Route;
+
+ class DefaultController extends Controller
+ {
+ /**
+ * @Route("/category/{name}", name="route1", options={"utf8": true})
+ */
+ public function categoryAction()
+ {
+ // ...
+ }
+
+ .. code-block:: yaml
+
+ # app/config/routing.yml
+ route1:
+ path: /category/{name}
+ defaults: { _controller: 'AppBundle:Default:category' }
+ options:
+ utf8: true
+
+ .. code-block:: xml
+
+
+
+
+
+
+ AppBundle:Default:category
+
+
+
+
+ .. code-block:: php
+
+ // app/config/routing.php
+ use Symfony\Component\Routing\Route;
+ use Symfony\Component\Routing\RouteCollection;
+
+ $routes = new RouteCollection();
+ $routes->add('route1', new Route('/category/{name}',
+ [
+ '_controller' => 'AppBundle:Default: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
+
+ // src/AppBundle/Controller/DefaultController.php
+ namespace AppBundle\Controller;
+
+ use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+ use Symfony\Component\Routing\Annotation\Route;
+
+ class DefaultController extends Controller
+ {
+ /**
+ * @Route(
+ * "/category/{name}",
+ * name="route2",
+ * defaults={"name": "한국어"},
+ * options={"utf8": true}
+ * )
+ */
+ public function defaultAction()
+ {
+ // ...
+ }
+
+ .. code-block:: yaml
+
+ # app/config/routing.yml
+ route2:
+ path: /category/{name}
+ defaults:
+ _controller: 'AppBundle:Default:default'
+ name: '한국어'
+ options:
+ utf8: true
+
+ .. code-block:: xml
+
+
+
+
+
+
+ AppBundle:Default:default
+ 한국어
+
+
+
+
+ .. code-block:: php
+
+ // app/config/routing.php
+ use Symfony\Component\Routing\Route;
+ use Symfony\Component\Routing\RouteCollection;
+
+ $routes = new RouteCollection();
+ $routes->add('route2', new Route('/default/{default}',
+ [
+ '_controller' => 'AppBundle:Default:default',
+ 'name' => '한국어',
+ ],
+ [],
+ [
+ '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.
+
+.. note::
+
+ In Symfony 3.2, there is no need to explicitly set the ``utf8`` option.
+ As soon as Symfony finds a UTF-8 character in the route path or requirements,
+ it will automatically turn on the UTF-8 support. However, this behavior
+ is deprecated and setting the option will be required in Symfony 4.0.
+
Learn more
----------
@@ -391,6 +579,5 @@ Learn more
/routing/*
/controller
/controller/*
- /configuration/apache_router
-.. _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 4449f45d16e..ae3d5c2ca69 100644
--- a/components/security.rst
+++ b/components/security.rst
@@ -6,24 +6,22 @@ The Security Component
The Security component provides a complete security system for your web
application. It ships with facilities for authenticating using HTTP basic
- or digest authentication, interactive form login or X.509 certificate
- login, but also allows you to implement your own authentication strategies.
- Furthermore, the component provides ways to authorize authenticated users
- based on their roles, and it contains an advanced ACL system.
+ authentication, interactive form login or X.509 certificate login, but also
+ allows you to implement your own authentication strategies. Furthermore, the
+ component provides ways to authorize authenticated users based on their
+ roles.
Installation
------------
.. code-block:: terminal
- $ composer require symfony/security
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require symfony/security:^3.4
.. 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,8 +34,9 @@ used separately:
``symfony/security-csrf``
It provides protection against `CSRF attacks`_.
-``symfony/security-acl``
- It provides a fine grained permissions mechanism based on Access Control Lists.
+``symfony/security-guard``
+ It brings many layers of authentication together, allowing the creation
+ of complex authentication systems.
.. seealso::
@@ -58,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 b5cbbd66b3d..4ff251ef271 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\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+ use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
+ 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
{
@@ -74,7 +74,7 @@ The default authentication manager is an instance of
use Symfony\Component\Security\Core\Exception\AuthenticationException;
// instances of Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface
- $providers = array(...);
+ $providers = [...];
$authenticationManager = new AuthenticationProviderManager($providers);
@@ -90,7 +90,7 @@ authentication providers, each supporting a different type of token.
.. note::
- You may of course write your own authentication manager, it only has
+ You may write your own authentication manager, it only has
to implement :class:`Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationManagerInterface`.
.. _authentication_providers:
@@ -100,10 +100,10 @@ Authentication Providers
Each provider (since it implements
:class:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\AuthenticationProviderInterface`)
-has a method :method:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\AuthenticationProviderInterface::supports`
+has a :method:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\AuthenticationProviderInterface::supports` method
by which the ``AuthenticationProviderManager``
can determine if it supports the given token. If this is the case, the
-manager then calls the provider's method :method:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\AuthenticationProviderInterface::authenticate`.
+manager then calls the provider's :method:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\AuthenticationProviderInterface::authenticate` method.
This method should return an authenticated token or throw an
:class:`Symfony\\Component\\Security\\Core\\Exception\\AuthenticationException`
(or any other exception extending it).
@@ -127,18 +127,18 @@ 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(
- array(
- 'admin' => array(
+ [
+ 'admin' => [
// password is "foo"
'password' => '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg==',
- 'roles' => array('ROLE_ADMIN'),
- ),
- )
+ 'roles' => ['ROLE_ADMIN'],
+ ],
+ ]
);
// for some extra checks: is account enabled, locked, expired, etc.
@@ -181,11 +181,11 @@ receives an array of encoders::
$defaultEncoder = new MessageDigestPasswordEncoder('sha512', true, 5000);
$weakEncoder = new MessageDigestPasswordEncoder('md5', true, 1);
- $encoders = array(
+ $encoders = [
User::class => $defaultEncoder,
LegacyUser::class => $weakEncoder,
// ...
- );
+ ];
$encoderFactory = new EncoderFactory($encoders);
Each encoder should implement :class:`Symfony\\Component\\Security\\Core\\Encoder\\PasswordEncoderInterface`
@@ -306,7 +306,7 @@ logged into your website. It is important to distinguish this action from
non-interactive authentication methods, such as:
* authentication based on your session.
-* authentication using a HTTP basic or HTTP digest header.
+* authentication using a HTTP basic header.
You could listen on the ``security.interactive_login`` event, for example, in
order to give your user a welcome flash message every time they log in.
@@ -320,4 +320,3 @@ the ``switch_user`` firewall listener.
: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 36ac7181838..edd7eb857fa 100644
--- a/components/security/authorization.rst
+++ b/components/security/authorization.rst
@@ -47,14 +47,16 @@ recognizes several strategies:
grant access if there are more voters granting access than there are denying;
``unanimous``
- only grant access if none of the voters has denied access;
+ only grant access if none of the voters has denied access. If all voters
+ abstained from voting, the decision is based on the ``allow_if_all_abstain``
+ config option (which defaults to ``false``).
-.. code-block:: php
+Usage of the available options in detail::
use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;
// instances of Symfony\Component\Security\Core\Authorization\Voter\VoterInterface
- $voters = array(...);
+ $voters = [...];
// one of "affirmative", "consensus", "unanimous"
$strategy = ...;
@@ -85,25 +87,12 @@ of :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterf
which means they have to implement a few methods which allows the decision
manager to use them:
-``supportsAttribute($attribute)`` (deprecated as of 2.8)
- will be used to check if the voter knows how to handle the given attribute;
-
-``supportsClass($class)`` (deprecated as of 2.8)
- will be used to check if the voter is able to grant or deny access for
- an object of the given class;
-
``vote(TokenInterface $token, $object, array $attributes)``
this method will do the actual voting and return a value equal to one
of the class constants of :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`,
i.e. ``VoterInterface::ACCESS_GRANTED``, ``VoterInterface::ACCESS_DENIED``
or ``VoterInterface::ACCESS_ABSTAIN``;
-.. note::
-
- The ``supportsAttribute()`` and ``supportsClass()`` methods are deprecated
- as of Symfony 2.8 and no longer required in 3.0. These methods should not
- be called outside the voter class.
-
The Security component contains some standard voters which cover many use
cases:
@@ -114,9 +103,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;
@@ -132,14 +119,14 @@ on a "remember-me" cookie, or even authenticated anonymously?
// any object
$object = ...;
- $vote = $authenticatedVoter->vote($token, $object, array('IS_AUTHENTICATED_FULLY'));
+ $vote = $authenticatedVoter->vote($token, $object, ['IS_AUTHENTICATED_FULLY']);
RoleVoter
~~~~~~~~~
The :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\RoleVoter`
supports attributes starting with ``ROLE_`` and grants access to the user
-when the required ``ROLE_*`` attributes can all be found in the array of
+when at least one required ``ROLE_*`` attribute can be found in the array of
roles returned by the token's :method:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface::getRoles`
method::
@@ -147,7 +134,7 @@ method::
$roleVoter = new RoleVoter('ROLE_');
- $roleVoter->vote($token, $object, array('ROLE_ADMIN'));
+ $roleVoter->vote($token, $object, ['ROLE_ADMIN']);
RoleHierarchyVoter
~~~~~~~~~~~~~~~~~~
@@ -155,7 +142,7 @@ RoleHierarchyVoter
The :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\RoleHierarchyVoter`
extends :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\RoleVoter`
and provides some additional functionality: it knows how to handle a
-hierarchy of roles. For instance, a ``ROLE_SUPER_ADMIN`` role may have subroles
+hierarchy of roles. For instance, a ``ROLE_SUPER_ADMIN`` role may have sub-roles
``ROLE_ADMIN`` and ``ROLE_USER``, so that when a certain object requires the
user to have the ``ROLE_ADMIN`` role, it grants access to users who in fact
have the ``ROLE_ADMIN`` role, but also to users having the ``ROLE_SUPER_ADMIN``
@@ -164,28 +151,62 @@ role::
use Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter;
use Symfony\Component\Security\Core\Role\RoleHierarchy;
- $hierarchy = array(
- 'ROLE_SUPER_ADMIN' => array('ROLE_ADMIN', 'ROLE_USER'),
- );
+ $hierarchy = [
+ 'ROLE_SUPER_ADMIN' => ['ROLE_ADMIN', 'ROLE_USER'],
+ ];
$roleHierarchy = new RoleHierarchy($hierarchy);
$roleHierarchyVoter = new RoleHierarchyVoter($roleHierarchy);
+ExpressionVoter
+~~~~~~~~~~~~~~~
+
+The :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\ExpressionVoter`
+grants access based on the evaluation of expressions created with the
+:doc:`ExpressionLanguage component `. These
+expressions have access to a number of
+:ref:`special security variables `::
+
+ use Symfony\Component\ExpressionLanguage\Expression;
+ use Symfony\Component\Security\Core\Authorization\Voter\ExpressionVoter;
+
+ // Symfony\Component\Security\Core\Authorization\ExpressionLanguage;
+ $expressionLanguage = ...;
+
+ // instance of Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface
+ $trustResolver = ...;
+
+ // Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface
+ $authorizationChecker = ...;
+
+ $expressionVoter = new ExpressionVoter($expressionLanguage, $trustResolver, $authorizationChecker);
+
+ // instance of Symfony\Component\Security\Core\Authentication\Token\TokenInterface
+ $token = ...;
+
+ // any object
+ $object = ...;
+
+ $expression = new Expression(
+ '"ROLE_ADMIN" in roles or (not is_anonymous() and user.isSuperAdmin())'
+ )
+
+ $vote = $expressionVoter->vote($token, $object, [$expression]);
+
.. note::
- When you make your own voter, you may of course use its constructor
- to inject any dependencies it needs to come to a decision.
+ 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 objects that give expression to a certain right the user has.
-The only requirement is that they implement :class:`Symfony\\Component\\Security\\Core\\Role\\RoleInterface`,
-which means they should also have a :method:`Symfony\\Component\\Security\\Core\\Role\\RoleInterface::getRole`
-method that returns a string representation of the role itself. The default
-:class:`Symfony\\Component\\Security\\Core\\Role\\Role` simply returns its
-first constructor argument::
+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::
use Symfony\Component\Security\Core\Role\Role;
@@ -217,16 +238,18 @@ 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\Core\Authentication\Token\Storage\TokenStorage;
+ use Symfony\Component\Security\Http\AccessMap;
use Symfony\Component\Security\Http\Firewall\AccessListener;
$accessMap = new AccessMap();
+ $tokenStorage = new TokenStorage();
$requestMatcher = new RequestMatcher('^/admin');
- $accessMap->add($requestMatcher, array('ROLE_ADMIN'));
+ $accessMap->add($requestMatcher, ['ROLE_ADMIN']);
$accessListener = new AccessListener(
- $securityContext,
+ $tokenStorage,
$accessDecisionManager,
$accessMap,
$authenticationManager
diff --git a/components/security/firewall.rst b/components/security/firewall.rst
index 2b089bf0b5f..f2ef3d4f67b 100644
--- a/components/security/firewall.rst
+++ b/components/security/firewall.rst
@@ -52,16 +52,16 @@ 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();
$requestMatcher = new RequestMatcher('^/secured-area/');
// instances of Symfony\Component\Security\Http\Firewall\ListenerInterface
- $listeners = array(...);
+ $listeners = [...];
$exceptionListener = new ExceptionListener(...);
@@ -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 = ...;
@@ -80,7 +80,7 @@ with the event dispatcher that is used by the :class:`Symfony\\Component\\HttpKe
$dispatcher->addListener(
KernelEvents::REQUEST,
- array($firewall, 'onKernelRequest')
+ [$firewall, 'onKernelRequest']
);
The firewall is registered to listen to the ``kernel.request`` event that
@@ -88,6 +88,22 @@ will be dispatched by the HttpKernel at the beginning of each request
it processes. This way, the firewall may prevent the user from going any
further than allowed.
+Firewall Config
+~~~~~~~~~~~~~~~
+
+.. versionadded:: 3.2
+
+ The ``FirewallConfig`` class was introduced in Symfony 3.2.
+
+The information about a given firewall, such as its name, provider, context,
+entry point and access denied URL, is provided by instances of the
+:class:`Symfony\\Bundle\\SecurityBundle\\Security\\FirewallConfig` class.
+
+This object can be accessed through the ``getFirewallConfig(Request $request)``
+method of the :class:`Symfony\\Bundle\\SecurityBundle\\Security\\FirewallMap` class and
+through the ``getConfig()`` method of the
+:class:`Symfony\\Bundle\\SecurityBundle\\Security\\FirewallContext` class.
+
.. _firewall_listeners:
Firewall Listeners
diff --git a/components/serializer.rst b/components/serializer.rst
index 0cfc9fb9928..3013973cef1 100644
--- a/components/serializer.rst
+++ b/components/serializer.rst
@@ -11,9 +11,6 @@ The Serializer Component
In order to do so, the Serializer component follows the following
simple schema.
-.. _component-serializer-encoders:
-.. _component-serializer-normalizers:
-
.. image:: /_images/components/serializer/serializer_workflow.png
As you can see in the picture above, an array is used as an intermediary between
@@ -29,9 +26,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/serializer
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require symfony/serializer:^3.4
.. include:: /components/require_autoload.rst.inc
@@ -43,21 +38,24 @@ 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.
Using the Serializer component is really simple. You just need to set up
the :class:`Symfony\\Component\\Serializer\\Serializer` specifying
which encoders and normalizer are going to be available::
- use Symfony\Component\Serializer\Serializer;
- use Symfony\Component\Serializer\Encoder\XmlEncoder;
+
use Symfony\Component\Serializer\Encoder\JsonEncoder;
+ use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+ use Symfony\Component\Serializer\Serializer;
- $encoders = array(new XmlEncoder(), new JsonEncoder());
- $normalizers = array(new ObjectNormalizer());
+ $encoders = [new XmlEncoder(), new JsonEncoder()];
+ $normalizers = [new ObjectNormalizer()];
$serializer = new Serializer($normalizers, $encoders);
@@ -168,6 +166,34 @@ needs three parameters:
#. The name of the class this information will be decoded to
#. The encoder used to convert that information into an array
+.. versionadded:: 3.3
+
+ Support for the ``allow_extra_attributes`` key in the context was introduced
+ in Symfony 3.3.
+
+By default, additional attributes that are not mapped to the denormalized object
+will be ignored by the Serializer component. If you prefer to throw an exception
+when this happens, set the ``allow_extra_attributes`` context option to
+``false`` and provide an object that implements ``ClassMetadataFactoryInterface``
+when constructing the normalizer::
+
+ $data = <<
+ foo
+ 99
+ Paris
+
+ EOF;
+
+ // this will throw a Symfony\Component\Serializer\Exception\ExtraAttributesException
+ // because "city" is not an attribute of the Person class
+ $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
+ $normalizer = new ObjectNormalizer($classMetadataFactory);
+ $serializer = new Serializer([$normalizer]);
+ $person = $serializer->deserialize($data, 'App\Model\Person', 'xml', [
+ 'allow_extra_attributes' => false,
+ ]);
+
Deserializing in an Existing Object
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -186,7 +212,7 @@ The serializer can also be used to update an existing object::
EOF;
- $serializer->deserialize($data, Person::class, 'xml', array('object_to_populate' => $person));
+ $serializer->deserialize($data, Person::class, 'xml', ['object_to_populate' => $person]);
// $person = App\Model\Person(name: 'foo', age: '69', sportsperson: true)
This is a common need when working with an ORM.
@@ -196,10 +222,6 @@ This is a common need when working with an ORM.
Attributes Groups
-----------------
-.. versionadded:: 2.7
- The support of serialization and deserialization groups was introduced
- in Symfony 2.7.
-
Sometimes, you want to serialize different sets of attributes from your
entities. Groups are a handy way to achieve this need.
@@ -228,23 +250,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.yml'));
+
+* 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.yml'));
+
+* 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:
@@ -266,7 +295,7 @@ Then, create your groups definition:
public $foo;
/**
- * @Groups({"group3"})
+ * @Groups("group3")
*/
public function getBar() // is* methods are also supported
{
@@ -291,7 +320,7 @@ Then, create your groups definition:
@@ -307,24 +336,24 @@ 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';
$obj->setBar('bar');
$normalizer = new ObjectNormalizer($classMetadataFactory);
- $serializer = new Serializer(array($normalizer));
+ $serializer = new Serializer([$normalizer]);
- $data = $serializer->normalize($obj, null, array('groups' => array('group1')));
- // $data = array('foo' => 'foo');
+ $data = $serializer->normalize($obj, null, ['groups' => ['group1']]);
+ // $data = ['foo' => 'foo'];
$obj2 = $serializer->denormalize(
- array('foo' => 'foo', 'bar' => 'bar'),
+ ['foo' => 'foo', 'bar' => 'bar'],
'MyObj',
null,
- array('groups' => array('group1', 'group3'))
+ ['groups' => ['group1', 'group3']]
);
// $obj2 = MyObj(foo: 'foo', bar: 'bar')
@@ -332,6 +361,46 @@ You are now able to serialize only attributes in the groups you want::
.. _ignoring-attributes-when-serializing:
+Selecting Specific Attributes
+-----------------------------
+
+It is also possible to serialize only a set of specific attributes::
+
+ use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+ use Symfony\Component\Serializer\Serializer;
+
+ class User
+ {
+ public $familyName;
+ public $givenName;
+ public $company;
+ }
+
+ class Company
+ {
+ public $name;
+ public $address;
+ }
+
+ $company = new Company();
+ $company->name = 'Les-Tilleuls.coop';
+ $company->address = 'Lille, France';
+
+ $user = new User();
+ $user->familyName = 'Dunglas';
+ $user->givenName = 'Kévin';
+ $user->company = $company;
+
+ $serializer = new Serializer([new ObjectNormalizer()]);
+
+ $data = $serializer->normalize($user, null, ['attributes' => ['familyName', 'company' => ['name']]]);
+ // $data = ['familyName' => 'Dunglas', 'company' => ['name' => 'Les-Tilleuls.coop']];
+
+Only attributes that are not ignored (see below) are available.
+If some serialization groups are set, only attributes allowed by those groups can be used.
+
+As for groups, attributes can be selected during both the serialization and deserialization process.
+
Ignoring Attributes
-------------------
@@ -340,28 +409,20 @@ Ignoring Attributes
Using attribute groups instead of the :method:`Symfony\\Component\\Serializer\\Normalizer\\AbstractNormalizer::setIgnoredAttributes`
method is considered best practice.
-.. versionadded:: 2.3
- The :method:`Symfony\\Component\\Serializer\\Normalizer\\AbstractNormalizer::setIgnoredAttributes`
- method was introduced in Symfony 2.3.
-
-.. versionadded:: 2.7
- Prior to Symfony 2.7, attributes were only ignored while serializing. Since Symfony
- 2.7, they are ignored when deserializing too.
-
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::
- use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+ use Symfony\Component\Serializer\Serializer;
$normalizer = new ObjectNormalizer();
- $normalizer->setIgnoredAttributes(array('age'));
+ $normalizer->setIgnoredAttributes(['age']);
$encoder = new JsonEncoder();
- $serializer = new Serializer(array($normalizer), array($encoder));
+ $serializer = new Serializer([$normalizer], [$encoder]);
$serializer->serialize($person, 'json'); // Output: {"name":"foo","sportsperson":false}
.. _component-serializer-converting-property-names-when-serializing-and-deserializing:
@@ -369,10 +430,6 @@ method on the normalizer definition::
Converting Property Names when Serializing and Deserializing
------------------------------------------------------------
-.. versionadded:: 2.7
- The :class:`Symfony\\Component\\Serializer\\NameConverter\\NameConverterInterface`
- interface was introduced in Symfony 2.7.
-
Sometimes serialized attributes must be named differently than properties
or getter/setter methods of PHP classes.
@@ -422,7 +479,7 @@ and :class:`Symfony\\Component\\Serializer\\Normalizer\\PropertyNormalizer`::
$nameConverter = new OrgPrefixNameConverter();
$normalizer = new ObjectNormalizer(null, $nameConverter);
- $serializer = new Serializer(array($normalizer), array(new JsonEncoder()));
+ $serializer = new Serializer([$normalizer], [new JsonEncoder()]);
$company = new Company();
$company->name = 'Acme Inc.';
@@ -438,10 +495,6 @@ and :class:`Symfony\\Component\\Serializer\\Normalizer\\PropertyNormalizer`::
CamelCase to snake_case
~~~~~~~~~~~~~~~~~~~~~~~
-.. versionadded:: 2.7
- The :class:`Symfony\\Component\\Serializer\\NameConverter\\CamelCaseToSnakeCaseNameConverter`
- interface was introduced in Symfony 2.7.
-
In many formats, it's common to use underscores to separate words (also known
as snake_case). However, in Symfony applications is common to use CamelCase to
name properties (even though the `PSR-1 standard`_ doesn't recommend any
@@ -475,7 +528,7 @@ processes::
$normalizer->normalize($kevin);
// ['first_name' => 'Kévin'];
- $anne = $normalizer->denormalize(array('first_name' => 'Anne'), 'Person');
+ $anne = $normalizer->denormalize(['first_name' => 'Anne'], 'Person');
// Person object with firstName: 'Anne'
Serializing Boolean Attributes
@@ -507,9 +560,9 @@ When serializing, you can set a callback to format a specific object property::
: '';
};
- $normalizer->setCallbacks(array('createdAt' => $callback));
+ $normalizer->setCallbacks(['createdAt' => $callback]);
- $serializer = new Serializer(array($normalizer), array($encoder));
+ $serializer = new Serializer([$normalizer], [$encoder]);
$person = new Person();
$person->setName('cordoval');
@@ -519,6 +572,8 @@ 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"}
+.. _component-serializer-normalizers:
+
Normalizers
-----------
@@ -527,13 +582,13 @@ There are several types of normalizers available:
:class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer`
This normalizer leverages the :doc:`PropertyAccess Component `
to read and write in the object. It means that it can access to properties
- directly and through getters, setters, hassers, adders and removers. It supports
+ directly and through getters, setters, hassers, issers, adders and removers. It supports
calling the constructor during the denormalization process.
Objects are normalized to a map of property names and values (names are
- generated removing the ``get``, ``set``, ``has`` or ``remove`` prefix from
- the method name and lowercasing the first letter; e.g. ``getFirstName()`` ->
- ``firstName``).
+ generated by removing the ``get``, ``set``, ``has``, ``is``, ``add`` or ``remove`` prefix from
+ the method name and transforming the first letter to lowercase; e.g.
+ ``getFirstName()`` -> ``firstName``).
The ``ObjectNormalizer`` is the most powerful normalizer. It is configured by
default when using the Symfony Standard Edition with the serializer enabled.
@@ -544,19 +599,84 @@ There are several types of normalizers available:
the constructor and the "setters" (public methods starting with "set").
Objects are normalized to a map of property names and values (names are
- generated removing the ``get`` prefix from the method name and lowercasing
- the first letter; e.g. ``getFirstName()`` -> ``firstName``).
+ generated by removing the ``get`` prefix from the method name and transforming
+ the first letter to lowercase; e.g. ``getFirstName()`` -> ``firstName``).
:class:`Symfony\\Component\\Serializer\\Normalizer\\PropertyNormalizer`
This normalizer directly reads and writes public properties as well as
- **private and protected** properties. It supports calling the constructor
- during the denormalization process.
+ **private and protected** properties (from both the class and all of its
+ parent classes). It supports calling the constructor during the denormalization process.
Objects are normalized to a map of property names to property values.
-.. versionadded:: 2.7
- The :class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer`
- class was introduced in Symfony 2.7.
+ .. versionadded:: 3.4
+
+ The ability to handle parent classes for ``PropertyNormalizer`` was
+ introduced in Symfony 3.4.
+
+:class:`Symfony\\Component\\Serializer\\Normalizer\\JsonSerializableNormalizer`
+ This normalizer works with classes that implement :phpclass:`JsonSerializable`.
+
+ It will call the :phpmethod:`JsonSerializable::jsonSerialize` method and
+ then further normalize the result. This means that nested
+ :phpclass:`JsonSerializable` classes will also be normalized.
+
+ This normalizer is particularly helpful when you want to gradually migrate
+ from an existing codebase using simple :phpfunction:`json_encode` to the Symfony
+ Serializer by allowing you to mix which normalizers are used for which classes.
+
+ Unlike with :phpfunction:`json_encode` circular references can be handled.
+
+:class:`Symfony\\Component\\Serializer\\Normalizer\\DateTimeNormalizer`
+ This normalizer converts :phpclass:`DateTimeInterface` objects (e.g.
+ :phpclass:`DateTime` and :phpclass:`DateTimeImmutable`) into strings.
+ By default, it uses the `RFC3339`_ format.
+
+ .. versionadded:: 3.2
+
+ Support for specifying datetime format during denormalization was
+ introduced in the ``DateTimeNormalizer`` in Symfony 3.2.
+
+:class:`Symfony\\Component\\Serializer\\Normalizer\\DataUriNormalizer`
+ This normalizer converts :phpclass:`SplFileInfo` objects into a data URI
+ string (``data:...``) such that files can be embedded into serialized data.
+
+:class:`Symfony\\Component\\Serializer\\Normalizer\\DateIntervalNormalizer`
+ This normalizer converts :phpclass:`DateInterval` objects into strings.
+ By default, it uses the ``P%yY%mM%dDT%hH%iM%sS`` format.
+
+ .. versionadded:: 3.4
+
+ The ``DateIntervalNormalizer`` normalizer was introduced in Symfony 3.4.
+
+.. _component-serializer-encoders:
+
+Encoders
+--------
+
+The Serializer component supports many formats out of the box:
+
+:class:`Symfony\\Component\\Serializer\\Encoder\\JsonEncoder`
+ This class encodes and decodes data in `JSON`_.
+
+:class:`Symfony\\Component\\Serializer\\Encoder\\XmlEncoder`
+ This class encodes and decodes data in `XML`_.
+
+:class:`Symfony\\Component\\Serializer\\Encoder\\YamlEncoder`
+ This encoder encodes and decodes data in `YAML`_. This encoder requires the
+ :doc:`Yaml Component `.
+
+:class:`Symfony\\Component\\Serializer\\Encoder\\CsvEncoder`
+ This encoder encodes and decodes data in `CSV`_.
+
+All these encoders are enabled by default when using the Symfony Standard Edition
+with the serializer enabled.
+
+.. versionadded:: 3.2
+
+ The ``YamlEncoder`` and ``CsvEncoder`` encoders were introduced in Symfony 3.2
+
+.. _component-serializer-handling-circular-references:
Handling Circular References
----------------------------
@@ -625,7 +745,7 @@ when such a case is encountered::
$organization = new Organization();
$organization->setName('Les-Tilleuls.coop');
- $organization->setMembers(array($member));
+ $organization->setMembers([$member]);
$member->setOrganization($organization);
@@ -646,10 +766,104 @@ having unique identifiers::
return $object->getName();
});
- $serializer = new Serializer(array($normalizer), array($encoder));
+ $serializer = new Serializer([$normalizer], [$encoder]);
var_dump($serializer->serialize($org, 'json'));
// {"name":"Les-Tilleuls.coop","members":[{"name":"K\u00e9vin", organization: "Les-Tilleuls.coop"}]}
+Handling Serialization Depth
+----------------------------
+
+The Serializer component is able to detect and limit the serialization depth.
+It is especially useful when serializing large trees. Assume the following data
+structure::
+
+ namespace Acme;
+
+ class MyObj
+ {
+ public $foo;
+
+ /**
+ * @var self
+ */
+ public $child;
+ }
+
+ $level1 = new MyObj();
+ $level1->foo = 'level1';
+
+ $level2 = new MyObj();
+ $level2->foo = 'level2';
+ $level1->child = $level2;
+
+ $level3 = new MyObj();
+ $level3->foo = 'level3';
+ $level2->child = $level3;
+
+The serializer can be configured to set a maximum depth for a given property.
+Here, we set it to 2 for the ``$child`` property:
+
+.. configuration-block::
+
+ .. code-block:: php-annotations
+
+ namespace Acme;
+
+ use Symfony\Component\Serializer\Annotation\MaxDepth;
+
+ class MyObj
+ {
+ /**
+ * @MaxDepth(2)
+ */
+ public $child;
+
+ // ...
+ }
+
+ .. code-block:: yaml
+
+ Acme\MyObj:
+ attributes:
+ child:
+ max_depth: 2
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+The metadata loader corresponding to the chosen format must be configured in
+order to use this feature. It is done automatically when using the Symfony
+Standard Edition. When using the standalone component, refer to
+:ref:`the groups documentation ` to
+learn how to do that.
+
+The check is only done if the ``enable_max_depth`` key of the serializer context
+is set to ``true``. In the following example, the third level is not serialized
+because it is deeper than the configured maximum depth of 2::
+
+ $result = $serializer->normalize($level1, null, ['enable_max_depth' => true]);
+ /*
+ $result = [
+ 'foo' => 'level1',
+ 'child' => [
+ 'foo' => 'level2',
+ 'child' => [
+ 'child' => null,
+ ],
+ ],
+ ];
+ */
+
Handling Arrays
---------------
@@ -668,23 +882,16 @@ Serializing arrays works just like serializing a single object::
$person2->setAge(33);
$person2->setSportsman(true);
- $persons = array($person1, $person2);
+ $persons = [$person1, $person2];
$data = $serializer->serialize($persons, 'json');
// $data contains [{"name":"foo","age":99,"sportsman":false},{"name":"bar","age":33,"sportsman":true}]
-.. versionadded:: 2.8
- The :class:`Symfony\\Component\\Serializer\\Normalizer\\ArrayDenormalizer`
- class was introduced in 2.8. Prior to Symfony 2.8, only the serialization of
- arrays is supported.
-
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;
@@ -692,13 +899,150 @@ you indicate that you're expecting an array instead of a single object.
use Symfony\Component\Serializer\Serializer;
$serializer = new Serializer(
- array(new GetSetMethodNormalizer(), new ArrayDenormalizer()),
- array(new JsonEncoder())
+ [new GetSetMethodNormalizer(), new ArrayDenormalizer()],
+ [new JsonEncoder()]
);
$data = ...; // The serialized data from the previous example
$persons = $serializer->deserialize($data, 'Acme\Person[]', 'json');
+The ``XmlEncoder``
+------------------
+
+This encoder transforms arrays into XML and vice versa. For example, take an
+object normalized as following::
+
+ ['foo' => [1, 2], 'bar' => true];
+
+The ``XmlEncoder`` encodes this object as follows:
+
+.. code-block:: xml
+
+
+
+ 1
+ 2
+ 1
+
+
+The array keys beginning with ``@`` are considered XML attributes::
+
+ ['foo' => ['@bar' => 'value']];
+
+ // is encoded as follows:
+ //
+ //
+ //
+ //
+
+Use the special ``#`` key to define the data of a node::
+
+ ['foo' => ['@bar' => 'value', '#' => 'baz']];
+
+ // is encoded as follows:
+ //
+ //
+ // baz
+ //
+
+Context
+~~~~~~~
+
+The ``encode()`` method defines a third optional parameter called ``context``
+which defines the configuration options for the XmlEncoder an associative array::
+
+ $xmlEncoder->encode($array, 'xml', $context);
+
+These are the options available:
+
+``xml_format_output``
+ If set to true, formats the generated XML with line breaks and indentation.
+
+``xml_version``
+ Sets the XML version attribute (default: ``1.1``).
+
+``xml_encoding``
+ Sets the XML encoding attribute (default: ``utf-8``).
+
+``xml_standalone``
+ Adds standalone attribute in the generated XML (default: ``true``).
+
+``xml_root_node_name``
+ Sets the root node name (default: ``response``).
+
+``remove_empty_tags``
+ If set to true, removes all empty tags in the generated XML.
+
+Recursive Denormalization and Type Safety
+-----------------------------------------
+
+The Serializer Component can use the :doc:`PropertyInfo Component ` to denormalize
+complex types (objects). The type of the class' property will be guessed using the provided
+extractor and used to recursively denormalize the inner data.
+
+When using the Symfony Standard Edition, all normalizers are automatically configured to use the registered extractors.
+When using the component standalone, an implementation of :class:`Symfony\\Component\\PropertyInfo\\PropertyTypeExtractorInterface`,
+(usually an instance of :class:`Symfony\\Component\\PropertyInfo\\PropertyInfoExtractor`) must be passed as the 4th
+parameter of the ``ObjectNormalizer``::
+
+ namespace Acme;
+
+ use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
+ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
+ use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+ use Symfony\Component\Serializer\Serializer;
+
+ class ObjectOuter
+ {
+ private $inner;
+ private $date;
+
+ public function getInner()
+ {
+ return $this->inner;
+ }
+
+ public function setInner(ObjectInner $inner)
+ {
+ $this->inner = $inner;
+ }
+
+ public function setDate(\DateTimeInterface $date)
+ {
+ $this->date = $date;
+ }
+
+ public function getDate()
+ {
+ return $this->date;
+ }
+ }
+
+ class ObjectInner
+ {
+ public $foo;
+ public $bar;
+ }
+
+ $normalizer = new ObjectNormalizer(null, null, null, new ReflectionExtractor());
+ $serializer = new Serializer([new DateTimeNormalizer(), $normalizer]);
+
+ $obj = $serializer->denormalize(
+ ['inner' => ['foo' => 'foo', 'bar' => 'bar'], 'date' => '1988/01/21'],
+ 'Acme\ObjectOuter'
+ );
+
+ dump($obj->getInner()->foo); // 'foo'
+ dump($obj->getInner()->bar); // 'bar'
+ dump($obj->getDate()->format('Y-m-d')); // '1988-01-21'
+
+When a ``PropertyTypeExtractor`` is available, the normalizer will also check that the data to denormalize
+matches the type of the property (even for primitive types). For instance, if a ``string`` is provided, but
+the type of the property is ``int``, an :class:`Symfony\\Component\\Serializer\\Exception\\UnexpectedValueException`
+will be thrown. The type enforcement of the properties can be disabled by setting
+the serializer context option ``ObjectNormalizer::DISABLE_TYPE_ENFORCEMENT``
+to ``true``.
+
Learn more
----------
@@ -715,4 +1059,8 @@ 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/
+.. _`YAML`: http://yaml.org/
+.. _`CSV`: https://tools.ietf.org/html/rfc4180
diff --git a/components/stopwatch.rst b/components/stopwatch.rst
index 878a1275a1f..ce38c0fb56a 100644
--- a/components/stopwatch.rst
+++ b/components/stopwatch.rst
@@ -12,27 +12,30 @@ Installation
.. code-block:: terminal
- $ composer require symfony/stopwatch
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require symfony/stopwatch:^3.4
.. 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 simple
+:phpfunction:`microtime` by yourself. Instead, use the simple
:class:`Symfony\\Component\\Stopwatch\\Stopwatch` class::
use Symfony\Component\Stopwatch\Stopwatch;
$stopwatch = new Stopwatch();
+
// starts event named 'eventName'
$stopwatch->start('eventName');
- // ... some code goes here
+
+ // ... run your code here
+
$event = $stopwatch->stop('eventName');
+ // you can convert $event into a string for a quick summary
+ // e.g. (string) $event = '4.50 MiB - 26 ms'
The :class:`Symfony\\Component\\Stopwatch\\StopwatchEvent` object can be retrieved
from the :method:`Symfony\\Component\\Stopwatch\\Stopwatch::start`,
@@ -42,6 +45,18 @@ from the :method:`Symfony\\Component\\Stopwatch\\Stopwatch::start`,
The latter should be used when you need to retrieve the duration of an event
while it is still running.
+.. tip::
+
+ By default, the stopwatch truncates any sub-millisecond time measure to ``0``,
+ so you can't measure microseconds or nanoseconds. If you need more precision,
+ pass ``true`` to the ``Stopwatch`` class constructor to enable full precision::
+
+ $stopwatch = new Stopwatch(true);
+
+ .. versionadded:: 3.4
+
+ Full precision support was introduced in Symfony 3.4.
+
You can also provide a category name to an event::
$stopwatch->start('eventName', 'categoryName');
@@ -49,6 +64,12 @@ You can also provide a category name to an event::
You can consider categories as a way of tagging events. For example, the
Symfony Profiler tool uses categories to nicely color-code different events.
+.. tip::
+
+ 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.
+
Periods
-------
@@ -104,5 +125,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 f46cc3957c5..381e47aefa9 100644
--- a/components/templating.rst
+++ b/components/templating.rst
@@ -18,9 +18,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/templating
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require symfony/templating:^3.4
.. include:: /components/require_autoload.rst.inc
@@ -41,20 +39,20 @@ 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%');
$templating = new PhpEngine(new TemplateNameParser(), $filesystemLoader);
- echo $templating->render('hello.php', array('firstname' => 'Fabien'));
+ echo $templating->render('hello.php', ['firstname' => 'Fabien']);
.. code-block:: html+php
- Hello, !
+ Hello, = $firstname ?>!
The :method:`Symfony\\Component\\Templating\\PhpEngine::render` method parses
the ``views/hello.php`` file and returns the output text. The second argument
@@ -83,9 +81,9 @@ can then be included by other templates. As the ``$view`` variable is an
instance of ``PhpEngine``, you can use the ``render()`` method (which was used
to render the template originally) inside the template to render another template::
-
+
- render('hello.php', array('firstname' => $name)) ?>
+ = $view->render('hello.php', ['firstname' => $name]) ?>
Global Variables
@@ -103,7 +101,7 @@ In a template:
.. code-block:: html+php
-
The google tracking code is:
+
The google tracking code is: = $ga_tracking ?>
.. caution::
@@ -123,13 +121,13 @@ JavaScript code isn't written out to your page. This will prevent things like
XSS attacks. To do this, use the
:method:`Symfony\\Component\\Templating\\PhpEngine::escape` method::
- escape($firstname) ?>
+ = $view->escape($firstname) ?>
By default, the ``escape()`` method assumes that the variable is outputted
within an HTML context. The second argument lets you change the context. For
example, to output something inside JavaScript, use the ``js`` context::
- escape($var, 'js') ?>
+ = $view->escape($var, 'js') ?>
The component comes with an HTML and JS escaper. You can register your own
escaper using the
@@ -144,20 +142,18 @@ escaper using the
Helpers
-------
-The Templating component can be easily extended via helpers. Helpers are PHP objects that
-provide features useful in a template context. The component has
-2 built-in helpers:
+The Templating component can be extended via helpers. Helpers are PHP objects that
+provide features useful in a template context. The component has one built-in helper:
-* :doc:`/components/templating/assetshelper`
* :doc:`/components/templating/slotshelper`
Before you can use these helpers, you need to register them using
:method:`Symfony\\Component\\Templating\\PhpEngine::set`::
- use Symfony\Component\Templating\Helper\AssetsHelper;
+ use Symfony\Component\Templating\Helper\SlotsHelper;
// ...
- $templating->set(new AssetsHelper());
+ $templating->set(new SlotsHelper());
Custom Helpers
~~~~~~~~~~~~~~
@@ -179,7 +175,7 @@ using the Templating component. To do that, create a new class which
implements the :class:`Symfony\\Component\\Templating\\EngineInterface`. This
requires 3 method:
-* :method:`render($name, array $parameters = array()) `
+* :method:`render($name, array $parameters = []) `
- Renders a template
* :method:`exists($name) `
- Checks if the template exists
@@ -198,13 +194,13 @@ 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(array(
+ $templating = new DelegatingEngine([
new PhpEngine(...),
new CustomEngine(...),
- ));
+ ]);
Learn More
----------
@@ -216,5 +212,3 @@ Learn More
/components/templating/*
/templating
/templating/*
-
-.. _Packagist: https://packagist.org/packages/symfony/templating
diff --git a/components/templating/assetshelper.rst b/components/templating/assetshelper.rst
deleted file mode 100644
index f5a4700cfe9..00000000000
--- a/components/templating/assetshelper.rst
+++ /dev/null
@@ -1,131 +0,0 @@
-.. index::
- single: Templating Helpers; Assets Helper
-
-Assets Helper
-=============
-
-The assets helper's main purpose is to make your application more portable by
-generating asset paths:
-
-.. code-block:: html+php
-
-
-
-
-
-The assets helper can then be configured to render paths to a CDN or modify
-the paths in case your assets live in a sub-directory of your host (e.g. ``http://example.com/app``).
-
-Configure Paths
----------------
-
-By default, the assets helper will prefix all paths with a slash. You can
-configure this by passing a base assets path as the first argument of the
-constructor::
-
- use Symfony\Component\Templating\Helper\AssetsHelper;
-
- // ...
- $templateEngine->set(new AssetsHelper('/foo/bar'));
-
-Now, if you use the helper, everything will be prefixed with ``/foo/bar``:
-
-.. code-block:: html+php
-
-
-
-
-Absolute Urls
--------------
-
-You can also specify a URL to use in the second parameter of the constructor::
-
- // ...
- $templateEngine->set(new AssetsHelper(null, 'http://cdn.example.com/'));
-
-Now URLs are rendered like ``http://cdn.example.com/images/logo.png``.
-
-You can also use the third argument of the helper to force an absolute URL:
-
-.. code-block:: html+php
-
-
-
-
-.. note::
-
- If you already set a URL in the constructor, using the third argument of
- ``getUrl()`` will not affect the generated URL.
-
-Versioning
-----------
-
-To avoid using the cached resource after updating the old resource, you can
-use versions which you bump every time you release a new project. The version
-can be specified in the third argument::
-
- // ...
- $templateEngine->set(new AssetsHelper(null, null, '328rad75'));
-
-Now, every URL is suffixed with ``?328rad75``. If you want to have a different
-format, you can specify the new format in fourth argument. It's a string that
-is used in :phpfunction:`sprintf`. The first argument is the path and the
-second is the version. For instance, ``%s?v=%s`` will be rendered as
-``/images/logo.png?v=328rad75``.
-
-You can also generate a versioned URL on an asset-by-asset basis using the
-fourth argument of the helper:
-
-.. code-block:: html+php
-
-
-
-
-Multiple Packages
------------------
-
-Asset path generation is handled internally by packages. The component provides
-2 packages by default:
-
-* :class:`Symfony\\Component\\Templating\\Asset\\PathPackage`
-* :class:`Symfony\\Component\\Templating\\Asset\\UrlPackage`
-
-You can also use multiple packages::
-
- use Symfony\Component\Templating\Asset\PathPackage;
-
- // ...
- $templateEngine->set(new AssetsHelper());
-
- $templateEngine->get('assets')->addPackage('images', new PathPackage('/images/'));
- $templateEngine->get('assets')->addPackage('scripts', new PathPackage('/scripts/'));
-
-This will setup the assets helper with 3 packages: the default package which
-defaults to ``/`` (set by the constructor), the images package which prefixes
-it with ``/images/`` and the scripts package which prefixes it with
-``/scripts/``.
-
-If you want to set another default package, you can use
-:method:`Symfony\\Component\\Templating\\Helper\\AssetsHelper::setDefaultPackage`.
-
-You can specify which package you want to use in the second argument of
-:method:`Symfony\\Component\\Templating\\Helper\\AssetsHelper::getUrl`:
-
-.. code-block:: html+php
-
-
-
-
-Custom Packages
----------------
-
-You can create your own package by extending
-:class:`Symfony\\Component\\Templating\\Asset\\Package`.
diff --git a/components/templating/slotshelper.rst b/components/templating/slotshelper.rst
index e368b83f86a..42c088c9fd9 100644
--- a/components/templating/slotshelper.rst
+++ b/components/templating/slotshelper.rst
@@ -38,7 +38,7 @@ optional second argument, which is the default value to use if the slot is not
available.
The ``_content`` slot is a special slot set by the ``PhpEngine``. It contains
-the content of the subtemplate.
+the content of the sub-template.
.. caution::
@@ -67,10 +67,10 @@ set in a slot is in the ``_content`` slot.
set('title', $page->title) ?>
- title ?>
+ = $page->title ?>
- body ?>
+ = $page->body ?>
.. note::
diff --git a/components/translation.rst b/components/translation.rst
deleted file mode 100644
index 4ee67440aef..00000000000
--- a/components/translation.rst
+++ /dev/null
@@ -1,234 +0,0 @@
-.. index::
- single: Translation
- single: Components; Translation
-
-The Translation Component
-=========================
-
- The Translation component provides tools to internationalize your
- application.
-
-Installation
-------------
-
-.. code-block:: terminal
-
- $ composer require symfony/translation
-
-Alternatively, you can clone the ``_ repository.
-
-.. include:: /components/require_autoload.rst.inc
-
-.. seealso::
-
- This article explains how to use the Translation features as an independent
- component in any PHP application. Read the :doc:`/translation` article to
- learn about how to internationalize and manage the user locale in Symfony
- applications.
-
-Constructing the Translator
----------------------------
-
-The main access point of the Translation component is
-:class:`Symfony\\Component\\Translation\\Translator`. Before you can use it,
-you need to configure it and load the messages to translate (called *message
-catalogs*).
-
-Configuration
-~~~~~~~~~~~~~
-
-The constructor of the ``Translator`` class needs one argument: The locale::
-
- use Symfony\Component\Translation\Translator;
-
- $translator = new Translator('fr_FR');
-
-.. note::
-
- The locale set here is the default locale to use. You can override this
- locale when translating strings.
-
-.. note::
-
- The term *locale* refers roughly to the user's language and country. It
- can be any string that your application uses to manage translations and
- other format differences (e.g. currency format). The `ISO 639-1`_
- *language* code, an underscore (``_``), then the `ISO 3166-1 alpha-2`_
- *country* code (e.g. ``fr_FR`` for French/France) is recommended.
-
-.. _component-translator-message-catalogs:
-
-Loading Message Catalogs
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-The messages are stored in message catalogs inside the ``Translator``
-class. A message catalog is like a dictionary of translations for a specific
-locale.
-
-The Translation component uses Loader classes to load catalogs. You can load
-multiple resources for the same locale, which will then be combined into one
-catalog.
-
-The component comes with some default Loaders and you can create your own
-Loader too. The default loaders are:
-
-* :class:`Symfony\\Component\\Translation\\Loader\\ArrayLoader` - to load
- catalogs from PHP arrays.
-* :class:`Symfony\\Component\\Translation\\Loader\\CsvFileLoader` - to load
- catalogs from CSV files.
-* :class:`Symfony\\Component\\Translation\\Loader\\IcuDatFileLoader` - to load
- catalogs from resource bundles.
-* :class:`Symfony\\Component\\Translation\\Loader\\IcuResFileLoader` - to load
- catalogs from resource bundles.
-* :class:`Symfony\\Component\\Translation\\Loader\\IniFileLoader` - to load
- catalogs from ini files.
-* :class:`Symfony\\Component\\Translation\\Loader\\MoFileLoader` - to load
- catalogs from gettext files.
-* :class:`Symfony\\Component\\Translation\\Loader\\PhpFileLoader` - to load
- catalogs from PHP files.
-* :class:`Symfony\\Component\\Translation\\Loader\\PoFileLoader` - to load
- catalogs from gettext files.
-* :class:`Symfony\\Component\\Translation\\Loader\\QtFileLoader` - to load
- catalogs from QT XML files.
-* :class:`Symfony\\Component\\Translation\\Loader\\XliffFileLoader` - to load
- catalogs from Xliff files.
-* :class:`Symfony\\Component\\Translation\\Loader\\JsonFileLoader` - to load
- catalogs from JSON files.
-* :class:`Symfony\\Component\\Translation\\Loader\\YamlFileLoader` - to load
- catalogs from Yaml files (requires the :doc:`Yaml component`).
-
-All file loaders require the :doc:`Config component `.
-
-You can also :doc:`create your own Loader `,
-in case the format is not already supported by one of the default loaders.
-
-At first, you should add one or more loaders to the ``Translator``::
-
- // ...
- $translator->addLoader('array', new ArrayLoader());
-
-The first argument is the name to which you can refer the loader in the
-translator and the second argument is an instance of the loader itself. After
-this, you can add your resources using the correct loader.
-
-Loading Messages with the ``ArrayLoader``
-.........................................
-
-Loading messages can be done by calling
-:method:`Symfony\\Component\\Translation\\Translator::addResource`. The first
-argument is the loader name (this was the first argument of the ``addLoader()``
-method), the second is the resource and the third argument is the locale::
-
- // ...
- $translator->addResource('array', array(
- 'Hello World!' => 'Bonjour',
- ), 'fr_FR');
-
-Loading Messages with the File Loaders
-......................................
-
-If you use one of the file loaders, you should also use the ``addResource()``
-method. The only difference is that you should put the file name to the resource
-file as the second argument, instead of an array::
-
- // ...
- $translator->addLoader('yaml', new YamlFileLoader());
- $translator->addResource('yaml', 'path/to/messages.fr.yml', 'fr_FR');
-
-The Translation Process
------------------------
-
-To actually translate the message, the Translator uses a simple process:
-
-* A catalog of translated messages is loaded from translation resources defined
- for the ``locale`` (e.g. ``fr_FR``). Messages from the
- :ref:`components-fallback-locales` are also loaded and added to the
- catalog, if they don't already exist. The end result is a large "dictionary"
- of translations;
-
-* If the message is located in the catalog, the translation is returned. If
- not, the translator returns the original message.
-
-You start this process by calling
-:method:`Symfony\\Component\\Translation\\Translator::trans` or
-:method:`Symfony\\Component\\Translation\\Translator::transChoice`. Then, the
-Translator looks for the exact string inside the appropriate message catalog
-and returns it (if it exists).
-
-.. _components-fallback-locales:
-
-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:
-
-#. 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 ``fr``
- locale;
-
-#. If the translation still isn't found, the translator uses the one or more
- fallback locales set explicitly on the translator.
-
-For (3), the fallback locales can be set by calling
-:method:`Symfony\\Component\\Translation\\Translator::setFallbackLocales`::
-
- // ...
- $translator->setFallbackLocales(array('en'));
-
-.. _using-message-domains:
-
-Using Message Domains
----------------------
-
-As you've seen, message files are organized into the different locales that
-they translate. The message files can also be organized further into "domains".
-
-The domain is specified in the fourth argument of the ``addResource()``
-method. The default domain is ``messages``. For example, suppose that, for
-organization, translations were split into three different domains:
-``messages``, ``admin`` and ``navigation``. The French translation would be
-loaded like this::
-
- // ...
- $translator->addLoader('xlf', new XliffFileLoader());
-
- $translator->addResource('xlf', 'messages.fr.xlf', 'fr_FR');
- $translator->addResource('xlf', 'admin.fr.xlf', 'fr_FR', 'admin');
- $translator->addResource(
- 'xlf',
- 'navigation.fr.xlf',
- 'fr_FR',
- 'navigation'
- );
-
-When translating strings that are not in the default domain (``messages``),
-you must specify the domain as the third argument of ``trans()``::
-
- $translator->trans('Symfony is great', array(), 'admin');
-
-Symfony will now look for the message in the ``admin`` domain of the
-specified locale.
-
-Usage
------
-
-Read how to use the Translation component in :doc:`/components/translation/usage`.
-
-Learn More
-----------
-
-.. toctree::
- :maxdepth: 1
- :glob:
-
- /components/translation/*
- /translation
- /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
deleted file mode 100644
index 07ee62f0feb..00000000000
--- a/components/translation/custom_formats.rst
+++ /dev/null
@@ -1,127 +0,0 @@
-.. index::
- single: Translation; Adding Custom Format Support
-
-Adding Custom Format Support
-============================
-
-Sometimes, you need to deal with custom formats for translation files. The
-Translation component is flexible enough to support this. Just create a
-loader (to load translations) and, optionally, a dumper (to dump translations).
-
-Imagine that you have a custom format where translation messages are defined
-using one line for each translation and parentheses to wrap the key and the
-message. A translation file would look like this:
-
-.. code-block:: text
-
- (welcome)(accueil)
- (goodbye)(au revoir)
- (hello)(bonjour)
-
-.. _components-translation-custom-loader:
-
-Creating a Custom Loader
-------------------------
-
-To define a custom loader that is able to read these kinds of files, you must create a
-new class that implements the
-:class:`Symfony\\Component\\Translation\\Loader\\LoaderInterface`. The
-:method:`Symfony\\Component\\Translation\\Loader\\LoaderInterface::load`
-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;
-
- class MyFormatLoader implements LoaderInterface
- {
- public function load($resource, $locale, $domain = 'messages')
- {
- $messages = array();
- $lines = file($resource);
-
- foreach ($lines as $line) {
- if (preg_match('/\(([^\)]+)\)\(([^\)]+)\)/', $line, $matches)) {
- $messages[$matches[1]] = $matches[2];
- }
- }
-
- $messageCatalogue = new MessageCatalogue($locale);
- $messageCatalogue->add($messages, $domain);
-
- return $messageCatalogue;
- }
-
- }
-
-Once created, it can be used as any other loader::
-
- use Symfony\Component\Translation\Translator;
-
- $translator = new Translator('fr_FR');
- $translator->addLoader('my_format', new MyFormatLoader());
-
- $translator->addResource('my_format', __DIR__.'/translations/messages.txt', 'fr_FR');
-
- var_dump($translator->trans('welcome'));
-
-It will print *"accueil"*.
-
-.. _components-translation-custom-dumper:
-
-Creating a Custom Dumper
-------------------------
-
-It is also possible to create a custom dumper for your format, which is
-useful when using the extraction commands. To do so, a new class
-implementing the
-:class:`Symfony\\Component\\Translation\\Dumper\\DumperInterface`
-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;
-
- class MyFormatDumper extends FileDumper
- {
- public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array())
- {
- $output = '';
-
- foreach ($messages->all($domain) as $source => $target) {
- $output .= sprintf("(%s)(%s)\n", $source, $target);
- }
-
- return $output;
- }
-
- protected function getExtension()
- {
- return 'txt';
- }
- }
-
-.. sidebar:: Format a message catalogue
-
- .. versionadded:: 2.8
- The ability to format a message catalogue without dumping it was introduced in Symfony 2.8.
-
- In some cases, you want to send the dump contents as a response instead of writing them in files.
- To do this, you can use the ``formatCatalogue`` method. In this case, you must pass the domain argument,
- which determines the list of messages that should be dumped.
-
-The :method:`Symfony\\Component\\Translation\\Dumper\\FileDumper::formatCatalogue`
-method creates the output string, that will be used by the
-:method:`Symfony\\Component\\Translation\\Dumper\\FileDumper::dump` method
-of the FileDumper class to create the file. The dumper can be used like any other
-built-in dumper. In the following example, the translation messages defined in the
-YAML file are dumped into a text file with the custom format::
-
- use Symfony\Component\Translation\Loader\YamlFileLoader;
-
- $loader = new YamlFileLoader();
- $translations = $loader->load(__DIR__ . '/translations/messages.fr_FR.yml' , 'fr_FR');
-
- $dumper = new MyFormatDumper();
- $dumper->dump($translations, array('path' => __DIR__.'/dumps'));
diff --git a/components/translation/usage.rst b/components/translation/usage.rst
deleted file mode 100644
index 3963f4dc130..00000000000
--- a/components/translation/usage.rst
+++ /dev/null
@@ -1,400 +0,0 @@
-.. index::
- single: Translation; Usage
-
-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;
-
- $translator = new Translator('fr_FR');
- $translator->addLoader('array', new ArrayLoader());
- $translator->addResource('array', array(
- 'Symfony is great!' => 'Symfony est super !',
- ), 'fr_FR');
-
- var_dump($translator->trans('Symfony is great!'));
-
-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%',
- array('%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 array(
- '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
----------------------
-
-The act of creating translation files is an important part of "localization"
-(often abbreviated `L10n`_). Translation files consist of a series of
-id-translation pairs for the given domain and locale. The source is the identifier
-for the individual translation, and can be the message in the main locale (e.g.
-*"Symfony is great"*) of your application or a unique identifier (e.g.
-``symfony.great`` - see the sidebar below).
-
-Translation files can be created in several different formats, XLIFF being the
-recommended format. These files are parsed by one of the loader classes.
-
-.. configuration-block::
-
- .. code-block:: xml
-
-
-
-
-
-
- Symfony is great
- J'aime Symfony
-
-
- symfony.great
- J'aime Symfony
-
-
-
-
-
- .. code-block:: yaml
-
- Symfony is great: J'aime Symfony
- symfony.great: J'aime Symfony
-
- .. code-block:: php
-
- return array(
- 'Symfony is great' => 'J\'aime Symfony',
- 'symfony.great' => 'J\'aime Symfony',
- );
-
-.. _translation-real-vs-keyword-messages:
-
-.. sidebar:: Using Real or Keyword Messages
-
- This example illustrates the two different philosophies when creating
- messages to be translated::
-
- $translator->trans('Symfony is great');
-
- $translator->trans('symfony.great');
-
- In the first method, messages are written in the language of the default
- locale (English in this case). That message is then used as the "id"
- when creating translations.
-
- In the second method, messages are actually "keywords" that convey the
- idea of the message. The keyword message is then used as the "id" for
- any translations. In this case, translations must be made for the default
- locale (i.e. to translate ``symfony.great`` to ``Symfony is great``).
-
- The second method is handy because the message key won't need to be changed
- in every translation file if you decide that the message should actually
- read "Symfony is really great" in the default locale.
-
- The choice of which method to use is entirely up to you, but the "keyword"
- format is often recommended.
-
- Additionally, the ``php`` and ``yaml`` file formats support nested ids to
- avoid repeating yourself if you use keywords instead of real text for your
- ids:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- symfony:
- is:
- great: Symfony is great
- amazing: Symfony is amazing
- has:
- bundles: Symfony has bundles
- user:
- login: Login
-
- .. code-block:: php
-
- array(
- 'symfony' => array(
- 'is' => array(
- 'great' => 'Symfony is great',
- 'amazing' => 'Symfony is amazing',
- ),
- 'has' => array(
- 'bundles' => 'Symfony has bundles',
- ),
- ),
- 'user' => array(
- 'login' => 'Login',
- ),
- );
-
- The multiple levels are flattened into single id/translation pairs by
- adding a dot (``.``) between every level, therefore the above examples are
- equivalent to the following:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- symfony.is.great: Symfony is great
- symfony.is.amazing: Symfony is amazing
- symfony.has.bundles: Symfony has bundles
- user.login: Login
-
- .. code-block:: php
-
- return array(
- 'symfony.is.great' => 'Symfony is great',
- 'symfony.is.amazing' => 'Symfony is amazing',
- 'symfony.has.bundles' => 'Symfony has bundles',
- '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::
-
- $translator->transChoice(
- 'There is one apple|There are %count% apples',
- 10,
- array('%count%' => 10)
- );
-
-The second argument (``10`` in this example) is the *number* of objects being
-described and is used to determine which translation to use and also to populate
-the ``%count%`` placeholder.
-
-Based on the given number, the translator chooses the right plural form.
-In English, most words have a singular form when there is exactly one object
-and a plural form for all other numbers (0, 2, 3...). So, if ``count`` is
-``1``, the translator will use the first string (``There is one apple``)
-as the translation. Otherwise it will use ``There are %count% apples``.
-
-Here is the French translation:
-
-.. 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
------------------------------
-
-When translating a message, the Translator uses the specified locale or the
-``fallback`` locale if necessary. You can also manually specify the locale to
-use for translation::
-
- $translator->trans(
- 'Symfony is great',
- array(),
- 'messages',
- 'fr_FR'
- );
-
- $translator->transChoice(
- '{0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples',
- 10,
- array('%count%' => 10),
- 'messages',
- 'fr_FR'
- );
-
-Retrieving the Message Catalogue
---------------------------------
-
-In case you want to use the same translation catalogue outside your application
-(e.g. use translation on the client side), it's possible to fetch raw translation
-messages. Just specify the required locale::
-
- $catalogue = $translator->getCatalogue('fr_FR');
- $messages = $catalogue->all();
- while ($catalogue = $catalogue->getFallbackCatalogue()) {
- $messages = array_replace_recursive($catalogue->all(), $messages);
- }
-
-The ``$messages`` variable will have the following structure::
-
- array(
- 'messages' => array(
- 'Hello world' => 'Bonjour tout le monde',
- ),
- 'validators' => array(
- 'Value should not be empty' => 'Valeur ne doit pas être vide',
- 'Value is too long' => 'Valeur est trop long',
- ),
- );
-
-.. _`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 5c84ba3be91..ce2801fa5e3 100644
--- a/components/using_components.rst
+++ b/components/using_components.rst
@@ -24,7 +24,7 @@ Using the Finder Component
.. code-block:: terminal
- $ composer require symfony/finder
+ $ composer require symfony/finder:^3.4
The name ``symfony/finder`` is written at the top of the documentation for
whatever component you want.
@@ -64,7 +64,7 @@ them one by one, you can include the ``symfony/symfony`` package:
.. code-block:: terminal
- $ composer require symfony/symfony
+ $ composer require symfony/symfony:^3.4
This will also include the Bundle and Bridge libraries, which you may or
may not actually need.
@@ -72,7 +72,7 @@ may not actually need.
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 3c4c745bc5a..82850d1faad 100644
--- a/components/validator.rst
+++ b/components/validator.rst
@@ -13,9 +13,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/validator
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require symfony/validator:^3.4
.. include:: /components/require_autoload.rst.inc
@@ -36,15 +34,15 @@ 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', array(
- new Length(array('min' => 10)),
+ $violations = $validator->validate('Bernhard', [
+ new Length(['min' => 10]),
new NotBlank(),
- ));
+ ]);
if (0 !== count($violations)) {
// there are errors, now you can show them
@@ -53,13 +51,26 @@ characters long::
}
}
-The validator returns the list of violations.
+The ``validate()`` method returns the list of violations as an object that
+implements :class:`Symfony\\Component\\Validator\\ConstraintViolationListInterface`.
+If you have lots of validation errors, you can filter them by error code::
+
+ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
+
+ $violations = $validator->validate(...);
+ if (0 !== count($violations->findByCodes(UniqueEntity::NOT_UNIQUE_ERROR))) {
+ // handle this specific error (display some message, send an email, etc.)
+ }
+
+.. versionadded:: 3.3
+
+ The ``findByCodes()`` method was introduced in Symfony 3.3.
Retrieving a Validator Instance
-------------------------------
-The :class:`Symfony\\Component\\Validator\\Validator` class is the main access
-point of the Validator component. To create a new instance of this class, it's
+The Validator object (that implements :class:`Symfony\\Component\\Validator\\Validator\\ValidatorInterface`) is the main access
+point of the Validator component. To create a new instance of it, it's
recommended to use the :class:`Symfony\\Component\\Validator\\Validation` class::
use Symfony\Component\Validator\Validation;
@@ -68,7 +79,7 @@ recommended to use the :class:`Symfony\\Component\\Validator\\Validation` class:
This ``$validator`` object can validate simple variables such as strings, numbers
and arrays, but it can't validate objects. To do so, configure the
-``Validator`` class as explained in the next sections.
+``Validator`` as explained in the next sections.
Learn More
----------
@@ -82,4 +93,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 2b68d535b40..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
{
@@ -27,7 +27,7 @@ the ``Author`` class has at least 3 characters::
$metadata->addPropertyConstraint('firstName', new Assert\NotBlank());
$metadata->addPropertyConstraint(
'firstName',
- new Assert\Length(array("min" => 3))
+ new Assert\Length(["min" => 3])
);
}
}
@@ -51,16 +51,16 @@ 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
{
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
- $metadata->addGetterConstraint('passwordSafe', new Assert\IsTrue(array(
+ $metadata->addGetterConstraint('passwordSafe', new Assert\IsTrue([
'message' => 'The password cannot match your first name',
- )));
+ ]));
}
}
@@ -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 03cee104be1..a3645162e51 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
{
@@ -45,10 +45,10 @@ In this example, the validation metadata is retrieved executing the
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint('name', new Assert\NotBlank());
- $metadata->addPropertyConstraint('name', new Assert\Length(array(
+ $metadata->addPropertyConstraint('name', new Assert\Length([
'min' => 5,
'max' => 20,
- )));
+ ]));
}
}
@@ -100,8 +100,8 @@ prefixed classes included in doc block comments (``/** ... */``). For example::
class User
{
/**
- * @Assert\NotBlank
- */
+ * @Assert\NotBlank
+ */
protected $name;
}
@@ -145,12 +145,18 @@ Caching
Using many loaders to load metadata from different places is convenient, but it
can slow down your application because each file needs to be parsed, validated
and converted into a :class:`Symfony\\Component\\Validator\\Mapping\\ClassMetadata`
-instance. To solve this problem, you can cache the ``ClassMetadata`` information.
+instance.
+
+To solve this problem, call the :method:`Symfony\\Component\\Validator\\ValidatorBuilder::setMetadataCache`
+method of the Validator builder and pass your own caching class (which must
+implement :class:`Symfony\\Component\\Validator\\Mapping\\Cache\\CacheInterface`)::
+
+ use Symfony\Component\Validator\Validation;
-The Validator component comes with an
-:class:`Symfony\\Component\\Validator\\Mapping\\Cache\\ApcCache`
-implementation. You can easily create other cachers by creating a class which
-implements :class:`Symfony\\Component\\Validator\\Mapping\\Cache\\CacheInterface`.
+ $validator = Validation::createValidatorBuilder()
+ // ... add loaders
+ ->setMetadataCache(new SomeImplementCacheInterface());
+ ->getValidator();
.. note::
@@ -160,18 +166,6 @@ implements :class:`Symfony\\Component\\Validator\\Mapping\\Cache\\CacheInterface
the Validator still needs to merge all metadata of one class from every
loader when it is requested.
-Enable the cache calling the
-:method:`Symfony\\Component\\Validator\\ValidatorBuilder::setMetadataCache`
-method of the Validator builder::
-
- use Symfony\Component\Validator\Validation;
- use Symfony\Component\Validator\Mapping\Cache\ApcCache;
-
- $validator = Validation::createValidatorBuilder()
- // ... add loaders
- ->setMetadataCache(new ApcCache('some_apc_prefix'))
- ->getValidator();
-
Using a Custom MetadataFactory
------------------------------
@@ -198,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 fe41bf170d3..2ea2a1f2ecb 100644
--- a/components/var_dumper.rst
+++ b/components/var_dumper.rst
@@ -5,18 +5,16 @@
The VarDumper Component
=======================
- The VarDumper component provides mechanisms for walking through any
- arbitrary PHP variable. Built on top, it provides a better ``dump()``
- function that you can use instead of :phpfunction:`var_dump`.
+ The VarDumper component provides mechanisms for extracting the state out of
+ any PHP variables. Built on top, it provides a better ``dump()`` function
+ that you can use instead of :phpfunction:`var_dump`.
Installation
------------
.. code-block:: terminal
- $ composer require symfony/var-dumper --dev
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require --dev symfony/var-dumper:^3.4
.. include:: /components/require_autoload.rst.inc
@@ -53,6 +51,9 @@ For example::
dump($someVar);
+ // dump() returns the passed value, so you can dump an object and keep using it
+ dump($someObject)->someMethod();
+
By default, the output format and destination are selected based on your
current PHP SAPI:
@@ -108,13 +109,22 @@ This behavior can be changed by configuring the ``debug.dump_destination``
option. Read more about this and other options in
:doc:`the DebugBundle configuration reference `.
+.. tip::
+
+ .. versionadded:: 3.3
+
+ The local search box was introduced in Symfony 3.3.
+
+ 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 ``Ctrl. + F`` or ``Cmd. + F`` to make the local
+ search box appear. All the common shortcuts to navigate the search results
+ 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
--------------------------------------------------------
-.. versionadded:: 2.7
- The :class:`Symfony\\Component\\VarDumper\\Test\\VarDumperTestTrait` was
- introduced in Symfony 2.7.
-
The VarDumper component provides
:class:`a trait `
that can help writing some of your tests for PHPUnit.
@@ -132,14 +142,15 @@ 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()
{
- $testedVar = array(123, 'foo');
+ $testedVar = [123, 'foo'];
$expectedDump = << "in an array of 5 elements",
'a float' => 1.0,
'an integer' => 1,
'a boolean' => true,
- 'an empty array' => array(),
- );
+ 'an empty array' => [],
+ ];
dump($var);
.. image:: /_images/components/var_dumper/01-simple.png
@@ -251,11 +257,11 @@ then its dump representation::
.. code-block:: php
- $var = array();
+ $var = [];
$var[0] = 1;
$var[1] =& $var[0];
$var[1] += 1;
- $var[2] = array("Hard references (circular or sibling)");
+ $var[2] = ["Hard references (circular or sibling)"];
$var[3] =& $var[2];
$var[3][] = "are dumped using `&number` prefixes.";
dump($var);
@@ -293,5 +299,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 ccc0308cad7..88edcefc05a 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();
@@ -34,8 +34,7 @@ A cloner is used to create an intermediate representation of any PHP variable.
Its output is a :class:`Symfony\\Component\\VarDumper\\Cloner\\Data`
object that wraps this representation.
-You can create a :class:`Symfony\\Component\\VarDumper\\Cloner\\Data`
-object this way::
+You can create a ``Data`` object this way::
use Symfony\Component\VarDumper\Cloner\VarCloner;
@@ -45,34 +44,52 @@ object this way::
// see the example at the top of this page
// $dumper->dump($data);
-A cloner also applies limits when creating the representation, so that the
-corresponding Data object could represent only a subset of the cloned variable.
+Whatever the cloned data structure, resulting ``Data`` objects are always
+serializable.
+
+A cloner applies limits when creating the representation, so that one
+can represent only a subset of the cloned variable.
Before calling :method:`Symfony\\Component\\VarDumper\\Cloner\\VarCloner::cloneVar`,
you can configure these limits:
:method:`Symfony\\Component\\VarDumper\\Cloner\\VarCloner::setMaxItems`
- configures the maximum number of items that will be cloned
- *past the first nesting level*. Items are counted using a breadth-first
+ Configures the maximum number of items that will be cloned
+ *past the minimum nesting depth*. Items are counted using a breadth-first
algorithm so that lower level items have higher priority than deeply nested
- items;
+ items. Specifying ``-1`` removes the limit.
-:method:`Symfony\\Component\\VarDumper\\Cloner\\VarCloner::setMaxString`
- configures the maximum number of characters that will be cloned before
- cutting overlong strings;
+:method:`Symfony\\Component\\VarDumper\\Cloner\\VarCloner::setMinDepth`
+ Configures the minimum tree depth where we are guaranteed to clone
+ all the items. After this depth is reached, only ``setMaxItems``
+ items will be cloned. The default value is ``1``, which is consistent
+ with older Symfony versions.
-In both cases, specifying ``-1`` removes any limit.
+ .. versionadded:: 3.4
+
+ The ``setMinDepth()`` method was introduced in Symfony 3.4.
+
+:method:`Symfony\\Component\\VarDumper\\Cloner\\VarCloner::setMaxString`
+ Configures the maximum number of characters that will be cloned before
+ cutting overlong strings. Specifying ``-1`` removes the limit.
Before dumping it, you can further limit the resulting
:class:`Symfony\\Component\\VarDumper\\Cloner\\Data` object using the following methods:
:method:`Symfony\\Component\\VarDumper\\Cloner\\Data::withMaxDepth`
- Allows limiting dumps in the depth dimension.
+ Limits dumps in the depth dimension.
:method:`Symfony\\Component\\VarDumper\\Cloner\\Data::withMaxItemsPerDepth`
Limits the number of items per depth level.
:method:`Symfony\\Component\\VarDumper\\Cloner\\Data::withRefHandles`
- Allows removing internal objects' handles for sparser output (useful for tests).
+ Removes internal objects' handles for sparser output (useful for tests).
+
+:method:`Symfony\\Component\\VarDumper\\Cloner\\Data::seek`
+ Selects only sub-parts of already cloned arrays, objects or resources.
+
+ .. versionadded:: 3.2
+
+ The ``seek()`` method was introduced in Symfony 3.2.
Unlike the previous limits on cloners that remove data on purpose, these can
be changed back and forth before dumping since they do not affect the
@@ -82,7 +99,7 @@ intermediate representation internally.
When no limit is applied, a :class:`Symfony\\Component\\VarDumper\\Cloner\\Data`
object is as accurate as the native :phpfunction:`serialize` function,
- and thus could be for purposes beyond dumping for debugging.
+ and thus could be used for purposes beyond debugging.
Dumpers
-------
@@ -154,6 +171,18 @@ Another option for doing the same could be::
// $output is now populated with the dump representation of $variable
+.. tip::
+
+ You can pass ``true`` to the second argument of the
+ :method:`Symfony\\Component\\VarDumper\\Dumper\\AbstractDumper::dump`
+ method to make it return the dump as a string::
+
+ $output = $dumper->dump($cloner->cloneVar($variable), true);
+
+ .. versionadded:: 3.2
+
+ The ability to return a string was introduced in Symfony 3.2.
+
Dumpers implement the :class:`Symfony\\Component\\VarDumper\\Dumper\\DataDumperInterface`
interface that specifies the
:method:`dump(Data $data) `
@@ -162,12 +191,122 @@ 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` limits string
+length and nesting depth of the output to make it more readable. These options
+can be overridden by the third optional parameter of the
+:method:`dump(Data $data) `
+method::
+
+ use Symfony\Component\VarDumper\Dumper\HtmlDumper;
+
+ $output = fopen('php://memory', 'r+b');
+
+ $dumper = new HtmlDumper();
+ $dumper->dump($var, $output, [
+ // 1 and 160 are the default values for these options
+ 'maxDepth' => 1,
+ 'maxStringLength' => 160
+ ]);
+
+.. versionadded:: 3.2
+
+ Support for passing display options to the ``dump()`` method was introduced
+ in Symfony 3.2.
+
+The output format of a dumper can be fine tuned by the two flags
+``DUMP_STRING_LENGTH`` and ``DUMP_LIGHT_ARRAY`` which are passed as a bitmap
+in the third constructor argument. They can also be set via environment
+variables when using
+:method:`assertDumpEquals($dump, $data, $filter, $message) `
+during unit testing.
+
+The ``$filter`` argument of ``assertDumpEquals()`` can be used to pass a
+bit field of ``Caster::EXCLUDE_*`` constants and influences the expected
+output produced by the different casters.
+
+.. versionadded:: 3.4
+
+ The ``$filter`` argument of ``assertDumpEquals()`` was introduced in
+ Symfony 3.4.
+
+.. versionadded:: 3.1
+
+ The ``DUMP_STRING_LENGTH`` and ``DUMP_LIGHT_ARRAY`` flags were introduced
+ in Symfony 3.1.
+
+If ``DUMP_STRING_LENGTH`` is set, then the length of a string is displayed
+next to its content::
+
+ use Symfony\Component\VarDumper\Cloner\VarCloner;
+ use Symfony\Component\VarDumper\Dumper\AbstractDumper;
+ use Symfony\Component\VarDumper\Dumper\CliDumper;
+
+ $varCloner = new VarCloner();
+ $var = ['test'];
+
+ $dumper = new CliDumper();
+ echo $dumper->dump($varCloner->cloneVar($var), true);
+
+ // array:1 [
+ // 0 => "test"
+ // ]
+
+ $dumper = new CliDumper(null, null, AbstractDumper::DUMP_STRING_LENGTH);
+ echo $dumper->dump($varCloner->cloneVar($var), true);
+
+ // (added string length before the string)
+ // array:1 [
+ // 0 => (4) "test"
+ // ]
+
+If ``DUMP_LIGHT_ARRAY`` is set, then arrays are dumped in a shortened format
+similar to PHP's short array notation::
+
+ use Symfony\Component\VarDumper\Cloner\VarCloner;
+ use Symfony\Component\VarDumper\Dumper\AbstractDumper;
+ use Symfony\Component\VarDumper\Dumper\CliDumper;
+
+ $varCloner = new VarCloner();
+ $var = ['test'];
+
+ $dumper = new CliDumper();
+ echo $dumper->dump($varCloner->cloneVar($var), true);
+
+ // array:1 [
+ // 0 => "test"
+ // ]
+
+ $dumper = new CliDumper(null, null, AbstractDumper::DUMP_LIGHT_ARRAY);
+ echo $dumper->dump($varCloner->cloneVar($var), true);
+
+ // (no more array:1 prefix)
+ // [
+ // 0 => "test"
+ // ]
+
+If you would like to use both options, then you can just combine them by
+using the logical OR operator ``|``::
+
+ use Symfony\Component\VarDumper\Cloner\VarCloner;
+ use Symfony\Component\VarDumper\Dumper\AbstractDumper;
+ use Symfony\Component\VarDumper\Dumper\CliDumper;
+
+ $varCloner = new VarCloner();
+ $var = ['test'];
+
+ $dumper = new CliDumper(null, null, AbstractDumper::DUMP_STRING_LENGTH | AbstractDumper::DUMP_LIGHT_ARRAY);
+ echo $dumper->dump($varCloner->cloneVar($var), true);
+
+ // [
+ // 0 => (4) "test"
+ // ]
+
Casters
-------
Objects and resources nested in a PHP variable are "cast" to arrays in the
intermediate :class:`Symfony\\Component\\VarDumper\\Cloner\\Data`
-representation. You can tweak the array representation for each object/resource
+representation. You can customize the array representation for each object/resource
by hooking a Caster into this process. The component already includes many
casters for base PHP classes and other common classes.
@@ -177,7 +316,7 @@ or its ``addCasters()`` method::
use Symfony\Component\VarDumper\Cloner\VarCloner;
- $myCasters = array(...);
+ $myCasters = [...];
$cloner = new VarCloner($myCasters);
// or
@@ -187,10 +326,10 @@ or its ``addCasters()`` method::
The provided ``$myCasters`` argument is an array that maps a class,
an interface or a resource type to a callable::
- $myCasters = array(
+ $myCasters = [
'FooClass' => $myFooClassCallableCaster,
':bar resource' => $myBarResourceCallableCaster,
- );
+ ];
As you can notice, resource types are prefixed by a ``:`` to prevent
colliding with a class name.
@@ -203,17 +342,21 @@ can also be registered for the same resource type/class/interface.
They are called in registration order.
Casters are responsible for returning the properties of the object or resource
-being cloned in an array. They are callables that accept four arguments:
+being cloned in an array. They are callables that accept five arguments:
-* the object or resource being casted,
-* an array modelled for objects after PHP's native ``(array)`` cast operator,
+* the object or resource being casted;
+* an array modeled for objects after PHP's native ``(array)`` cast operator;
* a :class:`Symfony\\Component\\VarDumper\\Cloner\\Stub` object
- representing the main properties of the object (class, type, etc.),
-* true/false when the caster is called nested in a structure or not.
+ representing the main properties of the object (class, type, etc.);
+* true/false when the caster is called nested in a structure or not;
+* A bit field of :class:`Symfony\\Component\\VarDumper\\Caster\\Caster` ``::EXCLUDE_*``
+ constants.
Here is a simple caster not doing anything::
- function myCaster($object, $array, $stub, $isNested)
+ use Symfony\Component\VarDumper\Cloner\Stub;
+
+ function myCaster($object, $array, Stub $stub, $isNested, $filter)
{
// ... populate/alter $array to your needs
@@ -240,3 +383,51 @@ properties not in the class declaration).
.. tip::
Before writing your own casters, you should check the existing ones.
+
+Adding Semantics with Metadata
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 3.2
+
+ As of Symfony 3.2, casters can attach metadata attributes to
+ :class:`Symfony\\Component\\VarDumper\\Cloner\\Stub` objects to inform
+ dumpers about the precise type of the dumped values.
+
+Since casters are hooked on specific classes or interfaces, they know about the
+objects they manipulate. By altering the ``$stub`` object (the third argument of
+any caster), one can transfer this knowledge to the resulting ``Data`` object,
+thus to dumpers. To help you do this (see the source code for how it works),
+the component comes with a set of wrappers for common additional semantics. You
+can use:
+
+* :class:`Symfony\\Component\\VarDumper\\Caster\\ConstStub` to wrap a value that is
+ best represented by a PHP constant;
+* :class:`Symfony\\Component\\VarDumper\\Caster\\ClassStub` to wrap a PHP identifier
+ (*i.e.* a class name, a method name, an interface, *etc.*);
+* :class:`Symfony\\Component\\VarDumper\\Caster\\CutStub` to replace big noisy
+ objects/strings/*etc.* by ellipses;
+* :class:`Symfony\\Component\\VarDumper\\Caster\\CutArrayStub` to keep only some
+ useful keys of an array;
+* :class:`Symfony\\Component\\VarDumper\\Caster\\EnumStub` to wrap a set of virtual
+ values (*i.e.* values that do not exist as properties in the original PHP data
+ structure, but are worth listing alongside with real ones);
+* :class:`Symfony\\Component\\VarDumper\\Caster\\LinkStub` to wrap strings that can
+ be turned into links by dumpers;
+* :class:`Symfony\\Component\\VarDumper\\Caster\\TraceStub` and their
+* :class:`Symfony\\Component\\VarDumper\\Caster\\FrameStub` and
+* :class:`Symfony\\Component\\VarDumper\\Caster\\ArgsStub` relatives to wrap PHP
+ traces (used by :class:`Symfony\\Component\\VarDumper\\Caster\\ExceptionCaster`).
+
+For example, if you know that your ``Product`` objects have a ``brochure`` property
+that holds a file name or a URL, you can wrap them in a ``LinkStub`` to tell
+``HtmlDumper`` to make them clickable::
+
+ use Symfony\Component\VarDumper\Caster\LinkStub;
+ use Symfony\Component\VarDumper\Cloner\Stub;
+
+ function ProductCaster(Product $object, $array, Stub $stub, $isNested, $filter = 0)
+ {
+ $array['brochure'] = new LinkStub($array['brochure']);
+
+ return $array;
+ }
diff --git a/components/web_link.rst b/components/web_link.rst
new file mode 100644
index 00000000000..4fec7ceeae3
--- /dev/null
+++ b/components/web_link.rst
@@ -0,0 +1,41 @@
+.. index::
+ single: WebLink
+ single: Components; WebLink
+
+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`_.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/web-link:^3.4
+
+.. include:: /components/require_autoload.rst.inc
+
+Usage
+-----
+
+The following example shows the component in action::
+
+ use Fig\Link\GenericLinkProvider;
+ use Fig\Link\Link;
+ use Symfony\Component\WebLink\HttpHeaderSerializer;
+
+ $linkProvider = (new GenericLinkProvider())
+ ->withLink(new Link('preload', '/bootstrap.min.css'));
+
+ header('Link: '.(new HttpHeaderSerializer())->serialize($linkProvider->getLinks()));
+
+ echo 'Hello';
+
+Read the full :doc:`WebLink documentation ` to learn about all the
+features of the component and its integration with the Symfony framework.
+
+.. _`Web Linking`: https://tools.ietf.org/html/rfc5988
+.. _`HTTP/2 Server Push`: https://tools.ietf.org/html/rfc7540#section-8.2
+.. _`Resource Hints`: https://www.w3.org/TR/resource-hints/
diff --git a/components/workflow.rst b/components/workflow.rst
new file mode 100644
index 00000000000..297b7a1b044
--- /dev/null
+++ b/components/workflow.rst
@@ -0,0 +1,110 @@
+.. index::
+ single: Workflow
+ single: Components; Workflow
+
+The Workflow Component
+======================
+
+ The Workflow component provides tools for managing a workflow or finite
+ state machine.
+
+.. versionadded:: 3.2
+
+ The Workflow component was introduced in Symfony 3.2.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/workflow:^3.4
+
+.. include:: /components/require_autoload.rst.inc
+
+Creating a Workflow
+-------------------
+
+The workflow component gives you an object oriented way to define a process
+or a life cycle that your object goes through. Each step or stage in the
+process is called a *place*. You do also define *transitions* that describe
+the action to get from one place to another.
+
+.. image:: /_images/components/workflow/states_transitions.png
+
+A set of places and transitions creates a **definition**. A workflow needs
+a ``Definition`` and a way to write the states to the objects (i.e. an
+instance of a :class:`Symfony\\Component\\Workflow\\MarkingStore\\MarkingStoreInterface`).
+
+Consider the following example for a blog post. A post can have one of a number
+of predefined statuses (`draft`, `review`, `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\SingleStateMarkingStore;
+ use Symfony\Component\Workflow\Transition;
+ use Symfony\Component\Workflow\Workflow;
+
+ $definitionBuilder = new DefinitionBuilder();
+ $definition = $definitionBuilder->addPlaces(['draft', 'review', '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'))
+ ->build()
+ ;
+
+ $marking = new SingleStateMarkingStore('currentState');
+ $workflow = new Workflow($definition, $marking);
+
+.. versionadded:: 3.3
+
+ The fluent interface for the ``DefinitionBuilder`` class was introduced in
+ Symfony 3.3. Before you had to call the ``addPlaces()``, ``addTransition()``
+ and ``build()`` methods separately.
+
+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
+logic in one place and not spread all over your application.
+
+When you define multiple workflows you should consider using a ``Registry``,
+which is an object that stores and provides access to different workflows.
+A registry will also help you to decide if a workflow supports the object you
+are trying to use it with::
+
+ use Acme\Entity\BlogPost;
+ use Acme\Entity\Newsletter;
+ use Symfony\Component\Workflow\Registry;
+
+ $blogWorkflow = ...
+ $newsletterWorkflow = ...
+
+ $registry = new Registry();
+ $registry->add($blogWorkflow, BlogPost::class);
+ $registry->add($newsletterWorkflow, Newsletter::class);
+
+Usage
+-----
+
+When you have configured a ``Registry`` with your workflows, you may use it as follows::
+
+ // ...
+ $post = new BlogPost();
+ $workflow = $registry->get($post);
+
+ $workflow->can($post, 'publish'); // False
+ $workflow->can($post, 'to_review'); // True
+
+ $workflow->apply($post, 'to_review');
+ $workflow->can($post, 'publish'); // True
+ $workflow->getEnabledTransitions($post); // ['publish', 'reject']
+
+Learn more
+----------
+
+Read more about the usage of the :doc:`Workflow component ` inside a Symfony application.
+
+.. toctree::
+ :maxdepth: 1
+ :glob:
+
+ /workflow/*
diff --git a/components/yaml.rst b/components/yaml.rst
index 774675f7559..8155109dbdd 100644
--- a/components/yaml.rst
+++ b/components/yaml.rst
@@ -31,9 +31,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/yaml
-
-Alternatively, you can clone the ``_ repository.
+ $ composer require symfony/yaml:^3.4
.. include:: /components/require_autoload.rst.inc
@@ -71,7 +69,7 @@ level configuration for pretty outputs.
Types Support
~~~~~~~~~~~~~
-It supports most of the YAML built-in types like dates, integers, octals,
+It supports most of the YAML built-in types like dates, integers, octal numbers,
booleans, and much more...
Full Merge Key Support
@@ -93,21 +91,16 @@ other dumps a PHP array to a YAML string
On top of these two classes, the :class:`Symfony\\Component\\Yaml\\Yaml` class
acts as a thin wrapper that simplifies common uses.
-Reading YAML Files
-~~~~~~~~~~~~~~~~~~
+Reading YAML Contents
+~~~~~~~~~~~~~~~~~~~~~
The :method:`Symfony\\Component\\Yaml\\Yaml::parse` method parses a YAML
string and converts it to a PHP array::
use Symfony\Component\Yaml\Yaml;
- $value = Yaml::parse(file_get_contents('/path/to/file.yml'));
-
-.. caution::
-
- Because it is currently possible to pass a filename to this method, you
- must validate the input first. Passing a filename is deprecated in
- Symfony 2.2, and was removed in Symfony 3.0.
+ $value = Yaml::parse("foo: bar");
+ // $value = ['foo' => 'bar']
If an error occurs during parsing, the parser throws a
:class:`Symfony\\Component\\Yaml\\Exception\\ParseException` exception
@@ -117,26 +110,28 @@ error occurred::
use Symfony\Component\Yaml\Exception\ParseException;
try {
- $value = Yaml::parse(file_get_contents('/path/to/file.yml'));
+ $value = Yaml::parse('...');
} catch (ParseException $exception) {
- printf("Unable to parse the YAML string: %s", $exception->getMessage());
+ printf('Unable to parse the YAML string: %s', $exception->getMessage());
}
-.. _components-yaml-dump:
+Reading YAML Files
+~~~~~~~~~~~~~~~~~~
+
+The :method:`Symfony\\Component\\Yaml\\Yaml::parseFile` method parses the YAML
+contents of the given file path and converts them to a PHP value::
+
+ use Symfony\Component\Yaml\Yaml;
-Objects for Mappings
-....................
+ $value = Yaml::parseFile('/path/to/file.yml');
-.. versionadded:: 2.7
- Support for parsing mappings as objects was introduced in Symfony 2.7.
+.. versionadded:: 3.4
-Yaml :ref:`mappings ` are basically associative
-arrays. You can instruct the parser to return mappings as objects (i.e.
-``\stdClass`` instances) by setting the fourth argument to ``true``::
+ The ``parseFile()`` method was introduced in Symfony 3.4.
- $object = Yaml::parse('{"foo": "bar"}', false, false, true);
- echo get_class($object); // stdClass
- echo $object->foo; // bar
+If an error occurs during parsing, the parser throws a ``ParseException`` exception.
+
+.. _components-yaml-dump:
Writing YAML Files
~~~~~~~~~~~~~~~~~~
@@ -146,10 +141,10 @@ array to its YAML representation::
use Symfony\Component\Yaml\Yaml;
- $array = array(
+ $array = [
'foo' => 'bar',
- 'bar' => array('foo' => 'bar', 'bar' => 'baz'),
- );
+ 'bar' => ['foo' => 'bar', 'bar' => 'baz'],
+ ];
$yaml = Yaml::dump($array);
@@ -158,8 +153,10 @@ array to its YAML representation::
If an error occurs during the dump, the parser throws a
:class:`Symfony\\Component\\Yaml\\Exception\\DumpException` exception.
-Array Expansion and Inlining
-............................
+.. _array-expansion-and-inlining:
+
+Expanded and Inlined Arrays
+...........................
The YAML format supports two kind of representation for arrays, the expanded
one, and the inline one. By default, the dumper uses the expanded
@@ -197,7 +194,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
@@ -210,30 +207,49 @@ changed using the third argument as follows::
foo: bar
bar: baz
-Invalid Types and Object Serialization
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Numeric Literals
+................
+
+.. versionadded:: 3.2
+
+ Support for parsing integers grouped by underscores was introduced in
+ Symfony 3.2.
+
+Long numeric literals, being integer, float or hexadecimal, are known for their
+poor readability in code and configuration files. That's why YAML files allow to
+add underscores to improve their readability:
-By default the YAML component will encode any "unsupported" type (i.e.
-resources and objects) as ``null``.
+.. code-block:: yaml
+
+ parameters:
+ credit_card_number: 1234_5678_9012_3456
+ long_number: 10_000_000_000
+ pi: 3.14159_26535_89793
+ hex_words: 0x_CAFE_F00D
+
+During the parsing of the YAML contents, all the ``_`` characters are removed
+from the numeric literal contents, so there is not a limit in the number of
+underscores you can include or the way you group contents.
-Instead of encoding as ``null`` you can choose to throw an exception if an invalid
-type is encountered in either the dumper or parser as follows::
+Advanced Usage: Flags
+---------------------
- // throws an exception if a resource or object is encountered
- Yaml::dump($data, 2, 4, true);
+.. _objects-for-mappings:
- // throws an exception if an encoded object is found in the YAML string
- Yaml::parse($yaml, true);
+Object Parsing and Dumping
+~~~~~~~~~~~~~~~~~~~~~~~~~~
-However, you can activate object support using the next argument::
+You can dump objects by using the ``DUMP_OBJECT`` flag::
$object = new \stdClass();
$object->foo = 'bar';
- $dumped = Yaml::dump($object, 2, 4, false, true);
- // !!php/object:O:8:"stdClass":1:{s:5:"foo";s:7:"bar";}
+ $dumped = Yaml::dump($object, 2, 4, Yaml::DUMP_OBJECT);
+ // !php/object 'O:8:"stdClass":1:{s:5:"foo";s:7:"bar";}'
- $parsed = Yaml::parse($dumped, false, true);
+And parse them by using the ``PARSE_OBJECT`` flag::
+
+ $parsed = Yaml::parse($dumped, Yaml::PARSE_OBJECT);
var_dump(is_object($parsed)); // true
echo $parsed->foo; // bar
@@ -246,6 +262,192 @@ representation of the object.
parsers will likely not recognize the ``php/object`` tag and non-PHP
implementations certainly won't - use with discretion!
+Parsing and Dumping Objects as Maps
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 3.2
+
+ Support for parsing and dumping objects as maps was introduced in Symfony 3.2.
+
+You can dump objects as Yaml maps by using the ``DUMP_OBJECT_AS_MAP`` flag::
+
+ $object = new \stdClass();
+ $object->foo = 'bar';
+
+ $dumped = Yaml::dump(['data' => $object], 2, 4, Yaml::DUMP_OBJECT_AS_MAP);
+ // $dumped = "data:\n foo: bar"
+
+And parse them by using the ``PARSE_OBJECT_FOR_MAP`` flag::
+
+ $parsed = Yaml::parse($dumped, Yaml::PARSE_OBJECT_FOR_MAP);
+ var_dump(is_object($parsed)); // true
+ var_dump(is_object($parsed->data)); // true
+ echo $parsed->data->foo; // bar
+
+The YAML component uses PHP's ``(array)`` casting to generate a string
+representation of the object as a map.
+
+.. _invalid-types-and-object-serialization:
+
+Handling Invalid Types
+~~~~~~~~~~~~~~~~~~~~~~
+
+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::
+
+ $yaml = '!php/object \'O:8:"stdClass":1:{s:5:"foo";s:7:"bar";}\'';
+ Yaml::parse($yaml, Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE); // throws an exception
+
+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
+
+Date Handling
+~~~~~~~~~~~~~
+
+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`_)::
+
+ Yaml::parse('2016-05-27'); // 1464307200
+
+You can make it convert to a ``DateTime`` instance by using the ``PARSE_DATETIME``
+flag::
+
+ $date = Yaml::parse('2016-05-27', Yaml::PARSE_DATETIME);
+ var_dump(get_class($date)); // DateTime
+
+Dumping Multi-line Literal Blocks
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+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"];
+ $yaml = Yaml::dump($string);
+ echo $yaml; // string: "Multiple\nLine\nString"
+
+You can make it use a literal block with the ``DUMP_MULTI_LINE_LITERAL_BLOCK``
+flag::
+
+ $string = ["string" => "Multiple\nLine\nString"];
+ $yaml = Yaml::dump($string, 2, 4, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
+ echo $yaml;
+ // string: |
+ // Multiple
+ // Line
+ // String
+
+Parsing PHP Constants
+~~~~~~~~~~~~~~~~~~~~~
+
+By default, the YAML parser treats the PHP constants included in the contents as
+regular strings. Use the ``PARSE_CONSTANT`` flag and the special ``!php/const``
+syntax to parse them as proper PHP constants::
+
+ $yaml = '{ foo: PHP_INT_SIZE, bar: !php/const PHP_INT_SIZE }';
+ $parameters = Yaml::parse($yaml, Yaml::PARSE_CONSTANT);
+ // $parameters = ['foo' => 'PHP_INT_SIZE', 'bar' => 8];
+
+Parsing and Dumping of Binary Data
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 3.2
+
+ Support for parsing and dumping binary data was introduced in Symfony 3.2.
+
+You can dump binary data by using the ``DUMP_BASE64_BINARY_DATA`` flag::
+
+ $imageContents = file_get_contents(__DIR__.'/images/logo.png');
+
+ $dumped = Yaml::dump(['logo' => $imageContents], 2, 4, Yaml::DUMP_BASE64_BINARY_DATA);
+ // logo: !!binary iVBORw0KGgoAAAANSUhEUgAAA6oAAADqCAY...
+
+Binary data is automatically parsed if they include the ``!!binary`` YAML tag
+(there's no need to pass any flag to the Yaml parser)::
+
+ $dumped = 'logo: !!binary iVBORw0KGgoAAAANSUhEUgAAA6oAAADqCAY...';
+ $parsed = Yaml::parse($dumped);
+ $imageContents = $parsed['logo'];
+
+Parsing and Dumping Custom Tags
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 3.3
+
+ Support for parsing and dumping custom tags was introduced in Symfony 3.3.
+
+In addition to the built-in support of tags like ``!php/const`` and
+``!!binary``, you can define your own custom YAML tags and parse them with the
+``PARSE_CUSTOM_TAGS`` flag::
+
+ $data = "!my_tag { foo: bar }";
+ $parsed = Yaml::parse($data, Yaml::PARSE_CUSTOM_TAGS);
+ // $parsed = Symfony\Component\Yaml\Tag\TaggedValue('my_tag', ['foo' => 'bar']);
+ $tagName = $parsed->getTag(); // $tagName = 'my_tag'
+ $tagValue = $parsed->getValue(); // $tagValue = ['foo' => 'bar']
+
+If the contents to dump contain :class:`Symfony\\Component\\Yaml\\Tag\\TaggedValue`
+objects, they are automatically transformed into YAML tags::
+
+ use Symfony\Component\Yaml\Tag\TaggedValue;
+
+ $data = new TaggedValue('my_tag', ['foo' => 'bar']);
+ $dumped = Yaml::dump($data);
+ // $dumped = '!my_tag { foo: bar }'
+
+Syntax Validation
+~~~~~~~~~~~~~~~~~
+
+The syntax of YAML contents can be validated through the CLI using the
+:class:`Symfony\\Component\\Yaml\\Command\\LintCommand` command.
+
+First, install the Console component:
+
+.. code-block:: terminal
+
+ $ composer require symfony/console:^3.4
+
+Create a console application with ``lint:yaml`` as its only command::
+
+ // lint.php
+ use Symfony\Component\Console\Application;
+ use Symfony\Component\Yaml\Command\LintCommand;
+
+ (new Application('yaml/lint'))
+ ->add(new LintCommand())
+ ->getApplication()
+ ->setDefaultCommand('lint:yaml', true)
+ ->run();
+
+Then, execute the script for validating contents:
+
+.. code-block:: terminal
+
+ # validates a single file
+ $ php lint.php path/to/file.yml
+
+ # or all the files in a directory
+ $ php lint.php path/to/directory
+
+ # or contents passed to STDIN
+ $ cat path/to/file.yml | php lint.php
+
+The result is written to STDOUT and uses a plain text format by default.
+Add the ``--format`` option to get the output in JSON format:
+
+.. code-block:: terminal
+
+ $ php lint.php path/to/file.yml --format json
+
+.. tip::
+
+ The linting command will also report any deprecations in the checked
+ YAML files. This may for example be useful for recognizing deprecations of
+ contents of YAML files during automated tests.
+
Learn More
----------
@@ -255,6 +457,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
diff --git a/components/yaml/yaml_format.rst b/components/yaml/yaml_format.rst
index 357cfdcac70..274a0729f28 100644
--- a/components/yaml/yaml_format.rst
+++ b/components/yaml/yaml_format.rst
@@ -94,9 +94,17 @@ where each line break is replaced by a space:
>
This is a very long sentence
- that spans several lines in the YAML
- but which will be rendered as a string
- without carriage returns.
+ that spans several lines in the YAML.
+
+ # This will be parsed as follows: (notice the trailing \n)
+ # "This is a very long sentence that spans several lines in the YAML.\n"
+
+ >-
+ This is a very long sentence
+ that spans several lines in the YAML.
+
+ # This will be parsed as follows: (without a trailing \n)
+ # "This is a very long sentence that spans several lines in the YAML."
.. note::
@@ -149,7 +157,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
@@ -179,7 +187,7 @@ Sequences use a dash followed by a space:
The previous YAML file is equivalent to the following PHP code::
- array('PHP', 'Perl', 'Python');
+ ['PHP', 'Perl', 'Python'];
Mappings use a colon followed by a space (``:`` ) to mark each key/value pair:
@@ -191,7 +199,7 @@ Mappings use a colon followed by a space (``:`` ) to mark each key/value pair:
which is equivalent to this PHP code::
- array('PHP' => 5.2, 'MySQL' => 5.1, 'Apache' => '2.2.20');
+ ['PHP' => 5.2, 'MySQL' => 5.1, 'Apache' => '2.2.20'];
.. note::
@@ -218,16 +226,16 @@ YAML uses indentation with one or more spaces to describe nested collections:
The above YAML is equivalent to the following PHP code::
- array(
- 'symfony 1.0' => array(
+ [
+ 'symfony 1.0' => [
'PHP' => 5.0,
'Propel' => 1.2,
- ),
- 'symfony 1.2' => array(
+ ],
+ 'symfony 1.2' => [
'PHP' => 5.2,
'Propel' => 1.3,
- ),
- );
+ ],
+ ];
There is one important thing you need to remember when using indentation in a
YAML file: *Indentation must be done with one or more spaces, but never with
@@ -297,6 +305,9 @@ The YAML specification defines some tags to set the type of any data explicitly:
.. code-block:: yaml
data:
+ # this value is parsed as a string (it's not transformed into a DateTime)
+ start_date: !!str 2002-12-14
+
# this value is parsed as a float number (it will be 3.0 instead of 3)
price: !!float 3
@@ -307,6 +318,10 @@ The YAML specification defines some tags to set the type of any data explicitly:
Pz7Y6OjuDg4J+fn5OTk6enp
56enmleECcgggoBADs=
+.. versionadded:: 3.4
+
+ Support for the ``!!str`` tag was introduced in Symfony 3.4.
+
Unsupported YAML Features
-------------------------
@@ -315,12 +330,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
+.. _`YAML specification`: https://yaml.org/spec/1.2/spec.html
diff --git a/configuration.rst b/configuration.rst
index c3094570042..f089e89dc25 100644
--- a/configuration.rst
+++ b/configuration.rst
@@ -8,8 +8,8 @@ Every Symfony application consists of a collection of bundles that add useful to
(:doc:`services `) to your project. Each bundle can be customized
via configuration files that live - by default - in the ``app/config`` directory.
-Configuration: config.yml
--------------------------
+Configuration: ``config.yml``
+-----------------------------
The main configuration file is called ``config.yml``:
@@ -25,7 +25,7 @@ The main configuration file is called ``config.yml``:
framework:
secret: '%secret%'
- router: { resource: '%kernel.root_dir%/config/routing.yml' }
+ router: { resource: '%kernel.project_dir%/app/config/routing.yml' }
# ...
# Twig Configuration
@@ -44,25 +44,25 @@ The main configuration file is called ``config.yml``:
xmlns:framework="http://symfony.com/schema/dic/symfony"
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/symfony
- http://symfony.com/schema/dic/symfony/symfony-1.0.xsd
+ https://symfony.com/schema/dic/symfony/symfony-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">
-
-
-
+
+
+
-
+
-
+
@@ -74,19 +74,19 @@ The main configuration file is called ``config.yml``:
$this->import('security.yml');
$this->import('services.yml');
- $container->loadFromExtension('framework', array(
+ $container->loadFromExtension('framework', [
'secret' => '%secret%',
- 'router' => array(
- 'resource' => '%kernel.root_dir%/config/routing.php',
- ),
+ 'router' => [
+ 'resource' => '%kernel.project_dir%/app/config/routing.php',
+ ],
// ...
- ));
+ ]);
// Twig Configuration
- $container->loadFromExtension('twig', array(
- 'debug' => '%kernel.debug%',
+ $container->loadFromExtension('twig', [
+ 'debug' => '%kernel.debug%',
'strict_variables' => '%kernel.debug%',
- ));
+ ]);
// ...
@@ -119,7 +119,7 @@ dump of all available configuration options by running:
.. code-block:: terminal
- $ php app/console config:dump-reference twig
+ $ php bin/console config:dump-reference twig
.. index::
single: Environments; Introduction
@@ -151,12 +151,12 @@ it *also* loads other configuration files via its ``imports`` key:
+ https://symfony.com/schema/dic/services/services-1.0.xsd">
-
-
-
+
+
+
@@ -175,6 +175,42 @@ The ``imports`` key works a lot like the PHP ``include()`` function: the content
``parameters.yml``, ``security.yml`` and ``services.yml`` are read and loaded. You
can also load XML files or PHP files.
+.. tip::
+
+ If your application uses unconventional file extensions (for example, your
+ YAML files have a ``.res`` extension) you can set the file type explicitly
+ with the ``type`` option:
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # app/config/config.yml
+ imports:
+ - { resource: parameters.res, type: yml }
+ # ...
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // app/config/config.php
+ $this->import('parameters.res', 'yml');
+ // ...
+
.. _config-parameter-intro:
The parameters Key: Parameters (Variables)
@@ -211,9 +247,9 @@ key:
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">
@@ -234,10 +270,10 @@ key:
$container->setParameter('locale', 'en');
- $container->loadFromExtension('framework', array(
+ $container->loadFromExtension('framework', [
'default_locale' => '%locale%',
// ...
- ));
+ ]);
// ...
@@ -255,8 +291,8 @@ a controller - see :ref:`service-container-parameters`.
.. _config-parameters-yml:
-The Special parameters.yml File
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+The Special ``parameters.yml`` File
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
On the surface, ``parameters.yml`` is just like any other configuration file: it
is imported by ``config.yml`` and defines several parameters:
@@ -291,31 +327,31 @@ configure DoctrineBundle and other parts of Symfony:
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">
+ password="%database_password%"/>
.. code-block:: php
// app/config/config.php
- $container->loadFromExtension('doctrine', array(
- 'dbal' => array(
+ $container->loadFromExtension('doctrine', [
+ 'dbal' => [
'driver' => 'pdo_mysql',
// ...
'user' => '%database_user%',
'password' => '%database_password%',
- ),
- ));
+ ],
+ ]);
But the ``parameters.yml`` file *is* special: it defines the values that usually
change on each server. For example, the database credentials on your local
diff --git a/configuration/apache_router.rst b/configuration/apache_router.rst
deleted file mode 100644
index 1bfab14d958..00000000000
--- a/configuration/apache_router.rst
+++ /dev/null
@@ -1,155 +0,0 @@
-.. index::
- single: Apache Router
-
-How to Use the Apache Router
-============================
-
-.. caution::
-
- **Using the Apache Router is no longer considered a good practice**.
- The small increase obtained in the application routing performance is not
- worth the hassle of continuously updating the routes configuration.
-
- The Apache Router will be removed in Symfony 3 and it's highly recommended
- to not use it in your applications.
-
-Symfony, while fast out of the box, also provides various ways to increase that
-speed with a little bit of tweaking. One of these ways is by letting Apache
-handle routes directly, rather than using Symfony for this task.
-
-.. caution::
-
- Apache router was deprecated in Symfony 2.5 and will be removed in Symfony
- 3.0. Since the PHP implementation of the Router was improved, performance
- gains were no longer significant (while it's very hard to replicate the
- same behavior).
-
-Change Router Configuration Parameters
---------------------------------------
-
-To dump Apache routes you must first tweak some configuration parameters to tell
-Symfony to use the ``ApacheUrlMatcher`` instead of the default one:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config_prod.yml
- parameters:
- router.options.matcher.cache_class: ~ # disable router cache
- router.options.matcher_class: Symfony\Component\Routing\Matcher\ApacheUrlMatcher
-
- .. code-block:: xml
-
-
-
- null
- Symfony\Component\Routing\Matcher\ApacheUrlMatcher
-
-
- .. code-block:: php
-
- // app/config/config_prod.php
- $container->setParameter('router.options.matcher.cache_class', null); // disable router cache
- $container->setParameter(
- 'router.options.matcher_class',
- 'Symfony\Component\Routing\Matcher\ApacheUrlMatcher'
- );
-
-.. tip::
-
- Note that :class:`Symfony\\Component\\Routing\\Matcher\\ApacheUrlMatcher`
- extends :class:`Symfony\\Component\\Routing\\Matcher\\UrlMatcher` so even
- if you don't regenerate the mod_rewrite rules, everything will work (because
- at the end of ``ApacheUrlMatcher::match()`` a call to ``parent::match()``
- is done).
-
-Generating mod_rewrite Rules
-----------------------------
-
-To test that it's working, create a very basic route for the AppBundle:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/routing.yml
- hello:
- path: /hello/{name}
- defaults: { _controller: AppBundle:Greet:hello }
-
- .. code-block:: xml
-
-
-
- AppBundle:Greet:hello
-
-
- .. code-block:: php
-
- // app/config/routing.php
- $collection->add('hello', new Route('/hello/{name}', array(
- '_controller' => 'AppBundle:Greet:hello',
- )));
-
-Now generate the mod_rewrite rules:
-
-.. code-block:: terminal
-
- $ php app/console router:dump-apache -e=prod --no-debug
-
-Which should roughly output the following:
-
-.. code-block:: apache
-
- # skip "real" requests
- RewriteCond %{REQUEST_FILENAME} -f
- RewriteRule .* - [QSA,L]
-
- # hello
- RewriteCond %{REQUEST_URI} ^/hello/([^/]+?)$
- RewriteRule .* app.php [QSA,L,E=_ROUTING__route:hello,E=_ROUTING_name:%1,E=_ROUTING__controller:AppBundle\:Greet\:hello]
-
-You can now rewrite ``web/.htaccess`` to use the new rules, so with this example
-it should look like this:
-
-.. code-block:: apache
-
-
- RewriteEngine On
-
- # skip "real" requests
- RewriteCond %{REQUEST_FILENAME} -f
- RewriteRule .* - [QSA,L]
-
- # hello
- RewriteCond %{REQUEST_URI} ^/hello/([^/]+?)$
- RewriteRule .* app.php [QSA,L,E=_ROUTING__route:hello,E=_ROUTING_name:%1,E=_ROUTING__controller:AppBundle\:Greet\:hello]
-
-
-.. note::
-
- The procedure above should be done each time you add/change a route if you
- want to take full advantage of this setup.
-
-That's it!
-You're now all set to use Apache routes.
-
-Additional Tweaks
------------------
-
-To save some processing time, change occurrences of ``Request``
-to ``ApacheRequest`` in ``web/app.php``::
-
- // web/app.php
-
- require_once __DIR__.'/../app/bootstrap.php.cache';
- require_once __DIR__.'/../app/AppKernel.php';
- // require_once __DIR__.'/../app/AppCache.php';
-
- use Symfony\Component\HttpFoundation\ApacheRequest;
-
- $kernel = new AppKernel('prod', false);
- $kernel->loadClassCache();
- // $kernel = new AppCache($kernel);
- $kernel->handle(ApacheRequest::createFromGlobals())->send();
diff --git a/configuration/configuration_organization.rst b/configuration/configuration_organization.rst
index 093141f43e9..5b662023ee7 100644
--- a/configuration/configuration_organization.rst
+++ b/configuration/configuration_organization.rst
@@ -6,7 +6,7 @@ How to Organize Configuration Files
The default Symfony Standard Edition defines three
:doc:`execution environments ` called
-``dev``, ``prod`` and ``test``. An environment simply represents a way to
+``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
@@ -14,8 +14,8 @@ executes the ``registerContainerConfiguration()`` method of the ``AppKernel``
class::
// app/AppKernel.php
- use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Config\Loader\LoaderInterface;
+ use Symfony\Component\HttpKernel\Kernel;
class AppKernel extends Kernel
{
@@ -23,7 +23,7 @@ class::
public function registerContainerConfiguration(LoaderInterface $loader)
{
- $loader->load($this->getRootDir().'/config/config_'.$this->getEnvironment().'.yml');
+ $loader->load($this->getProjectDir().'/app/config/config_'.$this->getEnvironment().'.yml');
}
}
@@ -34,8 +34,9 @@ default Symfony Standard Edition follow this structure:
.. code-block:: text
- /
+ your-project/
├─ app/
+ │ ├─ ...
│ └─ config/
│ ├─ config.yml
│ ├─ config_dev.yml
@@ -46,9 +47,7 @@ default Symfony Standard Edition follow this structure:
│ ├─ routing.yml
│ ├─ routing_dev.yml
│ └─ security.yml
- ├─ src/
- ├─ vendor/
- └─ web/
+ ├─ ...
This default structure was chosen for its simplicity — one file per environment.
But as any other Symfony feature, you can customize it to better suit your needs.
@@ -65,8 +64,9 @@ name as the environment:
.. code-block:: text
- /
+ your-project/
├─ app/
+ │ ├─ ...
│ └─ config/
│ ├─ common/
│ │ ├─ config.yml
@@ -83,17 +83,15 @@ name as the environment:
│ ├─ parameters.yml
│ ├─ routing.yml
│ └─ security.yml
- ├─ src/
- ├─ vendor/
- └─ web/
+ ├─ ...
To make this work, change the code of the
:method:`Symfony\\Component\\HttpKernel\\KernelInterface::registerContainerConfiguration`
method::
// app/AppKernel.php
- use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Config\Loader\LoaderInterface;
+ use Symfony\Component\HttpKernel\Kernel;
class AppKernel extends Kernel
{
@@ -101,7 +99,7 @@ method::
public function registerContainerConfiguration(LoaderInterface $loader)
{
- $loader->load($this->getRootDir().'/config/'.$this->getEnvironment().'/config.yml');
+ $loader->load($this->getProjectDir().'/app/config/'.$this->getEnvironment().'/config.yml');
}
}
@@ -128,14 +126,14 @@ needed for the ``app/config/dev/config.yml`` file:
+ https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
-
-
-
+
+
+
@@ -161,8 +159,9 @@ and several files to define all application services:
.. code-block:: text
- /
+ your-project/
├─ app/
+ │ ├─ ...
│ └─ config/
│ ├─ bundles/
│ │ ├─ bundle1.yml
@@ -182,16 +181,14 @@ and several files to define all application services:
│ ├─ backend.yml
│ ├─ ...
│ └─ security.yml
- ├─ src/
- ├─ vendor/
- └─ web/
+ ├─ ...
Again, change the code of the ``registerContainerConfiguration()`` method to
make Symfony aware of the new file organization::
// app/AppKernel.php
- use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Config\Loader\LoaderInterface;
+ use Symfony\Component\HttpKernel\Kernel;
class AppKernel extends Kernel
{
@@ -199,7 +196,7 @@ make Symfony aware of the new file organization::
public function registerContainerConfiguration(LoaderInterface $loader)
{
- $loader->load($this->getRootDir().'/config/environments/'.$this->getEnvironment().'.yml');
+ $loader->load($this->getProjectDir().'/app/config/environments/'.$this->getEnvironment().'.yml');
}
}
@@ -240,15 +237,15 @@ format (``.yml``, ``.xml``, ``.php``, ``.ini``):
+ https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
-
-
-
-
+
+
+
+
@@ -304,13 +301,13 @@ any other configuration file:
+ https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
-
-
+
+
@@ -347,13 +344,13 @@ doesn't exist:
+ https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
-
-
+
+
diff --git a/configuration/environments.rst b/configuration/environments.rst
index ea430a6beb1..9fb3a6712bb 100644
--- a/configuration/environments.rst
+++ b/configuration/environments.rst
@@ -43,7 +43,7 @@ class::
public function registerContainerConfiguration(LoaderInterface $loader)
{
- $loader->load($this->getRootDir().'/config/config_'.$this->getEnvironment().'.yml');
+ $loader->load($this->getProjectDir().'/app/config/config_'.$this->getEnvironment().'.yml');
}
}
@@ -51,10 +51,10 @@ As you can see, when Symfony is loaded, it uses the given environment to
determine which configuration file to load. This accomplishes the goal of
multiple environments in an elegant, powerful and transparent way.
-Of course, in reality, each environment differs only somewhat from others.
+However, in practice each environment differs only somewhat from others.
Generally, all environments will share a large base of common configuration.
Opening the ``config_dev.yml`` configuration file, you can see how this is
-accomplished easily and transparently:
+accomplished:
.. configuration-block::
@@ -72,12 +72,12 @@ accomplished easily and transparently:
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">
-
+
@@ -118,15 +118,15 @@ configuration file:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:webprofiler="http://symfony.com/schema/dic/webprofiler"
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/webprofiler
- http://symfony.com/schema/dic/webprofiler/webprofiler-1.0.xsd">
+ https://symfony.com/schema/dic/webprofiler/webprofiler-1.0.xsd">
-
+
-
+
@@ -135,10 +135,10 @@ configuration file:
// app/config/config_dev.php
$loader->import('config.php');
- $container->loadFromExtension('web_profiler', array(
+ $container->loadFromExtension('web_profiler', [
'toolbar' => true,
// ...
- ));
+ ]);
.. index::
single: Environments; Executing different environments
@@ -198,10 +198,10 @@ this code and changing the environment string.
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``. This affects many things in the application,
- such as if errors should be displayed or if cache files are
+ 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.
+ 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 `.
@@ -214,9 +214,9 @@ this code and changing the environment string.
.. code-block:: yaml
doctrine:
- dbal:
- logging: '%kernel.debug%'
- # ...
+ dbal:
+ logging: '%kernel.debug%'
+ # ...
.. code-block:: xml
@@ -225,27 +225,23 @@ this code and changing the environment string.
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">
-
+
+
+
+
-.. code-block:: nginx
+
+
+
- server {
- server_name domain.tld www.domain.tld;
- root /var/www/project/web;
+
- location / {
- try_files $uri /app.php$is_args$args;
- }
+ .. code-block:: php
- location ~ ^/app\.php(/|$) {
- fastcgi_pass unix:/var/run/php5-fpm.sock;
- fastcgi_split_path_info ^(.+\.php)(/.*)$;
- include fastcgi_params;
- fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
- fastcgi_param DOCUMENT_ROOT $realpath_root;
- fastcgi_param SYMFONY__DATABASE__USER user;
- fastcgi_param SYMFONY__DATABASE__PASSWORD secret;
- internal;
- }
+ // app/config/config.php
+ $container->loadFromExtension('doctrine', [
+ 'dbal' => [
+ 'host' => '%env(DATABASE_HOST)%',
+ ],
+ ]);
- # ...
- }
+You can also give the ``env()`` parameters a default value: the default value
+will be used whenever the corresponding environment variable is *not* found:
-.. note::
+.. configuration-block::
- The examples above are for an Apache and Nginx configuration. However, this
- will work for any web server which supports the setting of environment
- variables.
+ .. code-block:: yaml
- Also, in order for your console to work (which does not use a web server),
- you must export these as shell variables. On a Unix system, you can run
- the following:
+ # app/config/parameters.yml
+ parameters:
+ database_host: '%env(DATABASE_HOST)%'
+ env(DATABASE_HOST): localhost
+
+ .. code-block:: xml
+
+
+
+
+
+
+ %env(DATABASE_HOST)%
+ localhost
+
+
+
+ .. code-block:: php
+
+ // app/config/parameters.php
+ $container->setParameter('database_host', '%env(DATABASE_HOST)%');
+ $container->setParameter('env(DATABASE_HOST)', 'localhost');
+
+Setting environment variables is generally done at the web server level or in the
+terminal. If you're using Apache, nginx or just the console, you can use e.g. one
+of the following:
+
+.. configuration-block::
+
+ .. code-block:: apache
+
+
+ # ...
+
+ SetEnv DATABASE_USER user
+ SetEnv DATABASE_PASSWORD secret
+
+
+ .. code-block:: nginx
+
+ fastcgi_param DATABASE_USER user;
+ fastcgi_param DATABASE_PASSWORD secret;
.. code-block:: terminal
- $ export SYMFONY__DATABASE__USER=user
- $ export SYMFONY__DATABASE__PASSWORD=secret
+ $ export DATABASE_USER=user
+ $ export DATABASE_PASSWORD=secret
+
+.. tip::
+
+ .. deprecated:: 3.3
+
+ The support of the special ``SYMFONY__`` environment variables was
+ deprecated in Symfony 3.3 and will be removed in 4.0. Instead of
+ using those variables, define regular environment variables and get
+ their values using the ``%env(...)%`` syntax in your config files.
+
+ You can also define the default value of any existing parameters using
+ special environment variables named after their corresponding parameter
+ prefixed with ``SYMFONY__`` after replacing dots by double underscores
+ (e.g. ``SYMFONY__KERNEL__CHARSET`` to set the default value of the
+ ``kernel.charset`` parameter). These default values are resolved when
+ compiling the service container and won't change at runtime once dumped.
+
+ 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.
-Now that you have declared an environment variable, it will be present
-in the PHP ``$_SERVER`` global variable. Symfony then automatically sets all
-``$_SERVER`` variables prefixed with ``SYMFONY__`` as parameters in the service
-container.
+Environment Variable Processors
+-------------------------------
-You can now reference these parameters wherever you need them.
+.. versionadded:: 3.4
+
+ Environment variable processors were introduced in Symfony 3.4.
+
+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
- doctrine:
- dbal:
- driver: pdo_mysql
- dbname: symfony_project
- user: '%database.user%'
- password: '%database.password%'
+ # app/config/config.yml
+ framework:
+ router:
+ http_port: '%env(int:HTTP_PORT)%'
.. code-block:: xml
@@ -107,33 +167,344 @@ You can now reference these parameters wherever you need them.
-
-
-
-
+ 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">
+
+
+
.. code-block:: php
- $container->loadFromExtension('doctrine', array(
- 'dbal' => array(
- 'driver' => 'pdo_mysql',
- 'dbname' => 'symfony_project',
- 'user' => '%database.user%',
- 'password' => '%database.password%',
- )
- ));
+ // app/config/config.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
+
+ # app/config/config.yml
+ parameters:
+ env(SECRET): 'some_secret'
+ framework:
+ secret: '%env(string:SECRET)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ some_secret
+
+
+
+
+
+ .. code-block:: php
+
+ // app/config/config.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
+
+ # app/config/config.yml
+ parameters:
+ env(HTTP_METHOD_OVERRIDE): 'true'
+ framework:
+ http_method_override: '%env(bool:HTTP_METHOD_OVERRIDE)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ true
+
+
+
+
+
+ .. code-block:: php
+
+ // app/config/config.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 constant value named in ``FOO``:
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # app/config/config.yml
+ 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
+
+ // app/config/config.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
+
+ # app/config/config.yml
+ 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
+
+ // app/config/config.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
+
+ # app/config/config.yml
+ parameters:
+ env(HOST): '10.0.0.1'
+ sentry_host: '%env(HOST)%'
+ env(SENTRY_DSN): 'https://%sentry_host%/project'
+ sentry:
+ dsn: '%env(resolve:SENTRY_DSN)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ 10.0.0.1
+ %env(HOST)%
+ https://%sentry_host%/project
+
+
+
+
+
+ .. code-block:: php
+
+ // app/config/config.php
+ $container->setParameter('env(HOST)', '10.0.0.1');
+ $container->setParameter('sentry_host', '%env(HOST)%');
+ $container->setParameter('env(SENTRY_DSN)', 'https://%sentry_host%/project');
+ $container->loadFromExtension('sentry', [
+ 'dsn' => '%env(resolve:SENTRY_DSN)%',
+ ]);
+
+``env(file:FOO)``
+ Returns the contents of a file whose path is the value of the ``FOO`` env var:
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # app/config/config.yml
+ parameters:
+ env(AUTH_FILE): '../auth.json'
+
+ services:
+ some_authenticator:
+ arguments:
+ $auth: '%env(file:AUTH_FILE)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ ../auth.json
+
+
+
+
+ "%env(file:AUTH_FILE)%"
+
+
+
+
+ .. code-block:: php
+
+ // app/config/config.php
+ $container->setParameter('env(AUTH_FILE)', __DIR__.'/../auth.json');
+
+ $container->register('some_authenticator')
+ ->setArgument('$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"
+
+ services:
+ some_authenticator:
+ arguments:
+ # 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` and
+then, define a service for that class::
+
+ class LowercasingEnvVarProcessor implements EnvVarProcessorInterface
+ {
+ private $container;
+
+ public function __construct(ContainerInterface $container)
+ {
+ $this->container = $container;
+ }
+
+ public function getEnv($prefix, $name, \Closure $getEnv)
+ {
+ $env = $getEnv($name);
+
+ return strtolower($env);
+ }
+
+ public static function getProvidedTypes()
+ {
+ return [
+ 'lowercase' => 'string',
+ ];
+ }
+ }
Constants
---------
@@ -163,10 +534,10 @@ in the container. The following imports a file named ``parameters.php``.
+ https://symfony.com/schema/dic/services/services-1.0.xsd">
-
+
@@ -190,5 +561,3 @@ the Symfony service container::
include_once('/path/to/drupal/sites/default/settings.php');
$container->setParameter('drupal.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 eb925992698..e57eb30021e 100644
--- a/configuration/front_controllers_and_kernel.rst
+++ b/configuration/front_controllers_and_kernel.rst
@@ -46,7 +46,7 @@ to `decorate`_ the kernel with additional features. Examples include:
* Adding HTTP level caching by wrapping the kernel with an instance of
:ref:`AppCache `;
* Enabling (or skipping) the :doc:`ClassCache `;
-* Enabling the :doc:`Debug Component `.
+* Enabling the `Debug component`_.
The front controller can be chosen by requesting URLs like:
@@ -55,7 +55,7 @@ The front controller can be chosen by requesting URLs like:
http://localhost/app_dev.php/some/path/...
As you can see, this URL contains the PHP script to be used as the front
-controller. You can use that to easily switch the front controller or use
+controller. You can use that to switch the front controller or use
a custom one by placing it in the ``web/`` directory (e.g. ``app_cache.php``).
When using Apache and the `RewriteRule shipped with the Symfony Standard Edition`_,
@@ -75,7 +75,7 @@ as the default one.
access. For example, you don't want to make a debugging environment
available to arbitrary users in your production environment.
-Technically, the `app/console`_ script used when running Symfony on the command
+Technically, the `bin/console`_ script used when running Symfony on the command
line is also a front controller, only that is not used for web, but for command
line requests.
@@ -111,9 +111,9 @@ to decide which bundles to create. The logic for that is in ``registerBundles()`
a method meant to be extended by you when you start adding bundles to your
application.
-You are, of course, free to create your own, alternative or additional
-``AppKernel`` 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 ``AppKernel``
+variants. All you need is to adapt your (or add a new) front controller to make
+use of the new kernel.
.. note::
@@ -153,17 +153,18 @@ front controller to the ``AppKernel``'s constructor. This name can then be
used in the :method:`Symfony\\Component\\HttpKernel\\KernelInterface::registerContainerConfiguration`
method to decide which configuration files to load.
-The Symfony Standard Edition's `AppKernel`_ class implements this method by simply
-loading the ``app/config/config_*environment*.yml`` file. You are, of course,
-free to implement this method differently if you need a more sophisticated
-way of loading your configuration.
+The Symfony Standard Edition's `AppKernel`_ class implements this method by
+loading the ``app/config/config_*environment*.yml`` file. You are free to
+implement this method differently if you need a more sophisticated way of
+loading your configuration.
+.. _Debug component: https://github.com/symfony/debug
.. _front controller: https://en.wikipedia.org/wiki/Front_Controller_pattern
.. _Symfony Standard Edition: https://github.com/symfony/symfony-standard
-.. _app.php: https://github.com/symfony/symfony-standard/blob/master/web/app.php
-.. _app_dev.php: https://github.com/symfony/symfony-standard/blob/master/web/app_dev.php
-.. _app/console: https://github.com/symfony/symfony-standard/blob/master/app/console
-.. _AppKernel: https://github.com/symfony/symfony-standard/blob/master/app/AppKernel.php
+.. _app.php: https://github.com/symfony/symfony-standard/blob/3.4/web/app.php
+.. _app_dev.php: https://github.com/symfony/symfony-standard/blob/3.4/web/app_dev.php
+.. _bin/console: https://github.com/symfony/symfony-standard/blob/3.4/bin/console
+.. _AppKernel: https://github.com/symfony/symfony-standard/blob/3.4/app/AppKernel.php
.. _decorate: https://en.wikipedia.org/wiki/Decorator_pattern
-.. _RewriteRule shipped with the Symfony Standard Edition: https://github.com/symfony/symfony-standard/blob/master/web/.htaccess
+.. _RewriteRule shipped with the Symfony Standard Edition: https://github.com/symfony/symfony-standard/blob/3.4/web/.htaccess
.. _template methods: https://en.wikipedia.org/wiki/Template_method_pattern
diff --git a/configuration/micro_kernel_trait.rst b/configuration/micro_kernel_trait.rst
index b4daf9f476d..985dbb12ad4 100644
--- a/configuration/micro_kernel_trait.rst
+++ b/configuration/micro_kernel_trait.rst
@@ -11,24 +11,19 @@ as one file? This is possible thanks to the new
you to start with a tiny application, and then add features and structure as you
need to.
-.. note::
-
- The MicroKernelTrait requires PHP 5.4. However, there's nothing special about
- this trait. If you're using PHP 5.3, simply copy its methods into *your* kernel
- class to get the same functionality.
-
A Single-File Symfony Application
---------------------------------
Start with a completely empty directory. Get ``symfony/symfony`` as a dependency
via Composer:
-.. code-block:: bash
+.. code-block:: terminal
$ composer require symfony/symfony
Next, create an ``index.php`` file that creates a kernel class and executes it::
+ // index.php
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -46,17 +41,17 @@ Next, create an ``index.php`` file that creates a kernel class and executes it::
public function registerBundles()
{
- return array(
+ return [
new Symfony\Bundle\FrameworkBundle\FrameworkBundle()
- );
+ ];
}
protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader)
{
// PHP equivalent of config.yml
- $c->loadFromExtension('framework', array(
+ $c->loadFromExtension('framework', [
'secret' => 'S0ME_SECRET'
- ));
+ ]);
}
protected function configureRoutes(RouteCollectionBuilder $routes)
@@ -68,9 +63,9 @@ Next, create an ``index.php`` file that creates a kernel class and executes it::
public function randomAction($limit)
{
- return new JsonResponse(array(
+ return new JsonResponse([
'number' => rand(0, $limit)
- ));
+ ]);
}
}
@@ -80,11 +75,12 @@ Next, create an ``index.php`` file that creates a 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:: bash
+.. code-block:: terminal
- $ php -S localhost:8000
+ $ symfony server:start
Then see the JSON response in your browser:
@@ -106,9 +102,9 @@ that define your bundles, your services and your routes:
directly in PHP or load external configuration files (shown below).
**configureRoutes(RouteCollectionBuilder $routes)**
- Your job in this method is to add routes to the application. The ``RouteCollectionBuilder``
- is new in Symfony 2.8 and has methods that make adding routes in PHP more fun.
- You can also load external routing files (shown below).
+ Your job in this method is to add routes to the application. The
+ ``RouteCollectionBuilder`` has methods that make adding routes in PHP more
+ fun. You can also load external routing files (shown below).
Advanced Example: Twig, Annotations and the Web Debug Toolbar
-------------------------------------------------------------
@@ -132,30 +128,24 @@ your ``composer.json`` file to load from there:
}
}
-Now, suppose you want to use Twig and load routes via annotations. For annotation
-routing, you need SensioFrameworkExtraBundle. This comes with a normal Symfony project.
-But in this case, you need to download it:
-
-.. code-block:: bash
-
- $ composer require sensio/framework-extra-bundle
+Then, run ``composer dump-autoload`` to dump your new autoload config.
-Instead of putting *everything* in ``index.php``, create a new ``app/AppKernel.php``
-to hold the kernel. Now it looks like this::
+Now, suppose you want to use Twig and load routes via annotations. Instead of
+putting *everything* in ``index.php``, create a new ``app/AppKernel.php`` to
+hold the kernel. Now it looks like this::
// app/AppKernel.php
-
+ use Doctrine\Common\Annotations\AnnotationRegistry;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Routing\RouteCollectionBuilder;
- use Doctrine\Common\Annotations\AnnotationRegistry;
// require Composer's autoloader
$loader = require __DIR__.'/../vendor/autoload.php';
// auto-load annotations
- AnnotationRegistry::registerLoader(array($loader, 'loadClass'));
+ AnnotationRegistry::registerLoader([$loader, 'loadClass']);
class AppKernel extends Kernel
{
@@ -163,11 +153,10 @@ to hold the kernel. Now it looks like this::
public function registerBundles()
{
- $bundles = array(
+ $bundles = [
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new Symfony\Bundle\TwigBundle\TwigBundle(),
- new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle()
- );
+ ];
if ($this->getEnvironment() == 'dev') {
$bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
@@ -182,10 +171,10 @@ to hold the kernel. Now it looks like this::
// configure WebProfilerBundle only if the bundle is enabled
if (isset($this->bundles['WebProfilerBundle'])) {
- $c->loadFromExtension('web_profiler', array(
+ $c->loadFromExtension('web_profiler', [
'toolbar' => true,
'intercept_redirects' => false,
- ));
+ ]);
}
}
@@ -198,10 +187,28 @@ to hold the kernel. Now it looks like this::
}
// load the annotation routes
- $routes->import(__DIR__.'/../src/App/Controller/', '/', 'annotation');
+ $routes->import(__DIR__.'/../src/Controller/', '/', 'annotation');
+ }
+
+ // optional, to use the standard Symfony cache directory
+ public function getCacheDir()
+ {
+ return __DIR__.'/../var/cache/'.$this->getEnvironment();
+ }
+
+ // optional, to use the standard Symfony logs directory
+ public function getLogDir()
+ {
+ return __DIR__.'/../var/logs';
}
}
+.. versionadded:: 3.4
+
+ Support for annotation routing without an external bundle was introduced
+ in Symfony 3.4. Prior to version 3.4, you needed to install the
+ SensioFrameworkExtraBundle.
+
Unlike the previous kernel, this loads an external ``app/config/config.yml`` file,
because the configuration started to get bigger:
@@ -223,38 +230,38 @@ 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">
twig
-
+
.. code-block:: php
// app/config/config.php
- $container->loadFromExtension('framework', array(
+ $container->loadFromExtension('framework', [
'secret' => 'S0ME_SECRET',
- 'templating' => array(
- 'engines' => array('twig'),
- ),
- 'profiler' => array(
+ 'templating' => [
+ 'engines' => ['twig'],
+ ],
+ 'profiler' => [
'only_exceptions' => false,
- ),
- ));
+ ],
+ ]);
-This also loads annotation routes from an ``src/App/Controller/`` directory, which
+This also loads annotation routes from an ``src/Controller/`` directory, which
has one file in it::
- // src/App/Controller/MicroController.php
+ // src/Controller/MicroController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
- use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
+ use Symfony\Component\Routing\Annotation\Route;
class MicroController extends Controller
{
@@ -265,9 +272,9 @@ has one file in it::
{
$number = rand(0, $limit);
- return $this->render('micro/random.html.twig', array(
+ return $this->render('micro/random.html.twig', [
'number' => $number
- ));
+ ]);
}
}
@@ -292,7 +299,6 @@ Finally, you need a front controller to boot and run the application. Create a
``web/index.php``::
// web/index.php
-
use Symfony\Component\HttpFoundation\Request;
require __DIR__.'/../app/AppKernel.php';
@@ -312,9 +318,7 @@ this:
your-project/
├─ app/
| ├─ AppKernel.php
- │ ├─ cache/
│ ├─ config/
- │ ├─ logs/
│ └─ Resources
| └─ views
| └─ micro
@@ -323,6 +327,9 @@ this:
│ └─ App
| └─ Controller
| └─ MicroController.php
+ ├─ var/
+ | ├─ cache/
+ │ └─ logs/
├─ vendor/
│ └─ ...
├─ web/
@@ -330,17 +337,18 @@ 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:: bash
+.. code-block:: terminal
cd web/
- $ php -S localhost:8000
+ $ symfony server:start
-Then see webpage in browser:
+Then visit the page in your browser:
http://localhost:8000/random/10
Hey, that looks a lot like a *traditional* Symfony application! You're right: the
``MicroKernelTrait`` *is* still Symfony: but you can control your structure and
-features quite easily.
+features with less boilerplate configuration and code.
diff --git a/configuration/multiple_kernels.rst b/configuration/multiple_kernels.rst
index 20de4cc288c..5576efee50f 100644
--- a/configuration/multiple_kernels.rst
+++ b/configuration/multiple_kernels.rst
@@ -79,8 +79,8 @@ sure to also change the location of the cache, logs and configuration files so
they don't collide with the files from ``AppKernel``::
// app/ApiKernel.php
- use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Config\Loader\LoaderInterface;
+ use Symfony\Component\HttpKernel\Kernel;
class ApiKernel extends Kernel
{
@@ -101,7 +101,7 @@ they don't collide with the files from ``AppKernel``::
public function registerContainerConfiguration(LoaderInterface $loader)
{
- $loader->load($this->getRootDir().'/config/api/config_'.$this->getEnvironment().'.yml');
+ $loader->load($this->getProjectDir().'/app/config/api/config_'.$this->getEnvironment().'.yml');
}
}
@@ -119,7 +119,7 @@ to your ``composer.json`` autoload section:
}
}
-Then, run ``composer install`` to dump your new autoload config.
+Then, run ``composer dump-autoload`` to dump your new autoload config.
Step 3) Define the Kernel Configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -143,19 +143,18 @@ config files or better, import them and override the needed options:
Executing Commands with a Different Kernel
------------------------------------------
-The ``app/console`` script used to run Symfony commands always uses the default
+The ``bin/console`` script used to run Symfony commands always uses the default
``AppKernel`` class to build the application and load the commands. If you need
-to execute console commands using the new kernel, duplicate the ``app/console``
+to execute console commands using the new kernel, duplicate the ``bin/console``
script and rename it (e.g. ``bin/api``).
-Then, replace the ``AppKernel`` instantiation by your own kernel instantiation
-(e.g. ``ApiKernel``) and now you can execute commands using the new kernel
-(e.g. ``php bin/api cache:clear``) Now you can use execute commands using the
-new kernel.
+Then, replace the ``AppKernel`` instance by your own kernel instance (e.g.
+``ApiKernel``). Now you can execute commands using the new kernel (e.g.
+``php bin/api cache:clear``).
.. note::
- The commands available for each console script (e.g. ``app/console`` and
+ The commands available for each console script (e.g. ``bin/console`` and
``bin/api``) can differ because they depend on the bundles enabled for each
kernel, which could be different.
@@ -175,7 +174,7 @@ In order to solve this issue, add the following configuration to your kernel:
twig:
paths:
# allows to use app/Resources/views/ templates in the ApiKernel
- "%kernel.root_dir%/../app/Resources/views": ~
+ "%kernel.project_dir%/app/Resources/views": ~
Running Tests Using a Different Kernel
--------------------------------------
diff --git a/configuration/override_dir_structure.rst b/configuration/override_dir_structure.rst
index 146debeabe3..38b3a0ab821 100644
--- a/configuration/override_dir_structure.rst
+++ b/configuration/override_dir_structure.rst
@@ -5,21 +5,27 @@ How to Override Symfony's default Directory Structure
=====================================================
Symfony automatically ships with a default directory structure. You can
-easily override this directory structure to create your own. The default
+override this directory structure to create your own. The default
directory structure is:
.. code-block:: text
your-project/
├─ app/
- │ ├─ cache/
│ ├─ config/
- │ ├─ logs/
│ ├─ Resources/
│ │ └─ views/
│ └─ ...
+ ├─ bin/
+ │ └─ ...
├─ src/
│ └─ ...
+ ├─ tests/
+ │ └─ ...
+ ├─ var/
+ │ ├─ cache/
+ │ ├─ logs/
+ │ └─ ...
├─ vendor/
│ └─ ...
└─ web/
@@ -43,13 +49,13 @@ in the ``AppKernel`` class of your application::
public function getCacheDir()
{
- return $this->rootDir.'/'.$this->environment.'/cache';
+ return dirname(__DIR__).'/var/'.$this->environment.'/cache';
}
}
-``$this->rootDir`` is the absolute path to the ``app`` directory and ``$this->environment``
-is the current environment (i.e. ``dev``). In this case you have changed
-the location of the cache directory to ``app/{environment}/cache``.
+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``.
.. caution::
@@ -76,11 +82,11 @@ method::
public function getLogDir()
{
- return $this->rootDir.'/'.$this->environment.'/logs';
+ return dirname(__DIR__).'/var/'.$this->environment.'/logs';
}
}
-Here you have changed the location of the directory to ``app/{environment}/logs``.
+Here you have changed the location of the directory to ``var/{environment}/logs``.
.. _override-templates-dir:
@@ -88,8 +94,8 @@ Override the Templates Directory
--------------------------------
If your templates are not stored in the default ``app/Resources/views/``
-directory, use the :ref:`twig.paths ` configuration option to
-define your own templates directory (or directories):
+directory, use the :ref:`twig.default_path ` configuration option to
+define your own templates directory (use :ref:`twig.paths ` for multiple directories):
.. configuration-block::
@@ -98,7 +104,7 @@ define your own templates directory (or directories):
# app/config/config.yml
twig:
# ...
- paths: ["%kernel.root_dir%/../templates"]
+ default_path: "%kernel.project_dir%/templates"
.. code-block:: xml
@@ -108,12 +114,12 @@ define your 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.root_dir%/../templates
+ %kernel.project_dir%/templates
@@ -121,11 +127,9 @@ define your own templates directory (or directories):
.. code-block:: php
// app/config/config.php
- $container->loadFromExtension('twig', array(
- 'paths' => array(
- '%kernel.root_dir%/../templates',
- ),
- ));
+ $container->loadFromExtension('twig', [
+ 'default_path' => '%kernel.project_dir%/templates',
+ ]);
.. _override-web-dir:
@@ -133,23 +137,22 @@ Override the ``web`` Directory
------------------------------
If you need to rename or move your ``web`` directory, the only thing you
-need to guarantee is that the path to the ``app`` directory is still correct
-in your ``app.php`` and ``app_dev.php`` front controllers. 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::
+need to guarantee is that the path to the ``var`` directory is still correct
+in your ``app.php`` and ``app_dev.php`` front controllers. 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__.'/../Symfony/app/bootstrap.php.cache';
- require_once __DIR__.'/../Symfony/app/AppKernel.php';
+ require_once __DIR__.'/../path/to/app/autoload.php';
-You also need to change the ``extra.symfony-web-dir`` option in the ``composer.json``
-file:
+You also need to change the ``extra.symfony-web-dir`` option in the
+``composer.json`` file:
-.. code-block:: javascript
+.. code-block:: json
{
- ...
+ "...": "...",
"extra": {
- ...
+ "...": "...",
"symfony-web-dir": "my_new_web_dir"
}
}
@@ -177,7 +180,7 @@ file:
# ...
assetic:
# ...
- read_from: '%kernel.root_dir%/../../public_html'
+ read_from: '%kernel.project_dir%/../public_html'
.. code-block:: xml
@@ -187,12 +190,12 @@ file:
xmlns:assetic="http://symfony.com/schema/dic/assetic"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/assetic
- http://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
+ https://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
-
+
@@ -201,18 +204,18 @@ file:
// app/config/config.php
// ...
- $container->loadFromExtension('assetic', array(
+ $container->loadFromExtension('assetic', [
// ...
- 'read_from' => '%kernel.root_dir%/../../public_html',
- ));
+ 'read_from' => '%kernel.project_dir%/../public_html',
+ ]);
Now you just need to clear the cache and dump the assets again and your
application should work:
.. code-block:: terminal
- $ php app/console cache:clear --env=prod
- $ php app/console assetic:dump --env=prod --no-debug
+ $ php bin/console cache:clear --env=prod
+ $ php bin/console assetic:dump --env=prod --no-debug
Override the ``vendor`` Directory
---------------------------------
@@ -234,6 +237,7 @@ The change in the ``composer.json`` will look like this:
Then, update the path to the ``autoload.php`` file in ``app/autoload.php``::
// app/autoload.php
+
// ...
$loader = require '/some/dir/vendor/autoload.php';
diff --git a/configuration/using_parameters_in_dic.rst b/configuration/using_parameters_in_dic.rst
index 4d45d7738c2..f39bf1726d2 100644
--- a/configuration/using_parameters_in_dic.rst
+++ b/configuration/using_parameters_in_dic.rst
@@ -51,17 +51,21 @@ Now, examine the results to see this closely:
+ xmlns:my-bundle="http://example.org/schema/dic/my_bundle"
+ xsi:schemaLocation="http://symfony.com/schema/dic/services
+ https://symfony.com/schema/dic/services/services-1.0.xsd
+ http://example.org/schema/dic/my_bundle
+ https://example.org/schema/dic/my_bundle/my_bundle-1.0.xsd">
-
+
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
+Symfony also looks in the ``Command/`` directory of each bundle for commands
+non registered as a service and automatically registers those classes as
+commands. However, this auto-registration was deprecated in Symfony 3.4. In
+Symfony 4.0, commands won't be auto-registered anymore.
- // app/config/services.php
- use AppBundle\Command\MyCommand;
+.. note::
- $container
- ->register('app.command.my_command', MyCommand::class)
- ->addTag('console.command')
- ;
-
-Using Dependencies and Parameters to Set Default Values for Options
--------------------------------------------------------------------
+ You can also manually register your command as a service by configuring the service
+ and :doc:`tagging it ` with ``console.command``.
-Imagine you want to provide a default value for the ``name`` option. You could
-pass one of the following as the 5th argument of ``addOption()``:
+In either case, if your class extends :class:`Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerAwareCommand`,
+you can access public services via ``$this->getContainer()->get('SERVICE_ID')``.
-* a hardcoded string;
-* a container parameter (e.g. something from ``parameters.yml``);
-* a value computed by a service (e.g. a repository).
+But if your class is registered as a service, you can instead access services by
+using normal :ref:`dependency injection `.
-By extending ``ContainerAwareCommand``, only the first is possible, because you
-can't access the container inside the ``configure()`` method. Instead, inject
-any parameter or service you need into the constructor. For example, suppose you
-store the default value in some ``%command.default_name%`` parameter::
+For example, suppose you want to log something from within your command::
- // src/AppBundle/Command/GreetCommand.php
namespace AppBundle\Command;
+ use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
- use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
- class GreetCommand extends Command
+ class SunshineCommand extends Command
{
- protected $defaultName;
+ protected static $defaultName = 'app:sunshine';
+ private $logger;
- public function __construct($defaultName)
+ public function __construct(LoggerInterface $logger)
{
- $this->defaultName = $defaultName;
+ $this->logger = $logger;
+ // you *must* call the parent constructor
parent::__construct();
}
protected function configure()
{
- // try to avoid work here (e.g. database query)
- // this method is *always* called - see warning below
-
$this
- ->setName('demo:greet')
- ->setDescription('Greet someone')
- ->addOption(
- 'name',
- '-n',
- InputOption::VALUE_REQUIRED,
- 'Who do you want to greet?',
- $this->defaultName
- )
- ;
+ ->setDescription('Good morning!');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
- $name = $input->getOption('name');
-
- $output->writeln($name);
+ $this->logger->info('Waking up the sun');
+ // ...
}
}
-Now, just update the arguments of your service configuration like normal to
-inject the ``command.default_name`` parameter:
+If you're using the :ref:`default services.yml configuration `,
+the command class will automatically be registered as a service and passed the ``$logger``
+argument (thanks to autowiring). In other words, *just* by creating this class, everything
+works! You can call the ``app:sunshine`` command and start logging.
+
+.. caution::
+
+ You *do* have access to services in ``configure()``. However, if your command is
+ not :ref:`lazy `, try to avoid doing any
+ work (e.g. making database queries), as that code will be run, even if you're using
+ the console to execute a different command.
+
+.. _console-command-service-lazy-loading:
+
+Lazy Loading
+------------
+
+.. versionadded:: 3.4
+
+ Support for command lazy loading was introduced in Symfony 3.4.
+
+To make your command lazily loaded, either define its ``$defaultName`` static property::
+
+ class SunshineCommand extends Command
+ {
+ protected static $defaultName = 'app:sunshine';
+
+ // ...
+ }
+
+Or set the ``command`` attribute on the ``console.command`` tag in your service definition:
.. configuration-block::
.. code-block:: yaml
- # app/config/config.yml
- parameters:
- command.default_name: Javier
-
services:
- app.command.my_command:
- class: AppBundle\Command\MyCommand
- arguments: ["%command.default_name%"]
+ # ...
+
+ AppBundle\Command\SunshineCommand:
tags:
- - { name: console.command }
+ - { name: 'console.command', command: 'app:sunshine' }
.. code-block:: xml
-
-
-
- Javier
-
+ https://symfony.com/schema/dic/services/services-1.0.xsd">
-
- %command.default_name%
-
+
+
+
+
-
+
.. code-block:: php
- // app/config/config.php
- use AppBundle\Command\MyCommand;
+ use AppBundle\Command\SunshineCommand;
- $container->setParameter('command.default_name', 'Javier');
+ // ...
- $container
- ->register('app.command.my_command', MyCommand::class)
- ->setArguments(array('%command.default_name%'))
- ->addTag('console.command')
+ $container->register(SunshineCommand::class)
+ ->addTag('console.command', ['command' => 'app:sunshine'])
;
-Great, you now have a dynamic default value!
+.. note::
+
+ If the command defines aliases (using the
+ :method:`Symfony\\Component\\Console\\Command\\Command::getAliases` method)
+ you must add one ``console.command`` tag per alias.
+
+That's it. One way or another, the ``SunshineCommand`` will be instantiated
+only when the ``app:sunshine`` command is actually called.
+
+.. note::
+
+ You don't need to call ``setName()`` for configuring the command when it is lazy.
.. caution::
- Be careful not to actually do any work in ``configure`` (e.g. make database
- queries), as your code will be run, even if you're using the console to
- execute a different command.
+ Calling the ``list`` command will instantiate all commands, including lazy commands.
diff --git a/console/hide_commands.rst b/console/hide_commands.rst
new file mode 100644
index 00000000000..4a747b23245
--- /dev/null
+++ b/console/hide_commands.rst
@@ -0,0 +1,37 @@
+How to Hide Console Commands
+============================
+
+By default, all console commands are listed when executing the console application
+script without arguments or when using the ``list`` command.
+
+However, sometimes commands are not intended to be executed by end-users; for
+example, commands for the legacy parts of the application, commands exclusively
+executed through scheduled tasks, etc.
+
+In those cases, you can define the command as **hidden** by setting the
+``setHidden()`` method to ``true`` in the command configuration::
+
+ // src/AppBundle/Command/LegacyCommand.php
+ namespace AppBundle\Command;
+
+ use Symfony\Component\Console\Command\Command;
+
+ class LegacyCommand extends Command
+ {
+ protected static $defaultName = 'app:legacy';
+
+ protected function configure()
+ {
+ $this
+ ->setHidden(true)
+ // ...
+ ;
+ }
+ }
+
+Hidden commands behave the same as normal commands but they are no longer displayed
+in command listings, so end-users are not aware of their existence.
+
+.. note::
+
+ Hidden commands are still available using the JSON or XML descriptor.
diff --git a/console/input.rst b/console/input.rst
index 61c19ef368c..3ac28516674 100644
--- a/console/input.rst
+++ b/console/input.rst
@@ -14,6 +14,7 @@ or required. For example, to add an optional ``last_name`` argument to the comma
and make the ``name`` argument required::
// ...
+ use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
class GreetCommand extends Command
@@ -33,6 +34,10 @@ and make the ``name`` argument required::
You now have access to a ``last_name`` argument in your command::
// ...
+ use Symfony\Component\Console\Command\Command;
+ use Symfony\Component\Console\Input\InputInterface;
+ use Symfony\Component\Console\Output\OutputInterface;
+
class GreetCommand extends Command
{
// ...
@@ -54,10 +59,10 @@ The command can now be used in either of the following ways:
.. code-block:: terminal
- $ php app/console app:greet Fabien
+ $ php bin/console app:greet Fabien
Hi Fabien!
- $ php app/console app:greet Fabien Potencier
+ $ php bin/console app:greet Fabien Potencier
Hi Fabien Potencier!
It is also possible to let an argument take a list of values (imagine you want
@@ -69,13 +74,14 @@ to greet all your friends). Only the last argument can be a list::
'names',
InputArgument::IS_ARRAY,
'Who do you want to greet (separate multiple names with a space)?'
- );
+ )
+ ;
To use this, just specify as many names as you want:
.. code-block:: terminal
- $ php app/console app:greet Fabien Ryan Bernhard
+ $ php bin/console app:greet Fabien Ryan Bernhard
You can access the ``names`` argument as an array::
@@ -98,7 +104,7 @@ There are three argument variants you can use:
The argument can contain any number of values. For that reason, it must be
used at the end of the argument list.
-You can combine ``IS_ARRAY`` with ``REQUIRED`` and ``OPTIONAL`` like this::
+You can combine ``IS_ARRAY`` with ``REQUIRED`` or ``OPTIONAL`` like this::
$this
// ...
@@ -106,7 +112,8 @@ You can combine ``IS_ARRAY`` with ``REQUIRED`` and ``OPTIONAL`` like this::
'names',
InputArgument::IS_ARRAY | InputArgument::REQUIRED,
'Who do you want to greet (separate multiple names with a space)?'
- );
+ )
+ ;
Using Command Options
---------------------
@@ -114,7 +121,7 @@ Using Command Options
Unlike arguments, options are not ordered (meaning you can specify them in any
order) and are specified with two dashes (e.g. ``--yell``). Options are
*always* optional, and can be setup to accept a value (e.g. ``--dir=src``) or
-simply as a boolean flag without a value (e.g. ``--yell``).
+as a boolean flag without a value (e.g. ``--yell``).
For example, add a new option to the command that can be used to specify
how many times in a row the message should be printed::
@@ -130,7 +137,8 @@ how many times in a row the message should be printed::
InputOption::VALUE_REQUIRED,
'How many times should the message be printed?',
1
- );
+ )
+ ;
Next, use this in the command to print the message multiple times::
@@ -144,25 +152,25 @@ flag:
.. code-block:: terminal
# no --iterations provided, the default (1) is used
- $ php app/console app:greet Fabien
+ $ php bin/console app:greet Fabien
Hi Fabien!
- $ php app/console app:greet Fabien --iterations=5
- Hi Fabien
- Hi Fabien
- Hi Fabien
- Hi Fabien
- Hi Fabien
+ $ php bin/console app:greet Fabien --iterations=5
+ Hi Fabien!
+ Hi Fabien!
+ Hi Fabien!
+ Hi Fabien!
+ Hi Fabien!
# the order of options isn't important
- $ php app/console app:greet Fabien --iterations=5 --yell
- $ php app/console app:greet Fabien --yell --iterations=5
- $ php app/console app:greet --yell --iterations=5 Fabien
+ $ php bin/console app:greet Fabien --iterations=5 --yell
+ $ php bin/console app:greet Fabien --yell --iterations=5
+ $ php bin/console app:greet --yell --iterations=5 Fabien
.. tip::
- You can also declare a one-letter shortcut that you can call with a single
- dash, like ``-i``::
+ You can also declare a one-letter shortcut that you can call with a single
+ dash, like ``-i``::
$this
// ...
@@ -172,7 +180,8 @@ flag:
InputOption::VALUE_REQUIRED,
'How many times should the message be printed?',
1
- );
+ )
+ ;
Note that to comply with the `docopt standard`_, long options can specify their
values after a white space or an ``=`` sign (e.g. ``--iterations 5`` or
@@ -183,7 +192,7 @@ separation at all (e.g. ``-i 5`` or ``-i5``).
While it is possible to separate an option from its value with a white space,
using this form leads to an ambiguity should the option appear before the
- command name. For example, ``php app/console --iterations 5 app:greet Fabien``
+ command name. For example, ``php bin/console --iterations 5 app:greet Fabien``
is ambiguous; Symfony would interpret ``5`` as the command name. To avoid
this situation, always place options after the command name, or avoid using
a space to separate the option name from its value.
@@ -215,8 +224,9 @@ You can combine ``VALUE_IS_ARRAY`` with ``VALUE_REQUIRED`` or
null,
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Which colors do you like?',
- array('blue', 'red')
- );
+ ['blue', 'red']
+ )
+ ;
Options with optional arguments
-------------------------------
@@ -234,7 +244,8 @@ optionally accepts a value, but it's a bit tricky. Consider this example::
null,
InputOption::VALUE_OPTIONAL,
'Should I yell while greeting?'
- );
+ )
+ ;
This option can be used in 3 ways: ``--yell``, ``yell=louder``, and not passing
the option at all. However, it's hard to distinguish between passing the option
@@ -253,7 +264,8 @@ To solve this issue, you have to set the option's default value to ``false``::
InputOption::VALUE_OPTIONAL,
'Should I yell while greeting?',
false // this is the new default value, instead of null
- );
+ )
+ ;
Now check the value of the option and keep in mind that ``false !== null``::
@@ -261,10 +273,4 @@ Now check the value of the option and keep in mind that ``false !== null``::
$yell = ($optionValue !== false);
$yellLouder = ($optionValue === 'louder');
-.. caution::
-
- Due to a PHP limitation, passing an empty string is indistinguishable from
- not passing any value at all. In ``command --prefix`` and ``command --prefix=''``
- cases, the value of the ``prefix`` option will be ``null``.
-
.. _`docopt standard`: http://docopt.org/
diff --git a/console/lazy_commands.rst b/console/lazy_commands.rst
new file mode 100644
index 00000000000..9c4e9585e07
--- /dev/null
+++ b/console/lazy_commands.rst
@@ -0,0 +1,84 @@
+How to Make Commands Lazily Loaded
+==================================
+
+.. versionadded:: 3.4
+
+ Support for command lazy loading was introduced in Symfony 3.4.
+
+.. note::
+
+ If you are using the Symfony full-stack framework, you are probably looking for
+ details about :ref:`creating lazy commands `
+
+The traditional way of adding commands to your application is to use
+:method:`Symfony\\Component\\Console\\Application::add`, which expects a
+``Command`` instance as an argument.
+
+In order to lazy-load commands, you need to register an intermediate loader
+which will be responsible for returning ``Command`` instances::
+
+ use AppBundle\Command\HeavyCommand;
+ use Symfony\Component\Console\Application;
+ use Symfony\Component\Console\CommandLoader\FactoryCommandLoader;
+
+ $commandLoader = new FactoryCommandLoader([
+ 'app:heavy' => function () { return new HeavyCommand(); },
+ ]);
+
+ $application = new Application();
+ $application->setCommandLoader($commandLoader);
+ $application->run();
+
+This way, the ``HeavyCommand`` instance will be created only when the ``app:heavy``
+command is actually called.
+
+This example makes use of the built-in
+:class:`Symfony\\Component\\Console\\CommandLoader\\FactoryCommandLoader` class,
+but the :method:`Symfony\\Component\\Console\\Application::setCommandLoader`
+method accepts any
+:class:`Symfony\\Component\\Console\\CommandLoader\\CommandLoaderInterface`
+instance so you can use your own implementation.
+
+Built-in Command Loaders
+------------------------
+
+``FactoryCommandLoader``
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+The :class:`Symfony\\Component\\Console\\CommandLoader\\FactoryCommandLoader`
+class provides a simple way of getting commands lazily loaded as it takes an
+array of ``Command`` factories as its only constructor argument::
+
+ use Symfony\Component\Console\CommandLoader\FactoryCommandLoader;
+
+ $commandLoader = new FactoryCommandLoader([
+ 'app:foo' => function () { return new FooCommand(); },
+ 'app:bar' => [BarCommand::class, 'create'],
+ ]);
+
+Factories can be any PHP callable and will be executed each time
+:method:`Symfony\\Component\\Console\\CommandLoader\\FactoryCommandLoader::get`
+is called.
+
+``ContainerCommandLoader``
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The :class:`Symfony\\Component\\Console\\CommandLoader\\ContainerCommandLoader`
+class can be used to load commands from a PSR-11 container. As such, its
+constructor takes a PSR-11 ``ContainerInterface`` implementation as its first
+argument and a command map as its last argument. The command map must be an array
+with command names as keys and service identifiers as values::
+
+ use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
+ use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+ $containerBuilder = new ContainerBuilder();
+ $containerBuilder->register(FooCommand::class, FooCommand::class);
+ $containerBuilder->compile();
+
+ $commandLoader = new ContainerCommandLoader($containerBuilder, [
+ 'app:foo' => FooCommand::class,
+ ]);
+
+Like this, executing the ``app:foo`` command will load the ``FooCommand`` service
+by calling ``$containerBuilder->get(FooCommand::class)``.
diff --git a/console/lockable_trait.rst b/console/lockable_trait.rst
new file mode 100644
index 00000000000..3f6aad3c4cf
--- /dev/null
+++ b/console/lockable_trait.rst
@@ -0,0 +1,48 @@
+Prevent Multiple Executions of a Console Command
+================================================
+
+.. versionadded:: 3.2
+
+ The ``LockableTrait`` was introduced in Symfony 3.2.
+
+A simple but effective way to prevent multiple executions of the same command in
+a single server is to use `locks`_. The :doc:`Lock component `
+provides multiple classes to create locks based on the filesystem (:ref:`FlockStore `),
+shared memory (:ref:`SemaphoreStore `) and even databases
+and Redis servers.
+
+In addition, the Console component provides a PHP trait called ``LockableTrait``
+that adds two convenient methods to lock and release commands::
+
+ // ...
+ use Symfony\Component\Console\Command\Command;
+ use Symfony\Component\Console\Command\LockableTrait;
+ use Symfony\Component\Console\Input\InputInterface;
+ use Symfony\Component\Console\Output\OutputInterface;
+
+ class UpdateContentsCommand extends Command
+ {
+ use LockableTrait;
+
+ // ...
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ if (!$this->lock()) {
+ $output->writeln('The command is already running in another process.');
+
+ return 0;
+ }
+
+ // If you prefer to wait until the lock is released, use this:
+ // $this->lock(null, true);
+
+ // ...
+
+ // if not released explicitly, Symfony releases the lock
+ // automatically when the execution of the command ends
+ $this->release();
+ }
+ }
+
+.. _`locks`: https://en.wikipedia.org/wiki/Lock_(computer_science)
diff --git a/console/logging.rst b/console/logging.rst
deleted file mode 100644
index 0ce9072c6f6..00000000000
--- a/console/logging.rst
+++ /dev/null
@@ -1,257 +0,0 @@
-.. index::
- single: Console; Enabling logging
-
-How to Enable Logging in Console Commands
-=========================================
-
-The Console component doesn't provide any logging capabilities out of the box.
-Normally, you run console commands manually and observe the output, which is
-why logging is not provided. However, there are cases when you might need
-logging. For example, if you are running console commands unattended, such
-as from cron jobs or deployment scripts, it may be easier to use Symfony's
-logging capabilities instead of configuring other tools to gather console
-output and process it. This can be especially handful if you already have
-some existing setup for aggregating and analyzing Symfony logs.
-
-There are basically two logging cases you would need:
-
-* Manually logging some information from your command;
-* Logging uncaught exceptions.
-
-Manually Logging from a Console Command
----------------------------------------
-
-This one is really simple. When you create a console command within the full-stack
-framework as described in ":doc:`/console`", your command extends
-:class:`Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerAwareCommand`.
-This means that you can simply access the standard logger service through the
-container and use it to do the logging::
-
- // src/AppBundle/Command/GreetCommand.php
- namespace AppBundle\Command;
-
- use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
- use Symfony\Component\Console\Input\InputArgument;
- use Symfony\Component\Console\Input\InputInterface;
- use Symfony\Component\Console\Input\InputOption;
- use Symfony\Component\Console\Output\OutputInterface;
- use Psr\Log\LoggerInterface;
-
- class GreetCommand extends ContainerAwareCommand
- {
- // ...
-
- protected function execute(InputInterface $input, OutputInterface $output)
- {
- /** @var $logger LoggerInterface */
- $logger = $this->getContainer()->get('logger');
-
- $name = $input->getArgument('name');
- if ($name) {
- $text = 'Hello '.$name;
- } else {
- $text = 'Hello';
- }
-
- if ($input->getOption('yell')) {
- $text = strtoupper($text);
- $logger->warning('Yelled: '.$text);
- } else {
- $logger->info('Greeted: '.$text);
- }
-
- $output->writeln($text);
- }
- }
-
-Depending on the environment in which you run your command (and your logging
-setup), you should see the logged entries in ``app/logs/dev.log`` or ``app/logs/prod.log``.
-
-Enabling automatic Exceptions Logging
--------------------------------------
-
-To get your console application to automatically log uncaught exceptions for
-all of your commands, you can use :doc:`console events`.
-
-.. versionadded:: 2.3
- Console events were introduced in Symfony 2.3.
-
-First configure a listener for console exception events in the service container:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/services.yml
- services:
- app.listener.command_exception:
- class: AppBundle\EventListener\ConsoleExceptionListener
- arguments: ['@logger']
- tags:
- - { name: kernel.event_listener, event: console.exception }
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/services.php
- use AppBundle\EventListener\ConsoleExceptionListener;
- use Symfony\Component\DependencyInjection\Reference;
-
- $container->register('app.listener.command_exception', ConsoleExceptionListener::class)
- ->addArgument(new Reference('logger'))
- ->addTag(
- 'kernel.event_listener',
- array('event' => 'console.exception')
- );
-
-Then implement the actual listener::
-
- // src/AppBundle/EventListener/ConsoleExceptionListener.php
- namespace AppBundle\EventListener;
-
- use Symfony\Component\Console\Event\ConsoleExceptionEvent;
- use Psr\Log\LoggerInterface;
-
- class ConsoleExceptionListener
- {
- private $logger;
-
- public function __construct(LoggerInterface $logger)
- {
- $this->logger = $logger;
- }
-
- public function onConsoleException(ConsoleExceptionEvent $event)
- {
- $command = $event->getCommand();
- $exception = $event->getException();
-
- $message = sprintf(
- '%s: %s (uncaught exception) at %s line %s while running console command `%s`',
- get_class($exception),
- $exception->getMessage(),
- $exception->getFile(),
- $exception->getLine(),
- $command->getName()
- );
-
- $this->logger->error($message, array('exception' => $exception));
- }
- }
-
-In the code above, when any command throws an exception, the listener will
-receive an event. You can simply log it by passing the logger service via the
-service configuration. Your method receives a
-:class:`Symfony\\Component\\Console\\Event\\ConsoleExceptionEvent` object,
-which has methods to get information about the event and the exception.
-
-.. _logging-non-0-exit-statuses:
-
-Logging Error Exit Statuses
----------------------------
-
-The logging capabilities of the console can be further extended by logging
-commands that return error exit statuses, which are any number different than
-zero. This way you will know if a command had any errors, even if no exceptions
-were thrown.
-
-First configure a listener for console terminate events in the service container:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/services.yml
- services:
- app.listener.command_error:
- class: AppBundle\EventListener\ErrorLoggerListener
- arguments: ['@logger']
- tags:
- - { name: kernel.event_listener, event: console.terminate }
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/services.php
- use AppBundle\EventListener\ErrorLoggerListener;
- use Symfony\Component\DependencyInjection\Reference;
-
- $container->register('app.listener.command_error', ErrorLoggerListener::class)
- ->addArgument(new Reference('logger'))
- ->addTag(
- 'kernel.event_listener',
- array('event' => 'console.terminate')
- );
-
-Then implement the actual listener::
-
- // src/AppBundle/EventListener/ErrorLoggerListener.php
- namespace AppBundle\EventListener;
-
- use Symfony\Component\Console\Event\ConsoleTerminateEvent;
- use Psr\Log\LoggerInterface;
-
- class ErrorLoggerListener
- {
- private $logger;
-
- public function __construct(LoggerInterface $logger)
- {
- $this->logger = $logger;
- }
-
- public function onConsoleTerminate(ConsoleTerminateEvent $event)
- {
- $statusCode = $event->getExitCode();
- $command = $event->getCommand();
-
- if ($statusCode === 0) {
- return;
- }
-
- if ($statusCode > 255) {
- $statusCode = 255;
- $event->setExitCode($statusCode);
- }
-
- $this->logger->warning(sprintf(
- 'Command `%s` exited with status code %d',
- $command->getName(),
- $statusCode
- ));
- }
- }
diff --git a/console/request_context.rst b/console/request_context.rst
index 5205277fd7f..130714d4d47 100644
--- a/console/request_context.rst
+++ b/console/request_context.rst
@@ -21,8 +21,8 @@ Configuring the Request Context Globally
To configure the Request Context - which is used by the URL Generator - you can
redefine the parameters it uses as default values to change the default host
-(localhost) and scheme (http). You can also configure the base path if Symfony
-is not running in the root directory.
+(localhost) and scheme (http). You can also configure the base path (both for
+the URL generator and the assets) if Symfony is not running in the root directory.
Note that this does not impact URLs generated via normal web requests, since those
will override the defaults.
@@ -36,6 +36,8 @@ will override the defaults.
router.request_context.host: 'example.org'
router.request_context.scheme: 'https'
router.request_context.base_url: 'my/path'
+ asset.request_context.base_path: '%router.request_context.base_url%'
+ asset.request_context.secure: true
.. code-block:: xml
@@ -48,6 +50,8 @@ will override the defaults.
example.orghttpsmy/path
+ %router.request_context.base_url%
+ true
@@ -58,11 +62,17 @@ will override the defaults.
$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);
+
+.. versionadded:: 3.4
+
+ The ``asset.request_context.*`` parameters were introduced in Symfony 3.4.
Configuring the Request Context per Command
-------------------------------------------
-To change it only in one command you can simply fetch the Request Context
+To change it only in one command you need to fetch the Request Context
from the ``router`` service and override its settings::
// src/AppBundle/Command/DemoCommand.php
@@ -78,7 +88,7 @@ from the ``router`` service and override its settings::
$context->setScheme('https');
$context->setBaseUrl('my/path');
- $url = $router->generate('route-name', array('param-name' => 'param-value'));
+ $url = $router->generate('route-name', ['param-name' => 'param-value']);
// ...
}
}
diff --git a/console/style.rst b/console/style.rst
index 2b36e3862b9..466f20ac744 100644
--- a/console/style.rst
+++ b/console/style.rst
@@ -4,9 +4,6 @@
How to Style a Console Command
==============================
-.. versionadded:: 2.7
- Symfony Styles for console commands were introduced in Symfony 2.7.
-
One of the most boring tasks when creating console commands is to deal with the
styling of the command's input and output. Displaying titles and tables or asking
questions to the user involves a lot of repetitive code.
@@ -26,11 +23,11 @@ Consider for example the code used to display the title of the following command
protected function execute(InputInterface $input, OutputInterface $output)
{
- $output->writeln(array(
+ $output->writeln([
'Lorem Ipsum Dolor Sit Amet>',
'==========================>',
'',
- ));
+ ]);
// ...
}
@@ -57,9 +54,9 @@ title of the command::
namespace AppBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
- use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
+ use Symfony\Component\Console\Style\SymfonyStyle;
class GreetCommand extends ContainerAwareCommand
{
@@ -116,31 +113,31 @@ Content Methods
// ...
// consider using arrays when displaying long messages
- $io->text(array(
+ $io->text([
'Lorem ipsum dolor sit amet',
'Consectetur adipiscing elit',
'Aenean sit amet arcu vitae sem faucibus porta',
- ));
+ ]);
:method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::listing`
It displays an unordered list of elements passed as an array::
- $io->listing(array(
+ $io->listing([
'Element #1 Lorem ipsum dolor sit amet',
'Element #2 Lorem ipsum dolor sit amet',
'Element #3 Lorem ipsum dolor sit amet',
- ));
+ ]);
:method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::table`
It displays the given array of headers and rows as a compact table::
$io->table(
- array('Header 1', 'Header 2'),
- array(
- array('Cell 1-1', 'Cell 1-2'),
- array('Cell 2-1', 'Cell 2-2'),
- array('Cell 3-1', 'Cell 3-2'),
- )
+ ['Header 1', 'Header 2'],
+ [
+ ['Cell 1-1', 'Cell 1-2'],
+ ['Cell 2-1', 'Cell 2-2'],
+ ['Cell 3-1', 'Cell 3-2'],
+ ]
);
:method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::newLine`
@@ -168,11 +165,11 @@ Admonition Methods
// ...
// consider using arrays when displaying long notes
- $io->note(array(
+ $io->note([
'Lorem ipsum dolor sit amet',
'Consectetur adipiscing elit',
'Aenean sit amet arcu vitae sem faucibus porta',
- ));
+ ]);
:method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::caution`
Similar to the ``note()`` helper, but the contents are more prominently
@@ -185,11 +182,11 @@ Admonition Methods
// ...
// consider using arrays when displaying long caution messages
- $io->caution(array(
+ $io->caution([
'Lorem ipsum dolor sit amet',
'Consectetur adipiscing elit',
'Aenean sit amet arcu vitae sem faucibus porta',
- ));
+ ]);
Progress Bar Methods
~~~~~~~~~~~~~~~~~~~~
@@ -229,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');
@@ -251,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.');
@@ -265,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);
@@ -274,12 +273,12 @@ User Input Methods
It asks a question whose answer is constrained to the given list of valid
answers::
- $io->choice('Select the queue to analyze', array('queue1', 'queue2', 'queue3'));
+ $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', array('queue1', 'queue2', 'queue3'), 'queue1');
+ $io->choice('Select the queue to analyze', ['queue1', 'queue2', 'queue3'], 'queue1');
Result Methods
~~~~~~~~~~~~~~
@@ -296,10 +295,10 @@ Result Methods
// ...
// consider using arrays when displaying long success messages
- $io->success(array(
+ $io->success([
'Lorem ipsum dolor sit amet',
'Consectetur adipiscing elit',
- ));
+ ]);
:method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::warning`
It displays the given string or array of strings highlighted as a warning
@@ -313,10 +312,10 @@ Result Methods
// ...
// consider using arrays when displaying long warning messages
- $io->warning(array(
+ $io->warning([
'Lorem ipsum dolor sit amet',
'Consectetur adipiscing elit',
- ));
+ ]);
:method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::error`
It displays the given string or array of strings highlighted as an error
@@ -330,10 +329,10 @@ Result Methods
// ...
// consider using arrays when displaying long error messages
- $io->error(array(
+ $io->error([
'Lorem ipsum dolor sit amet',
'Consectetur adipiscing elit',
- ));
+ ]);
Defining your Own Styles
------------------------
@@ -355,6 +354,7 @@ 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/AppBundle/Command/GreetCommand.php
namespace AppBundle\Console;
use AppBundle\Console\CustomStyle;
@@ -368,10 +368,43 @@ of your commands to change their appearance::
protected function execute(InputInterface $input, OutputInterface $output)
{
// Before
- // $io = new SymfonyStyle($input, $output);
+ $io = new SymfonyStyle($input, $output);
// After
$io = new CustomStyle($input, $output);
+
// ...
}
}
+
+Writing to the error output
+---------------------------
+
+If you reuse the output of a command as the input of other commands or dump it
+into a file for later reuse, you probably want to exclude progress bars, notes
+and other output that provides no real value.
+
+Commands can output information in two different streams: ``stdout`` (standard
+output) is the stream where the real contents should be output and ``stderr``
+(standard error) is the stream where the errors and the debugging messages
+should be output.
+
+The :class:`Symfony\\Component\\Console\\Style\\SymfonyStyle` class provides a
+convenient method called :method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::getErrorStyle`
+to switch between both streams. This method returns a new ``SymfonyStyle``
+instance which makes use of the error output::
+
+ $io = new SymfonyStyle($input, $output);
+
+ // Write to the standard output
+ $io->write('Reusable information');
+
+ // Write to the error output
+ $io->getErrorStyle()->warning('Debugging information or errors');
+
+.. note::
+
+ If you create a ``SymfonyStyle`` instance with an ``OutputInterface`` object
+ that is not an instance of :class:`Symfony\\Component\\Console\\Output\\ConsoleOutputInterface`,
+ the ``getErrorStyle()`` method will have no effect and the returned object
+ will still write to the standard output instead of the error output.
diff --git a/console/usage.rst b/console/usage.rst
index ca2ed6f9237..21cdc42751a 100644
--- a/console/usage.rst
+++ b/console/usage.rst
@@ -8,22 +8,20 @@ The :doc:`/components/console/usage` page of the components documentation looks
at the global console options. When you use the console as part of the full-stack
framework, some additional global options are available as well.
-By default, console commands run in the ``dev`` environment and you may want
-to change this for some commands. For example, you may want to run some commands
-in the ``prod`` environment for performance reasons. Also, the result of some commands
-will be different depending on the environment. For example, the ``cache:clear``
-command will clear and warm the cache for the specified environment only. To
-clear and warm the ``prod`` cache you need to run:
+By default, console commands run in the ``dev`` environment and you may want to
+change this for some commands. For example, you may want to run some commands in
+the ``prod`` environment for performance reasons. Also, the result of some
+commands will be different depending on the environment. For example, the
+``cache:clear`` command will clear and warm up the cache for the specified
+environment only:
.. code-block:: terminal
- $ php app/console cache:clear --env=prod
+ # clear (and warm up) the cache of the 'prod' environment
+ $ php bin/console cache:clear --env=prod
-or the equivalent:
-
-.. code-block:: terminal
-
- $ php app/console cache:clear -e prod
+ # this is equivalent:
+ $ php bin/console cache:clear --no-warmup -e prod
In addition to changing the environment, you can also choose to disable debug mode.
This can be useful where you want to run commands in the ``dev`` environment
@@ -31,4 +29,4 @@ but avoid the performance hit of collecting debug data:
.. code-block:: terminal
- $ php app/console list --no-debug
+ $ php bin/console list --no-debug
diff --git a/console/verbosity.rst b/console/verbosity.rst
index c11f340fa4c..c16737c2b61 100644
--- a/console/verbosity.rst
+++ b/console/verbosity.rst
@@ -1,27 +1,50 @@
Verbosity Levels
================
-.. versionadded:: 2.3
- The ``VERBOSITY_VERY_VERBOSE`` and ``VERBOSITY_DEBUG`` constants were introduced
- in version 2.3
-
-The console has five verbosity levels. These are defined in the
-:class:`Symfony\\Component\\Console\\Output\\OutputInterface`:
-
-=========================================== ================================== =====================
-Value Meaning Console option
-=========================================== ================================== =====================
-``OutputInterface::VERBOSITY_QUIET`` Do not output any messages ``-q`` or ``--quiet``
-``OutputInterface::VERBOSITY_NORMAL`` The default verbosity level (none)
-``OutputInterface::VERBOSITY_VERBOSE`` Increased verbosity of messages ``-v``
-``OutputInterface::VERBOSITY_VERY_VERBOSE`` Informative non essential messages ``-vv``
-``OutputInterface::VERBOSITY_DEBUG`` Debug messages ``-vvv``
-=========================================== ================================== =====================
+Console commands have different verbosity levels, which determine the messages
+displayed in their output. By default, commands display only the most useful
+messages, but you can control their verbosity with the ``-q`` and ``-v`` options:
+
+.. code-block:: terminal
+
+ # do not output any message (not even the command result messages)
+ $ php bin/console some-command -q
+ $ php bin/console some-command --quiet
+
+ # normal behavior, no option required (display only the useful messages)
+ $ php bin/console some-command
+
+ # increase verbosity of messages
+ $ php bin/console some-command -v
+
+ # display also the informative non essential messages
+ $ php bin/console some-command -vv
+
+ # display all messages (useful to debug errors)
+ $ php bin/console some-command -vvv
+
+The verbosity level can also be controlled globally for all commands with the
+``SHELL_VERBOSITY`` environment variable (the ``-q`` and ``-v`` options still
+have more precedence over the value of ``SHELL_VERBOSITY``):
+
+===================== ========================= ===========================================
+Console option ``SHELL_VERBOSITY`` value Equivalent PHP constant
+===================== ========================= ===========================================
+``-q`` or ``--quiet`` ``-1`` ``OutputInterface::VERBOSITY_QUIET``
+(none) ``0`` ``OutputInterface::VERBOSITY_NORMAL``
+``-v`` ``1`` ``OutputInterface::VERBOSITY_VERBOSE``
+``-vv`` ``2`` ``OutputInterface::VERBOSITY_VERY_VERBOSE``
+``-vvv`` ``3`` ``OutputInterface::VERBOSITY_DEBUG``
+===================== ========================= ===========================================
It is possible to print a message in a command for only a specific verbosity
level. For example::
// ...
+ use Symfony\Component\Console\Command\Command;
+ use Symfony\Component\Console\Input\InputInterface;
+ use Symfony\Component\Console\Output\OutputInterface;
+
class CreateUserCommand extends Command
{
// ...
@@ -30,17 +53,17 @@ level. For example::
{
$user = new User(...);
- $output->writeln(array(
+ $output->writeln([
'Username: '.$input->getArgument('username'),
'Password: '.$input->getArgument('password'),
- ));
+ ]);
- // the user class is only printed when the verbose verbosity level is used
- if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
+ // available methods: ->isQuiet(), ->isVerbose(), ->isVeryVerbose(), ->isDebug()
+ if ($output->isVerbose()) {
$output->writeln('User class: '.get_class($user));
}
- // alternatively you can pass the verbosity level to writeln()
+ // alternatively you can pass the verbosity level PHP constant to writeln()
$output->writeln(
'Will only be printed in verbose mode or higher',
OutputInterface::VERBOSITY_VERBOSE
@@ -48,36 +71,6 @@ level. For example::
}
}
-.. versionadded:: 2.8
- The ability to pass the verbosity level to the ``writeln()`` method was
- introduced in Symfony 2.8.
-
-There are also more semantic methods you can use to test for each of the
-verbosity levels::
-
- if ($output->isQuiet()) {
- // ...
- }
-
- if ($output->isVerbose()) {
- // ...
- }
-
- if ($output->isVeryVerbose()) {
- // ...
- }
-
- if ($output->isDebug()) {
- // ...
- }
-
-.. note::
-
- These semantic methods are defined in the ``OutputInterface`` starting from
- Symfony 3.0. In previous Symfony versions they are defined in the different
- implementations of the interface (e.g. :class:`Symfony\\Component\\Console\\Output\\Output`)
- in order to keep backward compatibility.
-
When the quiet level is used, all output is suppressed as the default
:method:`Symfony\\Component\\Console\\Output\\Output::write` method returns
without actually printing.
diff --git a/contributing/code/bc.rst b/contributing/code/bc.rst
index 9f72bccf2ee..52dd1499b8c 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 can be fixed changing the name of a single 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
@@ -32,6 +32,15 @@ The second section, "Working on Symfony Code", is targeted at Symfony
contributors. This section lists detailed rules that every contributor needs to
follow to ensure smooth upgrades for our users.
+.. caution::
+
+ :doc:`Experimental Features ` and code
+ marked with the ``@internal`` tags are excluded from our Backward
+ Compatibility promise.
+
+ Also note that backward compatibility breaks are tolerated if they are
+ required to fix a security issue.
+
Using Symfony Code
------------------
@@ -85,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.
@@ -195,7 +204,7 @@ Change name No
Move to parent interface Yes
Add argument without a default value No
Add argument with a default value No
-Remove argument Yes [3]_
+Remove argument No [3]_
Add default value to an argument No
Remove default value of an argument No
Add type hint to an argument No
@@ -263,7 +272,7 @@ Make final No [6]_
Move to parent class Yes
Add argument without a default value No
Add argument with a default value No [7]_ [8]_
-Remove argument Yes [3]_
+Remove argument No [3]_
Add default value to an argument No [7]_ [8]_
Remove default value of an argument No
Add type hint to an argument No [7]_ [8]_
@@ -282,7 +291,7 @@ Make public No [7]_ [8]_
Move to parent class Yes
Add argument without a default value No [7]_
Add argument with a default value No [7]_ [8]_
-Remove argument Yes [3]_
+Remove argument No [3]_
Add default value to an argument No [7]_ [8]_
Remove default value of an argument No [7]_
Add type hint to an argument No [7]_ [8]_
@@ -407,8 +416,8 @@ Turn static into non static No
.. [2] The added parent interface must not introduce any new methods that don't
exist in the interface already.
-.. [3] Only the last argument(s) of a method may be removed, as PHP does not
- care about additional arguments that you pass to a method.
+.. [3] Only the last optional argument(s) of a method may be removed, as PHP
+ does not care about additional arguments that you pass to a method.
.. [4] When changing the parent class, the original parent class must remain an
ancestor of the class.
@@ -436,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..440b2dac519 100644
--- a/contributing/code/bugs.rst
+++ b/contributing/code/bugs.rst
@@ -15,7 +15,7 @@ Before submitting a bug:
framework;
* Ask for assistance on `Stack Overflow`_, on the #support channel of
- `the Symfony Slack`_ or on the #symfony `IRC channel`_ if you're not sure if
+ `the Symfony Slack`_ or on the ``#symfony`` `IRC channel`_ if you're not sure if
your issue really is a bug.
If your problem definitely looks like a bug, report it using the official bug
@@ -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..05dff1ebf61 100644
--- a/contributing/code/conventions.rst
+++ b/contributing/code/conventions.rst
@@ -72,15 +72,15 @@ must be used instead (where ``XXX`` is the name of the related thing):
.. note::
- While "setXXX" and "replaceXXX" are very similar, there is one notable
- difference: "setXXX" may replace, or add new elements to the relation.
- "replaceXXX", on the other hand, cannot add new elements. If an unrecognized
- key is passed to "replaceXXX" it must throw an exception.
+ While ``setXXX()`` and ``replaceXXX()`` are very similar, there is one notable
+ difference: ``setXXX()`` may replace, or add new elements to the relation.
+ ``replaceXXX()``, on the other hand, cannot add new elements. If an unrecognized
+ key is passed to ``replaceXXX()`` it must throw an exception.
.. _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
@@ -88,22 +88,38 @@ because of backward compatibility issues, but we still want to propose a
"better" alternative. In that case, the old implementation can simply be
**deprecated**.
-A feature is marked as deprecated by adding a ``@deprecated`` phpdoc to
+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
@@ -113,20 +129,59 @@ the Web Debug Toolbar or by the PHPUnit bridge).
.. _`@-silencing operator`: https://php.net/manual/en/language.operators.errorcontrol.php
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`_::
+after the use declarations, like in this example from
+`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 0ca248fa0be..47fba9c2d94 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,61 +49,40 @@ 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_ and the Serializer_
- component;
-
- * **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.
+ * **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`_);
+ * **Tobias Nyholm** (`Nyholm`_);
+ * **Wouter De Jong** (`wouterj`_);
+ * **Alexander M. Turek** (`derrabus`_);
+ * **Jérémy Derussé** (`jderusse`_).
-* **Deciders Team** (``@symfony/deciders`` on GitHub):
+* **Security Team** (``@symfony/security`` on GitHub):
- * **Jordi Boggiano** (`seldaek`_);
- * **Lukas Kahwe Smith** (`lsmith77`_).
+ * **Fabien Potencier** (`fabpot`_);
+ * **Michael Cullum** (`michaelcullum`_);
+ * **Jérémy Derussé** (`jderusse`_).
-* **Security Team** (``@symfony/security`` on GitHub):
+* **Recipes Team**:
* **Fabien Potencier** (`fabpot`_);
- * **Michael Cullum** (`michaelcullum`_).
+ * **Tobias Nyholm** (`Nyholm`_).
* **Documentation Team** (``@symfony/team-symfony-docs`` on GitHub):
@@ -114,6 +92,7 @@ Active Core Members
* **Wouter De Jong** (`wouterj`_);
* **Jules Pietri** (`HeahDude`_);
* **Javier Eguiluz** (`javiereguiluz`_).
+ * **Oskar Stark** (`OskarStark`_).
Former Core Members
~~~~~~~~~~~~~~~~~~~
@@ -123,7 +102,9 @@ Symfony contributions:
* **Bernhard Schussek** (`webmozart`_);
* **Abdellatif AitBoudad** (`aitboudad`_);
-* **Romain Neutron**.
+* **Romain Neutron** (`romainneutron`_);
+* **Jordi Boggiano** (`Seldaek`_);
+* **Lukas Kahwe Smith** (`lsmith77`_).
Core Membership Application
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -170,11 +151,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,53 +180,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
.. _`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/
@@ -255,6 +198,12 @@ 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
+.. _`romainneutron`: https://github.com/romainneutron
+.. _`lsmith77`: https://github.com/lsmith77/
+.. _`derrabus`: https://github.com/derrabus/
+.. _`jderusse`: https://github.com/jderusse/
diff --git a/contributing/code/experimental.rst b/contributing/code/experimental.rst
new file mode 100644
index 00000000000..9081cf5184c
--- /dev/null
+++ b/contributing/code/experimental.rst
@@ -0,0 +1,23 @@
+Experimental Features
+=====================
+
+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
+the API, or remove it altogether. Doing so is not possible with a no BC-break
+approach.
+
+To avoid being bound to our backward compatibility promise, such features can
+be marked as **experimental** and their classes and methods must be marked with
+the ``@experimental`` tag.
+
+A feature can be marked as being experimental for only one minor version, and
+can never be introduced in an :ref:`LTS version `. The core team
+can decide to extend the experimental period for another minor version on a
+case by case basis.
+
+To ease upgrading projects using experimental features, the changelog must
+explain backward incompatible changes and explain how to upgrade code.
diff --git a/contributing/code/git.rst b/contributing/code/git.rst
index ce0642c5599..59ca5a4a24d 100644
--- a/contributing/code/git.rst
+++ b/contributing/code/git.rst
@@ -10,8 +10,8 @@ Pull Requests
Whenever a pull request is merged, all the information contained in the pull
request (including comments) is saved in the repository.
-You can easily spot pull request merges as the commit message always follows
-this pattern:
+You can spot pull request merges as the commit message always follows this
+pattern:
.. code-block:: text
diff --git a/contributing/code/index.rst b/contributing/code/index.rst
index 37eb3290c87..0bb29d6abc4 100644
--- a/contributing/code/index.rst
+++ b/contributing/code/index.rst
@@ -6,12 +6,13 @@ Contributing Code
bugs
reproducer
- patches
+ pull_requests
maintenance
core_team
security
tests
bc
+ experimental
standards
conventions
git
diff --git a/contributing/code/license.rst b/contributing/code/license.rst
index 916fbd637f4..8c7c2fd19db 100644
--- a/contributing/code/license.rst
+++ b/contributing/code/license.rst
@@ -5,7 +5,7 @@ Symfony Code License
Symfony code is released under `the MIT license`_:
-Copyright (c) 2004-2018 Fabien Potencier
+Copyright (c) 2004-2020 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/contributing/code/maintenance.rst b/contributing/code/maintenance.rst
index bc16a6cf805..854cd74b219 100644
--- a/contributing/code/maintenance.rst
+++ b/contributing/code/maintenance.rst
@@ -13,7 +13,7 @@ acceptable changes.
.. note::
- When documentation (or phpdoc) is not in sync with the code, code behavior
+ When documentation (or PHPDoc) is not in sync with the code, code behavior
should always be considered as being the correct one.
Besides bug fixes, other minor changes can be accepted in a patch version:
@@ -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 64%
rename from contributing/code/patches.rst
rename to contributing/code/pull_requests.rst
index 1be2547a16e..816130b1168 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
@@ -14,7 +25,7 @@ Before working on Symfony, setup a friendly environment with the following
software:
* Git;
-* PHP version 5.3.9 or above.
+* PHP version 5.5.9 or above.
Configure Git
~~~~~~~~~~~~~
@@ -90,58 +101,71 @@ 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:
-* ``2.8``, if you are fixing a bug for an existing feature or want to make a
+* ``3.4``, if you are fixing a bug for an existing feature or want to make a
change that falls into the :doc:`list of acceptable changes in patch versions
` (you may have to choose a higher branch if
the feature you are fixing was introduced in a later version);
- * ``master``, if you are adding a new feature.
+* ``5.x``, 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 ``2.8`` branch, the patch will also be applied by the core team on
- the ``master`` branch.
+ 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 ``5.x`` 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
+ $ git checkout -b BRANCH_NAME 5.x
-Or, if you want to provide a bugfix for the ``2.8`` branch, first track the remote
-``2.8`` branch locally:
+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/2.8
+ $ git checkout --track origin/3.4
-Then create a new branch off the ``2.8`` 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
- $ git checkout -b BRANCH_NAME 2.8
+ $ git checkout -b BRANCH_NAME 3.4
.. tip::
@@ -167,8 +191,18 @@ 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
-~~~~~~~~~~~~~~~~~~
+.. tip::
+
+ If symlinks to your local Symfony fork cannot be resolved inside your project due to
+ your dev environment (for instance when using Vagrant where only the current project
+ directory is mounted), you can alternatively use the ``--copy`` option.
+ When finishing testing your Symfony code into your project, you can use
+ the ``--rollback`` option to make your project back to its original dependencies.
+
+.. _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 +215,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 +233,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 +244,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:
-When your patch is not about a bug fix (when you add a new feature or change
+Prepare your Pull Request for Submission
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+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,30 +259,34 @@ 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:
+
+Rebase your Pull Request
+~~~~~~~~~~~~~~~~~~~~~~~~
-Before submitting your patch, update your branch (needed if it takes you a
+Before submitting your PR, update your branch (needed if it takes you a
while to finish your changes):
.. code-block:: terminal
- $ git checkout master
+ $ git checkout 5.x
$ git fetch upstream
- $ git merge upstream/master
+ $ git merge upstream/5.x
$ git checkout BRANCH_NAME
- $ git rebase master
+ $ git rebase 5.x
.. tip::
- Replace ``master`` with the branch you selected previously (e.g. ``2.8``)
- if you are working on a bugfix
+ Replace ``5.x`` with the branch you selected previously (e.g. ``3.4``)
+ 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,
@@ -272,8 +312,8 @@ You can now make a pull request on the ``symfony/symfony`` GitHub repository.
.. tip::
- Take care to point your pull request towards ``symfony:2.8`` if you want
- the core team to pull a bugfix based on the ``2.8`` branch.
+ Take care to point your pull request towards ``symfony:3.4`` if you want
+ the core team to pull a bug fix based on the ``3.4`` branch.
To ease the core team work, always include the modified components in your
pull request message, like in:
@@ -296,10 +336,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 +366,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,23 +380,41 @@ 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
-``upstream/2.8``, don't merge; and force the push to the origin:
+PR. Before re-submitting the PR, rebase with ``upstream/5.x`` or
+``upstream/3.4``, don't merge; and force the push to the origin:
.. code-block:: terminal
- $ git rebase -f upstream/master
+ $ git rebase -f upstream/5.x
$ git push --force origin BRANCH_NAME
.. note::
When doing a ``push --force``, always specify the branch name explicitly
- to avoid messing other branches in the repo (``--force`` tells Git that
- you really want to mess with things so do it carefully).
+ to avoid messing other branches in the repository (``--force`` tells Git
+ that you really want to mess with things so do it carefully).
Moderators earlier asked you to "squash" your commits. This means you will
convert many commits to one commit. This is no longer necessary today, because
@@ -364,13 +423,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 26049b094c9..f92cf460ea6 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
-----------------------
@@ -37,7 +37,7 @@ a PHP script, it's better to reproduce the bug by forking the Symfony Standard
edition. To do so:
#. Go to https://github.com/symfony/symfony-standard and click on the **Fork**
- button to make a fork of that repository or go to your already forked copy.
+ button to make a fork of that repository or go to your already-forked copy.
#. Clone the forked repository into your computer:
``git clone git://github.com/YOUR-GITHUB-USERNAME/symfony-standard.git``
#. Browse the project and create a new branch (e.g. ``issue_23567``,
diff --git a/contributing/code/security.rst b/contributing/code/security.rst
index e1c95f4f171..557e4c13501 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
--------------------------
@@ -19,7 +19,7 @@ Resolving Process
For each report, we first try to confirm the vulnerability. When it is
confirmed, the core team works on a solution following these steps:
-#. Send an acknowledgement to the reporter;
+#. Send an acknowledgment to the reporter;
#. Work on a patch;
#. Get a CVE identifier from `mitre.org`_;
#. Write a security announcement for the official Symfony `blog`_ about the
@@ -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
+ :doc:`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
@@ -91,21 +92,92 @@ of the downstream projects included in this process:
* Drupal (releases typically happen on Wednesdays)
* eZPublish
+Issue Severity
+--------------
+
+In order to determine the severity of a security issue we take into account
+the complexity of any potential attack, the impact of the vulnerability and
+also how many projects it is likely to affect. This score out of 15 is then
+converted into a level of: Low, Medium, High, Critical, or Exceptional.
+
+Attack Complexity
+~~~~~~~~~~~~~~~~~
+
+*Score of between 1 and 5 depending on how complex it is to exploit the
+vulnerability*
+
+* 4 - 5 Basic: attacker must follow a set of simple steps
+* 2 - 3 Complex: attacker must follow non-intuitive steps with a high level
+ of dependencies
+* 1 - 2 High: A successful attack depends on conditions beyond the attacker's
+ control. That is, a successful attack cannot be accomplished at will, but
+ requires the attacker to invest in some measurable amount of effort in
+ preparation or execution against the vulnerable component before a successful
+ attack can be expected.
+
+Impact
+~~~~~~
+
+*Scores from the following areas are added together to produce a score. The
+score for Impact is capped at 6. Each area is scored between 0 and 4.*
+
+* Integrity: Does this vulnerability cause non-public data to be accessible?
+ If so, does the attacker have control over the data disclosed? (0-4)
+* Disclosure: Can this exploit allow system data (or data handled by the
+ system) to be compromised? If so, does the attacker have control over
+ modification? (0-4)
+* Code Execution: Does the vulnerability allow arbitrary code to be executed
+ on an end-users system, or the server that it runs on? (0-4)
+* Availability: Is the availability of a service or application affected? Is
+ it reduced availability or total loss of availability of a service /
+ application? Availability includes networked services (e.g., databases) or
+ resources such as consumption of network bandwidth, processor cycles, or
+ disk space. (0-4)
+
+Affected Projects
+~~~~~~~~~~~~~~~~~
+
+*Scores from the following areas are added together to produce a score. The
+score for Affected Projects is capped at 4.*
+
+* Will it affect some or all using a component? (1-2)
+* Is the usage of the component that would cause such a thing already
+ considered bad practice? (0-1)
+* How common/popular is the component (e.g. Console vs HttpFoundation vs
+ Lock)? (0-2)
+* Are a number of well-known open source projects using Symfony affected
+ that requires coordinated releases? (0-1)
+
+Score Totals
+~~~~~~~~~~~~
+
+* Attack Complexity: 1 - 5
+* Impact: 1 - 6
+* Affected Projects: 1 - 4
+
+Severity levels
+~~~~~~~~~~~~~~~
+
+* Low: 1 - 5
+* Medium: 6 - 10
+* High: 11 - 12
+* Critical: 13 - 14
+* Exceptional: 15
+
Security Advisories
-------------------
.. tip::
You can check your Symfony application for known security vulnerabilities
- using the ``security:check`` command (see :doc:`/security/security_checker`).
+ using the ``check:security`` command (see :doc:`/security/security_checker`).
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 329d6d83b36..2cf52addf82 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
-
- fooBar = $this->transformText($dummy);
+ $this->qux = $qux;
}
/**
@@ -71,7 +72,7 @@ short example containing most features described below:
*/
public function someDeprecatedMethod()
{
- @trigger_error(sprintf('The %s() method is deprecated since version 2.8 and will be removed in 3.0. Use Acme\Baz::someMethod() instead.', __METHOD__), E_USER_DEPRECATED);
+ @trigger_error(sprintf('The %s() method is deprecated since vendor-name/package-name 2.8 and will be removed in 3.0. Use Acme\Baz::someMethod() instead.', __METHOD__), E_USER_DEPRECATED);
return Baz::someMethod();
}
@@ -86,16 +87,16 @@ short example containing most features described below:
*
* @throws \RuntimeException When an invalid option is provided
*/
- private function transformText($dummy, array $options = array())
+ private function transformText($dummy, array $options = [])
{
- $defaultOptions = array(
+ $defaultOptions = [
'some_default' => 'values',
'another_default' => 'more values',
- );
+ ];
- foreach ($options as $option) {
- if (!in_array($option, $defaultOptions)) {
- throw new \RuntimeException(sprintf('Unrecognized option "%s"', $option));
+ foreach ($options as $name => $value) {
+ if (!array_key_exists($name, $defaultOptions)) {
+ throw new \RuntimeException(sprintf('Unrecognized option "%s"', $name));
}
}
@@ -105,33 +106,34 @@ short example containing most features described below:
);
if (true === $dummy) {
- return null;
+ return 'something';
}
- if ('string' === $dummy) {
+ if (is_string($dummy)) {
if ('values' === $mergedOptions['some_default']) {
return substr($dummy, 0, 5);
}
return ucwords($dummy);
}
+
+ return null;
}
/**
- * Performs some basic check for a given value.
+ * Performs some basic operations for a given value.
*
- * @param mixed $value Some value to check against
+ * @param mixed $value Some value to operate against
* @param bool $theSwitch Some switch to control the method's flow
- *
- * @return bool|void The resultant check if $theSwitch isn't false, void otherwise
*/
- private function reverseBoolean($value = null, $theSwitch = false)
+ private function performOperations($value = null, $theSwitch = false)
{
if (!$theSwitch) {
return;
}
- return !$value;
+ $this->qux->doFoo($value);
+ $this->qux->doBar($value);
}
}
@@ -213,7 +215,7 @@ Naming Conventions
* Prefix all abstract classes with ``Abstract`` except PHPUnit ``*TestCase``.
Please note some early Symfony classes do not follow this convention and
- have not been renamed for backward compatibility reasons. However all new
+ have not been renamed for backward compatibility reasons. However, all new
abstract classes must follow this naming convention;
* Suffix interfaces with ``Interface``;
@@ -238,13 +240,19 @@ Naming Conventions
Service Naming Conventions
~~~~~~~~~~~~~~~~~~~~~~~~~~
-* A service name contains groups, separated by dots;
+* A service name must be the same as the fully qualified class name (FQCN) of
+ its class (e.g. ``App\EventSubscriber\UserSubscriber``);
-* The DI alias of the bundle is the first group (e.g. ``fos_user``);
+* If there are multiple services for the same class, use the FQCN for the main
+ service and use lowercase and underscored names for the rest of services.
+ Optionally divide them in groups separated with dots (e.g.
+ ``something.service_name``, ``fos_user.something.service_name``);
-* Use lowercase letters for service and parameter names;
+* Use lowercase letters for parameter names (except when referring
+ to environment variables with the ``%env(VARIABLE_NAME)%`` syntax);
-* A group name uses the underscore notation.
+* Add class aliases for public services (e.g. alias ``Symfony\Component\Something\ClassName``
+ to ``something.service_name``).
Documentation
~~~~~~~~~~~~~
@@ -266,7 +274,7 @@ Documentation
* When adding a new class or when making significant changes to an existing class,
an ``@author`` tag with personal contact information may be added, or expanded.
Please note it is possible to have the personal contact information updated or
- removed per request to the doc:`core team `.
+ removed per request to the :doc:`core team `.
License
~~~~~~~
@@ -274,7 +282,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..ae342b89999 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**
@@ -29,7 +31,7 @@ also contact all of them at once by emailing **coc@symfony.com**:
* **Michelle Sanver**
- * *E-mail*: hello [at] michellesanver.com
+ * *E-mail*: michelle [at] liip.ch
* *Twitter*: `@michellesanver `_
* *SymfonyConnect*: `michellesanver `_
@@ -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/code_of_conduct.rst b/contributing/code_of_conduct/code_of_conduct.rst
index 7ecd91e8dbd..857d5437538 100644
--- a/contributing/code_of_conduct/code_of_conduct.rst
+++ b/contributing/code_of_conduct/code_of_conduct.rst
@@ -37,7 +37,7 @@ Examples of unacceptable behavior by participants include:
Our Responsibilities
--------------------
-:doc:`CoC Active Response Ensurers, or CARE`,
+:doc:`CoC Active Response Ensurers, or CARE `,
are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
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 2662af995ec..d2336e21e86 100644
--- a/contributing/community/releases.rst
+++ b/contributing/community/releases.rst
@@ -1,17 +1,25 @@
The Release Process
===================
-This document explains the **release process** of the Symfony project (i.e. the
-code hosted on the main ``symfony/symfony`` `Git repository`_).
+This document explains the process followed by the Symfony project to develop,
+release and maintain its different versions.
-Symfony manages its releases through a *time-based model* and follows the
-`Semantic Versioning`_ strategy:
+Symfony releases follow the `semantic versioning`_ strategy and they are
+published through a *time-based model*:
-* 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*;
-* A new Symfony major version (e.g., 3.0, 4.0) comes out every *two years* and
- it's released at the same time of the last minor version of the previous major
- version.
+* 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 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 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
+ applications before upgrading.
+
+.. tip::
+
+ `Subscribe to Symfony Roadmap notifications`_ to receive an email when a new
+ Symfony version is published or when a Symfony version reaches its end of life.
.. _contributing-release-development:
@@ -32,113 +40,36 @@ During the development phase, any new feature can be reverted if it won't be
finished in time or if it won't be stable enough to be included in the current
final release.
+.. tip::
+
+ Check out the `Symfony Roadmap`_ to learn more about any specific version.
+
.. _contributing-release-maintenance:
+.. _symfony-versions:
+.. _releases-lts:
Maintenance
-----------
-Each Symfony version is maintained for a fixed period of time, depending on the
-type of the release. This maintenance is divided into:
+Starting from the Symfony 3.x branch, the number of minor versions is limited to
+five per branch (X.0, X.1, X.2, X.3 and X.4). The last minor version of a branch
+(e.g. 3.4, 4.4, 5.4) is considered a **long-term support version** and the other
+ones are considered **standard versions**:
-* *Bug fixes and security fixes*: During this period, all issues can be fixed.
- The end of this period is referenced as being the *end of maintenance* of a
- release.
-
-* *Security fixes only*: During this period, only security related issues can
- be fixed. The end of this period is referenced as being the *end of life* of
- a release.
+======================= ===================== ================================
+Version Type Bugs are fixed for... Security issues are fixed for...
+======================= ===================== ================================
+Standard 8 months 14 months
+Long-Term Support (LTS) 3 years 4 years
+======================= ===================== ================================
.. note::
- The :doc:`maintenance document ` describes
- the boundaries of acceptable changes during maintenance.
-
-Symfony Versions
-----------------
-
-Standard Versions
-~~~~~~~~~~~~~~~~~
-
-A **Standard Minor Version** is maintained for an *eight month* period for bug
-fixes, and for a *fourteen month* period for security issue fixes.
-
-In Symfony 2.x branch, the number of minor versions wasn't constrained, so that
-branch ended up with nine minor versions (from 2.0 to 2.8). Starting from
-3.x branch, the number of minor versions is limited to five (from X.0 to X.4).
-
-.. _releases-lts:
-
-Long Term Support Versions
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Every two years, a new **Long Term Support Version** (usually abbreviated as "LTS")
-is published. Each LTS version is supported for a *three year* period for bug
-fixes, and for a *four year* period for security issue fixes.
-
-.. note::
+ After the active maintenance of a Symfony version has ended, you can get
+ `professional Symfony support`_ from SensioLabs, the company which sponsors
+ the Symfony project.
- Paid support after the three year support provided by the community can
- also be bought from `SensioLabs`_.
-
-In the Symfony 2.x branch, the LTS versions are 2.3, 2.7 and 2.8. Starting from the 3.x
-branch, only the last minor version of each branch is considered LTS (e.g. 3.4,
-4.4, 5.4, etc.)
-
-Schedule
---------
-
-Below is the schedule for the first few versions that use this release model:
-
-.. image:: /_images/contributing/release-process.jpg
- :align: center
-
-* **Yellow** represents the Development phase
-* **Blue** represents the Stabilization phase
-* **Green** represents the Maintenance period
-
-.. tip::
-
- If you want to learn more about the timeline of any given Symfony version,
- use the online `timeline calculator`_.
-
-.. tip::
-
- Whenever an important event related to Symfony versions happens (a version
- reaches end of maintenance or a new patch version is released for
- instance), you can automatically receive an email notification if you
- subscribed on the `roadmap notification`_ page.
-
-.. _version-history:
-
-============ ============== ======= ======================== ===========
-Version Feature Freeze Release End of Maintenance End of Life
-============ ============== ======= ======================== ===========
-`2.0`_ 05/2011 07/2011 03/2013 (20 months) 09/2013
-`2.1`_ 07/2012 09/2012 05/2013 (9 months) 11/2013
-`2.2`_ 01/2013 03/2013 11/2013 (8 months) 05/2014
-`2.3`_ (LTS) 03/2013 05/2013 05/2016 (36 months) 05/2017
-`2.4`_ 09/2013 11/2013 09/2014 (10 months [1]_) 01/2015
-`2.5`_ 03/2014 05/2014 01/2015 (8 months) 07/2015
-`2.6`_ 09/2014 11/2014 07/2015 (8 months) 01/2016
-`2.7`_ (LTS) 03/2015 05/2015 05/2018 (36 months) 05/2019
-`2.8`_ (LTS) 09/2015 11/2015 11/2018 (36 months [2]_) 11/2019
-`3.0`_ 09/2015 11/2015 07/2016 (8 months [3]_) 01/2017
-`3.1`_ 03/2016 05/2016 01/2017 (8 months) 07/2017
-`3.2`_ 09/2016 11/2016 07/2017 (8 months) 01/2018
-`3.3`_ 03/2017 05/2017 01/2018 (8 months) 07/2018
-`3.4`_ (LTS) 09/2017 11/2017 11/2020 (36 months) 11/2021
-`4.0`_ 09/2017 11/2017 07/2018 (8 months) 01/2019
-`4.1`_ 03/2018 05/2018 01/2019 (8 months) 07/2019
-`4.2`_ 09/2018 11/2018 07/2019 (8 months) 01/2020
-`4.3`_ 03/2019 05/2019 01/2020 (8 months) 07/2020
-`4.4`_ (LTS) 09/2019 11/2019 11/2022 (36 months) 11/2023
-`5.0`_ 09/2019 11/2019 07/2020 (8 months) 01/2021
-... ... ... ... ...
-============ ============== ======= ======================== ===========
-
-.. [1] Symfony 2.4 maintenance has been `extended to September 2014`_.
-.. [2] Symfony 2.8 is the last version of the Symfony 2.x branch.
-.. [3] Symfony 3.0 is the first version to use the new release process based on five minor releases.
+.. _deprecations:
Backward Compatibility
----------------------
@@ -147,18 +78,29 @@ Our :doc:`Backward Compatibility Promise ` is very
strict and allows developers to upgrade with confidence from one minor version
of Symfony to the next one.
-Whenever keeping backward compatibility is not possible, the feature, the
-enhancement or the bug fix will be scheduled for the next major version.
-
-Deprecations
-------------
-
When a feature implementation cannot be replaced with a better one without
-breaking backward compatibility, there is still the possibility to deprecate
-the old implementation and add a new preferred one along side. Read the
+breaking backward compatibility, Symfony deprecates the old implementation and
+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
---------
@@ -174,7 +116,9 @@ This release process was adopted to give more *predictability* and
* Coordinate the Symfony timeline with popular PHP projects that work well
with Symfony and with projects using Symfony;
* Give time to the Symfony ecosystem to catch up with the new versions
- (bundle authors, documentation writers, translators, ...).
+ (bundle authors, documentation writers, translators, ...);
+* Give companies a strict and predictable timeline they can rely on to plan
+ their own projects development.
The six month period was chosen as two releases fit in a year. It also allows
for plenty of time to work on new features and it allows for non-ready
@@ -187,29 +131,7 @@ version: a new version is published every six months, and there is a two months
period to upgrade. Companies wanting more stability use the LTS versions: a new
version is published every two years and there is a year to upgrade.
-.. _Semantic Versioning: https://semver.org/
-.. _Git repository: https://github.com/symfony/symfony
-.. _SensioLabs: https://sensiolabs.com/
-.. _roadmap notification: https://symfony.com/roadmap
-.. _extended to September 2014: https://symfony.com/blog/extended-maintenance-for-symfony-2-4
-.. _timeline calculator: https://symfony.com/roadmap#checker
-.. _`2.0`: https://symfony.com/roadmap?version=2.0
-.. _`2.1`: https://symfony.com/roadmap?version=2.1
-.. _`2.2`: https://symfony.com/roadmap?version=2.2
-.. _`2.3`: https://symfony.com/roadmap?version=2.3
-.. _`2.4`: https://symfony.com/roadmap?version=2.4
-.. _`2.5`: https://symfony.com/roadmap?version=2.5
-.. _`2.6`: https://symfony.com/roadmap?version=2.6
-.. _`2.7`: https://symfony.com/roadmap?version=2.7
-.. _`2.8`: https://symfony.com/roadmap?version=2.8
-.. _`3.0`: https://symfony.com/roadmap?version=3.0
-.. _`3.1`: https://symfony.com/roadmap?version=3.1
-.. _`3.2`: https://symfony.com/roadmap?version=3.2
-.. _`3.3`: https://symfony.com/roadmap?version=3.3
-.. _`3.4`: https://symfony.com/roadmap?version=3.4
-.. _`4.0`: https://symfony.com/roadmap?version=4.0
-.. _`4.1`: https://symfony.com/roadmap?version=4.1
-.. _`4.2`: https://symfony.com/roadmap?version=4.2
-.. _`4.3`: https://symfony.com/roadmap?version=4.3
-.. _`4.4`: https://symfony.com/roadmap?version=4.4
-.. _`5.0`: https://symfony.com/roadmap?version=5.0
+.. _`semantic versioning`: https://semver.org/
+.. _`Subscribe to Symfony Roadmap notifications`: https://symfony.com/account
+.. _`Symfony Roadmap`: https://symfony.com/roadmap#checker
+.. _`professional Symfony support`: https://sensiolabs.com/
diff --git a/contributing/community/review-comments.rst b/contributing/community/review-comments.rst
index a317c12fa27..6336f92dcbb 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.
@@ -74,12 +74,12 @@ Assume everyone is intelligent and well-meaning.
.. tip::
- Good questions avoid judgement and avoid assumptions about the author's perspective.
+ Good questions avoid judgment and avoid assumptions about the author's perspective.
Maybe you can ask for clarification? Suggest an alternative?
Or provide a simple explanation *why* you disagree with their proposal.
- * ``This looks wrong. Are you sure it's correct?`` (eg. typo/syntax error)
+ * ``This looks wrong. Are you sure it's correct?`` (e.g. typo/syntax error)
* ``What do you think of "RequestFactory" instead of RequestCreator?``
@@ -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 array()`` instead of ``[]``)
+* "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..f412e4ecdd2 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
@@ -60,7 +67,7 @@ The steps for the review are:
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 by `forking`_ the `Symfony Standard Edition`_.
#. **Update the Issue Status**
@@ -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
@@ -209,8 +216,6 @@ Pick a pull request from the `PRs in need of review`_ and follow these steps:
.. _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
new file mode 100644
index 00000000000..8dd302ccc0a
--- /dev/null
+++ b/contributing/diversity/governance.rst
@@ -0,0 +1,143 @@
+Diversity Initiative Governance
+===============================
+
+Membership
+----------
+
+Membership of Symfony's Diversity Initiative is open to any member of the
+Symfony community; to avoid the risk of elitism or meritocracy, no requirement
+is needed to be involved. All members, at any time, are invited to put forward
+ideas and suggestions as a proposal for an actionable item.
+
+Guidance
+--------
+
+The project leader, Fabien Potencier, is responsible for publicly appointing
+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
+ community's benefit.
+* Recognize that meeting the initiative's purpose is an ongoing effort.
+* 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 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 judgment 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 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
+~~~~~~~~~~~~~~
+
+When a proposal on an actionable item is ready to be decided on, insight from
+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 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
+~~~~~~
+
+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 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
+-------------------
+
+Purpose
+~~~~~~~
+
+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 initiative members to ensure that these are
+being delivered effectively and sustainably.
+
+Integrity
+~~~~~~~~~
+
+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
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+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 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
+~~~~~~~~~~~~
+
+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 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/diversity/index.rst b/contributing/diversity/index.rst
new file mode 100644
index 00000000000..a932c27648b
--- /dev/null
+++ b/contributing/diversity/index.rst
@@ -0,0 +1,7 @@
+Diversity Initiative
+====================
+
+.. toctree::
+ :maxdepth: 2
+
+ governance
diff --git a/contributing/documentation/format.rst b/contributing/documentation/format.rst
index 56fcbba574d..875ae836c96 100644
--- a/contributing/documentation/format.rst
+++ b/contributing/documentation/format.rst
@@ -8,7 +8,7 @@ such as HTML and PDF.
reStructuredText
----------------
-reStructuredText is a plaintext markup syntax similar to Markdown, but much
+reStructuredText is a plain text markup syntax similar to Markdown, but much
stricter with its syntax. If you are new to reStructuredText, take some time to
familiarize with this format by reading the existing `Symfony documentation`_
source code.
@@ -29,7 +29,7 @@ Sphinx
Sphinx_ is a build system that provides tools to create documentation from
reStructuredText documents. As such, it adds new directives and interpreted text
-roles to the standard reST markup. Read more about the `Sphinx Markup Constructs`_.
+roles to the standard reStructuredText markup. Read more about the `Sphinx Markup Constructs`_.
Syntax Highlighting
~~~~~~~~~~~~~~~~~~~
@@ -74,7 +74,7 @@ directive to show the configuration in all supported configuration formats
// Configuration in PHP
-The previous reST snippet renders as follow:
+The previous reStructuredText snippet renders as follow:
.. configuration-block::
@@ -148,12 +148,10 @@ If you want to modify that title, use this alternative syntax:
:doc:`environments`
**Links to the API** follow a different syntax, where you must specify the type
-of the linked resource (``namespace``, ``class`` or ``method``):
+of the linked resource (``class`` or ``method``):
.. code-block:: rst
- :namespace:`Symfony\\Component\\BrowserKit`
-
:class:`Symfony\\Component\\Routing\\Matcher\\ApacheUrlMatcher`
:method:`Symfony\\Component\\HttpKernel\\Bundle\\Bundle::build`
@@ -168,40 +166,52 @@ 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 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:: 3.x``
+directive:
+
+.. code-block:: rst
+
+ .. versionadded:: 3.4
-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:
+ The special ``!`` template prefix was introduced in Symfony 3.4.
+
+If you are documenting a behavior change, it may be helpful to *briefly*
+describe how the behavior has changed:
.. code-block:: rst
- .. versionadded:: 2.7
- The ``askHiddenResponse()`` method was introduced in Symfony 2.7.
+ .. versionadded:: 3.4
- You can also ask a question and hide the response. This is particularly [...]
+ Support for annotation routing without an external bundle was introduced
+ in Symfony 3.4. Prior, you needed to install the SensioFrameworkExtraBundle.
-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:: 3.x`` directive:
.. code-block:: rst
- .. versionadded:: 2.7
- The ``include()`` function is a new Twig feature that's available in
- Symfony 2.7. Prior, the ``{% include %}`` tag was used.
+ .. deprecated:: 3.3
+
+ This technique is discouraged and the ``addClassesToCompile()`` method was
+ deprecated in Symfony 3.3 because modern PHP versions make it unnecessary.
-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. 3.0, 4.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 4.0 were released today, 3.0 to 3.4 ``versionadded`` and ``deprecated``
+tags would be removed from the new ``4.0`` branch.
-.. _reStructuredText: http://docutils.sourceforge.net/rst.html
-.. _Sphinx: http://sphinx-doc.org/
+.. _reStructuredText: https://docutils.sourceforge.io/rst.html
+.. _Sphinx: https://www.sphinx-doc.org/
.. _`Symfony documentation`: https://github.com/symfony/symfony-docs
-.. _`reStructuredText Primer`: http://sphinx-doc.org/rest.html
-.. _`reStructuredText Reference`: http://docutils.sourceforge.net/docs/user/rst/quickref.html
-.. _`Sphinx Markup Constructs`: http://sphinx-doc.org/markup/
+.. _`reStructuredText Primer`: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
+.. _`reStructuredText Reference`: https://docutils.sourceforge.io/docs/user/rst/quickref.html
+.. _`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 5fdca2428c7..e7cbfea9bf3 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
@@ -112,16 +112,16 @@ memorable name for the new branch (if you are fixing a reported issue, use
.. code-block:: terminal
- $ git checkout -b improve_install_article upstream/2.8
+ $ git checkout -b improve_install_article upstream/3.4
In this example, the name of the branch is ``improve_install_article`` and the
-``upstream/2.8`` value tells Git to create this branch based on the ``2.8``
+``upstream/3.4`` value tells Git to create this branch based on the ``3.4``
branch of the ``upstream`` remote, which is the original Symfony Docs repository.
Fixes should always be based on the **oldest maintained branch** which contains
-the error. Nowadays this is the ``2.8`` branch. If you are instead documenting a
+the error. Nowadays this is the ``3.4`` branch. If you are instead documenting a
new feature, switch to the first Symfony version that included it, e.g.
-``upstream/3.1``. Not sure? That's ok! Just use the ``upstream/master`` branch.
+``upstream/3.1``. Not sure? That's OK! Just use the ``upstream/master`` branch.
**Step 5.** Now make your changes in the documentation. Add, tweak, reword and
even remove any content and do your best to comply with the
@@ -155,7 +155,7 @@ changes should be applied:
:align: center
In this example, the **base fork** should be ``symfony/symfony-docs`` and
-the **base** branch should be the ``2.8``, which is the branch that you selected
+the **base** branch should be the ``3.4``, which is the branch that you selected
to base your changes on. The **head fork** should be your forked copy
of ``symfony-docs`` and the **compare** branch should be ``improve_install_article``,
which is the name of the branch you created and where you made your changes.
@@ -205,7 +205,7 @@ contribution to the Symfony docs:
# create a new branch based on the oldest maintained version
$ cd projects/symfony-docs/
$ git fetch upstream
- $ git checkout -b my_changes upstream/2.8
+ $ git checkout -b my_changes upstream/3.4
# ... do your changes
@@ -229,28 +229,42 @@ this hard work, it's **time to celebrate again!**
Review your changes
-------------------
-Every GitHub Pull Request is automatically built and deployed by `Platform.sh`_
-on a single environment that you can access on your browser to review your
-changes.
+Every GitHub Pull Request is automatically built and deployed by
+`SymfonyCloud`_ on a single environment that you can access on your browser to
+review your changes.
-.. image:: /_images/contributing/docs-pull-request-platformsh.png
+.. image:: /_images/contributing/docs-pull-request-symfonycloud.png
:align: center
- :alt: Platform.sh Pull Request Deployment
+ :alt: SymfonyCloud Pull Request Deployment
-To access the `Platform.sh`_ environment URL, go to your Pull Request page on
-GitHub, click on the **Show all checks** link and finally, click on the ``Details``
-link displayed for Platform.sh service.
+To access the `SymfonyCloud`_ environment URL, go to your Pull Request page on
+GitHub, click on the **Show all checks** link and finally, click on the
+``Details`` link displayed for SymfonyCloud service.
.. note::
Only Pull Requests to maintained branches are automatically built by
- Platform.sh. Check the `roadmap`_ for maintained branches.
+ SymfonyCloud. Check the `roadmap`_ for maintained branches.
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;
@@ -278,7 +292,7 @@ Why Do my Changes Take so Long to Be Reviewed and/or Merged?
Please be patient. It can take up to several days before your pull request can
be fully reviewed. After merging the changes, it could take again several hours
-before your changes appear on the symfony.com website.
+before your changes appear on the Symfony website.
Why Should I Use the Oldest Maintained Branch Instead of the Master Branch?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -288,8 +302,8 @@ into multiple branches, corresponding to the different versions of Symfony itsel
The ``master`` branch holds the documentation for the development branch of
the code.
-Unless you're documenting a feature that was introduced after Symfony 2.8,
-your changes should always be based on the ``2.8`` branch. Documentation managers
+Unless you're documenting a feature that was introduced after Symfony 3.4,
+your changes should always be based on the ``3.4`` branch. Documentation managers
will use the necessary Git-magic to also apply your changes to all the active
branches of the documentation.
@@ -319,16 +333,15 @@ your proposal after you put all that hard work into making the changes. We
definitely don't want you to waste your time!
.. _`github.com/symfony/symfony-docs`: https://github.com/symfony/symfony-docs
-.. _`reStructuredText`: http://docutils.sourceforge.net/rst.html
+.. _`reStructuredText`: https://docutils.sourceforge.io/rst.html
.. _`GitHub`: https://github.com/
.. _`fork the repository`: https://help.github.com/articles/fork-a-repo
.. _`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
-.. _`Platform.sh`: https://platform.sh
+.. _`SymfonyCloud`: https://symfony.com/cloud
.. _`roadmap`: https://symfony.com/roadmap
.. _`pip`: https://pip.pypa.io/en/stable/
.. _`pip installation`: https://pip.pypa.io/en/stable/installing/
-.. _`Sphinx`: http://sphinx-doc.org/
+.. _`Sphinx`: https://www.sphinx-doc.org/
.. _`Sphinx Extensions for PHP and Symfony`: https://github.com/fabpot/sphinx-php
diff --git a/contributing/documentation/standards.rst b/contributing/documentation/standards.rst
index 05083334eb9..08787eb0331 100644
--- a/contributing/documentation/standards.rst
+++ b/contributing/documentation/standards.rst
@@ -63,8 +63,8 @@ Code Examples
* To avoid horizontal scrolling on code blocks, we prefer to break a line
correctly if it crosses the 85th character;
* When you fold one or more lines of code, place ``...`` in a comment at the point
- of the fold. These comments are: ``// ...`` (php), ``# ...`` (yaml/bash), ``{# ... #}``
- (twig), ```` (xml/html), ``; ...`` (ini), ``...`` (text);
+ of the fold. These comments are: ``// ...`` (PHP), ``# ...`` (Yaml/bash), ``{# ... #}``
+ (Twig), ```` (XML/HTML), ``; ...`` (INI), ``...`` (text);
* When you fold a part of a line, e.g. a variable value, put ``...`` (without comment)
at the place of the fold;
* Description of the folded code: (optional)
@@ -137,7 +137,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
@@ -175,6 +175,23 @@ In addition, documentation follows these rules:
* his or hers, use theirs
* himself or herself, use themselves
+* **Avoid belittling words**: 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/index.rst b/contributing/index.rst
index ee61fb73bd2..923a4e48ed4 100644
--- a/contributing/index.rst
+++ b/contributing/index.rst
@@ -8,6 +8,6 @@ Contributing
code/index
documentation/index
community/index
- code_of_conduct/index
+ diversity/index
.. include:: /contributing/map.rst.inc
diff --git a/contributing/map.rst.inc b/contributing/map.rst.inc
index 15775643c62..b495a0d76d0 100644
--- a/contributing/map.rst.inc
+++ b/contributing/map.rst.inc
@@ -8,16 +8,16 @@
* **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 `
* :doc:`Tests `
* :doc:`Backward Compatibility `
- * :doc:`Coding Standards`
- * :doc:`Code Conventions`
- * :doc:`Git`
+ * :doc:`Coding Standards `
+ * :doc:`Code Conventions `
+ * :doc:`Git `
* :doc:`License `
* **Documentation**
@@ -32,4 +32,7 @@
* :doc:`Release Process `
* :doc:`Respectful Review comments `
* :doc:`Community Reviews `
- * :doc:`Other Resources `
+
+* **Diversity**
+
+ * :doc:`Governance `
diff --git a/controller.rst b/controller.rst
index 77883db9d93..5c2ae3421d9 100644
--- a/controller.rst
+++ b/controller.rst
@@ -16,8 +16,8 @@ This renders a page that prints a lucky (random) number::
// src/AppBundle/Controller/LuckyController.php
namespace AppBundle\Controller;
- use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Response;
+ use Symfony\Component\Routing\Annotation\Route;
class LuckyController
{
@@ -59,7 +59,7 @@ class::
namespace AppBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
- use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
+ use Symfony\Component\Routing\Annotation\Route;
class LuckyController
{
@@ -116,18 +116,15 @@ For more information on routing, see :doc:`/routing`.
.. index::
single: Controller; Base controller class
-The Base Controller Class & Services
-------------------------------------
+.. _the-base-controller-class-services:
-For convenience, Symfony comes with an optional base
-:class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller` class.
-If you extend it, this won't change anything about how your controller
-works, but you'll get access to a number of **helper methods** and the
-**service container** (see :ref:`controller-accessing-services`): an
-array-like object that gives you access to every useful object in the
-system. These useful objects are called **services**, and Symfony ships
-with a service object that can render Twig templates, another that can
-log messages and many more.
+The Base Controller Classes & Services
+--------------------------------------
+
+For convenience, Symfony comes with two optional base
+:class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller` and
+:class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController`
+classes. You can extend either to get access to a number of helper methods.
Add the ``use`` statement atop the ``Controller`` class and then modify
``LuckyController`` to extend it::
@@ -142,11 +139,25 @@ Add the ``use`` statement atop the ``Controller`` class and then modify
// ...
}
-Helper methods are just shortcuts to using core Symfony functionality
-that's available to you with or without the use of the base
-``Controller`` class. A great way to see the core functionality in
-action is to look in the
-:class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller` class.
+That's it! You now have access to methods like :ref:`$this->render() `
+and many others that you'll learn about next.
+
+.. _controller-abstract-versus-controller:
+
+.. tip::
+
+ You can extend either ``Controller`` or ``AbstractController``. The difference
+ is that when you extend ``AbstractController``, you can't access your services
+ via ``$this->get()`` or ``$this->container->get()``, only to a set of common
+ Symfony services. This forces you to write more robust code to access services.
+
+ Moreover, in Symfony 4.2 ``Controller`` was deprecated in favor of
+ ``AbstractController``, so using the latter will make your applications
+ future-proof.
+
+.. versionadded:: 3.3
+
+ The ``AbstractController`` class was introduced in Symfony 3.3.
.. index::
single: Controller; Redirecting
@@ -157,7 +168,7 @@ Generating URLs
The :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::generateUrl`
method is just a helper method that generates the URL for a given route::
- $url = $this->generateUrl('blog_show', array('slug' => 'slug-value'));
+ $url = $this->generateUrl('blog_show', ['slug' => 'slug-value']);
Redirecting
~~~~~~~~~~~
@@ -171,22 +182,18 @@ and ``redirect()`` methods::
return $this->redirectToRoute('homepage');
// does a permanent - 301 redirect
- return $this->redirectToRoute('homepage', array(), 301);
+ return $this->redirectToRoute('homepage', [], 301);
// redirects to a route with parameters
- return $this->redirectToRoute('blog_show', array('slug' => 'my-page'));
+ return $this->redirectToRoute('blog_show', ['slug' => 'my-page']);
- // redirects to a route and mantains the original query string parameters
+ // redirects to a route and maintains the original query string parameters
return $this->redirectToRoute('blog_show', $request->query->all());
// redirects externally
return $this->redirect('http://symfony.com/doc');
}
-.. versionadded:: 2.6
- The ``redirectToRoute()`` method was introduced in Symfony 2.6. Previously (and still now), you
- could use ``redirect()`` and ``generateUrl()`` together for this.
-
For more information, see the :doc:`Routing article `.
.. caution::
@@ -221,15 +228,15 @@ method renders a template **and** puts that content into a ``Response``
object for you::
// renders app/Resources/views/lucky/number.html.twig
- return $this->render('lucky/number.html.twig', array('number' => $number));
+ return $this->render('lucky/number.html.twig', ['number' => $number]);
Templates can also live in deeper sub-directories. Just try to avoid
creating unnecessarily deep structures::
// renders app/Resources/views/lottery/lucky/number.html.twig
- return $this->render('lottery/lucky/number.html.twig', array(
+ return $this->render('lottery/lucky/number.html.twig', [
'number' => $number,
- ));
+ ]);
The Symfony templating system and Twig are explained more in the
:doc:`Creating and Using Templates article `.
@@ -239,46 +246,150 @@ The Symfony templating system and Twig are explained more in the
.. _controller-accessing-services:
-Accessing Other Services
-~~~~~~~~~~~~~~~~~~~~~~~~
+Fetching Services as Controller Arguments
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Symfony comes packed with a lot of useful objects, called *services*. These
-are used for rendering templates, sending emails, querying the database and
-any other "work" you can think of. When you install a new bundle, it probably
-brings in even *more* services.
+.. versionadded:: 3.3
-When extending the base controller class, you can access any Symfony service
-via the :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::get`
-method of the ``Controller`` class. Here are several common services you might
-need::
+ The ability to type-hint a controller argument in order to receive a service
+ was introduced in Symfony 3.3.
- $templating = $this->get('templating');
+Symfony comes *packed* with a lot of useful classes and functionalities, called :doc:`services `.
+These are used for rendering templates, sending emails, querying the database and
+any other "work" you can think of.
- $router = $this->get('router');
+If you need a service in a controller, just type-hint an argument with its class
+(or interface) name. Symfony will automatically pass you the service you need::
- $mailer = $this->get('mailer');
+ use Psr\Log\LoggerInterface;
+ // ...
+
+ /**
+ * @Route("/lucky/number/{max}")
+ */
+ public function numberAction($max, LoggerInterface $logger)
+ {
+ $logger->info('We are logging!');
+ // ...
+ }
+
+Awesome!
-What other services exist? To list all services, use the ``debug:container``
-console command:
+What other services can you type-hint? To see them, use the ``debug:autowiring`` console
+command:
.. code-block:: terminal
- $ php app/console debug:container
+ $ php bin/console debug:autowiring
-For more information, see the :doc:`/service_container` article.
+If you need control over the *exact* value of an argument, you can :ref:`bind `
+the argument by its name:
-.. tip::
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # app/config/services.yml
+ services:
+ # ...
+
+ # explicitly configure the service
+ AppBundle\Controller\LuckyController:
+ tags: [controller.service_arguments]
+ bind:
+ # for any $logger argument, pass this specific service
+ $logger: '@monolog.logger.doctrine'
+
+ .. code-block:: xml
+
+
+
+
- To get a :ref:`container configuration parameter `,
- use the
- :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::getParameter`
- method::
+
+
- $from = $this->getParameter('app.mailer.from');
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // app/config/services.php
+ use AppBundle\Controller\LuckyController;
+ use Symfony\Component\DependencyInjection\Reference;
+
+ $container->register(LuckyController::class)
+ ->addTag('controller.service_arguments')
+ ->setBindings([
+ '$logger' => new Reference('monolog.logger.doctrine'),
+ ])
+ ;
+
+You can also use normal :ref:`constructor injection `
+in your controllers.
+
+.. caution::
+
+ You can *only* pass *services* to your controller arguments in this way. It's not
+ possible, for example, to pass a config parameter as a controller argument,
+ even by using ``bind``. If you need a parameter, use the ``$this->getParameter('kernel.debug')``
+ shortcut or pass the value through your controller's ``__construct()`` method
+ and specify its value with ``bind``.
+
+For more information about services, see the :doc:`/service_container` article.
+
+.. _controller-service-arguments-tag:
+
+.. note::
+
+ If this isn't working, make sure your controller is registered as a service,
+ is :ref:`autoconfigured ` and extends either
+ :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller` or
+ :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController`. If
+ you use the :ref:`services.yml configuration from the Symfony Standard Edition `,
+ then your controllers are already registered as services and autoconfigured.
+
+ If you're not using the default configuration, you can tag your service manually
+ with ``controller.service_arguments``.
+
+.. _accessing-other-services:
+.. _controller-access-services-directly:
+
+Accessing the Container Directly
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you extend the base ``Controller`` class, you can access any Symfony service
+via the :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::get`
+method. Here are several common services you might need::
+
+ $templating = $this->get('templating');
+
+ $router = $this->get('router');
+
+ $mailer = $this->get('mailer');
- .. versionadded:: 2.7
- The ``Controller::getParameter()`` method was introduced in Symfony
- 2.7. Use ``$this->container->getParameter()`` in versions prior to 2.7.
+ // you can also fetch parameters
+ $someParameter = $this->getParameter('some_parameter');
+
+If you receive an error like:
+
+.. code-block:: text
+
+ You have requested a non-existent service "my_service_id"
+
+Check to make sure the service exists (use :ref:`debug:container `)
+and that it's :ref:`public `.
.. index::
single: Controller; Managing errors
@@ -289,7 +400,8 @@ Managing Errors and 404 Pages
When things are not found, you should play well with the HTTP protocol and
return a 404 response. To do this, you'll throw a special type of exception.
-If you're extending the base ``Controller`` class, do the following::
+If you're extending the base ``Controller`` or the base ``AbstractController``
+class, do the following::
public function indexAction()
{
@@ -355,18 +467,18 @@ Symfony provides a nice session object that you can use to store information
about the user between requests. By default, Symfony stores the token in a
cookie and writes the attributes to a file by using native PHP sessions.
-To retrieve the session, call
-:method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::getSession`
-method on the ``Request`` object. This method returns a
-:class:`Symfony\\Component\\HttpFoundation\\Session\\SessionInterface` with easy
-methods for storing and fetching things from the session::
+.. versionadded:: 3.3
- use Symfony\Component\HttpFoundation\Request;
+ The ability to request a ``Session`` instance in controllers was introduced
+ in Symfony 3.3.
- public function indexAction(Request $request)
- {
- $session = $request->getSession();
+To retrieve the session, add the :class:`Symfony\\Component\\HttpFoundation\\Session\\SessionInterface`
+type-hint to your argument and Symfony will provide you with a session::
+
+ use Symfony\Component\HttpFoundation\Session\SessionInterface;
+ public function indexAction(SessionInterface $session)
+ {
// stores an attribute for reuse during a later user request
$session->set('foo', 'bar');
@@ -374,11 +486,16 @@ methods for storing and fetching things from the session::
$foobar = $session->get('foobar');
// uses a default value if the attribute doesn't exist
- $filters = $session->get('filters', array());
+ $filters = $session->get('filters', []);
}
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 arguments instead.
+
.. index::
single: Session; Flash messages
@@ -418,28 +535,42 @@ and then redirects. The message key (``notice`` in this example) can be anything
you'll use this key to retrieve the message.
In the template of the next page (or even better, in your base layout template),
-read any flash messages from the session:
+read any flash messages from the session using ``app.flashes()``:
.. code-block:: html+twig
{# app/Resources/views/base.html.twig #}
- {# you can read and display just one flash message type... #}
- {% for flash_message in app.session.flashBag.get('notice') %}
+ {# read and display just one flash message type #}
+ {% for message in app.flashes('notice') %}
- {{ flash_message }}
+ {{ message }}
{% endfor %}
- {# ...or you can read and display every flash message available #}
- {% for type, flash_messages in app.session.flashBag.all %}
- {% for flash_message in flash_messages %}
-
- {{ flash_message }}
+ {# 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 %}
+
+ {{ message }}
+
+ {% endfor %}
+ {% endfor %}
+
+.. versionadded:: 3.3
+
+ The ``app.flashes()`` Twig function was introduced in Symfony 3.3. Prior,
+ you had to use ``app.session.flashBag()``.
+
.. note::
It's common to use ``notice``, ``warning`` and ``error`` as the keys of the
@@ -470,7 +601,7 @@ the ``Request`` class::
{
$request->isXmlHttpRequest(); // is it an Ajax request?
- $request->getPreferredLanguage(array('en', 'fr'));
+ $request->getPreferredLanguage(['en', 'fr']);
// retrieves GET and POST variables respectively
$request->query->get('page');
@@ -509,16 +640,12 @@ headers and content that's sent back to the client::
// creates a simple Response with a 200 status code (the default)
$response = new Response('Hello '.$name, Response::HTTP_OK);
- // JsonResponse is a sub-class of Response
- $response = new JsonResponse(array('name' => $name));
- // sets a header!
- $response->headers->set('X-Rate-Limit', 10);
+ // creates a CSS-response with a 200 status code
+ $response = new Response('');
+ $response->headers->set('Content-Type', 'text/css');
There are special classes that make certain kinds of responses easier:
-* For JSON, there is :class:`Symfony\\Component\\HttpFoundation\\JsonResponse`.
- See :ref:`component-http-foundation-json-response`.
-
* For files, there is :class:`Symfony\\Component\\HttpFoundation\\BinaryFileResponse`.
See :ref:`component-http-foundation-serving-files`.
@@ -532,6 +659,61 @@ There are special classes that make certain kinds of responses easier:
``Request`` and ``Response`` object in the
:ref:`HttpFoundation component documentation `.
+JSON Helper
+~~~~~~~~~~~
+
+To return JSON from a controller, use the ``json()`` helper method on the base controller.
+This returns a special ``JsonResponse`` object that encodes the data automatically::
+
+ // ...
+ public function indexAction()
+ {
+ // returns '{"username":"jane.doe"}' and sets the proper Content-Type header
+ return $this->json(['username' => 'jane.doe']);
+
+ // the shortcut defines three optional arguments
+ // return $this->json($data, $status = 200, $headers = [], $context = []);
+ }
+
+If the :doc:`serializer service ` is enabled in your
+application, contents passed to ``json()`` are encoded with it. Otherwise,
+the :phpfunction:`json_encode` function is used.
+
+File helper
+~~~~~~~~~~~
+
+.. versionadded:: 3.2
+
+ The ``file()`` helper was introduced in Symfony 3.2.
+
+You can use the :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::file`
+helper to serve a file from inside a controller::
+
+ public function fileAction()
+ {
+ // send the file contents and force the browser to download it
+ return $this->file('/path/to/some_file.pdf');
+ }
+
+The ``file()`` helper provides some arguments to configure its behavior::
+
+ use Symfony\Component\HttpFoundation\File\File;
+ use Symfony\Component\HttpFoundation\ResponseHeaderBag;
+
+ public function fileAction()
+ {
+ // load the file from the filesystem
+ $file = new File('/path/to/some_file.pdf');
+
+ return $this->file($file);
+
+ // rename the downloaded file
+ return $this->file($file, 'custom_name.pdf');
+
+ // display the file contents in the browser instead of downloading it
+ return $this->file('invoice_3241.pdf', 'my_invoice.pdf', ResponseHeaderBag::DISPOSITION_INLINE);
+ }
+
Final Thoughts
--------------
@@ -541,12 +723,7 @@ 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.
To make life easier, you'll probably extend the base ``Controller`` class because
-this gives two things:
-
-A) Shortcut methods (like ``render()`` and ``redirectToRoute()``);
-
-B) Access to *all* of the useful objects (services) in the system via the
- :ref:`get() ` method.
+this gives access to shortcut methods (like ``render()`` and ``redirectToRoute()``).
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,
diff --git a/controller/argument_value_resolver.rst b/controller/argument_value_resolver.rst
new file mode 100644
index 00000000000..70f0ea58610
--- /dev/null
+++ b/controller/argument_value_resolver.rst
@@ -0,0 +1,250 @@
+.. index::
+ single: Controller; Argument Value Resolvers
+
+Extending Action Argument Resolving
+===================================
+
+In the :doc:`controller guide `, you've learned that you can get the
+:class:`Symfony\\Component\\HttpFoundation\\Request` object via an argument in
+your controller. This argument has to be type-hinted by the ``Request`` class
+in order to be recognized. This is done via the
+:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver`. By
+creating and registering custom argument value resolvers, you can extend this
+functionality.
+
+.. _functionality-shipped-with-the-httpkernel:
+
+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.
+
+:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\RequestValueResolver`
+ Injects the current ``Request`` if type-hinted with ``Request`` or a class
+ extending ``Request``.
+
+:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\ServiceValueResolver`
+ Injects a service if type-hinted with a valid service class or interface. This
+ works like :doc:`autowiring `.
+
+:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\SessionValueResolver`
+ Injects the configured session class extending ``SessionInterface`` if
+ type-hinted with ``SessionInterface`` or a class extending
+ ``SessionInterface``.
+
+:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\DefaultValueResolver`
+ Will set the default value of the argument if present and the argument
+ is optional.
+
+:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\VariadicValueResolver`
+ Verifies if the request data is an array and will add all of them to the
+ argument list. When the action is called, the last (variadic) argument will
+ contain all the values of this array.
+
+In addition, some official bundles provide other value resolvers:
+
+: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`_.
+
+``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
+------------------------------
+
+In the next example, you'll create a value resolver to inject the object that
+represents the current user whenever a controller method type-hints an argument
+with the ``User`` class::
+
+ namespace AppBundle\Controller;
+
+ use AppBundle\Entity\User;
+ use Symfony\Component\HttpFoundation\Response;
+
+ class UserController
+ {
+ public function indexAction(User $user)
+ {
+ return new Response('Hello '.$user->getUsername().'!');
+ }
+ }
+
+Beware that this feature is already provided by the `@ParamConverter`_
+annotation from the SensioFrameworkExtraBundle. If you have that bundle
+installed in your project, add this config to disable the auto-conversion of
+type-hinted method arguments:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # app/config/config.yml
+ sensio_framework_extra:
+ request:
+ converters: true
+ auto_convert: false
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // app/config/config.php
+ $container->loadFromExtension('sensio_framework_extra', [
+ 'request' => [
+ 'converters' => true,
+ 'auto_convert' => false,
+ ],
+ ]);
+
+Adding a new value resolver requires creating a class that implements
+:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolverInterface`
+and defining a service for it. The interface defines two methods:
+
+``supports()``
+ This method is used to check whether the value resolver supports the
+ given argument. ``resolve()`` will only be executed when this returns ``true``.
+``resolve()``
+ This method will resolve the actual value for the argument. Once the value
+ is resolved, you must `yield`_ the value to the ``ArgumentResolver``.
+
+Both methods get the ``Request`` object, which is the current request, and an
+:class:`Symfony\\Component\\HttpKernel\\ControllerMetadata\\ArgumentMetadata`
+instance. This object contains all information retrieved from the method signature
+for the current argument.
+
+Now that you know what to do, you can implement this interface. To get the
+current ``User``, you need the current security token. This token can be
+retrieved from the token storage::
+
+ // src/AppBundle/ArgumentResolver/UserValueResolver.php
+ namespace AppBundle\ArgumentResolver;
+
+ use AppBundle\Entity\User;
+ use Symfony\Component\HttpFoundation\Request;
+ use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
+ use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
+ use Symfony\Component\Security\Core\Security;
+
+ class UserValueResolver implements ArgumentValueResolverInterface
+ {
+ private $security;
+
+ public function __construct(Security $security)
+ {
+ $this->security = $security;
+ }
+
+ public function supports(Request $request, ArgumentMetadata $argument)
+ {
+ if (User::class !== $argument->getType()) {
+ return false;
+ }
+
+ return $this->security->getUser() instanceof User;
+ }
+
+ public function resolve(Request $request, ArgumentMetadata $argument)
+ {
+ yield $this->security->getUser();
+ }
+ }
+
+In order to get the actual ``User`` object in your argument, the given value
+must fulfill the following requirements:
+
+* An argument must be type-hinted as ``User`` in your action method signature;
+* The value must be an instance of the ``User`` class.
+
+When all those requirements are met and ``true`` is returned, the
+``ArgumentResolver`` calls ``resolve()`` with the same values as it called
+``supports()``.
+
+That's it! Now all you have to do is add the configuration for the service
+container. This can be done by tagging the service with ``controller.argument_value_resolver``
+and adding a priority.
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # app/config/services.yml
+ services:
+ AppBundle\ArgumentResolver\UserValueResolver:
+ autowire: true
+ tags:
+ - { name: controller.argument_value_resolver, priority: 50 }
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // app/config/services.php
+ use AppBundle\ArgumentResolver\UserValueResolver;
+
+ $container->autowire(UserValueResolver::class)
+ ->addTag('controller.argument_value_resolver', ['priority' => 50])
+ ;
+
+While adding a priority is optional, it's recommended to add one to make sure
+the expected value is injected. The ``RequestAttributeValueResolver`` has a
+priority of 100. As this one is responsible for fetching attributes from the
+``Request``, it's recommended to trigger your custom value resolver with a
+lower priority. This makes sure the argument resolvers are not triggered when
+the attribute is present. For instance, when passing the user along a
+sub-requests.
+
+.. tip::
+
+ As you can see in the ``UserValueResolver::supports()`` method, the user
+ may not be available (e.g. when the controller is not behind a firewall).
+ In these cases, the resolver will not be executed. If no argument value
+ is resolved, an exception will be thrown.
+
+ To prevent this, you can add a default value in the controller (e.g. ``User
+ $user = null``). The ``DefaultValueResolver`` is executed as the last
+ resolver and will use the default value if no value was already resolved.
+
+.. _`@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/csrf_token_validation.rst b/controller/csrf_token_validation.rst
index 5bf60980925..3bc49401b15 100644
--- a/controller/csrf_token_validation.rst
+++ b/controller/csrf_token_validation.rst
@@ -9,20 +9,11 @@ want to use the Symfony Form component. If, for example, you are implementing
a DELETE action, you can use the :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::isCsrfTokenValid`
method to check the validity of a CSRF token::
- public function deleteAction()
+ use Symfony\Component\HttpFoundation\Request;
+
+ public function deleteAction(Request $request)
{
- if ($this->isCsrfTokenValid('token_id', $submittedToken)) {
+ if ($this->isCsrfTokenValid('token_id', $request->request->get('token_param'))) {
// ... do something, like deleting an object
}
}
-
-.. versionadded:: 2.6
- The ``isCsrfTokenValid()`` shortcut method was introduced in Symfony 2.6.
- It is equivalent to executing the following code:
-
- .. code-block:: php
-
- use Symfony\Component\Security\Csrf\CsrfToken;
-
- $this->get('security.csrf.token_manager')
- ->isTokenValid(new CsrfToken('token_id', 'TOKEN'));
diff --git a/controller/error_pages.rst b/controller/error_pages.rst
index d9cef3786a5..7be7c6f4dad 100644
--- a/controller/error_pages.rst
+++ b/controller/error_pages.rst
@@ -154,10 +154,10 @@ To use this feature, you need to have a definition in your
+ https://symfony.com/schema/routing/routing-1.0.xsd">
+ prefix="/_error"/>
.. code-block:: php
@@ -207,7 +207,7 @@ configuration option to point to it:
# app/config/config.yml
twig:
- exception_controller: AppBundle:Exception:showException
+ exception_controller: AppBundle:Exception:showAction
.. code-block:: xml
@@ -217,12 +217,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">
- AppBundle:Exception:showException
+ AppBundle:Exception:showAction
@@ -230,10 +230,10 @@ configuration option to point to it:
.. code-block:: php
// app/config/config.php
- $container->loadFromExtension('twig', array(
- 'exception_controller' => 'AppBundle:Exception:showException',
+ $container->loadFromExtension('twig', [
+ 'exception_controller' => 'AppBundle:Exception:showAction',
// ...
- ));
+ ]);
The :class:`Symfony\\Component\\HttpKernel\\EventListener\\ExceptionListener`
class used by the TwigBundle as a listener of the ``kernel.exception`` event creates
@@ -248,8 +248,8 @@ will be passed two parameters:
A :class:`\\Symfony\\Component\\HttpKernel\\Log\\DebugLoggerInterface`
instance which may be ``null`` in some circumstances.
-Instead of creating a new exception controller from scratch you can, of course,
-also extend the default :class:`Symfony\\Bundle\\TwigBundle\\Controller\\ExceptionController`.
+Instead of creating a new exception controller from scratch you can also extend
+the default :class:`Symfony\\Bundle\\TwigBundle\\Controller\\ExceptionController`.
In that case, you might want to override one or both of the ``showAction()`` and
``findTemplate()`` methods. The latter one locates the template to be used.
@@ -266,9 +266,15 @@ In that case, you might want to override one or both of the ``showAction()`` and
# app/config/services.yml
services:
- app.exception_controller:
- class: AppBundle\Controller\CustomExceptionController
- arguments: ['@twig', '%kernel.debug%']
+ _defaults:
+ # ... be sure autowiring is enabled
+ autowire: true
+ # ...
+
+ AppBundle\Controller\CustomExceptionController:
+ public: true
+ arguments:
+ $debug: '%kernel.debug%'
.. code-block:: xml
@@ -277,14 +283,15 @@ 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">
-
-
- %kernel.debug%
+
+
+
+
+
+ %kernel.debug%
@@ -294,16 +301,9 @@ In that case, you might want to override one or both of the ``showAction()`` and
// app/config/services.php
use AppBundle\Controller\CustomExceptionController;
- use Symfony\Component\DependencyInjection\Reference;
-
- $container->register('app.exception_controller', CustomExceptionController::class)
- ->setArguments(array(
- new Reference('twig'),
- '%kernel.debug%',
- ));
- And then configure ``twig.exception_controller`` using the controller as
- services syntax (e.g. ``app.exception_controller:showAction``).
+ $container->autowire(CustomExceptionController::class)
+ ->setArgument('$debug', '%kernel.debug%');
.. tip::
@@ -350,6 +350,4 @@ time and again, you can have just one (or several) listeners deal with them.
and takes measures like redirecting the user to the login page, logging them
out and other things.
-.. _`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 df8a4c685b1..cf727128436 100644
--- a/controller/forwarding.rst
+++ b/controller/forwarding.rst
@@ -13,10 +13,10 @@ from *that* controller::
public function indexAction($name)
{
- $response = $this->forward('AppBundle:Something:fancy', array(
+ $response = $this->forward('AppBundle:Something:fancy', [
'name' => $name,
'color' => 'green',
- ));
+ ]);
// ... further modify the response or return it directly
diff --git a/controller/service.rst b/controller/service.rst
index 11328734b51..1254b44383a 100644
--- a/controller/service.rst
+++ b/controller/service.rst
@@ -4,121 +4,109 @@
How to Define Controllers as Services
=====================================
-.. caution::
+In Symfony, a controller does *not* need to be registered as a service. But if you're
+using the :ref:`default services.yml configuration `,
+your controllers *are* already registered as services. This means you can use dependency
+injection like any other normal service.
- Defining controllers as services is **not officially recommended** by Symfony.
- They are used by some developers for very specific use cases, such as
- DDD (*domain-driven design*) and Hexagonal Architecture applications.
+Referencing your Service from Routing
+-------------------------------------
-In the :doc:`/controller` guide, you've learned how easily a controller can be
-used when it extends the base
-:class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller` class. While
-this works fine, controllers can also be specified as services. Even if you don't
-specify your controllers as services, you might see them being used in some
-open-source Symfony bundles, so it may be useful to understand both approaches.
+Registering your controller as a service is great, but you also need to make sure
+that your routing references the service properly, so that Symfony knows to use it.
-These are the main **advantages** of defining controllers as services:
+If the service id is the fully-qualified class name (FQCN) of your controller, you're
+done! You can use the normal ``AppBundle:Hello:index`` syntax in your routing and
+it will find your service.
-* The entire controller and any service passed to it can be modified via the
- service container configuration. This is useful when developing reusable bundles;
-* Your controllers are more "sandboxed". By looking at the constructor arguments,
- it's easy to see what types of things this controller may or may not do;
-* If you're not passing some required dependencies or if you are injecting some
- non-existent services, you'll get errors during the container compilation
- instead of during runtime execution;
-* Since dependencies must be injected manually, it's more obvious when your
- controller is becoming too big (i.e. if you have many constructor arguments).
+But, if your service has a different id, you can use a special ``SERVICEID:METHOD``
+syntax:
-These are the main **drawbacks** of defining controllers as services:
-
-* It takes more work to create the controllers and they become more verbose
- because they don't have automatic access to the services and the base
- controller shortcuts;
-* The constructor of the controllers can rapidly become too complex because you
- must inject every single dependency needed by them.
-
-The recommendation from the :doc:`best practices `
-is also valid for controllers defined as services: avoid putting your business
-logic into the controllers. Instead, inject services that do the bulk of the work.
-
-Defining the Controller as a Service
-------------------------------------
+.. configuration-block::
-A controller can be defined as a service in the same way as any other class.
-For example, if you have the following simple controller::
+ .. code-block:: php-annotations
- // src/AppBundle/Controller/HelloController.php
- namespace AppBundle\Controller;
+ // src/AppBundle/Controller/HelloController.php
- use Symfony\Component\HttpFoundation\Response;
+ // You need to use Sensio's annotation to specify a service id
+ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
- class HelloController
- {
- public function indexAction($name)
+ /**
+ * @Route(service="app.hello_controller")
+ */
+ class HelloController
{
- return new Response('Hello '.$name.'!');
+ // ...
}
- }
-
-Then you can define it as a service as follows:
-
-.. configuration-block::
.. code-block:: yaml
- # app/config/services.yml
- services:
- app.hello_controller:
- class: AppBundle\Controller\HelloController
+ # app/config/routing.yml
+ hello:
+ path: /hello
+ defaults: { _controller: app.hello_controller:indexAction }
.. code-block:: xml
-
+
-
+ xsi:schemaLocation="http://symfony.com/schema/routing
+ https://symfony.com/schema/routing/routing-1.0.xsd">
-
-
-
+
+ app.hello_controller:indexAction
+
-
+
.. code-block:: php
- // app/config/services.php
- use AppBundle\Controller\HelloController;
+ // app/config/routing.php
+ $collection->add('hello', new Route('/hello', [
+ '_controller' => 'app.hello_controller:indexAction',
+ ]));
- $container->register('app.hello_controller', HelloController::class);
+.. note::
-Referring to the Service
-------------------------
+ You cannot drop the ``Action`` part of the method name when using the
+ single colon notation.
-To refer to a controller that's defined as a service, use the single colon (:)
-notation. For example, to forward to the ``indexAction()`` method of the service
-defined above with the id ``app.hello_controller``::
+.. _controller-service-invoke:
- $this->forward('app.hello_controller:indexAction', array('name' => $name));
+Invokable Controllers
+---------------------
-.. note::
+Controllers can also define a single action using the ``__invoke()`` method,
+which is a common practice when following the `ADR pattern`_
+(Action-Domain-Responder):
- Make sure the method name in your route (e.g. ``indexAction``) matches the
- method name exactly. Unlike the traditional ``Bundle:Controller:method``
- notation, the ``Action`` suffix is not automatically added for you.
+.. configuration-block::
-You can also route to the service by using the same notation when defining
-the route ``_controller`` value:
+ .. code-block:: php-annotations
-.. configuration-block::
+ // src/AppBundle/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
# app/config/routing.yml
hello:
- path: /hello
- defaults: { _controller: app.hello_controller:indexAction }
+ path: /hello/{name}
+ defaults: { _controller: app.hello_controller }
.. code-block:: xml
@@ -127,10 +115,10 @@ the route ``_controller`` value:
+ https://symfony.com/schema/routing/routing-1.0.xsd">
-
- app.hello_controller:indexAction
+
+ app.hello_controller
@@ -138,59 +126,21 @@ the route ``_controller`` value:
.. code-block:: php
// app/config/routing.php
- $collection->add('hello', new Route('/hello', array(
- '_controller' => 'app.hello_controller:indexAction',
- )));
-
-.. tip::
-
- You can also use annotations to configure routing using a controller
- defined as a service. Make sure you specify the service ID in the
- ``@Route`` annotation. See the `FrameworkExtraBundle documentation`_ for
- details.
-
-.. tip::
-
- If your controller implements the ``__invoke()`` method, you can simply
- refer to the service id (``app.hello_controller``).
+ $collection->add('hello', new Route('/hello', [
+ '_controller' => 'app.hello_controller',
+ ]));
Alternatives to base Controller Methods
---------------------------------------
-When using a controller defined as a service, it will most likely not extend
-the base ``Controller`` class. Instead of relying on its shortcut methods,
-you'll interact directly with the services that you need. Fortunately, this is
-usually pretty easy and the base `Controller class source code`_ is a great
-source on how to perform many common tasks.
-
-For example, if you want to render a template instead of creating the ``Response``
-object directly, then your code would look like this if you were extending
-Symfony's base controller::
-
- // src/AppBundle/Controller/HelloController.php
- namespace AppBundle\Controller;
-
- use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+When using a controller defined as a service, you can still extend any of the
+:ref:`normal base controller ` classes and
+use their shortcuts. But, you don't need to! You can choose to extend *nothing*,
+and use dependency injection to access different services.
- class HelloController extends Controller
- {
- public function indexAction($name)
- {
- return $this->render(
- 'hello/index.html.twig',
- array('name' => $name)
- );
- }
- }
-
-If you look at the source code for the ``render()`` function in Symfony's
-`base Controller class`_, you'll see that this method actually uses the
-``templating`` service::
-
- public function render($view, array $parameters = array(), Response $response = null)
- {
- return $this->container->get('templating')->renderResponse($view, $parameters, $response);
- }
+The base `Controller class source code`_ is a great way to see how to accomplish
+common tasks. For example, ``$this->render()`` is usually used to render a Twig
+template and return a Response. But, you can also do this directly:
In a controller that's defined as a service, you can instead inject the ``templating``
service and use it directly::
@@ -198,174 +148,42 @@ service and use it directly::
// src/AppBundle/Controller/HelloController.php
namespace AppBundle\Controller;
- use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\HttpFoundation\Response;
+ use Twig\Environment;
class HelloController
{
- private $templating;
+ private $twig;
- public function __construct(EngineInterface $templating)
+ public function __construct(Environment $twig)
{
- $this->templating = $templating;
+ $this->twig = $twig;
}
public function indexAction($name)
{
- return $this->templating->renderResponse(
+ $content = $this->twig->render(
'hello/index.html.twig',
- array('name' => $name)
+ ['name' => $name]
);
+
+ return new Response($content);
}
}
-The service definition also needs modifying to specify the constructor
-argument:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/services.yml
- services:
- app.hello_controller:
- class: AppBundle\Controller\HelloController
- arguments: ['@templating']
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/services.php
- use AppBundle\Controller\HelloController;
- use Symfony\Component\DependencyInjection\Reference;
-
- $container->register('app.hello_controller', HelloController::class)
- ->addArgument(new Reference('templating'));
-
-Rather than fetching the ``templating`` service from the container, you can
-inject *only* the exact service(s) that you need directly into the controller.
-
-.. note::
-
- This does not mean that you cannot extend these controllers from your own
- base controller. The move away from the standard base controller is because
- its helper methods rely on having the container available which is not
- the case for controllers that are defined as services. It may be a good
- idea to extract common code into a service that's injected rather than
- place that code into a base controller that you extend. Both approaches
- are valid, exactly how you want to organize your reusable code is up to
- you.
+You can also use a special :ref:`action-based dependency injection `
+to receive services as arguments to your controller action methods.
Base Controller Methods and Their Service Replacements
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-This list explains how to replace the convenience methods of the base
-controller:
-
-:method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::createForm` (service: ``form.factory``)
- .. code-block:: php
-
- $formFactory->create($type, $data, $options);
-
-:method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::createFormBuilder` (service: ``form.factory``)
- .. code-block:: php
-
- $formFactory->createBuilder('form', $data, $options);
-
-:method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::createNotFoundException`
- .. code-block:: php
-
- new NotFoundHttpException($message, $previous);
-
-:method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::forward` (service: ``http_kernel``)
- .. code-block:: php
-
- use Symfony\Component\HttpKernel\HttpKernelInterface;
- // ...
-
- $request = ...;
- $attributes = array_merge($path, array('_controller' => $controller));
- $subRequest = $request->duplicate($query, null, $attributes);
- $httpKernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
-
-:method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::generateUrl` (service: ``router``)
- .. code-block:: php
-
- $router->generate($route, $params, $referenceType);
-
- .. note::
-
- The ``$referenceType`` argument must be one of the constants defined
- in the :class:`Symfony\\Component\\Routing\\Generator\\UrlGeneratorInterface`.
-
-:method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::getDoctrine` (service: ``doctrine``)
- *Simply inject doctrine instead of fetching it from the container.*
-
-:method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::getUser` (service: ``security.token_storage``)
- .. code-block:: php
-
- $user = null;
- $token = $tokenStorage->getToken();
- if (null !== $token && is_object($token->getUser())) {
- $user = $token->getUser();
- }
-
-:method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::isGranted` (service: ``security.authorization_checker``)
- .. code-block:: php
-
- $authChecker->isGranted($attributes, $object);
-
-:method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::redirect`
- .. code-block:: php
-
- use Symfony\Component\HttpFoundation\RedirectResponse;
-
- return new RedirectResponse($url, $status);
-
-:method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::render` (service: ``templating``)
- .. code-block:: php
-
- $templating->renderResponse($view, $parameters, $response);
-
-:method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::renderView` (service: ``templating``)
- .. code-block:: php
-
- $templating->render($view, $parameters);
-
-:method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::stream` (service: ``templating``)
- .. code-block:: php
-
- use Symfony\Component\HttpFoundation\StreamedResponse;
-
- $templating = $this->templating;
- $callback = function () use ($templating, $view, $parameters) {
- $templating->stream($view, $parameters);
- };
-
- return new StreamedResponse($callback);
-
-.. tip::
+The best way to see how to replace base ``Controller`` convenience methods is to
+look at the `ControllerTrait`_ that holds its logic.
- ``getRequest()`` has been deprecated. Instead, have an argument to your
- controller action method called ``Request $request``. The order of the
- parameters is not important, but the typehint must be provided.
+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/Controller.php
-.. _`base Controller class`: https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php
-.. _`FrameworkExtraBundle documentation`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/routing.html#controller-as-service
+.. _`Controller class source code`: 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 5a253a0c1b8..4bbda5f2db6 100644
--- a/controller/soap_web_service.rst
+++ b/controller/soap_web_service.rst
@@ -6,10 +6,10 @@
How to Create a SOAP Web Service in a Symfony Controller
========================================================
-Setting up a controller to act as a SOAP server is simple with a couple
-tools. You must, of course, have the `PHP SOAP`_ extension installed.
-As the PHP SOAP extension cannot currently generate a WSDL, you must either
-create one from scratch or use a 3rd party generator.
+Setting up a controller to act as a SOAP server is simple with a couple tools.
+You must have the `PHP SOAP`_ extension installed. As the PHP SOAP extension
+cannot currently generate a WSDL, you must either create one from scratch or use
+a 3rd party generator.
.. note::
@@ -24,8 +24,8 @@ which represents the functionality that you'll expose in your SOAP service.
In this case, the SOAP service will allow the client to call a method called
``hello``, which happens to send an email::
- // src/Acme/SoapBundle/Services/HelloService.php
- namespace Acme\SoapBundle\Services;
+ // src/AppBundle/Service/HelloService.php
+ namespace AppBundle\Service;
class HelloService
{
@@ -49,62 +49,30 @@ In this case, the SOAP service will allow the client to call a method called
}
}
-Next, you can train Symfony to be able to create an instance of this class.
-Since the class sends an email, it's been designed to accept a ``Swift_Mailer``
-instance. Using the Service Container, you can configure Symfony to construct
-a ``HelloService`` object properly:
+Next, make sure that your new class is registered as a service. If you're using
+the :ref:`default services configuration `,
+you don't need to do anything!
-.. configuration-block::
+Finally, below is an example of a controller that is capable of handling a SOAP
+request. Because ``indexAction()`` is accessible via ``/soap``, the WSDL document
+can be retrieved via ``/soap?wsdl``::
- .. code-block:: yaml
-
- # app/config/services.yml
- services:
- hello_service:
- class: Acme\SoapBundle\Services\HelloService
- arguments: ['@mailer']
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/services.php
- use Acme\SoapBundle\Services\HelloService;
-
- $container
- ->register('hello_service', HelloService::class)
- ->addArgument(new Reference('mailer'));
-
-Below is an example of a controller that is capable of handling a SOAP
-request. If ``indexAction()`` is accessible via the route ``/soap``, then the
-WSDL document can be retrieved via ``/soap?wsdl``::
-
- namespace Acme\SoapBundle\Controller;
+ namespace AppBundle\Controller;
+ use AppBundle\Service\HelloService;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
+ use Symfony\Component\Routing\Annotation\Route;
class HelloServiceController extends Controller
{
- public function indexAction()
+ /**
+ * @Route("/soap")
+ */
+ public function indexAction(HelloService $helloService)
{
$soapServer = new \SoapServer('/path/to/hello.wsdl');
- $soapServer->setObject($this->get('hello_service'));
+ $soapServer->setObject($helloService);
$response = new Response();
$response->headers->set('Content-Type', 'text/xml; charset=ISO-8859-1');
@@ -121,7 +89,7 @@ Take note of the calls to ``ob_start()`` and ``ob_get_clean()``. These
methods control `output buffering`_ which allows you to "trap" the echoed
output of ``$server->handle()``. This is necessary because Symfony expects
your controller to return a ``Response`` object with the output as its "content".
-You must also remember to set the "Content-Type" header to "text/xml", as
+You must also remember to set the ``"Content-Type"`` header to ``"text/xml"``, as
this is what the client will expect. So, you use ``ob_start()`` to start
buffering the STDOUT and use ``ob_get_clean()`` to dump the echoed output
into the content of the Response and clear the output buffer. Finally, you're
@@ -133,7 +101,7 @@ route ``/soap``::
$soapClient = new \SoapClient('http://example.com/app.php/soap?wsdl');
- $result = $soapClient->call('hello', array('name' => 'Scott'));
+ $result = $soapClient->call('hello', ['name' => 'Scott']);
An example WSDL is below.
@@ -144,7 +112,7 @@ An example WSDL is below.
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
- xmlns:tns="urn:arnleadservicewsdl"
+ xmlns:tns="urn:helloservicewsdl"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns="http://schemas.xmlsoap.org/wsdl/"
@@ -152,17 +120,17 @@ An example WSDL is below.
-
-
+
+
-
+
-
+
@@ -192,7 +160,7 @@ An example WSDL is below.
-
+
diff --git a/controller/upload_file.rst b/controller/upload_file.rst
index 3cbd3e83265..8b16bc7c499 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/AppBundle/Entity/Product.php
namespace AppBundle\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/AppBundle/Form/ProductType.php
namespace AppBundle\Form;
use AppBundle\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,16 +67,38 @@ Then, add a new ``brochure`` field to the form that manages the ``Product`` enti
{
$builder
// ...
- ->add('brochure', FileType::class, array('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
+ // every time 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',
+ ])
+ ],
+ ])
// ...
;
}
public function configureOptions(OptionsResolver $resolver)
{
- $resolver->setDefaults(array(
+ $resolver->setDefaults([
'data_class' => Product::class,
- ));
+ ]);
}
}
@@ -99,12 +122,13 @@ Finally, you need to update the code of the controller that handles the form::
// src/AppBundle/Controller/ProductController.php
namespace AppBundle\Controller;
- use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
+ use AppBundle\Entity\Product;
+ use AppBundle\Form\ProductType;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
+ use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
- use AppBundle\Entity\Product;
- use AppBundle\Form\ProductType;
+ use Symfony\Component\Routing\Annotation\Route;
class ProductController extends Controller
{
@@ -118,44 +142,40 @@ 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->get('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'));
+ return $this->redirectToRoute('app_product_list');
}
- return $this->render('product/new.html.twig', array(
+ return $this->render('product/new.html.twig', [
'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());
+ ]);
}
}
@@ -168,13 +188,10 @@ controller to specify the directory in which the brochures should be stored:
# ...
parameters:
- brochures_directory: '%kernel.root_dir%/../web/uploads/brochures'
+ brochures_directory: '%kernel.project_dir%/web/uploads/brochures'
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;
@@ -193,7 +210,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::
@@ -206,8 +223,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
@@ -216,8 +233,8 @@ Creating an Uploader Service
To avoid logic in controllers, making them big, you can extract the upload
logic to a separate service::
- // src/AppBundle/FileUploader.php
- namespace AppBundle;
+ // src/AppBundle/Service/FileUploader.php
+ namespace AppBundle\Service;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Symfony\Component\HttpFoundation\File\UploadedFile;
@@ -233,7 +250,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);
@@ -259,49 +278,52 @@ Then, define a service for this class:
# app/config/services.yml
services:
# ...
- app.brochure_uploader:
- class: AppBundle\FileUploader
- arguments: ['%brochures_directory%']
+
+ AppBundle\Service\FileUploader:
+ arguments:
+ $targetDirectory: '%brochures_directory%'
.. code-block:: xml
-
+
+ https://symfony.com/schema/dic/services/services-1.0.xsd">
-
+ %brochures_directory%
-
.. code-block:: php
// app/config/services.php
- use AppBundle\FileUploader;
+ use AppBundle\Service\FileUploader;
- // ...
- $container->register('app.brochure_uploader', FileUploader::class)
- ->addArgument('%brochures_directory%');
+ $container->autowire(FileUploader::class)
+ ->setArgument('$targetDirectory', '%brochures_directory%');
Now you're ready to use this service in the controller::
// src/AppBundle/Controller/ProductController.php
+ use AppBundle\Service\FileUploader;
+ use Symfony\Component\HttpFoundation\Request;
// ...
- public function newAction(Request $request)
+ public function newAction(Request $request, FileUploader $fileUploader)
{
// ...
if ($form->isSubmitted() && $form->isValid()) {
- $file = $product->getBrochure();
- $fileName = $this->get('app.brochure_uploader')->upload($file);
-
- $product->setBrochure($fileName);
+ /** @var UploadedFile $brochureFile */
+ $brochureFile = $form->get('brochure')->getData();
+ if ($brochureFile) {
+ $brochureFileName = $fileUploader->upload($brochureFile);
+ $product->setBrochureFilename($brochureFileName);
+ }
// ...
}
@@ -312,150 +334,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/AppBundle/EventListener/BrochureUploadListener.php
- namespace AppBundle\EventListener;
-
- use Symfony\Component\HttpFoundation\File\UploadedFile;
- use Symfony\Component\HttpFoundation\File\File;
- use Doctrine\ORM\Event\LifecycleEventArgs;
- use Doctrine\ORM\Event\PreUpdateEventArgs;
- use AppBundle\Entity\Product;
- use AppBundle\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
-
- # app/config/services.yml
- services:
- # ...
- app.doctrine_brochure_listener:
- class: AppBundle\EventListener\BrochureUploadListener
- arguments: ['@app.brochure_uploader']
- 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
-
- // app/config/services.php
- use AppBundle\EventListener\BrochureUploaderListener;
- use Symfony\Component\DependencyInjection\Reference;
-
- // ...
- $container->register('app.doctrine_brochure_listener', BrochureUploaderListener::class)
- ->addArgument(new Reference('app.brochure_uploader'))
- ->addTag('doctrine.event_listener', array(
- 'event' => 'prePersist',
- ))
- ->addTag('doctrine.event_listener', array(
- 'event' => 'prePersist',
- ));
-
-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->getTargetDir().'/'.$fileName));
- }
- }
- }
+Moreover, Doctrine listeners are often dependent on internal Doctrine behavior
+which may change in future versions. Also, they can introduce performance issues
+unwillingly (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 b4bbf19a703..97292d22843 100644
--- a/create_framework/dependency_injection.rst
+++ b/create_framework/dependency_injection.rst
@@ -9,9 +9,11 @@ to it::
// example.com/src/Simplex/Framework.php
namespace Simplex;
- use Symfony\Component\Routing;
- use Symfony\Component\HttpKernel;
use Symfony\Component\EventDispatcher\EventDispatcher;
+ use Symfony\Component\HttpFoundation;
+ use Symfony\Component\HttpFoundation\RequestStack;
+ use Symfony\Component\HttpKernel;
+ use Symfony\Component\Routing;
class Framework extends HttpKernel\HttpKernel
{
@@ -19,13 +21,16 @@ to it::
{
$context = new Routing\RequestContext();
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);
- $resolver = new HttpKernel\Controller\ControllerResolver();
+ $requestStack = new RequestStack();
+
+ $controllerResolver = new HttpKernel\Controller\ControllerResolver();
+ $argumentResolver = new HttpKernel\Controller\ArgumentResolver();
$dispatcher = new EventDispatcher();
- $dispatcher->addSubscriber(new HttpKernel\EventListener\RouterListener($matcher));
+ $dispatcher->addSubscriber(new HttpKernel\EventListener\RouterListener($matcher, $requestStack));
$dispatcher->addSubscriber(new HttpKernel\EventListener\ResponseListener('UTF-8'));
- parent::__construct($dispatcher, $resolver);
+ parent::__construct($dispatcher, $controllerResolver, $requestStack, $argumentResolver);
}
}
@@ -61,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).
@@ -92,38 +97,44 @@ 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);
$containerBuilder->register('matcher', Routing\Matcher\UrlMatcher::class)
- ->setArguments(array($routes, new Reference('context')))
+ ->setArguments([$routes, new Reference('context')])
;
$containerBuilder->register('request_stack', HttpFoundation\RequestStack::class);
- $containerBuilder->register('resolver', HttpKernel\Controller\ControllerResolver::class);
+ $containerBuilder->register('controller_resolver', HttpKernel\Controller\ControllerResolver::class);
+ $containerBuilder->register('argument_resolver', HttpKernel\Controller\ArgumentResolver::class);
$containerBuilder->register('listener.router', HttpKernel\EventListener\RouterListener::class)
- ->setArguments(array(new Reference('matcher'), new Reference('request_stack')))
+ ->setArguments([new Reference('matcher'), new Reference('request_stack')])
;
$containerBuilder->register('listener.response', HttpKernel\EventListener\ResponseListener::class)
- ->setArguments(array('UTF-8'))
+ ->setArguments(['UTF-8'])
;
$containerBuilder->register('listener.exception', HttpKernel\EventListener\ExceptionListener::class)
- ->setArguments(array('Calendar\Controller\ErrorController::exceptionAction'))
+ ->setArguments(['Calendar\Controller\ErrorController::exceptionAction'])
;
$containerBuilder->register('dispatcher', EventDispatcher\EventDispatcher::class)
- ->addMethodCall('addSubscriber', array(new Reference('listener.router')))
- ->addMethodCall('addSubscriber', array(new Reference('listener.response')))
- ->addMethodCall('addSubscriber', array(new Reference('listener.exception')))
+ ->addMethodCall('addSubscriber', [new Reference('listener.router')])
+ ->addMethodCall('addSubscriber', [new Reference('listener.response')])
+ ->addMethodCall('addSubscriber', [new Reference('listener.exception')])
;
$containerBuilder->register('framework', Framework::class)
- ->setArguments(array(new Reference('dispatcher'), new Reference('resolver')))
+ ->setArguments([
+ new Reference('dispatcher'),
+ new Reference('controller_resolver'),
+ new Reference('request_stack'),
+ new Reference('argument_resolver'),
+ ])
;
return $containerBuilder;
@@ -187,7 +198,7 @@ Now, here is how you can register a custom listener in the front controller::
$container->register('listener.string_response', StringResponseListener::class);
$container->getDefinition('dispatcher')
- ->addMethodCall('addSubscriber', array(new Reference('listener.string_response')))
+ ->addMethodCall('addSubscriber', [new Reference('listener.string_response')])
;
Beside describing your objects, the dependency injection container can also be
@@ -203,7 +214,7 @@ charset configurable::
// ...
$container->register('listener.response', HttpKernel\EventListener\ResponseListener::class)
- ->setArguments(array('%charset%'))
+ ->setArguments(['%charset%'])
;
After this change, you must set the charset before using the response listener
@@ -216,16 +227,16 @@ Instead of relying on the convention that the routes are defined by the
// ...
$container->register('matcher', Routing\Matcher\UrlMatcher::class)
- ->setArguments(array('%routes%', new Reference('context')))
+ ->setArguments(['%routes%', new Reference('context')])
;
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 scope support to dumping a container to a plain PHP class,
+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
and is able to manage any kind of PHP class.
@@ -242,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 f740a8d39a6..4adea3cd058 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"
@@ -36,24 +35,27 @@ the Response instance::
// example.com/src/Simplex/Framework.php
namespace Simplex;
+ use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
- use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
- use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+ use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface;
use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
- use Symfony\Component\EventDispatcher\EventDispatcher;
+ use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+ use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
class Framework
{
- private $matcher;
- private $resolver;
private $dispatcher;
+ private $matcher;
+ private $controllerResolver;
+ private $argumentResolver;
- public function __construct(EventDispatcher $dispatcher, UrlMatcherInterface $matcher, ControllerResolverInterface $resolver)
+ public function __construct(EventDispatcher $dispatcher, UrlMatcherInterface $matcher, ControllerResolverInterface $controllerResolver, ArgumentResolverInterface $argumentResolver)
{
- $this->matcher = $matcher;
- $this->resolver = $resolver;
$this->dispatcher = $dispatcher;
+ $this->matcher = $matcher;
+ $this->controllerResolver = $controllerResolver;
+ $this->argumentResolver = $argumentResolver;
}
public function handle(Request $request)
@@ -63,8 +65,8 @@ the Response instance::
try {
$request->attributes->add($this->matcher->match($request->getPathInfo()));
- $controller = $this->resolver->getController($request);
- $arguments = $this->resolver->getArguments($request, $controller);
+ $controller = $this->controllerResolver->getController($request);
+ $arguments = $this->argumentResolver->getArguments($request, $controller);
$response = call_user_func_array($controller, $arguments);
} catch (ResourceNotFoundException $exception) {
@@ -86,9 +88,9 @@ now dispatched::
// example.com/src/Simplex/ResponseEvent.php
namespace Simplex;
+ use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
- use Symfony\Component\EventDispatcher\Event;
class ResponseEvent extends Event
{
@@ -136,7 +138,10 @@ the registration of a listener for the ``response`` event::
$response->setContent($response->getContent().'GA CODE');
});
- $framework = new Simplex\Framework($dispatcher, $matcher, $resolver);
+ $controllerResolver = new ControllerResolver();
+ $argumentResolver = new ArgumentResolver();
+
+ $framework = new Simplex\Framework($dispatcher, $matcher, $controllerResolver, $argumentResolver);
$response = $framework->handle($request);
$response->send();
@@ -234,16 +239,16 @@ And do the same with the other listener::
Our front controller should now look like the following::
$dispatcher = new EventDispatcher();
- $dispatcher->addListener('response', array(new Simplex\ContentLengthListener(), 'onResponse'), -255);
- $dispatcher->addListener('response', array(new Simplex\GoogleListener(), 'onResponse'));
+ $dispatcher->addListener('response', [new Simplex\ContentLengthListener(), 'onResponse'], -255);
+ $dispatcher->addListener('response', [new Simplex\GoogleListener(), 'onResponse']);
Even if the code is now nicely wrapped in classes, there is still a slight
issue: the knowledge of the priorities is "hardcoded" in the front controller,
instead of being in the listeners themselves. For each application, you have
to remember to set the appropriate priorities. Moreover, the listener method
names are also exposed here, which means that refactoring our listeners would
-mean changing all the applications that rely on those listeners. Of course,
-there is a solution: use subscribers instead of listeners::
+mean changing all the applications that rely on those listeners. But there is a
+solution: use subscribers instead of listeners::
$dispatcher = new EventDispatcher();
$dispatcher->addSubscriber(new Simplex\ContentLengthListener());
@@ -264,7 +269,7 @@ look at the new version of the ``GoogleListener``::
public static function getSubscribedEvents()
{
- return array('response' => 'onResponse');
+ return ['response' => 'onResponse'];
}
}
@@ -281,7 +286,7 @@ And here is the new version of ``ContentLengthListener``::
public static function getSubscribedEvents()
{
- return array('response' => array('onResponse', -255));
+ return ['response' => ['onResponse', -255]];
}
}
@@ -296,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 f11e8fde69b..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 easily 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::
@@ -80,10 +80,10 @@ Such a script might look like the following::
$request = Request::createFromGlobals();
$response = new Response();
- $map = array(
+ $map = [
'/hello' => __DIR__.'/hello.php',
'/bye' => __DIR__.'/bye.php',
- );
+ ];
$path = $request->getPathInfo();
if (isset($map[$path])) {
@@ -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::
@@ -190,7 +190,7 @@ And the ``hello.php`` script can now be converted to a template::
get('name', 'World') ?>
- Hello
+ Hello = htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?>
We have the first version of our framework::
@@ -203,10 +203,10 @@ We have the first version of our framework::
$request = Request::createFromGlobals();
$response = new Response();
- $map = array(
+ $map = [
'/hello' => __DIR__.'/../src/pages/hello.php',
'/bye' => __DIR__.'/../src/pages/bye.php',
- );
+ ];
$path = $request->getPathInfo();
if (isset($map[$path])) {
@@ -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 d54990def79..b86ac80ada5 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::
@@ -201,7 +200,7 @@ You can also simulate a request::
$request = Request::create('/index.php?name=Fabien');
-With the ``Response`` class, you can easily tweak the response::
+With the ``Response`` class, you can tweak the response::
$response = new Response();
@@ -256,7 +255,7 @@ code in production without a proxy, it becomes trivially easy to abuse your
system. That's not the case with the ``getClientIp()`` method as you must
explicitly trust your reverse proxies by calling ``setTrustedProxies()``::
- Request::setTrustedProxies(array('10.0.0.1'));
+ Request::setTrustedProxies(['10.0.0.1']);
if ($myIp === $request->getClientIp()) {
// the client is a known one, so give it some more privilege
@@ -271,7 +270,7 @@ cases by yourself. Why not using a technology that already works?
.. note::
If you want to learn more about the HttpFoundation component, you can have
- a look at the :namespace:`Symfony\\Component\\HttpFoundation` API or read
+ a look at the ``Symfony\Component\HttpFoundation`` API or read
its dedicated :doc:`documentation `.
Believe or not but we have our first framework. You can stop now if you want.
@@ -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 8c0e5a041cb..20c7e6ff399 100644
--- a/create_framework/http_kernel_controller_resolver.rst
+++ b/create_framework/http_kernel_controller_resolver.rst
@@ -22,13 +22,13 @@ class::
Update the route definition accordingly::
- $routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', array(
+ $routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', [
'year' => null,
- '_controller' => array(new LeapYearController(), 'indexAction'),
- )));
+ '_controller' => [new LeapYearController(), 'indexAction'],
+ ]));
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
@@ -43,10 +43,10 @@ component:
$ composer require symfony/http-kernel
-The HttpKernel component has many interesting features, but the one we need
-right now is the *controller resolver*. A controller resolver knows how to
-determine the controller to execute and the arguments to pass to it, based on
-a Request object. All controller resolvers implement the following interface::
+The HttpKernel component has many interesting features, but the ones we need
+right now are the *controller resolver* and *argument resolver*. A controller resolver knows how to
+determine the controller to execute and the argument resolver determines the arguments to pass to it,
+based on a Request object. All controller resolvers implement the following interface::
namespace Symfony\Component\HttpKernel\Controller;
@@ -58,26 +58,35 @@ a Request object. All controller resolvers implement the following interface::
function getArguments(Request $request, $controller);
}
+.. caution::
+
+ The ``getArguments()`` method is deprecated as of Symfony 3.1. and will be
+ removed in 4.0. You can use the
+ :class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver` which
+ uses the :class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolverInterface`
+ instead.
+
The ``getController()`` method relies on the same convention as the one we
have defined earlier: the ``_controller`` request attribute must contain the
controller associated with the Request. Besides the built-in PHP callbacks,
``getController()`` also supports strings composed of a class name followed by
two colons and a method name as a valid callback, like 'class::method'::
- $routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', array(
+ $routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', [
'year' => null,
'_controller' => 'LeapYearController::indexAction',
- )));
+ ]));
To make this code work, modify the framework code to use the controller
resolver from HttpKernel::
use Symfony\Component\HttpKernel;
- $resolver = new HttpKernel\Controller\ControllerResolver();
+ $controllerResolver = new HttpKernel\Controller\ControllerResolver();
+ $argumentResolver = new HttpKernel\Controller\ArgumentResolver();
- $controller = $resolver->getController($request);
- $arguments = $resolver->getArguments($request, $controller);
+ $controller = $controllerResolver->getController($request);
+ $arguments = $argumentResolver->getArguments($request, $controller);
$response = call_user_func_array($controller, $arguments);
@@ -133,21 +142,19 @@ Let's just inject the ``$year`` request attribute for our controller::
}
}
-The controller resolver also takes care of validating the controller callable
-and its arguments. In case of a problem, it throws an exception with a nice
-message explaining the problem (the controller class does not exist, the
-method is not defined, an argument has no matching attribute, ...).
+The resolvers also take care of validating the controller callable and its
+arguments. In case of a problem, it throws an exception with a nice message
+explaining the problem (the controller class does not exist, the method is not
+defined, an argument has no matching attribute, ...).
.. note::
- With the great flexibility of the default controller resolver, you might
- wonder why someone would want to create another one (why would there be an
- interface if not?). Two examples: in Symfony, ``getController()`` is
- enhanced to support
- :doc:`controllers as services `; and in
- `FrameworkExtraBundle`_, ``getArguments()`` is enhanced to support
- parameter converters, where request attributes are converted to objects
- automatically.
+ With the great flexibility of the default controller resolver and argument
+ resolver, you might wonder why someone would want to create another one
+ (why would there be an interface if not?). Two examples: in Symfony,
+ ``getController()`` is enhanced to support :doc:`controllers as services `;
+ and ``getArguments()`` provides an extension point to alter or enhance
+ the resolving of arguments.
Let's conclude with the new version of our framework::
@@ -156,8 +163,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)
{
@@ -174,13 +181,15 @@ Let's conclude with the new version of our framework::
$context = new Routing\RequestContext();
$context->fromRequest($request);
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);
- $resolver = new HttpKernel\Controller\ControllerResolver();
+
+ $controllerResolver = new HttpKernel\Controller\ControllerResolver();
+ $argumentResolver = new HttpKernel\Controller\ArgumentResolver();
try {
$request->attributes->add($matcher->match($request->getPathInfo()));
- $controller = $resolver->getController($request);
- $arguments = $resolver->getArguments($request, $controller);
+ $controller = $controllerResolver->getController($request);
+ $arguments = $argumentResolver->getArguments($request, $controller);
$response = call_user_func_array($controller, $arguments);
} catch (Routing\Exception\ResourceNotFoundException $exception) {
@@ -192,7 +201,6 @@ Let's conclude with the new version of our framework::
$response->send();
Think about it once more: our framework is more robust and more flexible than
-ever and it still has less than 40 lines of code.
+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 05d289a619a..056ec134802 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?
@@ -36,24 +36,27 @@ And the new front controller::
// example.com/web/front.php
require_once __DIR__.'/../vendor/autoload.php';
+ use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\Request;
+ use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
- use Symfony\Component\Routing;
use Symfony\Component\HttpKernel;
- use Symfony\Component\EventDispatcher\EventDispatcher;
- use Symfony\Component\HttpFoundation\RequestStack;
+ use Symfony\Component\Routing;
$request = Request::createFromGlobals();
+ $requestStack = new RequestStack();
$routes = include __DIR__.'/../src/app.php';
$context = new Routing\RequestContext();
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);
- $resolver = new HttpKernel\Controller\ControllerResolver();
+
+ $controllerResolver = new HttpKernel\Controller\ControllerResolver();
+ $argumentResolver = new HttpKernel\Controller\ArgumentResolver();
$dispatcher = new EventDispatcher();
- $dispatcher->addSubscriber(new HttpKernel\EventListener\RouterListener($matcher, new RequestStack()));
+ $dispatcher->addSubscriber(new HttpKernel\EventListener\RouterListener($matcher, $requestStack));
- $framework = new Simplex\Framework($dispatcher, $resolver);
+ $framework = new Simplex\Framework($dispatcher, $controllerResolver, $requestStack, $argumentResolver);
$response = $framework->handle($request);
$response->send();
@@ -88,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
{
@@ -101,8 +104,8 @@ The error controller reads as follows::
}
}
-Voilà! Clean and customizable error management without efforts. And of course,
-if your controller throws an exception, HttpKernel will handle it nicely.
+*Voilà!* Clean and customizable error management without efforts. And if your
+controller 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
@@ -111,9 +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? Just subscribe to
-``StreamedResponseListener``::
+If you want out of the box support for streamed responses, subscribe
+to ``StreamedResponseListener``::
$dispatcher->addSubscriber(new HttpKernel\EventListener\StreamedResponseListener());
@@ -153,7 +155,6 @@ only if needed::
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
- use Symfony\Component\HttpKernel\KernelEvents;
class StringResponseListener implements EventSubscriberInterface
{
@@ -168,7 +169,7 @@ only if needed::
public static function getSubscribedEvents()
{
- return array(KernelEvents::VIEW => 'onView');
+ return ['kernel.view' => 'onView'];
}
}
diff --git a/create_framework/http_kernel_httpkernelinterface.rst b/create_framework/http_kernel_httpkernelinterface.rst
index 310f4619a1a..3ec76549b2e 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
@@ -54,7 +54,11 @@ PHP; it implements ``HttpKernelInterface`` and wraps another
``HttpKernelInterface`` instance::
// example.com/web/front.php
- $framework = new Simplex\Framework($dispatcher, $matcher, $resolver);
+
+ // ...
+ use Symfony\Component\HttpKernel;
+
+ $framework = new Simplex\Framework($dispatcher, $matcher, $controllerResolver, $argumentResolver);
$framework = new HttpKernel\HttpCache\HttpCache(
$framework,
new HttpKernel\HttpCache\Store(__DIR__.'/../cache')
@@ -87,11 +91,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
- easily 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::
@@ -111,18 +114,18 @@ comfortable with these concepts, read the `HTTP caching`_ chapter of the
Symfony documentation.
The Response class contains many other methods that let you configure the
-HTTP cache very easily. One of the most powerful is ``setCache()`` as it
-abstracts the most frequently used caching strategies into one simple array::
+HTTP cache. One of the most powerful is ``setCache()`` as it abstracts the
+most frequently used caching strategies into one array::
$date = date_create_from_format('Y-m-d H:i:s', '2005-10-15 10:00:00');
- $response->setCache(array(
+ $response->setCache([
'public' => true,
'etag' => 'abcde',
'last_modified' => $date,
'max_age' => 10,
's_maxage' => 10,
- ));
+ ]);
// it is equivalent to the following code
$response->setPublic();
@@ -132,8 +135,8 @@ abstracts the most frequently used caching strategies into one simple array::
$response->setSharedMaxAge(10);
When using the validation model, the ``isNotModified()`` method allows you to
-easily 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');
@@ -155,7 +158,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
@@ -183,7 +186,7 @@ ease debugging, you can enable the debug mode::
$framework,
new HttpKernel\HttpCache\Store(__DIR__.'/../cache'),
new HttpKernel\HttpCache\Esi(),
- array('debug' => true)
+ ['debug' => true]
);
The debug mode adds a ``X-Symfony-Cache`` header to each response that
diff --git a/create_framework/introduction.rst b/create_framework/introduction.rst
index e761ae5de13..11948890bba 100644
--- a/create_framework/introduction.rst
+++ b/create_framework/introduction.rst
@@ -38,10 +38,9 @@ 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 of course, each step will be the occasion to learn more about some of the
-Symfony Components.
+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
+Many modern web frameworks advertise themselves as being MVC frameworks. This
tutorial won't talk about the MVC pattern, as the Symfony Components are able to
create any type of frameworks, not just the ones that follow the MVC
architecture. Anyway, if you have a look at the MVC semantics, this book is
@@ -62,8 +61,8 @@ Before You Start
Reading about how to create a framework is not enough. You will have to follow
along and actually type all the examples included in this tutorial. For that,
-you need a recent version of PHP (5.3.9 or later is good enough), a web server
-(like Apache, NGinx or PHP's built-in web server), a good knowledge of PHP and
+you need a recent version of PHP (5.5.9 or later is good enough), a web server
+(like Apache, nginx or PHP's built-in web server), a good knowledge of PHP and
an understanding of Object Oriented programming.
Ready to go? Read on!
@@ -87,7 +86,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 +100,17 @@ start with the simplest web application we can think of in PHP::
printf('Hello %s', $name);
-If you have PHP 5.4, 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 deaf9a02420..717ed1068fb 100644
--- a/create_framework/routing.rst
+++ b/create_framework/routing.rst
@@ -12,10 +12,10 @@ framework just a little to make templates even more readable::
$request = Request::createFromGlobals();
- $map = array(
+ $map = [
'/hello' => 'hello',
'/bye' => 'bye',
- );
+ ];
$path = $request->getPathInfo();
if (isset($map[$path])) {
@@ -33,7 +33,7 @@ As we now extract the request query parameters, simplify the ``hello.php``
template as follows::
- Hello
+ Hello = htmlspecialchars(isset($name) ? $name : 'World', ENT_QUOTES, 'UTF-8') ?>
Now, we are in good shape to add new features.
@@ -61,27 +61,27 @@ one for the simple ``/bye`` one::
use Symfony\Component\Routing\Route;
- $routes->add('hello', new Route('/hello/{name}', array('name' => 'World')));
+ $routes->add('hello', new Route('/hello/{name}', ['name' => 'World']));
$routes->add('bye', new Route('/bye'));
Each entry in the collection is defined by a name (``hello``) and a ``Route``
instance, which is defined by a route pattern (``/hello/{name}``) and an array
-of default values for route attributes (``array('name' => 'World')``).
+of default values for route attributes (``['name' => 'World']``).
.. note::
Read the
:doc:`Routing component documentation ` to
learn more about its many features like URL generation, attribute
- requirements, HTTP method enforcements, loaders for YAML or XML files,
+ requirements, HTTP method enforcement, loaders for YAML or XML files,
dumpers to PHP or Apache rewrite rules for enhanced performance and much
more.
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
+ // 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}', array('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,13 +189,13 @@ 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? Insanely easy::
+impact. You can use the generator this way::
use Symfony\Component\Routing;
$generator = new Routing\Generator\UrlGenerator($routes, $context);
- echo $generator->generate('hello', array('name' => 'Fabien'));
+ echo $generator->generate('hello', ['name' => 'Fabien']);
// outputs /hello/Fabien
The code should be self-explanatory; and thanks to the context, you can even
@@ -205,7 +205,7 @@ generate absolute URLs::
echo $generator->generate(
'hello',
- array('name' => 'Fabien'),
+ ['name' => 'Fabien'],
UrlGeneratorInterface::ABSOLUTE_URL
);
// outputs something like http://example.com/somewhere/hello/Fabien
diff --git a/create_framework/separation_of_concerns.rst b/create_framework/separation_of_concerns.rst
index a3c1265697e..3af0e98dd46 100644
--- a/create_framework/separation_of_concerns.rst
+++ b/create_framework/separation_of_concerns.rst
@@ -2,7 +2,7 @@ The Separation of Concerns
==========================
One down-side of our framework right now is that we need to copy and paste the
-code in ``front.php`` each time we create a new website. 40 lines of code is
+code in ``front.php`` each time we create a new website. 60 lines of code is
not that much, but it would be nice if we could wrap this code into a proper
class. It would bring us better *reusability* and easier testing to name just
a few benefits.
@@ -13,26 +13,29 @@ 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;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
- use Symfony\Component\Routing\Matcher\UrlMatcher;
- use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+ use Symfony\Component\HttpKernel\Controller\ArgumentResolver;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
+ use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+ use Symfony\Component\Routing\Matcher\UrlMatcher;
class Framework
{
protected $matcher;
- protected $resolver;
+ protected $controllerResolver;
+ protected $argumentResolver;
- public function __construct(UrlMatcher $matcher, ControllerResolver $resolver)
+ public function __construct(UrlMatcher $matcher, ControllerResolver $controllerResolver, ArgumentResolver $argumentResolver)
{
$this->matcher = $matcher;
- $this->resolver = $resolver;
+ $this->controllerResolver = $controllerResolver;
+ $this->argumentResolver = $argumentResolver;
}
public function handle(Request $request)
@@ -42,8 +45,8 @@ request handling logic into its own ``Simplex\\Framework`` class::
try {
$request->attributes->add($this->matcher->match($request->getPathInfo()));
- $controller = $this->resolver->getController($request);
- $arguments = $this->resolver->getArguments($request, $controller);
+ $controller = $this->controllerResolver->getController($request);
+ $arguments = $this->argumentResolver->getArguments($request, $controller);
return call_user_func_array($controller, $arguments);
} catch (ResourceNotFoundException $exception) {
@@ -64,9 +67,11 @@ And update ``example.com/web/front.php`` accordingly::
$context = new Routing\RequestContext();
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);
- $resolver = new HttpKernel\Controller\ControllerResolver();
- $framework = new Simplex\Framework($matcher, $resolver);
+ $controllerResolver = new ControllerResolver();
+ $argumentResolver = new ArgumentResolver();
+
+ $framework = new Simplex\Framework($matcher, $controllerResolver, $argumentResolver);
$response = $framework->handle($request);
$response->send();
@@ -95,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
{
@@ -131,10 +136,10 @@ And move the ``is_leap_year()`` function to its own class too::
Don't forget to update the ``example.com/src/app.php`` file accordingly::
- $routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', array(
+ $routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', [
'year' => null,
'_controller' => 'Calendar\Controller\LeapYearController::indexAction',
- )));
+ ]));
To sum up, here is the new file layout:
@@ -158,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
@@ -166,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 a00f5b352fb..30dbf992110 100644
--- a/create_framework/templating.rst
+++ b/create_framework/templating.rst
@@ -55,10 +55,10 @@ controller... your choice.
As a convention, for each route, the associated controller is configured via
the ``_controller`` route attribute::
- $routes->add('hello', new Routing\Route('/hello/{name}', array(
+ $routes->add('hello', new Routing\Route('/hello/{name}', [
'name' => 'World',
'_controller' => 'render_template',
- )));
+ ]));
try {
$request->attributes->add($matcher->match($request->getPathInfo()));
@@ -69,20 +69,20 @@ the ``_controller`` route attribute::
$response = new Response('An error occurred', 500);
}
-A route can now be associated with any controller and of course, 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}', array(
+ $routes->add('hello', new Routing\Route('/hello/{name}', [
'name' => 'World',
'_controller' => function ($request) {
return render_template($request);
}
- )));
+ ]));
This is rather flexible as you can change the Response object afterwards and
you can even pass additional arguments to the template::
- $routes->add('hello', new Routing\Route('/hello/{name}', array(
+ $routes->add('hello', new Routing\Route('/hello/{name}', [
'name' => 'World',
'_controller' => function ($request) {
// $foo will be available in the template
@@ -95,7 +95,7 @@ you can even pass additional arguments to the template::
return $response;
}
- )));
+ ]));
Here is the updated and improved version of our framework::
@@ -142,8 +142,8 @@ framework does not need to be modified in any way, just 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) {
@@ -154,7 +154,7 @@ framework does not need to be modified in any way, just create a new
}
$routes = new Routing\RouteCollection();
- $routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', array(
+ $routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', [
'year' => null,
'_controller' => function ($request) {
if (is_leap_year($request->attributes->get('year'))) {
@@ -163,7 +163,7 @@ framework does not need to be modified in any way, just create a new
return new Response('Nope, this is not a leap year.');
}
- )));
+ ]));
return $routes;
diff --git a/create_framework/unit_testing.rst b/create_framework/unit_testing.rst
index 9848cc93f65..ca406e1365f 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;
}
// ...
@@ -75,6 +78,7 @@ We are now ready to write our first test::
use PHPUnit\Framework\TestCase;
use Simplex\Framework;
use Symfony\Component\HttpFoundation\Request;
+ use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface;
use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
use Symfony\Component\Routing;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
@@ -106,9 +110,10 @@ We are now ready to write our first test::
->method('getContext')
->will($this->returnValue($this->createMock(Routing\RequestContext::class)))
;
- $resolver = $this->createMock(ControllerResolverInterface::class);
+ $controllerResolver = $this->createMock(ControllerResolverInterface::class);
+ $argumentResolver = $this->createMock(ArgumentResolverInterface::class);
- return new Framework($matcher, $resolver);
+ return new Framework($matcher, $controllerResolver, $argumentResolver);
}
}
@@ -131,7 +136,7 @@ Executing this test is as simple as running ``phpunit`` from the
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 is just as easy::
+Adding a unit test for any exception thrown in a controller::
public function testErrorHandling()
{
@@ -146,6 +151,7 @@ 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\ArgumentResolver;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
// ...
@@ -158,22 +164,23 @@ Response::
$matcher
->expects($this->once())
->method('match')
- ->will($this->returnValue(array(
+ ->will($this->returnValue([
'_route' => 'foo',
'name' => 'Fabien',
'_controller' => function ($name) {
return new Response('Hello '.$name);
}
- )))
+ ]))
;
$matcher
->expects($this->once())
->method('getContext')
->will($this->returnValue($this->createMock(Routing\RequestContext::class)))
;
- $resolver = new ControllerResolver();
+ $controllerResolver = new ControllerResolver();
+ $argumentResolver = new ArgumentResolver();
- $framework = new Framework($matcher, $resolver);
+ $framework = new Framework($matcher, $controllerResolver, $argumentResolver);
$response = $framework->handle(new Request());
diff --git a/debug/debugging.rst b/debug/debugging.rst
index 246da9c3380..c345ebf8acc 100644
--- a/debug/debugging.rst
+++ b/debug/debugging.rst
@@ -30,22 +30,21 @@ The ``app_dev.php`` front controller reads as follows by default::
// ...
- $loader = require_once __DIR__.'/../app/bootstrap.php.cache';
- require_once __DIR__.'/../app/AppKernel.php';
+ $loader = require __DIR__.'/../app/autoload.php';
+ Debug::enable();
$kernel = new AppKernel('dev', true);
$kernel->loadClassCache();
$request = Request::createFromGlobals();
+ // ...
To make your debugger happier, disable the loading of all PHP class caches
-by removing the call to ``loadClassCache()`` and by replacing the require
-statements like below::
+by removing the call to ``loadClassCache()``::
// ...
- // $loader = require_once __DIR__.'/../app/bootstrap.php.cache';
$loader = require_once __DIR__.'/../app/autoload.php';
- require_once __DIR__.'/../app/AppKernel.php';
+ Debug::enable();
$kernel = new AppKernel('dev', true);
// $kernel->loadClassCache();
@@ -62,6 +61,12 @@ cache files, or you can change the extension used by Symfony for these files::
$kernel->loadClassCache('classes', '.php.cache');
+.. deprecated:: 3.3
+
+ The ``loadClassCache()`` was deprecated in Symfony 3.3 and removed in
+ Symfony 4.0. No alternative is provided because this feature is useless
+ when using PHP 7 or higher.
+
Useful Debugging Commands
-------------------------
@@ -69,6 +74,10 @@ When developing a large application, it can be hard to keep track of all the
different services, routes and translations. Luckily, Symfony has some commands
that can help you visualize and find the information.
+``about``
+ Shows information about the current project, such as the Symfony version,
+ the Kernel and PHP.
+
``debug:container``
Displays information about the contents of the Symfony container for all public
services. To find only those matching a name, append the name as an argument.
@@ -76,6 +85,9 @@ that can help you visualize and find the information.
``debug:config``
Shows all configured bundles, their class and their alias.
+``debug:form``
+ Displays information about form types and their options.
+
``debug:event-dispatcher``
Displays information about all the registered listeners in the event dispatcher.
diff --git a/deployment.rst b/deployment.rst
index 0c3c19f4a37..6a92e4df9b1 100644
--- a/deployment.rst
+++ b/deployment.rst
@@ -79,7 +79,7 @@ There are also tools to help ease the pain of deployment. Some of them have been
specifically tailored to the requirements of Symfony.
`EasyDeployBundle`_
- A Symfony bundle that adds easy deploy tools to your application.
+ A Symfony bundle that adds deploy tools to your application.
`Deployer`_
This is another native PHP rewrite of Capistrano, with some ready recipes for
@@ -101,13 +101,6 @@ specifically tailored to the requirements of Symfony.
`Symfony plugin`_ is a plugin to ease Symfony related tasks, inspired by `Capifony`_
(which works only with Capistrano 2).
-`sf2debpkg`_
- Helps you build a native Debian package for your Symfony project.
-
-Basic scripting
- You can of course use shell, `Ant`_ or any other build tool to script
- the deploying of your project.
-
Common Post-Deployment Tasks
----------------------------
@@ -117,11 +110,12 @@ you'll need to do:
A) Check Requirements
~~~~~~~~~~~~~~~~~~~~~
-Check if your server meets the requirements by running:
+Check if your server meets the requirements by running the following command
+provided by the ``symfony`` binary created when `installing Symfony`_:
.. code-block:: terminal
- $ php app/check.php
+ $ symfony check:requirements
.. _b-configure-your-app-config-parameters-yml-file:
@@ -138,6 +132,11 @@ If your application uses environment variables instead of these parameters, you
must define those env vars in your production server using the tools provided by
your hosting service.
+At the very least you need to define the ``SYMFONY_ENV=prod`` (or
+``APP_ENV=prod`` if you're using :doc:`Symfony Flex `) to run the
+application in ``prod`` mode, but depending on your application you may need to
+define other env vars too.
+
C) Install/Update your Vendors
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -159,28 +158,20 @@ as you normally do:
.. caution::
If you get a "class not found" error during this step, you may need to
- run ``export SYMFONY_ENV=prod`` before running this command so that
- the ``post-install-cmd`` scripts run in the ``prod`` environment.
+ run ``export SYMFONY_ENV=prod`` (or ``export APP_ENV=prod`` if you're
+ using :doc:`Symfony Flex `) before running this command so
+ that the ``post-install-cmd`` scripts run in the ``prod`` environment.
D) Clear your Symfony Cache
~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Make sure you clear (and warm-up) your Symfony cache:
-
-.. code-block:: terminal
-
- $ php app/console cache:clear --env=prod --no-debug
-
-E) Dump your Assetic Assets
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-If you're using Assetic, you'll also want to dump your assets:
+Make sure you clear and warm-up your Symfony cache:
.. code-block:: terminal
- $ php app/console assetic:dump --env=prod --no-debug
+ $ php bin/console cache:clear --env=prod --no-debug
-F) Other Things!
+E) Other Things!
~~~~~~~~~~~~~~~~
There may be lots of other things that you need to do, depending on your
@@ -188,8 +179,8 @@ setup:
* Running any database migrations
* Clearing your APC cache
-* Running ``assets:install`` (already taken care of in ``composer install``)
* Add/edit CRON jobs
+* :ref:`Building and minifying your assets ` with Webpack Encore
* Pushing assets to a CDN
* ...
@@ -209,16 +200,44 @@ Don't forget that deploying your application also involves updating any dependen
(typically via Composer), migrating your database, clearing your cache and
other potential things like pushing assets to a CDN (see `Common Post-Deployment Tasks`_).
-.. _`Git Tagging`: https://git-scm.com/book/en/v2/Git-Basics-Tagging
+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::
+
+ // app/AppKernel.php
+ // ...
+ class AppKernel extends Kernel
+ {
+ // ...
+
+ public function getProjectDir()
+ {
+ return dirname(__DIR__);
+ }
+ }
+
.. _`Capifony`: https://github.com/everzet/capifony
.. _`Capistrano`: http://capistranorb.com/
-.. _`sf2debpkg`: https://github.com/liip/sf2debpkg
.. _`Fabric`: http://www.fabfile.org/
.. _`Ansistrano`: https://ansistrano.com/
.. _`Magallanes`: https://github.com/andres-montanez/Magallanes
-.. _`Ant`: http://blog.sznapka.pl/deploying-symfony2-applications-with-ant
.. _`Memcached`: http://memcached.org/
.. _`Redis`: http://redis.io/
.. _`Symfony plugin`: https://github.com/capistrano/symfony/
.. _`Deployer`: http://deployer.org/
+.. _`Git Tagging`: https://git-scm.com/book/en/v2/Git-Basics-Tagging
.. _`EasyDeployBundle`: https://github.com/EasyCorp/easy-deploy-bundle
+.. _`installing Symfony`: https://symfony.com/download
diff --git a/deployment/azure-website.rst b/deployment/azure-website.rst
index 385ab888363..71c7cd288a1 100644
--- a/deployment/azure-website.rst
+++ b/deployment/azure-website.rst
@@ -65,7 +65,7 @@ application code to the Git repository.
:alt: Configure Azure Website credentials
Congratulations! Your Azure Website is now up and running. You can check
-it by browsing to the Website url you configured in the first step. You should
+it by browsing to the website URL you configured in the first step. You should
see the following display in your web browser:
.. image:: /_images/deployment/azure-website/step-05.png
@@ -90,9 +90,9 @@ and how to properly configure PHP for a production environment.
Configuring the latest PHP Runtime
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Even though Symfony only requires PHP 5.3.9 to run, it's always recommended
-to use the most recent PHP version whenever possible. PHP 5.3 is no longer
-supported by the PHP core team, but you can update it easily in Azure.
+Even though Symfony only requires PHP 5.5.9 to run, it's always recommended
+to use the most recent PHP version whenever possible. Earlier versions are no longer
+supported by the PHP core team, but you can update it in Azure.
To update your PHP version on Azure, go to the **Application settings** under
**SETTINGS** and select the version you want.
@@ -117,8 +117,8 @@ the web server.
.. image:: /_images/deployment/azure-website/step-08.png
:alt: OPCache Configuration
-Tweaking php.ini Configuration Settings
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Tweaking ``php.ini`` Configuration Settings
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Microsoft Azure allows you to override the ``php.ini`` global configuration
settings by creating a custom ``.user.ini`` file under the project root
@@ -132,7 +132,7 @@ directory (``site/wwwroot``).
upload_max_filesize = 10M
None of these settings *needs* to be overridden. The default PHP configuration
-is already pretty good, so this is just an example to show how you can easily
+is already pretty good, so this is just an example to show how you can
tweak PHP internal settings by uploading your custom ``.ini`` file.
You can either manually create this file on your Azure Website FTP server under
@@ -156,7 +156,7 @@ Enabling the PHP intl Extension
no longer necessary.** You can check if the ``intl`` extension is enabled in the
:phpfunction:`phpinfo` page.
-However if the ``intl`` extension is not enabled you can follow these steps.
+However, if the ``intl`` extension is not enabled you can follow these steps.
This is the tricky part of the guide! To enable the ``intl`` extension, there is
no need to upload any DLL files as the ``php_intl.dll`` file already exists on
@@ -172,7 +172,7 @@ access the online **Kudu** tool by browsing to the following URL:
**Kudu** is a set of tools to manage your application. It comes with a file
explorer, a command line prompt, a log stream and a configuration settings summary
-page. Of course, this section can only be accessed if you're logged in to
+page. This section can only be accessed if you're logged in to
your main Azure Website account.
.. image:: /_images/deployment/azure-website/step-09.png
@@ -252,13 +252,13 @@ directory with at least the following contents:
.. code-block:: text
- /app/bootstrap.php.cache
- /app/cache/*
+ /var/bootstrap.php.cache
+ /var/cache/*
/app/config/parameters.yml
- /app/logs/*
- !app/cache/.gitkeep
- !app/logs/.gitkeep
- /app/SymfonyRequirements.php
+ /var/logs/*
+ !var/cache/.gitkeep
+ !var/logs/.gitkeep
+ /var/SymfonyRequirements.php
/build/
/vendor/
/bin/
@@ -336,7 +336,7 @@ make them appear.
:alt: MySQL database settings
The displayed MySQL database settings should be something similar to the code
-below. Of course, each value depends on what you've already configured.
+below. Each value depends on what you've already configured.
.. code-block:: text
@@ -363,14 +363,14 @@ the host-name and credentials of some other third-party mailing service if
your application needs to send emails.
Your Symfony application is now configured and should be almost operational. The
-final step is to build the database schema. This can easily be done with the
+final step is to build the database schema. This can be done with the
command line interface if you're using Doctrine. In the online **Console** tool
of the Kudu application, run the following command to mount the tables into your
MySQL database.
.. code-block:: terminal
- $ php app/console doctrine:schema:update --force
+ $ php bin/console doctrine:schema:update --force
This command builds the tables and indexes for your MySQL database. If your
Symfony application is more complex than a basic Symfony Standard Edition, you
@@ -391,7 +391,7 @@ Configure the Web Server
At this point, the Symfony application has been deployed and works perfectly on
the Azure Website. However, the ``web`` folder is still part of the URL, which
-you definitely don't want. But don't worry! You can easily configure the web
+you definitely don't want. But don't worry! You can configure the web
server to point to the ``web`` folder and remove the ``web`` in the URL (and
guarantee that nobody can access files outside of the ``web`` directory.)
@@ -404,32 +404,32 @@ application, configure it with the following content:
.. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
As you can see, the latest rule ``RewriteRequestsToPublic`` is responsible for
@@ -446,7 +446,7 @@ Conclusion
----------
Nice work! You've now deployed your Symfony application to the Microsoft
-Azure Website Cloud platform. You also saw that Symfony can be easily configured
+Azure Website Cloud platform. You also saw that Symfony can be configured
and executed on a Microsoft IIS web server. The process is simple and easy
to implement. And as a bonus, Microsoft is continuing to reduce the number
of steps needed so that deployment becomes even easier.
diff --git a/deployment/fortrabbit.rst b/deployment/fortrabbit.rst
index ffe968e813d..31195949912 100644
--- a/deployment/fortrabbit.rst
+++ b/deployment/fortrabbit.rst
@@ -48,27 +48,27 @@ to redirect it to :phpfunction:`error_log`:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:monolog="http://symfony.com/schema/dic/monolog"
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/monolog
- http://symfony.com/schema/dic/monolog/monolog-1.0.xsd">
+ https://symfony.com/schema/dic/monolog/monolog-1.0.xsd">
-
+
.. code-block:: php
// app/config/config_prod.php
- $container->loadFromExtension('monolog', array(
+ $container->loadFromExtension('monolog', [
// ...
- 'handlers' => array(
- 'nested' => array(
+ 'handlers' => [
+ 'nested' => [
'type' => 'error_log',
- ),
- ),
- ));
+ ],
+ ],
+ ]);
Configuring Database Access & Session Handler
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -78,7 +78,7 @@ Create the file ``app/config/config_prod_secrets.php`` with the following
contents::
// get the path to the secrects.json file
- $secrets = getenv("APP_SECRETS")
+ $secrets = getenv("APP_SECRETS");
if (!$secrets) {
return;
}
@@ -98,7 +98,7 @@ contents::
// check if the Memcache component is present
if (isset($secrets['MEMCACHE'])) {
$memcache = $secrets['MEMCACHE'];
- $handlers = array();
+ $handlers = [];
foreach (range(1, $memcache['COUNT']) as $num) {
$handlers[] = $memcache['HOST'.$num].':'.$memcache['PORT'.$num];
@@ -126,12 +126,12 @@ Make sure this file is imported into the main config file:
- { resource: config.yml }
- { resource: config_prod_secrets.php }
- # ..
+ # ...
framework:
session:
# set handler_id to null to use default session handler from php.ini (memcached)
handler_id: ~
- # ..
+ # ...
.. code-block:: xml
@@ -140,18 +140,18 @@ Make sure this file is imported into the main config file:
+ 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">
-
-
+
+
-
+
-
-
+
+
@@ -161,11 +161,11 @@ Make sure this file is imported into the main config file:
$loader->import('config.php');
$loader->import('config_prod_secrets.php');
- $container->loadFromExtension('framework', array(
- 'session' => array(
+ $container->loadFromExtension('framework', [
+ 'session' => [
'handler_id' => null,
- ),
- ));
+ ],
+ ]);
// ...
@@ -182,7 +182,7 @@ Environment Variables
~~~~~~~~~~~~~~~~~~~~~
Set the ``SYMFONY_ENV`` environment variable to ``prod`` to make sure the right
-config files get loaded. ENV vars are configuable in fortrabbit Dashboard as well.
+config files get loaded. ENV vars are configurable in fortrabbit Dashboard as well.
Document Root
~~~~~~~~~~~~~
@@ -198,7 +198,7 @@ It is assumed that your codebase is under version-control with Git and dependenc
are managed with Composer (locally).
Every time you push to fortrabbit composer install runs before your code gets
-deployed. To finetune the deployment behavior put a `fortrabbit.yml`_. deployment
+deployed. To fine-tune the deployment behavior put a `fortrabbit.yml`_. deployment
file (optional) in the project root.
Add fortrabbit as a (additional) Git remote and add your configuration changes:
@@ -270,8 +270,8 @@ Commit and push
.. note::
- The first ``git push`` takes much longer as all composer dependencies get
- downloaded. All subsequent deploys are done within seconds.
+ The first ``git push`` takes much longer as all composer dependencies get
+ downloaded. All subsequent deploys are done within seconds.
That's it! Your application is being deployed on fortrabbit. More information
about `database migrations and tunneling`_ can be found in the fortrabbit
diff --git a/deployment/heroku.rst b/deployment/heroku.rst
index 5438208f4fa..fbef77703b2 100644
--- a/deployment/heroku.rst
+++ b/deployment/heroku.rst
@@ -24,7 +24,7 @@ Preparing your Application
Deploying a Symfony application to Heroku doesn't require any change in its
code, but it requires some minor tweaks to its configuration.
-By default, the Symfony app will log into your application's ``app/log/``
+By default, the Symfony app will log into your application's ``var/log/``
directory. This is not ideal as Heroku uses an `ephemeral file system`_. On
Heroku, the best way to handle logging is using `Logplex`_. And the best way to
send log data to Logplex is by writing to ``STDERR`` or ``STDOUT``. Luckily,
@@ -85,43 +85,33 @@ below:
~~~~~~~~~~~~~~~~~~~~
By default, Heroku will launch an Apache web server together with PHP to serve
-applications. However, two special circumstances apply to Symfony applications:
-
-#. The document root is in the ``web/`` directory and not in the root directory
- of the application;
-#. The Composer ``bin-dir``, where vendor binaries (and thus Heroku's own boot
- scripts) are placed, is ``bin/`` , and not the default ``vendor/bin``.
-
-.. note::
-
- Vendor binaries are usually installed to ``vendor/bin`` by Composer, but
- sometimes (e.g. when running a Symfony Standard Edition project!), the
- location will be different. If in doubt, you can always run
- ``composer config bin-dir`` to figure out the right location.
+applications. However, a special circumstance apply to Symfony applications:
+the document root is in the ``web/`` directory and not in the root directory
+of the application.
Create a new file called ``Procfile`` (without any extension) at the root
directory of the application and add just the following content:
.. code-block:: text
- web: bin/heroku-php-apache2 web/
+ web: vendor/bin/heroku-php-apache2 web/
.. note::
- If you prefer to use Nginx, which is also available on Heroku, you can create
+ If you prefer to use nginx, which is also available on Heroku, you can create
a configuration file for it and point to it from your Procfile as described
in the `Heroku documentation`_:
.. code-block:: text
- web: bin/heroku-php-nginx -C nginx_app.conf web/
+ web: vendor/bin/heroku-php-nginx -C nginx_app.conf web/
If you prefer working on the command console, execute the following commands to
create the ``Procfile`` file and to add it to the repository:
.. code-block:: terminal
- $ echo "web: bin/heroku-php-apache2 web/" > Procfile
+ $ echo "web: vendor/bin/heroku-php-apache2 web/" > Procfile
$ git add .
$ git commit -m "Procfile for Apache and PHP"
[master 35075db] Procfile for Apache and PHP
@@ -275,7 +265,7 @@ This is also very useful to build assets on the production system, e.g. with Ass
{
"scripts": {
"compile": [
- "app/console assetic:dump"
+ "bin/console assetic:dump"
]
}
}
diff --git a/deployment/platformsh.rst b/deployment/platformsh.rst
index 212099d87d0..e1fd257f538 100644
--- a/deployment/platformsh.rst
+++ b/deployment/platformsh.rst
@@ -40,7 +40,7 @@ Platform.sh how to deploy your application (read more about
# The type of the application to build.
type: php:5.6
build:
- flavor: composer
+ flavor: composer
# The relationships of the application with services or other applications.
# The left-hand side is the name of the relationship as it will be exposed
@@ -61,16 +61,17 @@ Platform.sh how to deploy your application (read more about
# The mounts that will be performed when the package is deployed.
mounts:
- '/app/cache': 'shared:files/cache'
- '/app/logs': 'shared:files/logs'
+ '/var/cache': 'shared:files/cache'
+ '/var/logs': 'shared:files/logs'
+ '/var/sessions': 'shared:files/sessions'
# The hooks that will be performed when the package is deployed.
hooks:
build: |
- rm web/app_dev.php
- app/console --env=prod assetic:dump --no-debug
+ rm web/app_dev.php
+ php bin/console --env=prod assetic:dump --no-debug
deploy: |
- app/console --env=prod cache:clear
+ php bin/console --env=prod cache:clear
For best practices, you should also add a ``.platform`` folder at the root of
your Git repository which contains the following files:
@@ -110,7 +111,7 @@ following file (it's your role to add this file to your code base)::
foreach ($relationships['database'] as $endpoint) {
if (empty($endpoint['query']['is_master'])) {
- continue;
+ continue;
}
$container->setParameter('database_driver', 'pdo_' . $endpoint['scheme']);
diff --git a/deployment/proxies.rst b/deployment/proxies.rst
index 4cf4927965e..7a130f4783c 100644
--- a/deployment/proxies.rst
+++ b/deployment/proxies.rst
@@ -3,73 +3,50 @@ How to Configure Symfony to Work behind a Load Balancer or a Reverse Proxy
When you deploy your application, you may be behind a load balancer (e.g.
an AWS Elastic Load Balancing) or a reverse proxy (e.g. Varnish for
-:doc:`caching`).
+:doc:`caching `).
For the most part, this doesn't cause any problems with Symfony. But, when
a request passes through a proxy, certain request information is sent using
-either the standard ``Forwarded`` header or non-standard special ``X-Forwarded-*``
-headers. For example, instead of reading the ``REMOTE_ADDR`` header (which
-will now be the IP address of your reverse proxy), the user's true IP will be
-stored in a standard ``Forwarded: for="..."`` header or a non standard
-``X-Forwarded-For`` header.
-
-.. versionadded:: 2.7
- ``Forwarded`` header support was introduced in Symfony 2.7.
+either the standard ``Forwarded`` header or ``X-Forwarded-*`` headers. For example,
+instead of reading the ``REMOTE_ADDR`` header (which will now be the IP address of
+your reverse proxy), the user's true IP will be stored in a standard ``Forwarded: for="..."``
+header or a ``X-Forwarded-For`` header.
If you don't configure Symfony to look for these headers, you'll get incorrect
information about the client's IP address, whether or not the client is connecting
via HTTPS, the client's port and the hostname being requested.
-Solution: trusted_proxies
--------------------------
-
-This is no problem, but you *do* need to tell Symfony what is happening
-and which reverse proxy IP addresses will be doing this type of thing:
-
-.. configuration-block::
-
- .. code-block:: yaml
+.. _request-set-trusted-proxies:
- # app/config/config.yml
- # ...
- framework:
- trusted_proxies: [192.0.0.1, 10.0.0.0/8]
+Solution: ``setTrustedProxies()``
+---------------------------------
- .. code-block:: xml
+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::
-
-
-
+ // web/app.php
-
-
-
-
+ // ...
+ $request = Request::createFromGlobals();
- .. code-block:: php
+ // tell Symfony about your reverse proxy
+ Request::setTrustedProxies(
+ // the IP address (or range) of your proxy
+ ['192.0.0.1', '10.0.0.0/8'],
- // app/config/config.php
- $container->loadFromExtension('framework', array(
- 'trusted_proxies' => array('192.0.0.1', '10.0.0.0/8'),
- ));
+ // trust *all* "X-Forwarded-*" headers
+ Request::HEADER_X_FORWARDED_ALL
-In this example, you're saying that your reverse proxy (or proxies) has
-the IP address ``192.0.0.1`` or matches the range of IP addresses that use
-the CIDR notation ``10.0.0.0/8``. For more details, see the
-:ref:`framework.trusted_proxies ` option.
+ // or, if your proxy instead uses the "Forwarded" header
+ // Request::HEADER_FORWARDED
-You are also saying that you trust that the proxy does not send conflicting
-headers, e.g. sending both ``X-Forwarded-For`` and ``Forwarded`` in the same
-request.
+ // or, if you're using AWS ELB
+ // Request::HEADER_X_FORWARDED_AWS_ELB
+ );
-That's it! Symfony will now look for the correct headers to get information
-like the client's IP address, host, port and whether the request is
-using HTTPS.
+The Request object has several ``Request::HEADER_*`` constants that control exactly
+*which* headers from your reverse proxy are trusted. The argument is a bit field,
+so you can also pass your own value (e.g. ``0b00110``).
But what if the IP of my Reverse Proxy Changes Constantly!
----------------------------------------------------------
@@ -82,84 +59,46 @@ 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. This is
- done inside of your front controller:
-
- .. code-block:: diff
+ proxies, configure Symfony to *always* trust incoming request::
- // web/app.php
-
- // ...
- $request = Request::createFromGlobals();
- + Request::setTrustedProxies(array('127.0.0.1', $request->server->get('REMOTE_ADDR')));
+ // web/app.php
- // ...
+ // ...
+ Request::setTrustedProxies(
+ // trust *all* requests
+ ['127.0.0.1', $request->server->get('REMOTE_ADDR')],
-#. Ensure that the trusted_proxies setting in your ``app/config/config.yml``
- is not set or it will overwrite the ``setTrustedProxies()`` call above.
+ // if you're using ELB, otherwise use a constant from above
+ Request::HEADER_X_FORWARDED_AWS_ELB
+ );
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.
-.. _request-untrust-header:
-
-My Reverse Proxy Sends X-Forwarded-For but Does not Filter the Forwarded Header
--------------------------------------------------------------------------------
+If you are also using a reverse proxy on top of your load balancer (e.g.
+`CloudFront`_), calling ``$request->server->get('REMOTE_ADDR')`` won't be
+enough, as it will only trust the node sitting directly above your application
+(in this case your load balancer). You also need to append the IP addresses or
+ranges of any additional proxy (e.g. `CloudFront IP ranges`_) to the array of
+trusted proxies.
-Many popular proxy implementations do not yet support the ``Forwarded`` header
-and do not filter it by default. Ideally, you would configure this in your
-proxy. If this is not possible, you can tell Symfony to distrust the ``Forwarded``
-header, while still trusting your proxy's ``X-Forwarded-For`` header.
+Custom Headers When Using a Reverse Proxy
+-----------------------------------------
-This is done inside of your front controller::
+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``.
- // web/app.php
-
- // ...
- Request::setTrustedHeaderName(Request::HEADER_FORWARDED, null);
-
- $response = $kernel->handle($request);
- // ...
-
-Configuring the proxy server trust is very important, as not doing so will
-allow malicious users to "spoof" their IP address.
-
-My Reverse Proxy Uses Non-Standard (not X-Forwarded) Headers
-------------------------------------------------------------
-
-Although `RFC 7239`_ recently defined a standard ``Forwarded`` header to disclose
-all proxy information, most reverse proxies store information in non-standard
-``X-Forwarded-*`` headers.
-
-But if your reverse proxy uses other non-standard header names, you can configure
-these (see ":doc:`/components/http_foundation/trusting_proxies`").
-
-The code for doing this will need to live in your front controller (e.g. ``web/app.php``).
-
-.. _my-reverse-proxy-does-not-provide-all-the-standards-headers:
-
-My Reverse Proxy Does not Provide All the Standard Headers
-----------------------------------------------------------
-
-AWS Elastic Load Balancing for example does not provide the ``X-Forwarded-Host``
-and ``X-Forwarded`` HTTP headers, so you must make the following changes in the
-front controller:
-
-.. code-block:: diff
+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::
// web/app.php
// ...
- $request = Request::createFromGlobals();
- // be very careful with the next line; see "But what if the IP of my Reverse Proxy Changes Constantly!"
- + Request::setTrustedProxies(array('127.0.0.1', $request->server->get('REMOTE_ADDR')));
- // the next line is needed because AWS ELB doesn't send X-Forwarded-Host
- + Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, null);
- // the next line is needed because AWS ELB doesn't use RFC 7239
- + Request::setTrustedHeaderName(Request::HEADER_FORWARDED, null);
-
+ $_SERVER['HTTP_X_FORWARDED_PROTO'] = $_SERVER['HTTP_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
+.. _`CloudFront IP ranges`: https://ip-ranges.amazonaws.com/ip-ranges.json
diff --git a/doctrine.rst b/doctrine.rst
index 2fd5d6d3988..1cfeda2fb3d 100644
--- a/doctrine.rst
+++ b/doctrine.rst
@@ -4,6 +4,11 @@
Databases and the Doctrine ORM
==============================
+.. admonition:: Screencast
+ :class: screencast
+
+ Do you prefer video tutorials? Check out the `Doctrine screencast series`_.
+
One of the most common and challenging tasks for any application
involves persisting and reading information to and from a database. Although
the Symfony Framework doesn't integrate any component to work with databases,
@@ -78,9 +83,9 @@ information. By convention, this information is usually configured in an
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">
+ password="%database_password%"/>
.. code-block:: php
// app/config/config.php
- $container->loadFromExtension('doctrine', array(
- 'dbal' => array(
- 'driver' => 'pdo_mysql',
- 'host' => '%database_host%',
- 'dbname' => '%database_name%',
- 'user' => '%database_user%',
+ $container->loadFromExtension('doctrine', [
+ 'dbal' => [
+ 'driver' => 'pdo_mysql',
+ 'host' => '%database_host%',
+ 'dbname' => '%database_name%',
+ 'user' => '%database_user%',
'password' => '%database_password%',
- ),
- ));
+ ],
+ ]);
By separating the database information into a separate file, you can
- easily keep different versions of the file on each server. You can also
- easily store database configuration (or any sensitive information) outside
+ keep different versions of the file on each server. You can also
+ store database configuration (or any sensitive information) outside
of your project, like inside your Apache configuration, for example. For
more information, see :doc:`/configuration/external_parameters`.
@@ -116,7 +121,7 @@ can automatically generate an empty ``test_project`` database for you:
.. code-block:: terminal
- $ php app/console doctrine:database:create
+ $ php bin/console doctrine:database:create
.. sidebar:: Setting up the Database to be UTF8
@@ -128,12 +133,8 @@ can automatically generate an empty ``test_project`` database for you:
.. code-block:: terminal
- $ php app/console doctrine:database:drop --force
- $ php app/console doctrine:database:create
-
- There's no way to configure these defaults inside Doctrine, as it tries to be
- as agnostic as possible in terms of environment configuration. One way to solve
- this problem is to configure server-level defaults.
+ $ php bin/console doctrine:database:drop --force
+ $ php bin/console doctrine:database:create
Setting UTF8 defaults for MySQL is as simple as adding a few lines to
your configuration file (typically ``my.cnf``):
@@ -145,6 +146,55 @@ can automatically generate an empty ``test_project`` database for you:
collation-server = utf8mb4_unicode_ci # Replaces utf8_unicode_ci
character-set-server = utf8mb4 # Replaces utf8
+ You can also change the defaults for Doctrine so that the generated SQL
+ uses the correct character set.
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # app/config/config.yml
+ doctrine:
+ dbal:
+ charset: utf8mb4
+ default_table_options:
+ charset: utf8mb4
+ collate: utf8mb4_unicode_ci
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+ utf8mb4
+ utf8mb4_unicode_ci
+
+
+
+
+ .. code-block:: php
+
+ // app/config/config.php
+ $configuration->loadFromExtension('doctrine', [
+ 'dbal' => [
+ 'charset' => 'utf8mb4',
+ 'default_table_options' => [
+ 'charset' => 'utf8mb4',
+ 'collate' => 'utf8mb4_unicode_ci',
+ ]
+ ],
+ ]);
+
We recommend against MySQL's ``utf8`` character set, since it does not
support 4-byte unicode characters, and strings containing them will be
truncated. This is fixed by the `newer utf8mb4 character set`_.
@@ -172,7 +222,7 @@ can automatically generate an empty ``test_project`` database for you:
doctrine:
dbal:
driver: pdo_sqlite
- path: '%kernel.root_dir%/sqlite.db'
+ path: '%kernel.project_dir%/app/sqlite.db'
charset: UTF8
.. code-block:: xml
@@ -183,28 +233,28 @@ can automatically generate an empty ``test_project`` database for you:
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">
+ path="%kernel.project_dir%/app/sqlite.db"
+ charset="UTF-8"/>
.. code-block:: php
// app/config/config.php
- $container->loadFromExtension('doctrine', array(
- 'dbal' => array(
- 'driver' => 'pdo_sqlite',
- 'path' => '%kernel.root_dir%/sqlite.db',
+ $container->loadFromExtension('doctrine', [
+ 'dbal' => [
+ 'driver' => 'pdo_sqlite',
+ 'path' => '%kernel.project_dir%/app/sqlite.db',
'charset' => 'UTF-8',
- ),
- ));
+ ],
+ ]);
Creating an Entity Class
~~~~~~~~~~~~~~~~~~~~~~~~
@@ -237,7 +287,7 @@ just a simple PHP class.
.. code-block:: terminal
- $ php app/console doctrine:generate:entity
+ $ php bin/console doctrine:generate:entity
.. index::
single: Doctrine; Adding mapping metadata
@@ -329,18 +379,25 @@ directly inside the ``Product`` class via DocBlock annotations:
+ https://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
-
+
-
-
-
+
+
+
+.. 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.
+
.. note::
A bundle can accept only one metadata definition format. For example, it's
@@ -399,7 +456,7 @@ see the :ref:`doctrine-field-types` section.
.. code-block:: terminal
- $ php app/console doctrine:schema:validate
+ $ php bin/console doctrine:schema:validate
.. _doctrine-generating-getters-and-setters:
@@ -419,14 +476,14 @@ Creating the Database Tables/Schema
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You now have a usable ``Product`` class with mapping information so that
-Doctrine knows exactly how to persist it. Of course, you don't yet have the
-corresponding ``product`` table in your database. Fortunately, Doctrine can
-automatically create all the database tables needed for every known entity
-in your application. To do this, run:
+Doctrine knows exactly how to persist it. You don't yet have the corresponding
+``product`` table in your database. Fortunately, Doctrine can automatically
+create all the database tables needed for every known entity in your
+application. To do this, run:
.. code-block:: terminal
- $ php app/console doctrine:schema:update --force
+ $ php bin/console doctrine:schema:update --force
.. tip::
@@ -463,18 +520,20 @@ a controller, this is pretty easy. Add the following method to the
// ...
use AppBundle\Entity\Product;
+ use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Response;
- // ...
public function createAction()
{
+ // you can fetch the EntityManager via $this->getDoctrine()
+ // or you can add an argument to your action: createAction(EntityManagerInterface $entityManager)
+ $entityManager = $this->getDoctrine()->getManager();
+
$product = new Product();
$product->setName('Keyboard');
$product->setPrice(19.99);
$product->setDescription('Ergonomic and stylish!');
- $entityManager = $this->getDoctrine()->getManager();
-
// tells Doctrine you want to (eventually) save the Product (no queries yet)
$entityManager->persist($product);
@@ -484,33 +543,34 @@ a controller, this is pretty easy. Add the following method to the
return new Response('Saved new product with id '.$product->getId());
}
+ // if you have multiple entity managers, use the registry to fetch them
+ public function editAction()
+ {
+ $doctrine = $this->getDoctrine();
+ $entityManager = $doctrine->getManager();
+ $otherEntityManager = $doctrine->getManager('other_connection');
+ }
+
.. note::
If you're following along with this example, you'll need to create a
route that points to this action to see it work.
-.. tip::
+Take a look at the previous example in more detail:
- This article shows working with Doctrine from within a controller by using
- the :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::getDoctrine`
- method of the controller. This method is a shortcut to get the
- ``doctrine`` service. You can work with Doctrine anywhere else
- by injecting that service in the service. See
- :doc:`/service_container` for more on creating your own services.
+.. _doctrine-entity-manager:
-Take a look at the previous example in more detail:
+* **line 12** 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 10-13** In this section, you instantiate and work with the ``$product``
+* **lines 14-17** In this section, you instantiate and work with the ``$product``
object like any other normal PHP object.
-* **line 15** This line fetches Doctrine's *entity manager* object, which is
- responsible for the process of persisting objects to, and fetching objects
- from, the database.
-
-* **line 18** The ``persist($product)`` call tells Doctrine to "manage" the
+* **line 20** The ``persist($product)`` call tells Doctrine to "manage" the
``$product`` object. This does **not** cause a query to be made to the database.
-* **line 21** When the ``flush()`` method is called, Doctrine looks through
+* **line 23** 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,
@@ -602,23 +662,24 @@ Once you have a repository object, you can access all sorts of helpful methods::
.. note::
- Of course, you can also issue complex queries, which you'll learn more
+ You can also issue complex queries, which you'll learn more
about in the :ref:`doctrine-queries` section.
You can also take advantage of the useful ``findBy()`` and ``findOneBy()`` methods
-to easily fetch objects based on multiple conditions::
+to fetch objects based on multiple conditions::
$repository = $this->getDoctrine()->getRepository(Product::class);
// looks for a single product matching the given name and price
- $product = $repository->findOneBy(
- array('name' => 'Keyboard', 'price' => 19.99)
- );
+ $product = $repository->findOneBy([
+ 'name' => 'Keyboard',
+ 'price' => 19.99
+ ]);
// looks for multiple products matching the given name, ordered by price
$products = $repository->findBy(
- array('name' => 'Keyboard'),
- array('price' => 'ASC')
+ ['name' => 'Keyboard'],
+ ['price' => 'ASC']
);
.. tip::
@@ -698,10 +759,10 @@ without any work::
$product = $repository->find($productId);
$product = $repository->findOneByName('Keyboard');
-Of course, Doctrine also allows you to write more complex queries using the
-Doctrine Query Language (DQL). DQL is similar to SQL except that you should
-imagine that you're querying for one or more objects of an entity class (e.g. ``Product``)
-instead of querying for rows on a table (e.g. ``product``).
+Doctrine also allows you to write more complex queries using the Doctrine Query
+Language (DQL). DQL is similar to SQL except that you should imagine that you're
+querying for one or more objects of an entity class (e.g. ``Product``) instead
+of querying for rows on a table (e.g. ``product``).
When querying in Doctrine, you have two main options: writing pure DQL queries
or using Doctrine's Query Builder.
@@ -713,7 +774,6 @@ Imagine that you want to query for products that cost more than ``19.99``,
ordered from least to most expensive. You can use DQL, Doctrine's native
SQL-like language, to construct a query for this scenario::
- $entityManager = $this->getDoctrine()->getManager();
$query = $entityManager->createQuery(
'SELECT p
FROM AppBundle:Product p
@@ -740,7 +800,7 @@ result, you can use ``getOneOrNullResult()``::
$product = $query->setMaxResults(1)->getOneOrNullResult();
-The DQL syntax is incredibly powerful, allowing you to easily join between
+The DQL syntax is incredibly powerful, allowing you to join between
entities (the topic of :doc:`relations ` will be
covered later), group, etc. For more information, see the official
`Doctrine Query Language`_ documentation.
@@ -838,7 +898,7 @@ Learn more
.. _`Basic Mapping Documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html
.. _`Query Builder`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/query-builder.html
.. _`Doctrine Query Language`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html
-.. _`Mapping Types Documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#property-mapping
+.. _`Mapping Types documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#property-mapping
.. _`Property Mapping`: 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
.. _`Creating Classes for the Database`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#creating-classes-for-the-database
@@ -848,4 +908,5 @@ Learn more
.. _`FrameworkExtraBundle documentation`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
.. _`newer utf8mb4 character set`: https://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html
.. _`Transactions and Concurrency`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/transactions-and-concurrency.html
-.. _`limit of 767 bytes for the index key prefix`: https://dev.mysql.com/doc/refman/5.6/en/innodb-restrictions.html
+.. _`limit of 767 bytes for the index key prefix`: https://dev.mysql.com/doc/refman/5.6/en/innodb-limits.html
+.. _`Doctrine screencast series`: https://symfonycasts.com/screencast/symfony3-doctrine
diff --git a/doctrine/associations.rst b/doctrine/associations.rst
index 0a481bfcd27..24a333c8a36 100644
--- a/doctrine/associations.rst
+++ b/doctrine/associations.rst
@@ -4,6 +4,12 @@
How to Work with Doctrine Associations / Relations
==================================================
+.. admonition:: Screencast
+ :class: screencast
+
+ Do you prefer video tutorials? Check out the `Mastering Doctrine Relations`_
+ screencast series.
+
Suppose that each product in your application belongs to exactly one category.
In this case, you'll need a ``Category`` class, and a way to relate a
``Product`` object to a ``Category`` object.
@@ -14,7 +20,7 @@ the class for you.
.. code-block:: terminal
- $ php app/console doctrine:generate:entity --no-interaction \
+ $ php bin/console doctrine:generate:entity --no-interaction \
--entity="AppBundle:Category" \
--fields="name:string(255)"
@@ -77,7 +83,7 @@ property on the ``Product`` class, annotated as follows:
+ https://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
@@ -87,7 +93,7 @@ property on the ``Product`` class, annotated as follows:
inversed-by="products"
join-column="category">
-
+
@@ -144,14 +150,14 @@ to hold those associated objects.
+ https://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
+ mapped-by="category"/>
+
+
+
-
-
+
+
-
-
+
+
@@ -86,47 +80,32 @@ managers that use this connection.
use AppBundle\EventListener\SearchIndexer2;
use AppBundle\EventListener\SearchIndexerSubscriber;
- $container->loadFromExtension('doctrine', array(
- 'dbal' => array(
- 'default_connection' => 'default',
- 'connections' => array(
- 'default' => array(
- 'driver' => 'pdo_sqlite',
- 'memory' => true,
- ),
- ),
- ),
- ));
-
- $container
- ->register('my.listener', SearchIndexer::class)
- ->addTag('doctrine.event_listener', array('event' => 'postPersist'))
+ $container->autowire(SearchIndexer::class)
+ ->addTag('doctrine.event_listener', ['event' => 'postPersist'])
;
- $container
- ->register('my.listener2', SearchIndexer2::class)
- ->addTag('doctrine.event_listener', array(
+ $container->autowire(SearchIndexer2::class)
+ ->addTag('doctrine.event_listener', [
'event' => 'postPersist',
'connection' => 'default',
- ))
+ ])
;
- $container
- ->register('my.subscriber', SearchIndexerSubscriber::class)
- ->addTag('doctrine.event_subscriber', array('connection' => 'default'))
+ $container->autowire(SearchIndexerSubscriber::class)
+ ->addTag('doctrine.event_subscriber', ['connection' => 'default'])
;
Creating the Listener Class
---------------------------
-In the previous example, a service ``my.listener`` was configured as a Doctrine
+In the previous example, a ``SearchIndexer`` service was configured as a Doctrine
listener on the event ``postPersist``. The class behind that service must have
a ``postPersist()`` method, which will be called when the event is dispatched::
// src/AppBundle/EventListener/SearchIndexer.php
namespace AppBundle\EventListener;
- // for Doctrine < 2.4: use Doctrine\ORM\Event\LifecycleEventArgs;
- use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use AppBundle\Entity\Product;
+ // for Doctrine < 2.4: use Doctrine\ORM\Event\LifecycleEventArgs;
+ use Doctrine\Persistence\Event\LifecycleEventArgs;
class SearchIndexer
{
@@ -158,7 +137,7 @@ entity), you should check for the entity's class type in your method
In Doctrine 2.4, a feature called Entity Listeners was introduced.
It is a lifecycle listener class used for an entity. You can read
- about it in `the Doctrine Documentation`_.
+ about it in `the DoctrineBundle documentation`_.
Creating the Subscriber Class
-----------------------------
@@ -169,20 +148,20 @@ interface and have an event method for each event it subscribes to::
// src/AppBundle/EventListener/SearchIndexerSubscriber.php
namespace AppBundle\EventListener;
+ use AppBundle\Entity\Product;
use Doctrine\Common\EventSubscriber;
// for Doctrine < 2.4: use Doctrine\ORM\Event\LifecycleEventArgs;
- use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use Doctrine\ORM\Events;
- use AppBundle\Entity\Product;
+ use Doctrine\Persistence\Event\LifecycleEventArgs;
class SearchIndexerSubscriber implements EventSubscriber
{
public function getSubscribedEvents()
{
- return array(
+ return [
Events::postPersist,
Events::postUpdate,
- );
+ ];
}
public function postUpdate(LifecycleEventArgs $args)
@@ -251,7 +230,7 @@ to the tag like so:
-
+
@@ -262,17 +241,14 @@ to the tag like so:
$container
->register('my.listener', SearchIndexer::class)
- ->addTag('doctrine.event_listener', array('event' => 'postPersist', 'lazy' => 'true'))
+ ->addTag('doctrine.event_listener', ['event' => 'postPersist', 'lazy' => 'true'])
;
.. note::
- Marking an event listener as ``lazy`` has nothing to do with lazy service
+ Marking an event listener as ``lazy`` has nothing to do with lazy service
definitions which are described :doc:`in their own article `
-.. _`The Event System`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html
-.. _`the Doctrine Documentation`: https://symfony.com/doc/current/bundles/DoctrineBundle/entity-listeners.html
-
Priorities for Event Listeners
------------------------------
@@ -304,10 +280,10 @@ numbers mean that listeners are invoked earlier.
-
+
-
+
@@ -319,10 +295,14 @@ numbers mean that listeners are invoked earlier.
$container
->register('my.listener.with_high_priority', MyHighPriorityListener::class)
- ->addTag('doctrine.event_listener', array('event' => 'postPersist', 'priority' => 10))
+ ->addTag('doctrine.event_listener', ['event' => 'postPersist', 'priority' => 10])
;
$container
->register('my.listener.with_low_priority', MyLowPriorityListener::class)
- ->addTag('doctrine.event_listener', array('event' => 'postPersist', 'priority' => 1))
+ ->addTag('doctrine.event_listener', ['event' => 'postPersist', 'priority' => 1])
;
+
+.. _`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 a6535a9c749..873de528404 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/AppBundle/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/AppBundle/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/mapping_model_classes.rst b/doctrine/mapping_model_classes.rst
index 8e0b7b95c0d..5f2715a8c77 100644
--- a/doctrine/mapping_model_classes.rst
+++ b/doctrine/mapping_model_classes.rst
@@ -16,20 +16,13 @@ register the mappings for your model classes.
for one of the ODMs. For reusable bundles, rather than duplicate model classes
just to get the auto-mapping, use the compiler pass.
-.. versionadded:: 2.3
- The base mapping compiler pass was introduced in Symfony 2.3. The Doctrine bundles
- support it from DoctrineBundle >= 1.3.0, MongoDBBundle >= 3.0.0,
- PHPCRBundle >= 1.0.0 and the (unversioned) CouchDBBundle supports the
- compiler pass since the `CouchDB Mapping Compiler Pass pull request`_
- was merged.
-
In your bundle class, write the following code to register the compiler pass.
This one is written for the CmfRoutingBundle, so parts of it will need to
be adapted for your case::
+ use Doctrine\Bundle\CouchDBBundle\DependencyInjection\Compiler\DoctrineCouchDBMappingsPass;
use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DoctrineOrmMappingsPass;
use Doctrine\Bundle\MongoDBBundle\DependencyInjection\Compiler\DoctrineMongoDBMappingsPass;
- use Doctrine\Bundle\CouchDBBundle\DependencyInjection\Compiler\DoctrineCouchDBMappingsPass;
use Doctrine\Bundle\PHPCRBundle\DependencyInjection\Compiler\DoctrinePhpcrMappingsPass;
use Symfony\Cmf\RoutingBundle\Model;
@@ -41,17 +34,17 @@ be adapted for your case::
// ...
$modelDirectory = realpath(__DIR__.'/Resources/config/doctrine/model');
- $mappings = array(
+ $mappings = [
$modelDirectory => Model::class,
- );
+ ];
if (class_exists(DoctrineOrmMappingsPass::class)) {
$container->addCompilerPass(
DoctrineOrmMappingsPass::createXmlMappingDriver(
$mappings,
- array('cmf_routing.model_manager_name'),
+ ['cmf_routing.model_manager_name'],
'cmf_routing.backend_type_orm',
- array('CmfRoutingBundle' => Model::class)
+ ['CmfRoutingBundle' => Model::class]
));
}
@@ -59,9 +52,9 @@ be adapted for your case::
$container->addCompilerPass(
DoctrineMongoDBMappingsPass::createXmlMappingDriver(
$mappings,
- array('cmf_routing.model_manager_name'),
+ ['cmf_routing.model_manager_name'],
'cmf_routing.backend_type_mongodb',
- array('CmfRoutingBundle' => Model::class)
+ ['CmfRoutingBundle' => Model::class]
));
}
@@ -69,9 +62,9 @@ be adapted for your case::
$container->addCompilerPass(
DoctrineCouchDBMappingsPass::createXmlMappingDriver(
$mappings,
- array('cmf_routing.model_manager_name'),
+ ['cmf_routing.model_manager_name'],
'cmf_routing.backend_type_couchdb',
- array('CmfRoutingBundle' => Model::class)
+ ['CmfRoutingBundle' => Model::class]
));
}
@@ -79,9 +72,9 @@ be adapted for your case::
$container->addCompilerPass(
DoctrinePhpcrMappingsPass::createXmlMappingDriver(
$mappings,
- array('cmf_routing.model_manager_name'),
+ ['cmf_routing.model_manager_name'],
'cmf_routing.backend_type_phpcr',
- array('CmfRoutingBundle' => Model::class)
+ ['CmfRoutingBundle' => Model::class]
));
}
}
@@ -122,23 +115,23 @@ Annotations, XML, Yaml, PHP and StaticPHP. The arguments are:
``DoctrineOrmMappingsPass`` and adapted to use the ``DefaultFileLocator``
instead of the ``SymfonyFileLocator``::
- use Doctrine\Common\Persistence\Mapping\Driver\DefaultFileLocator;
- use Doctrine\ORM\Mapping\Driver\XmlDriver;
use AppBundle\Model;
+ use Doctrine\ORM\Mapping\Driver\XmlDriver;
+ use Doctrine\Persistence\Mapping\Driver\DefaultFileLocator;
// ...
private function buildMappingCompilerPass()
{
- $fileLocator = new Definition(DefaultFileLocator::class, array(
- array(realpath(__DIR__ . '/Resources/config/doctrine-base')),
+ $fileLocator = new Definition(DefaultFileLocator::class, [
+ [realpath(__DIR__ . '/Resources/config/doctrine-base')],
'.orm.xml'
- ));
- $driver = new Definition(XmlDriver::class, array($fileLocator));
+ ]);
+ $driver = new Definition(XmlDriver::class, [$fileLocator]);
return new DoctrineOrmMappingsPass(
$driver,
- array(Model::class),
- array('your_bundle.manager_name'),
+ [Model::class],
+ ['your_bundle.manager_name'],
'your_bundle.orm_enabled'
);
}
@@ -152,5 +145,3 @@ Annotations, XML, Yaml, PHP and StaticPHP. The arguments are:
the ``SymfonyFileLocator`` will get confused.
Adjust accordingly for the other Doctrine implementations.
-
-.. _`CouchDB Mapping Compiler Pass pull request`: https://github.com/doctrine/DoctrineCouchDBBundle/pull/27
diff --git a/doctrine/mongodb_session_storage.rst b/doctrine/mongodb_session_storage.rst
index fcfecfe3dd2..f097d713beb 100644
--- a/doctrine/mongodb_session_storage.rst
+++ b/doctrine/mongodb_session_storage.rst
@@ -44,9 +44,9 @@ need to change/add some parameters in the main configuration file:
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">
@@ -79,28 +79,28 @@ need to change/add some parameters in the main configuration file:
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler;
- $container->loadFromExtension('framework', array(
- 'session' => array(
+ $container->loadFromExtension('framework', [
+ 'session' => [
// ...
'handler_id' => 'session.handler.mongo',
'cookie_lifetime' => 2592000, // optional, it is set to 30 days here
'gc_maxlifetime' => 2592000, // optional, it is set to 30 days here
- ),
- ));
+ ],
+ ]);
$container->register('mongo_client', \MongoClient::class)
- ->setArguments(array(
+ ->setArguments([
// if using a username and password
- array('mongodb://%mongodb_username%:%mongodb_password%@%mongodb_host%:27017'),
+ ['mongodb://%mongodb_username%:%mongodb_password%@%mongodb_host%:27017'],
// if not using a username and password
- array('mongodb://%mongodb_host%:27017'),
- ));
+ ['mongodb://%mongodb_host%:27017'],
+ ]);
$container->register('session.handler.mongo', MongoDbSessionHandler::class)
- ->setArguments(array(
+ ->setArguments([
new Reference('mongo_client'),
'%mongo.session.options%',
- ));
+ ]);
The parameters used above should be defined somewhere in your application, often in your main
parameters configuration:
@@ -126,9 +126,9 @@ parameters configuration:
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">
@@ -148,10 +148,10 @@ parameters configuration:
use Symfony\Component\DependencyInjection\Reference;
- $container->setParameter('mongo.session.options', array(
+ $container->setParameter('mongo.session.options', [
'database' => 'session_db', // your MongoDB database name
'collection' => 'session', // your MongoDB collection name
- ));
+ ]);
$container->setParameter('mongodb_host', '1.2.3.4'); // your MongoDB server's IP
$container->setParameter('mongodb_username', 'my_username');
$container->setParameter('mongodb_password', 'my_password');
diff --git a/doctrine/multiple_entity_managers.rst b/doctrine/multiple_entity_managers.rst
index d1624a9b307..4fd29b05b14 100644
--- a/doctrine/multiple_entity_managers.rst
+++ b/doctrine/multiple_entity_managers.rst
@@ -69,9 +69,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">
@@ -98,12 +98,12 @@ The following configuration code shows how you can configure two entity managers
-
-
+
+
-
+
@@ -111,11 +111,11 @@ The following configuration code shows how you can configure two entity managers
.. code-block:: php
- $container->loadFromExtension('doctrine', array(
- 'dbal' => array(
+ $container->loadFromExtension('doctrine', [
+ 'dbal' => [
'default_connection' => 'default',
- 'connections' => array(
- 'default' => array(
+ 'connections' => [
+ 'default' => [
'driver' => 'pdo_mysql',
'host' => '%database_host%',
'port' => '%database_port%',
@@ -123,8 +123,8 @@ The following configuration code shows how you can configure two entity managers
'user' => '%database_user%',
'password' => '%database_password%',
'charset' => 'UTF8',
- ),
- 'customer' => array(
+ ],
+ 'customer' => [
'driver' => 'pdo_mysql',
'host' => '%database_host2%',
'port' => '%database_port2%',
@@ -132,29 +132,29 @@ The following configuration code shows how you can configure two entity managers
'user' => '%database_user2%',
'password' => '%database_password2%',
'charset' => 'UTF8',
- ),
- ),
- ),
+ ],
+ ],
+ ],
- 'orm' => array(
+ 'orm' => [
'default_entity_manager' => 'default',
- 'entity_managers' => array(
- 'default' => array(
+ 'entity_managers' => [
+ 'default' => [
'connection' => 'default',
- 'mappings' => array(
+ 'mappings' => [
'AppBundle' => null,
'AcmeStoreBundle' => null,
- ),
- ),
- 'customer' => array(
+ ],
+ ],
+ 'customer' => [
'connection' => 'customer',
- 'mappings' => array(
+ 'mappings' => [
'AcmeCustomerBundle' => null,
- ),
- ),
- ),
- ),
- ));
+ ],
+ ],
+ ],
+ ],
+ ]);
In this case, you've defined two entity managers and called them ``default``
and ``customer``. The ``default`` entity manager manages entities in the
@@ -173,35 +173,37 @@ When working with multiple connections to create your databases:
.. code-block:: terminal
# Play only with "default" connection
- $ php app/console doctrine:database:create
+ $ php bin/console doctrine:database:create
# Play only with "customer" connection
- $ php app/console doctrine:database:create --connection=customer
+ $ php bin/console doctrine:database:create --connection=customer
When working with multiple entity managers to update your schema:
.. code-block:: terminal
# Play only with "default" mappings
- $ php app/console doctrine:schema:update --force
+ $ php bin/console doctrine:schema:update --force
# Play only with "customer" mappings
- $ php app/console doctrine:schema:update --force --em=customer
+ $ php bin/console doctrine:schema:update --force --em=customer
If you *do* omit the entity manager's name when asking for it,
the default entity manager (i.e. ``default``) is returned::
+ // ...
+
class UserController extends Controller
{
public function indexAction()
{
- // All three return the "default" entity manager
- $entityManager = $this->get('doctrine')->getManager();
- $entityManager = $this->get('doctrine')->getManager('default');
+ // All 3 return the "default" entity manager
+ $entityManager = $this->getDoctrine()->getManager();
+ $entityManager = $this->getDoctrine()->getManager('default');
$entityManager = $this->get('doctrine.orm.default_entity_manager');
// Both of these return the "customer" entity manager
- $customerEntityManager = $this->get('doctrine')->getManager('customer');
+ $customerEntityManager = $this->getDoctrine()->getManager('customer');
$customerEntityManager = $this->get('doctrine.orm.customer_entity_manager');
}
}
@@ -214,25 +216,26 @@ The same applies to repository calls::
use AcmeStoreBundle\Entity\Customer;
use AcmeStoreBundle\Entity\Product;
+ // ...
class UserController extends Controller
{
public function indexAction()
{
// Retrieves a repository managed by the "default" em
- $products = $this->get('doctrine')
+ $products = $this->getDoctrine()
->getRepository(Product::class)
->findAll()
;
// Explicit way to deal with the "default" em
- $products = $this->get('doctrine')
+ $products = $this->getDoctrine()
->getRepository(Product::class, 'default')
->findAll()
;
// Retrieves a repository managed by the "customer" em
- $customers = $this->get('doctrine')
+ $customers = $this->getDoctrine()
->getRepository(Customer::class, 'customer')
->findAll()
;
diff --git a/doctrine/pdo_session_storage.rst b/doctrine/pdo_session_storage.rst
index 5aa8f9e75d5..3c21c61f5a8 100644
--- a/doctrine/pdo_session_storage.rst
+++ b/doctrine/pdo_session_storage.rst
@@ -11,24 +11,20 @@ multiple web server environment.
Symfony has a built-in solution for database session storage called
:class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\PdoSessionHandler`.
-To use it, you just need to change some parameters in the main configuration file:
+To use it, first register a new handler service:
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
- framework:
- session:
- # ...
- handler_id: session.handler.pdo
-
services:
- session.handler.pdo:
- class: Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler
+ # ...
+
+ Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler:
public: false
arguments:
- - 'mysql:dbname=mydatabase'
+ - 'mysql:dbname=mydatabase; host=myhost; port=myport'
- { db_username: myuser, db_password: mypassword }
.. code-block:: xml
@@ -39,16 +35,12 @@ To use it, you just need to change some parameters in the main configuration fil
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">
-
- mysql:dbname=mydatabase
+
+ mysql:dbname=mydatabase, host=myhostmyusermypassword
@@ -60,23 +52,54 @@ To use it, you just need to change some parameters in the main configuration fil
.. code-block:: php
// app/config/config.php
+ use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
+
+ $storageDefinition = $container->register(PdoSessionHandler::class)
+ ->setArguments([
+ 'mysql:dbname=mydatabase; host=myhost; port=myport',
+ ['db_username' => 'myuser', 'db_password' => 'mypassword'],
+ ])
+ ;
+.. tip::
+
+ Configure the database credentials as
+ :doc:`parameters defined with environment variables `
+ to make your application more secure.
+
+Next, tell Symfony to use your service as the session handler:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # app/config/config.yml
+ framework:
+ session:
+ # ...
+ handler_id: Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // app/config/config.php
use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
// ...
- $container->loadFromExtension('framework', array(
+ $container->loadFromExtension('framework', [
// ...
- 'session' => array(
+ 'session' => [
// ...
- 'handler_id' => 'session.handler.pdo',
- ),
- ));
-
- $container->register('session.handler.pdo', PdoSessionHandler::class)
- ->setArguments(array(
- 'mysql:dbname=mydatabase',
- array('db_username' => 'myuser', 'db_password' => 'mypassword'),
- ));
+ 'handler_id' => PdoSessionHandler::class,
+ ],
+ ]);
Configuring the Table and Column Names
--------------------------------------
@@ -92,12 +115,12 @@ a second array argument to ``PdoSessionHandler``:
# app/config/config.yml
services:
# ...
- session.handler.pdo:
- class: Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler
+
+ Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler:
public: false
arguments:
- - 'mysql:dbname=mydatabase'
- - { db_table: sessions, db_username: myuser, db_password: mypassword }
+ - 'mysql:dbname=mydatabase; host=myhost; port=myport'
+ - { db_table: 'sessions', db_username: 'myuser', db_password: 'mypassword' }
.. code-block:: xml
@@ -106,11 +129,11 @@ a second array argument to ``PdoSessionHandler``:
+ https://symfony.com/schema/dic/services/services-1.0.xsd">
-
- mysql:dbname=mydatabase
+
+ mysql:dbname=mydatabase, host=myhostsessionsmyuser
@@ -123,15 +146,15 @@ a second array argument to ``PdoSessionHandler``:
.. code-block:: php
// app/config/config.php
-
use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
// ...
- $container->register('session.handler.pdo', PdoSessionHandler::class)
- ->setArguments(array(
- 'mysql:dbname=mydatabase',
- array('db_table' => 'sessions', 'db_username' => 'myuser', 'db_password' => 'mypassword'),
- ));
+ $container->register(PdoSessionHandler::class)
+ ->setArguments([
+ 'mysql:dbname=mydatabase; host=myhost; port=myport',
+ ['db_table' => 'sessions', 'db_username' => 'myuser', 'db_password' => 'mypassword']
+ ])
+ ;
These are parameters that you can configure:
@@ -166,8 +189,9 @@ of your project's data, you can use the connection settings from the
.. code-block:: yaml
services:
- session.handler.pdo:
- class: Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler
+ # ...
+
+ Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler:
public: false
arguments:
- 'mysql:host=%database_host%;port=%database_port%;dbname=%database_name%'
@@ -179,10 +203,10 @@ of your project's data, you can use the connection settings from the
+ https://symfony.com/schema/dic/services/services-1.0.xsd">
-
+ mysql:host=%database_host%;port=%database_port%;dbname=%database_name%%database_user%
@@ -195,10 +219,12 @@ of your project's data, you can use the connection settings from the
.. code-block:: php
// ...
- $storageDefinition = new Definition(PdoSessionHandler::class, array(
- 'mysql:host=%database_host%;port=%database_port%;dbname=%database_name%',
- array('db_username' => '%database_user%', 'db_password' => '%database_password%')
- ));
+ $container->register(PdoSessionHandler::class)
+ ->setArguments([
+ 'mysql:host=%database_host%;port=%database_port%;dbname=%database_name%',
+ ['db_username' => '%database_user%', 'db_password' => '%database_password%']
+ ])
+ ;
.. _example-sql-statements:
@@ -228,8 +254,8 @@ MySQL
`sess_id` VARCHAR(128) NOT NULL PRIMARY KEY,
`sess_data` BLOB NOT NULL,
`sess_time` INTEGER UNSIGNED NOT NULL,
- `sess_lifetime` MEDIUMINT NOT NULL
- ) COLLATE utf8_bin, ENGINE = InnoDB;
+ `sess_lifetime` INTEGER UNSIGNED NOT NULL
+ ) COLLATE utf8mb4_bin, ENGINE = InnoDB;
.. note::
diff --git a/doctrine/registration_form.rst b/doctrine/registration_form.rst
index bfa3b69b450..80aa8eb27e5 100644
--- a/doctrine/registration_form.rst
+++ b/doctrine/registration_form.rst
@@ -43,9 +43,9 @@ With some validation added, your class may look something like this::
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
- use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;
+ use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity
@@ -95,7 +95,7 @@ With some validation added, your class may look something like this::
public function __construct()
{
- $this->roles = array('ROLE_USER');
+ $this->roles = ['ROLE_USER'];
}
// other properties and methods
@@ -142,7 +142,7 @@ With some validation added, your class may look something like this::
public function getSalt()
{
- // The bcrypt algorithm doesn't require a separate salt.
+ // The bcrypt and argon2i algorithms don't require a separate salt.
// You *may* need a real salt if you choose a different encoder.
return null;
}
@@ -189,12 +189,12 @@ Next, create the form for the ``User`` entity::
use AppBundle\Entity\User;
use Symfony\Component\Form\AbstractType;
- use Symfony\Component\Form\FormBuilderInterface;
- use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
- use Symfony\Component\Form\Extension\Core\Type\TextType;
- use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
+ use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
+ use Symfony\Component\Form\Extension\Core\Type\TextType;
+ use Symfony\Component\Form\FormBuilderInterface;
+ use Symfony\Component\OptionsResolver\OptionsResolver;
class UserType extends AbstractType
{
@@ -203,19 +203,19 @@ Next, create the form for the ``User`` entity::
$builder
->add('email', EmailType::class)
->add('username', TextType::class)
- ->add('plainPassword', RepeatedType::class, array(
+ ->add('plainPassword', RepeatedType::class, [
'type' => PasswordType::class,
- 'first_options' => array('label' => 'Password'),
- 'second_options' => array('label' => 'Repeat Password'),
- ))
+ 'first_options' => ['label' => 'Password'],
+ 'second_options' => ['label' => 'Repeat Password'],
+ ])
;
}
public function configureOptions(OptionsResolver $resolver)
{
- $resolver->setDefaults(array(
+ $resolver->setDefaults([
'data_class' => User::class,
- ));
+ ]);
}
}
@@ -237,18 +237,19 @@ into the database::
// src/AppBundle/Controller/RegistrationController.php
namespace AppBundle\Controller;
- use AppBundle\Form\UserType;
use AppBundle\Entity\User;
- use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
+ use AppBundle\Form\UserType;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
+ use Symfony\Component\Routing\Annotation\Route;
+ use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
class RegistrationController extends Controller
{
/**
* @Route("/register", name="user_registration")
*/
- public function registerAction(Request $request)
+ public function registerAction(Request $request, UserPasswordEncoderInterface $passwordEncoder)
{
// 1) build the form
$user = new User();
@@ -259,8 +260,7 @@ into the database::
if ($form->isSubmitted() && $form->isValid()) {
// 3) Encode the password (you could also do this via Doctrine listener)
- $password = $this->get('security.password_encoder')
- ->encodePassword($user, $user->getPlainPassword());
+ $password = $passwordEncoder->encodePassword($user, $user->getPlainPassword());
$user->setPassword($password);
// 4) save the User!
@@ -276,7 +276,7 @@ into the database::
return $this->render(
'registration/register.html.twig',
- array('form' => $form->createView())
+ ['form' => $form->createView()]
);
}
}
@@ -300,7 +300,7 @@ encoder in the security configuration:
+ xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
bcrypt
@@ -312,11 +312,11 @@ encoder in the security configuration:
// app/config/security.php
use AppBundle\Entity\User;
- $container->loadFromExtension('security', array(
- 'encoders' => array(
+ $container->loadFromExtension('security', [
+ 'encoders' => [
User::class => 'bcrypt',
- ),
- ));
+ ],
+ ]);
In this case the recommended `bcrypt`_ algorithm is used. If needed, check out
the :ref:`user password encoding ` article.
@@ -341,7 +341,7 @@ the :ref:`user password encoding ` article.
+ xsi:schemaLocation="http://symfony.com/schema/routing https://symfony.com/schema/routing/routing-1.0.xsd">
AppBundle:Registration:register
@@ -351,13 +351,13 @@ the :ref:`user password encoding ` article.
.. code-block:: php
// app/config/routing.php
- use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
+ use Symfony\Component\Routing\RouteCollection;
$routes = new RouteCollection();
- $routes->add('user_registration', new Route('/register', array(
+ $routes->add('user_registration', new Route('/register', [
'_controller' => 'AppBundle:Registration:register',
- )));
+ ]));
return $routes;
@@ -366,7 +366,6 @@ Next, create the template:
.. code-block:: html+twig
{# app/Resources/views/registration/register.html.twig #}
-
{{ form_start(form) }}
{{ form_row(form.username) }}
{{ form_row(form.email) }}
@@ -386,7 +385,7 @@ your database schema using this command:
.. code-block:: terminal
- $ php app/console doctrine:schema:update --force
+ $ php bin/console doctrine:schema:update --force
That's it! Head to ``/register`` to try things out!
@@ -431,22 +430,23 @@ To do this, add a ``termsAccepted`` field to your form, but set its
// src/AppBundle/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, array(
+ ->add('termsAccepted', CheckboxType::class, [
'mapped' => false,
'constraints' => new IsTrue(),
- ))
- );
+ ])
+ ;
}
}
diff --git a/doctrine/repository.rst b/doctrine/repository.rst
index 5afa9d8e911..5428c71bb95 100644
--- a/doctrine/repository.rst
+++ b/doctrine/repository.rst
@@ -25,7 +25,7 @@ To do this, add the repository class name to your entity's mapping definition:
*/
class Product
{
- //...
+ // ...
}
.. code-block:: yaml
@@ -43,7 +43,7 @@ To do this, add the repository class name to your entity's mapping definition:
+ https://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
getDoctrine()->getManager();
- $products = $entityManager->getRepository(Product::class)
- ->findAllOrderedByName();
+ public function listAction()
+ {
+ $products = $this->getDoctrine()
+ ->getRepository(Product::class)
+ ->findAllOrderedByName();
+ }
.. note::
diff --git a/doctrine/resolve_target_entity.rst b/doctrine/resolve_target_entity.rst
index 9c5901b354d..543f7711252 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/AppBundle/Entity/Customer.php
-
namespace AppBundle\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">
@@ -146,14 +143,14 @@ about the replacement:
use Acme\InvoiceBundle\Model\InvoiceSubjectInterface;
use AppBundle\Entity\Customer;
- $container->loadFromExtension('doctrine', array(
- 'orm' => array(
+ $container->loadFromExtension('doctrine', [
+ 'orm' => [
// ...
- 'resolve_target_entities' => array(
+ 'resolve_target_entities' => [
InvoiceSubjectInterface::class => Customer::class,
- ),
- ),
- ));
+ ],
+ ],
+ ]);
Final Thoughts
--------------
diff --git a/doctrine/reverse_engineering.rst b/doctrine/reverse_engineering.rst
index ec48dc23455..0a48e98c7d2 100644
--- a/doctrine/reverse_engineering.rst
+++ b/doctrine/reverse_engineering.rst
@@ -48,9 +48,7 @@ to a post record thanks to a foreign key constraint.
Before diving into the recipe, be sure your database connection parameters are
correctly setup in the ``app/config/parameters.yml`` file (or wherever your
-database configuration is kept) and that you have initialized a bundle that
-will host your future entity class. In this tutorial it's assumed that an
-AcmeBlogBundle exists and is located under the ``src/Acme/BlogBundle`` folder.
+database configuration is kept).
The first step towards building entity classes from an existing database
is to ask Doctrine to introspect the database and generate the corresponding
@@ -59,10 +57,10 @@ table fields.
.. code-block:: terminal
- $ php app/console doctrine:mapping:import --force AcmeBlogBundle xml
+ $ php bin/console doctrine:mapping:import --force AppBundle xml
This command line tool asks Doctrine to introspect the database and generate
-the XML metadata files under the ``src/Acme/BlogBundle/Resources/config/doctrine``
+the XML metadata files under the ``src/AppBundle/Resources/config/doctrine``
folder of your bundle. This generates two files: ``BlogPost.orm.xml`` and
``BlogComment.orm.xml``.
@@ -75,16 +73,19 @@ The generated ``BlogPost.orm.xml`` metadata file looks as follows:
.. code-block:: xml
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
Once the metadata files are generated, you can ask Doctrine to build related
@@ -93,7 +94,7 @@ entity classes by executing the following command.
.. code-block:: terminal
// generates entity classes with annotation mappings
- $ php app/console doctrine:mapping:convert annotation ./src
+ $ php bin/console doctrine:mapping:convert annotation ./src
.. caution::
@@ -103,14 +104,12 @@ entity classes by executing the following command.
For example, the newly created ``BlogComment`` entity class looks as follow::
- // src/Acme/BlogBundle/Entity/BlogComment.php
- namespace Acme\BlogBundle\Entity;
+ // src/AppBundle/Entity/BlogComment.php
+ namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
- * Acme\BlogBundle\Entity\BlogComment
- *
* @ORM\Table(name="blog_comment")
* @ORM\Entity
*/
@@ -170,4 +169,4 @@ entity in the ``BlogComment`` entity class.
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 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 06e537b77e3..35795e83dc2 100644
--- a/email.rst
+++ b/email.rst
@@ -46,8 +46,9 @@ already included:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:swiftmailer="http://symfony.com/schema/dic/swiftmailer"
xsi:schemaLocation="http://symfony.com/schema/dic/services
- http://symfony.com/schema/dic/services/services-1.0.xsd
- http://symfony.com/schema/dic/swiftmailer http://symfony.com/schema/dic/swiftmailer/swiftmailer-1.0.xsd">
+ https://symfony.com/schema/dic/services/services-1.0.xsd
+ http://symfony.com/schema/dic/swiftmailer
+ https://symfony.com/schema/dic/swiftmailer/swiftmailer-1.0.xsd">
loadFromExtension('swiftmailer', array(
+ $container->loadFromExtension('swiftmailer', [
'transport' => "%mailer_transport%",
'host' => "%mailer_host%",
'username' => "%mailer_user%",
'password' => "%mailer_password%",
- ));
+ ]);
These values (e.g. ``%mailer_transport%``), are reading from the parameters
that are set in the :ref:`parameters.yml ` file. You
@@ -101,7 +102,7 @@ The Swift Mailer library works by creating, configuring and then sending
of the message and is accessible via the ``mailer`` service. Overall, sending
an email is pretty straightforward::
- public function indexAction($name)
+ public function indexAction($name, \Swift_Mailer $mailer)
{
$message = (new \Swift_Message('Hello Email'))
->setFrom('send@example.com')
@@ -110,23 +111,25 @@ an email is pretty straightforward::
$this->renderView(
// app/Resources/views/Emails/registration.html.twig
'Emails/registration.html.twig',
- array('name' => $name)
+ ['name' => $name]
),
'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(
'Emails/registration.txt.twig',
- array('name' => $name)
+ ['name' => $name]
),
'text/plain'
)
- */
;
- $this->get('mailer')->send($message);
+ $mailer->send($message);
+
+ // or, you can also fetch the mailer service this way
+ // $this->get('mailer')->send($message);
return $this->render(...);
}
@@ -135,7 +138,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
{# app/Resources/views/Emails/registration.html.twig #}
You did it! You registered!
@@ -150,11 +153,6 @@ template might look something like this:
{# Makes an absolute URL to the /images/logo.png file #}
-.. versionadded:: 2.7
- The ``absolute_url()`` function was introduced in Symfony 2.7. Prior
- to 2.7, the ``asset()`` function has an argument to enable returning
- an absolute URL.
-
The ``$message`` object supports many more options, such as including attachments,
adding HTML content, and much more. Fortunately, Swift Mailer covers the topic
of `Creating Messages`_ in great detail in its documentation.
diff --git a/email/cloud.rst b/email/cloud.rst
index d7ddbd33f3b..51103f1190d 100644
--- a/email/cloud.rst
+++ b/email/cloud.rst
@@ -12,8 +12,8 @@ option. If setting up and maintaining your own reliable mail server causes
you a headache there's a simple solution: Leverage the cloud to send your
emails.
-This article shows how easy it is to integrate
-`Amazon's Simple Email Service (SES)`_ into Symfony.
+This article shows how to integrate `Amazon's Simple Email Service (SES)`_
+into Symfony.
.. note::
@@ -47,9 +47,9 @@ and complete the configuration with the provided ``username`` and ``password``:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:swiftmailer="http://symfony.com/schema/dic/swiftmailer"
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/swiftmailer
- http://symfony.com/schema/dic/swiftmailer/swiftmailer-1.0.xsd">
+ https://symfony.com/schema/dic/swiftmailer/swiftmailer-1.0.xsd">
loadFromExtension('swiftmailer', array(
+ $container->loadFromExtension('swiftmailer', [
'transport' => 'smtp',
'host' => 'email-smtp.us-east-1.amazonaws.com',
'port' => 587,
'encryption' => 'tls',
'username' => 'AWS_SES_SMTP_USERNAME',
'password' => 'AWS_SES_SMTP_PASSWORD',
- ));
+ ]);
The ``port`` and ``encryption`` keys are not present in the Symfony Standard
-Edition configuration by default, but you can simply add them as needed.
+Edition configuration by default, but you can add them if needed.
And that's it, you're ready to start sending emails through the cloud!
diff --git a/email/dev_environment.rst b/email/dev_environment.rst
index 9b79e3f816c..8224eab88c8 100644
--- a/email/dev_environment.rst
+++ b/email/dev_environment.rst
@@ -7,9 +7,9 @@ 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 easily 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
+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).
@@ -38,20 +38,20 @@ will not be sent when you run tests, but will continue to be sent in the
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:swiftmailer="http://symfony.com/schema/dic/swiftmailer"
xsi:schemaLocation="http://symfony.com/schema/dic/services
- http://symfony.com/schema/dic/services/services-1.0.xsd
- http://symfony.com/schema/dic/swiftmailer http://symfony.com/schema/dic/swiftmailer/swiftmailer-1.0.xsd">
+ https://symfony.com/schema/dic/services/services-1.0.xsd
+ http://symfony.com/schema/dic/swiftmailer https://symfony.com/schema/dic/swiftmailer/swiftmailer-1.0.xsd">
-
+
.. code-block:: php
// app/config/config_test.php
- $container->loadFromExtension('swiftmailer', array(
+ $container->loadFromExtension('swiftmailer', [
'disable_delivery' => "true",
- ));
+ ]);
-If you'd also like to disable deliver in the ``dev`` environment, simply
+If you'd also like to disable deliver in the ``dev`` environment,
add this same configuration to the ``config_dev.yml`` file.
.. _sending-to-a-specified-address:
@@ -79,9 +79,9 @@ via the ``delivery_addresses`` option:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:swiftmailer="http://symfony.com/schema/dic/swiftmailer"
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/swiftmailer
- http://symfony.com/schema/dic/swiftmailer/swiftmailer-1.0.xsd">
+ https://symfony.com/schema/dic/swiftmailer/swiftmailer-1.0.xsd">
dev@example.com
@@ -91,13 +91,13 @@ via the ``delivery_addresses`` option:
.. code-block:: php
// app/config/config_dev.php
- $container->loadFromExtension('swiftmailer', array(
- 'delivery_addresses' => array("dev@example.com"),
- ));
+ $container->loadFromExtension('swiftmailer', [
+ 'delivery_addresses' => ["dev@example.com"],
+ ]);
Now, suppose you're sending an email to ``recipient@example.com``::
- public function indexAction($name)
+ public function indexAction($name, \Swift_Mailer $mailer)
{
$message = (new \Swift_Message('Hello Email'))
->setFrom('send@example.com')
@@ -105,12 +105,11 @@ Now, suppose you're sending an email to ``recipient@example.com``::
->setBody(
$this->renderView(
'HelloBundle:Hello:email.txt.twig',
- array('name' => $name)
+ ['name' => $name]
)
)
;
-
- $this->get('mailer')->send($message);
+ $mailer->send($message);
return $this->render(...);
}
@@ -146,10 +145,10 @@ by adding the ``delivery_whitelist`` option:
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$/'
+ # 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
@@ -159,9 +158,9 @@ by adding the ``delivery_whitelist`` option:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:swiftmailer="http://symfony.com/schema/dic/swiftmailer"
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/swiftmailer
- http://symfony.com/schema/dic/swiftmailer/swiftmailer-1.0.xsd">
+ https://symfony.com/schema/dic/swiftmailer/swiftmailer-1.0.xsd">
loadFromExtension('swiftmailer', array(
+ $container->loadFromExtension('swiftmailer', [
'transport' => 'gmail',
'username' => 'your_gmail_username',
'password' => 'your_gmail_password',
- ));
+ ]);
.. tip::
@@ -81,9 +81,9 @@ In the development configuration file, change the ``transport`` setting to
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:swiftmailer="http://symfony.com/schema/dic/swiftmailer"
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/swiftmailer
- http://symfony.com/schema/dic/swiftmailer/swiftmailer-1.0.xsd">
+ https://symfony.com/schema/dic/swiftmailer/swiftmailer-1.0.xsd">
loadFromExtension('swiftmailer', array(
+ $container->loadFromExtension('swiftmailer', [
'transport' => 'gmail',
'username' => '%mailer_user%',
'password' => '%mailer_password%',
- ));
+ ]);
Redefining the Default Configuration Parameters
-----------------------------------------------
-The ``gmail`` transport is simply a shortcut that uses the ``smtp`` transport
+The ``gmail`` transport is a shortcut that uses the ``smtp`` transport
and sets these options:
============== ==================
@@ -122,7 +122,7 @@ parameters.
If your Gmail account uses 2-Step-Verification, you must `generate an App password`_
and use it as the value of the ``mailer_password`` parameter. You must also ensure
-that you `allow less secure apps to access your Gmail account`_.
+that you `allow less secure applications to access your Gmail account`_.
.. seealso::
@@ -130,4 +130,4 @@ that you `allow less secure apps to access your Gmail account`_.
for more details.
.. _`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
diff --git a/email/spool.rst b/email/spool.rst
index 53622386f49..ca7b89a76fd 100644
--- a/email/spool.rst
+++ b/email/spool.rst
@@ -21,7 +21,7 @@ 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
-swiftmailer with the memory option, use the following configuration:
+Swift Mailer with the memory option, use the following configuration:
.. configuration-block::
@@ -39,21 +39,21 @@ swiftmailer with the memory option, use the following configuration:
+ xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
+ http://symfony.com/schema/dic/swiftmailer https://symfony.com/schema/dic/swiftmailer/swiftmailer-1.0.xsd">
-
+
.. code-block:: php
// app/config/config.php
- $container->loadFromExtension('swiftmailer', array(
- // ...
- 'spool' => array('type' => 'memory'),
- ));
+ $container->loadFromExtension('swiftmailer', [
+ // ...
+ 'spool' => ['type' => 'memory'],
+ ]);
.. _spool-using-a-file:
@@ -63,7 +63,7 @@ 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)!
+writable by Symfony (or your webserver/PHP)!
In order to use the spool with files, use the following configuration:
@@ -86,8 +86,8 @@ In order to use the spool with files, use the following configuration:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:swiftmailer="http://symfony.com/schema/dic/swiftmailer"
xsi:schemaLocation="http://symfony.com/schema/dic/services
- http://symfony.com/schema/dic/services/services-1.0.xsd
- http://symfony.com/schema/dic/swiftmailer http://symfony.com/schema/dic/swiftmailer/swiftmailer-1.0.xsd">
+ https://symfony.com/schema/dic/services/services-1.0.xsd
+ http://symfony.com/schema/dic/swiftmailer https://symfony.com/schema/dic/swiftmailer/swiftmailer-1.0.xsd">
loadFromExtension('swiftmailer', array(
- // ...
+ $container->loadFromExtension('swiftmailer', [
+ // ...
- 'spool' => array(
+ '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.root_dir%`` parameter to reference
+ remember that you can use the ``%kernel.project_dir%`` parameter to reference
the project's root:
.. code-block:: yaml
- path: '%kernel.root_dir%/spool'
+ path: '%kernel.project_dir%/app/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.
@@ -125,38 +125,38 @@ There is a console command to send the messages in the spool:
.. code-block:: terminal
- $ php app/console swiftmailer:spool:send --env=prod
+ $ 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 app/console swiftmailer:spool:send --message-limit=10 --env=prod
+ $ php bin/console swiftmailer:spool:send --message-limit=10 --env=prod
You can also set the time limit in seconds:
.. code-block:: terminal
- $ php app/console swiftmailer:spool:send --time-limit=10 --env=prod
+ $ php bin/console swiftmailer:spool:send --time-limit=10 --env=prod
-Of course you will not want to run this manually in reality. Instead, the
-console command should be triggered by a cron job or scheduled task and run
-at a regular interval.
+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``
+ When you create a message with Swift Mailer, 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.
+ class name changes on every cache clear.
- On the next execution of ``swiftmailer:spool:send`` an error will raise because
- the class ``Swift_Message_`` doesn't exist (anymore).
+ So if you send a mail and then you clear the cache, 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
index 95df725142c..58ba1cebd13 100644
--- a/email/testing.rst
+++ b/email/testing.rst
@@ -10,9 +10,9 @@ 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 an easy controller action that sends an email::
+Start with a controller action that sends an email::
- public function sendEmailAction($name)
+ public function sendEmailAction($name, \Swift_Mailer $mailer)
{
$message = (new \Swift_Message('Hello Email'))
->setFrom('send@example.com')
@@ -20,7 +20,7 @@ Start with an easy controller action that sends an email::
->setBody('You should see me from the profiler!')
;
- $this->get('mailer')->send($message);
+ $mailer->send($message);
return $this->render(...);
}
@@ -28,7 +28,9 @@ Start with an easy controller action that sends an email::
In your functional test, use the ``swiftmailer`` collector on the profiler
to get information about the messages sent on the previous request::
- // src/AppBundle/Tests/Controller/MailControllerTest.php
+ // tests/AppBundle/Controller/MailControllerTest.php
+ namespace Tests\AppBundle\Controller;
+
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class MailControllerTest extends WebTestCase
diff --git a/event_dispatcher.rst b/event_dispatcher.rst
index 6ee02e6af70..b7835fa778b 100644
--- a/event_dispatcher.rst
+++ b/event_dispatcher.rst
@@ -26,8 +26,8 @@ The most common way to listen to an event is to register an **event listener**::
// src/AppBundle/EventListener/ExceptionListener.php
namespace AppBundle\EventListener;
- use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpFoundation\Response;
+ use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
class ExceptionListener
@@ -77,8 +77,7 @@ using a special "tag":
# app/config/services.yml
services:
- app.exception_listener:
- class: AppBundle\EventListener\ExceptionListener
+ AppBundle\EventListener\ExceptionListener:
tags:
- { name: kernel.event_listener, event: kernel.exception }
@@ -89,13 +88,11 @@ using a special "tag":
+ https://symfony.com/schema/dic/services/services-1.0.xsd">
-
-
-
+
+
@@ -105,24 +102,25 @@ using a special "tag":
// app/config/services.php
use AppBundle\EventListener\ExceptionListener;
- $container
- ->register('app.exception_listener', ExceptionListener::class)
- ->addTag('kernel.event_listener', array('event' => 'kernel.exception'))
+ $container->register(ExceptionListener::class)
+ ->addTag('kernel.event_listener', ['event' => 'kernel.exception'])
;
.. note::
There is an optional tag attribute called ``method`` which defines which method
- to execute when the event is triggered. By default the name of the method is
+ to execute when the event is triggered. By default, the name of the method is
``on`` + "camel-cased event name". If the event is ``kernel.exception`` the
method executed by default is ``onKernelException()``.
The other optional tag attribute is called ``priority``, which defaults to
- ``0`` and it controls the order in which listeners are executed (the higher
- the number the earlier a listener is executed). This is useful when you
- need to guarantee that one listener is executed before another. The priorities
- of the internal Symfony listeners usually range from ``-255`` to ``255`` but
- your own listeners can use any positive or negative integer.
+ ``0`` and it controls the order in which listeners are executed for a given
+ event (the higher the number the earlier a listener is executed). This is
+ useful when you need to guarantee that one listener is executed before
+ another. The priorities of the internal Symfony listeners usually range from
+ ``-255`` to ``255`` but your own listeners can use any positive or negative integer.
+
+.. _events-subscriber:
Creating an Event Subscriber
----------------------------
@@ -132,10 +130,12 @@ that defines one or more methods that listen to one or various events. The main
difference with the event listeners is that subscribers always know which events
they are listening to.
-In a given subscriber, different methods can listen to the same event. The order
-in which methods are executed is defined by the ``priority`` parameter of each
-method (the higher the number the earlier the method is called). To learn more
-about event subscribers, read :doc:`/components/event_dispatcher`.
+If different event subscriber methods listen to the same event, their order is
+defined by the ``priority`` parameter. This value is a positive or negative
+integer which defaults to ``0``. The higher the number, the earlier the method
+is called. **Priority is aggregated for all listeners and subscribers**, so your
+methods could be executed before or after the methods defined in other listeners
+and subscribers. To learn more about event subscribers, read :doc:`/components/event_dispatcher`.
The following example shows an event subscriber that defines several methods which
listen to the same ``kernel.exception`` event::
@@ -152,13 +152,13 @@ listen to the same ``kernel.exception`` event::
public static function getSubscribedEvents()
{
// return the subscribed events, their methods and priorities
- return array(
- KernelEvents::EXCEPTION => array(
- array('processException', 10),
- array('logException', 0),
- array('notifyException', -10),
- )
- );
+ return [
+ KernelEvents::EXCEPTION => [
+ ['processException', 10],
+ ['logException', 0],
+ ['notifyException', -10],
+ ],
+ ];
}
public function processException(GetResponseForExceptionEvent $event)
@@ -177,47 +177,17 @@ listen to the same ``kernel.exception`` event::
}
}
-Now, you just need to register the class as a service and add the
-``kernel.event_subscriber`` tag to tell Symfony that this is an event subscriber:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/services.yml
- services:
- app.exception_subscriber:
- class: AppBundle\EventSubscriber\ExceptionSubscriber
- tags:
- - { name: kernel.event_subscriber }
+That's it! Your ``services.yml`` file should already be setup to load services from
+the ``EventSubscriber`` directory. Symfony takes care of the rest.
- .. code-block:: xml
+.. _ref-event-subscriber-configuration:
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/services.php
- use AppBundle\EventSubscriber\ExceptionSubscriber;
+.. tip::
- $container
- ->register('app.exception_subscriber', ExceptionSubscriber::class)
- ->addTag('kernel.event_subscriber')
- ;
+ If your methods are *not* called when an exception is thrown, double-check that
+ you're :ref:`loading services ` from
+ the ``EventSubscriber`` directory and have :ref:`autoconfigure `
+ enabled. You can also manually add the ``kernel.event_subscriber`` tag.
Request Events, Checking Types
------------------------------
@@ -231,8 +201,6 @@ or a "sub request"::
namespace AppBundle\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
- use Symfony\Component\HttpKernel\HttpKernel;
- use Symfony\Component\HttpKernel\HttpKernelInterface;
class RequestListener
{
@@ -273,14 +241,14 @@ using the console. To show all events and their listeners, run:
.. code-block:: terminal
- $ php app/console debug:event-dispatcher
+ $ php bin/console debug:event-dispatcher
You can get registered listeners for a particular event by specifying
its name:
.. code-block:: terminal
- $ php app/console debug:event-dispatcher kernel.exception
+ $ php bin/console debug:event-dispatcher kernel.exception
Learn more
----------
diff --git a/event_dispatcher/before_after_filters.rst b/event_dispatcher/before_after_filters.rst
index 0ed6588c0e6..4b5a36bd401 100644
--- a/event_dispatcher/before_after_filters.rst
+++ b/event_dispatcher/before_after_filters.rst
@@ -8,10 +8,10 @@ It is quite common in web application development to need some logic to be
executed just before or just after your controller actions acting as filters
or hooks.
-In symfony1, this was achieved with the preExecute and postExecute methods.
-Most major frameworks have similar methods but there is no such thing in Symfony.
-The good news is that there is a much better way to interfere with the
-Request -> Response process using the :doc:`EventDispatcher component `.
+Some web frameworks define methods like ``preExecute()`` and ``postExecute()``,
+but there is no such thing in Symfony. The good news is that there is a much
+better way to interfere with the Request -> Response process using the
+:doc:`EventDispatcher component `.
Token Validation Example
------------------------
@@ -53,7 +53,7 @@ parameters key:
+ https://symfony.com/schema/dic/services/services-1.0.xsd">
@@ -66,17 +66,17 @@ parameters key:
.. code-block:: php
// app/config/config.php
- $container->setParameter('tokens', array(
+ $container->setParameter('tokens', [
'client1' => 'pass1',
'client2' => 'pass2',
- ));
+ ]);
Tag Controllers to Be Checked
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-A ``kernel.controller`` listener gets notified on *every* request, right before
-the controller is executed. So, first, you need some way to identify if the
-controller that matches the request needs token validation.
+A ``kernel.controller`` (aka ``KernelEvents::CONTROLLER``) listener gets notified
+on *every* request, right before the controller is executed. So, first, you need
+some way to identify if the controller that matches the request needs token validation.
A clean and easy way is to create an empty interface and make the controllers
implement it::
@@ -104,21 +104,23 @@ A controller that implements this interface simply looks like this::
}
}
-Creating an Event Listener
-~~~~~~~~~~~~~~~~~~~~~~~~~~
+Creating an Event Subscriber
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Next, you'll need to create an event listener, which will hold the logic
+Next, you'll need to create an event subscriber, which will hold the logic
that you want to be executed before your controllers. If you're not familiar with
-event listeners, you can learn more about them at :doc:`/event_dispatcher`::
+event subscribers, you can learn more about them at :doc:`/event_dispatcher`::
- // src/AppBundle/EventListener/TokenListener.php
- namespace AppBundle\EventListener;
+ // src/AppBundle/EventSubscriber/TokenSubscriber.php
+ namespace AppBundle\EventSubscriber;
use AppBundle\Controller\TokenAuthenticatedController;
- use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
+ use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+ use Symfony\Component\HttpKernel\KernelEvents;
- class TokenListener
+ class TokenSubscriber implements EventSubscriberInterface
{
private $tokens;
@@ -131,92 +133,56 @@ event listeners, you can learn more about them at :doc:`/event_dispatcher`::
{
$controller = $event->getController();
- /*
- * $controller passed can be either a class or a Closure.
- * This is not usual in Symfony but it may happen.
- * If it is a class, it comes in array format
- */
- if (!is_array($controller)) {
- return;
+ // when a controller class defines multiple action methods, the controller
+ // is returned as [$controllerInstance, 'methodName']
+ if (is_array($controller)) {
+ $controller = $controller[0];
}
- if ($controller[0] instanceof TokenAuthenticatedController) {
+ if ($controller instanceof TokenAuthenticatedController) {
$token = $event->getRequest()->query->get('token');
if (!in_array($token, $this->tokens)) {
throw new AccessDeniedHttpException('This action needs a valid token!');
}
}
}
- }
-
-Registering the Listener
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-Finally, register your listener as a service and tag it as an event listener.
-By listening on ``kernel.controller``, you're telling Symfony that you want
-your listener to be called just before any controller is executed.
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/services.yml
- services:
- app.tokens.action_listener:
- class: AppBundle\EventListener\TokenListener
- arguments: ['%tokens%']
- tags:
- - { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
- .. code-block:: xml
-
-
-
-
-
-
-
- %tokens%
-
-
-
-
-
- .. code-block:: php
+ public static function getSubscribedEvents()
+ {
+ return [
+ KernelEvents::CONTROLLER => 'onKernelController',
+ ];
+ }
+ }
- // app/config/services.php
- use AppBundle\EventListener\TokenListener;
+That's it! Your ``services.yml`` file should already be setup to load services from
+the ``EventSubscriber`` directory. Symfony takes care of the rest. Your
+``TokenSubscriber`` ``onKernelController()`` method will be executed on each request.
+If the controller that is about to be executed implements ``TokenAuthenticatedController``,
+token authentication is applied. This lets you have a "before" filter on any controller
+you want.
- $container->register('app.tokens.action_listener', TokenListener::class)
- ->addArgument('%tokens%')
- ->addTag('kernel.event_listener', array(
- 'event' => 'kernel.controller',
- 'method' => 'onKernelController',
- ));
+.. tip::
-With this configuration, your ``TokenListener`` ``onKernelController()`` method
-will be executed on each request. If the controller that is about to be executed
-implements ``TokenAuthenticatedController``, token authentication is
-applied. This lets you have a "before" filter on any controller that you
-want.
+ If your subscriber is *not* called on each request, double-check that
+ you're :ref:`loading services ` from
+ the ``EventSubscriber`` directory and have :ref:`autoconfigure `
+ enabled. You can also manually add the ``kernel.event_subscriber`` tag.
After Filters with the ``kernel.response`` Event
------------------------------------------------
In addition to having a "hook" that's executed *before* your controller, you
can also add a hook that's executed *after* your controller. For this example,
-imagine that you want to add a sha1 hash (with a salt using that token) to
+imagine that you want to add a ``sha1`` hash (with a salt using that token) to
all responses that have passed this token authentication.
-Another core Symfony event - called ``kernel.response`` - is notified on
-every request, but after the controller returns a Response object. Creating
-an "after" listener is as easy as creating a listener class and registering
+Another core Symfony event - called ``kernel.response`` (aka ``KernelEvents::RESPONSE``) -
+is notified on every request, but after the controller returns a Response object.
+Creating an "after" listener is as easy as creating a listener class and registering
it as a service on this event.
-For example, take the ``TokenListener`` from the previous example and first
+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::
@@ -235,9 +201,9 @@ serve as a basic flag that this request underwent token authentication::
}
}
-Now, add another method to this class - ``onKernelResponse()`` - that looks
-for this flag on the request object and sets a custom header on the response
-if it's found::
+Now, configure the subscriber to listen to another event and add ``onKernelResponse()``.
+This will look for the ``auth_token`` flag on the request object and set a custom
+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;
@@ -256,58 +222,15 @@ if it's found::
$response->headers->set('X-CONTENT-HASH', $hash);
}
-Finally, a second "tag" is needed in the service definition to notify Symfony
-that the ``onKernelResponse`` event should be notified for the ``kernel.response``
-event:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/services.yml
- services:
- app.tokens.action_listener:
- class: AppBundle\EventListener\TokenListener
- arguments: ['%tokens%']
- tags:
- - { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
- - { name: kernel.event_listener, event: kernel.response, method: onKernelResponse }
-
- .. code-block:: xml
-
-
-
-
-
-
-
- %tokens%
-
-
-
-
-
-
- .. code-block:: php
+ public static function getSubscribedEvents()
+ {
+ return [
+ KernelEvents::CONTROLLER => 'onKernelController',
+ KernelEvents::RESPONSE => 'onKernelResponse',
+ ];
+ }
- // app/config/services.php
- use AppBundle\EventListener\TokenListener;
-
- $container->register('app.tokens.action_listener', TokenListener::class)
- ->addArgument('%tokens%')
- ->addTag('kernel.event_listener', array(
- 'event' => 'kernel.controller',
- 'method' => 'onKernelController',
- ))
- ->addTag('kernel.event_listener', array(
- 'event' => 'kernel.response',
- 'method' => 'onKernelResponse',
- ));
-
-That's it! The ``TokenListener`` is now notified before every controller is
+That's it! The ``TokenSubscriber`` is now notified before every controller is
executed (``onKernelController()``) and after every controller returns a response
(``onKernelResponse()``). By making specific controllers implement the ``TokenAuthenticatedController``
interface, your listener knows which controllers it should take action on.
diff --git a/event_dispatcher/class_extension.rst b/event_dispatcher/class_extension.rst
deleted file mode 100644
index 382c8dcf970..00000000000
--- a/event_dispatcher/class_extension.rst
+++ /dev/null
@@ -1,117 +0,0 @@
-.. index::
- single: EventDispatcher
-
-How to Extend a Class without Using Inheritance
-===============================================
-
-To allow multiple classes to add methods to another one, you can define the
-magic ``__call()`` method in the class you want to be extended like this::
-
- class Foo
- {
- // ...
-
- public function __call($method, $arguments)
- {
- // creates an event named 'foo.method_is_not_found'
- $event = new HandleUndefinedMethodEvent($this, $method, $arguments);
- $this->dispatcher->dispatch('foo.method_is_not_found', $event);
-
- // no listener was able to process the event? The method does not exist
- if (!$event->isProcessed()) {
- throw new \Exception(sprintf('Call to undefined method %s::%s.', get_class($this), $method));
- }
-
- // returns the listener returned value
- return $event->getReturnValue();
- }
- }
-
-This uses a special ``HandleUndefinedMethodEvent`` that should also be
-created. This is a generic class that could be reused each time you need to
-use this pattern of class extension::
-
- use Symfony\Component\EventDispatcher\Event;
-
- class HandleUndefinedMethodEvent extends Event
- {
- protected $subject;
- protected $method;
- protected $arguments;
- protected $returnValue;
- protected $isProcessed = false;
-
- public function __construct($subject, $method, $arguments)
- {
- $this->subject = $subject;
- $this->method = $method;
- $this->arguments = $arguments;
- }
-
- public function getSubject()
- {
- return $this->subject;
- }
-
- public function getMethod()
- {
- return $this->method;
- }
-
- public function getArguments()
- {
- return $this->arguments;
- }
-
- /**
- * Sets the value to return and stops other listeners from being notified
- */
- public function setReturnValue($returnValue)
- {
- $this->returnValue = $returnValue;
- $this->isProcessed = true;
- $this->stopPropagation();
- }
-
- public function getReturnValue()
- {
- return $this->returnValue;
- }
-
- public function isProcessed()
- {
- return $this->isProcessed;
- }
- }
-
-Next, create a class that will listen to the ``foo.method_is_not_found`` event
-and *add* the method ``bar()``::
-
- class Bar
- {
- public function onFooMethodIsNotFound(HandleUndefinedMethodEvent $event)
- {
- // only respond to the calls to the 'bar' method
- if ('bar' != $event->getMethod()) {
- // allow another listener to take care of this unknown method
- return;
- }
-
- // the subject object (the foo instance)
- $foo = $event->getSubject();
-
- // the bar method arguments
- $arguments = $event->getArguments();
-
- // ... do something
-
- // set the return value
- $event->setReturnValue($someValue);
- }
- }
-
-Finally, add the new ``bar()`` method to the ``Foo`` class by registering an
-instance of ``Bar`` with the ``foo.method_is_not_found`` event::
-
- $bar = new Bar();
- $dispatcher->addListener('foo.method_is_not_found', array($bar, 'onFooMethodIsNotFound'));
diff --git a/event_dispatcher/method_behavior.rst b/event_dispatcher/method_behavior.rst
index 0356899ebc3..5ea5e74ffab 100644
--- a/event_dispatcher/method_behavior.rst
+++ b/event_dispatcher/method_behavior.rst
@@ -11,45 +11,129 @@ If you want to do something just before, or just after a method is called, you
can dispatch an event respectively at the beginning or at the end of the
method::
- class Foo
+ class CustomMailer
{
// ...
- public function send($foo, $bar)
+ public function send($subject, $message)
{
- // do something before the method
- $event = new FilterBeforeSendEvent($foo, $bar);
- $this->dispatcher->dispatch('foo.pre_send', $event);
+ // dispatch an event before the method
+ $event = new BeforeSendMailEvent($subject, $message);
+ $this->dispatcher->dispatch('mailer.pre_send', $event);
// get $foo and $bar from the event, they may have been modified
- $foo = $event->getFoo();
- $bar = $event->getBar();
+ $subject = $event->getSubject();
+ $message = $event->getMessage();
// the real method implementation is here
$returnValue = ...;
// do something after the method
- $event = new FilterSendReturnValue($returnValue);
- $this->dispatcher->dispatch('foo.post_send', $event);
+ $event = new AfterSendMailEvent($returnValue);
+ $this->dispatcher->dispatch('mailer.post_send', $event);
return $event->getReturnValue();
}
}
-In this example, two events are thrown: ``foo.pre_send``, before the method is
-executed, and ``foo.post_send`` after the method is executed. Each uses a
+In this example, two events are thrown: ``mailer.pre_send``, before the method is
+executed, and ``mailer.post_send`` after the method is executed. Each uses a
custom Event class to communicate information to the listeners of the two
-events. These event classes would need to be created by you and should allow,
-in this example, the variables ``$foo``, ``$bar`` and ``$returnValue`` to be retrieved
-and set by the listeners.
+events. For example, ``BeforeSendMailEvent`` might look like this::
-For example, assuming the ``FilterSendReturnValue`` has a ``setReturnValue()``
-method, one listener might look like this::
+ // src/AppBundle/Event/BeforeSendMailEvent.php
+ namespace AppBundle\Event;
- public function onFooPostSend(FilterSendReturnValue $event)
+ use Symfony\Component\EventDispatcher\Event;
+
+ class BeforeSendMailEvent extends Event
+ {
+ private $subject;
+ private $message;
+
+ public function __construct($subject, $message)
+ {
+ $this->subject = $subject;
+ $this->message = $message;
+ }
+
+ public function getSubject()
+ {
+ return $this->subject;
+ }
+
+ public function setSubject($subject)
+ {
+ $this->subject = $subject;
+ }
+
+ public function getMessage()
+ {
+ return $this->message;
+ }
+
+ public function setMessage($message)
+ {
+ $this->message = $message;
+ }
+ }
+
+And the ``AfterSendMailEvent`` even like this::
+
+ // src/AppBundle/Event/AfterSendMailEvent.php
+ namespace AppBundle\Event;
+
+ use Symfony\Component\EventDispatcher\Event;
+
+ class AfterSendMailEvent extends Event
{
- $returnValue = $event->getReturnValue();
- // modify the original ``$returnValue`` value
+ private $returnValue;
- $event->setReturnValue($returnValue);
+ public function __construct($returnValue)
+ {
+ $this->returnValue = $returnValue;
+ }
+
+ public function getReturnValue()
+ {
+ return $this->returnValue;
+ }
+
+ public function setReturnValue($returnValue)
+ {
+ $this->returnValue = $returnValue;
+ }
+ }
+
+Both events allow you to get some information (e.g. ``getMessage()``) and even change
+that information (e.g. ``setMessage()``).
+
+Now, you can create an event subscriber to hook into this event. For example, you
+could listen to the ``mailer.post_send`` event and change the method's return value::
+
+ // src/AppBundle/EventSubscriber/MailPostSendSubscriber.php
+ namespace AppBundle\EventSubscriber;
+
+ use AppBundle\Event\AfterSendMailEvent;
+ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+ class MailPostSendSubscriber implements EventSubscriberInterface
+ {
+ public function onMailerPostSend(AfterSendMailEvent $event)
+ {
+ $returnValue = $event->getReturnValue();
+ // modify the original ``$returnValue`` value
+
+ $event->setReturnValue($returnValue);
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return [
+ 'mailer.post_send' => 'onMailerPostSend'
+ ];
+ }
}
+
+That's it! Your subscriber should be called automatically (or read more about
+:ref:`event subscriber configuration `).
diff --git a/form/action_method.rst b/form/action_method.rst
index 40a236292b0..06bd7e3a858 100644
--- a/form/action_method.rst
+++ b/form/action_method.rst
@@ -89,10 +89,10 @@ options:
{
// ...
- $form = $this->createForm(TaskType::class, $task, array(
+ $form = $this->createForm(TaskType::class, $task, [
'action' => $this->generateUrl('target_route'),
'method' => 'GET',
- ));
+ ]);
// ...
}
@@ -109,15 +109,15 @@ options:
$formFactory = $formFactoryBuilder->getFormFactory();
- $form = $formFactory->create(TaskType::class, $task, array(
+ $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
+.. code-block:: twig
{# app/Resources/views/default/new.html.twig #}
{{ form_start(form, {'action': path('target_route'), 'method': 'GET'}) }}
diff --git a/form/bootstrap4.rst b/form/bootstrap4.rst
new file mode 100644
index 00000000000..6a349fb33e1
--- /dev/null
+++ b/form/bootstrap4.rst
@@ -0,0 +1,119 @@
+Bootstrap 4 Form Theme
+======================
+
+Symfony provides several ways of integrating Bootstrap into your application. The
+most straightforward way is to just add the required ```` and ``
{% endjavascripts %}
-In the ``dev`` environment, each file is still served individually, so that
-you can debug problems more easily. However, in the ``prod`` environment
-(or more specifically, when the ``debug`` flag is ``false``), this will be
-rendered as a single ``script`` tag, which contains the contents of all of
-the JavaScript files.
+In the ``dev`` environment, each file is still served individually, so that you
+can debug problems. However, in the ``prod`` environment (or more specifically,
+when the ``debug`` flag is ``false``), this will be rendered as a single
+``script`` tag, which contains the contents of all of the JavaScript files.
.. tip::
@@ -323,9 +322,9 @@ configuration under the ``assetic`` section. Read more in the
xmlns:assetic="http://symfony.com/schema/dic/assetic"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/assetic
- http://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
+ https://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
@@ -338,16 +337,16 @@ configuration under the ``assetic`` section. Read more in the
.. code-block:: php
// app/config/config.php
- $container->loadFromExtension('assetic', array(
- 'assets' => array(
- 'jquery_and_ui' => array(
- 'inputs' => array(
+ $container->loadFromExtension('assetic', [
+ 'assets' => [
+ 'jquery_and_ui' => [
+ 'inputs' => [
'@AppBundle/Resources/public/js/thirdparty/jquery.js',
'@AppBundle/Resources/public/js/thirdparty/jquery.ui.js',
- ),
- ),
- ),
- ));
+ ],
+ ],
+ ],
+ ]);
After you have defined the named assets, you can reference them in your templates
with the ``@named_asset`` notation:
@@ -404,27 +403,27 @@ should be defined:
xmlns:assetic="http://symfony.com/schema/dic/assetic"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/assetic
- http://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
+ https://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
+ bin="/usr/local/bin/uglifyjs"/>
.. code-block:: php
// app/config/config.php
- $container->loadFromExtension('assetic', array(
- 'filters' => array(
- 'uglifyjs2' => array(
+ $container->loadFromExtension('assetic', [
+ 'filters' => [
+ 'uglifyjs2' => [
'bin' => '/usr/local/bin/uglifyjs',
- ),
- ),
- ));
+ ],
+ ],
+ ]);
Now, to actually *use* the filter on a group of JavaScript files, add it
into your template:
@@ -452,10 +451,11 @@ done from the template and is relative to the public document root:
.. note::
- Symfony also contains a method for cache *busting*, where the final URL
- generated by Assetic contains a query parameter that can be incremented
- via configuration on each deployment. For more information, see the
- :ref:`reference-framework-assets-version` configuration option.
+ Symfony provides various cache busting implementations via the
+ :ref:`version `,
+ :ref:`version_format `, and
+ :ref:`json_manifest_path `
+ configuration options.
.. _assetic-dumping:
@@ -498,7 +498,7 @@ each time you deploy), you should run the following command:
.. code-block:: terminal
- $ php app/console assetic:dump --env=prod --no-debug
+ $ php bin/console assetic:dump --env=prod --no-debug
This will physically generate and write each file that you need (e.g. ``/js/abcd123.js``).
If you update any of your assets, you'll need to run this again to regenerate
@@ -531,26 +531,26 @@ the following change in your ``config_dev.yml`` file:
xmlns:assetic="http://symfony.com/schema/dic/assetic"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/assetic
- http://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
+ https://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
-
+
.. code-block:: php
// app/config/config_dev.php
- $container->loadFromExtension('assetic', array(
+ $container->loadFromExtension('assetic', [
'use_controller' => false,
- ));
+ ]);
Next, since Symfony is no longer generating these assets for you, you'll
need to dump them manually. To do so, run the following command:
.. code-block:: terminal
- $ php app/console assetic:dump
+ $ php bin/console assetic:dump
This physically writes all of the asset files you need for your ``dev``
environment. The big disadvantage is that you need to run this each time
@@ -559,7 +559,7 @@ assets will be regenerated automatically *as they change*:
.. code-block:: terminal
- $ php app/console assetic:watch
+ $ php bin/console assetic:watch
The ``assetic:watch`` command was introduced in AsseticBundle 2.4. In prior
versions, you had to use the ``--watch`` option of the ``assetic:dump``
diff --git a/frontend/assetic/jpeg_optimize.rst b/frontend/assetic/jpeg_optimize.rst
index 324975cbae7..b4253a0de56 100644
--- a/frontend/assetic/jpeg_optimize.rst
+++ b/frontend/assetic/jpeg_optimize.rst
@@ -37,27 +37,27 @@ using the ``bin`` option of the ``jpegoptim`` filter:
xmlns:assetic="http://symfony.com/schema/dic/assetic"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/assetic
- http://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
+ https://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
+ bin="path/to/jpegoptim"/>
.. code-block:: php
// app/config/config.php
- $container->loadFromExtension('assetic', array(
- 'filters' => array(
- 'jpegoptim' => array(
+ $container->loadFromExtension('assetic', [
+ 'filters' => [
+ 'jpegoptim' => [
'bin' => 'path/to/jpegoptim',
- ),
- ),
- ));
+ ],
+ ],
+ ]);
It can now be used from a template:
@@ -94,36 +94,36 @@ to ``true``:
xmlns:assetic="http://symfony.com/schema/dic/assetic"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/assetic
- http://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
+ https://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
+ strip-all="true"/>
.. code-block:: php
// app/config/config.php
- $container->loadFromExtension('assetic', array(
- 'filters' => array(
- 'jpegoptim' => array(
+ $container->loadFromExtension('assetic', [
+ 'filters' => [
+ 'jpegoptim' => [
'bin' => 'path/to/jpegoptim',
'strip_all' => 'true',
- ),
- ),
- ));
+ ],
+ ],
+ ]);
Lowering Maximum Quality
~~~~~~~~~~~~~~~~~~~~~~~~
By default, the ``jpegoptim`` filter doesn't alter the quality level of the JPEG
image. Use the ``max`` option to configure the maximum quality setting (in a
-scale of ``0`` to ``100``). The reduction in the image file size will of course
+scale of ``0`` to ``100``). The reduction in the image file size will
be at the expense of its quality:
.. configuration-block::
@@ -145,29 +145,29 @@ be at the expense of its quality:
xmlns:assetic="http://symfony.com/schema/dic/assetic"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/assetic
- http://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
+ https://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
+ max="70"/>
.. code-block:: php
// app/config/config.php
- $container->loadFromExtension('assetic', array(
- 'filters' => array(
- 'jpegoptim' => array(
+ $container->loadFromExtension('assetic', [
+ 'filters' => [
+ 'jpegoptim' => [
'bin' => 'path/to/jpegoptim',
'max' => '70',
- ),
- ),
- ));
+ ],
+ ],
+ ]);
Shorter Syntax: Twig Function
-----------------------------
@@ -197,17 +197,17 @@ following configuration:
xmlns:assetic="http://symfony.com/schema/dic/assetic"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/assetic
- http://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
+ https://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
+ bin="path/to/jpegoptim"/>
+ name="jpegoptim"/>
@@ -215,16 +215,16 @@ following configuration:
.. code-block:: php
// app/config/config.php
- $container->loadFromExtension('assetic', array(
- 'filters' => array(
- 'jpegoptim' => array(
+ $container->loadFromExtension('assetic', [
+ 'filters' => [
+ 'jpegoptim' => [
'bin' => 'path/to/jpegoptim',
- ),
- ),
- 'twig' => array(
- 'functions' => array('jpegoptim'),
- ),
- ));
+ ],
+ ],
+ 'twig' => [
+ 'functions' => ['jpegoptim'],
+ ],
+ ]);
The Twig template can now be changed to the following:
@@ -256,18 +256,18 @@ file:
xmlns:assetic="http://symfony.com/schema/dic/assetic"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/assetic
- http://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
+ https://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
+ bin="path/to/jpegoptim"/>
+ output="images/*.jpg"/>
@@ -275,20 +275,20 @@ file:
.. code-block:: php
// app/config/config.php
- $container->loadFromExtension('assetic', array(
- 'filters' => array(
- 'jpegoptim' => array(
+ $container->loadFromExtension('assetic', [
+ 'filters' => [
+ 'jpegoptim' => [
'bin' => 'path/to/jpegoptim',
- ),
- ),
- 'twig' => array(
- 'functions' => array(
- 'jpegoptim' => array(
+ ],
+ ],
+ 'twig' => [
+ 'functions' => [
+ 'jpegoptim' => [
'output' => 'images/*.jpg',
- ),
- ),
- ),
- ));
+ ],
+ ],
+ ],
+ ]);
.. tip::
diff --git a/frontend/assetic/php.rst b/frontend/assetic/php.rst
index bea6f2f8946..3f2db541d2e 100644
--- a/frontend/assetic/php.rst
+++ b/frontend/assetic/php.rst
@@ -46,27 +46,27 @@ and some regular CSS and JavaScript application files (called ``main.css`` and
web/assets/
├── css
- │ ├── main.css
- │ └── code-highlight.css
+ │ ├── main.css
+ │ └── code-highlight.css
├── js
- │ ├── bootstrap.js
- │ ├── jquery.js
- │ └── main.js
+ │ ├── bootstrap.js
+ │ ├── jquery.js
+ │ └── main.js
└── scss
├── bootstrap
- │ ├── _alerts.scss
- │ ├── ...
- │ ├── _variables.scss
- │ ├── _wells.scss
- │ └── mixins
- │ ├── _alerts.scss
- │ ├── ...
- │ └── _vendor-prefixes.scss
+ │ ├── _alerts.scss
+ │ ├── ...
+ │ ├── _variables.scss
+ │ ├── _wells.scss
+ │ └── mixins
+ │ ├── _alerts.scss
+ │ ├── ...
+ │ └── _vendor-prefixes.scss
├── bootstrap.scss
├── font-awesome
- │ ├── _animated.scss
- │ ├── ...
- │ └── _variables.scss
+ │ ├── _animated.scss
+ │ ├── ...
+ │ └── _variables.scss
└── font-awesome.scss
Combining and Minimizing CSS Files and Compiling SCSS Files
@@ -93,12 +93,12 @@ First, configure a new ``scssphp`` Assetic filter:
xmlns:assetic="http://symfony.com/schema/dic/assetic"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/assetic
- http://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
+ https://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
-
+
@@ -106,14 +106,14 @@ First, configure a new ``scssphp`` Assetic filter:
.. code-block:: php
// app/config/config.php
- $container->loadFromExtension('assetic', array(
- 'filters' => array(
- 'scssphp' => array(
- 'formatter' => 'Leafo\ScssPhp\Formatter\Compressed',
- ),
- // ...
- ),
- ));
+ $container->loadFromExtension('assetic', [
+ 'filters' => [
+ 'scssphp' => [
+ 'formatter' => 'Leafo\ScssPhp\Formatter\Compressed',
+ ],
+ // ...
+ ],
+ ]);
The value of the ``formatter`` option is the fully qualified class name of the
formatter used by the filter to produce the compiled CSS file. Using the
@@ -136,7 +136,7 @@ by Assetic:
"assets/scss/font-awesome.scss"
"assets/css/*.css"
%}
-
+
{% endstylesheets %}
This simple configuration compiles, combines and minifies the SCSS files into a
@@ -166,12 +166,12 @@ First, configure a new ``jsqueeze`` Assetic filter as follows:
xmlns:assetic="http://symfony.com/schema/dic/assetic"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/assetic
- http://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
+ https://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
-
+
@@ -179,12 +179,12 @@ First, configure a new ``jsqueeze`` Assetic filter as follows:
.. code-block:: php
// app/config/config.php
- $container->loadFromExtension('assetic', array(
- 'filters' => array(
- 'jsqueeze' => null,
- // ...
- ),
- ));
+ $container->loadFromExtension('assetic', [
+ 'filters' => [
+ 'jsqueeze' => null,
+ // ...
+ ],
+ ]);
Next, update the code of your Twig template to add the ``{% javascripts %}`` tag
defined by Assetic:
diff --git a/frontend/assetic/uglifyjs.rst b/frontend/assetic/uglifyjs.rst
index 4da6c61e005..af46db3e775 100644
--- a/frontend/assetic/uglifyjs.rst
+++ b/frontend/assetic/uglifyjs.rst
@@ -21,6 +21,16 @@ Install UglifyJS
UglifyJS is available as a `Node.js`_ module. First, you need to `install Node.js`_
and then, decide the installation method: global or local.
+.. caution::
+
+ Some Linux distributions rename the Node.js binary from ``node`` to ``nodejs``.
+ This may result in errors like *"/usr/bin/env: node: No such file or
+ directory"*. You can solve this problem with a symlink:
+
+ .. code-block:: terminal
+
+ $ ln -s /usr/bin/nodejs /usr/bin/node
+
Global Installation
~~~~~~~~~~~~~~~~~~~
@@ -86,29 +96,29 @@ your JavaScripts:
xmlns:assetic="http://symfony.com/schema/dic/assetic"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/assetic
- http://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
+ https://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
+ bin="/usr/local/bin/uglifyjs"/>
.. code-block:: php
// app/config/config.php
- $container->loadFromExtension('assetic', array(
- 'filters' => array(
- 'uglifyjs2' => array(
+ $container->loadFromExtension('assetic', [
+ 'filters' => [
+ 'uglifyjs2' => [
// the path to the uglifyjs executable
'bin' => '/usr/local/bin/uglifyjs',
- ),
- ),
- ));
+ ],
+ ],
+ ]);
.. note::
@@ -154,28 +164,28 @@ can configure its location using the ``node`` key:
xmlns:assetic="http://symfony.com/schema/dic/assetic"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/assetic
- http://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
+ https://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
+ bin="/usr/local/bin/uglifyjs"/>
.. code-block:: php
// app/config/config.php
- $container->loadFromExtension('assetic', array(
+ $container->loadFromExtension('assetic', [
'node' => '/usr/bin/nodejs',
- 'uglifyjs2' => array(
+ 'uglifyjs2' => [
// the path to the uglifyjs executable
'bin' => '/usr/local/bin/uglifyjs',
- ),
- ));
+ ],
+ ]);
Minify your Assets
------------------
@@ -193,7 +203,7 @@ asset tags of your templates to tell Assetic to use the ``uglifyjs2`` filter:
The above example assumes that you have a bundle called AppBundle and your
JavaScript files are in the ``Resources/public/js`` directory under your
- bundle. However you can include your JavaScript files no matter where they are.
+ bundle. However, you can include your JavaScript files no matter where they are.
With the addition of the ``uglifyjs2`` filter to the asset tags above, you
should now see minified JavaScripts coming over the wire much faster.
@@ -258,27 +268,27 @@ Next, add the configuration for this filter:
xmlns:assetic="http://symfony.com/schema/dic/assetic"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/assetic
- http://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
+ https://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
+ bin="/usr/local/bin/uglifycss"/>
.. code-block:: php
// app/config/config.php
- $container->loadFromExtension('assetic', array(
- 'filters' => array(
- 'uglifycss' => array(
+ $container->loadFromExtension('assetic', [
+ 'filters' => [
+ 'uglifycss' => [
'bin' => '/usr/local/bin/uglifycss',
- ),
- ),
- ));
+ ],
+ ],
+ ]);
To use the filter for your CSS files, add the filter to the Assetic ``stylesheets``
helper:
@@ -286,7 +296,7 @@ helper:
.. code-block:: html+twig
{% stylesheets 'bundles/App/css/*' filter='uglifycss' filter='cssrewrite' %}
-
+
{% endstylesheets %}
Just like with the ``uglifyjs2`` filter, if you prefix the filter name with
diff --git a/frontend/assetic/yuicompressor.rst b/frontend/assetic/yuicompressor.rst
index c0d4aa8018a..7a96df63642 100644
--- a/frontend/assetic/yuicompressor.rst
+++ b/frontend/assetic/yuicompressor.rst
@@ -14,7 +14,7 @@ How to Minify JavaScripts and Stylesheets with YUI Compressor
Yahoo! provides an excellent utility for minifying JavaScripts and stylesheets
so they travel over the wire faster, the `YUI Compressor`_. Thanks to Assetic,
-you can take advantage of this tool very easily.
+you can take advantage of this tool.
Download the YUI Compressor JAR
-------------------------------
@@ -38,9 +38,9 @@ stylesheets:
# java: '/usr/bin/java'
filters:
yui_css:
- jar: '%kernel.root_dir%/Resources/java/yuicompressor.jar'
+ jar: '%kernel.project_dir%/app/Resources/java/yuicompressor.jar'
yui_js:
- jar: '%kernel.root_dir%/Resources/java/yuicompressor.jar'
+ jar: '%kernel.project_dir%/app/Resources/java/yuicompressor.jar'
.. code-block:: xml
@@ -50,34 +50,34 @@ stylesheets:
xmlns:assetic="http://symfony.com/schema/dic/assetic"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/assetic
- http://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
+ https://symfony.com/schema/dic/assetic/assetic-1.0.xsd">
+ jar="%kernel.project_dir%/app/Resources/java/yuicompressor.jar"/>
+ jar="%kernel.project_dir%/app/Resources/java/yuicompressor.jar"/>
.. code-block:: php
// app/config/config.php
- $container->loadFromExtension('assetic', array(
+ $container->loadFromExtension('assetic', [
// 'java' => '/usr/bin/java',
- 'filters' => array(
- 'yui_css' => array(
- 'jar' => '%kernel.root_dir%/Resources/java/yuicompressor.jar',
- ),
- 'yui_js' => array(
- 'jar' => '%kernel.root_dir%/Resources/java/yuicompressor.jar',
- ),
- ),
- ));
+ 'filters' => [
+ 'yui_css' => [
+ 'jar' => '%kernel.project_dir%/app/Resources/java/yuicompressor.jar',
+ ],
+ 'yui_js' => [
+ 'jar' => '%kernel.project_dir%/app/Resources/java/yuicompressor.jar',
+ ],
+ ],
+ ]);
.. note::
@@ -115,7 +115,7 @@ can be repeated to minify your stylesheets.
.. code-block:: html+twig
{% stylesheets '@AppBundle/Resources/public/css/*' filter='yui_css' %}
-
+
{% endstylesheets %}
Disable Minification in Debug Mode
diff --git a/frontend/custom_version_strategy.rst b/frontend/custom_version_strategy.rst
new file mode 100644
index 00000000000..ba56f1f7689
--- /dev/null
+++ b/frontend/custom_version_strategy.rst
@@ -0,0 +1,195 @@
+.. index::
+ single: Asset; Custom Version Strategy
+
+How to Use a Custom Version Strategy for Assets
+===============================================
+
+Asset versioning is a technique that improves the performance of web
+applications by adding a version identifier to the URL of the static assets
+(CSS, JavaScript, images, etc.) When the content of the asset changes, its
+identifier is also modified to force the browser to download it again instead of
+reusing the cached asset.
+
+If your application requires advanced versioning, such as generating the
+version dynamically based on some external information, you can create your
+own version strategy.
+
+.. note::
+
+ Symfony provides various cache busting implementations via the
+ :ref:`version `,
+ :ref:`version_format `, and
+ :ref:`json_manifest_path `
+ configuration options.
+
+Creating your Own Asset Version Strategy
+----------------------------------------
+
+The following example shows how to create a version strategy compatible with
+`gulp-buster`_. This tool defines a configuration file called ``busters.json``
+which maps each asset file to its content hash:
+
+.. code-block:: json
+
+ {
+ "js/script.js": "f9c7afd05729f10f55b689f36bb20172",
+ "css/style.css": "91cd067f79a5839536b46c494c4272d8"
+ }
+
+Implement VersionStrategyInterface
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Asset version strategies are PHP classes that implement the
+:class:`Symfony\\Component\\Asset\\VersionStrategy\\VersionStrategyInterface`.
+In this example, the constructor of the class takes as arguments the path to
+the manifest file generated by `gulp-buster`_ and the format of the generated
+version string::
+
+ // src/AppBundle/Asset/VersionStrategy/GulpBusterVersionStrategy.php
+ namespace AppBundle\Asset\VersionStrategy;
+
+ use Symfony\Component\Asset\VersionStrategy\VersionStrategyInterface;
+
+ class GulpBusterVersionStrategy implements VersionStrategyInterface
+ {
+ /**
+ * @var string
+ */
+ private $manifestPath;
+
+ /**
+ * @var string
+ */
+ private $format;
+
+ /**
+ * @var string[]
+ */
+ private $hashes;
+
+ /**
+ * @param string $manifestPath
+ * @param string|null $format
+ */
+ public function __construct($manifestPath, $format = null)
+ {
+ $this->manifestPath = $manifestPath;
+ $this->format = $format ?: '%s?%s';
+ }
+
+ public function getVersion($path)
+ {
+ if (!is_array($this->hashes)) {
+ $this->hashes = $this->loadManifest();
+ }
+
+ return isset($this->hashes[$path]) ? $this->hashes[$path] : '';
+ }
+
+ public function applyVersion($path)
+ {
+ $version = $this->getVersion($path);
+
+ if ('' === $version) {
+ return $path;
+ }
+
+ return sprintf($this->format, $path, $version);
+ }
+
+ private function loadManifest()
+ {
+ return json_decode(file_get_contents($this->manifestPath), true);
+ }
+ }
+
+Register the Strategy Service
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+After creating the strategy PHP class, register it as a Symfony service.
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # app/config/services.yml
+ services:
+ AppBundle\Asset\VersionStrategy\GulpBusterVersionStrategy:
+ arguments:
+ - "%kernel.project_dir%/busters.json"
+ - "%%s?version=%%s"
+ public: false
+
+ .. code-block:: xml
+
+
+
+
+
+
+ %kernel.project_dir%/busters.json
+ %%s?version=%%s
+
+
+
+
+ .. code-block:: php
+
+ // app/config/services.php
+ use AppBundle\Asset\VersionStrategy\GulpBusterVersionStrategy;
+ use Symfony\Component\DependencyInjection\Definition;
+
+ $container->autowire(GulpBusterVersionStrategy::class)
+ ->setArguments(
+ [
+ '%kernel.project_dir%/busters.json',
+ '%%s?version=%%s',
+ ]
+ )->setPublic(false);
+
+Finally, enable the new asset versioning for all the application assets or just
+for some :ref:`asset package ` thanks to
+the :ref:`version_strategy ` option:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # app/config/config.yml
+ framework:
+ # ...
+ assets:
+ version_strategy: 'AppBundle\Asset\VersionStrategy\GulpBusterVersionStrategy'
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // app/config/config.php
+ use AppBundle\Asset\VersionStrategy\GulpBusterVersionStrategy;
+
+ $container->loadFromExtension('framework', [
+ // ...
+ 'assets' => [
+ 'version_strategy' => GulpBusterVersionStrategy::class,
+ ],
+ ]);
+
+.. _`gulp-buster`: https://www.npmjs.com/package/gulp-buster
diff --git a/frontend/encore/advanced-config.rst b/frontend/encore/advanced-config.rst
new file mode 100644
index 00000000000..2c309d9b466
--- /dev/null
+++ b/frontend/encore/advanced-config.rst
@@ -0,0 +1,224 @@
+Advanced Webpack Config
+=======================
+
+Summarized, Encore generates the Webpack configuration that's used in your
+``webpack.config.js`` file. Encore doesn't support adding all of Webpack's
+`configuration options`_, because many can be added on your own.
+
+For example, suppose you need to resolve automatically a new extension.
+To do that, modify the config after fetching it from Encore:
+
+.. code-block:: javascript
+
+ // webpack.config.js
+
+ var Encore = require('@symfony/webpack-encore');
+
+ // ... all Encore config here
+
+ // fetch the config, then modify it!
+ var config = Encore.getWebpackConfig();
+
+ // add an extension
+ config.resolve.extensions.push('json');
+
+ // export the final config
+ module.exports = config;
+
+But be careful not to accidentally override any config from Encore:
+
+.. code-block:: javascript
+
+ // webpack.config.js
+ // ...
+
+ // GOOD - this modifies the config.resolve.extensions array
+ config.resolve.extensions.push('json');
+
+ // BAD - this replaces any extensions added by Encore
+ // config.resolve.extensions = ['json'];
+
+Configuring Watching Options and Polling
+----------------------------------------
+
+Encore provides the method ``configureWatchOptions()`` to configure
+`Watching Options`_ when running ``encore dev --watch`` or ``encore dev-server``:
+
+.. code-block:: javascript
+
+ Encore.configureWatchOptions(function(watchOptions) {
+ // enable polling and check for changes every 250ms
+ // polling is useful when running Encore inside a Virtual Machine
+ watchOptions.poll = 250;
+ });
+
+Defining Multiple Webpack Configurations
+----------------------------------------
+
+Webpack supports passing an `array of configurations`_, which are processed in
+parallel. Webpack Encore includes a ``reset()`` object allowing to reset the
+state of the current configuration to build a new one:
+
+.. code-block:: javascript
+
+ // define the first configuration
+ Encore
+ .setOutputPath('web/build/first_build/')
+ .setPublicPath('/build/first_build')
+ .addEntry('app', './assets/js/app.js')
+ .addStyleEntry('global', './assets/css/global.scss')
+ .enableSassLoader()
+ .autoProvidejQuery()
+ .enableSourceMaps(!Encore.isProduction())
+ ;
+
+ // build the first configuration
+ const firstConfig = Encore.getWebpackConfig();
+
+ // Set a unique name for the config (needed later!)
+ firstConfig.name = 'firstConfig';
+
+ // reset Encore to build the second config
+ Encore.reset();
+
+ // define the second configuration
+ Encore
+ .setOutputPath('web/build/second_build/')
+ .setPublicPath('/build/second_build')
+ .addEntry('mobile', './assets/js/mobile.js')
+ .addStyleEntry('mobile', './assets/css/mobile.less')
+ .enableLessLoader()
+ .enableSourceMaps(!Encore.isProduction())
+ ;
+
+ // build the second configuration
+ const secondConfig = Encore.getWebpackConfig();
+
+ // Set a unique name for the config (needed later!)
+ secondConfig.name = 'secondConfig';
+
+ // export the final configuration as an array of multiple configurations
+ module.exports = [firstConfig, secondConfig];
+
+When running Encore, both configurations will be built in parallel. If you
+prefer to build configs separately, pass the ``--config-name`` option:
+
+.. code-block:: terminal
+
+ $ yarn encore dev --config-name firstConfig
+
+Next, define the output directories of each build:
+
+.. code-block:: yaml
+
+ # app/config/config.yml
+ webpack_encore:
+ output_path: '%kernel.project_dir%/web/default_build'
+ builds:
+ firstConfig: '%kernel.project_dir%/web/first_build'
+ secondConfig: '%kernel.project_dir%/web/second_build'
+
+Finally, use the third optional parameter of the ``encore_entry_*_tags()``
+functions to specify which build to use:
+
+.. code-block:: twig
+
+ {# Using the entrypoints.json file located in ./web/first_build #}
+ {{ encore_entry_script_tags('app', null, 'firstConfig') }}
+ {{ encore_entry_link_tags('global', null, 'firstConfig') }}
+
+ {# Using the entrypoints.json file located in ./web/second_build #}
+ {{ encore_entry_script_tags('mobile', null, 'secondConfig') }}
+ {{ encore_entry_link_tags('mobile', null, 'secondConfig') }}
+
+Generating a Webpack Configuration Object without using the Command-Line Interface
+----------------------------------------------------------------------------------
+
+Ordinarily you would use your ``webpack.config.js`` file by calling Encore
+from the command-line interface. But sometimes, having access to the generated
+Webpack configuration can be required by tools that don't use Encore (for
+instance a test-runner such as `Karma`_).
+
+The problem is that if you try generating that Webpack configuration object
+without using the ``encore`` command you will encounter the following error:
+
+.. code-block:: text
+
+ Error: Encore.setOutputPath() cannot be called yet because the runtime environment doesn't appear to be configured. Make sure you're using the encore executable or call Encore.configureRuntimeEnvironment() first if you're purposely not calling Encore directly.
+
+The reason behind that message is that Encore needs to know a few things before
+being able to create a configuration object, the most important one being what
+the target environment is.
+
+To solve this issue you can use ``configureRuntimeEnvironment``. This method
+must be called from a JavaScript file **before** requiring ``webpack.config.js``.
+
+For instance:
+
+.. code-block:: javascript
+
+ const Encore = require('@symfony/webpack-encore');
+
+ // Set the runtime environment
+ Encore.configureRuntimeEnvironment('dev');
+
+ // Retrieve the Webpack configuration object
+ const webpackConfig = require('./webpack.config');
+
+If needed, you can also pass to that method all the options that you would
+normally use from the command-line interface:
+
+.. code-block:: javascript
+
+ Encore.configureRuntimeEnvironment('dev-server', {
+ // Same options you would use with the
+ // CLI utility, with their name in camelCase.
+ https: true,
+ keepPublicPath: true,
+ });
+
+Having the full control on Loaders Rules
+----------------------------------------
+
+The method ``configureLoaderRule()`` provides a clean way to configure Webpack loaders rules (``module.rules``, see `Configuration `_).
+
+This is a low-level method. All your modifications will be applied just before pushing the loaders rules to Webpack.
+It means that you can override the default configuration provided by Encore, which may break things. Be careful when using it.
+
+One use might be to configure the ``eslint-loader`` to lint Vue files too.
+The following code is equivalent:
+
+.. code-block:: javascript
+
+ // Manually
+ const webpackConfig = Encore.getWebpackConfig();
+
+ const eslintLoader = webpackConfig.module.rules.find(rule => rule.loader === 'eslint-loader');
+ eslintLoader.test = /\.(jsx?|vue)$/;
+
+ return webpackConfig;
+
+ // Using Encore.configureLoaderRule()
+ Encore.configureLoaderRule('eslint', loaderRule => {
+ loaderRule.test = /\.(jsx?|vue)$/
+ });
+
+ return Encore.getWebpackConfig();
+
+The following loaders are configurable with ``configureLoaderRule()``:
+ - ``javascript`` (alias ``js``)
+ - ``css``
+ - ``images``
+ - ``fonts``
+ - ``sass`` (alias ``scss``)
+ - ``less``
+ - ``stylus``
+ - ``vue``
+ - ``eslint``
+ - ``typescript`` (alias ``ts``)
+ - ``handlebars``
+
+.. _`configuration options`: https://webpack.js.org/configuration/
+.. _`array of configurations`: https://github.com/webpack/docs/wiki/configuration#multiple-configurations
+.. _`Karma`: https://karma-runner.github.io
+.. _`Watching Options`: https://webpack.js.org/configuration/watch/#watchoptions
diff --git a/frontend/encore/babel.rst b/frontend/encore/babel.rst
new file mode 100644
index 00000000000..7f10688b150
--- /dev/null
+++ b/frontend/encore/babel.rst
@@ -0,0 +1,65 @@
+Configuring Babel
+=================
+
+`Babel`_ is automatically configured for all ``.js`` and ``.jsx`` files via the
+``babel-loader`` with sensible defaults (e.g. with the ``@babel/preset-env`` and
+``@babel/preset-react`` if requested).
+
+Need to extend the Babel configuration further? The easiest way is via
+``configureBabel()``:
+
+.. code-block:: javascript
+
+ // webpack.config.js
+ // ...
+
+ Encore
+ // ...
+
+ .configureBabel(function(babelConfig) {
+ // add additional presets
+ babelConfig.presets.push('@babel/preset-flow');
+
+ // no plugins are added by default, but you can add some
+ babelConfig.plugins.push('styled-jsx/babel');
+ }, {
+ // node_modules is not processed through Babel by default
+ // but you can whitelist specific modules to process
+ includeNodeModules: ['foundation-sites'],
+
+ // or completely control the exclude rule (note that you
+ // can't use both "includeNodeModules" and "exclude" at
+ // the same time)
+ exclude: /bower_components/
+ })
+ ;
+
+Configuring Browser Targets
+---------------------------
+
+The ``@babel/preset-env`` preset rewrites your JavaScript so that the final syntax
+will work in whatever browsers you want. To configure the browsers that you need
+to support, see :ref:`browserslist_package_config`.
+
+After changing your "browserslist" config, you will need to manually remove the babel
+cache directory:
+
+.. code-block:: terminal
+
+ # On Unix run this command. On Windows, clear this directory manually
+ $ rm -rf node_modules/.cache/babel-loader/
+
+Creating a ``.babelrc`` File
+----------------------------
+
+Instead of calling ``configureBabel()``, you could create a ``.babelrc`` file
+at the root of your project. This is a more "standard" way of configuring
+Babel, but it has a downside: as soon as a ``.babelrc`` file is present,
+**Encore can no longer add any Babel configuration for you**. For example,
+if you call ``Encore.enableReactPreset()``, the ``react`` preset will *not*
+automatically be added to Babel: you must add it yourself in ``.babelrc``.
+
+As soon as a ``.babelrc`` file is present, it will take priority over the Babel
+configuration added by Encore.
+
+.. _`Babel`: http://babeljs.io/
diff --git a/frontend/encore/bootstrap.rst b/frontend/encore/bootstrap.rst
new file mode 100644
index 00000000000..ff281f588ac
--- /dev/null
+++ b/frontend/encore/bootstrap.rst
@@ -0,0 +1,81 @@
+Using Bootstrap CSS & JS
+========================
+
+Want to use Bootstrap (or something similar) in your project? No problem!
+First, install it. To be able to customize things further, we'll install
+``bootstrap``:
+
+.. code-block:: terminal
+
+ $ yarn add bootstrap --dev
+
+Importing Bootstrap Styles
+--------------------------
+
+Now that ``bootstrap`` lives in your ``node_modules/`` directory, you can
+import it from any Sass or JavaScript file. For example, if you already have
+a ``global.scss`` file, import it from there:
+
+.. code-block:: scss
+
+ // assets/css/global.scss
+
+ // customize some Bootstrap variables
+ $primary: darken(#428bca, 20%);
+
+ // the ~ allows you to reference things in node_modules
+ @import "~bootstrap/scss/bootstrap";
+
+That's it! This imports the ``node_modules/bootstrap/scss/bootstrap.scss``
+file into ``global.scss``. You can even customize the Bootstrap variables first!
+
+.. tip::
+
+ If you don't need *all* of Bootstrap's features, you can include specific files
+ in the ``bootstrap`` directory instead - e.g. ``~bootstrap/scss/alert``.
+
+Importing Bootstrap JavaScript
+------------------------------
+
+Bootstrap JavaScript requires jQuery and Popper.js, so make sure you have this installed:
+
+.. code-block:: terminal
+
+ $ yarn add jquery popper.js --dev
+
+Now, require bootstrap from any of your JavaScript files:
+
+.. code-block:: javascript
+
+ // app.js
+
+ const $ = require('jquery');
+ // this "modifies" the jquery module: adding behavior to it
+ // the bootstrap module doesn't export/return anything
+ require('bootstrap');
+
+ // or you can include specific pieces
+ // require('bootstrap/js/dist/tooltip');
+ // require('bootstrap/js/dist/popover');
+
+ $(document).ready(function() {
+ $('[data-toggle="popover"]').popover();
+ });
+
+Using other Bootstrap / jQuery Plugins
+--------------------------------------
+
+If you need to use jQuery plugins that work well with jQuery, you may need to use
+Encore's :ref:`autoProvidejQuery() ` method so that
+these plugins know where to find jQuery. Then, you can include the needed JavaScript
+and CSS like normal:
+
+.. code-block:: javascript
+
+ // ...
+
+ // require the JavaScript
+ require('bootstrap-star-rating');
+ // require 2 CSS files needed
+ require('bootstrap-star-rating/css/star-rating.css');
+ require('bootstrap-star-rating/themes/krajee-svg/theme.css');
diff --git a/frontend/encore/cdn.rst b/frontend/encore/cdn.rst
new file mode 100644
index 00000000000..0c486f06057
--- /dev/null
+++ b/frontend/encore/cdn.rst
@@ -0,0 +1,47 @@
+Using a CDN
+===========
+
+Are you deploying to a CDN? That's awesome :) Once you've made sure that your
+built files are uploaded to the CDN, configure it in Encore:
+
+.. code-block:: diff
+
+ // webpack.config.js
+ // ...
+
+ Encore
+ .setOutputPath('web/build/')
+ // in dev mode, don't use the CDN
+ .setPublicPath('/build');
+ // ...
+ ;
+
+ + if (Encore.isProduction()) {
+ + Encore.setPublicPath('https://my-cool-app.com.global.prod.fastly.net');
+ +
+ + // guarantee that the keys in manifest.json are *still*
+ + // prefixed with build/
+ + // (e.g. "build/dashboard.js": "https://my-cool-app.com.global.prod.fastly.net/dashboard.js")
+ + Encore.setManifestKeyPrefix('build/');
+ + }
+
+That's it! Internally, Webpack will now know to load assets from your CDN -
+e.g. ``https://my-cool-app.com.global.prod.fastly.net/dashboard.js``.
+
+.. note::
+
+ It's still your responsibility to put your assets on the CDN - e.g. by
+ uploading them or by using "origin pull", where your CDN pulls assets
+ directly from your web server.
+
+You *do* need to make sure that the ``script`` and ``link`` tags you include on your
+pages also use the CDN. Fortunately, the
+:ref:`entrypoints.json ` paths are updated
+to include the full URL to the CDN.
+
+If you are using ``Encore.enableIntegrityHashes()`` and your CDN and your domain
+are not the `same-origin`_, you may need to set the ``crossorigin`` option in
+your webpack_encore.yaml configuration to ``anonymous`` or ``use-credentials``
+to overcome CORS errors.
+
+.. _`same-origin`: https://en.wikipedia.org/wiki/Same-origin_policy
diff --git a/frontend/encore/code-splitting.rst b/frontend/encore/code-splitting.rst
new file mode 100644
index 00000000000..ffbfe8b4d28
--- /dev/null
+++ b/frontend/encore/code-splitting.rst
@@ -0,0 +1,64 @@
+Async Code Splitting
+====================
+
+When you require/import a JavaScript or CSS module, Webpack compiles that code into
+the final JavaScript or CSS file. Usually, that's exactly what you want. But what
+if you only need to use a piece of code under certain conditions? For example,
+what if you want to use `video.js`_ to play a video, but only once a user has
+clicked a link:
+
+.. code-block:: javascript
+
+ // assets/js/app.js
+
+ import $ from 'jquery';
+ // a fictional "large" module (e.g. it imports video.js internally)
+ import VideoPlayer from './components/VideoPlayer';
+
+ $('.js-open-video').on('click', function() {
+ // use the larger VideoPlayer module
+ const player = new VideoPlayer('some-element');
+ });
+
+In this example, the VideoPlayer module and everything it imports will be packaged
+into the final, built JavaScript file, even though it may not be very common for
+someone to actually need it. A better solution is to use `dynamic imports`_: load
+the code via AJAX when it's needed:
+
+.. code-block:: javascript
+
+ // assets/js/app.js
+
+ import $ from 'jquery';
+
+ $('.js-open-video').on('click', function() {
+ // you could start a loading animation here
+
+ // use import() as a function - it returns a Promise
+ import('./components/VideoPlayer').then(({ default: VideoPlayer }) => {
+ // you could stop a loading animation here
+
+ // use the larger VideoPlayer module
+ const player = new VideoPlayer('some-element');
+
+ }).catch(error => 'An error occurred while loading the component');
+ });
+
+By using ``import()`` like a function, the module will be downloaded async and
+the ``.then()`` callback will be executed when it's finished. The ``VideoPlayer``
+argument to the callback will be the loaded module. In other words, it works like
+normal AJAX calls! Behind the scenes, Webpack will package the ``VideoPlayer`` module
+into a separate file (e.g. ``0.js``) so it can be downloaded. All the details are
+handled for you.
+
+The ``{ default: VideoPlayer }`` part may look strange. When using the async
+import, your ``.then()`` callback is passed an object, where the *actual* module
+is on a ``.default`` key. There are reasons why this is done, but it does look
+quirky. The ``{ default: VideoPlayer }`` code makes sure that the ``VideoPlayer``
+module we want is read from this ``.default`` property.
+
+For more details and configuration options, see `dynamic imports`_ on Webpack's
+documentation.
+
+.. _`video.js`: https://videojs.com/
+.. _`dynamic imports`: https://webpack.js.org/guides/code-splitting/#dynamic-imports
diff --git a/frontend/encore/copy-files.rst b/frontend/encore/copy-files.rst
new file mode 100644
index 00000000000..c8d3edbb243
--- /dev/null
+++ b/frontend/encore/copy-files.rst
@@ -0,0 +1,71 @@
+Copying & Referencing Images
+============================
+
+Need to reference a static file - like the path to an image for an ``img`` tag?
+That can be tricky if you store your assets outside of the public document root.
+Fortunately, depending on your situation, there is a solution!
+
+Referencing Images from Inside a Webpacked JavaScript File
+----------------------------------------------------------
+
+To reference an image tag from inside a JavaScript file, *require* the file:
+
+.. code-block:: javascript
+
+ // assets/js/app.js
+
+ // returns the final, public path to this file
+ // path is relative to this file - e.g. assets/images/logo.png
+ const logoPath = require('../images/logo.png');
+
+ var html = ``;
+
+When you ``require`` (or ``import``) an image file, Webpack copies it into your
+output directory and returns the final, *public* path to that file.
+
+Referencing Image files from a Template
+---------------------------------------
+
+To reference an image file from outside of a JavaScript file that's processed by
+Webpack - like a template - you can use the ``copyFiles()`` method to copy those
+files into your final output directory.
+
+.. code-block:: diff
+
+ // webpack.config.js
+
+ Encore
+ // ...
+ .setOutputPath('web/build/')
+
+ + .copyFiles({
+ + from: './assets/images',
+ +
+ + // optional target path, relative to the output dir
+ + //to: 'images/[path][name].[ext]',
+ +
+ + // if versioning is enabled, add the file hash too
+ + //to: 'images/[path][name].[hash:8].[ext]',
+ +
+ + // only copy files matching this pattern
+ + //pattern: /\.(png|jpg|jpeg)$/
+ + })
+
+This will copy all files from ``assets/images`` into ``web/build`` (the output
+path). If you have :doc:`versioning enabled `, the copied files will
+include a hash based on their content.
+
+To render inside Twig, use the ``asset()`` function:
+
+.. code-block:: html+twig
+
+ {# assets/images/logo.png was copied to web/build/logo.png #}
+
+
+ {# assets/images/subdir/logo.png was copied to web/build/subdir/logo.png #}
+
+
+Make sure you've enabled the :ref:`json_manifest_path ` option,
+which tells the ``asset()`` function to read the final paths from the ``manifest.json``
+file. If you're not sure what path argument to pass to the ``asset()`` function,
+find the file in ``manifest.json`` and use the *key* as the argument.
diff --git a/frontend/encore/css-preprocessors.rst b/frontend/encore/css-preprocessors.rst
new file mode 100644
index 00000000000..6b70e8f38cb
--- /dev/null
+++ b/frontend/encore/css-preprocessors.rst
@@ -0,0 +1,33 @@
+CSS Preprocessors: Sass, LESS, Stylus, etc.
+===========================================
+
+To use the Sass, LESS or Stylus pre-processors, enable the one you want in ``webpack.config.js``:
+
+.. code-block:: javascript
+
+ // webpack.config.js
+ // ...
+
+ Encore
+ // ...
+
+ // enable just the one you want
+
+ // processes files ending in .scss or .sass
+ .enableSassLoader()
+
+ // processes files ending in .less
+ .enableLessLoader()
+
+ // processes files ending in .styl
+ .enableStylusLoader()
+ ;
+
+Then restart Encore. When you do, it will give you a command you can run to
+install any missing dependencies. After running that command and restarting
+Encore, you're done!
+
+You can also pass configuration options to each of the loaders. See the
+`Encore's index.js file`_ for detailed documentation.
+
+.. _`Encore's index.js file`: https://github.com/symfony/webpack-encore/blob/master/index.js
diff --git a/frontend/encore/custom-loaders-plugins.rst b/frontend/encore/custom-loaders-plugins.rst
new file mode 100644
index 00000000000..66ce1f7c5cc
--- /dev/null
+++ b/frontend/encore/custom-loaders-plugins.rst
@@ -0,0 +1,66 @@
+Adding Custom Loaders & Plugins
+===============================
+
+Adding Custom Loaders
+---------------------
+
+Encore already comes with a variety of different loaders out of the box,
+but if there is a specific loader that you want to use that is not currently supported, you
+can add your own loader through the ``addLoader`` function.
+The ``addLoader`` takes any valid webpack rules config.
+
+If, for example, you want to add the `handlebars-loader`_, call ``addLoader`` with
+your loader config
+
+.. code-block:: javascript
+
+ Encore
+ // ...
+ .addLoader({ test: /\.handlebars$/, loader: 'handlebars-loader' })
+ ;
+
+Since the loader config accepts any valid Webpack rules object, you can pass any
+additional information your need for the loader
+
+.. code-block:: javascript
+
+ Encore
+ // ...
+ .addLoader({
+ test: /\.handlebars$/,
+ loader: 'handlebars-loader',
+ options: {
+ helperDirs: [
+ __dirname + '/helpers1',
+ __dirname + '/helpers2',
+ ],
+ partialDirs: [
+ path.join(__dirname, 'templates', 'partials')
+ ]
+ }
+ })
+ ;
+
+Adding Custom Plugins
+---------------------
+
+Encore uses a variety of different `plugins`_ internally. But, you can add your own
+via the ``addPlugin()`` method. For example, if you use `Moment.js`_, you might want
+to use the `IgnorePlugin`_ (see `moment/moment#2373`_):
+
+.. code-block:: diff
+
+ // webpack.config.js
+ + var webpack = require('webpack');
+
+ Encore
+ // ...
+
+ + .addPlugin(new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/))
+ ;
+
+.. _`handlebars-loader`: https://github.com/pcardune/handlebars-loader
+.. _`plugins`: https://webpack.js.org/plugins/
+.. _`Moment.js`: https://momentjs.com/
+.. _`IgnorePlugin`: https://webpack.js.org/plugins/ignore-plugin/
+.. _`moment/moment#2373`: https://github.com/moment/moment/issues/2373
diff --git a/frontend/encore/dev-server.rst b/frontend/encore/dev-server.rst
new file mode 100644
index 00000000000..4fbb0e1f879
--- /dev/null
+++ b/frontend/encore/dev-server.rst
@@ -0,0 +1,99 @@
+Using webpack-dev-server and HMR
+================================
+
+While developing, instead of using ``yarn encore dev --watch``, you can use the
+`webpack-dev-server`_:
+
+.. code-block:: terminal
+
+ $ yarn encore dev-server
+
+This serves the built assets from a new server at ``http://localhost:8080`` (it does
+not actually write any files to disk). This means your ``script`` and ``link`` tags
+need to change to point to this.
+
+If you're using the ``encore_entry_script_tags()`` and ``encore_entry_link_tags()``
+Twig shortcuts (or are :ref:`processing your assets through entrypoints.json `
+in some other way), you're done: the paths in your templates will automatically point
+to the dev server.
+
+The ``dev-server`` command supports all the options defined by `webpack-dev-server`_.
+You can set these options via command line options:
+
+.. code-block:: terminal
+
+ $ yarn encore dev-server --https --port 9000
+
+You can also set these options using the ``Encore.configureDevServerOptions()``
+method in your ``webpack.config.js`` file:
+
+.. code-block:: javascript
+
+ // webpack.config.js
+ // ...
+
+ Encore
+ // ...
+
+ .configureDevServerOptions(options => {
+ options.https = {
+ key: '/path/to/server.key',
+ cert: '/path/to/server.crt',
+ }
+ })
+ ;
+
+.. versionadded:: 0.28.4
+
+ The ``Encore.configureDevServerOptions()`` method was introduced in Encore 0.28.4.
+
+Hot Module Replacement HMR
+--------------------------
+
+Encore *does* support `HMR`_ for :doc:`Vue.js `, but
+does *not* work for styles anywhere at this time. To activate it, pass the ``--hot``
+option:
+
+.. code-block:: terminal
+
+ $ ./node_modules/.bin/encore dev-server --hot
+
+If you want to use SSL with self-signed certificates, add the ``--https``,
+``--pfx=``, and ``--allowed-hosts`` options to the ``dev-server`` command in
+the ``package.json`` file:
+
+.. code-block:: diff
+
+ {
+ ...
+ "scripts": {
+ - "dev-server": "encore dev-server",
+ + "dev-server": "encore dev-server --https --pfx=$HOME/.symfony/certs/default.p12 --allowed-hosts=mydomain.wip",
+ ...
+ }
+ }
+
+If you experience issues related to CORS (Cross Origin Resource Sharing), add
+the ``--disable-host-check`` and ``--port`` options to the ``dev-server``
+command in the ``package.json`` file:
+
+.. code-block:: diff
+
+ {
+ ...
+ "scripts": {
+ - "dev-server": "encore dev-server",
+ + "dev-server": "encore dev-server --port 8080 --disable-host-check",
+ ...
+ }
+ }
+
+.. caution::
+
+ Beware that `it's not recommended to disable host checking`_ in general, but
+ here it's required to solve the CORS issue.
+
+
+.. _`webpack-dev-server`: https://webpack.js.org/configuration/dev-server/
+.. _`HMR`: https://webpack.js.org/concepts/hot-module-replacement/
+.. _`it's not recommended to disable host checking`: https://webpack.js.org/configuration/dev-server/#devserverdisablehostcheck
diff --git a/frontend/encore/faq.rst b/frontend/encore/faq.rst
new file mode 100644
index 00000000000..48c8a07b88d
--- /dev/null
+++ b/frontend/encore/faq.rst
@@ -0,0 +1,170 @@
+FAQ and Common Issues
+=====================
+
+.. _how-do-i-deploy-my-encore-assets:
+
+How Do I Deploy My Encore Assets?
+---------------------------------
+
+There are two important things to remember when deploying your assets.
+
+**1) Compile Assets for Production**
+
+Optimize your assets for production by running:
+
+.. code-block:: terminal
+
+ $ ./node_modules/.bin/encore production
+
+That will minify your assets and make other performance optimizations. Yay!
+
+But, what server should you run this command on? That depends on how you deploy.
+For example, you could execute this locally (or on a build server), and use
+`rsync`_ or something else to transfer the generated files to your production
+server. Or, you could put your files on your production server first (e.g. via
+``git pull``) and then run this command on production (ideally, before traffic
+hits your code). In this case, you'll need to install Node.js on your production
+server.
+
+**2) Only Deploy the Built Assets**
+
+The *only* files that need to be deployed to your production servers are the
+final, built assets (e.g. the ``web/build`` directory). You do *not* need to install
+Node.js, deploy ``webpack.config.js``, the ``node_modules`` directory or even your source
+asset files, **unless** you plan on running ``encore production`` on your production
+machine. Once your assets are built, these are the *only* thing that need to live
+on the production server.
+
+Do I Need to Install Node.js on My Production Server?
+-----------------------------------------------------
+
+No, unless you plan to build your production assets on your production server,
+which is not recommended. See `How Do I Deploy my Encore Assets?`_.
+
+What Files Should I Commit to git? And which Should I Ignore?
+-------------------------------------------------------------
+
+You should commit all of your files to git, except for the ``node_modules/`` directory
+and the built files. Your ``.gitignore`` file should include:
+
+.. code-block:: text
+
+ /node_modules/
+ # whatever path you're passing to Encore.setOutputPath()
+ /web/build
+
+You *should* commit all of your source asset files, ``package.json`` and ``yarn.lock``.
+
+My App Lives under a Subdirectory
+---------------------------------
+
+If your app does not live at the root of your web server (i.e. it lives under a subdirectory,
+like ``/myAppSubdir``), you will need to configure that when calling ``Encore.setPublicPath()``:
+
+.. code-block:: diff
+
+ // webpack.config.js
+ Encore
+ // ...
+
+ .setOutputPath('web/build/')
+
+ - .setPublicPath('/build')
+ + // this is your *true* public path
+ + .setPublicPath('/myAppSubdir/build')
+
+ + // this is now needed so that your manifest.json keys are still `build/foo.js`
+ + // (which is a file that's used by Symfony's `asset()` function)
+ + .setManifestKeyPrefix('build')
+ ;
+
+If you're using the ``encore_entry_script_tags()`` and ``encore_entry_link_tags()``
+Twig shortcuts (or are :ref:`processing your assets through entrypoints.json `
+in some other way) you're done! These shortcut methods read from an
+:ref:`entrypoints.json ` file that will
+now contain the subdirectory.
+
+"jQuery is not defined" or "$ is not defined"
+---------------------------------------------
+
+This error happens when your code (or some library that you are using) expects ``$``
+or ``jQuery`` to be a global variable. But, when you use Webpack and ``require('jquery')``,
+no global variables are set.
+
+The fix depends on if the error is happening in your code or inside some third-party
+code that you're using. See :doc:`/frontend/encore/legacy-applications` for the fix.
+
+Uncaught ReferenceError: webpackJsonp is not defined
+----------------------------------------------------
+
+If you get this error, it's probably because you've forgotten to add a ``script``
+tag for the ``runtime.js`` file that contains Webpack's runtime. If you're using
+the ``encore_entry_script_tags()`` Twig function, this should never happen: the
+file script tag is rendered automatically.
+
+This dependency was not found: some-module in ./path/to/file.js
+---------------------------------------------------------------
+
+Usually, after you install a package via yarn, you can require / import it to use
+it. For example, after running ``yarn add respond.js``, you try to require that module:
+
+.. code-block:: javascript
+
+ require('respond.js');
+
+But, instead of working, you see an error:
+
+ This dependency was not found:
+
+ * respond.js in ./app/Resources/assets/js/app.js
+
+Typically, a package will "advertise" its "main" file by adding a ``main`` key to
+its ``package.json``. But sometimes, old libraries won't have this. Instead, you'll
+need to specifically require the file you need. In this case, the file you should
+use is located at ``node_modules/respond.js/dest/respond.src.js``. You can require
+this via:
+
+.. code-block:: javascript
+
+ // require a non-minified file whenever possible
+ require('respond.js/dest/respond.src.js');
+
+I need to execute Babel on a third-party Module
+-----------------------------------------------
+
+For performance, Encore does not process libraries inside ``node_modules/`` through
+Babel. But, you can change that via the ``configureBabel()`` method. See
+:doc:`/frontend/encore/babel` for details.
+
+.. _`rsync`: https://rsync.samba.org/
+
+How Do I Integrate my Encore Configuration with my IDE?
+-------------------------------------------------------
+
+`Webpack integration in PhpStorm`_ and other IDEs makes your development more
+productive (for example by resolving aliases). However, you may face this error:
+
+.. code-block:: text
+
+ Encore.setOutputPath() cannot be called yet because the runtime environment
+ doesn't appear to be configured. Make sure you're using the encore executable
+ or call Encore.configureRuntimeEnvironment() first if you're purposely not
+ calling Encore directly.
+
+It fails because the Encore Runtime Environment is only configured when you are
+running it (e.g. when executing ``yarn encore dev``). Fix this issue calling to
+``Encore.isRuntimeEnvironmentConfigured()`` and
+``Encore.configureRuntimeEnvironment()`` methods:
+
+.. code-block:: javascript
+
+ // webpack.config.js
+ const Encore = require('@symfony/webpack-encore')
+
+ if (!Encore.isRuntimeEnvironmentConfigured()) {
+ Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev');
+ }
+
+ // ... the rest of the Encore configuration
+
+.. _`Webpack integration in PhpStorm`: https://www.jetbrains.com/help/phpstorm/using-webpack.html
diff --git a/frontend/encore/installation.rst b/frontend/encore/installation.rst
new file mode 100644
index 00000000000..f0d2575d4d2
--- /dev/null
+++ b/frontend/encore/installation.rst
@@ -0,0 +1,143 @@
+Installing Encore
+=================
+
+First, make sure you `install Node.js`_ and also the `Yarn package manager`_.
+The following instructions depend on whether you are installing Encore in a
+Symfony application or not.
+
+Installing Encore in Symfony Applications
+-----------------------------------------
+
+Run these commands to install both the PHP and JavaScript dependencies in your
+project:
+
+.. code-block:: terminal
+
+ $ composer require symfony/webpack-encore-bundle
+ $ yarn install
+
+If you are using :doc:`Symfony Flex `, this will install and enable
+the `WebpackEncoreBundle`_, create the ``assets/`` directory, add a
+``webpack.config.js`` file, and add ``node_modules/`` to ``.gitignore``. You can
+skip the rest of this article and go write your first JavaScript and CSS by
+reading :doc:`/frontend/encore/simple-example`!
+
+If you are not using Symfony Flex, you'll need to create all these directories
+and files by yourself following the instructions shown in the next section.
+
+Installing Encore in non Symfony Applications
+---------------------------------------------
+
+Install Encore into your project via Yarn:
+
+.. code-block:: terminal
+
+ $ yarn add @symfony/webpack-encore --dev
+
+ # if you prefer npm, run this command instead:
+ $ npm install @symfony/webpack-encore --save-dev
+
+This command creates (or modifies) a ``package.json`` file and downloads
+dependencies into a ``node_modules/`` directory. Yarn also creates/updates a
+``yarn.lock`` (called ``package-lock.json`` if you use npm).
+
+.. tip::
+
+ You *should* commit ``package.json`` and ``yarn.lock`` (or ``package-lock.json``
+ if using npm) to version control, but ignore ``node_modules/``.
+
+Creating the webpack.config.js File
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Next, create a new ``webpack.config.js`` file at the root of your project. This
+is the main config file for both Webpack and Webpack Encore:
+
+.. code-block:: javascript
+
+ var Encore = require('@symfony/webpack-encore');
+
+ Encore
+ // directory where compiled assets will be stored
+ .setOutputPath('web/build/')
+ // public path used by the web server to access the output path
+ .setPublicPath('/build')
+ // only needed for CDN's or sub-directory deploy
+ //.setManifestKeyPrefix('build/')
+
+ /*
+ * ENTRY CONFIG
+ *
+ * Add 1 entry for each "page" of your app
+ * (including one that's included on every page - e.g. "app")
+ *
+ * Each entry will result in one JavaScript file (e.g. app.js)
+ * and one CSS file (e.g. app.css) if your JavaScript imports CSS.
+ */
+ .addEntry('app', './assets/js/app.js')
+ //.addEntry('page1', './assets/js/page1.js')
+ //.addEntry('page2', './assets/js/page2.js')
+
+ // will require an extra script tag for runtime.js
+ // but, you probably want this, unless you're building a single-page app
+ .enableSingleRuntimeChunk()
+
+ .cleanupOutputBeforeBuild()
+ .enableSourceMaps(!Encore.isProduction())
+ // enables hashed filenames (e.g. app.abc123.css)
+ .enableVersioning(Encore.isProduction())
+
+ // uncomment if you use TypeScript
+ //.enableTypeScriptLoader()
+
+ // uncomment if you use Sass/SCSS files
+ //.enableSassLoader()
+
+ // uncomment if you're having problems with a jQuery plugin
+ //.autoProvidejQuery()
+ ;
+
+ module.exports = Encore.getWebpackConfig();
+
+Next, open the new ``assets/js/app.js`` file which contains some JavaScript code
+*and* imports some CSS:
+
+.. code-block:: javascript
+
+ // assets/js/app.js
+ /*
+ * Welcome to your app's main JavaScript file!
+ *
+ * We recommend including the built version of this JavaScript file
+ * (and its CSS file) in your base layout (base.html.twig).
+ */
+
+ // any CSS you import will output into a single css file (app.css in this case)
+ import '../css/app.css';
+
+ // Need jQuery? Install it with "yarn add jquery", then uncomment to import it.
+ // import $ from 'jquery';
+
+ console.log('Hello Webpack Encore');
+
+And the new ``assets/css/app.css`` file:
+
+.. code-block:: css
+
+ /* assets/css/app.css */
+ body {
+ background-color: lightgray;
+ }
+
+You'll customize and learn more about these file in :doc:`/frontend/encore/simple-example`.
+
+.. caution::
+
+ Some of the documentation will use features that are specific to Symfony or
+ Symfony's `WebpackEncoreBundle`_. These are optional, and are special ways
+ of pointing to the asset paths generated by Encore that enable features like
+ :doc:`versioning ` and
+ :doc:`split chunks `.
+
+.. _`install Node.js`: https://nodejs.org/en/download/
+.. _`Yarn package manager`: https://yarnpkg.com/lang/en/docs/install/
+.. _`WebpackEncoreBundle`: https://github.com/symfony/webpack-encore-bundle
diff --git a/frontend/encore/legacy-applications.rst b/frontend/encore/legacy-applications.rst
new file mode 100644
index 00000000000..4f1193d7e06
--- /dev/null
+++ b/frontend/encore/legacy-applications.rst
@@ -0,0 +1,86 @@
+jQuery Plugins and Legacy Applications
+======================================
+
+Inside Webpack, when you require a module, it does *not* (usually) set a global variable.
+Instead, it just returns a value:
+
+.. code-block:: javascript
+
+ // this loads jquery, but does *not* set a global $ or jQuery variable
+ const $ = require('jquery');
+
+In practice, this will cause problems with some outside libraries that *rely* on
+jQuery to be global *or* if *your* JavaScript isn't being processed through Webpack
+(e.g. you have some JavaScript in your templates) and you need jQuery. Both will
+cause similar errors:
+
+.. code-block:: text
+
+ Uncaught ReferenceError: $ is not defined at [...]
+ Uncaught ReferenceError: jQuery is not defined at [...]
+
+The fix depends on what code is causing the problem.
+
+.. _encore-autoprovide-jquery:
+
+Fixing jQuery Plugins that Expect jQuery to be Global
+-----------------------------------------------------
+
+jQuery plugins often expect that jQuery is already available via the ``$`` or
+``jQuery`` global variables. To fix this, call ``autoProvidejQuery()`` from your
+``webpack.config.js`` file:
+
+.. code-block:: diff
+
+ Encore
+ // ...
+ + .autoProvidejQuery()
+ ;
+
+After restarting Encore, Webpack will look for all uninitialized ``$`` and ``jQuery``
+variables and automatically require ``jquery`` and set those variables for you.
+It "rewrites" the "bad" code to be correct.
+
+Internally, this ``autoProvidejQuery()`` method calls the ``autoProvideVariables()``
+method from Encore. In practice, it's equivalent to doing:
+
+.. code-block:: javascript
+
+ Encore
+ // you can use this method to provide other common global variables,
+ // such as '_' for the 'underscore' library
+ .autoProvideVariables({
+ $: 'jquery',
+ jQuery: 'jquery',
+ 'window.jQuery': 'jquery',
+ })
+ // ...
+ ;
+
+Accessing jQuery from outside of Webpack JavaScript Files
+---------------------------------------------------------
+
+If *your* code needs access to ``$`` or ``jQuery`` and you are inside of a file
+that's processed by Webpack/Encore, you should remove any "$ is not defined" errors
+by requiring jQuery: ``var $ = require('jquery')``.
+
+But if you also need to provide access to ``$`` and ``jQuery`` variables outside of
+JavaScript files processed by Webpack (e.g. JavaScript that still lives in your
+templates), you need to manually set these as global variables in some JavaScript
+file that is loaded before your legacy code.
+
+For example, in your ``app.js`` file that's processed by Webpack and loaded on every
+page, add:
+
+.. code-block:: diff
+
+ // require jQuery normally
+ const $ = require('jquery');
+
+ + // create global $ and jQuery variables
+ + global.$ = global.jQuery = $;
+
+The ``global`` variable is a special way of setting things in the ``window``
+variable. In a web context, using ``global`` and ``window`` are equivalent,
+except that ``window.jQuery`` won't work when using ``autoProvidejQuery()``.
+In other words, use ``global``.
diff --git a/frontend/encore/page-specific-assets.rst b/frontend/encore/page-specific-assets.rst
new file mode 100644
index 00000000000..92ab00a0a61
--- /dev/null
+++ b/frontend/encore/page-specific-assets.rst
@@ -0,0 +1,27 @@
+Creating Page-Specific CSS/JS
+=============================
+
+If you're creating a single page app (SPA), then you probably only need to define
+*one* entry in ``webpack.config.js``. But if you have multiple pages, you might
+want page-specific CSS and JavaScript.
+
+To learn how to set this up, see the :ref:`multiple-javascript-entries` example.
+
+Multiple Entries Per Page?
+--------------------------
+
+Typically, you should include only *one* JavaScript entry per page. Think of the
+checkout page as its own "app", where ``checkout.js`` includes all the functionality
+you need.
+
+However, it's pretty common to need to include some global JavaScript and CSS on
+every page. For that reason, it usually makes sense to have one entry (e.g. ``app``)
+that contains this global code (both JavaScript & CSS) and is included on every
+page (i.e. it's included in the *layout* of your app). This means that you will
+always have one, global entry on every page (e.g. ``app``) and you *may* have one
+page-specific JavaScript and CSS file from a page-specific entry (e.g. ``checkout``).
+
+.. tip::
+
+ Be sure to use :doc:`split chunks `
+ to avoid duplicating and shared code between your entry files.
diff --git a/frontend/encore/postcss.rst b/frontend/encore/postcss.rst
new file mode 100644
index 00000000000..4a9bc1ac6e6
--- /dev/null
+++ b/frontend/encore/postcss.rst
@@ -0,0 +1,94 @@
+PostCSS and autoprefixing (postcss-loader)
+==========================================
+
+`PostCSS`_ is a CSS post-processing tool that can transform your CSS in a lot
+of cool ways, like `autoprefixing`_, `linting`_ and more!
+
+First, download ``postcss-loader`` and any plugins you want, like ``autoprefixer``:
+
+.. code-block:: terminal
+
+ $ yarn add postcss-loader autoprefixer --dev
+
+Next, create a ``postcss.config.js`` file at the root of your project:
+
+.. code-block:: javascript
+
+ module.exports = {
+ plugins: {
+ // include whatever plugins you want
+ // but make sure you install these via yarn or npm!
+
+ // add browserslist config to package.json (see below)
+ autoprefixer: {}
+ }
+ }
+
+Then, enable the loader in Encore!
+
+.. code-block:: diff
+
+ // webpack.config.js
+
+ Encore
+ // ...
+ + .enablePostCssLoader()
+ ;
+
+Because you just modified ``webpack.config.js``, stop and restart Encore.
+
+That's it! The ``postcss-loader`` will now be used for all CSS, Sass, etc files.
+You can also pass options to the `postcss-loader`_ by passing a callback:
+
+.. code-block:: diff
+
+ // webpack.config.js
+
+ Encore
+ // ...
+ + .enablePostCssLoader((options) => {
+ + options.config = {
+ + // the directory where the postcss.config.js file is stored
+ + path: 'path/to/config'
+ + };
+ + })
+ ;
+
+.. _browserslist_package_config:
+
+Adding browserslist to ``package.json``
+---------------------------------------
+
+The ``autoprefixer`` (and many other tools) need to know what browsers you want to
+support. The best-practice is to configure this directly in your ``package.json``
+(so that all the tools can read this):
+
+.. code-block:: diff
+
+ {
+ + "browserslist": [
+ + "defaults"
+ + ]
+ }
+
+The ``defaults`` option is recommended for most users and would be equivalent
+to the following browserslist:
+
+.. code-block:: diff
+
+ {
+ + "browserslist": [
+ + "> 0.5%",
+ + "last 2 versions",
+ + "Firefox ESR",
+ + "not dead"
+ + ]
+ }
+
+See `browserslist`_ for more details on the syntax.
+
+.. _`PostCSS`: http://postcss.org/
+.. _`autoprefixing`: https://github.com/postcss/autoprefixer
+.. _`linting`: https://stylelint.io/
+.. _`browserslist`: https://github.com/browserslist/browserslist
+.. _`postcss-loader`: https://github.com/postcss/postcss-loader
diff --git a/frontend/encore/reactjs.rst b/frontend/encore/reactjs.rst
new file mode 100644
index 00000000000..ca3b017f13b
--- /dev/null
+++ b/frontend/encore/reactjs.rst
@@ -0,0 +1,35 @@
+Enabling React.js
+=================
+
+.. admonition:: Screencast
+ :class: screencast
+
+ Do you prefer video tutorials? Check out the `React.js screencast series`_.
+
+Using React? First add some dependencies with Yarn:
+
+.. code-block:: terminal
+
+ $ yarn add @babel/preset-react --dev
+ $ yarn add react react-dom prop-types
+
+Enable react in your ``webpack.config.js``:
+
+.. code-block:: diff
+
+ // webpack.config.js
+ // ...
+
+ Encore
+ // ...
+ + .enableReactPreset()
+ ;
+
+
+Then restart Encore. When you do, it will give you a command you can run to
+install any missing dependencies. After running that command and restarting
+Encore, you're done!
+
+Your ``.js`` and ``.jsx`` files will now be transformed through ``babel-preset-react``.
+
+.. _`React.js screencast series`: https://symfonycasts.com/screencast/reactjs
diff --git a/frontend/encore/server-data.rst b/frontend/encore/server-data.rst
new file mode 100644
index 00000000000..ebb1f3cb8a5
--- /dev/null
+++ b/frontend/encore/server-data.rst
@@ -0,0 +1,45 @@
+Passing Information from Twig to JavaScript
+===========================================
+
+In Symfony applications, you may find that you need to pass some dynamic data
+(e.g. user information) from Twig to your JavaScript code. One great way to pass
+dynamic configuration is by storing information in ``data`` attributes and reading
+them later in JavaScript. For example:
+
+.. code-block:: html+twig
+
+
+
+
+
+Fetch this in JavaScript:
+
+.. code-block:: javascript
+
+ document.addEventListener('DOMContentLoaded', function() {
+ var userRating = document.querySelector('.js-user-rating');
+ var isAuthenticated = userRating.dataset.isAuthenticated;
+
+ // or with jQuery
+ //var isAuthenticated = $('.js-user-rating').data('isAuthenticated');
+ });
+
+.. note::
+
+ When `accessing data attributes from JavaScript`_, the attribute names are
+ converted from dash-style to camelCase. For example, ``data-is-authenticated``
+ becomes ``isAuthenticated`` and ``data-number-of-reviews`` becomes
+ ``numberOfReviews``.
+
+There is no size limit for the value of the ``data-`` attributes, so you can
+store any content. In Twig, use the ``html_attr`` escaping strategy to avoid messing
+with HTML attributes. For example, if your ``User`` object has some ``getProfileData()``
+method that returns an array, you could do the following:
+
+.. code-block:: html+twig
+
+
+
+
+
+.. _`accessing data attributes from JavaScript`: https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes
diff --git a/frontend/encore/shared-entry.rst b/frontend/encore/shared-entry.rst
new file mode 100644
index 00000000000..6693b649d8d
--- /dev/null
+++ b/frontend/encore/shared-entry.rst
@@ -0,0 +1,42 @@
+Creating a Shared Commons Entry
+===============================
+
+.. caution::
+
+ While this method still works, see :doc:`/frontend/encore/split-chunks` for
+ the preferred solution to sharing assets between multiple entry files.
+
+Suppose you have multiple entry files and *each* requires ``jquery``. In this
+case, *each* output file will contain jQuery, slowing down your user's experience.
+To solve this, you can *extract* the common libraries to a "shared" entry file
+that's included on every page.
+
+Suppose you already have an entry called ``app`` that's included on every page.
+Update your code to use ``createSharedEntry()``:
+
+.. code-block:: diff
+
+ Encore
+ // ...
+ - .addEntry('app', './assets/js/app.js')
+ + .createSharedEntry('app', './assets/js/app.js')
+ .addEntry('homepage', './assets/js/homepage.js')
+ .addEntry('blog', './assets/js/blog.js')
+ .addEntry('store', './assets/js/store.js')
+
+Before making this change, if both ``app.js`` and ``store.js`` require ``jquery``,
+then ``jquery`` would be packaged into *both* files, which is wasteful. By making
+``app.js`` your "shared" entry, *any* code required by ``app.js`` (like jQuery) will
+*no longer* be packaged into any other files. The same is true for any CSS.
+
+Because ``app.js`` contains all the common code that other entry files depend on,
+its script (and link) tag must be on every page.
+
+.. tip::
+
+ The ``app.js`` file works best when its contents are changed *rarely*
+ and you're using :ref:`long-term caching `. Why?
+ If ``app.js`` contains application code that *frequently* changes, then
+ (when using versioning), its filename hash will frequently change. This means
+ your users won't enjoy the benefits of long-term caching for this file (which
+ is generally quite large).
diff --git a/frontend/encore/simple-example.rst b/frontend/encore/simple-example.rst
new file mode 100644
index 00000000000..7a9800dd3b3
--- /dev/null
+++ b/frontend/encore/simple-example.rst
@@ -0,0 +1,351 @@
+Encore: Setting up your Project
+===============================
+
+After :doc:`installing Encore `, your app already has one
+CSS and one JS file, organized into an ``assets/`` directory:
+
+* ``assets/js/app.js``
+* ``assets/css/app.css``
+
+With Encore, think of your ``app.js`` file like a standalone JavaScript
+application: it will *require* all of the dependencies it needs (e.g. jQuery or React),
+*including* any CSS. Your ``app.js`` file is already doing this with a special
+``require()`` function:
+
+.. code-block:: javascript
+
+ // assets/js/app.js
+ // ...
+
+ require('../css/app.css');
+
+ // var $ = require('jquery');
+
+Encore's job (via Webpack) is simple: to read and follow *all* of the ``require()``
+statements and create one final ``app.js`` (and ``app.css``) that contains *everything*
+your app needs. Encore can do a lot more: minify files, pre-process Sass/LESS,
+support React, Vue.js, etc.
+
+Configuring Encore/Webpack
+--------------------------
+
+Everything in Encore is configured via a ``webpack.config.js`` file at the root
+of your project. It already holds the basic config you need:
+
+.. code-block:: javascript
+
+ // webpack.config.js
+ var Encore = require('@symfony/webpack-encore');
+
+ Encore
+ // directory where compiled assets will be stored
+ .setOutputPath('web/build/')
+ // public path used by the web server to access the output path
+ .setPublicPath('/build')
+
+ .addEntry('app', './assets/js/app.js')
+
+ // ...
+ ;
+
+ // ...
+
+The *key* part is ``addEntry()``: this tells Encore to load the ``assets/js/app.js``
+file and follow *all* of the ``require()`` statements. It will then package everything
+together and - thanks to the first ``app`` argument - output final ``app.js`` and
+``app.css`` files into the ``web/build`` directory.
+
+.. _encore-build-assets:
+
+To build the assets, run:
+
+.. code-block:: terminal
+
+ # compile assets once
+ $ yarn encore dev
+
+ # or, recompile assets automatically when files change
+ $ yarn encore dev --watch
+
+ # on deploy, create a production build
+ $ yarn encore production
+
+.. note::
+
+ Stop and restart ``encore`` each time you update your ``webpack.config.js`` file.
+
+Congrats! You now have three new files:
+
+* ``web/build/app.js`` (holds all the JavaScript for your "app" entry)
+* ``web/build/app.css`` (holds all the CSS for your "app" entry)
+* ``web/build/runtime.js`` (a file that helps Webpack do its job)
+
+Next, include these in your base layout file. Two Twig helpers from WebpackEncoreBundle
+can do most of the work for you:
+
+.. code-block:: html+twig
+
+ {# app/Resources/views/base.html.twig #}
+
+
+
+
+
+ {% block stylesheets %}
+ {# 'app' must match the first argument to addEntry() in webpack.config.js #}
+ {{ encore_entry_link_tags('app') }}
+
+
+ {% endblock %}
+
+
+
+
+ {% block javascripts %}
+ {{ encore_entry_script_tags('app') }}
+
+
+ {% endblock %}
+
+
+
+.. _encore-entrypointsjson-simple-description:
+
+That's it! When you refresh your page, all of the JavaScript from
+``assets/js/app.js`` - as well as any other JavaScript files it included - will
+be executed. All the CSS files that were required will also be displayed.
+
+The ``encore_entry_link_tags()`` and ``encore_entry_script_tags()`` functions
+read from an ``entrypoints.json`` file that's generated by Encore to know the exact
+filename(s) to render. This file is *especially* useful because you can
+:doc:`enable versioning ` or
+:doc:`point assets to a CDN ` without making *any* changes to your
+template: the paths in ``entrypoints.json`` will always be the final, correct paths.
+
+If you're *not* using Symfony, you can ignore the ``entrypoints.json`` file and
+point to the final, built file directly. ``entrypoints.json`` is only required for
+some optional features.
+
+.. versionadded:: 0.21.0
+
+ The ``encore_entry_link_tags()`` comes from WebpackEncoreBundle and relies
+ on a feature in Encore that was first introduced in version 0.21.0. Previously,
+ the ``asset()`` function was used to point directly to the file.
+
+Requiring JavaScript Modules
+----------------------------
+
+Webpack is a module bundler... which means that you can ``require`` other JavaScript
+files. First, create a file that exports a function:
+
+.. code-block:: javascript
+
+ // assets/js/greet.js
+ module.exports = function(name) {
+ return `Yo yo ${name} - welcome to Encore!`;
+ };
+
+We'll use jQuery to print this message on the page. Install it via:
+
+.. code-block:: terminal
+
+ $ yarn add jquery --dev
+
+Great! Use ``require()`` to import ``jquery`` and ``greet.js``:
+
+.. code-block:: diff
+
+ // assets/js/app.js
+ // ...
+
+ + // loads the jquery package from node_modules
+ + var $ = require('jquery');
+
+ + // import the function from greet.js (the .js extension is optional)
+ + // ./ (or ../) means to look for a local file
+ + var greet = require('./greet');
+
+ + $(document).ready(function() {
+ + $('body').prepend('
'+greet('jill')+'
');
+ + });
+
+That's it! If you previously ran ``encore dev --watch``, your final, built files
+have already been updated: jQuery and ``greet.js`` have been automatically
+added to the output file (``app.js``). Refresh to see the message!
+
+The import and export Statements
+--------------------------------
+
+Instead of using ``require()`` and ``module.exports`` like shown above, JavaScript
+provides an alternate syntax based on the `ECMAScript 6 modules`_ that includes
+the ability to use dynamic imports.
+
+To export values, use ``export``:
+
+.. code-block:: diff
+
+ // assets/js/greet.js
+ - module.exports = function(name) {
+ + export default function(name) {
+ return `Yo yo ${name} - welcome to Encore!`;
+ };
+
+To import values, use ``import``:
+
+.. code-block:: diff
+
+ // assets/js/app.js
+ - require('../css/app.css');
+ + import '../css/app.css';
+
+ - var $ = require('jquery');
+ + import $ from 'jquery';
+
+ - var greet = require('./greet');
+ + import greet from './greet';
+
+.. _multiple-javascript-entries:
+
+Page-Specific JavaScript or CSS (Multiple Entries)
+--------------------------------------------------
+
+So far, you only have one final JavaScript file: ``app.js``. For small applications
+or SPA's (Single Page Applications), that might be fine! However, as your app grows,
+you may want to have page-specific JavaScript or CSS (e.g. checkout, account,
+etc.). To handle this, create a new "entry" JavaScript file for each page:
+
+.. code-block:: javascript
+
+ // assets/js/checkout.js
+ // custom code for your checkout page
+
+.. code-block:: javascript
+
+ // assets/js/account.js
+ // custom code for your account page
+
+Next, use ``addEntry()`` to tell Webpack to read these two new files when it builds:
+
+.. code-block:: diff
+
+ // webpack.config.js
+ Encore
+ // ...
+ .addEntry('app', './assets/js/app.js')
+ + .addEntry('checkout', './assets/js/checkout.js')
+ + .addEntry('account', './assets/js/account.js')
+ // ...
+
+And because you just changed the ``webpack.config.js`` file, make sure to stop
+and restart Encore:
+
+.. code-block:: terminal
+
+ $ yarn run encore dev --watch
+
+Webpack will now output a new ``checkout.js`` file and a new ``account.js`` file
+in your build directory. And, if any of those files require/import CSS, Webpack
+will *also* output ``checkout.css`` and ``account.css`` files.
+
+Finally, include the ``script`` and ``link`` tags on the individual pages where
+you need them:
+
+.. code-block:: diff
+
+ {# templates/.../checkout.html.twig #}
+ {% extends 'base.html.twig' %}
+
+ + {% block stylesheets %}
+ + {{ parent() }}
+ + {{ encore_entry_link_tags('checkout') }}
+ + {% endblock %}
+
+ + {% block javascripts %}
+ + {{ parent() }}
+ + {{ encore_entry_script_tags('checkout') }}
+ + {% endblock %}
+
+Now, the checkout page will contain all the JavaScript and CSS for the ``app`` entry
+(because this is included in ``base.html.twig`` and there is the ``{{ parent() }}`` call)
+*and* your ``checkout`` entry.
+
+See :doc:`/frontend/encore/page-specific-assets` for more details. To avoid duplicating
+the same code in different entry files, see :doc:`/frontend/encore/split-chunks`.
+
+Using Sass/LESS/Stylus
+----------------------
+
+You've already mastered the basics of Encore. Nice! But, there are *many* more
+features that you can opt into if you need them. For example, instead of using plain
+CSS you can also use Sass, LESS or Stylus. To use Sass, rename the ``app.css``
+file to ``app.scss`` and update the ``import`` statement:
+
+.. code-block:: diff
+
+ // assets/js/app.js
+ - import '../css/app.css';
+ + import '../css/app.scss';
+
+Then, tell Encore to enable the Sass pre-processor:
+
+.. code-block:: diff
+
+ // webpack.config.js
+ Encore
+ // ...
+
+ + .enableSassLoader()
+ ;
+
+Because you just changed your ``webpack.config.js`` file, you'll need to restart
+Encore. When you do, you'll see an error!
+
+.. code-block:: terminal
+
+ > Error: Install sass-loader & node-sass to use enableSassLoader()
+ > yarn add sass-loader@^8.0.0 node-sass --dev
+
+Encore supports many features. But, instead of forcing all of them on you, when
+you need a feature, Encore will tell you what you need to install. Run:
+
+.. code-block:: terminal
+
+ $ yarn add sass-loader@^8.0.0 node-sass --dev
+ $ yarn encore dev --watch
+
+Your app now supports Sass. Encore also supports LESS and Stylus. See
+:doc:`/frontend/encore/css-preprocessors`.
+
+Compiling Only a CSS File
+-------------------------
+
+.. caution::
+
+ Using ``addStyleEntry()`` is supported, but not recommended. A better option
+ is to follow the pattern above: use ``addEntry()`` to point to a JavaScript
+ file, then require the CSS needed from inside of that.
+
+If you want to only compile a CSS file, that's possible via ``addStyleEntry()``:
+
+.. code-block:: javascript
+
+ // webpack.config.js
+ Encore
+ // ...
+
+ .addStyleEntry('some_page', './assets/css/some_page.css')
+ ;
+
+This will output a new ``some_page.css``.
+
+Keep Going!
+-----------
+
+Encore supports many more features! For a full list of what you can do, see
+`Encore's index.js file`_. Or, go back to :ref:`list of Encore articles `.
+
+.. _`Encore's index.js file`: https://github.com/symfony/webpack-encore/blob/master/index.js
+.. _`ECMAScript 6 modules`: https://hacks.mozilla.org/2015/08/es6-in-depth-modules/
diff --git a/frontend/encore/sourcemaps.rst b/frontend/encore/sourcemaps.rst
new file mode 100644
index 00000000000..62d4c6a351b
--- /dev/null
+++ b/frontend/encore/sourcemaps.rst
@@ -0,0 +1,23 @@
+Enabling Source Maps
+====================
+
+`Source maps`_ allow browsers to access the original code related to some
+asset (e.g. the Sass code that was compiled to CSS or the TypeScript code that
+was compiled to JavaScript). Source maps are useful for debugging purposes but
+unnecessary when executing the application in production.
+
+Encore's default ``webpack.config.js`` file enables source maps in the ``dev``
+build:
+
+.. code-block:: javascript
+
+ // webpack.config.js
+ // ...
+
+ Encore
+ // ...
+
+ .enableSourceMaps(!Encore.isProduction())
+ ;
+
+.. _`Source maps`: https://developer.mozilla.org/en-US/docs/Tools/Debugger/How_to/Use_a_source_map
diff --git a/frontend/encore/split-chunks.rst b/frontend/encore/split-chunks.rst
new file mode 100644
index 00000000000..ebaa4ee48ce
--- /dev/null
+++ b/frontend/encore/split-chunks.rst
@@ -0,0 +1,64 @@
+Preventing Duplication by "Splitting" Shared Code into Separate Files
+=====================================================================
+
+Suppose you have multiple entry files and *each* requires ``jquery``. In this
+case, *each* output file will contain jQuery, making your files much larger than
+necessary. To solve this, you can ask webpack to analyze your files and *split* them
+into additional files, which will contain "shared" code.
+
+To enable this, call ``splitEntryChunks()``:
+
+.. code-block:: diff
+
+ Encore
+ // ...
+
+ // multiple entry files, which probably import the same code
+ .addEntry('app', './assets/js/app.js')
+ .addEntry('homepage', './assets/js/homepage.js')
+ .addEntry('blog', './assets/js/blog.js')
+ .addEntry('store', './assets/js/store.js')
+
+ + .splitEntryChunks()
+
+
+Now, each output file (e.g. ``homepage.js``) *may* be split into multiple file
+(e.g. ``homepage.js``, ``vendor~homepage.js``). This means that you *may* need to
+include *multiple* ``script`` tags (or ``link`` tags for CSS) in your template.
+Encore creates an :ref:`entrypoints.json `
+file that lists exactly which CSS and JavaScript files are needed for each entry.
+
+If you're using the ``encore_entry_link_tags()`` and ``encore_entry_script_tags()``
+Twig functions from WebpackEncoreBundle, you don't need to do anything else! These
+functions automatically read this file and render as many ``script`` or ``link``
+tags as needed:
+
+.. code-block:: html+twig
+
+ {#
+ May now render multiple script tags:
+
+
+
+ #}
+ {{ encore_entry_script_tags('homepage') }}
+
+Controlling how Assets are Split
+--------------------------------
+
+The logic for *when* and *how* to split the files is controlled by the
+`SplitChunksPlugin from Webpack`_. You can control the configuration passed to
+this plugin with the ``configureSplitChunks()`` function:
+
+.. code-block:: diff
+
+ Encore
+ // ...
+
+ .splitEntryChunks()
+ + .configureSplitChunks(function(splitChunks) {
+ + // change the configuration
+ + splitChunks.minSize = 0;
+ + })
+
+.. _`SplitChunksPlugin from Webpack`: https://webpack.js.org/plugins/split-chunks-plugin/
diff --git a/frontend/encore/typescript.rst b/frontend/encore/typescript.rst
new file mode 100644
index 00000000000..b1af45d9c04
--- /dev/null
+++ b/frontend/encore/typescript.rst
@@ -0,0 +1,58 @@
+Enabling TypeScript (ts-loader)
+===============================
+
+Want to use `TypeScript`_? No problem! First, enable it:
+
+.. code-block:: diff
+
+ // webpack.config.js
+ // ...
+
+ Encore
+ // ...
+ + .addEntry('main', './assets/main.ts')
+
+ + .enableTypeScriptLoader()
+
+ // optionally enable forked type script for faster builds
+ // https://www.npmjs.com/package/fork-ts-checker-webpack-plugin
+ // requires that you have a tsconfig.json file that is setup correctly.
+ + //.enableForkedTypeScriptTypesChecking()
+ ;
+
+Then restart Encore. When you do, it will give you a command you can run to
+install any missing dependencies. After running that command and restarting
+Encore, you're done!
+
+Any ``.ts`` files that you require will be processed correctly. You can
+also configure the `ts-loader options`_ via the ``enableTypeScriptLoader()``
+method.
+
+.. code-block:: diff
+
+ Encore
+ // ...
+ .addEntry('main', './assets/main.ts')
+
+ - .enableTypeScriptLoader()
+ + .enableTypeScriptLoader(function(tsConfig) {
+ + // You can use this callback function to adjust ts-loader settings
+ + // https://github.com/TypeStrong/ts-loader/blob/master/README.md#loader-options
+ + // For example:
+ + // tsConfig.silent = false
+ + })
+
+ // ...
+ ;
+
+See the `Encore's index.js file`_ for detailed documentation and check
+out the `tsconfig.json reference`_ and the `Webpack guide about Typescript`_.
+
+If React is enabled (``.enableReactPreset()``), any ``.tsx`` file will also be
+processed by ``ts-loader``.
+
+.. _`TypeScript`: https://www.typescriptlang.org/
+.. _`ts-loader options`: https://github.com/TypeStrong/ts-loader#options
+.. _`Encore's index.js file`: https://github.com/symfony/webpack-encore/blob/master/index.js
+.. _`tsconfig.json reference`: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html
+.. _`Webpack guide about Typescript`: https://webpack.js.org/guides/typescript/
diff --git a/frontend/encore/url-loader.rst b/frontend/encore/url-loader.rst
new file mode 100644
index 00000000000..976cd6974d8
--- /dev/null
+++ b/frontend/encore/url-loader.rst
@@ -0,0 +1,51 @@
+Inlining files in CSS with Webpack URL Loader
+=============================================
+
+A simple technique to improve the performance of web applications is to reduce
+the number of HTTP requests inlining small files as base64 encoded URLs in the
+generated CSS files.
+
+Webpack Encore provides this feature via Webpack's `URL Loader`_ plugin, but
+it's disabled by default. First, add the URL loader to your project:
+
+.. code-block:: terminal
+
+ $ yarn add url-loader --dev
+
+Then enable it in your ``webpack.config.js``:
+
+.. code-block:: javascript
+
+ // webpack.config.js
+ // ...
+
+ Encore
+ // ...
+ .configureUrlLoader({
+ fonts: { limit: 4096 },
+ images: { limit: 4096 }
+ })
+ ;
+
+The ``limit`` option defines the maximum size in bytes of the inlined files. In
+the previous example, font and image files having a size below or equal to 4 KB
+will be inlined and the rest of files will be processed as usual.
+
+You can also use all the other options supported by the `URL Loader`_. If you
+want to disable this loader for either images or fonts, remove the corresponding
+key from the object that is passed to the ``configureUrlLoader()`` method:
+
+.. code-block:: javascript
+
+ // webpack.config.js
+ // ...
+
+ Encore
+ // ...
+ .configureUrlLoader({
+ // 'fonts' is not defined, so only images will be inlined
+ images: { limit: 4096 }
+ })
+ ;
+
+.. _`URL Loader`: https://github.com/webpack-contrib/url-loader
diff --git a/frontend/encore/versioning.rst b/frontend/encore/versioning.rst
new file mode 100644
index 00000000000..98a2b7fb6b2
--- /dev/null
+++ b/frontend/encore/versioning.rst
@@ -0,0 +1,92 @@
+Asset Versioning
+================
+
+.. _encore-long-term-caching:
+
+Tired of deploying and having browser's cache the old version of your assets?
+By calling ``enableVersioning()``, each filename will now include a hash that
+changes whenever the *contents* of that file change (e.g. ``app.123abc.js``
+instead of ``app.js``). This allows you to use aggressive caching strategies
+(e.g. a far future ``Expires``) because, whenever a file change, its hash will change,
+ignoring any existing cache:
+
+.. code-block:: diff
+
+ // webpack.config.js
+ // ...
+
+ Encore
+ .setOutputPath('web/build/')
+ // ...
+ + .enableVersioning()
+
+To link to these assets, Encore creates two files ``entrypoints.json`` and
+``manifest.json``.
+
+.. _load-manifest-files:
+
+Loading Assets from ``entrypoints.json`` & ``manifest.json``
+------------------------------------------------------------
+
+Whenever you run Encore, two configuration files are generated: ``entrypoints.json``
+and ``manifest.json``. Each file is similar, and contains a map to the final, versioned
+filename.
+
+The first file - ``entrypoints.json`` - is used by the ``encore_entry_script_tags()``
+and ``encore_entry_link_tags()`` Twig helpers. If you're using these, then your
+CSS and JavaScript files will render with the new, versioned filename. If you're
+not using Symfony, your app will need to read this file in a similar way.
+
+The ``manifest.json`` file is only needed to get the versioned filename of *other*
+files, like font files or image files (though it also contains information about
+the CSS and JavaScript files):
+
+.. code-block:: json
+
+ {
+ "build/app.js": "/build/app.123abc.js",
+ "build/dashboard.css": "/build/dashboard.a4bf2d.css",
+ "build/images/logo.png": "/build/images/logo.3eed42.png"
+ }
+
+In your app, you need to read this file if you want to be able to link (e.g. via
+an ``img`` tag) to certain assets. If you're using Symfony, just activate the
+``json_manifest_file`` versioning strategy in ``config.yml``:
+
+.. code-block:: yaml
+
+ # app/config/config.yml
+ framework:
+ # ...
+ assets:
+ # feature is supported in Symfony 3.3 and higher
+ json_manifest_path: '%kernel.project_dir%/web/build/manifest.json'
+
+That's it! Just be sure to wrap each path in the Twig ``asset()`` function
+like normal:
+
+.. code-block:: html+twig
+
+
+
+Troubleshooting
+---------------
+
+Asset Versioning and Deployment
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When deploying a new version of your application, versioned assets will include
+a new hash, making the previous assets no longer available. This is usually not
+a problem when deploying applications using a rolling update, blue/green or
+symlink strategies.
+
+However, even when applying those techniques, there could be a lapse of time
+when some publicly/privately cached response requests the previous version of
+the assets. If your application can't afford to serve any broken asset, the best
+solution is to use a CDN (or custom made service) that keeps all the old assets
+cached for some time.
+
+Learn more
+----------
+
+* :doc:`/components/asset`
diff --git a/frontend/encore/versus-assetic.rst b/frontend/encore/versus-assetic.rst
new file mode 100644
index 00000000000..afc2373b2b7
--- /dev/null
+++ b/frontend/encore/versus-assetic.rst
@@ -0,0 +1,56 @@
+Encore Versus Assetic?
+======================
+
+Symfony originally shipped with support for :doc:`Assetic `: a
+pure PHP library capable of processing, combining and minifying CSS and JavaScript
+files. And while Encore is now the recommended way of processing your assets, Assetic
+still works well.
+
+So what are the differences between Assetic and Encore?
+
++----------------------------------+-------------------------------+-------------------------+
+| | Assetic | Encore +
++----------------------------------+-------------------------------+-------------------------+
+| Language | Pure PHP, relies on other | Node.js |
+| | language tools for some tasks | |
++----------------------------------+-------------------------------+-------------------------+
+| Combine assets? | Yes | Yes |
++----------------------------------+-------------------------------+-------------------------+
+| Minify assets? | Yes (when configured) | Yes (out-of-the-box) |
++----------------------------------+-------------------------------+-------------------------+
+| Process Sass/Less? | Yes | Yes |
++----------------------------------+-------------------------------+-------------------------+
+| Loads JS Modules? [1]_ | No | Yes |
++----------------------------------+-------------------------------+-------------------------+
+| Load CSS dependencies in JS? [1] | No | Yes |
++----------------------------------+-------------------------------+-------------------------+
+| React, Vue.js support? | No [2]_ | Yes |
++----------------------------------+-------------------------------+-------------------------+
+| Support | Not actively maintained | Actively maintained |
++----------------------------------+-------------------------------+-------------------------+
+
+.. [1] JavaScript modules allow you to organize your JavaScript into small files
+ called modules and import them:
+
+ .. code-block:: javascript
+
+ // require third-party modules
+ var $ = require('jquery');
+
+ // require your own CoolComponent.js modules
+ var coolComponent = require('./components/CoolComponent');
+
+ Encore (via Webpack) parses these automatically and creates a JavaScript
+ file that contains all needed dependencies. You can even require CSS or
+ images.
+
+.. [2] Assetic has outdated support for React.js only. Encore ships with modern
+ support for React.js, Vue.js, TypeScript, etc.
+
+Should I Upgrade from Assetic to Encore
+---------------------------------------
+
+If you already have Assetic working in an application, and haven't needed any of
+the features that Encore offers over Assetic, continuing to use Assetic is fine.
+If you *do* start to need more features, then you might have a business case for
+changing to Encore.
diff --git a/frontend/encore/virtual-machine.rst b/frontend/encore/virtual-machine.rst
new file mode 100644
index 00000000000..068d5c8451f
--- /dev/null
+++ b/frontend/encore/virtual-machine.rst
@@ -0,0 +1,121 @@
+Using Encore in a Virtual Machine
+=================================
+
+Encore is compatible with virtual machines such as `VirtualBox`_ and `VMWare`_
+but you may need to make some changes to your configuration to make it work.
+
+File Watching Issues
+--------------------
+
+When using a virtual machine, your project root directory is shared with the
+virtual machine using `NFS`_. This introduces issues with files watching, so
+you must enable the `polling`_ option to make it work:
+
+.. code-block:: javascript
+
+ // webpack.config.js
+
+ // ...
+
+ // will be applied for `encore dev --watch` and `encore dev-server` commands
+ Encore.configureWatchOptions(watchOptions => {
+ watchOptions.poll = 250; // check for changes every 250 milliseconds
+ });
+
+Development Server Issues
+-------------------------
+
+Configure the Public Path
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. note::
+
+ You can skip this section if your application is running on
+ ``http://localhost`` instead a custom local domain-name like
+ ``http://app.vm``.
+
+When running the development server, you will probably see the following errors
+in the web console:
+
+.. code-block:: text
+
+ GET http://localhost:8080/build/vendors~app.css net::ERR_CONNECTION_REFUSED
+ GET http://localhost:8080/build/runtime.js net::ERR_CONNECTION_REFUSED
+ ...
+
+If your Symfony application is running on a custom domain (e.g.
+``http://app.vm``), you must configure the public path explicitly in your
+``package.json``:
+
+.. code-block:: diff
+
+ {
+ ...
+ "scripts": {
+ - "dev-server": "encore dev-server",
+ + "dev-server": "encore dev-server --public http://app.vm:8080",
+ ...
+ }
+ }
+
+After restarting Encore and reloading your web page, you will probably see
+different issues in the web console:
+
+.. code-block:: text
+
+ GET http://app.vm:8080/build/vendors~app.css net::ERR_CONNECTION_REFUSED
+ GET http://app.vm:8080/build/runtime.js net::ERR_CONNECTION_REFUSED
+
+You still need to make other configuration changes, as explained in the
+following sections.
+
+Allow External Access
+~~~~~~~~~~~~~~~~~~~~~
+
+Add the ``--host 0.0.0.0`` argument to the ``dev-server`` configuration in your
+``package.json`` file to make the development server accept all incoming
+connections:
+
+.. code-block:: diff
+
+ {
+ ...
+ "scripts": {
+ - "dev-server": "encore dev-server --public http://app.vm:8080",
+ + "dev-server": "encore dev-server --public http://app.vm:8080 --host 0.0.0.0",
+ ...
+ }
+ }
+
+.. caution::
+
+ Make sure to run the development server inside your virtual machine only;
+ otherwise other computers can have access to it.
+
+Fix "Invalid Host header" Issue
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Webpack will respond ``Invalid Host header`` when trying to access files from
+the dev-server. To fix this, add the argument ``--disable-host-check``:
+
+.. code-block:: diff
+
+ {
+ ...
+ "scripts": {
+ - "dev-server": "encore dev-server --public http://app.vm:8080 --host 0.0.0.0",
+ + "dev-server": "encore dev-server --public http://app.vm:8080 --host 0.0.0.0 --disable-host-check",
+ ...
+ }
+ }
+
+.. caution::
+
+ Beware that `it's not recommended to disable host checking`_ in general, but
+ here it's required to solve the issue when using Encore in a virtual machine.
+
+.. _`VirtualBox`: https://www.virtualbox.org/
+.. _`VMWare`: https://www.vmware.com
+.. _`NFS`: https://en.wikipedia.org/wiki/Network_File_System
+.. _`polling`: https://webpack.js.org/configuration/watch/#watchoptionspoll
+.. _`it's not recommended to disable host checking`: https://webpack.js.org/configuration/dev-server/#devserverdisablehostcheck
diff --git a/frontend/encore/vuejs.rst b/frontend/encore/vuejs.rst
new file mode 100644
index 00000000000..4c67362ada1
--- /dev/null
+++ b/frontend/encore/vuejs.rst
@@ -0,0 +1,162 @@
+Enabling Vue.js (``vue-loader``)
+================================
+
+.. admonition:: Screencast
+ :class: screencast
+
+ Do you prefer video tutorials? Check out the `Vue screencast series`_.
+
+Want to use `Vue.js`_? No problem! First enable it in ``webpack.config.js``:
+
+.. code-block:: diff
+
+ // webpack.config.js
+ // ...
+
+ Encore
+ // ...
+ .addEntry('main', './assets/main.js')
+
+ + .enableVueLoader()
+ ;
+
+Then restart Encore. When you do, it will give you a command you can run to
+install any missing dependencies. After running that command and restarting
+Encore, you're done!
+
+Any ``.vue`` files that you require will be processed correctly. You can also
+configure the `vue-loader options`_ by passing an options callback to
+``enableVueLoader()``. See the `Encore's index.js file`_ for detailed documentation.
+
+Hot Module Replacement (HMR)
+----------------------------
+
+The ``vue-loader`` supports hot module replacement: just update your code and watch
+your Vue.js app update *without* a browser refresh! To activate it, just use the
+``dev-server`` with the ``--hot`` option:
+
+.. code-block:: terminal
+
+ $ yarn encore dev-server --hot
+
+That's it! Change one of your ``.vue`` files and watch your browser update. But
+note: this does *not* currently work for *style* changes in a ``.vue`` file. Seeing
+updated styles still requires a page refresh.
+
+See :doc:`/frontend/encore/dev-server` for more details.
+
+JSX Support
+-----------
+
+You can enable `JSX with Vue.js`_ by configuring the second parameter of the
+``.enableVueLoader()`` method:
+
+.. code-block:: diff
+
+ // webpack.config.js
+ // ...
+
+ Encore
+ // ...
+ .addEntry('main', './assets/main.js')
+
+ - .enableVueLoader()
+ + .enableVueLoader(() => {}, {
+ + useJsx: true
+ + })
+ ;
+
+Next, run or restart Encore. When you do, you will see an error message helping
+you install any missing dependencies. After running that command and restarting
+Encore, you're done!
+
+Your ``.jsx`` files will now be transformed through ``@vue/babel-preset-jsx``.
+
+Using styles
+~~~~~~~~~~~~
+
+You can't use ``