diff --git a/.doctor-rst.yaml b/.doctor-rst.yaml index aea27d929db..6ef4959d8f9 100644 --- a/.doctor-rst.yaml +++ b/.doctor-rst.yaml @@ -1,43 +1,54 @@ rules: - no_inheritdoc: ~ + american_english: ~ avoid_repetetive_words: ~ + blank_line_after_anchor: ~ blank_line_after_directive: ~ - short_array_syntax: ~ - no_app_console: ~ - typo: ~ - replacement: ~ + blank_line_before_directive: ~ composer_dev_option_not_at_the_end: ~ - yarn_dev_option_at_the_end: ~ - versionadded_directive_should_have_version: ~ + correct_code_block_directive_based_on_the_content: ~ deprecated_directive_should_have_version: ~ - no_composer_req: ~ - no_php_open_tag_in_code_block_php_directive: ~ - no_blank_line_after_filepath_in_php_code_block: ~ - no_blank_line_after_filepath_in_yaml_code_block: ~ - no_blank_line_after_filepath_in_xml_code_block: ~ - no_blank_line_after_filepath_in_twig_code_block: ~ - php_prefix_before_bin_console: ~ - use_deprecated_directive_instead_of_versionadded: ~ - no_space_before_self_xml_closing_tag: ~ - no_explicit_use_of_code_block_php: ~ + ensure_exactly_one_space_between_link_definition_and_link: ~ + ensure_link_definition_contains_valid_url: ~ ensure_order_of_code_blocks_in_configuration_block: ~ - american_english: ~ - valid_use_statements: ~ + extend_abstract_controller: ~ + extension_xlf_instead_of_xliff: ~ + indention: ~ lowercase_as_in_use_statements: ~ - ordered_use_statements: ~ - no_namespace_after_use_statements: ~ - correct_code_block_directive_based_on_the_content: ~ max_blank_lines: max: 2 + max_colons: ~ + no_app_console: ~ + no_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_directive_after_shorthand: ~ + 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: ~ + string_replacement: ~ + typo: ~ + unused_links: ~ + use_deprecated_directive_instead_of_versionadded: ~ use_https_xsd_urls: ~ - blank_line_before_directive: ~ - extension_xlf_instead_of_xliff: ~ valid_inline_highlighted_namespaces: ~ - indention: ~ - unused_links: ~ + valid_use_statements: ~ + versionadded_directive_should_have_version: ~ yaml_instead_of_yml_suffix: ~ - extend_abstract_controller: ~ + yarn_dev_option_at_the_end: ~ # no_app_bundle: ~ # 4.x @@ -68,7 +79,7 @@ whitelist: - '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:: 1.9.0' # Encore - '.. versionadded:: 0.28.4' # Encore - '.. versionadded:: 2.4.0' # SwiftMailer - '.. versionadded:: 1.30' # Twig @@ -77,7 +88,7 @@ whitelist: - '.. versionadded:: 1.11' # MakerBundle - '.. versionadded:: 1.3' # MakerBundle - '.. versionadded:: 1.8' # MakerBundle - - '.. versionadded:: 1.6' # Flex in setup/upgrade_minor.rst + - '.. versionadded:: 1.18' # Flex in setup/upgrade_minor.rst - '0 => 123' # assertion for var_dumper - components/var_dumper.rst - '1 => "foo"' # assertion for var_dumper - components/var_dumper.rst - '$var .= "Because of this `\xE9` octet (\\xE9),\n";' @@ -85,3 +96,5 @@ whitelist: - ".. _`Deploying Symfony 4 Apps on Heroku`: https://devcenter.heroku.com/articles/deploying-symfony4" - '.. versionadded:: 0.2' # MercureBundle - '.. code-block:: twig' + - 'End to End Tests (E2E)' + - '.. _`a feature to test applications using Mercure`: https://github.com/symfony/panther#creating-isolated-browsers-to-test-apps-using-mercure-or-websocket' diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 51ce53a1a89..9eb5d91783b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,6 @@ +# GithubActions workflows +/.github/workflows* @OskarStark + # Console /console* @chalasr /components/console* @chalasr diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md deleted file mode 100644 index 9a4e5a2cedc..00000000000 --- a/.github/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,12 +0,0 @@ -Code of Conduct -=============== - -This project follows a [Code of Conduct][code_of_conduct] in order to ensure an -open and welcoming environment. Please read the full text for understanding the -accepted and unaccepted behavior. - -Please read also the [reporting guidelines][guidelines], in case you encountered -or witnessed any misbehavior. - -[code_of_conduct]: https://symfony.com/doc/current/contributing/code_of_conduct/code_of_conduct.html -[guidelines]: https://symfony.com/doc/current/contributing/code_of_conduct/reporting_guidelines.html diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 26f0e537118..73bbcca0235 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,3 +1,5 @@ +name: CI + on: push: branches-ignore: @@ -6,45 +8,50 @@ on: branches-ignore: - 'github-comments' -name: CI +permissions: + contents: read jobs: - build: - name: Build + symfony-docs-builder-build: + name: Build (symfony-tools/docs-builder) runs-on: ubuntu-latest + continue-on-error: true + steps: - name: "Checkout" uses: actions/checkout@v2 - - name: "Set up Python 3.7" - uses: actions/setup-python@v1 + - name: "Set-up PHP" + uses: shivammathur/setup-php@v2 with: - python-version: '3.7' # Semantic version range syntax or exact version of a Python version + php-version: 8.0 + coverage: none + tools: "composer:v2" - - name: "Display Python version" - run: python -c "import sys; print(sys.version)" + - name: Get composer cache directory + id: composercache + working-directory: _build + run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - name: "Install Sphinx dependencies" - run: sudo apt-get install python-dev build-essential - - - name: "Cache pip" + - name: Cache dependencies uses: actions/cache@v2 with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('_build/.requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip- + path: ${{ steps.composercache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- - - name: "Install Sphinx + requirements via pip" - run: pip install -r _build/.requirements.txt + - name: "Install dependencies" + working-directory: _build + run: composer install --prefer-dist --no-progress - - name: "Build documentation" - run: make -C _build SPHINXOPTS="-nqW -j auto" html + - name: "Build the docs" + working-directory: _build + run: php build.php --disable-cache doctor-rst: - name: DOCtor-RST + name: Lint (DOCtor-RST) runs-on: ubuntu-latest @@ -60,7 +67,7 @@ jobs: id: extract_base_branch - name: "Cache DOCtor-RST" - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: .cache key: ${{ runner.os }}-doctor-rst-${{ steps.extract_base_branch.outputs.branch }} @@ -69,3 +76,68 @@ jobs: uses: docker://oskarstark/doctor-rst with: args: --short --error-format=github --cache-file=/github/workspace/.cache/doctor-rst.cache + + symfony-code-block-checker: + name: Code Blocks + runs-on: Ubuntu-20.04 + continue-on-error: true + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + path: 'docs' + + - name: Set-up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.0 + coverage: none + + - name: Fetch branch from where the PR started + working-directory: docs + run: git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/* + + - name: Find modified files + id: find-files + working-directory: docs + run: echo "::set-output name=files::$(git diff --name-only origin/${{ github.base_ref }} HEAD | grep ".rst" | tr '\n' ' ')" + + - name: Get composer cache directory + id: composercache + working-directory: docs/_build + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache dependencies + if: ${{ steps.find-files.outputs.files }} + uses: actions/cache@v2 + with: + path: ${{ steps.composercache.outputs.dir }} + key: ${{ runner.os }}-composer-codeBlocks-${{ hashFiles('_checker/composer.lock', '_sf_app/composer.lock') }} + restore-keys: ${{ runner.os }}-composer-codeBlocks- + + - name: Install dependencies + if: ${{ steps.find-files.outputs.files }} + run: composer create-project symfony-tools/code-block-checker:@dev _checker + + - name: Install test application + if: ${{ steps.find-files.outputs.files }} + run: | + git clone -b ${{ github.base_ref }} --depth 5 --single-branch https://github.com/symfony-tools/symfony-application.git _sf_app + cd _sf_app + composer update + + - name: Generate baseline + if: ${{ steps.find-files.outputs.files }} + working-directory: docs + run: | + CURRENT=$(git rev-parse HEAD) + git checkout -m ${{ github.base_ref }} + ../_checker/code-block-checker.php verify:docs `pwd` ${{ steps.find-files.outputs.files }} --generate-baseline=baseline.json --symfony-application=`realpath ../_sf_app` + git checkout -m $CURRENT + cat baseline.json + + - name: Verify examples + if: ${{ steps.find-files.outputs.files }} + working-directory: docs + run: | + ../_checker/code-block-checker.php verify:docs `pwd` ${{ steps.find-files.outputs.files }} --baseline=baseline.json --output-format=github --symfony-application=`realpath ../_sf_app` diff --git a/.gitignore b/.gitignore index 6a20088680a..b69047f69a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,2 @@ -/_build/doctrees -/_build/spelling -/_build/html -*.pyc +/_build/vendor +/_build/output diff --git a/.symfony.cloud.yaml b/.symfony.cloud.yaml deleted file mode 100644 index faa3c24780e..00000000000 --- a/.symfony.cloud.yaml +++ /dev/null @@ -1,58 +0,0 @@ -# This file describes an application. You can have multiple applications -# in the same project. - -# The name of this app. Must be unique within a project. -name: symfonydocs - -# The toolstack used to build the application. -type: "python:3.7" - -# The configuration of app when it is exposed to the web. -web: - # The public directory of the app, relative to its root. - document_root: "/_build/html" - index_files: - - index.html - whitelist: - - \.html$ - - \.txt$ - - # CSS and Javascript. - - \.css$ - - \.js$ - - \.hbs$ - - # image/* types. - - \.gif$ - - \.png$ - - \.ico$ - - \.svgz?$ - - # fonts types. - - \.ttf$ - - \.eot$ - - \.woff$ - - \.otf$ - - # robots.txt. - - /robots\.txt$ - -# The size of the persistent disk of the application (in MB). -disk: 512 - -# Build time dependencies. -dependencies: - python: - virtualenv: 15.1.0 - -# The hooks that will be performed when the package is deployed. -hooks: - build: | - virtualenv .virtualenv - . .virtualenv/bin/activate - # SymfonyCloud currently sets PIP_USER=1. - export PIP_USER= - pip install pip==9.0.1 wheel==0.29.0 - pip install -r _build/.requirements.txt - find .virtualenv -type f -name "*.rst" -delete - make -C _build html diff --git a/.symfony/routes.yaml b/.symfony/routes.yaml deleted file mode 100644 index caf4875f732..00000000000 --- a/.symfony/routes.yaml +++ /dev/null @@ -1,11 +0,0 @@ -https://{default}/: - cache: - cookies: - - '*' - default_ttl: 0 - enabled: true - headers: - - Accept - - Accept-Language - type: upstream - upstream: symfonydocs:http diff --git a/.symfony/services.yaml b/.symfony/services.yaml deleted file mode 100644 index ec9369f2b00..00000000000 --- a/.symfony/services.yaml +++ /dev/null @@ -1 +0,0 @@ -# Keeping this file empty to not deploy unused services. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index d211dd419d0..00000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,83 +0,0 @@ -Code of Conduct -=============== - -Our Pledge ----------- - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnic origin, gender identity and expression, level of -experience, education, socio-economic status, nationality, personal appearance, -religion, or sexual identity and orientation. - -Our Standards -------------- - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -Our Responsibilities --------------------- - -[CoC Active Response Ensurers, or CARE][1], are responsible for clarifying the -standards of acceptable behavior and are expected to take appropriate and fair -corrective action in response to any instances of unacceptable behavior. - -CARE team members have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, or to ban temporarily or permanently any -contributor for other behaviors that they deem inappropriate, threatening, -offensive, or harmful. - -Scope ------ - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by CARE team members. - -Enforcement ------------ - -Instances of abusive, harassing, or otherwise unacceptable behavior -[may be reported][2] by contacting the [CARE team members][1]. -All complaints will be reviewed and investigated and will result in a response -that is deemed necessary and appropriate to the circumstances. The CARE team is -obligated to maintain confidentiality with regard to the reporter of an -incident. Further details of specific enforcement policies may be posted -separately. - -CARE team members who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by the -[core team][3]. - -Attribution ------------ - -This Code of Conduct is adapted from the [Contributor Covenant version 1.4][4]. - -[1]: https://symfony.com/doc/current/contributing/code_of_conduct/care_team.html -[2]: https://symfony.com/doc/current/contributing/code_of_conduct/reporting_guidelines.html -[3]: https://symfony.com/doc/current/contributing/code/core_team.html -[4]: https://www.contributor-covenant.org/version/1/4/code-of-conduct.html diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index c1e63debe91..00000000000 --- a/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM python:2-stretch as builder - -WORKDIR /www - -COPY ./_build/.requirements.txt _build/ - -RUN pip install pip==9.0.1 wheel==0.29.0 \ - && pip install -r _build/.requirements.txt - -COPY . /www - -RUN make -C _build html - -FROM nginx:latest - -COPY --from=builder /www/_build/html /usr/share/nginx/html diff --git a/README.markdown b/README.markdown index 1d94f6f1ff0..8bd67bed4a3 100644 --- a/README.markdown +++ b/README.markdown @@ -1,39 +1,55 @@ -Symfony Documentation -===================== - -This documentation is rendered online at https://symfony.com/doc/current/ +

+ +

+ +

+ The official Symfony Documentation +

+ +

+ + Online version + + | + + Components + + | + + Screencasts + +

Contributing ------------ -We love contributors! For more information on how you can contribute to the -Symfony documentation, please read -[Contributing to the Documentation](https://symfony.com/doc/current/contributing/documentation/overview.html) +We love contributors! For more information on how you can contribute, please read +the [Symfony Docs Contributing Guide](https://symfony.com/doc/current/contributing/documentation/overview.html) -> **Note** -> Unless you're documenting a feature that was introduced *after* Symfony 3.4 -> (e.g. in Symfony 4.4), all pull requests must be based off of the **3.4** branch, -> **not** the master or older branches. +**Important**: use `4.4` branch as the base of your pull requests, unless you are +documenting a feature that was introduced *after* Symfony 4.4 (e.g. in Symfony 5.2). -SymfonyCloud ------------- +Build Documentation Locally +--------------------------- -Thanks to [SymfonyCloud](https://symfony.com/cloud) for providing an integration -server where Pull Requests are built and can be reviewed by contributors. +This is not needed for contributing, but it's useful if you want to debug some +issue in the docs or if you want to read Symfony Documentation offline. -Docker ------- +```bash +$ git clone git@github.com:symfony/symfony-docs.git -You can build the doc locally with these commands: +$ cd symfony-docs/ +$ cd _build/ -```bash -# build the image... -$ docker build . -t symfony-docs +$ composer install -# ...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 +$ php build.php +``` + +After generating docs, serve them with the internal PHP server: + +```bash +$ php -S localhost:8000 -t output/ ``` -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`). +Browse `http://localhost:8000` to read the docs. diff --git a/_build/.requirements.txt b/_build/.requirements.txt deleted file mode 100644 index 47f076e9403..00000000000 --- a/_build/.requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -docutils==0.13.1 -Pygments==2.2.0 -sphinx==1.8.5 -git+https://github.com/fabpot/sphinx-php.git@v2.0.0#egg_name=sphinx-php -jsx-lexer===0.0.8 -sphinx_rtd_theme==0.5.0 diff --git a/_build/Makefile b/_build/Makefile deleted file mode 100644 index 25b660056fe..00000000000 --- a/_build/Makefile +++ /dev/null @@ -1,153 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = . - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -c $(BUILDDIR) -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) ../ -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Symfony.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Symfony.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/Symfony" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Symfony" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." diff --git a/_build/_exts/symfonycom/__init__.py b/_build/_exts/symfonycom/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/_build/_exts/symfonycom/sphinx/__init__.py b/_build/_exts/symfonycom/sphinx/__init__.py deleted file mode 100644 index 4a61e711809..00000000000 --- a/_build/_exts/symfonycom/sphinx/__init__.py +++ /dev/null @@ -1,86 +0,0 @@ -from pygments.style import Style -from pygments.token import Keyword, Name, Comment, String, Error, \ - Number, Operator, Generic, Whitespace, Punctuation, Other, Literal - -class SensioStyle(Style): - background_color = "#000000" - default_style = "" - - styles = { - # No corresponding class for the following: - #Text: "", # class: '' - Whitespace: "underline #f8f8f8", # class: 'w' - Error: "#a40000 border:#ef2929", # class: 'err' - Other: "#ffffff", # class 'x' - - Comment: "italic #B729D9", # class: 'c' - Comment.Single: "italic #B729D9", # class: 'c1' - Comment.Multiline: "italic #B729D9", # class: 'cm' - Comment.Preproc: "noitalic #aaa", # class: 'cp' - - Keyword: "#FF8400", # class: 'k' - Keyword.Constant: "#FF8400", # class: 'kc' - Keyword.Declaration: "#FF8400", # class: 'kd' - Keyword.Namespace: "#FF8400", # class: 'kn' - Keyword.Pseudo: "#FF8400", # class: 'kp' - Keyword.Reserved: "#FF8400", # class: 'kr' - Keyword.Type: "#FF8400", # class: 'kt' - - Operator: "#E0882F", # class: 'o' - Operator.Word: "#E0882F", # class: 'ow' - like keywords - - Punctuation: "#999999", # class: 'p' - - # because special names such as Name.Class, Name.Function, etc. - # are not recognized as such later in the parsing, we choose them - # to look the same as ordinary variables. - Name: "#ffffff", # class: 'n' - Name.Attribute: "#ffffff", # class: 'na' - to be revised - Name.Builtin: "#ffffff", # class: 'nb' - Name.Builtin.Pseudo: "#3465a4", # class: 'bp' - Name.Class: "#ffffff", # class: 'nc' - to be revised - Name.Constant: "#ffffff", # class: 'no' - to be revised - Name.Decorator: "#888", # class: 'nd' - to be revised - Name.Entity: "#ce5c00", # class: 'ni' - Name.Exception: "#cc0000", # class: 'ne' - Name.Function: "#ffffff", # class: 'nf' - Name.Property: "#ffffff", # class: 'py' - Name.Label: "#f57900", # class: 'nl' - Name.Namespace: "#ffffff", # class: 'nn' - to be revised - Name.Other: "#ffffff", # class: 'nx' - Name.Tag: "#cccccc", # class: 'nt' - like a keyword - Name.Variable: "#ffffff", # class: 'nv' - to be revised - Name.Variable.Class: "#ffffff", # class: 'vc' - to be revised - Name.Variable.Global: "#ffffff", # class: 'vg' - to be revised - Name.Variable.Instance: "#ffffff", # class: 'vi' - to be revised - - Number: "#1299DA", # class: 'm' - - Literal: "#ffffff", # class: 'l' - Literal.Date: "#ffffff", # class: 'ld' - - String: "#56DB3A", # class: 's' - String.Backtick: "#56DB3A", # class: 'sb' - String.Char: "#56DB3A", # class: 'sc' - String.Doc: "italic #B729D9", # class: 'sd' - like a comment - String.Double: "#56DB3A", # class: 's2' - String.Escape: "#56DB3A", # class: 'se' - String.Heredoc: "#56DB3A", # class: 'sh' - String.Interpol: "#56DB3A", # class: 'si' - String.Other: "#56DB3A", # class: 'sx' - String.Regex: "#56DB3A", # class: 'sr' - String.Single: "#56DB3A", # class: 's1' - String.Symbol: "#56DB3A", # class: 'ss' - - Generic: "#ffffff", # class: 'g' - Generic.Deleted: "#a40000", # class: 'gd' - Generic.Emph: "italic #ffffff", # class: 'ge' - Generic.Error: "#ef2929", # class: 'gr' - Generic.Heading: "#000080", # class: 'gh' - Generic.Inserted: "#00A000", # class: 'gi' - Generic.Output: "#888", # class: 'go' - Generic.Prompt: "#745334", # class: 'gp' - Generic.Strong: "bold #ffffff", # class: 'gs' - Generic.Subheading: "bold #800080", # class: 'gu' - Generic.Traceback: "bold #a40000", # class: 'gt' - } diff --git a/_build/_exts/symfonycom/sphinx/lexer.py b/_build/_exts/symfonycom/sphinx/lexer.py deleted file mode 100644 index f1e87066236..00000000000 --- a/_build/_exts/symfonycom/sphinx/lexer.py +++ /dev/null @@ -1,23 +0,0 @@ -from pygments.lexer import RegexLexer, bygroups, using -from pygments.token import * -from pygments.lexers.shell import BashLexer, BatchLexer - -class TerminalLexer(RegexLexer): - name = 'Terminal' - aliases = ['terminal'] - filenames = [] - - tokens = { - 'root': [ - ('^\$', Generic.Prompt, 'bash-prompt'), - ('^>', Generic.Prompt, 'dos-prompt'), - ('^#.+$', Comment.Single), - ('^.+$', Generic.Output), - ], - 'bash-prompt': [ - ('(.+)$', bygroups(using(BashLexer)), '#pop') - ], - 'dos-prompt': [ - ('(.+)$', bygroups(using(BatchLexer)), '#pop') - ], - } diff --git a/_build/_static/rtd_custom.css b/_build/_static/rtd_custom.css deleted file mode 100644 index 01298437755..00000000000 --- a/_build/_static/rtd_custom.css +++ /dev/null @@ -1,23 +0,0 @@ -body { - font-family:Lucida Grande,Lucida Sans Unicode,Lucida Sans,Geneva,Verdana,sans-serif !important; -} - -h1, h2, h3, h4, h5, h6 { - font-family:Georgia,Times New Roman,Times,serif !important; - line-height:1.2 !important; - margin-top:0 !important; - margin-bottom:.5em !important; -} -p, .rst-content li{ - font-size:14px !important; - line-height:1.45 !important; -} -.wy-menu-vertical a { - font-size:14px !important; - padding-right:0 !important; -} - -.highlight { - background:#1e2125 !important; - color:#fafafa !important; -} diff --git a/_build/_static/symfony-logo.svg b/_build/_static/symfony-logo.svg deleted file mode 100644 index 828c2b297b0..00000000000 --- a/_build/_static/symfony-logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/_build/build.php b/_build/build.php new file mode 100755 index 00000000000..66470a0df59 --- /dev/null +++ b/_build/build.php @@ -0,0 +1,68 @@ +#!/usr/bin/env php +register('build-docs') + ->addOption('generate-fjson-files', null, InputOption::VALUE_NONE, 'Use this option to generate docs both in HTML and JSON formats') + ->addOption('disable-cache', null, InputOption::VALUE_NONE, 'Use this option to force a full regeneration of all doc contents') + ->setCode(function(InputInterface $input, OutputInterface $output) { + $io = new SymfonyStyle($input, $output); + $io->text('Building all Symfony Docs...'); + + $outputDir = __DIR__.'/output'; + $buildConfig = (new BuildConfig()) + ->setSymfonyVersion('4.4') + ->setContentDir(__DIR__.'/..') + ->setOutputDir($outputDir) + ->setImagesDir(__DIR__.'/output/_images') + ->setImagesPublicPrefix('_images') + ->setTheme('rtd') + ; + + $buildConfig->setExcludedPaths(['.github/', '_build/']); + + if (!$generateJsonFiles = $input->getOption('generate-fjson-files')) { + $buildConfig->disableJsonFileGeneration(); + } + + if ($isCacheDisabled = $input->getOption('disable-cache')) { + $buildConfig->disableBuildCache(); + } + + $io->comment(sprintf('cache: %s / output file type(s): %s', $isCacheDisabled ? 'disabled' : 'enabled', $generateJsonFiles ? 'HTML and JSON' : 'HTML')); + if (!$isCacheDisabled) { + $io->comment('Tip: add the --disable-cache option to this command to force the re-build of all docs.'); + } + + $result = (new DocBuilder())->build($buildConfig); + + if ($result->isSuccessful()) { + // fix assets URLs to make them absolute (otherwise, they don't work in subdirectories) + foreach (glob($outputDir.'/**/*.html') as $htmlFilePath) { + $htmlContents = file_get_contents($htmlFilePath); + file_put_contents($htmlFilePath, str_replace('href="assets/', 'href="/assets/', $htmlContents)); + } + + $io->success(sprintf("The Symfony Docs were successfully built at %s", realpath($outputDir))); + } else { + $io->error(sprintf("There were some errors while building the docs:\n\n%s\n", $result->getErrorTrace())); + $io->newLine(); + $io->comment('Tip: you can add the -v, -vv or -vvv flags to this command to get debug information.'); + + return 1; + } + + return 0; + }) + ->getApplication() + ->setDefaultCommand('build-docs', true) + ->run(); diff --git a/_build/composer.json b/_build/composer.json new file mode 100644 index 00000000000..fd7ec177c15 --- /dev/null +++ b/_build/composer.json @@ -0,0 +1,19 @@ +{ + "minimum-stability": "dev", + "prefer-stable": true, + "config": { + "platform": { + "php": "7.4.14" + }, + "preferred-install": { + "*": "dist" + }, + "sort-packages": true + }, + "require": { + "php": ">=7.4", + "symfony/console": "^5.4", + "symfony/process": "^5.4", + "symfony-tools/docs-builder": "^0.18" + } +} diff --git a/_build/composer.lock b/_build/composer.lock new file mode 100644 index 00000000000..503bfab012b --- /dev/null +++ b/_build/composer.lock @@ -0,0 +1,1896 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "4cd8dc9a70f9ccfb279a426fffbcf2bc", + "packages": [ + { + "name": "doctrine/event-manager", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/event-manager.git", + "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/41370af6a30faa9dc0368c4a6814d596e81aba7f", + "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": "<2.9@dev" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/event-manager.html", + "keywords": [ + "event", + "event dispatcher", + "event manager", + "event system", + "events" + ], + "support": { + "issues": "https://github.com/doctrine/event-manager/issues", + "source": "https://github.com/doctrine/event-manager/tree/1.1.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", + "type": "tidelift" + } + ], + "time": "2020-05-29T18:28:51+00:00" + }, + { + "name": "doctrine/rst-parser", + "version": "0.5.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/rst-parser.git", + "reference": "3b914d5eb8f6a91afc7462ea7794b0e05b884a35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/rst-parser/zipball/3b914d5eb8f6a91afc7462ea7794b0e05b884a35", + "reference": "3b914d5eb8f6a91afc7462ea7794b0e05b884a35", + "shasum": "" + }, + "require": { + "doctrine/event-manager": "^1.0", + "php": "^7.2 || ^8.0", + "symfony/filesystem": "^4.1 || ^5.0 || ^6.0", + "symfony/finder": "^4.1 || ^5.0 || ^6.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/string": "^5.3 || ^6.0", + "symfony/translation-contracts": "^1.1 || ^2.0", + "twig/twig": "^2.9 || ^3.3" + }, + "require-dev": { + "doctrine/coding-standard": "^8.0", + "gajus/dindent": "^2.0.2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-deprecation-rules": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^7.5 || ^8.0 || ^9.0", + "symfony/css-selector": "4.4 || ^5.2 || ^6.0", + "symfony/dom-crawler": "4.4 || ^5.2 || ^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\RST\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Passault", + "email": "g.passault@gmail.com", + "homepage": "http://www.gregwar.com/" + }, + { + "name": "Jonathan H. Wage", + "email": "jonwage@gmail.com", + "homepage": "https://jwage.com" + } + ], + "description": "PHP library to parse reStructuredText documents and generate HTML or LaTeX documents.", + "homepage": "https://github.com/doctrine/rst-parser", + "keywords": [ + "html", + "latex", + "markup", + "parser", + "reStructuredText", + "rst" + ], + "support": { + "issues": "https://github.com/doctrine/rst-parser/issues", + "source": "https://github.com/doctrine/rst-parser/tree/0.5.2" + }, + "time": "2022-03-22T13:52:20+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "scrivo/highlight.php", + "version": "v9.18.1.9", + "source": { + "type": "git", + "url": "https://github.com/scrivo/highlight.php.git", + "reference": "d45585504777e6194a91dffc7270ca39833787f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/scrivo/highlight.php/zipball/d45585504777e6194a91dffc7270ca39833787f8", + "reference": "d45585504777e6194a91dffc7270ca39833787f8", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=5.4" + }, + "require-dev": { + "phpunit/phpunit": "^4.8|^5.7", + "sabberworm/php-css-parser": "^8.3", + "symfony/finder": "^2.8|^3.4", + "symfony/var-dumper": "^2.8|^3.4" + }, + "suggest": { + "ext-mbstring": "Allows highlighting code with unicode characters and supports language with unicode keywords" + }, + "type": "library", + "autoload": { + "files": [ + "HighlightUtilities/functions.php" + ], + "psr-0": { + "Highlight\\": "", + "HighlightUtilities\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Geert Bergman", + "homepage": "http://www.scrivo.org/", + "role": "Project Author" + }, + { + "name": "Vladimir Jimenez", + "homepage": "https://allejo.io", + "role": "Maintainer" + }, + { + "name": "Martin Folkers", + "homepage": "https://twobrain.io", + "role": "Contributor" + } + ], + "description": "Server side syntax highlighter that supports 185 languages. It's a PHP port of highlight.js", + "keywords": [ + "code", + "highlight", + "highlight.js", + "highlight.php", + "syntax" + ], + "support": { + "issues": "https://github.com/scrivo/highlight.php/issues", + "source": "https://github.com/scrivo/highlight.php" + }, + "funding": [ + { + "url": "https://github.com/allejo", + "type": "github" + } + ], + "time": "2021-12-03T06:45:28+00:00" + }, + { + "name": "symfony-tools/docs-builder", + "version": "v0.18.9", + "source": { + "type": "git", + "url": "https://github.com/symfony-tools/docs-builder.git", + "reference": "1bc91f91887b115d78e7d2c8879c19af515b36ae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony-tools/docs-builder/zipball/1bc91f91887b115d78e7d2c8879c19af515b36ae", + "reference": "1bc91f91887b115d78e7d2c8879c19af515b36ae", + "shasum": "" + }, + "require": { + "doctrine/rst-parser": "^0.5", + "ext-curl": "*", + "ext-json": "*", + "php": ">=7.4", + "scrivo/highlight.php": "^9.12.0", + "symfony/console": "^5.2 || ^6.0", + "symfony/css-selector": "^5.2 || ^6.0", + "symfony/dom-crawler": "^5.2 || ^6.0", + "symfony/filesystem": "^5.2 || ^6.0", + "symfony/finder": "^5.2 || ^6.0", + "symfony/http-client": "^5.2 || ^6.0", + "twig/twig": "^2.14 || ^3.3" + }, + "require-dev": { + "gajus/dindent": "^2.0", + "symfony/phpunit-bridge": "^5.2 || ^6.0", + "symfony/process": "^5.2 || ^6.0" + }, + "bin": [ + "bin/docs-builder" + ], + "type": "project", + "autoload": { + "psr-4": { + "SymfonyDocsBuilder\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The build system for Symfony's documentation", + "support": { + "issues": "https://github.com/symfony-tools/docs-builder/issues", + "source": "https://github.com/symfony-tools/docs-builder/tree/v0.18.9" + }, + "time": "2022-03-22T14:32:49+00:00" + }, + { + "name": "symfony/console", + "version": "v5.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "ffe3aed36c4d60da2cf1b0a1cee6b8f2e5fa881b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/ffe3aed36c4d60da2cf1b0a1cee6b8f2e5fa881b", + "reference": "ffe3aed36c4d60da2cf1b0a1cee6b8f2e5fa881b", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.1|^6.0" + }, + "conflict": { + "psr/log": ">=3", + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0" + }, + "require-dev": { + "psr/log": "^1|^2", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.4.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-12T16:02:29+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v5.4.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "b0a190285cd95cb019237851205b8140ef6e368e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/b0a190285cd95cb019237851205b8140ef6e368e", + "reference": "b0a190285cd95cb019237851205b8140ef6e368e", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Converts CSS selectors to XPath expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/css-selector/tree/v5.4.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:53:40+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:53:40+00:00" + }, + { + "name": "symfony/dom-crawler", + "version": "v5.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "c0bda97480d96337bd3866026159a8b358665457" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/c0bda97480d96337bd3866026159a8b358665457", + "reference": "c0bda97480d96337bd3866026159a8b358665457", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "masterminds/html5": "<2.6" + }, + "require-dev": { + "masterminds/html5": "^2.6", + "symfony/css-selector": "^4.4|^5.0|^6.0" + }, + "suggest": { + "symfony/css-selector": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases DOM navigation for HTML and XML documents", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dom-crawler/tree/v5.4.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-02T12:42:23+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v5.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "3a4442138d80c9f7b600fb297534ac718b61d37f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/3a4442138d80c9f7b600fb297534ac718b61d37f", + "reference": "3a4442138d80c9f7b600fb297534ac718b61d37f", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v5.4.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-01T12:33:59+00:00" + }, + { + "name": "symfony/finder", + "version": "v5.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "9b630f3427f3ebe7cd346c277a1408b00249dad9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/9b630f3427f3ebe7cd346c277a1408b00249dad9", + "reference": "9b630f3427f3ebe7cd346c277a1408b00249dad9", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v5.4.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-15T08:07:45+00:00" + }, + { + "name": "symfony/http-client", + "version": "v5.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "0dabec4e3898d3e00451dd47b5ef839168f9bbf5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/0dabec4e3898d3e00451dd47b5ef839168f9bbf5", + "reference": "0dabec4e3898d3e00451dd47b5ef839168f9bbf5", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/http-client-contracts": "^2.4", + "symfony/polyfill-php73": "^1.11", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.0|^2|^3" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "2.4" + }, + "require-dev": { + "amphp/amp": "^2.5", + "amphp/http-client": "^4.2.1", + "amphp/http-tunnel": "^1.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^4.4.13|^5.1.5|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/stopwatch": "^4.4|^5.0|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-client/tree/v5.4.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-12T16:02:29+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v2.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "1a4f708e4e87f335d1b1be6148060739152f0bd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/1a4f708e4e87f335d1b1be6148060739152f0bd5", + "reference": "1a4f708e4e87f335d1b1be6148060739152f0bd5", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "suggest": { + "symfony/http-client-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v2.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-13T20:07:29+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "30885182c981ab175d4d034db0f6f469898070ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", + "reference": "30885182c981ab175d4d034db0f6f469898070ab", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-10-20T20:35:02+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "81b86b50cf841a64252b439e738e97f4a34e2783" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/81b86b50cf841a64252b439e738e97f4a34e2783", + "reference": "81b86b50cf841a64252b439e738e97f4a34e2783", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-23T21:10:46+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-19T12:13:01+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/cc5db0e22b3cb4111010e48785a97f670b350ca5", + "reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-06-05T21:20:04+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-04T08:16:47+00:00" + }, + { + "name": "symfony/process", + "version": "v5.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "597f3fff8e3e91836bb0bd38f5718b56ddbde2f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/597f3fff8e3e91836bb0bd38f5718b56ddbde2f3", + "reference": "597f3fff8e3e91836bb0bd38f5718b56ddbde2f3", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v5.4.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-08T05:07:18+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "24d9dc654b83e91aa59f9d167b131bc3b5bea24c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/24d9dc654b83e91aa59f9d167b131bc3b5bea24c", + "reference": "24d9dc654b83e91aa59f9d167b131bc3b5bea24c", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-03-13T20:07:29+00:00" + }, + { + "name": "symfony/string", + "version": "v5.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "3c061a76bff6d6ea427d85e12ad1bb8ed8cd43e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/3c061a76bff6d6ea427d85e12ad1bb8ed8cd43e8", + "reference": "3c061a76bff6d6ea427d85e12ad1bb8ed8cd43e8", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "conflict": { + "symfony/translation-contracts": ">=3.0" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.4.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-19T10:40:37+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v2.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "1211df0afa701e45a04253110e959d4af4ef0f07" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/1211df0afa701e45a04253110e959d4af4ef0f07", + "reference": "1211df0afa701e45a04253110e959d4af4ef0f07", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "suggest": { + "symfony/translation-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v2.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:53:40+00:00" + }, + { + "name": "twig/twig", + "version": "v3.3.10", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "8442df056c51b706793adf80a9fd363406dd3674" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/8442df056c51b706793adf80a9fd363406dd3674", + "reference": "8442df056c51b706793adf80a9fd363406dd3674", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3" + }, + "require-dev": { + "psr/container": "^1.0", + "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.3.10" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2022-04-06T06:47:41+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": [], + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": ">=7.4" + }, + "platform-dev": [], + "platform-overrides": { + "php": "7.4.14" + }, + "plugin-api-version": "2.2.0" +} diff --git a/_build/conf.py b/_build/conf.py deleted file mode 100644 index 49cc12581ad..00000000000 --- a/_build/conf.py +++ /dev/null @@ -1,301 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Symfony documentation build configuration file, created by -# sphinx-quickstart on Sat Jul 28 21:58:57 2012. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.append(os.path.abspath('_exts')) - -# adding PhpLexer -from sphinx.highlighting import lexers -from pygments.lexers.compiled import CLexer -from pygments.lexers.shell import BashLexer -from pygments.lexers.special import TextLexer -from pygments.lexers.text import RstLexer -from pygments.lexers.web import PhpLexer -from symfonycom.sphinx.lexer import TerminalLexer - -# -- General configuration ----------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -needs_sphinx = '1.8.5' - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [ - 'sphinx.ext.autodoc', 'sphinx.ext.doctest', - 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.ifconfig', - 'sphinx.ext.viewcode', 'sphinx.ext.extlinks', - 'sensio.sphinx.codeblock', 'sensio.sphinx.configurationblock', 'sensio.sphinx.phpcode', 'sensio.sphinx.bestpractice' - #,'sphinxcontrib.spelling' -] - -#spelling_show_sugestions=True -#spelling_lang='en_US' -#spelling_word_list_filename='_build/spelling_word_list.txt' - -# Add any paths that contain templates here, relative to this directory. -# templates_path = ['_theme/_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = 'Symfony Framework Documentation' -copyright = '' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -# version = '2.2' -# The full version, including alpha/beta/rc tags. -# release = '2.2.13' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'symfonycom.sphinx.SensioStyle' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# -- Settings for symfony doc extension --------------------------------------------------- - -# enable highlighting for PHP code not between ```` by default -lexers['markdown'] = TextLexer() -lexers['php'] = PhpLexer(startinline=True) -lexers['php-annotations'] = PhpLexer(startinline=True) -lexers['php-standalone'] = PhpLexer(startinline=True) -lexers['php-symfony'] = PhpLexer(startinline=True) -lexers['rst'] = RstLexer() -lexers['varnish2'] = CLexer() -lexers['varnish3'] = CLexer() -lexers['varnish4'] = CLexer() -lexers['terminal'] = TerminalLexer() -lexers['env'] = BashLexer() - -config_block = { - 'apache': 'Apache', - 'markdown': 'Markdown', - 'nginx': 'Nginx', - 'rst': 'reStructuredText', - 'varnish2': 'Varnish 2', - 'varnish3': 'Varnish 3', - 'varnish4': 'Varnish 4', - 'env': '.env' -} - -# don't enable Sphinx Domains -primary_domain = None - -# set url for API links -api_url = 'https://github.com/symfony/symfony/blob/master/src/%s.php' - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = "sphinx_rtd_theme" - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -html_theme_options = { - 'logo_only': True, - 'prev_next_buttons_location': None, - 'style_nav_header_background': '#f0f0f0' -} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -html_logo = '_static/symfony-logo.svg' - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] -html_css_files = ['rtd_custom.css'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'SymfonyDoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'Symfony.tex', u'Symfony Documentation', - u'Symfony community', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'symfony', u'Symfony Documentation', - [u'Symfony community'], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------------ - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'Symfony', u'Symfony Documentation', - u'Symfony community', 'Symfony', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# Use PHP syntax highlighting in code examples by default -highlight_language='php' diff --git a/_build/maintainer_guide.rst b/_build/maintainer_guide.rst index 7eff3143941..d7eefad8edc 100644 --- a/_build/maintainer_guide.rst +++ b/_build/maintainer_guide.rst @@ -39,14 +39,14 @@ contributes again, it's OK to mention some of the minor issues to educate them. $ gh merge 11059 - Working on symfony/symfony-docs (branch master) + Working on symfony/symfony-docs (branch 4.4) Merging Pull Request 11059: dmaicher/patch-3 ... # This is important!! Say NO to push the changes now Push the changes now? (Y/n) n - Now, push with: git push gh "master" refs/notes/github-comments + Now, push with: git push gh "4.4" refs/notes/github-comments # Now, open your editor and make the needed changes ... @@ -54,7 +54,7 @@ contributes again, it's OK to mention some of the minor issues to educate them. # Use "Minor reword", "Minor tweak", etc. as the commit message # now run the 'push' command shown above by 'gh' (it's different each time) - $ git push gh "master" refs/notes/github-comments + $ git push gh "4.4" refs/notes/github-comments Merging Pull Requests --------------------- @@ -335,6 +335,43 @@ in the tree as follows: $ git push origin $ git push upstream +Merging in the wrong branch +........................... + +A Pull Request was made against ``5.x`` but it should be merged in ``5.1`` and you +forgot to merge as ``gh merge NNNNN -s 5.1`` to change the merge branch. Solution: + +.. code-block:: terminal + + $ git checkout 5.1 + $ git cherry-pick -m 1 + $ git checkout 5.x + $ git revert -m 1 + # now continue with the normal "upmerging" + $ git checkout 5.2 + $ git merge 5.1 + $ ... + +Merging while the target branch changed +....................................... + +Sometimes, someone else merges a PR in ``5.x`` at the same time as you are +doing it. In these cases, ``gh merge ...`` fails to push. Solve this by +resetting your local branch and restarting the merge: + +.. code-block:: terminal + + $ gh merge ... + # this failed + + # fetch the updated 5.x branch from GitHub + $ git fetch upstream + $ git checkout 5.x + $ git reset --hard upstream/5.x + + # restart the merge + $ gh merge ... + .. _`symfony/symfony-docs`: https://github.com/symfony/symfony-docs .. _`Symfony Docs team`: https://github.com/orgs/symfony/teams/team-symfony-docs .. _`Symfony's respectful review comments`: https://symfony.com/doc/current/contributing/community/review-comments.html diff --git a/_build/make.bat b/_build/make.bat deleted file mode 100644 index 6d3f205272f..00000000000 --- a/_build/make.bat +++ /dev/null @@ -1,263 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=. -set ALLSPHINXOPTS=-c %BUILDDIR% -d %BUILDDIR%/doctrees %SPHINXOPTS% .. -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - echo. coverage to run coverage check of the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -REM Check if sphinx-build is available and fallback to Python version if any -%SPHINXBUILD% 2> nul -if errorlevel 9009 goto sphinx_python -goto sphinx_ok - -:sphinx_python - -set SPHINXBUILD=python -m sphinx.__init__ -%SPHINXBUILD% 2> nul -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -:sphinx_ok - - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Symfony.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Symfony.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "coverage" ( - %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage - if errorlevel 1 exit /b 1 - echo. - echo.Testing of coverage in the sources finished, look at the ^ -results in %BUILDDIR%/coverage/python.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) - -:end diff --git a/_build/redirection_map b/_build/redirection_map index 75b0c28da2b..f0b726e546f 100644 --- a/_build/redirection_map +++ b/_build/redirection_map @@ -132,11 +132,6 @@ /cookbook/controller/upload_file /controller/upload_file /cookbook/debugging / /debug/debugging / -/cookbook/deployment/azure-website /cookbook/azure-website -/cookbook/deployment/fortrabbit /deployment/fortrabbit -/cookbook/deployment/heroku /deployment/heroku -/cookbook/deployment/index /deployment -/cookbook/deployment/platformsh /deployment/platformsh /cookbook/deployment/tools /deployment/tools /cookbook/doctrine/common_extensions /doctrine/common_extensions /cookbook/doctrine/console /doctrine @@ -448,16 +443,17 @@ /reference/requirements /setup /bundles/inheritance /bundles/override /templating /templates -/templating/escaping /templates -/templating/syntax /templates -/templating/debug /templates -/templating/render_without_controller /templates -/templating/app_variable /templates +/templating/escaping /templates#output-escaping +/templating/syntax /templates#linting-twig-templates +/templating/debug /templates#the-dump-twig-utilities +/templating/render_without_controller /templates#rendering-a-template-directly-from-a-route +/templating/app_variable /templates#the-app-global-variable /templating/formats /templates -/templating/namespaced_paths /templates -/templating/embedding_controllers /templates -/templating/inheritance /templates +/templating/namespaced_paths /templates#template-namespaces +/templating/embedding_controllers /templates#embedding-controllers +/templating/inheritance /templates#template-inheritance-and-layouts /testing/doctrine /testing/database +/translation/templates /translation#translation-in-templates /doctrine/lifecycle_callbacks /doctrine/events /doctrine/event_listeners_subscribers /doctrine/events /doctrine/common_extensions /doctrine @@ -506,4 +502,10 @@ /frontend/encore/versus-assetic /frontend /components/http_client /http_client /components/mailer /mailer -/messenger/message-recorder messenger/dispatch_after_current_bus +/messenger/message-recorder /messenger/dispatch_after_current_bus +/components/stopwatch https://github.com/symfony/stopwatch +/service_container/3.3-di-changes https://symfony.com/doc/3.4/service_container/3.3-di-changes.html +/testing/functional_tests_assertions /testing#testing-application-assertions +/components https://symfony.com/components +/components/index https://symfony.com/components +/serializer/normalizers /components/serializer#normalizers diff --git a/_build/spelling_word_list.txt b/_build/spelling_word_list.txt index 3b1d630fa11..70240ceb6d1 100644 --- a/_build/spelling_word_list.txt +++ b/_build/spelling_word_list.txt @@ -113,7 +113,6 @@ filesystem filesystems formatter formatters -fortrabbit frontend getter getters diff --git a/_images/components/form/general_flow.png b/_images/components/form/general_flow.png deleted file mode 100644 index 31650e52af6..00000000000 Binary files a/_images/components/form/general_flow.png and /dev/null differ diff --git a/_images/components/form/set_data_flow.png b/_images/components/form/set_data_flow.png deleted file mode 100644 index 3cd4b1e2f7b..00000000000 Binary files a/_images/components/form/set_data_flow.png and /dev/null differ diff --git a/_images/components/form/submission_flow.png b/_images/components/form/submission_flow.png deleted file mode 100644 index a3c6e9cfb90..00000000000 Binary files a/_images/components/form/submission_flow.png and /dev/null differ diff --git a/_images/components/workflow/states_transitions.png b/_images/components/workflow/states_transitions.png index 1e68f9ca597..d1f54391afd 100644 Binary files a/_images/components/workflow/states_transitions.png and b/_images/components/workflow/states_transitions.png differ diff --git a/_images/contributing/docs-pull-request-symfonycloud.png b/_images/contributing/docs-pull-request-symfonycloud.png deleted file mode 100644 index 0c485c1491c..00000000000 Binary files a/_images/contributing/docs-pull-request-symfonycloud.png and /dev/null differ diff --git a/_images/docs-pull-request-change-base.png b/_images/docs-pull-request-change-base.png deleted file mode 100644 index d824e8ef1bc..00000000000 Binary files a/_images/docs-pull-request-change-base.png and /dev/null differ diff --git a/_images/form/form-custom-type-postal-address-fragment-names.svg b/_images/form/form-custom-type-postal-address-fragment-names.svg index 9b6092c9808..db9463b8327 100644 --- a/_images/form/form-custom-type-postal-address-fragment-names.svg +++ b/_images/form/form-custom-type-postal-address-fragment-names.svg @@ -1 +1 @@ - + diff --git a/_images/form/form-custom-type-postal-address.svg b/_images/form/form-custom-type-postal-address.svg index ab0fde8af3a..42ffce4067f 100644 --- a/_images/form/form-custom-type-postal-address.svg +++ b/_images/form/form-custom-type-postal-address.svg @@ -1 +1 @@ - + diff --git a/_images/form/form_prepopulation_workflow.svg b/_images/form/form_prepopulation_workflow.svg new file mode 100644 index 00000000000..1db13f94c72 --- /dev/null +++ b/_images/form/form_prepopulation_workflow.svg @@ -0,0 +1,54 @@ + + + + + + New form + + + + + + Prepopulated form + + + + + + + + + + Model data + + + + + + POST_SET_DATA + + + + + + PRE_SET_DATA + + + + + + setData($data) + + + + + + + + + + normalization + + + + diff --git a/_images/form/form_submission_workflow.svg b/_images/form/form_submission_workflow.svg new file mode 100644 index 00000000000..b58e11190a1 --- /dev/null +++ b/_images/form/form_submission_workflow.svg @@ -0,0 +1,76 @@ + + + + + + denormalization + + + + + + normalization + + + + + + New form + + + + + + Prepopulated form + + + + + + Submitted form + + + + + + + + + + + + + + Request data + + + + + + handleRequest($request) + + + + + + + + + + PRE_SUBMIT + + + + + + SUBMIT + + + + + + POST_SUBMIT + + + + diff --git a/_images/form/form_workflow.svg b/_images/form/form_workflow.svg new file mode 100644 index 00000000000..a256c2073ef --- /dev/null +++ b/_images/form/form_workflow.svg @@ -0,0 +1,66 @@ + + + + + + New form + + + + + + Prepopulated form + + + + + + Submitted form + + + + + + + + + + + + + + + + + + Model data + + + + + + Request data + + + + + + setData($data) + + + + + + handleRequest($request) + + + + + + + + + + + + diff --git a/_images/sources/README.md b/_images/sources/README.md index 9e40e0ac884..8ca7538bf5d 100644 --- a/_images/sources/README.md +++ b/_images/sources/README.md @@ -27,6 +27,12 @@ Saving and Exporting the Diagram * Save the original diagram in `*.dia` format in `_images/sources/`; * Export the diagram to SVG format and save it in `_images/`. +Important: choose "Cairo Scalable Vector Graphics (.svg)" format instead of +plain " Scalable Vector Graphics (.svg)" because the former is the only format +that transforms text into vector shapes (resulting file is larger in size, but +it's truly portable because text is displayed the same even if you don't have +some fonts installed). + Including the Diagram in the Symfony Docs ----------------------------------------- diff --git a/_images/sources/form/form-custom-type-postal-address-fragment-names.dia b/_images/sources/form/form-custom-type-postal-address-fragment-names.dia index aebdadb4170..ca12fcdeadc 100644 Binary files a/_images/sources/form/form-custom-type-postal-address-fragment-names.dia and b/_images/sources/form/form-custom-type-postal-address-fragment-names.dia differ diff --git a/_images/sources/form/form-custom-type-postal-address.dia b/_images/sources/form/form-custom-type-postal-address.dia index 35a1eaebfd6..1b7c6226315 100644 Binary files a/_images/sources/form/form-custom-type-postal-address.dia and b/_images/sources/form/form-custom-type-postal-address.dia differ diff --git a/_images/sources/form/form_events.dia b/_images/sources/form/form_events.dia new file mode 100644 index 00000000000..8e7afb1cb83 Binary files /dev/null and b/_images/sources/form/form_events.dia differ diff --git a/_includes/service_container/_my_mailer.rst.inc b/_includes/service_container/_my_mailer.rst.inc deleted file mode 100644 index 01eafdfe87a..00000000000 --- a/_includes/service_container/_my_mailer.rst.inc +++ /dev/null @@ -1,33 +0,0 @@ -.. configuration-block:: - - .. code-block:: yaml - - # config/services.yaml - services: - app.mailer: - class: App\Mailer - arguments: [sendmail] - - .. code-block:: xml - - - - - - - - sendmail - - - - - .. code-block:: php - - // config/services.php - use App\Mailer; - - $container->register('app.mailer', Mailer::class) - ->addArgument('sendmail'); diff --git a/best_practices.rst b/best_practices.rst index 02434a7c812..865f7549fa3 100644 --- a/best_practices.rst +++ b/best_practices.rst @@ -30,7 +30,7 @@ to create new Symfony applications: .. code-block:: terminal - $ symfony new my_project_name + $ symfony new my_project_directory Under the hood, this Symfony binary command executes the needed `Composer`_ command to :ref:`create a new Symfony application ` @@ -88,8 +88,10 @@ application behavior. :ref:`Use env vars in your project ` to define these options and create multiple ``.env`` files to :ref:`configure env vars per environment `. -Use Secret for Sensitive Information -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. _use-secret-for-sensitive-information: + +Use Secrets for Sensitive Information +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When your application has sensitive configuration - like an API key - you should store those securely via :doc:`Symfony’s secrets management system `. @@ -130,7 +132,7 @@ Use Constants to Define Options that Rarely Change ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Configuration options like the number of items to display in some listing rarely -change. Instead of defining them as :ref:`service container parameters `, +change. Instead of defining them as :ref:`configuration parameters `, define them as PHP constants in the related classes. Example:: // src/Entity/Post.php @@ -170,7 +172,7 @@ Use Autowiring to Automate the Configuration of Application Services :doc:`Service autowiring ` is a feature that reads the type-hints on your constructor (or other methods) and automatically -passes the correct services to each method, making unnecessary to configure +passes the correct services to each method, making it unnecessary to configure services explicitly and simplifying the application maintenance. Use it in combination with :ref:`service autoconfiguration ` @@ -265,7 +267,7 @@ Templates Use Snake Case for Template Names and Variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Use lowercased snake_case for template names, directories and variables (e.g. +Use lowercase snake_case for template names, directories and variables (e.g. ``user_profile`` instead of ``userProfile`` and ``product/edit_form.html.twig`` instead of ``Product/EditForm.html.twig``). @@ -320,6 +322,8 @@ are two of the main tasks when handling forms. Both are too similar (most of the times, almost identical), so it's much simpler to let a single controller action handle both. +.. _best-practice-internationalization: + Internationalization -------------------- @@ -381,7 +385,7 @@ Use Webpack Encore to Process Web Assets ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Web assets are things like CSS, JavaScript and image files that make the -frontend of your site look and work great. `Webpack`_ is the leading JavaScript +frontend of your site looks and works great. `Webpack`_ is the leading JavaScript module bundler that compiles, transforms and packages assets for usage in a browser. :doc:`Webpack Encore ` is a JavaScript library that gets rid of most @@ -397,8 +401,8 @@ Smoke Test your URLs In software engineering, `smoke testing`_ consists of *"preliminary testing to reveal simple failures severe enough to reject a prospective software release"*. -Using :ref:`PHPUnit data providers ` you can define a -functional test that checks that all application URLs load successfully:: +Using `PHPUnit data providers`_ you can define a functional test that +checks that all application URLs load successfully:: // tests/ApplicationAvailabilityFunctionalTest.php namespace App\Tests; @@ -433,7 +437,9 @@ Add this test while creating your application because it requires little effort and checks that none of your pages returns an error. Later, you'll add more specific tests for each page. -Hardcode URLs in a Functional Test +.. _hardcode-urls-in-a-functional-test: + +Hard-code URLs in a Functional Test ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In Symfony applications, it's recommended to :ref:`generate URLs ` @@ -452,3 +458,4 @@ you must set up a redirection. .. _`feature toggles`: https://en.wikipedia.org/wiki/Feature_toggle .. _`smoke testing`: https://en.wikipedia.org/wiki/Smoke_testing_(software) .. _`Webpack`: https://webpack.js.org/ +.. _`PHPUnit data providers`: https://phpunit.readthedocs.io/en/9.5/writing-tests-for-phpunit.html#data-providers diff --git a/bundles/best_practices.rst b/bundles/best_practices.rst index e80050e2fce..addc59014ba 100644 --- a/bundles/best_practices.rst +++ b/bundles/best_practices.rst @@ -63,35 +63,52 @@ configuration options (see below for some usage examples). Directory Structure ------------------- -The basic directory structure of an AcmeBlogBundle must read as follows: +The following is the recommended directory structure of an AcmeBlogBundle: .. code-block:: text / - ├─ AcmeBlogBundle.php - ├─ Controller/ - ├─ README.md - ├─ LICENSE - ├─ Resources/ - │ ├─ config/ - │ ├─ doc/ - │ │ └─ index.rst - │ ├─ translations/ - │ ├─ views/ - │ └─ public/ - └─ Tests/ + ├── config/ + ├── docs/ + │ └─ index.md + ├── public/ + ├── src/ + │ ├── Controller/ + │ ├── DependencyInjection/ + │ └── AcmeBlogBundle.php + ├── templates/ + ├── tests/ + ├── translations/ + ├── LICENSE + └── README.md + +.. versionadded:: 4.4 + + This directory convention was introduced in Symfony 4.4 and can be used only + when requiring ``symfony/http-kernel`` 4.4 or superior. + +This directory structure requires to configure the bundle path to its root +directory as follows:: + + class AcmeBlogBundle extends Bundle + { + public function getPath(): string + { + return \dirname(__DIR__); + } + } **The following files are mandatory**, because they ensure a structure convention that automated tools can rely on: -* ``AcmeBlogBundle.php``: This is the class that transforms a plain directory +* ``src/AcmeBlogBundle.php``: This is the class that transforms a plain directory into a Symfony bundle (change this to your bundle's name); * ``README.md``: This file contains the basic description of the bundle and it usually shows some basic examples and links to its full documentation (it can use any of the markup formats supported by GitHub, such as ``README.rst``); * ``LICENSE``: The full contents of the license used by the code. Most third-party bundles are published under the MIT license, but you can `choose any license`_; -* ``Resources/doc/index.rst``: The root file for the Bundle documentation. +* ``docs/index.md``: The root file for the Bundle documentation. The depth of subdirectories should be kept to a minimum for the most used classes and files. Two levels is the maximum. @@ -107,19 +124,19 @@ and others are just conventions followed by most developers): =================================================== ======================================== Type Directory =================================================== ======================================== -Commands ``Command/`` -Controllers ``Controller/`` -Service Container Extensions ``DependencyInjection/`` -Doctrine ORM entities (when not using annotations) ``Entity/`` -Doctrine ODM documents (when not using annotations) ``Document/`` -Event Listeners ``EventListener/`` -Configuration (routes, services, etc.) ``Resources/config/`` -Web Assets (CSS, JS, images) ``Resources/public/`` -Translation files ``Resources/translations/`` -Validation (when not using annotations) ``Resources/config/validation/`` -Serialization (when not using annotations) ``Resources/config/serialization/`` -Templates ``Resources/views/`` -Unit and Functional Tests ``Tests/`` +Commands ``src/Command/`` +Controllers ``src/Controller/`` +Service Container Extensions ``src/DependencyInjection/`` +Doctrine ORM entities (when not using annotations) ``src/Entity/`` +Doctrine ODM documents (when not using annotations) ``src/Document/`` +Event Listeners ``src/EventListener/`` +Configuration (routes, services, etc.) ``config/`` +Web Assets (CSS, JS, images) ``public/`` +Translation files ``translations/`` +Validation (when not using annotations) ``config/validation/`` +Serialization (when not using annotations) ``config/serialization/`` +Templates ``templates/`` +Unit and Functional Tests ``tests/`` =================================================== ======================================== Classes @@ -127,7 +144,7 @@ Classes The bundle directory structure is used as the namespace hierarchy. For instance, a ``ContentController`` controller which is stored in -``Acme/BlogBundle/Controller/ContentController.php`` would have the fully +``src/Controller/ContentController.php`` would have the fully qualified class name of ``Acme\BlogBundle\Controller\ContentController``. All classes and files must follow the :doc:`Symfony coding standards `. @@ -153,7 +170,7 @@ Tests ----- A bundle should come with a test suite written with PHPUnit and stored under -the ``Tests/`` directory. Tests should follow the following principles: +the ``tests/`` directory. Tests should follow the following principles: * The test suite must be executable with a simple ``phpunit`` command run from a sample application; @@ -171,73 +188,61 @@ Continuous Integration Testing bundle code continuously, including all its commits and pull requests, is a good practice called Continuous Integration. There are several services -providing this feature for free for open source projects. The most popular -service for Symfony bundles is called `Travis CI`_. - -Here is the recommended configuration file (``.travis.yml``) for Symfony bundles, -which test the two latest :doc:`LTS versions ` -of Symfony and the latest beta release: - -.. code-block:: yaml - - language: php - - cache: - directories: - - $HOME/.composer/cache/files - - $HOME/symfony-bridge/.phpunit - - env: - global: - - PHPUNIT_FLAGS="-v" - - SYMFONY_PHPUNIT_DIR="$HOME/symfony-bridge/.phpunit" - - matrix: - fast_finish: true - include: - # Minimum supported dependencies with the latest and oldest PHP version - - php: 7.2 - env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" SYMFONY_DEPRECATIONS_HELPER="max[self]=0" - - php: 7.1 - env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" SYMFONY_DEPRECATIONS_HELPER="max[self]=0" - - # Test the latest stable release - - php: 7.1 - - php: 7.2 - env: COVERAGE=true PHPUNIT_FLAGS="-v --coverage-text" - - # Test LTS versions. This makes sure we do not use Symfony packages with version greater - # than 2 or 3 respectively. Read more at https://github.com/symfony/lts - - php: 7.2 - env: DEPENDENCIES="symfony/lts:^2" - - php: 7.2 - env: DEPENDENCIES="symfony/lts:^3" - - # Latest commit to master - - php: 7.2 - env: STABILITY="dev" - - allow_failures: - # Dev-master is allowed to fail. - - env: STABILITY="dev" - - before_install: - - if [[ $COVERAGE != true ]]; then phpenv config-rm xdebug.ini || true; fi - - if ! [ -z "$STABILITY" ]; then composer config minimum-stability ${STABILITY}; fi; - - if ! [ -v "$DEPENDENCIES" ]; then composer require --no-update ${DEPENDENCIES}; fi; - - install: - - composer update ${COMPOSER_FLAGS} --prefer-dist --no-interaction - - ./vendor/bin/simple-phpunit install - - script: - - composer validate --strict --no-check-lock - # simple-phpunit is the PHPUnit wrapper provided by the PHPUnit Bridge component and - # it helps with testing legacy code and deprecations (composer require symfony/phpunit-bridge) - - ./vendor/bin/simple-phpunit $PHPUNIT_FLAGS - -Consider using the `Travis cron`_ tool to make sure your project is built even if -there are no new pull requests or commits. +providing this feature for free for open source projects, like `GitHub Actions`_ +and `Travis CI`_. + +A bundle should at least test: + +* The lower bound of their dependencies (by running ``composer update --prefer-lowest``); +* The supported PHP versions; +* All supported major Symfony versions (e.g. both ``4.x`` and ``5.x`` if + support is claimed for both). + +Thus, a bundle supporting PHP 7.3, 7.4 and 8.0, and Symfony 3.4 and 4.x should +have at least this test matrix: + +=========== =============== =================== +PHP version Symfony version Composer flags +=========== =============== =================== +7.3 ``3.*`` ``--prefer-lowest`` +7.4 ``4.*`` +8.0 ``4.*`` +=========== =============== =================== + +.. tip:: + + The tests should be run with the ``SYMFONY_DEPRECATIONS_HELPER`` + env variable set to ``max[direct]=0``. This ensures no code in the + bundle uses deprecated features directly. + + The lowest dependency tests can be run with this variable set to + ``disabled=1``. + +Require a Specific Symfony Version +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can use the special ``SYMFONY_REQUIRE`` environment variable together +with Symfony Flex to install a specific Symfony version: + +.. code-block:: bash + + # this requires Symfony 5.x for all Symfony packages + export SYMFONY_REQUIRE=5.* + # alternatively you can run this command to update composer.json config + # composer config extra.symfony.require "5.*" + + # install Symfony Flex in the CI environment + composer global require --no-progress --no-scripts --no-plugins symfony/flex + + # install the dependencies (using --prefer-dist and --no-progress is + # recommended to have a better output and faster download time) + composer update --prefer-dist --no-progress + +.. caution:: + + If you want to cache your Composer dependencies, **do not** cache the + ``vendor/`` directory as this has side-effects. Instead cache + ``$HOME/.composer/cache/files``. Installation ------------ @@ -254,10 +259,10 @@ Documentation All classes and functions must come with full PHPDoc. -Extensive documentation should also be provided in the ``Resources/doc/`` +Extensive documentation should also be provided in the ``docs/`` directory. -The index file (for example ``Resources/doc/index.rst`` or -``Resources/doc/index.md``) is the only mandatory file and must be the entry +The index file (for example ``docs/index.rst`` or +``docs/index.md``) is the only mandatory file and must be the entry point for the documentation. The :doc:`reStructuredText (rST) ` is the format used to render the documentation on the Symfony website. @@ -418,8 +423,8 @@ The end user can provide values in any configuration file: - + https://symfony.com/schema/dic/services/services-1.0.xsd" + > fabien@example.com @@ -429,7 +434,13 @@ The end user can provide values in any configuration file: .. code-block:: php // config/services.php - $container->setParameter('acme_blog.author.email', 'fabien@example.com'); + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + return static function (ContainerConfigurator $container) { + $container->parameters() + ->set('acme_blog.author.email', 'fabien@example.com') + ; + }; Retrieve the configuration parameters in your code from the container:: @@ -477,7 +488,7 @@ The ``composer.json`` file should include at least the following metadata: Consists of the vendor and the short bundle name. If you are releasing the bundle on your own instead of on behalf of a company, use your personal name (e.g. ``johnsmith/blog-bundle``). Exclude the vendor name from the bundle - short name and separate each word with an hyphen. For example: AcmeBlogBundle + short name and separate each word with a hyphen. For example: AcmeBlogBundle is transformed into ``blog-bundle`` and AcmeSocialConnectBundle is transformed into ``social-connect-bundle``. @@ -494,10 +505,22 @@ The ``composer.json`` file should include at least the following metadata: This information is used by Symfony to load the classes of the bundle. It's recommended to use the `PSR-4`_ autoload standard: use the namespace as key, and the location of the bundle's main class (relative to ``composer.json``) - as value. For example, if the main class is located in the bundle root - directory: ``"autoload": { "psr-4": { "SomeVendor\\BlogBundle\\": "" } }``. - If the main class is located in the ``src/`` directory of the bundle: - ``"autoload": { "psr-4": { "SomeVendor\\BlogBundle\\": "src/" } }``. + as value. As the main class is located in the ``src/`` directory of the bundle: + + .. code-block:: json + + { + "autoload": { + "psr-4": { + "Acme\\BlogBundle\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Acme\\BlogBundle\\Tests\\": "tests/" + } + } + } In order to make it easier for developers to find your bundle, register it on `Packagist`_, the official repository for Composer packages. @@ -507,15 +530,15 @@ Resources If the bundle references any resources (config files, translation files, etc.), don't use physical paths (e.g. ``__DIR__/config/services.xml``) but logical -paths (e.g. ``@FooBundle/Resources/config/services.xml``). +paths (e.g. ``@AcmeBlogBundle/config/services.xml``). The logical paths are required because of the bundle overriding mechanism that lets you override any resource/file of any bundle. See :ref:`http-kernel-resource-locator` for more details about transforming physical paths into logical paths. Beware that templates use a simplified version of the logical path shown above. -For example, an ``index.html.twig`` template located in the ``Resources/views/Default/`` -directory of the FooBundle, is referenced as ``@Foo/Default/index.html.twig``. +For example, an ``index.html.twig`` template located in the ``templates/Default/`` +directory of the AcmeBlogBundle, is referenced as ``@AcmeBlog/Default/index.html.twig``. Learn more ---------- @@ -529,5 +552,5 @@ Learn more .. _`Packagist`: https://packagist.org/ .. _`choose any license`: https://choosealicense.com/ .. _`valid license identifier`: https://spdx.org/licenses/ -.. _`Travis CI`: https://travis-ci.org/ -.. _`Travis cron`: https://docs.travis-ci.com/user/cron-jobs/ +.. _`GitHub Actions`: https://docs.github.com/en/free-pro-team@latest/actions +.. _`Travis CI`: https://docs.travis-ci.com/ diff --git a/bundles/configuration.rst b/bundles/configuration.rst index 25f8d76e76f..41c34ee7bbc 100644 --- a/bundles/configuration.rst +++ b/bundles/configuration.rst @@ -20,19 +20,22 @@ as integration of other related components: .. code-block:: yaml + # config/packages/framework.yaml framework: form: true .. code-block:: xml + - + https://symfony.com/schema/dic/symfony/symfony-1.0.xsd" + > @@ -40,9 +43,14 @@ as integration of other related components: .. code-block:: php - $container->loadFromExtension('framework', [ - 'form' => true, - ]); + // config/packages/framework.php + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + return static function (ContainerConfigurator $container) { + $container->extension('framework', [ + 'form' => true, + ]); + }; Using the Bundle Extension -------------------------- @@ -64,29 +72,33 @@ can add some configuration that looks like this: .. code-block:: xml - + - + https://symfony.com/schema/dic/services/services-1.0.xsd" + > - + - - .. code-block:: php // config/packages/acme_social.php - $container->loadFromExtension('acme_social', [ - 'twitter' => [ - 'client_id' => 123, - 'client_secret' => 'your_secret', - ], - ]); + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + return static function (ContainerConfigurator $container) { + $container->extension('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, @@ -242,8 +254,8 @@ For example, imagine your bundle has the following example config: - + https://symfony.com/schema/dic/services/services-1.0.xsd" + > @@ -332,9 +344,9 @@ bundle in the console using the Yaml format. As long as your bundle's configuration is located in the standard location (``YourBundle\DependencyInjection\Configuration``) and does not have -a constructor it will work automatically. If you +a constructor, it will work automatically. If you have something different, your ``Extension`` class must override the -:method:`Extension::getConfiguration() ` +:method:`Extension::getConfiguration() ` method and return an instance of your ``Configuration``. Supporting XML @@ -416,15 +428,15 @@ Assuming the XSD file is called ``hello-1.0.xsd``, the schema location will be .. code-block:: xml - + - + https://acme_company.com/schema/dic/hello/hello-1.0.xsd" + > diff --git a/bundles/index.rst b/bundles/index.rst index e4af2cd357b..58bcd13761e 100644 --- a/bundles/index.rst +++ b/bundles/index.rst @@ -1,5 +1,3 @@ -:orphan: - Bundles ======= diff --git a/bundles/override.rst b/bundles/override.rst index bf53eb5ce3c..6cf3d37c386 100644 --- a/bundles/override.rst +++ b/bundles/override.rst @@ -139,8 +139,8 @@ to a new validation group: - + https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd" + > diff --git a/bundles/prepend_extension.rst b/bundles/prepend_extension.rst index 2b6f9dbfe3f..fe551f31083 100644 --- a/bundles/prepend_extension.rst +++ b/bundles/prepend_extension.rst @@ -8,7 +8,7 @@ How to Simplify Configuration of Multiple Bundles When building reusable and extensible applications, developers are often faced with a choice: either create a single large bundle or multiple smaller bundles. Creating a single bundle has the drawback that it's impossible for -users to choose to remove functionality they are not using. Creating multiple +users to remove unused functionality. Creating multiple bundles has the drawback that configuration becomes more tedious and settings often need to be repeated for various bundles. @@ -80,17 +80,18 @@ in case a specific other bundle is not registered:: } } - // process the configuration of AcmeHelloExtension + // get the configuration of AcmeHelloExtension (it's a list of configuration) $configs = $container->getExtensionConfig($this->getAlias()); - // use the Configuration class to generate a config array with - // the settings "acme_hello" - $config = $this->processConfiguration(new Configuration(), $configs); - - // check if entity_manager_name is set in the "acme_hello" configuration - if (isset($config['entity_manager_name'])) { - // prepend the acme_something settings with the entity_manager_name - $config = ['entity_manager_name' => $config['entity_manager_name']]; - $container->prependExtensionConfig('acme_something', $config); + + // iterate in reverse to preserve the original order after prepending the config + foreach (array_reverse($configs) as $config) { + // check if entity_manager_name is set in the "acme_hello" configuration + if (isset($config['entity_manager_name'])) { + // prepend the acme_something settings with the entity_manager_name + $container->prependExtensionConfig('acme_something', [ + 'entity_manager_name' => $config['entity_manager_name'], + ]); + } } } @@ -126,29 +127,35 @@ registered and the ``entity_manager_name`` setting for ``acme_hello`` is set to 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"> - + https://example.org/schema/dic/acme_something/acme_other-1.0.xsd" + > non_default - + + + .. code-block:: php // config/packages/acme_something.php - $container->loadFromExtension('acme_something', [ - // ... - 'use_acme_goodbye' => false, - 'entity_manager_name' => 'non_default', - ]); - $container->loadFromExtension('acme_other', [ - // ... - 'use_acme_goodbye' => false, - ]); + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + return static function (ContainerConfigurator $container) { + $container->extension('acme_something', [ + // ... + 'use_acme_goodbye' => false, + 'entity_manager_name' => 'non_default', + ]); + $container->extension('acme_other', [ + // ... + 'use_acme_goodbye' => false, + ]); + }; More than one Bundle using PrependExtensionInterface ---------------------------------------------------- diff --git a/cache.rst b/cache.rst index a06cd0bd4c3..d7990c0d88b 100644 --- a/cache.rst +++ b/cache.rst @@ -49,7 +49,7 @@ of: An adapter is a *template* that you use to create pools. **Provider** A provider is a service that some adapters use to connect to the storage. - Redis and Memcached are example of such adapters. If a DSN is used as the + Redis and Memcached are examples 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 @@ -77,10 +77,11 @@ adapter (template) they use by using the ``app`` and ``system`` key like: xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony - https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> - + https://symfony.com/schema/dic/symfony/symfony-1.0.xsd" + > - @@ -89,12 +90,21 @@ adapter (template) they use by using the ``app`` and ``system`` key like: .. code-block:: php // config/packages/cache.php - $container->loadFromExtension('framework', [ - 'cache' => [ - 'app' => 'cache.adapter.filesystem', - 'system' => 'cache.adapter.system', - ], - ]); + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + return static function (ContainerConfigurator $container) { + $container->extension('framework', [ + 'cache' => [ + 'app' => 'cache.adapter.filesystem', + 'system' => 'cache.adapter.system', + ], + ]); + }; + +.. tip:: + + While it is possible to reconfigure the ``system`` cache, it's recommended + to keep the default configuration applied to it by Symfony. The Cache component comes with a series of adapters pre-configured: @@ -140,8 +150,8 @@ will create pools with service IDs that follow the pattern ``cache.[type]``. xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony - https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> - + https://symfony.com/schema/dic/symfony/symfony-1.0.xsd" + > + + + + + + + + + + + + .. code-block:: php + + // config/services.php + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + return function(ContainerConfigurator $container) { + $container->services() + // ... + + ->set('app.cache.adapter.redis') + ->parent('cache.adapter.redis') + ->tag('cache.pool', ['namespace' => 'my_custom_namespace']) + ; + }; + Custom Provider Options ----------------------- @@ -368,11 +439,14 @@ and use that when configuring the pool. xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony - https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> - + https://symfony.com/schema/dic/symfony/symfony-1.0.xsd" + > - + @@ -391,27 +465,34 @@ and use that when configuring the pool. .. code-block:: php // config/packages/cache.php + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + use Symfony\Component\Cache\Adapter\RedisAdapter; - $container->loadFromExtension('framework', [ - 'cache' => [ - 'pools' => [ - 'cache.my_redis' => [ - 'adapter' => 'cache.adapter.redis', - 'provider' => 'app.my_custom_redis_provider', + return static function (ContainerConfigurator $container) { + $container->extension('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 - ]) - ; + ]); + + $container->services() + ->set('app.my_custom_redis_provider', \Redis::class) + ->factory([RedisAdapter::class, 'createConnection']) + ->args([ + 'redis://localhost', + [ + 'retry_interval' => 2, + 'timeout' => 10, + ] + ]) + ; + }; Creating a Cache Chain ---------------------- @@ -459,11 +540,14 @@ Symfony stores the item automatically in all the missing pools. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:framework="http://symfony.com/schema/dic/symfony" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> - + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/symfony + https://symfony.com/schema/dic/symfony/symfony-1.0.xsd" + > - + @@ -475,20 +559,24 @@ Symfony stores the item automatically in all the missing pools. .. code-block:: php // config/packages/cache.php - $container->loadFromExtension('framework', [ - 'cache' => [ - 'pools' => [ - 'my_cache_pool' => [ - 'default_lifetime' => 31536000, // One year - 'adapters' => [ - 'cache.adapter.array', - 'cache.adapter.apcu', - ['name' => 'cache.adapter.redis', 'provider' => 'redis://user:password@example.com'], + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + return static function (ContainerConfigurator $container) { + $container->extension('framework', [ + 'cache' => [ + 'pools' => [ + 'my_cache_pool' => [ + 'default_lifetime' => 31536000, // One year + 'adapters' => [ + 'cache.adapter.array', + 'cache.adapter.apcu', + ['name' => 'cache.adapter.redis', 'provider' => 'redis://user:password@example.com'], + ], ], ], ], - ], - ]); + ]); + }; Using Cache Tags ---------------- @@ -555,11 +643,14 @@ to enable this feature. This could be added by using the following configuration xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony - https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> - + https://symfony.com/schema/dic/symfony/symfony-1.0.xsd" + > - + @@ -567,16 +658,20 @@ to enable this feature. This could be added by using the following configuration .. code-block:: php // config/packages/cache.php - $container->loadFromExtension('framework', [ - 'cache' => [ - 'pools' => [ - 'my_cache_pool' => [ - 'adapter' => 'cache.adapter.redis', - 'tags' => true, + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + return static function (ContainerConfigurator $container) { + $container->extension('framework', [ + 'cache' => [ + 'pools' => [ + 'my_cache_pool' => [ + 'adapter' => 'cache.adapter.redis', + 'tags' => true, + ], ], ], - ], - ]); + ]); + }; Tags are stored in the same pool by default. This is good in most scenarios. But sometimes it might be better to store the tags in a different pool. That could be @@ -604,12 +699,17 @@ achieved by specifying the adapter. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:framework="http://symfony.com/schema/dic/symfony" xsi:schemaLocation="http://symfony.com/schema/dic/services - https://symfony.com/schema/dic/services/services-1.0.xsd"> - + https://symfony.com/schema/dic/services/services-1.0.xsd + http://symfony.com/schema/dic/symfony + https://symfony.com/schema/dic/symfony/symfony-1.0.xsd" + > - - + + @@ -617,19 +717,23 @@ achieved by specifying the adapter. .. code-block:: php // config/packages/cache.php - $container->loadFromExtension('framework', [ - 'cache' => [ - 'pools' => [ - 'my_cache_pool' => [ - 'adapter' => 'cache.adapter.redis', - 'tags' => 'tag_pool', - ], - 'tag_pool' => [ - 'adapter' => 'cache.adapter.apcu', + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + return static function (ContainerConfigurator $container) { + $container->extension('framework', [ + 'cache' => [ + 'pools' => [ + 'my_cache_pool' => [ + 'adapter' => 'cache.adapter.redis', + 'tags' => 'tag_pool', + ], + 'tag_pool' => [ + 'adapter' => 'cache.adapter.apcu', + ], ], ], - ], - ]); + ]); + }; .. note:: diff --git a/components/asset.rst b/components/asset.rst index 6aab732333f..5be1003ef15 100644 --- a/components/asset.rst +++ b/components/asset.rst @@ -8,7 +8,7 @@ The Asset Component The Asset component manages URL generation and versioning of web assets such as CSS stylesheets, JavaScript files and image files. -In the past, it was common for web applications to hardcode URLs of web assets. +In the past, it was common for web applications to hard-code the URLs of web assets. For example: .. code-block:: html @@ -357,7 +357,7 @@ they all have different base paths:: $packages = new Packages($defaultPackage, $namedPackages); The ``Packages`` class allows to define a default package, which will be applied -to assets that don't define the name of package to use. In addition, this +to assets that don't define the name of the package to use. In addition, this application defines a package named ``img`` to serve images from an external domain and a ``doc`` package to avoid repeating long paths when linking to a document inside a template:: diff --git a/components/browser_kit.rst b/components/browser_kit.rst index 76c0e33d5e1..9a618d8bad2 100644 --- a/components/browser_kit.rst +++ b/components/browser_kit.rst @@ -317,10 +317,28 @@ dedicated web crawler or scraper such as `Goutte`_:: '.table-list-header-toggle a:nth-child(1)' )->text()); +.. tip:: + + You can also use HTTP client options like ``ciphers``, ``auth_basic`` and + ``query``. They have to be passed as the default options argument to the + client which is used by the HTTP browser. + .. versionadded:: 4.3 The feature to make external HTTP requests was introduced in Symfony 4.3. +Dealing with HTTP responses +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When using the BrowserKit component, you may need to deal with responses of +the requests you made. To do so, call the ``getResponse()`` method of the +``HttpBrowser`` object. This method returns the last response the browser received:: + + $browser = new HttpBrowser(HttpClient::create()); + + $browser->request('GET', 'https://foo.com'); + $response = $browser->getResponse(); + Learn more ---------- diff --git a/components/cache.rst b/components/cache.rst index a620206682f..9a1b6611f8d 100644 --- a/components/cache.rst +++ b/components/cache.rst @@ -90,7 +90,10 @@ generate and return the value:: Use cache tags to delete more than one key at the time. Read more at :doc:`/components/cache/cache_invalidation`. -The Cache Contracts also comes with built in `Stampede prevention`_. This will +Stampede Prevention +~~~~~~~~~~~~~~~~~~~ + +The Cache Contracts also come with built in `Stampede prevention`_. This will remove CPU spikes at the moments when the cache is cold. If an example application spends 5 seconds to compute data that is cached for 1 hour and this data is accessed 10 times every second, this means that you mostly have cache hits and everything diff --git a/components/cache/adapters/chain_adapter.rst b/components/cache/adapters/chain_adapter.rst index acb4cccaa43..b0dd5d029ee 100644 --- a/components/cache/adapters/chain_adapter.rst +++ b/components/cache/adapters/chain_adapter.rst @@ -12,7 +12,7 @@ This adapter allows combining any number of the other fetched from the first adapter containing them and cache items are saved to all the given adapters. This exposes a simple and efficient method for creating a layered cache. -The ChainAdapter must be provided an array of adapters and optionally a maximum cache +The ChainAdapter must be provided an array of adapters and optionally a default cache lifetime as its constructor arguments:: use Symfony\Component\Cache\Adapter\ChainAdapter; @@ -21,8 +21,8 @@ lifetime as its constructor arguments:: // The ordered list of adapters used to fetch cached items array $adapters, - // The max lifetime of items propagated from lower adapters to upper ones - $maxLifetime = 0 + // The default lifetime of items propagated from lower adapters to upper ones + $defaultLifetime = 0 ); .. note:: diff --git a/components/cache/adapters/filesystem_adapter.rst b/components/cache/adapters/filesystem_adapter.rst index 33097fbd202..2a168d2522e 100644 --- a/components/cache/adapters/filesystem_adapter.rst +++ b/components/cache/adapters/filesystem_adapter.rst @@ -45,15 +45,29 @@ and cache root path as constructor parameters:: 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. + (: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. + This adapter implements :class:`Symfony\\Component\\Cache\\PruneableInterface`, + enabling manual :ref:`pruning of expired cache items ` + by calling its ``prune()`` method. + +.. _filesystem-tag-aware-adapter: + +Working with Tags +----------------- + +In order to use tag-based invalidation, you can wrap your adapter in +:class:`Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter`, but it's often +more interesting to use the dedicated :class:`Symfony\\Component\\Cache\\Adapter\\FilesystemTagAwareAdapter`. +Since tag invalidation logic is implemented using links on filesystem, this +adapter offers better read performance when using tag-based invalidation:: + + use Symfony\Component\Cache\Adapter\FilesystemTagAwareAdapter; + + $cache = new FilesystemTagAwareAdapter(); + .. _`tmpfs`: https://wiki.archlinux.org/index.php/tmpfs .. _`RAM disk solutions`: https://en.wikipedia.org/wiki/List_of_RAM_drive_software diff --git a/components/cache/adapters/memcached_adapter.rst b/components/cache/adapters/memcached_adapter.rst index 9cd8c8cdd25..1b8103433e1 100644 --- a/components/cache/adapters/memcached_adapter.rst +++ b/components/cache/adapters/memcached_adapter.rst @@ -124,7 +124,6 @@ option names and their respective values:: // associative array of configuration options [ - 'compression' => true, 'libketama_compatible' => true, 'serializer' => 'igbinary', ] @@ -143,17 +142,6 @@ Available Options server(s). Any action that retrieves data, quits the connection, or closes down the connection will cause the buffer to be committed. -``compression`` (type: ``bool``, default: ``true``) - Enables or disables payload compression, where item values longer than 100 - bytes are compressed during storage and decompressed during retrieval. - -``compression_type`` (type: ``string``) - Specifies the compression method used on value payloads. when the - **compression** option is enabled. - - Valid option values include ``fastlz`` and ``zlib``, with a default value - that *varies based on flags used at compilation*. - ``connect_timeout`` (type: ``int``, default: ``1000``) Specifies the timeout (in milliseconds) of socket connection operations when the ``no_block`` option is enabled. diff --git a/components/cache/adapters/pdo_doctrine_dbal_adapter.rst b/components/cache/adapters/pdo_doctrine_dbal_adapter.rst index 841071dc586..b840da76de7 100644 --- a/components/cache/adapters/pdo_doctrine_dbal_adapter.rst +++ b/components/cache/adapters/pdo_doctrine_dbal_adapter.rst @@ -44,7 +44,7 @@ your code. .. note:: - Since Symfony 3.4, this adapter implements :class:`Symfony\\Component\\Cache\\PruneableInterface`, + This adapter implements :class:`Symfony\\Component\\Cache\\PruneableInterface`, allowing for manual :ref:`pruning of expired cache entries ` by calling its ``prune()`` method. diff --git a/components/cache/adapters/php_array_cache_adapter.rst b/components/cache/adapters/php_array_cache_adapter.rst index 631c153f5cb..52259b87f86 100644 --- a/components/cache/adapters/php_array_cache_adapter.rst +++ b/components/cache/adapters/php_array_cache_adapter.rst @@ -7,7 +7,7 @@ PHP Array Cache Adapter This adapter is a high performance cache for static data (e.g. application configuration) that is optimized and preloaded into OPcache memory storage. It is suited for any data that -is mostly read-only after warmup:: +is mostly read-only after warm-up:: use Symfony\Component\Cache\Adapter\FilesystemAdapter; use Symfony\Component\Cache\Adapter\PhpArrayAdapter; @@ -23,7 +23,7 @@ is mostly read-only after warmup:: $cache = new PhpArrayAdapter( // single file where values are cached __DIR__ . '/somefile.cache', - // a backup adapter, if you set values after warmup + // a backup adapter, if you set values after warm-up new FilesystemAdapter() ); $cache->warmUp($values); diff --git a/components/cache/adapters/php_files_adapter.rst b/components/cache/adapters/php_files_adapter.rst index 5bec5715801..fcb5bcfffd1 100644 --- a/components/cache/adapters/php_files_adapter.rst +++ b/components/cache/adapters/php_files_adapter.rst @@ -63,7 +63,7 @@ directory path as constructor arguments:: .. note:: - Since Symfony 3.4, this adapter implements :class:`Symfony\\Component\\Cache\\PruneableInterface`, + This adapter implements :class:`Symfony\\Component\\Cache\\PruneableInterface`, allowing for manual :ref:`pruning of expired cache entries ` by calling its ``prune()`` method. diff --git a/components/cache/adapters/redis_adapter.rst b/components/cache/adapters/redis_adapter.rst index c97ab697831..842e717c874 100644 --- a/components/cache/adapters/redis_adapter.rst +++ b/components/cache/adapters/redis_adapter.rst @@ -62,15 +62,22 @@ helper method allows creating and configuring the Redis client class instance us ); The DSN can specify either an IP/host (and an optional port) or a socket path, as well as a -password and a database index. +password and a database index. To enable TLS for connections, the scheme ``redis`` must be +replaced by ``rediss`` (the second ``s`` means "secure"). .. note:: - A `Data Source Name (DSN)`_ for this adapter must use the following format. + A `Data Source Name (DSN)`_ for this adapter must use either one of the following formats. .. code-block:: text - redis://[pass@][ip|host|socket[:port]][/db-index] + redis[s]://[pass@][ip|host|socket[:port]][/db-index] + + .. code-block:: text + + redis[s]:[[user]:pass@]?[ip|host|socket[:port]][¶ms] + + Values for placeholders ``[user]``, ``[:port]``, ``[/db-index]`` and ``[¶ms]`` are optional. Below are common examples of valid DSNs showing a combination of available values:: @@ -88,20 +95,35 @@ Below are common examples of valid DSNs showing a combination of available value // socket "/var/run/redis.sock" and auth "bad-pass" RedisAdapter::createConnection('redis://bad-pass@/var/run/redis.sock'); - // a single DSN can define multiple servers using the following syntax: - // host[hostname-or-IP:port] (where port is optional). Sockets must include a trailing ':' + // host "redis1" (docker container) with alternate DSN syntax and selecting database index "3" + RedisAdapter::createConnection('redis:?host[redis1:6379]&dbindex=3'); + + // providing credentials with alternate DSN syntax + RedisAdapter::createConnection('redis:default:verysecurepassword@?host[redis1:6379]&dbindex=3'); + + // a single DSN can also define multiple servers RedisAdapter::createConnection( 'redis:?host[localhost]&host[localhost:6379]&host[/var/run/redis.sock:]&auth=my-password&redis_cluster=1' ); `Redis Sentinel`_, which provides high availability for Redis, is also supported -when using the Predis library. Use the ``redis_sentinel`` parameter to set the -name of your service group:: +when using the PHP Redis Extension v5.2+ or the Predis library. Use the ``redis_sentinel`` +parameter to set the name of your service group:: RedisAdapter::createConnection( 'redis:?host[redis1:26379]&host[redis2:26379]&host[redis3:26379]&redis_sentinel=mymaster' ); + // providing credentials + RedisAdapter::createConnection( + 'redis:default:verysecurepassword@?host[redis1:26379]&host[redis2:26379]&host[redis3:26379]&redis_sentinel=mymaster' + ); + + // providing credentials and selecting database index "3" + RedisAdapter::createConnection( + 'redis:default:verysecurepassword@?host[redis1:26379]&host[redis2:26379]&host[redis3:26379]&redis_sentinel=mymaster&dbindex=3' + ); + .. versionadded:: 4.2 The option to define multiple servers in a single DSN was introduced in Symfony 4.2. @@ -131,14 +153,19 @@ array of ``key => value`` pairs representing option names and their respective v // associative array of configuration options [ - 'compression' => true, - 'lazy' => false, + 'class' => null, 'persistent' => 0, 'persistent_id' => null, - 'tcp_keepalive' => 0, 'timeout' => 30, 'read_timeout' => 0, 'retry_interval' => 0, + 'tcp_keepalive' => 0, + 'lazy' => null, + 'redis_cluster' => false, + 'redis_sentinel' => null, + 'dbindex' => 0, + 'failover' => 'none', + 'ssl' => null, ] ); @@ -146,19 +173,11 @@ array of ``key => value`` pairs representing option names and their respective v Available Options ~~~~~~~~~~~~~~~~~ -``class`` (type: ``string``) +``class`` (type: ``string``, default: ``null``) Specifies the connection library to return, either ``\Redis`` or ``\Predis\Client``. If none is specified, it will return ``\Redis`` if the ``redis`` extension is - available, and ``\Predis\Client`` otherwise. - -``compression`` (type: ``bool``, default: ``true``) - Enables or disables compression of items. This requires phpredis v4 or higher with - LZF support enabled. - -``lazy`` (type: ``bool``, default: ``false``) - Enables or disables lazy connections to the backend. It's ``false`` by - default when using this as a stand-alone component and ``true`` by default - when using it inside a Symfony application. + available, and ``\Predis\Client`` otherwise. Explicitly set this to ``\Predis\Client`` for Sentinel if you are + running into issues when retrieving master information. ``persistent`` (type: ``int``, default: ``0``) Enables or disables use of persistent connections. A value of ``0`` disables persistent @@ -167,6 +186,10 @@ Available Options ``persistent_id`` (type: ``string|null``, default: ``null``) Specifies the persistent id string to use for a persistent connection. +``timeout`` (type: ``int``, default: ``30``) + Specifies the time (in seconds) used to connect to a Redis server before the + connection attempt times out. + ``read_timeout`` (type: ``int``, default: ``0``) Specifies the time (in seconds) used when performing read operations on the underlying network resource before the operation times out. @@ -179,9 +202,28 @@ Available Options Specifies the `TCP-keepalive`_ timeout (in seconds) of the connection. This requires phpredis v4 or higher and a TCP-keepalive enabled server. -``timeout`` (type: ``int``, default: ``30``) - Specifies the time (in seconds) used to connect to a Redis server before the - connection attempt times out. +``lazy`` (type: ``bool``, default: ``null``) + Enables or disables lazy connections to the backend. It's ``false`` by + default when using this as a stand-alone component and ``true`` by default + when using it inside a Symfony application. + +``redis_cluster`` (type: ``bool``, default: ``false``) + Enables or disables redis cluster. The actual value passed is irrelevant as long as it passes loose comparison + checks: `redis_cluster=1` will suffice. + +``redis_sentinel`` (type: ``string``, default: ``null``) + Specifies the master name connected to the sentinels. + +``dbindex`` (type: ``int``, default: ``0``) + Specifies the database index to select. + +``failover`` (type: ``string``, default: ``none``) + Specifies failover for cluster implementations. For ``\RedisCluster`` valid options are ``none`` (default), + ``error``, ``distribute`` or ``slaves``. For ``\Predis\ClientInterface`` valid options are ``slaves`` + or ``distribute``. + +``ssl`` (type: ``bool``, default: ``null``) + SSL context options. See `php.net/context.ssl`_ for more information. .. note:: @@ -201,6 +243,23 @@ In order to use tag-based invalidation, you can wrap your adapter in :class:`Sym $client = RedisAdapter::createConnection('redis://localhost'); $cache = new RedisTagAwareAdapter($client); +Configuring Redis +~~~~~~~~~~~~~~~~~ + +When using Redis as cache, you should configure the ``maxmemory`` and ``maxmemory-policy`` +settings. By setting ``maxmemory``, you limit how much memory Redis is allowed to consume. +If the amount is too low, Redis will drop entries that would still be useful and you benefit +less from your cache. Setting the ``maxmemory-policy`` to ``allkeys-lru`` tells Redis that +it is ok to drop data when it runs out of memory, and to first drop the oldest entries (least +recently used). If you do not allow Redis to drop entries, it will return an error when you +try to add data when no memory is available. An example setting could look as follows: + +.. code-block:: ini + + maxmemory 100mb + maxmemory-policy allkeys-lru + +Read more about this topic in the official `Redis LRU Cache Documentation`_. .. _`Data Source Name (DSN)`: https://en.wikipedia.org/wiki/Data_source_name .. _`Redis server`: https://redis.io/ @@ -211,3 +270,5 @@ In order to use tag-based invalidation, you can wrap your adapter in :class:`Sym .. _`Predis Connection Parameters`: https://github.com/nrk/predis/wiki/Connection-Parameters#list-of-connection-parameters .. _`TCP-keepalive`: https://redis.io/topics/clients#tcp-keepalive .. _`Redis Sentinel`: https://redis.io/topics/sentinel +.. _`Redis LRU Cache Documentation`: https://redis.io/topics/lru-cache +.. _`php.net/context.ssl`: https://php.net/context.ssl diff --git a/components/cache/cache_invalidation.rst b/components/cache/cache_invalidation.rst index 22f5830cf3e..e9bedfbd7d6 100644 --- a/components/cache/cache_invalidation.rst +++ b/components/cache/cache_invalidation.rst @@ -7,7 +7,7 @@ Cache Invalidation Cache invalidation is the process of removing all cached items related to a change in the state of your model. The most basic kind of invalidation is direct -items deletion. But when the state of a primary resource has spread across +item deletion. But when the state of a primary resource has spread across several cached items, keeping them in sync can be difficult. The Symfony Cache component provides two mechanisms to help solve this problem: @@ -47,7 +47,7 @@ you can invalidate the cached items by calling // if you know the cache key, you can also delete the item directly $cache->delete('cache_key'); -Using tags invalidation is very useful when tracking cache keys becomes difficult. +Using tag invalidation is very useful when tracking cache keys becomes difficult. Tag Aware Adapters ~~~~~~~~~~~~~~~~~~ @@ -61,7 +61,8 @@ method. .. note:: When using a Redis backend, consider using :ref:`RedisTagAwareAdapter ` - which is optimized for this purpose. + which is optimized for this purpose. When using filesystem, likewise consider to use + :ref:`FilesystemTagAwareAdapter `. The :class:`Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter` class implements instantaneous invalidation (time complexity is ``O(N)`` where ``N`` is the number @@ -86,7 +87,7 @@ your fronts and have very fast invalidation checks:: .. note:: - Since Symfony 3.4, :class:`Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter` + :class:`Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter` implements :class:`Symfony\\Component\\Cache\\PruneableInterface`, enabling manual :ref:`pruning of expired cache entries ` by diff --git a/components/config/definition.rst b/components/config/definition.rst index 67c7392eb33..2864bddb570 100644 --- a/components/config/definition.rst +++ b/components/config/definition.rst @@ -159,7 +159,7 @@ Array Nodes ~~~~~~~~~~~ It is possible to add a deeper level to the hierarchy, by adding an array -node. The array node itself, may have a pre-defined set of variable nodes:: +node. The array node itself, may have a predefined set of variable nodes:: $rootNode ->children() @@ -197,7 +197,7 @@ above, it is possible to have multiple connection arrays (containing a ``driver` ``host``, etc.). Sometimes, to improve the user experience of your application or bundle, you may -allow to use a simple string or numeric value where an array value is required. +allow the use of a simple string or numeric value where an array value is required. Use the ``castToArray()`` helper to turn those variables into arrays:: ->arrayNode('hosts') @@ -820,7 +820,8 @@ character (``.``):: $node = $treeBuilder->buildTree(); $children = $node->getChildren(); - $path = $children['driver']->getPath(); + $childChildren = $children['connection']->getChildren(); + $path = $childChildren['driver']->getPath(); // $path = 'database.connection.driver' Use the ``setPathSeparator()`` method on the config builder to change the path @@ -831,7 +832,8 @@ separator:: $treeBuilder->setPathSeparator('/'); $node = $treeBuilder->buildTree(); $children = $node->getChildren(); - $path = $children['driver']->getPath(); + $childChildren = $children['connection']->getChildren(); + $path = $childChildren['driver']->getPath(); // $path = 'database/connection/driver' Processing Configuration Values diff --git a/components/config/resources.rst b/components/config/resources.rst index 73d28a5db78..99e20093402 100644 --- a/components/config/resources.rst +++ b/components/config/resources.rst @@ -4,13 +4,6 @@ Loading Resources ================= -.. caution:: - - The ``IniFileLoader`` parses the file contents using the - :phpfunction:`parse_ini_file` function. Therefore, you can only set - parameters to string values. To set parameters to other data types - (e.g. boolean, integer, etc), the other loaders are recommended. - Loaders populate the application's configuration from different sources like YAML files. The Config component defines the interface for such loaders. The :doc:`Dependency Injection ` diff --git a/components/console.rst b/components/console.rst index 6a2abe2366e..e8f3f9a7578 100644 --- a/components/console.rst +++ b/components/console.rst @@ -63,5 +63,4 @@ Learn more /console /components/console/* - /components/console/helpers/index /console/* diff --git a/components/console/helpers/formatterhelper.rst b/components/console/helpers/formatterhelper.rst index ba3c2743d24..78dd3dfa581 100644 --- a/components/console/helpers/formatterhelper.rst +++ b/components/console/helpers/formatterhelper.rst @@ -9,8 +9,8 @@ You can do more advanced things with this helper than you can in :doc:`/console/coloring`. The :class:`Symfony\\Component\\Console\\Helper\\FormatterHelper` is included -in the default helper set, which you can get by calling -:method:`Symfony\\Component\\Console\\Command\\Command::getHelperSet`:: +in the default helper set and you can get it by calling +:method:`Symfony\\Component\\Console\\Command\\Command::getHelper`:: $formatter = $this->getHelper('formatter'); @@ -78,7 +78,9 @@ you can write:: $truncatedMessage = $formatter->truncate($message, 7); $output->writeln($truncatedMessage); -And the output will be:: +And the output will be: + +.. code-block:: text This is... @@ -93,7 +95,9 @@ from the end of the string:: $truncatedMessage = $formatter->truncate($message, -5); -This will result in:: +This will result in: + +.. code-block:: text This is a very long message, which should be trun... diff --git a/components/console/helpers/index.rst b/components/console/helpers/index.rst index 87c62ca7629..5f328d47472 100644 --- a/components/console/helpers/index.rst +++ b/components/console/helpers/index.rst @@ -15,6 +15,6 @@ The Console Helpers debug_formatter The Console component comes with some useful helpers. These helpers contain -function to ease some common tasks. +functions to ease some common tasks. .. include:: map.rst.inc diff --git a/components/console/helpers/progressbar.rst b/components/console/helpers/progressbar.rst index 1aa4dbfc6e2..2e2de44e399 100644 --- a/components/console/helpers/progressbar.rst +++ b/components/console/helpers/progressbar.rst @@ -301,7 +301,7 @@ to display it can be customized:: .. caution:: - For performance reasons, Symfony redraws screen every 100ms. If this is too + For performance reasons, Symfony redraws the screen once every 100ms. If this is too fast or to slow for your application, use the methods :method:`Symfony\\Component\\Console\\Helper\\ProgressBar::minSecondsBetweenRedraws` and :method:`Symfony\\Component\\Console\\Helper\\ProgressBar::maxSecondsBetweenRedraws`:: @@ -362,8 +362,8 @@ placeholder before displaying the progress bar:: $progressBar->start(); // 0/100 -- Start - $progressBar->advance(); $progressBar->setMessage('Task is in progress...'); + $progressBar->advance(); // 1/100 -- Task is in progress... Messages can be combined with custom placeholders too. In this example, the diff --git a/components/console/helpers/questionhelper.rst b/components/console/helpers/questionhelper.rst index de1aefd2cfa..e736e288fb4 100644 --- a/components/console/helpers/questionhelper.rst +++ b/components/console/helpers/questionhelper.rst @@ -6,13 +6,13 @@ Question Helper The :class:`Symfony\\Component\\Console\\Helper\\QuestionHelper` provides functions to ask the user for more information. It is included in the default -helper set, which you can get by calling -:method:`Symfony\\Component\\Console\\Command\\Command::getHelperSet`:: +helper set and you can get it by calling +:method:`Symfony\\Component\\Console\\Command\\Command::getHelper`:: $helper = $this->getHelper('question'); The Question Helper has a single method -:method:`Symfony\\Component\\Console\\Command\\Command::ask` that needs an +:method:`Symfony\\Component\\Console\\Helper\\QuestionHelper::ask` that needs an :class:`Symfony\\Component\\Console\\Input\\InputInterface` instance as the first argument, an :class:`Symfony\\Component\\Console\\Output\\OutputInterface` instance as the second argument and a @@ -123,7 +123,7 @@ 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 reach the maximum number of attempts. The default value for the maximum number -of attempts is ``null``, which means infinite number of attempts. You can define +of attempts is ``null``, which means an infinite number of attempts. You can define your own error message using :method:`Symfony\\Component\\Console\\Question\\ChoiceQuestion::setErrorMessage`. @@ -367,7 +367,7 @@ was successful. You can set the max number of times to ask with the :method:`Symfony\\Component\\Console\\Question\\Question::setMaxAttempts` method. If you reach this max number it will use the default value. Using ``null`` means -the amount of attempts is infinite. The user will be asked as long as they provide an +the number of attempts is infinite. The user will be asked as long as they provide an invalid answer and will only be able to proceed if their input is valid. Validating a Hidden Response @@ -384,8 +384,11 @@ You can also use a validator with a hidden question:: $helper = $this->getHelper('question'); $question = new Question('Please enter your password'); + $question->setNormalizer(function ($value) { + return $value ?? ''; + }); $question->setValidator(function ($value) { - if (trim($value) == '') { + if ('' === trim($value)) { throw new \Exception('The password cannot be empty'); } diff --git a/components/console/helpers/table.rst b/components/console/helpers/table.rst index bc680cc5ad0..9b479d0f9a6 100644 --- a/components/console/helpers/table.rst +++ b/components/console/helpers/table.rst @@ -72,8 +72,8 @@ You can add a table separator anywhere in the output by passing an instance of You can optionally display titles at the top and the bottom of the table:: // ... - $table->setHeaderTitle('Books') - $table->setFooterTitle('Page 1/2') + $table->setHeaderTitle('Books'); + $table->setFooterTitle('Page 1/2'); $table->render(); .. code-block:: terminal @@ -147,7 +147,7 @@ The output of this command will be: | 99921 | Divine Com | Dante Alighieri | | -58-1 | edy | | | 0-7 | | | - | (the rest of rows...) | + | (the rest of the rows...) | +-------+------------+--------------------------------+ The table style can be changed to any built-in styles via @@ -233,7 +233,7 @@ If the built-in styles do not fit your need, define your own:: // customizes the style $tableStyle - ->setDefaultCrossingChars('|') + ->setHorizontalBorderChars('|') ->setVerticalBorderChars('-') ->setDefaultCrossingChar(' ') ; @@ -244,7 +244,7 @@ If the built-in styles do not fit your need, define your own:: Here is a full list of things you can customize: * :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setPaddingChar` -* :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setDefaultCrossingChars` +* :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setHorizontalBorderChars` * :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setVerticalBorderChars` * :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setCrossingChars` * :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setDefaultCrossingChar` @@ -305,7 +305,7 @@ This results in: $table->setHeaders([ [new TableCell('Main table title', ['colspan' => 3])], ['ISBN', 'Title', 'Author'], - ]) + ]); // ... This generates: @@ -350,7 +350,7 @@ This outputs: | 978-0804169127 | Divine Comedy | spans multiple rows | +----------------+---------------+---------------------+ -You can use the ``colspan`` and ``rowspan`` options at the same time which allows +You can use the ``colspan`` and ``rowspan`` options at the same time, which allows you to create any table layout you may wish. .. _console-modify-rendered-tables: diff --git a/components/console/single_command_tool.rst b/components/console/single_command_tool.rst index 457efb48dae..ff4c2be8f1c 100644 --- a/components/console/single_command_tool.rst +++ b/components/console/single_command_tool.rst @@ -5,7 +5,7 @@ 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, +In such a case, having to pass the command name each time is tedious. Fortunately, it is possible to remove this need by declaring a single command application:: #!/usr/bin/env php @@ -30,7 +30,7 @@ it is possible to remove this need by declaring a 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 +accepts a boolean as the 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:: diff --git a/components/css_selector.rst b/components/css_selector.rst index c8100793ab4..0d3dbf8dbf9 100644 --- a/components/css_selector.rst +++ b/components/css_selector.rst @@ -25,8 +25,8 @@ Usage component in any PHP application. Read the :ref:`Symfony Functional Tests ` article to learn about how to use it when creating Symfony tests. -Why to Use CSS selectors? -~~~~~~~~~~~~~~~~~~~~~~~~~ +Why Use CSS selectors? +~~~~~~~~~~~~~~~~~~~~~~ When you're parsing an HTML or an XML document, by far the most powerful method is `XPath`_. @@ -40,7 +40,7 @@ long and unwieldy expressions. Many developers -- particularly web developers -- are more comfortable using CSS selectors to find elements. As well as working in stylesheets, CSS selectors are used in JavaScript with the ``querySelectorAll()`` function -and in popular JavaScript libraries such as jQuery, Prototype and MooTools. +and in popular JavaScript libraries such as jQuery. CSS selectors are less powerful than XPath, but far easier to write, read and understand. Since they are less powerful, almost all CSS selectors can diff --git a/components/dependency_injection.rst b/components/dependency_injection.rst index 486f89e599d..fab46ff3d26 100644 --- a/components/dependency_injection.rst +++ b/components/dependency_injection.rst @@ -259,15 +259,16 @@ config files: newsletter_manager: class: NewsletterManager calls: - - setMailer: ['@mailer'] + - [setMailer, ['@mailer']] .. code-block:: xml - + xsi:schemaLocation="http://symfony.com/schema/dic/services + https://symfony.com/schema/dic/services/services-1.0.xsd" + > sendmail @@ -290,24 +291,21 @@ config files: namespace Symfony\Component\DependencyInjection\Loader\Configurator; - return function(ContainerConfigurator $configurator) { - $configurator->parameters() + return static function (ContainerConfigurator $container) { + $container->parameters() // ... ->set('mailer.transport', 'sendmail') ; - $services = $configurator->services(); - - $services->set('mailer', 'Mailer') - ->args(['%mailer.transport%']) - ; + $container->services() + ->set('mailer', 'Mailer') + ->args(['%mailer.transport%']) - $services->set('newsletter_manager', 'NewsletterManager') - ->call('setMailer', [ref('mailer')]) + ->set('newsletter_manager', 'NewsletterManager') + ->call('setMailer', [ref('mailer')]) ; }; - Learn More ---------- diff --git a/components/dependency_injection/_imports-parameters-note.rst.inc b/components/dependency_injection/_imports-parameters-note.rst.inc index 92868df1985..50c6b736353 100644 --- a/components/dependency_injection/_imports-parameters-note.rst.inc +++ b/components/dependency_injection/_imports-parameters-note.rst.inc @@ -19,8 +19,8 @@ - + https://symfony.com/schema/dic/services/services-1.0.xsd" + > @@ -29,4 +29,8 @@ .. code-block:: php // config/services.php - $loader->import('%kernel.project_dir%/somefile.yaml'); + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + return static function (ContainerConfigurator $container) { + $container->import('%kernel.project_dir%/somefile.yaml'); + }; diff --git a/components/dependency_injection/compilation.rst b/components/dependency_injection/compilation.rst index 8f50b2b0d0c..3f5812529b2 100644 --- a/components/dependency_injection/compilation.rst +++ b/components/dependency_injection/compilation.rst @@ -197,16 +197,19 @@ The XML version of the config would then look like this: .. code-block:: xml - + - - + xmlns:acme-demo="http://www.example.com/schema/dic/acme_demo" + xsi:schemaLocation="http://symfony.com/schema/dic/services + https://symfony.com/schema/dic/services/services-1.0.xsd + http://www.example.com/schema/dic/acme_demo + https://www.example.com/schema/dic/acme_demo/acme_demo-1.0.xsd" + > + fooValue barValue - + .. note:: @@ -358,8 +361,8 @@ methods described in :doc:`/service_container/definitions`. method call if some required service is not available. A common use-case of compiler passes is to search for all service definitions -that have a certain tag in order to process dynamically plug each into some -other service. See the section on :ref:`service tags ` +that have a certain tag, in order to dynamically plug each one into other services. +See the section on :ref:`service tags ` for an example. .. _components-di-separate-compiler-passes: @@ -564,8 +567,8 @@ Now the cached dumped container is used regardless of whether debug mode is on or not. The difference is that the ``ConfigCache`` is set to debug mode with its second constructor argument. When the cache is not in debug mode the cached container will always be used if it exists. In debug mode, -an additional metadata file is written with the timestamps of all the resource -files. These are then checked to see if the files have changed, if they +an additional metadata file is written with all the involved resource +files. These are then checked to see if their timestamps have changed, if they have the cache will be considered stale. .. note:: diff --git a/components/dependency_injection/workflow.rst b/components/dependency_injection/workflow.rst index 750420f4d47..eb0bbb06984 100644 --- a/components/dependency_injection/workflow.rst +++ b/components/dependency_injection/workflow.rst @@ -21,11 +21,11 @@ Working with a Cached Container ------------------------------- Before building it, the kernel checks to see if a cached version of the -container exists. The HttpKernel has a debug setting and if this is false, +container exists. The kernel has a debug setting and if this is false, the cached version is used if it exists. If debug is true then the kernel :doc:`checks to see if configuration is fresh ` and if it is, the cached version of the container is used. If not then the -container is built from the application-level configuration and the bundles's +container is built from the application-level configuration and the bundles' extension configuration. Read :ref:`Dumping the Configuration for Performance ` diff --git a/components/dom_crawler.rst b/components/dom_crawler.rst index 1363d977a3a..fd35b5e7fd8 100644 --- a/components/dom_crawler.rst +++ b/components/dom_crawler.rst @@ -126,7 +126,7 @@ Consider the XML below: .. code-block:: xml - + '); @@ -553,7 +555,9 @@ You can virtually set and get values on the form:: // where "registration" is its own array $values = $form->getPhpValues(); -To work with multi-dimensional fields:: +To work with multi-dimensional fields: + +.. code-block:: html
diff --git a/components/event_dispatcher.rst b/components/event_dispatcher.rst index e89441e6a08..dd1ce9c310a 100644 --- a/components/event_dispatcher.rst +++ b/components/event_dispatcher.rst @@ -238,7 +238,7 @@ determine which instance is passed. $containerBuilder->addCompilerPass(new AddEventAliasesPass([ \AcmeFooActionEvent::class => 'acme.foo.action', ])); - $containerBuilder->addCompilerPass(new RegisterListenersPass(), PassConfig::TYPE_BEFORE_REMOVING) + $containerBuilder->addCompilerPass(new RegisterListenersPass(), PassConfig::TYPE_BEFORE_REMOVING); $containerBuilder->register('event_dispatcher', EventDispatcher::class); @@ -313,7 +313,7 @@ order. Start by creating this custom event class and documenting it:: $this->order = $order; } - public function getOrder() + public function getOrder(): Order { return $this->order; } @@ -499,9 +499,9 @@ is dispatched, are passed as arguments to the listener:: use Symfony\Contracts\EventDispatcher\Event; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; - class Foo + class MyListener { - public function myEventListener(Event $event, $eventName, EventDispatcherInterface $dispatcher) + public function myEventListener(Event $event, string $eventName, EventDispatcherInterface $dispatcher) { // ... do something with the event name } @@ -532,4 +532,4 @@ Learn More .. _Mediator: https://en.wikipedia.org/wiki/Mediator_pattern .. _Observer: https://en.wikipedia.org/wiki/Observer_pattern .. _Closures: https://www.php.net/manual/en/functions.anonymous.php -.. _PHP callable: https://www.php.net/manual/en/language.pseudo-types.php#language.types.callback +.. _PHP callable: https://www.php.net/manual/en/language.types.callable.php diff --git a/components/event_dispatcher/generic_event.rst b/components/event_dispatcher/generic_event.rst index 1f9be477151..1dc2a5be638 100644 --- a/components/event_dispatcher/generic_event.rst +++ b/components/event_dispatcher/generic_event.rst @@ -80,7 +80,7 @@ access the event arguments:: { public function handler(GenericEvent $event) { - if (isset($event['type']) && $event['type'] === 'foo') { + if (isset($event['type']) && 'foo' === $event['type']) { // ... do something } diff --git a/components/expression_language.rst b/components/expression_language.rst index edd3587aa6d..988bda75884 100644 --- a/components/expression_language.rst +++ b/components/expression_language.rst @@ -105,7 +105,7 @@ PHP type (including objects):: )); // displays "Honeycrisp" For more information, see the :doc:`/components/expression_language/syntax` -entry, especially :ref:`component-expression-objects` and :ref:`component-expression-arrays`. +entry, especially :ref:`Working with Objects ` and :ref:`Working with Arrays `. .. caution:: diff --git a/components/expression_language/ast.rst b/components/expression_language/ast.rst index 0f15c20647a..2bd2bf80023 100644 --- a/components/expression_language/ast.rst +++ b/components/expression_language/ast.rst @@ -5,8 +5,8 @@ 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 +It’s difficult to manipulate or inspect the expressions created with the ExpressionLanguage +component, because the expressions are plain strings. A better approach is to turn those expressions into an AST. In computer science, `AST`_ (*Abstract Syntax Tree*) is *"a tree representation of the structure of source code written in a programming language"*. In Symfony, a ExpressionLanguage AST is a set of diff --git a/components/expression_language/caching.rst b/components/expression_language/caching.rst index 770c2768ca5..29e1e0116f7 100644 --- a/components/expression_language/caching.rst +++ b/components/expression_language/caching.rst @@ -25,7 +25,7 @@ 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 +it can skip the tokenization and parsing steps with duplicate expressions. The caching is done by a PSR-6 `CacheItemPoolInterface`_ instance (by default, it uses an :class:`Symfony\\Component\\Cache\\Adapter\\ArrayAdapter`). You can customize this by creating a custom cache pool or using one of the available diff --git a/components/expression_language/syntax.rst b/components/expression_language/syntax.rst index 5b7ee72ec7a..b78ac907ca8 100644 --- a/components/expression_language/syntax.rst +++ b/components/expression_language/syntax.rst @@ -195,7 +195,7 @@ Comparison Operators $expressionLanguage->evaluate('not ("foo" matches "/bar/")'); // returns true - You must use parenthesis because the unary operator ``not`` has precedence + You must use parentheses because the unary operator ``not`` has precedence over the binary operator ``matches``. Examples:: @@ -204,7 +204,6 @@ Examples:: 'life == everything', [ 'life' => 10, - 'universe' => 10, 'everything' => 22, ] ); @@ -213,7 +212,6 @@ Examples:: 'life > everything', [ 'life' => 10, - 'universe' => 10, 'everything' => 22, ] ); diff --git a/components/filesystem.rst b/components/filesystem.rst index c0cad3dc9b9..a56ed09da0b 100644 --- a/components/filesystem.rst +++ b/components/filesystem.rst @@ -32,24 +32,12 @@ endpoint for filesystem operations:: echo "An error occurred while creating your directory at ".$exception->getPath(); } -.. note:: - - Methods :method:`Symfony\\Component\\Filesystem\\Filesystem::mkdir`, - :method:`Symfony\\Component\\Filesystem\\Filesystem::exists`, - :method:`Symfony\\Component\\Filesystem\\Filesystem::touch`, - :method:`Symfony\\Component\\Filesystem\\Filesystem::remove`, - :method:`Symfony\\Component\\Filesystem\\Filesystem::chmod`, - :method:`Symfony\\Component\\Filesystem\\Filesystem::chown` and - :method:`Symfony\\Component\\Filesystem\\Filesystem::chgrp` can receive a - string, an array or any object implementing :phpclass:`Traversable` as - the target argument. - ``mkdir`` ~~~~~~~~~ :method:`Symfony\\Component\\Filesystem\\Filesystem::mkdir` creates a directory recursively. On POSIX filesystems, directories are created with a default mode value -`0777`. You can use the second argument to set your own mode:: +``0777``. You can use the second argument to set your own mode:: $filesystem->mkdir('/tmp/photos', 0700); @@ -162,7 +150,7 @@ permissions of a file. The fourth argument is a boolean recursive option:: // sets the mode of the video to 0600 $filesystem->chmod('video.ogg', 0600); - // changes the mod of the src directory recursively + // changes the mode of the src directory recursively $filesystem->chmod('src', 0700, 0000, true); .. note:: @@ -214,13 +202,9 @@ support symbolic links, a third boolean argument is available:: :method:`Symfony\\Component\\Filesystem\\Filesystem::readlink` read links targets. -PHP's :phpfunction:`readlink` function returns the target of a symbolic link. However, its behavior -is completely different under Windows and Unix. On Windows systems, ``readlink()`` -resolves recursively the children links of a link until a final target is found. On -Unix-based systems ``readlink()`` only resolves the next link. - -The :method:`Symfony\\Component\\Filesystem\\Filesystem::readlink` method provided -by the Filesystem component always behaves in the same way:: +The :method:`Symfony\\Component\\Filesystem\\Filesystem::readlink` method +provided by the Filesystem component behaves in the same way on all operating +systems (unlike PHP's :phpfunction:`readlink` function):: // returns the next direct target of the link without considering the existence of the target $filesystem->readlink('/path/to/link'); @@ -230,9 +214,7 @@ by the Filesystem component always behaves in the same way:: Its behavior is the following:: - public function readlink($path, $canonicalize = false) - -* When ``$canonicalize`` is ``false``: +* When ``$canonicalize`` is ``false`` (the default value): * 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. @@ -252,7 +234,7 @@ absolute paths and returns the relative path from the second path to the first o '/var/lib/symfony/src/Symfony/Component' ); // returns 'videos/' - $filesystem->makePathRelative('/tmp/videos', '/tmp') + $filesystem->makePathRelative('/tmp/videos', '/tmp'); ``mirror`` ~~~~~~~~~~ diff --git a/components/filesystem/lock_handler.rst b/components/filesystem/lock_handler.rst index e7dab2fa625..5997fd3887b 100644 --- a/components/filesystem/lock_handler.rst +++ b/components/filesystem/lock_handler.rst @@ -1,5 +1,3 @@ -:orphan: - LockHandler =========== diff --git a/components/finder.rst b/components/finder.rst index f799208558e..0279b967b0a 100644 --- a/components/finder.rst +++ b/components/finder.rst @@ -214,7 +214,7 @@ Use the forward slash (i.e. ``/``) as the directory separator on all platforms, including Windows. The component makes the necessary conversion internally. The ``path()`` method accepts a string, a regular expression or an array of -strings or regulars expressions:: +strings or regular expressions:: $finder->path('foo/bar'); $finder->path('/^foo\/bar/'); @@ -302,6 +302,7 @@ Directory Depth By default, the Finder recursively traverses directories. Restrict the depth of traversing with :method:`Symfony\\Component\\Finder\\Finder::depth`:: + // this will only consider files/directories which are direct children $finder->depth('== 0'); $finder->depth('< 3'); diff --git a/components/form.rst b/components/form.rst index 7ac59478ceb..ee13dd35289 100644 --- a/components/form.rst +++ b/components/form.rst @@ -370,10 +370,6 @@ you need to. If your application uses global or static variables (not usually a good idea), then you can store the object on some static class or do something similar. -Regardless of how you architect your application, remember that you -should only have one form factory and that you'll need to be able to access -it throughout your application. - .. _component-form-intro-create-simple-form: Creating a simple Form @@ -382,7 +378,8 @@ Creating a simple Form .. tip:: If you're using the Symfony Framework, then the form factory is available - automatically as a service called ``form.factory``. Also, the default + automatically as a service called ``form.factory``, you can inject it as + ``Symfony\Component\Form\FormFactoryInterface``. Also, the default base controller class has a :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController::createFormBuilder` method, which is a shortcut to fetch the form factory and call ``createBuilder()`` on it. @@ -648,14 +645,15 @@ method: This defines a common form "workflow", which contains 3 different possibilities: -1) On the initial GET request (i.e. when the user "surfs" to your page), +#. On the initial GET request (i.e. when the user "surfs" to your page), build your form and render it; -If the request is a POST, process the submitted data (via :method:`Symfony\\Component\\Form\\Form::handleRequest`). -Then: + If the request is a POST, process the submitted data (via :method:`Symfony\\Component\\Form\\Form::handleRequest`). + + 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. +#. if the form is invalid, re-render the form (which will now contain errors); +#. if the form is valid, perform some action and redirect. Luckily, you don't need to decide whether or not a form has been submitted. Just pass the current request to the :method:`Symfony\\Component\\Form\\Form::handleRequest` @@ -686,7 +684,7 @@ option when building each field: 'constraints' => [ new NotBlank(), new Type(\DateTime::class), - ] + ], ]) ->getForm(); @@ -713,7 +711,7 @@ option when building each field: 'constraints' => [ new NotBlank(), new Type(\DateTime::class), - ] + ], ]) ->getForm(); // ... diff --git a/components/http_foundation.rst b/components/http_foundation.rst index 980eb597c3b..4ebce4526f0 100644 --- a/components/http_foundation.rst +++ b/components/http_foundation.rst @@ -331,11 +331,11 @@ analysis purposes. Use the ``anonymize()`` method from the use Symfony\Component\HttpFoundation\IpUtils; $ipv4 = '123.234.235.236'; - $anonymousIpv4 = IPUtils::anonymize($ipv4); + $anonymousIpv4 = IpUtils::anonymize($ipv4); // $anonymousIpv4 = '123.234.235.0' $ipv6 = '2a01:198:603:10:396e:4789:8e99:890f'; - $anonymousIpv6 = IPUtils::anonymize($ipv6); + $anonymousIpv6 = IpUtils::anonymize($ipv6); // $anonymousIpv6 = '2a01:198:603:10::' Accessing other Data @@ -547,7 +547,7 @@ represented by a PHP callable instead of a string:: header in the response:: // disables FastCGI buffering in nginx only for this response - $response->headers->set('X-Accel-Buffering', 'no') + $response->headers->set('X-Accel-Buffering', 'no'); .. _component-http-foundation-serving-files: @@ -633,7 +633,7 @@ handling, switching to chunked encoding instead:: use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\File\Stream; - $stream = new Stream('path/to/stream'); + $stream = new Stream('path/to/stream'); $response = new BinaryFileResponse($stream); .. note:: @@ -668,9 +668,11 @@ class, which can make this even easier:: // if you know the data to send when creating the response $response = new JsonResponse(['data' => 123]); - // if you don't know the data to send when creating the response + // if you don't know the data to send or if you want to customize the encoding options $response = new JsonResponse(); // ... + // configure any custom encoding options (if needed, it must be called before "setData()") + //$response->setEncodingOptions(JsonResponse::DEFAULT_ENCODING_OPTIONS | \JSON_PRESERVE_ZERO_FRACTION); $response->setData(['data' => 123]); // if the data to send is already encoded in JSON diff --git a/components/http_foundation/session_configuration.rst b/components/http_foundation/session_configuration.rst index c8b29fb00b4..36ca212b006 100644 --- a/components/http_foundation/session_configuration.rst +++ b/components/http_foundation/session_configuration.rst @@ -29,7 +29,7 @@ All native save handlers are internal to PHP and as such, have no public facing They must be configured by ``php.ini`` directives, usually ``session.save_path`` and potentially other driver specific directives. Specific details can be found in the docblock of the ``setOptions()`` method of each class. For instance, the one -provided by the Memcached extension can be found on :phpmethod:`php.net `. +provided by the Memcached extension can be found on :phpmethod:`php.net `. While native save handlers can be activated by directly using ``ini_set('session.save_handler', $name);``, Symfony provides a convenient way to @@ -154,28 +154,59 @@ Configuring Garbage Collection ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When a session opens, PHP will call the ``gc`` handler randomly according to the -probability set by ``session.gc_probability`` / ``session.gc_divisor``. For -example if these were set to ``5/100`` respectively, it would mean a probability -of 5%. Similarly, ``3/4`` would mean a 3 in 4 chance of being called, i.e. 75%. +probability set by ``session.gc_probability`` / ``session.gc_divisor`` in ``php.ini``. +For example if these were set to ``5/100``, it would mean a probability of 5%. -If the garbage collection handler is invoked, PHP will pass the value stored in -the ``php.ini`` directive ``session.gc_maxlifetime``. The meaning in this context is -that any stored session that was saved more than ``gc_maxlifetime`` ago should be -deleted. This allows one to expire records based on idle time. +If the garbage collection handler is invoked, PHP will pass the value of +``session.gc_maxlifetime``, meaning that any stored session that was saved more +than ``gc_maxlifetime`` seconds ago should be deleted. This allows to expire records +based on idle time. However, some operating systems (e.g. Debian) do their own session handling and set -the ``session.gc_probability`` variable to ``0`` to stop PHP doing garbage +the ``session.gc_probability`` directive to ``0`` to stop PHP doing garbage collection. That's why Symfony now overwrites this value to ``1``. If you wish to use the original value set in your ``php.ini``, add the following configuration: -.. code-block:: yaml - - # config/packages/framework.yaml - framework: - session: - gc_probability: null +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/framework.yaml + framework: + session: + gc_probability: null + + .. code-block:: xml + + + + + + + + + + .. code-block:: php + + // config/packages/framework.php + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + return static function (ContainerConfigurator $container) { + $container->extension('framework', [ + 'session' => [ + 'gc_probability' => null, + ], + ]); + }; You can configure these settings by passing ``gc_probability``, ``gc_divisor`` and ``gc_maxlifetime`` in an array to the constructor of diff --git a/components/http_foundation/sessions.rst b/components/http_foundation/sessions.rst index 9c9479e3e5e..e8ca28d35d3 100644 --- a/components/http_foundation/sessions.rst +++ b/components/http_foundation/sessions.rst @@ -53,7 +53,7 @@ Quick example:: .. caution:: Symfony sessions are incompatible with ``php.ini`` directive ``session.auto_start = 1`` - This directive should be turned off in ``php.ini``, in the webserver directives or + This directive should be turned off in ``php.ini``, in the web server directives or in ``.htaccess``. Session API @@ -152,7 +152,7 @@ the following API which is intended mainly for internal purposes: Returns the name of the session bag. :method:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface::clear` - Clears out data from bag. + Clears out data from the bag. .. _attribute-bag-interface: @@ -291,7 +291,7 @@ has the API Gets flashes by type (read only). :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::peekAll` - Gets all flashes (read only) as keyed array of arrays. + Gets all flashes (read only) as a keyed array of arrays. :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::has` Returns true if the type exists, false if not. diff --git a/components/http_kernel.rst b/components/http_kernel.rst index 9a1d5a31049..0bdb8c24b2f 100644 --- a/components/http_kernel.rst +++ b/components/http_kernel.rst @@ -249,7 +249,7 @@ on the request's information. The Symfony Framework uses the built-in :class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver` - class (actually, it uses a sub-class with some extra functionality + class (actually, it uses a subclass with some extra functionality mentioned below). This class leverages the information that was placed on the ``Request`` object's ``attributes`` property during the ``RouterListener``. @@ -358,7 +358,7 @@ of arguments that should be passed when executing that callable. 5) Calling the Controller ~~~~~~~~~~~~~~~~~~~~~~~~~ -The next step ``HttpKernel::handle()`` does is executing the controller. +The next step of ``HttpKernel::handle()`` is executing the controller. The job of the controller is to build the response for the given resource. This could be an HTML page, a JSON string or anything else. Unlike every @@ -498,12 +498,6 @@ as possible to the client (e.g. sending emails). Using the ``kernel.terminate`` event is optional, and should only be called if your kernel implements :class:`Symfony\\Component\\HttpKernel\\TerminableInterface`. -.. sidebar:: ``kernel.terminate`` in the Symfony Framework - - If you use the :ref:`memory spooling ` option of the - default Symfony mailer, then the `EmailSenderListener`_ is activated, which - actually delivers any emails that you scheduled to send during the request. - .. _component-http-kernel-kernel-exception: Handling Exceptions: the ``kernel.exception`` Event @@ -516,7 +510,7 @@ Handling Exceptions: the ``kernel.exception`` Event If an exception is thrown at any point inside ``HttpKernel::handle()``, another event - ``kernel.exception`` is thrown. Internally, the body of the ``handle()`` -function is wrapped in a try-catch block. When any exception is thrown, the +method is wrapped in a try-catch block. When any exception is thrown, the ``kernel.exception`` event is dispatched so that your system can somehow respond to the exception. @@ -608,7 +602,7 @@ on creating and attaching event listeners, see :doc:`/components/event_dispatche The name of each of the "kernel" events is defined as a constant on the :class:`Symfony\\Component\\HttpKernel\\KernelEvents` class. Additionally, each -event listener is passed a single argument, which is some sub-class of :class:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent`. +event listener is passed a single argument, which is some subclass of :class:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent`. This object contains information about the current state of the system and each event has their own event object: @@ -698,7 +692,7 @@ Sub Requests ------------ In addition to the "main" request that's sent into ``HttpKernel::handle()``, -you can also send so-called "sub request". A sub request looks and acts like +you can also send a so-called "sub request". A sub request looks and acts like any other request, but typically serves to render just one small portion of a page instead of a full page. You'll most commonly make sub-requests from your controller (or perhaps from inside a template, that's being rendered by @@ -727,7 +721,7 @@ argument as follows:: This creates another full request-response cycle where this new ``Request`` is transformed into a ``Response``. The only difference internally is that some listeners (e.g. security) may only act upon the master request. Each listener -is passed some sub-class of :class:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent`, +is passed some subclass of :class:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent`, whose :method:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent::isMasterRequest` can be used to check if the current request is a "master" or "sub" request. @@ -783,5 +777,4 @@ Learn more .. _`SensioFrameworkExtraBundle`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html .. _`@ParamConverter`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html .. _`@Template`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/view.html -.. _`EmailSenderListener`: https://github.com/symfony/swiftmailer-bundle/blob/master/EventListener/EmailSenderListener.php .. _variadic: https://www.php.net/manual/en/functions.arguments.php#functions.variable-arg-list diff --git a/components/index.rst b/components/index.rst deleted file mode 100644 index bf28bf3b5d8..00000000000 --- a/components/index.rst +++ /dev/null @@ -1,16 +0,0 @@ -The Components -============== - -.. seealso:: - - See the dedicated `Symfony Components`_ webpage for a full overview of decoupled - and reusable Symfony components. - -.. toctree:: - :maxdepth: 1 - :glob: - - using_components - * - -.. _`Symfony Components`: https://symfony.com/components diff --git a/components/inflector.rst b/components/inflector.rst index 5e9f4325884..960cb04d4ba 100644 --- a/components/inflector.rst +++ b/components/inflector.rst @@ -60,5 +60,5 @@ forms:: Inflector::singularize('indices'); // ['index', 'indix', 'indice'] Inflector::singularize('leaves'); // ['leaf', 'leave', 'leaff'] - Inflector::pluralize('matrix'); // ['matricies', 'matrixes'] + Inflector::pluralize('matrix'); // ['matrices', 'matrixes'] Inflector::pluralize('person'); // ['persons', 'people'] diff --git a/components/intl.rst b/components/intl.rst index 6417489b14e..387956f85e3 100644 --- a/components/intl.rst +++ b/components/intl.rst @@ -11,7 +11,7 @@ The Intl Component .. caution:: The replacement layer is limited to the ``en`` locale. If you want to use - other locales, you should `install the intl extension`_. There is no conflict + other locales, you should `install the intl extension`_. There is no conflict between the two because, even if you use the extension, this package can still be useful to access the ICU data. @@ -68,8 +68,8 @@ This component provides the following ICU data: Language and Script Names ~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``Languages`` class provides access to the name of all languages -according to the `ISO 639-1 alpha-2`_ list and the `ISO 639-2 alpha-3`_ list:: +The :class:`Symfony\\Component\\Intl\\Languages` class provides access to the name of all languages +according to the `ISO 639-1 alpha-2`_ list and the `ISO 639-2 alpha-3 (2T)`_ list:: use Symfony\Component\Intl\Languages; @@ -110,7 +110,7 @@ to catching the exception, you can also check if a given language code is valid: $isValidLanguage = Languages::exists($languageCode); -Or if you have a alpha3 language code you want to check:: +Or if you have an alpha3 language code you want to check:: $isValidLanguage = Languages::alpha3CodeExists($alpha3Code); @@ -128,7 +128,7 @@ You may convert codes between two-letter alpha2 and three-letter alpha3 codes:: The full support for alpha3 codes was introduced in Symfony 4.4. -The ``Scripts`` class provides access to the optional four-letter script code +The :class:`Symfony\\Component\\Intl\\Scripts` class provides access to the optional four-letter script code that can follow the language code according to the `Unicode ISO 15924 Registry`_ (e.g. ``HANS`` in ``zh_HANS`` for simplified Chinese and ``HANT`` in ``zh_HANT`` for traditional Chinese):: @@ -166,9 +166,9 @@ to catching the exception, you can also check if a given script code is valid:: Country Names ~~~~~~~~~~~~~ -The ``Countries`` class provides access to the name of all countries according -to the `ISO 3166-1 alpha-2`_ list and the `ISO 3166-1 alpha-3`_ list -of officially recognized countries and territories:: +The :class:`Symfony\\Component\\Intl\\Countries` class provides access to the +name of all countries according to the `ISO 3166-1 alpha-2`_ list and the +`ISO 3166-1 alpha-3`_ list of officially recognized countries and territories:: use Symfony\Component\Intl\Countries; @@ -209,7 +209,7 @@ to catching the exception, you can also check if a given country code is valid:: $isValidCountry = Countries::exists($alpha2Code); -Or if you have a alpha3 country code you want to check:: +Or if you have an alpha3 country code you want to check:: $isValidCountry = Countries::alpha3CodeExists($alpha3Code); @@ -231,10 +231,10 @@ Locales ~~~~~~~ A locale is the combination of a language, a region and some parameters that -define the interface preferences of the user. For example, "Chinese" is the -language and ``zh_Hans_MO`` is the locale for "Chinese" (language) + "Simplified" -(script) + "Macau SAR China" (region). The ``Locales`` class provides access to -the name of all locales:: +define the interface preferences of the user. For example, "Chinese" is the +language and ``zh_Hans_MO`` is the locale for "Chinese" (language) + "Simplified" +(script) + "Macau SAR China" (region). The :class:`Symfony\\Component\\Intl\\Locales` +class provides access to the name of all locales:: use Symfony\Component\Intl\Locales; @@ -269,8 +269,8 @@ to catching the exception, you can also check if a given locale code is valid:: Currencies ~~~~~~~~~~ -The ``Currencies`` class provides access to the name of all currencies as well -as some of their information (symbol, fraction digits, etc.):: +The :class:`Symfony\\Component\\Intl\\Currencies` class provides access to the name +of all currencies as well as some of their information (symbol, fraction digits, etc.):: use Symfony\Component\Intl\Currencies; @@ -317,8 +317,9 @@ to catching the exception, you can also check if a given currency code is valid: Timezones ~~~~~~~~~ -The ``Timezones`` class provides several utilities related to timezones. First, -you can get the name and values of all timezones in all languages:: +The :class:`Symfony\\Component\\Intl\\Timezones` class provides several utilities +related to timezones. First, you can get the name and values of all timezones in +all languages:: use Symfony\Component\Intl\Timezones; @@ -351,7 +352,7 @@ translate into any locale with the ``getName()`` method shown earlier:: The reverse lookup is also possible thanks to the ``getCountryCode()`` method, which returns the code of the country where the given timezone ID belongs to:: - $countryCode = Timezones::getCountryCode('America/Vancouver') + $countryCode = Timezones::getCountryCode('America/Vancouver'); // => $countryCode = 'CA' (CA = Canada) The `UTC/GMT time offsets`_ of all timezones are provided by ``getRawOffset()`` @@ -381,8 +382,8 @@ arguments to get the offset at any given point in time:: The string representation of the GMT offset can vary depending on the locale, so you can pass the locale as the third optional argument:: - $offset = Timezones::getGmtOffset('Europe/Madrid', strtotime('October 28, 2019'), 'ar')); // $offset = 'غرينتش+01:00' - $offset = Timezones::getGmtOffset('Europe/Madrid', strtotime('October 28, 2019'), 'dz')); // $offset = 'ཇི་ཨེམ་ཏི་+01:00' + $offset = Timezones::getGmtOffset('Europe/Madrid', strtotime('October 28, 2019'), 'ar'); // $offset = 'غرينتش+01:00' + $offset = Timezones::getGmtOffset('Europe/Madrid', strtotime('October 28, 2019'), 'dz'); // $offset = 'ཇི་ཨེམ་ཏི་+01:00' If the given timezone ID doesn't exist, the methods trigger a :class:`Symfony\\Component\\Intl\\Exception\\MissingResourceException`. In addition @@ -416,4 +417,4 @@ Learn more .. _`UTC/GMT time offsets`: https://en.wikipedia.org/wiki/List_of_UTC_time_offsets .. _`daylight saving time (DST)`: https://en.wikipedia.org/wiki/Daylight_saving_time .. _`ISO 639-1 alpha-2`: https://en.wikipedia.org/wiki/ISO_639-1 -.. _`ISO 639-2 alpha-3`: https://en.wikipedia.org/wiki/ISO_639-2 +.. _`ISO 639-2 alpha-3 (2T)`: https://en.wikipedia.org/wiki/ISO_639-2 diff --git a/components/ldap.rst b/components/ldap.rst index 8e73b077760..0c164d305dc 100644 --- a/components/ldap.rst +++ b/components/ldap.rst @@ -115,6 +115,10 @@ to the ``LDAP_SCOPE_BASE`` scope of :phpfunction:`ldap_read`) and ``SCOPE_ONE`` $query = $ldap->query('dc=symfony,dc=com', '...', ['scope' => QueryInterface::SCOPE_ONE]); +Use the ``filter`` option to only retrieve some specific attributes: + + $query = $ldap->query('dc=symfony,dc=com', '...', ['filter' => ['cn', 'mail']); + Creating or Updating Entries ---------------------------- @@ -139,6 +143,10 @@ delete existing ones:: $query = $ldap->query('dc=symfony,dc=com', '(&(objectclass=person)(ou=Maintainers))'); $result = $query->execute(); $entry = $result[0]; + + $phoneNumber = $entry->getAttribute('phoneNumber'); + $isContractor = $entry->hasAttribute('contractorCompany'); + $entry->setAttribute('email', ['fabpot@symfony.com']); $entryManager->update($entry); diff --git a/components/lock.rst b/components/lock.rst index 45237fed221..cd783ff6d94 100644 --- a/components/lock.rst +++ b/components/lock.rst @@ -51,7 +51,7 @@ method will try to acquire the lock:: if ($lock->acquire()) { // The resource "pdf-invoice-generation" is locked. - // You can compute and generate invoice safely here. + // You can compute and generate the invoice safely here. $lock->release(); } @@ -61,18 +61,46 @@ method can be safely called repeatedly, even if the lock is already acquired. .. note:: - Unlike other implementations, the Lock Component distinguishes locks - instances even when they are created for the same resource. If a lock has - to be used by several services, they should share the same ``Lock`` instance - returned by the ``LockFactory::createLock`` method. + Unlike other implementations, the Lock Component distinguishes lock + instances even when they are created for the same resource. It means that for + a given scope and resource one lock instance can be acquired multiple times. + If a lock has to be used by several services, they should share the same ``Lock`` + instance returned by the ``LockFactory::createLock`` method. .. tip:: If you don't release the lock explicitly, it will be released automatically - on instance destruction. In some cases, it can be useful to lock a resource + upon instance destruction. In some cases, it can be useful to lock a resource across several requests. To disable the automatic release behavior, set the third argument of the ``createLock()`` method to ``false``. +Serializing Locks +------------------ + +The :class:`Symfony\\Component\\Lock\\Key` contains the state of the +:class:`Symfony\\Component\\Lock\\Lock` and can be serialized. This +allows the user to begin a long job in a process by acquiring the lock, and +continue the job in another process using the same lock:: + + use Symfony\Component\Lock\Key; + use Symfony\Component\Lock\Lock; + + $key = new Key('article.'.$article->getId()); + $lock = new Lock($key, $this->store, 300, false); + $lock->acquire(true); + + $this->bus->dispatch(new RefreshTaxonomy($article, $key)); + +.. note:: + + Don't forget to disable the autoRelease to avoid releasing the lock when + the destructor is called. + +Not all stores are compatible with serialization and cross-process locking: +for example, the kernel will automatically release semaphores acquired by the +:ref:`SemaphoreStore ` store. If you use an incompatible +store, an exception will be thrown when the application tries to serialize the key. + .. _lock-blocking-locks: Blocking Locks @@ -118,7 +146,7 @@ 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 + // create an expiring lock that lasts 30 seconds (default is 300.0) $lock = $factory->createLock('charts-generation', 30); if (!$lock->acquire()) { @@ -132,7 +160,7 @@ method, the resource will stay locked until the timeout:: .. tip:: - To avoid letting the lock in a locking state, it's recommended to wrap the + To avoid leaving the lock in a locked state, it's recommended to wrap the job in a try/catch/finally block to always try to release the expiring lock. In case of long-running tasks, it's better to start with a not too long TTL and @@ -170,8 +198,40 @@ to reset the TTL to its original value:: $lock->refresh(600); This component also provides two useful methods related to expiring locks: -``getExpiringDate()`` (which returns ``null`` or a ``\DateTimeImmutable`` -object) and ``isExpired()`` (which returns a boolean). +``getRemainingLifetime()`` (which returns ``null`` or a ``float`` +as seconds) and ``isExpired()`` (which returns a boolean). + +Automatically Releasing The Lock +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Locks are automatically released when their Lock objects are destructed. This is +an implementation detail that will be important when sharing Locks between +processes. In the example below, ``pcntl_fork()`` creates two processes and the +Lock will be released automatically as soon as one process finishes:: + + // ... + $lock = $factory->createLock('report-generation', 3600); + if (!$lock->acquire()) { + return; + } + + $pid = pcntl_fork(); + if (-1 === $pid) { + // Could not fork + exit(1); + } elseif ($pid) { + // Parent process + sleep(30); + } else { + // Child process + echo 'The lock will be released now.'; + exit(0); + } + // ... + +To disable this behavior, set to ``false`` the third argument of +``LockFactory::createLock()``. That will make the lock acquired for 3600 seconds +or until ``Lock::release()`` is called. The Owner of The Lock --------------------- @@ -415,7 +475,7 @@ PHP process is terminated:: Reliability ----------- -The component guarantees that the same resource can't be lock twice as long as +The component guarantees that the same resource can't be locked twice as long as the component is used in the following way. Remote Stores @@ -427,12 +487,7 @@ Remote stores (:ref:`MemcachedStore `, :ref:`ZookeeperStore `) use a unique token to recognize the true owner of the lock. This token is stored in the :class:`Symfony\\Component\\Lock\\Key` object and is used internally by -the ``Lock``, therefore this key must not be shared between processes (session, -caching, fork, ...). - -.. caution:: - - Do not share a key between processes. +the ``Lock``. 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``. @@ -501,11 +556,11 @@ FlockStore ~~~~~~~~~~ By using the file system, this ``Store`` is reliable as long as concurrent -processes use the same physical directory to stores locks. +processes use the same physical directory to store locks. Processes must run on the same machine, virtual machine or container. -Be careful when updating a Kubernetes or Swarm service because for a short -period of time, there can be two running containers in parallel. +Be careful when updating a Kubernetes or Swarm service because, for a short +period of time, there can be two containers running in parallel. The absolute path to the directory must remain the same. Be careful of symlinks that could change at anytime: Capistrano and blue/green deployment often use @@ -517,7 +572,7 @@ 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. + on the same machine and using the same absolute path to the lock directory. By definition, usage of ``FlockStore`` in an HTTP context is incompatible with multiple front servers, unless to ensure that the same resource will @@ -539,7 +594,7 @@ MemcachedStore The way Memcached works is to store items in memory. That means that by using the :ref:`MemcachedStore ` the locks are not persisted -and may disappear by mistake at anytime. +and may disappear by mistake at any time. If the Memcached service or the machine hosting it restarts, every lock would be lost without notifying the running processes. @@ -575,7 +630,7 @@ The PdoStore relies on the `ACID`_ properties of the SQL engine. .. caution:: In a cluster configured with multiple primaries, ensure writes are - synchronously propagated to every nodes, or always use the same node. + synchronously propagated to every node, or always use the same node. .. caution:: @@ -596,7 +651,7 @@ RedisStore The way Redis works is to store items in memory. That means that by using the :ref:`RedisStore ` the locks are not persisted -and may disappear by mistake at anytime. +and may disappear by mistake at any time. If the Redis service or the machine hosting it restarts, every locks would be lost without notifying the running processes. @@ -623,7 +678,7 @@ removed by mistake. CombinedStore ~~~~~~~~~~~~~ -Combined stores allow to store locks across several backends. It's a common +Combined stores allow the storage of locks across several backends. It's a common mistake to think that the lock mechanism will be more reliable. This is wrong. The ``CombinedStore`` will be, at best, as reliable as the least reliable of all managed stores. As soon as one managed store returns erroneous information, @@ -650,7 +705,7 @@ 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 + concurrent process on a new machine, check that other processes are stopped on the old one. .. caution:: diff --git a/components/messenger.rst b/components/messenger.rst index b0bcb785aa7..b71680ff70e 100644 --- a/components/messenger.rst +++ b/components/messenger.rst @@ -56,7 +56,7 @@ Concepts which means they can tweak the envelope, by adding stamps to it or even replacing it, as well as interrupt the middleware chain. Middleware are called both when a message is originally dispatched and again later when a message - is received from a transport, + is received from a transport. **Envelope**: Messenger specific concept, it gives full flexibility inside the message bus, @@ -148,21 +148,26 @@ through the transport layer, use the ``SerializerStamp`` stamp:: ])) ); -At the moment, the Symfony Messenger has the following built-in envelope stamps: +Here are some important envelope stamps that are shipped with the Symfony Messenger: -#. :class:`Symfony\\Component\\Messenger\\Stamp\\SerializerStamp`, - to configure the serialization groups used by the transport. -#. :class:`Symfony\\Component\\Messenger\\Stamp\\ValidationStamp`, - to configure the validation groups used when the validation middleware is enabled. +#. :class:`Symfony\\Component\\Messenger\\Stamp\\DelayStamp`, + to delay handling of an asynchronous message. +#. :class:`Symfony\\Component\\Messenger\\Stamp\\DispatchAfterCurrentBusStamp`, + to make the message be handled after the current bus has executed. Read more + at :doc:`/messenger/dispatch_after_current_bus`. +#. :class:`Symfony\\Component\\Messenger\\Stamp\\HandledStamp`, + a stamp that marks the message as handled by a specific handler. + Allows accessing the handler returned value and the handler name. #. :class:`Symfony\\Component\\Messenger\\Stamp\\ReceivedStamp`, an internal stamp that marks the message as received from a transport. #. :class:`Symfony\\Component\\Messenger\\Stamp\\SentStamp`, a stamp that marks the message as sent by a specific sender. Allows accessing the sender FQCN and the alias if available from the :class:`Symfony\\Component\\Messenger\\Transport\\Sender\\SendersLocator`. -#. :class:`Symfony\\Component\\Messenger\\Stamp\\HandledStamp`, - a stamp that marks the message as handled by a specific handler. - Allows accessing the handler returned value and the handler name. +#. :class:`Symfony\\Component\\Messenger\\Stamp\\SerializerStamp`, + to configure the serialization groups used by the transport. +#. :class:`Symfony\\Component\\Messenger\\Stamp\\ValidationStamp`, + to configure the validation groups used when the validation middleware is enabled. Instead of dealing directly with the messages in the middleware you receive the envelope. Hence you can inspect the envelope content and its stamps, or add any:: @@ -352,4 +357,4 @@ Learn more /messenger/* .. _`blog posts about command buses`: https://matthiasnoback.nl/tags/command%20bus/ -.. _`SimpleBus project`: http://docs.simplebus.io/en/latest/ +.. _`SimpleBus project`: https://docs.simplebus.io/en/latest/ diff --git a/components/mime.rst b/components/mime.rst index ce884a51193..965c7a377ae 100644 --- a/components/mime.rst +++ b/components/mime.rst @@ -38,7 +38,7 @@ complexity to provide two ways of creating MIME messages: * A high-level API based on the :class:`Symfony\\Component\\Mime\\Email` class to quickly create email messages with all the common features; * A low-level API based on the :class:`Symfony\\Component\\Mime\\Message` class - to have an absolute control over every single part of the email message. + to have absolute control over every single part of the email message. Usage ----- @@ -60,7 +60,7 @@ methods to compose the entire email message:: ->html('

Lorem ipsum

...

') ; -This only purpose of this component is to create the email messages. Use the +The only purpose of this component is to create the email messages. Use the :doc:`Mailer component ` to actually send them. Twig Integration @@ -103,12 +103,12 @@ extension: .. code-block:: terminal - $ composer require twig/cssinliner-extension + $ composer require twig/cssinliner-extra Now, enable the extension:: // ... - use Twig\CssInliner\CssInlinerExtension; + use Twig\Extra\CssInliner\CssInlinerExtension; $loader = new FilesystemLoader(__DIR__.'/templates'); $twig = new Environment($loader); @@ -242,10 +242,10 @@ MIME types and file name extensions:: $exts = $mimeTypes->getExtensions('image/jpeg'); // $exts = ['jpeg', 'jpg', 'jpe'] - $mimeTypes = $mimeTypes->getMimeTypes('js'); - // $mimeTypes = ['application/javascript', 'application/x-javascript', 'text/javascript'] - $mimeTypes = $mimeTypes->getMimeTypes('apk'); - // $mimeTypes = ['application/vnd.android.package-archive'] + $types = $mimeTypes->getMimeTypes('js'); + // $types = ['application/javascript', 'application/x-javascript', 'text/javascript'] + $types = $mimeTypes->getMimeTypes('apk'); + // $types = ['application/vnd.android.package-archive'] These methods return arrays with one or more elements. The element position indicates its priority, so the first returned extension is the preferred one. diff --git a/components/options_resolver.rst b/components/options_resolver.rst index a88c9f95d31..4d556d86fda 100644 --- a/components/options_resolver.rst +++ b/components/options_resolver.rst @@ -434,8 +434,8 @@ if you need to use other options during normalization:: } } -To normalize a new allowed value in sub-classes that are being normalized -in parent classes use :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::addNormalizer`. +To normalize a new allowed value in subclasses that are being normalized +in parent classes, use :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::addNormalizer` method. This way, the ``$value`` argument will receive the previously normalized value, otherwise you can prepend the new normalizer by passing ``true`` as third argument. @@ -452,7 +452,7 @@ encryption chosen by the user of the ``Mailer`` class. More precisely, you want to set the port to ``465`` if SSL is used and to ``25`` otherwise. You can implement this feature by passing a closure as the default value of -the ``port`` option. The closure receives the options as argument. Based on +the ``port`` option. The closure receives the options as arguments. Based on these options, you can return the desired default value:: use Symfony\Component\OptionsResolver\Options; @@ -484,7 +484,7 @@ these options, you can return the desired default value:: .. note:: The closure is only executed if the ``port`` option isn't set by the user - or overwritten in a sub-class. + or overwritten in a subclass. A previously set default value can be accessed by adding a second argument to the closure:: @@ -511,7 +511,7 @@ the closure:: $resolver->setDefault('host', function (Options $options, $previousValue) { if ('ssl' === $options['encryption']) { - return 'secure.example.org' + return 'secure.example.org'; } // Take default value configured in the base class diff --git a/components/phpunit_bridge.rst b/components/phpunit_bridge.rst index 095a4840d64..cb77a1e376f 100644 --- a/components/phpunit_bridge.rst +++ b/components/phpunit_bridge.rst @@ -48,7 +48,7 @@ Installation always use its very latest stable major version to get the most accurate deprecation report. -If you plan to :ref:`write-assertions-about-deprecations` and use the regular +If you plan to :ref:`write assertions about deprecations ` and use the regular PHPUnit script (not the modified PHPUnit script provided by Symfony), you have to register a new `test listener`_ called ``SymfonyTestsListener``: @@ -165,6 +165,9 @@ Deprecation notices can be triggered by using:: @trigger_error('Your deprecation message', E_USER_DEPRECATED); +You can also require the ``symfony/deprecation-contracts`` package that provides +a global ``trigger_deprecation()`` function for this usage. + Without the `@-silencing operator`_, users would need to opt-out from deprecation notices. Silencing by default swaps this behavior and allows users to opt-in when they are ready to cope with them (by adding a custom error handler like the @@ -223,10 +226,10 @@ message contains the ``"foobar"`` string. Making Tests Fail ~~~~~~~~~~~~~~~~~ -By default, any non-legacy-tagged or any non-`@-silenced <@-silencing operator>`_ +By default, any non-legacy-tagged or any non-silenced (`@-silencing operator`_) deprecation notices will make tests fail. Alternatively, you can configure an arbitrary threshold by setting ``SYMFONY_DEPRECATIONS_HELPER`` to -``max[total]=320`` for instance. It will make the tests fails only if a +``max[total]=320`` for instance. It will make the tests fail only if a higher number of deprecation notices is reached (``0`` is the default value). @@ -234,7 +237,7 @@ You can have even finer-grained control by using other keys of the ``max`` array, which are ``self``, ``direct``, and ``indirect``. The ``SYMFONY_DEPRECATIONS_HELPER`` environment variable accepts a URL-encoded string, meaning you can combine thresholds and any other configuration setting, -like this: ``SYMFONY_DEPRECATIONS_HELPER=max[total]=42&max[self]=0&verbose=0`` +like this: ``SYMFONY_DEPRECATIONS_HELPER='max[total]=42&max[self]=0&verbose=0'`` Internal deprecations ..................... @@ -305,8 +308,6 @@ to completely disable the deprecation helper. This is useful to make use of the rest of features provided by this component without getting errors or messages related to deprecations. -.. _write-assertions-about-deprecations: - Deprecation Notices at Autoloading Time ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -333,6 +334,8 @@ time. This can be disabled with the ``debug-class-loader`` option. The ``DebugClassLoader`` integration was introduced in Symfony 4.2. +.. _write-assertions-about-deprecations: + Write Assertions about Deprecations ----------------------------------- @@ -477,8 +480,8 @@ If you have this kind of time-related tests:: } } -You used the :doc:`Symfony Stopwatch Component ` to -calculate the duration time of your process, here 10 seconds. However, depending +You calculated the duration time of your process using the Stopwatch utilities to +:ref:`profile Symfony applications `. However, depending on the load of the server or the processes running on your local machine, the ``$duration`` could for example be ``10.000023s`` instead of ``10s``. @@ -497,7 +500,7 @@ is mocked so it uses the mocked time if no timestamp is specified. Other functions with an optional timestamp parameter that defaults to ``time()`` will still use the system time instead of the mocked time. This means that you may need to change some code in your tests. For example, instead of ``new DateTime()``, -you should use ``DateTime::createFromFormat('U', time())`` to use the mocked +you should use ``DateTime::createFromFormat('U', (string) time())`` to use the mocked ``time()`` function. To use the ``ClockMock`` class in your test, add the ``@group time-sensitive`` @@ -626,7 +629,7 @@ constraint to test the validity of the email domain:: { public function testEmail() { - $validator = ... + $validator = ...; $constraint = new Email(['checkMX' => true]); $result = $validator->validate('foo@example.com', $constraint); @@ -635,7 +638,7 @@ constraint to test the validity of the email domain:: } } -In order to avoid making a real network connection, add the ``@dns-sensitive`` +In order to avoid making a real network connection, add the ``@group dns-sensitive`` annotation to the class and use the ``DnsMock::withMockedHosts()`` to configure the data you expect to get for the given hosts:: @@ -651,7 +654,7 @@ the data you expect to get for the given hosts:: { DnsMock::withMockedHosts(['example.com' => [['type' => 'MX']]]); - $validator = ... + $validator = ...; $constraint = new Email(['checkMX' => true]); $result = $validator->validate('foo@example.com', $constraint); @@ -786,10 +789,16 @@ namespaces in the ``phpunit.xml`` file, as done for example in the Under the hood, a PHPUnit listener injects the mocked functions in the tested classes' namespace. In order to work as expected, the listener has to run before -the tested class ever runs. By default, the mocked functions are created when the -annotation are found and the corresponding tests are run. Depending on how your -tests are constructed, this might be too late. In this case, you will need to declare -the namespaces of the tested classes in your ``phpunit.xml.dist``. +the tested class ever runs. + +By default, the mocked functions are created when the annotation are found and +the corresponding tests are run. Depending on how your tests are constructed, +this might be too late. + +You can either: + +* Declare the namespaces of the tested classes in your ``phpunit.xml.dist``; +* Register the namespaces at the end of the ``config/bootstrap.php`` file. .. code-block:: xml @@ -805,6 +814,16 @@ the namespaces of the tested classes in your ``phpunit.xml.dist``. +:: + + // config/bootstrap.php + use Symfony\Bridge\PhpUnit\ClockMock; + + // ... + if ('test' === $_SERVER['APP_ENV']) { + ClockMock::register('Acme\\MyClassTest\\'); + } + Modified PHPUnit script ----------------------- @@ -825,18 +844,6 @@ configured by the ``SYMFONY_PHPUNIT_DIR`` env var, or in the same directory as the ``simple-phpunit`` if it is not provided. It's also possible to set this env var in the ``phpunit.xml.dist`` file. -By default, these are the PHPUnit versions used depending on the installed PHP versions: - -===================== =============================== -Installed PHP version PHPUnit version used by default -===================== =============================== -PHP <= 5.5 PHPUnit 4.8 -PHP 5.6 PHPUnit 5.7 -PHP 7.0 PHPUnit 6.5 -PHP 7.1 PHPUnit 7.5 -PHP >= 7.2 PHPUnit 8.3 -===================== =============================== - If you have installed the bridge through Composer, you can run it by calling e.g.: .. code-block:: terminal @@ -845,7 +852,7 @@ If you have installed the bridge through Composer, you can run it by calling e.g .. tip:: - It's possible to change the base version of PHPUnit by setting the + It's possible to change the PHPUnit version by setting the ``SYMFONY_PHPUNIT_VERSION`` env var in the ``phpunit.xml.dist`` file (e.g. ````). This is the preferred method as it can be committed to your version control repository. diff --git a/components/process.rst b/components/process.rst index 7136dcc1048..d8d585fe987 100644 --- a/components/process.rst +++ b/components/process.rst @@ -110,7 +110,7 @@ with a non-zero code):: Using Features From the OS Shell -------------------------------- -Using array of arguments is the recommended way to define commands. This +Using an array of arguments is the recommended way to define commands. This saves you from any escaping and allows sending signals seamlessly (e.g. to stop processes while they run):: @@ -325,7 +325,7 @@ provides the :class:`Symfony\\Component\\Process\\InputStream` class:: echo $process->getOutput(); The :method:`Symfony\\Component\\Process\\InputStream::write` method accepts scalars, -stream resources or ``Traversable`` objects as argument. As shown in the above example, +stream resources or ``Traversable`` objects as arguments. As shown in the above example, you need to explicitly call the :method:`Symfony\\Component\\Process\\InputStream::close` method when you are done writing to the standard input of the subprocess. @@ -352,6 +352,35 @@ The input of a process can also be defined using `PHP streams`_:: // will echo: 'foobar' echo $process->getOutput(); +Using TTY and PTY Modes +----------------------- + +All examples above show that your program has control over the input of a +process (using ``setInput()``) and the output from that process (using +``getOutput()``). The Process component has two special modes that tweak +the relationship between your program and the process: teletype (tty) and +pseudo-teletype (pty). + +In TTY mode, you connect the input and output of the process to the input +and output of your program. This allows for instance to open an editor like +Vim or Nano as a process. You enable TTY mode by calling +:method:`Symfony\\Component\\Process\\Process::setTty`:: + + $process = new Process(['vim']); + $process->setTty(true); + $process->run(); + + // As the output is connected to the terminal, it is no longer possible + // to read or modify the output from the process! + dump($process->getOutput()); // null + +In PTY mode, your program behaves as a terminal for the process instead of +a plain input and output. Some programs behave differently when +interacting with a real terminal instead of another program. For instance, +some programs prompt for a password when talking with a terminal. Use +:method:`Symfony\\Component\\Process\\Process::setPty` to enable this +mode. + Stopping a Process ------------------ @@ -359,7 +388,7 @@ Any asynchronous process can be stopped at any time with the :method:`Symfony\\Component\\Process\\Process::stop` method. This method takes two arguments: a timeout and a signal. Once the timeout is reached, the signal is sent to the running process. The default signal sent to a process is ``SIGKILL``. -Please read the :ref:`signal documentation below` +Please read the :ref:`signal documentation below ` to find out more about signal handling in the Process component:: $process = new Process(['ls', '-lsa']); @@ -483,10 +512,31 @@ Use :method:`Symfony\\Component\\Process\\Process::disableOutput` and However, it is possible to pass a callback to the ``start``, ``run`` or ``mustRun`` methods to handle process output in a streaming fashion. +Finding an Executable +--------------------- + +The Process component provides a utility class called +:class:`Symfony\\Component\\Process\\ExecutableFinder` which finds +and returns the absolute path of an executable:: + + use Symfony\Component\Process\ExecutableFinder; + + $executableFinder = new ExecutableFinder(); + $chromedriverPath = $executableFinder->find('chromedriver'); + // $chromedriverPath = '/usr/local/bin/chromedriver' (the result will be different on your computer) + +The :method:`Symfony\\Component\\Process\\ExecutableFinder::find` method also takes extra parameters to specify a default value +to return and extra directories where to look for the executable:: + + use Symfony\Component\Process\ExecutableFinder; + + $executableFinder = new ExecutableFinder(); + $chromedriverPath = $executableFinder->find('chromedriver', '/path/to/chromedriver', ['local-bin/']); + Finding the Executable PHP Binary --------------------------------- -This component also provides a utility class called +This component also provides a special utility class called :class:`Symfony\\Component\\Process\\PhpExecutableFinder` which returns the absolute path of the executable PHP binary available on your server:: diff --git a/components/property_access.rst b/components/property_access.rst index 1ff482f5216..f9375516cf8 100644 --- a/components/property_access.rst +++ b/components/property_access.rst @@ -72,7 +72,7 @@ You can also use multi dimensional arrays:: ], [ 'first_name' => 'Ryan', - ] + ], ]; var_dump($propertyAccessor->getValue($persons, '[0][first_name]')); // 'Wouter' @@ -223,7 +223,7 @@ The ``getValue()`` method can also use the magic ``__get()`` method:: Magic ``__call()`` Method ~~~~~~~~~~~~~~~~~~~~~~~~~ -At last, ``getValue()`` can use the magic ``__call()`` method, but you need to +Lastly, ``getValue()`` can use the magic ``__call()`` method, but you need to enable this feature by using :class:`Symfony\\Component\\PropertyAccess\\PropertyAccessorBuilder`:: // ... @@ -237,9 +237,7 @@ enable this feature by using :class:`Symfony\\Component\\PropertyAccess\\Propert { $property = lcfirst(substr($name, 3)); if ('get' === substr($name, 0, 3)) { - return isset($this->children[$property]) - ? $this->children[$property] - : null; + return $this->children[$property] ?? null; } elseif ('set' === substr($name, 0, 3)) { $value = 1 == count($args) ? $args[0] : null; $this->children[$property] = $value; @@ -334,9 +332,7 @@ see `Enable other Features`_:: { $property = lcfirst(substr($name, 3)); if ('get' === substr($name, 0, 3)) { - return isset($this->children[$property]) - ? $this->children[$property] - : null; + return $this->children[$property] ?? null; } elseif ('set' === substr($name, 0, 3)) { $value = 1 == count($args) ? $args[0] : null; $this->children[$property] = $value; @@ -394,7 +390,7 @@ properties through *adder* and *remover* methods:: The PropertyAccess component checks for methods called ``add()`` and ``remove()``. Both methods must be defined. For instance, in the previous example, the component looks for the ``addChild()`` -and ``removeChild()`` methods to access to the ``children`` property. +and ``removeChild()`` methods to access the ``children`` property. `The Inflector component`_ is used to find the singular of a property name. If available, *adder* and *remover* methods have priority over a *setter* method. diff --git a/components/property_info.rst b/components/property_info.rst index d8e3a693b12..8d86663c140 100644 --- a/components/property_info.rst +++ b/components/property_info.rst @@ -208,7 +208,7 @@ strings:: Example Result -------------- string(79): - These is the subsequent paragraph in the DocComment. + This is the subsequent paragraph in the DocComment. It can span multiple lines. */ diff --git a/components/security/authentication.rst b/components/security/authentication.rst index 4cb87975e08..2a1582b0636 100644 --- a/components/security/authentication.rst +++ b/components/security/authentication.rst @@ -276,15 +276,15 @@ Authentication Events The security component provides the following authentication events: -=============================== ================================================================= ============================================================================== -Name Event Constant Argument Passed to the Listener -=============================== ================================================================= ============================================================================== -security.authentication.success ``AuthenticationEvents::AUTHENTICATION_SUCCESS`` :class:`Symfony\\Component\\Security\\Core\\Event\\AuthenticationSuccessEvent` -security.authentication.failure ``AuthenticationEvents::AUTHENTICATION_FAILURE`` :class:`Symfony\\Component\\Security\\Core\\Event\\AuthenticationFailureEvent` -security.interactive_login ``SecurityEvents::INTERACTIVE_LOGIN`` :class:`Symfony\\Component\\Security\\Http\\Event\\InteractiveLoginEvent` -security.switch_user ``SecurityEvents::SWITCH_USER`` :class:`Symfony\\Component\\Security\\Http\\Event\\SwitchUserEvent` -security.logout_on_change ``Symfony\Component\Security\Http\Event\DeauthenticatedEvent`` :class:`Symfony\\Component\\Security\\Http\\Event\\DeauthenticatedEvent` -=============================== ================================================================= ============================================================================== +=============================== ======================================================================== ============================================================================== +Name Event Constant Argument Passed to the Listener +=============================== ======================================================================== ============================================================================== +security.authentication.success ``AuthenticationEvents::AUTHENTICATION_SUCCESS`` :class:`Symfony\\Component\\Security\\Core\\Event\\AuthenticationSuccessEvent` +security.authentication.failure ``AuthenticationEvents::AUTHENTICATION_FAILURE`` :class:`Symfony\\Component\\Security\\Core\\Event\\AuthenticationFailureEvent` +security.interactive_login ``SecurityEvents::INTERACTIVE_LOGIN`` :class:`Symfony\\Component\\Security\\Http\\Event\\InteractiveLoginEvent` +security.switch_user ``SecurityEvents::SWITCH_USER`` :class:`Symfony\\Component\\Security\\Http\\Event\\SwitchUserEvent` +security.logout_on_change ``Symfony\Component\Security\Http\Event\DeauthenticatedEvent::class`` :class:`Symfony\\Component\\Security\\Http\\Event\\DeauthenticatedEvent` +=============================== ======================================================================== ============================================================================== Authentication Success and Failure Events ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -292,7 +292,7 @@ Authentication Success and Failure Events When a provider authenticates the user, a ``security.authentication.success`` event is dispatched. But beware - this event may fire, for example, on *every* request if you have session-based authentication, if ``always_authenticate_before_granting`` -is enabled or if token is not authenticated before AccessListener is invoked. +is enabled or if the token is not authenticated before AccessListener is invoked. See ``security.interactive_login`` below if you need to do something when a user *actually* logs in. When a provider attempts authentication but fails (i.e. throws an ``AuthenticationException``), @@ -317,7 +317,7 @@ The ``security.switch_user`` event is triggered every time you activate the ``switch_user`` firewall listener. The ``Symfony\Component\Security\Http\Event\DeauthenticatedEvent`` event is triggered when a token has been deauthenticated -because of a user change, it can help you doing some clean-up task when a logout has been triggered. +because of a user change. It can help you perform clean-up tasks. .. versionadded:: 4.3 diff --git a/components/security/authorization.rst b/components/security/authorization.rst index f6a8776aa7b..dee133d53d5 100644 --- a/components/security/authorization.rst +++ b/components/security/authorization.rst @@ -87,7 +87,7 @@ 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: -``vote(TokenInterface $token, $object, array $attributes)`` +``vote(TokenInterface $token, $subject, 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`` @@ -188,7 +188,7 @@ expressions have access to a number of $expression = new Expression( '"ROLE_ADMIN" in roles or (not is_anonymous() and user.isSuperAdmin())' - ) + ); $vote = $expressionVoter->vote($token, $object, [$expression]); diff --git a/components/serializer.rst b/components/serializer.rst index 345642c9770..6aae1c72049 100644 --- a/components/serializer.rst +++ b/components/serializer.rst @@ -74,20 +74,20 @@ exists in your project:: class Person { - private $age; - private $name; - private $sportsperson; - private $createdAt; + private int $age; + private string $name; + private bool $sportsperson; + private ?\DateTime $createdAt; // Getters - public function getName() + public function getAge(): int { - return $this->name; + return $this->age; } - public function getAge() + public function getName(): string { - return $this->age; + return $this->name; } public function getCreatedAt() @@ -96,28 +96,28 @@ exists in your project:: } // Issers - public function isSportsperson() + public function isSportsperson(): bool { return $this->sportsperson; } // Setters - public function setName($name) + public function setAge(int $age): void { - $this->name = $name; + $this->age = $age; } - public function setAge($age) + public function setName(string $name): void { - $this->age = $age; + $this->name = $name; } - public function setSportsperson($sportsperson) + public function setSportsperson(bool $sportsperson): void { $this->sportsperson = $sportsperson; } - public function setCreatedAt($createdAt) + public function setCreatedAt(\DateTime $createdAt = null): void { $this->createdAt = $createdAt; } @@ -174,6 +174,8 @@ when this happens, set the ``AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES`` contex ``false`` and provide an object that implements ``ClassMetadataFactoryInterface`` when constructing the normalizer:: + use App\Model\Person; + $data = << foo @@ -189,7 +191,7 @@ when constructing the normalizer:: // this will throw a Symfony\Component\Serializer\Exception\ExtraAttributesException // because "city" is not an attribute of the Person class - $person = $serializer->deserialize($data, 'App\Model\Person', 'xml', [ + $person = $serializer->deserialize($data, Person::class, 'xml', [ AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false, ]); @@ -331,7 +333,7 @@ Then, create your groups definition: .. code-block:: xml - + + serialize(new Person('Kévin'), 'json'); // {"customer_name": "Kévin"} Serializing Boolean Attributes @@ -637,8 +639,8 @@ If you are using isser methods (methods prefixed by ``is``, like ``App\Model\Person::isSportsperson()``), the Serializer component will automatically detect and use it to serialize related attributes. -The ``ObjectNormalizer`` also takes care of methods starting with ``has``, ``add`` -and ``remove``. +The ``ObjectNormalizer`` also takes care of methods starting with ``has`` and +``get``. Using Callbacks to Serialize Properties with Object Instances ------------------------------------------------------------- @@ -691,7 +693,24 @@ When serializing, you can set a callback to format a specific object property:: Normalizers ----------- -There are several types of normalizers available: +Normalizers turn **objects** into **arrays** and vice versa. They implement +:class:`Symfony\\Component\\Serializer\\Normalizer\\NormalizerInterface` for +normalizing (object to array) and +:class:`Symfony\\Component\\Serializer\\Normalizer\\DenormalizerInterface` for +denormalizing (array to object). + +Normalizers are enabled in the serializer passing them as its first argument:: + + use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; + use Symfony\Component\Serializer\Serializer; + + $normalizers = [new ObjectNormalizer()]; + $serializer = new Serializer($normalizers, []); + +Built-in Normalizers +~~~~~~~~~~~~~~~~~~~~ + +The Serializer component provides several built-in normalizers: :class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer` This normalizer leverages the :doc:`PropertyAccess Component ` @@ -719,7 +738,8 @@ There are several types of normalizers available: :class:`Symfony\\Component\\Serializer\\Normalizer\\PropertyNormalizer` This normalizer directly reads and writes public properties as well as **private and protected** properties (from both the class and all of its - parent classes). It supports calling the constructor during the denormalization process. + parent classes) by using `PHP reflection`_. It supports calling the constructor + during the denormalization process. Objects are normalized to a map of property names to property values. @@ -750,7 +770,7 @@ There are several types of normalizers available: The ``DateTimeZoneNormalizer`` was introduced in Symfony 4.3. :class:`Symfony\\Component\\Serializer\\Normalizer\\DataUriNormalizer` - This normalizer converts :phpclass:`SplFileInfo` objects into a data URI + 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` @@ -762,9 +782,79 @@ There are several types of normalizers available: :class:`Symfony\\Component\\Validator\\ConstraintViolationListInterface` into a list of errors according to the `RFC 7807`_ standard. + .. versionadded:: 4.1 + + The ``ConstraintViolationListNormalizer`` was introduced in Symfony 4.1. + :class:`Symfony\\Component\\Serializer\\Normalizer\\ProblemNormalizer` Normalizes errors according to the API Problem spec `RFC 7807`_. + .. versionadded:: 4.4 + + The ``ProblemNormalizer`` was introduced in Symfony 4.4. + +:class:`Symfony\\Component\\Serializer\\Normalizer\\CustomNormalizer` + Normalizes a PHP object using an object that implements :class:`Symfony\\Component\\Serializer\\Normalizer\\NormalizableInterface`. + +.. note:: + + You can also create your own Normalizer to use another structure. Read more at + :doc:`/serializer/custom_normalizer`. + +Certain normalizers are enabled by default when using the Serializer component +in a Symfony application, additional ones can be enabled by tagging them with +:ref:`serializer.normalizer `. + +Here is an example of how to enable the built-in +:class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer`, a +faster alternative to the +:class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer`: + +.. configuration-block:: + + .. code-block:: yaml + + # config/services.yaml + services: + # ... + + get_set_method_normalizer: + class: Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer + tags: [serializer.normalizer] + + .. code-block:: xml + + + + + + + + + + + + + + .. code-block:: php + + // config/services.php + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; + + return static function (ContainerConfigurator $container) { + $container->services() + // ... + ->set('get_set_method_normalizer', GetSetMethodNormalizer::class) + ->tag('serializer.normalizer') + ; + }; + .. _component-serializer-encoders: Encoders @@ -803,6 +893,11 @@ The Serializer component provides several built-in encoders: :class:`Symfony\\Component\\Serializer\\Encoder\\CsvEncoder` This encoder encodes and decodes data in `CSV`_. +.. note:: + + You can also create your own Encoder to use another structure. Read more at + :doc:`/serializer/custom_encoders`. + All these encoders are enabled by default when using the Serializer component in a Symfony application. @@ -810,13 +905,55 @@ The ``JsonEncoder`` ~~~~~~~~~~~~~~~~~~~ The ``JsonEncoder`` encodes to and decodes from JSON strings, based on the PHP -:phpfunction:`json_encode` and :phpfunction:`json_decode` functions. +:phpfunction:`json_encode` and :phpfunction:`json_decode` functions. It can be +useful to modify how these functions operate in certain instances by providing +options such as ``JSON_PRESERVE_ZERO_FRACTION``. You can use the serialization +context to pass in these options using the key ``json_encode_options`` or +``json_decode_options`` respectively:: + + $this->serializer->serialize($data, 'json', ['json_encode_options' => \JSON_PRESERVE_ZERO_FRACTION]); The ``CsvEncoder`` ~~~~~~~~~~~~~~~~~~~ The ``CsvEncoder`` encodes to and decodes from CSV. +The ``CsvEncoder`` Context Options +.................................. + +The ``encode()`` method defines a third optional parameter called ``context`` +which defines the configuration options for the CsvEncoder an associative array:: + + $csvEncoder->encode($array, 'csv', $context); + +These are the options available: + +======================= ===================================================== ========================== +Option Description Default +======================= ===================================================== ========================== +``csv_delimiter`` Sets the field delimiter separating values (one ``,`` + character only) +``csv_enclosure`` Sets the field enclosure (one character only) ``"`` +``csv_escape_char`` Sets the escape character (at most one character) empty string +``csv_key_separator`` Sets the separator for array's keys during its ``.`` + flattening +``csv_headers`` Sets the order of the header and data columns + E.g.: if ``$data = ['c' => 3, 'a' => 1, 'b' => 2]`` + and ``$options = ['csv_headers' => ['a', 'b', 'c']]`` + then ``serialize($data, 'csv', $options)`` returns + ``a,b,c\n1,2,3`` ``[]``, inferred from input data's keys +``csv_escape_formulas`` Escapes fields containing formulas by prepending them ``false`` + with a ``\t`` character +``as_collection`` Always returns results as a collection, even if only ``true`` + one line is decoded. +``no_headers`` Disables header in the encoded CSV ``false`` +``output_utf8_bom`` Outputs special `UTF-8 BOM`_ along with encoded data ``false`` +======================= ===================================================== ========================== + +.. versionadded:: 4.4 + + The ``output_utf8_bom`` option was introduced in Symfony 4.4. + The ``XmlEncoder`` ~~~~~~~~~~~~~~~~~~ @@ -826,17 +963,31 @@ For example, take an object normalized as following:: ['foo' => [1, 2], 'bar' => true]; -The ``XmlEncoder`` will encode this object like that:: +The ``XmlEncoder`` will encode this object like that: - +.. code-block:: xml + + 1 2 1 -Be aware that this encoder will consider keys beginning with ``@`` as attributes, and will use -the key ``#comment`` for encoding XML comments:: +The special ``#`` key can be used to define the data of a node:: + + ['foo' => ['@bar' => 'value', '#' => 'baz']]; + + // is encoded as follows: + // + // + // + // baz + // + // + +Furthermore, keys beginning with ``@`` will be considered attributes, and +the key ``#comment`` can be used for encoding XML comments:: $encoder = new XmlEncoder(); $encoder->encode([ @@ -863,12 +1014,102 @@ always as a collection. changed with the optional ``$encoderIgnoredNodeTypes`` argument of the ``XmlEncoder`` class constructor. +The ``XmlEncoder`` Context Options +.................................. + +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: + +============================== ================================================= ========================== +Option Description Default +============================== ================================================= ========================== +``xml_format_output`` If set to true, formats the generated XML with ``false`` + line breaks and indentation +``xml_version`` Sets the XML version attribute ``1.1`` +``xml_encoding`` Sets the XML encoding attribute ``utf-8`` +``xml_standalone`` Adds standalone attribute in the generated XML ``true`` +``xml_type_cast_attributes`` This provides the ability to forget the attribute ``true`` + type casting +``xml_root_node_name`` Sets the root node name ``response`` +``as_collection`` Always returns results as a collection, even if ``false`` + only one line is decoded +``decoder_ignored_node_types`` Array of node types (`DOM XML_* constants`_) ``[\XML_PI_NODE, \XML_COMMENT_NODE]`` + to be ignored while decoding +``encoder_ignored_node_types`` Array of node types (`DOM XML_* constants`_) ``[]`` + to be ignored while encoding +``load_options`` XML loading `options with libxml`_ ``\LIBXML_NONET | \LIBXML_NOBLANKS`` +``remove_empty_tags`` If set to true, removes all empty tags in the ``false`` + generated XML +============================== ================================================= ========================== + +.. versionadded:: 4.2 + + The ``decoder_ignored_node_types`` and ``encoder_ignored_node_types`` + options were introduced in Symfony 4.2. + +Example with custom ``context``:: + + use Symfony\Component\Serializer\Encoder\XmlEncoder; + + // create encoder with specified options as new default settings + $xmlEncoder = new XmlEncoder(['xml_format_output' => true]); + + $data = [ + 'id' => 'IDHNQIItNyQ', + 'date' => '2019-10-24', + ]; + + // encode with default context + $xmlEncoder->encode($data, 'xml'); + // outputs: + // + // + // IDHNQIItNyQ + // 2019-10-24 + // + + // encode with modified context + $xmlEncoder->encode($data, 'xml', [ + 'xml_root_node_name' => 'track', + 'encoder_ignored_node_types' => [ + \XML_PI_NODE, // removes XML declaration (the leading xml tag) + ], + ]); + // outputs: + // + // IDHNQIItNyQ + // 2019-10-24 + // + The ``YamlEncoder`` ~~~~~~~~~~~~~~~~~~~ This encoder requires the :doc:`Yaml Component ` and transforms from and to Yaml. +The ``YamlEncoder`` Context Options +................................... + +The ``encode()`` method, like other encoder, uses ``context`` to set +configuration options for the YamlEncoder an associative array:: + + $yamlEncoder->encode($array, 'yaml', $context); + +These are the options available: + +=============== ======================================================== ========================== +Option Description Default +=============== ======================================================== ========================== +``yaml_inline`` The level where you switch to inline YAML ``0`` +``yaml_indent`` The level of indentation (used internally) ``0`` +``yaml_flags`` A bit field of ``Yaml::DUMP_*`` / ``PARSE_*`` constants ``0`` + to customize the encoding / decoding YAML string +=============== ======================================================== ========================== + Skipping ``null`` Values ------------------------ @@ -1046,7 +1287,7 @@ Here, we set it to 2 for the ``$child`` property: .. code-block:: xml - + - - 1 - 2 - 1 - - -The array keys beginning with ``@`` are considered XML attributes:: - - ['foo' => ['@bar' => 'value']]; - - // is encoded as follows: - // - // - // - // - -Use the special ``#`` key to define the data of a node:: - - ['foo' => ['@bar' => 'value', '#' => 'baz']]; - - // is encoded as follows: - // - // - // baz - // - -Context -~~~~~~~ - -The ``encode()`` method defines a third optional parameter called ``context`` -which defines the configuration options for the XmlEncoder an associative array:: - - $xmlEncoder->encode($array, 'xml', $context); - -These are the options available: - -``xml_format_output`` - If set to true, formats the generated XML with line breaks and indentation. - -``xml_version`` - Sets the XML version attribute (default: ``1.1``). - -``xml_encoding`` - Sets the XML encoding attribute (default: ``utf-8``). - -``xml_standalone`` - Adds standalone attribute in the generated XML (default: ``true``). - -``xml_root_node_name`` - Sets the root node name (default: ``response``). - -``remove_empty_tags`` - If set to true, removes all empty tags in the generated XML (default: ``false``). - -The ``CsvEncoder`` ------------------- - -This encoder transforms arrays into CSV and vice versa. - -Context -~~~~~~~ - -The ``encode()`` method defines a third optional parameter called ``context`` -which defines the configuration options for the CsvEncoder an associative array:: - - $csvEncoder->encode($array, 'csv', $context); - -These are the options available: - -``csv_delimiter`` - Sets the field delimiter separating values (one character only, default: ``,``). - -``csv_enclosure`` - Sets the field enclosure (one character only, default: ``"``). - -``csv_escape_char`` - Sets the escape character (at most one character, default: empty string). - -``csv_key_separator`` - Sets the separator for array's keys during its flattening (default: ``.``). - -``csv_headers`` - Sets the headers for the data (default: ``[]``, inferred from input data's keys). - -``csv_escape_formulas`` - Escapes fields containg formulas by prepending them with a ``\t`` character (default: ``false``). - -``as_collection`` - Always returns results as a collection, even if only one line is decoded. - -``no_headers`` - Disables header in the encoded CSV (default: ``false``). - -``output_utf8_bom`` - Outputs special `UTF-8 BOM`_ along with encoded data (default: ``false``). - -.. versionadded:: 4.4 - - The ``output_utf8_bom`` option was introduced in Symfony 4.4. - Handling Constructor Arguments ------------------------------ @@ -1461,7 +1589,7 @@ and ``BitBucketCodeRepository`` classes: * "bitbucket"="App\BitBucketCodeRepository" * }) */ - interface CodeRepository + abstract class CodeRepository { // ... } @@ -1477,7 +1605,7 @@ and ``BitBucketCodeRepository`` classes: .. code-block:: xml - + deserialize($serialized, CodeRepository::class, 'json'); // instanceof GitHubCodeRepository -Performance ------------ - -To figure which normalizer (or denormalizer) must be used to handle an object, -the :class:`Symfony\\Component\\Serializer\\Serializer` class will call the -:method:`Symfony\\Component\\Serializer\\Normalizer\\NormalizerInterface::supportsNormalization` -(or :method:`Symfony\\Component\\Serializer\\Normalizer\\DenormalizerInterface::supportsDenormalization`) -of all registered normalizers (or denormalizers) in a loop. - -The result of these methods can vary depending on the object to serialize, the -format and the context. That's why the result **is not cached** by default and -can result in a significant performance bottleneck. - -However, most normalizers (and denormalizers) always return the same result when -the object's type and the format are the same, so the result can be cached. To -do so, make those normalizers (and denormalizers) implement the -:class:`Symfony\\Component\\Serializer\\Normalizer\\CacheableSupportsMethodInterface` -and return ``true`` when -:method:`Symfony\\Component\\Serializer\\Normalizer\\CacheableSupportsMethodInterface::hasCacheableSupportsMethod` -is called. - -.. note:: - - All built-in :ref:`normalizers and denormalizers ` - as well the ones included in `API Platform`_ natively implement this interface. - Learn more ---------- @@ -1548,6 +1650,8 @@ Learn more .. _`PSR-1 standard`: https://www.php-fig.org/psr/psr-1/ .. _`JMS serializer`: https://github.com/schmittjoh/serializer .. _RFC3339: https://tools.ietf.org/html/rfc3339#section-5.8 +.. _`options with libxml`: https://www.php.net/manual/en/libxml.constants.php +.. _`DOM XML_* constants`: https://www.php.net/manual/en/dom.constants.php .. _JSON: http://www.json.org/ .. _XML: https://www.w3.org/XML/ .. _YAML: https://yaml.org/ @@ -1557,3 +1661,5 @@ Learn more .. _`Value Objects`: https://en.wikipedia.org/wiki/Value_object .. _`API Platform`: https://api-platform.com .. _`list of PHP timezones`: https://www.php.net/manual/en/timezones.php +.. _`PHP reflection`: https://php.net/manual/en/book.reflection.php +.. _`data URI`: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs diff --git a/components/stopwatch.rst b/components/stopwatch.rst deleted file mode 100644 index e6e11d9c53e..00000000000 --- a/components/stopwatch.rst +++ /dev/null @@ -1,126 +0,0 @@ -.. index:: - single: Stopwatch - single: Components; Stopwatch - -The Stopwatch Component -======================= - - The Stopwatch component provides a way to profile code. - -Installation ------------- - -.. code-block:: terminal - - $ composer require symfony/stopwatch - -.. include:: /components/require_autoload.rst.inc - -Usage ------ - -The Stopwatch component provides a consistent way to measure execution -time of certain parts of code so that you don't constantly have to parse -:phpfunction:`microtime` by yourself. Instead, use the -:class:`Symfony\\Component\\Stopwatch\\Stopwatch` class:: - - use Symfony\Component\Stopwatch\Stopwatch; - - $stopwatch = new Stopwatch(); - - // starts event named 'eventName' - $stopwatch->start('eventName'); - - // ... run your code here - - $event = $stopwatch->stop('eventName'); - // you can convert $event into a string for a quick summary - // e.g. (string) $event = '4.50 MiB - 26 ms' - -The :class:`Symfony\\Component\\Stopwatch\\StopwatchEvent` object can be retrieved -from the :method:`Symfony\\Component\\Stopwatch\\Stopwatch::start`, -:method:`Symfony\\Component\\Stopwatch\\Stopwatch::stop`, -:method:`Symfony\\Component\\Stopwatch\\Stopwatch::lap` and -:method:`Symfony\\Component\\Stopwatch\\Stopwatch::getEvent` methods. -The latter should be used when you need to retrieve the duration of an event -while it is still running. - -.. tip:: - - By default, the stopwatch truncates any sub-millisecond time measure to ``0``, - so you can't measure microseconds or nanoseconds. If you need more precision, - pass ``true`` to the ``Stopwatch`` class constructor to enable full precision:: - - $stopwatch = new Stopwatch(true); - -The stopwatch can be reset to its original state at any given time with the -:method:`Symfony\\Component\\Stopwatch\\Stopwatch::reset` method, which deletes -all the data measured so far. - -You can also provide a category name to an event:: - - $stopwatch->start('eventName', 'categoryName'); - -You can consider categories as a way of tagging events. For example, the -Symfony Profiler tool uses categories to nicely color-code different events. - -.. tip:: - - Read :ref:`this article ` to learn more about - integrating the Stopwatch component into the Symfony profiler. - -Periods -------- - -As you know from the real world, all stopwatches come with two buttons: -one to start and stop the stopwatch, and another to measure the lap time. -This is exactly what the :method:`Symfony\\Component\\Stopwatch\\Stopwatch::lap` -method does:: - - $stopwatch = new Stopwatch(); - // starts event named 'foo' - $stopwatch->start('foo'); - // ... some code goes here - $stopwatch->lap('foo'); - // ... some code goes here - $stopwatch->lap('foo'); - // ... some other code goes here - $event = $stopwatch->stop('foo'); - -Lap information is stored as "periods" within the event. To get lap information -call:: - - $event->getPeriods(); - -In addition to periods, you can get other useful information from the event object. -For example:: - - $event->getCategory(); // returns the category the event was started in - $event->getOrigin(); // returns the event start time in milliseconds - $event->ensureStopped(); // stops all periods not already stopped - $event->getStartTime(); // returns the start time of the very first period - $event->getEndTime(); // returns the end time of the very last period - $event->getDuration(); // returns the event duration, including all periods - $event->getMemory(); // returns the max memory usage of all periods - -Sections --------- - -Sections are a way to logically split the timeline into groups. You can see -how Symfony uses sections to nicely visualize the framework lifecycle in the -Symfony Profiler tool. Here is a basic usage example using sections:: - - $stopwatch = new Stopwatch(); - - $stopwatch->openSection(); - $stopwatch->start('parsing_config_file', 'filesystem_operations'); - $stopwatch->stopSection('routing'); - - $events = $stopwatch->getSectionEvents('routing'); - -You can reopen a closed section by calling the :method:`Symfony\\Component\\Stopwatch\\Stopwatch::openSection` -method and specifying the id of the section to be reopened:: - - $stopwatch->openSection('routing'); - $stopwatch->start('building_config_tree'); - $stopwatch->stopSection('routing'); diff --git a/components/validator/resources.rst b/components/validator/resources.rst index 1455c2518bc..4de84a7cb49 100644 --- a/components/validator/resources.rst +++ b/components/validator/resources.rst @@ -155,7 +155,7 @@ implement the PSR-6 interface :class:`Psr\\Cache\\CacheItemPoolInterface`):: $validator = Validation::createValidatorBuilder() // ... add loaders - ->setMappingCache(new SomePsr6Cache()); + ->setMappingCache(new SomePsr6Cache()) ->getValidator(); .. versionadded:: 4.4 diff --git a/components/var_dumper.rst b/components/var_dumper.rst index a607ddeb59b..1202791b97c 100644 --- a/components/var_dumper.rst +++ b/components/var_dumper.rst @@ -131,22 +131,27 @@ the :ref:`dump_destination option ` of the - - + http://symfony.com/schema/dic/debug + https://symfony.com/schema/dic/debug/debug-1.0.xsd" + > .. code-block:: php // config/packages/debug.php - $container->loadFromExtension('debug', [ - 'dump_destination' => 'tcp://%env(VAR_DUMPER_SERVER)%', - ]); + namespace Symfony\Component\DependencyInjection\Loader\Configurator; + + return static function (ContainerConfigurator $container) { + $container->extension('debug', [ + 'dump_destination' => 'tcp://%env(VAR_DUMPER_SERVER)%', + ]); + }; Outside a Symfony application, use the :class:`Symfony\\Component\\VarDumper\\Dumper\\ServerDumper` class:: diff --git a/components/var_dumper/advanced.rst b/components/var_dumper/advanced.rst index 0f429c52012..ded04cca902 100644 --- a/components/var_dumper/advanced.rst +++ b/components/var_dumper/advanced.rst @@ -200,7 +200,7 @@ method:: $dumper->dump($var, $output, [ // 1 and 160 are the default values for these options 'maxDepth' => 1, - 'maxStringLength' => 160 + 'maxStringLength' => 160, ]); The output format of a dumper can be fine tuned by the two flags @@ -223,7 +223,7 @@ next to its content:: $varCloner = new VarCloner(); $var = ['test']; - + $dumper = new CliDumper(); echo $dumper->dump($varCloner->cloneVar($var), true); @@ -248,7 +248,7 @@ similar to PHP's short array notation:: $varCloner = new VarCloner(); $var = ['test']; - + $dumper = new CliDumper(); echo $dumper->dump($varCloner->cloneVar($var), true); @@ -273,7 +273,7 @@ using the logical OR operator ``|``:: $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); diff --git a/components/var_exporter.rst b/components/var_exporter.rst index bf8f9b1f85a..810cc271a2b 100644 --- a/components/var_exporter.rst +++ b/components/var_exporter.rst @@ -28,7 +28,7 @@ PHP code, similar to PHP's :phpfunction:`var_export` function:: $exported = VarExporter::export($someVariable); // store the $exported data in some file or cache system for later reuse - $data = file_put_contents('exported.php', $exported); + $data = file_put_contents('exported.php', ' [$object1, $info1, $object2, $info2...] + "\0" => [$object1, $info1, $object2, $info2...], ]); // creates an ArrayObject populated with $inputArray $theObject = Instantiator::instantiate(ArrayObject::class, [ - "\0" => [$inputArray] + "\0" => [$inputArray], ]); .. _`OPcache`: https://www.php.net/opcache diff --git a/components/workflow.rst b/components/workflow.rst index a35602f1ac2..67b00730b69 100644 --- a/components/workflow.rst +++ b/components/workflow.rst @@ -68,8 +68,8 @@ are trying to use it with:: use Symfony\Component\Workflow\Registry; use Symfony\Component\Workflow\SupportStrategy\InstanceOfSupportStrategy; - $blogPostWorkflow = ... - $newsletterWorkflow = ... + $blogPostWorkflow = ...; + $newsletterWorkflow = ...; $registry = new Registry(); $registry->addWorkflow($blogPostWorkflow, new InstanceOfSupportStrategy(BlogPost::class)); @@ -94,6 +94,20 @@ you can retrieve a workflow from it and use it as follows:: $workflow->can($blogPost, 'publish'); // True $workflow->getEnabledTransitions($blogPost); // $blogPost can perform transition "publish" or "reject" +Initialization +-------------- + +If the property of your object is ``null`` and you want to set it with the +``initial_marking`` from the configuration, you can call the ``getMarking()`` +method to initialize the object property:: + + // ... + $blogPost = new BlogPost(); + $workflow = $registry->get($blogPost); + + // initiate workflow + $workflow->getMarking($blogPost); + Learn more ---------- diff --git a/components/yaml.rst b/components/yaml.rst index 763051ad6d1..ba6c0849db2 100644 --- a/components/yaml.rst +++ b/components/yaml.rst @@ -341,15 +341,14 @@ syntax to parse them as proper PHP constants:: Parsing and Dumping of Binary Data ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -You can dump binary data by using the ``DUMP_BASE64_BINARY_DATA`` flag:: +Non UTF-8 encoded strings are dumped as base64 encoded data:: $imageContents = file_get_contents(__DIR__.'/images/logo.png'); - $dumped = Yaml::dump(['logo' => $imageContents], 2, 4, Yaml::DUMP_BASE64_BINARY_DATA); + $dumped = Yaml::dump(['logo' => $imageContents]); // logo: !!binary iVBORw0KGgoAAAANSUhEUgAAA6oAAADqCAY... -Binary data is automatically parsed if they include the ``!!binary`` YAML tag -(there's no need to pass any flag to the Yaml parser):: +Binary data is automatically parsed if they include the ``!!binary`` YAML tag:: $dumped = 'logo: !!binary iVBORw0KGgoAAAANSUhEUgAAA6oAAADqCAY...'; $parsed = Yaml::parse($dumped); diff --git a/components/yaml/yaml_format.rst b/components/yaml/yaml_format.rst index 46d43a797be..d2b7e62e5d2 100644 --- a/components/yaml/yaml_format.rst +++ b/components/yaml/yaml_format.rst @@ -25,7 +25,7 @@ they can also be unquoted: A string in YAML - 'A singled-quoted string in YAML' + 'A single-quoted string in YAML' "A double-quoted string in YAML" diff --git a/configuration.rst b/configuration.rst index 8ab1d99dceb..e7f2680e917 100644 --- a/configuration.rst +++ b/configuration.rst @@ -69,7 +69,7 @@ readable. These are the main advantages and disadvantages of each format: * **YAML**: simple, clean and readable, but not all IDEs support autocompletion and validation for it. :doc:`Learn the YAML syntax `; -* **XML**:autocompleted/validated by most IDEs and is parsed natively by PHP, +* **XML**: autocompleted/validated by most IDEs and is parsed natively by PHP, but sometimes it generates configuration considered too verbose. `Learn the XML syntax`_; * **PHP**: very powerful and it allows you to create dynamic configuration, but the resulting configuration is less readable than the other formats. @@ -387,7 +387,9 @@ set in the previous ones): #. ``config/packages/*.yaml`` (and ``*.xml`` and ``*.php`` files too); #. ``config/packages//*.yaml`` (and ``*.xml`` and ``*.php`` files too); -#. ``config/packages/services.yaml`` (and ``services.xml`` and ``services.php`` files too); +#. ``config/services.yaml`` (and ``services.xml`` and ``services.php`` files too); +#. ``config/services_.yaml`` (and ``services_.xml`` + and ``services_.php`` files too). Take the ``framework`` package, installed by default, as an example: @@ -463,6 +465,12 @@ going to production: use `symbolic links`_ between ``config/packages//`` directories to reuse the same configuration. +Instead of creating new environments, you can use environment variables as +explained in the following section. This way you can use the same application +and environment (e.g. ``prod``) but change its behavior thanks to the +configuration based on environment variables (e.g. to run the application in +different scenarios: staging, quality assurance, client review, etc.) + .. _config-env-vars: Configuration Based on Environment Variables @@ -519,7 +527,7 @@ This example shows how you could configure the database connection using an env 'dbal' => [ // by convention the env var names are always uppercase 'url' => '%env(resolve:DATABASE_URL)%', - ] + ], ]); }; @@ -581,6 +589,11 @@ In addition to your own env vars, this ``.env`` file also contains the env vars defined by the third-party packages installed in your application (they are added automatically by :ref:`Symfony Flex ` when installing packages). +.. tip:: + + Since the ``.env`` file is read and parsed on every request, you don't need to + clear the Symfony cache or restart the PHP container if you're using Docker. + .env File Syntax ................ @@ -674,19 +687,13 @@ the env files ending in ``.local`` (``.env.local`` and ``.env..loca **should not be committed** because only you will use them. In fact, the ``.gitignore`` file that comes with Symfony prevents them from being committed. -.. caution:: - - Applications created before November 2018 had a slightly different system, - involving a ``.env.dist`` file. For information about upgrading, see: - :doc:`configuration/dot-env-changes`. - .. _configuration-env-var-in-prod: Configuring Environment Variables in Production ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In production, the ``.env`` files are also parsed and loaded on each request. So -the easiest way to define env vars is by deploying a ``.env.local`` file to your +the easiest way to define env vars is by creating a ``.env.local`` file on your production server(s) with your production values. To improve performance, you can optionally run the ``dump-env`` command (available @@ -763,12 +770,13 @@ use the ``getParameter()`` helper:: namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\HttpFoundation\Response; class UserController extends AbstractController { // ... - public function index() + public function index(): Response { $projectDir = $this->getParameter('kernel.project_dir'); $adminEmail = $this->getParameter('app.admin_email'); @@ -878,18 +886,15 @@ whenever a service/controller defines a ``$projectDir`` argument, use this: namespace Symfony\Component\DependencyInjection\Loader\Configurator; use App\Controller\LuckyController; - use Psr\Log\LoggerInterface; - use Symfony\Component\DependencyInjection\Reference; return static function (ContainerConfigurator $container) { $container->services() - ->set(LuckyController::class) - ->public() - ->args([ - // pass this value to any $projectDir argument for any service - // that's created in this file (including controller arguments) - '$projectDir' => '%kernel.project_dir%', - ]); + ->defaults() + // pass this value to any $projectDir argument for any service + // that's created in this file (including controller arguments) + ->bind('$projectDir', '%kernel.project_dir%'); + + // ... }; .. seealso:: @@ -950,4 +955,4 @@ And all the other topics related to configuration: .. _`Learn the XML syntax`: https://en.wikipedia.org/wiki/XML .. _`environment variables`: https://en.wikipedia.org/wiki/Environment_variable .. _`symbolic links`: https://en.wikipedia.org/wiki/Symbolic_link -.. _`utilities to manage env vars`: https://symfony.com/doc/master/cloud/cookbooks/env.html +.. _`utilities to manage env vars`: https://symfony.com/doc/current/cloud/env.html diff --git a/configuration/dot-env-changes.rst b/configuration/dot-env-changes.rst deleted file mode 100644 index df418e6ea75..00000000000 --- a/configuration/dot-env-changes.rst +++ /dev/null @@ -1,100 +0,0 @@ -Nov 2018 Changes to .env & How to Update -======================================== - -In November 2018, several changes were made to the core Symfony *recipes* related -to the ``.env`` file. These changes make working with environment variables easier -and more consistent - especially when writing functional tests. - -If your app was started before November 2018, your app **does not require any changes -to keep working**. However, if/when you are ready to take advantage of these improvements, -you will need to make a few small updates. - -What Changed Exactly? ---------------------- - -But first, what changed? On a high-level, not much. Here's a summary of the most -important changes: - -* A) The ``.env.dist`` file no longer exists. Its contents should be moved to your - ``.env`` file (see the next point). - -* B) The ``.env`` file **is** now committed to your repository. It was previously ignored - via the ``.gitignore`` file (the updated recipe does not ignore this file). Because - this file is committed, it should contain non-sensitive, default values. The - ``.env`` can be seen as the previous ``.env.dist`` file. - -* C) A ``.env.local`` file can now be created to *override* values in ``.env`` for - your machine. This file is ignored in the new ``.gitignore``. - -* D) When testing, your ``.env`` file is now read, making it consistent with all - other environments. You can also create a ``.env.test`` file for test-environment - overrides. - -* E) `One further change to the recipe in January 2019`_ means that your ``.env`` - files are *always* loaded, even if you set an ``APP_ENV=prod`` environment - variable. The purpose is for the ``.env`` files to define default values that - you can override if you want to with real environment values. - -There are a few other improvements, but these are the most important. To take advantage -of these, you *will* need to modify a few files in your existing app. - -Updating My Application ------------------------ - -If you created your application after November 15th 2018, you don't need to make -any changes! Otherwise, here is the list of changes you'll need to make - these -changes can be made to any Symfony 3.4 or higher app: - -#. Create a new `config/bootstrap.php`_ file in your project. This file loads Composer's - autoloader and loads all the ``.env`` files as needed (note: in an earlier recipe, - this file was called ``src/.bootstrap.php``; if you are upgrading from Symfony 3.3 - or 4.1, use the `3.3/config/bootstrap.php`_ file instead). - -#. Update your `public/index.php`_ (`index.php diff`_) file to load the new ``config/bootstrap.php`` - file. If you've customized this file, make sure to keep those changes (but use - the rest of the changes). - -#. Update your `bin/console`_ file to load the new ``config/bootstrap.php`` file. - -#. Update ``.gitignore``: - - .. code-block:: diff - - # .gitignore - # ... - - ###> symfony/framework-bundle ### - - /.env - + /.env.local - + /.env.local.php - + /.env.*.local - - # ... - -#. Rename ``.env`` to ``.env.local`` and ``.env.dist`` to ``.env``: - - .. code-block:: terminal - - # Unix - $ mv .env .env.local - $ git mv .env.dist .env - - # Windows - C:\> move .env .env.local - C:\> git mv .env.dist .env - - You can also update the `comment on the top of .env`_ to reflect the new changes. - -#. If you're using PHPUnit, you will also need to `create a new .env.test`_ file - and update your `phpunit.xml.dist file`_ so it loads the ``config/bootstrap.php`` - file. - -.. _`config/bootstrap.php`: https://github.com/symfony/recipes/blob/master/symfony/framework-bundle/4.2/config/bootstrap.php -.. _`3.3/config/bootstrap.php`: https://github.com/symfony/recipes/blob/master/symfony/framework-bundle/3.3/config/bootstrap.php -.. _`public/index.php`: https://github.com/symfony/recipes/blob/master/symfony/framework-bundle/4.2/public/index.php -.. _`index.php diff`: https://github.com/symfony/recipes/compare/8a4e5555e30d5dff64275e2788a901f31a214e79...86e2b6795c455f026e5ab0cba2aff2c7a18511f7#diff-7d73eabd1e5eb7d969ddf9a7ce94f954 -.. _`bin/console`: https://github.com/symfony/recipes/blob/master/symfony/console/3.3/bin/console -.. _`comment on the top of .env`: https://github.com/symfony/recipes/blob/master/symfony/flex/1.0/.env -.. _`create a new .env.test`: https://github.com/symfony/recipes/blob/master/symfony/phpunit-bridge/3.3/.env.test -.. _`phpunit.xml.dist file`: https://github.com/symfony/recipes/blob/master/symfony/phpunit-bridge/3.3/phpunit.xml.dist -.. _`One further change to the recipe in January 2019`: https://github.com/symfony/recipes/pull/501 diff --git a/configuration/env_var_processors.rst b/configuration/env_var_processors.rst index f9191d6ede9..e8421290481 100644 --- a/configuration/env_var_processors.rst +++ b/configuration/env_var_processors.rst @@ -279,12 +279,42 @@ Symfony provides the following env var processors: ``env(csv:FOO)`` Decodes the content of ``FOO``, which is a CSV-encoded string: - .. code-block:: yaml + .. configuration-block:: - parameters: - env(TRUSTED_HOSTS): "10.0.0.1, 10.0.0.2" - framework: - trusted_hosts: '%env(csv:TRUSTED_HOSTS)%' + .. code-block:: yaml + + # config/packages/framework.yaml + parameters: + env(TRUSTED_HOSTS): "10.0.0.1,10.0.0.2" + framework: + trusted_hosts: '%env(csv:TRUSTED_HOSTS)%' + + .. code-block:: xml + + + + + + + 10.0.0.1,10.0.0.2 + + + + + + .. code-block:: php + + // config/packages/framework.php + $container->setParameter('env(TRUSTED_HOSTS)', '10.0.0.1,10.0.0.2'); + $container->loadFromExtension('framework', [ + 'trusted_hosts' => '%env(csv:TRUSTED_HOSTS)%', + ]); ``env(file:FOO)`` Returns the contents of a file whose path is the value of the ``FOO`` env var: @@ -364,7 +394,7 @@ Symfony provides the following env var processors: // config/packages/framework.php $container->setParameter('env(PHP_FILE)', '../config/.runtime-evaluated.php'); $container->loadFromExtension('app', [ - 'auth' => '%env(require:AUTH_FILE)%', + 'auth' => '%env(require:PHP_FILE)%', ]); .. versionadded:: 4.3 @@ -495,8 +525,8 @@ Symfony provides the following env var processors: $container->setParameter('private_key', '%env(default:raw_key:file:PRIVATE_KEY)%'); $container->setParameter('raw_key', '%env(PRIVATE_KEY)%'); - When the fallback parameter is omitted (e.g. ``env(default::API_KEY)``), the - value returned is ``null``. + When the fallback parameter is omitted (e.g. ``env(default::API_KEY)``), then the + returned value is ``null``. .. versionadded:: 4.3 @@ -519,9 +549,9 @@ Symfony provides the following env var processors: clients: default: hosts: - - { host: '%env(key:host:url:MONGODB_URL)%', port: '%env(key:port:url:MONGODB_URL)%' } - username: '%env(key:user:url:MONGODB_URL)%' - password: '%env(key:pass:url:MONGODB_URL)%' + - { host: '%env(string:key:host:url:MONGODB_URL)%', port: '%env(int:key:port:url:MONGODB_URL)%' } + username: '%env(string:key:user:url:MONGODB_URL)%' + password: '%env(string:key:pass:url:MONGODB_URL)%' connections: default: database_name: '%env(key:path:url:MONGODB_URL)%' @@ -536,8 +566,8 @@ Symfony provides the following env var processors: https://symfony.com/schema/dic/services/services-1.0.xsd"> - - + + @@ -551,12 +581,12 @@ Symfony provides the following env var processors: 'default' => [ 'hosts' => [ [ - 'host' => '%env(key:host:url:MONGODB_URL)%', - 'port' => '%env(key:port:url:MONGODB_URL)%', + 'host' => '%env(string:key:host:url:MONGODB_URL)%', + 'port' => '%env(int:key:port:url:MONGODB_URL)%', ], ], - 'username' => '%env(key:user:url:MONGODB_URL)%', - 'password' => '%env(key:pass:url:MONGODB_URL)%', + 'username' => '%env(string:key:user:url:MONGODB_URL)%', + 'password' => '%env(string:key:pass:url:MONGODB_URL)%', ], ], 'connections' => [ @@ -627,16 +657,54 @@ Symfony provides the following env var processors: It is also possible to combine any number of processors: -.. code-block:: yaml +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/framework.yaml + parameters: + env(AUTH_FILE): "%kernel.project_dir%/config/auth.json" + google: + # 1. gets the value of the AUTH_FILE env var + # 2. replaces the values of any config param to get the config path + # 3. gets the content of the file stored in that path + # 4. JSON-decodes the content of the file and returns it + auth: '%env(json:file:resolve:AUTH_FILE)%' + + .. code-block:: xml + + + + + + + %kernel.project_dir%/config/auth.json + + + + + + + + - parameters: - env(AUTH_FILE): "%kernel.project_dir%/config/auth.json" - google: - # 1. gets the value of the AUTH_FILE env var - # 2. replaces the values of any config param to get the config path - # 3. gets the content of the file stored in that path - # 4. JSON-decodes the content of the file and returns it - auth: '%env(json:file:resolve:AUTH_FILE)%' + .. code-block:: php + + // config/packages/framework.php + $container->setParameter('env(AUTH_FILE)', '%kernel.project_dir%/config/auth.json'); + // 1. gets the value of the AUTH_FILE env var + // 2. replaces the values of any config param to get the config path + // 3. gets the content of the file stored in that path + // 4. JSON-decodes the content of the file and returns it + $container->loadFromExtension('google', [ + 'auth' => '%env(json:file:resolve:AUTH_FILE)%', + ]); Custom Environment Variable Processors -------------------------------------- diff --git a/configuration/front_controllers_and_kernel.rst b/configuration/front_controllers_and_kernel.rst index 090abb86e55..b7b70456cb7 100644 --- a/configuration/front_controllers_and_kernel.rst +++ b/configuration/front_controllers_and_kernel.rst @@ -135,7 +135,7 @@ should run in "debug mode". Regardless of the :ref:`configuration environment `, a Symfony application can be run with debug mode set to ``true`` or ``false``. -This affects many things in the application, such as displaying stacktraces on +This affects many things in the application, such as displaying stack traces on error pages or if cache files are dynamically rebuilt on each request. Though not a requirement, debug mode is generally set to ``true`` for the ``dev`` and ``test`` environments and ``false`` for the ``prod`` environment. @@ -244,15 +244,15 @@ the directory of the environment you're using (most commonly ``dev/`` while developing and debugging). While it can vary, the ``var/cache/dev/`` directory includes the following: -``appDevDebugProjectContainer.php`` +``srcApp_KernelDevDebugContainer.php`` The cached "service container" that represents the cached application configuration. -``appDevUrlGenerator.php`` +``UrlGenerator.php`` The PHP class generated from the routing configuration and used when generating URLs. -``appDevUrlMatcher.php`` +``UrlMatcher.php`` The PHP class used for route matching - look here to see the compiled regular expression logic used to match incoming URLs to different routes. diff --git a/configuration/micro_kernel_trait.rst b/configuration/micro_kernel_trait.rst index 16402bd5a54..1d37d9843cb 100644 --- a/configuration/micro_kernel_trait.rst +++ b/configuration/micro_kernel_trait.rst @@ -102,6 +102,44 @@ that define your bundles, your services and your routes: ``RouteCollectionBuilder`` has methods that make adding routes in PHP more fun. You can also load external routing files (shown below). +Adding Interfaces to "Micro" Kernel +----------------------------------- + +When using the ``MicroKernelTrait``, you can also implement the +``CompilerPassInterface`` to automatically register the kernel itself as a +compiler pass as explained in the dedicated +:ref:`compiler pass section `. + +It is also possible to implement the ``EventSubscriberInterface`` to handle +events directly from the kernel, again it will be registered automatically:: + + // ... + use App\Exception\Danger; + use Symfony\Component\EventDispatcher\EventSubscriberInterface; + use Symfony\Component\HttpKernel\Event\ExceptionEvent; + use Symfony\Component\HttpKernel\KernelEvents; + + class Kernel extends BaseKernel implements EventSubscriberInterface + { + use MicroKernelTrait; + + // ... + + public function onKernelException(ExceptionEvent $event): void + { + if ($event->getException() instanceof Danger) { + $event->setResponse(new Response('It\'s dangerous to go alone. Take this ⚔')); + } + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::EXCEPTION => 'onKernelException', + ]; + } + } + Advanced Example: Twig, Annotations and the Web Debug Toolbar ------------------------------------------------------------- diff --git a/configuration/multiple_kernels.rst b/configuration/multiple_kernels.rst index e6110bb69c2..029b4c1e5fb 100644 --- a/configuration/multiple_kernels.rst +++ b/configuration/multiple_kernels.rst @@ -77,7 +77,7 @@ Now you need to define the ``ApiKernel`` class used by the new front controller. The easiest way to do this is by duplicating the existing ``src/Kernel.php`` file and make the needed changes. -In this example, the ``ApiKernel`` will load less bundles than the default +In this example, the ``ApiKernel`` will load fewer bundles than the default Kernel. Be sure to also change the location of the cache, logs and configuration files so they don't collide with the files from ``src/Kernel.php``:: @@ -88,31 +88,50 @@ files so they don't collide with the files from ``src/Kernel.php``:: class ApiKernel extends Kernel { - // ... + use MicroKernelTrait; public function registerBundles() { - // load only the bundles strictly needed for the API... + // load only the bundles strictly needed for the API + $contents = require $this->getProjectDir().'/config/api_bundles.php'; + foreach ($contents as $class => $envs) { + if ($envs[$this->environment] ?? $envs['all'] ?? false) { + yield new $class(); + } + } + } + + public function getProjectDir(): string + { + return \dirname(__DIR__); } - public function getCacheDir() + public function getCacheDir(): string { - return dirname(__DIR__).'/var/cache/api/'.$this->getEnvironment(); + return $this->getProjectDir().'/var/cache/api/'.$this->getEnvironment(); } - public function getLogDir() + public function getLogDir(): string { - return dirname(__DIR__).'/var/log/api'; + return $this->getProjectDir().'/var/log/api'; } public function configureContainer(ContainerBuilder $container, LoaderInterface $loader) { - // load only the config files strictly needed for the API - $confDir = $this->getProjectDir().'/config'; - $loader->load($confDir.'/api/*'.self::CONFIG_EXTS, 'glob'); - if (is_dir($confDir.'/api/'.$this->environment)) { - $loader->load($confDir.'/api/'.$this->environment.'/**/*'.self::CONFIG_EXTS, 'glob'); - } + $container->addResource(new FileResource($this->getProjectDir().'/config/api_bundles.php')); + $container->setParameter('container.dumper.inline_factories', true); + $confDir = $this->getProjectDir().'/config/api'; + + $loader->load($confDir.'/{packages}/*'.self::CONFIG_EXTS, 'glob'); + $loader->load($confDir.'/{packages}/'.$this->environment.'/*'.self::CONFIG_EXTS, 'glob'); + $loader->load($confDir.'/{services}'.self::CONFIG_EXTS, 'glob'); + $loader->load($confDir.'/{services}_'.$this->environment.self::CONFIG_EXTS, 'glob'); + } + + protected function configureRoutes(RouteCollectionBuilder $routes): void + { + $confDir = $this->getProjectDir().'/config/api'; + // ... load only the config routes strictly needed for the API } } diff --git a/configuration/override_dir_structure.rst b/configuration/override_dir_structure.rst index fbfa119cc14..a1af58ba5db 100644 --- a/configuration/override_dir_structure.rst +++ b/configuration/override_dir_structure.rst @@ -51,7 +51,7 @@ method in the ``Kernel`` class of your application:: { // ... - public function getCacheDir() + public function getCacheDir(): string { return dirname(__DIR__).'/var/'.$this->environment.'/cache'; } @@ -80,11 +80,11 @@ method:: // src/Kernel.php // ... - class Kernel extends Kernel + class Kernel extends BaseKernel { // ... - public function getLogDir() + public function getLogDir(): string { return dirname(__DIR__).'/var/'.$this->environment.'/log'; } @@ -98,8 +98,9 @@ Override the Templates Directory -------------------------------- If your templates are not stored in the default ``templates/`` directory, use -the :ref:`twig.paths ` configuration option to define your -own templates directory (or directories): +the :ref:`twig.default_path ` configuration +option to define your own templates directory (use :ref:`twig.paths ` +for multiple directories): .. configuration-block:: @@ -108,12 +109,12 @@ own templates directory (or directories): # config/packages/twig.yaml twig: # ... - paths: ["%kernel.project_dir%/resources/views"] + default_path: "%kernel.project_dir%/resources/views" .. code-block:: xml - + - %kernel.project_dir%/resources/views + %kernel.project_dir%/resources/views @@ -132,17 +133,15 @@ own templates directory (or directories): // config/packages/twig.php $container->loadFromExtension('twig', [ - 'paths' => [ - '%kernel.project_dir%/resources/views', - ], + 'default_path' => '%kernel.project_dir%/resources/views', ]); Override the Translations Directory ----------------------------------- If your translation files are not stored in the default ``translations/`` -directory, use the :ref:`framework.translator.paths ` -configuration option to define your own translations directory (or directories): +directory, use the :ref:`framework.translator.default_path ` +configuration option to define your own translations directory (use :ref:`framework.translator.paths ` for multiple directories): .. configuration-block:: @@ -152,12 +151,12 @@ configuration option to define your own translations directory (or directories): framework: translator: # ... - paths: ["%kernel.project_dir%/i18n"] + default_path: "%kernel.project_dir%/i18n" .. code-block:: xml - + - %kernel.project_dir%/i18n + %kernel.project_dir%/i18n @@ -179,9 +178,7 @@ configuration option to define your own translations directory (or directories): // config/packages/translation.php $container->loadFromExtension('framework', [ 'translator' => [ - 'paths' => [ - '%kernel.project_dir%/i18n', - ], + 'default_path' => '%kernel.project_dir%/i18n', ], ]); @@ -192,7 +189,7 @@ Override the Public Directory ----------------------------- If you need to rename or move your ``public/`` directory, the only thing you -need to guarantee is that the path to the ``var/`` directory is still correct in +need to guarantee is that the path to the ``vendor/`` directory is still correct in your ``index.php`` front controller. If you renamed the directory, you're fine. But if you moved it in some way, you may need to modify these paths inside those files:: @@ -233,7 +230,7 @@ option in your ``composer.json`` file like this: "config": { "bin-dir": "bin", "vendor-dir": "/some/dir/vendor" - }, + } } .. tip:: diff --git a/configuration/secrets.rst b/configuration/secrets.rst index fb3b6da1578..845a2106af7 100644 --- a/configuration/secrets.rst +++ b/configuration/secrets.rst @@ -98,11 +98,16 @@ in ``config/secrets/prod``. You can also set the secret in a few other ways: # or let Symfony generate a random value for you $ php bin/console secrets:set REMEMBER_ME --random +.. note:: + + There's no command to rename secrets, so you'll need to create a new secret + and remove the old one. + Referencing Secrets in Configuration Files ------------------------------------------ Secret values can be referenced in the same way as -:ref:`environment variables`. Be careful that you don't +:ref:`environment variables `. Be careful that you don't accidentally define a secret *and* an environment variable with the same name: **environment variables override secrets**. @@ -145,7 +150,7 @@ If you stored a ``DATABASE_PASSWORD`` secret, you can reference it by: $container->loadFromExtension('doctrine', [ 'dbal' => [ 'password' => '%env(DATABASE_PASSWORD)%', - ] + ], ]); The actual value will be resolved at runtime: container compilation and cache @@ -212,9 +217,9 @@ Listing the secrets will now also display the local variable: DATABASE_PASSWORD "dev value" "root" ------------------- ------------- ------------- -Symfony also provides the ``secrets:decrypt-to-local`` command to decrypts -all secrets and stores them in the local vault and ``secrets:encrypt-from-local`` -to encrypt all local secrets to the vault. +Symfony also provides the ``secrets:decrypt-to-local`` command which decrypts +all secrets and stores them in the local vault and the ``secrets:encrypt-from-local`` +command to encrypt all local secrets to the vault. Secrets in the test Environment ------------------------------- @@ -235,32 +240,32 @@ Deploy Secrets to Production Due to the fact that decryption keys should never be committed, you will need to manually store this file somewhere and deploy it. There are 2 ways to do that: -1) Uploading the file: +#. Uploading the file -The first option is to copy the **production decryption key** - -``config/secrets/prod/prod.decrypt.private.php`` to your server. + The first option is to copy the **production decryption key** - + ``config/secrets/prod/prod.decrypt.private.php`` to your server. -2) Using an Environment Variable +#. Using an Environment Variable -The second way is to set the ``SYMFONY_DECRYPTION_SECRET`` environment variable -to the base64 encoded value of the **production decryption key**. A fancy way to -fetch the value of the key is: + The second way is to set the ``SYMFONY_DECRYPTION_SECRET`` environment variable + to the base64 encoded value of the **production decryption key**. A fancy way to + fetch the value of the key is: -.. code-block:: terminal + .. code-block:: terminal - # this command only gets the value of the key; you must also set an env var - # in your system with this value (e.g. `export SYMFONY_DECRYPTION_SECRET=...`) - $ php -r 'echo base64_encode(require "config/secrets/prod/prod.decrypt.private.php");' + # this command only gets the value of the key; you must also set an env var + # in your system with this value (e.g. `export SYMFONY_DECRYPTION_SECRET=...`) + $ php -r 'echo base64_encode(require "config/secrets/prod/prod.decrypt.private.php");' -To improve performance (i.e. avoid decrypting secrets at runtime), you can decrypt -your secrets during deployment to the "local" vault: + To improve performance (i.e. avoid decrypting secrets at runtime), you can decrypt + your secrets during deployment to the "local" vault: -.. code-block:: terminal + .. code-block:: terminal - $ php bin/console secrets:decrypt-to-local --force --env=prod + $ php bin/console secrets:decrypt-to-local --force --env=prod -This will write all the decrypted secrets into the ``.env.prod.local`` file. -After doing this, the decryption key does *not* need to remain on the server(s). + This will write all the decrypted secrets into the ``.env.prod.local`` file. + After doing this, the decryption key does *not* need to remain on the server(s). Rotating Secrets ---------------- diff --git a/configuration/using_parameters_in_dic.rst b/configuration/using_parameters_in_dic.rst index 730043af714..6bdf07ff886 100644 --- a/configuration/using_parameters_in_dic.rst +++ b/configuration/using_parameters_in_dic.rst @@ -77,16 +77,16 @@ Now, examine the results to see this closely: $container->loadFromExtension('my_bundle', [ 'logging' => true, // true, as expected - ) - ]; + ] + ); $container->loadFromExtension('my_bundle', [ 'logging' => "%kernel.debug%", // true/false (depends on 2nd parameter of Kernel), // as expected, because %kernel.debug% inside configuration // gets evaluated before being passed to the extension - ) - ]; + ] + ); $container->loadFromExtension('my_bundle'); // passes the string "%kernel.debug%". @@ -106,7 +106,7 @@ be injected with this parameter via the extension as follows:: { private $debug; - public function __construct($debug) + public function __construct($debug) { $this->debug = (bool) $debug; } diff --git a/console.rst b/console.rst index ad9bb8c16e6..f67dfb71f5d 100644 --- a/console.rst +++ b/console.rst @@ -38,16 +38,23 @@ want a command to create a user:: // the name of the command (the part after "bin/console") protected static $defaultName = 'app:create-user'; - protected function configure() + protected function configure(): void { // ... } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { - // ... + // ... put here the code to create the user + + // this method must return an integer number with the "exit status code" + // of the command. + // return this if there was no problem running the command return 0; + + // or return this if some error happened during the execution + // return 1; } } @@ -58,7 +65,7 @@ You can optionally define a description, help message and the :doc:`input options and arguments `:: // ... - protected function configure() + protected function configure(): void { $this // the short description shown while running "php bin/console list" @@ -93,7 +100,7 @@ available in the ``configure()`` method:: parent::__construct(); } - protected function configure() + protected function configure(): void { $this // ... @@ -129,7 +136,7 @@ The ``execute()`` method has access to the output stream to write messages to the console:: // ... - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { // outputs multiple lines to the console (adding "\n" at the end of each line) $output->writeln([ @@ -138,7 +145,7 @@ the console:: '', ]); - // the value returned by someMethod() can be an iterator (https://secure.php.net/iterator) + // the value returned by someMethod() can be an iterator (https://php.net/iterator) // that generates and returns the messages with the 'yield' PHP keyword $output->writeln($this->someMethod()); @@ -173,16 +180,24 @@ called "output sections". Create one or more of these sections when you need to clear and overwrite the output information. Sections are created with the -:method:`Symfony\\Component\\Console\\Output\\ConsoleOutput::section` method, -which returns an instance of +:method:`ConsoleOutput::section() ` +method, which returns an instance of :class:`Symfony\\Component\\Console\\Output\\ConsoleSectionOutput`:: + // ... + use Symfony\Component\Console\Output\ConsoleOutputInterface; + class MyCommand extends Command { - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { + if (!$output instanceof ConsoleOutputInterface) { + throw new \LogicException('This command accepts only an instance of "ConsoleOutputInterface".'); + } + $section1 = $output->section(); $section2 = $output->section(); + $section1->writeln('Hello'); $section2->writeln('World!'); // Output displays "Hello\nWorld!\n" @@ -221,7 +236,7 @@ Use input options or arguments to pass information to the command:: use Symfony\Component\Console\Input\InputArgument; // ... - protected function configure() + protected function configure(): void { $this // configure an argument @@ -231,7 +246,7 @@ Use input options or arguments to pass information to the command:: } // ... - public function execute(InputInterface $input, OutputInterface $output) + public function execute(InputInterface $input, OutputInterface $output): int { $output->writeln([ 'User Creator', @@ -285,7 +300,7 @@ as a service, you can use normal dependency injection. Imagine you have a // ... - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { // ... @@ -346,7 +361,7 @@ console:: { public function testExecute() { - $kernel = static::createKernel(); + $kernel = self::bootKernel(); $application = new Application($kernel); $command = $application->find('app:create-user'); @@ -357,11 +372,13 @@ console:: // prefix the key with two dashes when passing options, // e.g: '--some-option' => 'option_value', + // use brackets for testing array value, + // e.g: '--some-option' => ['option_value'], ]); // the output of the command in the console $output = $commandTester->getDisplay(); - $this->assertContains('Username: Wouter', $output); + $this->assertStringContainsString('Username: Wouter', $output); // ... } @@ -378,10 +395,20 @@ console:: not dispatched. If you need to test those events, use the :class:`Symfony\\Component\\Console\\Tester\\ApplicationTester` instead. +.. caution:: + + When testing commands using the :class:`Symfony\\Component\\Console\\Tester\\ApplicationTester` + class, don't forget to disable the auto exit flag:: + + $application = new Application(); + $application->setAutoExit(false); + + $tester = new ApplicationTester($application); + .. note:: When using the Console component in a standalone project, use - :class:`Symfony\\Component\\Console\\Application ` + :class:`Symfony\\Component\\Console\\Application` and extend the normal ``\PHPUnit\Framework\TestCase``. Logging Command Errors diff --git a/console/calling_commands.rst b/console/calling_commands.rst index 0b3919973e5..2defb04d49a 100644 --- a/console/calling_commands.rst +++ b/console/calling_commands.rst @@ -8,36 +8,40 @@ or if you want to create a "meta" command that runs a bunch of other commands changed on the production servers: clearing the cache, generating Doctrine proxies, dumping web assets, ...). -Calling a command from another one is straightforward:: +Use the :method:`Symfony\\Component\\Console\\Application::find` method to +find the command you want to run by passing the command name. Then, create a +new :class:`Symfony\\Component\\Console\\Input\\ArrayInput` with the +arguments and options you want to pass to the command. +Eventually, calling the ``run()`` method actually runs the command and returns +the returned code from the command (return value from command's ``execute()`` +method):: + + // ... + use Symfony\Component\Console\Command; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; - // ... - protected function execute(InputInterface $input, OutputInterface $output) + class CreateUserCommand extends Command { - $command = $this->getApplication()->find('demo:greet'); - - $arguments = [ - 'name' => 'Fabien', - '--yell' => true, - ]; + // ... - $greetInput = new ArrayInput($arguments); - $returnCode = $command->run($greetInput, $output); + protected function execute(InputInterface $input, OutputInterface $output): void + { + $command = $this->getApplication()->find('demo:greet'); - // ... - } + $arguments = [ + 'name' => 'Fabien', + '--yell' => true, + ]; -First, you :method:`Symfony\\Component\\Console\\Application::find` the -command you want to run by passing the command name. Then, you need to create -a new :class:`Symfony\\Component\\Console\\Input\\ArrayInput` with the arguments -and options you want to pass to the command. + $greetInput = new ArrayInput($arguments); + $returnCode = $command->run($greetInput, $output); -Eventually, calling the ``run()`` method actually runs the command and returns -the returned code from the command (return value from command's ``execute()`` -method). + // ... + } + } .. tip:: diff --git a/console/coloring.rst b/console/coloring.rst index 774a2ab96fa..d3b2d6f67d3 100644 --- a/console/coloring.rst +++ b/console/coloring.rst @@ -91,7 +91,7 @@ you can click on the *"Symfony Homepage"* text to open its URL in your default browser. Otherwise, you'll see *"Symfony Homepage"* as regular text and the URL will be lost. -.. _Cmder: https://cmder.net/ +.. _Cmder: https://github.com/cmderdev/cmder .. _ConEmu: https://conemu.github.io/ .. _ANSICON: https://github.com/adoxa/ansicon/releases .. _Mintty: https://mintty.github.io/ diff --git a/console/command_in_controller.rst b/console/command_in_controller.rst index 190584bfbda..91ead2a7801 100644 --- a/console/command_in_controller.rst +++ b/console/command_in_controller.rst @@ -20,11 +20,9 @@ Instead, you can run the command directly from the controller. a controller has a slight performance impact because of the request stack overhead. -Imagine you want to send spooled Swift Mailer messages by -:doc:`using the swiftmailer:spool:send command `. -Run this command from inside your controller via:: +Imagine you want to run the ``debug:twig`` from inside your controller:: - // src/Controller/SpoolController.php + // src/Controller/DebugTwigController.php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Console\Application; @@ -34,19 +32,19 @@ Run this command from inside your controller via:: use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\KernelInterface; - class SpoolController extends AbstractController + class DebugTwigController extends AbstractController { - public function sendSpool($messages = 10, KernelInterface $kernel) + public function debugTwig(KernelInterface $kernel): Response { $application = new Application($kernel); $application->setAutoExit(false); $input = new ArrayInput([ - 'command' => 'swiftmailer:spool:send', + 'command' => 'debug:twig', // (optional) define the value of command arguments 'fooArgument' => 'barValue', // (optional) pass options to the command - '--message-limit' => $messages, + '--bar' => 'fooValue', ]); // You can use NullOutput() if you don't need the output @@ -76,7 +74,7 @@ First, require the package: Now, use it in your controller:: - // src/Controller/SpoolController.php + // src/Controller/DebugTwigController.php namespace App\Controller; use SensioLabs\AnsiConverter\AnsiToHtmlConverter; @@ -85,9 +83,9 @@ Now, use it in your controller:: use Symfony\Component\HttpFoundation\Response; // ... - class SpoolController extends AbstractController + class DebugTwigController extends AbstractController { - public function sendSpool($messages = 10) + public function sendSpool(int $messages = 10): Response { // ... $output = new BufferedOutput( diff --git a/console/commands_as_services.rst b/console/commands_as_services.rst index fb5e7ff70eb..794ec8f46cb 100644 --- a/console/commands_as_services.rst +++ b/console/commands_as_services.rst @@ -35,17 +35,17 @@ For example, suppose you want to log something from within your command:: parent::__construct(); } - protected function configure() + protected function configure(): void { $this ->setDescription('Good morning!'); } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $this->logger->info('Waking up the sun'); // ... - + return 0; } } diff --git a/console/hide_commands.rst b/console/hide_commands.rst index db39ca824f8..2f9d2819873 100644 --- a/console/hide_commands.rst +++ b/console/hide_commands.rst @@ -20,7 +20,7 @@ In those cases, you can define the command as **hidden** by setting the { protected static $defaultName = 'app:legacy'; - protected function configure() + protected function configure(): void { $this ->setHidden(true) diff --git a/console/input.rst b/console/input.rst index 3e99b39e515..c48968c81fc 100644 --- a/console/input.rst +++ b/console/input.rst @@ -21,7 +21,7 @@ and make the ``name`` argument required:: { // ... - protected function configure() + protected function configure(): void { $this // ... @@ -42,7 +42,7 @@ You now have access to a ``last_name`` argument in your command:: { // ... - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $text = 'Hi '.$input->getArgument('name'); @@ -52,6 +52,8 @@ You now have access to a ``last_name`` argument in your command:: } $output->writeln($text.'!'); + + return 0; } } @@ -132,10 +134,17 @@ how many times in a row the message should be printed:: $this // ... ->addOption( + // this is the name that users must type to pass this option (e.g. --iterations=5) 'iterations', + // this is the optional shortcut of the option name, which usually is just a letter + // (e.g. `i`, so users pass it as `-i`); use it for commonly used options + // or options with long names null, + // this is the type of option (e.g. requires a value, can be passed more than once, etc.) InputOption::VALUE_REQUIRED, + // the option description displayed when showing the command help 'How many times should the message be printed?', + // the default value of the option (for those which allow to pass values) 1 ) ; @@ -203,8 +212,9 @@ There are four option variants you can use: This option accepts multiple values (e.g. ``--dir=/foo --dir=/bar``); ``InputOption::VALUE_NONE`` - Do not accept input for this option (e.g. ``--yell``). This is the default - behavior of options; + Do not accept input for this option (e.g. ``--yell``). The value returned + from is a boolean (``false`` if the option is not provided). + This is the default behavior of options; ``InputOption::VALUE_REQUIRED`` This value is required (e.g. ``--iterations=5`` or ``-i5``), the option @@ -214,7 +224,7 @@ There are four option variants you can use: This option may or may not have a value (e.g. ``--yell`` or ``--yell=loud``). -You can combine ``VALUE_IS_ARRAY`` with ``VALUE_REQUIRED`` or +You need to combine ``VALUE_IS_ARRAY`` with ``VALUE_REQUIRED`` or ``VALUE_OPTIONAL`` like this:: $this @@ -247,7 +257,7 @@ optionally accepts a value, but it's a bit tricky. Consider this example:: ) ; -This option can be used in 3 ways: ``greet --yell``, ``greet yell=louder``, +This option can be used in 3 ways: ``greet --yell``, ``greet --yell=louder``, and ``greet``. However, it's hard to distinguish between passing the option without a value (``greet --yell``) and not passing the option (``greet``). diff --git a/console/lockable_trait.rst b/console/lockable_trait.rst index 7f751d09012..98c94d82c57 100644 --- a/console/lockable_trait.rst +++ b/console/lockable_trait.rst @@ -22,7 +22,7 @@ that adds two convenient methods to lock and release commands:: // ... - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { if (!$this->lock()) { $output->writeln('The command is already running in another process.'); @@ -38,6 +38,8 @@ that adds two convenient methods to lock and release commands:: // if not released explicitly, Symfony releases the lock // automatically when the execution of the command ends $this->release(); + + return 0; } } diff --git a/console/style.rst b/console/style.rst index dd981436e50..79a4971b2c8 100644 --- a/console/style.rst +++ b/console/style.rst @@ -21,7 +21,7 @@ Consider for example the code used to display the title of the following command { // ... - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $output->writeln([ 'Lorem Ipsum Dolor Sit Amet', @@ -62,7 +62,7 @@ title of the command:: { // ... - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); $io->title('Lorem Ipsum Dolor Sit Amet'); @@ -399,7 +399,7 @@ of your commands to change their appearance:: { // ... - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { // Before $io = new SymfonyStyle($input, $output); diff --git a/console/verbosity.rst b/console/verbosity.rst index c16737c2b61..7df68d30f23 100644 --- a/console/verbosity.rst +++ b/console/verbosity.rst @@ -49,7 +49,7 @@ level. For example:: { // ... - public function execute(InputInterface $input, OutputInterface $output) + public function execute(InputInterface $input, OutputInterface $output): int { $user = new User(...); @@ -68,6 +68,8 @@ level. For example:: 'Will only be printed in verbose mode or higher', OutputInterface::VERBOSITY_VERBOSE ); + + return 0; } } diff --git a/contributing/code/bc.rst b/contributing/code/bc.rst index d0c82ab1c1f..3f1e6164087 100644 --- a/contributing/code/bc.rst +++ b/contributing/code/bc.rst @@ -4,10 +4,13 @@ Our Backward Compatibility Promise Ensuring smooth upgrades of your projects is our first priority. That's why we promise you backward compatibility (BC) for all minor Symfony releases. You probably recognize this strategy as `Semantic Versioning`_. In short, -Semantic Versioning means that only major releases (such as 2.0, 3.0 etc.) are -allowed to break backward compatibility. Minor releases (such as 2.5, 2.6 etc.) +Semantic Versioning means that only major releases (such as 5.0, 6.0 etc.) are +allowed to break backward compatibility. Minor releases (such as 5.1, 5.2 etc.) may introduce new features, but must do so without breaking the existing API of -that release branch (2.x in the previous example). +that release branch (5.x in the previous example). + +We also provide deprecation message triggered in the code base to help you with +the migration process across major releases. .. caution:: @@ -72,7 +75,7 @@ backward compatibility promise: +-----------------------------------------------+-----------------------------+ | Type hint against the interface | Yes | +-----------------------------------------------+-----------------------------+ -| Call a method | Yes | +| Call a method | Yes :ref:`[10] ` | +-----------------------------------------------+-----------------------------+ | **If you implement the interface and...** | **Then we guarantee BC...** | +-----------------------------------------------+-----------------------------+ @@ -114,13 +117,13 @@ covered by our backward compatibility promise: +-----------------------------------------------+-----------------------------+ | Access a public property | Yes | +-----------------------------------------------+-----------------------------+ -| Call a public method | Yes | +| Call a public method | Yes :ref:`[10] ` | +-----------------------------------------------+-----------------------------+ | **If you extend the class and...** | **Then we guarantee BC...** | +-----------------------------------------------+-----------------------------+ | Access a protected property | Yes | +-----------------------------------------------+-----------------------------+ -| Call a protected method | Yes | +| Call a protected method | Yes :ref:`[10] ` | +-----------------------------------------------+-----------------------------+ | Override a public property | Yes | +-----------------------------------------------+-----------------------------+ @@ -190,12 +193,12 @@ Changing Interfaces This table tells you which changes you are allowed to do when working on Symfony's interfaces: -============================================== ============== -Type of Change Change Allowed -============================================== ============== +============================================== ============== =============== +Type of Change Change Allowed Notes +============================================== ============== =============== Remove entirely No Change name or namespace No -Add parent interface Yes [2]_ +Add parent interface Yes :ref:`[2] ` Remove parent interface No **Methods** Add method No @@ -204,14 +207,14 @@ Change name No Move to parent interface Yes Add argument without a default value No Add argument with a default value No -Remove argument No [3]_ +Remove argument No :ref:`[3] ` Add default value to an argument No Remove default value of an argument No Add type hint to an argument No Remove type hint of an argument No Change argument type No Add return type No -Remove return type No [9]_ +Remove return type No :ref:`[9] ` Change return type No **Static Methods** Turn non static into static No @@ -219,8 +222,8 @@ Turn static into non static No **Constants** Add constant Yes Remove constant No -Change value of a constant Yes [1]_ [5]_ -============================================== ============== +Change value of a constant Yes :ref:`[1] ` :ref:`[5] ` +============================================== ============== =============== Changing Classes ~~~~~~~~~~~~~~~~ @@ -228,14 +231,14 @@ Changing Classes This table tells you which changes you are allowed to do when working on Symfony's classes: -================================================== ============== -Type of Change Change Allowed -================================================== ============== +================================================== ============== =============== +Type of Change Change Allowed Notes +================================================== ============== =============== Remove entirely No -Make final No [6]_ +Make final No :ref:`[6] ` Make abstract No Change name or namespace No -Change parent class Yes [4]_ +Change parent class Yes :ref:`[4] ` Add interface Yes Remove interface No **Public Properties** @@ -245,19 +248,19 @@ Reduce visibility No Move to parent class Yes **Protected Properties** Add protected property Yes -Remove protected property No [7]_ -Reduce visibility No [7]_ -Make public No [7]_ +Remove protected property No :ref:`[7] ` +Reduce visibility No :ref:`[7] ` +Make public No :ref:`[7] ` Move to parent class Yes **Private Properties** Add private property Yes Make public or protected Yes Remove private property Yes **Constructors** -Add constructor without mandatory arguments Yes [1]_ +Add constructor without mandatory arguments Yes :ref:`[1] ` Remove constructor No Reduce visibility of a public constructor No -Reduce visibility of a protected constructor No [7]_ +Reduce visibility of a protected constructor No :ref:`[7] ` Move to parent class Yes **Destructors** Add destructor Yes @@ -268,38 +271,38 @@ Add public method Yes Remove public method No Change name No Reduce visibility No -Make final No [6]_ +Make final No :ref:`[6] ` Move to parent class Yes Add argument without a default value No -Add argument with a default value No [7]_ [8]_ -Remove argument No [3]_ -Add default value to an argument No [7]_ [8]_ +Add argument with a default value No :ref:`[7] ` :ref:`[8] ` +Remove argument No :ref:`[3] ` +Add default value to an argument No :ref:`[7] ` :ref:`[8] ` Remove default value of an argument No -Add type hint to an argument No [7]_ [8]_ -Remove type hint of an argument No [7]_ [8]_ -Change argument type No [7]_ [8]_ -Add return type No [7]_ [8]_ -Remove return type No [7]_ [8]_ [9]_ -Change return type No [7]_ [8]_ +Add type hint to an argument No :ref:`[7] ` :ref:`[8] ` +Remove type hint of an argument No :ref:`[7] ` :ref:`[8] ` +Change argument type No :ref:`[7] ` :ref:`[8] ` +Add return type No :ref:`[7] ` :ref:`[8] ` +Remove return type No :ref:`[7] ` :ref:`[8] ` :ref:`[9] ` +Change return type No :ref:`[7] ` :ref:`[8] ` **Protected Methods** Add protected method Yes -Remove protected method No [7]_ -Change name No [7]_ -Reduce visibility No [7]_ -Make final No [6]_ -Make public No [7]_ [8]_ +Remove protected method No :ref:`[7] ` +Change name No :ref:`[7] ` +Reduce visibility No :ref:`[7] ` +Make final No :ref:`[6] ` +Make public No :ref:`[7] ` :ref:`[8] ` Move to parent class Yes -Add argument without a default value No [7]_ -Add argument with a default value No [7]_ [8]_ -Remove argument No [3]_ -Add default value to an argument No [7]_ [8]_ -Remove default value of an argument No [7]_ -Add type hint to an argument No [7]_ [8]_ -Remove type hint of an argument No [7]_ [8]_ -Change argument type No [7]_ [8]_ -Add return type No [7]_ [8]_ -Remove return type No [7]_ [8]_ [9]_ -Change return type No [7]_ [8]_ +Add argument without a default value No :ref:`[7] ` +Add argument with a default value No :ref:`[7] ` :ref:`[8] ` +Remove argument No :ref:`[3] ` +Add default value to an argument No :ref:`[7] ` :ref:`[8] ` +Remove default value of an argument No :ref:`[7] ` +Add type hint to an argument No :ref:`[7] ` :ref:`[8] ` +Remove type hint of an argument No :ref:`[7] ` :ref:`[8] ` +Change argument type No :ref:`[7] ` :ref:`[8] ` +Add return type No :ref:`[7] ` :ref:`[8] ` +Remove return type No :ref:`[7] ` :ref:`[8] ` :ref:`[9] ` +Change return type No :ref:`[7] ` :ref:`[8] ` **Private Methods** Add private method Yes Remove private method Yes @@ -317,13 +320,13 @@ Add return type Yes Remove return type Yes Change return type Yes **Static Methods and Properties** -Turn non static into static No [7]_ [8]_ +Turn non static into static No :ref:`[7] ` :ref:`[8] ` Turn static into non static No **Constants** Add constant Yes Remove constant No -Change value of a constant Yes [1]_ [5]_ -================================================== ============== +Change value of a constant Yes :ref:`[1] ` :ref:`[5] ` +================================================== ============== =============== Changing Traits ~~~~~~~~~~~~~~~ @@ -331,9 +334,9 @@ Changing Traits This table tells you which changes you are allowed to do when working on Symfony's traits: -================================================== ============== -Type of Change Change Allowed -================================================== ============== +================================================== ============== =============== +Type of Change Change Allowed Notes +================================================== ============== =============== Remove entirely No Change name or namespace No Use another trait Yes @@ -360,7 +363,7 @@ Add public method Yes Remove public method No Change name No Reduce visibility No -Make final No [6]_ +Make final No :ref:`[6] ` Move to used trait Yes Add argument without a default value No Add argument with a default value No @@ -376,8 +379,8 @@ Add protected method Yes Remove protected method No Change name No Reduce visibility No -Make final No [6]_ -Make public No [8]_ +Make final No :ref:`[6] ` +Make public No :ref:`[8] ` Move to used trait Yes Add argument without a default value No Add argument with a default value No @@ -408,41 +411,66 @@ Change return type No **Static Methods and Properties** Turn non static into static No Turn static into non static No -================================================== ============== +================================================== ============== =============== + +Notes +~~~~~ + +.. _note-1: + +**[1]** Should be avoided. When done, this change must be documented in the +UPGRADE file. + +.. _note-2: + +**[2]** The added parent interface must not introduce any new methods that don't +exist in the interface already. + +.. _note-3: + +**[3]** Only the last optional argument(s) of a method may be removed, as PHP +does not care about additional arguments that you pass to a method. + +.. _note-4: + +**[4]** When changing the parent class, the original parent class must remain an +ancestor of the class. + +.. _note-5: + +**[5]** The value of a constant may only be changed when the constants aren't +used in configuration (e.g. Yaml and XML files), as these do not support +constants and have to hardcode the value. For instance, event name constants +can't change the value without introducing a BC break. Additionally, if a +constant will likely be used in objects that are serialized, the value of a +constant should not be changed. + +.. _note-6: -.. [1] Should be avoided. When done, this change must be documented in the - UPGRADE file. +**[6]** Allowed using the ``@final`` annotation. -.. [2] The added parent interface must not introduce any new methods that don't - exist in the interface already. +.. _note-7: -.. [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. +**[7]** Allowed if the class is final. Classes that received the ``@final`` +annotation after their first release are considered final in their next major +version. Changing an argument type is only possible with a parent type. Changing +a return type is only possible with a child type. -.. [4] When changing the parent class, the original parent class must remain an - ancestor of the class. +.. _note-8: -.. [5] The value of a constant may only be changed when the constants aren't - used in configuration (e.g. Yaml and XML files), as these do not support - constants and have to hardcode the value. For instance, event name - constants can't change the value without introducing a BC break. - Additionally, if a constant will likely be used in objects that are - serialized, the value of a constant should not be changed. +**[8]** Allowed if the method is final. Methods that received the ``@final`` +annotation after their first release are considered final in their next major +version. Changing an argument type is only possible with a parent type. Changing +a return type is only possible with a child type. -.. [6] Allowed using the ``@final`` annotation. +.. _note-9: -.. [7] Allowed if the class is final. Classes that received the ``@final`` - annotation after their first release are considered final in their - next major version. - Changing an argument type is only possible with a parent type. - Changing a return type is only possible with a child type. +**[9]** Allowed for the ``void`` return type. -.. [8] Allowed if the method is final. Methods that received the ``@final`` - annotation after their first release are considered final in their - next major version. - Changing an argument type is only possible with a parent type. - Changing a return type is only possible with a child type. +.. _note-10: -.. [9] Allowed for the ``void`` return type. +**[10]** Parameter names are only covered by the compatibility promise for +constructors of Attribute classes. Using PHP named arguments might break your +code when upgrading to newer Symfony versions. .. _`Semantic Versioning`: https://semver.org/ diff --git a/contributing/code/conventions.rst b/contributing/code/conventions.rst index c5dda7e7972..7a41d20cc7c 100644 --- a/contributing/code/conventions.rst +++ b/contributing/code/conventions.rst @@ -7,8 +7,10 @@ coding standards and conventions used in the core framework to make it more consistent and predictable. You are encouraged to follow them in your own code, but you don't need to. -Method Names ------------- +.. _method-names: + +Naming a Method +--------------- When an object has a "main" many relation with related "things" (objects, parameters, ...), the method names are normalized: @@ -77,19 +79,63 @@ must be used instead (where ``XXX`` is the name of the related thing): ``replaceXXX()``, on the other hand, cannot add new elements. If an unrecognized key is passed to ``replaceXXX()`` it must throw an exception. +Writing a CHANGELOG Entry +------------------------- + +When adding a new feature in a minor version or deprecating an existing +behavior, an entry to the relevant CHANGELOG(s) should be added. + +New features and deprecations must be described in a file named +``CHANGELOG.md`` that should be at the root directory of the modified +Component, Bridge or Bundle. + +The file must be written with the Markdown syntax and follow the following +conventions: + +* The main title is always ``CHANGELOG``; + +* Each entry must be added to a minor version section (like ``5.3``) as a list + element; + +* No third level sections are allowed; + +* Messages should follow the :ref:`commit message conventions `: + should be short, capitalize the line, do not end with a period, use an + imperative verb to start the line; + +* New entries must be added on top of the list. + +Here is a complete example for reference: + +.. code-block:: markdown + + CHANGELOG + ========= + + 5.3 + --- + + * Add `MagicConfig` that allows configuring things + +.. note:: + + The main ``CHANGELOG-*`` files at the ``symfony/symfony`` root directory + are automatically generated when releases are prepared and should never be + modified manually. + .. _contributing-code-conventions-deprecations: Deprecating Code ---------------- -From time to time, some classes and/or methods are deprecated in the -framework; that happens when a feature implementation cannot be changed -because of backward compatibility issues, but we still want to propose a -"better" alternative. In that case, the old implementation can be **deprecated**. +From time to time, some classes and/or methods are deprecated in the framework; +that happens when a feature implementation cannot be changed because of +backward compatibility issues, but we still want to propose a "better" +alternative. In that case, the old implementation can be **deprecated**. Deprecations must only be introduced on the next minor version of the impacted -component (or bundle, or bridge, or contract). -They can exceptionally be introduced on previous supported versions if they are critical. +component (or bundle, or bridge, or contract). They can exceptionally be +introduced on previous supported versions if they are critical. A new class (or interface, or trait) cannot be introduced as deprecated, or contain deprecated methods. @@ -142,45 +188,61 @@ after the use declarations, like in this example from .. _`ServiceRouterLoader`: https://github.com/symfony/symfony/blob/4.4/src/Symfony/Component/Routing/Loader/DependencyInjection/ServiceRouterLoader.php -The deprecation must be added to the ``CHANGELOG.md`` file of the impacted component:: +The deprecation must be added to the ``CHANGELOG.md`` file of the impacted component: - 4.4.0 - ----- +.. code-block:: markdown - * Deprecated the `Deprecated` class, use `Replacement` instead. + 4.4 + --- + + * Deprecate the `Deprecated` class, use `Replacement` instead It must also be added to the ``UPGRADE.md`` file of the targeted minor version -(``UPGRADE-4.4.md`` in our example):: +(``UPGRADE-4.4.md`` in our example): + +.. code-block:: markdown DependencyInjection ------------------- - * Deprecated the `Deprecated` class, use `Replacement` instead. + * Deprecate the `Deprecated` class, use `Replacement` instead Finally, its consequences must be added to the ``UPGRADE.md`` file of the next major version -(``UPGRADE-5.0.md`` in our example):: +(``UPGRADE-5.0.md`` in our example): + +.. code-block:: markdown DependencyInjection ------------------- - * Removed the `Deprecated` class, use `Replacement` instead. + * Remove the `Deprecated` class, use `Replacement` instead All these tasks are mandatory and must be done in the same pull request. Removing Deprecated Code ------------------------ -Removing deprecated code can only be done once every 2 years, on the next major version of the -impacted component (``master`` branch). +Removing deprecated code can only be done once every two years, on the next +major version of the impacted component (``6.0`` branch, ``7.0`` branch, etc.). -When removing deprecated code, the consequences of the deprecation must be added to the ``CHANGELOG.md`` file -of the impacted component:: +When removing deprecated code, the consequences of the deprecation must be added +to the ``CHANGELOG.md`` file of the impacted component: - 5.0.0 - ----- +.. code-block:: markdown - * Removed the `Deprecated` class, use `Replacement` instead. + 5.0 + --- + + * Remove the `Deprecated` class, use `Replacement` instead This task is mandatory and must be done in the same pull request. .. _`@-silencing operator`: https://www.php.net/manual/en/language.operators.errorcontrol.php + +Naming Commands and Options +--------------------------- + +Commands and their options should be named and described using the English +imperative mood (i.e. 'run' instead of 'runs', 'list' instead of 'lists'). Using +the imperative mood is concise and consistent with similar command-line +interfaces (such as Unix man pages). diff --git a/contributing/code/core_team.rst b/contributing/code/core_team.rst index 1c6c64bbc98..a659666c2ec 100644 --- a/contributing/code/core_team.rst +++ b/contributing/code/core_team.rst @@ -24,27 +24,21 @@ The Symfony Core groups, in descending order of priority, are as follows: 1. **Project Leader** -* Elects members in any other group; -* Merges pull requests in all Symfony repositories. + * Elects members in any other group; + * Merges pull requests in all Symfony repositories. 2. **Mergers Team** -* Merge pull requests on the main Symfony repository. + * Merge pull requests on the main Symfony repository. In addition, there are other groups created to manage specific topics: -**Security Team** +* **Security Team**: manages the whole security process (triaging reported vulnerabilities, + fixing the reported issues, coordinating the release of security fixes, etc.) -* Manage the whole security process (triaging reported vulnerabilities, fixing - the reported issues, coordinating the release of security fixes, etc.) +* **Recipes Team**: manages the recipes in the main and contrib recipe repositories. -**Recipes Team** - -* Manage the recipes in the main and contrib recipe repositories. - -**Documentation Team** - -* Manage the whole `symfony-docs repository`_. +* **Documentation Team**: manages the whole `symfony-docs repository`_. Active Core Members ~~~~~~~~~~~~~~~~~~~ @@ -60,19 +54,28 @@ Active Core Members * **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`_). + * **Yonel Ceruto** (`yceruto`_); + * **Tobias Nyholm** (`Nyholm`_); + * **Wouter De Jong** (`wouterj`_); + * **Alexander M. Turek** (`derrabus`_); + * **Jérémy Derussé** (`jderusse`_); + * **Titouan Galopin** (`tgalopin`_); + * **Oskar Stark** (`OskarStark`_); + * **Thomas Calvet** (`fancyweb`_); + * **Mathieu Santostefano** (`welcomattic`_); + * **Kevin Bond** (`kbond`_); + * **Jérôme Tamarelle** (`gromnan`_). * **Security Team** (``@symfony/security`` on GitHub): * **Fabien Potencier** (`fabpot`_); - * **Michael Cullum** (`michaelcullum`_). + * **Michael Cullum** (`michaelcullum`_); + * **Jérémy Derussé** (`jderusse`_). * **Recipes Team**: @@ -85,7 +88,6 @@ Active Core Members * **Ryan Weaver** (`weaverryan`_); * **Christian Flothmann** (`xabbuh`_); * **Wouter De Jong** (`wouterj`_); - * **Jules Pietri** (`HeahDude`_); * **Javier Eguiluz** (`javiereguiluz`_). * **Oskar Stark** (`OskarStark`_). @@ -99,12 +101,15 @@ Symfony contributions: * **Abdellatif AitBoudad** (`aitboudad`_); * **Romain Neutron** (`romainneutron`_); * **Jordi Boggiano** (`Seldaek`_); -* **Lukas Kahwe Smith** (`lsmith77`_). +* **Lukas Kahwe Smith** (`lsmith77`_); +* **Jules Pietri** (`HeahDude`_); +* **Jakub Zalas** (`jakzal`_); +* **Samuel Rozé** (`sroze`_). Core Membership Application ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -At present, new Symfony Core membership applications are not accepted. +About once a year, the core team discusses the opportunity to invite new members. Core Membership Revocation ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -116,9 +121,6 @@ A Symfony Core membership can be revoked for any of the following reasons: * Willful negligence or intent to harm the Symfony project; * Upon decision of the **Project Leader**. -Should new Symfony Core memberships be accepted in the future, revoked -members must wait at least 12 months before re-applying. - Code Development Rules ---------------------- @@ -148,9 +150,13 @@ A pull request **can be merged** if: * Enough time was given for peer reviews; -* At least two **Merger Team** members voted ``+1`` (only one if the submitter - is part of the Merger team) and no Core member voted ``-1`` (via GitHub - reviews or as comments). +* It is a bug fix and at least two **Mergers Team** members voted ``+1`` + (only one if the submitter is part of the Mergers team) and no Core + member voted ``-1`` (via GitHub reviews or as comments). + +* It is a new feature and at least two **Mergers Team** members voted + ``+1`` (if the submitter is part of the Mergers team, two *other* members) + and no Core member voted ``-1`` (via GitHub reviews or as comments). Pull Request Merging Process ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -169,7 +175,7 @@ The **Project Leader** is also the release manager for every Symfony version. Symfony Core Rules and Protocol Amendments ------------------------------------------ -The rules described in this document may be amended at anytime at the +The rules described in this document may be amended at any time at the discretion of the **Project Leader**. .. [1] Minor changes comprise typos, DocBlock fixes, code standards @@ -200,3 +206,10 @@ discretion of the **Project Leader**. .. _`OskarStark`: https://github.com/OskarStark .. _`romainneutron`: https://github.com/romainneutron .. _`lsmith77`: https://github.com/lsmith77/ +.. _`derrabus`: https://github.com/derrabus/ +.. _`jderusse`: https://github.com/jderusse/ +.. _`tgalopin`: https://github.com/tgalopin/ +.. _`fancyweb`: https://github.com/fancyweb/ +.. _`welcomattic`: https://github.com/welcomattic/ +.. _`kbond`: https://github.com/kbond/ +.. _`gromnan`: https://github.com/gromnan/ diff --git a/contributing/code/license.rst b/contributing/code/license.rst index 8c7c2fd19db..8f0ff3f6501 100644 --- a/contributing/code/license.rst +++ b/contributing/code/license.rst @@ -5,7 +5,7 @@ Symfony Code License Symfony code is released under `the MIT license`_: -Copyright (c) 2004-2020 Fabien Potencier +Copyright (c) 2004-2021 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/contributing/code/maintenance.rst b/contributing/code/maintenance.rst index 854cd74b219..04740ce8c6e 100644 --- a/contributing/code/maintenance.rst +++ b/contributing/code/maintenance.rst @@ -16,21 +16,23 @@ acceptable changes. When documentation (or PHPDoc) is not in sync with the code, code behavior should always be considered as being the correct one. -Besides bug fixes, other minor changes can be accepted in a patch version: +To avoid backward compatibility breaks, we tend to be very strict about changes +accepted for patch versions. -* **Performance improvement**: Performance improvement should only be accepted - if the changes are local (located in one class) and only for algorithmic - issues (any such patches must come with numbers that show a significant - improvement on real-world code); +Besides bug fixes, other minor changes might be accepted in a patch version on +a case by case basis: -* **Newer versions of PHP**: Fixes that add support for newer versions of - PHP are acceptable if they don't break the unit test suite; +* **Newer versions of PHP**: Fixes that add support for newer versions of PHP + are acceptable if they don't break the unit test suite, but we never add + support for newer PHP features; * **Newer versions of popular OSes**: Fixes that add support for newer versions of popular OSes (Linux, MacOS and Windows) are acceptable if they don't break - the unit test suite; + the unit test suite, but we never add support for newer PHP features or newer + versions of OSes; -* **Translations**: Translation updates and additions are accepted; +* **Translations**: Translation updates and additions are always merged in the + oldest maintained branch; * **External data**: Updates for external data included in Symfony can be updated (like ICU for instance); @@ -39,19 +41,40 @@ Besides bug fixes, other minor changes can be accepted in a patch version: of a dependency is possible, bumping to a major one or increasing PHP minimal version is not; +* **Tests**: Tests that increase the code coverage can be added. + +The following changes are **generally not accepted** in a patch version, except +on a case by case basis (mostly when this is related to fixing a security +issue): + +* **Performance improvement**: Performance improvement should only be accepted + if the changes are local (located in one class) and only for algorithmic + issues (any such patches must come with numbers that show a significant + improvement on real-world code); + * **Coding standard and refactoring**: Coding standard fixes or code - refactoring are not recommended but can be accepted for consistency with the - existing code base, if they are not too invasive, and if merging them on - master would not lead to complex branch merging; + refactoring are almost never accepted except for consistency with the + existing code base, if they are not too invasive, and if merging them into + higher branches would not lead to complex branch merging. -* **Tests**: Tests that increase the code coverage can be added. +* **Adding new classes or non private methods**: While working on a bug fix, + never introduce new classes or public/protected methods (or global + functions). + +* **Adding configuration options**: Introducing new configuration options is + never allowed. + +* **Adding new deprecations**: After a version reaches stability, new + deprecations cannot be added anymore. Anything not explicitly listed above should be done on the next minor or major -version instead (aka the *master* branch). For instance, the following changes -are never accepted in a patch version: +version instead. For instance, the following changes are never accepted in a +patch version: * **New features**; +* **Security hardening**; + * **Backward compatibility breaks**: Note that backward compatibility breaks can be done when fixing a security issue if it would not be possible to fix it otherwise; @@ -74,7 +97,7 @@ are never accepted in a patch version: .. note:: This policy is designed to enable a continuous upgrade path that allows one - to move forward with newest Symfony versions in the safest way. One should + to move forward with the newest Symfony versions in the safest way. One should be able to move PHP versions, OS or Symfony versions almost independently. That's the reason why supporting the latest PHP versions or OS features is considered as bug fixes. diff --git a/contributing/code/pull_requests.rst b/contributing/code/pull_requests.rst index 7c4f1b1d4cf..4364190bcde 100644 --- a/contributing/code/pull_requests.rst +++ b/contributing/code/pull_requests.rst @@ -1,6 +1,12 @@ Proposing a Change ================== +.. admonition:: Screencast + :class: screencast + + Do you prefer video tutorials? Check out the `Contributing Back To Symfony`_ + screencast series. + A pull request, "PR" for short, is the best way to provide a bug fix or to propose enhancements to Symfony. @@ -93,7 +99,7 @@ Get the Symfony source code: .. code-block:: terminal $ cd symfony - $ git remote add upstream git://github.com/symfony/symfony.git + $ git remote add upstream https://github.com/symfony/symfony.git Check that the current Tests Pass ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -101,12 +107,6 @@ Check that the current Tests Pass Now that Symfony is installed, check that all unit tests pass for your environment as explained in the dedicated :doc:`document `. -.. tip:: - - If tests are failing, check on `Travis-CI`_ if the same test is - failing there as well. In that case you do not need to be concerned - about the test failing locally. - .. _step-2-work-on-your-patch: Step 3: Work on your Pull Request @@ -124,25 +124,26 @@ Choose the right Branch Before working on a PR, you must determine on which branch you need to work: -* ``3.4``, if you are fixing a bug for an existing feature or want to make a - change that falls into the :doc:`list of acceptable changes in patch versions - ` (you may have to choose a higher branch if - the feature you are fixing was introduced in a later version); +* If you are fixing a bug for an existing feature or want to make a change + that falls into the :doc:`list of acceptable changes in patch versions + `, pick the oldest concerned maintained + branch (you can find them on the `Symfony releases page`_). E.g. if you + found a bug introduced in ``v5.1.10``, you need to work on ``5.4``. -* ``master``, if you are adding a new feature. +* ``6.2``, if you are adding a new feature. The only exception is when a new :doc:`major Symfony version ` - (4.0, 5.0, etc.) comes out every two years. Because of the + (5.0, 6.0, etc.) comes out every two years. Because of the :ref:`special development process ` of those versions, - you need to use the previous minor version for the features (e.g. use ``3.4`` - instead of ``4.0``, use ``4.4`` instead of ``5.0``, etc.) + you need to use the previous minor version for the features (e.g. use ``4.4`` + instead of ``5.0``, use ``5.4`` instead of ``6.0``, etc.) .. note:: All bug fixes merged into maintenance branches are also merged into more recent branches on a regular basis. For instance, if you submit a PR - for the ``3.4`` branch, the PR will also be applied by the core team on - the ``master`` branch. + for the ``4.4`` branch, the PR will also be applied by the core team on + the ``5.x`` and ``6.x`` branches. Create a Topic Branch ~~~~~~~~~~~~~~~~~~~~~ @@ -152,20 +153,20 @@ 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 bug fix for the ``3.4`` branch, first track the remote -``3.4`` branch locally: +Or, if you want to provide a bug fix for the ``4.4`` branch, first track the remote +``4.4`` branch locally: .. code-block:: terminal - $ git checkout -t origin/3.4 + $ git checkout --track origin/4.4 -Then create a new branch off the ``3.4`` branch to work on the bug fix: +Then create a new branch off the ``4.4`` branch to work on the bug fix: .. code-block:: terminal - $ git checkout -b BRANCH_NAME 3.4 + $ git checkout -b BRANCH_NAME 4.4 .. tip:: @@ -193,8 +194,8 @@ want to debug are installed by running ``composer install`` inside it. .. 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 + 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. @@ -224,7 +225,20 @@ in mind the following: * Never fix coding standards in some existing code as it makes the code review more difficult; -* Write good commit messages (see the tip below). +.. _commit-messages: + +* Write good commit messages: Start by a short subject line (the first line), + followed by a blank line and a more detailed description. + + The subject line should start with the Component, Bridge or Bundle you are + working on in square brackets (``[DependencyInjection]``, + ``[FrameworkBundle]``, ...). + + Then, capitalize the sentence, do not end with a period, and use an + imperative verb to start. + + Here is a full example of a subject line: ``[MagicBundle] Add `MagicConfig` + that allows configuring things``. .. tip:: @@ -233,16 +247,7 @@ in mind the following: as defined in `PSR-1`_ and `PSR-2`_. A status is posted below the pull request description with a summary - of any problems it detects or any `Travis-CI`_ build failures. - -.. tip:: - - A good commit message is composed of a summary (the first line), - optionally followed by a blank line and a more detailed description. The - summary should start with the Component you are working on in square - brackets (``[DependencyInjection]``, ``[FrameworkBundle]``, ...). Use a - verb (``fixed ...``, ``added ...``, ...) to start the summary and don't - add a period at the end. + of any problems it detects or any GitHub Actions build failures. .. _prepare-your-patch-for-submission: @@ -277,15 +282,15 @@ 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. ``3.4``) + Replace ``5.x`` with the branch you selected previously (e.g. ``4.4``) if you are working on a bug fix. When doing the ``rebase`` command, you might have to fix merge conflicts. @@ -312,8 +317,8 @@ You can now make a pull request on the ``symfony/symfony`` GitHub repository. .. tip:: - Take care to point your pull request towards ``symfony:3.4`` if you want - the core team to pull a bug fix based on the ``3.4`` branch. + Take care to point your pull request towards ``symfony:4.4`` if you want + the core team to pull a bug fix based on the ``4.4`` branch. To ease the core team work, always include the modified components in your pull request message, like in: @@ -366,8 +371,7 @@ because you want early feedback on your work, add an item to todo-list: - [ ] gather feedback for my changes As long as you have items in the todo-list, please prefix the pull request -title with "[WIP]". If you do not yet want to trigger the automated tests, -you can also set the PR to `draft status`_. +title with "[WIP]". In the pull request description, give as much detail as possible about your changes (don't hesitate to give code examples to illustrate your points). If @@ -396,18 +400,110 @@ The :doc:`core team ` is responsible for deciding which PR gets merged, so their feedback is the most relevant. So do not feel pressured to refactor your code immediately when someone provides feedback. +Automated Feedback +~~~~~~~~~~~~~~~~~~ + +There are many automated scripts that will provide feedback on a pull request. + +fabbot +"""""" + +`fabbot`_ will review code style, check for common typos and make sure the git +history looks good. If there are any issues, fabbot will often suggest what changes +that should be done. Most of the time you get a command to run to automatically +fix the changes. + +It is rare, but fabbot could be wrong. One should verify if the suggested changes +make sense and that they are related to the pull request. + +Psalm +""""" + +`Psalm`_ will make a comment on a pull request if it discovers any potential +type errors. The Psalm errors are not always correct, but each should be reviewed +and discussed. A pull request should not update the Psalm baseline nor add ``@psalm-`` +annotations. + +After the `Psalm phar is installed`_, the analysis can be run locally with: + +.. code-block:: terminal + + $ psalm.phar src/Symfony/Component/Workflow + +Automated Tests +~~~~~~~~~~~~~~~ + +A series of automated tests will run when submitting the pull request. +These test the code under different conditions, to be sure nothing +important is broken. Test failures can be unrelated to your changes. If you +think this is the case, you can check if the target branch has the same +errors and leave a comment on your PR. + +Otherwise, the test failure might be caused by your changes. The following +test scenarios run on each change: + +``PHPUnit / Tests`` + This job runs on Ubuntu using multiple PHP versions (each in their + own job). These jobs run the testsuite just like you would do locally. + + A failure in these jobs often indicates a bug in the code. + +``PHPUnit / Tests (high-deps)`` + This job checks each package (bridge, bundle or component) in ``src/`` + individually by calling ``composer update`` and ``phpunit`` from inside + each package. + + A failure in this job often indicates a missing package in the + ``composer.json`` of the failing package (e.g. + ``src/Symfony/Bundle/FrameworkBundle/composer.json``). + + This job also runs relevant packages using a "flipped" test (indicated + by a ``^`` suffix in the package name). These tests checkout the + previous major release (e.g. ``4.4`` for a pull requests on ``5.4``) + and run the tests with your branch as dependency. + + A failure in these flipped tests indicate a backwards compatibility + break in your changes. + +``PHPUnit / Tests (low-deps)`` + This job also checks each package individually, but then uses + ``composer update --prefer-lowest`` before running the tests. + + A failure in this job often indicates a wrong version range or a + missing package in the ``composer.json`` of the failing package. + +``continuous-integration/appveyor/pr`` + This job runs on Windows using the x86 architecture and the lowest + supported PHP version. All tests first run without extra PHP + extensions. Then, all skipped tests are run using all required PHP + extensions. + + A failure in this job often indicate that your changes do not support + Windows, x86 or PHP with minimal extensions. + +``Integration / Tests`` + Integration tests require other services (e.g. Redis or RabbitMQ) to + run. This job only runs the tests in the ``integration`` PHPUnit group. + + A failure in this job indicates a bug in the communication with these + services. + +``PHPUnit / Tests (experimental)`` + This job always passes (even with failing tests) and is used by the + core team to prepare for the upcoming PHP versions. + .. _rework-your-patch: Rework your Pull Request ~~~~~~~~~~~~~~~~~~~~~~~~ Based on the feedback on the pull request, you might need to rework your -PR. Before re-submitting the PR, rebase with ``upstream/master`` or -``upstream/3.4``, don't merge; and force the push to the origin: +PR. Before re-submitting the PR, rebase with ``upstream/5.x`` or +``upstream/4.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:: @@ -425,11 +521,13 @@ before merging. .. _GitHub: https://github.com/join .. _`GitHub's documentation`: https://help.github.com/github/using-git/ignoring-files .. _Symfony repository: https://github.com/symfony/symfony +.. _Symfony releases page: https://symfony.com/releases#maintained-symfony-branches .. _`documentation repository`: https://github.com/symfony/symfony-docs .. _`fabbot`: https://fabbot.io +.. _`Psalm`: https://psalm.dev/ .. _`PSR-1`: https://www.php-fig.org/psr/psr-1/ .. _`PSR-2`: https://www.php-fig.org/psr/psr-2/ .. _`searching on GitHub`: https://github.com/symfony/symfony/issues?q=+is%3Aopen+ .. _`Symfony Slack`: https://symfony.com/slack-invite -.. _`Travis-CI`: https://travis-ci.org/symfony/symfony -.. _`draft status`: https://help.github.com/github/collaborating-with-issues-and-pull-requests/about-pull-requests#draft-pull-requests +.. _`Psalm phar is installed`: https://psalm.dev/docs/running_psalm/installation/ +.. _`Contributing Back To Symfony`: https://symfonycasts.com/screencast/contributing diff --git a/contributing/code/security.rst b/contributing/code/security.rst index 32401d658f9..7aab51ff919 100644 --- a/contributing/code/security.rst +++ b/contributing/code/security.rst @@ -13,6 +13,28 @@ bug tracker and don't publish it publicly. Instead, all security issues must be sent to **security [at] symfony.com**. Emails sent to this address are forwarded to the Symfony core team private mailing-list. +The following issues are not considered security issues and should be handled +as regular bug fixes (if you have any doubts, don't hesitate to send us an +email for confirmation): + +* Any security issues found in debug tools that must never be enabled in + production (including the web profiler or anything enabled when ``APP_DEBUG`` + is set to ``true`` or ``APP_ENV`` set to anything but ``prod``); + +* Any fix that can be classified as **security hardening** like route + enumeration, login throttling bypasses, denial of service attacks, or timing + attacks. + +In any case, the core team has the final decision on which issues are +considered security vulnerabilities. + +Security Bug Bounties +--------------------- + +Symfony is an Open-Source project where most of the work is done by volunteers. +We appreciate that developers are trying to find security issues in Symfony and +report them responsibly, but we are currently unable to pay bug bounties. + Resolving Process ----------------- diff --git a/contributing/code/stack_trace.rst b/contributing/code/stack_trace.rst index 11163b2dcd4..cd672e05a2a 100644 --- a/contributing/code/stack_trace.rst +++ b/contributing/code/stack_trace.rst @@ -56,7 +56,7 @@ things for you beforehand, like routing or access control. Symfony being both a framework and library of components, it calls your code and then your code might call it. This means you will always have at least 2 parts, very often 3 in your stack traces when using Symfony: -a part that starts in one of the entrypoints of the framework +a part that starts in one of the entry points of the framework (``bin/console`` or ``public/index.php`` in most cases), and ends when reaching your code, most times in a command or in a controller found under ``src``. Then, either the exception is thrown in your code or in @@ -75,7 +75,7 @@ Next, you can have a look at what packages are involved. Files under library and ``acme/router`` the Composer package. If you plan on reporting the bug, make sure to report it to the library throwing the exception. ``composer home acme/router`` should lead you to the right -place for that. As Symfony is a monorepository, use ``composer home +place for that. As Symfony is a mono-repository, use ``composer home symfony/symfony`` when reporting a bug for any component. Getting Stack Traces with Symfony @@ -92,7 +92,7 @@ from your development environment through a web browser: 1. Are there several exceptions? If yes, the most interesting one is often exception 1/n which, is shown *last* in the example below (it - is the one marked as exception [1/2]). + is the one marked as an exception [1/2]). 2. Under the "Stack Traces" tab, you will find exceptions in plain text, so that you can easily share them in e.g. bug reports. Make sure to **remove any sensitive information** before doing so. @@ -109,7 +109,7 @@ Since stack traces may contain sensitive data, they should not be exposed in production. Getting a stack trace from your production environment, although more involving, is still possible with solutions that include but are not limited to sending them to an email address -with monolog. +with Monolog. Stack Traces in the CLI ~~~~~~~~~~~~~~~~~~~~~~~ @@ -137,7 +137,7 @@ going on: If that is not the case, you can obtain a stack trace by increasing the -:doc:`verbosity level` with ``--verbose``: +:doc:`verbosity level ` with ``--verbose``: .. code-block:: terminal diff --git a/contributing/code/standards.rst b/contributing/code/standards.rst index 586a868aae2..134da5c1196 100644 --- a/contributing/code/standards.rst +++ b/contributing/code/standards.rst @@ -5,8 +5,8 @@ Symfony code is contributed by thousands of developers around the world. To make every piece of code look and feel familiar, Symfony defines some coding standards that all contributions must follow. -These Symfony coding standards are based on the `PSR-1`_, `PSR-2`_ and `PSR-4`_ -standards, so you may already know most of them. +These Symfony coding standards are based on the `PSR-1`_, `PSR-2`_, `PSR-4`_ +and `PSR-12`_ standards, so you may already know most of them. Making your Code Follow the Coding Standards -------------------------------------------- @@ -78,7 +78,7 @@ short example containing most features described below:: } /** - * Transforms the input given as first argument. + * Transforms the input given as the first argument. * * @param bool|string $dummy Some argument description * @param array $options An options collection to be used within the transformation @@ -210,8 +210,12 @@ Naming Conventions * Use `snake_case`_ for configuration parameters and Twig template variables (e.g. ``framework.csrf_protection``, ``http_status_code``); -* Use namespaces for all PHP classes and `UpperCamelCase`_ for their names (e.g. - ``ConsoleLogger``); +* Use SCREAMING_SNAKE_CASE for constants (e.g. ``InputArgument::IS_ARRAY``); + +* Use `UpperCamelCase`_ for enumeration cases (e.g. ``InputArgumentMode::IsArray``); + +* Use namespaces for all PHP classes, interfaces, traits and enums and + `UpperCamelCase`_ for their names (e.g. ``ConsoleLogger``); * Prefix all abstract classes with ``Abstract`` except PHPUnit ``*TestCase``. Please note some early Symfony classes do not follow this convention and @@ -222,8 +226,14 @@ Naming Conventions * Suffix traits with ``Trait``; +* Don't use a dedicated suffix for classes or enumerations (e.g. like ``Class`` + or ``Enum``), except for the cases listed below. + * Suffix exceptions with ``Exception``; +* Prefix PHP attributes with ``As`` where applicable (e.g. ``#[AsCommand]`` + instead of ``#[Command]``, but ``#[When]`` is kept as-is); + * Use UpperCamelCase for naming PHP files (e.g. ``EnvVarProcessor.php``) and snake case for naming Twig templates and web assets (``section_layout.html.twig``, ``index.scss``); @@ -287,6 +297,7 @@ License .. _`PSR-1`: https://www.php-fig.org/psr/psr-1/ .. _`PSR-2`: https://www.php-fig.org/psr/psr-2/ .. _`PSR-4`: https://www.php-fig.org/psr/psr-4/ +.. _`PSR-12`: https://www.php-fig.org/psr/psr-12/ .. _`identical comparison`: https://www.php.net/manual/en/language.operators.comparison.php .. _`Yoda conditions`: https://en.wikipedia.org/wiki/Yoda_conditions .. _`camelCase`: https://en.wikipedia.org/wiki/Camel_case diff --git a/contributing/code/tests.rst b/contributing/code/tests.rst index 3ba250a50bb..376792f879f 100644 --- a/contributing/code/tests.rst +++ b/contributing/code/tests.rst @@ -24,6 +24,16 @@ tests, such as Doctrine, Twig and Monolog. To do so, $ composer update +.. tip:: + + Dependencies might fail to update and in this case Composer might need you to + tell it what Symfony version you are working on. + To do so set ``COMPOSER_ROOT_VERSION`` variable, e.g.: + + .. code-block:: terminal + + $ COMPOSER_ROOT_VERSION=4.4.x-dev composer update + .. _running: Running the Tests diff --git a/contributing/code_of_conduct/care_team.rst b/contributing/code_of_conduct/care_team.rst index ae342b89999..fb2c60faebd 100644 --- a/contributing/code_of_conduct/care_team.rst +++ b/contributing/code_of_conduct/care_team.rst @@ -21,25 +21,34 @@ Members Here are all the members of the CARE team (in alphabetic order). You can contact any of them directly using the contact details below or you can also contact all -of them at once by emailing **care@symfony.com**: +of them at once by emailing ** care@symfony.com **. -* **Emilie Lorenzo** +* **Timo Bakx** - * *E-mail*: emilie.lorenzo [at] symfony.com - * *Twitter*: `@EmilieLorenzo `_ - * *SymfonyConnect*: `emilielorenzo `_ + * *E-mail*: timobakx [at] gmail.com + * *Twitter*: `@TimoBakx `_ + * *SymfonyConnect*: `timobakx `_ + * *SymfonySlack*: `@Timo Bakx `_ -* **Michelle Sanver** +* **Zan Baldwin** - * *E-mail*: michelle [at] liip.ch - * *Twitter*: `@michellesanver `_ - * *SymfonyConnect*: `michellesanver `_ + * *E-mail*: hello [at] zanbaldwin.com + * *Twitter*: `@ZanBaldwin `_ + * *SymfonyConnect*: `zanbaldwin `_ + * *SymfonySlack*: `@Zan `_ + +* **Magali Milbergue** + + * *E-mail*: magali.milbergue [at] gmail.com + * *Twitter*: `@magalimilbergue `_ + * *SymfonyConnect*: `magali_milbergue `_ * **Tobias Nyholm** * *E-mail*: tobias.nyholm [at] gmail.com * *Twitter*: `@tobiasnyholm `_ * *SymfonyConnect*: `tobias `_ + * *SymfonySlack*: `@Tobias Nyholm `_ About the CARE Team ------------------- diff --git a/contributing/code_of_conduct/code_of_conduct.rst b/contributing/code_of_conduct/code_of_conduct.rst index 16ec8f53eb1..6202fdad424 100644 --- a/contributing/code_of_conduct/code_of_conduct.rst +++ b/contributing/code_of_conduct/code_of_conduct.rst @@ -4,12 +4,15 @@ Code of Conduct Our Pledge ---------- -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnic origin, gender identity and expression, level of experience, -education, socio-economic status, nationality, personal appearance, -religion, or sexual identity and orientation. +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. Our Standards ------------- @@ -17,67 +20,115 @@ Our Standards Examples of behavior that contributes to creating a positive environment include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community -Examples of unacceptable behavior by participants include: +Examples of unacceptable behavior include: -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission +* Publishing others’ private information, such as a physical or email address, + without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting Our Responsibilities -------------------- -:doc:`CoC Active Response Ensurers, or CARE`, -are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. +:doc:`CoC Active Response Ensurers (CARE) team members ` +are responsible for clarifying and enforcing our standards of acceptable +behavior and will take appropriate and fair corrective action in response to any +behavior that they deem inappropriate, threatening, offensive, or harmful. -CARE team members have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. +CARE team members have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. Scope ----- -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by CARE team members. +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. Enforcement ----------- Instances of abusive, harassing, or otherwise unacceptable behavior -:doc:`may be reported ` -by contacting the :doc:`CARE team members `. -All complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The CARE team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. +:doc:`may be reported ` by +contacting the :doc:`CARE team members `. +All complaints will be reviewed and investigated promptly and fairly. + +CARE team members are obligated to respect the privacy and security of the +reporter of any incident. + +Enforcement Guidelines +---------------------- + +The :doc:`CARE team members ` will +follow these Community Impact Guidelines in determining the consequences for any +action they deem in violation of this Code of Conduct: + +1. Correction +~~~~~~~~~~~~~ + +Community Impact: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +Consequence: A private, written warning from a CARE team member, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +2. Warning +~~~~~~~~~~ -CARE team members who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by the -:doc:`core team `. +Community Impact: A violation through a single incident or series of actions. + +Consequence: A warning with consequences for continued behavior. No interaction +with the people involved, including unsolicited interaction with those enforcing +the Code of Conduct, for a specified period of time. This includes avoiding +interactions in community spaces as well as external channels like social media. +Violating these terms may lead to a temporary or permanent ban. + +3. Temporary Ban +~~~~~~~~~~~~~~~~ + +Community Impact: A serious violation of community standards, including +sustained inappropriate behavior. + +Consequence: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +4. Permanent Ban +~~~~~~~~~~~~~~~~ + +Community Impact: Demonstrating a pattern of violation of community standards, +including sustained inappropriate behavior, harassment of an individual, or +aggression toward or disparagement of classes of individuals. + +Consequence: A permanent ban from any sort of public interaction within the +community. Attribution ----------- -This Code of Conduct is adapted from the `Contributor Covenant`_, version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct/ +This Code of Conduct is adapted from the `Contributor Covenant`_, version 2.1, +available at https://www.contributor-covenant.org/version/2/1/code_of_conduct.html + +Community Impact Guidelines were inspired by `Mozilla’s code of conduct enforcement ladder`_. Related Documents ----------------- @@ -90,3 +141,4 @@ Related Documents concrete_example_document .. _Contributor Covenant: https://www.contributor-covenant.org +.. _Mozilla’s code of conduct enforcement ladder: https://github.com/mozilla/diversity diff --git a/contributing/code_of_conduct/reporting_guidelines.rst b/contributing/code_of_conduct/reporting_guidelines.rst index 63c4e820ce6..a00394bce65 100644 --- a/contributing/code_of_conduct/reporting_guidelines.rst +++ b/contributing/code_of_conduct/reporting_guidelines.rst @@ -76,7 +76,7 @@ members will not be included in any communication on the incidents as well as re created related to the incidents. CARE team members are expected to inform the CARE team and the reporters -in case of conflicts on interest and recuse themselves if this is deemed a problem. +in case of a conflict of interest, and recuse themselves if this is deemed to be a problem. Appealing the response ---------------------- @@ -93,6 +93,6 @@ Reporting Guidelines derived from those of the `Stumptown Syndicate`_ and the Adopted by `Symfony`_ organizers on 21 February 2018. -.. _`Stumptown Syndicate`: http://stumptownsyndicate.org/code-of-conduct/reporting-guidelines/ +.. _`Stumptown Syndicate`: https://github.com/stumpsyn/policies/blob/master/reporting_guidelines.md/ .. _`Django Software Foundation`: https://www.djangoproject.com/conduct/reporting/ .. _`Symfony`: https://symfony.com diff --git a/contributing/community/mentoring.rst b/contributing/community/mentoring.rst index 040a6ee90f0..511a61e6e82 100644 --- a/contributing/community/mentoring.rst +++ b/contributing/community/mentoring.rst @@ -7,7 +7,7 @@ it might still seem overwhelming - contributing can be complex! For this purpose we created a dedicated `Symfony Slack`_ channel called `#mentoring`_ to connect new contributors to long-time contributors. This is a great way to get one-on-one advice on the entire process. These long-time contributors -do really want to help new contributors - so feel free to ask anything! +truly want to help new contributors - so feel free to ask anything! .. _`Symfony Slack`: https://symfony.com/slack-invite .. _`#mentoring`: https://symfony-devs.slack.com/messages/mentoring diff --git a/contributing/community/releases.rst b/contributing/community/releases.rst index fa8471717f2..916a44f3894 100644 --- a/contributing/community/releases.rst +++ b/contributing/community/releases.rst @@ -7,12 +7,14 @@ release and maintain its different versions. Symfony releases follow the `semantic versioning`_ strategy and they are published through a *time-based model*: -* A new **Symfony patch version** (e.g. 3.4.41, 4.4.9) comes out roughly every +* A new **Symfony patch version** (e.g. 4.4.12, 5.1.9) comes out roughly every month. It only contains bug fixes, so you can safely upgrade your applications; * A new **Symfony minor version** (e.g. 4.4, 5.0, 5.1) comes out every *six months*: - one in *May* and one in *November*. It contains bug fixes and new features, but - it doesn't include any breaking change, so you can safely upgrade your applications; -* A new **Symfony major version** (e.g. 4.0, 5.0) comes out every *two years*. + one in *May* and one in *November*. It contains bug fixes and new features, + can contain new deprecations but it doesn't include any breaking change, + so you can safely upgrade your applications; +* A new **Symfony major version** (e.g. 4.0, 5.0) comes out every *two years* + in November of odd years (e.g. 2019, 2021). It can contain breaking changes, so you may need to do some changes in your applications before upgrading. @@ -53,7 +55,7 @@ Maintenance Starting from the Symfony 3.x branch, the number of minor versions is limited to five per branch (X.0, X.1, X.2, X.3 and X.4). The last minor version of a branch -(e.g. 3.4, 4.4, 5.4) is considered a **long-term support version** and the other +(e.g. 4.4, 5.4) is considered a **long-term support version** and the other ones are considered **standard versions**: ======================= ===================== ================================ @@ -80,27 +82,49 @@ of Symfony to the next one. When a feature implementation cannot be replaced with a better one without breaking backward compatibility, Symfony deprecates the old implementation and -adds a new preferred one along side. Read the +adds a new preferred one alongside. Read the :ref:`conventions ` document to learn more about how deprecations are handled in Symfony. .. _major-version-development: This deprecation policy also requires a custom development process for major -versions (4.0, 5.0, 6.0, etc.) In those cases, Symfony develops at the same time -two versions: the new major one (e.g. 4.0) and the latest version of the -previous branch (e.g. 3.4). +versions (5.0, 6.0, etc.) In those cases, Symfony develops at the same time +two versions: the new major one (e.g. 5.0) and the latest version of the +previous branch (e.g. 4.4). Both versions have the same new features, but they differ in the deprecated -features. The oldest version (3.4 in this example) contains all the deprecated -features whereas the new version (4.0 in this example) removes all of them. +features. The oldest version (4.4 in this example) contains all the deprecated +features whereas the new version (5.0 in this example) removes all of them. -This allows you to upgrade your projects to the latest minor version (e.g. 3.4), +This allows you to upgrade your projects to the latest minor version (e.g. 4.4), see all the deprecation messages and fix them. Once you have fixed all those -deprecations, you can upgrade to the new major version (e.g. 4.0) without +deprecations, you can upgrade to the new major version (e.g. 5.0) without effort, because it contains the same features (the only difference are the deprecated features, which your project no longer uses). +PHP Compatibility +----------------- + +The **minimum** PHP version is decided for each **major** Symfony version by consensus +amongst the :doc:`core team ` and documented as +part of the :ref:`technical requirements for running Symfony applications +`. + +Throughout each Symfony release's support lifetime, all released versions of PHP +including new major versions will be supported. In this way, the **maximum** supported +version of PHP for a maintained Symfony release is the latest released +one that is publicly available. + +For out-of-support releases of Symfony, the latest PHP version at time of EOL is the last +supported PHP version. Newer versions of PHP may or may not function. + +.. note:: + + By exception to the rule, bumping the minimum **minor** version of PHP is + possible for a **minor** Symfony version when this helps fix important + issues. + Rationale --------- diff --git a/contributing/community/review-comments.rst b/contributing/community/review-comments.rst index 36bad6d7221..0a048d8fa6e 100644 --- a/contributing/community/review-comments.rst +++ b/contributing/community/review-comments.rst @@ -28,8 +28,8 @@ constructive, respectful and helpful reviews and replies. welcoming place for everyone. **You are free to disagree with someone's opinions, but don't be disrespectful.** -First of, accept that many programming decisions are opinions. -Discuss trade offs, which you prefer, and reach a resolution quickly. +It’s important to accept that many programming decisions are opinions. +Discuss trade-offs, which you prefer, and reach a resolution quickly. It's not about being right or wrong, but using what works. Tone of Voice @@ -118,13 +118,13 @@ If a piece of code is in fact wrong, explain why: * "We only provide integration with very popular projects (e.g. we integrate Bootstrap but not your own CSS framework)" * "This would require adding lots of code and making lots of changes for a feature that doesn't look so important. - That could hurt maintaining in the future." + That could hurt maintenance in the future." Asking for Changes ------------------ Rarely something is perfect from the start, while the code itself is good. -It may not be optimal or conform the Symfony coding style. +It may not be optimal or conform to the Symfony coding style. Again, understand the author already spent time on the issue and asking for (small) changes may be misinterpreted or seen as a personal attack. @@ -143,7 +143,7 @@ Use words like "Please", "Thank you" and "Could you" instead of making demands; * "Please use 4 spaces instead of tabs", "This needs be on the previous line"; -During a pull request review you can usually leave more then one comment, +During a pull request review you can usually leave more than one comment, you don't have to use "Please" all the time. But it wouldn't hurt. It may not seem like much, but saying "Thank you" does make others feel @@ -158,7 +158,7 @@ In that case, it is better to try to approach the discussion in a different way, to not escalate further. If you want someone to mediate, please join the ``#contribs`` channel on `Symfony Slack`_, -to have a safe environment and keep working together on the common goals. +to have a safe environment and keep working together on common goals. Using Humor ----------- @@ -172,8 +172,8 @@ to the Symfony community.** And don't marginalize someone's problems; Even if someone's explanation is "inviting to joke about it", it's a real problem to them. Making jokes about this doesn't help with solving their -problem and only makes them *feel stupid*. Instead try to discover what -the problem is really about. +problem and only makes them *feel stupid*. Instead, try to discover the +actual problem. Final Words ----------- diff --git a/contributing/community/reviews.rst b/contributing/community/reviews.rst index 14d8b71a28d..bca36099eeb 100644 --- a/contributing/community/reviews.rst +++ b/contributing/community/reviews.rst @@ -109,7 +109,7 @@ to understand the functionality that has been fixed or added and find out whether the implementation is complete. It is okay to do partial reviews! If you do a partial review, comment how far -you got and leave the PR in "Needs Review" state. +you got and leave the PR in the "Needs Review" state. Pick a pull request from the `PRs in need of review`_ and follow these steps: diff --git a/contributing/community/speaker-mentoring.rst b/contributing/community/speaker-mentoring.rst index d8dc6bdde71..82b25c61f57 100644 --- a/contributing/community/speaker-mentoring.rst +++ b/contributing/community/speaker-mentoring.rst @@ -23,7 +23,7 @@ speakers with people who are just taking their first steps in this area: A good first step might be to give a talk at a local user group to a smaller crowd that one knows more intimately. A next step could be to - give a talk at conference in your first language. + give a talk at a conference in your first language. The best way to find people that can review your talk idea or slides is the `#speaker-mentoring`_ channel on `Symfony Slack`_. There are many diff --git a/contributing/diversity/governance.rst b/contributing/diversity/governance.rst index 8dd302ccc0a..93a79ed30fa 100644 --- a/contributing/diversity/governance.rst +++ b/contributing/diversity/governance.rst @@ -64,11 +64,11 @@ 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 guidance team has 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 +For an actionable item to pass, approval from more 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. diff --git a/contributing/documentation/format.rst b/contributing/documentation/format.rst index 1f6f1787918..297db06adf2 100644 --- a/contributing/documentation/format.rst +++ b/contributing/documentation/format.rst @@ -201,9 +201,9 @@ For a deprecation use the ``.. deprecated:: 4.x`` directive: Not passing the root node name to ``TreeBuilder`` was deprecated in Symfony 4.2. Whenever a new major version of Symfony is released (e.g. 5.0, 6.0, etc), -a new branch of the documentation is created from the ``master`` branch. -At this point, all the ``versionadded`` and ``deprecated`` tags for Symfony -versions that have a lower major version will be removed. For example, if +a new branch of the documentation is created from the x.4 branch of the previous +major version. At this point, all the ``versionadded`` and ``deprecated`` tags for +Symfony versions that have a lower major version will be removed. For example, if Symfony 5.0 were released today, 4.0 to 4.4 ``versionadded`` and ``deprecated`` tags would be removed from the new ``5.0`` branch. diff --git a/contributing/documentation/index.rst b/contributing/documentation/index.rst index f16f4e32cc7..9af054d0502 100644 --- a/contributing/documentation/index.rst +++ b/contributing/documentation/index.rst @@ -20,12 +20,3 @@ documentation: :doc:`License ` Explains the details of the Creative Commons BY-SA 3.0 license used for the Symfony Documentation. - -.. toctree:: - :hidden: - - format - license - overview - standards - translations diff --git a/contributing/documentation/overview.rst b/contributing/documentation/overview.rst index fc6a2c4e5e5..78e90d04483 100644 --- a/contributing/documentation/overview.rst +++ b/contributing/documentation/overview.rst @@ -76,7 +76,7 @@ this value accordingly): .. code-block:: terminal $ cd projects/ - $ git clone git://github.com/YOUR-GITHUB-USERNAME/symfony-docs.git + $ git clone git@github.com:YOUR-GITHUB-USERNAME/symfony-docs.git **Step 3.** Add the original Symfony docs repository as a "Git remote" executing this command: @@ -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/3.4 + $ git checkout -b improve_install_article upstream/4.4 In this example, the name of the branch is ``improve_install_article`` and the -``upstream/3.4`` value tells Git to create this branch based on the ``3.4`` +``upstream/4.4`` value tells Git to create this branch based on the ``4.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 ``3.4`` branch. If you are instead documenting a +the error. Nowadays this is the ``4.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/5.4``. **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 ``3.4``, which is the branch that you selected +the **base** branch should be the ``4.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. @@ -194,7 +194,7 @@ Your Next Documentation Contributions Check you out! You've made your first contribution to the Symfony documentation! Somebody throw a party! Your first contribution took a little extra time because -you needed to learn a few standards and setup your computer. But from now on, +you had to learn a few standards and set up your computer. But from now on, your contributions will be much easier to complete. Here is a **checklist** of steps that will guide you through your next @@ -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/3.4 + $ git checkout -b my_changes upstream/4.4 # ... do your changes @@ -229,82 +229,33 @@ this hard work, it's **time to celebrate again!** 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. +Symfony repository checks every Pull Request automatically to look for common +errors, inappropriate words, syntax issues in code blocks, etc. -.. image:: /_images/contributing/docs-pull-request-symfonycloud.png - :align: center - :alt: SymfonyCloud Pull Request Deployment - -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 - SymfonyCloud. Check the `roadmap`_ for maintained branches. - -Build the Documentation Locally -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -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; - -#. Install `Sphinx`_ and `Sphinx Extensions for PHP and Symfony`_ - (depending on your system, you may need to execute this command as root user): - - .. code-block:: terminal - - $ cd _build/ - $ pip install -r .requirements.txt - -#. Run the following command to build the documentation in HTML format: - - .. code-block:: terminal - - $ cd _build/ - $ make html - -The generated documentation is available in the ``_build/html`` directory. +Optionally you can also build the docs in your local machine to debug issues or +to read the documentation offline. To do so, follow the instructions included in +`the README file of symfony-docs repository`_. Frequently Asked Questions -------------------------- -Why Do my Changes Take so Long to Be Reviewed and/or Merged? +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 website. -Why Should I Use the Oldest Maintained Branch Instead of the Master Branch? +Why Should I Use the Oldest Maintained Branch Instead of the Latest Branch? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Consistent with Symfony's source code, the documentation repository is split into multiple branches, corresponding to the different versions of Symfony itself. -The ``master`` branch holds the documentation for the development branch of +The latest (e.g. ``5.x``) branch holds the documentation for the development branch of the code. -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 +Unless you're documenting a feature that was introduced after Symfony 4.4, +your changes should always be based on the ``4.4`` branch. Documentation managers will use the necessary Git-magic to also apply your changes to all the active branches of the documentation. @@ -338,11 +289,6 @@ definitely don't want you to waste your time! .. _`GitHub`: https://github.com/ .. _`fork the repository`: https://help.github.com/github/getting-started-with-github/fork-a-repo .. _`Symfony Documentation Contributors`: https://symfony.com/contributors/doc -.. _`SymfonyConnect`: https://connect.symfony.com/ +.. _`SymfonyConnect`: https://symfony.com/connect/login .. _`Symfony Documentation Badge`: https://connect.symfony.com/badge/36/symfony-documentation-contributor -.. _`SymfonyCloud`: https://symfony.com/cloud -.. _`roadmap`: https://symfony.com/releases -.. _`pip`: https://pip.pypa.io/en/stable/ -.. _`pip installation`: https://pip.pypa.io/en/stable/installing/ -.. _`Sphinx`: https://www.sphinx-doc.org/ -.. _`Sphinx Extensions for PHP and Symfony`: https://github.com/fabpot/sphinx-php +.. _`the README file of symfony-docs repository`: https://github.com/symfony/symfony-docs#readme diff --git a/contributing/index.rst b/contributing/index.rst index 923a4e48ed4..d76b4a8e037 100644 --- a/contributing/index.rst +++ b/contributing/index.rst @@ -7,6 +7,7 @@ Contributing code_of_conduct/index code/index documentation/index + translations/index community/index diversity/index diff --git a/contributing/map.rst.inc b/contributing/map.rst.inc index ffa9b03a5a7..92bc1e2e142 100644 --- a/contributing/map.rst.inc +++ b/contributing/map.rst.inc @@ -16,9 +16,9 @@ * :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** diff --git a/contributing/translations/index.rst b/contributing/translations/index.rst new file mode 100644 index 00000000000..82679a6a0f2 --- /dev/null +++ b/contributing/translations/index.rst @@ -0,0 +1,103 @@ +Contributing Translations +========================= + +Some Symfony Components include certain messages that must be translated to +different languages. For example, if a user submits a form with a wrong value in +a :doc:`TimezoneType ` field, Symfony shows the +following error message by default: "This value is not a valid timezone." + +These messages are translated into tens of languages thanks to the Symfony +community. Symfony adds new messages on a regular basis, so this is an ongoing +translation process and you can help us by providing the missing translations. + +How to Contribute a Translation +------------------------------- + +Imagine that you can speak both English and Swedish and want to check if there's +some missing Swedish translations to contribute them. + +**Step 1.** Translations are contributed to the oldest maintained branch of the +Symfony repository. Visit the `Symfony Releases`_ page to find out which is the +current oldest maintained branch. + +Then, you need to either download or browse that Symfony version contents: + +* If you know Git and prefer the command console, clone the Symfony repository + and check out the oldest maintained branch (read the + :doc:`Symfony Documentation contribution guide ` + if you want to learn about this process); +* If you prefer to use a web based interface, visit + `https://github.com/symfony/symfony `_ + and switch to the oldest maintained branch. + +**Step 2.** Check out if there's some missing translation in your language by +checking these directories: + +* ``src/Symfony/Component/Form/Resources/translations/`` +* ``src/Symfony/Component/Security/Core/Resources/translations/`` +* ``src/Symfony/Component/Validator/Resources/translations/`` + +Symfony uses the :ref:`XLIFF format ` to +store translations. In this example, you are looking for missing Swedish +translations, so you should look for files called ``*.sv.xlf``. + +.. note:: + + If there's no XLIFF file for your language yet, create it yourself + duplicating the original English file (e.g. ``validators.en.xlf``). + +**Step 3.** Contribute the missing translations. To do that, compare the file +in your language to the equivalent file in English. + +Imagine that you open the ``validators.sv.xlf`` and see this at the end of the file: + +.. code-block:: xml + + + + + + This value should be either negative or zero. + Detta värde bör vara antingen negativt eller noll. + + + This value is not a valid timezone. + Detta värde är inte en giltig tidszon. + + +If you open the equivalent ``validators.en.xlf`` file, you can see that the +English file has more messages to translate: + +.. code-block:: xml + + + + + + This value should be either negative or zero. + This value should be either negative or zero. + + + This value is not a valid timezone. + This value is not a valid timezone. + + + This password has been leaked in a data breach, it must not be used. Please use another password. + This password has been leaked in a data breach, it must not be used. Please use another password. + + + This value should be between {{ min }} and {{ max }}. + This value should be between {{ min }} and {{ max }}. + + +The messages with ``id=93`` and ``id=94`` are missing in the Swedish file. +Copy and paste the messages from the English file, translate the content +inside the ```` tag and save the changes. + +**Step 4.** Make the pull request against the +`https://github.com/symfony/symfony `_ repository. +If you need help, check the other Symfony guides about +:doc:`contributing code or docs ` because the process is +the same. + +.. _`Symfony Releases`: https://symfony.com/releases diff --git a/controller.rst b/controller.rst index d29608e6128..e04bfd7a43d 100644 --- a/controller.rst +++ b/controller.rst @@ -36,7 +36,7 @@ class:: /** * @Route("/lucky/number/{max}", name="app_lucky_number") */ - public function number($max) + public function number(int $max): Response { $number = random_int(0, $max); @@ -98,16 +98,16 @@ Add the ``use`` statement atop your controller class and then modify .. code-block:: diff - // src/Controller/LuckyController.php - namespace App\Controller; + // src/Controller/LuckyController.php + namespace App\Controller; + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; - class LuckyController + class LuckyController extends AbstractController - { - // ... - } + { + // ... + } That's it! You now have access to methods like :ref:`$this->render() ` and many others that you'll learn about next. @@ -132,9 +132,10 @@ If you want to redirect the user to another page, use the ``redirectToRoute()`` and ``redirect()`` methods:: use Symfony\Component\HttpFoundation\RedirectResponse; + use Symfony\Component\HttpFoundation\Response; // ... - public function index() + public function index(): RedirectResponse { // redirects to the "homepage" route return $this->redirectToRoute('homepage'); @@ -142,14 +143,19 @@ and ``redirect()`` methods:: // redirectToRoute is a shortcut for: // return new RedirectResponse($this->generateUrl('homepage')); - // does a permanent - 301 redirect + // does a permanent HTTP 301 redirect return $this->redirectToRoute('homepage', [], 301); + // if you prefer, you can use PHP constants instead of hardcoded numbers + return $this->redirectToRoute('homepage', [], Response::HTTP_MOVED_PERMANENTLY); // redirect to a route with parameters return $this->redirectToRoute('app_lucky_number', ['max' => 10]); // redirects to a route and maintains the original query string parameters return $this->redirectToRoute('blog_show', $request->query->all()); + + // redirects to the current route (e.g. for Post/Redirect/Get pattern): + return $this->redirectToRoute($request->attributes->get('_route')); // redirects externally return $this->redirect('http://symfony.com/doc'); @@ -196,12 +202,13 @@ If you need a service in a controller, type-hint an argument with its class (or interface) name. Symfony will automatically pass you the service you need:: use Psr\Log\LoggerInterface; + use Symfony\Component\HttpFoundation\Response; // ... /** * @Route("/lucky/number/{max}") */ - public function number($max, LoggerInterface $logger) + public function number(int $max, LoggerInterface $logger): Response { $logger->info('We are logging!'); // ... @@ -270,7 +277,7 @@ the argument by its name: ->addTag('controller.service_arguments') ->setBindings([ '$logger' => new Reference('monolog.logger.doctrine'), - '$projectDir' => '%kernel.project_dir%' + '$projectDir' => '%kernel.project_dir%', ]) ; @@ -322,10 +329,11 @@ Managing Errors and 404 Pages When things are not found, you should return a 404 response. To do this, throw a special type of exception:: + use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; // ... - public function index() + public function index(): Response { // retrieve the object from database $product = ...; @@ -370,8 +378,10 @@ object. To access it in your controller, add it as an argument and **type-hint it with the Request class**:: use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; + // ... - public function index(Request $request, $firstName, $lastName) + public function index(Request $request, string $firstName, string $lastName): Response { $page = $request->query->get('page', 1); @@ -401,9 +411,11 @@ Session storage and other configuration can be controlled under the To get the session, add an argument and type-hint it with :class:`Symfony\\Component\\HttpFoundation\\Session\\SessionInterface`:: + use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\SessionInterface; + // ... - public function index(SessionInterface $session) + public function index(SessionInterface $session): Response { // stores an attribute for reuse during a later user request $session->set('foo', 'bar'); @@ -413,6 +425,8 @@ To get the session, add an argument and type-hint it with // uses a default value if the attribute doesn't exist $filters = $session->get('filters', []); + + // ... } Stored attributes remain in the session for the remainder of that user's session. @@ -435,8 +449,10 @@ from the session automatically as soon as you retrieve them. This feature makes For example, imagine you're processing a :doc:`form ` submission:: use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; + // ... - public function update(Request $request) + public function update(Request $request): Response { // ... @@ -515,8 +531,9 @@ pass the ``Request`` object to any controller argument that is type-hinted with the ``Request`` class:: use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; - public function index(Request $request) + public function index(Request $request): Response { $request->isXmlHttpRequest(); // is it an Ajax request? @@ -572,7 +589,7 @@ To get the value of any :ref:`configuration parameter from a controller, use the ``getParameter()`` helper method:: // ... - public function index() + public function index(): Response { $contentsDir = $this->getParameter('kernel.project_dir').'/contents'; // ... @@ -584,8 +601,10 @@ Returning JSON Response To return JSON from a controller, use the ``json()`` helper method. This returns a ``JsonResponse`` object that encodes the data automatically:: + use Symfony\Component\HttpFoundation\JsonResponse; // ... - public function index() + + public function index(): JsonResponse { // returns '{"username":"jane.doe"}' and sets the proper Content-Type header return $this->json(['username' => 'jane.doe']); @@ -604,7 +623,10 @@ Streaming File Responses You can use the :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController::file` helper to serve a file from inside a controller:: - public function download() + use Symfony\Component\HttpFoundation\BinaryFileResponse; + // ... + + public function download(): BinaryFileResponse { // send the file contents and force the browser to download it return $this->file('/path/to/some_file.pdf'); @@ -614,8 +636,9 @@ The ``file()`` helper provides some arguments to configure its behavior:: use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\HttpFoundation\ResponseHeaderBag; + // ... - public function download() + public function download(): BinaryFileResponse { // load the file from the filesystem $file = new File('/path/to/some_file.pdf'); diff --git a/controller/argument_value_resolver.rst b/controller/argument_value_resolver.rst index da209a731df..aaf1fa6d390 100644 --- a/controller/argument_value_resolver.rst +++ b/controller/argument_value_resolver.rst @@ -64,11 +64,6 @@ In addition, some components and official bundles provide other value resolvers: The ``SecurityUserValueResolver`` was deprecated in Symfony 4.1 in favor of :class:`Symfony\\Component\\Security\\Http\\Controller\\UserValueResolver`. -``Psr7ServerRequestResolver`` - Injects a `PSR-7`_ compliant version of the current request if type-hinted - with ``RequestInterface``, ``MessageInterface`` or ``ServerRequestInterface``. - It requires installing the `SensioFrameworkExtraBundle`_. - Adding a Custom Value Resolver ------------------------------ @@ -76,6 +71,7 @@ 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:: + // src/Controller/UserController.php namespace App\Controller; use App\Entity\User; @@ -169,7 +165,7 @@ retrieved from the token storage:: $this->security = $security; } - public function supports(Request $request, ArgumentMetadata $argument) + public function supports(Request $request, ArgumentMetadata $argument): bool { if (User::class !== $argument->getType()) { return false; @@ -178,7 +174,7 @@ retrieved from the token storage:: return $this->security->getUser() instanceof User; } - public function resolve(Request $request, ArgumentMetadata $argument) + public function resolve(Request $request, ArgumentMetadata $argument): iterable { yield $this->security->getUser(); } @@ -265,5 +261,3 @@ passing the user along sub-requests). .. _`@ParamConverter`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html .. _`yield`: https://www.php.net/manual/en/language.generators.syntax.php .. _`SecurityBundle`: https://github.com/symfony/security-bundle -.. _`PSR-7`: https://www.php-fig.org/psr/psr-7/ -.. _`SensioFrameworkExtraBundle`: https://github.com/sensiolabs/SensioFrameworkExtraBundle diff --git a/controller/error_pages.rst b/controller/error_pages.rst index 613a245ef8d..3fe650054b8 100644 --- a/controller/error_pages.rst +++ b/controller/error_pages.rst @@ -184,12 +184,11 @@ automatically when installing ``symfony/framework-bundle``): }; With this route added, you can use URLs like these to preview the *error* page -for a given status code as HTML or for a given status code and format. +for a given status code as HTML or for a given status code and format (you might +need to replace ``http://localhost/`` by the host used in your local setup): -.. code-block:: text - - http://localhost/index.php/_error/{statusCode} - http://localhost/index.php/_error/{statusCode}.{format} +* ``http://localhost/_error/{statusCode}`` for HTML +* ``http://localhost/_error/{statusCode}.{format}`` for any other format .. _overriding-non-html-error-output: @@ -208,7 +207,7 @@ JSON/XML/CSV/YAML encoders. When your application throws an exception, Symfony can output it in one of those formats. If you want to change the output contents, create a new Normalizer that supports the ``FlattenException`` input:: - # src/App/Serializer/MyCustomProblemNormalizer.php + # src/Serializer/MyCustomProblemNormalizer.php namespace App\Serializer; use Symfony\Component\ErrorHandler\Exception\FlattenException; @@ -284,7 +283,7 @@ the request that will be dispatched to your controller. In addition, your contro will be passed two parameters: ``exception`` - The original :class:`Throwable` instance being handled. + The original :phpclass:`Throwable` instance being handled. ``logger`` A :class:`\\Symfony\\Component\\HttpKernel\\Log\\DebugLoggerInterface` diff --git a/controller/service.rst b/controller/service.rst index 18ee56ee707..5d9fe9ade26 100644 --- a/controller/service.rst +++ b/controller/service.rst @@ -26,6 +26,8 @@ a service like: ``App\Controller\HelloController::index``: .. code-block:: php-annotations // src/Controller/HelloController.php + namespace App\Controller; + use Symfony\Component\Routing\Annotation\Route; class HelloController @@ -87,6 +89,8 @@ which is a common practice when following the `ADR pattern`_ .. code-block:: php-annotations // src/Controller/Hello.php + namespace App\Controller; + use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; @@ -106,7 +110,7 @@ which is a common practice when following the `ADR pattern`_ # config/routes.yaml hello: path: /hello/{name} - defaults: { _controller: app.hello_controller } + controller: app.hello_controller .. code-block:: xml diff --git a/controller/soap_web_service.rst b/controller/soap_web_service.rst index 8d1f7ae1f0a..effa613c1c5 100644 --- a/controller/soap_web_service.rst +++ b/controller/soap_web_service.rst @@ -38,8 +38,7 @@ In this case, the SOAP service will allow the client to call a method called public function hello($name) { - - $message = new \Swift_Message('Hello Service') + $message = (new \Swift_Message('Hello Service')) ->setTo('me@example.com') ->setBody($name.' says hi!'); @@ -57,6 +56,7 @@ Finally, below is an example of a controller that is capable of handling a SOAP request. Because ``index()`` is accessible via ``/soap``, the WSDL document can be retrieved via ``/soap?wsdl``:: + // src/Controller/HelloServiceController.php namespace App\Controller; use App\Service\HelloService; @@ -95,13 +95,13 @@ 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 ready to return the ``Response``. -Below is an example calling the service using a `NuSOAP`_ client. This example +Below is an example of calling the service using a native `SoapClient`_ client. This example assumes that the ``index()`` method in the controller above is accessible via the route ``/soap``:: $soapClient = new \SoapClient('http://example.com/index.php/soap?wsdl'); - $result = $soapClient->call('hello', ['name' => 'Scott']); + $result = $soapClient->__soapCall('hello', ['name' => 'Scott']); An example WSDL is below. @@ -169,3 +169,4 @@ An example WSDL is below. .. _`NuSOAP`: https://sourceforge.net/projects/nusoap .. _`output buffering`: https://www.php.net/manual/en/book.outcontrol.php .. _`Laminas SOAP`: https://docs.laminas.dev/laminas-soap/server/ +.. _`SoapClient`: https://www.php.net/manual/en/class.soapclient.php diff --git a/controller/upload_file.rst b/controller/upload_file.rst index a50368e9f93..dad80ee957d 100644 --- a/controller/upload_file.rst +++ b/controller/upload_file.rst @@ -327,6 +327,8 @@ Then, define a service for this class: Now you're ready to use this service in the controller:: // src/Controller/ProductController.php + namespace App\Controller; + use App\Service\FileUploader; use Symfony\Component\HttpFoundation\Request; diff --git a/create_framework/dependency_injection.rst b/create_framework/dependency_injection.rst index b38241e3ce2..cd20a947251 100644 --- a/create_framework/dependency_injection.rst +++ b/create_framework/dependency_injection.rst @@ -205,7 +205,7 @@ Now, here is how you can register a custom listener in the front controller:: ->addMethodCall('addSubscriber', [new Reference('listener.string_response')]) ; -Beside describing your objects, the dependency injection container can also be +Besides describing your objects, the dependency injection container can also be configured via parameters. Let's create one that defines if we are in debug mode or not:: diff --git a/create_framework/event_dispatcher.rst b/create_framework/event_dispatcher.rst index fd655a93ebf..bf872a5bb50 100644 --- a/create_framework/event_dispatcher.rst +++ b/create_framework/event_dispatcher.rst @@ -23,7 +23,7 @@ version of this pattern: How does it work? The *dispatcher*, the central object of the event dispatcher system, notifies *listeners* of an *event* dispatched to it. Put another way: your code dispatches an event to the dispatcher, the dispatcher notifies all -registered listeners for the event, and each listener do whatever it wants +registered listeners for the event, and each listener does whatever it wants with the event. As an example, let's create a listener that transparently adds the Google diff --git a/create_framework/front_controller.rst b/create_framework/front_controller.rst index 39286ba8c16..733e764c94e 100644 --- a/create_framework/front_controller.rst +++ b/create_framework/front_controller.rst @@ -56,9 +56,9 @@ not feel like a good abstraction, does it? We still have the ``send()`` method for all pages, our pages do not look like templates and we are still not able to test this code properly. -Moreover, adding a new page means that we need to create a new PHP script, -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 +Moreover, adding a new page means that we need to create a new PHP script, the name of +which is exposed to the end user via the URL +(``http://127.0.0.1:4321/bye.php``). There is a direct mapping between the PHP script name and the client URL. This is because the dispatching of the request is done by the web server directly. It might be a good idea to move this dispatching to our code for better flexibility. This can be achieved by routing @@ -105,7 +105,7 @@ In the ``front.php`` script, ``$map`` associates URL paths with their corresponding PHP script paths. As a bonus, if the client asks for a path that is not defined in the URL map, -we return a custom 404 page; you are now in control of your website. +we return a custom 404 page. You are now in control of your website. To access a page, you must now use the ``front.php`` script: @@ -127,13 +127,13 @@ its sub-directories (only if needed -- see above tip). .. tip:: - You don't even need to setup a web server to test the code. Instead, + You don't even need to set up a web server to test the code. Instead, replace the ``$request = Request::createFromGlobals();`` call to something like ``$request = Request::create('/hello?name=Fabien');`` where the argument is the URL path you want to simulate. -Now that the web server always access the same script (``front.php``) for all -pages, we can secure the code further by moving all other PHP files outside the +Now that the web server always accesses the same script (``front.php``) for all +pages, we can secure the code further by moving all other PHP files outside of the web root directory: .. code-block:: text @@ -151,7 +151,7 @@ web root directory: └── front.php Now, configure your web server root directory to point to ``web/`` and all -other files won't be accessible from the client anymore. +other files will no longer be accessible from the client. To test your changes in a browser (``http://localhost:4321/hello?name=Fabien``), run the :doc:`Symfony Local Web Server `: @@ -166,7 +166,7 @@ run the :doc:`Symfony Local Web Server `: various PHP files; the changes are left as an exercise for the reader. The last thing that is repeated in each page is the call to ``setContent()``. -We can convert all pages to "templates" by just echoing the content and calling +We can convert all pages to "templates" by echoing the content and calling the ``setContent()`` directly from the front controller script:: // example.com/web/front.php @@ -185,7 +185,9 @@ the ``setContent()`` directly from the front controller script:: // ... -And the ``hello.php`` script can now be converted to a template:: +And the ``hello.php`` script can now be converted to a template: + +.. code-block:: html+php get('name', 'World') ?> diff --git a/create_framework/http_foundation.rst b/create_framework/http_foundation.rst index b56834319a8..14f4b00023b 100644 --- a/create_framework/http_foundation.rst +++ b/create_framework/http_foundation.rst @@ -11,7 +11,7 @@ top of the Symfony components is better than creating a framework from scratch. 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. + already has 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:: @@ -25,7 +25,7 @@ First, if the ``name`` query parameter is not defined in the URL query string, you will get a PHP warning; so let's fix it:: // framework/index.php - $name = isset($_GET['name']) ? $_GET['name'] : 'World'; + $name = $_GET['name'] ?? 'World'; printf('Hello %s', $name); @@ -33,7 +33,7 @@ Then, this *application is not secure*. Can you believe it? Even this simple snippet of PHP code is vulnerable to one of the most widespread Internet security issue, XSS (Cross-Site Scripting). Here is a more secure version:: - $name = isset($_GET['name']) ? $_GET['name'] : 'World'; + $name = $_GET['name'] ?? 'World'; header('Content-Type: text/html; charset=utf-8'); @@ -265,7 +265,7 @@ So, the ``getClientIp()`` method works securely in all circumstances. You can use it in all your projects, whatever the configuration is, it will behave correctly and safely. That's one of the goals of using a framework. If you were to write a framework from scratch, you would have to think about all these -cases by yourself. Why not using a technology that already works? +cases by yourself. Why not use a technology that already works? .. note:: @@ -273,7 +273,7 @@ cases by yourself. Why not using a technology that already works? 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. +Believe it or not but we have our first framework. You can stop now if you want. Using just the Symfony HttpFoundation component already allows you to write better and more testable code. It also allows you to write code faster as many day-to-day problems have already been solved for you. @@ -282,7 +282,7 @@ As a matter of fact, projects like Drupal have adopted the HttpFoundation component; if it works for them, it will probably work for you. Don't reinvent the wheel. -I've almost forgot to talk about one added benefit: using the HttpFoundation +I've almost forgotten 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`_, `Laravel`_ and `ezPublish 5`_, and `more`_). diff --git a/create_framework/http_kernel_controller_resolver.rst b/create_framework/http_kernel_controller_resolver.rst index bac631073e6..12d9efead6e 100644 --- a/create_framework/http_kernel_controller_resolver.rst +++ b/create_framework/http_kernel_controller_resolver.rst @@ -31,7 +31,7 @@ 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... 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 +reason: performance-wise, all controllers for all routes must now be instantiated for every request. It would be better if controllers were lazy-loaded so that only the controller associated with the matched route is instantiated. diff --git a/create_framework/http_kernel_httpkernel_class.rst b/create_framework/http_kernel_httpkernel_class.rst index 1cf76830abd..0f4e565b084 100644 --- a/create_framework/http_kernel_httpkernel_class.rst +++ b/create_framework/http_kernel_httpkernel_class.rst @@ -133,7 +133,7 @@ instead of a full Response object:: class LeapYearController { - public function index(Request $request, $year) + public function index($year) { $leapYear = new LeapYear(); if ($leapYear->isLeapYear($year)) { diff --git a/create_framework/http_kernel_httpkernelinterface.rst b/create_framework/http_kernel_httpkernelinterface.rst index 9207ba342b0..ede06d965d4 100644 --- a/create_framework/http_kernel_httpkernelinterface.rst +++ b/create_framework/http_kernel_httpkernelinterface.rst @@ -46,8 +46,8 @@ Update your framework so that it implements this interface:: } } -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. +With this change, a little goes a long way! Let's talk about one of +the most impressive upsides: transparent :doc:`HTTP caching ` support. The ``HttpCache`` class implements a fully-featured reverse proxy, written in PHP; it implements ``HttpKernelInterface`` and wraps another @@ -64,7 +64,8 @@ PHP; it implements ``HttpKernelInterface`` and wraps another new HttpKernel\HttpCache\Store(__DIR__.'/../cache') ); - $framework->handle($request)->send(); + $response = $framework->handle($request); + $response->send(); That's all it takes to add HTTP caching support to our framework. Isn't it amazing? @@ -154,7 +155,7 @@ rest of the content? Edge Side Includes (`ESI`_) to the rescue! Instead of generating the whole content in one go, ESI allows you to mark a region of a page as being the content of a sub-request call: -.. code-block:: text +.. code-block:: html This is the content of your page diff --git a/create_framework/introduction.rst b/create_framework/introduction.rst index 1c068942110..d3574de4c94 100644 --- a/create_framework/introduction.rst +++ b/create_framework/introduction.rst @@ -62,9 +62,9 @@ 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.5.9 or later is good enough), a web server +you need a recent version of PHP (7.4 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. +an understanding of Object Oriented Programming. Ready to go? Read on! @@ -113,5 +113,5 @@ In the :doc:`next chapter `, we are going to introduce the HttpFoundation Component and see what it brings us. .. _`Symfony`: https://symfony.com/ -.. _`Composer`: https//getcomposer.org/ +.. _`Composer`: https://getcomposer.org/ .. _`download and install Composer`: https://getcomposer.org/download/ diff --git a/create_framework/routing.rst b/create_framework/routing.rst index d381daed2eb..f76167ec2fb 100644 --- a/create_framework/routing.rst +++ b/create_framework/routing.rst @@ -30,7 +30,9 @@ framework just a little to make templates even more readable:: $response->send(); As we now extract the request query parameters, simplify the ``hello.php`` -template as follows:: +template as follows: + +.. code-block:: html+php Hello @@ -161,7 +163,9 @@ There are a few new things in the code: * ``500`` errors are now managed correctly; -* Request attributes are extracted to keep our templates simple:: +* Request attributes are extracted to keep our templates simple: + +.. code-block:: html+php // example.com/src/pages/hello.php Hello diff --git a/create_framework/separation_of_concerns.rst b/create_framework/separation_of_concerns.rst index e1e46f3ebe3..24d34f0e82b 100644 --- a/create_framework/separation_of_concerns.rst +++ b/create_framework/separation_of_concerns.rst @@ -27,9 +27,9 @@ request handling logic into its own ``Simplex\Framework`` class:: class Framework { - protected $matcher; - protected $controllerResolver; - protected $argumentResolver; + private $matcher; + private $controllerResolver; + private $argumentResolver; public function __construct(UrlMatcher $matcher, ControllerResolver $controllerResolver, ArgumentResolver $argumentResolver) { diff --git a/create_framework/templating.rst b/create_framework/templating.rst index 4ae746e1c91..6fca67d84a1 100644 --- a/create_framework/templating.rst +++ b/create_framework/templating.rst @@ -145,7 +145,8 @@ framework does not need to be modified in any way, create a new use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing; - function is_leap_year($year = null) { + function is_leap_year($year = null) + { if (null === $year) { $year = date('Y'); } @@ -177,5 +178,5 @@ As always, you can decide to stop here and use the framework as is; it's probably all you need to create simple websites like those fancy one-page `websites`_ and hopefully a few others. -.. _`callbacks`: https://www.php.net/callback#language.types.callback +.. _`callbacks`: https://www.php.net/manual/en/language.types.callable.php .. _`websites`: https://kottke.org/08/02/single-serving-sites diff --git a/create_framework/unit_testing.rst b/create_framework/unit_testing.rst index 099ada7e704..fa7a93b077f 100644 --- a/create_framework/unit_testing.rst +++ b/create_framework/unit_testing.rst @@ -8,30 +8,35 @@ on it will exhibit the same bugs. The good news is that whenever you fix a bug, you are fixing a bunch of applications too. Today's mission is to write unit tests for the framework we have created by -using `PHPUnit`_. Create a PHPUnit configuration file in -``example.com/phpunit.xml.dist``: +using `PHPUnit`_. At first, install PHPUnit as a development dependency: + +.. code-block:: terminal + + $ composer require --dev phpunit/phpunit + +Then, create a PHPUnit configuration file in ``example.com/phpunit.xml.dist``: .. code-block:: xml + + + ./src + + + ./tests - - - - ./src - - This configuration defines sensible defaults for most PHPUnit settings; more @@ -49,7 +54,7 @@ resolver. Modify the framework to make use of them:: namespace Simplex; // ... - + use Calendar\Controller\LeapYearController; use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; @@ -167,7 +172,7 @@ Response:: ->will($this->returnValue([ '_route' => 'is_leap_year/{year}', 'year' => '2000', - '_controller' => [new LeapYearController(), 'index'] + '_controller' => [new LeapYearController(), 'index'], ])) ; $matcher @@ -215,6 +220,6 @@ Symfony code. Now that we are confident (again) about the code we have written, we can safely think about the next batch of features we want to add to our framework. -.. _`PHPUnit`: https://phpunit.de/manual/current/en/index.html -.. _`test doubles`: https://phpunit.de/manual/current/en/test-doubles.html +.. _`PHPUnit`: https://phpunit.readthedocs.io/en/9.5/ +.. _`test doubles`: https://phpunit.readthedocs.io/en/9.5/test-doubles.html .. _`XDebug`: https://xdebug.org/ diff --git a/deployment.rst b/deployment.rst index 85b772b3a55..495cddb5505 100644 --- a/deployment.rst +++ b/deployment.rst @@ -7,9 +7,9 @@ How to Deploy a Symfony Application =================================== Deploying a Symfony application can be a complex and varied task depending on -the setup and the requirements of your application. This article is not a step- -by-step guide, but is a general list of the most common requirements and ideas -for deployment. +the setup and the requirements of your application. This article is not a +step-by-step guide, but is a general list of the most common requirements and +ideas for deployment. .. _symfony2-deployment-basics: @@ -46,7 +46,7 @@ Basic File Transfer The most basic way of deploying an application is copying the files manually via FTP/SCP (or similar method). This has its disadvantages as you lack control over the system as the upgrade progresses. This method also requires you -to take some manual steps after transferring the files (see `Common Post-Deployment Tasks`_) +to take some manual steps after transferring the files (see `Common Deployment Tasks`_). Using Source Control ~~~~~~~~~~~~~~~~~~~~ @@ -58,21 +58,14 @@ system. When using Git, a common approach is to create a tag for each release and check out the appropriate tag on deployment (see `Git Tagging`_). This makes updating your files *easier*, but you still need to worry about -manually taking other steps (see `Common Post-Deployment Tasks`_). +manually taking other steps (see `Common Deployment Tasks`_). Using Platforms as a Service ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Using a Platform as a Service (PaaS) can be a great way to deploy your Symfony -app quickly. There are many PaaS - below are a few that work well with Symfony: - -* `Symfony Cloud`_ -* `Heroku`_ -* `Platform.sh`_ -* `Azure`_ -* `fortrabbit`_ -* `Clever Cloud`_ -* `Scalingo`_ +app quickly. There are many PaaS, but we recommend `Platform.sh`_ as it +provides a dedicated Symfony integration and help fund the Symfony development. Using Build Scripts and other Tools ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -80,9 +73,6 @@ Using Build Scripts and other Tools 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 deploy tools to your application. - `Deployer`_ This is another native PHP rewrite of Capistrano, with some ready recipes for Symfony. @@ -103,17 +93,43 @@ 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). -Common Post-Deployment Tasks ----------------------------- +.. _common-post-deployment-tasks: -After deploying your actual source code, there are a number of common things -you'll need to do: +Common Deployment Tasks +----------------------- + +Before and after deploying your actual source code, there are a number of common +things you'll need to do: A) Check Requirements ~~~~~~~~~~~~~~~~~~~~~ -Use the ``check:requirements`` command to check if your server meets the -:ref:`technical requirements for running Symfony applications `. +There are some :ref:`technical requirements for running Symfony applications `. +In your development machine, the recommended way to check these requirements is +to use `Symfony CLI`_. However, in your production server you might prefer to +not install the Symfony CLI tool. In those cases, install this other package in +your application: + +.. code-block:: terminal + + $ composer require symfony/requirements-checker + +Then, make sure that the checker is included in your Composer scripts: + +.. code-block:: json + + { + "...": "...", + + "scripts": { + "auto-scripts": { + "vendor/bin/requirements-checker": "php-script", + "...": "..." + }, + + "...": "..." + } + } .. _b-configure-your-app-config-parameters-yml-file: @@ -126,21 +142,29 @@ While developing locally, you'll usually store these in ``.env`` and ``.env.loca 1. Create "real" environment variables. How you set environment variables, depends on your setup: they can be set at the command line, in your Nginx configuration, - or via other methods provided by your hosting service. + or via other methods provided by your hosting service; -2. Or, create a ``.env.local`` file like your local development (see note below) +2. Or, create a ``.env.local`` file like your local development. There is no significant advantage to either of the two options: use whatever is most natural in your hosting environment. -.. note:: +.. tip:: + + You might not want your application to process the ``.env.*`` files on + every request. You can generate an optimized ``.env.local.php`` which + overrides all other configuration files: + + .. code-block:: terminal + + $ composer dump-env prod - If you use the ``.env.*`` files on production, you may need to move your - ``symfony/dotenv`` dependency from ``require-dev`` to ``require`` in ``composer.json``: + The generated file will contain all the configuration stored in ``.env``. If you + want to rely only on environment variables, generate one without any values using: .. code-block:: terminal - $ composer require symfony/dotenv + $ composer dump-env prod --empty C) Install/Update your Vendors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -187,7 +211,9 @@ setup: * Add/edit CRON jobs * :ref:`Building and minifying your assets ` with Webpack Encore * Pushing assets to a CDN -* ... +* On a shared hosting platform using the Apache web server, you may need to + install the :ref:`symfony/apache-pack package ` +* etc. Application Lifecycle: Continuous Integration, QA, etc. ------------------------------------------------------- @@ -203,7 +229,7 @@ are simple and more complex tools and one can make the deployment as easy Don't forget that deploying your application also involves updating any dependency (typically via Composer), migrating your database, clearing your cache and -other potential things like pushing assets to a CDN (see `Common Post-Deployment Tasks`_). +other potential things like pushing assets to a CDN (see `Common Deployment Tasks`_). Troubleshooting --------------- @@ -239,11 +265,5 @@ Learn More .. _`Symfony plugin`: https://github.com/capistrano/symfony/ .. _`Deployer`: https://deployer.org/ .. _`Git Tagging`: https://git-scm.com/book/en/v2/Git-Basics-Tagging -.. _`Heroku`: https://devcenter.heroku.com/articles/deploying-symfony4 -.. _`Platform.sh`: https://docs.platform.sh/frameworks/symfony.html -.. _`Azure`: https://azure.microsoft.com/en-us/develop/php/ -.. _`fortrabbit`: https://help.fortrabbit.com/install-symfony-4-uni -.. _`EasyDeployBundle`: https://github.com/EasyCorp/easy-deploy-bundle -.. _`Clever Cloud`: https://www.clever-cloud.com/doc/php/tutorial-symfony/ -.. _`Symfony Cloud`: https://symfony.com/doc/master/cloud/intro.html -.. _`Scalingo`: https://doc.scalingo.com/languages/php/symfony +.. _`Platform.sh`: https://symfony.com/cloud +.. _`Symfony CLI`: https://symfony.com/download diff --git a/deployment/azure-website.rst b/deployment/azure-website.rst deleted file mode 100644 index 15361b9e416..00000000000 --- a/deployment/azure-website.rst +++ /dev/null @@ -1,12 +0,0 @@ -:orphan: - -.. index:: - single: Deployment; Deploying to Microsoft Azure Website Cloud - -Deploying to Microsoft Azure -============================ - -If you want information about deploying to Azure, see their official documentation: -`Create your PHP web application on Azure`_ - -.. _`Create your PHP web application on Azure`: https://azure.microsoft.com/en-us/develop/php/ diff --git a/deployment/fortrabbit.rst b/deployment/fortrabbit.rst deleted file mode 100644 index f13bf4f0ad4..00000000000 --- a/deployment/fortrabbit.rst +++ /dev/null @@ -1,12 +0,0 @@ -:orphan: - -.. index:: - single: Deployment; Deploying to fortrabbit.com - -Deploying to fortrabbit -======================= - -For details on deploying to fortrabbit, see their official documentation: -`Install Symfony`_ - -.. _`Install Symfony`: https://help.fortrabbit.com/install-symfony-3-uni diff --git a/deployment/heroku.rst b/deployment/heroku.rst deleted file mode 100644 index 1a2b416d8f0..00000000000 --- a/deployment/heroku.rst +++ /dev/null @@ -1,12 +0,0 @@ -:orphan: - -.. index:: - single: Deployment; Deploying to Heroku Cloud - -Deploying to Heroku -=================== - -To deploy to Heroku, see their official documentation: -`Deploying Symfony 4 & 5 Applications on Heroku`_. - -.. _`Deploying Symfony 4 & 5 Applications on Heroku`: https://devcenter.heroku.com/articles/deploying-symfony4 diff --git a/deployment/platformsh.rst b/deployment/platformsh.rst deleted file mode 100644 index c124da18674..00000000000 --- a/deployment/platformsh.rst +++ /dev/null @@ -1,12 +0,0 @@ -:orphan: - -.. index:: - single: Deployment; Deploying to Platform.sh - -Deploying to Platform.sh -======================== - -To deploy to Platform.sh, see their official documentation: -`Symfony Platform.sh Documentation`_. - -.. _`Symfony Platform.sh Documentation`: https://docs.platform.sh/frameworks/symfony.html diff --git a/deployment/proxies.rst b/deployment/proxies.rst index 80d4882ae08..285dd221b11 100644 --- a/deployment/proxies.rst +++ b/deployment/proxies.rst @@ -3,7 +3,7 @@ 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 @@ -48,6 +48,11 @@ The Request object has several ``Request::HEADER_*`` constants that control exac *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``). +.. caution:: + + The "trusted proxies" feature does not work as expected when using the + `nginx realip module`_. Disable that module when serving Symfony applications. + But what if the IP of my Reverse Proxy Changes Constantly! ---------------------------------------------------------- @@ -118,3 +123,4 @@ In this case, you'll need to set the header ``X-Forwarded-Proto`` with the value .. _`security groups`: https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-security-groups.html .. _`CloudFront`: https://en.wikipedia.org/wiki/Amazon_CloudFront .. _`CloudFront IP ranges`: https://ip-ranges.amazonaws.com/ip-ranges.json +.. _`nginx realip module`: http://nginx.org/en/docs/http/ngx_http_realip_module.html diff --git a/doctrine.rst b/doctrine.rst index 9a2ee33db89..46770aa35b5 100644 --- a/doctrine.rst +++ b/doctrine.rst @@ -46,12 +46,18 @@ The database connection information is stored as an environment variable called # customize this line! DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7" + # to use mariadb: + DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=mariadb-10.5.8" + # to use sqlite: # DATABASE_URL="sqlite:///%kernel.project_dir%/var/app.db" - + # to use postgresql: # DATABASE_URL="postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=11&charset=utf8" + # to use oracle: + # DATABASE_URL="oci8://db_user:db_password@127.0.0.1:1521/db_name" + .. caution:: If the username, password, host or database name contain any character considered @@ -126,7 +132,7 @@ need. The command will ask you some questions - answer them like done below: The interactive behavior of the ``make:entity`` command was introduced in MakerBundle 1.3. -Woh! You now have a new ``src/Entity/Product.php`` file:: +Whoa! You now have a new ``src/Entity/Product.php`` file:: // src/Entity/Product.php namespace App\Entity; @@ -277,20 +283,20 @@ methods: .. code-block:: diff - // src/Entity/Product.php - // ... + // src/Entity/Product.php + // ... - class Product - { - // ... + class Product + { + // ... + /** + * @ORM\Column(type="text") + */ + private $description; - // getDescription() & setDescription() were also added - } + // getDescription() & setDescription() were also added + } The new property is mapped, but it doesn't exist yet in the ``product`` table. No problem! Generate a new migration: @@ -503,46 +509,60 @@ Fetching an object back out of the database is even easier. Suppose you want to be able to go to ``/product/1`` to see your new product:: // src/Controller/ProductController.php + namespace App\Controller; + + use App\Entity\Product; + use Symfony\Component\HttpFoundation\Response; // ... - /** - * @Route("/product/{id}", name="product_show") - */ - public function show($id) + class ProductController extends AbstractController { - $product = $this->getDoctrine() - ->getRepository(Product::class) - ->find($id); - - if (!$product) { - throw $this->createNotFoundException( - 'No product found for id '.$id - ); - } + /** + * @Route("/product/{id}", name="product_show") + */ + public function show(int $id): Response + { + $product = $this->getDoctrine() + ->getRepository(Product::class) + ->find($id); + + if (!$product) { + throw $this->createNotFoundException( + 'No product found for id '.$id + ); + } - return new Response('Check out this great product: '.$product->getName()); + return new Response('Check out this great product: '.$product->getName()); - // or render a template - // in the template, print things with {{ product.name }} - // return $this->render('product/show.html.twig', ['product' => $product]); + // or render a template + // in the template, print things with {{ product.name }} + // return $this->render('product/show.html.twig', ['product' => $product]); + } } Another possibility is to use the ``ProductRepository`` using Symfony's autowiring and injected by the dependency injection container:: // src/Controller/ProductController.php - // ... + namespace App\Controller; + + use App\Entity\Product; use App\Repository\ProductRepository; + use Symfony\Component\HttpFoundation\Response; + // ... - /** - * @Route("/product/{id}", name="product_show") - */ - public function show($id, ProductRepository $productRepository) + class ProductController extends AbstractController { - $product = $productRepository - ->find($id); + /** + * @Route("/product/{id}", name="product_show") + */ + public function show(int $id, ProductRepository $productRepository): Response + { + $product = $productRepository + ->find($id); - // ... + // ... + } } Try it out! @@ -608,15 +628,23 @@ for you automatically! First, install the bundle in case you don't have it: Now, simplify your controller:: // src/Controller/ProductController.php + namespace App\Controller; + use App\Entity\Product; + use App\Repository\ProductRepository; + use Symfony\Component\HttpFoundation\Response; + // ... - /** - * @Route("/product/{id}", name="product_show") - */ - public function show(Product $product) + class ProductController extends AbstractController { - // use the Product! - // ... + /** + * @Route("/product/{id}", name="product_show") + */ + public function show(Product $product): Response + { + // use the Product! + // ... + } } That's it! The bundle uses the ``{id}`` from the route to query for the ``Product`` @@ -630,26 +658,37 @@ Updating an Object Once you've fetched an object from Doctrine, you interact with it the same as with any PHP model:: - /** - * @Route("/product/edit/{id}") - */ - public function update($id) + // src/Controller/ProductController.php + namespace App\Controller; + + use App\Entity\Product; + use App\Repository\ProductRepository; + use Symfony\Component\HttpFoundation\Response; + // ... + + class ProductController extends AbstractController { - $entityManager = $this->getDoctrine()->getManager(); - $product = $entityManager->getRepository(Product::class)->find($id); + /** + * @Route("/product/edit/{id}") + */ + public function update(int $id): Response + { + $entityManager = $this->getDoctrine()->getManager(); + $product = $entityManager->getRepository(Product::class)->find($id); - if (!$product) { - throw $this->createNotFoundException( - 'No product found for id '.$id - ); - } + if (!$product) { + throw $this->createNotFoundException( + 'No product found for id '.$id + ); + } - $product->setName('New product name!'); - $entityManager->flush(); + $product->setName('New product name!'); + $entityManager->flush(); - return $this->redirectToRoute('product_show', [ - 'id' => $product->getId() - ]); + return $this->redirectToRoute('product_show', [ + 'id' => $product->getId() + ]); + } } Using Doctrine to edit an existing product consists of three steps: @@ -725,7 +764,7 @@ a new method for this to your repository:: /** * @return Product[] */ - public function findAllGreaterThanPrice($price): array + public function findAllGreaterThanPrice(int $price): array { $entityManager = $this->getEntityManager(); @@ -770,25 +809,28 @@ based on PHP conditions):: // src/Repository/ProductRepository.php // ... - public function findAllGreaterThanPrice($price, $includeUnavailableProducts = false): array + class ProductRepository extends ServiceEntityRepository { - // automatically knows to select Products - // the "p" is an alias you'll use in the rest of the query - $qb = $this->createQueryBuilder('p') - ->where('p.price > :price') - ->setParameter('price', $price) - ->orderBy('p.price', 'ASC'); - - if (!$includeUnavailableProducts) { - $qb->andWhere('p.available = TRUE') - } + public function findAllGreaterThanPrice(int $price, bool $includeUnavailableProducts = false): array + { + // automatically knows to select Products + // the "p" is an alias you'll use in the rest of the query + $qb = $this->createQueryBuilder('p') + ->where('p.price > :price') + ->setParameter('price', $price) + ->orderBy('p.price', 'ASC'); + + if (!$includeUnavailableProducts) { + $qb->andWhere('p.available = TRUE'); + } - $query = $qb->getQuery(); + $query = $qb->getQuery(); - return $query->execute(); + return $query->execute(); - // to get just one result: - // $product = $query->setMaxResults(1)->getOneOrNullResult(); + // to get just one result: + // $product = $query->setMaxResults(1)->getOneOrNullResult(); + } } Querying with SQL @@ -799,20 +841,23 @@ In addition, you can query directly with SQL if you need to:: // src/Repository/ProductRepository.php // ... - public function findAllGreaterThanPrice($price): array + class ProductRepository extends ServiceEntityRepository { - $conn = $this->getEntityManager()->getConnection(); - - $sql = ' - SELECT * FROM product p - WHERE p.price > :price - ORDER BY p.price ASC - '; - $stmt = $conn->prepare($sql); - $stmt->execute(['price' => $price]); - - // returns an array of arrays (i.e. a raw data set) - return $stmt->fetchAll(); + public function findAllGreaterThanPrice(int $price): array + { + $conn = $this->getEntityManager()->getConnection(); + + $sql = ' + SELECT * FROM product p + WHERE p.price > :price + ORDER BY p.price ASC + '; + $stmt = $conn->prepare($sql); + $resultSet = $stmt->executeQuery(['price' => $price]); + + // returns an array of arrays (i.e. a raw data set) + return $resultSet->fetchAllAssociative(); + } } With SQL, you will get back raw data, not objects (unless you use the `NativeQuery`_ @@ -878,5 +923,5 @@ Learn more .. _`Doctrine screencast series`: https://symfonycasts.com/screencast/symfony-doctrine .. _`API Platform`: https://api-platform.com/docs/core/validation/ .. _`PDO`: https://www.php.net/pdo -.. _`available Doctrine extensions`: https://github.com/Atlantic18/DoctrineExtensions -.. _`StofDoctrineExtensionsBundle`: https://github.com/antishov/StofDoctrineExtensionsBundle +.. _`available Doctrine extensions`: https://github.com/doctrine-extensions/DoctrineExtensions +.. _`StofDoctrineExtensionsBundle`: https://github.com/stof/StofDoctrineExtensionsBundle diff --git a/doctrine/associations.rst b/doctrine/associations.rst index 017f5903391..a3c138c008f 100644 --- a/doctrine/associations.rst +++ b/doctrine/associations.rst @@ -299,7 +299,7 @@ config. *exactly* like an array, but has some added flexibility. Just imagine that it is an ``array`` and you'll be in good shape. -Your database is setup! Now, run the migrations like normal: +Your database is set up! Now, run the migrations like normal: .. code-block:: terminal @@ -327,7 +327,7 @@ Now you can see this new code in action! Imagine you're inside a controller:: /** * @Route("/product", name="product") */ - public function index() + public function index(): Response { $category = new Category(); $category->setName('Computer Peripherals'); @@ -378,20 +378,26 @@ When you need to fetch associated objects, your workflow looks like it did before. First, fetch a ``$product`` object and then access its related ``Category`` object:: + // src/Controller/ProductController.php + namespace App\Controller; + use App\Entity\Product; // ... - public function show($id) + class ProductController extends AbstractController { - $product = $this->getDoctrine() - ->getRepository(Product::class) - ->find($id); + public function show(int $id): Response + { + $product = $this->getDoctrine() + ->getRepository(Product::class) + ->find($id); - // ... + // ... - $categoryName = $product->getCategory()->getName(); + $categoryName = $product->getCategory()->getName(); - // ... + // ... + } } In this example, you first query for a ``Product`` object based on the product's @@ -411,15 +417,21 @@ the category (i.e. it's "lazily loaded"). Because we mapped the optional ``OneToMany`` side, you can also query in the other direction:: - public function showProducts($id) + // src/Controller/ProductController.php + + // ... + class ProductController extends AbstractController { - $category = $this->getDoctrine() - ->getRepository(Category::class) - ->find($id); + public function showProducts(int $id): Response + { + $category = $this->getDoctrine() + ->getRepository(Category::class) + ->find($id); - $products = $category->getProducts(); + $products = $category->getProducts(); - // ... + // ... + } } In this case, the same things occur: you first query for a single ``Category`` @@ -475,18 +487,23 @@ can avoid the second query by issuing a join in the original query. Add the following method to the ``ProductRepository`` class:: // src/Repository/ProductRepository.php - public function findOneByIdJoinedToCategory($productId) + + // ... + class ProductRepository extends ServiceEntityRepository { - $entityManager = $this->getEntityManager(); + public function findOneByIdJoinedToCategory(int $productId): ?Product + { + $entityManager = $this->getEntityManager(); - $query = $entityManager->createQuery( - 'SELECT p, c - FROM App\Entity\Product p - INNER JOIN p.category c - WHERE p.id = :id' - )->setParameter('id', $productId); + $query = $entityManager->createQuery( + 'SELECT p, c + FROM App\Entity\Product p + INNER JOIN p.category c + WHERE p.id = :id' + )->setParameter('id', $productId); - return $query->getOneOrNullResult(); + return $query->getOneOrNullResult(); + } } This will *still* return an array of ``Product`` objects. But now, when you call @@ -495,15 +512,21 @@ This will *still* return an array of ``Product`` objects. But now, when you call Now, you can use this method in your controller to query for a ``Product`` object and its related ``Category`` in one query:: - public function show($id) + // src/Controller/ProductController.php + + // ... + class ProductController extends AbstractController { - $product = $this->getDoctrine() - ->getRepository(Product::class) - ->findOneByIdJoinedToCategory($id); + public function show(int $id): Response + { + $product = $this->getDoctrine() + ->getRepository(Product::class) + ->findOneByIdJoinedToCategory($id); - $category = $product->getCategory(); + $category = $product->getCategory(); - // ... + // ... + } } .. _associations-inverse-side: @@ -548,6 +571,7 @@ What about *removing* a ``Product`` from a ``Category``? The ``make:entity`` com also generated a ``removeProduct()`` method:: // src/Entity/Category.php + namespace App\Entity; // ... class Category diff --git a/doctrine/custom_dql_functions.rst b/doctrine/custom_dql_functions.rst index 9485509da49..39ded025967 100644 --- a/doctrine/custom_dql_functions.rst +++ b/doctrine/custom_dql_functions.rst @@ -5,7 +5,7 @@ How to Register custom DQL Functions ==================================== Doctrine allows you to specify custom DQL functions. For more information -on this topic, read Doctrine's cookbook article "`DQL User Defined Functions`_". +on this topic, read Doctrine's cookbook article `DQL User Defined Functions`_. In Symfony, you can register your custom DQL functions as follows: @@ -131,16 +131,14 @@ In Symfony, you can register your custom DQL functions as follows: use App\DQL\DatetimeFunction; $container->loadFromExtension('doctrine', [ - 'doctrine' => [ - 'orm' => [ - // ... - 'entity_managers' => [ - 'example_manager' => [ - // place your functions here - 'dql' => [ - 'datetime_functions' => [ - 'test_datetime' => DatetimeFunction::class, - ], + 'orm' => [ + // ... + 'entity_managers' => [ + 'example_manager' => [ + // place your functions here + 'dql' => [ + 'datetime_functions' => [ + 'test_datetime' => DatetimeFunction::class, ], ], ], @@ -148,4 +146,10 @@ In Symfony, you can register your custom DQL functions as follows: ], ]); +.. caution:: + + DQL functions are instantiated by Doctrine outside of the Symfony + :doc:`service container ` so you can't inject services + or parameters into a custom DQL function. + .. _`DQL User Defined Functions`: https://www.doctrine-project.org/projects/doctrine-orm/en/current/cookbook/dql-user-defined-functions.html diff --git a/doctrine/dbal.rst b/doctrine/dbal.rst index 8487ffdce91..ab1947bd1bb 100644 --- a/doctrine/dbal.rst +++ b/doctrine/dbal.rst @@ -35,7 +35,7 @@ Then configure the ``DATABASE_URL`` environment variable in ``.env``: # .env (or override DATABASE_URL in .env.local to avoid committing your changes) # customize this line! - DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name" + DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7" Further things can be configured in ``config/packages/doctrine.yaml`` - see :ref:`reference-dbal-configuration`. Remove the ``orm`` key in that file @@ -44,13 +44,18 @@ if you *don't* want to use the Doctrine ORM. You can then access the Doctrine DBAL connection by autowiring the ``Connection`` object:: + // src/Controller/UserController.php + namespace App\Controller; + use Doctrine\DBAL\Driver\Connection; + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Symfony\Component\HttpFoundation\Response; class UserController extends AbstractController { - public function index(Connection $connection) + public function index(Connection $connection): Response { - $users = $connection->fetchAll('SELECT * FROM users'); + $users = $connection->fetchAllAssociative('SELECT * FROM users'); // ... } @@ -115,7 +120,7 @@ Registering custom Mapping Types in the SchemaTool The SchemaTool is used to inspect the database to compare the schema. To achieve this task, it needs to know which mapping type needs to be used -for each database types. Registering new ones can be done through the configuration. +for each database type. Registering new ones can be done through the configuration. Now, map the ENUM type (not supported by DBAL by default) to the ``string`` mapping type: diff --git a/doctrine/events.rst b/doctrine/events.rst index 9b44b35cba1..b2a76b6ac56 100644 --- a/doctrine/events.rst +++ b/doctrine/events.rst @@ -16,7 +16,7 @@ on other common tasks (e.g. ``loadClassMetadata``, ``onClear``). There are different ways to listen to these Doctrine events: -* **Lifecycle callbacks**, they are defined as methods on the entity classes and +* **Lifecycle callbacks**, they are defined as public methods on the entity classes and they are called when the events are triggered; * **Lifecycle listeners and subscribers**, they are classes with callback methods for one or more events and they are called for all entities; @@ -46,7 +46,7 @@ to learn everything about them. Doctrine Lifecycle Callbacks ---------------------------- -Lifecycle callbacks are defined as methods inside the entity you want to modify. +Lifecycle callbacks are defined as public methods inside the entity you want to modify. For example, suppose you want to set a ``createdAt`` date column to the current date, but only when the entity is first persisted (i.e. inserted). To do so, define a callback for the ``prePersist`` Doctrine event: @@ -56,6 +56,8 @@ define a callback for the ``prePersist`` Doctrine event: .. code-block:: php-annotations // src/Entity/Product.php + namespace App\Entity; + use Doctrine\ORM\Mapping as ORM; // When using annotations, don't forget to add @ORM\HasLifecycleCallbacks() @@ -72,9 +74,9 @@ define a callback for the ``prePersist`` Doctrine event: /** * @ORM\PrePersist */ - public function setCreatedAtValue() + public function setCreatedAtValue(): void { - $this->createdAt = new \DateTime(); + $this->createdAt = new \DateTimeImmutable(); } } @@ -130,7 +132,7 @@ do so, define a listener for the ``postPersist`` Doctrine event:: { // the listener methods receive an argument which gives you access to // both the entity object of the event and the entity manager itself - public function postPersist(LifecycleEventArgs $args) + public function postPersist(LifecycleEventArgs $args): void { $entity = $args->getObject(); @@ -174,7 +176,7 @@ with the ``doctrine.event_listener`` tag: .. code-block:: xml - + @@ -239,7 +241,7 @@ define a listener for the ``postUpdate`` Doctrine event:: { // the entity listener methods receive two arguments: // the entity instance and the lifecycle event - public function postUpdate(User $user, LifecycleEventArgs $event) + public function postUpdate(User $user, LifecycleEventArgs $event): void { // ... do something to notify the changes } @@ -281,7 +283,7 @@ with the ``doctrine.orm.entity_listener`` tag: .. code-block:: xml - + @@ -355,15 +357,15 @@ want to log all the database activity. To do so, define a subscriber for the namespace App\EventListener; use App\Entity\Product; - use Doctrine\Common\EventSubscriber; + use Doctrine\Bundle\DoctrineBundle\EventSubscriber\EventSubscriberInterface; use Doctrine\ORM\Events; use Doctrine\Persistence\Event\LifecycleEventArgs; - class DatabaseActivitySubscriber implements EventSubscriber + class DatabaseActivitySubscriber implements EventSubscriberInterface { // this method can only return the event names; you cannot define a // custom method name to execute when each event triggers - public function getSubscribedEvents() + public function getSubscribedEvents(): array { return [ Events::postPersist, @@ -375,22 +377,22 @@ want to log all the database activity. To do so, define a subscriber for the // callback methods must be called exactly like the events they listen to; // they receive an argument of type LifecycleEventArgs, which gives you access // to both the entity object of the event and the entity manager itself - public function postPersist(LifecycleEventArgs $args) + public function postPersist(LifecycleEventArgs $args): void { $this->logActivity('persist', $args); } - public function postRemove(LifecycleEventArgs $args) + public function postRemove(LifecycleEventArgs $args): void { $this->logActivity('remove', $args); } - public function postUpdate(LifecycleEventArgs $args) + public function postUpdate(LifecycleEventArgs $args): void { $this->logActivity('update', $args); } - private function logActivity(string $action, LifecycleEventArgs $args) + private function logActivity(string $action, LifecycleEventArgs $args): void { $entity = $args->getObject(); @@ -404,48 +406,13 @@ want to log all the database activity. To do so, define a subscriber for the } } -The next step is to enable the Doctrine subscriber in the Symfony application by -creating a new service for it and :doc:`tagging it ` -with the ``doctrine.event_subscriber`` tag: - -.. configuration-block:: - - .. code-block:: yaml - - # config/services.yaml - services: - # ... - - App\EventListener\DatabaseActivitySubscriber: - tags: - - { name: 'doctrine.event_subscriber' } - - .. code-block:: xml - - - - - - - - - - - - - - .. code-block:: php - - // config/services.php - use App\EventListener\DatabaseActivitySubscriber; - - $container->autowire(DatabaseActivitySubscriber::class) - ->addTag('doctrine.event_subscriber') - ; +If you're using the :ref:`default services.yaml configuration ` +and DoctrineBundle 2.1 (released May 25, 2020) or newer, this example will already +work! Otherwise, :ref:`create a service ` for this +subscriber and :doc:`tag it ` with ``doctrine.event_subscriber``. If you need to associate the subscriber with a specific Doctrine connection, you -can do it in the service configuration: +must do that in the manual service configuration: .. configuration-block:: @@ -462,7 +429,7 @@ can do it in the service configuration: .. code-block:: xml - + diff --git a/doctrine/multiple_entity_managers.rst b/doctrine/multiple_entity_managers.rst index b7f61039a1e..c609ad1291b 100644 --- a/doctrine/multiple_entity_managers.rst +++ b/doctrine/multiple_entity_managers.rst @@ -9,6 +9,8 @@ application. This is necessary if you are using different databases or even vendors with entirely different sets of entities. In other words, one entity manager that connects to one database will handle some entities while another entity manager that connects to another database might handle the rest. +It is also possible to use multiple entity managers to manage a common set of +entities, each with their own database connection strings or separate cache configuration. .. note:: @@ -44,7 +46,6 @@ The following configuration code shows how you can configure two entity managers driver: 'pdo_mysql' server_version: '5.7' charset: utf8mb4 - orm: default_entity_manager: default entity_managers: @@ -70,7 +71,7 @@ The following configuration code shows how you can configure two entity managers .. code-block:: xml - + '%kernel.project_dir%/src/Entity/Main', 'prefix' => 'App\Entity\Main', 'alias' => 'Main', - ] + ], ], ], 'customer' => [ @@ -172,7 +173,7 @@ The following configuration code shows how you can configure two entity managers 'dir' => '%kernel.project_dir%/src/Entity/Customer', 'prefix' => 'App\Entity\Customer', 'alias' => 'Customer', - ] + ], ], ], ], @@ -183,7 +184,7 @@ In this case, you've defined two entity managers and called them ``default`` and ``customer``. The ``default`` entity manager manages entities in the ``src/Entity/Main`` directory, while the ``customer`` entity manager manages entities in ``src/Entity/Customer``. You've also defined two connections, one -for each entity manager. +for each entity manager, but you are free to define the same connection for both. .. caution:: @@ -229,13 +230,15 @@ When working with multiple entity managers to generate migrations: If you *do* omit the entity manager's name when asking for it, the default entity manager (i.e. ``default``) is returned:: - // ... + // src/Controller/UserController.php + namespace App\Controller; + // ... use Doctrine\ORM\EntityManagerInterface; class UserController extends AbstractController { - public function index(EntityManagerInterface $entityManager) + public function index(EntityManagerInterface $entityManager): Response { // These methods also return the default entity manager, but it's preferred // to get it by injecting EntityManagerInterface in the action method @@ -246,22 +249,32 @@ the default entity manager (i.e. ``default``) is returned:: // Both of these return the "customer" entity manager $customerEntityManager = $this->getDoctrine()->getManager('customer'); $customerEntityManager = $this->get('doctrine.orm.customer_entity_manager'); + + // ... } } +Entity managers also benefit from :ref:`autowiring aliases ` +when the :ref:`framework bundle ` is used. For +example, to inject the ``customer`` entity manager, type-hint your method with +``EntityManagerInterface $customerEntityManager``. + You can now use Doctrine like you did before - using the ``default`` entity manager to persist and fetch entities that it manages and the ``customer`` entity manager to persist and fetch its entities. The same applies to repository calls:: + // src/Controller/UserController.php + namespace App\Controller; + use AcmeStoreBundle\Entity\Customer; use AcmeStoreBundle\Entity\Product; // ... class UserController extends AbstractController { - public function index() + public function index(): Response { // Retrieves a repository managed by the "default" em $products = $this->getDoctrine() @@ -280,7 +293,31 @@ The same applies to repository calls:: ->getRepository(Customer::class, 'customer') ->findAll() ; + + // ... } } +.. caution:: + + One entity can be managed by more than one entity manager. This however + results in unexpected behavior when extending from ``ServiceEntityRepository`` + in your custom repository. The ``ServiceEntityRepository`` always + uses the configured entity manager for that entity. + + In order to fix this situation, extend ``EntityRepository`` instead and + no longer rely on autowiring:: + + // src/Repository/CustomerRepository.php + namespace App\Repository; + + use Doctrine\ORM\EntityRepository; + + class CustomerRepository extends EntityRepository + { + // ... + } + + You should now always fetch this repository using ``ManagerRegistry::getRepository()``. + .. _`several alternatives`: https://stackoverflow.com/a/11494543 diff --git a/doctrine/registration_form.rst b/doctrine/registration_form.rst index d999eda77e9..841e1960512 100644 --- a/doctrine/registration_form.rst +++ b/doctrine/registration_form.rst @@ -14,7 +14,7 @@ form you must: #. :doc:`Create a form ` to ask for the registration information (you can generate this with the ``make:registration-form`` command provided by the `MakerBundle`_); #. Create :doc:`a controller ` to :ref:`process the form `; -#. :ref:`Protect some parts of your application ` so - only registered users can access to them. +#. :ref:`Protect some parts of your application ` so that + only registered users can access them. .. _`MakerBundle`: https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html diff --git a/doctrine/resolve_target_entity.rst b/doctrine/resolve_target_entity.rst index a60dfe3e604..9be8730ba4a 100644 --- a/doctrine/resolve_target_entity.rst +++ b/doctrine/resolve_target_entity.rst @@ -5,7 +5,7 @@ How to Define Relationships with Abstract Classes and Interfaces ================================================================ -One of the goals of bundles is to create discreet bundles of functionality +One of the goals of bundles is to create discrete bundles of functionality that do not have many (if any) dependencies, allowing you to use that functionality in other applications without including unnecessary items. @@ -42,8 +42,8 @@ A Customer entity:: // src/Entity/Customer.php namespace App\Entity; - use Acme\CustomerBundle\Entity\Customer as BaseCustomer; - use Acme\InvoiceBundle\Model\InvoiceSubjectInterface; + use App\Entity\CustomerInterface as BaseCustomer; + use App\Model\InvoiceSubjectInterface; use Doctrine\ORM\Mapping as ORM; /** @@ -58,10 +58,10 @@ A Customer entity:: An Invoice entity:: - // src/Acme/InvoiceBundle/Entity/Invoice.php - namespace Acme\InvoiceBundle\Entity; + // src/Entity/Invoice.php + namespace App\Entity; - use Acme\InvoiceBundle\Model\InvoiceSubjectInterface; + use App\Model\InvoiceSubjectInterface; use Doctrine\ORM\Mapping as ORM; /** @@ -73,7 +73,7 @@ An Invoice entity:: class Invoice { /** - * @ORM\ManyToOne(targetEntity="Acme\InvoiceBundle\Model\InvoiceSubjectInterface") + * @ORM\ManyToOne(targetEntity="App\Model\InvoiceSubjectInterface") * @var InvoiceSubjectInterface */ protected $subject; @@ -81,8 +81,8 @@ An Invoice entity:: An InvoiceSubjectInterface:: - // src/Acme/InvoiceBundle/Model/InvoiceSubjectInterface.php - namespace Acme\InvoiceBundle\Model; + // src/Model/InvoiceSubjectInterface.php + namespace App\Model; /** * An interface that the invoice Subject object should implement. @@ -96,10 +96,7 @@ An InvoiceSubjectInterface:: // will need to access on the subject so that you can // be sure that you have access to those methods. - /** - * @return string - */ - public function getName(); + public function getName(): string; } Next, you need to configure the listener, which tells the DoctrineBundle @@ -115,7 +112,7 @@ about the replacement: orm: # ... resolve_target_entities: - Acme\InvoiceBundle\Model\InvoiceSubjectInterface: App\Entity\Customer + App\Model\InvoiceSubjectInterface: App\Entity\Customer .. code-block:: xml @@ -132,7 +129,7 @@ about the replacement: - App\Entity\Customer + App\Entity\Customer @@ -140,8 +137,8 @@ about the replacement: .. code-block:: php // config/packages/doctrine.php - use Acme\InvoiceBundle\Model\InvoiceSubjectInterface; use App\Entity\Customer; + use App\Model\InvoiceSubjectInterface; $container->loadFromExtension('doctrine', [ 'orm' => [ diff --git a/doctrine/reverse_engineering.rst b/doctrine/reverse_engineering.rst index 087e41db955..a80d6fa91c0 100644 --- a/doctrine/reverse_engineering.rst +++ b/doctrine/reverse_engineering.rst @@ -4,111 +4,13 @@ How to Generate Entities from an Existing Database ================================================== -When starting work on a brand new project that uses a database, two different -situations comes naturally. In most cases, the database model is designed -and built from scratch. Sometimes, however, you'll start with an existing and -probably unchangeable database model. Fortunately, Doctrine comes with a bunch -of tools to help generate model classes from your existing database. +.. caution:: -.. note:: + The ``doctrine:mapping:import`` command used to generate Doctrine entities + from existing databases was deprecated by Doctrine in 2019 and it's no + longer recommended to use it. - As the `Doctrine tools documentation`_ says, reverse engineering is a - one-time process to get started on a project. Doctrine is able to convert - approximately 70-80% of the necessary mapping information based on fields, - indexes and foreign key constraints. Doctrine can't discover inverse - associations, inheritance types, entities with foreign keys as primary keys - or semantical operations on associations such as cascade or lifecycle - events. Some additional work on the generated entities will be necessary - afterwards to design each to fit your domain model specificities. + Instead, you can use the ``make:entity`` command from `Symfony Maker Bundle`_ + to quickly generate the Doctrine entities of your application. -This tutorial assumes you're using a simple blog application with the following -two tables: ``blog_post`` and ``blog_comment``. A comment record is linked -to a post record thanks to a foreign key constraint. - -.. code-block:: sql - - CREATE TABLE `blog_post` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `title` varchar(100) COLLATE utf8_unicode_ci NOT NULL, - `content` longtext COLLATE utf8_unicode_ci NOT NULL, - `created_at` datetime NOT NULL, - PRIMARY KEY (`id`) - ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; - - CREATE TABLE `blog_comment` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `post_id` bigint(20) NOT NULL, - `author` varchar(20) COLLATE utf8_unicode_ci NOT NULL, - `content` longtext COLLATE utf8_unicode_ci NOT NULL, - `created_at` datetime NOT NULL, - PRIMARY KEY (`id`), - KEY `blog_comment_post_id_idx` (`post_id`), - CONSTRAINT `blog_post_id` FOREIGN KEY (`post_id`) REFERENCES `blog_post` (`id`) ON DELETE CASCADE - ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; - -Before diving into the recipe, be sure your database connection parameters are -correctly setup in the ``.env`` file (or ``.env.local`` override file). - -The first step towards building entity classes from an existing database -is to ask Doctrine to introspect the database and generate the corresponding -metadata files. Metadata files describe the entity class to generate based on -table fields. - -.. code-block:: terminal - - $ php bin/console doctrine:mapping:import "App\Entity" annotation --path=src/Entity - -This command line tool asks Doctrine to introspect the database and generate -new PHP classes with annotation metadata into ``src/Entity``. This generates two -files: ``BlogPost.php`` and ``BlogComment.php``. - -.. tip:: - - It's also possible to generate the metadata files into XML or eventually into YAML: - - .. code-block:: terminal - - $ php bin/console doctrine:mapping:import "App\Entity" xml --path=config/doctrine - - In this case, make sure to adapt your mapping configuration accordingly: - - .. code-block:: yaml - - # config/packages/doctrine.yaml - doctrine: - # ... - orm: - # ... - mappings: - App: - is_bundle: false - type: xml # "yml" is marked as deprecated for doctrine v2.6+ and will be removed in v3 - dir: '%kernel.project_dir%/config/doctrine' - prefix: 'App\Entity' - alias: App - -Generating the Getters & Setters or PHP Classes ------------------------------------------------ - -The generated PHP classes now have properties and annotation metadata, but they -do *not* have any getter or setter methods. If you generated XML or YAML metadata, -you don't even have the PHP classes! - -To generate the missing getter/setter methods (or to *create* the classes if necessary), -run: - -.. code-block:: terminal - - // generates getter/setter methods - $ php bin/console make:entity --regenerate App - -.. note:: - - If you want to have a OneToMany relationship, you will need to add - it manually into the entity (e.g. add a ``comments`` property to ``BlogPost``) - or to the generated XML or YAML files. Add a section on the specific entities - for one-to-many defining the ``inversedBy`` and the ``mappedBy`` pieces. - -The generated entities are now ready to be used. Have fun! - -.. _`Doctrine tools documentation`: https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/tools.html#reverse-engineering +.. _`Symfony Maker Bundle`: https://symfony.com/bundles/SymfonyMakerBundle/current/index.html diff --git a/email.rst b/email.rst index 60e630abc38..6f54812729a 100644 --- a/email.rst +++ b/email.rst @@ -4,10 +4,11 @@ Swift Mailer ============ -.. note:: +.. caution:: - In Symfony 4.3, the :doc:`Mailer ` component was introduced and can - be used instead of Swift Mailer. + In Symfony 4.3, the :doc:`Mailer ` component was introduced and should + be used instead of Swift Mailer as it won't be maintained anymore as of November + 2021. Symfony provides a mailer feature based on the popular `Swift Mailer`_ library via the `SwiftMailerBundle`_. This mailer supports sending messages with your @@ -417,7 +418,7 @@ How to Spool Emails The default behavior of the Symfony mailer is to send the email messages immediately. You may, however, want to avoid the performance hit of the communication to the email server, which could cause the user to wait for the -next page to load while the email is sending. This can be avoided by choosing to +next page to load while the email is being sent. This can be avoided by choosing to "spool" the emails instead of sending them directly. This makes the mailer to not attempt to send the email message but instead save diff --git a/event_dispatcher.rst b/event_dispatcher.rst index b162fe035f1..5ac86e55cd5 100644 --- a/event_dispatcher.rst +++ b/event_dispatcher.rst @@ -109,7 +109,7 @@ using a special "tag": use App\EventListener\ExceptionListener; $container->register(ExceptionListener::class) - ->addTag('kernel.event_listener', ['event' => 'kernel.exception']) + ->tag('kernel.event_listener', ['event' => 'kernel.exception']) ; Symfony follows this logic to decide which method to call inside the event @@ -122,7 +122,7 @@ listener class: the ``kernel.exception`` event); #. If that method is not defined either, try to call the ``__invoke()`` magic method (which makes event listeners invokable); -#. If the ``_invoke()`` method is not defined either, throw an exception. +#. If the ``__invoke()`` method is not defined either, throw an exception. .. note:: @@ -141,8 +141,8 @@ Creating an Event Subscriber Another way to listen to events is via an **event subscriber**, which is a class 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. +difference with the event listeners is that subscribers always know the events +to which they are listening. 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 @@ -289,6 +289,8 @@ This alias mapping can be extended for custom events by registering the compiler pass ``AddEventAliasesPass``:: // src/Kernel.php + namespace App; + use App\Event\MyCustomEvent; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\EventDispatcher\DependencyInjection\AddEventAliasesPass; diff --git a/event_dispatcher/before_after_filters.rst b/event_dispatcher/before_after_filters.rst index 6c48d62ee24..5be62d9ac09 100644 --- a/event_dispatcher/before_after_filters.rst +++ b/event_dispatcher/before_after_filters.rst @@ -189,7 +189,7 @@ serve as a basic flag that this request underwent token authentication:: { // ... - 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!'); diff --git a/event_dispatcher/method_behavior.rst b/event_dispatcher/method_behavior.rst index cea11e72d8d..4e2f00fef0e 100644 --- a/event_dispatcher/method_behavior.rst +++ b/event_dispatcher/method_behavior.rst @@ -21,7 +21,7 @@ end of the method:: $event = new BeforeSendMailEvent($subject, $message); $this->dispatcher->dispatch($event, 'mailer.pre_send'); - // get $foo and $bar from the event, they may have been modified + // get $subject and $message from the event, they may have been modified $subject = $event->getSubject(); $message = $event->getMessage(); @@ -134,7 +134,7 @@ could listen to the ``mailer.post_send`` event and change the method's return va public static function getSubscribedEvents() { return [ - 'mailer.post_send' => 'onMailerPostSend' + 'mailer.post_send' => 'onMailerPostSend', ]; } } diff --git a/form/bootstrap4.rst b/form/bootstrap4.rst index cc19dd2f8c9..31f7e50cf1a 100644 --- a/form/bootstrap4.rst +++ b/form/bootstrap4.rst @@ -77,6 +77,8 @@ If you prefer to apply the Bootstrap styles on a form to form basis, include the {{ form(form) }} {% endblock %} +.. _reference-forms-bootstrap4-error-messages: + Error Messages -------------- @@ -86,12 +88,26 @@ is a strong connection between the error and its ````, as required by the ``form_label()`` internally. If you call to ``form_errors()`` in your template, you'll get the error messages displayed *twice*. +.. tip:: + + Since form errors are rendered *inside* the ``