`,
-all services are private by default.
-
-.. best-practice::
-
- Services should be ``private`` whenever possible. This will prevent you from
- accessing that service via ``$container->get()``. Instead, you will need to use
- dependency injection.
-
-Service Format: YAML
---------------------
-
-In the previous section, YAML was used to define the service.
-
-.. best-practice::
-
- Use the YAML format to define your own services.
-
-This is controversial, and in our experience, YAML and XML usage is evenly
-distributed among developers, with a slight preference towards YAML.
-Both formats have the same performance, so this is ultimately a matter of
-personal taste.
-
-We recommend YAML because it's friendly to newcomers and concise, but you can
-use whatever format you like.
-
-Service: No Class Parameter
----------------------------
-
-You may have noticed that the previous service definition doesn't configure
-the class namespace as a parameter:
-
-.. code-block:: yaml
-
- # app/config/services.yml
-
- # service definition with class namespace as parameter
- parameters:
- slugger.class: AppBundle\Utils\Slugger
-
- services:
- app.slugger:
- class: '%slugger.class%'
-
-This practice is cumbersome and completely unnecessary for your own services.
-
-.. best-practice::
-
- Don't define parameters for the classes of your services.
-
-This practice was wrongly adopted from third-party bundles. When Symfony
-introduced its service container, some developers used this technique to
-allow overriding services. However, overriding a service by just changing its
-class name is a very rare use case because, frequently, the new service has
-different constructor arguments.
-
-Using a Persistence Layer
--------------------------
-
-Symfony is an HTTP framework that only cares about generating an HTTP response
-for each HTTP request. That's why Symfony doesn't provide a way to talk to
-a persistence layer (e.g. database, external API). You can choose whatever
-library or strategy you want for this.
-
-In practice, many Symfony applications rely on the independent
-`Doctrine project`_ to define their model using entities and repositories.
-
-Doctrine support is not enabled by default in Symfony. So to use Doctrine
-as shown in the examples below you will need to install :doc:`Doctrine ORM support `
-by executing the following command:
-
-.. code-block:: terminal
-
- $ composer require symfony/orm-pack
-
-Just like with business logic, we recommend storing Doctrine entities in the
-AppBundle.
-
-The three entities defined by our sample blog application are a good example:
-
-.. code-block:: text
-
- symfony-project/
- ├─ ...
- └─ src/
- └─ AppBundle/
- └─ Entity/
- ├─ Comment.php
- ├─ Post.php
- └─ User.php
-
-.. tip::
-
- If you're more advanced, you can store them under your own namespace in ``src/``.
-
-Doctrine Mapping Information
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Doctrine entities are plain PHP objects that you store in some "database".
-Doctrine only knows about your entities through the mapping metadata configured
-for your model classes. Doctrine supports four metadata formats: YAML, XML,
-PHP and annotations.
-
-.. best-practice::
-
- Use annotations to define the mapping information of the Doctrine entities.
-
-Annotations are by far the most convenient and agile way of setting up and
-looking for mapping information::
-
- namespace AppBundle\Entity;
-
- use Doctrine\Common\Collections\ArrayCollection;
- use Doctrine\ORM\Mapping as ORM;
-
- /**
- * @ORM\Entity
- */
- class Post
- {
- const NUMBER_OF_ITEMS = 10;
-
- /**
- * @ORM\Id
- * @ORM\GeneratedValue
- * @ORM\Column(type="integer")
- */
- private $id;
-
- /**
- * @ORM\Column(type="string")
- */
- private $title;
-
- /**
- * @ORM\Column(type="string")
- */
- private $slug;
-
- /**
- * @ORM\Column(type="text")
- */
- private $content;
-
- /**
- * @ORM\Column(type="string")
- */
- private $authorEmail;
-
- /**
- * @ORM\Column(type="datetime")
- */
- private $publishedAt;
-
- /**
- * @ORM\OneToMany(
- * targetEntity="App\Entity\Comment",
- * mappedBy="post",
- * orphanRemoval=true
- * )
- * @ORM\OrderBy({"publishedAt"="ASC"})
- */
- private $comments;
-
- public function __construct()
- {
- $this->publishedAt = new \DateTime();
- $this->comments = new ArrayCollection();
- }
-
- // getters and setters ...
- }
-
-All formats have the same performance, so this is once again ultimately a
-matter of taste.
-
-Data Fixtures
-~~~~~~~~~~~~~
-
-As fixtures support is not enabled by default in Symfony, you should execute
-the following command to install the Doctrine fixtures bundle:
-
-.. code-block:: terminal
-
- $ composer require --dev doctrine/doctrine-fixtures-bundle
-
-Then, enable the bundle in ``AppKernel.php``, but only for the ``dev`` and
-``test`` environments::
-
- use Symfony\Component\HttpKernel\Kernel;
-
- class AppKernel extends Kernel
- {
- public function registerBundles()
- {
- $bundles = [
- // ...
- ];
-
- if (in_array($this->getEnvironment(), ['dev', 'test'])) {
- // ...
- $bundles[] = new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle();
- }
-
- return $bundles;
- }
-
- // ...
- }
-
-We recommend creating just *one* `fixture class`_ for simplicity, though
-you're welcome to have more if that class gets quite large.
-
-Assuming you have at least one fixtures class and that the database access
-is configured properly, you can load your fixtures by executing the following
-command:
-
-.. code-block:: terminal
-
- $ php bin/console doctrine:fixtures:load
-
- Careful, database will be purged. Do you want to continue Y/N ? Y
- > purging database
- > loading AppBundle\DataFixtures\ORM\LoadFixtures
-
-Coding Standards
-----------------
-
-The Symfony source code follows the `PSR-1`_ and `PSR-2`_ coding standards that
-were defined by the PHP community. You can learn more about
-:doc:`the Symfony Coding standards ` and even
-use the `PHP-CS-Fixer`_, which is a command-line utility that can fix the
-coding standards of an entire codebase in a matter of seconds.
-
-----
-
-Next: :doc:`/best_practices/controllers`
-
-.. _`full definition`: https://en.wikipedia.org/wiki/Business_logic
-.. _`Doctrine project`: http://www.doctrine-project.org/
-.. _`fixture class`: https://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html#writing-simple-fixtures
-.. _`PSR-1`: https://www.php-fig.org/psr/psr-1/
-.. _`PSR-2`: https://www.php-fig.org/psr/psr-2/
-.. _`PHP-CS-Fixer`: https://github.com/FriendsOfPHP/PHP-CS-Fixer
diff --git a/best_practices/configuration.rst b/best_practices/configuration.rst
deleted file mode 100644
index d6e51c47451..00000000000
--- a/best_practices/configuration.rst
+++ /dev/null
@@ -1,222 +0,0 @@
-Configuration
-=============
-
-Configuration usually involves different application parts (such as infrastructure
-and security credentials) and different environments (development, production).
-That's why Symfony recommends that you split the application configuration into
-three parts.
-
-.. _config-parameters.yml:
-
-Infrastructure-Related Configuration
-------------------------------------
-
-.. best-practice::
-
- Define the infrastructure-related configuration options in the
- ``app/config/parameters.yml`` file.
-
-The default ``parameters.yml`` file follows this recommendation and defines the
-options related to the database and mail server infrastructure:
-
-.. code-block:: yaml
-
- # app/config/parameters.yml
- parameters:
- database_driver: pdo_mysql
- database_host: 127.0.0.1
- database_port: ~
- database_name: symfony
- database_user: root
- database_password: ~
-
- mailer_transport: smtp
- mailer_host: 127.0.0.1
- mailer_user: ~
- mailer_password: ~
-
- # ...
-
-These options aren't defined inside the ``app/config/config.yml`` file because
-they have nothing to do with the application's behavior. In other words, your
-application doesn't care about the location of your database or the credentials
-to access to it, as long as the database is correctly configured.
-
-.. _best-practices-canonical-parameters:
-
-Canonical Parameters
-~~~~~~~~~~~~~~~~~~~~
-
-.. best-practice::
-
- Define all your application's parameters in the
- ``app/config/parameters.yml.dist`` file.
-
-Symfony includes a configuration file called ``parameters.yml.dist``, which
-stores the canonical list of configuration parameters for the application.
-
-Whenever a new configuration parameter is defined for the application, you
-should also add it to this file and submit the changes to your version control
-system. Then, whenever a developer updates the project or deploys it to a server,
-Symfony will check if there is any difference between the canonical
-``parameters.yml.dist`` file and your local ``parameters.yml`` file. If there
-is a difference, Symfony will ask you to provide a value for the new parameter
-and it will add it to your local ``parameters.yml`` file.
-
-Application-Related Configuration
----------------------------------
-
-.. best-practice::
-
- Define the application behavior related configuration options in the
- ``app/config/config.yml`` file.
-
-The ``config.yml`` file contains the options used by the application to modify
-its behavior, such as the sender of email notifications, or the enabled
-`feature toggles`_. Defining these values in ``parameters.yml`` file would
-add an extra layer of configuration that's not needed because you don't need
-or want these configuration values to change on each server.
-
-The configuration options defined in the ``config.yml`` file usually vary from
-one :doc:`environment ` to another. That's
-why Symfony already includes ``app/config/config_dev.yml`` and ``app/config/config_prod.yml``
-files so that you can override specific values for each environment.
-
-Constants vs Configuration Options
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-One of the most common errors when defining application configuration is to
-create new options for values that never change, such as the number of items for
-paginated results.
-
-.. best-practice::
-
- Use constants to define configuration options that rarely change.
-
-The traditional approach for defining configuration options has caused many
-Symfony applications to include an option like the following, which would be
-used to control the number of posts to display on the blog homepage:
-
-.. code-block:: yaml
-
- # app/config/config.yml
- parameters:
- homepage.number_of_items: 10
-
-If you've done something like this in the past, it's likely that you've in fact
-*never* actually needed to change that value. Creating a configuration
-option for a value that you are never going to configure just isn't necessary.
-Our recommendation is to define these values as constants in your application.
-You could, for example, define a ``NUMBER_OF_ITEMS`` constant in the ``Post`` entity::
-
- // src/AppBundle/Entity/Post.php
- namespace AppBundle\Entity;
-
- class Post
- {
- const NUMBER_OF_ITEMS = 10;
-
- // ...
- }
-
-The main advantage of defining constants is that you can use their values
-everywhere in your application. When using parameters, they are only available
-from places with access to the Symfony container.
-
-Constants can be used for example in your Twig templates thanks to the
-`constant() function`_:
-
-.. code-block:: html+twig
-
-
- Displaying the {{ constant('NUMBER_OF_ITEMS', post) }} most recent results.
-
-
-And Doctrine entities and repositories can access these values too, whereas they
-cannot access the container parameters::
-
- namespace AppBundle\Repository;
-
- use AppBundle\Entity\Post;
- use Doctrine\ORM\EntityRepository;
-
- class PostRepository extends EntityRepository
- {
- public function findLatest($limit = Post::NUMBER_OF_ITEMS)
- {
- // ...
- }
- }
-
-The only notable disadvantage of using constants for this kind of configuration
-values is that it's complicated to redefine their values in your tests.
-
-Parameter Naming
-----------------
-
-.. best-practice::
-
- The name of your configuration parameters should be as short as possible and
- should include a common prefix for the entire application.
-
-Using ``app.`` as the prefix of your parameters is a common practice to avoid
-collisions with Symfony and third-party bundles/libraries parameters. Then, use
-just one or two words to describe the purpose of the parameter:
-
-.. code-block:: yaml
-
- # app/config/config.yml
- parameters:
- # don't do this: 'dir' is too generic and it doesn't convey any meaning
- app.dir: '...'
- # do this: short but easy to understand names
- app.contents_dir: '...'
- # it's OK to use dots, underscores, dashes or nothing, but always
- # be consistent and use the same format for all the parameters
- app.dir.contents: '...'
- app.contents-dir: '...'
-
-Semantic Configuration: Don't Do It
------------------------------------
-
-.. best-practice::
-
- Don't define a semantic dependency injection configuration for your bundles.
-
-As explained in :doc:`/bundles/extension` article, Symfony bundles
-have two choices on how to handle configuration: normal service configuration
-through the ``services.yml`` file and semantic configuration through a special
-``*Extension`` class.
-
-Although semantic configuration is much more powerful and provides nice features
-such as configuration validation, the amount of work needed to define that
-configuration isn't worth it for bundles that aren't meant to be shared as
-third-party bundles.
-
-Moving Sensitive Options Outside of Symfony Entirely
-----------------------------------------------------
-
-When dealing with sensitive options, like database credentials, we also recommend
-that you store them outside the Symfony project and make them available
-through environment variables:
-
-.. code-block:: yaml
-
- # app/config/config.yml
- doctrine:
- dbal:
- # ...
- password: "%env(DB_PASSWORD)%"
-
-.. versionadded:: 3.2
-
- Support for runtime environment variables via the ``%env(...)%`` syntax
- was introduced in Symfony 3.2. Prior to version 3.2, you needed to use the
- :doc:`special SYMFONY__ variables `.
-
-----
-
-Next: :doc:`/best_practices/business-logic`
-
-.. _`feature toggles`: https://en.wikipedia.org/wiki/Feature_toggle
-.. _`constant() function`: https://twig.symfony.com/doc/2.x/functions/constant.html
diff --git a/best_practices/controllers.rst b/best_practices/controllers.rst
deleted file mode 100644
index b96facacae3..00000000000
--- a/best_practices/controllers.rst
+++ /dev/null
@@ -1,226 +0,0 @@
-Controllers
-===========
-
-Symfony follows the philosophy of *"thin controllers and fat models"*. This
-means that controllers should hold just the thin layer of *glue-code*
-needed to coordinate the different parts of the application.
-
-As a rule of thumb, you should follow the 5-10-20 rule, where controllers should
-only define 5 variables or fewer, contain 10 actions or fewer and include 20 lines
-of code or fewer in each action. This isn't an exact science, but it should
-help you realize when code should be refactored out of the controller and
-into a service.
-
-.. best-practice::
-
- Make your controller extend the FrameworkBundle base controller and use
- annotations to configure routing, caching and security whenever possible.
-
-Coupling the controllers to the underlying framework allows you to leverage
-all of its features and increases your productivity.
-
-And since your controllers should be thin and contain nothing more than a
-few lines of *glue-code*, spending hours trying to decouple them from your
-framework doesn't benefit you in the long run. The amount of time *wasted*
-isn't worth the benefit.
-
-In addition, using annotations for routing, caching and security simplifies
-configuration. You don't need to browse tens of files created with different
-formats (YAML, XML, PHP): all the configuration is just where you need it
-and it only uses one format.
-
-Overall, this means you should aggressively decouple your business logic
-from the framework while, at the same time, aggressively coupling your controllers
-and routing *to* the framework in order to get the most out of it.
-
-Routing Configuration
----------------------
-
-To load routes defined as annotations in your controllers, add the following
-configuration to the main routing configuration file:
-
-.. code-block:: yaml
-
- # app/config/routing.yml
- app:
- resource: '@AppBundle/Controller/'
- type: annotation
-
-This configuration will load annotations from any controller stored inside the
-``src/AppBundle/Controller/`` directory and even from its subdirectories.
-So if your application defines lots of controllers, it's perfectly OK to
-reorganize them into subdirectories:
-
-.. code-block:: text
-
- /
- ├─ ...
- └─ src/
- └─ AppBundle/
- ├─ ...
- └─ Controller/
- ├─ DefaultController.php
- ├─ ...
- ├─ Api/
- │ ├─ ...
- │ └─ ...
- └─ Backend/
- ├─ ...
- └─ ...
-
-Template Configuration
-----------------------
-
-.. best-practice::
-
- Don't use the ``@Template`` annotation to configure the template used by
- the controller.
-
-The ``@Template`` annotation is useful, but also involves some magic. We
-don't think its benefit is worth the magic, and so recommend against using
-it.
-
-Most of the time, ``@Template`` is used without any parameters, which makes it
-more difficult to know which template is being rendered. It also hides the fact
-that a controller should always return a Response object (unless you're using a
-view layer).
-
-What does the Controller look like
-----------------------------------
-
-Considering all this, here is an example of what the controller should look like
-for the homepage of our app::
-
- namespace AppBundle\Controller;
-
- use AppBundle\Entity\Post;
- use Symfony\Bundle\FrameworkBundle\Controller\Controller;
- use Symfony\Component\Routing\Annotation\Route;
-
- class DefaultController extends Controller
- {
- /**
- * @Route("/", name="homepage")
- */
- public function indexAction()
- {
- $posts = $this->getDoctrine()
- ->getRepository(Post::class)
- ->findLatest();
-
- return $this->render('default/index.html.twig', [
- 'posts' => $posts,
- ]);
- }
- }
-
-Fetching Services
------------------
-
-If you extend the base ``Controller`` class, you can access services directly from
-the container via ``$this->container->get()`` or ``$this->get()``. But instead, you
-should use dependency injection to fetch services by
-:ref:`type-hinting action method arguments `:
-
-.. best-practice::
-
- Don't use ``$this->get()`` or ``$this->container->get()`` to fetch services
- from the container. Instead, use dependency injection.
-
-By not fetching services directly from the container, you can make your services
-*private*, which has :ref:`several advantages `.
-
-.. _best-practices-paramconverter:
-
-Using the ParamConverter
-------------------------
-
-If you're using Doctrine, then you can *optionally* use the `ParamConverter`_
-to automatically query for an entity and pass it as an argument to your controller.
-
-.. best-practice::
-
- Use the ParamConverter trick to automatically query for Doctrine entities
- when it's simple and convenient.
-
-For example::
-
- use AppBundle\Entity\Post;
- use Symfony\Component\Routing\Annotation\Route;
-
- /**
- * @Route("/{id}", name="admin_post_show")
- */
- public function showAction(Post $post)
- {
- $deleteForm = $this->createDeleteForm($post);
-
- return $this->render('admin/post/show.html.twig', [
- 'post' => $post,
- 'delete_form' => $deleteForm->createView(),
- ]);
- }
-
-Normally, you'd expect a ``$id`` argument to ``showAction()``. Instead, by
-creating a new argument (``$post``) and type-hinting it with the ``Post``
-class (which is a Doctrine entity), the ParamConverter automatically queries
-for an object whose ``$id`` property matches the ``{id}`` value. It will
-also show a 404 page if no ``Post`` can be found.
-
-When Things Get More Advanced
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The above example works without any configuration because the wildcard name ``{id}`` matches
-the name of the property on the entity. If this isn't true, or if you have
-even more complex logic, the easiest thing to do is just query for the entity
-manually. In our application, we have this situation in ``CommentController``::
-
- /**
- * @Route("/comment/{postSlug}/new", name="comment_new")
- */
- public function newAction(Request $request, $postSlug)
- {
- $post = $this->getDoctrine()
- ->getRepository(Post::class)
- ->findOneBy(['slug' => $postSlug]);
-
- if (!$post) {
- throw $this->createNotFoundException();
- }
-
- // ...
- }
-
-You can also use the ``@ParamConverter`` configuration, which is infinitely
-flexible::
-
- use AppBundle\Entity\Post;
- use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
- use Symfony\Component\HttpFoundation\Request;
- use Symfony\Component\Routing\Annotation\Route;
-
- /**
- * @Route("/comment/{postSlug}/new", name="comment_new")
- * @ParamConverter("post", options={"mapping"={"postSlug"="slug"}})
- */
- public function newAction(Request $request, Post $post)
- {
- // ...
- }
-
-The point is this: the ParamConverter shortcut is great for most situations.
-However, there is nothing wrong with querying for entities directly if the
-ParamConverter would get complicated.
-
-Pre and Post Hooks
-------------------
-
-If you need to execute some code before or after the execution of your controllers,
-you can use the EventDispatcher component to
-:doc:`set up before and after filters `.
-
-----
-
-Next: :doc:`/best_practices/templates`
-
-.. _`ParamConverter`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
diff --git a/best_practices/creating-the-project.rst b/best_practices/creating-the-project.rst
deleted file mode 100644
index abcda84b621..00000000000
--- a/best_practices/creating-the-project.rst
+++ /dev/null
@@ -1,185 +0,0 @@
-Creating the Project
-====================
-
-Installing Symfony
-------------------
-
-In the past, Symfony projects were created with `Composer`_, the dependency manager
-for PHP applications. However, the current recommendation is to use the **Symfony
-Installer**, which has to be installed before creating your first project.
-
-.. best-practice::
-
- Use the Symfony Installer to create new Symfony-based projects.
-
-Read the :doc:`/setup` article learn how to install and use the Symfony
-Installer.
-
-.. _linux-and-mac-os-x-systems:
-.. _windows-systems:
-
-Creating the Blog Application
------------------------------
-
-Now that everything is correctly set up, you can create a new project based on
-Symfony. In your command console, browse to a directory where you have permission
-to create files and execute the following commands:
-
-**Linux and macOS systems**:
-
-.. class:: command-linux
-.. code-block:: terminal
-
- $ cd projects/
- $ symfony new blog --version=3.4
-
-**Windows systems**:
-
-.. class:: command-windows
-.. code-block:: terminal
-
- > cd projects/
- > php symfony new blog --version=3.4
-
-.. note::
-
- If the installer doesn't work for you or doesn't output anything, make sure
- that the `Phar extension`_ is installed and enabled on your computer.
-
-This command creates a new directory called ``blog`` that contains a fresh new
-project based on the most recent stable Symfony version available. In addition,
-the installer checks if your system meets the technical requirements to execute
-Symfony applications. If not, you'll see the list of changes needed to meet those
-requirements.
-
-.. tip::
-
- Symfony releases are digitally signed for security reasons. If you want to
- verify the integrity of your Symfony installation, take a look at the
- `public checksums repository`_ and follow `these steps`_ to verify the
- signatures.
-
-Structuring the Application
----------------------------
-
-After creating the application, enter the ``blog/`` directory and you'll see a
-number of files and directories generated automatically:
-
-.. code-block:: text
-
- blog/
- ├─ app/
- │ ├─ config/
- │ └─ Resources/
- ├─ bin
- │ └─ console
- ├─ src/
- │ └─ AppBundle/
- ├─ var/
- │ ├─ cache/
- │ ├─ logs/
- │ └─ sessions/
- ├─ tests/
- │ └─ AppBundle/
- ├─ vendor/
- └─ web/
-
-This file and directory hierarchy is the convention proposed by Symfony to
-structure your applications. The recommended purpose of each directory is the
-following:
-
-* ``app/config/``, stores all the configuration defined for any environment;
-* ``app/Resources/``, stores all the templates and the translation files for the
- application;
-* ``src/AppBundle/``, stores the Symfony specific code (controllers and routes),
- your domain code (e.g. Doctrine classes) and all your business logic;
-* ``var/cache/``, stores all the cache files generated by the application;
-* ``var/logs/``, stores all the log files generated by the application;
-* ``var/sessions/``, stores all the session files generated by the application;
-* ``tests/AppBundle/``, stores the automatic tests (e.g. Unit tests) of the
- application.
-* ``vendor/``, this is the directory where Composer installs the application's
- dependencies and you should never modify any of its contents;
-* ``web/``, stores all the front controller files and all the web assets, such
- as stylesheets, JavaScript files and images.
-
-Application Bundles
-~~~~~~~~~~~~~~~~~~~
-
-When Symfony 2.0 was released, most developers naturally adopted the Symfony
-1.x way of dividing applications into logical modules. That's why many Symfony
-applications use bundles to divide their code into logical features: UserBundle,
-ProductBundle, InvoiceBundle, etc.
-
-But a bundle is *meant* to be something that can be reused as a stand-alone
-piece of software. If UserBundle cannot be used *"as is"* in other Symfony
-applications, then it shouldn't be its own bundle. Moreover, if InvoiceBundle
-depends on ProductBundle, then there's no advantage to having two separate bundles.
-
-.. best-practice::
-
- Create only one bundle called AppBundle for your application logic.
-
-Implementing a single AppBundle bundle in your projects will make your code
-more concise and easier to understand.
-
-.. note::
-
- There is no need to prefix the AppBundle with your own vendor (e.g.
- AcmeAppBundle), because this application bundle is never going to be
- shared.
-
-.. note::
-
- Another reason to create a new bundle is when you're overriding something
- in a vendor's bundle (e.g. a controller). See :doc:`/bundles/inheritance`.
-
-All in all, this is the typical directory structure of a Symfony application
-that follows these best practices:
-
-.. code-block:: text
-
- blog/
- ├─ app/
- │ ├─ config/
- │ └─ Resources/
- ├─ bin/
- │ └─ console
- ├─ src/
- │ └─ AppBundle/
- ├─ tests/
- │ └─ AppBundle/
- ├─ var/
- │ ├─ cache/
- │ ├─ logs/
- └─ sessions/
- ├─ vendor/
- └─ web/
- ├─ app.php
- └─ app_dev.php
-
-.. tip::
-
- If your Symfony installation doesn't come with a pre-generated AppBundle,
- you can generate it by hand executing this command:
-
- .. code-block:: terminal
-
- $ php bin/console generate:bundle --namespace=AppBundle --dir=src --format=annotation --no-interaction
-
-Extending the Directory Structure
----------------------------------
-
-If your project or infrastructure requires some changes to the default directory
-structure of Symfony, you can
-:doc:`override the location of the main directories `:
-``cache/``, ``logs/`` and ``web/``.
-
-----
-
-Next: :doc:`/best_practices/configuration`
-
-.. _`Composer`: https://getcomposer.org/
-.. _`Phar extension`: https://php.net/manual/en/intro.phar.php
-.. _`public checksums repository`: https://github.com/sensiolabs/checksums
-.. _`these steps`: http://fabien.potencier.org/signing-project-releases.html
diff --git a/best_practices/forms.rst b/best_practices/forms.rst
deleted file mode 100644
index 0e556216eeb..00000000000
--- a/best_practices/forms.rst
+++ /dev/null
@@ -1,238 +0,0 @@
-Forms
-=====
-
-Forms are one of the most misused Symfony components due to its vast scope and
-endless list of features. In this chapter we'll show you some of the best
-practices so you can leverage forms but get work done quickly.
-
-Building Forms
---------------
-
-.. best-practice::
-
- Define your forms as PHP classes.
-
-The Form component allows you to build forms right inside your controller
-code. This is perfectly fine if you don't need to reuse the form somewhere else.
-But for organization and reuse, we recommend that you define each
-form in its own PHP class::
-
- namespace AppBundle\Form;
-
- use AppBundle\Entity\Post;
- use Symfony\Component\Form\AbstractType;
- use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
- use Symfony\Component\Form\Extension\Core\Type\EmailType;
- use Symfony\Component\Form\Extension\Core\Type\TextareaType;
- use Symfony\Component\Form\FormBuilderInterface;
- use Symfony\Component\OptionsResolver\OptionsResolver;
-
- class PostType extends AbstractType
- {
- public function buildForm(FormBuilderInterface $builder, array $options)
- {
- $builder
- ->add('title')
- ->add('summary', TextareaType::class)
- ->add('content', TextareaType::class)
- ->add('authorEmail', EmailType::class)
- ->add('publishedAt', DateTimeType::class)
- ;
- }
-
- public function configureOptions(OptionsResolver $resolver)
- {
- $resolver->setDefaults([
- 'data_class' => Post::class,
- ]);
- }
- }
-
-.. best-practice::
-
- Put the form type classes in the ``AppBundle\Form`` namespace, unless you
- use other custom form classes like data transformers.
-
-To use the class, use ``createForm()`` and pass the fully qualified class name::
-
- // ...
- use AppBundle\Form\PostType;
-
- // ...
- public function newAction(Request $request)
- {
- $post = new Post();
- $form = $this->createForm(PostType::class, $post);
-
- // ...
- }
-
-Registering Forms as Services
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You can also :ref:`register your form type as a service `.
-This is only needed if your form type requires some dependencies to be injected
-by the container, otherwise it is unnecessary overhead and therefore *not*
-recommended to do this for all form type classes.
-
-Form Button Configuration
--------------------------
-
-Form classes should try to be agnostic to *where* they will be used. This
-makes them easier to re-use later.
-
-.. best-practice::
-
- Add buttons in the templates, not in the form classes or the controllers.
-
-The Symfony Form component allows you to add buttons as fields on your form.
-This is a nice way to simplify the template that renders your form. But if you
-add the buttons directly in your form class, this would effectively limit the
-scope of that form::
-
- class PostType extends AbstractType
- {
- public function buildForm(FormBuilderInterface $builder, array $options)
- {
- $builder
- // ...
- ->add('save', SubmitType::class, ['label' => 'Create Post'])
- ;
- }
-
- // ...
- }
-
-This form *may* have been designed for creating posts, but if you wanted
-to reuse it for editing posts, the button label would be wrong. Instead,
-some developers configure form buttons in the controller::
-
- namespace AppBundle\Controller\Admin;
-
- use AppBundle\Entity\Post;
- use AppBundle\Form\PostType;
- use Symfony\Bundle\FrameworkBundle\Controller\Controller;
- use Symfony\Component\Form\Extension\Core\Type\SubmitType;
- use Symfony\Component\HttpFoundation\Request;
-
- class PostController extends Controller
- {
- // ...
-
- public function newAction(Request $request)
- {
- $post = new Post();
- $form = $this->createForm(PostType::class, $post);
- $form->add('submit', SubmitType::class, [
- 'label' => 'Create',
- 'attr' => ['class' => 'btn btn-default pull-right'],
- ]);
-
- // ...
- }
- }
-
-This is also an important error, because you are mixing presentation markup
-(labels, CSS classes, etc.) with pure PHP code. Separation of concerns is
-always a good practice to follow, so put all the view-related things in the
-view layer:
-
-.. code-block:: html+twig
-
- {{ form_start(form) }}
- {{ form_widget(form) }}
-
-
- {{ form_end(form) }}
-
-Validation
-----------
-
-The :ref:`constraints ` option allows you to
-attach :doc:`validation constraints ` to any form field.
-However, doing that prevents the validation from being reused in other forms or
-other places where the mapped object is used.
-
-.. best-practice::
-
- Do not define your validation constraints in the form but on the object the
- form is mapped to.
-
-For example, to validate that the title of the post edited with a form is not
-blank, add the following in the ``Post`` object::
-
- // src/Entity/Post.php
-
- // ...
- use Symfony\Component\Validator\Constraints as Assert;
-
- class Post
- {
- /**
- * @Assert\NotBlank
- */
- public $title;
- }
-
-Rendering the Form
-------------------
-
-There are a lot of ways to render your form, ranging from rendering the entire
-thing in one line to rendering each part of each field independently. The
-best way depends on how much customization you need.
-
-One of the simplest ways - which is especially useful during development -
-is to render the form tags and use the ``form_widget()`` function to render
-all of the fields:
-
-.. code-block:: twig
-
- {{ form_start(form, {'attr': {'class': 'my-form-class'} }) }}
- {{ form_widget(form) }}
- {{ form_end(form) }}
-
-If you need more control over how your fields are rendered, then you should
-remove the ``form_widget(form)`` function and render your fields individually.
-See :doc:`/form/form_customization` for more information on this and how you
-can control *how* the form renders at a global level using form theming.
-
-Handling Form Submits
----------------------
-
-Handling a form submit usually follows a similar template::
-
- public function newAction(Request $request)
- {
- // build the form ...
-
- $form->handleRequest($request);
-
- if ($form->isSubmitted() && $form->isValid()) {
- $entityManager = $this->getDoctrine()->getManager();
- $entityManager->persist($post);
- $entityManager->flush();
-
- return $this->redirect($this->generateUrl(
- 'admin_post_show',
- ['id' => $post->getId()]
- ));
- }
-
- // render the template
- }
-
-There are really only two notable things here. First, we recommend that you
-use a single action for both rendering the form and handling the form submit.
-For example, you *could* have a ``newAction()`` that *only* renders the form
-and a ``createAction()`` that *only* processes the form submit. Both those
-actions will be almost identical. So it's much simpler to let ``newAction()``
-handle everything.
-
-Second, is it required to call ``$form->isSubmitted()`` in the ``if`` statement
-before calling ``isValid()``. Calling ``isValid()`` with an unsubmitted form
-is deprecated since version 3.2 and will throw an exception in 4.0.
-
-----
-
-Next: :doc:`/best_practices/i18n`
diff --git a/best_practices/i18n.rst b/best_practices/i18n.rst
deleted file mode 100644
index 57f3c1c94de..00000000000
--- a/best_practices/i18n.rst
+++ /dev/null
@@ -1,99 +0,0 @@
-Internationalization
-====================
-
-Internationalization and localization adapt the applications and their contents
-to the specific region or language of the users. In Symfony this is an opt-in
-feature that needs to be enabled before using it. To do this, uncomment the
-following ``translator`` configuration option and set your application locale:
-
-.. code-block:: yaml
-
- # app/config/config.yml
- framework:
- # ...
- translator: { fallbacks: ['%locale%'] }
-
- # app/config/parameters.yml
- parameters:
- # ...
- locale: en
-
-Translation Source File Format
-------------------------------
-
-The Symfony Translation component supports lots of different translation
-formats: PHP, Qt, ``.po``, ``.mo``, JSON, CSV, INI, etc.
-
-.. best-practice::
-
- Use the XLIFF format for your translation files.
-
-Of all the available translation formats, only XLIFF and gettext have broad
-support in the tools used by professional translators. And since it's based
-on XML, you can validate XLIFF file contents as you write them.
-
-Symfony supports notes in XLIFF files, making them more user-friendly for
-translators. At the end, good translations are all about context, and these
-XLIFF notes allow you to define that context.
-
-.. tip::
-
- The `PHP Translation Bundle`_ includes advanced extractors that can read
- your project and automatically update the XLIFF files.
-
-Translation Source File Location
---------------------------------
-
-.. best-practice::
-
- Store the translation files in the ``app/Resources/translations/``
- directory.
-
-Traditionally, Symfony developers have created these files in the
-``Resources/translations/`` directory of each bundle. But since the
-``app/Resources/`` directory is considered the global location for the
-application's resources, storing translations in ``app/Resources/translations/``
-centralizes them *and* gives them priority over any other translation file.
-This lets you override translations defined in third-party bundles.
-
-Translation Keys
-----------------
-
-.. best-practice::
-
- Always use keys for translations instead of content strings.
-
-Using keys simplifies the management of the translation files because you
-can change the original contents without having to update all of the translation
-files.
-
-Keys should always describe their *purpose* and *not* their location. For
-example, if a form has a field with the label "Username", then a nice key
-would be ``label.username``, *not* ``edit_form.label.username``.
-
-Example Translation File
-------------------------
-
-Applying all the previous best practices, the sample translation file for
-English in the application would be:
-
-.. code-block:: xml
-
-
-
-
-
-
-
- title.post_list
- Post List
-
-
-
-
-
-----
-
-Next: :doc:`/best_practices/security`
-
-.. _`PHP Translation Bundle`: https://github.com/php-translation/symfony-bundle
diff --git a/best_practices/index.rst b/best_practices/index.rst
deleted file mode 100644
index 8df4abb1364..00000000000
--- a/best_practices/index.rst
+++ /dev/null
@@ -1,19 +0,0 @@
-Official Symfony Best Practices
-===============================
-
-.. toctree::
- :hidden:
-
- introduction
- creating-the-project
- configuration
- business-logic
- controllers
- templates
- forms
- i18n
- security
- web-assets
- tests
-
-.. include:: /best_practices/map.rst.inc
diff --git a/best_practices/introduction.rst b/best_practices/introduction.rst
deleted file mode 100644
index cb510c34462..00000000000
--- a/best_practices/introduction.rst
+++ /dev/null
@@ -1,105 +0,0 @@
-.. index::
- single: Symfony Framework Best Practices
-
-The Symfony Framework Best Practices
-====================================
-
-The Symfony Framework is well-known for being *really* flexible and is used
-to build micro-sites, enterprise applications that handle billions of connections
-and even as the basis for *other* frameworks. Since its release in July 2011,
-the community has learned a lot about what's possible and how to do things *best*.
-
-These community resources - like blog posts or presentations - have created
-an unofficial set of recommendations for developing Symfony applications.
-Unfortunately, a lot of these recommendations are unneeded for web applications.
-Much of the time, they unnecessarily overcomplicate things and don't follow the
-original pragmatic philosophy of Symfony.
-
-What is this Guide About?
--------------------------
-
-This guide aims to fix that by describing the **best practices for developing
-web applications with the Symfony full-stack Framework**. These are best practices
-that fit the philosophy of the framework as envisioned by its original creator
-`Fabien Potencier`_.
-
-.. note::
-
- **Best practice** is a noun that means *"a well-defined procedure that is
- known to produce near-optimum results"*. And that's exactly what this
- guide aims to provide. Even if you don't agree with every recommendation,
- we believe these will help you build great applications with less complexity.
-
-This guide is **specially suited** for:
-
-* Websites and web applications developed with the full-stack Symfony Framework.
-
-For other situations, this guide might be a good **starting point** that you can
-then **extend and fit to your specific needs**:
-
-* Bundles shared publicly to the Symfony community;
-* Advanced developers or teams who have created their own standards;
-* Some complex applications that have highly customized requirements;
-* Bundles that may be shared internally within a company.
-
-We know that old habits die hard and some of you will be shocked by some
-of these best practices. But by following these, you'll be able to develop
-applications faster, with less complexity and with the same or even higher
-quality. It's also a moving target that will continue to improve.
-
-Keep in mind that these are **optional recommendations** that you and your team
-may or may not follow to develop Symfony applications. If you want to continue
-using your own best practices and methodologies, you can do it. Symfony is
-flexible enough to adapt to your needs. That will never change.
-
-Who this Book Is for (Hint: It's not a Tutorial)
-------------------------------------------------
-
-Any Symfony developer, whether you are an expert or a newcomer, can read this
-guide. But since this isn't a tutorial, you'll need some basic knowledge of
-Symfony to follow everything. If you are totally new to Symfony, welcome!
-Start with :doc:`The Quick Tour ` tutorial first.
-
-We've deliberately kept this guide short. We won't repeat explanations that
-you can find in the vast Symfony documentation, like discussions about Dependency
-Injection or front controllers. We'll solely focus on explaining how to do
-what you already know.
-
-The Application
----------------
-
-In addition to this guide, a sample application has been developed with all these
-best practices in mind. This project, called the Symfony Demo application, can
-be obtained through the Symfony Installer. First, `download and install`_ the
-installer and then execute this command to download the demo application:
-
-.. code-block:: terminal
-
- $ symfony demo
-
-**The demo application is a simple blog engine**, because that will allow us to
-focus on the Symfony concepts and features without getting buried in difficult
-implementation details. Instead of developing the application step by step in
-this guide, you'll find selected snippets of code through the chapters.
-
-Don't Update Your Existing Applications
----------------------------------------
-
-After reading this handbook, some of you may be considering refactoring your
-existing Symfony applications. Our recommendation is sound and clear: **you
-should not refactor your existing applications to comply with these best
-practices**. The reasons for not doing it are various:
-
-* Your existing applications are not wrong, they just follow another set of
- guidelines;
-* A full codebase refactorization is prone to introduce errors in your
- applications;
-* The amount of work spent on this could be better dedicated to improving
- your tests or adding features that provide real value to the end users.
-
-----
-
-Next: :doc:`/best_practices/creating-the-project`
-
-.. _`Fabien Potencier`: https://connect.symfony.com/profile/fabpot
-.. _`download and install`: https://symfony.com/download
diff --git a/best_practices/map.rst.inc b/best_practices/map.rst.inc
deleted file mode 100644
index f9dfd0c3e9d..00000000000
--- a/best_practices/map.rst.inc
+++ /dev/null
@@ -1,11 +0,0 @@
-* :doc:`/best_practices/introduction`
-* :doc:`/best_practices/creating-the-project`
-* :doc:`/best_practices/configuration`
-* :doc:`/best_practices/business-logic`
-* :doc:`/best_practices/controllers`
-* :doc:`/best_practices/templates`
-* :doc:`/best_practices/forms`
-* :doc:`/best_practices/i18n`
-* :doc:`/best_practices/security`
-* :doc:`/best_practices/web-assets`
-* :doc:`/best_practices/tests`
diff --git a/best_practices/security.rst b/best_practices/security.rst
deleted file mode 100644
index 19ba9f7e330..00000000000
--- a/best_practices/security.rst
+++ /dev/null
@@ -1,389 +0,0 @@
-Security
-========
-
-Authentication and Firewalls (i.e. Getting the User's Credentials)
-------------------------------------------------------------------
-
-You can configure Symfony to authenticate your users using any method you
-want and to load user information from any source. This is a complex topic, but
-the :doc:`Security guide` has a lot of information about
-this.
-
-Regardless of your needs, authentication is configured in ``security.yml``,
-primarily under the ``firewalls`` key.
-
-.. best-practice::
-
- Unless you have two legitimately different authentication systems and
- users (e.g. form login for the main site and a token system for your
- API only), we recommend having only *one* firewall entry with the ``anonymous``
- key enabled.
-
-Most applications only have one authentication system and one set of users. For
-this reason, you only need *one* firewall entry. If you have separated web and
-API sections on your site, you will need more firewall entries. But the point is
-to keep things simple.
-
-Additionally, you should use the ``anonymous`` key under your firewall. If
-you need to require users to be logged in for different sections of your
-site (or maybe nearly *all* sections), use the ``access_control`` area.
-
-.. best-practice::
-
- Use the ``bcrypt`` encoder for encoding your users' passwords.
-
-If your users have a password, then we recommend encoding it using the ``bcrypt``
-encoder, instead of the traditional SHA-512 hashing encoder. The main advantages
-of ``bcrypt`` are the inclusion of a *salt* value to protect against rainbow
-table attacks, and its adaptive nature, which allows to make it slower to
-remain resistant to brute-force search attacks.
-
-.. note::
-
- :ref:`Argon2i ` is the hashing algorithm as
- recommended by industry standards, but this won't be available to you unless
- you are using PHP 7.2+ or have the `libsodium`_ extension installed.
- ``bcrypt`` is sufficient for most applications.
-
-With this in mind, here is the authentication setup from our application,
-which uses a login form to load users from the database:
-
-.. code-block:: yaml
-
- # app/config/security.yml
- security:
- encoders:
- AppBundle\Entity\User: bcrypt
-
- providers:
- database_users:
- entity: { class: AppBundle:User, property: username }
-
- firewalls:
- secured_area:
- pattern: ^/
- anonymous: true
- form_login:
- check_path: login
- login_path: login
-
- logout:
- path: security_logout
- target: homepage
-
- # ... access_control exists, but is not shown here
-
-.. tip::
-
- The source code for our project contains comments that explain each part.
-
-Authorization (i.e. Denying Access)
------------------------------------
-
-Symfony gives you several ways to enforce authorization, including the ``access_control``
-configuration in :doc:`security.yml `, the
-:ref:`@Security annotation ` and using
-:ref:`isGranted ` on the ``security.authorization_checker``
-service directly.
-
-.. best-practice::
-
- * For protecting broad URL patterns, use ``access_control``;
- * Whenever possible, use the ``@Security`` annotation;
- * Check security directly on the ``security.authorization_checker`` service whenever
- you have a more complex situation.
-
-There are also different ways to centralize your authorization logic, like
-with a custom security voter or with ACL.
-
-.. best-practice::
-
- * For fine-grained restrictions, define a custom security voter;
- * For restricting access to *any* object by *any* user via an admin
- interface, use the Symfony ACL.
-
-.. _best-practices-security-annotation:
-
-The @Security Annotation
-------------------------
-
-For controlling access on a controller-by-controller basis, use the ``@Security``
-annotation whenever possible. Placing it above each action makes it consistent and readable.
-
-In our application, you need the ``ROLE_ADMIN`` in order to create a new post.
-Using ``@Security``, this looks like::
-
- use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
- use Symfony\Component\Routing\Annotation\Route;
- // ...
-
- /**
- * Displays a form to create a new Post entity.
- *
- * @Route("/new", name="admin_post_new")
- * @Security("has_role('ROLE_ADMIN')")
- */
- public function newAction()
- {
- // ...
- }
-
-Using Expressions for Complex Security Restrictions
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-If your security logic is a little bit more complex, you can use an :doc:`expression `
-inside ``@Security``. In the following example, a user can only access the
-controller if their email matches the value returned by the ``getAuthorEmail()``
-method on the ``Post`` object::
-
- use AppBundle\Entity\Post;
- use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
- use Symfony\Component\Routing\Annotation\Route;
-
- /**
- * @Route("/{id}/edit", name="admin_post_edit")
- * @Security("user.getEmail() == post.getAuthorEmail()")
- */
- public function editAction(Post $post)
- {
- // ...
- }
-
-Notice that this requires the use of the `ParamConverter`_, which automatically
-queries for the ``Post`` object and puts it on the ``$post`` argument. This
-is what makes it possible to use the ``post`` variable in the expression.
-
-This has one major drawback: an expression in an annotation cannot
-be reused in other parts of the application. Imagine that you want to add
-a link in a template that will only be seen by authors. Right now you'll
-need to repeat the expression code using Twig syntax:
-
-.. code-block:: html+twig
-
- {% if app.user and app.user.email == post.authorEmail %}
- ...
- {% endif %}
-
-A good solution - if your logic is simple enough - can be to add a new method
-to the ``Post`` entity that checks if a given user is its author::
-
- // src/AppBundle/Entity/Post.php
- // ...
-
- class Post
- {
- // ...
-
- /**
- * Is the given User the author of this Post?
- *
- * @return bool
- */
- public function isAuthor(User $user = null)
- {
- return $user && $user->getEmail() === $this->getAuthorEmail();
- }
- }
-
-Now you can reuse this method both in the template and in the security expression::
-
- use AppBundle\Entity\Post;
- use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
- use Symfony\Component\Routing\Annotation\Route;
-
- /**
- * @Route("/{id}/edit", name="admin_post_edit")
- * @Security("post.isAuthor(user)")
- */
- public function editAction(Post $post)
- {
- // ...
- }
-
-.. code-block:: html+twig
-
- {% if post.isAuthor(app.user) %}
- ...
- {% endif %}
-
-.. _best-practices-directly-isGranted:
-.. _checking-permissions-without-security:
-.. _manually-checking-permissions:
-
-Checking Permissions without @Security
---------------------------------------
-
-The above example with ``@Security`` only works because we're using the
-:ref:`ParamConverter `, which gives the expression
-access to the ``post`` variable. If you don't use this, or have some other
-more advanced use-case, you can always do the same security check in PHP::
-
- /**
- * @Route("/{id}/edit", name="admin_post_edit")
- */
- public function editAction($id)
- {
- $post = $this->getDoctrine()
- ->getRepository(Post::class)
- ->find($id);
-
- if (!$post) {
- throw $this->createNotFoundException();
- }
-
- if (!$post->isAuthor($this->getUser())) {
- $this->denyAccessUnlessGranted('edit', $post);
- }
- // equivalent code without using the "denyAccessUnlessGranted()" shortcut:
- //
- // use Symfony\Component\Security\Core\Exception\AccessDeniedException;
- // ...
- //
- // if (!$this->get('security.authorization_checker')->isGranted('edit', $post)) {
- // throw $this->createAccessDeniedException();
- // }
-
- // ...
- }
-
-Security Voters
----------------
-
-If your security logic is complex and can't be centralized into a method
-like ``isAuthor()``, you should leverage custom voters. These are an order
-of magnitude easier than :doc:`ACLs ` and will give
-you the flexibility you need in almost all cases.
-
-First, create a voter class. The following example shows a voter that implements
-the same ``getAuthorEmail()`` logic you used above::
-
- namespace AppBundle\Security;
-
- use AppBundle\Entity\Post;
- use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
- use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
- use Symfony\Component\Security\Core\Authorization\Voter\Voter;
- use Symfony\Component\Security\Core\User\UserInterface;
-
- class PostVoter extends Voter
- {
- const CREATE = 'create';
- const EDIT = 'edit';
-
- /**
- * @var AccessDecisionManagerInterface
- */
- private $decisionManager;
-
- public function __construct(AccessDecisionManagerInterface $decisionManager)
- {
- $this->decisionManager = $decisionManager;
- }
-
- protected function supports($attribute, $subject)
- {
- if (!in_array($attribute, [self::CREATE, self::EDIT])) {
- return false;
- }
-
- if (!$subject instanceof Post) {
- return false;
- }
-
- return true;
- }
-
- protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
- {
- $user = $token->getUser();
- /** @var Post */
- $post = $subject; // $subject must be a Post instance, thanks to the supports method
-
- if (!$user instanceof UserInterface) {
- return false;
- }
-
- switch ($attribute) {
- case self::CREATE:
- // if the user is an admin, allow them to create new posts
- if ($this->decisionManager->decide($token, ['ROLE_ADMIN'])) {
- return true;
- }
-
- break;
- case self::EDIT:
- // if the user is the author of the post, allow them to edit the posts
- if ($user->getEmail() === $post->getAuthorEmail()) {
- return true;
- }
-
- break;
- }
-
- return false;
- }
- }
-
-If you're using the :ref:`default services.yml configuration `,
-your application will :ref:`autoconfigure ` your security
-voter and inject an ``AccessDecisionManagerInterface`` instance into it thanks to
-:doc:`autowiring `.
-
-Now, you can use the voter with the ``@Security`` annotation::
-
- /**
- * @Route("/{id}/edit", name="admin_post_edit")
- * @Security("is_granted('edit', post)")
- */
- public function editAction(Post $post)
- {
- // ...
- }
-
-You can also use this directly with the ``security.authorization_checker`` service or
-via the even easier shortcut in a controller::
-
- /**
- * @Route("/{id}/edit", name="admin_post_edit")
- */
- public function editAction($id)
- {
- $post = ...; // query for the post
-
- $this->denyAccessUnlessGranted('edit', $post);
-
- // or without the shortcut:
- //
- // use Symfony\Component\Security\Core\Exception\AccessDeniedException;
- // ...
- //
- // if (!$this->get('security.authorization_checker')->isGranted('edit', $post)) {
- // throw $this->createAccessDeniedException();
- // }
- }
-
-Learn More
-----------
-
-The `FOSUserBundle`_, developed by the Symfony community, adds support for a
-database-backed user system in Symfony. It also handles common tasks like
-user registration and forgotten password functionality.
-
-Enable the :doc:`Remember Me feature ` to
-allow your users to stay logged in for a long period of time.
-
-When providing customer support, sometimes it's necessary to access the application
-as some *other* user so that you can reproduce the problem. Symfony provides
-the ability to :doc:`impersonate users `.
-
-If your company uses a user login method not supported by Symfony, you can
-develop :doc:`your own user provider ` and
-:doc:`your own authentication provider `.
-
-----
-
-Next: :doc:`/best_practices/web-assets`
-
-.. _`ParamConverter`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
-.. _`FOSUserBundle`: https://github.com/FriendsOfSymfony/FOSUserBundle
-.. _`libsodium`: https://pecl.php.net/package/libsodium
diff --git a/best_practices/templates.rst b/best_practices/templates.rst
deleted file mode 100644
index b1327a72fdf..00000000000
--- a/best_practices/templates.rst
+++ /dev/null
@@ -1,156 +0,0 @@
-Templates
-=========
-
-When PHP was created 20 years ago, developers loved its simplicity and how
-well it blended HTML and dynamic code. But as time passed, other template
-languages - like `Twig`_ - were created to make templating even better.
-
-.. best-practice::
-
- Use Twig templating format for your templates.
-
-Generally speaking, PHP templates are much more verbose than Twig templates because
-they lack native support for lots of modern features needed by templates,
-like inheritance, automatic escaping and named arguments for filters and
-functions.
-
-Twig is the default templating format in Symfony and has the largest community
-support of all non-PHP template engines (it's used in high profile projects
-such as Drupal 8).
-
-In addition, Twig is the only template format with guaranteed support in Symfony
-3.0. As a matter of fact, PHP may be removed from the officially supported
-template engines.
-
-Template Locations
-------------------
-
-.. best-practice::
-
- Store all your application's templates in ``app/Resources/views/`` directory.
-
-Traditionally, Symfony developers stored the application templates in the
-``Resources/views/`` directory of each bundle. Then they used Twig namespaces
-to refer to them (e.g. ``@AcmeDemo/Default/index.html.twig``).
-
-But for the templates used in your application, it's much more convenient
-to store them in the ``app/Resources/views/`` directory. For starters, this
-drastically simplifies their logical names:
-
-============================================ ==================================
-Templates Stored inside Bundles Templates Stored in ``app/``
-============================================ ==================================
-``@AcmeDemo/index.html.twig`` ``index.html.twig``
-``@AcmeDemo/Default/index.html.twig`` ``default/index.html.twig``
-``@AcmeDemo/Default/subdir/index.html.twig`` ``default/subdir/index.html.twig``
-============================================ ==================================
-
-Another advantage is that centralizing your templates simplifies the work
-of your designers. They don't need to look for templates in lots of directories
-scattered through lots of bundles.
-
-.. best-practice::
-
- Use lowercase snake_case for directory and template names.
-
-.. best-practice::
-
- Use a prefixed underscore for partial templates in template names.
-
-You often want to reuse template code using the ``include`` function to avoid
-redundant code. To determine those partials in the filesystem you should
-prefix partials and any other template without HTML body or ``extends`` tag
-with a single underscore.
-
-Twig Extensions
----------------
-
-.. best-practice::
-
- Define your Twig extensions in the ``AppBundle/Twig/`` directory. Your
- application will automatically detect them and configure them.
-
-Our application needs a custom ``md2html`` Twig filter so that we can transform
-the Markdown contents of each post into HTML.
-
-To do this, first, install the excellent `Parsedown`_ Markdown parser as
-a new dependency of the project:
-
-.. code-block:: terminal
-
- $ composer require erusev/parsedown
-
-Then, create a new ``Markdown`` class that will be used later by the Twig
-extension. It just needs to define one single method to transform
-Markdown content into HTML::
-
- namespace AppBundle\Utils;
-
- class Markdown
- {
- private $parser;
-
- public function __construct()
- {
- $this->parser = new \Parsedown();
- }
-
- public function toHtml($text)
- {
- return $this->parser->text($text);
- }
- }
-
-Next, create a new Twig extension and define a new filter called ``md2html``
-using the ``Twig\TwigFilter`` class. Inject the newly defined ``Markdown``
-class in the constructor of the Twig extension::
-
- namespace AppBundle\Twig;
-
- use AppBundle\Utils\Markdown;
- use Twig\Extension\AbstractExtension;
- use Twig\TwigFilter;
-
- class AppExtension extends AbstractExtension
- {
- private $parser;
-
- public function __construct(Markdown $parser)
- {
- $this->parser = $parser;
- }
-
- public function getFilters()
- {
- return [
- new TwigFilter(
- 'md2html',
- [$this, 'markdownToHtml'],
- ['is_safe' => ['html'], 'pre_escape' => 'html']
- ),
- ];
- }
-
- public function markdownToHtml($content)
- {
- return $this->parser->toHtml($content);
- }
-
- public function getName()
- {
- return 'app_extension';
- }
- }
-
-And that's it!
-
-If you're using the :ref:`default services.yml configuration `,
-you're done! Symfony will automatically know about your new service and tag it to
-be used as a Twig extension.
-
-----
-
-Next: :doc:`/best_practices/forms`
-
-.. _`Twig`: https://twig.symfony.com/
-.. _`Parsedown`: https://parsedown.org/
diff --git a/best_practices/tests.rst b/best_practices/tests.rst
deleted file mode 100644
index f6f67685535..00000000000
--- a/best_practices/tests.rst
+++ /dev/null
@@ -1,122 +0,0 @@
-Tests
-=====
-
-Roughly speaking, there are two types of test. Unit testing allows you to
-test the input and output of specific functions. Functional testing allows
-you to command a "browser" where you browse to pages on your site, click
-links, fill out forms and assert that you see certain things on the page.
-
-Unit Tests
-----------
-
-Unit tests are used to test your "business logic", which should live in classes
-that are independent of Symfony. For that reason, Symfony doesn't really
-have an opinion on what tools you use for unit testing. However, the most
-popular tools are `PHPUnit`_ and `PHPSpec`_.
-
-Functional Tests
-----------------
-
-Creating really good functional tests can be tough so some developers skip
-these completely. Don't skip the functional tests! By defining some *simple*
-functional tests, you can quickly spot any big errors before you deploy them:
-
-.. best-practice::
-
- Define a functional test that at least checks if your application pages
- are successfully loading.
-
-A functional test like this is simple to implement thanks to
-:ref:`PHPUnit data providers `::
-
- // tests/AppBundle/ApplicationAvailabilityFunctionalTest.php
- namespace Tests\AppBundle;
-
- use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
-
- class ApplicationAvailabilityFunctionalTest extends WebTestCase
- {
- /**
- * @dataProvider urlProvider
- */
- public function testPageIsSuccessful($url)
- {
- $client = self::createClient();
- $client->request('GET', $url);
-
- $this->assertTrue($client->getResponse()->isSuccessful());
- }
-
- public function urlProvider()
- {
- return [
- ['/'],
- ['/posts'],
- ['/post/fixture-post-1'],
- ['/blog/category/fixture-category'],
- ['/archives'],
- // ...
- ];
- }
- }
-
-This code checks that all the given URLs load successfully, which means that
-their HTTP response status code is between ``200`` and ``299``. This may
-not look that useful, but given how little effort this took, it's worth
-having it in your application.
-
-In computer software, this kind of test is called `smoke testing`_ and consists
-of *"preliminary testing to reveal simple failures severe enough to reject a
-prospective software release"*.
-
-Hardcode URLs in a Functional Test
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Some of you may be asking why the previous functional test doesn't use the URL
-generator service:
-
-.. best-practice::
-
- Hardcode the URLs used in the functional tests instead of using the URL
- generator.
-
-Consider the following functional test that uses the ``router`` service to
-generate the URL of the tested page::
-
- public function testBlogArchives()
- {
- $client = self::createClient();
- $url = $client->getContainer()->get('router')->generate('blog_archives');
- $client->request('GET', $url);
-
- // ...
- }
-
-This will work, but it has one *huge* drawback. If a developer mistakenly
-changes the path of the ``blog_archives`` route, the test will still pass,
-but the original (old) URL won't work! This means that any bookmarks for
-that URL will be broken and you'll lose any search engine page ranking.
-
-Testing JavaScript Functionality
---------------------------------
-
-The built-in functional testing client is great, but it can't be used to
-test any JavaScript behavior on your pages. If you need to test this, consider
-using the `Mink`_ library from within PHPUnit.
-
-If you have a heavy JavaScript frontend, you should consider using pure
-JavaScript-based testing tools.
-
-Learn More about Functional Tests
----------------------------------
-
-Consider using the `HautelookAliceBundle`_ to generate real-looking data for
-your test fixtures using `Faker`_ and `Alice`_.
-
-.. _`PHPUnit`: https://phpunit.de/
-.. _`PHPSpec`: https://www.phpspec.net/
-.. _`smoke testing`: https://en.wikipedia.org/wiki/Smoke_testing_(software)
-.. _`Mink`: http://mink.behat.org
-.. _`HautelookAliceBundle`: https://github.com/hautelook/AliceBundle
-.. _`Faker`: https://github.com/fzaninotto/Faker
-.. _`Alice`: https://github.com/nelmio/alice
diff --git a/best_practices/web-assets.rst b/best_practices/web-assets.rst
deleted file mode 100644
index c567bf36e47..00000000000
--- a/best_practices/web-assets.rst
+++ /dev/null
@@ -1,102 +0,0 @@
-Web Assets
-==========
-
-Web assets are things like CSS, JavaScript and image files that make the
-frontend of your site look and work great. Symfony developers have traditionally
-stored these assets in the ``Resources/public/`` directory of each bundle.
-
-.. best-practice::
-
- Store your assets in the ``web/`` directory.
-
-Scattering your web assets across tens of different bundles makes it more
-difficult to manage them. Your designers' lives will be much easier if all
-the application assets are in one location.
-
-Templates also benefit from centralizing your assets, because the links are
-much more concise:
-
-.. code-block:: html+twig
-
-
-
-
- {# ... #}
-
-
-
-
-.. note::
-
- Keep in mind that ``web/`` is a public directory and that anything stored
- here will be publicly accessible, including all the original asset files
- (e.g. Sass, LESS and CoffeeScript files).
-
-Using Assetic
--------------
-
-.. include:: /assetic/_standard_edition_warning.rst.inc
-
-These days, you probably can't create static CSS and JavaScript files and
-include them in your template without much effort. Instead, you'll probably
-want to combine and minify these to improve client-side performance. You may
-also want to use LESS or Sass (for example), which means you'll need some way
-to process these into CSS files.
-
-A lot of tools exist to solve these problems, including pure-frontend (non-PHP)
-tools like GruntJS.
-
-.. best-practice::
-
- Use Assetic to compile, combine and minimize web assets, unless you're
- comfortable with frontend tools like GruntJS.
-
-:doc:`Assetic ` is an asset manager capable
-of compiling assets developed with a lot of different frontend technologies
-like LESS, Sass and CoffeeScript. Combining all your assets with Assetic is a
-matter of wrapping all the assets with a single Twig tag:
-
-.. code-block:: html+twig
-
- {% stylesheets
- 'css/bootstrap.min.css'
- 'css/main.css'
- filter='cssrewrite' output='css/compiled/app.css' %}
-
- {% endstylesheets %}
-
- {# ... #}
-
- {% javascripts
- 'js/jquery.min.js'
- 'js/bootstrap.min.js'
- output='js/compiled/app.js' %}
-
- {% endjavascripts %}
-
-Frontend-Based Applications
----------------------------
-
-Recently, frontend technologies like AngularJS have become pretty popular
-for developing frontend web applications that talk to an API.
-
-If you are developing an application like this, you should use the tools
-that are recommended by the technology, such as Bower and GruntJS. You should
-develop your frontend application separately from your Symfony backend (even
-separating the repositories if you want).
-
-Learn More about Assetic
-------------------------
-
-Assetic can also minimize CSS and JavaScript assets
-:doc:`using UglifyCSS/UglifyJS ` to speed up your
-websites. You can even :doc:`compress images `
-with Assetic to reduce their size before serving them to the user. Check out
-the `official Assetic documentation`_ to learn more about all the available
-features.
-
-----
-
-Next: :doc:`/best_practices/tests`
-
-.. _`official Assetic documentation`: https://github.com/kriswallsmith/assetic
diff --git a/bundles.rst b/bundles.rst
index 43ccbb10111..21ecea9f606 100644
--- a/bundles.rst
+++ b/bundles.rst
@@ -8,73 +8,43 @@ The Bundle System
.. caution::
- In Symfony versions prior to 3.4, it was recommended to organize your own
+ In Symfony versions prior to 4.0, it was recommended to organize your own
application code using bundles. This is no longer recommended and bundles
should only be used to share code and features between multiple applications.
-A bundle is similar to a plugin in other software, but even better. The key
-difference is that *everything* is a bundle in Symfony, including both the
-core framework functionality and the code written for your application.
-Bundles are first-class citizens in Symfony. This gives you the flexibility
-to use pre-built features packaged in `third-party bundles`_ or to distribute
-your own bundles. It makes it easy to pick and choose which features to enable
-in your application and to optimize them the way you want.
-
-.. note::
-
- While you'll learn the basics here, an entire article is devoted to the
- organization and best practices of :doc:`bundles `.
-
-A bundle is simply a structured set of files within a directory that implement
-a single feature. You might create a BlogBundle, a ForumBundle or
-a bundle for user management (many of these exist already as open source
-bundles). Each directory contains everything related to that feature, including
-PHP files, templates, stylesheets, JavaScript files, tests and anything else.
-Every aspect of a feature exists in a bundle and every feature lives in a
-bundle.
-
-Bundles used in your applications must be enabled by registering them in
-the ``registerBundles()`` method of the ``AppKernel`` class::
-
- // app/AppKernel.php
- public function registerBundles()
- {
- $bundles = [
- new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
- new Symfony\Bundle\SecurityBundle\SecurityBundle(),
- new Symfony\Bundle\TwigBundle\TwigBundle(),
- new Symfony\Bundle\MonologBundle\MonologBundle(),
- new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),
- new Symfony\Bundle\DoctrineBundle\DoctrineBundle(),
- new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
- new AppBundle\AppBundle(),
- ];
-
- if (in_array($this->getEnvironment(), ['dev', 'test'])) {
- $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
- $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();
- $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
- }
-
- return $bundles;
- }
-
-With the ``registerBundles()`` method, you have total control over which bundles
-are used by your application (including the core Symfony bundles).
+A bundle is similar to a plugin in other software, but even better. The core
+features of Symfony framework are implemented with bundles (FrameworkBundle,
+SecurityBundle, DebugBundle, etc.) They are also used to add new features in
+your application via `third-party bundles`_.
+
+Bundles used in your applications must be enabled per
+:ref:`environment ` in the ``config/bundles.php``
+file::
+
+ // config/bundles.php
+ return [
+ // 'all' means that the bundle is enabled for any Symfony environment
+ Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
+ Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
+ Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
+ Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
+ Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle::class => ['all' => true],
+ Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
+ Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
+ // this bundle is enabled only in 'dev' and 'test', so you can't use it in 'prod'
+ Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
+ ];
.. tip::
- A bundle can live *anywhere* as long as it can be autoloaded (via the
- autoloader configured at ``app/autoload.php``).
+ In a default Symfony application that uses :ref:`Symfony Flex `,
+ bundles are enabled/disabled automatically for you when installing/removing
+ them, so you don't need to look at or edit this ``bundles.php`` file.
Creating a Bundle
-----------------
-`SensioGeneratorBundle`_ is an optional bundle that includes commands to create
-different elements of your application, such as bundles. If you create lots of
-bundles, consider using it. However, this section creates and enables a new
-bundle by hand to show how simple it is to do it.
-
+This section creates and enables a new bundle to show there are only a few steps required.
The new bundle is called AcmeTestBundle, where the ``Acme`` portion is just a
dummy name that should be replaced by some "vendor" name that represents you or
your organization (e.g. ABCTestBundle for some company named ``ABC``).
@@ -83,7 +53,7 @@ Start by creating a ``src/Acme/TestBundle/`` directory and adding a new file
called ``AcmeTestBundle.php``::
// src/Acme/TestBundle/AcmeTestBundle.php
- namespace Acme\TestBundle;
+ namespace App\Acme\TestBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
@@ -100,50 +70,22 @@ called ``AcmeTestBundle.php``::
This empty class is the only piece you need to create the new bundle. Though
commonly empty, this class is powerful and can be used to customize the behavior
-of the bundle.
-
-Now that you've created the bundle, enable it via the ``AppKernel`` class::
-
- // app/AppKernel.php
- public function registerBundles()
- {
- $bundles = [
- // ...
+of the bundle. Now that you've created the bundle, enable it::
- // register your bundle
- new Acme\TestBundle\AcmeTestBundle(),
- ];
+ // config/bundles.php
+ return [
// ...
-
- return $bundles;
- }
+ App\Acme\TestBundle\AcmeTestBundle::class => ['all' => true],
+ ];
And while it doesn't do anything yet, AcmeTestBundle is now ready to be used.
-And as easy as this is, Symfony also provides a command-line interface for
-generating a basic bundle skeleton:
-
-.. code-block:: terminal
-
- $ php bin/console generate:bundle --namespace=Acme/TestBundle
-
-The bundle skeleton generates a basic controller, template and routing
-resource that can be customized. You'll learn more about Symfony's command-line
-tools later.
-
-.. tip::
-
- Whenever creating a new bundle or using a third-party bundle, always make
- sure the bundle has been enabled in ``registerBundles()``. When using
- the ``generate:bundle`` command, this is done for you.
-
Bundle Directory Structure
--------------------------
-The directory structure of a bundle is simple and flexible. By default, the
-bundle system follows a set of conventions that help to keep code consistent
-between all Symfony bundles. Take a look at AcmeDemoBundle, as it contains some
-of the most common elements of a bundle:
+The directory structure of a bundle is meant to help to keep code consistent
+between all Symfony bundles. It follows a set of conventions, but is flexible
+to be adjusted if needed:
``Controller/``
Contains the controllers of the bundle (e.g. ``RandomController.php``).
@@ -154,14 +96,14 @@ of the most common elements of a bundle:
necessary).
``Resources/config/``
- Houses configuration, including routing configuration (e.g. ``routing.yml``).
+ Houses configuration, including routing configuration (e.g. ``routing.yaml``).
``Resources/views/``
Holds templates organized by controller name (e.g. ``Random/index.html.twig``).
``Resources/public/``
Contains web assets (images, stylesheets, etc) and is copied or symbolically
- linked into the project ``web/`` directory via the ``assets:install`` console
+ linked into the project ``public/`` directory via the ``assets:install`` console
command.
``Tests/``
@@ -178,11 +120,10 @@ the bundle.
Learn more
----------
-.. toctree::
- :maxdepth: 1
- :glob:
-
- bundles/*
+* :doc:`/bundles/override`
+* :doc:`/bundles/best_practices`
+* :doc:`/bundles/configuration`
+* :doc:`/bundles/extension`
+* :doc:`/bundles/prepend_extension`
.. _`third-party bundles`: https://github.com/search?q=topic%3Asymfony-bundle&type=Repositories
-.. _`SensioGeneratorBundle`: https://symfony.com/doc/current/bundles/SensioGeneratorBundle/index.html
diff --git a/bundles/best_practices.rst b/bundles/best_practices.rst
index c9b68c1443b..3dcd90036dc 100644
--- a/bundles/best_practices.rst
+++ b/bundles/best_practices.rst
@@ -4,21 +4,10 @@
Best Practices for Reusable Bundles
===================================
-There are two types of bundles:
-
-* Application-specific bundles: only used to build your application;
-* Reusable bundles: meant to be shared across many projects.
-
This article is all about how to structure your **reusable bundles** to be
-configurable and extendable. Many of these recommendations do not
-apply to application bundles because you'll want to keep those as simple
-as possible. For application bundles, just follow the practices shown throughout
-the guides.
-
-.. seealso::
-
- The best practices for application-specific bundles are discussed in
- :doc:`/best_practices/introduction`.
+configurable and extendable. Reusable bundles are those meant to be shared
+privately across many company projects or publicly so any Symfony project can
+install them.
.. index::
pair: Bundle; Naming conventions
@@ -28,13 +17,13 @@ the guides.
Bundle Name
-----------
-A bundle is also a PHP namespace. The namespace must follow the `PSR-0`_ or
-`PSR-4`_ interoperability standards for PHP namespaces and class names: it starts
-with a vendor segment, followed by zero or more category segments, and it ends
-with the namespace short name, which must end with ``Bundle``.
+A bundle is also a PHP namespace. The namespace must follow the `PSR-4`_
+interoperability standard for PHP namespaces and class names: it starts with a
+vendor segment, followed by zero or more category segments, and it ends with the
+namespace short name, which must end with ``Bundle``.
A namespace becomes a bundle as soon as you add a bundle class to it. The
-bundle class name must follow these simple rules:
+bundle class name must follow these rules:
* Use only alphanumeric characters and underscores;
* Use a StudlyCaps name (i.e. camelCase with an uppercase first letter);
@@ -124,8 +113,8 @@ Service Container Extensions ``DependencyInjection/``
Doctrine ORM entities (when not using annotations) ``Entity/``
Doctrine ODM documents (when not using annotations) ``Document/``
Event Listeners ``EventListener/``
-Configuration ``Resources/config/``
-Web Resources (CSS, JS, images) ``Resources/public/``
+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/``
@@ -177,6 +166,89 @@ the ``Tests/`` directory. Tests should follow the following principles:
A test suite must not contain ``AllTests.php`` scripts, but must rely on the
existence of a ``phpunit.xml.dist`` file.
+Continuous Integration
+----------------------
+
+Testing bundle code continuously, including all its commits and pull requests,
+is a good practice called Continuous Integration. There are several services
+providing this feature for free for open source projects. The most popular
+service for Symfony bundles is called `Travis CI`_.
+
+Here is the recommended configuration file (``.travis.yml``) for Symfony bundles,
+which test the two latest :doc:`LTS versions `
+of Symfony and the latest beta release:
+
+.. code-block:: yaml
+
+ language: php
+ sudo: false
+ cache:
+ directories:
+ - $HOME/.composer/cache/files
+ - $HOME/symfony-bridge/.phpunit
+
+ env:
+ global:
+ - PHPUNIT_FLAGS="-v"
+ - SYMFONY_PHPUNIT_DIR="$HOME/symfony-bridge/.phpunit"
+
+ matrix:
+ fast_finish: true
+ include:
+ # Minimum supported dependencies with the latest and oldest PHP version
+ - php: 7.2
+ env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" SYMFONY_DEPRECATIONS_HELPER="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.
+
+Installation
+------------
+
+Bundles should set ``"type": "symfony-bundle"`` in their ``composer.json`` file.
+With this, :ref:`Symfony Flex ` will be able to automatically
+enable your bundle when it's installed.
+
+If your bundle requires any setup (e.g. configuration, new files, changes to
+``.gitignore``, etc), then you should create a `Symfony Flex recipe`_.
+
Documentation
-------------
@@ -207,8 +279,19 @@ following standardized instructions in your ``README.md`` file.
[installation chapter](https://getcomposer.org/doc/00-intro.md)
of the Composer documentation.
- Step 1: Download the Bundle
- ---------------------------
+ Applications that use Symfony Flex
+ ----------------------------------
+
+ Open a command console, enter your project directory and execute:
+
+ ```console
+ $ composer require
+ ```
+
+ Applications that don't use Symfony Flex
+ ----------------------------------------
+
+ ### Step 1: Download the Bundle
Open a command console, enter your project directory and execute the
following command to download the latest stable version of this bundle:
@@ -217,30 +300,18 @@ following standardized instructions in your ``README.md`` file.
$ composer require
```
- Step 2: Enable the Bundle
- -------------------------
+ ### Step 2: Enable the Bundle
Then, enable the bundle by adding it to the list of registered bundles
- in the `app/AppKernel.php` file of your project:
+ in the `config/bundles.php` file of your project:
```php
- // app/AppKernel.php
-
- // ...
- class AppKernel extends Kernel
- {
- public function registerBundles()
- {
- $bundles = [
- // ...
- new \\(),
- ];
-
- // ...
- }
+ // config/bundles.php
+ return [
// ...
- }
+ \\::class => ['all' => true],
+ ];
```
.. code-block:: rst
@@ -251,8 +322,19 @@ following standardized instructions in your ``README.md`` file.
Make sure Composer is installed globally, as explained in the
`installation chapter`_ of the Composer documentation.
+ ----------------------------------
+
+ Open a command console, enter your project directory and execute:
+
+ .. code-block:: bash
+
+ $ composer require
+
+ Applications that don't use Symfony Flex
+ ----------------------------------------
+
Step 1: Download the Bundle
- ---------------------------
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
Open a command console, enter your project directory and execute the
following command to download the latest stable version of this bundle:
@@ -262,29 +344,16 @@ following standardized instructions in your ``README.md`` file.
$ composer require
Step 2: Enable the Bundle
- -------------------------
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
Then, enable the bundle by adding it to the list of registered bundles
- in the ``app/AppKernel.php`` file of your project::
-
- // app/AppKernel.php
-
- // ...
- class AppKernel extends Kernel
- {
- public function registerBundles()
- {
- $bundles = [
- // ...
-
- new \\(),
- ];
-
- // ...
- }
+ in the ``config/bundles.php`` file of your project::
+ // config/bundles.php
+ return [
// ...
- }
+ \\::class => ['all' => true],
+ ];
.. _`installation chapter`: https://getcomposer.org/doc/00-intro.md
@@ -338,13 +407,13 @@ The end user can provide values in any configuration file:
.. code-block:: yaml
- # app/config/config.yml
+ # config/services.yaml
parameters:
acme_blog.author.email: 'fabien@example.com'
.. code-block:: xml
-
+
setParameter('acme_blog.author.email', 'fabien@example.com');
Retrieve the configuration parameters in your code from the container::
$container->getParameter('acme_blog.author.email');
-Even if this mechanism is simple enough, you should consider using the more
-advanced :doc:`semantic bundle configuration `.
+While this mechanism requires the least effort, you should consider using the
+more advanced :doc:`semantic bundle configuration ` to
+make your configuration more robust.
Versioning
----------
@@ -377,8 +447,11 @@ Bundles must be versioned following the `Semantic Versioning Standard`_.
Services
--------
-If the bundle defines services, they must be prefixed with the bundle alias.
-For example, AcmeBlogBundle services must be prefixed with ``acme_blog``.
+If the bundle defines services, they must be prefixed with the bundle alias
+instead of using fully qualified class names like you do in your project
+services. For example, AcmeBlogBundle services must be prefixed with ``acme_blog``.
+The reason is that bundles shouldn't rely on features such as service autowiring
+or autoconfiguration to not impose an overhead when compiling application services.
In addition, services not meant to be used by the application directly, should
be :ref:`defined as private `. For public services,
@@ -418,9 +491,13 @@ The ``composer.json`` file should include at least the following metadata:
a string (or array of strings) with a `valid license identifier`_, such as ``MIT``.
``autoload``
- This information is used by Symfony to load the classes of the bundle. The
- `PSR-4`_ autoload standard is recommended for modern bundles, but `PSR-0`_
- standard is also supported.
+ 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/" } }``.
In order to make it easier for developers to find your bundle, register it on
`Packagist`_, the official repository for Composer packages.
@@ -430,7 +507,7 @@ 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. ``@AppBundle/Resources/config/services.xml``).
+paths (e.g. ``@FooBundle/Resources/config/services.xml``).
The logical paths are required because of the bundle overriding mechanism that
lets you override any resource/file of any bundle. See :ref:`http-kernel-resource-locator`
@@ -438,7 +515,7 @@ 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 AppBundle, is referenced as ``@App/Default/index.html.twig``.
+directory of the FooBundle, is referenced as ``@Foo/Default/index.html.twig``.
Learn more
----------
@@ -446,9 +523,11 @@ Learn more
* :doc:`/bundles/extension`
* :doc:`/bundles/configuration`
-.. _`PSR-0`: https://www.php-fig.org/psr/psr-0/
.. _`PSR-4`: https://www.php-fig.org/psr/psr-4/
+.. _`Symfony Flex recipe`: https://github.com/symfony/recipes
.. _`Semantic Versioning Standard`: https://semver.org/
.. _`Packagist`: https://packagist.org/
.. _`choose any license`: https://choosealicense.com/
.. _`valid license identifier`: https://spdx.org/licenses/
+.. _`Travis CI`: https://travis-ci.org/
+.. _`Travis cron`: https://docs.travis-ci.com/user/cron-jobs/
diff --git a/bundles/configuration.rst b/bundles/configuration.rst
index 2521aa53274..9da8c884370 100644
--- a/bundles/configuration.rst
+++ b/bundles/configuration.rst
@@ -5,11 +5,12 @@
How to Create Friendly Configuration for a Bundle
=================================================
-If you open your application configuration file (usually ``app/config/config.yml``),
-you'll see a number of different configuration sections, such as ``framework``,
-``twig`` and ``doctrine``. Each of these configures a specific bundle, allowing
-you to define options at a high level and then let the bundle make all the
-low-level, complex changes based on your settings.
+If you open your main application configuration directory (usually
+``config/packages/``), you'll see a number of different files, such as
+``framework.yaml``, ``twig.yaml`` and ``doctrine.yaml``. Each of these
+configures a specific bundle, allowing you to define options at a high level and
+then let the bundle make all the low-level, complex changes based on your
+settings.
For example, the following configuration tells the FrameworkBundle to enable the
form integration, which involves the definition of quite a few services as well
@@ -43,29 +44,18 @@ as integration of other related components:
'form' => true,
]);
-.. sidebar:: Using Parameters to Configure your Bundle
-
- If you don't have plans to share your bundle between projects, it doesn't
- make sense to use this more advanced way of configuration. Since you use
- the bundle only in one project, you can just change the service
- configuration each time.
-
- If you *do* want to be able to configure something from within
- ``config.yml``, you can always create a parameter there and use that
- parameter somewhere else.
-
Using the Bundle Extension
--------------------------
Imagine you are creating a new bundle - AcmeSocialBundle - which provides
-integration with Twitter, etc. To make your bundle easy to use, you want to
-allow users to configure it with some configuration that looks like this:
+integration with Twitter. To make your bundle configurable to the user, you
+can add some configuration that looks like this:
.. configuration-block::
.. code-block:: yaml
- # app/config/config.yml
+ # config/packages/acme_social.yaml
acme_social:
twitter:
client_id: 123
@@ -73,7 +63,7 @@ allow users to configure it with some configuration that looks like this:
.. code-block:: xml
-
+
loadFromExtension('acme_social', [
'twitter' => [
'client_id' => 123,
@@ -151,20 +141,20 @@ For the configuration example in the previous section, the array passed to your
]
Notice that this is an *array of arrays*, not just a single flat array of the
-configuration values. This is intentional, as it allows Symfony to parse
-several configuration resources. For example, if ``acme_social`` appears in
-another configuration file - say ``config_dev.yml`` - with different values
-beneath it, the incoming array might look like this::
+configuration values. This is intentional, as it allows Symfony to parse several
+configuration resources. For example, if ``acme_social`` appears in another
+configuration file - say ``config/packages/dev/acme_social.yaml`` - with
+different values beneath it, the incoming array might look like this::
[
- // values from config.yml
+ // values from config/packages/acme_social.yaml
[
'twitter' => [
'client_id' => 123,
'client_secret' => 'your_secret',
],
],
- // values from config_dev.yml
+ // values from config/packages/dev/acme_social.yaml
[
'twitter' => [
'client_id' => 456,
@@ -192,10 +182,9 @@ The ``Configuration`` class to handle the sample configuration looks like::
{
public function getConfigTreeBuilder()
{
- $treeBuilder = new TreeBuilder();
- $rootNode = $treeBuilder->root('acme_social');
+ $treeBuilder = new TreeBuilder('acme_social');
- $rootNode
+ $treeBuilder->getRootNode()
->children()
->arrayNode('twitter')
->children()
@@ -308,7 +297,7 @@ In your extension, you can load this and dynamically set its arguments::
.. sidebar:: Processing the Configuration yourself
Using the Config component is fully optional. The ``load()`` method gets an
- array of configuration values. You can simply parse these arrays yourself
+ array of configuration values. You can instead parse these arrays yourself
(e.g. by overriding configurations and using :phpfunction:`isset` to check
for the existence of a value). Be aware that it'll be very hard to support XML::
@@ -326,12 +315,10 @@ In your extension, you can load this and dynamically set its arguments::
Modifying the Configuration of Another Bundle
---------------------------------------------
-If you have multiple bundles that depend on each other, it may be useful
-to allow one ``Extension`` class to modify the configuration passed to another
-bundle's ``Extension`` class, as if the end-developer has actually placed that
-configuration in their ``app/config/config.yml`` file. This can be achieved
-using a prepend extension. For more details, see
-:doc:`/bundles/prepend_extension`.
+If you have multiple bundles that depend on each other, it may be useful to
+allow one ``Extension`` class to modify the configuration passed to another
+bundle's ``Extension`` class. This can be achieved using a prepend extension.
+For more details, see :doc:`/bundles/prepend_extension`.
Dump the Configuration
----------------------
@@ -403,7 +390,7 @@ namespace is then replaced with the XSD validation base path returned from
method. This namespace is then followed by the rest of the path from the base
path to the file itself.
-By convention, the XSD file lives in the ``Resources/config/schema``, but you
+By convention, the XSD file lives in the ``Resources/config/schema/``, but you
can place it anywhere you like. You should return this path as the base path::
// src/Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php
@@ -424,7 +411,7 @@ Assuming the XSD file is called ``hello-1.0.xsd``, the schema location will be
.. code-block:: xml
-
+
`
-to return the correct DI alias. The DI alias is the name used to refer to the
-bundle in the container (e.g. in the ``app/config/config.yml`` file). By
+method to return the correct DI alias. The DI alias is the name used to refer to
+the bundle in the container (e.g. in the ``config/packages/`` files). By
default, this is done by removing the ``Extension`` suffix and converting the
class name to underscores (e.g. ``AcmeHelloExtension``'s DI alias is
``acme_hello``).
@@ -87,11 +84,10 @@ container.
In the ``load()`` method, you can use PHP code to register service definitions,
but it is more common if you put these definitions in a configuration file
-(using the Yaml, XML or PHP format). Luckily, you can use the file loaders in
-the extension!
+(using the YAML, XML or PHP format).
For instance, assume you have a file called ``services.xml`` in the
-``Resources/config`` directory of your bundle, your ``load()`` method looks like::
+``Resources/config/`` directory of your bundle, your ``load()`` method looks like::
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
@@ -106,67 +102,33 @@ For instance, assume you have a file called ``services.xml`` in the
$loader->load('services.xml');
}
-Other available loaders are the ``YamlFileLoader``, ``PhpFileLoader`` and
-``IniFileLoader``.
-
-.. note::
-
- The ``IniFileLoader`` can only be used to load parameters and it can only
- load them as strings.
-
-.. caution::
-
- If you removed the default file with service definitions (i.e.
- ``app/config/services.yml``), make sure to also remove it from the
- ``imports`` key in ``app/config/config.yml``.
+The other available loaders are ``YamlFileLoader`` and ``PhpFileLoader``.
Using Configuration to Change the Services
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The Extension is also the class that handles the configuration for that
-particular bundle (e.g. the configuration in ``app/config/config.yml``). To
-read more about it, see the ":doc:`/bundles/configuration`" article.
+particular bundle (e.g. the configuration in ``config/packages/.yaml``).
+To read more about it, see the ":doc:`/bundles/configuration`" article.
Adding Classes to Compile
-------------------------
-.. deprecated:: 3.3
-
- This technique is discouraged and the ``addClassesToCompile()`` method was
- deprecated in Symfony 3.3 because modern PHP versions make it unnecessary.
-
-Symfony creates a big ``classes.php`` file in the cache directory to aggregate
-the contents of the PHP classes that are used in every request. This reduces the
-I/O operations and increases the application performance.
+Bundles can hint Symfony about which of their classes contain annotations so
+they are compiled when generating the application cache to improve the overall
+performance. Define the list of annotated classes to compile in the
+``addAnnotatedClassesToCompile()`` method::
-.. versionadded:: 3.2
-
- The ``addAnnotatedClassesToCompile()`` method was introduced in Symfony 3.2.
-
-Your bundles can also add their own classes into this file thanks to the
-``addClassesToCompile()`` and ``addAnnotatedClassesToCompile()`` methods (both
-work in the same way, but the second one is for classes that contain PHP
-annotations). Define the classes to compile as an array of their fully qualified
-class names::
-
- use AppBundle\Manager\UserManager;
- use AppBundle\Utils\Slugger;
-
- // ...
public function load(array $configs, ContainerBuilder $container)
{
// ...
- // this method can't compile classes that contain PHP annotations
- $this->addClassesToCompile([
- UserManager::class,
- Slugger::class,
- // ...
- ]);
-
- // add here only classes that contain PHP annotations
$this->addAnnotatedClassesToCompile([
- 'AppBundle\\Controller\\DefaultController',
+ // you can define the fully qualified class names...
+ 'App\\Controller\\DefaultController',
+ // ... but glob patterns are also supported:
+ '**Bundle\\Controller\\',
+
// ...
]);
}
@@ -176,28 +138,6 @@ class names::
If some class extends from other classes, all its parents are automatically
included in the list of classes to compile.
-.. versionadded:: 3.2
-
- The option to add classes to compile using patterns was introduced in Symfony 3.2.
-
-The classes to compile can also be added using file path patterns::
-
- // ...
- public function load(array $configs, ContainerBuilder $container)
- {
- // ...
-
- $this->addClassesToCompile([
- '**Bundle\\Manager\\',
- // ...
- ]);
-
- $this->addAnnotatedClassesToCompile([
- '**Bundle\\Controller\\',
- // ...
- ]);
- }
-
Patterns are transformed into the actual class namespaces using the classmap
generated by Composer. Therefore, before using these patterns, you must generate
the full classmap executing the ``dump-autoload`` command of Composer.
diff --git a/bundles/index.rst b/bundles/index.rst
index 87641de5e23..e4af2cd357b 100644
--- a/bundles/index.rst
+++ b/bundles/index.rst
@@ -1,14 +1,13 @@
+:orphan:
+
Bundles
=======
.. toctree::
:maxdepth: 2
- installation
- best_practices
- inheritance
override
- remove
- extension
+ best_practices
configuration
+ extension
prepend_extension
diff --git a/bundles/inheritance.rst b/bundles/inheritance.rst
deleted file mode 100644
index 5720a944b88..00000000000
--- a/bundles/inheritance.rst
+++ /dev/null
@@ -1,113 +0,0 @@
-.. index::
- single: Bundle; Inheritance
-
-How to Use Bundle Inheritance to Override Parts of a Bundle
-===========================================================
-
-.. deprecated:: 3.4
-
- Bundle inheritance is deprecated since Symfony 3.4 and will be removed in
- 4.0, but you can :doc:`override any part of a bundle `
- without using bundle inheritance.
-
-When working with third-party bundles, you'll probably come across a situation
-where you want to override a file in that third-party bundle with a file
-in one of your own bundles. Symfony gives you a very convenient way to override
-things like controllers, templates, and other files in a bundle's
-``Resources/`` directory.
-
-For example, suppose that you have installed `FOSUserBundle`_, but you want to
-override its base ``layout.html.twig`` template, as well as one of its
-controllers.
-
-First, create a new bundle called UserBundle and enable it in your application.
-Then, register the third-party FOSUserBundle as the "parent" of your bundle::
-
- // src/UserBundle/UserBundle.php
- namespace UserBundle;
-
- use Symfony\Component\HttpKernel\Bundle\Bundle;
-
- class UserBundle extends Bundle
- {
- public function getParent()
- {
- return 'FOSUserBundle';
- }
- }
-
-By making this small change, you can now override several parts of the FOSUserBundle
-by creating a file with the same name.
-
-.. note::
-
- Despite the method name, there is no parent/child relationship between
- the bundles, it is just a way to extend and override an existing bundle.
-
-Overriding Controllers
-~~~~~~~~~~~~~~~~~~~~~~
-
-Suppose you want to add some functionality to the ``registerAction()`` of a
-``RegistrationController`` that lives inside FOSUserBundle. To do so,
-just create your own ``RegistrationController.php`` file, override the bundle's
-original method, and change its functionality::
-
- // src/UserBundle/Controller/RegistrationController.php
- namespace UserBundle\Controller;
-
- use FOS\UserBundle\Controller\RegistrationController as BaseController;
-
- class RegistrationController extends BaseController
- {
- public function registerAction()
- {
- $response = parent::registerAction();
-
- // ... do custom stuff
- return $response;
- }
- }
-
-.. tip::
-
- Depending on how severely you need to change the behavior, you might
- call ``parent::registerAction()`` or completely replace its logic with
- your own.
-
-.. note::
-
- Overriding controllers in this way only works if the bundle refers to
- the controller using the standard ``FOSUserBundle:Registration:register``
- syntax in routes and templates. This is the best practice.
-
-Overriding Resources: Templates, Routing, etc
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Most resources can also be overridden by creating a file in the same location
-as your parent bundle.
-
-For example, it's very common to need to override the FOSUserBundle's
-``layout.html.twig`` template so that it uses your application's base layout.
-Since the file lives at ``Resources/views/layout.html.twig`` in the FOSUserBundle,
-you can create your own file in the same location of UserBundle. Symfony will
-ignore the file that lives inside the FOSUserBundle entirely, and use your file
-instead.
-
-The same goes for routing files and some other resources.
-
-.. note::
-
- The overriding of resources only works when you refer to resources with
- the ``@FOSUserBundle/Resources/config/routing/security.xml`` method.
- You need to use the ``@BundleName`` shortcut when referring to resources
- so they can be successfully overridden (except templates, which are
- overridden in a different way, as explained in :doc:`/templating/overriding`).
-
-.. caution::
-
- Translation and validation files do not work in the same way as described
- above. Read ":ref:`override-translations`" if you want to learn how to
- override translations and see ":ref:`override-validation`" for tricks to
- override the validation.
-
-.. _`FOSUserBundle`: https://github.com/friendsofsymfony/fosuserbundle
diff --git a/bundles/installation.rst b/bundles/installation.rst
deleted file mode 100644
index c8f3e4ec1d5..00000000000
--- a/bundles/installation.rst
+++ /dev/null
@@ -1,160 +0,0 @@
-.. index::
- single: Bundle; Installation
-
-How to Install 3rd Party Bundles
-================================
-
-Most bundles provide their own installation instructions. However, the
-basic steps for installing a bundle are the same:
-
-* `A) Add Composer Dependencies`_
-* `B) Enable the Bundle`_
-* `C) Configure the Bundle`_
-
-A) Add Composer Dependencies
-----------------------------
-
-Dependencies are managed with Composer, so if Composer is new to you, learn
-some basics in `their documentation`_. This involves two steps:
-
-1) Find out the Name of the Bundle on Packagist
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The README for a bundle (e.g. `FOSUserBundle`_) usually tells you its name
-(e.g. ``friendsofsymfony/user-bundle``). If it doesn't, you can search for
-the bundle on the `Packagist.org`_ site.
-
-.. tip::
-
- Looking for bundles? Try searching for `symfony-bundle topic on GitHub`_.
-
-2) Install the Bundle via Composer
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Now that you know the package name, you can install it via Composer:
-
-.. code-block:: terminal
-
- $ composer require friendsofsymfony/user-bundle
-
-This will choose the best version for your project, add it to ``composer.json``
-and download its code into the ``vendor/`` directory. If you need a specific
-version, include it as the second argument of the `composer require`_ command:
-
-.. code-block:: terminal
-
- $ composer require friendsofsymfony/user-bundle "~2.0"
-
-B) Enable the Bundle
---------------------
-
-At this point, the bundle is installed in your Symfony project (e.g.
-``vendor/friendsofsymfony/``) and the autoloader recognizes its classes.
-The only thing you need to do now is register the bundle in ``AppKernel``::
-
- // app/AppKernel.php
-
- // ...
- class AppKernel extends Kernel
- {
- // ...
-
- public function registerBundles()
- {
- $bundles = [
- // ...
- new FOS\UserBundle\FOSUserBundle(),
- ];
-
- // ...
- }
- }
-
-In a few rare cases, you may want a bundle to be *only* enabled in the development
-:doc:`environment `. For example,
-the DoctrineFixturesBundle helps to load dummy data - something you probably
-only want to do while developing. To only load this bundle in the ``dev``
-and ``test`` environments, register the bundle in this way::
-
- // app/AppKernel.php
-
- // ...
- class AppKernel extends Kernel
- {
- // ...
-
- public function registerBundles()
- {
- $bundles = [
- // ...
- ];
-
- if (in_array($this->getEnvironment(), ['dev', 'test'])) {
- $bundles[] = new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle();
- }
-
- // ...
- }
- }
-
-C) Configure the Bundle
------------------------
-
-It's pretty common for a bundle to need some additional setup or configuration
-in ``app/config/config.yml``. The bundle's documentation will tell you about
-the configuration, but you can also get a reference of the bundle's configuration
-via the ``config:dump-reference`` command:
-
-.. code-block:: terminal
-
- $ php bin/console config:dump-reference AsseticBundle
-
-Instead of the full bundle name, you can also pass the short name used as the root
-of the bundle's configuration:
-
-.. code-block:: terminal
-
- $ php bin/console config:dump-reference assetic
-
-The output will look like this:
-
-.. code-block:: yaml
-
- assetic:
- debug: '%kernel.debug%'
- use_controller:
- enabled: '%kernel.debug%'
- profiler: false
- read_from: '%kernel.project_dir%/web'
- write_to: '%assetic.read_from%'
- java: /usr/bin/java
- node: /usr/local/bin/node
- node_paths: []
- # ...
-
-.. tip::
-
- For complex bundles that define lots of configuration options, you can pass
- a second optional argument to the ``config:dump-reference`` command to only
- display a section of the entire configuration:
-
- .. code-block:: terminal
-
- $ php bin/console config:dump-reference AsseticBundle use_controller
-
- # Default configuration for "AsseticBundle" at path "use_controller"
- use_controller:
- enabled: '%kernel.debug%'
- profiler: false
-
-Other Setup
------------
-
-At this point, check the ``README`` file of your brand new bundle to see
-what to do next. Have fun!
-
-.. _their documentation: https://getcomposer.org/doc/00-intro.md
-.. _Packagist.org: https://packagist.org
-.. _FOSUserBundle: https://github.com/FriendsOfSymfony/FOSUserBundle
-.. _`composer require`: https://getcomposer.org/doc/03-cli.md#require
-.. _`symfony-bundle topic on GitHub`: https://github.com/search?q=topic%3Asymfony-bundle&type=Repositories
diff --git a/bundles/override.rst b/bundles/override.rst
index e90db9b9315..09d113bd88a 100644
--- a/bundles/override.rst
+++ b/bundles/override.rst
@@ -4,33 +4,72 @@
How to Override any Part of a Bundle
====================================
-This document is a quick reference for how to override different parts of
-third-party bundles without using :doc:`/bundles/inheritance`, which is
-deprecated since Symfony 3.4.
+When using a third-party bundle, you might want to customize or override some of
+its features. This document describes ways of overriding the most common
+features of a bundle.
.. tip::
The bundle overriding mechanism means that you cannot use physical paths to
refer to bundle's resources (e.g. ``__DIR__/config/services.xml``). Always
- use logical paths in your bundles (e.g. ``@AppBundle/Resources/config/services.xml``)
+ use logical paths in your bundles (e.g. ``@FooBundle/Resources/config/services.xml``)
and call the :ref:`locateResource() method `
to turn them into physical paths when needed.
+.. _override-templates:
+
Templates
---------
-See :doc:`/templating/overriding`.
+Third-party bundle templates can be overridden in the
+``/templates/bundles//`` directory. The new templates
+must use the same name and path (relative to ``/Resources/views/``) as
+the original templates.
+
+For example, to override the ``Resources/views/Registration/confirmed.html.twig``
+template from the FOSUserBundle, create this template:
+``/templates/bundles/FOSUserBundle/Registration/confirmed.html.twig``
+
+.. caution::
+
+ If you add a template in a new location, you *may* need to clear your
+ cache (``php bin/console cache:clear``), even if you are in debug mode.
+
+Instead of overriding an entire template, you may just want to override one or
+more blocks. However, since you are overriding the template you want to extend
+from, you would end up in an infinite loop error. The solution is to use the
+special ``!`` prefix in the template name to tell Symfony that you want to
+extend from the original template, not from the overridden one:
+
+.. code-block:: twig
+
+ {# templates/bundles/FOSUserBundle/Registration/confirmed.html.twig #}
+ {# the special '!' prefix avoids errors when extending from an overridden template #}
+ {% extends "@!FOSUser/Registration/confirmed.html.twig" %}
+
+ {% block some_block %}
+ ...
+ {% endblock %}
+
+.. _templating-overriding-core-templates:
+
+.. tip::
+
+ Symfony internals use some bundles too, so you can apply the same technique
+ to override the core Symfony templates. For example, you can
+ :doc:`customize error pages ` overriding TwigBundle
+ templates.
Routing
-------
Routing is never automatically imported in Symfony. If you want to include
the routes from any bundle, then they must be manually imported from somewhere
-in your application (e.g. ``app/config/routing.yml``).
+in your application (e.g. ``config/routes.yaml``).
The easiest way to "override" a bundle's routing is to never import it at
-all. Instead of importing a third-party bundle's routing, copy that
-routing file into your application, modify it, and import it instead.
+all. Instead of importing a third-party bundle's routing, copy
+that routing file into your application, modify it, and import it instead.
Controllers
-----------
@@ -86,7 +125,7 @@ to a new validation group:
.. code-block:: yaml
- # src/Acme/UserBundle/Resources/config/validation.yml
+ # config/validator/validation.yaml
FOS\UserBundle\Model\User:
properties:
plainPassword:
@@ -99,7 +138,7 @@ to a new validation group:
.. code-block:: xml
-
+
`.
+Translations are not related to bundles, but to translation domains.
+For this reason, you can override any bundle translation file from the main
+``translations/`` directory, as long as the new file uses the same domain.
+
+For example, to override the translations defined in the
+``Resources/translations/FOSUserBundle.es.yml`` file of the FOSUserBundle,
+create a ``/translations/FOSUserBundle.es.yml`` file.
.. _`the Doctrine documentation`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html#overrides
diff --git a/bundles/prepend_extension.rst b/bundles/prepend_extension.rst
index 923796fc42e..f742df5ec69 100644
--- a/bundles/prepend_extension.rst
+++ b/bundles/prepend_extension.rst
@@ -12,11 +12,10 @@ users to choose to remove functionality they are not using. Creating multiple
bundles has the drawback that configuration becomes more tedious and settings
often need to be repeated for various bundles.
-It is possible to remove the disadvantage of the multiple bundle approach
-by enabling a single Extension to prepend the settings for any bundle.
-It can use the settings defined in the ``app/config/config.yml``
-to prepend settings just as if they had been written explicitly by
-the user in the application configuration.
+It is possible to remove the disadvantage of the multiple bundle approach by
+enabling a single Extension to prepend the settings for any bundle. It can use
+the settings defined in the ``config/*`` files to prepend settings just as if
+they had been written explicitly by the user in the application configuration.
For example, this could be used to configure the entity manager name to use in
multiple bundles. Or it can be used to enable an optional feature that depends
@@ -50,7 +49,7 @@ prepend settings to a bundle extension developers can use the
:method:`Symfony\\Component\\DependencyInjection\\ContainerBuilder::prependExtensionConfig`
method on the :class:`Symfony\\Component\\DependencyInjection\\ContainerBuilder`
instance. As this method only prepends settings, any other settings done explicitly
-inside the ``app/config/config.yml`` would override these prepended settings.
+inside the ``config/*`` files would override these prepended settings.
The following example illustrates how to prepend
a configuration setting in multiple bundles as well as disable a flag in multiple bundles
@@ -73,7 +72,7 @@ in case a specific other bundle is not registered::
// acme_something and acme_other
//
// note that if the user manually configured
- // use_acme_goodbye to true in app/config/config.yml
+ // use_acme_goodbye to true in config/services.yaml
// then the setting would in the end be true and not false
$container->prependExtensionConfig($name, $config);
break;
@@ -96,14 +95,15 @@ in case a specific other bundle is not registered::
}
The above would be the equivalent of writing the following into the
-``app/config/config.yml`` in case AcmeGoodbyeBundle is not registered and the
-``entity_manager_name`` setting for ``acme_hello`` is set to ``non_default``:
+``config/packages/acme_something.yaml`` in case AcmeGoodbyeBundle is not
+registered and the ``entity_manager_name`` setting for ``acme_hello`` is set to
+``non_default``:
.. configuration-block::
.. code-block:: yaml
- # app/config/config.yml
+ # config/packages/acme_something.yaml
acme_something:
# ...
use_acme_goodbye: false
@@ -115,7 +115,7 @@ The above would be the equivalent of writing the following into the
.. code-block:: xml
-
+
loadFromExtension('acme_something', [
// ...
'use_acme_goodbye' => false,
diff --git a/bundles/remove.rst b/bundles/remove.rst
deleted file mode 100644
index 0c27eacb68b..00000000000
--- a/bundles/remove.rst
+++ /dev/null
@@ -1,96 +0,0 @@
-.. index::
- single: Bundle; Removing a bundle
-
-How to Remove a Bundle
-======================
-
-1. Unregister the Bundle in the ``AppKernel``
----------------------------------------------
-
-To disconnect the bundle from the framework, you should remove the bundle from
-the ``AppKernel::registerBundles()`` method. The bundle will likely be found in
-the ``$bundles`` array declaration or added to it in a later statement if the
-bundle is only registered in the development environment::
-
- // app/AppKernel.php
-
- // ...
- class AppKernel extends Kernel
- {
- public function registerBundles()
- {
- $bundles = [
- new Acme\DemoBundle\AcmeDemoBundle(),
- ];
-
- if (in_array($this->getEnvironment(), ['dev', 'test'])) {
- // comment or remove this line:
- // $bundles[] = new Acme\DemoBundle\AcmeDemoBundle();
- // ...
- }
- }
- }
-
-2. Remove Bundle Configuration
-------------------------------
-
-Now that Symfony doesn't know about the bundle, you need to remove any
-configuration and routing configuration inside the ``app/config`` directory
-that refers to the bundle.
-
-2.1 Remove Bundle Routing
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-*Some* bundles require you to import routing configuration. Check for references
-to the bundle in ``app/config/routing.yml`` and ``app/config/routing_dev.yml``.
-If you find any references, remove them completely.
-
-2.2 Remove Bundle Configuration
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Some bundles contain configuration in one of the ``app/config/config*.yml``
-files. Be sure to remove the related configuration from these files. You can
-quickly spot bundle configuration by looking for an ``acme_demo`` (or whatever
-the name of the bundle is, e.g. ``fos_user`` for the FOSUserBundle) string in
-the configuration files.
-
-3. Remove the Bundle from the Filesystem
-----------------------------------------
-
-Now you have removed every reference to the bundle in your application, you
-should remove the bundle from the filesystem. The bundle will be located in
-`src/` for example the ``src/Acme/DemoBundle`` directory. You should remove this
-directory, and any parent directories that are now empty (e.g. ``src/Acme/``).
-
-.. tip::
-
- If you don't know the location of a bundle, you can use the
- :method:`Symfony\\Component\\HttpKernel\\Bundle\\BundleInterface::getPath` method
- to get the path of the bundle::
-
- dump($this->container->get('kernel')->getBundle('AcmeDemoBundle')->getPath());
- die();
-
-3.1 Remove Bundle Assets
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-Remove the assets of the bundle in the web/ directory (e.g.
-``web/bundles/acmedemo`` for the AcmeDemoBundle).
-
-4. Remove Integration in other Bundles
---------------------------------------
-
-Some bundles rely on other bundles, if you remove one of the two, the other
-will probably not work. Be sure that no other bundles, third party or self-made,
-rely on the bundle you are about to remove.
-
-.. tip::
-
- If one bundle relies on another, in most cases it means that it uses
- some services from the bundle. Searching for the bundle alias string may
- help you spot them (e.g. ``acme_demo`` for bundles depending on AcmeDemoBundle).
-
-.. tip::
-
- If a third party bundle relies on another bundle, you can find that bundle
- mentioned in the ``composer.json`` file included in the bundle directory.
diff --git a/cache.rst b/cache.rst
index 9912b879e93..6301cffcd39 100644
--- a/cache.rst
+++ b/cache.rst
@@ -10,19 +10,25 @@ developed for high performance.
The following example shows a typical usage of the cache::
- if (!$cache->has('my_cache_key')) {
+ use Symfony\Contracts\Cache\ItemInterface;
+
+ // The callable will only be executed on a cache miss.
+ $value = $pool->get('my_cache_key', function (ItemInterface $item) {
+ $item->expiresAfter(3600);
+
// ... do some HTTP request or heavy computations
- $cache->set('my_cache_key', 'foobar', 3600);
- }
+ $computedValue = 'foobar';
- echo $cache->get('my_cache_key'); // 'foobar'
+ return $computedValue;
+ });
- // ... and to remove the cache key
- $cache->delete('my_cache_key');
+ echo $value; // 'foobar'
+ // ... and to remove the cache key
+ $pool->delete('my_cache_key');
-Symfony supports PSR-6 and PSR-16 cache interfaces. You can read more about
-these at the :doc:`component documentation `.
+Symfony supports the Cache Contracts, PSR-6/16 and Doctrine Cache interfaces.
+You can read more about these at the :doc:`component documentation `.
.. _cache-configuration-with-frameworkbundle:
@@ -51,7 +57,7 @@ adapter (template) they use by using the ``app`` and ``system`` key like:
.. code-block:: yaml
- # app/config/config.yml
+ # config/packages/cache.yaml
framework:
cache:
app: cache.adapter.filesystem
@@ -59,7 +65,7 @@ adapter (template) they use by using the ``app`` and ``system`` key like:
.. code-block:: xml
-
+
loadFromExtension('framework', [
'cache' => [
'app' => 'cache.adapter.filesystem',
@@ -84,23 +90,16 @@ adapter (template) they use by using the ``app`` and ``system`` key like:
],
]);
-The Cache component comes with a series of adapters already created:
+The Cache component comes with a series of adapters pre-configured:
* :doc:`cache.adapter.apcu `
+* :doc:`cache.adapter.array `
* :doc:`cache.adapter.doctrine `
* :doc:`cache.adapter.filesystem `
* :doc:`cache.adapter.memcached `
* :doc:`cache.adapter.pdo `
+* :doc:`cache.adapter.psr6 `
* :doc:`cache.adapter.redis `
-* :doc:`PHPFileAdapter `
-* :doc:`PHPArrayAdapter `
-
-* :doc:`ChainAdapter `
-* :doc:`ProxyAdapter `
-* ``cache.adapter.psr6``
-
-* ``cache.adapter.system``
-* ``NullAdapter``
Some of these adapters could be configured via shortcuts. Using these shortcuts
will create pool with service id of ``cache.[type]``
@@ -109,7 +108,7 @@ will create pool with service id of ``cache.[type]``
.. code-block:: yaml
- # app/config/config.yml
+ # config/packages/cache.yaml
framework:
cache:
directory: '%kernel.cache_dir%/pools' # Only used with cache.adapter.filesystem
@@ -127,7 +126,7 @@ will create pool with service id of ``cache.[type]``
.. code-block:: xml
-
+
loadFromExtension('framework', [
'cache' => [
// Only used with cache.adapter.filesystem
@@ -174,31 +173,50 @@ will create pool with service id of ``cache.[type]``
],
]);
-Creating Custom Pools
----------------------
+Creating Custom (Namespaced) Pools
+----------------------------------
-You can also create more customized pools. All you need is an adapter:
+You can also create more customized pools:
.. configuration-block::
.. code-block:: yaml
- # app/config/config.yml
+ # config/packages/cache.yaml
framework:
cache:
default_memcached_provider: 'memcached://localhost'
+
pools:
+ # creates a "custom_thing.cache" service
+ # autowireable via "CacheInterface $customThingCache"
+ # uses the "app" cache configuration
+ custom_thing.cache:
+ adapter: cache.app
+
+ # creates a "my_cache_pool" service
+ # autowireable via "CacheInterface $myCachePool"
my_cache_pool:
- adapter: cache.adapter.filesystem
- cache.acme:
+ adapter: cache.adapter.array
+
+ # uses the default_memcached_provider from above
+ acme.cache:
adapter: cache.adapter.memcached
- cache.foobar:
+
+ # control adapter's configuration
+ foobar.cache:
adapter: cache.adapter.memcached
provider: 'memcached://user:password@example.com'
+ # uses the "foobar.cache" pool as its backend but controls
+ # the lifetime and (like all pools) has a separate cache namespace
+ short_cache:
+ adapter: foobar.cache
+ default_lifetime: 60
+
.. code-block:: xml
-
+
-
-
-
+
+
+
+
+
.. code-block:: php
- // app/config/config.php
+ // config/packages/cache.php
$container->loadFromExtension('framework', [
'cache' => [
'default_memcached_provider' => 'memcached://localhost',
'pools' => [
- 'my_cache_pool' => [
- 'adapter' => 'cache.adapter.filesystem',
+ 'custom_thing.cache' => [
+ 'adapter' => 'cache.app',
],
- 'cache.acme' => [
- 'adapter' => 'cache.adapter.memcached',
+ 'my_cache_pool' => [
+ 'adapter' => 'cache.adapter.array',
],
- 'cache.foobar' => [
+ 'acme.cache' => [
'adapter' => 'cache.adapter.memcached',
- 'provider' => 'memcached://user:password@example.com',
],
- ],
- ],
- ]);
-
-
-The configuration above will create 3 services: ``my_cache_pool``, ``cache.acme``
-and ``cache.foobar``. The ``my_cache_pool`` pool is using the ArrayAdapter
-and the other two are using the :doc:`MemcachedAdapter `.
-The ``cache.acme`` pool is using the Memcached server on localhost and ``cache.foobar``
-is using the Memcached server at example.com.
-
-For advanced configurations it could sometimes be useful to use a pool as an adapter.
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- framework:
- cache:
- app: my_configured_app_cache
- pools:
- my_cache_pool:
- adapter: cache.adapter.memcached
- provider: 'memcached://user:password@example.com'
- cache.short_cache:
- adapter: my_cache_pool
- default_lifetime: 60
- cache.long_cache:
- adapter: my_cache_pool
- default_lifetime: 604800
- my_configured_app_cache:
- # "cache.adapter.filesystem" is the default for "cache.app"
- adapter: cache.adapter.filesystem
- default_lifetime: 3600
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- $container->loadFromExtension('framework', [
- 'cache' => [
- 'app' => 'my_configured_app_cache',
- 'pools' => [
- 'my_cache_pool' => [
+ 'foobar.cache' => [
'adapter' => 'cache.adapter.memcached',
'provider' => 'memcached://user:password@example.com',
],
- 'cache.short_cache' => [
- 'adapter' => 'cache.adapter.memcached',
+ 'short_cache' => [
+ 'adapter' => 'foobar.cache',
'default_lifetime' => 60,
],
- 'cache.long_cache' => [
- 'adapter' => 'cache.adapter.memcached',
- 'default_lifetime' => 604800,
- ],
- 'my_configured_app_cache' => [
- // "cache.adapter.filesystem" is the default for "cache.app"
- 'adapter' => 'cache.adapter.filesystem',
- 'default_lifetime' => 3600,
- ],
],
],
]);
+Each pool manages a set of independent cache keys: keys of different pools
+*never* collide, even if they share the same backend. This is achieved by prefixing
+keys with a namespace that's generated by hashing the name of the pool, the name
+of the compiled container class and a :ref:`configurable seed`
+that defaults to the project directory.
+
+Each custom pool becomes a service where the service id is the name of the pool
+(e.g. ``custom_thing.cache``). An autowiring alias is also created for each pool
+using the camel case version of its name - e.g. ``custom_thing.cache`` can be
+injected automatically by naming the argument ``$customThingCache`` and type-hinting it
+with either :class:`Symfony\\Contracts\\Cache\\CacheInterface` or
+``Psr\Cache\CacheItemPoolInterface``::
+
+ use Symfony\Contracts\Cache\CacheInterface;
+
+ // from a controller method
+ public function listProducts(CacheInterface $customThingCache)
+ {
+ // ...
+ }
+
+ // in a service
+ public function __construct(CacheInterface $customThingCache)
+ {
+ // ...
+ }
+
Custom Provider Options
-----------------------
@@ -330,7 +303,7 @@ and use that when configuring the pool.
.. code-block:: yaml
- # app/config/config.yml
+ # config/packages/cache.yaml
framework:
cache:
pools:
@@ -348,7 +321,7 @@ and use that when configuring the pool.
.. code-block:: xml
-
+
loadFromExtension('framework', [
'cache' => [
'pools' => [
@@ -417,32 +390,114 @@ Symfony stores the item automatically in all the missing pools.
.. code-block:: yaml
- # app/config/config.yml
+ # config/packages/cache.yaml
framework:
cache:
pools:
my_cache_pool:
- adapter: cache.adapter.psr6
- provider: app.my_cache_chain_adapter
- cache.my_redis:
- adapter: cache.adapter.redis
- provider: 'redis://user:password@example.com'
- cache.apcu:
- adapter: cache.adapter.apcu
- cache.array:
- adapter: cache.adapter.filesystem
+ default_lifetime: 31536000 # One year
+ adapters:
+ - cache.adapter.array
+ - cache.adapter.apcu
+ - {name: cache.adapter.redis, provider: 'redis://user:password@example.com'}
+ .. code-block:: xml
- services:
- app.my_cache_chain_adapter:
- class: Symfony\Component\Cache\Adapter\ChainAdapter
- arguments:
- - ['@cache.array', '@cache.apcu', '@cache.my_redis']
- - 31536000 # One year
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: 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'],
+ ],
+ ],
+ ],
+ ],
+ ]);
+
+Using Cache Tags
+----------------
+
+In applications with many cache keys it could be useful to organize the data stored
+to be able to invalidate the cache more efficient. One way to achieve that is to
+use cache tags. One or more tags could be added to the cache item. All items with
+the same key could be invalidate with one function call::
+
+ use Symfony\Contracts\Cache\ItemInterface;
+ use Symfony\Contracts\Cache\TagAwareCacheInterface;
+
+ class SomeClass
+ {
+ private $myCachePool;
+
+ // using autowiring to inject the cache pool
+ public function __construct(TagAwareCacheInterface $myCachePool)
+ {
+ $this->myCachePool = $myCachePool;
+ }
+
+ public function someMethod()
+ {
+ $value0 = $this->myCachePool->get('item_0', function (ItemInterface $item) {
+ $item->tag(['foo', 'bar']);
+
+ return 'debug';
+ });
+
+ $value1 = $this->myCachePool->get('item_1', function (ItemInterface $item) {
+ $item->tag('foo');
+
+ return 'debug';
+ });
+
+ // Remove all cache keys tagged with "bar"
+ $this->myCachePool->invalidateTags(['bar']);
+ }
+ }
+
+The cache adapter needs to implement :class:`Symfony\\Contracts\\Cache\\TagAwareCacheInterface`
+to enable this feature. This could be added by using the following configuration.
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/cache.yaml
+ framework:
+ cache:
+ pools:
+ my_cache_pool:
+ adapter: cache.adapter.redis
+ tags: true
.. code-block:: xml
-
+
-
-
-
-
+
-
-
-
-
-
-
-
-
- 31536000
-
-
.. code-block:: php
- // app/config/config.php
+ // config/packages/cache.php
$container->loadFromExtension('framework', [
'cache' => [
'pools' => [
'my_cache_pool' => [
- 'adapter' => 'cache.adapter.psr6',
- 'provider' => 'app.my_cache_chain_adapter',
+ 'adapter' => 'cache.adapter.redis',
+ 'tags' => true,
],
- 'cache.my_redis' => [
+ ],
+ ],
+ ]);
+
+Tags are stored in the same pool by default. This is good in most scenarios. But
+sometimes it might be better to store the tags in a different pool. That could be
+achieved by specifying the adapter.
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/cache.yaml
+ framework:
+ cache:
+ pools:
+ my_cache_pool:
+ adapter: cache.adapter.redis
+ tags: tag_pool
+ tag_pool:
+ adapter: cache.adapter.apcu
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/cache.php
+ $container->loadFromExtension('framework', [
+ 'cache' => [
+ 'pools' => [
+ 'my_cache_pool' => [
'adapter' => 'cache.adapter.redis',
- 'provider' => 'redis://user:password@example.com',
+ 'tags' => 'tag_pool',
],
- 'cache.apcu' => [
+ 'tag_pool' => [
'adapter' => 'cache.adapter.apcu',
],
- 'cache.array' => [
- 'adapter' => 'cache.adapter.filesystem',
- ],
],
],
]);
- $container->getDefinition('app.my_cache_chain_adapter', \Symfony\Component\Cache\Adapter\ChainAdapter::class)
- ->addArgument([
- new Reference('cache.array'),
- new Reference('cache.apcu'),
- new Reference('cache.my_redis'),
- ])
- ->addArgument(31536000);
-
.. note::
- In this configuration the ``my_cache_pool`` pool is using the ``cache.adapter.psr6``
- adapter and the ``app.my_cache_chain_adapter`` service as a provider. That is
- because ``ChainAdapter`` does not support the ``cache.pool`` tag. So it is decorated
- with the ``ProxyAdapter``.
-
+ The interface :class:`Symfony\\Contracts\\Cache\\TagAwareCacheInterface` is
+ autowired to the ``cache.app`` service.
Clearing the Cache
------------------
@@ -527,6 +600,12 @@ The global clearer clears all the cache in every pool. The system cache clearer
is used in the ``bin/console cache:clear`` command. The app clearer is the default
clearer.
+To see all available cache pools:
+
+.. code-block:: terminal
+
+ $ php bin/console cache:pool:list
+
Clear one pool:
.. code-block:: terminal
diff --git a/components/asset.rst b/components/asset.rst
index a2e1bf69327..71b2aa13027 100644
--- a/components/asset.rst
+++ b/components/asset.rst
@@ -44,7 +44,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/asset:^3.4
+ $ composer require symfony/asset
.. include:: /components/require_autoload.rst.inc
@@ -281,8 +281,8 @@ You can also pass a schema-agnostic URL::
// result: //static.example.com/images/logo.png?v1
This is useful because assets will automatically be requested via HTTPS if
-a visitor is viewing your site in https. Just make sure that your CDN host
-supports https.
+a visitor is viewing your site in https. If you want to use this, make sure
+that your CDN host supports HTTPS.
In case you serve assets from more than one domain to improve application
performance, pass an array of URLs as the first argument to the ``UrlPackage``
@@ -371,6 +371,32 @@ document inside a template::
echo $packages->getUrl('resume.pdf', 'doc');
// result: /somewhere/deep/for/documents/resume.pdf?v1
+Local Files and Other Protocols
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In addition to HTTP this component supports other protocols (such as ``file://``
+and ``ftp://``). This allows for example to serve local files in order to
+improve performance::
+
+ use Symfony\Component\Asset\UrlPackage;
+ // ...
+
+ $localPackage = new UrlPackage(
+ 'file:///path/to/images/',
+ new EmptyVersionStrategy()
+ );
+
+ $ftpPackage = new UrlPackage(
+ 'ftp://example.com/images/',
+ new EmptyVersionStrategy()
+ );
+
+ echo $localPackage->getUrl('/logo.png');
+ // result: file:///path/to/images/logo.png
+
+ echo $ftpPackage->getUrl('/logo.png');
+ // result: ftp://example.com/images/logo.png
+
Learn more
----------
diff --git a/components/browser_kit.rst b/components/browser_kit.rst
index ba295db0eb4..bfeab500f46 100644
--- a/components/browser_kit.rst
+++ b/components/browser_kit.rst
@@ -10,16 +10,17 @@ The BrowserKit Component
.. note::
- The BrowserKit component can only make internal requests to your application.
- If you need to make requests to external sites and applications, consider
- using `Goutte`_, a simple web scraper based on Symfony Components.
+ In Symfony versions prior to 4.3, the BrowserKit component could only make
+ internal requests to your application. Starting from Symfony 4.3, this
+ component can also :ref:`make HTTP requests to any public site `
+ when using it in combination with the :doc:`HttpClient component `.
Installation
------------
.. code-block:: terminal
- $ composer require symfony/browser-kit:^3.4
+ $ composer require symfony/browser-kit
.. include:: /components/require_autoload.rst.inc
@@ -58,9 +59,10 @@ This method accepts a request and should return a response::
}
For a simple implementation of a browser based on the HTTP layer, have a look
-at `Goutte`_. For an implementation based on ``HttpKernelInterface``, have
-a look at the :class:`Symfony\\Component\\HttpKernel\\Client` provided by
-the :doc:`HttpKernel component `.
+at the :class:`Symfony\\Component\\BrowserKit\\HttpBrowser` provided by
+:ref:`this component `. For an implementation based
+on ``HttpKernelInterface``, have a look at the :class:`Symfony\\Component\\HttpKernel\\Client`
+provided by the :doc:`HttpKernel component `.
Making Requests
~~~~~~~~~~~~~~~
@@ -79,17 +81,35 @@ The value returned by the ``request()`` method is an instance of the
:doc:`DomCrawler component `, which allows accessing
and traversing HTML elements programmatically.
+The :method:`Symfony\\Component\\BrowserKit\\Client::xmlHttpRequest` method,
+which defines the same arguments as the ``request()`` method, is a shortcut to
+make AJAX requests::
+
+ use Acme\Client;
+
+ $client = new Client();
+ // the required HTTP_X_REQUESTED_WITH header is added automatically
+ $crawler = $client->xmlHttpRequest('GET', '/');
+
Clicking Links
~~~~~~~~~~~~~~
-The ``Crawler`` object is capable of simulating link clicks. First, pass the
-text content of the link to the ``selectLink()`` method, which returns a
-``Link`` object. Then, pass this object to the ``click()`` method, which
-performs the needed HTTP GET request to simulate the link click::
+The ``Client`` object is capable of simulating link clicks. Pass the text
+content of the link and the client will perform the needed HTTP GET request to
+simulate the link click::
use Acme\Client;
$client = new Client();
+ $client->request('GET', '/product/123');
+
+ $crawler = $client->clickLink('Go elsewhere...');
+
+If you need the :class:`Symfony\\Component\\DomCrawler\\Link` object that
+provides access to the link properties (e.g. ``$link->getMethod()``,
+``$link->getUri()``), use this other method::
+
+ // ...
$crawler = $client->request('GET', '/product/123');
$link = $crawler->selectLink('Go elsewhere...')->link();
$client->click($link);
@@ -97,27 +117,48 @@ performs the needed HTTP GET request to simulate the link click::
Submitting Forms
~~~~~~~~~~~~~~~~
-The ``Crawler`` object is also capable of selecting forms. First, select any of
-the form's buttons with the ``selectButton()`` method. Then, use the ``form()``
-method to select the form which the button belongs to.
-
-After selecting the form, fill in its data and send it using the ``submit()``
-method (which makes the needed HTTP POST request to submit the form contents)::
+The ``Client`` object is also capable of submitting forms. First, select the
+form using any of its buttons and then override any of its properties (method,
+field values, etc.) before submitting it::
use Acme\Client;
- // make a real request to an external site
$client = new Client();
$crawler = $client->request('GET', 'https://github.com/login');
+ // find the form with the 'Log in' button and submit it
+ // 'Log in' can be the text content, id, value or name of a or
+ $client->submitForm('Log in');
+
+ // the second optional argument lets you override the default form field values
+ $client->submitForm('Log in', [
+ 'login' => 'my_user',
+ 'password' => 'my_pass',
+ // to upload a file, the value must be the absolute file path
+ 'file' => __FILE__,
+ ]);
+
+ // you can override other form options too
+ $client->submitForm(
+ 'Log in',
+ ['login' => 'my_user', 'password' => 'my_pass'],
+ // override the default form HTTP method
+ 'PUT',
+ // override some $_SERVER parameters (e.g. HTTP headers)
+ ['HTTP_ACCEPT_LANGUAGE' => 'es']
+ );
+
+If you need the :class:`Symfony\\Component\\DomCrawler\\Form` object that
+provides access to the form properties (e.g. ``$form->getUri()``,
+``$form->getValues()``, ``$form->getFields()``), use this other method::
+
+ // ...
+
// select the form and fill in some values
$form = $crawler->selectButton('Log in')->form();
$form['login'] = 'symfonyfan';
$form['password'] = 'anypass';
- // To upload a file, the value should be the absolute file path
- $form['file'] = __FILE__;
-
// submit that form
$crawler = $client->submit($form);
@@ -240,6 +281,37 @@ also delete all the cookies::
// reset the client (history and cookies are cleared too)
$client->restart();
+.. _component-browserkit-external-requests:
+
+Making External HTTP Requests
+-----------------------------
+
+So far, all the examples in this article have assumed that you are making
+internal requests to your own application. However, you can run the exact same
+examples when making HTTP requests to external web sites and applications.
+
+First, install and configure the :doc:`HttpClient component `.
+Then, use the :class:`Symfony\\Component\\BrowserKit\\HttpBrowser` to create
+the client that will make the external HTTP requests::
+
+ use Symfony\Component\BrowserKit\HttpBrowser;
+ use Symfony\Component\HttpClient\HttpClient;
+
+ $browser = new HttpBrowser(HttpClient::create());
+
+You can now use any of the methods shown in this article to extract information,
+click links, submit forms, etc. This means that you no longer need to use a
+dedicated web crawler or scraper such as `Goutte`_::
+
+ $browser = new HttpBrowser(HttpClient::create());
+
+ $browser->request('GET', 'https://github.com');
+ $browser->clickLink('Sign in');
+ $browser->submitForm('Sign in', ['login' => '...', 'password' => '...']);
+ $openPullRequests = trim($browser->clickLink('Pull requests')->filter(
+ '.table-list-header-toggle a:nth-child(1)'
+ )->text());
+
Learn more
----------
diff --git a/components/cache.rst b/components/cache.rst
index 217937a8ec5..d323113e9bd 100644
--- a/components/cache.rst
+++ b/components/cache.rst
@@ -8,136 +8,147 @@
The Cache Component
===================
- The Cache component provides an extended `PSR-6`_ implementation as well as
- a `PSR-16`_ "Simple Cache" implementation for adding cache to your applications.
- It is designed for performance and resiliency, and ships with ready to use
- adapters for the most common caching backends, including proxies for adapting
- from/to `Doctrine Cache`_.
+ The Cache component provides features covering simple to advanced caching needs.
+ It natively implements `PSR-6`_ and the `Cache Contracts`_ for greatest
+ interoperability. It is designed for performance and resiliency, ships with
+ ready to use adapters for the most common caching backends. It enables tag-based
+ invalidation and cache stampede protection via locking and early expiration.
-.. versionadded:: 3.3
+.. tip::
- The PSR-16 "Simple Cache" implementation was introduced in Symfony 3.3.
+ The component also contains adapters to convert between PSR-6, PSR-16 and
+ Doctrine caches. See :doc:`/components/cache/psr6_psr16_adapters` and
+ :doc:`/components/cache/adapters/doctrine_adapter`.
Installation
------------
.. code-block:: terminal
- $ composer require symfony/cache:^3.4
+ $ composer require symfony/cache
.. include:: /components/require_autoload.rst.inc
-Cache (PSR-6) Versus Simple Cache (PSR-16)
-------------------------------------------
+Cache Contracts versus PSR-6
+----------------------------
This component includes *two* different approaches to caching:
:ref:`PSR-6 Caching `:
- A fully-featured cache system, which includes cache "pools", more advanced
- cache "items", and :ref:`cache tagging for invalidation `.
-
-:ref:`PSR-16 Simple Caching `:
- A simple way to store, fetch and remove items from a cache.
+ A generic cache system, which involves cache "pools" and cache "items".
-Both methods support the *same* cache adapters and will give you very similar performance.
+:ref:`Cache Contracts `:
+ A simpler yet more powerful way to cache values based on recomputation callbacks.
.. tip::
- The component also contains adapters to convert between PSR-6 and PSR-16 caches.
- See :doc:`/components/cache/psr6_psr16_adapters`.
+ Using the Cache Contracts approach is recommended: it requires less
+ code boilerplate and provides cache stampede protection by default.
-.. _cache-component-psr16-caching:
+.. _cache-component-contracts:
-Simple Caching (PSR-16)
------------------------
+Cache Contracts
+---------------
-This part of the component is an implementation of `PSR-16`_, which means that its
-basic API is the same as defined in the standard. First, create a cache object from
-one of the built-in cache classes. For example, to create a filesystem-based cache,
-instantiate :class:`Symfony\\Component\\Cache\\Simple\\FilesystemCache`::
+All adapters support the Cache Contracts. They contain only two methods:
+``get()`` and ``delete()``. There's no ``set()`` method because the ``get()``
+method both gets and sets the cache values.
- use Symfony\Component\Cache\Simple\FilesystemCache;
+The first thing you need is to instantiate a cache adapter. The
+:class:`Symfony\\Component\\Cache\\Adapter\\FilesystemAdapter` is used in this
+example::
+
+ use Symfony\Component\Cache\Adapter\FilesystemAdapter;
- $cache = new FilesystemCache();
+ $cache = new FilesystemAdapter();
-Now you can create, retrieve, update and delete items using this object::
+Now you can retrieve and delete cached data using this object. The first
+argument of the ``get()`` method is a key, an arbitrary string that you
+associate to the cached value so you can retrieve it later. The second argument
+is a PHP callable which is executed when the key is not found in the cache to
+generate and return the value::
- // save a new item in the cache
- $cache->set('stats.products_count', 4711);
+ use Symfony\Contracts\Cache\ItemInterface;
- // or set it with a custom ttl
- // $cache->set('stats.products_count', 4711, 3600);
+ // The callable will only be executed on a cache miss.
+ $value = $cache->get('my_cache_key', function (ItemInterface $item) {
+ $item->expiresAfter(3600);
- // retrieve the cache item
- if (!$cache->has('stats.products_count')) {
- // ... item does not exist in the cache
- }
+ // ... do some HTTP request or heavy computations
+ $computedValue = 'foobar';
- // retrieve the value stored by the item
- $productsCount = $cache->get('stats.products_count');
+ return $computedValue;
+ });
- // or specify a default value, if the key doesn't exist
- // $productsCount = $cache->get('stats.products_count', 100);
+ echo $value; // 'foobar'
- // remove the cache key
- $cache->delete('stats.products_count');
+ // ... and to remove the cache key
+ $cache->delete('my_cache_key');
- // clear *all* cache keys
- $cache->clear();
+.. note::
-You can also work with multiple items at once::
+ Use cache tags to delete more than one key at the time. Read more at
+ :doc:`/components/cache/cache_invalidation`.
- $cache->setMultiple([
- 'stats.products_count' => 4711,
- 'stats.users_count' => 1356,
- ]);
+The Cache Contracts also comes with built in `Stampede prevention`_. This will
+remove CPU spikes at the moments when the cache is cold. If an example application
+spends 5 seconds to compute data that is cached for 1 hour and this data is accessed
+10 times every second, this means that you mostly have cache hits and everything
+is fine. But after 1 hour, we get 10 new requests to a cold cache. So the data
+is computed again. The next second the same thing happens. So the data is computed
+about 50 times before the cache is warm again. This is where you need stampede
+prevention
- $stats = $cache->getMultiple([
- 'stats.products_count',
- 'stats.users_count',
- ]);
+The first solution is to use locking: only allow one PHP process (on a per-host basis)
+to compute a specific key at a time. Locking is built-in by default, so
+you don't need to do anything beyond leveraging the Cache Contracts.
- $cache->deleteMultiple([
- 'stats.products_count',
- 'stats.users_count',
- ]);
+The second solution is also built-in when using the Cache Contracts: instead of
+waiting for the full delay before expiring a value, recompute it ahead of its
+expiration date. The `Probabilistic early expiration`_ algorithm randomly fakes a
+cache miss for one user while others are still served the cached value. You can
+control its behavior with the third optional parameter of
+:method:`Symfony\\Contracts\\Cache\\CacheInterface::get`,
+which is a float value called "beta".
-Available Simple Cache (PSR-16) Classes
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+By default the beta is ``1.0`` and higher values mean earlier recompute. Set it
+to ``0`` to disable early recompute and set it to ``INF`` to force an immediate
+recompute::
+
+ use Symfony\Contracts\Cache\ItemInterface;
+
+ $beta = 1.0;
+ $value = $cache->get('my_cache_key', function (ItemInterface $item) {
+ $item->expiresAfter(3600);
+ $item->tag(['tag_0', 'tag_1']);
+
+ return '...';
+ }, $beta);
+
+Available Cache Adapters
+~~~~~~~~~~~~~~~~~~~~~~~~
The following cache adapters are available:
-.. tip::
+.. toctree::
+ :glob:
+ :maxdepth: 1
+
+ cache/adapters/*
- To find out more about each of these classes, you can read the
- :doc:`PSR-6 Cache Pool ` page. These "Simple"
- (PSR-16) cache classes aren't identical to the PSR-6 Adapters on that page, but
- each share constructor arguments and use-cases.
-
-* :class:`Symfony\\Component\\Cache\\Simple\\ApcuCache`
-* :class:`Symfony\\Component\\Cache\\Simple\\ArrayCache`
-* :class:`Symfony\\Component\\Cache\\Simple\\ChainCache`
-* :class:`Symfony\\Component\\Cache\\Simple\\DoctrineCache`
-* :class:`Symfony\\Component\\Cache\\Simple\\FilesystemCache`
-* :class:`Symfony\\Component\\Cache\\Simple\\MemcachedCache`
-* :class:`Symfony\\Component\\Cache\\Simple\\NullCache`
-* :class:`Symfony\\Component\\Cache\\Simple\\PdoCache`
-* :class:`Symfony\\Component\\Cache\\Simple\\PhpArrayCache`
-* :class:`Symfony\\Component\\Cache\\Simple\\PhpFilesCache`
-* :class:`Symfony\\Component\\Cache\\Simple\\RedisCache`
-* :class:`Symfony\\Component\\Cache\\Simple\\TraceableCache`
.. _cache-component-psr6-caching:
-More Advanced Caching (PSR-6)
------------------------------
+Generic Caching (PSR-6)
+-----------------------
-To use the more-advanced, PSR-6 Caching abilities, you'll need to learn its key
+To use the generic PSR-6 Caching abilities, you'll need to learn its key
concepts:
**Item**
A single unit of information stored as a key/value pair, where the key is
the unique identifier of the information and the value is its contents;
+ see the :doc:`/components/cache/cache_items` article for more details.
**Pool**
A logical repository of cache items. All cache operations (saving items,
looking for items, etc.) are performed through the pool. Applications can
@@ -151,7 +162,7 @@ Basic Usage (PSR-6)
-------------------
This part of the component is an implementation of `PSR-6`_, which means that its
-basic API is the same as defined in the standard. Before starting to cache information,
+basic API is the same as defined in the document. Before starting to cache information,
create the cache pool using any of the built-in adapters. For example, to create
a filesystem-based cache, instantiate :class:`Symfony\\Component\\Cache\\Adapter\\FilesystemAdapter`::
@@ -181,8 +192,8 @@ Now you can create, retrieve, update and delete items using this cache pool::
For a list of all of the supported adapters, see :doc:`/components/cache/cache_pools`.
-Advanced Usage (PSR-6)
-----------------------
+Advanced Usage
+--------------
.. toctree::
:glob:
@@ -191,5 +202,6 @@ Advanced Usage (PSR-6)
cache/*
.. _`PSR-6`: http://www.php-fig.org/psr/psr-6/
-.. _`PSR-16`: http://www.php-fig.org/psr/psr-16/
-.. _Doctrine Cache: https://www.doctrine-project.org/projects/cache.html
+.. _`Cache Contracts`: https://github.com/symfony/contracts/blob/master/Cache/CacheInterface.php
+.. _`Stampede prevention`: https://en.wikipedia.org/wiki/Cache_stampede
+.. _Probabilistic early expiration: https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration
diff --git a/components/cache/adapters/apcu_adapter.rst b/components/cache/adapters/apcu_adapter.rst
index 54507ccfc66..17ecd4058e6 100644
--- a/components/cache/adapters/apcu_adapter.rst
+++ b/components/cache/adapters/apcu_adapter.rst
@@ -1,6 +1,6 @@
.. index::
single: Cache Pool
- single: APC Cache, APCu Cache
+ single: APCu Cache
.. _apcu-adapter:
diff --git a/components/cache/adapters/array_cache_adapter.rst b/components/cache/adapters/array_cache_adapter.rst
new file mode 100644
index 00000000000..ffdc1d60dd0
--- /dev/null
+++ b/components/cache/adapters/array_cache_adapter.rst
@@ -0,0 +1,27 @@
+.. index::
+ single: Cache Pool
+ single: Array Cache
+
+Array Cache Adapter
+===================
+
+Generally, this adapter is useful for testing purposes, as its contents are stored in memory
+and not persisted outside the running PHP process in any way. It can also be useful while
+warming up caches, due to the :method:`Symfony\\Component\\Cache\\Adapter\\ArrayAdapter::getValues`
+method.
+
+This adapter can be passed a default cache lifetime as its first parameter, and a boolean that
+toggles serialization as its second parameter::
+
+ use Symfony\Component\Cache\Adapter\ArrayAdapter;
+
+ $cache = new ArrayAdapter(
+
+ // the default lifetime (in seconds) for cache items that do not define their
+ // own lifetime, with a value 0 causing items to be stored indefinitely (i.e.
+ // until the current PHP process finishes)
+ $defaultLifetime = 0,
+
+ // if ``true``, the values saved in the cache are serialized before storing them
+ $storeSerialized = true
+ );
diff --git a/components/cache/adapters/chain_adapter.rst b/components/cache/adapters/chain_adapter.rst
index 516c49bd802..de7555029d2 100644
--- a/components/cache/adapters/chain_adapter.rst
+++ b/components/cache/adapters/chain_adapter.rst
@@ -60,9 +60,3 @@ incompatible adapters are silently ignored::
// prune will proxy the call to FilesystemAdapter while silently skip ApcuAdapter
$cache->prune();
-
-.. versionadded:: 3.4
-
- Since Symfony 3.4, this adapter implements :class:`Symfony\\Component\\Cache\\PruneableInterface`,
- allowing for manual :ref:`pruning of expired cache entries ` by
- calling its ``prune()`` method.
diff --git a/components/cache/adapters/doctrine_adapter.rst b/components/cache/adapters/doctrine_adapter.rst
index f8f95126ae9..198ae19338c 100644
--- a/components/cache/adapters/doctrine_adapter.rst
+++ b/components/cache/adapters/doctrine_adapter.rst
@@ -34,4 +34,9 @@ third parameters::
$defaultLifetime = 0
);
+.. tip::
+
+ A :class:`Symfony\\Component\\Cache\\DoctrineProvider` class is also provided by the
+ component to use any PSR6-compatible implementations with Doctrine-compatible classes.
+
.. _`Doctrine Cache`: https://github.com/doctrine/cache
diff --git a/components/cache/adapters/memcached_adapter.rst b/components/cache/adapters/memcached_adapter.rst
index d51acc19aa0..6ebbe93d36d 100644
--- a/components/cache/adapters/memcached_adapter.rst
+++ b/components/cache/adapters/memcached_adapter.rst
@@ -7,10 +7,6 @@
Memcached Cache Adapter
=======================
-.. versionadded:: 3.3
-
- The Memcached adapter was introduced in Symfony 3.3.
-
This adapter stores the values in-memory using one (or more) `Memcached server`_
instances. Unlike the :ref:`APCu adapter `, and similarly to the
:ref:`Redis adapter `, it is not limited to the current server's
@@ -68,10 +64,11 @@ helper method allows creating and configuring a `Memcached`_ class instance usin
// etc...
]);
-.. versionadded:: 3.4
-
- The feature to pass configuration options in the memcached DSN was
- introduced in Symfony 3.4.
+ // a single DSN can define multiple servers using the following syntax:
+ // host[hostname-or-IP:port] (where port is optional). Sockets must include a trailing ':'
+ $client = MemcachedAdapter::createConnection(
+ 'memcached:?host[localhost]&host[localhost:12345]&host[/some/memcached.sock:]=3'
+ );
The `Data Source Name (DSN)`_ for this adapter must use the following format:
diff --git a/components/cache/adapters/pdo_doctrine_dbal_adapter.rst b/components/cache/adapters/pdo_doctrine_dbal_adapter.rst
index 20d9d5a836a..cc8c43976c5 100644
--- a/components/cache/adapters/pdo_doctrine_dbal_adapter.rst
+++ b/components/cache/adapters/pdo_doctrine_dbal_adapter.rst
@@ -7,10 +7,6 @@
PDO & Doctrine DBAL Cache Adapter
=================================
-.. versionadded:: 3.2
-
- The PDO & Doctrine DBAL adapter was introduced in Symfony 3.2.
-
This adapter stores the cache items in an SQL database. It requires a `PDO`_,
`Doctrine DBAL Connection`_, or `Data Source Name (DSN)`_ as its first parameter, and
optionally a namespace, default cache lifetime, and options array as its second,
@@ -35,6 +31,12 @@ third, and forth parameters::
$options = []
);
+The table where values are stored is created automatically on the first call to
+the :method:`Symfony\\Component\\Cache\\Adapter\\PdoAdapter::save` method.
+You can also create this table explicitly by calling the
+:method:`Symfony\\Component\\Cache\\Adapter\\PdoAdapter::createTable` method in
+your code.
+
.. tip::
When passed a `Data Source Name (DSN)`_ string (instead of a database connection
diff --git a/components/cache/adapters/php_array_cache_adapter.rst b/components/cache/adapters/php_array_cache_adapter.rst
index c27e4fd2ecf..631c153f5cb 100644
--- a/components/cache/adapters/php_array_cache_adapter.rst
+++ b/components/cache/adapters/php_array_cache_adapter.rst
@@ -6,7 +6,8 @@ PHP Array Cache Adapter
=======================
This adapter is a high performance cache for static data (e.g. application configuration)
-that is optimized and preloaded into OPcache memory storage::
+that is optimized and preloaded into OPcache memory storage. It is suited for any data that
+is mostly read-only after warmup::
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
@@ -34,5 +35,4 @@ that is optimized and preloaded into OPcache memory storage::
.. note::
- This adapter requires PHP 7.x and should be used with the ``php.ini``
- setting ``opcache.enable=On``.
+ This adapter requires turning on the ``opcache.enable`` php.ini setting.
diff --git a/components/cache/adapters/php_files_adapter.rst b/components/cache/adapters/php_files_adapter.rst
index 8e693be63ec..9faa9bf1ed1 100644
--- a/components/cache/adapters/php_files_adapter.rst
+++ b/components/cache/adapters/php_files_adapter.rst
@@ -29,15 +29,16 @@ file similar to the following::
.. note::
+ This adapter requires turning on the ``opcache.enable`` php.ini setting.
As cache items are included and parsed as native PHP code and due to the way `OPcache`_
handles file includes, this adapter has the potential to be much faster than other
filesystem-based caches.
.. caution::
- If you have configured OPcache to
- :ref:`not check the file timestamps `
- the cached items will not be invalidated unless you clear OPcache.
+ While it supports updates and because it is using OPcache as a backend, this adapter is
+ better suited for append-mostly needs. Using it in other scenarios might lead to
+ periodical reset of the OPcache memory, potentially leading to degraded performance.
The PhpFilesAdapter can optionally be provided a namespace, default cache lifetime, and cache
directory path as constructor arguments::
diff --git a/components/cache/adapters/proxy_adapter.rst b/components/cache/adapters/proxy_adapter.rst
index a0959781bca..85d41d2c6d1 100644
--- a/components/cache/adapters/proxy_adapter.rst
+++ b/components/cache/adapters/proxy_adapter.rst
@@ -9,6 +9,9 @@ This adapter wraps a `PSR-6`_ compliant `cache item pool interface`_. It is used
your application's cache item pool implementation with the Symfony :ref:`Cache Component `
by consuming any implementation of ``Psr\Cache\CacheItemPoolInterface``.
+It can also be used to prefix all keys automatically before storing items in the decorated pool,
+effectively allowing the creation of several namespaced pools out of a single one.
+
This adapter expects a ``Psr\Cache\CacheItemPoolInterface`` instance as its first parameter,
and optionally a namespace and default cache lifetime as its second and third parameters::
diff --git a/components/cache/adapters/redis_adapter.rst b/components/cache/adapters/redis_adapter.rst
index 6850f079b2c..1cbccc4d749 100644
--- a/components/cache/adapters/redis_adapter.rst
+++ b/components/cache/adapters/redis_adapter.rst
@@ -14,7 +14,8 @@ Redis Cache Adapter
:ref:`Symfony Cache configuration `
article if you are using it in a Symfony application.
-This adapter stores the values in-memory using one (or more) `Redis server`_ instances.
+This adapter stores the values in-memory using one (or more) `Redis server`_ instances.
+
Unlike the :ref:`APCu adapter `, and similarly to the
:ref:`Memcached adapter `, it is not limited to the current server's
shared memory; you can store contents independent of your PHP environment. The ability
@@ -60,8 +61,8 @@ helper method allows creating and configuring the Redis client class instance us
'redis://localhost'
);
-The DSN can specify either an IP/host (and an optional port) or a socket path, as well as a user
-and password and a database index.
+The DSN can specify either an IP/host (and an optional port) or a socket path, as well as a
+password and a database index.
.. note::
@@ -69,7 +70,7 @@ and password and a database index.
.. code-block:: text
- redis://[user:pass@][ip|host|socket[:port]][/db-index]
+ redis://[pass@][ip|host|socket[:port]][/db-index]
Below are common examples of valid DSNs showing a combination of available values::
@@ -81,11 +82,30 @@ Below are common examples of valid DSNs showing a combination of available value
// host "my.server.com" and port "6379" and database index "20"
RedisAdapter::createConnection('redis://my.server.com:6379/20');
- // host "localhost" and SASL use "rmf" and pass "abcdef"
- RedisAdapter::createConnection('redis://rmf:abcdef@localhost');
+ // host "localhost", auth "abcdef" and timeout 5 seconds
+ RedisAdapter::createConnection('redis://abcdef@localhost?timeout=5');
+
+ // socket "/var/run/redis.sock" and auth "bad-pass"
+ RedisAdapter::createConnection('redis://bad-pass@/var/run/redis.sock');
+
+ // a single DSN can define multiple servers using the following syntax:
+ // host[hostname-or-IP:port] (where port is optional). Sockets must include a trailing ':'
+ RedisAdapter::createConnection(
+ 'redis:?host[localhost]&host[localhost:6379]&host[/var/run/redis.sock:]&auth=my-password&redis_cluster=1'
+ );
+
+`Redis Sentinel`_, which provides high availability for Redis, is also supported
+when using the Predis library. Use the ``redis_sentinel`` parameter to set the
+name of your service group::
- // socket "/var/run/redis.sock" and SASL user "user1" and pass "bad-pass"
- RedisAdapter::createConnection('redis://user1:bad-pass@/var/run/redis.sock');
+ RedisAdapter::createConnection(
+ 'redis:?host[redis1:26379]&host[redis2:26379]&host[redis3:26379]&redis_sentinel=mymaster'
+ );
+
+.. note::
+
+ See the :class:`Symfony\\Component\\Cache\\Traits\\RedisTrait` for more options
+ you can pass as DSN parameters.
Configure the Options
---------------------
@@ -103,9 +123,11 @@ array of ``key => value`` pairs representing option names and their respective v
// associative array of configuration options
[
+ 'compression' => true,
'lazy' => false,
'persistent' => 0,
'persistent_id' => null,
+ 'tcp_keepalive' => 0,
'timeout' => 30,
'read_timeout' => 0,
'retry_interval' => 0,
@@ -121,6 +143,10 @@ Available Options
If none is specified, it will return ``\Redis`` if the ``redis`` extension is
available, and ``\Predis\Client`` otherwise.
+``compression`` (type: ``bool``, default: ``true``)
+ Enables or disables compression of items. This requires phpredis v4 or higher with
+ LZF support enabled.
+
``lazy`` (type: ``bool``, default: ``false``)
Enables or disables lazy connections to the backend. It's ``false`` by
default when using this as a stand-alone component and ``true`` by default
@@ -141,6 +167,10 @@ Available Options
Specifies the delay (in milliseconds) between reconnection attempts in case the client
loses connection with the server.
+``tcp_keepalive`` (type: ``int``, default: ``0``)
+ Specifies the `TCP-keepalive`_ timeout (in seconds) of the connection. This
+ requires phpredis v4 or higher and a TCP-keepalive enabled server.
+
``timeout`` (type: ``int``, default: ``30``)
Specifies the time (in seconds) used to connect to a Redis server before the
connection attempt times out.
@@ -157,3 +187,5 @@ Available Options
.. _`RedisCluster`: https://github.com/phpredis/phpredis/blob/master/cluster.markdown#readme
.. _`Predis`: https://packagist.org/packages/predis/predis
.. _`Predis Connection Parameters`: https://github.com/nrk/predis/wiki/Connection-Parameters#list-of-connection-parameters
+.. _`TCP-keepalive`: https://redis.io/topics/clients#tcp-keepalive
+.. _`Redis Sentinel`: https://redis.io/topics/sentinel
diff --git a/components/cache/cache_invalidation.rst b/components/cache/cache_invalidation.rst
index 9deefa6851f..e56153b9ce1 100644
--- a/components/cache/cache_invalidation.rst
+++ b/components/cache/cache_invalidation.rst
@@ -13,44 +13,39 @@ several cached items, keeping them in sync can be difficult.
The Symfony Cache component provides two mechanisms to help solve this problem:
* :ref:`Tags-based invalidation ` for managing data dependencies;
-* :ref:`Expiration based invalidation ` for time related dependencies.
+* :ref:`Expiration based invalidation ` for time-related dependencies.
.. _cache-component-tags:
Using Cache Tags
----------------
-.. versionadded:: 3.2
-
- Tags-based invalidation was introduced in Symfony 3.2.
-
To benefit from tags-based invalidation, you need to attach the proper tags to
each cached item. Each tag is a plain string identifier that you can use at any
time to trigger the removal of all items associated with this tag.
To attach tags to cached items, you need to use the
-:method:`Symfony\\Component\\Cache\\CacheItem::tag` method that is implemented by
-cache items, as returned by cache adapters::
+:method:`Symfony\\Contracts\\Cache\\ItemInterface::tag` method that is implemented by
+cache items::
- $item = $cache->getItem('cache_key');
- // ...
- // add one or more tags
- $item->tag('tag_1');
- $item->tag(['tag_2', 'tag_3']);
- $cache->save($item);
+ $item = $cache->get('cache_key', function (ItemInterface $item) {
+ // [...]
+ // add one or more tags
+ $item->tag('tag_1');
+ $item->tag(['tag_2', 'tag_3']);
-If ``$cache`` implements :class:`Symfony\\Component\\Cache\\Adapter\\TagAwareAdapterInterface`,
+ return $cachedValue;
+ });
+
+If ``$cache`` implements :class:`Symfony\\Contracts\\Cache\\TagAwareCacheInterface`,
you can invalidate the cached items by calling
-:method:`Symfony\\Component\\Cache\\Adapter\\TagAwareAdapterInterface::invalidateTags`::
+:method:`Symfony\\Contracts\\Cache\\TagAwareCacheInterface::invalidateTags`::
// invalidate all items related to `tag_1` or `tag_3`
$cache->invalidateTags(['tag_1', 'tag_3']);
// if you know the cache key, you can also delete the item directly
- $cache->deleteItem('cache_key');
-
- // If you don't remember the item key, you can use the getKey() method
- $cache->deleteItem($item->getKey());
+ $cache->delete('cache_key');
Using tags invalidation is very useful when tracking cache keys becomes difficult.
@@ -59,7 +54,7 @@ Tag Aware Adapters
To store tags, you need to wrap a cache adapter with the
:class:`Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter` class or implement
-:class:`Symfony\\Component\\Cache\\Adapter\\TagAwareAdapterInterface` and its only
+:class:`Symfony\\Contracts\\Cache\\TagAwareCacheInterface` and its
:method:`Symfony\\Component\\Cache\\Adapter\\TagAwareAdapterInterface::invalidateTags`
method.
diff --git a/components/cache/cache_items.rst b/components/cache/cache_items.rst
index 16eec288afe..027bb59f4a9 100644
--- a/components/cache/cache_items.rst
+++ b/components/cache/cache_items.rst
@@ -9,6 +9,7 @@ Cache Items
Cache items are the information units stored in the cache as a key/value pair.
In the Cache component they are represented by the
:class:`Symfony\\Component\\Cache\\CacheItem` class.
+They are used in both the Cache Contracts and the PSR-6 interfaces.
Cache Item Keys and Values
--------------------------
@@ -27,14 +28,22 @@ arrays and objects.
Creating Cache Items
--------------------
-Cache items are created with the ``getItem($key)`` method of the cache pool. The
-argument is the key of the item::
+The only way to create cache items is via cache pools. When using the Cache
+Contracts, they are passed as arguments to the recomputation callback::
+
+ // $cache pool object was created before
+ $productsCount = $cache->get('stats.products_count', function (ItemInterface $item) {
+ // [...]
+ });
+
+When using PSR-6, they are created with the ``getItem($key)`` method of the cache
+pool::
// $cache pool object was created before
$productsCount = $cache->getItem('stats.products_count');
-Then, use the :method:`Psr\\Cache\\CacheItemInterface::set` method to set
-the data stored in the cache item::
+Then, use the ``Psr\Cache\CacheItemInterface::set`` method to set the data stored
+in the cache item (this step is done automatically when using the Cache Contracts)::
// storing a simple integer
$productsCount->set(4711);
@@ -67,7 +76,6 @@ lifespan. Consider for example an application which caches the latest news just
for one minute. In those cases, use the ``expiresAfter()`` method to set the
number of seconds to cache the item::
- $latestNews = $cache->getItem('latest_news');
$latestNews->expiresAfter(60); // 60 seconds = 1 minute
// this method also accepts \DateInterval instances
@@ -76,23 +84,22 @@ number of seconds to cache the item::
Cache items define another related method called ``expiresAt()`` to set the
exact date and time when the item will expire::
- $mostPopularNews = $cache->getItem('popular_news');
$mostPopularNews->expiresAt(new \DateTime('tomorrow'));
Cache Item Hits and Misses
--------------------------
Using a cache mechanism is important to improve the application performance, but
-it should not be required to make the application work. In fact, the PSR-6
-standard states that caching errors should not result in application failures.
+it should not be required to make the application work. In fact, the PSR-6 document
+wisely states that caching errors should not result in application failures.
-In practice this means that the ``getItem()`` method always returns an object
-which implements the ``Psr\Cache\CacheItemInterface`` interface, even when the
-cache item doesn't exist. Therefore, you don't have to deal with ``null`` return
+In practice with PSR-6, this means that the ``getItem()`` method always returns an
+object which implements the ``Psr\Cache\CacheItemInterface`` interface, even when
+the cache item doesn't exist. Therefore, you don't have to deal with ``null`` return
values and you can safely store in the cache values such as ``false`` and ``null``.
-In order to decide if the returned object is correct or not, caches use the
-concept of hits and misses:
+In order to decide if the returned object represents a value coming from the storage
+or not, caches use the concept of hits and misses:
* **Cache Hits** occur when the requested item is found in the cache, its value
is not corrupted or invalid and it hasn't expired;
diff --git a/components/cache/cache_pools.rst b/components/cache/cache_pools.rst
index dd200eaf674..375b514fe80 100644
--- a/components/cache/cache_pools.rst
+++ b/components/cache/cache_pools.rst
@@ -1,6 +1,6 @@
.. index::
single: Cache Pool
- single: APC Cache, APCu Cache
+ single: APCu Cache
single: Array Cache
single: Chain Cache
single: Doctrine Cache
@@ -26,8 +26,9 @@ Creating Cache Pools
--------------------
Cache Pools are created through the **cache adapters**, which are classes that
-implement :class:`Symfony\\Component\\Cache\\Adapter\\AdapterInterface`. This
-component provides several adapters ready to use in your applications.
+implement both :class:`Symfony\\Contracts\\Cache\\CacheInterface` and
+``Psr\Cache\CacheItemPoolInterface``. This component provides several adapters
+ready to use in your applications.
.. toctree::
:glob:
@@ -35,8 +36,53 @@ component provides several adapters ready to use in your applications.
adapters/*
+
+Using the Cache Contracts
+-------------------------
+
+The :class:`Symfony\\Contracts\\Cache\\CacheInterface` allows fetching, storing
+and deleting cache items using only two methods and a callback::
+
+ use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+ use Symfony\Contracts\Cache\ItemInterface;
+
+ $cache = new FilesystemAdapter();
+
+ // The callable will only be executed on a cache miss.
+ $value = $cache->get('my_cache_key', function (ItemInterface $item) {
+ $item->expiresAfter(3600);
+
+ // ... do some HTTP request or heavy computations
+ $computedValue = 'foobar';
+
+ return $computedValue;
+ });
+
+ echo $value; // 'foobar'
+
+ // ... and to remove the cache key
+ $cache->delete('my_cache_key');
+
+Out of the box, using this interface provides stampede protection via locking
+and early expiration. Early expiration can be controlled via the third "beta"
+argument of the :method:`Symfony\\Contracts\\Cache\\CacheInterface::get` method.
+See the :doc:`/components/cache` article for more information.
+
+Early expiration can be detected inside the callback by calling the
+:method:`Symfony\\Contracts\\Cache\\ItemInterface::isHit` method: if this
+returns ``true``, it means we are currently recomputing a value ahead of its
+expiration date.
+
+For advanced use cases, the callback can accept a second ``bool &$save``
+argument passed by reference. By setting ``$save`` to ``false`` inside the
+callback, you can instruct the cache pool that the returned value *should not*
+be stored in the backend.
+
+Using PSR-6
+-----------
+
Looking for Cache Items
------------------------
+~~~~~~~~~~~~~~~~~~~~~~~
Cache Pools define three methods to look for cache items. The most common method
is ``getItem($key)``, which returns the cache item identified by the given key::
@@ -66,7 +112,7 @@ returns ``true`` if there is a cache item identified by the given key::
$hasBadges = $cache->hasItem('user_'.$userId.'_badges');
Saving Cache Items
-------------------
+~~~~~~~~~~~~~~~~~~
The most common method to save cache items is
``Psr\Cache\CacheItemPoolInterface::save``, which stores the
@@ -100,7 +146,7 @@ method returns ``true`` when all the pending items are successfully saved or
``false`` otherwise.
Removing Cache Items
---------------------
+~~~~~~~~~~~~~~~~~~~~
Cache Pools include methods to delete a cache item, some of them or all of them.
The most common is ``Psr\Cache\CacheItemPoolInterface::deleteItem``,
@@ -127,9 +173,20 @@ when all items are successfully deleted)::
.. tip::
If the cache component is used inside a Symfony application, you can remove
- *all items* from the *given pool(s)* using the following command (which resides within
+ items from cache pools using the following commands (which reside within
the :ref:`framework bundle `):
+ To remove *one specific item* from the *given pool*:
+
+ .. code-block:: terminal
+
+ $ php bin/console cache:pool:delete
+
+ # deletes the "cache_key" item from the "cache.app" pool
+ $ php bin/console cache:pool:delete cache.app cache_key
+
+ You can also remove *all items* from the *given pool(s)*:
+
.. code-block:: terminal
$ php bin/console cache:pool:clear
@@ -140,21 +197,11 @@ when all items are successfully deleted)::
# clears the "cache.validation" and "cache.app" pool
$ php bin/console cache:pool:clear cache.validation cache.app
- .. versionadded:: 3.4
-
- Starting from Symfony 3.4, the ``cache:clear`` command no longer clears
- the cache pools, so you must use the ``cache:pool:clear`` command to
- delete them.
-
.. _component-cache-cache-pool-prune:
Pruning Cache Items
-------------------
-.. versionadded:: 3.4
-
- Cache adapter pruning functionality was introduced in Symfony 3.4.
-
Some cache pools do not include an automated mechanism for pruning expired cache items.
For example, the :ref:`FilesystemAdapter ` cache
does not remove expired cache items *until an item is explicitly requested and determined to
diff --git a/components/cache/psr6_psr16_adapters.rst b/components/cache/psr6_psr16_adapters.rst
index 55014b9faba..275de9f1a57 100644
--- a/components/cache/psr6_psr16_adapters.rst
+++ b/components/cache/psr6_psr16_adapters.rst
@@ -6,7 +6,7 @@
Adapters For Interoperability between PSR-6 and PSR-16 Cache
============================================================
-Sometimes, you may have a Cache object that implements the :ref:`PSR-16 `
+Sometimes, you may have a Cache object that implements the `PSR-16`_
standard, but need to pass it to an object that expects a :ref:`PSR-6 `
cache adapter. Or, you might have the opposite situation. The cache component contains
two classes for bidirectional interoperability between PSR-6 and PSR-16 caches.
@@ -33,17 +33,15 @@ example::
But, you already have a PSR-16 cache object, and you'd like to pass this to the class
instead. No problem! The Cache component provides the
-:class:`Symfony\\Component\\Cache\\Adapter\\SimpleCacheAdapter` class for exactly
+:class:`Symfony\\Component\\Cache\\Adapter\\Psr16Adapter` class for exactly
this use-case::
- use Symfony\Component\Cache\Adapter\SimpleCacheAdapter;
- use Symfony\Component\Cache\Simple\FilesystemCache;
+ use Symfony\Component\Cache\Adapter\Psr16Adapter;
- // the PSR-16 cache object that you want to use
- $psr16Cache = new FilesystemCache();
+ // $psr16Cache is the PSR-16 object that you want to use as a PSR-6 one
// a PSR-6 cache that uses your cache internally!
- $psr6Cache = new SimpleCacheAdapter($psr16Cache);
+ $psr6Cache = new Psr16Adapter($psr16Cache);
// now use this wherever you want
$githubApiClient = new GitHubApiClient($psr6Cache);
@@ -70,17 +68,19 @@ example::
But, you already have a PSR-6 cache pool object, and you'd like to pass this to
the class instead. No problem! The Cache component provides the
-:class:`Symfony\\Component\\Cache\\Simple\\Psr6Cache` class for exactly
+:class:`Symfony\\Component\\Cache\\Psr16Cache` class for exactly
this use-case::
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
- use Symfony\Component\Cache\Simple\Psr6Cache;
+ use Symfony\Component\Cache\Psr16Cache;
// the PSR-6 cache object that you want to use
$psr6Cache = new FilesystemAdapter();
// a PSR-16 cache that uses your cache internally!
- $psr16Cache = new Psr6Cache($psr6Cache);
+ $psr16Cache = new Psr16Cache($psr6Cache);
// now use this wherever you want
$githubApiClient = new GitHubApiClient($psr16Cache);
+
+.. _`PSR-16`: http://www.php-fig.org/psr/psr-16/
diff --git a/components/class_loader.rst b/components/class_loader.rst
index ca27db3663b..f3268b1ca71 100644
--- a/components/class_loader.rst
+++ b/components/class_loader.rst
@@ -4,72 +4,9 @@
The ClassLoader Component
=========================
- The ClassLoader component provides tools to autoload your classes and
- cache their locations for performance.
-
.. caution::
- The ClassLoader component was deprecated in Symfony 3.3 and will be
- removed in 4.0. As an alternative, use any of the `class loading optimizations`_
- provided by Composer.
-
-Usage
------
-
-Whenever you reference a class that has not been required or included yet,
-PHP uses the `autoloading mechanism`_ to delegate the loading of a file
-defining the class. Symfony provides three autoloaders, which are able to
-load your classes:
-
-* :doc:`/components/class_loader/class_loader`: loads classes that follow
- the `PSR-0`_ class naming standard;
-
-* :doc:`/components/class_loader/psr4_class_loader`: loads classes that follow
- the `PSR-4`_ class naming standard;
-
-* :doc:`/components/class_loader/map_class_loader`: loads classes using
- a static map from class name to file path.
-
-Additionally, the Symfony ClassLoader component ships with a wrapper class
-which makes it possible
-:doc:`to cache the results of a class loader `.
-
-When using the `Debug component`_, you can also use a special
-:class:`Symfony\\Component\\Debug\\DebugClassLoader` that eases debugging by
-throwing more helpful exceptions when a class could not be found by a class
-loader.
-
-Installation
-------------
-
-.. code-block:: terminal
-
- $ composer require symfony/class-loader:^3.4
-
-Alternatively, you can clone the ``_ repository.
-
-.. include:: /components/require_autoload.rst.inc
-
-Learn More
-----------
-
-.. toctree::
- :glob:
- :maxdepth: 1
-
- class_loader/class_loader
- class_loader/class_map_generator
- class_loader/debug_class_loader
- class_loader/map_class_loader
- class_loader/psr4_class_loader
-
-.. toctree::
- :hidden:
-
- class_loader/cache_class_loader
+ The ClassLoader component was removed in Symfony 4.0. As an alternative, use
+ any of the `class loading optimizations`_ provided by Composer.
-.. _Debug component: https://github.com/symfony/debug
-.. _PSR-0: https://www.php-fig.org/psr/psr-0/
-.. _PSR-4: https://www.php-fig.org/psr/psr-4/
-.. _`autoloading mechanism`: https://php.net/manual/en/language.oop5.autoload.php
.. _`class loading optimizations`: https://getcomposer.org/doc/articles/autoloader-optimization.md
diff --git a/components/class_loader/cache_class_loader.rst b/components/class_loader/cache_class_loader.rst
deleted file mode 100644
index a75f743826c..00000000000
--- a/components/class_loader/cache_class_loader.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-.. index::
- single: APC; ApcClassLoader
- single: ClassLoader; ApcClassLoader
- single: ClassLoader; Cache
- single: ClassLoader; XcacheClassLoader
- single: XCache; XcacheClassLoader
-
-Cache a Class Loader
-====================
-
-The ``ApcClassLoader``, the ``WinCacheClassLoader`` and the ``XcacheClassLoader``
-are deprecated since Symfony 3.3. As an alternative, use any of the
-`class loading optimizations`_ provided by Composer.
-
-.. _`class loading optimizations`: https://getcomposer.org/doc/articles/autoloader-optimization.md
diff --git a/components/class_loader/class_loader.rst b/components/class_loader/class_loader.rst
deleted file mode 100644
index 3f5f6f5ac42..00000000000
--- a/components/class_loader/class_loader.rst
+++ /dev/null
@@ -1,75 +0,0 @@
-.. index::
- single: ClassLoader; PSR-0 Class Loader
-
-The PSR-0 Class Loader
-======================
-
-If your classes and third-party libraries follow the `PSR-0`_ standard,
-you can use the :class:`Symfony\\Component\\ClassLoader\\ClassLoader` class
-to load all of your project's classes.
-
-.. tip::
-
- You can use both the ``ApcClassLoader`` and the ``XcacheClassLoader``
- to :doc:`cache ` a ``ClassLoader``
- instance.
-
-Usage
------
-
-Registering the :class:`Symfony\\Component\\ClassLoader\\ClassLoader` autoloader
-is straightforward::
-
- require_once '/path/to/src/Symfony/Component/ClassLoader/ClassLoader.php';
-
- use Symfony\Component\ClassLoader\ClassLoader;
-
- $loader = new ClassLoader();
-
- // to enable searching the include path (e.g. for PEAR packages)
- $loader->setUseIncludePath(true);
-
- // ... register namespaces and prefixes here - see below
-
- $loader->register();
-
-Use :method:`Symfony\\Component\\ClassLoader\\ClassLoader::addPrefix` or
-:method:`Symfony\\Component\\ClassLoader\\ClassLoader::addPrefixes` to register
-your classes::
-
- // register a single namespaces
- $loader->addPrefix('Symfony', __DIR__.'/vendor/symfony/symfony/src');
-
- // registers several namespaces at once
- $loader->addPrefixes([
- 'Symfony' => __DIR__.'/../vendor/symfony/symfony/src',
- 'Monolog' => __DIR__.'/../vendor/monolog/monolog/src',
- ]);
-
- // registers a prefix for a class following the PEAR naming conventions
- $loader->addPrefix('Twig_', __DIR__.'/vendor/twig/twig/lib');
-
- $loader->addPrefixes([
- 'Swift_' => __DIR__.'/vendor/swiftmailer/swiftmailer/lib/classes',
- 'Twig_' => __DIR__.'/vendor/twig/twig/lib',
- ]);
-
-Classes from a sub-namespace or a sub-hierarchy of `PEAR`_ classes can be
-looked for in a location list to ease the splitting a sub-set of classes into
-another package for large projects::
-
- $loader->addPrefixes([
- 'Doctrine\Common' => __DIR__.'/vendor/doctrine/common/lib',
- 'Doctrine\DBAL\Migrations' => __DIR__.'/vendor/doctrine/migrations/lib',
- 'Doctrine\DBAL' => __DIR__.'/vendor/doctrine/dbal/lib',
- 'Doctrine' => __DIR__.'/vendor/doctrine/orm/lib',
- ]);
-
-In this example, if you try to use a class in the ``Doctrine\Common`` namespace
-or one of its children, the autoloader will first look for the class under
-the ``doctrine-common`` directory. If not found, it will then fallback to
-the default ``Doctrine`` directory (the last one configured) before giving
-up. The order of the prefix registrations is significant in this case.
-
-.. _PEAR: https://pear.php.net/manual/en/standards.naming.php
-.. _PSR-0: https://www.php-fig.org/psr/psr-0/
diff --git a/components/class_loader/class_map_generator.rst b/components/class_loader/class_map_generator.rst
deleted file mode 100644
index f7ceab4ebcd..00000000000
--- a/components/class_loader/class_map_generator.rst
+++ /dev/null
@@ -1,128 +0,0 @@
-.. index::
- single: Autoloading; Class Map Generator
- single: ClassLoader; Class Map Generator
-
-The Class Map Generator
-=======================
-
-Loading a class usually is an easy task given the `PSR-0`_ and `PSR-4`_
-standards. Thanks to the Symfony ClassLoader component or the autoloading
-mechanism provided by Composer, you don't have to map your class names to
-actual PHP files manually. Nowadays, PHP libraries usually come with autoloading
-support through Composer.
-
-But from time to time you may have to use a third-party library that comes
-without any autoloading support and therefore forces you to load each class
-manually. For example, imagine a library with the following directory structure:
-
-.. code-block:: text
-
- library/
- ├── bar/
- │ ├── baz/
- │ │ └── Boo.php
- │ └── Foo.php
- └── foo/
- ├── bar/
- │ └── Foo.php
- └── Bar.php
-
-These files contain the following classes:
-
-=========================== ================
-File Class Name
-=========================== ================
-``library/bar/baz/Boo.php`` ``Acme\Bar\Baz``
-``library/bar/Foo.php`` ``Acme\Bar``
-``library/foo/bar/Foo.php`` ``Acme\Foo\Bar``
-``library/foo/Bar.php`` ``Acme\Foo``
-=========================== ================
-
-To make your life easier, the ClassLoader component comes with a
-:class:`Symfony\\Component\\ClassLoader\\ClassMapGenerator` class that makes
-it possible to create a map of class names to files.
-
-Generating a Class Map
-----------------------
-
-To generate the class map, simply pass the root directory of your class
-files to the
-:method:`Symfony\\Component\\ClassLoader\\ClassMapGenerator::createMap`
-method::
-
- use Symfony\Component\ClassLoader\ClassMapGenerator;
-
- var_dump(ClassMapGenerator::createMap(__DIR__.'/library'));
-
-Given the files and class from the table above, you should see an output
-like this:
-
-.. code-block:: text
-
- Array
- (
- [Acme\Foo] => /var/www/library/foo/Bar.php
- [Acme\Foo\Bar] => /var/www/library/foo/bar/Foo.php
- [Acme\Bar\Baz] => /var/www/library/bar/baz/Boo.php
- [Acme\Bar] => /var/www/library/bar/Foo.php
- )
-
-Dumping the Class Map
----------------------
-
-Writing the class map to the console output is not really sufficient when
-it comes to autoloading. Luckily, the ``ClassMapGenerator`` provides the
-:method:`Symfony\\Component\\ClassLoader\\ClassMapGenerator::dump` method
-to save the generated class map to the filesystem::
-
- use Symfony\Component\ClassLoader\ClassMapGenerator;
-
- ClassMapGenerator::dump(__DIR__.'/library', __DIR__.'/class_map.php');
-
-This call to ``dump()`` generates the class map and writes it to the ``class_map.php``
-file in the same directory with the following contents::
-
- '/var/www/library/foo/Bar.php',
- 'Acme\\Foo\\Bar' => '/var/www/library/foo/bar/Foo.php',
- 'Acme\\Bar\\Baz' => '/var/www/library/bar/baz/Boo.php',
- 'Acme\\Bar' => '/var/www/library/bar/Foo.php',
- );
-
-Instead of loading each file manually, you'll only have to register the
-generated class map with, for example, the
-:class:`Symfony\\Component\\ClassLoader\\MapClassLoader`::
-
- use Symfony\Component\ClassLoader\MapClassLoader;
-
- $mapping = include __DIR__.'/class_map.php';
- $loader = new MapClassLoader($mapping);
- $loader->register();
-
- // you can now use the classes:
- use Acme\Foo;
-
- $foo = new Foo();
-
- // ...
-
-.. note::
-
- The example assumes that you already have autoloading working (e.g.
- through `Composer`_ or one of the other class loaders from the ClassLoader
- component).
-
-Besides dumping the class map for one directory, you can also pass an array
-of directories for which to generate the class map (the result actually
-is the same as in the example above)::
-
- use Symfony\Component\ClassLoader\ClassMapGenerator;
-
- ClassMapGenerator::dump(
- [__DIR__.'/library/bar', __DIR__.'/library/foo'],
- __DIR__.'/class_map.php'
- );
-
-.. _`PSR-0`: https://www.php-fig.org/psr/psr-0
-.. _`PSR-4`: https://www.php-fig.org/psr/psr-4
-.. _`Composer`: https://getcomposer.org
diff --git a/components/class_loader/debug_class_loader.rst b/components/class_loader/debug_class_loader.rst
deleted file mode 100644
index f60a726b3a4..00000000000
--- a/components/class_loader/debug_class_loader.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-Debugging a Class Loader
-========================
-
-.. caution::
-
- The ``DebugClassLoader`` from the ClassLoader component was deprecated in
- Symfony 2.5 and removed in Symfony 3.0. Use the
- :class:`Symfony\\Component\\Debug\\DebugClassLoader` provided by the Debug
- component.
diff --git a/components/class_loader/map_class_loader.rst b/components/class_loader/map_class_loader.rst
deleted file mode 100644
index 5f2e8cfab7a..00000000000
--- a/components/class_loader/map_class_loader.rst
+++ /dev/null
@@ -1,41 +0,0 @@
-.. index::
- single: ClassLoader; MapClassLoader
-
-MapClassLoader
-==============
-
-The :class:`Symfony\\Component\\ClassLoader\\MapClassLoader` allows you
-to autoload files via a static map from classes to files. This is useful
-if you use third-party libraries which don't follow the `PSR-0`_ standards
-and so can't use the
-:doc:`PSR-0 class loader `.
-
-The ``MapClassLoader`` can be used along with the
-:doc:`PSR-0 class loader ` by
-configuring and calling the ``register()`` method on both.
-
-.. note::
-
- The default behavior is to append the ``MapClassLoader`` on the autoload
- stack. If you want to use it as the first autoloader, pass ``true``
- when calling the ``register()`` method. Your class loader will then
- be prepended on the autoload stack.
-
-Usage
------
-
-Using it is as easy as passing your mapping to its constructor when creating
-an instance of the ``MapClassLoader`` class::
-
- require_once '/path/to/src/Symfony/Component/ClassLoader/MapClassLoader.php';
-
- $mapping = [
- 'Foo' => '/path/to/Foo',
- 'Bar' => '/path/to/Bar',
- ];
-
- $loader = new MapClassLoader($mapping);
-
- $loader->register();
-
-.. _PSR-0: https://www.php-fig.org/psr/psr-0/
diff --git a/components/class_loader/psr4_class_loader.rst b/components/class_loader/psr4_class_loader.rst
deleted file mode 100644
index 0093d8ba0c8..00000000000
--- a/components/class_loader/psr4_class_loader.rst
+++ /dev/null
@@ -1,61 +0,0 @@
-.. index::
- single: ClassLoader; PSR-4 Class Loader
-
-The PSR-4 Class Loader
-======================
-
-Libraries that follow the `PSR-4`_ standard can be loaded with the ``Psr4ClassLoader``.
-
-.. note::
-
- If you manage your dependencies via Composer, you get a PSR-4 compatible
- autoloader out of the box. Use this loader in environments where Composer
- is not available.
-
-.. tip::
-
- All Symfony components follow PSR-4.
-
-Usage
------
-
-The following example demonstrates how you can use the
-:class:`Symfony\\Component\\ClassLoader\\Psr4ClassLoader` autoloader to use
-Symfony's Yaml component. Imagine, you downloaded both the ClassLoader and
-Yaml component as ZIP packages and unpacked them to a ``lib/`` directory.
-The directory structure will look like this:
-
-.. code-block:: text
-
- lib/
- ClassLoader/
- Psr4ClassLoader.php
- ...
- Yaml/
- Yaml.php
- ...
- config.yml
- demo.php
-
-In ``demo.php`` you are going to parse the ``config.yml`` file. To do that, you
-first need to configure the ``Psr4ClassLoader``::
-
- use Symfony\Component\ClassLoader\Psr4ClassLoader;
- use Symfony\Component\Yaml\Yaml;
-
- require __DIR__.'/lib/ClassLoader/Psr4ClassLoader.php';
-
- $loader = new Psr4ClassLoader();
- $loader->addPrefix('Symfony\Component\Yaml\\', __DIR__.'/lib/Yaml');
- $loader->register();
-
- $data = Yaml::parse(file_get_contents(__DIR__.'/config.yml'));
-
-First of all, the class loader is loaded manually using a ``require``
-statement, since there is no autoload mechanism yet. With the
-:method:`Symfony\\Component\\ClassLoader\\Psr4ClassLoader::addPrefix` call, you
-tell the class loader where to look for classes with the
-``Symfony\Component\Yaml\`` namespace prefix. After registering the autoloader,
-the Yaml component is ready to be used.
-
-.. _PSR-4: https://www.php-fig.org/psr/psr-4/
diff --git a/components/config.rst b/components/config.rst
index ce58f64fc7e..7de46a6c6b7 100644
--- a/components/config.rst
+++ b/components/config.rst
@@ -14,7 +14,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/config:^3.4
+ $ composer require symfony/config
.. include:: /components/require_autoload.rst.inc
diff --git a/components/config/caching.rst b/components/config/caching.rst
index 9ef987a3165..833492dd45e 100644
--- a/components/config/caching.rst
+++ b/components/config/caching.rst
@@ -32,7 +32,7 @@ should be regenerated::
$userMatcherCache = new ConfigCache($cachePath, true);
if (!$userMatcherCache->isFresh()) {
- // fill this with an array of 'users.yml' file paths
+ // fill this with an array of 'users.yaml' file paths
$yamlUserFiles = ...;
$resources = [];
diff --git a/components/config/definition.rst b/components/config/definition.rst
index c6353644b88..ba668cd8613 100644
--- a/components/config/definition.rst
+++ b/components/config/definition.rst
@@ -59,10 +59,10 @@ implements the :class:`Symfony\\Component\\Config\\Definition\\ConfigurationInte
{
public function getConfigTreeBuilder()
{
- $treeBuilder = new TreeBuilder();
- $rootNode = $treeBuilder->root('database');
+ $treeBuilder = new TreeBuilder('database');
// ... add node definitions to the root of the tree
+ // $treeBuilder->getRootNode()->...
return $treeBuilder;
}
@@ -187,22 +187,11 @@ Or you may define a prototype for each node inside an array node::
->end()
;
-.. versionadded:: 3.3
-
- The ``arrayPrototype()`` method (and the related ``booleanPrototype()``
- ``integerPrototype()``, ``floatPrototype()``, ``scalarPrototype()`` and
- ``enumPrototype()``) was introduced in Symfony 3.3. In previous versions,
- you needed to use ``prototype('array')``, ``prototype('boolean')``, etc.
-
A prototype can be used to add a definition which may be repeated many times
inside the current node. According to the prototype definition in the example
above, it is possible to have multiple connection arrays (containing a ``driver``,
``host``, etc.).
-.. versionadded:: 3.3
-
- The ``castToArray()`` helper was introduced in Symfony 3.3.
-
Sometimes, to improve the user experience of your application or bundle, you may
allow to use a simple string or numeric value where an array value is required.
Use the ``castToArray()`` helper to turn those variables into arrays::
@@ -545,10 +534,9 @@ tree with ``append()``::
public function getConfigTreeBuilder()
{
- $treeBuilder = new TreeBuilder();
- $rootNode = $treeBuilder->root('database');
+ $treeBuilder = new TreeBuilder('database');
- $rootNode
+ $treeBuilder->getRootNode()
->children()
->arrayNode('connection')
->children()
@@ -575,10 +563,9 @@ tree with ``append()``::
public function addParametersNode()
{
- $treeBuilder = new TreeBuilder();
- $node = $treeBuilder->root('parameters');
+ $treeBuilder = new TreeBuilder('parameters');
- $node
+ $node = $treeBuilder->getRootNode()
->isRequired()
->requiresAtLeastOneElement()
->useAttributeAsKey('name')
@@ -798,6 +785,44 @@ A validation rule also requires a "then" part:
Usually, "then" is a closure. Its return value will be used as a new value
for the node, instead of the node's original value.
+Configuring the Node Path Separator
+-----------------------------------
+
+Consider the following config builder example::
+
+ $treeBuilder = new TreeBuilder('database');
+
+ $treeBuilder->getRootNode()
+ ->children()
+ ->arrayNode('connection')
+ ->children()
+ ->scalarNode('driver')->end()
+ ->end()
+ ->end()
+ ->end()
+ ;
+
+By default, the hierarchy of nodes in a config path is defined with a dot
+character (``.``)::
+
+ // ...
+
+ $node = $treeBuilder->buildTree();
+ $children = $node->getChildren();
+ $path = $children['driver']->getPath();
+ // $path = 'database.connection.driver'
+
+Use the ``setPathSeparator()`` method on the config builder to change the path
+separator::
+
+ // ...
+
+ $treeBuilder->setPathSeparator('/');
+ $node = $treeBuilder->buildTree();
+ $children = $node->getChildren();
+ $path = $children['driver']->getPath();
+ // $path = 'database/connection/driver'
+
Processing Configuration Values
-------------------------------
@@ -814,10 +839,10 @@ Otherwise the result is a clean array of configuration values::
use Symfony\Component\Yaml\Yaml;
$config = Yaml::parse(
- file_get_contents(__DIR__.'/src/Matthias/config/config.yml')
+ file_get_contents(__DIR__.'/src/Matthias/config/config.yaml')
);
$extraConfig = Yaml::parse(
- file_get_contents(__DIR__.'/src/Matthias/config/config_extra.yml')
+ file_get_contents(__DIR__.'/src/Matthias/config/config_extra.yaml')
);
$configs = [$config, $extraConfig];
diff --git a/components/config/resources.rst b/components/config/resources.rst
index bee2bd7b438..3ee380a9828 100644
--- a/components/config/resources.rst
+++ b/components/config/resources.rst
@@ -21,10 +21,10 @@ files. This can be done with the :class:`Symfony\\Component\\Config\\FileLocator
use Symfony\Component\Config\FileLocator;
- $configDirectories = [__DIR__.'/app/config'];
+ $configDirectories = [__DIR__.'/config'];
$fileLocator = new FileLocator($configDirectories);
- $yamlUserFiles = $fileLocator->locate('users.yml', null, false);
+ $yamlUserFiles = $fileLocator->locate('users.yaml', null, false);
The locator receives a collection of locations where it should look for
files. The first argument of ``locate()`` is the name of the file to look
@@ -57,12 +57,12 @@ which allows for recursively importing other resources::
// maybe import some other resource:
- // $this->import('extra_users.yml');
+ // $this->import('extra_users.yaml');
}
public function supports($resource, $type = null)
{
- return is_string($resource) && 'yml' === pathinfo(
+ return is_string($resource) && 'yaml' === pathinfo(
$resource,
PATHINFO_EXTENSION
);
@@ -93,5 +93,5 @@ the resource::
$delegatingLoader = new DelegatingLoader($loaderResolver);
// YamlUserLoader is used to load this resource because it supports
- // files with the '.yml' extension
- $delegatingLoader->load(__DIR__.'/users.yml');
+ // files with the '.yaml' extension
+ $delegatingLoader->load(__DIR__.'/users.yaml');
diff --git a/components/console.rst b/components/console.rst
index fb377baa1a0..6a2abe2366e 100644
--- a/components/console.rst
+++ b/components/console.rst
@@ -17,7 +17,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/console:^3.4
+ $ composer require symfony/console
.. include:: /components/require_autoload.rst.inc
diff --git a/components/console/changing_default_command.rst b/components/console/changing_default_command.rst
index b5baf00c022..6eb9f2b5227 100644
--- a/components/console/changing_default_command.rst
+++ b/components/console/changing_default_command.rst
@@ -5,7 +5,7 @@ Changing the Default Command
============================
The Console component will always run the ``ListCommand`` when no command name is
-passed. In order to change the default command you just need to pass the command
+passed. In order to change the default command you need to pass the command
name to the ``setDefaultCommand()`` method::
namespace Acme\Console\Command;
diff --git a/components/console/events.rst b/components/console/events.rst
index fa83e10189c..93f6d1a3791 100644
--- a/components/console/events.rst
+++ b/components/console/events.rst
@@ -83,21 +83,6 @@ C/C++ standard::
}
});
-The ``ConsoleEvents::EXCEPTION`` Event
---------------------------------------
-
-.. deprecated:: 3.3
-
- The ``ConsoleEvents::EXCEPTION`` event was deprecated in Symfony 3.3. Use
- the ``ConsoleEvents::ERROR`` event instead.
-
-**Typical Purposes**: Handle exceptions thrown during the execution of a
-command.
-
-Whenever an exception is thrown by a command, the ``ConsoleEvents::EXCEPTION``
-event is dispatched. A listener can wrap or change the exception or do
-anything useful before the exception is thrown by the application.
-
The ``ConsoleEvents::ERROR`` Event
----------------------------------
diff --git a/components/console/helpers/dialoghelper.rst b/components/console/helpers/dialoghelper.rst
deleted file mode 100644
index 8618ffcba17..00000000000
--- a/components/console/helpers/dialoghelper.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-.. index::
- single: Console Helpers; Dialog Helper
-
-Dialog Helper
-=============
-
-.. caution::
-
- The Dialog Helper was deprecated in Symfony 2.5 and removed in
- Symfony 3.0. You should now use the
- :doc:`Question Helper ` instead,
- which is simpler to use.
diff --git a/components/console/helpers/formatterhelper.rst b/components/console/helpers/formatterhelper.rst
index 25be9423202..ba3c2743d24 100644
--- a/components/console/helpers/formatterhelper.rst
+++ b/components/console/helpers/formatterhelper.rst
@@ -101,10 +101,10 @@ Custom Suffix
~~~~~~~~~~~~~
By default, the ``...`` suffix is used. If you wish to use a different suffix,
-simply pass it as the third argument to the method.
+pass it as the third argument to the method.
The suffix is always appended, unless truncate length is longer than a message
and a suffix length.
-If you don't want to use suffix at all, just pass an empty string::
+If you don't want to use suffix at all, pass an empty string::
$truncatedMessage = $formatter->truncate($message, 7, '!!'); // result: This is!!
$truncatedMessage = $formatter->truncate($message, 7, ''); // result: This is
diff --git a/components/console/helpers/index.rst b/components/console/helpers/index.rst
index 38a8c2a7939..87c62ca7629 100644
--- a/components/console/helpers/index.rst
+++ b/components/console/helpers/index.rst
@@ -7,7 +7,6 @@ The Console Helpers
.. toctree::
:hidden:
- dialoghelper
formatterhelper
processhelper
progressbar
diff --git a/components/console/helpers/progressbar.rst b/components/console/helpers/progressbar.rst
index 4a892603717..e592191f230 100644
--- a/components/console/helpers/progressbar.rst
+++ b/components/console/helpers/progressbar.rst
@@ -50,14 +50,23 @@ you can also set the current progress by calling the
If your platform doesn't support ANSI codes, updates to the progress
bar are added as new lines. To prevent the output from being flooded,
- adjust the
- :method:`Symfony\\Component\\Console\\Helper\\ProgressBar::setRedrawFrequency`
- accordingly. By default, when using a ``max``, the redraw frequency
- is set to *10%* of your ``max``.
+ use the :method:`Symfony\\Component\\Console\\Helper\\ProgressBar::minSecondsBetweenRedraws`
+ method to limit the number of redraws and the
+ :method:`Symfony\\Component\\Console\\Helper\\ProgressBar::setRedrawFrequency` method
+ to redraw every N iterations. By default, redraw frequency is
+ **100ms** or **10%** of your ``max``.
-If you don't know the number of steps in advance, just omit the steps argument
-when creating the :class:`Symfony\\Component\\Console\\Helper\\ProgressBar`
-instance::
+If you don't know the exact number of steps in advance, set it to a reasonable
+value and then call the ``setMaxSteps()`` method to update it as needed::
+
+ // start with a 50 units progressbar
+ $progressBar = new ProgressBar($output, 50);
+
+ // a complex task has just been created: increase the progressbar to 200 units
+ $progressBar->setMaxSteps(200);
+
+Another solution is to omit the steps argument when creating the
+:class:`Symfony\\Component\\Console\\Helper\\ProgressBar` instance::
$progressBar = new ProgressBar($output);
@@ -87,6 +96,29 @@ that the progress bar display is refreshed with a 100% completion.
:method:`Symfony\\Component\\Console\\Helper\\ProgressBar::display`
to show the progress bar again.
+If the progress information is stored in an iterable variable (such as an array
+or a PHP generator) you can use the
+:method:`Symfony\\Component\\Console\\Helper\\ProgressBar::iterate` method,
+which starts, advances and finishes the progress bar automatically::
+
+ use Symfony\Component\Console\Helper\ProgressBar;
+
+ $progressBar = new ProgressBar($output);
+
+ // $iterable can be for example an array ([1, 2, 3, ...]) or a generator
+ // $iterable = function () { yield 1; yield 2; ... };
+ foreach ($progressBar->iterate($iterable) as $value) {
+ // ... do some work
+ }
+
+If ``$iterable = [1, 2]``, the previous code will output the following:
+
+.. code-block:: text
+
+ 0/2 [>---------------------------] 0%
+ 1/2 [==============>-------------] 50%
+ 2/2 [============================] 100%
+
Customizing the Progress Bar
----------------------------
@@ -254,17 +286,19 @@ to display it can be customized::
.. caution::
- For performance reasons, be careful if you set the total number of steps
- to a high number. For example, if you're iterating over a large number of
- items, consider setting the redraw frequency to a higher value by calling
- :method:`Symfony\\Component\\Console\\Helper\\ProgressBar::setRedrawFrequency`,
- so it updates on only some iterations::
+ For performance reasons, Symfony redraws screen 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`::
$progressBar = new ProgressBar($output, 50000);
$progressBar->start();
- // update every 100 iterations
+ // this redraws the screen every 100 iterations, but sets additional limits:
+ // don't redraw slower than 200ms (0.2) or faster than 100ms (0.1)
$progressBar->setRedrawFrequency(100);
+ $progressBar->maxSecondsBetweenRedraws(0.2);
+ $progressBar->minSecondsBetweenRedraws(0.1);
$i = 0;
while ($i++ < 50000) {
@@ -331,3 +365,39 @@ of the custom placeholders::
$progressBar->advance();
// 2/100 -- Importing invoices... (client-001/invoices.xml)
}
+
+.. _console-multiple-progress-bars:
+
+Displaying Multiple Progress Bars
+---------------------------------
+
+When using :ref:`Console output sections ` it's
+possible to display multiple progress bars at the same time and change their
+progress independently::
+
+ $section1 = $output->section();
+ $section2 = $output->section();
+
+ $progress1 = new ProgressBar($section1);
+ $progress2 = new ProgressBar($section2);
+
+ $progress1->start(100);
+ $progress2->start(100);
+
+ $i = 0;
+ while (++$i < 100) {
+ $progress1->advance();
+
+ if ($i % 2 === 0) {
+ $progress2->advance(4);
+ }
+
+ usleep(50000);
+ }
+
+After a couple of iterations, the output in the terminal will look like this:
+
+.. code-block:: text
+
+ 34/100 [=========>------------------] 34%
+ 68/100 [===================>--------] 68%
diff --git a/components/console/helpers/questionhelper.rst b/components/console/helpers/questionhelper.rst
index 8e907bf04bd..73300db25c9 100644
--- a/components/console/helpers/questionhelper.rst
+++ b/components/console/helpers/questionhelper.rst
@@ -179,6 +179,61 @@ will be autocompleted as the user types::
$bundleName = $helper->ask($input, $output, $question);
}
+In more complex use cases, it may be necessary to generate suggestions on the
+fly, for instance if you wish to autocomplete a file path. In that case, you can
+provide a callback function to dynamically generate suggestions::
+
+ use Symfony\Component\Console\Question\Question;
+
+ // ...
+ public function execute(InputInterface $input, OutputInterface $output)
+ {
+ $helper = $this->getHelper('question');
+
+ // This function is called whenever the input changes and new
+ // suggestions are needed.
+ $callback = function (string $userInput): array {
+ // Strip any characters from the last slash to the end of the string
+ // to keep only the last directory and generate suggestions for it
+ $inputPath = preg_replace('%(/|^)[^/]*$%', '$1', $userInput);
+ $inputPath = '' === $inputPath ? '.' : $inputPath;
+
+ // CAUTION - this example code allows unrestricted access to the
+ // entire filesystem. In real applications, restrict the directories
+ // where files and dirs can be found
+ $foundFilesAndDirs = @scandir($inputPath) ?: [];
+
+ return array_map(function ($dirOrFile) use ($inputPath) {
+ return $inputPath.$dirOrFile;
+ }, $foundFilesAndDirs);
+ };
+
+ $question = new Question('Please provide the full path of a file to parse');
+ $question->setAutocompleterCallback($callback);
+
+ $filePath = $helper->ask($input, $output, $question);
+ }
+
+Do not Trim the Answer
+~~~~~~~~~~~~~~~~~~~~~~
+
+You can also specify if you want to not trim the answer by setting it directly with
+:method:`Symfony\\Component\\Console\\Question\\Question::setTrimmable`::
+
+ use Symfony\Component\Console\Question\Question;
+
+ // ...
+ public function execute(InputInterface $input, OutputInterface $output)
+ {
+ // ...
+ $helper = $this->getHelper('question');
+
+ $question = new Question('What is the name of the child?');
+ $question->setTrimmable(false);
+ // if the users inputs 'elsa ' it will not be trimmed and you will get 'elsa ' as value
+ $name = $helper->ask($input, $output, $question);
+ }
+
Hiding the User's Response
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -230,10 +285,6 @@ convenient for passwords::
// ...
}
- .. versionadded:: 3.3
-
- The ``QuestionHelper::disableStty()`` method was introduced in Symfony 3.3.
-
Normalizing the Answer
----------------------
@@ -252,7 +303,7 @@ method::
// ...
$helper = $this->getHelper('question');
- $question = new Question('Please enter the name of the bundle', 'AppBundle');
+ $question = new Question('Please enter the name of the bundle', 'AcmeDemoBundle');
$question->setNormalizer(function ($value) {
// $value can be null here
return $value ? trim($value) : '';
@@ -370,10 +421,6 @@ from the command line, you need to set the inputs that the command expects::
// $this->assertRegExp('/.../', $commandTester->getDisplay());
}
-.. versionadded:: 3.2
-
- The ``CommandTester::setInputs()`` method was introduced in Symfony 3.2.
-
By calling :method:`Symfony\\Component\\Console\\Tester\\CommandTester::setInputs`,
you imitate what the console would do internally with all user input through the CLI.
This method takes an array as only argument with, for each input that the command expects,
diff --git a/components/console/helpers/table.rst b/components/console/helpers/table.rst
index 0694a5a468a..bc680cc5ad0 100644
--- a/components/console/helpers/table.rst
+++ b/components/console/helpers/table.rst
@@ -69,6 +69,25 @@ You can add a table separator anywhere in the output by passing an instance of
| 80-902734-1-6 | And Then There Were None | Agatha Christie |
+---------------+--------------------------+------------------+
+You can optionally display titles at the top and the bottom of the table::
+
+ // ...
+ $table->setHeaderTitle('Books')
+ $table->setFooterTitle('Page 1/2')
+ $table->render();
+
+.. code-block:: terminal
+
+ +---------------+----------- Books --------+------------------+
+ | ISBN | Title | Author |
+ +---------------+--------------------------+------------------+
+ | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
+ | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
+ +---------------+--------------------------+------------------+
+ | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
+ | 80-902734-1-6 | And Then There Were None | Agatha Christie |
+ +---------------+--------- Page 1/2 -------+------------------+
+
By default, the width of the columns is calculated automatically based on their
contents. Use the :method:`Symfony\\Component\\Console\\Helper\\Table::setColumnWidths`
method to set the column widths explicitly::
@@ -79,7 +98,19 @@ method to set the column widths explicitly::
In this example, the first column width will be ``10``, the last column width
will be ``30`` and the second column width will be calculated automatically
-because of the ``0`` value. The output of this command will be:
+because of the ``0`` value.
+
+You can also set the width individually for each column with the
+:method:`Symfony\\Component\\Console\\Helper\\Table::setColumnWidth` method.
+Its first argument is the column index (starting from ``0``) and the second
+argument is the column width::
+
+ // ...
+ $table->setColumnWidth(0, 10);
+ $table->setColumnWidth(2, 30);
+ $table->render();
+
+The output of this command will be:
.. code-block:: terminal
@@ -98,16 +129,27 @@ widths. If the contents don't fit, the given column width is increased up to the
longest content length. That's why in the previous example the first column has
a ``13`` character length although the user defined ``10`` as its width.
-You can also set the width individually for each column with the
-:method:`Symfony\\Component\\Console\\Helper\\Table::setColumnWidth` method.
-Its first argument is the column index (starting from ``0``) and the second
-argument is the column width::
+If you prefer to wrap long contents in multiple rows, use the
+:method:`Symfony\\Component\\Console\\Helper\\Table::setColumnMaxWidth` method::
// ...
- $table->setColumnWidth(0, 10);
- $table->setColumnWidth(2, 30);
+ $table->setColumnMaxWidth(0, 5);
+ $table->setColumnMaxWidth(1, 10);
$table->render();
+The output of this command will be:
+
+.. code-block:: terminal
+
+ +-------+------------+--------------------------------+
+ | ISBN | Title | Author |
+ +-------+------------+--------------------------------+
+ | 99921 | Divine Com | Dante Alighieri |
+ | -58-1 | edy | |
+ | 0-7 | | |
+ | (the rest of rows...) |
+ +-------+------------+--------------------------------+
+
The table style can be changed to any built-in styles via
:method:`Symfony\\Component\\Console\\Helper\\Table::setStyle`::
@@ -146,6 +188,42 @@ which outputs:
80-902734-1-6 And Then There Were None Agatha Christie
=============== ========================== ==================
+You can also set the style to ``box``::
+
+ $table->setStyle('box');
+ $table->render();
+
+which outputs:
+
+.. code-block:: text
+
+ ┌───────────────┬──────────────────────────┬──────────────────┐
+ │ ISBN │ Title │ Author │
+ ├───────────────┼──────────────────────────┼──────────────────┤
+ │ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri │
+ │ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens │
+ │ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien │
+ │ 80-902734-1-6 │ And Then There Were None │ Agatha Christie │
+ └───────────────┴──────────────────────────┴──────────────────┘
+
+You can also set the style to ``box-double``::
+
+ $table->setStyle('box-double');
+ $table->render();
+
+which outputs:
+
+.. code-block:: text
+
+ ╔═══════════════╤══════════════════════════╤══════════════════╗
+ ║ ISBN │ Title │ Author ║
+ ╠═══════════════╪══════════════════════════╪══════════════════╣
+ ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║
+ ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║
+ ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║
+ ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
+ ╚═══════════════╧══════════════════════════╧══════════════════╝
+
If the built-in styles do not fit your need, define your own::
use Symfony\Component\Console\Helper\TableStyle;
@@ -155,9 +233,9 @@ If the built-in styles do not fit your need, define your own::
// customizes the style
$tableStyle
- ->setHorizontalBorderChar('|>')
- ->setVerticalBorderChar('->')
- ->setCrossingChar(' ')
+ ->setDefaultCrossingChars('|>')
+ ->setVerticalBorderChars('->')
+ ->setDefaultCrossingChar(' ')
;
// uses the custom style for this table
@@ -166,9 +244,10 @@ 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::setHorizontalBorderChar`
-* :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setVerticalBorderChar`
-* :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setCrossingChar`
+* :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setDefaultCrossingChars`
+* :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setVerticalBorderChars`
+* :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setCrossingChars`
+* :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setDefaultCrossingChar`
* :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setCellHeaderFormat`
* :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setCellRowFormat`
* :method:`Symfony\\Component\\Console\\Helper\\TableStyle::setBorderFormat`
@@ -273,3 +352,44 @@ This outputs:
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:
+
+Modifying Rendered Tables
+-------------------------
+
+The ``render()`` method requires passing the entire table contents. However,
+sometimes that information is not available beforehand because it's generated
+dynamically. In those cases, use the
+:method:`Symfony\\Component\\Console\\Helper\\Table::appendRow` method, which
+takes the same arguments as the ``addRow()`` method, to add rows at the bottom
+of an already rendered table.
+
+The only requirement to append rows is that the table must be rendered inside a
+:ref:`Console output section `::
+
+ use Symfony\Component\Console\Helper\Table;
+ // ...
+
+ class SomeCommand extends Command
+ {
+ public function execute(InputInterface $input, OutputInterface $output)
+ {
+ $section = $output->section();
+ $table = new Table($section);
+
+ $table->addRow(['Love']);
+ $table->render();
+
+ $table->appendRow(['Symfony']);
+ }
+ }
+
+This will display the following table in the terminal:
+
+.. code-block:: terminal
+
+ +---------+
+ | Love |
+ | Symfony |
+ +---------+
diff --git a/components/console/logger.rst b/components/console/logger.rst
index 528b7df3f2c..8f029e47002 100644
--- a/components/console/logger.rst
+++ b/components/console/logger.rst
@@ -109,10 +109,6 @@ constructor::
Errors
------
-.. versionadded:: 3.2
-
- The ``hasErrored()`` method was introduced in Symfony 3.2.
-
The Console logger includes a ``hasErrored()`` method which returns ``true`` as
soon as any error message has been logged during the execution of the command.
This is useful to decide which status code to return as the result of executing
diff --git a/components/console/single_command_tool.rst b/components/console/single_command_tool.rst
index 457efb48dae..500d679d1e1 100644
--- a/components/console/single_command_tool.rst
+++ b/components/console/single_command_tool.rst
@@ -5,34 +5,36 @@ Building a single Command Application
=====================================
When building a command line tool, you may not need to provide several commands.
-In such case, having to pass the command name each time is tedious. Fortunately,
-it is possible to remove this need by declaring a single command application::
+In such case, having to pass the command name each time is tedious.
+
+.. versionadded:: 5.1
+
+ The :class:`Symfony\\Component\\Console\\SingleCommandApplication` class was
+ introduced in Symfony 5.1.
+
+Fortunately, it is possible to remove this need by declaring a single command
+application::
#!/usr/bin/env php
register('echo')
- ->addArgument('foo', InputArgument::OPTIONAL, 'The directory')
- ->addOption('bar', null, InputOption::VALUE_REQUIRED)
- ->setCode(function(InputInterface $input, OutputInterface $output) {
- // output arguments and options
- })
- ->getApplication()
- ->setDefaultCommand('echo', true) // Single command application
+ use Symfony\Component\Console\SingleCommandApplication;
+
+ (new SingleCommandApplication())
+ ->setName('My Super Command') // Optional
+ ->setVersion('1.0.0') // Optional
+ ->addArgument('foo', InputArgument::OPTIONAL, 'The directory')
+ ->addOption('bar', null, InputOption::VALUE_REQUIRED)
+ ->setCode(function (InputInterface $input, OutputInterface $output) {
+ // output arguments and options
+ })
->run();
-The :method:`Symfony\\Component\\Console\\Application::setDefaultCommand` method
-accepts a boolean as second parameter. If true, the command ``echo`` will then
-always be used, without having to pass its name.
-
You can still register a command as usual::
#!/usr/bin/env php
@@ -49,3 +51,7 @@ You can still register a command as usual::
$application->setDefaultCommand($command->getName(), true);
$application->run();
+
+The :method:`Symfony\\Component\\Console\\Application::setDefaultCommand` method
+accepts a boolean as second parameter. If true, the command ``echo`` will then
+always be used, without having to pass its name.
diff --git a/components/console/usage.rst b/components/console/usage.rst
index b51716280af..e3a6601eec5 100644
--- a/components/console/usage.rst
+++ b/components/console/usage.rst
@@ -139,7 +139,7 @@ commands, then you can run ``help`` like this:
$ php application.php h
-If you have commands using ``:`` to namespace commands then you just have
+If you have commands using ``:`` to namespace commands then you only need
to type the shortest unambiguous text for each part. If you have created the
``demo:greet`` as shown in :doc:`/components/console` then you
can run it with:
@@ -156,9 +156,3 @@ can run it with:
If you enter a short command that's ambiguous (i.e. there are more than one
command that match), then no command will be run and some suggestions of
the possible commands to choose from will be output.
-
-.. versionadded:: 3.4
-
- Case-insensitivity of command shortcuts was introduced in Symfony 3.4. In
- previous Symfony versions, shortcuts had to match the case of the original
- command name (e.g. ``d:g`` was not the same shortcut as ``D:G``).
diff --git a/components/contracts.rst b/components/contracts.rst
new file mode 100644
index 00000000000..4f84c5f2935
--- /dev/null
+++ b/components/contracts.rst
@@ -0,0 +1,78 @@
+.. index::
+ single: Contracts
+ single: Components; Contracts
+
+The Contracts Component
+=======================
+
+ The Contracts component provides a set of abstractions extracted out of the
+ Symfony components. They can be used to build on semantics that the Symfony
+ components proved useful - and that already have battle-tested implementations.
+
+Installation
+------------
+
+Contracts are provided as separate packages, so you can install only the ones
+your projects really need:
+
+.. code-block:: terminal
+
+ $ composer require symfony/cache-contracts
+ $ composer require symfony/event-dispatcher-contracts
+ $ composer require symfony/http-client-contracts
+ $ composer require symfony/service-contracts
+ $ composer require symfony/translation-contracts
+
+.. include:: /components/require_autoload.rst.inc
+
+Usage
+-----
+
+The abstractions in this package are useful to achieve loose coupling and
+interoperability. By using the provided interfaces as type hints, you are able
+to reuse any implementations that match their contracts. It could be a Symfony
+component, or another package provided by the PHP community at large.
+
+Depending on their semantics, some interfaces can be combined with
+:doc:`autowiring ` to seamlessly inject a service
+in your classes.
+
+Others might be useful as labeling interfaces, to hint about a specific behavior
+that can be enabled when using :ref:`autoconfiguration `
+or manual :doc:`service tagging ` (or any other means
+provided by your framework.)
+
+Design Principles
+-----------------
+
+* Contracts are split by domain, each into their own sub-namespaces;
+* Contracts are small and consistent sets of PHP interfaces, traits, normative
+ docblocks and reference test suites when applicable, ...;
+* Contracts must have a proven implementation to enter this repository;
+* Contracts must be backward compatible with existing Symfony components.
+
+Packages that implement specific contracts should list them in the ``provide``
+section of their ``composer.json`` file, using the ``symfony/*-implementation``
+convention. For example:
+
+.. code-block:: javascript
+
+ {
+ "...": "...",
+ "provide": {
+ "symfony/cache-implementation": "1.0"
+ }
+ }
+
+Frequently Asked Questions
+--------------------------
+
+How Is this Different From PHP-FIG's PSRs?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When applicable, the provided contracts are built on top of `PHP-FIG`_'s PSRs.
+However, PHP-FIG has different goals and different processes. Symfony Contracts
+focuses on providing abstractions that are useful on their own while still
+compatible with implementations provided by Symfony.
+
+.. _`PHP-FIG`: https://www.php-fig.org/
diff --git a/components/css_selector.rst b/components/css_selector.rst
index 333a61c2326..e4a792a807c 100644
--- a/components/css_selector.rst
+++ b/components/css_selector.rst
@@ -12,7 +12,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/css-selector:^3.4
+ $ composer require symfony/css-selector
.. include:: /components/require_autoload.rst.inc
@@ -91,11 +91,12 @@ Pseudo-elements (``:before``, ``:after``, ``:first-line``,
``:first-letter``) are not supported because they select portions of text
rather than elements.
-Several pseudo-classes are not yet supported:
+Pseudo-classes are partially supported:
-* ``*:first-of-type``, ``*:last-of-type``, ``*:nth-of-type``,
- ``*:nth-last-of-type``, ``*:only-of-type``. (These work with an element
- name (e.g. ``li:first-of-type``) but not with ``*``).
+* Not supported: ``*:first-of-type``, ``*:last-of-type``, ``*:nth-of-type`` and
+ ``*:nth-last-of-type`` (all these work with an element name (e.g.
+ ``li:first-of-type``) but not with the ``*`` selector).
+* Supported: ``*:only-of-type``.
Learn more
----------
diff --git a/components/dependency_injection.rst b/components/dependency_injection.rst
index ae7f2c2258d..3213ed12513 100644
--- a/components/dependency_injection.rst
+++ b/components/dependency_injection.rst
@@ -17,7 +17,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/dependency-injection:^3.4
+ $ composer require symfony/dependency-injection
.. include:: /components/require_autoload.rst.inc
@@ -30,7 +30,7 @@ Basic Usage
independent component in any PHP application. Read the :doc:`/service_container`
article to learn about how to use it in Symfony applications.
-You might have a simple class like the following ``Mailer`` that
+You might have a class like the following ``Mailer`` that
you want to make available as a service::
class Mailer
@@ -214,7 +214,7 @@ Loading a YAML config file::
$containerBuilder = new ContainerBuilder();
$loader = new YamlFileLoader($containerBuilder, new FileLocator(__DIR__));
- $loader->load('services.yml');
+ $loader->load('services.yaml');
.. note::
@@ -288,17 +288,25 @@ config files:
.. code-block:: php
- use Symfony\Component\DependencyInjection\Reference;
+ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+
+ return function(ContainerConfigurator $configurator) {
+ $configurator->parameters()
+ // ...
+ ->set('mailer.transport', 'sendmail')
+ ;
+
+ $services = $configurator->services();
+
+ $services->set('mailer', 'Mailer')
+ ->args(['%mailer.transport%'])
+ ;
+
+ $services->set('newsletter_manager', 'NewsletterManager')
+ ->call('setMailer', [ref('mailer')])
+ ;
+ };
- // ...
- $container->setParameter('mailer.transport', 'sendmail');
- $container
- ->register('mailer', 'Mailer')
- ->addArgument('%mailer.transport%');
-
- $container
- ->register('newsletter_manager', 'NewsletterManager')
- ->addMethodCall('setMailer', [new Reference('mailer')]);
Learn More
----------
diff --git a/components/dependency_injection/_imports-parameters-note.rst.inc b/components/dependency_injection/_imports-parameters-note.rst.inc
index 18c35870ef3..92868df1985 100644
--- a/components/dependency_injection/_imports-parameters-note.rst.inc
+++ b/components/dependency_injection/_imports-parameters-note.rst.inc
@@ -8,13 +8,13 @@
.. code-block:: yaml
- # app/config/config.yml
+ # config/services.yaml
imports:
- - { resource: '%kernel.project_dir%/app/parameters.yml' }
+ - { resource: '%kernel.project_dir%/somefile.yaml' }
.. code-block:: xml
-
+
-
+
.. code-block:: php
- // app/config/config.php
- $loader->import('%kernel.project_dir%/app/parameters.yml');
+ // config/services.php
+ $loader->import('%kernel.project_dir%/somefile.yaml');
diff --git a/components/dependency_injection/compilation.rst b/components/dependency_injection/compilation.rst
index 6a1db7d02bb..a68acf8f854 100644
--- a/components/dependency_injection/compilation.rst
+++ b/components/dependency_injection/compilation.rst
@@ -121,7 +121,7 @@ are loaded::
$containerBuilder->registerExtension(new AcmeDemoExtension);
$loader = new YamlFileLoader($containerBuilder, new FileLocator(__DIR__));
- $loader->load('config.yml');
+ $loader->load('config.yaml');
// ...
$containerBuilder->compile();
@@ -423,10 +423,6 @@ been run, use::
PassConfig::TYPE_AFTER_REMOVING
);
-.. versionadded:: 3.2
-
- The option to prioritize compiler passes was introduced in Symfony 3.2.
-
You can also control the order in which compiler passes are run for each
compilation phase. Use the optional third argument of ``addCompilerPass()`` to
set the priority as an integer number. The default priority is ``0`` and the higher
@@ -453,7 +449,7 @@ need to be parsed and the PHP configuration built from them. The compilation
process makes the container more efficient but it takes time to run. You
can have the best of both worlds though by using configuration files and
then dumping and caching the resulting configuration. The ``PhpDumper``
-makes dumping the compiled container easy::
+serves at dumping the compiled container::
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
@@ -538,7 +534,7 @@ You do not need to work out which files to cache as the container builder
keeps track of all the resources used to configure it, not just the
configuration files but the extension classes and compiler passes as well.
This means that any changes to any of these files will invalidate the cache
-and trigger the container being rebuilt. You just need to ask the container
+and trigger the container being rebuilt. You need to ask the container
for these resources and use them as metadata for the cache::
// ...
diff --git a/components/dependency_injection/workflow.rst b/components/dependency_injection/workflow.rst
index 5ad8ae7c3d8..750420f4d47 100644
--- a/components/dependency_injection/workflow.rst
+++ b/components/dependency_injection/workflow.rst
@@ -34,7 +34,7 @@ for more details.
Application-level Configuration
-------------------------------
-Application level config is loaded from the ``app/config`` directory. Multiple
+Application level config is loaded from the ``config`` directory. Multiple
files are loaded which are then merged when the extensions are processed.
This allows for different configuration for different environments e.g.
dev, prod.
diff --git a/components/dom_crawler.rst b/components/dom_crawler.rst
index b1624c8f9de..8bc6a093fc8 100644
--- a/components/dom_crawler.rst
+++ b/components/dom_crawler.rst
@@ -17,7 +17,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/dom-crawler:^3.4
+ $ composer require symfony/dom-crawler
.. include:: /components/require_autoload.rst.inc
@@ -70,10 +70,17 @@ tree.
isn't meant to dump content, you can see the "fixed" version of your HTML
by :ref:`dumping it `.
+.. note::
+
+ If you need better support for HTML5 contents or want to get rid of the
+ inconsistencies of PHP's DOM extension, install the `html5-php library`_.
+ The DomCrawler component will use it automatically when the content has
+ an HTML5 doctype.
+
Node Filtering
~~~~~~~~~~~~~~
-Using XPath expressions is really easy::
+Using XPath expressions, you can select specific nodes within the document::
$crawler = $crawler->filterXPath('descendant-or-self::body/p');
@@ -81,8 +88,8 @@ Using XPath expressions is really easy::
``DOMXPath::query`` is used internally to actually perform an XPath query.
-Filtering is even easier if you have the CssSelector component installed.
-This allows you to use jQuery-like selectors to traverse::
+If you prefer CSS selectors over XPath, install the CssSelector component.
+It allows you to use jQuery-like selectors to traverse::
$crawler = $crawler->filter('body > p');
@@ -154,6 +161,10 @@ Namespaces can be explicitly registered with the
$crawler->registerNamespace('m', 'http://search.yahoo.com/mrss/');
$crawler = $crawler->filterXPath('//m:group//yt:aspectRatio');
+Verify if the current node matches a selector::
+
+ $crawler->matches('p.lorem');
+
Node Traversing
~~~~~~~~~~~~~~~
@@ -180,6 +191,14 @@ Get all the child or parent nodes::
$crawler->filter('body')->children();
$crawler->filter('body > p')->parents();
+Get all the direct child nodes matching a CSS selector::
+
+ $crawler->filter('body')->children('p.lorem');
+
+Get the first parent (heading toward the document root) of the element that matches the provided selector::
+
+ $crawler->closest('p.lorem');
+
.. note::
All the traversal methods return a new :class:`Symfony\\Component\\DomCrawler\\Crawler`
@@ -195,8 +214,16 @@ Access the node name (HTML tag name) of the first node of the current selection
Access the value of the first node of the current selection::
+ // if the node does not exist, calling to text() will result in an exception
$message = $crawler->filterXPath('//body/p')->text();
+ // avoid the exception passing an argument that text() returns when node does not exist
+ $message = $crawler->filterXPath('//body/p')->text('Default text content');
+
+ // pass TRUE as the second argument of text() to remove all extra white spaces, including
+ // the internal ones (e.g. " foo\n bar baz \n " is returned as "foo bar baz")
+ $crawler->filterXPath('//body/p')->text('Default text content', true);
+
Access the attribute value of the first node of the current selection::
$class = $crawler->filterXPath('//body/p')->attr('class');
@@ -205,12 +232,13 @@ Extract attribute and/or node values from the list of nodes::
$attributes = $crawler
->filterXpath('//body/p')
- ->extract(['_text', 'class'])
+ ->extract(['_name', '_text', 'class'])
;
.. note::
- Special attribute ``_text`` represents a node value.
+ Special attribute ``_text`` represents a node value, while ``_name``
+ represents the element name (the HTML tag name).
Call an anonymous function on each node of the list::
@@ -263,12 +291,6 @@ The crawler supports multiple ways of adding the content::
guesses the best charset according to the given contents and defaults to
``ISO-8859-1`` in case no charset can be guessed.
- .. versionadded:: 3.4
-
- The charset guessing mechanism of the ``addContent()`` method was
- introduced in Symfony 3.4. In previous Symfony versions, the ``ISO-8859-1``
- charset was always used.
-
As the Crawler's implementation is based on the DOM extension, it is also able
to interact with native :phpclass:`DOMDocument`, :phpclass:`DOMNodeList`
and :phpclass:`DOMNode` objects::
@@ -305,15 +327,19 @@ and :phpclass:`DOMNode` objects::
Or you can get the HTML of the first node using
:method:`Symfony\\Component\\DomCrawler\\Crawler::html`::
+ // if the node does not exist, calling to html() will result in an exception
$html = $crawler->html();
-Expression Evaluation
-~~~~~~~~~~~~~~~~~~~~~
+ // avoid the exception passing an argument that html() returns when node does not exist
+ $html = $crawler->html('Default HTML content');
-.. versionadded:: 3.2
+ Or you can get the outer HTML of the first node using
+ :method:`Symfony\\Component\\DomCrawler\\Crawler::outerHtml`::
- The :method:`Symfony\\Component\\DomCrawler\\Crawler::evaluate` method was
- introduced in Symfony 3.2.
+ $html = $crawler->outerHtml();
+
+Expression Evaluation
+~~~~~~~~~~~~~~~~~~~~~
The ``evaluate()`` method evaluates the given XPath expression. The return
value depends on the XPath expression. If the expression evaluates to a scalar
@@ -337,30 +363,34 @@ This behavior is best illustrated with examples::
$crawler->addHtmlContent($html);
$crawler->filterXPath('//span[contains(@id, "article-")]')->evaluate('substring-after(@id, "-")');
- /* array:3 [
- 0 => "100"
- 1 => "101"
- 2 => "102"
- ]
+ /* Result:
+ [
+ 0 => '100',
+ 1 => '101',
+ 2 => '102',
+ ];
*/
$crawler->evaluate('substring-after(//span[contains(@id, "article-")]/@id, "-")');
- /* array:1 [
- 0 => "100"
+ /* Result:
+ [
+ 0 => '100',
]
*/
$crawler->filterXPath('//span[@class="article"]')->evaluate('count(@id)');
- /* array:3 [
- 0 => 1.0
- 1 => 1.0
- 2 => 1.0
+ /* Result:
+ [
+ 0 => 1.0,
+ 1 => 1.0,
+ 2 => 1.0,
]
*/
$crawler->evaluate('count(//span[@class="article"])');
- /* array:1 [
- 0 => 3.0
+ /* Result:
+ [
+ 0 => 3.0,
]
*/
@@ -456,20 +486,20 @@ The :class:`Symfony\\Component\\DomCrawler\\Form` object has lots of very
useful methods for working with forms::
$uri = $form->getUri();
-
$method = $form->getMethod();
+ $name = $form->getName();
The :method:`Symfony\\Component\\DomCrawler\\Form::getUri` method does more
than just return the ``action`` attribute of the form. If the form method
is GET, then it mimics the browser's behavior and returns the ``action``
attribute followed by a query string of all of the form's values.
-.. versionadded:: 3.3
+.. note::
- Starting from Symfony 3.3, the optional ``formaction`` and ``formmethod``
- button attributes are supported. The ``getUri()`` and ``getMethod()``
- methods take into account those attributes to always return the right action
- and method depending on the button used to get the form.
+ The optional ``formaction`` and ``formmethod`` button attributes are
+ supported. The ``getUri()`` and ``getMethod()`` methods take into account
+ those attributes to always return the right action and method depending on
+ the button used to get the form.
You can virtually set and get values on the form::
@@ -552,15 +582,18 @@ of the information you need to create a POST request for the form::
// now use some HTTP client and post using this information
-One great example of an integrated system that uses all of this is `Goutte`_.
-Goutte understands the Symfony Crawler object and can use it to submit forms
+One great example of an integrated system that uses all of this is
+the :class:`Symfony\\Component\\BrowserKit\\HttpBrowser` provided by
+the :doc:`BrowserKit component `.
+It understands the Symfony Crawler object and can use it to submit forms
directly::
- use Goutte\Client;
+ use Symfony\Component\BrowserKit\HttpBrowser;
+ use Symfony\Component\HttpClient\HttpClient;
// makes a real request to an external site
- $client = new Client();
- $crawler = $client->request('GET', 'https://github.com/login');
+ $browser = new HttpBrowser(HttpClient::create());
+ $crawler = $browser->request('GET', 'https://github.com/login');
// select the form and fill in some values
$form = $crawler->selectButton('Sign in')->form();
@@ -568,7 +601,7 @@ directly::
$form['password'] = 'anypass';
// submits the given form
- $crawler = $client->submit($form);
+ $crawler = $browser->submit($form);
.. _components-dom-crawler-invalid:
@@ -587,10 +620,10 @@ the whole form or specific field(s)::
$form->disableValidation();
$form['country']->select('Invalid value');
-.. _`Goutte`: https://github.com/FriendsOfPHP/Goutte
-
Learn more
----------
* :doc:`/testing`
* :doc:`/components/css_selector`
+
+.. _`html5-php library`: https://github.com/Masterminds/html5-php
diff --git a/components/dotenv.rst b/components/dotenv.rst
index 85e2b0bccfd..882a44debb1 100644
--- a/components/dotenv.rst
+++ b/components/dotenv.rst
@@ -8,16 +8,12 @@ The Dotenv Component
The Dotenv Component parses ``.env`` files to make environment variables
stored in them accessible via ``$_ENV`` or ``$_SERVER``.
-.. versionadded:: 3.3
-
- The Dotenv component was introduced in Symfony 3.3.
-
Installation
------------
.. code-block:: terminal
- $ composer require symfony/dotenv:^3.4
+ $ composer require symfony/dotenv
.. include:: /components/require_autoload.rst.inc
@@ -60,13 +56,57 @@ Access the value with ``$_ENV`` in your code::
$dbUser = $_ENV['DB_USER'];
// you can also use ``$_SERVER``
+The ``load()`` method never overwrites existing environment variables. Use the
+``overload()`` method if you need to overwrite them::
+
+ // ...
+ $dotenv->overload(__DIR__.'/.env');
+
+As you're working with the Dotenv component you'll notice that you might want
+to have different files depending on the environment you're working in. Typically
+this happens for local development or Continuous Integration where you might
+want to have different files for your ``test`` and ``dev`` environments.
+
+You can use ``Dotenv::loadEnv()`` to ease this process::
+
+ use Symfony\Component\Dotenv\Dotenv;
+
+ $dotenv = new Dotenv();
+ $dotenv->loadEnv(__DIR__.'/.env');
+
+The Dotenv component will then look for the correct ``.env`` file to load
+in the following order whereas the files loaded later override the variables
+defined in previously loaded files:
+
+#. If ``.env`` exists, it is loaded first. In case there's no ``.env`` file but a
+ ``.env.dist``, this one will be loaded instead.
+#. If one of the previously mentioned files contains the ``APP_ENV`` variable, the
+ variable is populated and used to load environment-specific files hereafter. If
+ ``APP_ENV`` is not defined in either of the previously mentioned files, ``dev`` is
+ assumed for ``APP_ENV`` and populated by default.
+#. If there's a ``.env.local`` representing general local environment variables it's loaded now.
+#. If there's a ``.env.$env.local`` file, this one is loaded. Otherwise, it falls
+ back to ``.env.$env``.
+
+This might look complicated at first glance but it gives you the opportunity to
+commit multiple environment-specific files that can then be adjusted to your
+local environment. Given you commit ``.env``, ``.env.test`` and ``.env.dev`` to
+represent different configuration settings for your environments, each of them
+can be adjusted by using ``.env.local``, ``.env.test.local`` and
+``.env.dev.local`` respectively.
+
.. note::
- Symfony Dotenv never overwrites existing environment variables.
+ ``.env.local`` is always ignored in ``test`` environment because tests should produce the
+ same results for everyone.
-You should never store a ``.env`` file in your code repository as it might
-contain sensitive information; create a ``.env.dist`` file with sensible
-defaults instead.
+You can adjust the variable defining the environment, default environment and test
+environments by passing them as additional arguments to ``Dotenv::loadEnv()``
+(see :method:`Symfony\\Component\\Dotenv\\Dotenv::loadEnv` for details).
+
+You should never store a ``.env.local`` file in your code repository as it might
+contain sensitive information; create a ``.env`` file (or multiple
+environment-specific ones as shown above) with sensible defaults instead.
.. note::
@@ -105,6 +145,13 @@ Use environment variables in values by prefixing variables with ``$``:
its value will depend on the ``DB_USER`` value defined in other files
instead of the value defined in this file.
+Define a default value in case the environment variable is not set:
+
+.. code-block:: terminal
+
+ DB_USER=
+ DB_PASS=${DB_USER:-root}pass # results in DB_PASS=rootpass
+
Embed commands via ``$()`` (not supported on Windows):
.. code-block:: terminal
diff --git a/components/error_handler.rst b/components/error_handler.rst
new file mode 100644
index 00000000000..fe5afd0af1b
--- /dev/null
+++ b/components/error_handler.rst
@@ -0,0 +1,136 @@
+.. index::
+ single: Debug
+ single: Error
+ single: Exception
+ single: Components; ErrorHandler
+
+The ErrorHandler Component
+==========================
+
+ The ErrorHandler component provides tools to manage errors and ease
+ debugging PHP code.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/error-handler
+
+.. include:: /components/require_autoload.rst.inc
+
+Usage
+-----
+
+The ErrorHandler component provides several tools to help you debug PHP code.
+Call this method (e.g. in your :ref:`front controller `)
+to enable all of them in your application::
+
+ // public/index.php
+ use Symfony\Component\ErrorHandler\Debug;
+
+ if ($_SERVER['APP_DEBUG']) {
+ Debug::enable();
+ }
+
+ // ...
+
+Keep reading this article to learn more about each feature, including how to
+enable each of them separately.
+
+.. caution::
+
+ You should never enable the debug tools, except for the error handler, in a
+ production environment as they might disclose sensitive information to the user.
+
+Turning PHP Errors into Exceptions
+----------------------------------
+
+The :class:`Symfony\\Component\\ErrorHandler\\ErrorHandler` class catches PHP
+errors and uncaught PHP exceptions and turns them into PHP's
+:phpclass:`ErrorException` objects, except for fatal PHP errors, which are
+turned into Symfony's :class:`Symfony\\Component\\ErrorHandler\\Error\\FatalError`
+objects.
+
+If the application uses the FrameworkBundle, this error handler is enabled by
+default in the :ref:`production environment `
+because it generates better error logs.
+
+Use the following code (e.g. in your :ref:`front controller `)
+to enable this error handler::
+
+ use Symfony\Component\ErrorHandler\ErrorHandler;
+
+ ErrorHandler::register();
+
+Catching PHP Function Errors and Turning Them into Exceptions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Consider the following example::
+
+ $data = json_decode(file_get_contents($filename), true);
+ $data['read_at'] = date($datetimeFormat);
+ file_put_contents($filename, json_encode($data));
+
+Most PHP core functions were written before exception handling was introduced,
+so they return ``false`` or ``null`` in case of error instead of throwing an
+exception. That's why you need to add something like these to check for errors::
+
+ $content = @file_get_contents($filename);
+ if (false === $content) {
+ throw new \RuntimeException('Could not load file.');
+ }
+
+ // since PHP 7.3 json_decode() defines an option to throw JSON_THROW_ON_ERROR
+ // but you need to enable that option explicitly
+ $data = @json_decode($content, true);
+ if (null === $data) {
+ throw new \RuntimeException('File does not contain valid JSON.');
+ }
+
+ $datetime = @date($datetimeFormat);
+ if (false === $datetime) {
+ throw new \RuntimeException('Invalid datetime format.');
+ }
+
+To simplify this code, the :class:`Symfony\\Component\\ErrorHandler\\ErrorHandler`
+class provides a :method:`Symfony\\Component\\ErrorHandler\\ErrorHandler::call`
+method that throws an exception automatically when a PHP error occurs::
+
+ $content = ErrorHandler::call('file_get_contents', $filename);
+
+The first argument of ``call()`` is the name of the PHP function to execute and
+the rest of arguments are passed to the PHP function. The result of the PHP
+function is returned as the result of ``call()``.
+
+You can pass any PHP callable as the first argument of ``call()``, so you can
+wrap several function calls inside an anonymous function::
+
+ $data = ErrorHandler::call(static function () use ($filename, $datetimeFormat) {
+ // if any code executed inside this anonymous function fails, a PHP exception
+ // will be thrown, even if the code uses the '@' PHP silence operator
+ $data = json_decode(file_get_contents($filename), true);
+ $data['read_at'] = date($datetimeFormat);
+ file_put_contents($filename, json_encode($data));
+
+ return $data;
+ });
+
+.. _component-debug-class-loader:
+
+Class Loading Debugger
+----------------------
+
+The :class:`Symfony\\Component\\ErrorHandler\\DebugClassLoader` class throws
+more useful exceptions when a class isn't found by the registered autoloaders
+(e.g. looks for typos in the class names and suggest the right class name).
+
+In practice, this debugger looks for all registered autoloaders that implement a
+``findFile()`` method and replaces them by its own method to find class files.
+
+Use the following code (e.g. in your :ref:`front controller `)
+to enable this class loading debugger::
+
+ use Symfony\Component\ErrorHandler\DebugClassLoader;
+
+ DebugClassLoader::enable();
diff --git a/components/event_dispatcher.rst b/components/event_dispatcher.rst
index 33c6177b16d..6c472d90e97 100644
--- a/components/event_dispatcher.rst
+++ b/components/event_dispatcher.rst
@@ -29,7 +29,7 @@ The Symfony EventDispatcher component implements the `Mediator`_ and `Observer`_
design patterns to make all these things possible and to make your projects
truly extensible.
-Take a simple example from :doc:`the HttpKernel component `.
+Take an example from :doc:`the HttpKernel component `.
Once a ``Response`` object has been created, it may be useful to allow other
elements in the system to modify it (e.g. add some cache headers) before
it's actually used. To make this possible, the Symfony kernel throws an
@@ -54,7 +54,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/event-dispatcher:^3.4
+ $ composer require symfony/event-dispatcher
.. include:: /components/require_autoload.rst.inc
@@ -72,7 +72,7 @@ Events
When an event is dispatched, it's identified by a unique name (e.g.
``kernel.response``), which any number of listeners might be listening to.
-An :class:`Symfony\\Component\\EventDispatcher\\Event` instance is also
+An :class:`Symfony\\Contracts\\EventDispatcher\\Event` instance is also
created and passed to all of the listeners. As you'll see later, the ``Event``
object itself often contains data about the event being dispatched.
@@ -82,7 +82,7 @@ object itself often contains data about the event being dispatched.
Naming Conventions
..................
-The unique event name can be any string, but optionally follows a few simple
+The unique event name can be any string, but optionally follows a few
naming conventions:
* Use only lowercase letters, numbers, dots (``.``) and underscores (``_``);
@@ -97,8 +97,7 @@ Event Names and Event Objects
.............................
When the dispatcher notifies listeners, it passes an actual ``Event`` object
-to those listeners. The base ``Event`` class is very simple: it
-contains a method for stopping
+to those listeners. The base ``Event`` class contains a method for stopping
:ref:`event propagation `, but not much
else.
@@ -112,7 +111,7 @@ Often times, data about a specific event needs to be passed along with the
case, a special subclass that has additional methods for retrieving and
overriding information can be passed when dispatching an event. For example,
the ``kernel.response`` event uses a
-:class:`Symfony\\Component\\HttpKernel\\Event\\FilterResponseEvent`, which
+:class:`Symfony\\Component\\HttpKernel\\Event\\ResponseEvent`, which
contains methods to get and even replace the ``Response`` object.
The Dispatcher
@@ -162,7 +161,7 @@ The ``addListener()`` method takes up to three arguments:
So far, you've seen how PHP objects can be registered as listeners.
You can also register PHP `Closures`_ as event listeners::
- use Symfony\Component\EventDispatcher\Event;
+ use Symfony\Contracts\EventDispatcher\Event;
$dispatcher->addListener('acme.foo.action', function (Event $event) {
// will be executed when the acme.foo.action event is dispatched
@@ -173,7 +172,7 @@ is notified. In the above example, when the ``acme.foo.action`` event is dispatc
the dispatcher calls the ``AcmeListener::onFooAction()`` method and passes
the ``Event`` object as the single argument::
- use Symfony\Component\EventDispatcher\Event;
+ use Symfony\Contracts\EventDispatcher\Event;
class AcmeListener
{
@@ -253,7 +252,7 @@ order. Start by creating this custom event class and documenting it::
namespace Acme\Store\Event;
use Acme\Store\Order;
- use Symfony\Component\EventDispatcher\Event;
+ use Symfony\Contracts\EventDispatcher\Event;
/**
* The order.placed event is dispatched each time an order is created
@@ -261,7 +260,7 @@ order. Start by creating this custom event class and documenting it::
*/
class OrderPlacedEvent extends Event
{
- const NAME = 'order.placed';
+ public const NAME = 'order.placed';
protected $order;
@@ -282,7 +281,7 @@ Each listener now has access to the order via the ``getOrder()`` method.
If you don't need to pass any additional data to the event listeners, you
can also use the default
- :class:`Symfony\\Component\\EventDispatcher\\Event` class. In such case,
+ :class:`Symfony\\Contracts\\EventDispatcher\\Event` class. In such case,
you can document the event and its name in a generic ``StoreEvents`` class,
similar to the :class:`Symfony\\Component\\HttpKernel\\KernelEvents`
class.
@@ -292,8 +291,8 @@ Dispatch the Event
The :method:`Symfony\\Component\\EventDispatcher\\EventDispatcher::dispatch`
method notifies all listeners of the given event. It takes two arguments:
-the name of the event to dispatch and the ``Event`` instance to pass to
-each listener of that event::
+the ``Event`` instance to pass to each listener of that event and the name
+of the event to dispatch and ::
use Acme\Store\Event\OrderPlacedEvent;
use Acme\Store\Order;
@@ -304,7 +303,7 @@ each listener of that event::
// creates the OrderPlacedEvent and dispatches it
$event = new OrderPlacedEvent($order);
- $dispatcher->dispatch(OrderPlacedEvent::NAME, $event);
+ $dispatcher->dispatch($event, OrderPlacedEvent::NAME);
Notice that the special ``OrderPlacedEvent`` object is created and passed to
the ``dispatch()`` method. Now, any listener to the ``order.placed``
@@ -335,7 +334,7 @@ Take the following example of a subscriber that subscribes to the
use Acme\Store\Event\OrderPlacedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
- use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
+ use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class StoreSubscriber implements EventSubscriberInterface
@@ -351,12 +350,12 @@ Take the following example of a subscriber that subscribes to the
];
}
- public function onKernelResponsePre(FilterResponseEvent $event)
+ public function onKernelResponsePre(ResponseEvent $event)
{
// ...
}
- public function onKernelResponsePost(FilterResponseEvent $event)
+ public function onKernelResponsePost(ResponseEvent $event)
{
// ...
}
@@ -405,7 +404,7 @@ from being called. In other words, the listener needs to be able to tell
the dispatcher to stop all propagation of the event to future listeners
(i.e. to not notify any more listeners). This can be accomplished from
inside a listener via the
-:method:`Symfony\\Component\\EventDispatcher\\Event::stopPropagation` method::
+:method:`Symfony\\Contracts\\EventDispatcher\\Event::stopPropagation` method::
use Acme\Store\Event\OrderPlacedEvent;
@@ -420,11 +419,11 @@ Now, any listeners to ``order.placed`` that have not yet been called will
*not* be called.
It is possible to detect if an event was stopped by using the
-:method:`Symfony\\Component\\EventDispatcher\\Event::isPropagationStopped`
+:method:`Symfony\\Contracts\\EventDispatcher\\Event::isPropagationStopped`
method which returns a boolean value::
// ...
- $dispatcher->dispatch('foo.event', $event);
+ $dispatcher->dispatch($event, 'foo.event');
if ($event->isPropagationStopped()) {
// ...
}
@@ -442,36 +441,6 @@ name and a reference to itself to the listeners. This can lead to some advanced
applications of the ``EventDispatcher`` including dispatching other events inside
listeners, chaining events or even lazy loading listeners into the dispatcher object.
-.. index::
- single: EventDispatcher; Dispatcher shortcuts
-
-.. _event_dispatcher-shortcuts:
-
-Dispatcher Shortcuts
-~~~~~~~~~~~~~~~~~~~~
-
-If you do not need a custom event object, you can simply rely on a plain
-:class:`Symfony\\Component\\EventDispatcher\\Event` object. You do not even
-need to pass this to the dispatcher as it will create one by default unless you
-specifically pass one::
-
- $dispatcher->dispatch('order.placed');
-
-Moreover, the event dispatcher always returns whichever event object that
-was dispatched, i.e. either the event that was passed or the event that
-was created internally by the dispatcher. This allows for nice shortcuts::
-
- if (!$dispatcher->dispatch('foo.event')->isPropagationStopped()) {
- // ...
- }
-
-Or::
-
- $event = new OrderPlacedEvent($order);
- $order = $dispatcher->dispatch('bar.event', $event)->getOrder();
-
-and so on.
-
.. index::
single: EventDispatcher; Event name introspection
@@ -483,8 +452,8 @@ Event Name Introspection
The ``EventDispatcher`` instance, as well as the name of the event that
is dispatched, are passed as arguments to the listener::
- use Symfony\Component\EventDispatcher\Event;
- use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+ use Symfony\Contracts\EventDispatcher\Event;
+ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
class Foo
{
@@ -500,7 +469,6 @@ Other Dispatchers
Besides the commonly used ``EventDispatcher``, the component comes
with some other dispatchers:
-* :doc:`/components/event_dispatcher/container_aware_dispatcher`
* :doc:`/components/event_dispatcher/immutable_dispatcher`
* :doc:`/components/event_dispatcher/traceable_dispatcher`
diff --git a/components/event_dispatcher/container_aware_dispatcher.rst b/components/event_dispatcher/container_aware_dispatcher.rst
index a2d5e29a96a..659a94cee7a 100644
--- a/components/event_dispatcher/container_aware_dispatcher.rst
+++ b/components/event_dispatcher/container_aware_dispatcher.rst
@@ -4,104 +4,7 @@
The Container Aware Event Dispatcher
====================================
-.. deprecated:: 3.3
+.. caution::
- The ``ContainerAwareEventDispatcher`` class has been deprecated in Symfony 3.3
- and will be removed in Symfony 4.0. Use ``EventDispatcher`` with
- closure-proxy injection instead.
-
-Introduction
-------------
-
-The :class:`Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher`
-is a special ``EventDispatcher`` implementation which is coupled to the
-service container that is part of
-:doc:`the DependencyInjection component `.
-It allows services to be specified as event listeners making the ``EventDispatcher``
-extremely powerful.
-
-Services are lazy loaded meaning the services attached as listeners will
-only be created if an event is dispatched that requires those listeners.
-
-Setup
------
-
-Setup is straightforward by injecting a :class:`Symfony\\Component\\DependencyInjection\\ContainerInterface`
-into the :class:`Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher`::
-
- use Symfony\Component\DependencyInjection\ContainerBuilder;
- use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher;
-
- $containerBuilder = new ContainerBuilder();
- $dispatcher = new ContainerAwareEventDispatcher($containerBuilder);
-
-Adding Listeners
-----------------
-
-The ``ContainerAwareEventDispatcher`` can either load specified services
-directly or services that implement :class:`Symfony\\Component\\EventDispatcher\\EventSubscriberInterface`.
-
-The following examples assume the service container has been loaded with
-any services that are mentioned.
-
-.. note::
-
- Services must be marked as public in the container.
-
-Adding Services
-~~~~~~~~~~~~~~~
-
-To connect existing service definitions, use the
-:method:`Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher::addListenerService`
-method where the ``$callback`` is an array of ``[$serviceId, $methodName]``::
-
- $dispatcher->addListenerService($eventName, ['foo', 'logListener']);
-
-Adding Subscriber Services
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Event subscribers can be added using the
-:method:`Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher::addSubscriberService`
-method where the first argument is the service ID of the subscriber service,
-and the second argument is the service's class name (which must implement
-:class:`Symfony\\Component\\EventDispatcher\\EventSubscriberInterface`) as follows::
-
- $dispatcher->addSubscriberService(
- 'kernel.store_subscriber',
- 'StoreSubscriber'
- );
-
-The ``EventSubscriberInterface`` is exactly as you would expect::
-
- use Symfony\Component\EventDispatcher\EventSubscriberInterface;
- use Symfony\Component\HttpKernel\KernelEvents;
- // ...
-
- class StoreSubscriber implements EventSubscriberInterface
- {
- public static function getSubscribedEvents()
- {
- return [
- KernelEvents::RESPONSE => [
- ['onKernelResponsePre', 10],
- ['onKernelResponsePost', 0],
- ),
- 'store.order' => ['onStoreOrder', 0],
- );
- }
-
- public function onKernelResponsePre(FilterResponseEvent $event)
- {
- // ...
- }
-
- public function onKernelResponsePost(FilterResponseEvent $event)
- {
- // ...
- }
-
- public function onStoreOrder(FilterOrderEvent $event)
- {
- // ...
- }
- }
+ The ``ContainerAwareEventDispatcher`` was removed in Symfony 4.0. Use
+ ``EventDispatcher`` with closure-proxy injection instead.
diff --git a/components/event_dispatcher/generic_event.rst b/components/event_dispatcher/generic_event.rst
index 42efd41b902..1f9be477151 100644
--- a/components/event_dispatcher/generic_event.rst
+++ b/components/event_dispatcher/generic_event.rst
@@ -4,7 +4,7 @@
The Generic Event Object
========================
-The base :class:`Symfony\\Component\\EventDispatcher\\Event` class provided
+The base :class:`Symfony\\Contracts\\EventDispatcher\\Event` class provided
by the EventDispatcher component is deliberately sparse to allow the creation
of API specific event objects by inheritance using OOP. This allows for
elegant and readable code in complex applications.
@@ -16,9 +16,9 @@ box, because it follows the standard observer pattern where the event object
encapsulates an event 'subject', but has the addition of optional extra
arguments.
-:class:`Symfony\\Component\\EventDispatcher\\GenericEvent` has a simple
-API in addition to the base class
-:class:`Symfony\\Component\\EventDispatcher\\Event`
+:class:`Symfony\\Component\\EventDispatcher\\GenericEvent` adds some more
+methods in addition to the base class
+:class:`Symfony\\Contracts\\EventDispatcher\\Event`
* :method:`Symfony\\Component\\EventDispatcher\\GenericEvent::__construct`:
Constructor takes the event subject and any arguments;
@@ -48,12 +48,12 @@ the event subject.
The following examples show use-cases to give a general idea of the flexibility.
The examples assume event listeners have been added to the dispatcher.
-Simply passing a subject::
+Passing a subject::
use Symfony\Component\EventDispatcher\GenericEvent;
$event = new GenericEvent($subject);
- $dispatcher->dispatch('foo', $event);
+ $dispatcher->dispatch($event, 'foo');
class FooListener
{
@@ -74,7 +74,7 @@ access the event arguments::
$subject,
['type' => 'foo', 'counter' => 0]
);
- $dispatcher->dispatch('foo', $event);
+ $dispatcher->dispatch($event, 'foo');
class FooListener
{
@@ -93,7 +93,7 @@ Filtering data::
use Symfony\Component\EventDispatcher\GenericEvent;
$event = new GenericEvent($subject, ['data' => 'Foo']);
- $dispatcher->dispatch('foo', $event);
+ $dispatcher->dispatch($event, 'foo');
class FooListener
{
diff --git a/components/event_dispatcher/traceable_dispatcher.rst b/components/event_dispatcher/traceable_dispatcher.rst
index 57f05ba6e0d..33a98a2336b 100644
--- a/components/event_dispatcher/traceable_dispatcher.rst
+++ b/components/event_dispatcher/traceable_dispatcher.rst
@@ -38,13 +38,13 @@ to register event listeners and dispatch events::
// dispatches an event
$event = ...;
- $traceableEventDispatcher->dispatch('event.the_name', $event);
+ $traceableEventDispatcher->dispatch($event, 'event.the_name');
After your application has been processed, you can use the
-:method:`Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcherInterface::getCalledListeners`
+:method:`Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher::getCalledListeners`
method to retrieve an array of event listeners that have been called in
your application. Similarly, the
-:method:`Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcherInterface::getNotCalledListeners`
+:method:`Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher::getNotCalledListeners`
method returns an array of event listeners that have not been called::
// ...
diff --git a/components/expression_language.rst b/components/expression_language.rst
index db1fbb99955..90600bf7357 100644
--- a/components/expression_language.rst
+++ b/components/expression_language.rst
@@ -14,7 +14,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/expression-language:^3.4
+ $ composer require symfony/expression-language
.. include:: /components/require_autoload.rst.inc
diff --git a/components/expression_language/caching.rst b/components/expression_language/caching.rst
index 4f24942a559..770c2768ca5 100644
--- a/components/expression_language/caching.rst
+++ b/components/expression_language/caching.rst
@@ -37,13 +37,6 @@ ones and injecting this using the constructor::
$cache = new RedisAdapter(...);
$expressionLanguage = new ExpressionLanguage($cache);
-.. versionadded:: 3.2
-
- PSR-6 caching support was introduced in Symfony 3.2. Prior to version 3.2,
- a
- :class:`Symfony\\Component\\ExpressionLanguage\\ParserCache\\ParserCacheInterface`
- instance had to be injected.
-
.. seealso::
See the :doc:`/components/cache` documentation for more information about
diff --git a/components/expression_language/extending.rst b/components/expression_language/extending.rst
index b34af3e2c30..787d0f61d31 100644
--- a/components/expression_language/extending.rst
+++ b/components/expression_language/extending.rst
@@ -100,10 +100,6 @@ register::
ExpressionFunction::fromPhp('My\strtoupper', 'my_strtoupper');
- .. versionadded:: 3.3
-
- The ``ExpressionFunction::fromPhp()`` method was introduced in Symfony 3.3.
-
You can register providers using
:method:`Symfony\\Component\\ExpressionLanguage\\ExpressionLanguage::registerProvider`
or by using the second argument of the constructor::
@@ -124,17 +120,17 @@ or by using the second argument of the constructor::
It is recommended to create your own ``ExpressionLanguage`` class in your
library. Now you can add the extension by overriding the constructor::
+ use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage as BaseExpressionLanguage;
- use Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface;
class ExpressionLanguage extends BaseExpressionLanguage
{
- public function __construct(ParserCacheInterface $parser = null, array $providers = [])
+ public function __construct(CacheItemPoolInterface $cache = null, array $providers = [])
{
// prepends the default provider to let users override it
array_unshift($providers, new StringExpressionLanguageProvider());
- parent::__construct($parser, $providers);
+ parent::__construct($cache, $providers);
}
}
diff --git a/components/expression_language/syntax.rst b/components/expression_language/syntax.rst
index ca220061b36..045451491f5 100644
--- a/components/expression_language/syntax.rst
+++ b/components/expression_language/syntax.rst
@@ -19,6 +19,7 @@ The component supports:
* **hashes** - using JSON-like notation (e.g. ``{ foo: 'bar' }``)
* **booleans** - ``true`` and ``false``
* **null** - ``null``
+* **exponential** - also known as scientific (e.g. ``1.99E+3`` or ``1e-2``)
.. caution::
@@ -318,4 +319,4 @@ expressions (e.g. the request, the current user, etc.):
* :doc:`Variables available in security expressions `;
* :doc:`Variables available in service container expressions `;
-* :doc:`Variables available in routing expressions `.
+* :ref:`Variables available in routing expressions `.
diff --git a/components/filesystem.rst b/components/filesystem.rst
index 9a0c3579974..21881a307d3 100644
--- a/components/filesystem.rst
+++ b/components/filesystem.rst
@@ -11,7 +11,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/filesystem:^3.4
+ $ composer require symfony/filesystem
.. include:: /components/require_autoload.rst.inc
@@ -210,10 +210,6 @@ support symbolic links, a third boolean argument is available::
``readlink``
~~~~~~~~~~~~
-.. versionadded:: 3.2
-
- The :method:`Symfony\\Component\\Filesystem\\Filesystem::readlink` method was introduced in Symfony 3.2.
-
:method:`Symfony\\Component\\Filesystem\\Filesystem::readlink` read links targets.
PHP's ``readlink()`` function returns the target of a symbolic link. However, its behavior
@@ -307,11 +303,6 @@ The ``file.txt`` file contains ``Hello World`` now.
``appendToFile``
~~~~~~~~~~~~~~~~
-.. versionadded:: 3.3
-
- The :method:`Symfony\\Component\\Filesystem\\Filesystem::appendToFile`
- method was introduced in Symfony 3.3.
-
:method:`Symfony\\Component\\Filesystem\\Filesystem::appendToFile` adds new
contents at the end of some file::
@@ -332,13 +323,4 @@ Whenever something wrong happens, an exception implementing
An :class:`Symfony\\Component\\Filesystem\\Exception\\IOException` is
thrown if directory creation fails.
-Learn More
-----------
-
-.. toctree::
- :maxdepth: 1
- :glob:
-
- filesystem/*
-
.. _`umask`: https://en.wikipedia.org/wiki/Umask
diff --git a/components/filesystem/lock_handler.rst b/components/filesystem/lock_handler.rst
index 28eaf3a6ba8..e7dab2fa625 100644
--- a/components/filesystem/lock_handler.rst
+++ b/components/filesystem/lock_handler.rst
@@ -1,96 +1,9 @@
+:orphan:
+
LockHandler
===========
-.. deprecated:: 3.4
-
- The ``LockHandler`` class was deprecated in Symfony 3.4 and will be
- removed in Symfony 4.0. Use :ref:`SemaphoreStore `
- or :ref:`FlockStore ` from the Lock component instead.
-
-What is a Lock?
----------------
-
-File locking is a mechanism that restricts access to a computer file by allowing
-only one user or process access at any specific time. This mechanism was
-introduced a few decades ago for mainframes, but continues being useful for
-modern applications.
-
-Symfony provides a LockHelper to help you use locks in your project.
-
-Usage
------
-
.. caution::
- The lock handler only works if you're using just one server. If you have
- several hosts, you must not use this helper.
-
-A lock can be used, for example, to allow only one instance of a command to run::
-
- use Symfony\Component\Filesystem\LockHandler;
-
- $lockHandler = new LockHandler('hello.lock');
- if (!$lockHandler->lock()) {
- // the resource "hello" is already locked by another process
-
- return 0;
- }
-
-The first argument of the constructor is a string that it will use as part of
-the name of the file used to create the lock on the local filesystem. A best
-practice for Symfony commands is to use the command name, such as ``acme:my-command``.
-``LockHandler`` sanitizes the contents of the string before creating
-the file, so you can pass any value for this argument.
-
-.. tip::
-
- The ``.lock`` extension is optional, but it's a common practice to include
- it. This will make it easier to find lock files on the filesystem. Moreover,
- to avoid name collisions, ``LockHandler`` also appends a hash to the name of
- the lock file.
-
-By default, the lock will be created in the system's temporary directory, but
-you can optionally select the directory where locks are created by passing it as
-the second argument of the constructor.
-
-.. tip::
-
- Another way to configure the directory where the locks are created is to
- define a special environment variable, because PHP will use that value to
- override the default temporary directory. On Unix-based systems, define the
- ``TMPDIR`` variable. On Windows systems, define any of these variables:
- ``TMP``, ``TEMP`` or ``USERPROFILE`` (they are checked in this order). This
- technique is useful for example when deploying a third-party Symfony
- application whose code can't be modified.
-
-The :method:`Symfony\\Component\\Filesystem\\LockHandler::lock` method tries to
-acquire the lock. If the lock is acquired, the method returns ``true``,
-``false`` otherwise. If the ``lock()`` method is called several times on the same
-instance it will always return ``true`` if the lock was acquired on the first
-call.
-
-You can pass an optional blocking argument as the first argument to the
-``lock()`` method, which defaults to ``false``. If this is set to ``true``, your
-PHP code will wait indefinitely until the lock is released by another process.
-
-.. caution::
-
- Be aware of the fact that the resource lock is automatically released as soon
- as PHP applies the garbage-collection process to the ``LockHandler`` object.
- This means that if you refactor the first example shown in this article as
- follows::
-
- use Symfony\Component\Filesystem\LockHandler;
-
- if (!(new LockHandler('hello.lock'))->lock()) {
- // the resource "hello" is already locked by another process
-
- return 0;
- }
-
- Now the code won't work as expected because PHP's garbage collection mechanism
- removes the reference to the ``LockHandler`` object and thus, the lock is released
- just after it's been created.
-
- Another alternative way to release the lock explicitly when needed is to use the
- :method:`Symfony\\Component\\Filesystem\\LockHandler::release` method.
+ The ``LockHandler`` utility was removed in Symfony 4.0. Use the new Symfony
+ :doc:`Lock component ` instead.
diff --git a/components/finder.rst b/components/finder.rst
index d502086b4d6..bb4559ee749 100644
--- a/components/finder.rst
+++ b/components/finder.rst
@@ -5,15 +5,15 @@
The Finder Component
====================
- The Finder component finds files and directories via an intuitive fluent
- interface.
+ The Finder component finds files and directories based on different criteria
+ (name, file size, modification time, etc.) via an intuitive fluent interface.
Installation
------------
.. code-block:: terminal
- $ composer require symfony/finder:^3.4
+ $ composer require symfony/finder
.. include:: /components/require_autoload.rst.inc
@@ -26,33 +26,24 @@ directories::
use Symfony\Component\Finder\Finder;
$finder = new Finder();
+ // find all files in the current directory
$finder->files()->in(__DIR__);
- foreach ($finder as $file) {
- // dumps the absolute path
- var_dump($file->getRealPath());
-
- // dumps the relative path to the file, omitting the filename
- var_dump($file->getRelativePath());
-
- // dumps the relative path to the file
- var_dump($file->getRelativePathname());
+ // check if there are any search results
+ if ($finder->hasResults()) {
+ // ...
}
-The ``$file`` is an instance of :class:`Symfony\\Component\\Finder\\SplFileInfo`
-which extends PHP's own :phpclass:`SplFileInfo` to provide methods to work with relative
-paths.
-
-The above code prints the names of all the files in the current directory
-recursively. The Finder class uses a fluent interface, so all methods return
-the Finder instance.
+ foreach ($finder as $file) {
+ $absoluteFilePath = $file->getRealPath();
+ $fileNameWithExtension = $file->getRelativePathname();
-.. tip::
+ // ...
+ }
- A Finder instance is a PHP :phpclass:`IteratorAggregate`. So, in addition to iterating over the
- Finder with ``foreach``, you can also convert it to an array with the
- :phpfunction:`iterator_to_array` function, or get the number of items with
- :phpfunction:`iterator_count`.
+The ``$file`` variable is an instance of
+:class:`Symfony\\Component\\Finder\\SplFileInfo` which extends PHP's own
+:phpclass:`SplFileInfo` to provide methods to work with relative paths.
.. caution::
@@ -60,27 +51,11 @@ the Finder instance.
This means that you need to create a new instance if you do not want
to get mixed results.
-.. caution::
-
- When searching through multiple locations passed to the
- :method:`Symfony\\Component\\Finder\\Finder::in` method, a separate iterator
- is created internally for every location. This means we have multiple result
- sets aggregated into one.
- Since :phpfunction:`iterator_to_array` uses keys of result sets by default,
- when converting to an array, some keys might be duplicated and their values
- overwritten. This can be avoided by passing ``false`` as a second parameter
- to :phpfunction:`iterator_to_array`.
-
-Criteria
---------
-
-There are lots of ways to filter and sort your results. You can also use the
-:method:`Symfony\\Component\\Finder\\Finder::hasResults` method to check if
-there's any file or directory matching the search criteria.
+Searching for Files and Directories
+-----------------------------------
-.. versionadded:: 3.4
-
- The ``hasResults()`` method was introduced in Symfony 3.4.
+The component provides lots of methods to define the search criteria. They all
+can be chained because they implement a `fluent interface`_.
Location
~~~~~~~~
@@ -99,12 +74,11 @@ Search in several locations by chaining calls to
// same as above
$finder->in(__DIR__)->in('/elsewhere');
-Use wildcard characters to search in the directories matching a pattern::
+Use ``*`` as a wildcard character to search in the directories matching a
+pattern (each pattern has to resolve to at least one directory path)::
$finder->in('src/Symfony/*/*/Resources');
-Each pattern has to resolve to at least one directory path.
-
Exclude directories from matching with the
:method:`Symfony\\Component\\Finder\\Finder::exclude` method::
@@ -116,7 +90,7 @@ It's also possible to ignore directories that you don't have permission to read:
$finder->ignoreUnreadableDirs()->in(__DIR__);
As the Finder uses PHP iterators, you can pass any URL with a supported
-`protocol`_::
+`PHP wrapper for URL-style protocols`_ (``ftp://``, ``zlib://``, etc.)::
// always add a trailing slash when looking for in the FTP root dir
$finder->in('ftp://example.com/');
@@ -128,8 +102,9 @@ And it also works with user-defined streams::
use Symfony\Component\Finder\Finder;
- $s3 = new \Zend_Service_Amazon_S3($key, $secret);
- $s3->registerStreamWrapper('s3');
+ // register a 's3://' wrapper with the official AWS SDK
+ $s3Client = new Aws\S3\S3Client([/* config options */]);
+ $s3Client->registerStreamWrapper();
$finder = new Finder();
$finder->name('photos*')->size('< 100K')->date('since 1 hour ago');
@@ -137,76 +112,77 @@ And it also works with user-defined streams::
// ... do something with the file
}
-.. note::
+.. seealso::
- Read the `Streams`_ documentation to learn how to create your own streams.
+ Read the `PHP streams`_ documentation to learn how to create your own streams.
Files or Directories
~~~~~~~~~~~~~~~~~~~~
By default, the Finder returns both files and directories. If you need to find either files or directories only, use the :method:`Symfony\\Component\\Finder\\Finder::files` and :method:`Symfony\\Component\\Finder\\Finder::directories` methods::
+ // look for files only; ignore directories
$finder->files();
+ // look for directories only; ignore files
$finder->directories();
-If you want to follow links, use the ``followLinks()`` method::
+If you want to follow `symbolic links`_, use the ``followLinks()`` method::
$finder->files()->followLinks();
-By default, the iterator ignores popular VCS files. This can be changed with
-the ``ignoreVCS()`` method::
-
- $finder->ignoreVCS(false);
-
-Sorting
-~~~~~~~
+Version Control Files
+~~~~~~~~~~~~~~~~~~~~~
-Sort the result by name or by type (directories first, then files)::
-
- $finder->sortByName();
-
- $finder->sortByType();
-
-Sort the files and directories by the last accessed, changed or modified time::
+`Version Control Systems`_ (or "VCS" for short), such as Git and Mercurial,
+create some special files to store their metadata. Those files are ignored by
+default when looking for files and directories, but you can change this with the
+``ignoreVCS()`` method::
- $finder->sortByAccessedTime();
-
- $finder->sortByChangedTime();
-
- $finder->sortByModifiedTime();
-
-.. note::
-
- Notice that the ``sort*`` methods need to get all matching elements to do
- their jobs. For large iterators, it is slow.
+ $finder->ignoreVCS(false);
-You can also define your own sorting algorithm with ``sort()`` method::
+If the search directory contains a ``.gitignore`` file, you can reuse those
+rules to exclude files and directories from the results with the
+:method:`Symfony\\Component\\Finder\\Finder::ignoreVCSIgnored` method::
- $finder->sort(function (\SplFileInfo $a, \SplFileInfo $b) {
- return strcmp($a->getRealPath(), $b->getRealPath());
- });
+ // excludes files/directories matching the .gitignore patterns
+ $finder->ignoreVCSIgnored(true);
File Name
~~~~~~~~~
-Restrict files by name with the
+Find files by name with the
:method:`Symfony\\Component\\Finder\\Finder::name` method::
$finder->files()->name('*.php');
-The ``name()`` method accepts globs, strings, or regexes::
+The ``name()`` method accepts globs, strings, regexes or an array of globs,
+strings or regexes::
$finder->files()->name('/\.php$/');
+Multiple filenames can be defined by chaining calls or passing an array::
+
+ $finder->files()->name('*.php')->name('*.twig');
+
+ // same as above
+ $finder->files()->name(['*.php', '*.twig']);
+
The ``notName()`` method excludes files matching a pattern::
$finder->files()->notName('*.rb');
+Multiple filenames can be excluded by chaining calls or passing an array::
+
+ $finder->files()->notName('*.rb')->notName('*.py');
+
+ // same as above
+ $finder->files()->notName(['*.rb', '*.py']);
+
File Contents
~~~~~~~~~~~~~
-Restrict files by contents with the
+Find files by content with the
:method:`Symfony\\Component\\Finder\\Finder::contains` method::
$finder->files()->contains('lorem ipsum');
@@ -222,7 +198,7 @@ The ``notContains()`` method excludes files containing given pattern::
Path
~~~~
-Restrict files and directories by path with the
+Find files and directories by path with the
:method:`Symfony\\Component\\Finder\\Finder::path` method::
// matches files that contain "data" anywhere in their paths (files or directories)
@@ -230,39 +206,61 @@ Restrict files and directories by path with the
// for example this will match data/*.xml and data.xml if they exist
$finder->path('data')->name('*.xml');
-On all platforms slash (i.e. ``/``) should be used as the directory separator.
+Use the forward slash (i.e. ``/``) as the directory separator on all platforms,
+including Windows. The component makes the necessary conversion internally.
-The ``path()`` method accepts a string or a regular expression::
+The ``path()`` method accepts a string, a regular expression or an array of
+strings or regulars expressions::
$finder->path('foo/bar');
$finder->path('/^foo\/bar/');
+Multiple paths can be defined by chaining calls or passing an array::
+
+ $finder->path('data')->path('foo/bar');
+
+ // same as above
+ $finder->path(['data', 'foo/bar']);
+
Internally, strings are converted into regular expressions by escaping slashes
and adding delimiters:
-.. code-block:: text
+===================== =======================
+Original Given String Regular Expression Used
+===================== =======================
+``dirname`` ``/dirname/``
+``a/b/c`` ``/a\/b\/c/``
+===================== =======================
- dirname ===> /dirname/
- a/b/c ===> /a\/b\/c/
-
-The :method:`Symfony\\Component\\Finder\\Finder::notPath` method excludes files by path::
+The :method:`Symfony\\Component\\Finder\\Finder::notPath` method excludes files
+by path::
$finder->notPath('other/dir');
+Multiple paths can be excluded by chaining calls or passing an array::
+
+ $finder->notPath('first/dir')->notPath('other/dir');
+
+ // same as above
+ $finder->notPath(['first/dir', 'other/dir']);
+
File Size
~~~~~~~~~
-Restrict files by size with the
+Find files by size with the
:method:`Symfony\\Component\\Finder\\Finder::size` method::
$finder->files()->size('< 1.5K');
-Restrict by a size range by chaining calls::
+Restrict by a size range by chaining calls or passing an array::
$finder->files()->size('>= 1K')->size('<= 2K');
-The comparison operator can be any of the following: ``>``, ``>=``, ``<``, ``<=``,
-``==``, ``!=``.
+ // same as above
+ $finder->files()->size(['>= 1K', '<= 2K']);
+
+The comparison operator can be any of the following: ``>``, ``>=``, ``<``,
+``<=``, ``==``, ``!=``.
The target value may use magnitudes of kilobytes (``k``, ``ki``), megabytes
(``m``, ``mi``), or gigabytes (``g``, ``gi``). Those suffixed with an ``i`` use
@@ -271,30 +269,44 @@ the appropriate ``2**n`` version in accordance with the `IEC standard`_.
File Date
~~~~~~~~~
-Restrict files by last modified dates with the
+Find files by last modified dates with the
:method:`Symfony\\Component\\Finder\\Finder::date` method::
$finder->date('since yesterday');
-The comparison operator can be any of the following: ``>``, ``>=``, ``<``, ``<=``,
-``==``. You can also use ``since`` or ``after`` as an alias for ``>``, and
-``until`` or ``before`` as an alias for ``<``.
+Restrict by a date range by chaining calls or passing an array::
+
+ $finder->date('>= 2018-01-01')->date('<= 2018-12-31');
+
+ // same as above
+ $finder->date(['>= 2018-01-01', '<= 2018-12-31']);
+
+The comparison operator can be any of the following: ``>``, ``>=``, ``<``,
+``<=``, ``==``. You can also use ``since`` or ``after`` as an alias for ``>``,
+and ``until`` or ``before`` as an alias for ``<``.
-The target value can be any date supported by the `strtotime`_ function.
+The target value can be any date supported by :phpfunction:`strtotime`.
Directory Depth
~~~~~~~~~~~~~~~
-By default, the Finder recursively traverse directories. Restrict the depth of
+By default, the Finder recursively traverses directories. Restrict the depth of
traversing with :method:`Symfony\\Component\\Finder\\Finder::depth`::
$finder->depth('== 0');
$finder->depth('< 3');
+Restrict by a depth range by chaining calls or passing an array::
+
+ $finder->depth('> 2')->depth('< 5');
+
+ // same as above
+ $finder->depth(['> 2', '< 5']);
+
Custom Filtering
~~~~~~~~~~~~~~~~
-To restrict the matching file with your own strategy, use
+To filter results with your own strategy, use
:method:`Symfony\\Component\\Finder\\Finder::filter`::
$filter = function (\SplFileInfo $file)
@@ -311,8 +323,63 @@ it is called with the file as a :class:`Symfony\\Component\\Finder\\SplFileInfo`
instance. The file is excluded from the result set if the Closure returns
``false``.
+Sorting Results
+---------------
+
+Sort the results by name or by type (directories first, then files)::
+
+ $finder->sortByName();
+
+ $finder->sortByType();
+
+.. tip::
+
+ By default, the ``sortByName()`` method uses the :phpfunction:`strcmp` PHP
+ function (e.g. ``file1.txt``, ``file10.txt``, ``file2.txt``). Pass ``true``
+ as its argument to use PHP's `natural sort order`_ algorithm instead (e.g.
+ ``file1.txt``, ``file2.txt``, ``file10.txt``).
+
+Sort the files and directories by the last accessed, changed or modified time::
+
+ $finder->sortByAccessedTime();
+
+ $finder->sortByChangedTime();
+
+ $finder->sortByModifiedTime();
+
+You can also define your own sorting algorithm with the ``sort()`` method::
+
+ $finder->sort(function (\SplFileInfo $a, \SplFileInfo $b) {
+ return strcmp($a->getRealPath(), $b->getRealPath());
+ });
+
+You can reverse any sorting by using the ``reverseSorting()`` method::
+
+ // results will be sorted "Z to A" instead of the default "A to Z"
+ $finder->sortByName()->reverseSorting();
+
+.. note::
+
+ Notice that the ``sort*`` methods need to get all matching elements to do
+ their jobs. For large iterators, it is slow.
+
+Transforming Results into Arrays
+--------------------------------
+
+A Finder instance is an :phpclass:`IteratorAggregate` PHP class. So, in addition
+to iterating over the Finder results with ``foreach``, you can also convert it
+to an array with the :phpfunction:`iterator_to_array` function, or get the
+number of items with :phpfunction:`iterator_count`.
+
+If you call to the :method:`Symfony\\Component\\Finder\\Finder::in` method more
+than once to search through multiple locations, pass ``false`` as a second
+parameter to :phpfunction:`iterator_to_array` to avoid issues (a separate
+iterator is created for each location and, if you don't pass ``false`` to
+:phpfunction:`iterator_to_array`, keys of result sets are used and some of them
+might be duplicated and their values overwritten).
+
Reading Contents of Returned Files
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+----------------------------------
The contents of returned files can be read with
:method:`Symfony\\Component\\Finder\\SplFileInfo::getContents`::
@@ -328,7 +395,10 @@ The contents of returned files can be read with
// ...
}
-.. _strtotime: https://php.net/manual/en/datetime.formats.php
-.. _protocol: https://php.net/manual/en/wrappers.php
-.. _Streams: https://php.net/streams
-.. _IEC standard: https://physics.nist.gov/cuu/Units/binary.html
+.. _`fluent interface`: https://en.wikipedia.org/wiki/Fluent_interface
+.. _`symbolic links`: https://en.wikipedia.org/wiki/Symbolic_link
+.. _`Version Control Systems`: https://en.wikipedia.org/wiki/Version_control
+.. _`PHP wrapper for URL-style protocols`: https://php.net/manual/en/wrappers.php
+.. _`PHP streams`: https://php.net/streams
+.. _`IEC standard`: https://physics.nist.gov/cuu/Units/binary.html
+.. _`natural sort order`: https://en.wikipedia.org/wiki/Natural_sort_order
diff --git a/components/form.rst b/components/form.rst
index 84b4664d671..6178c93848f 100644
--- a/components/form.rst
+++ b/components/form.rst
@@ -18,7 +18,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/form:^3.4
+ $ composer require symfony/form
.. include:: /components/require_autoload.rst.inc
@@ -32,7 +32,8 @@ Configuration
about how to use it in Symfony applications.
In Symfony, forms are represented by objects and these objects are built
-by using a *form factory*. Building a form factory is simple::
+by using a *form factory*. Building a form factory is done with the factory
+method ``Forms::createFormFactory``::
use Symfony\Component\Form\Forms;
@@ -79,8 +80,8 @@ object to read data off of the correct PHP superglobals (i.e. ``$_POST`` or
.. seealso::
If you need more control over exactly when your form is submitted or which
- data is passed to it, you can use the :method:`Symfony\\Component\\Form\\FormInterface::submit`
- for this. Read more about it :ref:`form-call-submit-directly`.
+ data is passed to it,
+ :doc:`use the submit() method to handle form submissions `.
.. sidebar:: Integration with the HttpFoundation Component
@@ -114,7 +115,7 @@ use the built-in support, first install the Security CSRF component:
.. code-block:: terminal
- $ composer require symfony/security-csrf:^3.4
+ $ composer require symfony/security-csrf
The following snippet adds CSRF protection to the form factory::
@@ -172,14 +173,14 @@ between Twig and several Symfony components:
.. code-block:: terminal
- $ composer require symfony/twig-bridge:^3.4
+ $ composer require symfony/twig-bridge
-The TwigBridge integration provides you with several :doc:`Twig Functions `
-that help you render the HTML widget, label and error for each field
+The TwigBridge integration provides you with several
+:ref:`Twig Functions `
+that help you render the HTML widget, label, help and errors for each field
(as well as a few other things). To configure the integration, you'll need
to bootstrap or access Twig and add the :class:`Symfony\\Bridge\\Twig\\Extension\\FormExtension`::
-
use Symfony\Bridge\Twig\Extension\FormExtension;
use Symfony\Bridge\Twig\Form\TwigRendererEngine;
use Symfony\Component\Form\FormRenderer;
@@ -229,7 +230,7 @@ The exact details of your `Twig Configuration`_ will vary, but the goal is
always to add the :class:`Symfony\\Bridge\\Twig\\Extension\\FormExtension`
to Twig, which gives you access to the Twig functions for rendering forms.
To do this, you first need to create a :class:`Symfony\\Bridge\\Twig\\Form\\TwigRendererEngine`,
-where you define your :ref:`form themes `
+where you define your :doc:`form themes `
(i.e. resources/files that define form HTML markup).
For general details on rendering forms, see :doc:`/form/form_customization`.
@@ -245,13 +246,13 @@ Translation
~~~~~~~~~~~
If you're using the Twig integration with one of the default form theme files
-(e.g. ``form_div_layout.html.twig``), there are 2 Twig filters (``trans``
-and ``transChoice``) that are used for translating form labels, errors, option
+(e.g. ``form_div_layout.html.twig``), there is a Twig filter (``trans``)
+that is used for translating form labels, errors, option
text and other strings.
-To add these Twig filters, you can either use the built-in
+To add the ``trans`` Twig filter, you can either use the built-in
:class:`Symfony\\Bridge\\Twig\\Extension\\TranslationExtension` that integrates
-with Symfony's Translation component, or add the 2 Twig filters yourself,
+with Symfony's Translation component, or add the Twig filter yourself,
via your own Twig extension.
To use the built-in integration, be sure that your project has Symfony's
@@ -260,7 +261,7 @@ installed:
.. code-block:: terminal
- $ composer require symfony/translation symfony/config:^3.4
+ $ composer require symfony/translation symfony/config
Next, add the :class:`Symfony\\Bridge\\Twig\\Extension\\TranslationExtension`
to your ``Twig\Environment`` instance::
@@ -280,7 +281,7 @@ to your ``Twig\Environment`` instance::
'en'
);
- // adds the TranslationExtension (gives us trans and transChoice filters)
+ // adds the TranslationExtension (it gives us trans filter)
$twig->addExtension(new TranslationExtension($translator));
$formFactory = Forms::createFormFactoryBuilder()
@@ -297,7 +298,7 @@ Validation
The Form component comes with tight (but optional) integration with Symfony's
Validator component. If you're using a different solution for validation,
-no problem! Simply take the submitted/bound data of your form (which is an
+no problem! Take the submitted/bound data of your form (which is an
array or object) and pass it through your own validation system.
To use the integration with Symfony's Validator component, first make sure
@@ -305,7 +306,7 @@ it's installed in your application:
.. code-block:: terminal
- $ composer require symfony/validator:^3.4
+ $ composer require symfony/validator
If you're not familiar with Symfony's Validator component, read more about
it: :doc:`/validation`. The Form component comes with a
@@ -369,7 +370,7 @@ 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, just remember that you
+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.
@@ -410,17 +411,17 @@ is created from the form factory.
.. code-block:: php-symfony
- // src/Acme/TaskBundle/Controller/DefaultController.php
- namespace Acme\TaskBundle\Controller;
+ // src/Controller/TaskController.php
+ namespace App\Controller;
- use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
- class DefaultController extends Controller
+ class TaskController extends AbstractController
{
- public function newAction(Request $request)
+ public function new(Request $request)
{
// createFormBuilder is a shortcut to get the "form factory"
// and then call "createBuilder()" on it
@@ -430,7 +431,7 @@ is created from the form factory.
->add('dueDate', DateType::class)
->getForm();
- return $this->render('@AcmeTask/Default/new.html.twig', [
+ return $this->render('task/new.html.twig', [
'form' => $form->createView(),
]);
}
@@ -448,8 +449,7 @@ Setting default Values
~~~~~~~~~~~~~~~~~~~~~~
If you need your form to load with some default values (or you're building
-an "edit" form), simply pass in the default data when creating your form
-builder:
+an "edit" form), pass in the default data when creating your form builder:
.. configuration-block::
@@ -472,16 +472,16 @@ builder:
.. code-block:: php-symfony
- // src/Acme/TaskBundle/Controller/DefaultController.php
- namespace Acme\TaskBundle\Controller;
+ // src/Controller/DefaultController.php
+ namespace App\Controller;
- use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
- class DefaultController extends Controller
+ class DefaultController extends AbstractController
{
- public function newAction(Request $request)
+ public function new(Request $request)
{
$defaults = [
'dueDate' => new \DateTime('tomorrow'),
@@ -509,8 +509,8 @@ Rendering the Form
Now that the form has been created, the next step is to render it. This is
done by passing a special form "view" object to your template (notice the
-``$form->createView()`` in the controller above) and using a set of form
-helper functions:
+``$form->createView()`` in the controller above) and using a set of
+:ref:`form helper functions `:
.. code-block:: html+twig
@@ -524,10 +524,10 @@ helper functions:
:align: center
That's it! By printing ``form_widget(form)``, each field in the form is
-rendered, along with a label and error message (if there is one). As easy
-as this is, it's not very flexible (yet). Usually, you'll want to render each
+rendered, along with a label and error message (if there is one). While this is
+convenient, it's not very flexible (yet). Usually, you'll want to render each
form field individually so you can control how the form looks. You'll learn how
-to do that in the ":doc:`/form/rendering`" section.
+to do that in the :doc:`form customization ` article.
Changing a Form's Method and Action
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -554,15 +554,15 @@ by ``handleRequest()`` to determine whether a form has been submitted):
.. code-block:: php-symfony
- // src/Acme/TaskBundle/Controller/DefaultController.php
- namespace Acme\TaskBundle\Controller;
+ // src/Controller/DefaultController.php
+ namespace App\Controller;
- use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\FormType;
- class DefaultController extends Controller
+ class DefaultController extends AbstractController
{
- public function searchAction()
+ public function search()
{
$formBuilder = $this->createFormBuilder(null, [
'action' => '/search',
@@ -616,16 +616,16 @@ method:
.. code-block:: php-symfony
- // src/Acme/TaskBundle/Controller/DefaultController.php
- namespace Acme\TaskBundle\Controller;
+ // src/Controller/TaskController.php
+ namespace App\Controller;
- use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
- class DefaultController extends Controller
+ class TaskController extends AbstractController
{
- public function newAction(Request $request)
+ public function new(Request $request)
{
$form = $this->createFormBuilder()
->add('task', TextType::class)
@@ -658,7 +658,7 @@ Then:
3) if the form is valid, perform some action and redirect.
Luckily, you don't need to decide whether or not a form has been submitted.
-Just pass the current request to the ``handleRequest()`` method. Then, the Form
+Pass the current request to the ``handleRequest()`` method. Then, the Form
component will do all the necessary work for you.
.. _component-form-intro-validation:
@@ -692,18 +692,18 @@ option when building each field:
.. code-block:: php-symfony
- // src/Acme/TaskBundle/Controller/DefaultController.php
- namespace Acme\TaskBundle\Controller;
+ // src/Controller/DefaultController.php
+ namespace App\Controller;
- use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Type;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
- class DefaultController extends Controller
+ class DefaultController extends AbstractController
{
- public function newAction(Request $request)
+ public function new(Request $request)
{
$form = $this->createFormBuilder()
->add('task', TextType::class, [
@@ -754,6 +754,18 @@ method to access the list of errors. It returns a
// a FormErrorIterator instance representing the form tree structure
$errors = $form->getErrors(true, false);
+Clearing Form Errors
+~~~~~~~~~~~~~~~~~~~~
+
+Any errors can be manually cleared using the
+:method:`Symfony\\Component\\Form\\ClearableErrorsInterface::clearErrors`
+method. This is useful when you'd like to validate the form without showing
+validation errors to the user (i.e. during a partial AJAX submission or
+:doc:`dynamic form modification `).
+
+Because clearing the errors makes the form valid, ``clearErrors()`` should only
+be called after testing whether the form is valid.
+
Learn more
----------
diff --git a/components/http_client.rst b/components/http_client.rst
new file mode 100644
index 00000000000..e0bf92b934c
--- /dev/null
+++ b/components/http_client.rst
@@ -0,0 +1,974 @@
+.. index::
+ single: HttpClient
+ single: Components; HttpClient
+
+The HttpClient Component
+========================
+
+ The HttpClient component is a low-level HTTP client with support for both
+ PHP stream wrappers and cURL. It provides utilities to consume APIs and
+ supports synchronous and asynchronous operations.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/http-client
+
+.. include:: /components/require_autoload.rst.inc
+
+Basic Usage
+-----------
+
+Use the :class:`Symfony\\Component\\HttpClient\\HttpClient` class to create the
+low-level HTTP client that makes requests, like the following ``GET`` request::
+
+ use Symfony\Component\HttpClient\HttpClient;
+
+ $client = HttpClient::create();
+ $response = $client->request('GET', 'https://api.github.com/repos/symfony/symfony-docs');
+
+ $statusCode = $response->getStatusCode();
+ // $statusCode = 200
+ $contentType = $response->getHeaders()['content-type'][0];
+ // $contentType = 'application/json'
+ $content = $response->getContent();
+ // $content = '{"id":521583, "name":"symfony-docs", ...}'
+ $content = $response->toArray();
+ // $content = ['id' => 521583, 'name' => 'symfony-docs', ...]
+
+Performance
+-----------
+
+The component is built for maximum HTTP performance. By design, it is compatible
+with HTTP/2 and with doing concurrent asynchronous streamed and multiplexed
+requests/responses. Even when doing regular synchronous calls, this design
+allows keeping connections to remote hosts open between requests, improving
+performance by saving repetitive DNS resolution, SSL negotiation, etc.
+To leverage all these design benefits, the cURL extension is needed.
+
+Enabling cURL Support
+~~~~~~~~~~~~~~~~~~~~~
+
+This component supports both the native PHP streams and cURL to make the HTTP
+requests. Although both are interchangeable and provide the same features,
+including concurrent requests, HTTP/2 is only supported when using cURL.
+
+``HttpClient::create()`` selects the cURL transport if the `cURL PHP extension`_
+is enabled and falls back to PHP streams otherwise. If you prefer to select
+the transport explicitly, use the following classes to create the client::
+
+ use Symfony\Component\HttpClient\CurlHttpClient;
+ use Symfony\Component\HttpClient\NativeHttpClient;
+
+ // uses native PHP streams
+ $client = new NativeHttpClient();
+
+ // uses the cURL PHP extension
+ $client = new CurlHttpClient();
+
+When using this component in a full-stack Symfony application, this behavior is
+not configurable and cURL will be used automatically if the cURL PHP extension
+is installed and enabled. Otherwise, the native PHP streams will be used.
+
+HTTP/2 Support
+~~~~~~~~~~~~~~
+
+When requesting an ``https`` URL, HTTP/2 is enabled by default if libcurl >= 7.36
+is used. To force HTTP/2 for ``http`` URLs, you need to enable it explicitly via
+the ``http_version`` option::
+
+ $client = HttpClient::create(['http_version' => '2.0']);
+
+Support for HTTP/2 PUSH works out of the box when libcurl >= 7.61 is used with
+PHP >= 7.2.17 / 7.3.4: pushed responses are put into a temporary cache and are
+used when a subsequent request is triggered for the corresponding URLs.
+
+Making Requests
+---------------
+
+The client created with the ``HttpClient`` class provides a single ``request()``
+method to perform all kinds of HTTP requests::
+
+ $response = $client->request('GET', 'https://...');
+ $response = $client->request('POST', 'https://...');
+ $response = $client->request('PUT', 'https://...');
+ // ...
+
+Responses are always asynchronous, so that the call to the method returns
+immediately instead of waiting to receive the response::
+
+ // code execution continues immediately; it doesn't wait to receive the response
+ $response = $client->request('GET', 'http://releases.ubuntu.com/18.04.2/ubuntu-18.04.2-desktop-amd64.iso');
+
+ // getting the response headers waits until they arrive
+ $contentType = $response->getHeaders()['content-type'][0];
+
+ // trying to get the response contents will block the execution until
+ // the full response contents are received
+ $contents = $response->getContent();
+
+This component also supports :ref:`streaming responses `
+for full asynchronous applications.
+
+.. note::
+
+ HTTP compression and chunked transfer encoding are automatically enabled when
+ both your PHP runtime and the remote server support them.
+
+Authentication
+~~~~~~~~~~~~~~
+
+The HTTP client supports different authentication mechanisms. They can be
+defined globally when creating the client (to apply it to all requests) and to
+each request (which overrides any global authentication)::
+
+ // Use the same authentication for all requests to https://example.com/
+ $client = HttpClient::createForBaseUri('https://example.com/', [
+ // HTTP Basic authentication (there are multiple ways of configuring it)
+ 'auth_basic' => ['the-username'],
+ 'auth_basic' => ['the-username', 'the-password'],
+ 'auth_basic' => 'the-username:the-password',
+
+ // HTTP Bearer authentication (also called token authentication)
+ 'auth_bearer' => 'the-bearer-token',
+
+ // Microsoft NTLM authentication (there are multiple ways of configuring it)
+ 'auth_ntlm' => ['the-username'],
+ 'auth_ntlm' => ['the-username', 'the-password'],
+ 'auth_ntlm' => 'the-username:the-password',
+ ]);
+
+ $response = $client->request('GET', 'https://...', [
+ // use a different HTTP Basic authentication only for this request
+ 'auth_basic' => ['the-username', 'the-password'],
+
+ // ...
+ ]);
+
+.. note::
+
+ The NTLM authentication mechanism requires using the cURL transport.
+ By using ``HttpClient::createForBaseUri()``, we ensure that the auth credentials
+ won't be sent to any other hosts than https://example.com/.
+
+Query String Parameters
+~~~~~~~~~~~~~~~~~~~~~~~
+
+You can either append them manually to the requested URL, or define them as an
+associative array via the ``query`` option, that will be merged with the URL::
+
+ // it makes an HTTP GET request to https://httpbin.org/get?token=...&name=...
+ $response = $client->request('GET', 'https://httpbin.org/get', [
+ // these values are automatically encoded before including them in the URL
+ 'query' => [
+ 'token' => '...',
+ 'name' => '...',
+ ],
+ ]);
+
+Headers
+~~~~~~~
+
+Use the ``headers`` option to define both the default headers added to all
+requests and the specific headers for each request::
+
+ // this header is added to all requests made by this client
+ $client = HttpClient::create(['headers' => [
+ 'User-Agent' => 'My Fancy App',
+ ]]);
+
+ // this header is only included in this request and overrides the value
+ // of the same header if defined globally by the HTTP client
+ $response = $client->request('POST', 'https://...', [
+ 'headers' => [
+ 'Content-Type' => 'text/plain',
+ ],
+ ]);
+
+Uploading Data
+~~~~~~~~~~~~~~
+
+This component provides several methods for uploading data using the ``body``
+option. You can use regular strings, closures, iterables and resources and they'll be
+processed automatically when making the requests::
+
+ $response = $client->request('POST', 'https://...', [
+ // defining data using a regular string
+ 'body' => 'raw data',
+
+ // defining data using an array of parameters
+ 'body' => ['parameter1' => 'value1', '...'],
+
+ // using a closure to generate the uploaded data
+ 'body' => function (int $size): string {
+ // ...
+ },
+
+ // using a resource to get the data from it
+ 'body' => fopen('/path/to/file', 'r'),
+ ]);
+
+When uploading data with the ``POST`` method, if you don't define the
+``Content-Type`` HTTP header explicitly, Symfony assumes that you're uploading
+form data and adds the required
+``'Content-Type: application/x-www-form-urlencoded'`` header for you.
+
+When the ``body`` option is set as a closure, it will be called several times until
+it returns the empty string, which signals the end of the body. Each time, the
+closure should return a string smaller than the amount requested as argument.
+
+A generator or any ``Traversable`` can also be used instead of a closure.
+
+.. tip::
+
+ When uploading JSON payloads, use the ``json`` option instead of ``body``. The
+ given content will be JSON-encoded automatically and the request will add the
+ ``Content-Type: application/json`` automatically too::
+
+ $response = $client->request('POST', 'https://...', [
+ 'json' => ['param1' => 'value1', '...'],
+ ]);
+
+ $decodedPayload = $response->toArray();
+
+To submit a form with file uploads, it is your responsibility to encode the body
+according to the ``multipart/form-data`` content-type. The
+:doc:`Symfony Mime ` component makes it a few lines of code::
+
+ use Symfony\Component\Mime\Part\DataPart;
+ use Symfony\Component\Mime\Part\Multipart\FormDataPart;
+
+ $formFields = [
+ 'regular_field' => 'some value',
+ 'file_field' => DataPart::fromPath('/path/to/uploaded/file'),
+ ];
+ $formData = new FormDataPart($formFields);
+ $client->request('POST', 'https://...', [
+ 'headers' => $formData->getPreparedHeaders()->toArray(),
+ 'body' => $formData->bodyToIterable(),
+ ]);
+
+Cookies
+~~~~~~~
+
+The HTTP client provided by this component is stateless but handling cookies
+requires a stateful storage (because responses can update cookies and they must
+be used for subsequent requests). That's why this component doesn't handle
+cookies automatically.
+
+You can either handle cookies yourself using the ``Cookie`` HTTP header or use
+the :doc:`BrowserKit component ` which provides this
+feature and integrates seamlessly with the HttpClient component.
+
+Redirects
+~~~~~~~~~
+
+By default, the HTTP client follows redirects, up to a maximum of 20, when
+making a request. Use the ``max_redirects`` setting to configure this behavior
+(if the number of redirects is higher than the configured value, you'll get a
+:class:`Symfony\\Component\\HttpClient\\Exception\\RedirectionException`)::
+
+ $response = $client->request('GET', 'https://...', [
+ // 0 means to not follow any redirect
+ 'max_redirects' => 0,
+ ]);
+
+HTTP Proxies
+~~~~~~~~~~~~
+
+By default, this component honors the standard environment variables that your
+Operating System defines to direct the HTTP traffic through your local proxy.
+This means there is usually nothing to configure to have the client work with
+proxies, provided these env vars are properly configured.
+
+You can still set or override these settings using the ``proxy`` and ``no_proxy``
+options:
+
+* ``proxy`` should be set to the ``http://...`` URL of the proxy to get through
+
+* ``no_proxy`` disables the proxy for a comma-separated list of hosts that do not
+ require it to get reached.
+
+Progress Callback
+~~~~~~~~~~~~~~~~~
+
+By providing a callable to the ``on_progress`` option, one can track
+uploads/downloads as they complete. This callback is guaranteed to be called on
+DNS resolution, on arrival of headers and on completion; additionally it is
+called when new data is uploaded or downloaded and at least once per second::
+
+ $response = $client->request('GET', 'https://...', [
+ 'on_progress' => function (int $dlNow, int $dlSize, array $info): void {
+ // $dlNow is the number of bytes downloaded so far
+ // $dlSize is the total size to be downloaded or -1 if it is unknown
+ // $info is what $response->getInfo() would return at this very time
+ },
+ ]);
+
+Any exceptions thrown from the callback will be wrapped in an instance of
+``TransportExceptionInterface`` and will abort the request.
+
+Advanced Options
+~~~~~~~~~~~~~~~~
+
+The :class:`Symfony\\Contracts\\HttpClient\\HttpClientInterface` defines all the
+options you might need to take full control of the way the request is performed,
+including DNS pre-resolution, SSL parameters, public key pinning, etc.
+
+Processing Responses
+--------------------
+
+The response returned by all HTTP clients is an object of type
+:class:`Symfony\\Contracts\\HttpClient\\ResponseInterface` which provides the
+following methods::
+
+ $response = $client->request('GET', 'https://...');
+
+ // gets the HTTP status code of the response
+ $statusCode = $response->getStatusCode();
+
+ // gets the HTTP headers as string[][] with the header names lower-cased
+ $headers = $response->getHeaders();
+
+ // gets the response body as a string
+ $content = $response->getContent();
+
+ // casts the response JSON contents to a PHP array
+ $content = $response->toArray();
+
+ // casts the response content to a PHP stream resource
+ $content = $response->toStream();
+
+ // cancels the request/response
+ $response->cancel();
+
+ // returns info coming from the transport layer, such as "response_headers",
+ // "redirect_count", "start_time", "redirect_url", etc.
+ $httpInfo = $response->getInfo();
+ // you can get individual info too
+ $startTime = $response->getInfo('start_time');
+
+ // returns detailed logs about the requests and responses of the HTTP transaction
+ $httpLogs = $response->getInfo('debug');
+
+.. note::
+
+ ``$response->getInfo()`` is non-blocking: it returns *live* information
+ about the response. Some of them might not be known yet (e.g. ``http_code``)
+ when you'll call it.
+
+.. _http-client-streaming-responses:
+
+Streaming Responses
+~~~~~~~~~~~~~~~~~~~
+
+Call the ``stream()`` method of the HTTP client to get *chunks* of the
+response sequentially instead of waiting for the entire response::
+
+ $url = 'https://releases.ubuntu.com/18.04.1/ubuntu-18.04.1-desktop-amd64.iso';
+ $response = $client->request('GET', $url);
+
+ // Responses are lazy: this code is executed as soon as headers are received
+ if (200 !== $response->getStatusCode()) {
+ throw new \Exception('...');
+ }
+
+ // get the response contents in chunk and save them in a file
+ // response chunks implement Symfony\Contracts\HttpClient\ChunkInterface
+ $fileHandler = fopen('/ubuntu.iso', 'w');
+ foreach ($client->stream($response) as $chunk) {
+ fwrite($fileHandler, $chunk->getContent());
+ }
+
+.. note::
+
+ By default, ``text/*``, JSON and XML response bodies are buffered in a local
+ ``php://temp`` stream. You can control this behavior by using the ``buffer``
+ option: set it to ``true``/``false`` to enable/disable buffering, or to a
+ closure that should return the same based on the response headers it receives
+ as argument.
+
+Canceling Responses
+~~~~~~~~~~~~~~~~~~~
+
+To abort a request (e.g. because it didn't complete in due time, or you want to
+fetch only the first bytes of the response, etc.), you can either use the
+``cancel()`` method of ``ResponseInterface``::
+
+ $response->cancel()
+
+Or throw an exception from a progress callback::
+
+ $response = $client->request('GET', 'https://...', [
+ 'on_progress' => function (int $dlNow, int $dlSize, array $info): void {
+ // ...
+
+ throw new \MyException();
+ },
+ ]);
+
+The exception will be wrapped in an instance of ``TransportExceptionInterface``
+and will abort the request.
+
+In case the response was canceled using ``$response->cancel()``,
+``$response->getInfo('canceled')`` will return ``true``.
+
+Handling Exceptions
+~~~~~~~~~~~~~~~~~~~
+
+When the HTTP status code of the response is in the 300-599 range (i.e. 3xx,
+4xx or 5xx) your code is expected to handle it. If you don't do that, the
+``getHeaders()`` and ``getContent()`` methods throw an appropriate exception::
+
+ // the response of this request will be a 403 HTTP error
+ $response = $client->request('GET', 'https://httpbin.org/status/403');
+
+ // this code results in a Symfony\Component\HttpClient\Exception\ClientException
+ // because it doesn't check the status code of the response
+ $content = $response->getContent();
+
+ // pass FALSE as the optional argument to not throw an exception and return
+ // instead the original response content (even if it's an error message)
+ $content = $response->getContent(false);
+
+Concurrent Requests
+-------------------
+
+Thanks to responses being lazy, requests are always managed concurrently.
+On a fast enough network, the following code makes 379 requests in less than
+half a second when cURL is used::
+
+ use Symfony\Component\HttpClient\CurlHttpClient;
+
+ $client = new CurlHttpClient();
+
+ $responses = [];
+
+ for ($i = 0; $i < 379; ++$i) {
+ $uri = "https://http2.akamai.com/demo/tile-$i.png";
+ $responses[] = $client->request('GET', $uri);
+ }
+
+ foreach ($responses as $response) {
+ $content = $response->getContent();
+ // ...
+ }
+
+As you can read in the first "for" loop, requests are issued but are not consumed
+yet. That's the trick when concurrency is desired: requests should be sent
+first and be read later on. This will allow the client to monitor all pending
+requests while your code waits for a specific one, as done in each iteration of
+the above "foreach" loop.
+
+Multiplexing Responses
+~~~~~~~~~~~~~~~~~~~~~~
+
+If you look again at the snippet above, responses are read in requests' order.
+But maybe the 2nd response came back before the 1st? Fully asynchronous operations
+require being able to deal with the responses in whatever order they come back.
+
+In order to do so, the ``stream()`` method of HTTP clients accepts a list of
+responses to monitor. As mentioned :ref:`previously `,
+this method yields response chunks as they arrive from the network. By replacing
+the "foreach" in the snippet with this one, the code becomes fully async::
+
+ foreach ($client->stream($responses) as $response => $chunk) {
+ if ($chunk->isFirst()) {
+ // headers of $response just arrived
+ // $response->getHeaders() is now a non-blocking call
+ } elseif ($chunk->isLast()) {
+ // the full content of $response just completed
+ // $response->getContent() is now a non-blocking call
+ } else {
+ // $chunk->getContent() will return a piece
+ // of the response body that just arrived
+ }
+ }
+
+.. tip::
+
+ Use the ``user_data`` option combined with ``$response->getInfo('user_data')``
+ to track the identity of the responses in your foreach loops.
+
+Dealing with Network Timeouts
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This component allows dealing with both request and response timeouts.
+
+A timeout can happen when e.g. DNS resolution takes too much time, when the TCP
+connection cannot be opened in the given time budget, or when the response
+content pauses for too long. This can be configured with the ``timeout`` request
+option::
+
+ // A TransportExceptionInterface will be issued if nothing
+ // happens for 2.5 seconds when accessing from the $response
+ $response = $client->request('GET', 'https://...', ['timeout' => 2.5]);
+
+The ``default_socket_timeout`` PHP ini setting is used if the option is not set.
+
+The option can be overridden by using the 2nd argument of the ``stream()`` method.
+This allows monitoring several responses at once and applying the timeout to all
+of them in a group. If all responses become inactive for the given duration, the
+method will yield a special chunk whose ``isTimeout()`` will return ``true``::
+
+ foreach ($client->stream($responses, 1.5) as $response => $chunk) {
+ if ($chunk->isTimeout()) {
+ // $response staled for more than 1.5 seconds
+ }
+ }
+
+A timeout is not necessarily an error: you can decide to stream again the
+response and get remaining contents that might come back in a new timeout, etc.
+
+.. tip::
+
+ Passing ``0`` as timeout allows monitoring responses in a non-blocking way.
+
+.. note::
+
+ Timeouts control how long one is willing to wait *while the HTTP transaction
+ is idle*. Big responses can last as long as needed to complete, provided they
+ remain active during the transfer and never pause for longer than specified.
+
+ Use the ``max_duration`` option to limit the time a full request/response can last.
+
+Dealing with Network Errors
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Network errors (broken pipe, failed DNS resolution, etc.) are thrown as instances
+of :class:`Symfony\\Contracts\\HttpClient\\Exception\\TransportExceptionInterface`.
+
+First of all, you don't *have* to deal with them: letting errors bubble to your
+generic exception-handling stack might be really fine in most use cases.
+
+If you want to handle them, here is what you need to know:
+
+To catch errors, you need to wrap calls to ``$client->request()`` but also calls
+to any methods of the returned responses. This is because responses are lazy, so
+that network errors can happen when calling e.g. ``getStatusCode()`` too::
+
+ try {
+ // both lines can potentially throw
+ $response = $client->request(...);
+ $headers = $response->getHeaders();
+ // ...
+ } catch (TransportExceptionInterface $e) {
+ // ...
+ }
+
+.. note::
+
+ Because ``$response->getInfo()`` is non-blocking, it shouldn't throw by design.
+
+When multiplexing responses, you can deal with errors for individual streams by
+catching ``TransportExceptionInterface`` in the foreach loop::
+
+ foreach ($client->stream($responses) as $response => $chunk) {
+ try {
+ if ($chunk->isLast()) {
+ // ... do something with $response
+ }
+ } catch (TransportExceptionInterface $e) {
+ // ...
+ }
+ }
+
+Caching Requests and Responses
+------------------------------
+
+This component provides a :class:`Symfony\\Component\\HttpClient\\CachingHttpClient`
+decorator that allows caching responses and serving them from the local storage
+for next requests. The implementation leverages the
+:class:`Symfony\\Component\\HttpKernel\\HttpCache\\HttpCache` class under the hood
+so that the :doc:`HttpKernel component ` needs to be
+installed in your application::
+
+ use Symfony\Component\HttpClient\CachingHttpClient;
+ use Symfony\Component\HttpClient\HttpClient;
+ use Symfony\Component\HttpKernel\HttpCache\Store;
+
+ $store = new Store('/path/to/cache/storage/');
+ $client = HttpClient::create();
+ $client = new CachingHttpClient($client, $store);
+
+ // this won't hit the network if the resource is already in the cache
+ $response = $client->request('GET', 'https://example.com/cacheable-resource');
+
+``CachingHttpClient`` accepts a third argument to set the options of the ``HttpCache``.
+
+Scoping Client
+--------------
+
+It's common that some of the HTTP client options depend on the URL of the
+request (e.g. you must set some headers when making requests to GitHub API but
+not for other hosts). If that's your case, this component provides a special
+HTTP client via the :class:`Symfony\\Component\\HttpClient\\ScopingHttpClient`
+class to autoconfigure the HTTP client based on the requested URL::
+
+ use Symfony\Component\HttpClient\HttpClient;
+ use Symfony\Component\HttpClient\ScopingHttpClient;
+
+ $client = HttpClient::create();
+ $client = new ScopingHttpClient($client, [
+ // the options defined as values apply only to the URLs matching
+ // the regular expressions defined as keys
+ 'https://api\.github\.com/' => [
+ 'headers' => [
+ 'Accept' => 'application/vnd.github.v3+json',
+ 'Authorization' => 'token '.$githubToken,
+ ],
+ ],
+ // ...
+ ]);
+
+You can define several scopes, so that each set of options is added only if a
+requested URL matches one of the regular expressions provided as keys.
+
+If the request URL is relative (because you use the ``base_uri`` option), the
+scoping HTTP client can't make a match. That's why you can define a third
+optional argument in its constructor which will be considered the default
+regular expression applied to relative URLs::
+
+ // ...
+
+ $client = new ScopingHttpClient($client,
+ [
+ 'https://api\.github\.com/' => [
+ 'base_uri' => 'https://api.github.com/',
+ // ...
+ ],
+ ],
+ // this is the index in the previous array that defines
+ // the base URI that shoud be used to resolve relative URLs
+ 'https://api\.github\.com/'
+ );
+
+The above example can be reduced to a simpler call::
+
+ // ...
+
+ $client = ScopingHttpClient::forBaseUri($client, 'https://api.github.com/', [
+ // ...
+ ]);
+
+This way, the provided options will be used only if the requested URL is relative
+or if it matches the ``https://api.github.com/`` base URI.
+
+Interoperability
+----------------
+
+The component is interoperable with four different abstractions for HTTP
+clients: `Symfony Contracts`_, `PSR-18`_, `HTTPlug`_ v1/v2 and native PHP streams.
+If your application uses libraries that need any of them, the component is compatible
+with all of them. They also benefit from :ref:`autowiring aliases `
+when the :ref:`framework bundle ` is used.
+
+If you are writing or maintaining a library that makes HTTP requests, you can
+decouple it from any specific HTTP client implementations by coding against
+either Symfony Contracts (recommended), PSR-18 or HTTPlug v2.
+
+Symfony Contracts
+~~~~~~~~~~~~~~~~~
+
+The interfaces found in the ``symfony/http-client-contracts`` package define
+the primary abstractions implemented by the component. Its entry point is the
+:class:`Symfony\\Contracts\\HttpClient\\HttpClientInterface`. That's the
+interface you need to code against when a client is needed::
+
+ use Symfony\Contracts\HttpClient\HttpClientInterface;
+
+ class MyApiLayer
+ {
+ private $client;
+
+ public function __construct(HttpClientInterface $client)
+ {
+ $this->client = $client;
+ }
+
+ // [...]
+ }
+
+All request options mentioned above (e.g. timeout management) are also defined
+in the wordings of the interface, so that any compliant implementations (like
+this component) is guaranteed to provide them. That's a major difference with
+the other abstractions, which provide none related to the transport itself.
+
+Another major feature covered by the Symfony Contracts is async/multiplexing,
+as described in the previous sections.
+
+PSR-18 and PSR-17
+~~~~~~~~~~~~~~~~~
+
+This component implements the `PSR-18`_ (HTTP Client) specifications via the
+:class:`Symfony\\Component\\HttpClient\\Psr18Client` class, which is an adapter
+to turn a Symfony ``HttpClientInterface`` into a PSR-18 ``ClientInterface``.
+This class also implements the relevant methods of `PSR-17`_ to ease creating
+request objects.
+
+To use it, you need the ``psr/http-client`` package and a `PSR-17`_ implementation:
+
+.. code-block:: terminal
+
+ # installs the PSR-18 ClientInterface
+ $ composer require psr/http-client
+
+ # installs an efficient implementation of response and stream factories
+ # with autowiring aliases provided by Symfony Flex
+ $ composer require nyholm/psr7
+
+ # alternatively, install the php-http/discovery package to auto-discover
+ # any already installed implementations from common vendors:
+ # composer require php-http/discovery
+
+Now you can make HTTP requests with the PSR-18 client as follows::
+
+ use Symfony\Component\HttpClient\Psr18Client;
+
+ $client = new Psr18Client();
+
+ $url = 'https://symfony.com/versions.json';
+ $request = $client->createRequest('GET', $url);
+ $response = $client->sendRequest($request);
+
+ $content = json_decode($response->getBody()->getContents(), true);
+
+HTTPlug
+~~~~~~~
+
+The `HTTPlug`_ v1 specification was published before PSR-18 and is superseded by
+it. As such, you should not use it in newly written code. The component is still
+interoperable with libraries that require it thanks to the
+:class:`Symfony\\Component\\HttpClient\\HttplugClient` class. Similarly to
+``Psr18Client`` implementing relevant parts of PSR-17, ``HttplugClient`` also
+implements the factory methods defined in the related ``php-http/message-factory``
+package.
+
+.. code-block:: terminal
+
+ # Let's suppose php-http/httplug is already required by the lib you want to use
+
+ # installs an efficient implementation of response and stream factories
+ # with autowiring aliases provided by Symfony Flex
+ $ composer require nyholm/psr7
+
+ # alternatively, install the php-http/discovery package to auto-discover
+ # any already installed implementations from common vendors:
+ # composer require php-http/discovery
+
+Let's say you want to instantiate a class with the following constructor,
+that requires HTTPlug dependencies::
+
+ use Http\Client\HttpClient;
+ use Http\Message\RequestFactory;
+ use Http\Message\StreamFactory;
+
+ class SomeSdk
+ {
+ public function __construct(
+ HttpClient $httpClient,
+ RequestFactory $requestFactory,
+ StreamFactory $streamFactory
+ )
+ // [...]
+ }
+
+Because ``HttplugClient`` implements the three interfaces, you can use it this way::
+
+ use Symfony\Component\HttpClient\HttplugClient;
+
+ $httpClient = new HttplugClient();
+ $apiClient = new SomeSdk($httpClient, $httpClient, $httpClient);
+
+If you'd like to work with promises, ``HttplugClient`` also implements the
+``HttpAsyncClient`` interface. To use it, you need to install the
+``guzzlehttp/promises`` package:
+
+.. code-block:: terminal
+
+ $ composer require guzzlehttp/promises
+
+Then you're ready to go::
+
+ use Psr\Http\Message\ResponseInterface;
+ use Symfony\Component\HttpClient\HttplugClient;
+
+ $httpClient = new HttplugClient();
+ $request = $httpClient->createRequest('GET', 'https://my.api.com/');
+ $promise = $httpClient->sendRequest($request)
+ ->then(
+ function (ResponseInterface $response) {
+ echo 'Got status '.$response->getStatusCode();
+
+ return $response;
+ },
+ function (\Throwable $exception) {
+ echo 'Error: '.$exception->getMessage();
+
+ throw $exception;
+ }
+ );
+
+ // after you're done with sending several requests,
+ // you must wait for them to complete concurrently
+
+ // wait for a specific promise to resolve while monitoring them all
+ $response = $promise->wait();
+
+ // wait maximum 1 second for pending promises to resolve
+ $httpClient->wait(1.0);
+
+ // wait for all remaining promises to resolve
+ $httpClient->wait();
+
+Native PHP Streams
+~~~~~~~~~~~~~~~~~~
+
+Responses implementing :class:`Symfony\\Contracts\\HttpClient\\ResponseInterface`
+can be cast to native PHP streams with
+:method:`Symfony\\Component\\HttpClient\\Response\\StreamWrapper::createResource``.
+This allows using them where native PHP streams are needed::
+
+ use Symfony\Component\HttpClient\HttpClient;
+ use Symfony\Component\HttpClient\Response\StreamWrapper;
+
+ $client = HttpClient::create();
+ $response = $client->request('GET', 'https://symfony.com/versions.json');
+
+ $streamResource = StreamWrapper::createResource($response, $client);
+
+ // alternatively and contrary to the previous one, this returns
+ // a resource that is seekable and potentially stream_select()-able
+ $streamResource = $response->toStream();
+
+ echo stream_get_contents($streamResource); // outputs the content of the response
+
+ // later on if you need to, you can access the response from the stream
+ $response = stream_get_meta_data($streamResource)['wrapper_data']->getResponse();
+
+Symfony Framework Integration
+-----------------------------
+
+When using this component in a full-stack Symfony application, you can configure
+multiple clients with different configurations and inject them into your services.
+
+Configuration
+~~~~~~~~~~~~~
+
+Use the ``framework.http_client`` key to configure the default HTTP client used
+in the application. Check out the full
+:ref:`http_client config reference ` to learn about all
+the available config options:
+
+.. code-block:: yaml
+
+ # config/packages/framework.yaml
+ framework:
+ # ...
+ http_client:
+ max_host_connections: 10
+ default_options:
+ max_redirects: 7
+
+If you want to define multiple HTTP clients, use this other expanded configuration:
+
+.. code-block:: yaml
+
+ # config/packages/framework.yaml
+ framework:
+ # ...
+ http_client:
+ scoped_clients:
+ crawler.client:
+ headers: { 'X-Powered-By': 'ACME App' }
+ http_version: '1.0'
+ some_api.client:
+ max_redirects: 5
+
+Injecting the HTTP Client into Services
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If your application only needs one HTTP client, you can inject the default one
+into any services by type-hinting a constructor argument with the
+:class:`Symfony\\Contracts\\HttpClient\\HttpClientInterface`::
+
+ use Symfony\Contracts\HttpClient\HttpClientInterface;
+
+ class SomeService
+ {
+ private $client;
+
+ public function __construct(HttpClientInterface $client)
+ {
+ $this->client = $client;
+ }
+ }
+
+If you have several clients, you must use any of the methods defined by Symfony
+to :ref:`choose a specific service `. Each client
+has a unique service named after its configuration.
+
+Each scoped client also defines a corresponding named autowiring alias.
+If you use for example
+``Symfony\Contracts\HttpClient\HttpClientInterface $myApiClient``
+as the type and name of an argument, autowiring will inject the ``my_api.client``
+service into your autowired classes.
+
+Testing HTTP Clients and Responses
+----------------------------------
+
+This component includes the ``MockHttpClient`` and ``MockResponse`` classes to
+use them in tests that need an HTTP client which doesn't make actual HTTP
+requests.
+
+The first way of using ``MockHttpClient`` is to pass a list of responses to its
+constructor. These will be yielded in order when requests are made::
+
+ use Symfony\Component\HttpClient\MockHttpClient;
+ use Symfony\Component\HttpClient\Response\MockResponse;
+
+ $responses = [
+ new MockResponse($body1, $info1),
+ new MockResponse($body2, $info2),
+ ];
+
+ $client = new MockHttpClient($responses);
+ // responses are returned in the same order as passed to MockHttpClient
+ $response1 = $client->request('...'); // returns $responses[0]
+ $response2 = $client->request('...'); // returns $responses[1]
+
+Another way of using ``MockHttpClient`` is to pass a callback that generates the
+responses dynamically when it's called::
+
+ use Symfony\Component\HttpClient\MockHttpClient;
+ use Symfony\Component\HttpClient\Response\MockResponse;
+
+ $callback = function ($method, $url, $options) {
+ return new MockResponse('...');
+ };
+
+ $client = new MockHttpClient($callback);
+ $response = $client->request('...'); // calls $callback to get the response
+
+The responses provided to the mock client don't have to be instances of
+``MockResponse``. Any class implementing ``ResponseInterface`` will work (e.g.
+``$this->createMock(ResponseInterface::class)``).
+
+However, using ``MockResponse`` allows simulating chunked responses and timeouts::
+
+ $body = function () {
+ yield 'hello';
+ // empty strings are turned into timeouts so that they are easy to test
+ yield '';
+ yield 'world';
+ };
+
+ $mockResponse = new MockResponse($body());
+
+.. _`cURL PHP extension`: https://php.net/curl
+.. _`PSR-17`: https://www.php-fig.org/psr/psr-17/
+.. _`PSR-18`: https://www.php-fig.org/psr/psr-18/
+.. _`HTTPlug`: https://github.com/php-http/httplug/#readme
+.. _`Symfony Contracts`: https://github.com/symfony/contracts
diff --git a/components/http_foundation.rst b/components/http_foundation.rst
index 311ecbe08af..0d63d643e08 100644
--- a/components/http_foundation.rst
+++ b/components/http_foundation.rst
@@ -21,7 +21,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/http-foundation:^3.4
+ $ composer require symfony/http-foundation
.. include:: /components/require_autoload.rst.inc
@@ -57,6 +57,8 @@ which is almost equivalent to the more verbose, but also more flexible,
$_SERVER
);
+.. _accessing-request-data:
+
Accessing Request Data
~~~~~~~~~~~~~~~~~~~~~~
@@ -240,6 +242,36 @@ the
method tells you if the request contains a session which was started in one of
the previous requests.
+Processing HTTP Headers
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Processing HTTP headers is not a trivial task because of the escaping and white
+space handling of their contents. Symfony provides a
+:class:`Symfony\\Component\\HttpFoundation\\HeaderUtils` class that abstracts
+this complexity and defines some methods for the most common tasks::
+
+ use Symfony\Component\HttpFoundation\HeaderUtils;
+
+ // Splits an HTTP header by one or more separators
+ HeaderUtils::split('da, en-gb;q=0.8', ',;');
+ // => [['da'], ['en-gb','q=0.8']]
+
+ // Combines an array of arrays into one associative array
+ HeaderUtils::combine([['foo', 'abc'], ['bar']]);
+ // => ['foo' => 'abc', 'bar' => true]
+
+ // Joins an associative array into a string for use in an HTTP header
+ HeaderUtils::toString(['foo' => 'abc', 'bar' => true, 'baz' => 'a b c'], ',');
+ // => 'foo=abc, bar, baz="a b c"'
+
+ // Encodes a string as a quoted string, if necessary
+ HeaderUtils::quote('foo "bar"');
+ // => '"foo \"bar\""'
+
+ // Decodes a quoted string
+ HeaderUtils::unquote('"foo \"bar\""');
+ // => 'foo "bar"'
+
Accessing ``Accept-*`` Headers Data
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -275,6 +307,33 @@ If you need to get full access to parsed data from ``Accept``, ``Accept-Language
$acceptHeaders = AcceptHeader::fromString($request->headers->get('Accept'))
->all();
+The default values that can be optionally included in the ``Accept-*`` headers
+are also supported::
+
+ $acceptHeader = 'text/plain;q=0.5, text/html, text/*;q=0.8, */*;q=0.3';
+ $accept = AcceptHeader::fromString($acceptHeader);
+
+ $quality = $accept->get('text/xml')->getQuality(); // $quality = 0.8
+ $quality = $accept->get('application/xml')->getQuality(); // $quality = 0.3
+
+Anonymizing IP Addresses
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+An increasingly common need for applications to comply with user protection
+regulations is to anonymize IP addresses before logging and storing them for
+analysis purposes. Use the ``anonymize()`` method from the
+:class:`Symfony\\Component\\HttpFoundation\\IpUtils` to do that::
+
+ use Symfony\Component\HttpFoundation\IpUtils;
+
+ $ipv4 = '123.234.235.236';
+ $anonymousIpv4 = IPUtils::anonymize($ipv4);
+ // $anonymousIpv4 = '123.234.235.0'
+
+ $ipv6 = '2a01:198:603:10:396e:4789:8e99:890f';
+ $anonymousIpv6 = IPUtils::anonymize($ipv6);
+ // $anonymousIpv6 = '2a01:198:603:10::'
+
Accessing other Data
~~~~~~~~~~~~~~~~~~~~
@@ -291,7 +350,7 @@ represents an HTTP message. But when moving from a legacy system, adding
methods or changing some default behavior might help. In that case, register a
PHP callable that is able to create an instance of your ``Request`` class::
- use AppBundle\Http\SpecialRequest;
+ use App\Http\SpecialRequest;
use Symfony\Component\HttpFoundation\Request;
Request::setFactory(function (
@@ -361,7 +420,7 @@ incompatibility with the HTTP specification (e.g. a wrong ``Content-Type`` heade
$response->prepare($request);
-Sending the response to the client is then as simple as calling
+Sending the response to the client is done by calling the method
:method:`Symfony\\Component\\HttpFoundation\\Response::send`::
$response->send();
@@ -374,7 +433,7 @@ attribute::
use Symfony\Component\HttpFoundation\Cookie;
- $response->headers->setCookie(new Cookie('foo', 'bar'));
+ $response->headers->setCookie(Cookie::create('foo', 'bar'));
The
:method:`Symfony\\Component\\HttpFoundation\\ResponseHeaderBag::setCookie`
@@ -388,10 +447,6 @@ Note you can create a
:class:`Symfony\\Component\\HttpFoundation\\Cookie` object from a raw header
value using :method:`Symfony\\Component\\HttpFoundation\\Cookie::fromString`.
-.. versionadded:: 3.3
-
- The ``Cookie::fromString()`` method was introduced in Symfony 3.3.
-
Managing the HTTP Cache
~~~~~~~~~~~~~~~~~~~~~~~
@@ -410,6 +465,13 @@ of methods to manipulate the HTTP headers related to the cache:
* :method:`Symfony\\Component\\HttpFoundation\\Response::setEtag`;
* :method:`Symfony\\Component\\HttpFoundation\\Response::setVary`;
+.. note::
+
+ The methods :method:`Symfony\\Component\\HttpFoundation\\Response::setExpires`,
+ :method:`Symfony\\Component\\HttpFoundation\\Response::setLastModified` and
+ :method:`Symfony\\Component\\HttpFoundation\\Response::setDate` accept any
+ object that implements ``\DateTimeInterface``, including immutable date objects.
+
The :method:`Symfony\\Component\\HttpFoundation\\Response::setCache` method
can be used to set the most commonly used cache information in one method
call::
@@ -491,17 +553,18 @@ Serving Files
When sending a file, you must add a ``Content-Disposition`` header to your
response. While creating this header for basic file downloads is straightforward,
using non-ASCII filenames is more involved. The
-:method:`Symfony\\Component\\HttpFoundation\\ResponseHeaderBag::makeDisposition`
+:method:`Symfony\\Component\\HttpFoundation\\HeaderUtils::makeDisposition`
abstracts the hard work behind a simple API::
+ use Symfony\Component\HttpFoundation\HeaderUtils;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
$fileContent = ...; // the generated file content
$response = new Response($fileContent);
- $disposition = $response->headers->makeDisposition(
- ResponseHeaderBag::DISPOSITION_ATTACHMENT,
+ $disposition = HeaderUtils::makeDisposition(
+ HeaderUtils::DISPOSITION_ATTACHMENT,
'foo.pdf'
);
@@ -558,10 +621,6 @@ It is possible to delete the file after the request is sent with the
:method:`Symfony\\Component\\HttpFoundation\\BinaryFileResponse::deleteFileAfterSend` method.
Please note that this will not work when the ``X-Sendfile`` header is set.
-.. versionadded:: 3.3
-
- The ``Stream`` class was introduced in Symfony 3.3.
-
If the size of the served file is unknown (e.g. because it's being generated on the fly,
or because a PHP stream filter is registered on it, etc.), you can pass a ``Stream``
instance to ``BinaryFileResponse``. This will disable ``Range`` and ``Content-Length``
@@ -613,11 +672,6 @@ class, which can make this even easier::
// if the data to send is already encoded in JSON
$response = JsonResponse::fromJsonString('{ "data": 123 }');
-.. versionadded:: 3.2
-
- The :method:`Symfony\\Component\\HttpFoundation\\JsonResponse::fromJsonString`
- method was introduced in Symfony 3.2.
-
The ``JsonResponse`` class sets the ``Content-Type`` header to
``application/json`` and encodes your data to JSON when needed.
diff --git a/components/http_foundation/session_configuration.rst b/components/http_foundation/session_configuration.rst
index 8f8ce6e8ce7..8ad26fd83c6 100644
--- a/components/http_foundation/session_configuration.rst
+++ b/components/http_foundation/session_configuration.rst
@@ -72,8 +72,9 @@ The Symfony HttpFoundation component provides some by default and these can
serve as examples if you wish to write your own.
* :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\PdoSessionHandler`
-* :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MemcacheSessionHandler`
* :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MemcachedSessionHandler`
+* :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MigratingSessionHandler`
+* :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\RedisSessionHandler`
* :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MongoDbSessionHandler`
* :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NullSessionHandler`
@@ -87,6 +88,29 @@ Example usage::
$sessionStorage = new NativeSessionStorage([], new PdoSessionHandler($pdo));
$session = new Session($sessionStorage);
+Migrating Between Save Handlers
+-------------------------------
+
+If your application changes the way sessions are stored, use the
+:class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MigratingSessionHandler`
+to migrate between old and new save handlers without losing session data.
+
+This is the recommended migration workflow:
+
+#. Switch to the migrating handler, with your new handler as the write-only one.
+ The old handler behaves as usual and sessions get written to the new one::
+
+ $sessionStorage = new MigratingSessionHandler($oldSessionStorage, $newSessionStorage);
+
+#. After your session gc period, verify that the data in the new handler is correct.
+#. Update the migrating handler to use the old handler as the write-only one, so
+ the sessions will now be read from the new handler. This step allows easier rollbacks::
+
+ $sessionStorage = new MigratingSessionHandler($newSessionStorage, $oldSessionStorage);
+
+#. After verifying that the sessions in your application are working, switch
+ from the migrating handler to the new handler.
+
Configuring PHP Sessions
~~~~~~~~~~~~~~~~~~~~~~~~
@@ -148,7 +172,7 @@ configuration:
.. code-block:: yaml
- # config.yml
+ # config/packages/framework.yaml
framework:
session:
gc_probability: null
@@ -262,58 +286,6 @@ particular cookie by reading the ``getLifetime()`` method::
The expiry time of the cookie can be determined by adding the created
timestamp and the lifetime.
-PHP 5.4 Compatibility
-~~~~~~~~~~~~~~~~~~~~~
-
-Since PHP 5.4.0, :phpclass:`SessionHandler` and :phpclass:`SessionHandlerInterface`
-are available. Symfony provides forward compatibility for the :phpclass:`SessionHandlerInterface`
-so it can be used under PHP 5.3. This greatly improves interoperability with other
-libraries.
-
-:phpclass:`SessionHandler` is a special PHP internal class which exposes native save
-handlers to PHP user-space.
-
-In order to provide a solution for those using PHP 5.4, Symfony has a special
-class called :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeSessionHandler`
-which under PHP 5.4, extends from ``\SessionHandler`` and under PHP 5.3 is just a
-empty base class. This provides some interesting opportunities to leverage
-PHP 5.4 functionality if it is available.
-
-Save Handler Proxy
-~~~~~~~~~~~~~~~~~~
-
-A Save Handler Proxy is basically a wrapper around a Save Handler that was
-introduced to seamlessly support the migration from PHP 5.3 to PHP 5.4+. It
-further creates an extension point from where custom logic can be added that
-works independently of which handler is being wrapped inside.
-
-There are two kinds of save handler class proxies which inherit from
-:class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\AbstractProxy`:
-they are :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\NativeProxy`
-and :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\SessionHandlerProxy`.
-
-:class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage`
-automatically injects storage handlers into a save handler proxy unless already
-wrapped by one.
-
-:class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\NativeProxy`
-is used automatically under PHP 5.3 when internal PHP save handlers are specified
-using the ``Native*SessionHandler`` classes, while
-:class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\SessionHandlerProxy`
-will be used to wrap any custom save handlers, that implement :phpclass:`SessionHandlerInterface`.
-
-From PHP 5.4 and above, all session handlers implement :phpclass:`SessionHandlerInterface`
-including ``Native*SessionHandler`` classes which inherit from :phpclass:`SessionHandler`.
-
-The proxy mechanism allows you to get more deeply involved in session save handler
-classes. A proxy for example could be used to encrypt any session transaction
-without knowledge of the specific save handler.
-
-.. note::
-
- Before PHP 5.4, you can only proxy user-land save handlers but not
- native PHP save handlers.
-
.. _`php.net/session.customhandler`: https://php.net/session.customhandler
.. _`php.net/session.configuration`: https://php.net/session.configuration
.. _`php.net/memcached.setoption`: https://php.net/memcached.setoption
diff --git a/components/http_foundation/session_testing.rst b/components/http_foundation/session_testing.rst
index 95ee18ad395..7d8a570c17e 100644
--- a/components/http_foundation/session_testing.rst
+++ b/components/http_foundation/session_testing.rst
@@ -6,8 +6,8 @@ Testing with Sessions
=====================
Symfony is designed from the ground up with code-testability in mind. In order
-to make your code which utilizes session testable we provide two separate mock
-storage mechanisms for both unit testing and functional testing.
+to test your code which utilizes sessions, we provide two separate mock storage
+mechanisms for both unit testing and functional testing.
Testing code using real sessions is tricky because PHP's workflow state is global
and it is not possible to have multiple concurrent sessions in the same PHP
@@ -37,7 +37,7 @@ Unit Testing
------------
For unit testing where it is not necessary to persist the session, you should
-simply swap out the default storage engine with
+swap out the default storage engine with
:class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\MockArraySessionStorage`::
use Symfony\Component\HttpFoundation\Session\Session;
@@ -49,7 +49,7 @@ Functional Testing
------------------
For functional testing where you may need to persist session data across
-separate PHP processes, simply change the storage engine to
+separate PHP processes, change the storage engine to
:class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\MockFileSessionStorage`::
use Symfony\Component\HttpFoundation\Session\Session;
diff --git a/components/http_foundation/sessions.rst b/components/http_foundation/sessions.rst
index 32f85109826..7cb56159d71 100644
--- a/components/http_foundation/sessions.rst
+++ b/components/http_foundation/sessions.rst
@@ -6,10 +6,10 @@ Session Management
==================
The Symfony HttpFoundation component has a very powerful and flexible session
-subsystem which is designed to provide session management through a simple
+subsystem which is designed to provide session management through a clear
object-oriented interface using a variety of session storage drivers.
-Sessions are used via the simple :class:`Symfony\\Component\\HttpFoundation\\Session\\Session`
+Sessions are used via the :class:`Symfony\\Component\\HttpFoundation\\Session\\Session`
implementation of :class:`Symfony\\Component\\HttpFoundation\\Session\\SessionInterface` interface.
.. caution::
@@ -62,8 +62,8 @@ Session API
The :class:`Symfony\\Component\\HttpFoundation\\Session\\Session` class implements
:class:`Symfony\\Component\\HttpFoundation\\Session\\SessionInterface`.
-The :class:`Symfony\\Component\\HttpFoundation\\Session\\Session` has a simple API
-as follows divided into a couple of groups.
+The :class:`Symfony\\Component\\HttpFoundation\\Session\\Session` has the
+following API, divided into a couple of groups.
Session Workflow
................
@@ -167,7 +167,7 @@ and "Remember Me" login settings or other user based state information.
This implementation allows for attributes to be stored in a structured namespace.
:class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface`
-has a simple API
+has the API
:method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::set`
Sets an attribute by name (``set('name', 'value')``).
@@ -207,6 +207,8 @@ Example::
// ...
$session->clear();
+.. _namespaced-attributes:
+
Namespaced Attributes
.....................
@@ -226,8 +228,7 @@ store it again::
],
];
-So any processing of this might quickly get ugly, even simply adding a token to
-the array::
+So any processing of this might quickly get ugly, even adding a token to the array::
$tokens = $session->get('tokens');
$tokens['c'] = $value;
@@ -265,7 +266,7 @@ This is however just one application for flash messages.
caching.
:class:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface`
-has a simple API
+has the API
:method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::add`
Adds a flash message to the stack of specified type.
@@ -321,7 +322,7 @@ Examples of setting multiple flashes::
Displaying the flash messages might look as follows.
-Simple, display one type of message::
+Display one type of message::
// display warnings
foreach ($session->getFlashBag()->get('warning', []) as $message) {
diff --git a/components/http_kernel.rst b/components/http_kernel.rst
index 5970157680b..7066cc0c641 100644
--- a/components/http_kernel.rst
+++ b/components/http_kernel.rst
@@ -16,7 +16,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/http-kernel:^3.4
+ $ composer require symfony/http-kernel
.. include:: /components/require_autoload.rst.inc
@@ -45,12 +45,12 @@ This is a simplified overview of the request workflow in Symfony applications:
#. The **browser** displays the **resource** to the **user**.
Typically, some sort of framework or system is built to handle all the repetitive
-tasks (e.g. routing, security, etc) so that a developer can build
-each *page* of the application. Exactly *how* these systems are built varies
-greatly. The HttpKernel component provides an interface that formalizes
-the process of starting with a request and creating the appropriate response.
-The component is meant to be the heart of any application or framework, no
-matter how varied the architecture of that system::
+tasks (e.g. routing, security, etc) so that a developer can build each *page* of
+the application. Exactly *how* these systems are built varies greatly. The HttpKernel
+component provides an interface that formalizes the process of starting with a
+request and creating the appropriate response. The component is meant to be the
+heart of any application or framework, no matter how varied the architecture of
+that system::
namespace Symfony\Component\HttpKernel;
@@ -94,8 +94,8 @@ To help explain this process, this document looks at each step of the process
and talks about how one specific implementation of the HttpKernel - the Symfony
Framework - works.
-Initially, using the :class:`Symfony\\Component\\HttpKernel\\HttpKernel`
-is really simple and involves creating an
+Initially, using the :class:`Symfony\\Component\\HttpKernel\\HttpKernel` does
+not take many steps. You create an
:doc:`event dispatcher ` and a
:ref:`controller and argument resolver `
(explained below). To complete your working kernel, you'll add more event
@@ -225,8 +225,8 @@ that implements :class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerRe
and is one of the constructor arguments to ``HttpKernel``.
Your job is to create a class that implements the interface and fill in its
-two methods: ``getController()`` and ``getArguments()``. In fact, one default
-implementation already exists, which you can use directly or learn from:
+method: ``getController()``. In fact, one default implementation already
+exists, which you can use directly or learn from:
:class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver`.
This implementation is explained more in the sidebar below::
@@ -237,30 +237,14 @@ This implementation is explained more in the sidebar below::
interface ControllerResolverInterface
{
public function getController(Request $request);
-
- public function getArguments(Request $request, $controller);
}
-.. caution::
-
- The ``getArguments()`` method in the
- :class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver` and
- respective interface
- :class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface`
- are deprecated as of 3.1 and will be removed in 4.0. You can use the
- :class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver` which
- uses the :class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolverInterface`
- instead.
-
Internally, the ``HttpKernel::handle()`` method first calls
:method:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface::getController`
on the controller resolver. This method is passed the ``Request`` and is responsible
for somehow determining and returning a PHP callable (the controller) based
on the request's information.
-The second method, :method:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolverInterface::getArguments`,
-will be called after another event - ``kernel.controller`` - is dispatched.
-
.. sidebar:: Resolving the Controller in the Symfony Framework
The Symfony Framework uses the built-in
@@ -276,11 +260,11 @@ will be called after another event - ``kernel.controller`` - is dispatched.
information is typically placed on the ``Request`` via the ``RouterListener``).
This string is then transformed into a PHP callable by doing the following:
- a) The ``AcmeDemoBundle:Default:index`` format of the ``_controller`` key
- is changed to another string that contains the full class and method
- name of the controller by following the convention used in Symfony - e.g.
- ``Acme\DemoBundle\Controller\DefaultController::indexAction``. This transformation
- is specific to the :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerResolver`
+ a) If the ``_controller`` key doesn't follow the recommended PHP namespace
+ format (e.g. ``App\Controller\DefaultController::index``) its format is
+ transformed into it. For example, the legacy ``FooBundle:Default:index``
+ format would be changed to ``Acme\FooBundle\Controller\DefaultController::indexAction``.
+ This transformation is specific to the :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerResolver`
sub-class used by the Symfony Framework.
b) A new instance of your controller class is instantiated with no
@@ -308,7 +292,7 @@ have been determined (e.g. the controller, routing information) but before
the controller is executed. For some examples, see the Symfony section below.
Listeners to this event can also change the controller callable completely
-by calling :method:`FilterControllerEvent::setController `
+by calling :method:`ControllerEvent::setController `
on the event object that's passed to listeners on this event.
.. sidebar:: ``kernel.controller`` in the Symfony Framework
@@ -317,11 +301,10 @@ on the event object that's passed to listeners on this event.
the Symfony Framework, and many deal with collecting profiler data when
the profiler is enabled.
- One interesting listener comes from the `SensioFrameworkExtraBundle`_,
- which is packaged with the Symfony Standard Edition. This listener's
- `@ParamConverter`_ functionality allows you to pass a full object (e.g. a
- ``Post`` object) to your controller instead of a scalar value (e.g. an
- ``id`` parameter that was on your route). The listener -
+ One interesting listener comes from the `SensioFrameworkExtraBundle`_. This
+ listener's `@ParamConverter`_ functionality allows you to pass a full object
+ (e.g. a ``Post`` object) to your controller instead of a scalar value (e.g.
+ an ``id`` parameter that was on your route). The listener -
``ParamConverterListener`` - uses reflection to look at each of the
arguments of the controller and tries to use different methods to convert
those to objects, which are then stored in the ``attributes`` property of
@@ -375,7 +358,7 @@ of arguments that should be passed when executing that callable.
5) Calling the Controller
~~~~~~~~~~~~~~~~~~~~~~~~~
-The next step is simple! ``HttpKernel::handle()`` executes the controller.
+The next step ``HttpKernel::handle()`` does 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
@@ -427,12 +410,11 @@ return a ``Response``.
.. sidebar:: ``kernel.view`` in the Symfony Framework
There is no default listener inside the Symfony Framework for the ``kernel.view``
- event. However, one core bundle - `SensioFrameworkExtraBundle`_ - *does*
- add a listener to this event. If your controller returns an array,
- and you place the `@Template`_ annotation above the controller, then this
- listener renders a template, passes the array you returned from your
- controller to that template, and creates a ``Response`` containing the
- returned content from that template.
+ event. However, `SensioFrameworkExtraBundle`_ *does* add a listener to this
+ event. If your controller returns an array, and you place the `@Template`_
+ annotation above the controller, then this listener renders a template,
+ passes the array you returned from your controller to that template, and
+ creates a ``Response`` containing the returned content from that template.
Additionally, a popular community bundle `FOSRestBundle`_ implements
a listener on this event which aims to give you a robust view layer
@@ -518,9 +500,9 @@ as possible to the client (e.g. sending emails).
.. sidebar:: ``kernel.terminate`` in the Symfony Framework
- If you use the SwiftmailerBundle with Symfony and use ``memory`` spooling,
- then the `EmailSenderListener`_ is activated, which actually delivers
- any emails that you scheduled to send during the request.
+ 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:
@@ -542,9 +524,9 @@ to the exception.
-Each listener to this event is passed a :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent`
+Each listener to this event is passed a :class:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent`
object, which you can use to access the original exception via the
-:method:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent::getException`
+:method:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent::getThrowable`
method. A typical listener on this event will check for a certain type of
exception and create an appropriate error ``Response``.
@@ -572,7 +554,7 @@ below for more details).
The listener has several goals:
1) The thrown exception is converted into a
- :class:`Symfony\\Component\\Debug\\Exception\\FlattenException`
+ :class:`Symfony\\Component\\ErrorHandler\\Exception\\FlattenException`
object, which contains all the information about the request, but which
can be printed and serialized.
@@ -620,18 +602,18 @@ each event has their own event object:
.. _component-http-kernel-event-table:
-=========================== ====================================== ===================================================================================
+=========================== ====================================== ========================================================================
Name ``KernelEvents`` Constant Argument passed to the listener
-=========================== ====================================== ===================================================================================
-kernel.request ``KernelEvents::REQUEST`` :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseEvent`
-kernel.controller ``KernelEvents::CONTROLLER`` :class:`Symfony\\Component\\HttpKernel\\Event\\FilterControllerEvent`
-kernel.controller_arguments ``KernelEvents::CONTROLLER_ARGUMENTS`` :class:`Symfony\\Component\\HttpKernel\\Event\\FilterControllerArgumentsEvent`
-kernel.view ``KernelEvents::VIEW`` :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForControllerResultEvent`
-kernel.response ``KernelEvents::RESPONSE`` :class:`Symfony\\Component\\HttpKernel\\Event\\FilterResponseEvent`
+=========================== ====================================== ========================================================================
+kernel.request ``KernelEvents::REQUEST`` :class:`Symfony\\Component\\HttpKernel\\Event\\RequestEvent`
+kernel.controller ``KernelEvents::CONTROLLER`` :class:`Symfony\\Component\\HttpKernel\\Event\\ControllerEvent`
+kernel.controller_arguments ``KernelEvents::CONTROLLER_ARGUMENTS`` :class:`Symfony\\Component\\HttpKernel\\Event\\ControllerArgumentsEvent`
+kernel.view ``KernelEvents::VIEW`` :class:`Symfony\\Component\\HttpKernel\\Event\\ViewEvent`
+kernel.response ``KernelEvents::RESPONSE`` :class:`Symfony\\Component\\HttpKernel\\Event\\ResponseEvent`
kernel.finish_request ``KernelEvents::FINISH_REQUEST`` :class:`Symfony\\Component\\HttpKernel\\Event\\FinishRequestEvent`
-kernel.terminate ``KernelEvents::TERMINATE`` :class:`Symfony\\Component\\HttpKernel\\Event\\PostResponseEvent`
-kernel.exception ``KernelEvents::EXCEPTION`` :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent`
-=========================== ====================================== ===================================================================================
+kernel.terminate ``KernelEvents::TERMINATE`` :class:`Symfony\\Component\\HttpKernel\\Event\\TerminateEvent`
+kernel.exception ``KernelEvents::EXCEPTION`` :class:`Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent`
+=========================== ====================================== ========================================================================
.. _http-kernel-working-example:
@@ -727,10 +709,10 @@ can be used to check if the current request is a "master" or "sub" request.
For example, a listener that only needs to act on the master request may
look like this::
- use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+ use Symfony\Component\HttpKernel\Event\RequestEvent;
// ...
- public function onKernelRequest(GetResponseEvent $event)
+ public function onKernelRequest(RequestEvent $event)
{
if (!$event->isMasterRequest()) {
return;
@@ -751,15 +733,15 @@ translation files, etc.)
This overriding mechanism works because resources are referenced not by their
physical path but by their logical path. For example, the ``services.xml`` file
-stored in the ``Resources/config/`` directory of a bundle called AppBundle is
-referenced as ``@AppBundle/Resources/config/services.xml``. This logical path
+stored in the ``Resources/config/`` directory of a bundle called FooBundle is
+referenced as ``@FooBundle/Resources/config/services.xml``. This logical path
will work when the application overrides that file and even if you change the
-directory of AppBundle.
+directory of FooBundle.
The HttpKernel component provides a method called :method:`Symfony\\Component\\HttpKernel\\Kernel::locateResource`
which can be used to transform logical paths into physical paths::
- $path = $kernel->locateResource('@AppBundle/Resources/config/services.xml');
+ $path = $kernel->locateResource('@FooBundle/Resources/config/services.xml');
Learn more
----------
diff --git a/components/inflector.rst b/components/inflector.rst
new file mode 100644
index 00000000000..5e9f4325884
--- /dev/null
+++ b/components/inflector.rst
@@ -0,0 +1,64 @@
+.. index::
+ single: Inflector
+ single: Components; Inflector
+
+The Inflector Component
+=======================
+
+ The Inflector component converts English words between their singular and
+ plural forms.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/inflector
+
+.. include:: /components/require_autoload.rst.inc
+
+When you May Need an Inflector
+------------------------------
+
+In some scenarios such as code generation and code introspection, it's usually
+required to convert words from/to singular/plural. For example, if you need to
+know which property is associated with an *adder* method, you must convert from
+plural to singular (``addStories()`` method -> ``$story`` property).
+
+Although most human languages define simple pluralization rules, they also
+define lots of exceptions. For example, the general rule in English is to add an
+``s`` at the end of the word (``book`` -> ``books``) but there are lots of
+exceptions even for common words (``woman`` -> ``women``, ``life`` -> ``lives``,
+``news`` -> ``news``, ``radius`` -> ``radii``, etc.)
+
+This component abstracts all those pluralization rules so you can convert
+from/to singular/plural with confidence. However, due to the complexity of the
+human languages, this component only provides support for the English language.
+
+Usage
+-----
+
+The Inflector component provides two static methods to convert from/to
+singular/plural::
+
+ use Symfony\Component\Inflector\Inflector;
+
+ Inflector::singularize('alumni'); // 'alumnus'
+ Inflector::singularize('knives'); // 'knife'
+ Inflector::singularize('mice'); // 'mouse'
+
+ Inflector::pluralize('grandchild'); // 'grandchildren'
+ Inflector::pluralize('news'); // 'news'
+ Inflector::pluralize('bacterium'); // 'bacteria'
+
+Sometimes it's not possible to determine a unique singular/plural form for the
+given word. In those cases, the methods return an array with all the possible
+forms::
+
+ use Symfony\Component\Inflector\Inflector;
+
+ Inflector::singularize('indices'); // ['index', 'indix', 'indice']
+ Inflector::singularize('leaves'); // ['leaf', 'leave', 'leaff']
+
+ Inflector::pluralize('matrix'); // ['matricies', 'matrixes']
+ Inflector::pluralize('person'); // ['persons', 'people']
diff --git a/components/intl.rst b/components/intl.rst
index ae4634ffe30..81c72bfee08 100644
--- a/components/intl.rst
+++ b/components/intl.rst
@@ -5,8 +5,8 @@
The Intl Component
==================
- A PHP replacement layer for the C `intl extension`_ that also provides
- access to the localization data of the `ICU library`_.
+ This component provides access to the localization data of the `ICU library`_.
+ It also provides a PHP replacement layer for the C `intl extension`_.
.. caution::
@@ -24,7 +24,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/intl:^3.4
+ $ composer require symfony/intl
.. include:: /components/require_autoload.rst.inc
@@ -52,293 +52,312 @@ replace the intl classes:
Composer automatically exposes these classes in the global namespace.
-If you don't use Composer but the
-:doc:`Symfony ClassLoader component `,
-you need to expose them manually by adding the following lines to your autoload
-code::
+Accessing ICU Data
+------------------
+
+This component provides the following ICU data:
- if (!function_exists('intl_is_failure')) {
- require '/path/to/Icu/Resources/stubs/functions.php';
+* `Language and Script Names`_
+* `Country Names`_
+* `Locales`_
+* `Currencies`_
+* `Timezones`_
+
+Language and Script Names
+~~~~~~~~~~~~~~~~~~~~~~~~~
- $loader->registerPrefixFallback('/path/to/Icu/Resources/stubs');
- }
+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::
-Writing and Reading Resource Bundles
-------------------------------------
+ use Symfony\Component\Intl\Languages;
-The :phpclass:`ResourceBundle` class is not currently supported by this component.
-Instead, it includes a set of readers and writers for reading and writing
-arrays (or array-like objects) from/to resource bundle files. The following
-classes are supported:
+ \Locale::setDefault('en');
-* `TextBundleWriter`_
-* `PhpBundleWriter`_
-* `BinaryBundleReader`_
-* `PhpBundleReader`_
-* `BufferedBundleReader`_
-* `StructuredBundleReader`_
+ $languages = Languages::getNames();
+ // ('languageCode' => 'languageName')
+ // => ['ab' => 'Abkhazian', 'ace' => 'Achinese', ...]
-Continue reading if you are interested in how to use these classes. Otherwise
-skip this section and jump to `Accessing ICU Data`_.
+ $languages = Languages::getAlpha3Names();
+ // ('languageCode' => 'languageName')
+ // => ['abk' => 'Abkhazian', 'ace' => 'Achinese', ...]
-TextBundleWriter
-~~~~~~~~~~~~~~~~
+ $language = Languages::getName('fr');
+ // => 'French'
-The :class:`Symfony\\Component\\Intl\\ResourceBundle\\Writer\\TextBundleWriter`
-writes an array or an array-like object to a plain-text resource bundle. The
-resulting ``.txt`` file can be converted to a binary ``.res`` file with the
-:class:`Symfony\\Component\\Intl\\ResourceBundle\\Compiler\\BundleCompiler`
-class::
+ $language = Languages::getAlpha3Name('fra');
+ // => 'French'
- use Symfony\Component\Intl\ResourceBundle\Compiler\BundleCompiler;
- use Symfony\Component\Intl\ResourceBundle\Writer\TextBundleWriter;
+All methods accept the translation locale as the last, optional parameter,
+which defaults to the current default locale::
- $writer = new TextBundleWriter();
- $writer->write('/path/to/bundle', 'en', [
- 'Data' => [
- 'entry1',
- 'entry2',
- // ...
- ],
- ]);
+ $languages = Languages::getNames('de');
+ // => ['ab' => 'Abchasisch', 'ace' => 'Aceh', ...]
- $compiler = new BundleCompiler();
- $compiler->compile('/path/to/bundle', '/path/to/binary/bundle');
+ $languages = Languages::getAlpha3Names('de');
+ // => ['abk' => 'Abchasisch', 'ace' => 'Aceh', ...]
-The command ``genrb`` must be available for the
-:class:`Symfony\\Component\\Intl\\ResourceBundle\\Compiler\\BundleCompiler` to
-work. If the command is located in a non-standard location, you can pass its
-path to the
-:class:`Symfony\\Component\\Intl\\ResourceBundle\\Compiler\\BundleCompiler`
-constructor.
+ $language = Languages::getName('fr', 'de');
+ // => 'Französisch'
-PhpBundleWriter
-~~~~~~~~~~~~~~~
+ $language = Languages::getAlpha3Name('fra', 'de');
+ // => 'Französisch'
-The :class:`Symfony\\Component\\Intl\\ResourceBundle\\Writer\\PhpBundleWriter`
-writes an array or an array-like object to a ``.php`` resource bundle::
+If the given locale doesn't exist, the methods trigger a
+:class:`Symfony\\Component\\Intl\\Exception\\MissingResourceException`. In addition
+to catching the exception, you can also check if a given language code is valid::
- use Symfony\Component\Intl\ResourceBundle\Writer\PhpBundleWriter;
+ $isValidLanguage = Languages::exists($languageCode);
- $writer = new PhpBundleWriter();
- $writer->write('/path/to/bundle', 'en', [
- 'Data' => [
- 'entry1',
- 'entry2',
- // ...
- ],
- ]);
+Or if you have a alpha3 language code you want to check::
-BinaryBundleReader
-~~~~~~~~~~~~~~~~~~
+ $isValidLanguage = Languages::alpha3CodeExists($alpha3Code);
-The :class:`Symfony\\Component\\Intl\\ResourceBundle\\Reader\\BinaryBundleReader`
-reads binary resource bundle files and returns an array or an array-like object.
-This class currently only works with the `intl extension`_ installed::
+You may convert codes between two-letter alpha2 and three-letter alpha3 codes::
- use Symfony\Component\Intl\ResourceBundle\Reader\BinaryBundleReader;
+ $alpha3Code = Languages::getAlpha3Code($alpha2Code);
- $reader = new BinaryBundleReader();
- $data = $reader->read('/path/to/bundle', 'en');
+ $alpha2Code = Languages::getAlpha2Code($alpha3Code);
- var_dump($data['Data']['entry1']);
+The ``Scripts`` class provides access to the optional four-letter script code
+that can follow the language code according to the `Unicode ISO 15924 Registry`_
+(e.g. ``HANS`` in ``zh_HANS`` for simplified Chinese and ``HANT`` in ``zh_HANT``
+for traditional Chinese)::
-PhpBundleReader
-~~~~~~~~~~~~~~~
+ use Symfony\Component\Intl\Scripts;
-The :class:`Symfony\\Component\\Intl\\ResourceBundle\\Reader\\PhpBundleReader`
-reads resource bundles from ``.php`` files and returns an array or an array-like
-object::
+ \Locale::setDefault('en');
- use Symfony\Component\Intl\ResourceBundle\Reader\PhpBundleReader;
+ $scripts = Scripts::getNames();
+ // ('scriptCode' => 'scriptName')
+ // => ['Adlm' => 'Adlam', 'Afak' => 'Afaka', ...]
- $reader = new PhpBundleReader();
- $data = $reader->read('/path/to/bundle', 'en');
+ $script = Scripts::getName('Hans');
+ // => 'Simplified'
- var_dump($data['Data']['entry1']);
+All methods accept the translation locale as the last, optional parameter,
+which defaults to the current default locale::
-BufferedBundleReader
-~~~~~~~~~~~~~~~~~~~~
+ $scripts = Scripts::getNames('de');
+ // => ['Adlm' => 'Adlam', 'Afak' => 'Afaka', ...]
-The :class:`Symfony\\Component\\Intl\\ResourceBundle\\Reader\\BufferedBundleReader`
-wraps another reader, but keeps the last N reads in a buffer, where N is a
-buffer size passed to the constructor::
+ $script = Scripts::getName('Hans', 'de');
+ // => 'Vereinfacht'
- use Symfony\Component\Intl\ResourceBundle\Reader\BinaryBundleReader;
- use Symfony\Component\Intl\ResourceBundle\Reader\BufferedBundleReader;
+If the given script code doesn't exist, the methods trigger a
+:class:`Symfony\\Component\\Intl\\Exception\\MissingResourceException`. In addition
+to catching the exception, you can also check if a given script code is valid::
- $reader = new BufferedBundleReader(new BinaryBundleReader(), 10);
+ $isValidScript = Scripts::exists($scriptCode);
- // actually reads the file
- $data = $reader->read('/path/to/bundle', 'en');
+Country Names
+~~~~~~~~~~~~~
- // returns data from the buffer
- $data = $reader->read('/path/to/bundle', 'en');
+The ``Countries`` class provides access to the name of all countries according
+to the `ISO 3166-1 alpha-2`_ list and the `ISO 3166-1 alpha-3`_ list
+of officially recognized countries and territories::
- // actually reads the file
- $data = $reader->read('/path/to/bundle', 'fr');
+ use Symfony\Component\Intl\Countries;
-StructuredBundleReader
-~~~~~~~~~~~~~~~~~~~~~~
+ \Locale::setDefault('en');
-The :class:`Symfony\\Component\\Intl\\ResourceBundle\\Reader\\StructuredBundleReader`
-wraps another reader and offers a
-:method:`Symfony\\Component\\Intl\\ResourceBundle\\Reader\\StructuredBundleReaderInterface::readEntry`
-method for reading an entry of the resource bundle without having to worry
-whether array keys are set or not. If a path cannot be resolved, ``null`` is
-returned::
+ $countries = Countries::getNames();
+ // ('alpha2Code' => 'countryName')
+ // => ['AF' => 'Afghanistan', 'AX' => 'Åland Islands', ...]
- use Symfony\Component\Intl\ResourceBundle\Reader\BinaryBundleReader;
- use Symfony\Component\Intl\ResourceBundle\Reader\StructuredBundleReader;
+ $countries = Countries::getAlpha3Names();
+ // ('alpha3Code' => 'countryName')
+ // => ['AFG' => 'Afghanistan', 'ALA' => 'Åland Islands', ...]
- $reader = new StructuredBundleReader(new BinaryBundleReader());
+ $country = Countries::getName('GB');
+ // => 'United Kingdom'
- $data = $reader->read('/path/to/bundle', 'en');
+ $country = Countries::getAlpha3Name('NOR');
+ // => 'Norway'
- // produces an error if the key "Data" does not exist
- var_dump($data['Data']['entry1']);
+All methods accept the translation locale as the last, optional parameter,
+which defaults to the current default locale::
- // returns null if the key "Data" does not exist
- var_dump($reader->readEntry('/path/to/bundle', 'en', ['Data', 'entry1']));
+ $countries = Countries::getNames('de');
+ // => ['AF' => 'Afghanistan', 'EG' => 'Ägypten', ...]
-Additionally, the
-:method:`Symfony\\Component\\Intl\\ResourceBundle\\Reader\\StructuredBundleReaderInterface::readEntry`
-method resolves fallback locales. For example, the fallback locale of "en_GB" is
-"en". For single-valued entries (strings, numbers etc.), the entry will be read
-from the fallback locale if it cannot be found in the more specific locale. For
-multi-valued entries (arrays), the values of the more specific and the fallback
-locale will be merged. In order to suppress this behavior, the last parameter
-``$fallback`` can be set to ``false``::
+ $countries = Countries::getAlpha3Names('de');
+ // => ['AFG' => 'Afghanistan', 'EGY' => 'Ägypten', ...]
- var_dump($reader->readEntry(
- '/path/to/bundle',
- 'en',
- ['Data', 'entry1'],
- false
- ));
+ $country = Countries::getName('GB', 'de');
+ // => 'Vereinigtes Königreich'
-Accessing ICU Data
-------------------
+ $country = Countries::getAlpha3Name('GBR', 'de');
+ // => 'Vereinigtes Königreich'
-The ICU data is located in several "resource bundles". You can access a PHP
-wrapper of these bundles through the static
-:class:`Symfony\\Component\\Intl\\Intl` class. At the moment, the following
-data is supported:
+If the given country code doesn't exist, the methods trigger a
+:class:`Symfony\\Component\\Intl\\Exception\\MissingResourceException`. In addition
+to catching the exception, you can also check if a given country code is valid::
-* `Language and Script Names`_
-* `Country Names`_
-* `Locales`_
-* `Currencies`_
+ $isValidCountry = Countries::exists($alpha2Code);
-Language and Script Names
-~~~~~~~~~~~~~~~~~~~~~~~~~
+Or if you have a alpha3 country code you want to check::
-The translations of language and script names can be found in the language
-bundle::
+ $isValidCountry = Countries::alpha3CodeExists($alpha3Code);
- use Symfony\Component\Intl\Intl;
+You may convert codes between two-letter alpha2 and three-letter alpha3 codes::
- \Locale::setDefault('en');
+ $alpha3Code = Countries::getAlpha3Code($alpha2Code);
- $languages = Intl::getLanguageBundle()->getLanguageNames();
- // => ['ab' => 'Abkhazian', ...]
+ $alpha2Code = Countries::getAlpha2Code($alpha3Code);
- $language = Intl::getLanguageBundle()->getLanguageName('de');
- // => 'German'
+Locales
+~~~~~~~
- $language = Intl::getLanguageBundle()->getLanguageName('de', 'AT');
- // => 'Austrian German'
+A locale is the combination of a language and a region. For example, "Chinese"
+is the language and ``zh_Hans_MO`` is the locale for "Chinese" (language) +
+"Simplified" (script) + "Macau SAR China" (region). The ``Locales`` class
+provides access to the name of all locales::
- $scripts = Intl::getLanguageBundle()->getScriptNames();
- // => ['Arab' => 'Arabic', ...]
+ use Symfony\Component\Intl\Locales;
- $script = Intl::getLanguageBundle()->getScriptName('Hans');
- // => 'Simplified'
+ \Locale::setDefault('en');
+
+ $locales = Locales::getNames();
+ // ('localeCode' => 'localeName')
+ // => ['af' => 'Afrikaans', 'af_NA' => 'Afrikaans (Namibia)', ...]
+
+ $locale = Locales::getName('zh_Hans_MO');
+ // => 'Chinese (Simplified, Macau SAR China)'
All methods accept the translation locale as the last, optional parameter,
which defaults to the current default locale::
- $languages = Intl::getLanguageBundle()->getLanguageNames('de');
- // => ['ab' => 'Abchasisch', ...]
+ $locales = Locales::getNames('de');
+ // => ['af' => 'Afrikaans', 'af_NA' => 'Afrikaans (Namibia)', ...]
-Country Names
-~~~~~~~~~~~~~
+ $locale = Locales::getName('zh_Hans_MO', 'de');
+ // => 'Chinesisch (Vereinfacht, Sonderverwaltungsregion Macau)'
+
+If the given locale code doesn't exist, the methods trigger a
+:class:`Symfony\\Component\\Intl\\Exception\\MissingResourceException`. In addition
+to catching the exception, you can also check if a given locale code is valid::
+
+ $isValidLocale = Locales::exists($localeCode);
+
+Currencies
+~~~~~~~~~~
-The translations of country names can be found in the region bundle::
+The ``Currencies`` class provides access to the name of all currencies as well
+as some of their information (symbol, fraction digits, etc.)::
- use Symfony\Component\Intl\Intl;
+ use Symfony\Component\Intl\Currencies;
\Locale::setDefault('en');
- $countries = Intl::getRegionBundle()->getCountryNames();
- // => ['AF' => 'Afghanistan', ...]
+ $currencies = Currencies::getNames();
+ // ('currencyCode' => 'currencyName')
+ // => ['AFN' => 'Afghan Afghani', 'ALL' => 'Albanian Lek', ...]
- $country = Intl::getRegionBundle()->getCountryName('GB');
- // => 'United Kingdom'
+ $currency = Currencies::getName('INR');
+ // => 'Indian Rupee'
-All methods accept the translation locale as the last, optional parameter,
-which defaults to the current default locale::
+ $symbol = Currencies::getSymbol('INR');
+ // => '₹'
- $countries = Intl::getRegionBundle()->getCountryNames('de');
- // => ['AF' => 'Afghanistan', ...]
+ $fractionDigits = Currencies::getFractionDigits('INR');
+ // => 2
-Locales
-~~~~~~~
+ $roundingIncrement = Currencies::getRoundingIncrement('INR');
+ // => 0
+
+All methods (except for ``getFractionDigits()`` and ``getRoundingIncrement()``)
+accept the translation locale as the last, optional parameter, which defaults to
+the current default locale::
+
+ $currencies = Currencies::getNames('de');
+ // => ['AFN' => 'Afghanischer Afghani', 'EGP' => 'Ägyptisches Pfund', ...]
+
+ $currency = Currencies::getName('INR', 'de');
+ // => 'Indische Rupie'
+
+If the given currency code doesn't exist, the methods trigger a
+:class:`Symfony\\Component\\Intl\\Exception\\MissingResourceException`. In addition
+to catching the exception, you can also check if a given currency code is valid::
-The translations of locale names can be found in the locale bundle::
+ $isValidCurrency = Currencies::exists($currencyCode);
- use Symfony\Component\Intl\Intl;
+.. _component-intl-timezones:
+
+Timezones
+~~~~~~~~~
+
+The ``Timezones`` class provides several utilities related to timezones. First,
+you can get the name and values of all timezones in all languages::
+
+ use Symfony\Component\Intl\Timezones;
\Locale::setDefault('en');
- $locales = Intl::getLocaleBundle()->getLocaleNames();
- // => ['af' => 'Afrikaans', ...]
+ $timezones = Timezones::getNames();
+ // ('timezoneID' => 'timezoneValue')
+ // => ['America/Eirunepe' => 'Acre Time (Eirunepe)', 'America/Rio_Branco' => 'Acre Time (Rio Branco)', ...]
- $locale = Intl::getLocaleBundle()->getLocaleName('zh_Hans_MO');
- // => 'Chinese (Simplified, Macau SAR China)'
+ $timezone = Timezones::getName('Africa/Nairobi');
+ // => 'East Africa Time (Nairobi)'
All methods accept the translation locale as the last, optional parameter,
which defaults to the current default locale::
- $locales = Intl::getLocaleBundle()->getLocaleNames('de');
- // => ['af' => 'Afrikaans', ...]
+ $timezones = Timezones::getNames('de');
+ // => ['America/Eirunepe' => 'Acre-Zeit (Eirunepe)', 'America/Rio_Branco' => 'Acre-Zeit (Rio Branco)', ...]
-Currencies
-~~~~~~~~~~
+ $timezone = Timezones::getName('Africa/Nairobi', 'de');
+ // => 'Ostafrikanische Zeit (Nairobi)'
-The translations of currency names and other currency-related information can
-be found in the currency bundle::
+You can also get all the timezones that exist in a given country. The
+``forCountryCode()`` method returns one or more timezone IDs, which you can
+translate into any locale with the ``getName()`` method shown earlier::
- use Symfony\Component\Intl\Intl;
+ // unlike language codes, country codes are always uppercase (CL = Chile)
+ $timezones = Timezones::forCountryCode('CL');
+ // => ['America/Punta_Arenas', 'America/Santiago', 'Pacific/Easter']
- \Locale::setDefault('en');
+The reverse lookup is also possible thanks to the ``getCountryCode()`` method,
+which returns the code of the country where the given timezone ID belongs to::
- $currencies = Intl::getCurrencyBundle()->getCurrencyNames();
- // => ['AFN' => 'Afghan Afghani', ...]
+ $countryCode = Timezones::getCountryCode('America/Vancouver')
+ // => $countryCode = 'CA' (CA = Canada)
- $currency = Intl::getCurrencyBundle()->getCurrencyName('INR');
- // => 'Indian Rupee'
+The `UTC/GMT time offsets`_ of all timezones are provided by ``getRawOffset()``
+(which returns an integer representing the offset in seconds) and
+``getGmtOffset()`` (which returns a string representation of the offset to
+display it to users)::
- $symbol = Intl::getCurrencyBundle()->getCurrencySymbol('INR');
- // => '₹'
+ $offset = Timezones::getRawOffset('Etc/UTC'); // $offset = 0
+ $offset = Timezones::getRawOffset('America/Buenos_Aires'); // $offset = -10800
+ $offset = Timezones::getRawOffset('Asia/Katmandu'); // $offset = 20700
- $fractionDigits = Intl::getCurrencyBundle()->getFractionDigits('INR');
- // => 2
+ $offset = Timezones::getGmtOffset('Etc/UTC'); // $offset = 'GMT+00:00'
+ $offset = Timezones::getGmtOffset('America/Buenos_Aires'); // $offset = 'GMT-03:00'
+ $offset = Timezones::getGmtOffset('Asia/Katmandu'); // $offset = 'GMT+05:45'
- $roundingIncrement = Intl::getCurrencyBundle()->getRoundingIncrement('INR');
- // => 0
+The timezone offset can vary in time because of the `daylight saving time (DST)`_
+practice. By default these methods use the ``time()`` PHP function to get the
+current timezone offset value, but you can pass a timestamp as their second
+arguments to get the offset at any given point in time::
+
+ // In 2019, the DST period in Madrid (Spain) went from March 31 to October 27
+ $offset = Timezones::getRawOffset('Europe/Madrid', strtotime('March 31, 2019')); // $offset = 3600
+ $offset = Timezones::getRawOffset('Europe/Madrid', strtotime('April 1, 2019')); // $offset = 7200
+ $offset = Timezones::getGmtOffset('Europe/Madrid', strtotime('October 27, 2019')); // $offset = 'GMT+02:00'
+ $offset = Timezones::getGmtOffset('Europe/Madrid', strtotime('October 28, 2019')); // $offset = 'GMT+01:00'
+
+The string representation of the GMT offset can vary depending on the locale, so
+you can pass the locale as the third optional argument::
-All methods (except for
-:method:`Symfony\\Component\\Intl\\ResourceBundle\\CurrencyBundleInterface::getFractionDigits`
-and
-:method:`Symfony\\Component\\Intl\\ResourceBundle\\CurrencyBundleInterface::getRoundingIncrement`)
-accept the translation locale as the last, optional parameter, which defaults
-to the current default locale::
+ $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'
- $currencies = Intl::getCurrencyBundle()->getCurrencyNames('de');
- // => ['AFN' => 'Afghanische Afghani', ...]
+If the given timezone ID doesn't exist, the methods trigger a
+:class:`Symfony\\Component\\Intl\\Exception\\MissingResourceException`. In addition
+to catching the exception, you can also check if a given timezone ID is valid::
-That's all you need to know for now. Have fun coding!
+ $isValidTimezone = Timezones::exists($timezoneId);
Learn more
----------
@@ -351,7 +370,15 @@ Learn more
/reference/forms/types/currency
/reference/forms/types/language
/reference/forms/types/locale
+ /reference/forms/types/timezone
.. _intl extension: https://php.net/manual/en/book.intl.php
.. _install the intl extension: https://php.net/manual/en/intl.setup.php
.. _ICU library: http://site.icu-project.org/
+.. _`Unicode ISO 15924 Registry`: https://www.unicode.org/iso15924/iso15924-codes.html
+.. _`ISO 3166-1 alpha-2`: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
+.. _`ISO 3166-1 alpha-3`: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3
+.. _`UTC/GMT time offsets`: https://en.wikipedia.org/wiki/List_of_UTC_time_offsets
+.. _`daylight saving time (DST)`: https://en.wikipedia.org/wiki/Daylight_saving_time
+.. _`ISO 639-1 alpha-2`: https://en.wikipedia.org/wiki/ISO_639-1
+.. _`ISO 639-2 alpha-3`: https://en.wikipedia.org/wiki/ISO_639-2
diff --git a/components/ldap.rst b/components/ldap.rst
index a576344b208..73963d5f243 100644
--- a/components/ldap.rst
+++ b/components/ldap.rst
@@ -12,7 +12,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/ldap:^3.4
+ $ composer require symfony/ldap
.. include:: /components/require_autoload.rst.inc
@@ -101,15 +101,15 @@ array, you may use the
// Do something with the results array
-By default, LDAP queries use the ``Symfony\Component\Ldap\Adapter::SCOPE_SUB``
+By default, LDAP queries use the ``Symfony\Component\Ldap\Adapter\QueryInterface::SCOPE_SUB``
scope, which corresponds to the ``LDAP_SCOPE_SUBTREE`` scope of the
:phpfunction:`ldap_search` function. You can also use ``SCOPE_BASE`` (related
to the ``LDAP_SCOPE_BASE`` scope of :phpfunction:`ldap_read`) and ``SCOPE_ONE``
(related to the ``LDAP_SCOPE_ONELEVEL`` scope of :phpfunction:`ldap_list`)::
- use Symfony\Component\Ldap\Adapter;
+ use Symfony\Component\Ldap\Adapter\QueryInterface;
- $query = $ldap->query('dc=symfony,dc=com', '...', ['scope' => Adapter::SCOPE_ONE]);
+ $query = $ldap->query('dc=symfony,dc=com', '...', ['scope' => QueryInterface::SCOPE_ONE]);
Creating or Updating Entries
----------------------------
@@ -138,5 +138,37 @@ delete existing ones::
$entry->setAttribute('email', ['fabpot@symfony.com']);
$entryManager->update($entry);
+ // Adding or removing values to a multi-valued attribute is more efficient than using update()
+ $entryManager->addAttributeValues($entry, 'telephoneNumber', ['+1.111.222.3333', '+1.222.333.4444']);
+ $entryManager->removeAttributeValues($entry, 'telephoneNumber', ['+1.111.222.3333', '+1.222.333.4444']);
+
// Removing an existing entry
$entryManager->remove(new Entry('cn=Test User,dc=symfony,dc=com'));
+
+Batch Updating
+______________
+
+Use the entry manager's :method:`Symfony\\Component\\Ldap\\Adapter\\ExtLdap\\EntryManager::applyOperations`
+method to update multiple attributes at once::
+
+ use Symfony\Component\Ldap\Entry;
+ use Symfony\Component\Ldap\Ldap;
+ // ...
+
+ $entry = new Entry('cn=Fabien Potencier,dc=symfony,dc=com', [
+ 'sn' => ['fabpot'],
+ 'objectClass' => ['inetOrgPerson'],
+ ]);
+
+ $entryManager = $ldap->getEntryManager();
+
+ // Adding multiple email addresses at once
+ $entryManager->applyOperations($entry->getDn(), [
+ new UpdateOperation(LDAP_MODIFY_BATCH_ADD, 'mail', 'new1@example.com'),
+ new UpdateOperation(LDAP_MODIFY_BATCH_ADD, 'mail', 'new2@example.com'),
+ ]);
+
+Possible operation types are ``LDAP_MODIFY_BATCH_ADD``, ``LDAP_MODIFY_BATCH_REMOVE``,
+``LDAP_MODIFY_BATCH_REMOVE_ALL``, ``LDAP_MODIFY_BATCH_REPLACE``. Parameter
+``$values`` must be ``NULL`` when using ``LDAP_MODIFY_BATCH_REMOVE_ALL``
+operation type.
diff --git a/components/lock.rst b/components/lock.rst
index 79ef3541ed0..0b7a6e20364 100644
--- a/components/lock.rst
+++ b/components/lock.rst
@@ -8,16 +8,12 @@ The Lock Component
The Lock Component creates and manages `locks`_, a mechanism to provide
exclusive access to a shared resource.
-.. versionadded:: 3.4
-
- The Lock component was introduced in Symfony 3.4.
-
Installation
------------
.. code-block:: terminal
- $ composer require symfony/lock:^3.4
+ $ composer require symfony/lock
.. include:: /components/require_autoload.rst.inc
@@ -28,16 +24,16 @@ Locks are used to guarantee exclusive access to some shared resource. In
Symfony applications, you can use locks for example to ensure that a command is
not executed more than once at the same time (on the same or different servers).
-Locks are created using a :class:`Symfony\\Component\\Lock\\Factory` class,
+Locks are created using a :class:`Symfony\\Component\\Lock\\LockFactory` class,
which in turn requires another class to manage the storage of locks::
- use Symfony\Component\Lock\Factory;
+ use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\Store\SemaphoreStore;
$store = new SemaphoreStore();
- $factory = new Factory($store);
+ $factory = new LockFactory($store);
-The lock is created by calling the :method:`Symfony\\Component\\Lock\\Factory::createLock`
+The lock is created by calling the :method:`Symfony\\Component\\Lock\\LockFactory::createLock`
method. Its first argument is an arbitrary string that represents the locked
resource. Then, a call to the :method:`Symfony\\Component\\Lock\\LockInterface::acquire`
method will try to acquire the lock::
@@ -60,7 +56,7 @@ method can be safely called repeatedly, even if the lock is already acquired.
Unlike other implementations, the Lock Component distinguishes locks
instances even when they are created for the same resource. If a lock has
to be used by several services, they should share the same ``Lock`` instance
- returned by the ``Factory::createLock`` method.
+ returned by the ``LockFactory::createLock`` method.
.. tip::
@@ -83,13 +79,13 @@ until the lock is acquired.
Some of the built-in ``Store`` classes support this feature. When they don't,
they can be decorated with the ``RetryTillSaveStore`` class::
- use Symfony\Component\Lock\Factory;
+ use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\Store\RedisStore;
use Symfony\Component\Lock\Store\RetryTillSaveStore;
$store = new RedisStore(new \Predis\Client('tcp://localhost:6379'));
$store = new RetryTillSaveStore($store);
- $factory = new Factory($store);
+ $factory = new LockFactory($store);
$lock = $factory->createLock('notification-flush');
$lock->acquire(true);
@@ -148,6 +144,19 @@ to reset the TTL to its original value::
$lock->release();
}
+.. tip::
+
+ Another useful technique for long-running tasks is to pass a custom TTL as
+ an argument of the ``refresh()`` method to change the default lock TTL::
+
+ $lock = $factory->createLock('charts-generation', 30);
+ // ...
+ // refresh the lock for 30 seconds
+ $lock->refresh();
+ // ...
+ // refresh the lock for 600 seconds (next refresh() call will be 30 seconds again)
+ $lock->refresh(600);
+
This component also provides two useful methods related to expiring locks:
``getExpiringDate()`` (which returns ``null`` or a ``\DateTimeImmutable``
object) and ``isExpired()`` (which returns a boolean).
@@ -155,7 +164,7 @@ object) and ``isExpired()`` (which returns a boolean).
The Owner of The Lock
---------------------
-Locks that are acquired for the first time are owned[1]_ by the ``Lock`` instance that acquired
+Locks that are acquired for the first time are owned by the ``Lock`` instance that acquired
it. If you need to check whether the current ``Lock`` instance is (still) the owner of
a lock, you can use the ``isAcquired()`` method::
@@ -201,16 +210,20 @@ Available Stores
----------------
Locks are created and managed in ``Stores``, which are classes that implement
-:class:`Symfony\\Component\\Lock\\StoreInterface`. The component includes the
-following built-in store types:
+:class:`Symfony\\Component\\Lock\\PersistingStoreInterface` and, optionally,
+:class:`Symfony\\Component\\Lock\\BlockingStoreInterface`.
+
+The component includes the following built-in store types:
============================================ ====== ======== ========
Store Scope Blocking Expiring
============================================ ====== ======== ========
:ref:`FlockStore ` local yes no
:ref:`MemcachedStore ` remote no yes
+:ref:`PdoStore ` remote no yes
:ref:`RedisStore ` remote no yes
:ref:`SemaphoreStore ` local yes no
+:ref:`ZookeeperStore ` remote no no
============================================ ====== ======== ========
.. _lock-store-flock:
@@ -231,7 +244,7 @@ PHP process is terminated::
Beware that some file systems (such as some types of NFS) do not support
locking. In those cases, it's better to use a directory on a local disk
- drive or a remote store based on Redis or Memcached.
+ drive or a remote store based on PDO, Redis or Memcached.
.. _lock-store-memcached:
@@ -253,6 +266,45 @@ support blocking, and expects a TTL to avoid stalled locks::
Memcached does not support TTL lower than 1 second.
+.. _lock-store-pdo:
+
+PdoStore
+~~~~~~~~
+
+The PdoStore saves locks in an SQL database. It requires a `PDO`_ connection, a
+`Doctrine DBAL Connection`_, or a `Data Source Name (DSN)`_. This store does not
+support blocking, and expects a TTL to avoid stalled locks::
+
+ use Symfony\Component\Lock\Store\PdoStore;
+
+ // a PDO, a Doctrine DBAL connection or DSN for lazy connecting through PDO
+ $databaseConnectionOrDSN = 'mysql:host=127.0.0.1;dbname=lock';
+ $store = new PdoStore($databaseConnectionOrDSN, ['db_username' => 'myuser', 'db_password' => 'mypassword']);
+
+.. note::
+
+ This store does not support TTL lower than 1 second.
+
+Before storing locks in the database, you must create the table that stores
+the information. The store provides a method called
+:method:`Symfony\\Component\\Lock\\Store\\PdoStore::createTable`
+to set up this table for you according to the database engine used::
+
+ try {
+ $store->createTable();
+ } catch (\PDOException $exception) {
+ // the table could not be created for some reason
+ }
+
+A great way to set up the table in production is to call the ``createTable()``
+method in your local computer and then generate a
+:ref:`database migration `:
+
+.. code-block:: terminal
+
+ $ php bin/console doctrine:migrations:diff
+ $ php bin/console doctrine:migrations:migrate
+
.. _lock-store-redis:
RedisStore
@@ -317,6 +369,29 @@ the stores.
working when a single server fails (because this strategy requires that the
lock is acquired in more than half of the servers).
+.. _lock-store-zookeeper:
+
+ZookeeperStore
+~~~~~~~~~~~~~~
+
+The ZookeeperStore saves locks on a `ZooKeeper`_ server. It requires a ZooKeeper
+connection implementing the ``\Zookeeper`` class. This store does not
+support blocking and expiration but the lock is automatically released when the
+PHP process is terminated::
+
+ use Symfony\Component\Lock\Store\ZookeeperStore;
+
+ $zookeeper = new \Zookeeper('localhost:2181');
+ // use the following to define a high-availability cluster:
+ // $zookeeper = new \Zookeeper('localhost1:2181,localhost2:2181,localhost3:2181');
+
+ $store = new ZookeeperStore($zookeeper);
+
+.. note::
+
+ Zookeeper does not require a TTL as the nodes used for locking are ephemeral
+ and die when the PHP process is terminated.
+
Reliability
-----------
@@ -326,11 +401,13 @@ the component is used in the following way.
Remote Stores
~~~~~~~~~~~~~
-Remote stores (:ref:`MemcachedStore ` and
-:ref:`RedisStore `) use an unique token to recognize the true
-owner of the lock. This token is stored in the
-:class:`Symfony\\Component\\Lock\\Key` object and is used internally by the
-``Lock``, therefore this key must not be shared between processes (session,
+Remote stores (:ref:`MemcachedStore `,
+:ref:`PdoStore `,
+:ref:`RedisStore ` and
+:ref:`ZookeeperStore `) use a unique token to recognize
+the true owner of the lock. This token is stored in the
+:class:`Symfony\\Component\\Lock\\Key` object and is used internally by
+the ``Lock``, therefore this key must not be shared between processes (session,
caching, fork, ...).
.. caution::
@@ -349,11 +426,12 @@ different machines may allow two different processes to acquire the same ``Lock`
Expiring Stores
~~~~~~~~~~~~~~~
-Expiring stores (:ref:`MemcachedStore ` and
-:ref:`RedisStore `) guarantee that the lock is acquired
-only for the defined duration of time. If the task takes longer to be
-accomplished, then the lock can be released by the store and acquired by
-someone else.
+Expiring stores (:ref:`MemcachedStore `,
+:ref:`PdoStore ` and
+:ref:`RedisStore `)
+guarantee that the lock is acquired only for the defined duration of time. If
+the task takes longer to be accomplished, then the lock can be released by the
+store and acquired by someone else.
The ``Lock`` provides several methods to check its health. The ``isExpired()``
method checks whether or not its lifetime is over and the ``getRemainingLifetime()``
@@ -467,6 +545,30 @@ method uses the Memcached's ``flush()`` method which purges and removes everythi
The method ``flush()`` must not be called, or locks should be stored in a
dedicated Memcached service away from Cache.
+PdoStore
+~~~~~~~~~~
+
+The PdoStore relies on the `ACID`_ properties of the SQL engine.
+
+.. caution::
+
+ In a cluster configured with multiple primaries, ensure writes are
+ synchronously propagated to every nodes, or always use the same node.
+
+.. caution::
+
+ Some SQL engines like MySQL allow to disable the unique constraint check.
+ Ensure that this is not the case ``SET unique_checks=1;``.
+
+In order to purge old locks, this store uses a current datetime to define an
+expiration date reference. This mechanism relies on all server nodes to
+have synchronized clocks.
+
+.. caution::
+
+ To ensure locks don't expire prematurely; the TTLs should be set with
+ enough extra time to account for any clock drift between nodes.
+
RedisStore
~~~~~~~~~~
@@ -529,6 +631,38 @@ can be two running containers in parallel.
concurrent process on a new machine, check that other process are stopped
on the old one.
+.. caution::
+
+ When runing on systemd with non-system user with option ``RemoveIPC=yes``
+ (default value), locks are deleted by systemd when that user logs out.
+ Check that process is ran with a system user (UID <= SYS_UID_MAX) with
+ ``SYS_UID_MAX`` defined in ``/etc/login.defs``, or set the option
+ ``RemoveIPC=off`` in ``/etc/systemd/logind.conf``.
+
+ZookeeperStore
+~~~~~~~~~~~~~~
+
+The way ZookeeperStore works is by maintaining locks as ephemeral nodes on the
+server. That means that by using :ref:`ZookeeperStore `
+the locks will be automatically released at the end of the session in case the
+client cannot unlock for any reason.
+
+If the ZooKeeper service or the machine hosting it restarts, every lock would
+be lost without notifying the running processes.
+
+.. tip::
+
+ To use ZooKeeper's high-availability feature, you can setup a cluster of
+ multiple servers so that in case one of the server goes down, the majority
+ will still be up and serving the requests. All the available servers in the
+ cluster will see the same state.
+
+.. note::
+
+ As this store does not support multi-level node locks, since the clean up of
+ intermediate nodes becomes an overhead, all locks are maintained at the root
+ level.
+
Overall
~~~~~~~
@@ -537,5 +671,10 @@ instance, during the deployment of a new version. Processes with new
configuration must not be started while old processes with old configuration
are still running.
+.. _`ACID`: https://en.wikipedia.org/wiki/ACID
.. _`locks`: https://en.wikipedia.org/wiki/Lock_(computer_science)
.. _`PHP semaphore functions`: https://php.net/manual/en/book.sem.php
+.. _`PDO`: https://php.net/pdo
+.. _`Doctrine DBAL Connection`: https://github.com/doctrine/dbal/blob/master/lib/Doctrine/DBAL/Connection.php
+.. _`Data Source Name (DSN)`: https://en.wikipedia.org/wiki/Data_source_name
+.. _`ZooKeeper`: https://zookeeper.apache.org/
diff --git a/components/mailer.rst b/components/mailer.rst
new file mode 100644
index 00000000000..de99cf09639
--- /dev/null
+++ b/components/mailer.rst
@@ -0,0 +1,210 @@
+.. index::
+ single: Mailer
+ single: Components; Mailer
+
+The Mailer Component
+====================
+
+ The Mailer component helps sending emails.
+
+If you're using the Symfony Framework, read the
+:doc:`Symfony Framework Mailer documentation `.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/mailer
+
+.. include:: /components/require_autoload.rst.inc
+
+Usage
+-----
+
+The Mailer component has two main classes: a ``Transport`` and the ``Mailer`` itself::
+
+ use Symfony\Component\Mailer\Mailer;
+ use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;
+
+ $transport = new EsmtpTransport('localhost');
+ $mailer = new Mailer($transport);
+ $mailer->send($email);
+
+The ``$email`` object is created via the :doc:`Mime component `.
+
+Transport
+---------
+
+The only transport that comes pre-installed is SMTP.
+
+Below is the list of other popular providers with built-in support:
+
+================== =============================================
+Service Install with
+================== =============================================
+Amazon SES ``composer require symfony/amazon-mailer``
+Gmail ``composer require symfony/google-mailer``
+MailChimp ``composer require symfony/mailchimp-mailer``
+Mailgun ``composer require symfony/mailgun-mailer``
+Postmark ``composer require symfony/postmark-mailer``
+SendGrid ``composer require symfony/sendgrid-mailer``
+================== =============================================
+
+For example, suppose you want to use Google's Gmail SMTP server. First, install
+it:
+
+.. code-block:: terminal
+
+ $ composer require symfony/google-mailer
+
+Then, use the SMTP Gmail transport::
+
+ use Symfony\Component\Mailer\Bridge\Google\Transport\GmailSmtpTransport;
+ use Symfony\Component\Mailer\Mailer;
+
+ $transport = new GmailSmtpTransport('user', 'pass');
+ $mailer = new Mailer($transport);
+ $mailer->send($email);
+
+Each provider provides up to 3 transports: standard SMTP, HTTP (it uses the
+provider's API but the body is created by the mailer component), API (it uses
+the full API of the provider with no control over the body creation -- features
+might be limited as well).
+
+.. _mailer_dsn:
+
+The mailer component provides a convenient way to create a transport from a
+DSN::
+
+ use Symfony\Component\Mailer\Transport;
+
+ $transport = Transport::fromDsn($dsn);
+
+Where ``$dsn`` depends on the provider you want to use. For plain SMTP, use
+``smtp://user:pass@example.com`` or ``sendmail+smtp://default`` to use the
+``sendmail`` binary. To disable the transport, use ``null://null``.
+
+For third-party providers, refer to the following table:
+
+==================== ========================================== =========================================== ========================================
+ Provider SMTP HTTP API
+==================== ========================================== =========================================== ========================================
+ Amazon SES ses+smtp://ACCESS_KEY:SECRET_KEY@default ses+https://ACCESS_KEY:SECRET_KEY@default ses+api://ACCESS_KEY:SECRET_KEY@default
+ Google Gmail gmail+smtp://USERNAME:PASSWORD@default n/a n/a
+ Mailchimp Mandrill mandrill+smtp://USERNAME:PASSWORD@default mandrill+https://KEY@default mandrill+api://KEY@default
+ Mailgun mailgun+smtp://USERNAME:PASSWORD@default mailgun+https://KEY:DOMAIN@default mailgun+api://KEY:DOMAIN@default
+ Postmark postmark+smtp://ID:ID@default n/a postmark+api://KEY@default
+ Sendgrid sendgrid+smtp://apikey:KEY@default n/a sendgrid+api://KEY@default
+==================== ========================================== =========================================== ========================================
+
+Instead of choosing a specific protocol, you can also let Symfony pick the
+best one by omitting it from the scheme: for instance, ``mailgun://KEY:DOMAIN@default``
+is equivalent to ``mailgun+https://KEY:DOMAIN@default``.
+
+If you want to override the default host for a provider (to debug an issue using
+a service like ``requestbin.com``), change ``default`` by your host:
+
+.. code-block:: bash
+
+ mailgun+https://KEY:DOMAIN@example.com
+ mailgun+https://KEY:DOMAIN@example.com:99
+
+Note that the protocol is *always* HTTPs and cannot be changed.
+
+High Availability
+-----------------
+
+Symfony's mailer supports `high availability`_ via a technique called "failover"
+to ensure that emails are sent even if one mailer server fails .
+
+A failover transport is configured with two or more transports and the
+``failover`` keyword::
+
+ $dsn = 'failover(postmark+api://ID@default sendgrid+smtp://KEY@default)';
+
+The mailer will start using the first transport. If the sending fails, the
+mailer won't retry it with the other transports, but it will switch to the next
+transport automatically for the following deliveries.
+
+Load Balancing
+--------------
+
+Symfony's mailer supports `load balancing`_ via a technique called "round-robin"
+to distribute the mailing workload across multiple transports .
+
+A round-robin transport is configured with two or more transports and the
+``roundrobin`` keyword::
+
+ $dsn = 'roundrobin(postmark+api://ID@default sendgrid+smtp://KEY@default)'
+
+The mailer will start using the first transport and if it fails, it will retry
+the same delivery with the next transports until one of them succeeds (or until
+all of them fail).
+
+TLS Peer Verification
+---------------------
+
+By default, SMTP transports perform TLS peer verification. This behavior is
+configurable with the ``verify_peer`` option. Although it's not recommended to
+disable this verification for security reasons, it can be useful while developing
+the application or when using a self-signed certificate::
+
+ $dsn = 'smtp://user:pass@smtp.example.com?verify_peer=false'
+
+.. versionadded:: 5.1
+
+ The ``verify_peer`` option was introduced in Symfony 5.1.
+
+Sending emails asynchronously
+-----------------------------
+
+If you want to send emails asynchronously, install the :doc:`Messenger component
+`.
+
+.. code-block:: terminal
+
+ $ composer require symfony/messenger
+
+Then, instantiate and pass a ``MessageBus`` as a second argument to ``Mailer``::
+
+ use Symfony\Component\Mailer\Envelope;
+ use Symfony\Component\Mailer\Mailer;
+ use Symfony\Component\Mailer\Messenger\MessageHandler;
+ use Symfony\Component\Mailer\Messenger\SendEmailMessage;
+ use Symfony\Component\Mailer\Transport;
+ use Symfony\Component\Messenger\Handler\HandlersLocator;
+ use Symfony\Component\Messenger\MessageBus;
+ use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware;
+ use Symfony\Component\Mime\Address;
+
+ $dsn = 'change-dsn-accordingly';
+
+ $transport = Transport::fromDsn($dsn);
+ $handler = new MessageHandler($transport);
+
+ $bus = new MessageBus([
+ new HandleMessageMiddleware(new HandlersLocator([
+ SendEmailMessage::class => [$handler],
+ ])),
+ ]);
+
+ $mailer = new Mailer($transport, $bus);
+ $mailer->send($email);
+
+ // you can pass an optional Envelope
+ $mailer->send($email, new Envelope(
+ new Address('sender@example.com'),
+ [
+ new Address('recipient@example.com'),
+ ]
+ ));
+
+Learn More
+-----------
+
+To learn more about how to use the mailer component, refer to the
+:doc:`Symfony Framework Mailer documentation `.
+
+.. _`high availability`: https://en.wikipedia.org/wiki/High_availability
+.. _`load balancing`: https://en.wikipedia.org/wiki/Load_balancing_(computing)
diff --git a/components/mercure.rst b/components/mercure.rst
new file mode 100644
index 00000000000..9ad99419e8e
--- /dev/null
+++ b/components/mercure.rst
@@ -0,0 +1,46 @@
+.. index::
+ single: Mercure
+ single: Components; Mercure
+
+The Mercure Component
+=====================
+
+ `Mercure`_ is an open protocol allowing to push data updates to web
+ browsers and other HTTP clients in a convenient, fast, reliable
+ and battery-friendly way.
+ It is especially useful to publish real-time updates of resources served
+ through web APIs, to reactive web and mobile applications.
+
+The Mercure Component implements the "publisher" part of the Mercure Protocol.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/mercure
+
+.. include:: /components/require_autoload.rst.inc
+
+Usage
+-----
+
+The following example shows the component in action::
+
+ // change these values accordingly to your hub installation
+ define('HUB_URL', 'https://demo.mercure.rocks/.well-known/mercure');
+ define('JWT', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXJjdXJlIjp7InN1YnNjcmliZSI6WyJmb28iLCJiYXIiXSwicHVibGlzaCI6WyJmb28iXX19.LRLvirgONK13JgacQ_VbcjySbVhkSmHy3IznH3tA9PM');
+
+ use Symfony\Component\Mercure\Jwt\StaticJwtProvider;
+ use Symfony\Component\Mercure\Publisher;
+ use Symfony\Component\Mercure\Update;
+
+ $publisher = new Publisher(HUB_URL, new StaticJwtProvider(JWT));
+ // Serialize the update, and dispatch it to the hub, that will broadcast it to the clients
+ $id = $publisher(new Update('https://example.com/books/1.jsonld', 'Hi from Symfony!', ['target1', 'target2']));
+
+Read the full :doc:`Mercure integration documentation ` to learn
+about all the features of this component and its integration with the Symfony
+framework.
+
+.. _`Mercure`: https://mercure.rocks
diff --git a/components/messenger.rst b/components/messenger.rst
new file mode 100644
index 00000000000..8d79561401f
--- /dev/null
+++ b/components/messenger.rst
@@ -0,0 +1,338 @@
+.. index::
+ single: Messenger
+ single: Components; Messenger
+
+The Messenger Component
+=======================
+
+ The Messenger component helps applications send and receive messages to/from
+ other applications or via message queues.
+
+ The component is greatly inspired by Matthias Noback's series of
+ `blog posts about command buses`_ and the `SimpleBus project`_.
+
+.. seealso::
+
+ This article explains how to use the Messenger features as an independent
+ component in any PHP application. Read the :doc:`/messenger` article to
+ learn about how to use it in Symfony applications.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/messenger
+
+.. include:: /components/require_autoload.rst.inc
+
+Concepts
+--------
+
+.. raw:: html
+
+
+
+**Sender**:
+ Responsible for serializing and sending messages to *something*. This
+ something can be a message broker or a third party API for example.
+
+**Receiver**:
+ Responsible for retrieving, deserializing and forwarding messages to handler(s).
+ This can be a message queue puller or an API endpoint for example.
+
+**Handler**:
+ Responsible for handling messages using the business logic applicable to the messages.
+ Handlers are called by the ``HandleMessageMiddleware`` middleware.
+
+**Middleware**:
+ Middleware can access the message and its wrapper (the envelope) while it is
+ dispatched through the bus.
+ Literally *"the software in the middle"*, those are not about core concerns
+ (business logic) of an application. Instead, they are cross cutting concerns
+ applicable throughout the application and affecting the entire message bus.
+ For instance: logging, validating a message, starting a transaction, ...
+ They are also responsible for calling the next middleware in the chain,
+ which means they can tweak the envelope, by adding stamps to it or even
+ replacing it, as well as interrupt the middleware chain. Middleware are called
+ both when a message is originally dispatched and again later when a message
+ is received from a transport,
+
+**Envelope**
+ Messenger specific concept, it gives full flexibility inside the message bus,
+ by wrapping the messages into it, allowing to add useful information inside
+ through *envelope stamps*.
+
+**Envelope Stamps**
+ Piece of information you need to attach to your message: serializer context
+ to use for transport, markers identifying a received message or any sort of
+ metadata your middleware or transport layer may use.
+
+Bus
+---
+
+The bus is used to dispatch messages. The behavior of the bus is in its ordered
+middleware stack. The component comes with a set of middleware that you can use.
+
+When using the message bus with Symfony's FrameworkBundle, the following middleware
+are configured for you:
+
+#. :class:`Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware` (enables asynchronous processing, logs the processing of your messages if you pass a logger)
+#. :class:`Symfony\\Component\\Messenger\\Middleware\\HandleMessageMiddleware` (calls the registered handler(s))
+
+Example::
+
+ use App\Message\MyMessage;
+ use Symfony\Component\Messenger\Handler\HandlersLocator;
+ use Symfony\Component\Messenger\MessageBus;
+ use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware;
+
+ $bus = new MessageBus([
+ new HandleMessageMiddleware(new HandlersLocator([
+ MyMessage::class => [$handler],
+ ])),
+ ]);
+
+ $bus->dispatch(new MyMessage(/* ... */));
+
+.. note::
+
+ Every middleware needs to implement the :class:`Symfony\\Component\\Messenger\\Middleware\\MiddlewareInterface`.
+
+Handlers
+--------
+
+Once dispatched to the bus, messages will be handled by a "message handler". A
+message handler is a PHP callable (i.e. a function or an instance of a class)
+that will do the required processing for your message::
+
+ namespace App\MessageHandler;
+
+ use App\Message\MyMessage;
+
+ class MyMessageHandler
+ {
+ public function __invoke(MyMessage $message)
+ {
+ // Message processing...
+ }
+ }
+
+Adding Metadata to Messages (Envelopes)
+---------------------------------------
+
+If you need to add metadata or some configuration to a message, wrap it with the
+:class:`Symfony\\Component\\Messenger\\Envelope` class and add stamps.
+For example, to set the serialization groups used when the message goes
+through the transport layer, use the ``SerializerStamp`` stamp::
+
+ use Symfony\Component\Messenger\Envelope;
+ use Symfony\Component\Messenger\Stamp\SerializerStamp;
+
+ $bus->dispatch(
+ (new Envelope($message))->with(new SerializerStamp([
+ // groups are applied to the whole message, so make sure
+ // to define the group for every embedded object
+ 'groups' => ['my_serialization_groups'],
+ ]))
+ );
+
+At the moment, the Symfony Messenger has the following built-in envelope stamps:
+
+#. :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\\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.
+
+Instead of dealing directly with the messages in the middleware you receive the envelope.
+Hence you can inspect the envelope content and its stamps, or add any::
+
+ use App\Message\Stamp\AnotherStamp;
+ use Symfony\Component\Messenger\Envelope;
+ use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
+ use Symfony\Component\Messenger\Middleware\StackInterface;
+ use Symfony\Component\Messenger\Stamp\ReceivedStamp;
+
+ class MyOwnMiddleware implements MiddlewareInterface
+ {
+ public function handle(Envelope $envelope, StackInterface $stack): Envelope
+ {
+ if (null !== $envelope->last(ReceivedStamp::class)) {
+ // Message just has been received...
+
+ // You could for example add another stamp.
+ $envelope = $envelope->with(new AnotherStamp(/* ... */));
+ } else {
+ // Message was just originally dispatched
+ }
+
+ return $stack->next()->handle($envelope, $stack);
+ }
+ }
+
+The above example will forward the message to the next middleware with an
+additional stamp *if* the message has just been received (i.e. has at least one
+``ReceivedStamp`` stamp). You can create your own stamps by implementing
+:class:`Symfony\\Component\\Messenger\\Stamp\\StampInterface`.
+
+If you want to examine all stamps on an envelope, use the ``$envelope->all()``
+method, which returns all stamps grouped by type (FQCN). Alternatively, you can
+iterate through all stamps of a specific type by using the FQCN as first
+parameter of this method (e.g. ``$envelope->all(ReceivedStamp::class)``).
+
+.. note::
+
+ Any stamp must be serializable using the Symfony Serializer component
+ if going through transport using the :class:`Symfony\\Component\\Messenger\\Transport\\Serialization\\Serializer`
+ base serializer.
+
+Transports
+----------
+
+In order to send and receive messages, you will have to configure a transport. A
+transport will be responsible for communicating with your message broker or 3rd parties.
+
+Your own Sender
+~~~~~~~~~~~~~~~
+
+Imagine that you already have an ``ImportantAction`` message going through the
+message bus and being handled by a handler. Now, you also want to send this
+message as an email (using the :doc:`Mime ` and
+:doc:`Mailer ` components).
+
+Using the :class:`Symfony\\Component\\Messenger\\Transport\\Sender\\SenderInterface`,
+you can create your own message sender::
+
+ namespace App\MessageSender;
+
+ use App\Message\ImportantAction;
+ use Symfony\Component\Mailer\MailerInterface;
+ use Symfony\Component\Messenger\Envelope;
+ use Symfony\Component\Messenger\Transport\Sender\SenderInterface;
+ use Symfony\Component\Mime\Email;
+
+ class ImportantActionToEmailSender implements SenderInterface
+ {
+ private $mailer;
+ private $toEmail;
+
+ public function __construct(MailerInterface $mailer, string $toEmail)
+ {
+ $this->mailer = $mailer;
+ $this->toEmail = $toEmail;
+ }
+
+ public function send(Envelope $envelope): Envelope
+ {
+ $message = $envelope->getMessage();
+
+ if (!$message instanceof ImportantAction) {
+ throw new \InvalidArgumentException(sprintf('This transport only supports "%s" messages.', ImportantAction::class));
+ }
+
+ $this->mailer->send(
+ (new Email())
+ ->to($this->toEmail)
+ ->subject('Important action made')
+ ->html('Important action Made by '.$message->getUsername().'
')
+ );
+
+ return $envelope;
+ }
+ }
+
+Your own Receiver
+~~~~~~~~~~~~~~~~~
+
+A receiver is responsible for getting messages from a source and dispatching
+them to the application.
+
+Imagine you already processed some "orders" in your application using a
+``NewOrder`` message. Now you want to integrate with a 3rd party or a legacy
+application but you can't use an API and need to use a shared CSV file with new
+orders.
+
+You will read this CSV file and dispatch a ``NewOrder`` message. All you need to
+do is to write your own CSV receiver::
+
+ namespace App\MessageReceiver;
+
+ use App\Message\NewOrder;
+ use Symfony\Component\Messenger\Envelope;
+ use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
+ use Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface;
+ use Symfony\Component\Serializer\SerializerInterface;
+
+ class NewOrdersFromCsvFileReceiver implements ReceiverInterface
+ {
+ private $serializer;
+ private $filePath;
+
+ public function __construct(SerializerInterface $serializer, string $filePath)
+ {
+ $this->serializer = $serializer;
+ $this->filePath = $filePath;
+ }
+
+ public function get(): iterable
+ {
+ // Receive the envelope according to your transport ($yourEnvelope here),
+ // in most cases, using a connection is the easiest solution.
+ if (null === $yourEnvelope) {
+ return [];
+ }
+
+ try {
+ $envelope = $this->serializer->decode([
+ 'body' => $yourEnvelope['body'],
+ 'headers' => $yourEnvelope['headers'],
+ ]);
+ } catch (MessageDecodingFailedException $exception) {
+ $this->connection->reject($yourEnvelope['id']);
+ throw $exception;
+ }
+
+ return [$envelope->with(new CustomStamp($yourEnvelope['id']))];
+ }
+
+ public function ack(Envelope $envelope): void
+ {
+ // Add information about the handled message
+ }
+
+ public function reject(Envelope $envelope): void
+ {
+ // In the case of a custom connection
+ $this->connection->reject($this->findCustomStamp($envelope)->getId());
+ }
+ }
+
+Receiver and Sender on the same Bus
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To allow sending and receiving messages on the same bus and prevent an infinite
+loop, the message bus will add a :class:`Symfony\\Component\\Messenger\\Stamp\\ReceivedStamp`
+stamp to the message envelopes and the :class:`Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware`
+middleware will know it should not route these messages again to a transport.
+
+Learn more
+----------
+
+.. toctree::
+ :maxdepth: 1
+ :glob:
+
+ /messenger
+ /messenger/*
+
+.. _`blog posts about command buses`: https://matthiasnoback.nl/tags/command%20bus/
+.. _`SimpleBus project`: http://docs.simplebus.io/en/latest/
diff --git a/components/mime.rst b/components/mime.rst
new file mode 100644
index 00000000000..e2dd3b05056
--- /dev/null
+++ b/components/mime.rst
@@ -0,0 +1,301 @@
+.. index::
+ single: MIME
+ single: MIME Messages
+ single: Components; MIME
+
+The Mime Component
+==================
+
+ The Mime component allows manipulating the MIME messages used to send emails
+ and provides utilities related to MIME types.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/mime
+
+.. include:: /components/require_autoload.rst.inc
+
+Introduction
+------------
+
+`MIME`_ (Multipurpose Internet Mail Extensions) is an Internet standard that
+extends the original basic format of emails to support features like:
+
+* Headers and text contents using non-ASCII characters;
+* Message bodies with multiple parts (e.g. HTML and plain text contents);
+* Non-text attachments: audio, video, images, PDF, etc.
+
+The entire MIME standard is complex and huge, but Symfony abstracts all that
+complexity to provide two ways of creating MIME messages:
+
+* A high-level API based on the :class:`Symfony\\Component\\Mime\\Email` class
+ to quickly create email messages with all the common features;
+* A low-level API based on the :class:`Symfony\\Component\\Mime\\Message` class
+ to have an absolute control over every single part of the email message.
+
+Usage
+-----
+
+Use the :class:`Symfony\\Component\\Mime\\Email` class and their *chainable*
+methods to compose the entire email message::
+
+ use Symfony\Component\Mime\Email;
+
+ $email = (new Email())
+ ->from('fabien@symfony.com')
+ ->to('foo@example.com')
+ ->cc('bar@example.com')
+ ->bcc('baz@example.com')
+ ->replyTo('fabien@symfony.com')
+ ->priority(Email::PRIORITY_HIGH)
+ ->subject('Important Notification')
+ ->text('Lorem ipsum...')
+ ->html('Lorem ipsum ...
')
+ ;
+
+This only purpose of this component is to create the email messages. Use the
+:doc:`Mailer component ` to actually send them. In Symfony
+applications, it's easier to use the :doc:`Mailer integration `.
+
+Most of the details about how to create Email objects, including Twig integration,
+can be found in the :doc:`Mailer documentation `.
+
+Twig Integration
+----------------
+
+The Mime component comes with excellent integration with Twig, allowing you to
+create messages from Twig templates, embed images, inline CSS and more. Details
+on how to use those features can be found in the Mailer documentation:
+:ref:`Twig: HTML & CSS `.
+
+But if you're using the Mime component without the Symfony framework, you'll need
+to handle a few setup details.
+
+Twig Setup
+~~~~~~~~~~
+
+To integrate with Twig, use the :class:`Symfony\\Bridge\\Twig\\Mime\\BodyRenderer`
+class to render the template and update the email message contents with the results::
+
+ // ...
+ use Symfony\Bridge\Twig\Mime\BodyRenderer;
+ use Twig\Environment;
+ use Twig\Loader\FilesystemLoader;
+
+ // when using the Mime component inside a full-stack Symfony application, you
+ // don't need to do this Twig setup. You only have to inject the 'twig' service
+ $loader = new FilesystemLoader(__DIR__.'/templates');
+ $twig = new Environment($loader);
+
+ $renderer = new BodyRenderer($twig);
+ // this updates the $email object contents with the result of rendering
+ // the template defined earlier with the given context
+ $renderer->render($email);
+
+Inlining CSS Styles (and other Extensions)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To use the :ref:`inline_css ` filter, first install the Twig
+extension:
+
+.. code-block:: terminal
+
+ $ composer require twig/cssinliner-extension
+
+Now, enable the extension::
+
+ // ...
+ use Twig\CssInliner\CssInlinerExtension;
+
+ $loader = new FilesystemLoader(__DIR__.'/templates');
+ $twig = new Environment($loader);
+ $twig->addExtension(new CssInlinerExtension());
+
+The same process should be used for enabling other extensions, like the
+:ref:`MarkdownExtension ` and :ref:`InkyExtension `.
+
+Creating Raw Email Messages
+---------------------------
+
+This is useful for advanced applications that need absolute control over every
+email part. It's not recommended for applications with regular email
+requirements because it adds complexity for no real gain.
+
+Before continuing, it's important to have a look at the low level structure of
+an email message. Consider a message which includes some content as both text
+and HTML, a single PNG image embedded in those contents and a PDF file attached
+to it. The MIME standard allows structuring this message in different ways, but
+the following tree is the one that works on most email clients:
+
+.. code-block:: text
+
+ multipart/mixed
+ ├── multipart/related
+ │ ├── multipart/alternative
+ │ │ ├── text/plain
+ │ │ └── text/html
+ │ └── image/png
+ └── application/pdf
+
+This is the purpose of each MIME message part:
+
+* ``multipart/alternative``: used when two or more parts are alternatives of the
+ same (or very similar) content. The preferred format must be added last.
+* ``multipart/mixed``: used to send different content types in the same message,
+ such as when attaching files.
+* ``multipart/related``: used to indicate that each message part is a component
+ of an aggregate whole. The most common usage is to display images embedded
+ in the message contents.
+
+When using the low-level :class:`Symfony\\Component\\Mime\\Message` class to
+create the email message, you must keep all the above in mind to define the
+different parts of the email by hand::
+
+ use Symfony\Component\Mime\Header\Headers;
+ use Symfony\Component\Mime\Message;
+ use Symfony\Component\Mime\Part\Multipart\AlternativePart;
+ use Symfony\Component\Mime\Part\TextPart;
+
+ $headers = (new Headers())
+ ->addMailboxListHeader('From', ['fabien@symfony.com'])
+ ->addMailboxListHeader('To', ['foo@example.com'])
+ ->addTextHeader('Subject', 'Important Notification')
+ ;
+
+ $textContent = new TextPart('Lorem ipsum...');
+ $htmlContent = new TextPart('Lorem ipsum ...
', 'html');
+ $body = new AlternativePart($textContent, $htmlContent);
+
+ $email = new Message($headers, $body);
+
+Embedding images and attaching files is possible by creating the appropriate
+email multiparts::
+
+ // ...
+ use Symfony\Component\Mime\Part\DataPart;
+ use Symfony\Component\Mime\Part\Multipart\MixedPart;
+ use Symfony\Component\Mime\Part\Multipart\RelatedPart;
+
+ // ...
+ $embeddedImage = new DataPart(fopen('/path/to/images/logo.png', 'r'), null, 'image/png');
+ $imageCid = $embeddedImage->getContentId();
+
+ $attachedFile = new DataPart(fopen('/path/to/documents/terms-of-use.pdf', 'r'), null, 'application/pdf');
+
+ $textContent = new TextPart('Lorem ipsum...');
+ $htmlContent = new TextPart(sprintf(
+ ' Lorem ipsum ...
', $imageCid
+ ), 'html');
+ $bodyContent = new AlternativePart($textContent, $htmlContent);
+ $body = new RelatedPart($bodyContent, $embeddedImage);
+
+ $messageParts = new MixedPart($body, $attachedFile);
+
+ $email = new Message($headers, $messageParts);
+
+Serializing Email Messages
+--------------------------
+
+Email messages created with either the ``Email`` or ``Message`` classes can be
+serialized because they are simple data objects::
+
+ $email = (new Email())
+ ->from('fabien@symfony.com')
+ // ...
+ ;
+
+ $serializedEmail = serialize($email);
+
+A common use case is to store serialized email messages, include them in a
+message sent with the :doc:`Messenger component ` and
+recreate them later when sending them. Use the
+:class:`Symfony\\Component\\Mime\\RawMessage` class to recreate email messages
+from their serialized contents::
+
+ use Symfony\Component\Mime\RawMessage;
+
+ // ...
+ $serializedEmail = serialize($email);
+
+ // later, recreate the original message to actually send it
+ $message = new RawMessage(unserialize($serializedEmail));
+
+MIME Types Utilities
+--------------------
+
+Although MIME was designed mainly for creating emails, the content types (also
+known as `MIME types`_ and "media types") defined by MIME standards are also of
+importance in communication protocols outside of email, such as HTTP. That's
+why this component also provides utilities to work with MIME types.
+
+The :class:`Symfony\\Component\\Mime\\MimeTypes` class transforms between
+MIME types and file name extensions::
+
+ use Symfony\Component\Mime\MimeTypes;
+
+ $mimeTypes = new MimeTypes();
+ $exts = $mimeTypes->getExtensions('application/javascript');
+ // $exts = ['js', 'jsm', 'mjs']
+ $exts = $mimeTypes->getExtensions('image/jpeg');
+ // $exts = ['jpeg', 'jpg', 'jpe']
+
+ $mimeTypes = $mimeTypes->getMimeTypes('js');
+ // $mimeTypes = ['application/javascript', 'application/x-javascript', 'text/javascript']
+ $mimeTypes = $mimeTypes->getMimeTypes('apk');
+ // $mimeTypes = ['application/vnd.android.package-archive']
+
+These methods return arrays with one or more elements. The element position
+indicates its priority, so the first returned extension is the preferred one.
+
+.. _components-mime-type-guess:
+
+Guessing the MIME Type
+~~~~~~~~~~~~~~~~~~~~~~
+
+Another useful utility allows to guess the MIME type of any given file::
+
+ use Symfony\Component\Mime\MimeTypes;
+
+ $mimeTypes = new MimeTypes();
+ $mimeType = $mimeTypes->guessMimeType('/some/path/to/image.gif');
+ // Guessing is not based on the file name, so $mimeType will be 'image/gif'
+ // only if the given file is truly a GIF image
+
+Guessing the MIME type is a time-consuming process that requires inspecting
+part of the file contents. Symfony applies multiple guessing mechanisms, one
+of them based on the PHP `fileinfo extension`_. It's recommended to install
+that extension to improve the guessing performance.
+
+Adding a MIME Type Guesser
+..........................
+
+You can register your own MIME type guesser by creating a class that implements
+:class:`Symfony\\Component\\Mime\\MimeTypeGuesserInterface`::
+
+ namespace App;
+
+ use Symfony\Component\Mime\MimeTypeGuesserInterface;
+
+ class SomeMimeTypeGuesser implements MimeTypeGuesserInterface
+ {
+ public function isGuesserSupported(): bool
+ {
+ // return true when the guesser is supported (might depend on the OS for instance)
+ return true;
+ }
+
+ public function guessMimeType(string $path): ?string
+ {
+ // inspect the contents of the file stored in $path to guess its
+ // type and return a valid MIME type ... or null if unknown
+
+ return '...';
+ }
+ }
+
+.. _`MIME`: https://en.wikipedia.org/wiki/MIME
+.. _`MIME types`: https://en.wikipedia.org/wiki/Media_type
+.. _`fileinfo extension`: https://php.net/fileinfo
diff --git a/components/notifier.rst b/components/notifier.rst
new file mode 100644
index 00000000000..9e7be5c4197
--- /dev/null
+++ b/components/notifier.rst
@@ -0,0 +1,28 @@
+.. index::
+ single: Notifier
+ single: Notifications
+ single: Components; Notifier
+
+The Notifier Component
+======================
+
+ The Notifier component sends notifications via one or more channels
+ (email, SMS, Slack, ...).
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/notifier
+
+.. include:: /components/require_autoload.rst.inc
+
+
+Usage
+-----
+
+.. caution::
+
+ We're still working on the docs of this component. Check this page again
+ in a few days.
diff --git a/components/options_resolver.rst b/components/options_resolver.rst
index 817bada6731..c790d1ed561 100644
--- a/components/options_resolver.rst
+++ b/components/options_resolver.rst
@@ -15,7 +15,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/options-resolver:^3.4
+ $ composer require symfony/options-resolver
.. include:: /components/require_autoload.rst.inc
@@ -35,7 +35,7 @@ Imagine you have a ``Mailer`` class which has four options: ``host``,
}
}
-When accessing the ``$options``, you need to add a lot of boilerplate code to
+When accessing the ``$options``, you need to add some boilerplate code to
check which options are set::
class Mailer
@@ -45,29 +45,17 @@ check which options are set::
{
$mail = ...;
- $mail->setHost(isset($this->options['host'])
- ? $this->options['host']
- : 'smtp.example.org');
-
- $mail->setUsername(isset($this->options['username'])
- ? $this->options['username']
- : 'user');
-
- $mail->setPassword(isset($this->options['password'])
- ? $this->options['password']
- : 'pa$$word');
-
- $mail->setPort(isset($this->options['port'])
- ? $this->options['port']
- : 25);
+ $mail->setHost($this->options['host'] ?? 'smtp.example.org');
+ $mail->setUsername($this->options['username'] ?? 'user');
+ $mail->setPassword($this->options['password'] ?? 'pa$$word');
+ $mail->setPort($this->options['port'] ?? 25);
// ...
}
}
-This boilerplate is hard to read and repetitive. Also, the default values of the
-options are buried in the business logic of your code. Use the
-:phpfunction:`array_replace` to fix that::
+Also, the default values of the options are buried in the business logic of your
+code. Use the :phpfunction:`array_replace` to fix that::
class Mailer
{
@@ -333,11 +321,6 @@ You may also pass fully qualified class or interface names (which is checked
using ``instanceof``). Additionally, you can validate all items in an array
recursively by suffixing the type with ``[]``.
-.. versionadded:: 3.4
-
- Validating types of array items recursively was introduced in Symfony 3.4.
- Prior to Symfony 3.4, only scalar values could be validated.
-
If you pass an invalid option now, an
:class:`Symfony\\Component\\OptionsResolver\\Exception\\InvalidOptionsException`
is thrown::
@@ -451,6 +434,12 @@ if you need to use other options during normalization::
}
}
+To normalize a new allowed value in sub-classes that are being normalized
+in parent classes use :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::addNormalizer`.
+This way, the ``$value`` argument will receive the previously normalized
+value, otherwise you can prepend the new normalizer by passing ``true`` as
+third argument.
+
Default Values that Depend on another Option
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -536,8 +525,8 @@ Options without Default Values
In some cases, it is useful to define an option without setting a default value.
This is useful if you need to know whether or not the user *actually* set
an option or not. For example, if you set the default value for an option,
-it's not possible to know whether the user passed this value or if it simply
-comes from the default::
+it's not possible to know whether the user passed this value or if it comes
+from the default::
// ...
class Mailer
@@ -636,6 +625,159 @@ let you find out which options are defined::
}
}
+Nested Options
+~~~~~~~~~~~~~~
+
+Suppose you have an option named ``spool`` which has two sub-options ``type``
+and ``path``. Instead of defining it as a simple array of values, you can pass a
+closure as the default value of the ``spool`` option with a
+:class:`Symfony\\Component\\OptionsResolver\\OptionsResolver` argument. Based on
+this instance, you can define the options under ``spool`` and its desired
+default value::
+
+ class Mailer
+ {
+ // ...
+
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $resolver->setDefault('spool', function (OptionsResolver $spoolResolver) {
+ $spoolResolver->setDefaults([
+ 'type' => 'file',
+ 'path' => '/path/to/spool',
+ ]);
+ $spoolResolver->setAllowedValues('type', ['file', 'memory']);
+ $spoolResolver->setAllowedTypes('path', 'string');
+ });
+ }
+
+ public function sendMail($from, $to)
+ {
+ if ('memory' === $this->options['spool']['type']) {
+ // ...
+ }
+ }
+ }
+
+ $mailer = new Mailer([
+ 'spool' => [
+ 'type' => 'memory',
+ ],
+ ]);
+
+Nested options also support required options, validation (type, value) and
+normalization of their values. If the default value of a nested option depends
+on another option defined in the parent level, add a second ``Options`` argument
+to the closure to access to them::
+
+ class Mailer
+ {
+ // ...
+
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $resolver->setDefault('sandbox', false);
+ $resolver->setDefault('spool', function (OptionsResolver $spoolResolver, Options $parent) {
+ $spoolResolver->setDefaults([
+ 'type' => $parent['sandbox'] ? 'memory' : 'file',
+ // ...
+ ]);
+ });
+ }
+ }
+
+.. caution::
+
+ The arguments of the closure must be type hinted as ``OptionsResolver`` and
+ ``Options`` respectively. Otherwise, the closure itself is considered as the
+ default value of the option.
+
+In same way, parent options can access to the nested options as normal arrays::
+
+ class Mailer
+ {
+ // ...
+
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $resolver->setDefault('spool', function (OptionsResolver $spoolResolver) {
+ $spoolResolver->setDefaults([
+ 'type' => 'file',
+ // ...
+ ]);
+ });
+ $resolver->setDefault('profiling', function (Options $options) {
+ return 'file' === $options['spool']['type'];
+ });
+ }
+ }
+
+.. note::
+
+ The fact that an option is defined as nested means that you must pass
+ an array of values to resolve it at runtime.
+
+Deprecating the Option
+~~~~~~~~~~~~~~~~~~~~~~
+
+Once an option is outdated or you decided not to maintain it anymore, you can
+deprecate it using the :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setDeprecated`
+method::
+
+ $resolver
+ ->setDefined(['hostname', 'host'])
+ // this outputs the following generic deprecation message:
+ // The option "hostname" is deprecated.
+ ->setDeprecated('hostname')
+
+ // you can also pass a custom deprecation message
+ ->setDeprecated('hostname', 'The option "hostname" is deprecated, use "host" instead.')
+ ;
+
+.. note::
+
+ The deprecation message will be triggered only if the option is being used
+ somewhere, either its value is provided by the user or the option is evaluated
+ within closures of lazy options and normalizers.
+
+.. note::
+
+ When using an option deprecated by you in your own library, you can pass
+ ``false`` as the second argument of the
+ :method:`Symfony\\Component\\OptionsResolver\\Options::offsetGet` method
+ to not trigger the deprecation warning.
+
+Instead of passing the message, you may also pass a closure which returns
+a string (the deprecation message) or an empty string to ignore the deprecation.
+This closure is useful to only deprecate some of the allowed types or values of
+the option::
+
+ $resolver
+ ->setDefault('encryption', null)
+ ->setDefault('port', null)
+ ->setAllowedTypes('port', ['null', 'int'])
+ ->setDeprecated('port', function (Options $options, $value) {
+ if (null === $value) {
+ return 'Passing "null" to option "port" is deprecated, pass an integer instead.';
+ }
+
+ // deprecation may also depend on another option
+ if ('ssl' === $options['encryption'] && 456 !== $value) {
+ return 'Passing a different port than "456" when the "encryption" option is set to "ssl" is deprecated.';
+ }
+
+ return '';
+ })
+ ;
+
+.. note::
+
+ Deprecation based on the value is triggered only when the option is provided
+ by the user.
+
+This closure receives as argument the value of the option after validating it
+and before normalizing it when the option is being resolved.
+
Performance Tweaks
~~~~~~~~~~~~~~~~~~
diff --git a/components/phpunit_bridge.rst b/components/phpunit_bridge.rst
index 409647e3a0c..df039b826c8 100644
--- a/components/phpunit_bridge.rst
+++ b/components/phpunit_bridge.rst
@@ -6,7 +6,8 @@ The PHPUnit Bridge
==================
The PHPUnit Bridge provides utilities to report legacy tests and usage of
- deprecated code and a helper for time and network-sensitive tests.
+ deprecated code and helpers for mocking native functions related to time,
+ DNS and class existence.
It comes with the following features:
@@ -19,17 +20,24 @@ It comes with the following features:
* Displays the stack trace of a deprecation on-demand;
-* Provides a ``ClockMock`` and ``DnsMock`` helper classes for time and network-sensitive tests;
+* Provides a ``ClockMock``, ``DnsMock`` and ``ClassExistsMock`` classes for tests
+ sensitive to time, network or class existence;
-* Provides a modified version of PHPUnit that does not embed ``symfony/yaml`` nor
- ``prophecy`` to prevent any conflicts with these dependencies;
+* Provides a modified version of PHPUnit that allows 1. separating the
+ dependencies of your app from those of phpunit to prevent any unwanted
+ constraints to apply; 2. running tests in parallel when a test suite is split
+ in several phpunit.xml files; 3. recording and replaying skipped tests;
+
+* It allows to create tests that are compatible with multiple PHPUnit versions
+ (because it provides polyfills for missing methods, namespaced aliases for
+ non-namespaced classes, etc.).
Installation
------------
.. code-block:: terminal
- $ composer require --dev "symfony/phpunit-bridge:*"
+ $ composer require --dev symfony/phpunit-bridge
.. include:: /components/require_autoload.rst.inc
@@ -80,10 +88,10 @@ After running your PHPUnit tests, you will get a report similar to this one:
.. code-block:: terminal
- $ phpunit -c app
+ $ ./vendor/bin/simple-phpunit
PHPUnit by Sebastian Bergmann.
- Configuration read from /app/phpunit.xml.dist
+ Configuration read from /phpunit.xml.dist
.................
Time: 1.77 seconds, Memory: 5.75Mb
@@ -93,8 +101,8 @@ After running your PHPUnit tests, you will get a report similar to this one:
Remaining deprecation notices (2)
getEntityManager is deprecated since Symfony 2.1. Use getManager instead: 2x
- 1x in DefaultControllerTest::testPublicUrls from AppBundle\Tests\Controller
- 1x in BlogControllerTest::testIndex from AppBundle\Tests\Controller
+ 1x in DefaultControllerTest::testPublicUrls from App\Tests\Controller
+ 1x in BlogControllerTest::testIndex from App\Tests\Controller
The summary includes:
@@ -204,7 +212,7 @@ message, enclosed with ``/``. For example, with:
-
+
@@ -215,42 +223,113 @@ message contains the ``"foobar"`` string.
Making Tests Fail
~~~~~~~~~~~~~~~~~
-By default, any non-legacy-tagged or any non-`@-silenced`_ deprecation notices
-will make tests fail. Alternatively, setting ``SYMFONY_DEPRECATIONS_HELPER`` to
-an arbitrary value (ex: ``320``) will make the tests fails only if a higher
-number of deprecation notices is reached (``0`` is the default value). You can
-also set the value ``"weak"`` which will make the bridge ignore any deprecation
-notices. This is useful to projects that must use deprecated interfaces for
-backward compatibility reasons.
+By default, any non-legacy-tagged or any non-`@-silenced`_ deprecation
+notices will make tests fail. Alternatively, you can configure an
+arbitrary threshold by setting ``SYMFONY_DEPRECATIONS_HELPER`` to
+``max[total]=320`` for instance. It will make the tests fails only if a
+higher number of deprecation notices is reached (``0`` is the default
+value).
+
+You can have even finer-grained control by using other keys of the ``max``
+array, which are ``self``, ``direct``, and ``indirect``. The
+``SYMFONY_DEPRECATIONS_HELPER`` environment variable accepts a URL-encoded
+string, meaning you can combine thresholds and any other configuration setting,
+like this: ``SYMFONY_DEPRECATIONS_HELPER=max[total]=42&max[self]=0&verbose=0``
+
+Internal deprecations
+.....................
When you maintain a library, having the test suite fail as soon as a dependency
introduces a new deprecation is not desirable, because it shifts the burden of
-fixing that deprecation to any contributor that happens to submit a pull
-request shortly after a new vendor release is made with that deprecation. To
-mitigate this, you can either use tighter requirements, in the hope that
+fixing that deprecation to any contributor that happens to submit a pull request
+shortly after a new vendor release is made with that deprecation.
+
+To mitigate this, you can either use tighter requirements, in the hope that
dependencies will not introduce deprecations in a patch version, or even commit
-the Composer lock file, which would create another class of issues. Libraries
-will often use ``SYMFONY_DEPRECATIONS_HELPER=weak`` because of this. This has
-the drawback of allowing contributions that introduce deprecations but:
+the ``composer.lock`` file, which would create another class of issues.
+Libraries will often use ``SYMFONY_DEPRECATIONS_HELPER=max[total]=999999``
+because of this. This has the drawback of allowing contributions that introduce
+deprecations but:
* forget to fix the deprecated calls if there are any;
* forget to mark appropriate tests with the ``@group legacy`` annotations.
-By using the ``"weak_vendors"`` value, deprecations that are triggered outside
-the ``vendors`` directory will make the test suite fail, while deprecations
-triggered from a library inside it will not, giving you the best of both
-worlds.
+By using ``SYMFONY_DEPRECATIONS_HELPER=max[self]=0``, deprecations that are
+triggered outside the ``vendors`` directory will be accounted for separately,
+while deprecations triggered from a library inside it will not (unless you reach
+999999 of these), giving you the best of both worlds.
+
+Direct and Indirect Deprecations
+................................
+
+When working on a project, you might be more interested in ``max[direct]``.
+Let's say you want to fix deprecations as soon as they appear. A problem many
+developers experience is that some dependencies they have tend to lag behind
+their own dependencies, meaning they do not fix deprecations as soon as
+possible, which means you should create a pull request on the outdated vendor,
+and ignore these deprecations until your pull request is merged.
+
+The ``max[direct]`` config allows you to put a threshold on direct deprecations
+only, allowing you to notice when *your code* is using deprecated APIs, and to
+keep up with the changes. You can still use ``max[indirect]`` if you want to
+keep indirect deprecations under a given threshold.
+
+Here is a summary that should help you pick the right configuration:
+
++------------------------+-----------------------------------------------------+
+| Value | Recommended situation |
++========================+=====================================================+
+| max[total]=0 | Recommended for actively maintained projects |
+| | with robust/no dependencies |
++------------------------+-----------------------------------------------------+
+| max[direct]=0 | Recommended for projects with dependencies |
+| | that fail to keep up with new deprecations. |
++------------------------+-----------------------------------------------------+
+| max[self]=0 | Recommended for libraries that use |
+| | the deprecation system themselves and |
+| | cannot afford to use one of the modes above. |
++------------------------+-----------------------------------------------------+
+
+Disabling the Verbose Output
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, the bridge will display a detailed output with the number of
+deprecations and where they arise. If this is too much for you, you can use
+``SYMFONY_DEPRECATIONS_HELPER=verbose=0`` to turn the verbose output off.
Disabling the Deprecation Helper
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Set the ``SYMFONY_DEPRECATIONS_HELPER`` environment variable to ``disabled`` to
-completely disable the deprecation helper. This is useful to make use of the
+Set the ``SYMFONY_DEPRECATIONS_HELPER`` environment variable to ``disabled=1``
+to completely disable the deprecation helper. This is useful to make use of the
rest of features provided by this component without getting errors or messages
related to deprecations.
.. _write-assertions-about-deprecations:
+Deprecation Notices at Autoloading Time
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, the PHPUnit Bridge uses ``DebugClassLoader`` from the
+:doc:`ErrorHandler component ` to throw deprecation
+notices at class autoloading time. This can be disabled with the
+``debug-class-loader`` option.
+
+.. code-block:: xml
+
+
+
+
+
+
+
+
+ 0
+
+
+
+
+
Write Assertions about Deprecations
-----------------------------------
@@ -293,6 +372,71 @@ Running the following command will display the full stack trace:
$ SYMFONY_DEPRECATIONS_HELPER='/Doctrine\\Common\\ClassLoader is deprecated\./' ./vendor/bin/simple-phpunit
+Testing with Multiple PHPUnit Versions
+--------------------------------------
+
+When testing a library that has to be compatible with several versions of PHP,
+the test suite cannot use the latest versions of PHPUnit because:
+
+* PHPUnit 8 deprecated several methods in favor of other methods which are not
+ available in older versions (e.g. PHPUnit 4);
+* PHPUnit 8 added the ``void`` return type to the ``setUp()`` method, which is
+ not compatible with PHP 5.5;
+* PHPUnit switched to namespaced classes starting from PHPUnit 6, so tests must
+ work with and without namespaces.
+
+Polyfills for the Unavailable Methods
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When using the ``simple-phpunit`` script, PHPUnit Bridge injects polyfills for
+most methods of the ``TestCase`` and ``Assert`` classes (e.g. ``expectException()``,
+``expectExceptionMessage()``, ``assertContainsEquals()``, etc.). This allows writing
+test cases using the latest best practices while still remaining compatible with
+older PHPUnit versions.
+
+Removing the Void Return Type
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When running the ``simple-phpunit`` script with the ``SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT``
+environment variable set to ``1``, the PHPUnit bridge will alter the code of
+PHPUnit to remove the return type (introduced in PHPUnit 8) from ``setUp()``,
+``tearDown()``, ``setUpBeforeClass()`` and ``tearDownAfterClass()`` methods.
+This allows you to write a test compatible with both PHP 5 and PHPUnit 8.
+
+Alternatively, you can use the trait :class:`Symfony\\Bridge\\PhpUnit\\SetUpTearDownTrait`,
+which provides the right signature for the ``setUp()``, ``tearDown()``,
+``setUpBeforeClass()`` and ``tearDownAfterClass()`` methods and delegates the
+call to the ``doSetUp()``, ``doTearDown()``, ``doSetUpBeforeClass()`` and
+``doTearDownAfterClass()`` methods::
+
+ use PHPUnit\Framework\TestCase;
+ use Symfony\Bridge\PhpUnit\SetUpTearDownTrait;
+
+ class MyTest extends TestCase
+ {
+ // when using the SetUpTearDownTrait, methods like doSetup() can
+ // be defined with and without the 'void' return type, as you wish
+ use SetUpTearDownTrait;
+
+ private function doSetup()
+ {
+ // ...
+ }
+
+ protected function doSetup(): void
+ {
+ // ...
+ }
+ }
+
+Using Namespaced PHPUnit Classes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The PHPUnit bridge adds namespaced class aliases for most of the PHPUnit classes
+declared without namespaces (e.g. ``PHPUnit_Framework_Assert``), allowing you to
+always use the namespaced class declaration even when the test is executed with
+PHPUnit 4.
+
Time-sensitive Tests
--------------------
@@ -457,20 +601,19 @@ functions:
Use Case
~~~~~~~~
-Consider the following example that uses the ``checkMX`` option of the ``Email``
-constraint to test the validity of the email domain::
+Consider the following example that tests a custom class called ``DomainValidator``
+which defines a ``checkDnsRecord`` option to also validate that a domain is
+associated to a valid host::
+ use App\Validator\DomainValidator;
use PHPUnit\Framework\TestCase;
- use Symfony\Component\Validator\Constraints\Email;
class MyTest extends TestCase
{
public function testEmail()
{
- $validator = ...
- $constraint = new Email(['checkMX' => true]);
-
- $result = $validator->validate('foo@example.com', $constraint);
+ $validator = new DomainValidator(['checkDnsRecord' => true]);
+ $isValid = $validator->validate('example.com');
// ...
}
@@ -480,22 +623,23 @@ In order to avoid making a real network connection, add the ``@dns-sensitive``
annotation to the class and use the ``DnsMock::withMockedHosts()`` to configure
the data you expect to get for the given hosts::
+ use App\Validator\DomainValidator;
use PHPUnit\Framework\TestCase;
- use Symfony\Component\Validator\Constraints\Email;
+ use Symfony\Bridge\PhpUnit\DnsMock;
/**
* @group dns-sensitive
*/
- class MyTest extends TestCase
+ class DomainValidatorTest extends TestCase
{
public function testEmails()
{
- DnsMock::withMockedHosts(['example.com' => [['type' => 'MX']]]);
+ DnsMock::withMockedHosts([
+ 'example.com' => [['type' => 'A', 'ip' => '1.2.3.4']],
+ ]);
- $validator = ...
- $constraint = new Email(['checkMX' => true]);
-
- $result = $validator->validate('foo@example.com', $constraint);
+ $validator = new DomainValidator(['checkDnsRecord' => true]);
+ $isValid = $validator->validate('example.com');
// ...
}
@@ -519,6 +663,78 @@ conditions::
],
]);
+Class Existence Based Tests
+---------------------------
+
+Tests that behave differently depending on existing classes, for example Composer's
+development dependencies, are often hard to test for the alternate case. For that
+reason, this component also provides mocks for these PHP functions:
+
+* :phpfunction:`class_exists`
+* :phpfunction:`interface_exists`
+* :phpfunction:`trait_exists`
+
+Use Case
+~~~~~~~~
+
+Consider the following example that relies on the ``Vendor\DependencyClass`` to
+toggle a behavior::
+
+ use Vendor\DependencyClass;
+
+ class MyClass
+ {
+ public function hello(): string
+ {
+ if (class_exists(DependencyClass::class)) {
+ return 'The dependency behavior.';
+ }
+
+ return 'The default behavior.';
+ }
+ }
+
+A regular test case for ``MyClass`` (assuming the development dependencies
+are installed during tests) would look like::
+
+ use MyClass;
+ use PHPUnit\Framework\TestCase;
+
+ class MyClassTest extends TestCase
+ {
+ public function testHello()
+ {
+ $class = new MyClass();
+ $result = $class->hello(); // "The dependency behavior."
+
+ // ...
+ }
+ }
+
+In order to test the default behavior instead use the
+``ClassExistsMock::withMockedClasses()`` to configure the expected
+classes, interfaces and/or traits for the code to run::
+
+ use MyClass;
+ use PHPUnit\Framework\TestCase;
+ use Vendor\DependencyClass;
+
+ class MyClassTest extends TestCase
+ {
+ // ...
+
+ public function testHelloDefault()
+ {
+ ClassExistsMock::register(MyClass::class);
+ ClassExistsMock::withMockedClasses([DependencyClass::class => false]);
+
+ $class = new MyClass();
+ $result = $class->hello(); // "The default behavior."
+
+ // ...
+ }
+ }
+
Troubleshooting
---------------
@@ -577,16 +793,11 @@ the namespaces of the tested classes in your ``phpunit.xml.dist``.
Modified PHPUnit script
-----------------------
-.. versionadded:: 3.2
-
- This modified PHPUnit script was introduced in the 3.2 version of
- this component.
-
This bridge provides a modified version of PHPUnit that you can call by using
its ``bin/simple-phpunit`` command. It has the following features:
-* Does not embed ``symfony/yaml`` nor ``prophecy`` to prevent any conflicts with
- these dependencies;
+* Works with a standalone vendor directory that doesn't conflict with yours;
+* Does not embed ``prophecy`` to prevent any conflicts with its dependencies;
* Uses PHPUnit 4.8 when run with PHP <=5.5, PHPUnit 5.7 when run with PHP >=5.6
and PHPUnit 6.5 when run with PHP >=7.2;
* Collects and replays skipped tests when the ``SYMFONY_PHPUNIT_SKIPPED_TESTS``
@@ -598,7 +809,20 @@ its ``bin/simple-phpunit`` command. It has the following features:
The script writes the modified PHPUnit it builds in a directory that can be
configured by the ``SYMFONY_PHPUNIT_DIR`` env var, or in the same directory as
-the ``simple-phpunit`` if it is not provided.
+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.:
@@ -614,13 +838,15 @@ If you have installed the bridge through Composer, you can run it by calling e.g
preferred method as it can be committed to your version control repository.
It's also possible to set ``SYMFONY_PHPUNIT_VERSION`` as a real env var
- (not defined in a :doc:`dotenv ` file).
+ (not defined in a :ref:`dotenv file `).
.. tip::
If you still need to use ``prophecy`` (but not ``symfony/yaml``),
then set the ``SYMFONY_PHPUNIT_REMOVE`` env var to ``symfony/yaml``.
+ It's also possible to set this env var in the ``phpunit.xml.dist`` file.
+
Code Coverage Listener
----------------------
diff --git a/components/process.rst b/components/process.rst
index ea626f31a38..baaff0f920b 100644
--- a/components/process.rst
+++ b/components/process.rst
@@ -12,7 +12,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/process:^3.4
+ $ composer require symfony/process
.. include:: /components/require_autoload.rst.inc
@@ -20,8 +20,11 @@ Installation
Usage
-----
-The :class:`Symfony\\Component\\Process\\Process` class allows you to execute
-a command in a sub-process::
+The :class:`Symfony\\Component\\Process\\Process` class executes a command in a
+sub-process, taking care of the differences between operating system and
+escaping arguments to prevent security issues. It replaces PHP functions like
+:phpfunction:`exec`, :phpfunction:`passthru`, :phpfunction:`shell_exec` and
+:phpfunction:`system`::
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;
@@ -36,9 +39,6 @@ a command in a sub-process::
echo $process->getOutput();
-The component takes care of the subtle differences between the different platforms
-when executing the command.
-
The ``getOutput()`` method always returns the whole content of the standard
output of the command and ``getErrorOutput()`` the content of the error
output. Alternatively, the :method:`Symfony\\Component\\Process\\Process::getIncrementalOutput`
@@ -78,10 +78,6 @@ for new output before going to the next iteration::
echo $data."\n";
}
- .. versionadded:: 3.2
-
- The ``getIterator()`` method was introduced in Symfony 3.2.
-
The ``mustRun()`` method is identical to ``run()``, except that it will throw
a :class:`Symfony\\Component\\Process\\Exception\\ProcessFailedException`
if the process couldn't be executed successfully (i.e. the process exited
@@ -102,37 +98,73 @@ with a non-zero code)::
.. tip::
- .. versionadded:: 3.3
+ You can get the last output time in seconds by using the
+ :method:`Symfony\\Component\\Process\\Process::getLastOutputTime` method.
+ This method returns ``null`` if the process wasn't started!
+
+Using Features From the OS Shell
+--------------------------------
+
+Using array of arguments is the recommended way to define commands. This
+saves you from any escaping and allows sending signals seamlessly
+(e.g. to stop processes while they run)::
+
+ $process = new Process(['/path/command', '--option', 'argument', 'etc.']);
+ $process = new Process(['/path/to/php', '--define', 'memory_limit=1024M', '/path/to/script.php']);
+
+If you need to use stream redirections, conditional execution, or any other
+feature provided by the shell of your operating system, you can also define
+commands as strings using the
+:method:`Symfony\\Component\\Process\\Process::fromShellCommandline` static
+factory.
+
+Each operating system provides a different syntax for their command-lines,
+so it becomes your responsibility to deal with escaping and portability.
- The ability to define commands as arrays of arguments was introduced in
- Symfony 3.3.
+When using strings to define commands, variable arguments are passed as
+environment variables using the second argument of the ``run()``,
+``mustRun()`` or ``start()`` methods. Referencing them is also OS-dependent::
- Using array of arguments is the recommended way to define commands. This
- saves you from any escaping and allows sending signals seamlessly
- (e.g. to stop processes before completion.)::
+ // On Unix-like OSes (Linux, macOS)
+ $process = Process::fromShellCommandline('echo "$MESSAGE"');
- $process = new Process(['/path/command', '--option', 'argument', 'etc.']);
- $process = new Process(['/path/to/php', '--define', 'memory_limit=1024M', '/path/to/script.php']);
+ // On Windows
+ $process = Process::fromShellCommandline('echo "!MESSAGE!"');
- If you need to use stream redirections, conditional execution, or any other
- feature provided by the shell of your operating system, you can also define
- commands as strings.
+ // On both Unix-like and Windows
+ $process->run(null, ['MESSAGE' => 'Something to output']);
- Please note that each OS provides a different syntax for their command-lines
- so that it becomes your responsibility to deal with escaping and portability.
+If you prefer to create portable commands that are independent from the
+operating system, you can write the above command as follows::
- To provide any variable arguments to command-line string, pass them as
- environment variables using the second argument of the ``run()``,
- ``mustRun()`` or ``start()`` methods. Referencing them is also OS-dependent::
+ // works the same on Windows , Linux and macOS
+ $process = Process::fromShellCommandline('echo "${:MESSAGE}"');
- // On Unix-like OSes (Linux, macOS)
- $process = new Process('echo "$MESSAGE"');
+Portable commands require using a syntax that is specific to the component: when
+enclosing a variable name into ``"${:`` and ``}"`` exactly, the process object
+will replace it with its escaped value, or will fail if the variable is not
+found in the list of environment variables attached to the command.
- // On Windows
- $process = new Process('echo "!MESSAGE!"');
+Setting Environment Variables for Processes
+-------------------------------------------
- // On both Unix-like and Windows
- $process->run(null, ['MESSAGE' => 'Something to output']);
+The constructor of the :class:`Symfony\\Component\\Process\\Process` class and
+all of its methods related to executing processes (``run()``, ``mustRun()``,
+``start()``, etc.) allow passing an array of environment variables to set while
+running the process::
+
+ $process = new Process(['...'], null, ['ENV_VAR_NAME' => 'value']);
+ $process = Process::fromShellCommandline('...', null, ['ENV_VAR_NAME' => 'value']);
+ $process->run(null, ['ENV_VAR_NAME' => 'value']);
+
+In addition to the env vars passed explicitly, processes inherit all the env
+vars defined in your system. You can prevent this by setting to ``false`` the
+env vars you want to remove::
+
+ $process = new Process(['...'], null, [
+ 'APP_ENV' => false,
+ 'SYMFONY_DOTENV_VARS' => false,
+ ]);
Getting real-time Process Output
--------------------------------
@@ -231,6 +263,23 @@ in the output and its type::
}
});
+Instead of waiting until the process has finished, you can use the
+:method:`Symfony\\Component\\Process\\Process::waitUntil` method to keep or stop
+waiting based on some PHP logic. The following example starts a long running
+process and checks its output to wait until its fully initialized::
+
+ $process = new Process(['/usr/bin/php', 'slow-starting-server.php']);
+ $process->start();
+
+ // ... do other things
+
+ // waits until the given anonymous function returns true
+ $process->waitUntil(function ($type, $output) {
+ return $output === 'Ready. Waiting for commands...';
+ });
+
+ // ... do things after the process is ready
+
Streaming to the Standard Input of a Process
--------------------------------------------
@@ -239,7 +288,7 @@ Before a process is started, you can specify its standard input using either the
of the constructor. The provided input can be a string, a stream resource or a
``Traversable`` object::
- $process = new Process('cat');
+ $process = new Process(['cat']);
$process->setInput('foobar');
$process->run();
@@ -325,6 +374,23 @@ instead::
);
$process->run();
+Using a Prepared Command Line
+-----------------------------
+
+You can run the process by using a a prepared command line using the
+double bracket notation. You can use a placeholder in order to have a
+process that can only be changed with the values and without changing
+the PHP code::
+
+ use Symfony\Component\Process\Process;
+
+ $process = Process::fromShellCommandline('echo "$name"');
+ $process->run(null, ['name' => 'Elsa']);
+
+.. caution::
+
+ A prepared command line will not be escaped automatically!
+
Process Timeout
---------------
@@ -387,10 +453,10 @@ When running a program asynchronously, you can send it POSIX signals with the
// will send a SIGKILL to the process
$process->signal(SIGKILL);
-Process PID
+Process Pid
-----------
-You can access the `PID`_ of a running process with the
+You can access the `pid`_ of a running process with the
:method:`Symfony\\Component\\Process\\Process::getPid` method::
use Symfony\Component\Process\Process;
@@ -425,11 +491,6 @@ 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.
- .. versionadded:: 3.1
-
- The ability to pass a callback to these methods when output is disabled
- was introduced in Symfony 3.1.
-
Finding the Executable PHP Binary
---------------------------------
@@ -443,6 +504,18 @@ absolute path of the executable PHP binary available on your server::
$phpBinaryPath = $phpBinaryFinder->find();
// $phpBinaryPath = '/usr/local/bin/php' (the result will be different on your computer)
-.. _`PID`: https://en.wikipedia.org/wiki/Process_identifier
+Checking for TTY Support
+------------------------
+
+Another utility provided by this component is a method called
+:method:`Symfony\\Component\\Process\\Process::isTtySupported` which returns
+whether `TTY`_ is supported on the current operating system::
+
+ use Symfony\Component\Process\Process;
+
+ $process = (new Process())->setTty(Process::isTtySupported());
+
+.. _`pid`: https://en.wikipedia.org/wiki/Process_identifier
.. _`PHP streams`: https://www.php.net/manual/en/book.stream.php
.. _`output_buffering`: https://www.php.net/manual/en/outcontrol.configuration.php
+.. _`TTY`: https://en.wikipedia.org/wiki/Tty_(unix)
diff --git a/components/property_access.rst b/components/property_access.rst
index d1732b29893..df0f3d99b51 100644
--- a/components/property_access.rst
+++ b/components/property_access.rst
@@ -13,7 +13,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/property-access:^3.4
+ $ composer require symfony/property-access
.. include:: /components/require_autoload.rst.inc
@@ -21,7 +21,7 @@ Usage
-----
The entry point of this component is the
-:method:`PropertyAccess::createPropertyAccessor`
+:method:`Symfony\\Component\\PropertyAccess\\PropertyAccess::createPropertyAccessor`
factory. This factory will create a new instance of the
:class:`Symfony\\Component\\PropertyAccess\\PropertyAccessor` class with the
default configuration::
@@ -34,8 +34,8 @@ Reading from Arrays
-------------------
You can read an array with the
-:method:`PropertyAccessor::getValue`
-method. This is done using the index notation that is used in PHP::
+:method:`Symfony\\Component\\PropertyAccess\\PropertyAccessor::getValue` method.
+This is done using the index notation that is used in PHP::
// ...
$person = [
@@ -166,6 +166,31 @@ getters, this means that you can do something like this::
This will produce: ``This person is an author``
+Accessing a non Existing Property Path
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default a :class:`Symfony\\Component\\PropertyAccess\\Exception\\NoSuchPropertyException`
+is thrown if the property path passed to :method:`Symfony\\Component\\PropertyAccess\\PropertyAccessor::getValue`
+does not exist. You can change this behavior using the
+:method:`Symfony\\Component\\PropertyAccess\\PropertyAccessorBuilder::disableExceptionOnInvalidPropertyPath`
+method::
+
+ // ...
+ class Person
+ {
+ public $name;
+ }
+
+ $person = new Person();
+
+ $propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
+ ->disableExceptionOnInvalidPropertyPath()
+ ->getPropertyAccessor();
+
+ // instead of throwing an exception the following code returns null
+ $value = $propertyAccessor->getValue($person, 'birthday');
+
+
Magic ``__get()`` Method
~~~~~~~~~~~~~~~~~~~~~~~~
@@ -229,7 +254,7 @@ enable this feature by using :class:`Symfony\\Component\\PropertyAccess\\Propert
.. caution::
The ``__call()`` feature is disabled by default, you can enable it by calling
- :method:`PropertyAccessorBuilder::enableMagicCall`
+ :method:`Symfony\\Component\\PropertyAccess\\PropertyAccessorBuilder::enableMagicCall`
see `Enable other Features`_.
Writing to Arrays
@@ -237,8 +262,7 @@ Writing to Arrays
The ``PropertyAccessor`` class can do more than just read an array, it can
also write to an array. This can be achieved using the
-:method:`PropertyAccessor::setValue`
-method::
+:method:`Symfony\\Component\\PropertyAccess\\PropertyAccessor::setValue` method::
// ...
$person = [];
@@ -374,10 +398,9 @@ Checking Property Paths
-----------------------
When you want to check whether
-:method:`PropertyAccessor::getValue`
-can safely be called without actually calling that method, you can use
-:method:`PropertyAccessor::isReadable`
-instead::
+:method:`Symfony\\Component\\PropertyAccess\\PropertyAccessor::getValue` can
+safely be called without actually calling that method, you can use
+:method:`Symfony\\Component\\PropertyAccess\\PropertyAccessor::isReadable` instead::
$person = new Person();
@@ -385,9 +408,8 @@ instead::
// ...
}
-The same is possible for :method:`PropertyAccessor::setValue`:
-Call the
-:method:`PropertyAccessor::isWritable`
+The same is possible for :method:`Symfony\\Component\\PropertyAccess\\PropertyAccessor::setValue`:
+Call the :method:`Symfony\\Component\\PropertyAccess\\PropertyAccessor::isWritable`
method to find out whether a property path can be updated::
$person = new Person();
diff --git a/components/property_info.rst b/components/property_info.rst
index 05814ddb884..ffce72e40bb 100644
--- a/components/property_info.rst
+++ b/components/property_info.rst
@@ -21,7 +21,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/property-info:^3.4
+ $ composer require symfony/property-info
.. include:: /components/require_autoload.rst.inc
@@ -46,23 +46,27 @@ provide it with a set of information extractors::
$phpDocExtractor = new PhpDocExtractor();
$reflectionExtractor = new ReflectionExtractor();
- // array of PropertyListExtractorInterface
+ // list of PropertyListExtractorInterface (any iterable)
$listExtractors = [$reflectionExtractor];
- // array of PropertyTypeExtractorInterface
+ // list of PropertyTypeExtractorInterface (any iterable)
$typeExtractors = [$phpDocExtractor, $reflectionExtractor];
- // array of PropertyDescriptionExtractorInterface
+ // list of PropertyDescriptionExtractorInterface (any iterable)
$descriptionExtractors = [$phpDocExtractor];
- // array of PropertyAccessExtractorInterface
+ // list of PropertyAccessExtractorInterface (any iterable)
$accessExtractors = [$reflectionExtractor];
+ // list of PropertyInitializableExtractorInterface (any iterable)
+ $propertyInitializableExtractors = [$reflectionExtractor];
+
$propertyInfo = new PropertyInfoExtractor(
$listExtractors,
$typeExtractors,
$descriptionExtractors,
- $accessExtractors
+ $accessExtractors,
+ $propertyInitializableExtractors
);
// see below for more examples
@@ -114,12 +118,13 @@ Extractable Information
-----------------------
The :class:`Symfony\\Component\\PropertyInfo\\PropertyInfoExtractor`
-class exposes public methods to extract four types of information:
+class exposes public methods to extract several types of information:
-* :ref:`List of properties `: `getProperties()`
-* :ref:`Property type `: `getTypes()`
-* :ref:`Property description `: `getShortDescription()` and `getLongDescription()`
-* :ref:`Property access details `: `isReadable()` and `isWritable()`
+* :ref:`List of properties `: :method:`Symfony\\Component\\PropertyInfo\\PropertyListExtractorInterface::getProperties`
+* :ref:`Property type `: :method:`Symfony\\Component\\PropertyInfo\\PropertyTypeExtractorInterface::getTypes`
+* :ref:`Property description `: :method:`Symfony\\Component\\PropertyInfo\\PropertyDescriptionExtractorInterface::getShortDescription` and :method:`Symfony\\Component\\PropertyInfo\\PropertyDescriptionExtractorInterface::getLongDescription`
+* :ref:`Property access details `: :method:`Symfony\\Component\\PropertyInfo\\PropertyAccessExtractorInterface::isReadable` and :method:`Symfony\\Component\\PropertyInfo\\PropertyAccessExtractorInterface::isWritable`
+* :ref:`Property initializable through the constructor `: :method:`Symfony\\Component\\PropertyInfo\\PropertyInitializableExtractorInterface::isInitializable`
.. note::
@@ -221,41 +226,29 @@ provide whether properties are readable or writable as booleans::
// Example Result: bool(false)
The :class:`Symfony\\Component\\PropertyInfo\\Extractor\\ReflectionExtractor` looks
-for getter/isser/setter method in addition to whether or not a property is public
-to determine if it's accessible.
-
-This is based on how :doc:`PropertyAccess ` works,
-so it even looks for adder/remover methods and can transform between singular
-and plural property names::
-
- use Acme\Entity\Analysis;
+for getter/isser/setter/hasser method in addition to whether or not a property is public
+to determine if it's accessible. This based on how the :doc:`PropertyAccess `
+works.
- class SomeClass
- {
- private $analyses;
-
- public function addAnalysis(Analysis $analysis)
- {
- // ...
- }
+.. _property-info-initializable:
- public function removeAnalysis(Analysis $analysis)
- {
- // ...
- }
- }
+Property Initializable Information
+----------------------------------
- // to be writable, both the adder and the remover methods must be defined
- $propertyInfo->isWritable(SomeClass::class, 'analyses'); // returns true
+Extractors that implement :class:`Symfony\\Component\\PropertyInfo\\PropertyInitializableExtractorInterface`
+provide whether properties are initializable through the class's constructor as booleans::
-.. versionadded:: 3.2
+ $propertyInfo->isInitializable($class, $property);
+ // Example Result: bool(true)
- The support of adder/remover methods was introduced in Symfony 3.2.
+:method:`Symfony\\Component\\PropertyInfo\\Extractor\\ReflectionExtractor::isInitializable`
+returns ``true`` if a constructor's parameter of the given class matches the
+given property name.
.. tip::
The main :class:`Symfony\\Component\\PropertyInfo\\PropertyInfoExtractor`
- class implements all four interfaces, delegating the extraction of property
+ class implements all interfaces, delegating the extraction of property
information to the extractors that have been registered with it.
This means that any method available on each of the extractors is also
@@ -322,9 +315,12 @@ a collection - a non-scalar value capable of containing other values. Currently
this returns ``true`` if:
* The :ref:`built-in PHP data type `
- is ``array``, or
+ is ``array``;
* The mutator method the property is derived from has a prefix of ``add``
- or ``remove`` (which are defined as the list of array mutator prefixes).
+ or ``remove`` (which are defined as the list of array mutator prefixes);
+* The `phpDocumentor`_ annotation is of type "collection" (e.g.
+ ``@var SomeClass``, ``@var SomeClass``,
+ ``@var Doctrine\Common\Collections\Collection``, etc.)
``Type::getCollectionKeyType()`` & ``Type::getCollectionValueType()``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -358,7 +354,9 @@ ReflectionExtractor
Using PHP reflection, the :class:`Symfony\\Component\\PropertyInfo\\Extractor\\ReflectionExtractor`
provides list, type and access information from setter and accessor methods.
-It can also provide return and scalar types for PHP 7+::
+It can also give the type of a property (even extracting it from the constructor
+arguments), and if it is initializable through the constructor. It supports
+return and scalar types for PHP 7::
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
@@ -366,12 +364,17 @@ It can also provide return and scalar types for PHP 7+::
// List information.
$reflectionExtractor->getProperties($class);
+
// Type information.
$reflectionExtractor->getTypes($class, $property);
+
// Access information.
$reflectionExtractor->isReadable($class, $property);
$reflectionExtractor->isWritable($class, $property);
+ // Initializable information
+ $reflectionExtractor->isInitializable($class, $property);
+
.. note::
When using the Symfony framework, this service is automatically registered
@@ -379,7 +382,7 @@ It can also provide return and scalar types for PHP 7+::
.. code-block:: yaml
- # app/config/config.yml
+ # config/packages/framework.yaml
framework:
property_info:
enabled: true
@@ -455,7 +458,7 @@ with the ``property_info`` service in the Symfony Framework::
'driver' => 'pdo_sqlite',
// ...
], $config);
- $doctrineExtractor = new DoctrineExtractor($entityManager->getMetadataFactory());
+ $doctrineExtractor = new DoctrineExtractor($entityManager);
// List information.
$doctrineExtractor->getProperties($class);
@@ -471,8 +474,9 @@ You can create your own property information extractors by creating a
class that implements one or more of the following interfaces:
:class:`Symfony\\Component\\PropertyInfo\\PropertyAccessExtractorInterface`,
:class:`Symfony\\Component\\PropertyInfo\\PropertyDescriptionExtractorInterface`,
-:class:`Symfony\\Component\\PropertyInfo\\PropertyListExtractorInterface`
-and :class:`Symfony\\Component\\PropertyInfo\\PropertyTypeExtractorInterface`.
+:class:`Symfony\\Component\\PropertyInfo\\PropertyListExtractorInterface`,
+:class:`Symfony\\Component\\PropertyInfo\\PropertyTypeExtractorInterface` and
+:class:`Symfony\\Component\\PropertyInfo\\PropertyInitializableExtractorInterface`.
If you have enabled the PropertyInfo component with the FrameworkBundle,
you can automatically register your extractor class with the ``property_info``
@@ -490,3 +494,4 @@ service by defining it as a service with one or more of the following
.. _`symfony/serializer`: https://packagist.org/packages/symfony/serializer
.. _`symfony/doctrine-bridge`: https://packagist.org/packages/symfony/doctrine-bridge
.. _`doctrine/orm`: https://packagist.org/packages/doctrine/orm
+.. _`phpDocumentor`: https://www.phpdoc.org/
diff --git a/components/routing.rst b/components/routing.rst
index d081f381bc6..f01b94defb1 100644
--- a/components/routing.rst
+++ b/components/routing.rst
@@ -6,59 +6,67 @@ The Routing Component
=====================
The Routing component maps an HTTP request to a set of configuration
- variables.
+ variables. It's used to build routing systems for web applications where
+ each URL is associated with some code to execute.
Installation
------------
.. code-block:: terminal
- $ composer require symfony/routing:^3.4
+ $ composer require symfony/routing
.. include:: /components/require_autoload.rst.inc
Usage
-----
-.. seealso::
+The main :doc:`Symfony routing ` article explains all the features of
+this component when used inside a Symfony application. This article only
+explains the things you need to do to use it in a non-Symfony PHP application.
- This article explains how to use the Routing features as an independent
- component in any PHP application. Read the :doc:`/routing` article to learn
- about how to use it in Symfony applications.
+Routing System Setup
+--------------------
-In order to set up a basic routing system you need three parts:
+A routing system has three parts:
-* A :class:`Symfony\\Component\\Routing\\RouteCollection`, which contains the route definitions (instances of the :class:`Symfony\\Component\\Routing\\Route` class)
-* A :class:`Symfony\\Component\\Routing\\RequestContext`, which has information about the request
-* A :class:`Symfony\\Component\\Routing\\Matcher\\UrlMatcher`, which performs the mapping of the request to a single route
+* A :class:`Symfony\\Component\\Routing\\RouteCollection`, which contains the
+ route definitions (instances of the :class:`Symfony\\Component\\Routing\\Route` class);
+* A :class:`Symfony\\Component\\Routing\\RequestContext`, which has information
+ about the request;
+* A :class:`Symfony\\Component\\Routing\\Matcher\\UrlMatcher`, which performs
+ the mapping of the path to a single route.
-Here is a quick example. Notice that this assumes that you've already configured
-your autoloader to load the Routing component::
+Here is a quick example::
+ use App\Controller\BlogController;
+ use Symfony\Component\Routing\Generator\UrlGenerator;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
- $route = new Route('/foo', ['_controller' => 'MyController']);
+ $route = new Route('/blog/{slug}', ['_controller' => BlogController::class]);
$routes = new RouteCollection();
- $routes->add('route_name', $route);
+ $routes->add('blog_show', $route);
$context = new RequestContext('/');
+ // Routing can match routes with incoming requests
$matcher = new UrlMatcher($routes, $context);
+ $parameters = $matcher->match('/blog/lorem-ipsum');
+ // $parameters = [
+ // '_controller' => 'App\Controller\BlogController',
+ // 'slug' => 'lorem-ipsum',
+ // '_route' => 'blog_show'
+ // ]
- $parameters = $matcher->match('/foo');
- // ['_controller' => 'MyController', '_route' => 'route_name']
-
-.. note::
-
- The :class:`Symfony\\Component\\Routing\\RequestContext` parameters can be populated
- with the values stored in ``$_SERVER``, but it's easier to use the HttpFoundation
- component as explained :ref:`below `.
-
-You can add as many routes as you like to a
-:class:`Symfony\\Component\\Routing\\RouteCollection`.
+ // Routing can also generate URLs for a given route
+ $generator = new UrlGenerator($routes, $context);
+ $url = $generator->generate('blog_show', [
+ 'slug' => 'my-blog-post',
+ ]);
+ // $url = '/blog/my-blog-post'
The :method:`RouteCollection::add() `
method takes two arguments. The first is the name of the route. The second
@@ -68,50 +76,19 @@ of custom variables can be *anything* that's significant to your application,
and is returned when that route is matched.
The :method:`UrlMatcher::match() `
-returns the variables you set on the route as well as the wildcard placeholders
-(see below). Your application can now use this information to continue
-processing the request. In addition to the configured variables, a ``_route``
-key is added, which holds the name of the matched route.
+returns the variables you set on the route as well as the route parameters.
+Your application can now use this information to continue processing the request.
+In addition to the configured variables, a ``_route`` key is added, which holds
+the name of the matched route.
If no matching route can be found, a
:class:`Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException` will
be thrown.
Defining Routes
-~~~~~~~~~~~~~~~
-
-A full route definition can contain up to eight parts:
+---------------
-#. The URL pattern. This is matched against the URL passed to the
- ``RequestContext``. It is not a regular expression, but can contain named
- wildcard placeholders (e.g. ``{slug}``) to match dynamic parts in the URL.
- The component will create the regular expression from it.
-
-#. An array of default parameters. This contains an array of arbitrary values
- that will be returned when the request matches the route. It is used by
- convention to map a controller to the route.
-
-#. An array of requirements. These define constraints for the values of the
- placeholders in the pattern as regular expressions.
-
-#. An array of options. These contain advanced settings for the route and
- can be used to control encoding or customize compilation.
- See :ref:`routing-unicode-support` below. You can learn more about them by
- reading :method:`Symfony\\Component\\Routing\\Route::setOptions` implementation.
-
-#. A host. This is matched against the host of the request. See
- :doc:`/routing/hostname_pattern` for more details.
-
-#. An array of schemes. These enforce a certain HTTP scheme (``http``, ``https``).
-
-#. An array of methods. These enforce a certain HTTP request method (``HEAD``,
- ``GET``, ``POST``, ...).
-
-#. A condition, using the :doc:`/components/expression_language/syntax`.
- A string that must evaluate to ``true`` so the route matches. See
- :doc:`/routing/conditions` for more details.
-
-Take the following route, which combines several of these ideas::
+A full route definition can contain up to eight parts::
$route = new Route(
'/archive/{month}', // path
@@ -137,36 +114,13 @@ Take the following route, which combines several of these ideas::
$parameters = $matcher->match('/archive/foo');
// throws ResourceNotFoundException
-In this case, the route is matched by ``/archive/2012-01``, because the ``{month}``
-wildcard matches the regular expression wildcard given. However, ``/archive/foo``
-does *not* match, because "foo" fails the month wildcard.
-
-When using wildcards, these are returned in the array result when calling
-``match``. The part of the path that the wildcard matched (e.g. ``2012-01``) is used
-as value.
-
-A placeholder matches any character except slashes ``/`` by default, unless you define
-a specific requirement for it.
-The reason is that they are used by convention to separate different placeholders.
-
-If you want a placeholder to match anything, it must be the last of the route::
-
- $route = new Route(
- '/start/{required}/{anything}',
- ['required' => 'default'], // should always be defined
- ['anything' => '.*'] // explicit requirement to allow "/"
- );
-
-Learn more about it by reading :ref:`routing/slash_in_parameter`.
-
-Using Prefixes and Collection Settings
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Route Collections
+-----------------
You can add routes or other instances of
:class:`Symfony\\Component\\Routing\\RouteCollection` to *another* collection.
-This way you can build a tree of routes. Additionally you can define a prefix
-and default values for the parameters, requirements, options, schemes and the
-host to all routes of a subtree using methods provided by the
+This way you can build a tree of routes. Additionally you can define common
+options for all routes of a subtree using methods provided by the
``RouteCollection`` class::
$rootCollection = new RouteCollection();
@@ -185,8 +139,8 @@ host to all routes of a subtree using methods provided by the
$rootCollection->addCollection($subCollection);
-Set the Request Parameters
-~~~~~~~~~~~~~~~~~~~~~~~~~~
+Setting the Request Parameters
+------------------------------
The :class:`Symfony\\Component\\Routing\\RequestContext` provides information
about the current request. You can define all parameters of an HTTP request
@@ -216,67 +170,15 @@ Normally you can pass the values from the ``$_SERVER`` variable to populate the
$context = new RequestContext();
$context->fromRequest(Request::createFromGlobals());
-Generate a URL
-~~~~~~~~~~~~~~
-
-While the :class:`Symfony\\Component\\Routing\\Matcher\\UrlMatcher` tries
-to find a route that fits the given request you can also build a URL from
-a certain route with the :class:`Symfony\\Component\\Routing\\Generator\\UrlGenerator`::
-
- use Symfony\Component\Routing\Generator\UrlGenerator;
- use Symfony\Component\Routing\RequestContext;
- use Symfony\Component\Routing\Route;
- use Symfony\Component\Routing\RouteCollection;
-
- $routes = new RouteCollection();
- $routes->add('show_post', new Route('/show/{slug}'));
-
- $context = new RequestContext('/');
-
- $generator = new UrlGenerator($routes, $context);
-
- $url = $generator->generate('show_post', [
- 'slug' => 'my-blog-post',
- ]);
- // /show/my-blog-post
-
-.. note::
-
- If you have defined a scheme, an absolute URL is generated if the scheme
- of the current :class:`Symfony\\Component\\Routing\\RequestContext` does
- not match the requirement.
-
-Check if a Route Exists
-~~~~~~~~~~~~~~~~~~~~~~~
-
-In highly dynamic applications, it may be necessary to check whether a route
-exists before using it to generate a URL. In those cases, don't use the
-:method:`Symfony\\Component\\Routing\\Router::getRouteCollection` method because
-that regenerates the routing cache and slows down the application.
-
-Instead, try to generate the URL and catch the
-:class:`Symfony\\Component\\Routing\\Exception\\RouteNotFoundException` thrown
-when the route doesn't exist::
-
- use Symfony\Component\Routing\Exception\RouteNotFoundException;
+Loading Routes
+--------------
- // ...
-
- try {
- $url = $generator->generate($dynamicRouteName, $parameters);
- } catch (RouteNotFoundException $e) {
- // the route is not defined...
- }
+The Routing component comes with a number of loader classes, each giving you the
+ability to load a collection of route definitions from external resources.
-Load Routes from a File
-~~~~~~~~~~~~~~~~~~~~~~~
+File Routing Loaders
+~~~~~~~~~~~~~~~~~~~~
-You've already seen how you can add routes to a collection right inside
-PHP. But you can also load routes from a number of different files.
-
-The Routing component comes with a number of loader classes, each giving
-you the ability to load a collection of route definitions from an external
-file of some format.
Each loader expects a :class:`Symfony\\Component\\Config\\FileLocator` instance
as the constructor argument. You can use the :class:`Symfony\\Component\\Config\\FileLocator`
to define an array of paths in which the loader will look for the requested files.
@@ -286,17 +188,18 @@ If you're using the ``YamlFileLoader``, then route definitions look like this:
.. code-block:: yaml
- # routes.yml
+ # routes.yaml
route1:
- path: /foo
- defaults: { _controller: 'MyController::fooAction' }
-
+ path: /foo
+ controller: MyController::fooAction
+ methods: GET|HEAD
route2:
- path: /foo/bar
- defaults: { _controller: 'MyController::foobarAction' }
+ path: /foo/bar
+ controller: FooBarInvokableController
+ methods: PUT
To load this file, you can use the following code. This assumes that your
-``routes.yml`` file is in the same directory as the below code::
+``routes.yaml`` file is in the same directory as the below code::
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Routing\Loader\YamlFileLoader;
@@ -304,7 +207,7 @@ To load this file, you can use the following code. This assumes that your
// looks inside *this* directory
$fileLocator = new FileLocator([__DIR__]);
$loader = new YamlFileLoader($fileLocator);
- $routes = $loader->load('routes.yml');
+ $routes = $loader->load('routes.yaml');
Besides :class:`Symfony\\Component\\Routing\\Loader\\YamlFileLoader` there are two
other loaders that work the same way:
@@ -313,23 +216,22 @@ other loaders that work the same way:
* :class:`Symfony\\Component\\Routing\\Loader\\PhpFileLoader`
If you use the :class:`Symfony\\Component\\Routing\\Loader\\PhpFileLoader` you
-have to provide the name of a PHP file which returns a :class:`Symfony\\Component\\Routing\\RouteCollection`::
+have to provide the name of a PHP file which returns a callable handling a
+:class:`Symfony\\Component\\Routing\\Loader\\Configurator\\RoutingConfigurator`.
+This class allows to chain imports, collections or simple route definition calls::
// RouteProvider.php
- use Symfony\Component\Routing\Route;
- use Symfony\Component\Routing\RouteCollection;
-
- $routes = new RouteCollection();
- $routes->add(
- 'route_name',
- new Route('/foo', ['_controller' => 'ExampleController'])
- );
- // ...
+ use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
- return $routes;
+ return function (RoutingConfigurator $routes) {
+ $routes->add('route_name', '/foo')
+ ->controller('ExampleController')
+ // ...
+ ;
+ };
-Routes as Closures
-..................
+Closure Routing Loaders
+~~~~~~~~~~~~~~~~~~~~~~~
There is also the :class:`Symfony\\Component\\Routing\\Loader\\ClosureLoader`, which
calls a closure and uses the result as a :class:`Symfony\\Component\\Routing\\RouteCollection`::
@@ -343,14 +245,28 @@ calls a closure and uses the result as a :class:`Symfony\\Component\\Routing\\Ro
$loader = new ClosureLoader();
$routes = $loader->load($closure);
-Routes as Annotations
-.....................
+Annotation Routing Loaders
+~~~~~~~~~~~~~~~~~~~~~~~~~~
Last but not least there are
:class:`Symfony\\Component\\Routing\\Loader\\AnnotationDirectoryLoader` and
:class:`Symfony\\Component\\Routing\\Loader\\AnnotationFileLoader` to load
-route definitions from class annotations. The specific details are left
-out here.
+route definitions from class annotations::
+
+ use Doctrine\Common\Annotations\AnnotationReader;
+ use Symfony\Bundle\FrameworkBundle\Routing\AnnotatedRouteControllerLoader;
+ use Symfony\Component\Config\FileLocator;
+ use Symfony\Component\Routing\Loader\AnnotationDirectoryLoader;
+
+ $loader = new AnnotationDirectoryLoader(
+ new FileLocator(__DIR__.'/app/controllers/'),
+ new AnnotatedRouteControllerLoader(
+ new AnnotationReader()
+ )
+ );
+
+ $routes = $loader->load(__DIR__.'/app/controllers/');
+ // ...
.. include:: /_includes/_annotation_loader_tip.rst.inc
@@ -379,7 +295,7 @@ automatically in the background if you want to use it. A basic example of the
$router = new Router(
new YamlFileLoader($fileLocator),
- 'routes.yml',
+ 'routes.yaml',
['cache_dir' => __DIR__.'/cache'],
$requestContext
);
@@ -392,182 +308,6 @@ automatically in the background if you want to use it. A basic example of the
are saved in the ``cache_dir``. This means your script must have write
permissions for that location.
-.. _routing-unicode-support:
-
-Unicode Routing Support
-~~~~~~~~~~~~~~~~~~~~~~~
-
-.. versionadded:: 3.2
-
- UTF-8 support for route paths and requirements was introduced in
- Symfony 3.2.
-
-The Routing component supports UTF-8 characters in route paths and requirements.
-Thanks to the ``utf8`` route option, you can make Symfony match and generate
-routes with UTF-8 characters:
-
-.. configuration-block::
-
- .. code-block:: php-annotations
-
- // src/AppBundle/Controller/DefaultController.php
- namespace AppBundle\Controller;
-
- use Symfony\Bundle\FrameworkBundle\Controller\Controller;
- use Symfony\Component\Routing\Annotation\Route;
-
- class DefaultController extends Controller
- {
- /**
- * @Route("/category/{name}", name="route1", options={"utf8": true})
- */
- public function categoryAction()
- {
- // ...
- }
-
- .. code-block:: yaml
-
- # app/config/routing.yml
- route1:
- path: /category/{name}
- defaults: { _controller: 'AppBundle:Default:category' }
- options:
- utf8: true
-
- .. code-block:: xml
-
-
-
-
-
-
- AppBundle:Default:category
- true
-
-
-
- .. code-block:: php
-
- // app/config/routing.php
- use Symfony\Component\Routing\Route;
- use Symfony\Component\Routing\RouteCollection;
-
- $routes = new RouteCollection();
- $routes->add('route1', new Route('/category/{name}',
- [
- '_controller' => 'AppBundle:Default:category',
- ],
- [],
- [
- 'utf8' => true,
- ]
- ));
-
- // ...
-
- return $routes;
-
-In this route, the ``utf8`` option set to ``true`` makes Symfony consider the
-``.`` requirement to match any UTF-8 characters instead of just a single
-byte character. This means that so the following URLs would match:
-``/category/日本語``, ``/category/فارسی``, ``/category/한국어``, etc. In case you
-are wondering, this option also allows to include and match emojis in URLs.
-
-You can also include UTF-8 strings as routing requirements:
-
-.. configuration-block::
-
- .. code-block:: php-annotations
-
- // src/AppBundle/Controller/DefaultController.php
- namespace AppBundle\Controller;
-
- use Symfony\Bundle\FrameworkBundle\Controller\Controller;
- use Symfony\Component\Routing\Annotation\Route;
-
- class DefaultController extends Controller
- {
- /**
- * @Route(
- * "/category/{name}",
- * name="route2",
- * defaults={"name": "한국어"},
- * options={"utf8": true}
- * )
- */
- public function defaultAction()
- {
- // ...
- }
-
- .. code-block:: yaml
-
- # app/config/routing.yml
- route2:
- path: /category/{name}
- defaults:
- _controller: 'AppBundle:Default:default'
- name: '한국어'
- options:
- utf8: true
-
- .. code-block:: xml
-
-
-
-
-
-
- AppBundle:Default:default
- 한국어
- true
-
-
-
- .. code-block:: php
-
- // app/config/routing.php
- use Symfony\Component\Routing\Route;
- use Symfony\Component\Routing\RouteCollection;
-
- $routes = new RouteCollection();
- $routes->add('route2', new Route('/default/{default}',
- [
- '_controller' => 'AppBundle:Default:default',
- 'name' => '한국어',
- ],
- [],
- [
- 'utf8' => true,
- ]
- ));
-
- // ...
-
- return $routes;
-
-.. tip::
-
- In addition to UTF-8 characters, the Routing component also supports all
- the `PCRE Unicode properties`_, which are escape sequences that match
- generic character types. For example, ``\p{Lu}`` matches any uppercase
- character in any language, ``\p{Greek}`` matches any Greek character,
- ``\P{Han}`` matches any character not included in the Chinese Han script.
-
-.. note::
-
- In Symfony 3.2, there is no need to explicitly set the ``utf8`` option.
- As soon as Symfony finds a UTF-8 character in the route path or requirements,
- it will automatically turn on the UTF-8 support. However, this behavior
- is deprecated and setting the option will be required in Symfony 4.0.
-
Learn more
----------
@@ -579,5 +319,3 @@ Learn more
/routing/*
/controller
/controller/*
-
-.. _PCRE Unicode properties: http://php.net/manual/en/regexp.reference.unicode.php
diff --git a/components/security.rst b/components/security.rst
index ae3d5c2ca69..9985b611c63 100644
--- a/components/security.rst
+++ b/components/security.rst
@@ -14,12 +14,6 @@ The Security Component
Installation
------------
-.. code-block:: terminal
-
- $ composer require symfony/security:^3.4
-
-.. include:: /components/require_autoload.rst.inc
-
The Security component is divided into several smaller sub-components which can
be used separately:
@@ -38,6 +32,17 @@ be used separately:
It brings many layers of authentication together, allowing the creation
of complex authentication systems.
+You can install each of them separately in your project:
+
+.. code-block:: terminal
+
+ $ composer require symfony/security-core
+ $ composer require symfony/security-http
+ $ composer require symfony/security-csrf
+ $ composer require symfony/security-guard
+
+.. include:: /components/require_autoload.rst.inc
+
.. seealso::
This article explains how to use the Security features as an independent
diff --git a/components/security/authentication.rst b/components/security/authentication.rst
index 4ff251ef271..7aa1da73f5d 100644
--- a/components/security/authentication.rst
+++ b/components/security/authentication.rst
@@ -13,7 +13,7 @@ an *authenticated* token if the supplied credentials were found to be valid.
The listener should then store the authenticated token using
:class:`the token storage `::
- use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+ use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
@@ -38,7 +38,7 @@ The listener should then store the authenticated token using
// ...
- public function handle(GetResponseEvent $event)
+ public function handle(RequestEvent $event)
{
$request = $event->getRequest();
@@ -90,8 +90,8 @@ authentication providers, each supporting a different type of token.
.. note::
- You may write your own authentication manager, it only has
- to implement :class:`Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationManagerInterface`.
+ You may write your own authentication manager, the only requirement is that
+ it implements :class:`Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationManagerInterface`.
.. _authentication_providers:
@@ -196,7 +196,7 @@ Creating a custom Password Encoder
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are many built-in password encoders. But if you need to create your
-own, it just needs to follow these rules:
+own, it needs to follow these rules:
#. The class must implement :class:`Symfony\\Component\\Security\\Core\\Encoder\\PasswordEncoderInterface`;
@@ -276,22 +276,24 @@ Authentication Events
The security component provides 4 related authentication events:
-=============================== ================================================ ==============================================================================
-Name Event Constant Argument Passed to the Listener
-=============================== ================================================ ==============================================================================
-security.authentication.success ``AuthenticationEvents::AUTHENTICATION_SUCCESS`` :class:`Symfony\\Component\\Security\\Core\\Event\\AuthenticationEvent`
-security.authentication.failure ``AuthenticationEvents::AUTHENTICATION_FAILURE`` :class:`Symfony\\Component\\Security\\Core\\Event\\AuthenticationFailureEvent`
-security.interactive_login ``SecurityEvents::INTERACTIVE_LOGIN`` :class:`Symfony\\Component\\Security\\Http\\Event\\InteractiveLoginEvent`
-security.switch_user ``SecurityEvents::SWITCH_USER`` :class:`Symfony\\Component\\Security\\Http\\Event\\SwitchUserEvent`
-=============================== ================================================ ==============================================================================
+=============================== ================================================================= ==============================================================================
+Name Event Constant Argument Passed to the Listener
+=============================== ================================================================= ==============================================================================
+security.authentication.success ``AuthenticationEvents::AUTHENTICATION_SUCCESS`` :class:`Symfony\\Component\\Security\\Core\\Event\\AuthenticationEvent`
+security.authentication.failure ``AuthenticationEvents::AUTHENTICATION_FAILURE`` :class:`Symfony\\Component\\Security\\Core\\Event\\AuthenticationFailureEvent`
+security.interactive_login ``SecurityEvents::INTERACTIVE_LOGIN`` :class:`Symfony\\Component\\Security\\Http\\Event\\InteractiveLoginEvent`
+security.switch_user ``SecurityEvents::SWITCH_USER`` :class:`Symfony\\Component\\Security\\Http\\Event\\SwitchUserEvent`
+security.logout_on_change ``Symfony\Component\Security\Http\Event\DeauthenticatedEvent`` :class:`Symfony\\Component\\Security\\Http\\Event\\DeauthenticatedEvent`
+=============================== ================================================================= ==============================================================================
Authentication Success and Failure Events
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When a provider authenticates the user, a ``security.authentication.success``
-event is dispatched. But beware - this event will fire, for example, on *every*
-request if you have session-based authentication. See ``security.interactive_login``
-below if you need to do something when a user *actually* logs in.
+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.
+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``),
a ``security.authentication.failure`` event is dispatched. You could listen on
@@ -314,6 +316,9 @@ order to give your user a welcome flash message every time they log in.
The ``security.switch_user`` event is triggered every time you activate
the ``switch_user`` firewall listener.
+The ``Symfony\Component\Security\Http\Event\DeauthenticatedEvent`` event is triggered when a token has been deauthenticated
+because of a user change, it can help you doing some clean-up task when a logout has been triggered.
+
.. seealso::
For more information on switching users, see
diff --git a/components/security/authorization.rst b/components/security/authorization.rst
index 7e3cc1e5874..fe2f2b913f5 100644
--- a/components/security/authorization.rst
+++ b/components/security/authorization.rst
@@ -104,10 +104,8 @@ level of authentication, i.e. is the user fully authenticated, or only based
on a "remember-me" cookie, or even authenticated anonymously?::
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver;
- use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
- use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken;
- $trustResolver = new AuthenticationTrustResolver(AnonymousToken::class, RememberMeToken::class);
+ $trustResolver = new AuthenticationTrustResolver();
$authenticatedVoter = new AuthenticatedVoter($trustResolver);
@@ -200,24 +198,10 @@ expressions have access to a number of
Roles
-----
-Roles are objects that give expression to a certain right the user has. The only
-requirement is that they must define a ``getRole()`` method that returns a
-string representation of the role itself. To do so, you can optionally extend
-from the default :class:`Symfony\\Component\\Security\\Core\\Role\\Role` class,
-which returns its first constructor argument in this method::
-
- use Symfony\Component\Security\Core\Role\Role;
-
- $role = new Role('ROLE_ADMIN');
-
- // shows 'ROLE_ADMIN'
- var_dump($role->getRole());
-
-.. note::
-
- Most authentication tokens extend from :class:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\AbstractToken`,
- which means that the roles given to its constructor will be
- automatically converted from strings to these simple ``Role`` objects.
+Roles are strings that give expression to a certain right the user has (e.g.
+*"edit a blog post"*, *"create an invoice"*). You can freely choose those
+strings. The only requirement is that they must start with the ``ROLE_`` prefix
+(e.g. ``ROLE_POST_EDIT``, ``ROLE_INVOICE_CREATE``).
Using the Decision Manager
--------------------------
diff --git a/components/security/firewall.rst b/components/security/firewall.rst
index 53bd823ad3f..17b7d03e0d2 100644
--- a/components/security/firewall.rst
+++ b/components/security/firewall.rst
@@ -91,10 +91,6 @@ further than allowed.
Firewall Config
~~~~~~~~~~~~~~~
-.. versionadded:: 3.2
-
- The ``FirewallConfig`` class was introduced in Symfony 3.2.
-
The information about a given firewall, such as its name, provider, context,
entry point and access denied URL, is provided by instances of the
:class:`Symfony\\Bundle\\SecurityBundle\\Security\\FirewallConfig` class.
diff --git a/components/serializer.rst b/components/serializer.rst
index 9b26a698a72..bad4af9572c 100644
--- a/components/serializer.rst
+++ b/components/serializer.rst
@@ -8,10 +8,11 @@ The Serializer Component
The Serializer component is meant to be used to turn objects into a
specific format (XML, JSON, YAML, ...) and the other way around.
-In order to do so, the Serializer component follows the following
-simple schema.
+In order to do so, the Serializer component follows the following schema.
-.. image:: /_images/components/serializer/serializer_workflow.png
+.. raw:: html
+
+
As you can see in the picture above, an array is used as an intermediary between
objects and serialized contents. This way, encoders will only deal with turning
@@ -26,7 +27,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/serializer:^3.4
+ $ composer require symfony/serializer
.. include:: /components/require_autoload.rst.inc
@@ -44,10 +45,9 @@ Usage
the Serializer in a Symfony application, read :doc:`/serializer` after you
finish this article.
-Using the Serializer component is really simple. You just need to set up
-the :class:`Symfony\\Component\\Serializer\\Serializer` specifying
-which encoders and normalizer are going to be available::
-
+To use the Serializer component, set up the
+:class:`Symfony\\Component\\Serializer\\Serializer` specifying which encoders
+and normalizer are going to be available::
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
@@ -166,14 +166,9 @@ needs three parameters:
#. The name of the class this information will be decoded to
#. The encoder used to convert that information into an array
-.. versionadded:: 3.3
-
- Support for the ``allow_extra_attributes`` key in the context was introduced
- in Symfony 3.3.
-
By default, additional attributes that are not mapped to the denormalized object
will be ignored by the Serializer component. If you prefer to throw an exception
-when this happens, set the ``allow_extra_attributes`` context option to
+when this happens, set the ``AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES`` context option to
``false`` and provide an object that implements ``ClassMetadataFactoryInterface``
when constructing the normalizer::
@@ -185,13 +180,15 @@ when constructing the normalizer::
EOF;
- // this will throw a Symfony\Component\Serializer\Exception\ExtraAttributesException
- // because "city" is not an attribute of the Person class
- $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
+ // $loader is any of the valid loaders explained later in this article
+ $classMetadataFactory = new ClassMetadataFactory($loader);
$normalizer = new ObjectNormalizer($classMetadataFactory);
$serializer = new Serializer([$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', [
- 'allow_extra_attributes' => false,
+ AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false,
]);
Deserializing in an Existing Object
@@ -212,11 +209,22 @@ The serializer can also be used to update an existing object::
EOF;
- $serializer->deserialize($data, Person::class, 'xml', ['object_to_populate' => $person]);
+ $serializer->deserialize($data, Person::class, 'xml', [AbstractNormalizer::OBJECT_TO_POPULATE => $person]);
// $person = App\Model\Person(name: 'foo', age: '69', sportsperson: true)
This is a common need when working with an ORM.
+The ``AbstractNormalizer::OBJECT_TO_POPULATE`` is only used for the top level object. If that object
+is the root of a tree structure, all child elements that exist in the
+normalized data will be re-created with new instances.
+
+When the ``AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE`` option is set to
+true, existing children of the root ``OBJECT_TO_POPULATE`` are updated from the
+normalized data, instead of the denormalizer re-creating them. Note that
+``DEEP_OBJECT_TO_POPULATE`` only works for single child objects, but not for
+arrays of objects. Those will still be replaced when present in the normalized
+data.
+
.. _component-serializer-attributes-groups:
Attributes Groups
@@ -266,7 +274,7 @@ for each format:
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;
- $classMetadataFactory = new ClassMetadataFactory(new YamlFileLoader('/path/to/your/definition.yml'));
+ $classMetadataFactory = new ClassMetadataFactory(new YamlFileLoader('/path/to/your/definition.yaml'));
* XML files::
@@ -346,7 +354,7 @@ You are now able to serialize only attributes in the groups you want::
$normalizer = new ObjectNormalizer($classMetadataFactory);
$serializer = new Serializer([$normalizer]);
- $data = $serializer->normalize($obj, null, ['groups' => ['group1']]);
+ $data = $serializer->normalize($obj, null, ['groups' => 'group1']);
// $data = ['foo' => 'foo'];
$obj2 = $serializer->denormalize(
@@ -357,8 +365,6 @@ You are now able to serialize only attributes in the groups you want::
);
// $obj2 = MyObj(foo: 'foo', bar: 'bar')
-.. include:: /_includes/_annotation_loader_tip.rst.inc
-
.. _ignoring-attributes-when-serializing:
Selecting Specific Attributes
@@ -366,6 +372,7 @@ Selecting Specific Attributes
It is also possible to serialize only a set of specific attributes::
+ use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
@@ -393,7 +400,7 @@ It is also possible to serialize only a set of specific attributes::
$serializer = new Serializer([new ObjectNormalizer()]);
- $data = $serializer->normalize($user, null, ['attributes' => ['familyName', 'company' => ['name']]]);
+ $data = $serializer->normalize($user, null, [AbstractNormalizer::ATTRIBUTES => ['familyName', 'company' => ['name']]]);
// $data = ['familyName' => 'Dunglas', 'company' => ['name' => 'Les-Tilleuls.coop']];
Only attributes that are not ignored (see below) are available.
@@ -404,26 +411,25 @@ As for groups, attributes can be selected during both the serialization and dese
Ignoring Attributes
-------------------
-.. note::
-
- Using attribute groups instead of the :method:`Symfony\\Component\\Serializer\\Normalizer\\AbstractNormalizer::setIgnoredAttributes`
- method is considered best practice.
-
-As an option, there's a way to ignore attributes from the origin object. To remove
-those attributes use the
-:method:`Symfony\\Component\\Serializer\\Normalizer\\AbstractNormalizer::setIgnoredAttributes`
-method on the normalizer definition::
+As an option, there's a way to ignore attributes from the origin object.
+To remove those attributes provide an array via the ``AbstractNormalizer::IGNORED_ATTRIBUTES``
+key in the ``context`` parameter of the desired serializer method::
+ use Acme\Person;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
+ use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
+ $person = new Person();
+ $person->setName('foo');
+ $person->setAge(99);
+
$normalizer = new ObjectNormalizer();
- $normalizer->setIgnoredAttributes(['age']);
$encoder = new JsonEncoder();
$serializer = new Serializer([$normalizer], [$encoder]);
- $serializer->serialize($person, 'json'); // Output: {"name":"foo","sportsperson":false}
+ $serializer->serialize($person, 'json', [AbstractNormalizer::IGNORED_ATTRIBUTES => ['age']]); // Output: {"name":"foo"}
.. _component-serializer-converting-property-names-when-serializing-and-deserializing:
@@ -433,7 +439,7 @@ Converting Property Names when Serializing and Deserializing
Sometimes serialized attributes must be named differently than properties
or getter/setter methods of PHP classes.
-The Serializer Component provides a handy way to translate or map PHP field
+The Serializer component provides a handy way to translate or map PHP field
names to serialized names: The Name Converter System.
Given you have the following object::
@@ -490,6 +496,12 @@ and :class:`Symfony\\Component\\Serializer\\Normalizer\\PropertyNormalizer`::
$companyCopy = $serializer->deserialize($json, Company::class, 'json');
// Same data as $company
+.. note::
+
+ You can also implement
+ :class:`Symfony\\Component\\Serializer\\NameConverter\\AdvancedNameConverterInterface`
+ to access to the current class name, format and context.
+
.. _using-camelized-method-names-for-underscored-attributes:
CamelCase to snake_case
@@ -531,6 +543,80 @@ processes::
$anne = $normalizer->denormalize(['first_name' => 'Anne'], 'Person');
// Person object with firstName: 'Anne'
+Configure name conversion using metadata
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When using this component inside a Symfony application and the class metadata
+factory is enabled as explained in the :ref:`Attributes Groups section `,
+this is already set up and you only need to provide the configuration. Otherwise::
+
+ // ...
+ use Symfony\Component\Serializer\Encoder\JsonEncoder;
+ use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter;
+ use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+ use Symfony\Component\Serializer\Serializer;
+
+ $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
+
+ $metadataAwareNameConverter = new MetadataAwareNameConverter($classMetadataFactory);
+
+ $serializer = new Serializer(
+ [new ObjectNormalizer($classMetadataFactory, $metadataAwareNameConverter)],
+ ['json' => new JsonEncoder()]
+ );
+
+Now configure your name conversion mapping. Consider an application that
+defines a ``Person`` entity with a ``firstName`` property:
+
+.. configuration-block::
+
+ .. code-block:: php-annotations
+
+ namespace App\Entity;
+
+ use Symfony\Component\Serializer\Annotation\SerializedName;
+
+ class Person
+ {
+ /**
+ * @SerializedName("customer_name")
+ */
+ private $firstName;
+
+ public function __construct($firstName)
+ {
+ $this->firstName = $firstName;
+ }
+
+ // ...
+ }
+
+ .. code-block:: yaml
+
+ App\Entity\Person:
+ attributes:
+ firstName:
+ serialized_name: customer_name
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+This custom mapping is used to convert property names when serializing and
+deserializing objects::
+
+ $serialized = $serializer->serialize(new Person("Kévin"));
+ // {"customer_name": "Kévin"}
+
Serializing Boolean Attributes
------------------------------
@@ -552,15 +638,19 @@ When serializing, you can set a callback to format a specific object property::
use Symfony\Component\Serializer\Serializer;
$encoder = new JsonEncoder();
- $normalizer = new GetSetMethodNormalizer();
- $callback = function ($dateTime) {
- return $dateTime instanceof \DateTime
- ? $dateTime->format(\DateTime::ISO8601)
- : '';
+ // all callback parameters are optional (you can omit the ones you don't use)
+ $dateCallback = function ($innerObject, $outerObject, string $attributeName, string $format = null, array $context = []) {
+ return $innerObject instanceof \DateTime ? $innerObject->format(\DateTime::ISO8601) : '';
};
- $normalizer->setCallbacks(['createdAt' => $callback]);
+ $defaultContext = [
+ AbstractNormalizer::CALLBACKS => [
+ 'createdAt' => $dateCallback,
+ ],
+ ];
+
+ $normalizer = new GetSetMethodNormalizer(null, null, null, null, null, $defaultContext);
$serializer = new Serializer([$normalizer], [$encoder]);
@@ -591,7 +681,7 @@ There are several types of normalizers available:
``getFirstName()`` -> ``firstName``).
The ``ObjectNormalizer`` is the most powerful normalizer. It is configured by
- default when using the Symfony Standard Edition with the serializer enabled.
+ default in Symfony applications with the Serializer component enabled.
:class:`Symfony\\Component\\Serializer\\Normalizer\\GetSetMethodNormalizer`
This normalizer reads the content of the class by calling the "getters"
@@ -609,11 +699,6 @@ There are several types of normalizers available:
Objects are normalized to a map of property names to property values.
- .. versionadded:: 3.4
-
- The ability to handle parent classes for ``PropertyNormalizer`` was
- introduced in Symfony 3.4.
-
:class:`Symfony\\Component\\Serializer\\Normalizer\\JsonSerializableNormalizer`
This normalizer works with classes that implement :phpclass:`JsonSerializable`.
@@ -632,10 +717,9 @@ There are several types of normalizers available:
:phpclass:`DateTime` and :phpclass:`DateTimeImmutable`) into strings.
By default, it uses the `RFC3339`_ format.
- .. versionadded:: 3.2
-
- Support for specifying datetime format during denormalization was
- introduced in the ``DateTimeNormalizer`` in Symfony 3.2.
+:class:`Symfony\\Component\\Serializer\\Normalizer\\DateTimeZoneNormalizer`
+ This normalizer converts :phpclass:`DateTimeZone` objects into strings that
+ represent the name of the timezone according to the `list of PHP timezones`_.
:class:`Symfony\\Component\\Serializer\\Normalizer\\DataUriNormalizer`
This normalizer converts :phpclass:`SplFileInfo` objects into a data URI
@@ -645,16 +729,35 @@ There are several types of normalizers available:
This normalizer converts :phpclass:`DateInterval` objects into strings.
By default, it uses the ``P%yY%mM%dDT%hH%iM%sS`` format.
- .. versionadded:: 3.4
-
- The ``DateIntervalNormalizer`` normalizer was introduced in Symfony 3.4.
+:class:`Symfony\\Component\\Serializer\\Normalizer\\ConstraintViolationListNormalizer`
+ This normalizer converts objects that implement
+ :class:`Symfony\\Component\\Validator\\ConstraintViolationListInterface`
+ into a list of errors according to the `RFC 7807`_ standard.
.. _component-serializer-encoders:
Encoders
--------
-The Serializer component supports many formats out of the box:
+Encoders turn **arrays** into **formats** and vice versa. They implement
+:class:`Symfony\\Component\\Serializer\\Encoder\\EncoderInterface`
+for encoding (array to format) and
+:class:`Symfony\\Component\\Serializer\\Encoder\\DecoderInterface` for decoding
+(format to array).
+
+You can add new encoders to a Serializer instance by using its second constructor argument::
+
+ use Symfony\Component\Serializer\Encoder\JsonEncoder;
+ use Symfony\Component\Serializer\Encoder\XmlEncoder;
+ use Symfony\Component\Serializer\Serializer;
+
+ $encoders = [new XmlEncoder(), new JsonEncoder()];
+ $serializer = new Serializer([], $encoders);
+
+Built-in Encoders
+~~~~~~~~~~~~~~~~~
+
+The Serializer component provides several built-in encoders:
:class:`Symfony\\Component\\Serializer\\Encoder\\JsonEncoder`
This class encodes and decodes data in `JSON`_.
@@ -669,12 +772,87 @@ The Serializer component supports many formats out of the box:
:class:`Symfony\\Component\\Serializer\\Encoder\\CsvEncoder`
This encoder encodes and decodes data in `CSV`_.
-All these encoders are enabled by default when using the Symfony Standard Edition
-with the serializer enabled.
+All these encoders are enabled by default when using the Serializer component
+in a Symfony application.
-.. versionadded:: 3.2
+The ``JsonEncoder``
+~~~~~~~~~~~~~~~~~~~
- The ``YamlEncoder`` and ``CsvEncoder`` encoders were introduced in Symfony 3.2
+The ``JsonEncoder`` encodes to and decodes from JSON strings, based on the PHP
+:phpfunction:`json_encode` and :phpfunction:`json_decode` functions.
+
+The ``CsvEncoder``
+~~~~~~~~~~~~~~~~~~~
+
+The ``CsvEncoder`` encodes to and decodes from CSV.
+
+The ``XmlEncoder``
+~~~~~~~~~~~~~~~~~~
+
+This encoder transforms arrays into XML and vice versa.
+
+For example, take an object normalized as following::
+
+ ['foo' => [1, 2], 'bar' => true];
+
+The ``XmlEncoder`` will encode this object like that::
+
+
+
+ 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::
+
+ $encoder = new XmlEncoder();
+ $encoder->encode([
+ 'foo' => ['@bar' => 'value'],
+ 'qux' => ['#comment' => 'A comment'],
+ ], 'xml');
+ // will return:
+ //
+ //
+ //
+ //
+ //
+
+You can pass the context key ``as_collection`` in order to have the results
+always as a collection.
+
+.. tip::
+
+ XML comments are ignored by default when decoding contents, but this
+ behavior can be changed with the optional ``$decoderIgnoredNodeTypes`` argument of
+ the ``XmlEncoder`` class constructor.
+
+ Data with ``#comment`` keys are encoded to XML comments by default. This can be
+ changed with the optional ``$encoderIgnoredNodeTypes`` argument of the
+ ``XmlEncoder`` class constructor.
+
+The ``YamlEncoder``
+~~~~~~~~~~~~~~~~~~~
+
+This encoder requires the :doc:`Yaml Component ` and
+transforms from and to Yaml.
+
+Skipping ``null`` Values
+------------------------
+
+By default, the Serializer will preserve properties containing a ``null`` value.
+You can change this behavior by setting the ``AbstractObjectNormalizer::SKIP_NULL_VALUES`` context option
+to ``true``::
+
+ $dummy = new class {
+ public $foo;
+ public $bar = 'notNull';
+ };
+
+ $normalizer = new ObjectNormalizer();
+ $result = $normalizer->normalize($dummy, 'json', [AbstractObjectNormalizer::SKIP_NULL_VALUES => true]);
+ // ['bar' => 'notNull']
.. _component-serializer-handling-circular-references:
@@ -751,20 +929,21 @@ when such a case is encountered::
echo $serializer->serialize($organization, 'json'); // Throws a CircularReferenceException
-The ``setCircularReferenceLimit()`` method of this normalizer sets the number
-of times it will serialize the same object before considering it a circular
-reference. Its default value is ``1``.
+The key ``circular_reference_limit`` in the default context sets the number of
+times it will serialize the same object before considering it a circular
+reference. The default value is ``1``.
Instead of throwing an exception, circular references can also be handled
by custom callables. This is especially useful when serializing entities
having unique identifiers::
$encoder = new JsonEncoder();
- $normalizer = new ObjectNormalizer();
-
- $normalizer->setCircularReferenceHandler(function ($object) {
- return $object->getName();
- });
+ $defaultContext = [
+ AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object, $format, $context) {
+ return $object->getName();
+ },
+ ];
+ $normalizer = new ObjectNormalizer(null, null, null, null, null, null, $defaultContext);
$serializer = new Serializer([$normalizer], [$encoder]);
var_dump($serializer->serialize($org, 'json'));
@@ -842,16 +1021,16 @@ Here, we set it to 2 for the ``$child`` property:
The metadata loader corresponding to the chosen format must be configured in
-order to use this feature. It is done automatically when using the Symfony
-Standard Edition. When using the standalone component, refer to
+order to use this feature. It is done automatically when using the Serializer component
+in a Symfony application. When using the standalone component, refer to
:ref:`the groups documentation ` to
learn how to do that.
-The check is only done if the ``enable_max_depth`` key of the serializer context
+The check is only done if the ``AbstractObjectNormalizer::ENABLE_MAX_DEPTH`` key of the serializer context
is set to ``true``. In the following example, the third level is not serialized
because it is deeper than the configured maximum depth of 2::
- $result = $serializer->normalize($level1, null, ['enable_max_depth' => true]);
+ $result = $serializer->normalize($level1, null, [AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true]);
/*
$result = [
'foo' => 'level1',
@@ -864,6 +1043,64 @@ because it is deeper than the configured maximum depth of 2::
];
*/
+Instead of throwing an exception, a custom callable can be executed when the
+maximum depth is reached. This is especially useful when serializing entities
+having unique identifiers::
+
+ use Doctrine\Common\Annotations\AnnotationReader;
+ use Symfony\Component\Serializer\Annotation\MaxDepth;
+ use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
+ use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
+ use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
+ use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+ use Symfony\Component\Serializer\Serializer;
+
+ class Foo
+ {
+ public $id;
+
+ /**
+ * @MaxDepth(1)
+ */
+ public $child;
+ }
+
+ $level1 = new Foo();
+ $level1->id = 1;
+
+ $level2 = new Foo();
+ $level2->id = 2;
+ $level1->child = $level2;
+
+ $level3 = new Foo();
+ $level3->id = 3;
+ $level2->child = $level3;
+
+ $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
+
+ // all callback parameters are optional (you can omit the ones you don't use)
+ $maxDepthHandler = function ($innerObject, $outerObject, string $attributeName, string $format = null, array $context = []) {
+ return '/foos/'.$innerObject->id;
+ };
+
+ $defaultContext = [
+ AbstractObjectNormalizer::MAX_DEPTH_HANDLER => $maxDepthHandler,
+ ];
+ $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, null, null, null, $defaultContext);
+
+ $serializer = new Serializer([$normalizer]);
+
+ $result = $serializer->normalize($level1, null, [AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true]);
+ /*
+ $result = [
+ 'id' => 1,
+ 'child' => [
+ 'id' => 2,
+ 'child' => '/foos/3',
+ ],
+ ];
+ */
+
Handling Arrays
---------------
@@ -975,14 +1212,50 @@ These are the options available:
``remove_empty_tags``
If set to true, removes all empty tags in the generated XML.
+Handling Constructor Arguments
+------------------------------
+
+If the class constructor defines arguments, as usually happens with
+`Value Objects`_, the serializer won't be able to create the object if some
+arguments are missing. In those cases, use the ``default_constructor_arguments``
+context option::
+
+ use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
+ use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+ use Symfony\Component\Serializer\Serializer;
+
+ class MyObj
+ {
+ private $foo;
+ private $bar;
+
+ public function __construct($foo, $bar)
+ {
+ $this->foo = $foo;
+ $this->bar = $bar;
+ }
+ }
+
+ $normalizer = new ObjectNormalizer($classMetadataFactory);
+ $serializer = new Serializer([$normalizer]);
+
+ $data = $serializer->denormalize(
+ ['foo' => 'Hello'],
+ 'MyObj',
+ [AbstractNormalizer::DEFAULT_CONSTRUCTOR_ARGUMENTS => [
+ 'MyObj' => ['foo' => '', 'bar' => ''],
+ ]]
+ );
+ // $data = new MyObj('Hello', '');
+
Recursive Denormalization and Type Safety
-----------------------------------------
-The Serializer Component can use the :doc:`PropertyInfo Component ` to denormalize
+The Serializer component can use the :doc:`PropertyInfo Component ` to denormalize
complex types (objects). The type of the class' property will be guessed using the provided
extractor and used to recursively denormalize the inner data.
-When using the Symfony Standard Edition, all normalizers are automatically configured to use the registered extractors.
+When using this component in a Symfony application, all normalizers are automatically configured to use the registered extractors.
When using the component standalone, an implementation of :class:`Symfony\\Component\\PropertyInfo\\PropertyTypeExtractorInterface`,
(usually an instance of :class:`Symfony\\Component\\PropertyInfo\\PropertyInfoExtractor`) must be passed as the 4th
parameter of the ``ObjectNormalizer``::
@@ -1045,6 +1318,125 @@ will be thrown. The type enforcement of the properties can be disabled by settin
the serializer context option ``ObjectNormalizer::DISABLE_TYPE_ENFORCEMENT``
to ``true``.
+Serializing Interfaces and Abstract Classes
+-------------------------------------------
+
+When dealing with objects that are fairly similar or share properties, you may
+use interfaces or abstract classes. The Serializer component allows you to
+serialize and deserialize these objects using a *"discriminator class mapping"*.
+
+The discriminator is the field (in the serialized string) used to differentiate
+between the possible objects. In practice, when using the Serializer component,
+pass a :class:`Symfony\\Component\\Serializer\\Mapping\\ClassDiscriminatorResolverInterface`
+implementation to the :class:`Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer`.
+
+The Serializer component provides an implementation of ``ClassDiscriminatorResolverInterface``
+called :class:`Symfony\\Component\\Serializer\\Mapping\\ClassDiscriminatorFromClassMetadata`
+which uses the class metadata factory and a mapping configuration to serialize
+and deserialize objects of the correct class.
+
+When using this component inside a Symfony application and the class metadata factory is enabled
+as explained in the :ref:`Attributes Groups section `,
+this is already set up and you only need to provide the configuration. Otherwise::
+
+ // ...
+ use Symfony\Component\Serializer\Encoder\JsonEncoder;
+ use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata;
+ use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping;
+ use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+ use Symfony\Component\Serializer\Serializer;
+
+ $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
+
+ $discriminator = new ClassDiscriminatorFromClassMetadata($classMetadataFactory);
+
+ $serializer = new Serializer(
+ [new ObjectNormalizer($classMetadataFactory, null, null, null, $discriminator)],
+ ['json' => new JsonEncoder()]
+ );
+
+Now configure your discriminator class mapping. Consider an application that
+defines an abstract ``CodeRepository`` class extended by ``GitHubCodeRepository``
+and ``BitBucketCodeRepository`` classes:
+
+.. configuration-block::
+
+ .. code-block:: php-annotations
+
+ namespace App;
+
+ use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
+
+ /**
+ * @DiscriminatorMap(typeProperty="type", mapping={
+ * "github"="App\GitHubCodeRepository",
+ * "bitbucket"="App\BitBucketCodeRepository"
+ * })
+ */
+ interface CodeRepository
+ {
+ // ...
+ }
+
+ .. code-block:: yaml
+
+ App\CodeRepository:
+ discriminator_map:
+ type_property: type
+ mapping:
+ github: 'App\GitHubCodeRepository'
+ bitbucket: 'App\BitBucketCodeRepository'
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+Once configured, the serializer uses the mapping to pick the correct class::
+
+ $serialized = $serializer->serialize(new GitHubCodeRepository());
+ // {"type": "github"}
+
+ $repository = $serializer->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
----------
@@ -1056,13 +1448,23 @@ Learn more
.. seealso::
- A popular alternative to the Symfony Serializer Component is the third-party
- library, `JMS serializer`_ (versions before `v1.12.0` were released under the Apache license, so incompatible with GPLv2 projects).
+ Normalizers for the Symfony Serializer Component supporting popular web API formats
+ (JSON-LD, GraphQL, OpenAPI, HAL, JSON:API) are available as part of the `API Platform`_ project.
+
+.. seealso::
+
+ A popular alternative to the Symfony Serializer component is the third-party
+ library, `JMS serializer`_ (versions before ``v1.12.0`` were released under
+ the Apache license, so incompatible with GPLv2 projects).
.. _`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
-.. _`JSON`: http://www.json.org/
-.. _`XML`: https://www.w3.org/XML/
-.. _`YAML`: http://yaml.org/
-.. _`CSV`: https://tools.ietf.org/html/rfc4180
+.. _RFC3339: https://tools.ietf.org/html/rfc3339#section-5.8
+.. _JSON: http://www.json.org/
+.. _XML: https://www.w3.org/XML/
+.. _YAML: http://yaml.org/
+.. _CSV: https://tools.ietf.org/html/rfc4180
+.. _`RFC 7807`: https://tools.ietf.org/html/rfc7807
+.. _`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
diff --git a/components/stopwatch.rst b/components/stopwatch.rst
index f89e8581b2b..4f6c8dc0420 100644
--- a/components/stopwatch.rst
+++ b/components/stopwatch.rst
@@ -12,7 +12,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/stopwatch:^3.4
+ $ composer require symfony/stopwatch
.. include:: /components/require_autoload.rst.inc
@@ -21,7 +21,7 @@ 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 simple
+:phpfunction:`microtime` by yourself. Instead, use the
:class:`Symfony\\Component\\Stopwatch\\Stopwatch` class::
use Symfony\Component\Stopwatch\Stopwatch;
@@ -48,9 +48,9 @@ while it is still running.
$stopwatch = new Stopwatch(true);
- .. versionadded:: 3.4
-
- Full precision support was introduced in Symfony 3.4.
+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::
@@ -61,9 +61,8 @@ Symfony Profiler tool uses categories to nicely color-code different events.
.. tip::
- When you want to show events in the Symfony profiler, autowire
- ``Symfony\Component\Stopwatch\Stopwatch`` into your service. Each category
- is shown on a separate line.
+ Read :ref:`this article ` to learn more about
+ integrating the Stopwatch component into the Symfony profiler.
Periods
-------
diff --git a/components/string.rst b/components/string.rst
new file mode 100644
index 00000000000..b3ca048b75e
--- /dev/null
+++ b/components/string.rst
@@ -0,0 +1,472 @@
+.. index::
+ single: String
+ single: Components; String
+
+The String Component
+====================
+
+ The String component provides a single object-oriented API to work with
+ three "unit systems" of strings: bytes, code points and grapheme clusters.
+
+.. versionadded:: 5.0
+
+ The String component was introduced in Symfony 5.0 as an
+ :doc:`experimental feature `.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require symfony/string
+
+.. include:: /components/require_autoload.rst.inc
+
+What is a String?
+-----------------
+
+You can skip this section if you already know what a *"code point"* or a
+*"grapheme cluster"* are in the context of handling strings. Otherwise, read
+this section to learn about the terminology used by this component.
+
+Languages like English require a very limited set of characters and symbols to
+display any content. Each string is a series of characters (letters or symbols)
+and they can be encoded even with the most limited standards (e.g. `ASCII`_).
+
+However, other languages require thousands of symbols to display their contents.
+They need complex encoding standards such as `Unicode`_ and concepts like
+"character" no longer make sense. Instead, you have to deal with these terms:
+
+* `Code points`_: they are the atomic unit of information. A string is a series
+ of code points. Each code point is a number whose meaning is given by the
+ `Unicode`_ standard. For example, the English letter ``A`` is the ``U+0041``
+ code point and the Japanese *kana* ``の`` is the ``U+306E`` code point.
+* `Grapheme clusters`_: they are a sequence of one or more code points which are
+ displayed as a single graphical unit. For example, the Spanish letter ``ñ`` is
+ a grapheme cluster that contains two code points: ``U+006E`` = ``n`` (*"latin
+ small letter N"*) + ``U+0303`` = ``◌̃`` (*"combining tilde"*).
+* Bytes: they are the actual information stored for the string contents. Each
+ code point can require one or more bytes of storage depending on the standard
+ being used (UTF-8, UTF-16, etc.).
+
+The following image displays the bytes, code points and grapheme clusters for
+the same word written in English (``hello``) and Hindi (``नमस्ते``):
+
+.. image:: /_images/components/string/bytes-points-graphemes.png
+ :align: center
+
+Usage
+-----
+
+Create a new object of type :class:`Symfony\\Component\\String\\ByteString`,
+:class:`Symfony\\Component\\String\\CodePointString` or
+:class:`Symfony\\Component\\String\\UnicodeString`, pass the string contents as
+their arguments and then use the object-oriented API to work with those strings::
+
+ use Symfony\Component\String\UnicodeString;
+
+ $text = (new UnicodeString('This is a déjà-vu situation.'))
+ ->trimEnd('.')
+ ->replace('déjà-vu', 'jamais-vu')
+ ->append('!');
+ // $text = 'This is a jamais-vu situation!'
+
+ $content = new UnicodeString('नमस्ते दुनिया');
+ if ($content->ignoreCase()->startsWith('नमस्ते')) {
+ // ...
+ }
+
+Method Reference
+----------------
+
+Methods to Create String Objects
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+First, you can create objects prepared to store strings as bytes, code points
+and grapheme clusters with the following classes::
+
+ use Symfony\Component\String\ByteString;
+ use Symfony\Component\String\CodePointString;
+ use Symfony\Component\String\UnicodeString;
+
+ $foo = new ByteString('hello');
+ $bar = new CodePointString('hello');
+ // UnicodeString is the most commonly used class
+ $baz = new UnicodeString('hello');
+
+Use the ``wrap()`` static method to instantiate more than one string object::
+
+ $contents = ByteString::wrap(['hello', 'world']); // $contents = ByteString[]
+ $contents = UnicodeString::wrap(['I', '❤️', 'Symfony']); // $contents = UnicodeString[]
+
+ // use the unwrap method to make the inverse conversion
+ $contents = UnicodeString::unwrap([
+ new UnicodeString('hello'), new UnicodeString('world'),
+ ]); // $contents = ['hello', 'world']
+
+There are two shortcut functions called ``b()`` and ``u()`` to create
+``ByteString`` and ``UnicodeString`` objects::
+
+ // ...
+ use function Symfony\Component\String\b;
+ use function Symfony\Component\String\u;
+
+ // both are equivalent
+ $foo = new ByteString('hello');
+ $foo = b('hello');
+
+ // both are equivalent
+ $baz = new UnicodeString('hello');
+ $baz = u('hello');
+
+There are also some specialized constructors::
+
+ // ByteString can create a random string of the given length
+ $foo = ByteString::fromRandom(12);
+
+ // CodePointString and UnicodeString can create a string from code points
+ $foo = UnicodeString::fromCodePoints(0x928, 0x92E, 0x938, 0x94D, 0x924, 0x947);
+ // equivalent to: $foo = new UnicodeString('नमस्ते');
+
+Methods to Transform String Objects
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Each string object can be transformed into the other two types of objects::
+
+ $foo = ByteString::fromRandom(12)->toCodePointString();
+ $foo = (new CodePointString('hello'))->toUnicodeString();
+ $foo = UnicodeString::fromCodePoints(0x68, 0x65, 0x6C, 0x6C, 0x6F)->toByteString();
+
+ // the optional $toEncoding argument defines the encoding of the target string
+ $foo = (new CodePointString('hello'))->toByteString('Windows-1252');
+ // the optional $fromEncoding argument defines the encoding of the original string
+ $foo = (new ByteString('さよなら'))->toCodePointString('ISO-2022-JP');
+
+If the conversion is not possible for any reason, you'll get an
+:class:`Symfony\\Component\\String\\Exception\\InvalidArgumentException`.
+
+Methods Related to Length and White Spaces
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+ // returns the number of graphemes, code points or bytes of the given string
+ $word = 'नमस्ते';
+ (new ByteString($word))->length(); // 18 (bytes)
+ (new CodePointString($word))->length(); // 6 (code points)
+ (new UnicodeString($word))->length(); // 4 (graphemes)
+
+ // some symbols require double the width of others to represent them when using
+ // a monospaced font (e.g. in a console). This method returns the total width
+ // needed to represent the entire word
+ $word = 'नमस्ते';
+ (new ByteString($word))->width(); // 18
+ (new CodePointString($word))->width(); // 4
+ (new UnicodeString($word))->width(); // 4
+ // if the text contains multiple lines, it returns the max width of all lines
+ $text = "<<width(); // 14
+
+ // only returns TRUE if the string is exactly an empty string (not even white spaces)
+ u('hello world')->isEmpty(); // false
+ u(' ')->isEmpty(); // false
+ u('')->isEmpty(); // true
+
+ // removes all white spaces from the start and end of the string and replaces two
+ // or more consecutive white spaces inside contents by a single white space
+ u(" \n\n hello world \n \n")->collapseWhitespace(); // 'hello world'
+
+Methods to Change Case
+~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+ // changes all graphemes/code points to lower case
+ u('FOO Bar')->lower(); // 'foo bar'
+
+ // when dealing with different languages, uppercase/lowercase is not enough
+ // there are three cases (lower, upper, title), some characters have no case,
+ // case is context-sensitive and locale-sensitive, etc.
+ // this method returns a string that you can use in case-insensitive comparisons
+ u('FOO Bar')->folded(); // 'foo bar'
+ u('Die O\'Brian Straße')->folded(); // "die o'brian strasse"
+
+ // changes all graphemes/code points to upper case
+ u('foo BAR')->upper(); // 'FOO BAR'
+
+ // changes all graphemes/code points to "title case"
+ u('foo bar')->title(); // 'Foo bar'
+ u('foo bar')->title(true); // 'Foo Bar'
+
+ // changes all graphemes/code points to camelCase
+ u('Foo: Bar-baz.')->camel(); // 'fooBarBaz'
+ // changes all graphemes/code points to snake_case
+ u('Foo: Bar-baz.')->snake(); // 'foo_bar_baz'
+ // other cases can be achieved by chaining methods. E.g. PascalCase:
+ u('Foo: Bar-baz.')->camel()->title(); // 'FooBarBaz'
+
+The methods of all string classes are case-sensitive by default. You can perform
+case-insensitive operations with the ``ignoreCase()`` method::
+
+ u('abc')->indexOf('B'); // null
+ u('abc')->ignoreCase()->indexOf('B'); // 1
+
+Methods to Append and Prepend
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+ // adds the given content (one or more strings) at the beginning/end of the string
+ u('world')->prepend('hello'); // 'helloworld'
+ u('world')->prepend('hello', ' '); // 'hello world'
+
+ u('hello')->append('world'); // 'helloworld'
+ u('hello')->append(' ', 'world'); // 'hello world'
+
+ // adds the given content at the beginning of the string (or removes it) to
+ // make sure that the content starts exactly with that content
+ u('Name')->ensureStart('get'); // 'getName'
+ u('getName')->ensureStart('get'); // 'getName'
+ u('getgetName')->ensureStart('get'); // 'getName'
+ // this method is similar, but works on the end of the content instead of on the beginning
+ u('User')->ensureEnd('Controller'); // 'UserController'
+ u('UserController')->ensureEnd('Controller'); // 'UserController'
+ u('UserControllerController')->ensureEnd('Controller'); // 'UserController'
+
+ // returns the contents found before/after the first occurrence of the given string
+ u('hello world')->before('world'); // 'hello '
+ u('hello world')->before('o'); // 'hell'
+ u('hello world')->before('o', true); // 'hello'
+
+ u('hello world')->after('hello'); // ' world'
+ u('hello world')->after('o'); // ' world'
+ u('hello world')->after('o', true); // 'o world'
+
+ // returns the contents found before/after the last occurrence of the given string
+ u('hello world')->beforeLast('o'); // 'hello w'
+ u('hello world')->beforeLast('o', true); // 'hello wo'
+
+ u('hello world')->afterLast('o'); // 'rld'
+ u('hello world')->afterLast('o', true); // 'orld'
+
+Methods to Pad and Trim
+~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+ // makes a string as long as the first argument by adding the given
+ // string at the beginning, end or both sides of the string
+ u(' Lorem Ipsum ')->padBoth(20, '-'); // '--- Lorem Ipsum ----'
+ u(' Lorem Ipsum')->padStart(20, '-'); // '-------- Lorem Ipsum'
+ u('Lorem Ipsum ')->padEnd(20, '-'); // 'Lorem Ipsum --------'
+
+ // repeats the given string the number of times passed as argument
+ u('_.')->repeat(10); // '_._._._._._._._._._.'
+
+ // removes the given characters (by default, white spaces) from the string
+ u(' Lorem Ipsum ')->trim(); // 'Lorem Ipsum'
+ u('Lorem Ipsum ')->trim('m'); // 'Lorem Ipsum '
+ u('Lorem Ipsum')->trim('m'); // 'Lorem Ipsu'
+
+ u(' Lorem Ipsum ')->trimStart(); // 'Lorem Ipsum '
+ u(' Lorem Ipsum ')->trimEnd(); // ' Lorem Ipsum'
+
+Methods to Search and Replace
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+ // checks if the string starts/ends with the given string
+ u('https://symfony.com')->startsWith('https'); // true
+ u('report-1234.pdf')->endsWith('.pdf'); // true
+
+ // checks if the string contents are exactly the same as the given contents
+ u('foo')->equalsTo('foo'); // true
+
+ // checks if the string content match the given regular expression
+ u('avatar-73647.png')->match('/avatar-(\d+)\.png/');
+ // result = ['avatar-73647.png', '73647']
+
+ // finds the position of the first occurrence of the given string
+ // (the second argument is the position where the search starts and negative
+ // values have the same meaning as in PHP functions)
+ u('abcdeabcde')->indexOf('c'); // 2
+ u('abcdeabcde')->indexOf('c', 2); // 2
+ u('abcdeabcde')->indexOf('c', -4); // 7
+ u('abcdeabcde')->indexOf('eab'); // 4
+ u('abcdeabcde')->indexOf('k'); // null
+
+ // finds the position of the last occurrence of the given string
+ // (the second argument is the position where the search starts and negative
+ // values have the same meaning as in PHP functions)
+ u('abcdeabcde')->indexOfLast('c'); // 7
+ u('abcdeabcde')->indexOfLast('c', 2); // 7
+ u('abcdeabcde')->indexOfLast('c', -4); // 2
+ u('abcdeabcde')->indexOfLast('eab'); // 4
+ u('abcdeabcde')->indexOfLast('k'); // null
+
+ // replaces all occurrences of the given string
+ u('http://symfony.com')->replace('http://', 'https://'); // 'https://symfony.com'
+ // replaces all occurrences of the given regular expression
+ u('(+1) 206-555-0100')->replaceMatches('/[^A-Za-z0-9]++/', ''); // '12065550100'
+ // you can pass a callable as the second argument to perform advanced replacements
+ u('123')->replaceMatches('/\d/', function ($match) {
+ return '['.$match[0].']';
+ }); // result = '[1][2][3]'
+
+Methods to Join, Split, Truncate and Reverse
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+ // uses the string as the "glue" to merge all the given strings
+ u(', ')->join(['foo', 'bar']); // 'foo, bar'
+
+ // breaks the string into pieces using the given delimiter
+ u('template_name.html.twig')->split('.'); // ['template_name', 'html', 'twig']
+ // you can set the maximum number of pieces as the second argument
+ u('template_name.html.twig')->split('.', 2); // ['template_name', 'html.twig']
+
+ // returns a substring which starts at the first argument and has the length of the
+ // second optional argument (negative values have the same meaning as in PHP functions)
+ u('Symfony is great')->slice(0, 7); // 'Symfony'
+ u('Symfony is great')->slice(0, -6); // 'Symfony is'
+ u('Symfony is great')->slice(11); // 'great'
+ u('Symfony is great')->slice(-5); // 'great'
+
+ // reduces the string to the length given as argument (if it's longer)
+ u('Lorem Ipsum')->truncate(80); // 'Lorem Ipsum'
+ u('Lorem Ipsum')->truncate(3); // 'Lor'
+ u('Lorem Ipsum')->truncate(8, '…'); // 'Lorem I…'
+
+ // breaks the string into lines of the given length
+ u('Lorem Ipsum')->wordwrap(4); // 'Lorem\nIpsum'
+ // by default it breaks by white space; pass TRUE to break unconditionally
+ u('Lorem Ipsum')->wordwrap(4, "\n", true); // 'Lore\nm\nIpsu\nm'
+
+ // replaces a portion of the string with the given contents:
+ // the second argument is the position where the replacement starts;
+ // the third argument is the number of graphemes/code points removed from the string
+ u('0123456789')->splice('xxx'); // 'xxx'
+ u('0123456789')->splice('xxx', 0, 2); // 'xxx23456789'
+ u('0123456789')->splice('xxx', 0, 6); // 'xxx6789'
+ u('0123456789')->splice('xxx', 6); // '012345xxx'
+
+ // breaks the string into pieces of the length given as argument
+ u('0123456789')->chunk(3); // ['012', '345', '678', '9']
+
+ // reverses the order of the string contents
+ u('foo bar')->reverse(); // 'rab oof'
+ u('さよなら')->reverse(); // 'らなよさ'
+
+.. versionadded:: 5.1
+
+ The ``reverse()`` method was introduced in Symfony 5.1.
+
+Methods Added by ByteString
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+These methods are only available for ``ByteString`` objects::
+
+ // returns TRUE if the string contents are valid UTF-8 contents
+ b('Lorem Ipsum')->isUtf8(); // true
+ b("\xc3\x28")->isUtf8(); // false
+
+ // returns the value of the byte stored at the given position
+ // ('नमस्ते' bytes = [224, 164, 168, 224, 164, 174, 224, 164, 184,
+ // 224, 165, 141, 224, 164, 164, 224, 165, 135])
+ b('नमस्ते')->byteCode(0); // 224
+ b('नमस्ते')->byteCode(17); // 135
+
+Methods Added by CodePointString and UnicodeString
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+These methods are only available for ``CodePointString`` and ``UnicodeString``
+objects::
+
+ // transliterates any string into the latin alphabet defined by the ASCII encoding
+ // (don't use this method to build a slugger because this component already provides
+ // a slugger, as explained later in this article)
+ u('नमस्ते')->ascii(); // 'namaste'
+ u('さよなら')->ascii(); // 'sayonara'
+ u('спасибо')->ascii(); // 'spasibo'
+
+ // returns an array with the code point or points stored at the given position
+ // (code points of 'नमस्ते' graphemes = [2344, 2350, 2360, 2340]
+ u('नमस्ते')->codePointsAt(0); // [2344]
+ u('नमस्ते')->codePointsAt(2); // [2360]
+
+`Unicode equivalence`_ is the specification by the Unicode standard that
+different sequences of code points represent the same character. For example,
+the Swedish letter ``å`` can be a single code point (``U+00E5`` = *"latin small
+letter A with ring above"*) or a sequence of two code points (``U+0061`` =
+*"latin small letter A"* + ``U+030A`` = *"combining ring above"*). The
+``normalize()`` method allows to pick the normalization mode::
+
+ // these encode the letter as a single code point: U+00E5
+ u('å')->normalize(UnicodeString::NFC);
+ u('å')->normalize(UnicodeString::NFKC);
+ // these encode the letter as two code points: U+0061 + U+030A
+ u('å')->normalize(UnicodeString::NFD);
+ u('å')->normalize(UnicodeString::NFKD);
+
+Slugger
+-------
+
+In some contexts, such as URLs and file/directory names, it's not safe to use
+any Unicode character. A *slugger* transforms a given string into another string
+that only includes safe ASCII characters::
+
+ use Symfony\Component\String\Slugger\AsciiSlugger;
+
+ $slugger = new AsciiSlugger();
+ $slug = $slugger->slug('Wôrķšƥáçè ~~sèťtïñğš~~');
+ // $slug = 'Workspace-settings'
+
+The separator between words is a dash (``-``) by default, but you can define
+another separator as the second argument::
+
+ $slug = $slugger->slug('Wôrķšƥáçè ~~sèťtïñğš~~', '/');
+ // $slug = 'Workspace/settings'
+
+The slugger transliterates the original string into the Latin script before
+applying the other transformations. The locale of the original string is
+detected automatically, but you can define it explicitly::
+
+ // this tells the slugger to transliterate from Korean language
+ $slugger = new AsciiSlugger('ko');
+
+ // you can override the locale as the third optional parameter of slug()
+ $slug = $slugger->slug('...', '-', 'fa');
+
+In a Symfony application, you don't need to create the slugger yourself. Thanks
+to :doc:`service autowiring `, you can inject a
+slugger by type-hinting a service constructor argument with the
+:class:`Symfony\\Component\\String\\Slugger\\SluggerInterface`. The locale of
+the injected slugger is the same as the request locale::
+
+ use Symfony\Component\String\Slugger\SluggerInterface;
+
+ class MyService
+ {
+ private $slugger;
+
+ public function __construct(SluggerInterface $slugger)
+ {
+ $this->slugger = $slugger;
+ }
+
+ public function someMethod()
+ {
+ $slug = $this->slugger->slug('...');
+ }
+ }
+
+.. _`ASCII`: https://en.wikipedia.org/wiki/ASCII
+.. _`Unicode`: https://en.wikipedia.org/wiki/Unicode
+.. _`Code points`: https://en.wikipedia.org/wiki/Code_point
+.. _`Grapheme clusters`: https://en.wikipedia.org/wiki/Grapheme
+.. _`Unicode equivalence`: https://en.wikipedia.org/wiki/Unicode_equivalence
diff --git a/components/templating.rst b/components/templating.rst
index 381e47aefa9..a104ea3e1d8 100644
--- a/components/templating.rst
+++ b/components/templating.rst
@@ -18,7 +18,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/templating:^3.4
+ $ composer require symfony/templating
.. include:: /components/require_autoload.rst.inc
@@ -28,8 +28,8 @@ Usage
.. seealso::
This article explains how to use the Templating features as an independent
- component in any PHP application. Read the :doc:`/templating` article to
- learn about how to work with templates in Symfony applications.
+ component in any PHP application. Read the article about :doc:`templates `
+ to learn about how to work with templates in Symfony applications.
The :class:`Symfony\\Component\\Templating\\PhpEngine` class is the entry point
of the component. It needs a
@@ -142,8 +142,9 @@ escaper using the
Helpers
-------
-The Templating component can be extended via helpers. Helpers are PHP objects that
-provide features useful in a template context. The component has one built-in helper:
+The Templating component can be extended via helpers. Helpers are PHP objects
+that provide features useful in a template context. The component has one
+built-in helper:
* :doc:`/components/templating/slotshelper`
@@ -210,5 +211,5 @@ Learn More
:glob:
/components/templating/*
- /templating
+ /templates
/templating/*
diff --git a/components/using_components.rst b/components/using_components.rst
index ce2801fa5e3..0b77e4f6a4e 100644
--- a/components/using_components.rst
+++ b/components/using_components.rst
@@ -24,7 +24,7 @@ Using the Finder Component
.. code-block:: terminal
- $ composer require symfony/finder:^3.4
+ $ composer require symfony/finder
The name ``symfony/finder`` is written at the top of the documentation for
whatever component you want.
@@ -33,8 +33,8 @@ whatever component you want.
`Install Composer`_ if you don't have it already present on your system.
Depending on how you install, you may end up with a ``composer.phar``
- file in your directory. In that case, no worries! Just run
- ``php composer.phar require symfony/finder``.
+ file in your directory. In that case, no worries! Your command line in that
+ case is ``php composer.phar require symfony/finder``.
**3.** Write your code!
@@ -56,19 +56,6 @@ immediately::
// ...
-Using all of the Components
----------------------------
-
-If you want to use all of the Symfony Components, then instead of adding
-them one by one, you can include the ``symfony/symfony`` package:
-
-.. code-block:: terminal
-
- $ composer require symfony/symfony:^3.4
-
-This will also include the Bundle and Bridge libraries, which you may or
-may not actually need.
-
Now what?
---------
diff --git a/components/validator.rst b/components/validator.rst
index e92c8f3e80a..9028f8d6874 100644
--- a/components/validator.rst
+++ b/components/validator.rst
@@ -13,7 +13,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/validator:^3.4
+ $ composer require symfony/validator
.. include:: /components/require_autoload.rst.inc
@@ -62,10 +62,6 @@ If you have lots of validation errors, you can filter them by error code::
// handle this specific error (display some message, send an email, etc.)
}
-.. versionadded:: 3.3
-
- The ``findByCodes()`` method was introduced in Symfony 3.3.
-
Retrieving a Validator Instance
-------------------------------
diff --git a/components/validator/resources.rst b/components/validator/resources.rst
index a3645162e51..7f9b02fb544 100644
--- a/components/validator/resources.rst
+++ b/components/validator/resources.rst
@@ -71,7 +71,7 @@ configure the locations of these files::
use Symfony\Component\Validator\Validation;
$validator = Validation::createValidatorBuilder()
- ->addYamlMapping('config/validation.yml')
+ ->addYamlMapping('validator/validation.yaml')
->getValidator();
.. note::
@@ -136,7 +136,7 @@ multiple mappings::
$validator = Validation::createValidatorBuilder()
->enableAnnotationMapping()
->addMethodMapping('loadValidatorMetadata')
- ->addXmlMapping('config/validation.xml')
+ ->addXmlMapping('validator/validation.xml')
->getValidator();
Caching
@@ -147,15 +147,15 @@ can slow down your application because each file needs to be parsed, validated
and converted into a :class:`Symfony\\Component\\Validator\\Mapping\\ClassMetadata`
instance.
-To solve this problem, call the :method:`Symfony\\Component\\Validator\\ValidatorBuilder::setMetadataCache`
+To solve this problem, call the :method:`Symfony\\Component\\Validator\\ValidatorBuilder::setMappingCache`
method of the Validator builder and pass your own caching class (which must
-implement :class:`Symfony\\Component\\Validator\\Mapping\\Cache\\CacheInterface`)::
+implement the PSR-6 interface :class:`Psr\\Cache\\CacheItemPoolInterface`)::
use Symfony\Component\Validator\Validation;
$validator = Validation::createValidatorBuilder()
// ... add loaders
- ->setMetadataCache(new SomeImplementCacheInterface());
+ ->setMappingCache(new SomePsr6Cache());
->getValidator();
.. note::
diff --git a/components/var_dumper.rst b/components/var_dumper.rst
index 2ea2a1f2ecb..e89c822c21f 100644
--- a/components/var_dumper.rst
+++ b/components/var_dumper.rst
@@ -14,14 +14,14 @@ Installation
.. code-block:: terminal
- $ composer require --dev symfony/var-dumper:^3.4
+ $ composer require --dev symfony/var-dumper
.. include:: /components/require_autoload.rst.inc
.. note::
- If using it inside a Symfony application, make sure that the
- DebugBundle is enabled in your ``app/AppKernel.php`` file.
+ If using it inside a Symfony application, make sure that the DebugBundle has
+ been installed (or run ``composer require --dev symfony/debug-bundle`` to install it).
.. _components-var-dumper-dump:
@@ -62,6 +62,12 @@ current PHP SAPI:
mechanism;
* On other SAPIs, dumps are written as HTML in the regular output.
+.. tip::
+
+ You can also select the output format explicitly defining the
+ ``VAR_DUMPER_FORMAT`` environment variable and setting its value to either
+ ``html`` or ``cli``.
+
.. note::
If you want to catch the dump output as a string, please read the
@@ -80,20 +86,120 @@ current PHP SAPI:
#. From time to time, run ``composer global update symfony/var-dumper``
to have the latest bug fixes.
+.. tip::
+
+ The VarDumper component also provides a ``dd()`` ("dump and die") helper
+ function. This function dumps the variables using ``dump()`` and
+ immediately ends the execution of the script (using :phpfunction:`exit`).
+
+.. _var-dumper-dump-server:
+
+The Dump Server
+---------------
+
+The ``dump()`` function outputs its contents in the same browser window or
+console terminal as your own application. Sometimes mixing the real output
+with the debug output can be confusing. That's why this component provides a
+server to collect all the dumped data.
+
+Start the server with the ``server:dump`` command and whenever you call to
+``dump()``, the dumped data won't be displayed in the output but sent to that
+server, which outputs it to its own console or to an HTML file:
+
+.. code-block:: terminal
+
+ # displays the dumped data in the console:
+ $ php bin/console server:dump
+ [OK] Server listening on tcp://0.0.0.0:9912
+
+ # stores the dumped data in a file using the HTML format:
+ $ php bin/console server:dump --format=html > dump.html
+
+Inside a Symfony application, the output of the dump server is configured with
+the :ref:`dump_destination option ` of the
+``debug`` package:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/debug.yaml
+ debug:
+ dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%"
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/debug.php
+ $container->loadFromExtension('debug', [
+ 'dump_destination' => 'tcp://%env(VAR_DUMPER_SERVER)%',
+ ]);
+
+Outside a Symfony application, use the :class:`Symfony\\Component\\VarDumper\\Dumper\\ServerDumper` class::
+
+ require __DIR__.'/vendor/autoload.php';
+
+ use Symfony\Component\VarDumper\Cloner\VarCloner;
+ use Symfony\Component\VarDumper\Dumper\CliDumper;
+ use Symfony\Component\VarDumper\Dumper\ContextProvider\CliContextProvider;
+ use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider;
+ use Symfony\Component\VarDumper\Dumper\HtmlDumper;
+ use Symfony\Component\VarDumper\Dumper\ServerDumper;
+ use Symfony\Component\VarDumper\VarDumper;
+
+ $cloner = new VarCloner();
+ $fallbackDumper = \in_array(\PHP_SAPI, ['cli', 'phpdbg']) ? new CliDumper() : new HtmlDumper();
+ $dumper = new ServerDumper('tcp://127.0.0.1:9912', $fallbackDumper, [
+ 'cli' => new CliContextProvider(),
+ 'source' => new SourceContextProvider(),
+ ]);
+
+ VarDumper::setHandler(function ($var) use ($cloner, $dumper) {
+ $dumper->dump($cloner->cloneVar($var));
+ });
+
+.. note::
+
+ The second argument of :class:`Symfony\\Component\\VarDumper\\Dumper\\ServerDumper`
+ is a :class:`Symfony\\Component\\VarDumper\\Dumper\\DataDumperInterface` instance
+ used as a fallback when the server is unreachable. The third argument are the
+ context providers, which allow to gather some info about the context in which the
+ data was dumped. The built-in context providers are: ``cli``, ``request`` and ``source``.
+
+Then you can use the following command to start a server out-of-the-box:
+
+.. code-block:: terminal
+
+ $ ./vendor/bin/var-dump-server
+ [OK] Server listening on tcp://127.0.0.1:9912
+
DebugBundle and Twig Integration
--------------------------------
-The DebugBundle allows greater integration of the component into the Symfony
-full-stack framework. It is enabled by default in the *dev* and *test*
-environment of the Symfony Standard Edition.
+The DebugBundle allows greater integration of this component into Symfony
+applications.
Since generating (even debug) output in the controller or in the model
of your application may just break it by e.g. sending HTTP headers or
corrupting your view, the bundle configures the ``dump()`` function so that
variables are dumped in the web debug toolbar.
-But if the toolbar cannot be displayed because you e.g. called ``die``/``exit``
-or a fatal error occurred, then dumps are written on the regular output.
+But if the toolbar cannot be displayed because you e.g. called
+``die()``/``exit()``/``dd()`` or a fatal error occurred, then dumps are written
+on the regular output.
In a Twig template, two constructs are available for dumping a variable.
Choosing between both is mostly a matter of personal taste, still:
@@ -111,10 +217,6 @@ option. Read more about this and other options in
.. tip::
- .. versionadded:: 3.3
-
- The local search box was introduced in Symfony 3.3.
-
If the dumped contents are complex, consider using the local search box to
look for specific variables or values. First, click anywhere on the dumped
contents and then press ``Ctrl. + F`` or ``Cmd. + F`` to make the local
@@ -122,6 +224,9 @@ option. Read more about this and other options in
are supported (``Ctrl. + G`` or ``Cmd. + G``, ``F3``, etc.) When
finished, press ``Esc.`` to hide the box again.
+ If you want to use your browser search input, press ``Ctrl. + F`` or
+ ``Cmd. + F`` again while having focus on VarDumper's search input.
+
Using the VarDumper Component in your PHPUnit Test Suite
--------------------------------------------------------
@@ -133,12 +238,22 @@ This will provide you with two new assertions:
:method:`Symfony\\Component\\VarDumper\\Test\\VarDumperTestTrait::assertDumpEquals`
verifies that the dump of the variable given as the second argument matches
- the expected dump provided as a string in the first argument.
+ the expected dump provided as the first argument.
:method:`Symfony\\Component\\VarDumper\\Test\\VarDumperTestTrait::assertDumpMatchesFormat`
is like the previous method but accepts placeholders in the expected dump,
based on the ``assertStringMatchesFormat()`` method provided by PHPUnit.
+The ``VarDumperTestTrait`` also includes these other methods:
+
+:method:`Symfony\\Component\\VarDumper\\Test\\VarDumperTestTrait::setUpVarDumper`
+ is used to configure the available casters and their options, which is a way
+ to only control the fields you're expecting and allows writing concise tests.
+
+:method:`Symfony\\Component\\VarDumper\\Test\\VarDumperTestTrait::tearDownVarDumper`
+ is called automatically after each case to reset the custom configuration
+ made in ``setUpVarDumper()``.
+
Example::
use PHPUnit\Framework\TestCase;
@@ -148,18 +263,42 @@ Example::
{
use VarDumperTestTrait;
+ protected function setUp()
+ {
+ $casters = [
+ \DateTimeInterface::class => static function (\DateTimeInterface $date, array $a, Stub $stub): array {
+ $stub->class = 'DateTime';
+ return ['date' => $date->format('d/m/Y')];
+ },
+ ];
+
+ $flags = CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR;
+
+ // this configures the casters & flags to use for all the tests in this class.
+ // If you need custom configurations per test rather than for the whole class,
+ // call this setUpVarDumper() method from those tests instead.
+ $this->setUpVarDumper($casters, $flags);
+ }
+
public function testWithDumpEquals()
{
$testedVar = [123, 'foo'];
+ // the expected dump contents don't have the default VarDumper structure
+ // because of the custom casters and flags used in the test
$expectedDump = << 123
- 1 => "foo"
+ [
+ 123,
+ "foo",
]
EOTXT;
+ // if the first argument is a string, it must be the whole expected dump
$this->assertDumpEquals($expectedDump, $testedVar);
+
+ // if the first argument is not a string, assertDumpEquals() dumps it
+ // and compares it with the dump of the second argument
+ $this->assertDumpEquals($testedVar, $testedVar);
}
}
diff --git a/components/var_dumper/advanced.rst b/components/var_dumper/advanced.rst
index 88edcefc05a..0f429c52012 100644
--- a/components/var_dumper/advanced.rst
+++ b/components/var_dumper/advanced.rst
@@ -64,10 +64,6 @@ you can configure these limits:
items will be cloned. The default value is ``1``, which is consistent
with older Symfony versions.
- .. versionadded:: 3.4
-
- The ``setMinDepth()`` method was introduced in Symfony 3.4.
-
:method:`Symfony\\Component\\VarDumper\\Cloner\\VarCloner::setMaxString`
Configures the maximum number of characters that will be cloned before
cutting overlong strings. Specifying ``-1`` removes the limit.
@@ -87,10 +83,6 @@ Before dumping it, you can further limit the resulting
:method:`Symfony\\Component\\VarDumper\\Cloner\\Data::seek`
Selects only sub-parts of already cloned arrays, objects or resources.
- .. versionadded:: 3.2
-
- The ``seek()`` method was introduced in Symfony 3.2.
-
Unlike the previous limits on cloners that remove data on purpose, these can
be changed back and forth before dumping since they do not affect the
intermediate representation internally.
@@ -112,7 +104,7 @@ This component comes with an :class:`Symfony\\Component\\VarDumper\\Dumper\\Html
for HTML output and a :class:`Symfony\\Component\\VarDumper\\Dumper\\CliDumper`
for optionally colored command line output.
-For example, if you want to dump some ``$variable``, just do::
+For example, if you want to dump some ``$variable``, do::
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
@@ -179,10 +171,6 @@ Another option for doing the same could be::
$output = $dumper->dump($cloner->cloneVar($variable), true);
- .. versionadded:: 3.2
-
- The ability to return a string was introduced in Symfony 3.2.
-
Dumpers implement the :class:`Symfony\\Component\\VarDumper\\Dumper\\DataDumperInterface`
interface that specifies the
:method:`dump(Data $data) `
@@ -191,6 +179,13 @@ method. They also typically implement the
them from re-implementing the logic required to walk through a
:class:`Symfony\\Component\\VarDumper\\Cloner\\Data` object's internal structure.
+The :class:`Symfony\\Component\\VarDumper\\Dumper\\HtmlDumper` uses a dark
+theme by default. Use the :method:`Symfony\\Component\\VarDumper\\Dumper\\HtmlDumper::setTheme`
+method to use a light theme::
+
+ // ...
+ $htmlDumper->setTheme('light');
+
The :class:`Symfony\\Component\\VarDumper\\Dumper\\HtmlDumper` limits string
length and nesting depth of the output to make it more readable. These options
can be overridden by the third optional parameter of the
@@ -208,11 +203,6 @@ method::
'maxStringLength' => 160
]);
-.. versionadded:: 3.2
-
- Support for passing display options to the ``dump()`` method was introduced
- in Symfony 3.2.
-
The output format of a dumper can be fine tuned by the two flags
``DUMP_STRING_LENGTH`` and ``DUMP_LIGHT_ARRAY`` which are passed as a bitmap
in the third constructor argument. They can also be set via environment
@@ -224,16 +214,6 @@ The ``$filter`` argument of ``assertDumpEquals()`` can be used to pass a
bit field of ``Caster::EXCLUDE_*`` constants and influences the expected
output produced by the different casters.
-.. versionadded:: 3.4
-
- The ``$filter`` argument of ``assertDumpEquals()`` was introduced in
- Symfony 3.4.
-
-.. versionadded:: 3.1
-
- The ``DUMP_STRING_LENGTH`` and ``DUMP_LIGHT_ARRAY`` flags were introduced
- in Symfony 3.1.
-
If ``DUMP_STRING_LENGTH`` is set, then the length of a string is displayed
next to its content::
@@ -284,7 +264,7 @@ similar to PHP's short array notation::
// 0 => "test"
// ]
-If you would like to use both options, then you can just combine them by
+If you would like to use both options, then you can combine them by
using the logical OR operator ``|``::
use Symfony\Component\VarDumper\Cloner\VarCloner;
@@ -387,12 +367,6 @@ properties not in the class declaration).
Adding Semantics with Metadata
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.. versionadded:: 3.2
-
- As of Symfony 3.2, casters can attach metadata attributes to
- :class:`Symfony\\Component\\VarDumper\\Cloner\\Stub` objects to inform
- dumpers about the precise type of the dumped values.
-
Since casters are hooked on specific classes or interfaces, they know about the
objects they manipulate. By altering the ``$stub`` object (the third argument of
any caster), one can transfer this knowledge to the resulting ``Data`` object,
@@ -408,6 +382,7 @@ can use:
objects/strings/*etc.* by ellipses;
* :class:`Symfony\\Component\\VarDumper\\Caster\\CutArrayStub` to keep only some
useful keys of an array;
+* :class:`Symfony\\Component\\VarDumper\\Caster\\ImgStub` to wrap an image;
* :class:`Symfony\\Component\\VarDumper\\Caster\\EnumStub` to wrap a set of virtual
values (*i.e.* values that do not exist as properties in the original PHP data
structure, but are worth listing alongside with real ones);
diff --git a/components/var_exporter.rst b/components/var_exporter.rst
new file mode 100644
index 00000000000..6b403f79263
--- /dev/null
+++ b/components/var_exporter.rst
@@ -0,0 +1,132 @@
+.. index::
+ single: VarExporter
+ single: Components; VarExporter
+
+The VarExporter Component
+=========================
+
+ The VarExporter component exports any serializable PHP data structure to
+ plain PHP code and allows to instantiate and populate objects without
+ calling their constructors.
+
+Installation
+------------
+
+.. code-block:: terminal
+
+ $ composer require --dev symfony/var-exporter
+
+.. include:: /components/require_autoload.rst.inc
+
+Exporting/Serializing Variables
+-------------------------------
+
+The main feature of this component is to serialize PHP data structures to plain
+PHP code, similar to PHP's :phpfunction:`var_export` function::
+
+ use Symfony\Component\VarExporter\VarExporter;
+
+ $exported = VarExporter::export($someVariable);
+ // store the $exported data in some file or cache system for later reuse
+ $data = file_put_contents('exported.php', $exported);
+
+ // later, regenerate the original variable when you need it
+ $regeneratedVariable = require 'exported.php';
+
+The reason to use this component instead of ``serialize()`` or ``igbinary`` is
+performance: thanks to `OPcache`_, the resulting code is significantly faster
+and more memory efficient than using ``unserialize()`` or ``igbinary_unserialize()``.
+
+In addition, there are some minor differences:
+
+* If the original variable defines them, all the semantics associated with
+ ``serialize()`` (such as ``__wakeup()``, ``__sleep()``, and ``Serializable``)
+ are preserved (``var_export()`` ignores them);
+* References involving ``SplObjectStorage``, ``ArrayObject`` or ``ArrayIterator``
+ instances are preserved;
+* Missing classes throw a ``ClassNotFoundException`` instead of being
+ unserialized to ``PHP_Incomplete_Class`` objects;
+* ``Reflection*``, ``IteratorIterator`` and ``RecursiveIteratorIterator``
+ classes throw an exception when being serialized.
+
+The exported data is a `PSR-2`_ compatible PHP file. Consider for example the
+following class hierarchy::
+
+ abstract class AbstractClass
+ {
+ protected $foo;
+ private $bar;
+
+ protected function setBar($bar)
+ {
+ $this->bar = $bar;
+ }
+ }
+
+ class ConcreteClass extends AbstractClass
+ {
+ public function __construct()
+ {
+ $this->foo = 123;
+ $this->setBar(234);
+ }
+ }
+
+When exporting the ``ConcreteClass`` data with VarExporter, the generated PHP
+file looks like this::
+
+ [
+ 'foo' => [
+ 123,
+ ],
+ 'bar' => [
+ 234,
+ ],
+ ],
+ ],
+ $o[0],
+ []
+ );
+
+Instantiating PHP Classes
+-------------------------
+
+The other main feature provided by this component is an instantiator which can
+create objects and set their properties without calling their constructors or
+any other methods::
+
+ use Symfony\Component\VarExporter\Instantiator;
+
+ // creates an empty instance of Foo
+ $fooObject = Instantiator::instantiate(Foo::class);
+
+ // creates a Foo instance and sets one of its properties
+ $fooObject = Instantiator::instantiate(Foo::class, ['propertyName' => $propertyValue]);
+
+ // creates a Foo instance and sets a private property defined on its parent Bar class
+ $fooObject = Instantiator::instantiate(Foo::class, [], [
+ Bar::class => ['privateBarProperty' => $propertyValue],
+ ]);
+
+Instances of ``ArrayObject``, ``ArrayIterator`` and ``SplObjectHash`` can be
+created by using the special ``"\0"`` property name to define their internal value::
+
+ // Creates an SplObjectHash where $info1 is associated to $object1, etc.
+ $theObject = Instantiator::instantiate(SplObjectStorage::class, [
+ "\0" => [$object1, $info1, $object2, $info2...]
+ ]);
+
+ // creates an ArrayObject populated with $inputArray
+ $theObject = Instantiator::instantiate(ArrayObject::class, [
+ "\0" => [$inputArray]
+ ]);
+
+.. _`OPcache`: https://php.net/opcache
+.. _`PSR-2`: https://www.php-fig.org/psr/psr-2/
diff --git a/components/web_link.rst b/components/web_link.rst
index 4fec7ceeae3..4a89d0c0b48 100644
--- a/components/web_link.rst
+++ b/components/web_link.rst
@@ -13,7 +13,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/web-link:^3.4
+ $ composer require symfony/web-link
.. include:: /components/require_autoload.rst.inc
@@ -22,9 +22,9 @@ Usage
The following example shows the component in action::
- use Fig\Link\GenericLinkProvider;
- use Fig\Link\Link;
+ use Symfony\Component\WebLink\GenericLinkProvider;
use Symfony\Component\WebLink\HttpHeaderSerializer;
+ use Symfony\Component\WebLink\Link;
$linkProvider = (new GenericLinkProvider())
->withLink(new Link('preload', '/bootstrap.min.css'));
diff --git a/components/workflow.rst b/components/workflow.rst
index 83189840993..f04571d19b4 100644
--- a/components/workflow.rst
+++ b/components/workflow.rst
@@ -8,16 +8,12 @@ The Workflow Component
The Workflow component provides tools for managing a workflow or finite
state machine.
-.. versionadded:: 3.2
-
- The Workflow component was introduced in Symfony 3.2.
-
Installation
------------
.. code-block:: terminal
- $ composer require symfony/workflow:^3.4
+ $ composer require symfony/workflow
.. include:: /components/require_autoload.rst.inc
@@ -36,34 +32,30 @@ a ``Definition`` and a way to write the states to the objects (i.e. an
instance of a :class:`Symfony\\Component\\Workflow\\MarkingStore\\MarkingStoreInterface`).
Consider the following example for a blog post. A post can have one of a number
-of predefined statuses (`draft`, `review`, `rejected`, `published`). In a workflow,
+of predefined statuses (`draft`, `reviewed`, `rejected`, `published`). In a workflow,
these statuses are called **places**. You can define the workflow like this::
use Symfony\Component\Workflow\DefinitionBuilder;
- use Symfony\Component\Workflow\MarkingStore\SingleStateMarkingStore;
+ use Symfony\Component\Workflow\MarkingStore\MethodMarkingStore;
use Symfony\Component\Workflow\Transition;
use Symfony\Component\Workflow\Workflow;
$definitionBuilder = new DefinitionBuilder();
- $definition = $definitionBuilder->addPlaces(['draft', 'review', 'rejected', 'published'])
+ $definition = $definitionBuilder->addPlaces(['draft', 'reviewed', 'rejected', 'published'])
// Transitions are defined with a unique name, an origin place and a destination place
- ->addTransition(new Transition('to_review', 'draft', 'review'))
- ->addTransition(new Transition('publish', 'review', 'published'))
- ->addTransition(new Transition('reject', 'review', 'rejected'))
+ ->addTransition(new Transition('to_review', 'draft', 'reviewed'))
+ ->addTransition(new Transition('publish', 'reviewed', 'published'))
+ ->addTransition(new Transition('reject', 'reviewed', 'rejected'))
->build()
;
- $marking = new SingleStateMarkingStore('currentState');
+ $singleState = true; // true if the subject can be in only one state at a given time
+ $property = 'currentState'; // subject property name where the state is stored
+ $marking = new MethodMarkingStore($singleState, $property);
$workflow = new Workflow($definition, $marking);
-.. versionadded:: 3.3
-
- The fluent interface for the ``DefinitionBuilder`` class was introduced in
- Symfony 3.3. Before you had to call the ``addPlaces()``, ``addTransition()``
- and ``build()`` methods separately.
-
-The ``Workflow`` can now help you to decide what actions are allowed
-on a blog post depending on what *place* it is in. This will keep your domain
+The ``Workflow`` can now help you to decide what *transitions* (actions) are allowed
+on a blog post depending on what *place* (state) it is in. This will keep your domain
logic in one place and not spread all over your application.
When you define multiple workflows you should consider using a ``Registry``,
@@ -74,29 +66,33 @@ are trying to use it with::
use Acme\Entity\BlogPost;
use Acme\Entity\Newsletter;
use Symfony\Component\Workflow\Registry;
+ use Symfony\Component\Workflow\SupportStrategy\InstanceOfSupportStrategy;
- $blogWorkflow = ...
+ $blogPostWorkflow = ...
$newsletterWorkflow = ...
$registry = new Registry();
- $registry->add($blogWorkflow, BlogPost::class);
- $registry->add($newsletterWorkflow, Newsletter::class);
+ $registry->addWorkflow($blogPostWorkflow, new InstanceOfSupportStrategy(BlogPost::class));
+ $registry->addWorkflow($newsletterWorkflow, new InstanceOfSupportStrategy(Newsletter::class));
Usage
-----
-When you have configured a ``Registry`` with your workflows, you may use it as follows::
+When you have configured a ``Registry`` with your workflows,
+you can retrieve a workflow from it and use it as follows::
// ...
- $post = new BlogPost();
- $workflow = $registry->get($post);
+ // Consider that $blogPost is in place "draft" by default
+ $blogPost = new BlogPost();
+ $workflow = $registry->get($blogPost);
+
+ $workflow->can($blogPost, 'publish'); // False
+ $workflow->can($blogPost, 'to_review'); // True
- $workflow->can($post, 'publish'); // False
- $workflow->can($post, 'to_review'); // True
+ $workflow->apply($blogPost, 'to_review'); // $blogPost is now in place "reviewed"
- $workflow->apply($post, 'to_review');
- $workflow->can($post, 'publish'); // True
- $workflow->getEnabledTransitions($post); // ['publish', 'reject']
+ $workflow->can($blogPost, 'publish'); // True
+ $workflow->getEnabledTransitions($blogPost); // $blogPost can perform transition "publish" or "reject"
Learn more
----------
diff --git a/components/yaml.rst b/components/yaml.rst
index 8155109dbdd..5206f81b805 100644
--- a/components/yaml.rst
+++ b/components/yaml.rst
@@ -31,7 +31,7 @@ Installation
.. code-block:: terminal
- $ composer require symfony/yaml:^3.4
+ $ composer require symfony/yaml
.. include:: /components/require_autoload.rst.inc
@@ -83,7 +83,7 @@ yourself by referencing common configuration bits.
Using the Symfony YAML Component
--------------------------------
-The Symfony Yaml component is very simple and consists of two main classes:
+The Symfony Yaml component consists of two main classes:
one parses YAML strings (:class:`Symfony\\Component\\Yaml\\Parser`), and the
other dumps a PHP array to a YAML string
(:class:`Symfony\\Component\\Yaml\\Dumper`).
@@ -123,11 +123,7 @@ contents of the given file path and converts them to a PHP value::
use Symfony\Component\Yaml\Yaml;
- $value = Yaml::parseFile('/path/to/file.yml');
-
-.. versionadded:: 3.4
-
- The ``parseFile()`` method was introduced in Symfony 3.4.
+ $value = Yaml::parseFile('/path/to/file.yaml');
If an error occurs during parsing, the parser throws a ``ParseException`` exception.
@@ -148,7 +144,7 @@ array to its YAML representation::
$yaml = Yaml::dump($array);
- file_put_contents('/path/to/file.yml', $yaml);
+ file_put_contents('/path/to/file.yaml', $yaml);
If an error occurs during the dump, the parser throws a
:class:`Symfony\\Component\\Yaml\\Exception\\DumpException` exception.
@@ -210,11 +206,6 @@ changed using the third argument as follows::
Numeric Literals
................
-.. versionadded:: 3.2
-
- Support for parsing integers grouped by underscores was introduced in
- Symfony 3.2.
-
Long numeric literals, being integer, float or hexadecimal, are known for their
poor readability in code and configuration files. That's why YAML files allow to
add underscores to improve their readability:
@@ -265,10 +256,6 @@ representation of the object.
Parsing and Dumping Objects as Maps
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.. versionadded:: 3.2
-
- Support for parsing and dumping objects as maps was introduced in Symfony 3.2.
-
You can dump objects as Yaml maps by using the ``DUMP_OBJECT_AS_MAP`` flag::
$object = new \stdClass();
@@ -354,10 +341,6 @@ syntax to parse them as proper PHP constants::
Parsing and Dumping of Binary Data
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.. versionadded:: 3.2
-
- Support for parsing and dumping binary data was introduced in Symfony 3.2.
-
You can dump binary data by using the ``DUMP_BASE64_BINARY_DATA`` flag::
$imageContents = file_get_contents(__DIR__.'/images/logo.png');
@@ -375,10 +358,6 @@ Binary data is automatically parsed if they include the ``!!binary`` YAML tag
Parsing and Dumping Custom Tags
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.. versionadded:: 3.3
-
- Support for parsing and dumping custom tags was introduced in Symfony 3.3.
-
In addition to the built-in support of tags like ``!php/const`` and
``!!binary``, you can define your own custom YAML tags and parse them with the
``PARSE_CUSTOM_TAGS`` flag::
@@ -398,6 +377,19 @@ objects, they are automatically transformed into YAML tags::
$dumped = Yaml::dump($data);
// $dumped = '!my_tag { foo: bar }'
+Dumping Null Values
+~~~~~~~~~~~~~~~~~~~
+
+The official YAML specification uses both ``null`` and ``~`` to represent null
+values. This component uses ``null`` by default when dumping null values but
+you can dump them as ``~`` with the ``DUMP_NULL_AS_TILDE`` flag::
+
+ $dumped = Yaml::dump(['foo' => null]);
+ // foo: null
+
+ $dumped = Yaml::dump(['foo' => null], 2, 4, Yaml::DUMP_NULL_AS_TILDE);
+ // foo: ~
+
Syntax Validation
~~~~~~~~~~~~~~~~~
@@ -408,7 +400,7 @@ First, install the Console component:
.. code-block:: terminal
- $ composer require symfony/console:^3.4
+ $ composer require symfony/console
Create a console application with ``lint:yaml`` as its only command::
@@ -427,20 +419,26 @@ Then, execute the script for validating contents:
.. code-block:: terminal
# validates a single file
- $ php lint.php path/to/file.yml
+ $ php lint.php path/to/file.yaml
+
+ # or validates multiple files
+ $ php lint.php path/to/file1.yaml path/to/file2.yaml
# or all the files in a directory
$ php lint.php path/to/directory
+ # or all the files in multiple directories
+ $ php lint.php path/to/directory1 path/to/directory2
+
# or contents passed to STDIN
- $ cat path/to/file.yml | php lint.php
+ $ cat path/to/file.yaml | php lint.php
The result is written to STDOUT and uses a plain text format by default.
Add the ``--format`` option to get the output in JSON format:
.. code-block:: terminal
- $ php lint.php path/to/file.yml --format json
+ $ php lint.php path/to/file.yaml --format json
.. tip::
diff --git a/components/yaml/yaml_format.rst b/components/yaml/yaml_format.rst
index 90496c87c0e..c645f52c481 100644
--- a/components/yaml/yaml_format.rst
+++ b/components/yaml/yaml_format.rst
@@ -294,8 +294,8 @@ Comments can be added in YAML by prefixing them with a hash mark (``#``):
.. note::
- Comments are simply ignored by the YAML parser and do not need to be
- indented according to the current level of nesting in a collection.
+ Comments are ignored by the YAML parser and do not need to be indented
+ according to the current level of nesting in a collection.
Explicit Typing
---------------
@@ -318,10 +318,6 @@ The YAML specification defines some tags to set the type of any data explicitly:
Pz7Y6OjuDg4J+fn5OTk6enp
56enmleECcgggoBADs=
-.. versionadded:: 3.4
-
- Support for the ``!!str`` tag was introduced in Symfony 3.4.
-
Unsupported YAML Features
-------------------------
diff --git a/configuration.rst b/configuration.rst
index 49b5589cee8..781683ad577 100644
--- a/configuration.rst
+++ b/configuration.rst
@@ -1,333 +1,464 @@
.. index::
single: Configuration
-Configuring Symfony (and Environments)
-======================================
+Configuring Symfony
+===================
-Every Symfony application consists of a collection of bundles that add useful tools
-(:doc:`services `) to your project. Each bundle can be customized
-via configuration files that live - by default - in the ``app/config`` directory.
+Configuration Files
+-------------------
-Configuration: ``config.yml``
------------------------------
+Symfony applications are configured with the files stored in the ``config/``
+directory, which has this default structure:
-The main configuration file is called ``config.yml``:
+.. code-block:: text
+
+ your-project/
+ ├─ config/
+ │ ├─ packages/
+ │ ├─ bundles.php
+ │ ├─ routes.yaml
+ │ └─ services.yaml
+ ├─ ...
+
+The ``routes.yaml`` file defines the :doc:`routing configuration `;
+the ``services.yaml`` file configures the services of the
+:doc:`service container `; the ``bundles.php`` file enables/
+disables packages in your application.
+
+You'll be working most in the ``config/packages/`` directory. This directory
+stores the configuration of every package installed in your application.
+Packages (also called "bundles" in Symfony and "plugins/modules" in other
+projects) add ready-to-use features to your projects.
+
+When using :ref:`Symfony Flex `, which is enabled by default in
+Symfony applications, packages update the ``bundles.php`` file and create new
+files in ``config/packages/`` automatically during their installation. For
+example, this is the default file created by the "API Platform" package:
+
+.. code-block:: yaml
+
+ # config/packages/api_platform.yaml
+ api_platform:
+ mapping:
+ paths: ['%kernel.project_dir%/src/Entity']
+
+Splitting the configuration into lots of small files is intimidating for some
+Symfony newcomers. However, you'll get used to them quickly and you rarely need
+to change these files after package installation
+
+.. tip::
+
+ To learn about all the available configuration options, check out the
+ :doc:`Symfony Configuration Reference ` or run the
+ ``config:dump-reference`` command.
+
+Configuration Formats
+~~~~~~~~~~~~~~~~~~~~~
+
+Unlike other frameworks, Symfony doesn't impose you a specific format to
+configure your applications. Symfony lets you choose between YAML, XML and PHP
+and throughout the Symfony documentation, all configuration examples will be
+shown in these three formats.
+
+There isn't any practical difference between formats. In fact, Symfony
+transforms and caches all of them into PHP before running the application, so
+there's not even any performance difference between them.
+
+YAML is used by default when installing packages because it's concise and very
+readable. These are the main advantages and disadvantages of each format:
+
+* **YAML**: simple, clean and readable, but not all IDEs support autocompletion
+ and validation for it. :doc:`Learn the YAML syntax `;
+* **XML**:autocompleted/validated by most IDEs and is parsed natively by PHP,
+ but sometimes it generates too verbose configuration. `Learn the XML syntax`_;
+* **PHP**: very powerful and it allows to create dynamic configuration, but the
+ resulting configuration is less readable than the other formats.
+
+Importing Configuration Files
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Symfony loads configuration files using the :doc:`Config component
+`, which provides advanced features such as importing other
+configuration files, even if they use a different format:
.. configuration-block::
.. code-block:: yaml
- # app/config/config.yml
+ # config/services.yaml
imports:
- - { resource: parameters.yml }
- - { resource: security.yml }
- - { resource: services.yml }
-
- framework:
- secret: '%secret%'
- router: { resource: '%kernel.project_dir%/app/config/routing.yml' }
- # ...
-
- # Twig Configuration
- twig:
- debug: '%kernel.debug%'
- strict_variables: '%kernel.debug%'
+ - { resource: 'legacy_config.php' }
+ # ignore_errors silently discards errors if the loaded file doesn't exist
+ - { resource: 'my_config_file.xml', ignore_errors: true }
+ # glob expressions are also supported to load multiple files
+ - { resource: '/etc/myapp/*.yaml' }
# ...
.. code-block:: xml
-
+
+ https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
.. code-block:: php
- // app/config/config.php
- $this->import('parameters.yml');
- $this->import('security.yml');
- $this->import('services.yml');
+ // config/services.php
+ $loader->import('legacy_config.xml');
+ // the third optional argument of import() is 'ignore_errors', which
+ // silently discards errors if the loaded file doesn't exist
+ $loader->import('my_config_file.yaml', null, true);
+ // glob expressions are also supported to load multiple files
+ $loader->import('/etc/myapp/*.yaml');
- $container->loadFromExtension('framework', [
- 'secret' => '%secret%',
- 'router' => [
- 'resource' => '%kernel.project_dir%/app/config/routing.php',
- ],
- // ...
- ]);
+ // ...
- // Twig Configuration
- $container->loadFromExtension('twig', [
- 'debug' => '%kernel.debug%',
- 'strict_variables' => '%kernel.debug%',
- ]);
+.. _config-parameter-intro:
+.. _config-parameters-yml:
+.. _configuration-parameters:
- // ...
+Configuration Parameters
+------------------------
-Most top-level keys - like ``framework`` and ``twig`` - are configuration for a
-specific bundle (i.e. ``FrameworkBundle`` and ``TwigBundle``).
+Sometimes the same configuration value is used in several configuration files.
+Instead of repeating it, you can define it as a "parameter", which is like a
+reusable configuration value. By convention, parameters are defined under the
+``parameters`` key in the ``config/services.yaml`` file:
-.. sidebar:: Configuration Formats
+.. configuration-block::
- Throughout the documentation, all configuration examples will be shown in
- three formats (YAML, XML and PHP). YAML is used by default, but you can
- choose whatever you like best. There is no performance difference:
+ .. code-block:: yaml
- * :doc:`/components/yaml/yaml_format`: Simple, clean and readable;
+ # config/services.yaml
+ parameters:
+ # the parameter name is an arbitrary string (the 'app.' prefix is recommended
+ # to better differentiate your parameters from Symfony parameters).
+ app.admin_email: 'something@example.com'
- * *XML*: More powerful than YAML at times & supports IDE autocompletion;
+ # boolean parameters
+ app.enable_v2_protocol: true
- * *PHP*: Very powerful but less readable than standard configuration formats.
+ # array/collection parameters
+ app.supported_locales: ['en', 'es', 'fr']
-Configuration Reference & Dumping
----------------------------------
+ # binary content parameters (encode the contents with base64_encode())
+ app.some_parameter: !!binary VGhpcyBpcyBhIEJlbGwgY2hhciAH
-There are *two* ways to know *what* keys you can configure:
+ # PHP constants as parameter values
+ app.some_constant: !php/const GLOBAL_CONSTANT
+ app.another_constant: !php/const App\Entity\BlogPost::MAX_ITEMS
-#. Use the :doc:`Reference Section `;
+ # ...
-#. Use the ``config:dump-reference`` command.
+ .. code-block:: xml
-For example, if you want to configure something in Twig, you can see an example
-dump of all available configuration options by running:
+
+
+
-.. code-block:: terminal
+
+
+ something@example.com
+
+
+ true
+
+ true
+
+
+
+ en
+ es
+ fr
+
+
+
+ VGhpcyBpcyBhIEJlbGwgY2hhciAH
+
+
+ GLOBAL_CONSTANT
+ App\Entity\BlogPost::MAX_ITEMS
+
- $ php bin/console config:dump-reference twig
+
+
-.. index::
- single: Environments; Introduction
+ .. code-block:: php
-.. _page-creation-environments:
-.. _page-creation-prod-cache-clear:
+ // config/services.php
+ // the parameter name is an arbitrary string (the 'app.' prefix is recommended
+ // to better differentiate your parameters from Symfony parameters).
+ $container->setParameter('app.admin_email', 'something@example.com');
+
+ // boolean parameters
+ $container->setParameter('app.enable_v2_protocol', true);
-The imports Key: Loading other Configuration Files
---------------------------------------------------
+ // array/collection parameters
+ $container->setParameter('app.supported_locales', ['en', 'es', 'fr']);
-Symfony's main configuration file is ``app/config/config.yml``. But, for organization,
-it *also* loads other configuration files via its ``imports`` key:
+ // binary content parameters (use the PHP escape sequences)
+ $container->setParameter('app.some_parameter', 'This is a Bell char: \x07');
+
+ // PHP constants as parameter values
+ use App\Entity\BlogPost;
+
+ $container->setParameter('app.some_constant', GLOBAL_CONSTANT);
+ $container->setParameter('app.another_constant', BlogPost::MAX_ITEMS);
+
+ // ...
+
+.. caution::
+
+ When using XML configuration, the values between ```` tags are
+ not trimmed. This means that the value of the following parameter will be
+ ``'\n something@example.com\n'``:
+
+ .. code-block:: xml
+
+
+ something@example.com
+
+
+Once defined, you can reference this parameter value from any other
+configuration file using a special syntax: wrap the parameter name in two ``%``
+(e.g. ``%app.admin_email%``):
.. configuration-block::
.. code-block:: yaml
- # app/config/config.yml
- imports:
- - { resource: parameters.yml }
- - { resource: security.yml }
- - { resource: services.yml }
- # ...
+ # config/packages/some_package.yaml
+ some_package:
+ # any string surrounded by two % is replaced by that parameter value
+ email_address: '%app.admin_email%'
+
+ # ...
.. code-block:: xml
-
+
-
-
-
-
-
-
+ https://symfony.com/schema/dic/services/services-1.0.xsd
+ http://symfony.com/schema/dic/symfony
+ https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
-
+
+
+
+
.. code-block:: php
- // app/config/config.php
- $this->import('parameters.yml');
- $this->import('security.yml');
- $this->import('services.yml');
-
- // ...
+ // config/packages/some_package.php
+ $container->loadFromExtension('some_package', [
+ // any string surrounded by two % is replaced by that parameter value
+ 'email_address' => '%app.admin_email%',
-The ``imports`` key works a lot like the PHP ``include()`` function: the contents of
-``parameters.yml``, ``security.yml`` and ``services.yml`` are read and loaded. You
-can also load XML files or PHP files.
+ // ...
+ ]);
-.. tip::
+.. note::
- If your application uses unconventional file extensions (for example, your
- YAML files have a ``.res`` extension) you can set the file type explicitly
- with the ``type`` option:
+ If some parameter value includes the ``%`` character, you need to escape it
+ by adding another ``%`` so Symfony doesn't consider it a reference to a
+ parameter name:
.. configuration-block::
.. code-block:: yaml
- # app/config/config.yml
- imports:
- - { resource: parameters.res, type: yml }
- # ...
+ # config/services.yaml
+ parameters:
+ # Parsed as 'https://symfony.com/?foo=%s&bar=%d'
+ url_pattern: 'https://symfony.com/?foo=%%s&bar=%%d'
.. code-block:: xml
-
-
-
-
-
-
-
-
-
+
+
+ http://symfony.com/?foo=%%s&bar=%%d
+
.. code-block:: php
- // app/config/config.php
- $this->import('parameters.res', 'yml');
- // ...
+ // config/services.php
+ $container->setParameter('url_pattern', 'http://symfony.com/?foo=%%s&bar=%%d');
-.. _config-parameter-intro:
+.. include:: /components/dependency_injection/_imports-parameters-note.rst.inc
-The parameters Key: Parameters (Variables)
-------------------------------------------
+Configuration parameters are very common in Symfony applications. Some packages
+even define their own parameters (e.g. when installing the translation package,
+a new ``locale`` parameter is added to the ``config/services.yaml`` file).
-Another special key is called ``parameters``: it's used to define *variables* that
-can be referenced in *any* other configuration file. For example, in ``config.yml``,
-a ``locale`` parameter is defined and then referenced below under the ``framework``
-key:
+.. seealso::
-.. configuration-block::
+ Later in this article you can read how to
+ :ref:`get configuration parameters in controllers and services `.
- .. code-block:: yaml
+.. index::
+ single: Environments; Introduction
- # app/config/config.yml
- # ...
+.. _page-creation-environments:
+.. _page-creation-prod-cache-clear:
+.. _configuration-environments:
- parameters:
- locale: en
+Configuration Environments
+--------------------------
- framework:
- # ...
+You have just one application, but whether you realize it or not, you need it
+to behave differently at different times:
- # any string surrounded by two % is replaced by that parameter value
- default_locale: "%locale%"
+* While **developing**, you want to log everything and expose nice debugging tools;
+* After deploying to **production**, you want that same application to be
+ optimized for speed and only log errors.
- # ...
+The files stored in ``config/packages/`` are used by Symfony to configure the
+:doc:`application services `. In other words, you can change
+the application behavior by changing which configuration files are loaded.
+That's the idea of Symfony's **configuration environments**.
- .. code-block:: xml
+A typical Symfony application begins with three environments: ``dev`` (for local
+development), ``prod`` (for production servers) and ``test`` (for
+:doc:`automated tests `). When running the application, Symfony loads
+the configuration files in this order (the last files can override the values
+set in the previous ones):
-
-
-
+#. ``config/packages/*.yaml`` (and ``*.xml`` and ``*.php`` files too);
+#. ``config/packages//*.yaml`` (and ``*.xml`` and ``*.php`` files too);
+#. ``config/packages/services.yaml`` (and ``services.xml`` and ``services.php`` files too);
-
-
- en
-
+Take the ``framework`` package, installed by default, as an example:
-
-
-
+* First, ``config/packages/framework.yaml`` is loaded in all environments and
+ it configures the framework with some options;
+* In the **prod** environment, nothing extra will be set as there is no
+ ``config/packages/prod/framework.yaml`` file;
+* In the **dev** environment, there is no file either (
+ ``config/packages/dev/framework.yaml`` does not exist).
+* In the **test** environment, the ``config/packages/test/framework.yaml`` file
+ is loaded to override some of the settings previously configured in
+ ``config/packages/framework.yaml``.
-
-
+In reality, each environment differs only somewhat from others. This means that
+all environments share a large base of common configurations, which is put in
+files directly in the ``config/packages/`` directory.
- .. code-block:: php
+.. seealso::
- // app/config/config.php
- // ...
+ See the ``configureContainer()`` method of
+ :doc:`the Kernel class ` to
+ learn everything about the loading order of configuration files.
- $container->setParameter('locale', 'en');
+.. _selecting-the-active-environment:
- $container->loadFromExtension('framework', [
- 'default_locale' => '%locale%',
- // ...
- ]);
+Selecting the Active Environment
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- // ...
+Symfony applications come with a file called ``.env`` located at the project
+root directory. This file is used to define the value of environment variables
+and it's explained in detail :ref:`later in this article `.
-You can define whatever parameter names you want under the ``parameters`` key of
-any configuration file. To reference a parameter, surround its name with two percent
-signs - e.g. ``%locale%``.
+Open the ``.env`` file (or better, the ``.env.local`` file if you created one)
+and edit the value of the ``APP_ENV`` variable to change the environment in
+which the application runs. For example, to run the application in production:
-.. seealso::
+.. code-block:: bash
- You can also set parameters dynamically, like from environment variables.
- See :doc:`/configuration/external_parameters`.
+ # .env (or .env.local)
+ APP_ENV=prod
-For more information about parameters - including how to reference them from inside
-a controller - see :ref:`service-container-parameters`.
+This value is used both for the web and for the console commands. However, you
+can override it for commands by setting the ``APP_ENV`` value before running them:
-.. _config-parameters-yml:
+.. code-block:: terminal
-The Special ``parameters.yml`` File
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ # Use the environment defined in the .env file
+ $ php bin/console command_name
-On the surface, ``parameters.yml`` is just like any other configuration file: it
-is imported by ``config.yml`` and defines several parameters:
+ # Ignore the .env file and run this command in production
+ $ APP_ENV=prod php bin/console command_name
-.. code-block:: yaml
+Creating a New Environment
+~~~~~~~~~~~~~~~~~~~~~~~~~~
- parameters:
- # ...
- database_user: root
- database_password: ~
+The default three environments provided by Symfony are enough for most projects,
+but you can define your own environments too. For example, this is how you can
+define a ``staging`` environment where the client can test the project before
+going to production:
-Not surprisingly, these are referenced from inside of ``config.yml`` and help to
-configure DoctrineBundle and other parts of Symfony:
+#. Create a configuration directory with the same name as the environment (in
+ this case, ``config/packages/staging/``);
+#. Add the needed configuration files in ``config/packages/staging/`` to
+ define the behavior of the new environment. Symfony loads first the files in
+ ``config/packages/*.yaml``, so you must only configure the differences with
+ those files;
+#. Select the ``staging`` environment using the ``APP_ENV`` env var as explained
+ in the previous section.
+
+.. tip::
+
+ It's common for environments to be similar between each other, so you can
+ use `symbolic links`_ between ``config/packages//``
+ directories to reuse the same configuration.
+
+.. _config-env-vars:
+
+Configuration Based on Environment Variables
+--------------------------------------------
+
+Using `environment variables`_ (or "env vars" for short) is a common practice to
+configure options that depend on where the application is run (e.g. the database
+credentials are usually different in production versus your local machine). If
+the values are sensitive, you can even :doc:`encrypt them as secrets `.
+
+You can reference environment variables using the special syntax
+``%env(ENV_VAR_NAME)%``. The values of these options are resolved at runtime
+(only once per request, to not impact performance).
+
+This example shows how you could configure the database connection using an env var:
.. configuration-block::
.. code-block:: yaml
- # app/config/config.yml
+ # config/packages/doctrine.yaml
doctrine:
dbal:
- driver: pdo_mysql
- # ...
- user: '%database_user%'
- password: '%database_password%'
+ # by convention the env var names are always uppercase
+ url: '%env(DATABASE_URL)%'
+ # ...
.. code-block:: xml
-
+
-
+
+
+
.. code-block:: php
- // app/config/config.php
+ // config/packages/doctrine.php
$container->loadFromExtension('doctrine', [
'dbal' => [
- 'driver' => 'pdo_mysql',
- // ...
-
- 'user' => '%database_user%',
- 'password' => '%database_password%',
- ],
+ // by convention the env var names are always uppercase
+ 'url' => '%env(DATABASE_URL)%',
+ ]
]);
-But the ``parameters.yml`` file *is* special: it defines the values that usually
-change on each server. For example, the database credentials on your local
-development machine might be different from your workmates. That's why this file
-is not committed to the shared repository and is only stored on your machine.
+.. seealso::
+
+ The values of env vars can only be strings, but Symfony includes some
+ :doc:`env var processors ` to transform
+ their contents (e.g. to turn a string value into an integer).
+
+To define the value of an env var, you have several options:
+
+* :ref:`Add the value to a .env file `;
+* :ref:`Encrypt the value as a secret `;
+* Set the value as a real environment variable in your shell or your web server.
+
+.. tip::
+
+ Some hosts - like SymfonyCloud - offer easy `utilities to manage env vars`_
+ in production.
+
+.. caution::
+
+ Beware that dumping the contents of the ``$_SERVER`` and ``$_ENV`` variables
+ or outputting the ``phpinfo()`` contents will display the values of the
+ environment variables, exposing sensitive information such as the database
+ credentials.
+
+ The values of the env vars are also exposed in the web interface of the
+ :doc:`Symfony profiler `. In practice this shouldn't be a
+ problem because the web profiler must **never** be enabled in production.
+
+.. _configuration-env-var-in-dev:
+.. _config-dot-env:
+
+Configuring Environment Variables in .env Files
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Instead of defining env vars in your shell or your web server, Symfony provides
+a convenient way to define them inside a ``.env`` (with a leading dot) file
+located at the root of your project.
+
+The ``.env`` file is read and parsed on every request and its env vars are added
+to the ``$_ENV`` & ``$_SERVER`` PHP variables. Any existing env vars are *never*
+overwritten by the values defined in ``.env``, so you can combine both.
+
+For example, to define the ``DATABASE_URL`` env var shown earlier in this article,
+you can add:
+
+.. code-block:: bash
+
+ # .env
+ DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name"
+
+This file should be committed to your repository and (due to that fact) should
+only contain "default" values that are good for local development. This file
+should not contain production values.
+
+In addition to your own env vars, this ``.env`` file also contains the env vars
+defined by the third-party packages installed in your application (they are
+added automatically by :ref:`Symfony Flex ` when installing packages).
+
+.. _configuration-multiple-env-files:
+
+Overriding Environment Values via .env.local
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Because of that, **parameters.yml is not committed to your version control**. In fact,
-the ``.gitignore`` file that comes with Symfony prevents it from being committed.
+If you need to override an environment value (e.g. to a different value on your
+local machine), you can do that in a ``.env.local`` file:
-However, a ``parameters.yml.dist`` file *is* committed (with dummy values). This file
-isn't read by Symfony: it's just a reference so that Symfony knows which parameters
-need to be defined in the ``parameters.yml`` file. If you add or remove keys to
-``parameters.yml``, add or remove them from ``parameters.yml.dist`` too so both
-files are always in sync.
+.. code-block:: bash
-.. sidebar:: The Interactive Parameter Handler
+ # .env.local
+ DATABASE_URL="mysql://root:@127.0.0.1:3306/my_database_name"
- When you :ref:`install an existing Symfony project `, you
- will need to create the ``parameters.yml`` file using the committed ``parameters.yml.dist``
- file as a reference. To help with this, after you run ``composer install``, a
- Symfony script will automatically create this file by interactively asking you
- to supply the value for each parameter defined in ``parameters.yml.dist``. For
- more details - or to remove or control this behavior - see the
- `Incenteev Parameter Handler`_ documentation.
+This file should be ignored by git and should *not* be committed to your repository.
+Several other ``.env`` files are available to set environment variables in *just*
+the right situation:
-Environments & the Other Config Files
--------------------------------------
+* ``.env``: defines the default values of the env vars needed by the application;
+* ``.env.local``: overrides the default values for all environments but only on
+ the machine which contains the file. This file should not be committed to the
+ repository and it's ignored in the ``test`` environment (because tests should
+ produce the same results for everyone);
+* ``.env.`` (e.g. ``.env.test``): overrides env vars only for one
+ environment but for all machines (these files *are* committed);
+* ``.env..local`` (e.g. ``.env.test.local``): defines machine-specific
+ env var overrides only for one environment. It's similar to ``.env.local``,
+ but the overrides only apply to one environment.
-You have just *one* app, but whether you realize it or not, you need it to behave
-*differently* at different times:
+*Real* environment variables always win over env vars created by any of the
+``.env`` files.
-* While **developing**, you want your app to log everything and expose nice debugging
- tools;
+The ``.env`` and ``.env.`` files should be committed to the
+repository because they are the same for all developers and machines. However,
+the env files ending in ``.local`` (``.env.local`` and ``.env..local``)
+**should not be committed** because only you will use them. In fact, the
+``.gitignore`` file that comes with Symfony prevents them from being committed.
-* After deploying to **production**, you want that *same* app to be optimized for
- speed and only log errors.
+.. caution::
-How can you make *one* application behave in two different ways? With *environments*.
+ 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`.
-You've probably already been using the ``dev`` environment without even knowing it.
-After you deploy, you'll use the ``prod`` environment.
+.. _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
+production server(s) with your production values.
+
+To improve performance, you can optionally run the ``dump-env`` command (available
+in :ref:`Symfony Flex ` 1.2 or later):
+
+.. code-block:: terminal
+
+ # parses ALL .env files and dumps their final values to .env.local.php
+ $ composer dump-env prod
+
+After running this command, Symfony will load the ``.env.local.php`` file to
+get the environment variables and will not spend time parsing the ``.env`` files.
+
+.. tip::
+
+ Update your deployment tools/workflow to run the ``dump-env`` command after
+ each deploy to improve the application performance.
+
+.. _configuration-secrets:
+
+Encrypting Environment Variables (Secrets)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Instead of defining a real environment variable or adding it to a ``.env`` file,
+if the value of a variable is sensitive (e.g. an API key or a database password),
+you can encrypt the value using the :doc:`secrets management system `.
+
+Listing Environment Variables
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Regardless of how you set environment variables, you can see a full list with
+their values by running:
+
+.. code-block:: terminal
+
+ $ php bin/console debug:container --env-vars
+
+ ---------------- ----------------- ---------------------------------------------
+ Name Default value Real value
+ ---------------- ----------------- ---------------------------------------------
+ APP_SECRET n/a "471a62e2d601a8952deb186e44186cb3"
+ FOO "[1, "2.5", 3]" n/a
+ BAR null n/a
+ ---------------- ----------------- ---------------------------------------------
+
+ # you can also filter the list of env vars by name:
+ $ php bin/console debug:container --env-vars foo
+
+ # run this command to show all the details for a specific env var:
+ $ php bin/console debug:container --env-var=FOO
+
+.. _configuration-accessing-parameters:
+
+Accessing Configuration Parameters
+----------------------------------
+
+Controllers and services can access all the configuration parameters. This
+includes both the :ref:`parameters defined by yourself `
+and the parameters created by packages/bundles. Run the following command to see
+all the parameters that exist in your application:
+
+.. code-block:: terminal
+
+ $ php bin/console debug:container --parameters
+
+In controllers extending from the :ref:`AbstractController `,
+use the ``getParameter()`` helper::
+
+ // src/Controller/UserController.php
+ namespace App\Controller;
+
+ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+
+ class UserController extends AbstractController
+ {
+ // ...
-To learn more about *how* to execute and control each environment, see
-:doc:`/configuration/environments`.
+ public function index()
+ {
+ $projectDir = $this->getParameter('kernel.project_dir');
+ $adminEmail = $this->getParameter('app.admin_email');
+
+ // ...
+ }
+ }
+
+In services and controllers not extending from ``AbstractController``, inject
+the parameters as arguments of their constructors. You must inject them
+explicitly because :doc:`service autowiring `
+doesn't work for parameters:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/services.yaml
+ parameters:
+ app.contents_dir: '...'
+
+ services:
+ App\Service\MessageGenerator:
+ arguments:
+ $contentsDir: '%app.contents_dir%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ ...
+
+
+
+
+ %app.contents_dir%
+
+
+
+
+ .. code-block:: php
+
+ // config/services.php
+ use App\Service\MessageGenerator;
+ use Symfony\Component\DependencyInjection\Reference;
+
+ $container->setParameter('app.contents_dir', '...');
+
+ $container->getDefinition(MessageGenerator::class)
+ ->setArgument('$contentsDir', '%app.contents_dir%');
+
+If you inject the same parameters over and over again, use instead the
+``services._defaults.bind`` option. The arguments defined in that option are
+injected automatically whenever a service constructor or controller action
+define an argument with that exact name. For example, to inject the value of the
+:ref:`kernel.project_dir parameter `
+whenever a service/controller defines a ``$projectDir`` argument, use this:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/services.yaml
+ services:
+ _defaults:
+ bind:
+ # pass this value to any $projectDir argument for any service
+ # that's created in this file (including controller arguments)
+ $projectDir: '%kernel.project_dir%'
+
+ # ...
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+ %kernel.project_dir%
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/services.php
+ use App\Controller\LuckyController;
+ use Psr\Log\LoggerInterface;
+ use Symfony\Component\DependencyInjection\Reference;
+
+ $container->register(LuckyController::class)
+ ->setPublic(true)
+ ->setBindings([
+ // pass this value to any $projectDir argument for any service
+ // that's created in this file (including controller arguments)
+ '$projectDir' => '%kernel.project_dir%',
+ ])
+ ;
+
+.. seealso::
+
+ Read the article about :ref:`binding arguments by name and/or type `
+ to learn more about this powerful feature.
+
+Finally, if some service needs to access to lots of parameters, instead of
+injecting each of them individually, you can inject all the application
+parameters at once by type-hinting any of its constructor arguments with the
+:class:`Symfony\\Component\\DependencyInjection\\ParameterBag\\ContainerBagInterface`::
+
+ // src/Service/MessageGenerator.php
+ namespace App\Service;
+
+ // ...
+
+ use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface;
+
+ class MessageGenerator
+ {
+ private $params;
+
+ public function __construct(ContainerBagInterface $params)
+ {
+ $this->params = $params;
+ }
+
+ public function someMethod()
+ {
+ // get any container parameter from $this->params, which stores all of them
+ $sender = $this->params->get('mailer_sender');
+ // ...
+ }
+ }
Keep Going!
-----------
@@ -416,10 +832,7 @@ part of Symfony individually by following the guides. Check out:
* :doc:`/email`
* :doc:`/logging`
-And the many other topics.
-
-Learn more
-----------
+And all the other topics related to configuration:
.. toctree::
:maxdepth: 1
@@ -427,4 +840,7 @@ Learn more
configuration/*
-.. _`Incenteev Parameter Handler`: https://github.com/Incenteev/ParameterHandler
+.. _`Learn the XML syntax`: https://en.wikipedia.org/wiki/XML
+.. _`environment variables`: https://en.wikipedia.org/wiki/Environment_variable
+.. _`symbolic links`: https://en.wikipedia.org/wiki/Symbolic_link
+.. _`utilities to manage env vars`: https://symfony.com/doc/master/cloud/cookbooks/env.html
diff --git a/configuration/configuration_organization.rst b/configuration/configuration_organization.rst
deleted file mode 100644
index 5b662023ee7..00000000000
--- a/configuration/configuration_organization.rst
+++ /dev/null
@@ -1,370 +0,0 @@
-.. index::
- single: Configuration
-
-How to Organize Configuration Files
-===================================
-
-The default Symfony Standard Edition defines three
-:doc:`execution environments ` called
-``dev``, ``prod`` and ``test``. An environment represents a way to
-execute the same codebase with different configurations.
-
-In order to select the configuration file to load for each environment, Symfony
-executes the ``registerContainerConfiguration()`` method of the ``AppKernel``
-class::
-
- // app/AppKernel.php
- use Symfony\Component\Config\Loader\LoaderInterface;
- use Symfony\Component\HttpKernel\Kernel;
-
- class AppKernel extends Kernel
- {
- // ...
-
- public function registerContainerConfiguration(LoaderInterface $loader)
- {
- $loader->load($this->getProjectDir().'/app/config/config_'.$this->getEnvironment().'.yml');
- }
- }
-
-This method loads the ``app/config/config_dev.yml`` file for the ``dev``
-environment and so on. In turn, this file loads the common configuration file
-located at ``app/config/config.yml``. Therefore, the configuration files of the
-default Symfony Standard Edition follow this structure:
-
-.. code-block:: text
-
- your-project/
- ├─ app/
- │ ├─ ...
- │ └─ config/
- │ ├─ config.yml
- │ ├─ config_dev.yml
- │ ├─ config_prod.yml
- │ ├─ config_test.yml
- │ ├─ parameters.yml
- │ ├─ parameters.yml.dist
- │ ├─ routing.yml
- │ ├─ routing_dev.yml
- │ └─ security.yml
- ├─ ...
-
-This default structure was chosen for its simplicity — one file per environment.
-But as any other Symfony feature, you can customize it to better suit your needs.
-The following sections explain different ways to organize your configuration
-files. In order to simplify the examples, only the ``dev`` and ``prod``
-environments are taken into account.
-
-Different Directories per Environment
--------------------------------------
-
-Instead of suffixing the files with ``_dev`` and ``_prod``, this technique
-groups all the related configuration files under a directory with the same
-name as the environment:
-
-.. code-block:: text
-
- your-project/
- ├─ app/
- │ ├─ ...
- │ └─ config/
- │ ├─ common/
- │ │ ├─ config.yml
- │ │ ├─ parameters.yml
- │ │ ├─ routing.yml
- │ │ └─ security.yml
- │ ├─ dev/
- │ │ ├─ config.yml
- │ │ ├─ parameters.yml
- │ │ ├─ routing.yml
- │ │ └─ security.yml
- │ └─ prod/
- │ ├─ config.yml
- │ ├─ parameters.yml
- │ ├─ routing.yml
- │ └─ security.yml
- ├─ ...
-
-To make this work, change the code of the
-:method:`Symfony\\Component\\HttpKernel\\KernelInterface::registerContainerConfiguration`
-method::
-
- // app/AppKernel.php
- use Symfony\Component\Config\Loader\LoaderInterface;
- use Symfony\Component\HttpKernel\Kernel;
-
- class AppKernel extends Kernel
- {
- // ...
-
- public function registerContainerConfiguration(LoaderInterface $loader)
- {
- $loader->load($this->getProjectDir().'/app/config/'.$this->getEnvironment().'/config.yml');
- }
- }
-
-Then, make sure that each ``config.yml`` file loads the rest of the configuration
-files, including the common files. For instance, this would be the imports
-needed for the ``app/config/dev/config.yml`` file:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/dev/config.yml
- imports:
- - { resource: '../common/config.yml' }
- - { resource: 'parameters.yml' }
- - { resource: 'security.yml' }
-
- # ...
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/dev/config.php
- $loader->import('../common/config.php');
- $loader->import('parameters.php');
- $loader->import('security.php');
-
- // ...
-
-.. include:: /components/dependency_injection/_imports-parameters-note.rst.inc
-
-Semantic Configuration Files
-----------------------------
-
-A different organization strategy may be needed for complex applications with
-large configuration files. For instance, you could create one file per bundle
-and several files to define all application services:
-
-.. code-block:: text
-
- your-project/
- ├─ app/
- │ ├─ ...
- │ └─ config/
- │ ├─ bundles/
- │ │ ├─ bundle1.yml
- │ │ ├─ bundle2.yml
- │ │ ├─ ...
- │ │ └─ bundleN.yml
- │ ├─ environments/
- │ │ ├─ common.yml
- │ │ ├─ dev.yml
- │ │ └─ prod.yml
- │ ├─ routing/
- │ │ ├─ common.yml
- │ │ ├─ dev.yml
- │ │ └─ prod.yml
- │ └─ services/
- │ ├─ frontend.yml
- │ ├─ backend.yml
- │ ├─ ...
- │ └─ security.yml
- ├─ ...
-
-Again, change the code of the ``registerContainerConfiguration()`` method to
-make Symfony aware of the new file organization::
-
- // app/AppKernel.php
- use Symfony\Component\Config\Loader\LoaderInterface;
- use Symfony\Component\HttpKernel\Kernel;
-
- class AppKernel extends Kernel
- {
- // ...
-
- public function registerContainerConfiguration(LoaderInterface $loader)
- {
- $loader->load($this->getProjectDir().'/app/config/environments/'.$this->getEnvironment().'.yml');
- }
- }
-
-Following the same technique explained in the previous section, make sure to
-import the appropriate configuration files from each main file (``common.yml``,
-``dev.yml`` and ``prod.yml``).
-
-Advanced Techniques
--------------------
-
-Symfony loads configuration files using the
-:doc:`Config component `, which provides some
-advanced features.
-
-Mix and Match Configuration Formats
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Configuration files can import files defined with any other built-in configuration
-format (``.yml``, ``.xml``, ``.php``, ``.ini``):
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- imports:
- - { resource: 'parameters.yml' }
- - { resource: 'services.xml' }
- - { resource: 'security.yml' }
- - { resource: 'legacy.php' }
-
- # ...
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- $loader->import('parameters.yml');
- $loader->import('services.xml');
- $loader->import('security.yml');
- $loader->import('legacy.php');
-
- // ...
-
-.. caution::
-
- The ``IniFileLoader`` parses the file contents using the
- :phpfunction:`parse_ini_file` function. Therefore, you can only set
- parameters to string values. Use one of the other loaders if you want
- to use other data types (e.g. boolean, integer, etc.).
-
-If you use any other configuration format, you have to define your own loader
-class extending it from :class:`Symfony\\Component\\DependencyInjection\\Loader\\FileLoader`.
-When the configuration values are dynamic, you can use the PHP configuration
-file to execute your own logic. In addition, you can define your own services
-to load configurations from databases or web services.
-
-Global Configuration Files
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Some system administrators may prefer to store sensitive parameters in files
-outside the project directory. Imagine that the database credentials for your
-website are stored in the ``/etc/sites/mysite.com/parameters.yml`` file. Loading
-this file is as simple as indicating the full file path when importing it from
-any other configuration file:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- imports:
- - { resource: 'parameters.yml' }
- - { resource: '/etc/sites/mysite.com/parameters.yml' }
-
- # ...
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- $loader->import('parameters.yml');
- $loader->import('/etc/sites/mysite.com/parameters.yml');
-
- // ...
-
-Most of the time, local developers won't have the same files that exist on the
-production servers. For that reason, the Config component provides the
-``ignore_errors`` option to silently discard errors when the loaded file
-doesn't exist:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- imports:
- - { resource: 'parameters.yml' }
- - { resource: '/etc/sites/mysite.com/parameters.yml', ignore_errors: true }
-
- # ...
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- $loader->import('parameters.yml');
- $loader->import('/etc/sites/mysite.com/parameters.yml', null, true);
-
- // ...
-
-As you've seen, there are lots of ways to organize your configuration files. You
-can choose one of these or even create your own custom way of organizing the
-files. Don't feel limited by the Standard Edition that comes with Symfony. For even
-more customization, see ":doc:`/configuration/override_dir_structure`".
diff --git a/configuration/dot-env-changes.rst b/configuration/dot-env-changes.rst
new file mode 100644
index 00000000000..7484c4e1603
--- /dev/null
+++ b/configuration/dot-env-changes.rst
@@ -0,0 +1,100 @@
+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. Basically,
+ the ``.env.dist`` file was moved to ``.env``.
+
+* 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
new file mode 100644
index 00000000000..83eafef37aa
--- /dev/null
+++ b/configuration/env_var_processors.rst
@@ -0,0 +1,650 @@
+.. index::
+ single: Environment Variable Processors; env vars
+
+.. _env-var-processors:
+
+Environment Variable Processors
+===============================
+
+:ref:`Using env vars to configure Symfony applications ` is a
+common practice to make your applications truly dynamic.
+
+The main issue of env vars is that their values can only be strings and your
+application may need other data types (integer, boolean, etc.). Symfony solves
+this problem with "env var processors", which transform the original contents of
+the given environment variables. The following example uses the integer
+processor to turn the value of the ``HTTP_PORT`` env var into an integer:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/framework.yaml
+ framework:
+ router:
+ http_port: '%env(int:HTTP_PORT)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/framework.php
+ $container->loadFromExtension('framework', [
+ 'router' => [
+ 'http_port' => '%env(int:HTTP_PORT)%',
+ ],
+ ]);
+
+Built-In Environment Variable Processors
+----------------------------------------
+
+Symfony provides the following env var processors:
+
+``env(string:FOO)``
+ Casts ``FOO`` to a string:
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/framework.yaml
+ parameters:
+ env(SECRET): 'some_secret'
+ framework:
+ secret: '%env(string:SECRET)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ some_secret
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/framework.php
+ $container->setParameter('env(SECRET)', 'some_secret');
+ $container->loadFromExtension('framework', [
+ 'secret' => '%env(string:SECRET)%',
+ ]);
+
+``env(bool:FOO)``
+ Casts ``FOO`` to a bool:
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/framework.yaml
+ parameters:
+ env(HTTP_METHOD_OVERRIDE): 'true'
+ framework:
+ http_method_override: '%env(bool:HTTP_METHOD_OVERRIDE)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ true
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/framework.php
+ $container->setParameter('env(HTTP_METHOD_OVERRIDE)', 'true');
+ $container->loadFromExtension('framework', [
+ 'http_method_override' => '%env(bool:HTTP_METHOD_OVERRIDE)%',
+ ]);
+
+``env(int:FOO)``
+ Casts ``FOO`` to an int.
+
+``env(float:FOO)``
+ Casts ``FOO`` to a float.
+
+``env(const:FOO)``
+ Finds the const value named in ``FOO``:
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/security.yaml
+ parameters:
+ env(HEALTH_CHECK_METHOD): 'Symfony\Component\HttpFoundation\Request::METHOD_HEAD'
+ security:
+ access_control:
+ - { path: '^/health-check$', methods: '%env(const:HEALTH_CHECK_METHOD)%' }
+
+ .. code-block:: xml
+
+
+
+
+
+
+ Symfony\Component\HttpFoundation\Request::METHOD_HEAD
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/security.php
+ $container->setParameter('env(HEALTH_CHECK_METHOD)', 'Symfony\Component\HttpFoundation\Request::METHOD_HEAD');
+ $container->loadFromExtension('security', [
+ 'access_control' => [
+ [
+ 'path' => '^/health-check$',
+ 'methods' => '%env(const:HEALTH_CHECK_METHOD)%',
+ ],
+ ],
+ ]);
+
+``env(base64:FOO)``
+ Decodes the content of ``FOO``, which is a base64 encoded string.
+
+``env(json:FOO)``
+ Decodes the content of ``FOO``, which is a JSON encoded string. It returns
+ either an array or ``null``:
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/framework.yaml
+ parameters:
+ env(TRUSTED_HOSTS): '["10.0.0.1", "10.0.0.2"]'
+ framework:
+ trusted_hosts: '%env(json:TRUSTED_HOSTS)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ ["10.0.0.1", "10.0.0.2"]
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/framework.php
+ $container->setParameter('env(TRUSTED_HOSTS)', '["10.0.0.1", "10.0.0.2"]');
+ $container->loadFromExtension('framework', [
+ 'trusted_hosts' => '%env(json:TRUSTED_HOSTS)%',
+ ]);
+
+``env(resolve:FOO)``
+ Replaces the string ``FOO`` by the value of a config parameter with the
+ same name:
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/sentry.yaml
+ parameters:
+ env(HOST): '10.0.0.1'
+ sentry_host: '%env(HOST)%'
+ env(SENTRY_DSN): 'http://%sentry_host%/project'
+ sentry:
+ dsn: '%env(resolve:SENTRY_DSN)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ 10.0.0.1
+ %env(HOST)%
+ http://%sentry_host%/project
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/sentry.php
+ $container->setParameter('env(HOST)', '10.0.0.1');
+ $container->setParameter('sentry_host', '%env(HOST)%');
+ $container->setParameter('env(SENTRY_DSN)', 'http://%sentry_host%/project');
+ $container->loadFromExtension('sentry', [
+ 'dsn' => '%env(resolve:SENTRY_DSN)%',
+ ]);
+
+``env(csv:FOO)``
+ Decodes the content of ``FOO``, which is a CSV-encoded string:
+
+ .. code-block:: yaml
+
+ parameters:
+ env(TRUSTED_HOSTS): "10.0.0.1, 10.0.0.2"
+ framework:
+ trusted_hosts: '%env(csv:TRUSTED_HOSTS)%'
+
+``env(file:FOO)``
+ Returns the contents of a file whose path is the value of the ``FOO`` env var:
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/framework.yaml
+ parameters:
+ env(AUTH_FILE): '../config/auth.json'
+ google:
+ auth: '%env(file:AUTH_FILE)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ ../config/auth.json
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/framework.php
+ $container->setParameter('env(AUTH_FILE)', '../config/auth.json');
+ $container->loadFromExtension('google', [
+ 'auth' => '%env(file:AUTH_FILE)%',
+ ]);
+
+``env(require:FOO)``
+ ``require()`` the PHP file whose path is the value of the ``FOO``
+ env var and return the value returned from it.
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/framework.yaml
+ parameters:
+ env(PHP_FILE): '../config/.runtime-evaluated.php'
+ app:
+ auth: '%env(require:PHP_FILE)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ ../config/.runtime-evaluated.php
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/framework.php
+ $container->setParameter('env(PHP_FILE)', '../config/.runtime-evaluated.php');
+ $container->loadFromExtension('app', [
+ 'auth' => '%env(require:AUTH_FILE)%',
+ ]);
+
+``env(trim:FOO)``
+ Trims the content of ``FOO`` env var, removing whitespaces from the beginning
+ and end of the string. This is especially useful in combination with the
+ ``file`` processor, as it'll remove newlines at the end of a file.
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/framework.yaml
+ parameters:
+ env(AUTH_FILE): '../config/auth.json'
+ google:
+ auth: '%env(trim:file:AUTH_FILE)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ ../config/auth.json
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/framework.php
+ $container->setParameter('env(AUTH_FILE)', '../config/auth.json');
+ $container->loadFromExtension('google', [
+ 'auth' => '%env(trim:file:AUTH_FILE)%',
+ ]);
+
+``env(key:FOO:BAR)``
+ Retrieves the value associated with the key ``FOO`` from the array whose
+ contents are stored in the ``BAR`` env var:
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/services.yaml
+ parameters:
+ env(SECRETS_FILE): '/opt/application/.secrets.json'
+ database_password: '%env(key:database_password:json:file:SECRETS_FILE)%'
+ # if SECRETS_FILE contents are: {"database_password": "secret"} it returns "secret"
+
+ .. code-block:: xml
+
+
+
+
+
+
+ /opt/application/.secrets.json
+ %env(key:database_password:json:file:SECRETS_FILE)%
+
+
+
+ .. code-block:: php
+
+ // config/services.php
+ $container->setParameter('env(SECRETS_FILE)', '/opt/application/.secrets.json');
+ $container->setParameter('database_password', '%env(key:database_password:json:file:SECRETS_FILE)%');
+
+``env(default:fallback_param:BAR)``
+ Retrieves the value of the parameter ``fallback_param`` when the ``BAR`` env
+ var is not available:
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/services.yaml
+ parameters:
+ # if PRIVATE_KEY is not a valid file path, the content of raw_key is returned
+ private_key: '%env(default:raw_key:file:PRIVATE_KEY)%'
+ raw_key: '%env(PRIVATE_KEY)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+ %env(default:raw_key:file:PRIVATE_KEY)%
+ %env(PRIVATE_KEY)%
+
+
+
+ .. code-block:: php
+
+ // config/services.php
+
+ // if PRIVATE_KEY is not a valid file path, the content of raw_key is returned
+ $container->setParameter('private_key', '%env(default:raw_key:file:PRIVATE_KEY)%');
+ $container->setParameter('raw_key', '%env(PRIVATE_KEY)%');
+
+ When the fallback parameter is omitted (e.g. ``env(default::API_KEY)``), the
+ value returned is ``null``.
+
+``env(url:FOO)``
+ Parses an absolute URL and returns its components as an associative array.
+
+ .. code-block:: bash
+
+ # .env
+ MONGODB_URL="mongodb://db_user:db_password@127.0.0.1:27017/db_name"
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/mongodb.yaml
+ mongo_db_bundle:
+ clients:
+ default:
+ hosts:
+ - { host: '%env(key:host:url:MONGODB_URL)%', port: '%env(key:port:url:MONGODB_URL)%' }
+ username: '%env(key:user:url:MONGODB_URL)%'
+ password: '%env(key:pass:url:MONGODB_URL)%'
+ connections:
+ default:
+ database_name: '%env(key:path:url:MONGODB_URL)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/mongodb.php
+ $container->loadFromExtension('mongodb', [
+ 'clients' => [
+ 'default' => [
+ 'hosts' => [
+ [
+ 'host' => '%env(key:host:url:MONGODB_URL)%',
+ 'port' => '%env(key:port:url:MONGODB_URL)%',
+ ],
+ ],
+ 'username' => '%env(key:user:url:MONGODB_URL)%',
+ 'password' => '%env(key:pass:url:MONGODB_URL)%',
+ ],
+ ],
+ 'connections' => [
+ 'default' => [
+ 'database_name' => '%env(key:path:url:MONGODB_URL)%',
+ ],
+ ],
+ ]);
+
+ .. caution::
+
+ In order to ease extraction of the resource from the URL, the leading
+ ``/`` is trimmed from the ``path`` component.
+
+``env(query_string:FOO)``
+ Parses the query string part of the given URL and returns its components as
+ an associative array.
+
+ .. code-block:: bash
+
+ # .env
+ MONGODB_URL="mongodb://db_user:db_password@127.0.0.1:27017/db_name?timeout=3000"
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/mongodb.yaml
+ mongo_db_bundle:
+ clients:
+ default:
+ # ...
+ connectTimeoutMS: '%env(int:key:timeout:query_string:MONGODB_URL)%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/mongodb.php
+ $container->loadFromExtension('mongodb', [
+ 'clients' => [
+ 'default' => [
+ // ...
+ 'connectTimeoutMS' => '%env(int:key:timeout:query_string:MONGODB_URL)%',
+ ],
+ ],
+ ]);
+
+It is also possible to combine any number of processors:
+
+.. code-block:: yaml
+
+ parameters:
+ env(AUTH_FILE): "%kernel.project_dir%/config/auth.json"
+ google:
+ # 1. gets the value of the AUTH_FILE env var
+ # 2. replaces the values of any config param to get the config path
+ # 3. gets the content of the file stored in that path
+ # 4. JSON-decodes the content of the file and returns it
+ auth: '%env(json:file:resolve:AUTH_FILE)%'
+
+Custom Environment Variable Processors
+--------------------------------------
+
+It's also possible to add your own processors for environment variables. First,
+create a class that implements
+:class:`Symfony\\Component\\DependencyInjection\\EnvVarProcessorInterface`::
+
+ use Symfony\Component\DependencyInjection\EnvVarProcessorInterface;
+
+ class LowercasingEnvVarProcessor implements EnvVarProcessorInterface
+ {
+ public function getEnv($prefix, $name, \Closure $getEnv)
+ {
+ $env = $getEnv($name);
+
+ return strtolower($env);
+ }
+
+ public static function getProvidedTypes()
+ {
+ return [
+ 'lowercase' => 'string',
+ ];
+ }
+ }
+
+To enable the new processor in the app, register it as a service and
+:doc:`tag it ` with the ``container.env_var_processor``
+tag. If you're using the
+:ref:`default services.yaml configuration `,
+this is already done for you, thanks to :ref:`autoconfiguration `.
diff --git a/configuration/environments.rst b/configuration/environments.rst
deleted file mode 100644
index 2d78e6c56da..00000000000
--- a/configuration/environments.rst
+++ /dev/null
@@ -1,429 +0,0 @@
-.. index::
- single: Environments
-
-How to Master and Create new Environments
-=========================================
-
-Every application is the combination of code and a set of configuration that
-dictates how that code should function. The configuration may define the database
-being used, if something should be cached or how verbose logging should be.
-
-In Symfony, the idea of "environments" is the idea that the same codebase can be
-run using multiple different configurations. For example, the ``dev`` environment
-should use configuration that makes development easy and friendly, while the
-``prod`` environment should use a set of configuration optimized for speed.
-
-.. index::
- single: Environments; Configuration files
-
-Different Environments, different Configuration Files
------------------------------------------------------
-
-A typical Symfony application begins with three environments: ``dev``,
-``prod``, and ``test``. As mentioned, each environment simply represents
-a way to execute the same codebase with different configuration. It should
-be no surprise then that each environment loads its own individual configuration
-file. If you're using the YAML configuration format, the following files
-are used:
-
-* for the ``dev`` environment: ``app/config/config_dev.yml``
-* for the ``prod`` environment: ``app/config/config_prod.yml``
-* for the ``test`` environment: ``app/config/config_test.yml``
-
-This works via a simple standard that's used by default inside the ``AppKernel``
-class::
-
- // app/AppKernel.php
-
- // ...
-
- class AppKernel extends Kernel
- {
- // ...
-
- public function registerContainerConfiguration(LoaderInterface $loader)
- {
- $loader->load($this->getProjectDir().'/app/config/config_'.$this->getEnvironment().'.yml');
- }
- }
-
-As you can see, when Symfony is loaded, it uses the given environment to
-determine which configuration file to load. This accomplishes the goal of
-multiple environments in an elegant, powerful and transparent way.
-
-However, in practice each environment differs only somewhat from others.
-Generally, all environments will share a large base of common configuration.
-Opening the ``config_dev.yml`` configuration file, you can see how this is
-accomplished:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- imports:
- - { resource: config.yml }
-
- # ...
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- $loader->import('config.php');
-
- // ...
-
-To share common configuration, each environment's configuration file
-simply first imports from a central configuration file (``config.yml``).
-The remainder of the file can then deviate from the default configuration
-by overriding individual parameters. For example, by default, the ``web_profiler``
-toolbar is disabled. However, in the ``dev`` environment, the toolbar is
-activated by modifying the value of the ``toolbar`` option in the ``config_dev.yml``
-configuration file:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config_dev.yml
- imports:
- - { resource: config.yml }
-
- web_profiler:
- toolbar: true
- # ...
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config_dev.php
- $loader->import('config.php');
-
- $container->loadFromExtension('web_profiler', [
- 'toolbar' => true,
- // ...
- ]);
-
-.. index::
- single: Environments; Executing different environments
-
-Executing an Application in different Environments
---------------------------------------------------
-
-To execute the application in each environment, load up the application using
-either ``app.php`` (for the ``prod`` environment) or ``app_dev.php``
-(for the ``dev`` environment) front controller:
-
-.. code-block:: text
-
- http://localhost/app.php -> *prod* environment
- http://localhost/app_dev.php -> *dev* environment
-
-If you don't have *either* filename in your URL, then it's up to your web server
-to decide *which* file to execute behind the scenes. If you're using the built-in
-PHP web server, it knows to use the ``app_dev.php`` file. On production, you'll
-:doc:`configure your web server ` to use ``app.php``.
-Either way: *one of these two files is always executed*.
-
-.. note::
-
- The given URLs assume that your web server is configured to use the ``web/``
- directory of the application as its root. Read more in
- :doc:`Installing Symfony `.
-
-If you open up one of these files, you'll quickly see that the environment
-used by each is explicitly set::
-
- // web/app.php
- // ...
-
- $kernel = new AppKernel('prod', false);
-
- // ...
-
-The ``prod`` key specifies that this application will run in the ``prod``
-environment. A Symfony application can be executed in any environment by using
-this code and changing the environment string.
-
-.. note::
-
- The ``test`` environment is used when writing functional tests and is
- not accessible in the browser directly via a front controller. In other
- words, unlike the other environments, there is no ``app_test.php`` front
- controller file.
-
-.. index::
- single: Configuration; Debug mode
-
-.. sidebar:: *Debug* Mode
-
- Important, but unrelated to the topic of *environments* is the ``false``
- argument as the second argument to the ``AppKernel`` constructor. This
- specifies if the application should run in "debug mode". Regardless
- of the environment, a Symfony application can be run with debug mode
- set to ``true`` or ``false``. This affects many things in the application,
- such as displaying stacktraces on error pages or if cache files are
- dynamically rebuilt on each request. Though not a requirement, debug mode
- is generally set to ``true`` for the ``dev`` and ``test`` environments and
- ``false`` for the ``prod`` environment.
-
- Internally, the value of the debug mode becomes the ``kernel.debug``
- parameter used inside the :doc:`service container `.
- If you look inside the application configuration file, you'll see the
- parameter used, for example, to turn logging on or off when using the
- Doctrine DBAL:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- doctrine:
- dbal:
- logging: '%kernel.debug%'
- # ...
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- $container->loadFromExtension('doctrine', [
- 'dbal' => [
- 'logging' => '%kernel.debug%',
- // ...
- ],
- // ...
- ]);
-
-Selecting the Environment for Console Commands
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-By default, Symfony commands are executed in the ``dev`` environment and with the
-debug mode enabled. Use the ``--env`` and ``--no-debug`` options to modify this
-behavior:
-
-.. code-block:: terminal
-
- # 'dev' environment and debug enabled
- $ php bin/console command_name
-
- # 'prod' environment (debug is always disabled for 'prod')
- $ php bin/console command_name --env=prod
-
- # 'test' environment and debug disabled
- $ php bin/console command_name --env=test --no-debug
-
-In addition to the ``--env`` and ``--no-debug`` options, the behavior of Symfony
-commands can also be controlled with environment variables. The Symfony console
-application checks the existence and value of these environment variables before
-executing any command:
-
-``SYMFONY_ENV``
- Sets the execution environment of the command to the value of this variable
- (``dev``, ``prod``, ``test``, etc.);
-``SYMFONY_DEBUG``
- If ``0``, debug mode is disabled. Otherwise, debug mode is enabled.
-
-These environment variables are very useful for production servers because they
-allow you to ensure that commands always run in the ``prod`` environment without
-having to add any command option.
-
-.. index::
- single: Environments; Creating a new environment
-
-Creating a new Environment
---------------------------
-
-By default, a Symfony application has three environments that handle most
-cases. However, since an environment is nothing more than a string that
-corresponds to a set of configuration, creating a new environment is quite
-easy.
-
-Suppose, for example, that before deployment, you need to benchmark your
-application. One way to benchmark the application is to use near-production
-settings, but with Symfony's ``web_profiler`` enabled. This allows Symfony
-to record information about your application while benchmarking.
-
-The best way to accomplish this is via a new environment called, for example,
-``benchmark``. Start by creating a new configuration file:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config_benchmark.yml
- imports:
- - { resource: config_prod.yml }
-
- framework:
- profiler: { only_exceptions: false }
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config_benchmark.php
- $loader->import('config_prod.php');
-
- $container->loadFromExtension('framework', [
- 'profiler' => ['only_exceptions' => false],
- ]);
-
-.. include:: /components/dependency_injection/_imports-parameters-note.rst.inc
-
-And with this simple addition, the application now supports a new environment
-called ``benchmark``.
-
-This new configuration file imports the configuration from the ``prod`` environment
-and modifies it. This guarantees that the new environment is identical to
-the ``prod`` environment, except for any changes explicitly made here.
-
-Because you'll want this environment to be accessible via a browser, you
-should also create a front controller for it. Copy the ``web/app.php`` file
-to ``web/app_benchmark.php`` and edit the environment to be ``benchmark``::
-
- // web/app_benchmark.php
- // ...
-
- // change just this line
- $kernel = new AppKernel('benchmark', false);
-
- // ...
-
-The new environment is now accessible via::
-
- http://localhost/app_benchmark.php
-
-.. note::
-
- Some environments, like the ``dev`` environment, are never meant to be
- accessed on any deployed server by the public. This is because
- certain environments, for debugging purposes, may give too much information
- about the application or underlying infrastructure. To be sure these environments
- aren't accessible, the front controller is usually protected from external
- IP addresses via the following code at the top of the controller::
-
- if (!in_array(@$_SERVER['REMOTE_ADDR'], ['127.0.0.1', '::1'])) {
- die('You are not allowed to access this file. Check '.basename(__FILE__).' for more information.');
- }
-
-.. index::
- single: Environments; Cache directory
-
-Environments and the Cache Directory
-------------------------------------
-
-Symfony takes advantage of caching in many ways: the application configuration,
-routing configuration, Twig templates and more are cached to PHP objects
-stored in files on the filesystem.
-
-By default, these cached files are largely stored in the ``var/cache`` directory.
-However, each environment caches its own set of files:
-
-.. code-block:: text
-
- your-project/
- ├─ var/
- │ ├─ cache/
- │ │ ├─ dev/ # cache directory for the *dev* environment
- │ │ └─ prod/ # cache directory for the *prod* environment
- │ ├─ ...
-
-Sometimes, when debugging, it may be helpful to inspect a cached file to
-understand how something is working. When doing so, remember to look in
-the directory of the environment you're using (most commonly ``dev`` while
-developing and debugging). While it can vary, the ``var/cache/dev`` directory
-includes the following:
-
-``appDevDebugProjectContainer.php``
- The cached "service container" that represents the cached application
- configuration.
-
-``appDevUrlGenerator.php``
- The PHP class generated from the routing configuration and used when
- generating URLs.
-
-``appDevUrlMatcher.php``
- The PHP class used for route matching - look here to see the compiled regular
- expression logic used to match incoming URLs to different routes.
-
-``twig/``
- This directory contains all the cached Twig templates.
-
-.. note::
-
- You can change the directory location and name. For more information
- read the article :doc:`/configuration/override_dir_structure`.
-
-Going further
--------------
-
-Read the article on :doc:`/configuration/external_parameters`.
diff --git a/configuration/external_parameters.rst b/configuration/external_parameters.rst
deleted file mode 100644
index 367ec2bc331..00000000000
--- a/configuration/external_parameters.rst
+++ /dev/null
@@ -1,552 +0,0 @@
-.. index::
- single: Environments; External parameters
-
-How to Set external Parameters in the Service Container
-=======================================================
-
-In the article :doc:`/configuration`, you learned how to manage your application
-configuration. At times, it may benefit your application to store certain
-credentials outside of your project code. Database configuration is one such
-example. The flexibility of the Symfony service container allows you to do this.
-
-Environment Variables
----------------------
-
-.. versionadded:: 3.2
-
- ``env()`` parameters were introduced in Symfony 3.2.
-
-You can reference environment variables by using special parameters named after
-the variables you want to use enclosed between ``env()``. Their actual values
-will be resolved at runtime (once per request), so that dumped containers can be
-reconfigured dynamically even after being compiled.
-
-For example, if you want to use the value of the ``DATABASE_HOST`` environment
-variable in your service container configuration, you can reference it using
-``%env(DATABASE_HOST)%`` in your configuration files:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- doctrine:
- dbal:
- host: '%env(DATABASE_HOST)%'
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- $container->loadFromExtension('doctrine', [
- 'dbal' => [
- 'host' => '%env(DATABASE_HOST)%',
- ],
- ]);
-
-You can also give the ``env()`` parameters a default value: the default value
-will be used whenever the corresponding environment variable is *not* found:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/parameters.yml
- parameters:
- database_host: '%env(DATABASE_HOST)%'
- env(DATABASE_HOST): localhost
-
- .. code-block:: xml
-
-
-
-
-
-
- %env(DATABASE_HOST)%
- localhost
-
-
-
- .. code-block:: php
-
- // app/config/parameters.php
- $container->setParameter('database_host', '%env(DATABASE_HOST)%');
- $container->setParameter('env(DATABASE_HOST)', 'localhost');
-
-Setting environment variables is generally done at the web server level or in the
-terminal. If you're using Apache, nginx or just the console, you can use e.g. one
-of the following:
-
-.. configuration-block::
-
- .. code-block:: apache
-
-
- # ...
-
- SetEnv DATABASE_USER user
- SetEnv DATABASE_PASSWORD secret
-
-
- .. code-block:: nginx
-
- fastcgi_param DATABASE_USER user;
- fastcgi_param DATABASE_PASSWORD secret;
-
- .. code-block:: terminal
-
- $ export DATABASE_USER=user
- $ export DATABASE_PASSWORD=secret
-
-.. tip::
-
- .. deprecated:: 3.3
-
- The support of the special ``SYMFONY__`` environment variables was
- deprecated in Symfony 3.3 and will be removed in 4.0. Instead of
- using those variables, define regular environment variables and get
- their values using the ``%env(...)%`` syntax in your config files.
-
- You can also define the default value of any existing parameters using
- special environment variables named after their corresponding parameter
- prefixed with ``SYMFONY__`` after replacing dots by double underscores
- (e.g. ``SYMFONY__KERNEL__CHARSET`` to set the default value of the
- ``kernel.charset`` parameter). These default values are resolved when
- compiling the service container and won't change at runtime once dumped.
-
- The values of the env vars are also exposed in the web interface of the
- :doc:`Symfony profiler `. In practice this shouldn't be a
- problem because the web profiler must **never** be enabled in production.
-
-Environment Variable Processors
--------------------------------
-
-.. versionadded:: 3.4
-
- Environment variable processors were introduced in Symfony 3.4.
-
-The values of environment variables are considered strings by default.
-However, your code may expect other data types, like integers or booleans.
-Symfony solves this problem with *processors*, which modify the contents of the
-given environment variables. The following example uses the integer processor to
-turn the value of the ``HTTP_PORT`` env var into an integer:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- framework:
- router:
- http_port: env(int:HTTP_PORT)
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- $container->loadFromExtension('framework', [
- 'router' => [
- 'http_port' => '%env(int:HTTP_PORT)%',
- ],
- ]);
-
-Symfony provides the following env var processors:
-
-``env(string:FOO)``
- Casts ``FOO`` to a string:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- parameters:
- env(SECRET): 'some_secret'
- framework:
- secret: '%env(string:SECRET)%'
-
- .. code-block:: xml
-
-
-
-
-
-
- some_secret
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- $container->setParameter('env(SECRET)', 'some_secret');
- $container->loadFromExtension('framework', [
- 'secret' => '%env(string:SECRET)%',
- ]);
-
-``env(bool:FOO)``
- Casts ``FOO`` to a bool:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- parameters:
- env(HTTP_METHOD_OVERRIDE): 'true'
- framework:
- http_method_override: '%env(bool:HTTP_METHOD_OVERRIDE)%'
-
- .. code-block:: xml
-
-
-
-
-
-
- true
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- $container->setParameter('env(HTTP_METHOD_OVERRIDE)', 'true');
- $container->loadFromExtension('framework', [
- 'http_method_override' => '%env(bool:HTTP_METHOD_OVERRIDE)%',
- ]);
-
-``env(int:FOO)``
- Casts ``FOO`` to an int.
-
-``env(float:FOO)``
- Casts ``FOO`` to a float.
-
-``env(const:FOO)``
- Finds the constant value named in ``FOO``:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- parameters:
- env(HEALTH_CHECK_METHOD): 'Symfony\Component\HttpFoundation\Request::METHOD_HEAD'
-
- security:
- access_control:
- - { path: '^/health-check$', methods: '%env(const:HEALTH_CHECK_METHOD)%' }
-
- .. code-block:: xml
-
-
-
-
-
-
- Symfony\Component\HttpFoundation\Request::METHOD_HEAD
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- $container->setParameter('env(HEALTH_CHECK_METHOD)', 'Symfony\Component\HttpFoundation\Request::METHOD_HEAD');
- $container->loadFromExtension('security', [
- 'access_control' => [
- [
- 'path' => '^/health-check$',
- 'methods' => '%env(const:HEALTH_CHECK_METHOD)%',
- ],
- ],
- ]);
-
-``env(base64:FOO)``
- Decodes the content of ``FOO``, which is a base64 encoded string.
-
-``env(json:FOO)``
- Decodes the content of ``FOO``, which is a JSON encoded string. It returns
- either an array or ``null``:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- parameters:
- env(TRUSTED_HOSTS): '["10.0.0.1", "10.0.0.2"]'
- framework:
- trusted_hosts: '%env(json:TRUSTED_HOSTS)%'
-
- .. code-block:: xml
-
-
-
-
-
-
- ["10.0.0.1", "10.0.0.2"]
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- $container->setParameter('env(TRUSTED_HOSTS)', '["10.0.0.1", "10.0.0.2"]');
- $container->loadFromExtension('framework', [
- 'trusted_hosts' => '%env(json:TRUSTED_HOSTS)%',
- ]);
-
-``env(resolve:FOO)``
- Replaces the string ``FOO`` by the value of a config parameter with the
- same name:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- parameters:
- env(HOST): '10.0.0.1'
- sentry_host: '%env(HOST)%'
- env(SENTRY_DSN): 'http://%sentry_host%/project'
- sentry:
- dsn: '%env(resolve:SENTRY_DSN)%'
-
- .. code-block:: xml
-
-
-
-
-
-
- 10.0.0.1
- %env(HOST)%
- http://%sentry_host%/project
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- $container->setParameter('env(HOST)', '10.0.0.1');
- $container->setParameter('sentry_host', '%env(HOST)%');
- $container->setParameter('env(SENTRY_DSN)', 'http://%sentry_host%/project');
- $container->loadFromExtension('sentry', [
- 'dsn' => '%env(resolve:SENTRY_DSN)%',
- ]);
-
-``env(file:FOO)``
- Returns the contents of a file whose path is the value of the ``FOO`` env var:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- parameters:
- env(AUTH_FILE): '../config/auth.json'
- google:
- auth: '%env(file:AUTH_FILE)%'
-
- .. code-block:: xml
-
-
-
-
-
-
- ../config/auth.json
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- $container->setParameter('env(AUTH_FILE)', '../config/auth.json');
- $container->loadFromExtension('google', [
- 'auth' => '%env(file:AUTH_FILE)%',
- ]);
-
-It is also possible to combine any number of processors:
-
-.. code-block:: yaml
-
- parameters:
- env(AUTH_FILE): "%kernel.project_dir%/config/auth.json"
- google:
- # 1. gets the value of the AUTH_FILE env var
- # 2. replaces the values of any config param to get the config path
- # 3. gets the content of the file stored in that path
- # 4. JSON-decodes the content of the file and returns it
- auth: '%env(json:file:resolve:AUTH_FILE)%'
-
-Custom Environment Variable Processors
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-It's also possible to add your own processors for environment variables. First,
-create a class that implements
-:class:`Symfony\\Component\\DependencyInjection\\EnvVarProcessorInterface` and
-then, define a service for that class::
-
- class LowercasingEnvVarProcessor implements EnvVarProcessorInterface
- {
- private $container;
-
- public function __construct(ContainerInterface $container)
- {
- $this->container = $container;
- }
-
- public function getEnv($prefix, $name, \Closure $getEnv)
- {
- $env = $getEnv($name);
-
- return strtolower($env);
- }
-
- public static function getProvidedTypes()
- {
- return [
- 'lowercase' => 'string',
- ];
- }
- }
-
-Constants
----------
-
-The container also has support for setting PHP constants as parameters.
-See :ref:`component-di-parameters-constants` for more details.
-
-Miscellaneous Configuration
----------------------------
-
-The ``imports`` directive can be used to pull in parameters stored elsewhere.
-Importing a PHP file gives you the flexibility to add whatever is needed
-in the container. The following imports a file named ``parameters.php``.
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/config.yml
- imports:
- - { resource: parameters.php }
-
- .. code-block:: xml
-
-
-
-
-
-
-
-
-
-
-
- .. code-block:: php
-
- // app/config/config.php
- $loader->import('parameters.php');
-
-.. note::
-
- A resource file can be one of many types. PHP, XML, YAML, INI, and
- closure resources are all supported by the ``imports`` directive.
-
-In ``parameters.php``, tell the service container the parameters that you wish
-to set. This is useful when important configuration is in a non-standard
-format. The example below includes a Drupal database configuration in
-the Symfony service container::
-
- // app/config/parameters.php
- include_once('/path/to/drupal/sites/default/settings.php');
- $container->setParameter('drupal.database.url', $db_url);
diff --git a/configuration/front_controllers_and_kernel.rst b/configuration/front_controllers_and_kernel.rst
index 178c9daaf2d..ba702cbdc49 100644
--- a/configuration/front_controllers_and_kernel.rst
+++ b/configuration/front_controllers_and_kernel.rst
@@ -1,15 +1,15 @@
.. index::
- single: How the front controller, ``AppKernel`` and environments
+ single: How the front controller, ``Kernel`` and environments
work together
Understanding how the Front Controller, Kernel and Environments Work together
=============================================================================
-The section :doc:`/configuration/environments` explained the basics
-on how Symfony uses environments to run your application with different configuration
-settings. This section will explain a bit more in-depth what happens when
-your application is bootstrapped. To hook into this process, you need to understand
-three parts that work together:
+The :ref:`configuration environments ` section
+explained the basics on how Symfony uses environments to run your application
+with different configuration settings. This section will explain a bit more
+in-depth what happens when your application is bootstrapped. To hook into this
+process, you need to understand three parts that work together:
* `The Front Controller`_
* `The Kernel Class`_
@@ -18,25 +18,24 @@ three parts that work together:
.. note::
Usually, you will not need to define your own front controller or
- ``AppKernel`` class as the `Symfony Standard Edition`_ provides
- sensible default implementations.
+ ``Kernel`` class as Symfony provides sensible default implementations.
+ This article is provided to explain what is going on behind the scenes.
- This documentation section is provided to explain what is going on behind
- the scenes.
+.. _architecture-front-controller:
The Front Controller
--------------------
-The `front controller`_ is a well-known design pattern; it is a section of
-code that *all* requests served by an application run through.
+The `front controller`_ is a design pattern; it is a section of code that *all*
+requests served by an application run through.
-In the `Symfony Standard Edition`_, this role is taken by the `app.php`_
-and `app_dev.php`_ files in the ``web/`` directory. These are the very
-first PHP scripts executed when a request is processed.
+In the Symfony Skeleton, this role is taken by the ``index.php`` file in the
+``public/`` directory. This is the very first PHP script executed when a
+request is processed.
The main purpose of the front controller is to create an instance of the
-``AppKernel`` (more on that in a second), make it handle the request
-and return the resulting response to the browser.
+``Kernel`` (more on that in a second), make it handle the request and return
+the resulting response to the browser.
Because every request is routed through it, the front controller can be
used to perform global initialization prior to setting up the kernel or
@@ -44,38 +43,26 @@ to `decorate`_ the kernel with additional features. Examples include:
* Configuring the autoloader or adding additional autoloading mechanisms;
* Adding HTTP level caching by wrapping the kernel with an instance of
- :ref:`AppCache `;
-* Enabling (or skipping) the :doc:`ClassCache `;
+ :ref:`HttpCache `;
* Enabling the `Debug component`_.
-The front controller can be chosen by requesting URLs like:
+You can choose the front controller that's used by adding it in the URL, like:
.. code-block:: text
- http://localhost/app_dev.php/some/path/...
+ http://localhost/index.php/some/path/...
As you can see, this URL contains the PHP script to be used as the front
-controller. You can use that to switch the front controller or use
-a custom one by placing it in the ``web/`` directory (e.g. ``app_cache.php``).
+controller. You can use that to switch to a custom made front controller
+that is located in the ``public/`` directory.
-When using Apache and the `RewriteRule shipped with the Symfony Standard Edition`_,
-you can omit the filename from the URL and the RewriteRule will use ``app.php``
-as the default one.
+.. seealso::
-.. note::
-
- Pretty much every other web server should be able to achieve a
- behavior similar to that of the RewriteRule described above.
- Check your server documentation for details or see
+ You almost never want to show the front controller in the URL. This is
+ achieved by configuring the web server, as shown in
:doc:`/setup/web_server_configuration`.
-.. note::
-
- Make sure you appropriately secure your front controllers against unauthorized
- access. For example, you don't want to make a debugging environment
- available to arbitrary users in your production environment.
-
-Technically, the `bin/console`_ script used when running Symfony on the command
+Technically, the ``bin/console`` script used when running Symfony on the command
line is also a front controller, only that is not used for web, but for command
line requests.
@@ -83,88 +70,200 @@ The Kernel Class
----------------
The :class:`Symfony\\Component\\HttpKernel\\Kernel` is the core of
-Symfony. It is responsible for setting up all the bundles that make up
+Symfony. It is responsible for setting up all the bundles used by
your application and providing them with the application's configuration.
It then creates the service container before serving requests in its
:method:`Symfony\\Component\\HttpKernel\\HttpKernelInterface::handle`
method.
-There are two methods declared in the
-:class:`Symfony\\Component\\HttpKernel\\KernelInterface` that are
-left unimplemented in :class:`Symfony\\Component\\HttpKernel\\Kernel`
-and thus serve as `template methods`_:
+The kernel used in Symfony applications extends from :class:`Symfony\\Component\\HttpKernel\\Kernel`
+and uses the :class:`Symfony\\Bundle\\FrameworkBundle\\Kernel\\MicroKernelTrait`.
+The ``Kernel`` class leaves some methods from :class:`Symfony\\Component\\HttpKernel\\KernelInterface`
+unimplemented and the ``MicroKernelTrait`` defines several abstract methods, so
+you must implement them all:
:method:`Symfony\\Component\\HttpKernel\\KernelInterface::registerBundles`
It must return an array of all bundles needed to run the application.
-:method:`Symfony\\Component\\HttpKernel\\KernelInterface::registerContainerConfiguration`
- It loads the application configuration.
-To fill these (small) blanks, your application needs to subclass the
-Kernel and implement these methods. The resulting class is conventionally
-called the ``AppKernel``.
+:method:`Symfony\\Bundle\\FrameworkBundle\\Kernel\\MicroKernelTrait::configureRoutes`
+ It adds individual routes or collections of routes to the application (for
+ example loading the routes defined in some config file).
+
+:method:`Symfony\\Bundle\\FrameworkBundle\\Kernel\\MicroKernelTrait::configureContainer`
+ It loads the application configuration from config files or using the
+ ``loadFromExtension()`` method and can also register new container parameters
+ and services.
-Again, the Symfony Standard Edition provides an `AppKernel`_ in the ``app/``
-directory. This class uses the name of the environment - which is passed to
-the Kernel's :method:`constructor `
+To fill these (small) blanks, your application needs to extend the Kernel class
+and use the MicroKernelTrait to implement these methods. Symfony provides by
+default that kernel in the ``src/Kernel.php`` file.
+
+This class uses the name of the environment - which is passed to the Kernel's
+:method:`constructor `
method and is available via :method:`Symfony\\Component\\HttpKernel\\Kernel::getEnvironment` -
-to decide which bundles to create. The logic for that is in ``registerBundles()``,
-a method meant to be extended by you when you start adding bundles to your
-application.
+to decide which bundles to enable. The logic for that is in ``registerBundles()``.
-You are free to create your own, alternative or additional ``AppKernel``
-variants. All you need is to adapt your (or add a new) front controller to make
-use of the new kernel.
+You are free to create your own, alternative or additional ``Kernel`` variants.
+All you need is to adapt your (or add a new) front controller to make use of the
+new kernel.
.. note::
- The name and location of the ``AppKernel`` is not fixed. When
- putting multiple Kernels into a single application,
- it might therefore make sense to add additional sub-directories,
- for example ``app/admin/AdminKernel.php`` and
- ``app/api/ApiKernel.php``. All that matters is that your front
- controller is able to create an instance of the appropriate kernel.
-
-Having different ``AppKernels`` might be useful to enable different front
-controllers (on potentially different servers) to run parts of your application
-independently (for example, the admin UI, the front-end UI and database migrations).
+ The name and location of the ``Kernel`` is not fixed. When putting
+ :doc:`multiple kernels into a single application `,
+ it might therefore make sense to add additional sub-directories, for example
+ ``src/admin/AdminKernel.php`` and ``src/api/ApiKernel.php``. All that matters
+ is that your front controller is able to create an instance of the appropriate kernel.
.. note::
- There's a lot more the ``AppKernel`` can be used for, for example
+ There's a lot more the ``Kernel`` can be used for, for example
:doc:`overriding the default directory structure `.
But odds are high that you don't need to change things like this on the
- fly by having several ``AppKernel`` implementations.
+ fly by having several ``Kernel`` implementations.
+
+.. index::
+ single: Configuration; Debug mode
+
+.. _debug-mode:
+
+Debug Mode
+~~~~~~~~~~
+
+The second argument to the ``Kernel`` constructor specifies if the application
+should run in "debug mode". Regardless of the
+:ref:`configuration environment `, a Symfony
+application can be run with debug mode set to ``true`` or ``false``.
+
+This affects many things in the application, such as displaying stacktraces on
+error pages or if cache files are dynamically rebuilt on each request. Though
+not a requirement, debug mode is generally set to ``true`` for the ``dev`` and
+``test`` environments and ``false`` for the ``prod`` environment.
+
+Similar to :ref:`configuring the environment `
+you can also enable/disable the debug mode using :ref:`the .env file `:
+
+.. code-block:: bash
+
+ # .env
+ # set it to 1 to enable the debug mode
+ APP_DEBUG=0
+
+This value can be overridden for commands by passing the ``APP_DEBUG`` value
+before running them:
+
+.. code-block:: terminal
+
+ # Use the debug mode defined in the .env file
+ $ php bin/console command_name
+
+ # Ignore the .env file and enable the debug mode for this command
+ $ APP_DEBUG=1 php bin/console command_name
+
+Internally, the value of the debug mode becomes the ``kernel.debug``
+parameter used inside the :doc:`service container `.
+If you look inside the application configuration file, you'll see the
+parameter used, for example, to turn Twig's debug mode on:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/twig.yaml
+ twig:
+ debug: '%kernel.debug%'
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ $container->loadFromExtension('twig', [
+ 'debug' => '%kernel.debug%',
+ // ...
+ ]);
The Environments
----------------
-As just mentioned, the ``AppKernel`` has to implement another method -
-:method:`Symfony\\Component\\HttpKernel\\KernelInterface::registerContainerConfiguration`.
-This method is responsible for loading the application's
-configuration from the right *environment*.
+As mentioned above, the ``Kernel`` has to implement another method -
+:method:`Symfony\\Bundle\\FrameworkBundle\\Kernel\\MicroKernelTrait::configureContainer`.
+This method is responsible for loading the application's configuration from the
+right *environment*.
-Environments have been covered extensively
-:doc:`in the previous article `,
-and you probably remember that the Symfony Standard Edition comes with three
-of them - ``dev``, ``prod`` and ``test``.
+:ref:`Configuration environments ` allow to execute
+the same code using different configuration. Symfony provides three environments
+by default called ``dev``, ``prod`` and ``test``.
More technically, these names are nothing more than strings passed from the
-front controller to the ``AppKernel``'s constructor. This name can then be
-used in the :method:`Symfony\\Component\\HttpKernel\\KernelInterface::registerContainerConfiguration`
-method to decide which configuration files to load.
+front controller to the ``Kernel``'s constructor. This name can then be used in
+the ``configureContainer()`` method to decide which configuration files to load.
+
+Symfony's default ``Kernel`` class implements this method by loading first the
+config files found on ``config/packages/*`` and then, the files found on
+``config/packages/ENVIRONMENT_NAME/``. You are free to implement this method
+differently if you need a more sophisticated way of loading your configuration.
+
+.. index::
+ single: Environments; Cache directory
+
+Environments and the Cache Directory
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Symfony takes advantage of caching in many ways: the application configuration,
+routing configuration, Twig templates and more are cached to PHP objects
+stored in files on the filesystem.
+
+By default, these cached files are largely stored in the ``var/cache/`` directory.
+However, each environment caches its own set of files:
+
+.. code-block:: text
+
+ your-project/
+ ├─ var/
+ │ ├─ cache/
+ │ │ ├─ dev/ # cache directory for the *dev* environment
+ │ │ └─ prod/ # cache directory for the *prod* environment
+ │ ├─ ...
+
+Sometimes, when debugging, it may be helpful to inspect a cached file to
+understand how something is working. When doing so, remember to look in
+the directory of the environment you're using (most commonly ``dev/`` while
+developing and debugging). While it can vary, the ``var/cache/dev/`` directory
+includes the following:
+
+``appDevDebugProjectContainer.php``
+ The cached "service container" that represents the cached application
+ configuration.
+
+``appDevUrlGenerator.php``
+ The PHP class generated from the routing configuration and used when
+ generating URLs.
+
+``appDevUrlMatcher.php``
+ The PHP class used for route matching - look here to see the compiled regular
+ expression logic used to match incoming URLs to different routes.
+
+``twig/``
+ This directory contains all the cached Twig templates.
+
+.. note::
-The Symfony Standard Edition's `AppKernel`_ class implements this method by
-loading the ``app/config/config_*environment*.yml`` file. You are free to
-implement this method differently if you need a more sophisticated way of
-loading your configuration.
+ You can change the cache directory location and name. For more information
+ read the article :doc:`/configuration/override_dir_structure`.
+.. _`front controller`: https://en.wikipedia.org/wiki/Front_Controller_pattern
+.. _`decorate`: https://en.wikipedia.org/wiki/Decorator_pattern
.. _Debug component: https://github.com/symfony/debug
-.. _front controller: https://en.wikipedia.org/wiki/Front_Controller_pattern
-.. _Symfony Standard Edition: https://github.com/symfony/symfony-standard
-.. _app.php: https://github.com/symfony/symfony-standard/blob/master/web/app.php
-.. _app_dev.php: https://github.com/symfony/symfony-standard/blob/master/web/app_dev.php
-.. _bin/console: https://github.com/symfony/symfony-standard/blob/master/bin/console
-.. _AppKernel: https://github.com/symfony/symfony-standard/blob/master/app/AppKernel.php
-.. _decorate: https://en.wikipedia.org/wiki/Decorator_pattern
-.. _RewriteRule shipped with the Symfony Standard Edition: https://github.com/symfony/symfony-standard/blob/master/web/.htaccess
-.. _template methods: https://en.wikipedia.org/wiki/Template_method_pattern
diff --git a/configuration/micro_kernel_trait.rst b/configuration/micro_kernel_trait.rst
index 3dabb98a436..d43d0bfe591 100644
--- a/configuration/micro_kernel_trait.rst
+++ b/configuration/micro_kernel_trait.rst
@@ -1,40 +1,38 @@
Building your own Framework with the MicroKernelTrait
=====================================================
-A :ref:`traditional Symfony app ` contains a sensible
-directory structure, various configuration files and an ``AppKernel`` with several
-bundles already-registered. This is a fully-featured app that's ready to go.
+The default ``Kernel`` class included in Symfony applications uses a
+:class:`Symfony\\Bundle\\FrameworkBundle\\Kernel\\MicroKernelTrait` to configure
+the bundles, the routes and the service container in the same class.
-But did you know, you can create a fully-functional Symfony application in as little
-as one file? This is possible thanks to the new
-:class:`Symfony\\Bundle\\FrameworkBundle\\Kernel\\MicroKernelTrait`. This allows
-you to start with a tiny application, and then add features and structure as you
-need to.
+This micro-kernel approach is flexible, allowing you to control your application
+structure and features.
A Single-File Symfony Application
---------------------------------
-Start with a completely empty directory. Get ``symfony/symfony`` as a dependency
+Start with a completely empty directory and install these Symfony components
via Composer:
.. code-block:: terminal
- $ composer require symfony/symfony
+ $ composer require symfony/config symfony/http-kernel \
+ symfony/http-foundation symfony/routing \
+ symfony/dependency-injection symfony/framework-bundle
-Next, create an ``index.php`` file that creates a kernel class and executes it::
+Next, create an ``index.php`` file that defines the kernel class and executes it::
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
- use Symfony\Component\HttpKernel\Kernel;
+ use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use Symfony\Component\Routing\RouteCollectionBuilder;
- // require Composer's autoloader
require __DIR__.'/vendor/autoload.php';
- class AppKernel extends Kernel
+ class Kernel extends BaseKernel
{
use MicroKernelTrait;
@@ -47,7 +45,7 @@ Next, create an ``index.php`` file that creates a kernel class and executes it::
protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader)
{
- // PHP equivalent of config.yml
+ // PHP equivalent of config/packages/framework.yaml
$c->loadFromExtension('framework', [
'secret' => 'S0ME_SECRET'
]);
@@ -57,18 +55,18 @@ Next, create an ``index.php`` file that creates a kernel class and executes it::
{
// kernel is a service that points to this class
// optional 3rd argument is the route name
- $routes->add('/random/{limit}', 'kernel:randomAction');
+ $routes->add('/random/{limit}', 'kernel::randomNumber');
}
- public function randomAction($limit)
+ public function randomNumber($limit)
{
return new JsonResponse([
- 'number' => rand(0, $limit)
+ 'number' => random_int(0, $limit),
]);
}
}
- $kernel = new AppKernel('dev', true);
+ $kernel = new Kernel('dev', true);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
@@ -97,8 +95,8 @@ that define your bundles, your services and your routes:
**configureContainer(ContainerBuilder $c, LoaderInterface $loader)**
This method builds and configures the container. In practice, you will use
``loadFromExtension`` to configure different bundles (this is the equivalent
- of what you see in a normal ``config.yml`` file). You can also register services
- directly in PHP or load external configuration files (shown below).
+ of what you see in a normal ``config/packages/*`` file). You can also register
+ services directly in PHP or load external configuration files (shown below).
**configureRoutes(RouteCollectionBuilder $routes)**
Your job in this method is to add routes to the application. The
@@ -122,39 +120,35 @@ your ``composer.json`` file to load from there:
},
"autoload": {
"psr-4": {
- "": "src/"
+ "App\\": "src/"
}
}
}
Then, run ``composer dump-autoload`` to dump your new autoload config.
-
+
Now, suppose you want to use Twig and load routes via annotations. Instead of
-putting *everything* in ``index.php``, create a new ``app/AppKernel.php`` to
+putting *everything* in ``index.php``, create a new ``src/Kernel.php`` to
hold the kernel. Now it looks like this::
- // app/AppKernel.php
- use Doctrine\Common\Annotations\AnnotationRegistry;
+ // src/Kernel.php
+ namespace App;
+
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
- use Symfony\Component\HttpKernel\Kernel;
+ use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use Symfony\Component\Routing\RouteCollectionBuilder;
- // require Composer's autoloader
- $loader = require __DIR__.'/../vendor/autoload.php';
- // auto-load annotations
- AnnotationRegistry::registerLoader([$loader, 'loadClass']);
-
- class AppKernel extends Kernel
+ class Kernel extends BaseKernel
{
use MicroKernelTrait;
public function registerBundles()
{
$bundles = [
- new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
- new Symfony\Bundle\TwigBundle\TwigBundle(),
+ new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
+ new \Symfony\Bundle\TwigBundle\TwigBundle(),
];
if ($this->getEnvironment() == 'dev') {
@@ -166,7 +160,7 @@ hold the kernel. Now it looks like this::
protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader)
{
- $loader->load(__DIR__.'/config/config.yml');
+ $loader->load(__DIR__.'/../config/framework.yaml');
// configure WebProfilerBundle only if the bundle is enabled
if (isset($this->bundles['WebProfilerBundle'])) {
@@ -198,33 +192,31 @@ hold the kernel. Now it looks like this::
// optional, to use the standard Symfony logs directory
public function getLogDir()
{
- return __DIR__.'/../var/logs';
+ return __DIR__.'/../var/log';
}
}
-.. versionadded:: 3.4
+Before continuing, run this command to add support for the new dependencies:
+
+.. code-block:: terminal
- Support for annotation routing without an external bundle was introduced
- in Symfony 3.4. Prior to version 3.4, you needed to install the
- SensioFrameworkExtraBundle.
+ $ composer require symfony/yaml symfony/twig-bundle symfony/web-profiler-bundle doctrine/annotations
-Unlike the previous kernel, this loads an external ``app/config/config.yml`` file,
+Unlike the previous kernel, this loads an external ``config/framework.yaml`` file,
because the configuration started to get bigger:
.. configuration-block::
.. code-block:: yaml
- # app/config/config.yml
+ # config/framework.yaml
framework:
secret: S0ME_SECRET
- templating:
- engines: ['twig']
profiler: { only_exceptions: false }
.. code-block:: xml
-
+
-
- twig
-
.. code-block:: php
- // app/config/config.php
+ // config/framework.php
$container->loadFromExtension('framework', [
'secret' => 'S0ME_SECRET',
- 'templating' => [
- 'engines' => ['twig'],
- ],
'profiler' => [
'only_exceptions' => false,
],
@@ -259,31 +245,30 @@ has one file in it::
// src/Controller/MicroController.php
namespace App\Controller;
- use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
- class MicroController extends Controller
+ class MicroController extends AbstractController
{
/**
* @Route("/random/{limit}")
*/
- public function randomAction($limit)
+ public function randomNumber($limit)
{
- $number = rand(0, $limit);
+ $number = random_int(0, $limit);
return $this->render('micro/random.html.twig', [
- 'number' => $number
+ 'number' => $number,
]);
}
}
-Template files should live in the ``Resources/views`` directory of whatever directory
-your *kernel* lives in. Since ``AppKernel`` lives in ``app/``, this template lives
-at ``app/Resources/views/micro/random.html.twig``:
+Template files should live in the ``templates/`` directory at the root of your project.
+This template lives at ``templates/micro/random.html.twig``:
.. code-block:: html+twig
-
+
@@ -295,14 +280,18 @@ at ``app/Resources/views/micro/random.html.twig``:
Finally, you need a front controller to boot and run the application. Create a
-``web/index.php``::
+``public/index.php``::
- // web/index.php
+ // public/index.php
+ use App\Kernel;
+ use Doctrine\Common\Annotations\AnnotationRegistry;
use Symfony\Component\HttpFoundation\Request;
- require __DIR__.'/../app/AppKernel.php';
+ $loader = require __DIR__.'/../vendor/autoload.php';
+ // auto-load annotations
+ AnnotationRegistry::registerLoader([$loader, 'loadClass']);
- $kernel = new AppKernel('dev', true);
+ $kernel = new Kernel('dev', true);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
@@ -315,24 +304,22 @@ this:
.. code-block:: text
your-project/
- ├─ app/
- | ├─ AppKernel.php
- │ ├─ config/
- │ └─ Resources
- | └─ views
- | └─ micro
- | └─ random.html.twig
+ ├─ config/
+ │ └─ framework.yaml
+ ├─ public/
+ | └─ index.php
├─ src/
- │ └─ App
- | └─ Controller
- | └─ MicroController.php
+ | ├─ Controller
+ | | └─ MicroController.php
+ | └─ Kernel.php
+ ├─ templates/
+ | └─ micro/
+ | └─ random.html.twig
├─ var/
| ├─ cache/
- │ └─ logs/
+ │ └─ log/
├─ vendor/
│ └─ ...
- ├─ web/
- | └─ index.php
├─ composer.json
└─ composer.lock
@@ -341,13 +328,9 @@ As before you can use the :doc:`Symfony Local Web Server
.. code-block:: terminal
- cd web/
+ cd public/
$ symfony server:start
Then visit the page in your browser:
http://localhost:8000/random/10
-
-Hey, that looks a lot like a *traditional* Symfony application! You're right: the
-``MicroKernelTrait`` *is* still Symfony: but you can control your structure and
-features with less boilerplate configuration and code.
diff --git a/configuration/multiple_kernels.rst b/configuration/multiple_kernels.rst
index 5576efee50f..a43aa491920 100644
--- a/configuration/multiple_kernels.rst
+++ b/configuration/multiple_kernels.rst
@@ -4,16 +4,21 @@
How To Create Symfony Applications with Multiple Kernels
========================================================
+.. caution::
+
+ Creating applications with multiple kernels is no longer recommended by
+ Symfony. Consider creating multiple small applications instead.
+
In most Symfony applications, incoming requests are processed by the
-``web/app.php`` front controller, which instantiates the ``app/AppKernel.php``
+``public/index.php`` front controller, which instantiates the ``src/Kernel.php``
class to create the application kernel that loads the bundles and handles the
request to generate the response.
-This single kernel approach is a convenient default provided by the Symfony
-Standard edition, but Symfony applications can define any number of kernels.
-Whereas :doc:`environments ` execute the same
-application with different configurations, kernels can execute different parts
-of the same application.
+This single kernel approach is a convenient default, but Symfony applications
+can define any number of kernels. Whereas
+:ref:`environments ` execute the same application
+with different configurations, kernels can execute different parts of the same
+application.
These are some of the common use cases for creating multiple kernels:
@@ -45,45 +50,46 @@ Step 1) Create a new Front Controller
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Instead of creating the new front controller from scratch, it's easier to
-duplicate the existing ones. For example, create ``web/api_dev.php`` from
-``web/app_dev.php`` and ``web/api.php`` from ``web/app.php``.
-
-Then, update the code of the new front controllers to instantiate the new kernel
-class instead of the usual ``AppKernel`` class::
+duplicate the existing one. For example, create ``public/api.php`` from
+``public/index.php``.
- // web/api.php
- // ...
- $kernel = new ApiKernel('prod', false);
- // ...
+Then, update the code of the new front controller to instantiate the new kernel
+class instead of the usual ``Kernel`` class::
- // web/api_dev.php
+ // public/api.php
// ...
- $kernel = new ApiKernel('dev', true);
+ $kernel = new ApiKernel(
+ $_SERVER['APP_ENV'] ?? 'dev',
+ $_SERVER['APP_DEBUG'] ?? ('prod' !== ($_SERVER['APP_ENV'] ?? 'dev'))
+ );
// ...
.. tip::
- Another approach is to keep the existing front controller (e.g. ``app.php`` and
- ``app_dev.php``), but add an ``if`` statement to load the different kernel based
- on the URL (e.g. if the URL starts with ``/api``, use the ``ApiKernel``).
+ Another approach is to keep the existing ``index.php`` front controller, but
+ add an ``if`` statement to load the different kernel based on the URL (e.g.
+ if the URL starts with ``/api``, use the ``ApiKernel``).
Step 2) Create the new Kernel Class
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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 ``app/AppKernel.php``
+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 AppKernel. Be
-sure to also change the location of the cache, logs and configuration files so
-they don't collide with the files from ``AppKernel``::
+In this example, the ``ApiKernel`` will load less 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``::
- // app/ApiKernel.php
+ // src/ApiKernel.php
use Symfony\Component\Config\Loader\LoaderInterface;
+ use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Kernel;
class ApiKernel extends Kernel
{
+ // ...
+
public function registerBundles()
{
// load only the bundles strictly needed for the API...
@@ -96,61 +102,44 @@ they don't collide with the files from ``AppKernel``::
public function getLogDir()
{
- return dirname(__DIR__).'/var/logs/api';
+ return dirname(__DIR__).'/var/log/api';
}
- public function registerContainerConfiguration(LoaderInterface $loader)
+ public function configureContainer(ContainerBuilder $container, LoaderInterface $loader)
{
- $loader->load($this->getProjectDir().'/app/config/api/config_'.$this->getEnvironment().'.yml');
- }
- }
-
-In order for the autoloader to find your new ``ApiKernel``, make sure you add it
-to your ``composer.json`` autoload section:
-
-.. code-block:: json
-
- {
- "...": "..."
-
- "autoload": {
- "psr-4": { "": "src/" },
- "classmap": [ "app/AppKernel.php", "app/AppCache.php", "app/ApiKernel.php" ]
+ // 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');
+ }
}
}
-Then, run ``composer dump-autoload`` to dump your new autoload config.
-
Step 3) Define the Kernel Configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Finally, define the configuration files that the new ``ApiKernel`` will load.
-According to the above code, this config will live in the ``app/config/api/``
-directory.
-
-The new configuration can be created from scratch when you load just a few
-bundles, because it will be very simple. Otherwise, duplicate the existing
-config files or better, import them and override the needed options:
-
-.. code-block:: yaml
-
- # app/config/api/config_dev.yml
- imports:
- - { resource: ../config_dev.yml }
+According to the above code, this config will live in one or multiple files
+stored in ``config/api/`` and ``config/api/ENVIRONMENT_NAME/`` directories.
- # override option values ...
+The new configuration files can be created from scratch when you load just a few
+bundles, because it will be small. Otherwise, duplicate the existing
+config files in ``config/packages/`` or better, import them and override the
+needed options.
Executing Commands with a Different Kernel
------------------------------------------
The ``bin/console`` script used to run Symfony commands always uses the default
-``AppKernel`` class to build the application and load the commands. If you need
+``Kernel`` class to build the application and load the commands. If you need
to execute console commands using the new kernel, duplicate the ``bin/console``
script and rename it (e.g. ``bin/api``).
-Then, replace the ``AppKernel`` instance by your own kernel instance (e.g.
-``ApiKernel``). Now you can execute commands using the new kernel (e.g.
-``php bin/api cache:clear``).
+Then, replace the ``Kernel`` instance by your own kernel instance
+(e.g. ``ApiKernel``) and now you can execute commands using the new kernel
+(e.g. ``php bin/api cache:clear``) Now you can use execute commands using the
+new kernel.
.. note::
@@ -162,19 +151,19 @@ Rendering Templates Defined in a Different Kernel
-------------------------------------------------
If you follow the Symfony Best Practices, the templates of the default kernel
-will be stored in ``app/Resources/views/``. Trying to render those templates in
-a different kernel will result in a *There are no registered paths for
-namespace "__main__"* error.
+will be stored in ``templates/``. Trying to render those templates in a
+different kernel will result in a *There are no registered paths for namespace
+"__main__"* error.
In order to solve this issue, add the following configuration to your kernel:
.. code-block:: yaml
- # api/config/config.yml
+ # config/api/twig.yaml
twig:
paths:
- # allows to use app/Resources/views/ templates in the ApiKernel
- "%kernel.project_dir%/app/Resources/views": ~
+ # allows to use api/templates/ dir in the ApiKernel
+ "%kernel.project_dir%/api/templates": ~
Running Tests Using a Different Kernel
--------------------------------------
@@ -197,7 +186,7 @@ return the fully qualified class name of the kernel to use::
{
protected static function getKernelClass()
{
- return 'ApiKernel';
+ return 'App\ApiKernel';
}
// this is needed because the KernelTestCase class keeps a reference to
@@ -218,23 +207,19 @@ Adding more Kernels to the Application
If your application is very complex and you create several kernels, it's better
to store them in their own directories instead of messing with lots of files in
-the default ``app/`` directory:
+the default ``src/`` directory:
.. code-block:: text
project/
- ├─ app/
+ ├─ src/
│ ├─ ...
- │ ├─ config/
- │ └─ AppKernel.php
+ │ └─ Kernel.php
├─ api/
│ ├─ ...
- │ ├─ config/
│ └─ ApiKernel.php
├─ ...
- └─ web/
+ └─ public/
├─ ...
- ├─ app.php
- ├─ app_dev.php
├─ api.php
- └─ api_dev.php
+ └─ index.php
diff --git a/configuration/override_dir_structure.rst b/configuration/override_dir_structure.rst
index 16c9a93ef98..fbfa119cc14 100644
--- a/configuration/override_dir_structure.rst
+++ b/configuration/override_dir_structure.rst
@@ -4,46 +4,50 @@
How to Override Symfony's default Directory Structure
=====================================================
-Symfony automatically ships with a default directory structure. You can
-override this directory structure to create your own. The default
-directory structure is:
+Symfony applications have the following default directory structure, but you can
+override it to create your own structure:
.. code-block:: text
your-project/
- ├─ app/
- │ ├─ config/
- │ ├─ Resources/
- │ │ └─ views/
- │ └─ ...
+ ├─ assets/
├─ bin/
- │ └─ ...
+ │ └─ console
+ ├─ config/
+ ├─ public/
+ │ └─ index.php
├─ src/
│ └─ ...
+ ├─ templates/
├─ tests/
- │ └─ ...
+ ├─ translations/
├─ var/
│ ├─ cache/
- │ ├─ logs/
- │ └─ ...
- ├─ vendor/
+ │ ├─ log/
│ └─ ...
- └─ web/
- ├─ app.php
- └─ ...
+ └─ vendor/
+
+.. _override-config-dir:
+
+Override the Configuration Directory
+------------------------------------
+
+The configuration directory is the only one which cannot be overridden in a
+Symfony application. Its location is hardcoded as the ``config/`` directory
+at your project root directory.
.. _override-cache-dir:
-Override the ``cache`` Directory
---------------------------------
+Override the Cache Directory
+----------------------------
-You can change the default cache directory by overriding the ``getCacheDir()`` method
-in the ``AppKernel`` class of your application::
+You can change the default cache directory by overriding the ``getCacheDir()``
+method in the ``Kernel`` class of your application::
- // app/AppKernel.php
+ // src/Kernel.php
// ...
- class AppKernel extends Kernel
+ class Kernel extends BaseKernel
{
// ...
@@ -55,60 +59,60 @@ in the ``AppKernel`` class of your application::
In this code, ``$this->environment`` is the current environment (i.e. ``dev``).
In this case you have changed the location of the cache directory to
-``var/{environment}/cache``.
+``var/{environment}/cache/``.
.. caution::
- You should keep the ``cache`` directory different for each environment,
+ You should keep the cache directory different for each environment,
otherwise some unexpected behavior may happen. Each environment generates
its own cached configuration files, and so each needs its own directory to
store those cache files.
.. _override-logs-dir:
-Override the ``logs`` Directory
--------------------------------
+Override the Log Directory
+--------------------------
-Overriding the ``logs`` directory is the same as overriding the ``cache``
+Overriding the ``var/log/`` directory is the same as overriding the ``var/cache/``
directory. The only difference is that you need to override the ``getLogDir()``
method::
- // app/AppKernel.php
+ // src/Kernel.php
// ...
- class AppKernel extends Kernel
+ class Kernel extends Kernel
{
// ...
public function getLogDir()
{
- return dirname(__DIR__).'/var/'.$this->environment.'/logs';
+ return dirname(__DIR__).'/var/'.$this->environment.'/log';
}
}
-Here you have changed the location of the directory to ``var/{environment}/logs``.
+Here you have changed the location of the directory to ``var/{environment}/log/``.
.. _override-templates-dir:
Override the Templates Directory
--------------------------------
-If your templates are not stored in the default ``app/Resources/views/``
-directory, use the :ref:`twig.paths ` configuration option to
-define your own templates directory (or directories):
+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):
.. configuration-block::
.. code-block:: yaml
- # app/config/config.yml
+ # config/packages/twig.yaml
twig:
# ...
- paths: ["%kernel.project_dir%/templates"]
+ paths: ["%kernel.project_dir%/resources/views"]
.. code-block:: xml
-
+
- %kernel.project_dir%/templates
+ %kernel.project_dir%/resources/views
.. code-block:: php
- // app/config/config.php
+ // config/packages/twig.php
$container->loadFromExtension('twig', [
'paths' => [
- '%kernel.project_dir%/templates',
+ '%kernel.project_dir%/resources/views',
],
]);
-.. _override-web-dir:
-
-Override the ``web`` Directory
-------------------------------
-
-If you need to rename or move your ``web`` directory, the only thing you
-need to guarantee is that the path to the ``var`` directory is still correct
-in your ``app.php`` and ``app_dev.php`` front controllers. If you renamed
-the directory, you're fine. But if you moved it in some way, you may need
-to modify these paths inside those files::
-
- require_once __DIR__.'/../path/to/app/autoload.php';
+Override the Translations Directory
+-----------------------------------
-You also need to change the ``extra.symfony-web-dir`` option in the
-``composer.json`` file:
+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):
-.. code-block:: json
-
- {
- "...": "...",
- "extra": {
- "...": "...",
- "symfony-web-dir": "my_new_web_dir"
- }
- }
-
-.. tip::
-
- Some shared hosts have a ``public_html`` web directory root. Renaming
- your web directory from ``web`` to ``public_html`` is one way to make
- your Symfony project work on your shared host. Another way is to deploy
- your application to a directory outside of your web root, delete your
- ``public_html`` directory, and then replace it with a symbolic link to
- the ``web`` in your project.
+.. configuration-block::
-.. note::
+ .. code-block:: yaml
- If you use the AsseticBundle, you need to configure the ``read_from`` option
- to point to the correct ``web`` directory:
+ # config/packages/translation.yaml
+ framework:
+ translator:
+ # ...
+ paths: ["%kernel.project_dir%/i18n"]
- .. configuration-block::
+ .. code-block:: xml
- .. code-block:: yaml
+
+
+
- # app/config/config.yml
+
+
+ %kernel.project_dir%/i18n
+
+
- # ...
- assetic:
- # ...
- read_from: '%kernel.project_dir%/../public_html'
+
- .. code-block:: xml
+ .. code-block:: php
-
-
-
+ // config/packages/translation.php
+ $container->loadFromExtension('framework', [
+ 'translator' => [
+ 'paths' => [
+ '%kernel.project_dir%/i18n',
+ ],
+ ],
+ ]);
-
-
+.. _override-web-dir:
+.. _override-the-web-directory:
-
+Override the Public Directory
+-----------------------------
- .. code-block:: php
+If you need to rename or move your ``public/`` directory, the only thing you
+need to guarantee is that the path to the ``var/`` directory is still correct in
+your ``index.php`` front controller. If you renamed the directory, you're fine.
+But if you moved it in some way, you may need to modify these paths inside those
+files::
- // app/config/config.php
+ require_once __DIR__.'/../path/to/vendor/autoload.php';
- // ...
- $container->loadFromExtension('assetic', [
- // ...
- 'read_from' => '%kernel.project_dir%/../public_html',
- ]);
+You also need to change the ``extra.public-dir`` option in the ``composer.json``
+file:
- Now you just need to clear the cache and dump the assets again and your
- application should work:
+.. code-block:: json
- .. code-block:: terminal
+ {
+ "...": "...",
+ "extra": {
+ "...": "...",
+ "public-dir": "my_new_public_dir"
+ }
+ }
- $ php bin/console cache:clear --env=prod
- $ php bin/console assetic:dump --env=prod --no-debug
+.. tip::
-Override the ``vendor`` Directory
----------------------------------
+ Some shared hosts have a ``public_html/`` web directory root. Renaming
+ your web directory from ``public/`` to ``public_html/`` is one way to make
+ your Symfony project work on your shared host. Another way is to deploy
+ your application to a directory outside of your web root, delete your
+ ``public_html/`` directory, and then replace it with a symbolic link to
+ the ``public/`` dir in your project.
-To override the ``vendor`` directory, you need to introduce changes in the
-``app/autoload.php`` and ``composer.json`` files.
+Override the Vendor Directory
+-----------------------------
-The change in the ``composer.json`` will look like this:
+To override the ``vendor/`` directory, you need to define the ``vendor-dir``
+option in your ``composer.json`` file like this:
.. code-block:: json
@@ -236,15 +236,8 @@ The change in the ``composer.json`` will look like this:
},
}
-Then, update the path to the ``autoload.php`` file in ``app/autoload.php``::
-
- // app/autoload.php
-
- // ...
- $loader = require '/some/dir/vendor/autoload.php';
-
.. tip::
- This modification can be of interest if you are working in a virtual environment
- and cannot use NFS - for example, if you're running a Symfony application using
- Vagrant/VirtualBox in a guest operating system.
+ This modification can be of interest if you are working in a virtual
+ environment and cannot use NFS - for example, if you're running a Symfony
+ application using Vagrant/VirtualBox in a guest operating system.
diff --git a/configuration/secrets.rst b/configuration/secrets.rst
new file mode 100644
index 00000000000..86329773b90
--- /dev/null
+++ b/configuration/secrets.rst
@@ -0,0 +1,316 @@
+.. index::
+ single: Secrets
+
+How to Keep Sensitive Information Secret
+========================================
+
+:ref:`Environment variables ` are the best way to store configuration
+that depends on where the application is run - for example, some API key that
+might be set to one value while developing locally and another value on production.
+
+When these values are *sensitive* and need to be kept private, you can safely
+store them by using Symfony's secrets management system - sometimes called a
+"vault".
+
+.. note::
+
+ The Secrets system requires the sodium PHP extension that is bundled
+ with PHP 7.2. If you're using an earlier PHP version, you can
+ install the `libsodium`_ PHP extension or use the
+ `paragonie/sodium_compat`_ package.
+
+.. _secrets-generate-keys:
+
+Generate Cryptographic Keys
+---------------------------
+
+In order to encrypt and decrypt **secrets**, Symfony needs **cryptographic keys**.
+A pair of keys can be generated by running:
+
+.. code-block:: terminal
+
+ $ php bin/console secrets:generate-keys
+
+This will generate a pair of asymmetric **cryptographic keys**. Each
+:ref:`environment ` has its own set of keys. Assuming
+you're coding locally in the ``dev`` environment, this will create:
+
+``config/secrets/dev/dev.encrypt.public.php``
+ Used to encrypt/add secrets to the vault. Can be safely committed.
+
+``config/secrets/dev/dev.decrypt.private.php``
+ Used to decrypt/read secrets from the vault. The ``dev`` decryption key can
+ be committed (assuming no highly-sensitive secrets are stored in the dev vault)
+ but the ``prod`` decryption key should *never* be committed.
+
+You can generate a pair of cryptographic keys for the ``prod`` environment by
+running:
+
+.. code-block:: terminal
+
+ $ php bin/console secrets:generate-keys --env=prod
+
+This will generate ``config/secrets/prod/prod.encrypt.public.php`` and
+``config/secrets/prod/prod.decrypt.private.php``.
+
+.. caution::
+
+ The ``prod.decrypt.private.php`` file is highly sensitive. Your team of developers
+ and even Continuous Integration services don't need that key. If the
+ **decryption key** has been exposed (ex-employee leaving for instance), you
+ should consider generating a new one by running:
+ ``secrets:generate-keys --rotate``.
+
+.. _secrets-set:
+
+Create or Update Secrets
+------------------------
+
+Suppose you want to store your database password a secret. By using the
+``secrets:set`` command, you should add this secret to both the ``dev`` *and*
+``prod`` vaults:
+
+.. code-block:: terminal
+
+ # the input is hidden as you type for security
+
+ # set your a default development value (can be overridden locally)
+ $ php bin/console secrets:set DATABASE_PASSWORD
+
+ # set your production value
+ $ php bin/console secrets:set DATABASE_PASSWORD --env=prod
+
+This will create a new file for the secret in ``config/secrets/dev`` and another
+in ``config/secrets/prod``. You can also set the secret in a few other ways:
+
+.. code-block:: terminal
+
+ # provide a file where to read the secret from
+ $ php bin/console secrets:set DATABASE_PASSWORD ~/Download/password.json
+
+ # or contents passed to STDIN
+ $ echo -n "$DB_PASS" | php bin/console secrets:set DATABASE_PASSWORD -
+
+ # or let Symfony generate a random value for you
+ $ php bin/console secrets:set REMEMBER_ME --random
+
+Referencing Secrets in Configuration Files
+------------------------------------------
+
+Secret values can be referenced in the same way as
+: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**.
+
+If you stored a ``DATABASE_PASSWORD`` secret, you can reference by:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/doctrine.yaml
+ doctrine:
+ dbal:
+ password: '%env(DATABASE_PASSWORD)%'
+ # ...
+ # ...
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/doctrine.php
+ $container->loadFromExtension('doctrine', [
+ 'dbal' => [
+ 'password' => '%env(secret:DATABASE_PASSWORD)%',
+ ]
+ ]);
+
+The actual value will be resolved at runtime: container compilation and cache
+warmup don't need the **decryption key**.
+
+List Existing Secrets
+---------------------
+
+Everybody is allowed to list the secrets names with the command
+``secrets:list``. If you have the **decryption key** you can also reveal the
+secrets' values by passing the ``--reveal`` option:
+
+.. code-block:: terminal
+
+ $ php bin/console secrets:list --reveal
+
+ ------------------- ------------ -------------
+ Name Value Local Value
+ ------------------- ------------ -------------
+ DATABASE_PASSWORD "my secret"
+ ------------------- ------------ -------------
+
+Remove Secrets
+--------------
+
+Symfony provides a convenient command to remove a Secret:
+
+.. code-block:: terminal
+
+ $ php bin/console secrets:remove DATABASE_PASSWORD
+
+Local secrets: Overriding Secrets Locally
+-----------------------------------------
+
+The ``dev`` environment secrets should contain nice default values for development.
+But sometimes a developer *still* needs to override a secret value locally when
+developing.
+
+Most of the ``secrets`` commands - including ``secrets:set`` - have a ``--local``
+option that stores the "secret" in the ``.env.{env}.local`` file as a standard
+environment variable. To override the ``DATABASE_PASSWORD`` secret locally, run:
+
+.. code-block:: terminal
+
+ $ php bin/console secrets:set DATABASE_PASSWORD --local
+
+If you entered ``root``, you will now see this in your ``.env.dev.local`` file:
+
+.. code-block:: bash
+
+ DATABASE_PASSWORD=root
+
+This will *override* the ``DATABASE_PASSWORD`` secret because environment variables
+always take precedence over secrets.
+
+Listing the secrets will now also display the local variable:
+
+.. code-block:: terminal
+
+ $ php bin/console secrets:list --reveal
+ ------------------- ------------- -------------
+ Name Value Local Value
+ ------------------- ------------- -------------
+ 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.
+
+Secrets in the test Environment
+-------------------------------
+
+If you add a secret in the ``dev`` and ``prod`` environments, it will be missing
+from the ``test`` environment. You *could* create a "vault" for the ``test``
+environment and define the secrets there. But an easier way is to set the test
+values via the ``.env.test`` file:
+
+.. code-block:: bash
+
+ # .env.test
+ DATABASE_PASSWORD="testing"
+
+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:
+
+The first option is to copy the **decryption key** -
+``/config/secrets/prod/prod.decrypt.private.php`` to your server(s).
+
+2) Using an Environment Variable
+
+The second way is to set the ``SYMFONY_DECRYPTION_SECRET`` environment variable
+to the base64 encoded value of the **decryption key**. A fancy way to fetch the
+value of the key is:
+
+.. code-block:: terminal
+
+ $ 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:
+
+.. code-block:: terminal
+
+ $ php bin/console secrets:decrypt-to-local --force --env=prod
+
+This will put all the decrypted secrets into ``.env.php.local``. After doing this,
+the decryption key does *not* need to remain on the server.
+
+Rotating Secrets
+----------------
+
+The ``secrets:generate-keys`` command provides a ``--rotate`` option to
+regenerate the **cryptographic keys**. Symfony will decrypt existing secrets with
+the old key, generate new **cryptographic keys** and re-encrypt secrets with the
+new key. In order to decrypt previous secrets, the developer must have the
+**decryption key**.
+
+Configuration
+-------------
+
+The secrets system is enabled by default and some of its behavior can be configured:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/framework.yaml
+ framework:
+ secrets:
+ #vault_directory: '%kernel.project_dir%/config/secrets/%kernel.environment%'
+ #local_dotenv_file: '%kernel.project_dir%/.env.%kernel.environment%.local'
+ #decryption_env_var: 'base64:default::SYMFONY_DECRYPTION_SECRET'
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/framework.php
+ $container->loadFromExtension('framework', [
+ 'secrets' => [
+ // 'vault_directory' => '%kernel.project_dir%/config/secrets/%kernel.environment%',
+ // 'local_dotenv_file' => '%kernel.project_dir%/.env.%kernel.environment%.local',
+ // 'decryption_env_var' => 'base64:default::SYMFONY_DECRYPTION_SECRET',
+ ],
+ ]);
+
+
+.. _`libsodium`: https://pecl.php.net/package/libsodium
+.. _`paragonie/sodium_compat`: https://github.com/paragonie/sodium_compat
diff --git a/configuration/using_parameters_in_dic.rst b/configuration/using_parameters_in_dic.rst
index 13a179d1133..758bb68d39a 100644
--- a/configuration/using_parameters_in_dic.rst
+++ b/configuration/using_parameters_in_dic.rst
@@ -37,7 +37,7 @@ Now, examine the results to see this closely:
my_bundle:
logging: '%kernel.debug%'
- # true/false (depends on 2nd parameter of AppKernel),
+ # true/false (depends on 2nd argument of the Kernel class),
# as expected, because %kernel.debug% inside configuration
# gets evaluated before being passed to the extension
@@ -57,7 +57,7 @@ Now, examine the results to see this closely:
-
@@ -78,7 +78,7 @@ Now, examine the results to see this closely:
$container->loadFromExtension('my_bundle', [
'logging' => "%kernel.debug%",
- // true/false (depends on 2nd parameter of AppKernel),
+ // true/false (depends on 2nd parameter of Kernel),
// as expected, because %kernel.debug% inside configuration
// gets evaluated before being passed to the extension
)
@@ -93,7 +93,7 @@ Now, examine the results to see this closely:
In order to support this use case, the ``Configuration`` class has to
be injected with this parameter via the extension as follows::
- namespace AppBundle\DependencyInjection;
+ namespace App\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
@@ -109,10 +109,9 @@ be injected with this parameter via the extension as follows::
public function getConfigTreeBuilder()
{
- $treeBuilder = new TreeBuilder();
- $rootNode = $treeBuilder->root('my_bundle');
+ $treeBuilder = new TreeBuilder('my_bundle');
- $rootNode
+ $treeBuilder->getRootNode()
->children()
// ...
->booleanNode('logging')->defaultValue($this->debug)->end()
@@ -126,7 +125,7 @@ be injected with this parameter via the extension as follows::
And set it in the constructor of ``Configuration`` via the ``Extension`` class::
- namespace AppBundle\DependencyInjection;
+ namespace App\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
@@ -141,16 +140,8 @@ And set it in the constructor of ``Configuration`` via the ``Extension`` class::
}
}
-.. sidebar:: Setting the Default in the Extension
+.. tip::
- There are some instances of ``%kernel.debug%`` usage within a ``Configurator``
- class in TwigBundle and AsseticBundle. However, this is because the default
- parameter value is set by the Extension class. For example in AsseticBundle,
- you can find::
-
- $container->setParameter('assetic.debug', $config['debug']);
-
- The string ``%kernel.debug%`` passed here as an argument handles the
- interpreting job to the container which in turn does the evaluation.
- Both ways accomplish similar goals. AsseticBundle will not use
- ``%kernel.debug%`` but rather the new ``%assetic.debug%`` parameter.
+ There are some instances of ``%kernel.debug%`` usage within a
+ ``Configurator`` class for example in TwigBundle. However, this is because
+ the default parameter value is set by the Extension class.
diff --git a/console.rst b/console.rst
index ad00db815a2..8b7b38b0d46 100644
--- a/console.rst
+++ b/console.rst
@@ -9,17 +9,25 @@ The Symfony framework provides lots of commands through the ``bin/console`` scri
created with the :doc:`Console component `. You can also
use it to create your own commands.
+The Console: APP_ENV & APP_DEBUG
+---------------------------------
+
+Console commands run in the :ref:`environment ` defined in the ``APP_ENV``
+variable of the ``.env`` file, which is ``dev`` by default. It also reads the ``APP_DEBUG``
+value to turn "debug" mode on or off (it defaults to ``1``, which is on).
+
+To run the command in another environment or debug mode, edit the value of ``APP_ENV``
+and ``APP_DEBUG``.
+
Creating a Command
------------------
-Commands are defined in classes which should be created in the ``Command`` namespace
-of your bundle (e.g. ``AppBundle\Command``) and their names should end with the
-``Command`` suffix.
-
-For example, you may want a command to create a user::
+Commands are defined in classes extending
+:class:`Symfony\\Component\\Console\\Command\\Command`. For example, you may
+want a command to create a user::
- // src/AppBundle/Command/CreateUserCommand.php
- namespace AppBundle\Command;
+ // src/Command/CreateUserCommand.php
+ namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@@ -38,14 +46,15 @@ For example, you may want a command to create a user::
protected function execute(InputInterface $input, OutputInterface $output)
{
// ...
+
+ return 0;
}
}
Configuring the Command
-----------------------
-First of all, you must configure the name of the command in the ``configure()``
-method. Then you can optionally define a help message and the
+You can optionally define a description, help message and the
:doc:`input options and arguments `::
// ...
@@ -96,16 +105,10 @@ available in the ``configure()`` method::
Registering the Command
-----------------------
-Symfony commands must be registered before using them. In order to be registered
-automatically, a command must be:
-
-#. Stored in a directory called ``Command/``;
-#. Defined in a class whose name ends with ``Command``;
-#. Defined in a class that extends from
- :class:`Symfony\\Component\\Console\\Command\\Command`.
-
-If you can't meet these conditions for a command, the alternative is to manually
-:doc:`register the command as a service `.
+Symfony commands must be registered as services and :doc:`tagged `
+with the ``console.command`` tag. If you're using the
+:ref:`default services.yaml configuration `,
+this is already done for you, thanks to :ref:`autoconfiguration `.
Executing the Command
---------------------
@@ -116,16 +119,14 @@ After configuring and registering the command, you can execute it in the termina
$ php bin/console app:create-user
-.. caution::
+As you might expect, this command will do nothing as you didn't write any logic
+yet. Add your own logic inside the ``execute()`` method.
- Symfony also looks in the ``Command/`` directory of bundles for commands
- that are not registered as a service. But this auto discovery is deprecated
- since Symfony 3.4 and won't be supported anymore in Symfony 4.0.
+Console Output
+--------------
-As you might expect, this command will do nothing as you didn't write any logic
-yet. Add your own logic inside the ``execute()`` method, which has access to the
-input stream (e.g. options and arguments) and the output stream (to write
-messages to the console)::
+The ``execute()`` method has access to the output stream to write messages to
+the console::
// ...
protected function execute(InputInterface $input, OutputInterface $output)
@@ -137,12 +138,18 @@ messages to the console)::
'',
]);
+ // the value returned by someMethod() can be an iterator (https://secure.php.net/iterator)
+ // that generates and returns the messages with the 'yield' PHP keyword
+ $output->writeln($this->someMethod());
+
// outputs a message followed by a "\n"
$output->writeln('Whoa!');
// outputs a message without adding a "\n" at the end of the line
$output->write('You are about to ');
$output->write('create a user.');
+
+ return 0;
}
Now, try executing the command:
@@ -156,6 +163,56 @@ Now, try executing the command:
Whoa!
You are about to create a user.
+.. _console-output-sections:
+
+Output Sections
+~~~~~~~~~~~~~~~
+
+The regular console output can be divided into multiple independent regions
+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
+:class:`Symfony\\Component\\Console\\Output\\ConsoleSectionOutput`::
+
+ class MyCommand extends Command
+ {
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $section1 = $output->section();
+ $section2 = $output->section();
+ $section1->writeln('Hello');
+ $section2->writeln('World!');
+ // Output displays "Hello\nWorld!\n"
+
+ // overwrite() replaces all the existing section contents with the given content
+ $section1->overwrite('Goodbye');
+ // Output now displays "Goodbye\nWorld!\n"
+
+ // clear() deletes all the section contents...
+ $section2->clear();
+ // Output now displays "Goodbye\n"
+
+ // ...but you can also delete a given number of lines
+ // (this example deletes the last two lines of the section)
+ $section1->clear(2);
+ // Output is now completely empty!
+
+ return 0;
+ }
+ }
+
+.. note::
+
+ A new line is appended automatically when displaying information in a section.
+
+Output sections let you manipulate the Console output in advanced ways, such as
+:ref:`displaying multiple progress bars ` which
+are updated independently and :ref:`appending rows to tables `
+that have already been rendered.
+
Console Input
-------------
@@ -184,6 +241,8 @@ Use input options or arguments to pass information to the command::
// retrieve the argument value using getArgument()
$output->writeln('Username: '.$input->getArgument('username'));
+
+ return 0;
}
Now, you can pass the username to the command:
@@ -207,10 +266,10 @@ Getting Services from the Service Container
To actually create a new user, the command has to access some
:doc:`services `. Since your command is already registered
as a service, you can use normal dependency injection. Imagine you have a
-``AppBundle\Service\UserManager`` service that you want to access::
+``App\Service\UserManager`` service that you want to access::
// ...
- use AppBundle\Service\UserManager;
+ use App\Service\UserManager;
use Symfony\Component\Console\Command\Command;
class CreateUserCommand extends Command
@@ -233,6 +292,8 @@ as a service, you can use normal dependency injection. Imagine you have a
$this->userManager->create($input->getArgument('username'));
$output->writeln('User successfully generated!');
+
+ return 0;
}
}
@@ -256,7 +317,8 @@ command:
:method:`Symfony\\Component\\Console\\Command\\Command::execute` *(required)*
This method is executed after ``interact()`` and ``initialize()``.
- It contains the logic you want the command to execute.
+ It contains the logic you want the command to execute and it must
+ return an integer which will be used as the command `exit status`_.
.. _console-testing-commands:
@@ -268,10 +330,9 @@ useful one is the :class:`Symfony\\Component\\Console\\Tester\\CommandTester`
class. It uses special input and output classes to ease testing without a real
console::
- // tests/AppBundle/Command/CreateUserCommandTest.php
- namespace Tests\AppBundle\Command;
+ // tests/Command/CreateUserCommandTest.php
+ namespace App\Tests\Command;
- use AppBundle\Command\CreateUserCommand;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Console\Tester\CommandTester;
@@ -343,3 +404,5 @@ tools capable of helping you with different tasks:
* :doc:`/components/console/helpers/formatterhelper`: customize the output colorization
* :doc:`/components/console/helpers/progressbar`: shows a progress bar
* :doc:`/components/console/helpers/table`: displays tabular data as a table
+
+.. _`exit status`: https://en.wikipedia.org/wiki/Exit_status
diff --git a/console/calling_commands.rst b/console/calling_commands.rst
index 5d4c3d55e42..10eeb3fd1eb 100644
--- a/console/calling_commands.rst
+++ b/console/calling_commands.rst
@@ -6,7 +6,7 @@ user to remember the order of execution, you can call it directly yourself.
This is also useful if you want to create a "meta" command that just runs a
bunch of other commands (for instance, all commands that need to be run when
the project's code has changed on the production servers: clearing the cache,
-generating Doctrine2 proxies, dumping Assetic assets, ...).
+generating Doctrine2 proxies, dumping web assets, ...).
Calling a command from another one is straightforward::
diff --git a/console/coloring.rst b/console/coloring.rst
index 093538ad8d5..3684d71709d 100644
--- a/console/coloring.rst
+++ b/console/coloring.rst
@@ -43,7 +43,7 @@ It is possible to define your own styles using the
$outputStyle = new OutputFormatterStyle('red', 'yellow', ['bold', 'blink']);
$output->getFormatter()->setStyle('fire', $outputStyle);
- $output->writeln('foo ');
+ $output->writeln('foo>');
Available foreground and background colors are: ``black``, ``red``, ``green``,
``yellow``, ``blue``, ``magenta``, ``cyan`` and ``white``.
@@ -74,8 +74,22 @@ You can also set these colors and options directly inside the tag name::
or use the :method:`Symfony\\Component\\Console\\Formatter\\OutputFormatter::escape`
method to escape all the tags included in the given string.
+Displaying Clickable Links
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Commands can use the special ```` tag to display links similar to the
+```` elements of web pages::
+
+ $output->writeln('Symfony Homepage>');
+
+If your terminal belongs to the `list of terminal emulators that support links`_
+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/
.. _ConEmu: https://conemu.github.io/
.. _ANSICON: https://github.com/adoxa/ansicon/releases
.. _Mintty: https://mintty.github.io/
.. _Hyper: https://hyper.is/
+.. _`list of terminal emulators that support links`: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
diff --git a/console/command_in_controller.rst b/console/command_in_controller.rst
index 1dbf4813f48..6559ae15c0a 100644
--- a/console/command_in_controller.rst
+++ b/console/command_in_controller.rst
@@ -21,22 +21,22 @@ their code. Instead, you can execute the command directly.
overhead.
Imagine you want to send spooled Swift Mailer messages by
-:doc:`using the swiftmailer:spool:send command `.
+:doc:`using the swiftmailer:spool:send command `.
Run this command from inside your controller via::
- // src/AppBundle/Controller/SpoolController.php
- namespace AppBundle\Controller;
+ // src/Controller/SpoolController.php
+ namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Console\Application;
- use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\KernelInterface;
- class SpoolController extends Controller
+ class SpoolController extends AbstractController
{
- public function sendSpoolAction($messages = 10, KernelInterface $kernel)
+ public function sendSpool($messages = 10, KernelInterface $kernel)
{
$application = new Application($kernel);
$application->setAutoExit(false);
@@ -76,8 +76,8 @@ First, require the package:
Now, use it in your controller::
- // src/AppBundle/Controller/SpoolController.php
- namespace AppBundle\Controller;
+ // src/Controller/SpoolController.php
+ namespace App\Controller;
use SensioLabs\AnsiConverter\AnsiToHtmlConverter;
use Symfony\Component\Console\Output\BufferedOutput;
@@ -85,9 +85,9 @@ Now, use it in your controller::
use Symfony\Component\HttpFoundation\Response;
// ...
- class SpoolController extends Controller
+ class SpoolController extends AbstractController
{
- public function sendSpoolAction($messages = 10)
+ public function sendSpool($messages = 10)
{
// ...
$output = new BufferedOutput(
diff --git a/console/commands_as_services.rst b/console/commands_as_services.rst
index 30ba7265f23..4e0c4e38b62 100644
--- a/console/commands_as_services.rst
+++ b/console/commands_as_services.rst
@@ -4,29 +4,18 @@
How to Define Commands as Services
==================================
-If you're using the :ref:`default services.yml configuration `,
+If you're using the :ref:`default services.yaml configuration `,
your command classes are already registered as services. Great! This is the
recommended setup.
-Symfony also looks in the ``Command/`` directory of each bundle for commands
-non registered as a service and automatically registers those classes as
-commands. However, this auto-registration was deprecated in Symfony 3.4. In
-Symfony 4.0, commands won't be auto-registered anymore.
-
.. note::
You can also manually register your command as a service by configuring the service
and :doc:`tagging it ` with ``console.command``.
-In either case, if your class extends :class:`Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerAwareCommand`,
-you can access public services via ``$this->getContainer()->get('SERVICE_ID')``.
-
-But if your class is registered as a service, you can instead access services by
-using normal :ref:`dependency injection `.
-
For example, suppose you want to log something from within your command::
- namespace AppBundle\Command;
+ namespace App\Command;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Command\Command;
@@ -59,7 +48,7 @@ For example, suppose you want to log something from within your command::
}
}
-If you're using the :ref:`default services.yml configuration `,
+If you're using the :ref:`default services.yaml configuration `,
the command class will automatically be registered as a service and passed the ``$logger``
argument (thanks to autowiring). In other words, *just* by creating this class, everything
works! You can call the ``app:sunshine`` command and start logging.
@@ -76,10 +65,6 @@ works! You can call the ``app:sunshine`` command and start logging.
Lazy Loading
------------
-.. versionadded:: 3.4
-
- Support for command lazy loading was introduced in Symfony 3.4.
-
To make your command lazily loaded, either define its ``$defaultName`` static property::
class SunshineCommand extends Command
@@ -95,33 +80,32 @@ Or set the ``command`` attribute on the ``console.command`` tag in your service
.. code-block:: yaml
+ # config/services.yaml
services:
-
- AppBundle\Command\SunshineCommand:
+ App\Command\SunshineCommand:
tags:
- { name: 'console.command', command: 'app:sunshine' }
# ...
.. code-block:: xml
+
-
-
+
-
.. code-block:: php
- use AppBundle\Command\SunshineCommand;
-
+ // config/services.php
+ use App\Command\SunshineCommand;
// ...
$container
diff --git a/console/hide_commands.rst b/console/hide_commands.rst
index 4a747b23245..814888fe660 100644
--- a/console/hide_commands.rst
+++ b/console/hide_commands.rst
@@ -11,8 +11,8 @@ executed through scheduled tasks, etc.
In those cases, you can define the command as **hidden** by setting the
``setHidden()`` method to ``true`` in the command configuration::
- // src/AppBundle/Command/LegacyCommand.php
- namespace AppBundle\Command;
+ // src/Command/LegacyCommand.php
+ namespace App\Command;
use Symfony\Component\Console\Command\Command;
diff --git a/console/input.rst b/console/input.rst
index c9d934ff725..4b44fddb387 100644
--- a/console/input.rst
+++ b/console/input.rst
@@ -76,7 +76,7 @@ to greet all your friends). Only the last argument can be a list::
'Who do you want to greet (separate multiple names with a space)?'
);
-To use this, just specify as many names as you want:
+To use this, specify as many names as you want:
.. code-block:: terminal
diff --git a/console/lazy_commands.rst b/console/lazy_commands.rst
index 9c4e9585e07..553490c845e 100644
--- a/console/lazy_commands.rst
+++ b/console/lazy_commands.rst
@@ -1,10 +1,6 @@
How to Make Commands Lazily Loaded
==================================
-.. versionadded:: 3.4
-
- Support for command lazy loading was introduced in Symfony 3.4.
-
.. note::
If you are using the Symfony full-stack framework, you are probably looking for
@@ -17,7 +13,7 @@ The traditional way of adding commands to your application is to use
In order to lazy-load commands, you need to register an intermediate loader
which will be responsible for returning ``Command`` instances::
- use AppBundle\Command\HeavyCommand;
+ use App\Command\HeavyCommand;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\CommandLoader\FactoryCommandLoader;
@@ -46,7 +42,7 @@ Built-in Command Loaders
~~~~~~~~~~~~~~~~~~~~~~~~
The :class:`Symfony\\Component\\Console\\CommandLoader\\FactoryCommandLoader`
-class provides a simple way of getting commands lazily loaded as it takes an
+class provides a way of getting commands lazily loaded as it takes an
array of ``Command`` factories as its only constructor argument::
use Symfony\Component\Console\CommandLoader\FactoryCommandLoader;
diff --git a/console/lockable_trait.rst b/console/lockable_trait.rst
index 3f6aad3c4cf..eaecdcee893 100644
--- a/console/lockable_trait.rst
+++ b/console/lockable_trait.rst
@@ -1,10 +1,6 @@
Prevent Multiple Executions of a Console Command
================================================
-.. versionadded:: 3.2
-
- The ``LockableTrait`` was introduced in Symfony 3.2.
-
A simple but effective way to prevent multiple executions of the same command in
a single server is to use `locks`_. The :doc:`Lock component `
provides multiple classes to create locks based on the filesystem (:ref:`FlockStore `),
diff --git a/console/request_context.rst b/console/request_context.rst
deleted file mode 100644
index 130714d4d47..00000000000
--- a/console/request_context.rst
+++ /dev/null
@@ -1,94 +0,0 @@
-.. index::
- single: Console; Generating URLs
-
-How to Generate URLs from the Console
-=====================================
-
-Unfortunately, the command line context does not know about your VirtualHost
-or domain name. This means that if you generate absolute URLs within a
-console command you'll probably end up with something like ``http://localhost/foo/bar``
-which is not very useful.
-
-To fix this, you need to configure the "request context", which is a fancy
-way of saying that you need to configure your environment so that it knows
-what URL it should use when generating URLs.
-
-There are two ways of configuring the request context: at the application level
-and per Command.
-
-Configuring the Request Context Globally
-----------------------------------------
-
-To configure the Request Context - which is used by the URL Generator - you can
-redefine the parameters it uses as default values to change the default host
-(localhost) and scheme (http). You can also configure the base path (both for
-the URL generator and the assets) if Symfony is not running in the root directory.
-
-Note that this does not impact URLs generated via normal web requests, since those
-will override the defaults.
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/parameters.yml
- parameters:
- router.request_context.host: 'example.org'
- router.request_context.scheme: 'https'
- router.request_context.base_url: 'my/path'
- asset.request_context.base_path: '%router.request_context.base_url%'
- asset.request_context.secure: true
-
- .. code-block:: xml
-
-
-
-
-
-
- example.org
- https
- my/path
- %router.request_context.base_url%
- true
-
-
-
-
- .. code-block:: php
-
- // app/config/parameters.php
- $container->setParameter('router.request_context.host', 'example.org');
- $container->setParameter('router.request_context.scheme', 'https');
- $container->setParameter('router.request_context.base_url', 'my/path');
- $container->setParameter('asset.request_context.base_path', $container->getParameter('router.request_context.base_url'));
- $container->setParameter('asset.request_context.secure', true);
-
-.. versionadded:: 3.4
-
- The ``asset.request_context.*`` parameters were introduced in Symfony 3.4.
-
-Configuring the Request Context per Command
--------------------------------------------
-
-To change it only in one command you need to fetch the Request Context
-from the ``router`` service and override its settings::
-
- // src/AppBundle/Command/DemoCommand.php
-
- // ...
- class DemoCommand extends ContainerAwareCommand
- {
- protected function execute(InputInterface $input, OutputInterface $output)
- {
- $router = $this->getContainer()->get('router');
- $context = $router->getContext();
- $context->setHost('example.com');
- $context->setScheme('https');
- $context->setBaseUrl('my/path');
-
- $url = $router->generate('route-name', ['param-name' => 'param-value']);
- // ...
- }
- }
diff --git a/console/style.rst b/console/style.rst
index 466f20ac744..c800e56f976 100644
--- a/console/style.rst
+++ b/console/style.rst
@@ -10,14 +10,14 @@ questions to the user involves a lot of repetitive code.
Consider for example the code used to display the title of the following command::
- // src/AppBundle/Command/GreetCommand.php
- namespace AppBundle\Command;
+ // src/Command/GreetCommand.php
+ namespace App\Command;
- use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
+ use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
- class GreetCommand extends ContainerAwareCommand
+ class GreetCommand extends Command
{
// ...
@@ -50,15 +50,15 @@ class and pass the ``$input`` and ``$output`` variables as its arguments. Then,
you can start using any of its helpers, such as ``title()``, which displays the
title of the command::
- // src/AppBundle/Command/GreetCommand.php
- namespace AppBundle\Command;
+ // src/Command/GreetCommand.php
+ namespace App\Command;
- use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
+ use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
- class GreetCommand extends ContainerAwareCommand
+ class GreetCommand extends Command
{
// ...
@@ -140,6 +140,31 @@ Content Methods
]
);
+:method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::horizontalTable`
+ It displays the given array of headers and rows as a compact horizontal table::
+
+ $io->horizontalTable(
+ ['Header 1', 'Header 2'],
+ [
+ ['Cell 1-1', 'Cell 1-2'],
+ ['Cell 2-1', 'Cell 2-2'],
+ ['Cell 3-1', 'Cell 3-2'],
+ ]
+ );
+
+:method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::definitionList`
+ It displays the given ``key => value`` pairs as a compact list of elements::
+
+ $io->definitionList(
+ 'This is a title',
+ ['foo1' => 'bar1'],
+ ['foo2' => 'bar2'],
+ ['foo3' => 'bar3'],
+ new TableSeparator(),
+ 'This is another title',
+ ['foo4' => 'bar4']
+ );
+
:method:`Symfony\\Component\\Console\\Style\\SymfonyStyle::newLine`
It displays a blank line in the command output. Although it may seem useful,
most of the times you won't need it at all. The reason is that every helper
@@ -338,10 +363,10 @@ Defining your Own Styles
------------------------
If you don't like the design of the commands that use the Symfony Style, you can
-define your own set of console styles. Just create a class that implements the
+define your own set of console styles. Create a class that implements the
:class:`Symfony\\Component\\Console\\Style\\StyleInterface`::
- namespace AppBundle\Console;
+ namespace App\Console;
use Symfony\Component\Console\Style\StyleInterface;
@@ -354,14 +379,15 @@ Then, instantiate this custom class instead of the default ``SymfonyStyle`` in
your commands. Thanks to the ``StyleInterface`` you won't need to change the code
of your commands to change their appearance::
- // src/AppBundle/Command/GreetCommand.php
- namespace AppBundle\Console;
+ // src/Command/GreetCommand.php
+ namespace App\Console;
- use AppBundle\Console\CustomStyle;
+ use App\Console\CustomStyle;
+ use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
- class GreetCommand extends ContainerAwareCommand
+ class GreetCommand extends Command
{
// ...
diff --git a/console/usage.rst b/console/usage.rst
deleted file mode 100644
index 21cdc42751a..00000000000
--- a/console/usage.rst
+++ /dev/null
@@ -1,32 +0,0 @@
-.. index::
- single: Console; Usage
-
-How to Use the Console
-======================
-
-The :doc:`/components/console/usage` page of the components documentation looks
-at the global console options. When you use the console as part of the full-stack
-framework, some additional global options are available as well.
-
-By default, console commands run in the ``dev`` environment and you may want to
-change this for some commands. For example, you may want to run some commands in
-the ``prod`` environment for performance reasons. Also, the result of some
-commands will be different depending on the environment. For example, the
-``cache:clear`` command will clear and warm up the cache for the specified
-environment only:
-
-.. code-block:: terminal
-
- # clear (and warm up) the cache of the 'prod' environment
- $ php bin/console cache:clear --env=prod
-
- # this is equivalent:
- $ php bin/console cache:clear --no-warmup -e prod
-
-In addition to changing the environment, you can also choose to disable debug mode.
-This can be useful where you want to run commands in the ``dev`` environment
-but avoid the performance hit of collecting debug data:
-
-.. code-block:: terminal
-
- $ php bin/console list --no-debug
diff --git a/contributing/code/bc.rst b/contributing/code/bc.rst
index 4db7b796c92..0252c7cb43c 100644
--- a/contributing/code/bc.rst
+++ b/contributing/code/bc.rst
@@ -22,7 +22,7 @@ method signature.
Also, not every BC break has the same impact on application code. While some BC
breaks require you to make significant changes to your classes or your
-architecture, others can be fixed changing the name of a single method.
+architecture, others are fixed by changing the name of a method.
That's why we created this page for you. The section "Using Symfony Code" will
tell you how you can ensure that your application won't break completely when
diff --git a/contributing/code/bugs.rst b/contributing/code/bugs.rst
index 440b2dac519..271c7f1203b 100644
--- a/contributing/code/bugs.rst
+++ b/contributing/code/bugs.rst
@@ -26,7 +26,7 @@ If your problem definitely looks like a bug, report it using the official bug
* Describe the steps needed to reproduce the bug with short code examples
(providing a unit test that illustrates the bug is best);
-* If the bug you experienced is not obvious or affects more than one layer,
+* If the bug you experienced is not simple or affects more than one layer,
providing a simple failing unit test may not be sufficient. In this case,
please :doc:`provide a reproducer `;
diff --git a/contributing/code/conventions.rst b/contributing/code/conventions.rst
index 7099ace5db5..39898f41ec8 100644
--- a/contributing/code/conventions.rst
+++ b/contributing/code/conventions.rst
@@ -85,8 +85,7 @@ Deprecating Code
From time to time, some classes and/or methods are deprecated in the
framework; that happens when a feature implementation cannot be changed
because of backward compatibility issues, but we still want to propose a
-"better" alternative. In that case, the old implementation can simply be
-**deprecated**.
+"better" alternative. In that case, the old implementation can be **deprecated**.
Deprecations must only be introduced on the next minor version of the impacted
component (or bundle, or bridge, or contract).
diff --git a/contributing/code/experimental.rst b/contributing/code/experimental.rst
index 9081cf5184c..998f112ba7b 100644
--- a/contributing/code/experimental.rst
+++ b/contributing/code/experimental.rst
@@ -5,8 +5,8 @@ All Symfony features benefit from our :doc:`Backward Compatibility Promise
` to give developers the confidence to upgrade to new
versions safely and more often.
-But sometimes, a new feature is controversial. Or finding a good API is not
-easy. In such cases, we prefer to gather feedback from real-world usage, adapt
+But sometimes, a new feature is controversial or you cannot find a convincing API.
+In such cases, we prefer to gather feedback from real-world usage, adapt
the API, or remove it altogether. Doing so is not possible with a no BC-break
approach.
diff --git a/contributing/code/git.rst b/contributing/code/git.rst
index 59ca5a4a24d..4ff26d324b3 100644
--- a/contributing/code/git.rst
+++ b/contributing/code/git.rst
@@ -10,8 +10,8 @@ Pull Requests
Whenever a pull request is merged, all the information contained in the pull
request (including comments) is saved in the repository.
-You can spot pull request merges as the commit message always follows this
-pattern:
+You can identify pull request merges as the commit message always follows
+this pattern:
.. code-block:: text
diff --git a/contributing/code/reproducer.rst b/contributing/code/reproducer.rst
index f92cf460ea6..771bd69eeac 100644
--- a/contributing/code/reproducer.rst
+++ b/contributing/code/reproducer.rst
@@ -33,21 +33,23 @@ Reproducing Complex Bugs
------------------------
If the bug is related to the Symfony Framework or if it's too complex to create
-a PHP script, it's better to reproduce the bug by forking the Symfony Standard
-edition. To do so:
-
-#. Go to https://github.com/symfony/symfony-standard and click on the **Fork**
- button to make a fork of that repository or go to your already-forked copy.
-#. Clone the forked repository into your computer:
- ``git clone git://github.com/YOUR-GITHUB-USERNAME/symfony-standard.git``
-#. Browse the project and create a new branch (e.g. ``issue_23567``,
- ``reproduce_23657``, etc.)
+a PHP script, it's better to reproduce the bug by creating a new project. To do so:
+
+#. Create a new project:
+
+.. code-block:: terminal
+
+ $ composer create-project symfony/skeleton bug_app
+
#. Add and commit the changes generated by Symfony.
#. Now you must add the minimum amount of code to reproduce the bug. This is the
trickiest part and it's explained a bit more later.
-#. Add, commit and push all your own changes.
+#. Add and commit your changes.
+#. Create a `new repository`_ on GitHub (give it any name).
+#. Follow the instructions on GitHub to add the ``origin`` remote to your local project
+ and push it.
#. Add a comment in your original issue report to share the URL of your forked
- project (e.g. ``https://github.com/YOUR-GITHUB-USERNAME/symfony-standard/tree/issue_23567``)
+ project (e.g. ``https://github.com/YOUR-GITHUB-USERNAME/symfony_issue_23567``)
and, if necessary, explain the steps to reproduce (e.g. "browse this URL",
"fill in this data in the form and submit it", etc.)
@@ -56,23 +58,24 @@ Adding the Minimum Amount of Code Possible
The key to create a bug reproducer is to solely focus on the feature that you
suspect is failing. For example, imagine that you suspect that the bug is related
-to a route definition. Then, after forking the Symfony Standard Edition:
+to a route definition. Then, after creating your project:
#. Don't edit any of the default Symfony configuration options.
#. Don't copy your original application code and don't use the same structure
- of bundles, controllers, actions, etc. as in your original application.
-#. Open the default controller class of the AppBundle and add your routing
- definition using annotations.
+ of controllers, actions, etc. as in your original application.
+#. Create a small controller and add your routing definition that shows the bug.
#. Don't create or modify any other file.
-#. Execute the ``server:run`` command and browse the previously defined route
- to see if the bug appears or not.
+#. Execute ``composer require symfony/web-server-bundle`` and use the ``server:run``
+ command to browse to the new route and see if the bug appears or not.
#. If you can see the bug, you're done and you can already share the code with us.
#. If you can't see the bug, you must keep making small changes. For example, if
your original route was defined using XML, forget about the previous route
- annotation and define the route using XML instead. Or maybe your application
- uses bundle inheritance and that's where the real bug is. Then, forget about
- AppBundle and quickly generate a new AppParentBundle, make AppBundle inherit
- from it and test if the route is working.
+ and define the route using XML instead. Or maybe your application
+ registers some event listeners and that's where the real bug is. In that case,
+ add an event listener that's similar to your real app to see if you can find
+ the bug.
+
+In short, the idea is to keep adding small and incremental changes to a new project
+until you can reproduce the bug.
-In short, the idea is to keep adding small and incremental changes to the default
-Symfony Standard edition until you can reproduce the bug.
+.. _`new repository`: https://github.com/new
diff --git a/contributing/code/security.rst b/contributing/code/security.rst
index 557e4c13501..32401d658f9 100644
--- a/contributing/code/security.rst
+++ b/contributing/code/security.rst
@@ -39,7 +39,7 @@ confirmed, the core team works on a solution following these steps:
the "`Security Advisories`_" category);
#. Update the public `security advisories database`_ maintained by the
FriendsOfPHP organization and which is used by
- :doc:`the check:security command `.
+ :ref:`the check:security command `.
.. note::
@@ -170,7 +170,7 @@ Security Advisories
.. tip::
You can check your Symfony application for known security vulnerabilities
- using the ``check:security`` command (see :doc:`/security/security_checker`).
+ using :ref:`the check:security command `.
Check the `Security Advisories`_ blog category for a list of all security
vulnerabilities that were fixed in Symfony releases, starting from Symfony
diff --git a/contributing/community/releases.rst b/contributing/community/releases.rst
index d2336e21e86..f302faa087f 100644
--- a/contributing/community/releases.rst
+++ b/contributing/community/releases.rst
@@ -59,7 +59,7 @@ ones are considered **standard versions**:
======================= ===================== ================================
Version Type Bugs are fixed for... Security issues are fixed for...
======================= ===================== ================================
-Standard 8 months 14 months
+Standard 8 months 8 months
Long-Term Support (LTS) 3 years 4 years
======================= ===================== ================================
diff --git a/contributing/community/review-comments.rst b/contributing/community/review-comments.rst
index 6336f92dcbb..36bad6d7221 100644
--- a/contributing/community/review-comments.rst
+++ b/contributing/community/review-comments.rst
@@ -26,7 +26,7 @@ constructive, respectful and helpful reviews and replies.
your ideas and opinions but helping you to better communicate,
prevent possible confusion, and keeping the Symfony community a
welcoming place for everyone. **You are free to disagree with
- someone's opinions, just don't be disrespectful.**
+ 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.
diff --git a/contributing/community/reviews.rst b/contributing/community/reviews.rst
index f412e4ecdd2..06cc404d821 100644
--- a/contributing/community/reviews.rst
+++ b/contributing/community/reviews.rst
@@ -58,16 +58,16 @@ The steps for the review are:
#. **Is the Report Complete?**
- Good bug reports contain a link to a fork of the `Symfony Standard Edition`_
- (the "reproduction project") that reproduces the bug. If it doesn't, the
- report should at least contain enough information and code samples to
- reproduce the bug.
+ Good bug reports contain a link to a project (the "reproduction project")
+ created with the `Symfony skeleton`_ or the `Symfony website skeleton`_
+ that reproduces the bug. If it doesn't, the report should at least contain
+ enough information and code samples to reproduce the bug.
#. **Reproduce the Bug**
Download the reproduction project and test whether the bug can be reproduced
on your system. If the reporter did not provide a reproduction project,
- create one by `forking`_ the `Symfony Standard Edition`_.
+ create one based on one `Symfony skeleton`_ (or the `Symfony website skeleton`_).
#. **Update the Issue Status**
@@ -96,7 +96,7 @@ The steps for the review are:
Thank you @weaverryan for creating this bug report! This indeed looks
like a bug. I reproduced the bug in the "kernel-bug" branch of
- https://github.com/webmozart/symfony-standard.
+ https://github.com/webmozart/some-project.
Status: Reviewed
@@ -134,9 +134,9 @@ Pick a pull request from the `PRs in need of review`_ and follow these steps:
#. **Reproduce the Problem**
Read the issue that the pull request is supposed to fix. Reproduce the
- problem on a clean `Symfony Standard Edition`_ project and try to understand
- why it exists. If the linked issue already contains such a project, install
- it and run it on your system.
+ problem on a new project created with the `Symfony skeleton`_ (or the
+ `Symfony website skeleton`_) and try to understand why it exists. If the
+ linked issue already contains such a project, install it and run it on your system.
#. **Review the Code**
@@ -211,9 +211,9 @@ Pick a pull request from the `PRs in need of review`_ and follow these steps:
.. _GitHub: https://github.com
.. _Symfony issue tracker: https://github.com/symfony/symfony/issues
-.. _Symfony Standard Edition: https://github.com/symfony/symfony-standard
+.. _`Symfony skeleton`: https://github.com/symfony/skeleton
+.. _`Symfony website skeleton`: https://github.com/symfony/website-skeleton
.. _create a GitHub account: https://help.github.com/articles/signing-up-for-a-new-github-account/
-.. _forking: https://help.github.com/articles/fork-a-repo/
.. _bug reports in need of review: https://github.com/symfony/symfony/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue+label%3A%22Bug%22+label%3A%22Status%3A+Needs+Review%22+
.. _PRs in need of review: https://github.com/symfony/symfony/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Apr+label%3A%22Status%3A+Needs+Review%22+
.. _Symfony Roadmap: https://symfony.com/roadmap
diff --git a/contributing/documentation/format.rst b/contributing/documentation/format.rst
index 43147f26341..275db5125eb 100644
--- a/contributing/documentation/format.rst
+++ b/contributing/documentation/format.rst
@@ -131,7 +131,7 @@ If you want to modify that title, use this alternative syntax:
.. code-block:: rst
- :doc:`Spooling Email `
+ :doc:`Doctrine Associations `
.. note::
@@ -173,40 +173,39 @@ If you are documenting a brand new feature, a change or a deprecation that's
been made in Symfony, you should precede your description of the change with
the corresponding directive and a short description:
-For a new feature or a behavior change use the ``.. versionadded:: 3.x``
+For a new feature or a behavior change use the ``.. versionadded:: 5.x``
directive:
.. code-block:: rst
- .. versionadded:: 3.4
+ .. versionadded:: 5.2
- The special ``!`` template prefix was introduced in Symfony 3.4.
+ ... ... ... was introduced in Symfony 5.2.
If you are documenting a behavior change, it may be helpful to *briefly*
describe how the behavior has changed:
.. code-block:: rst
- .. versionadded:: 3.4
+ .. versionadded:: 5.2
- Support for annotation routing without an external bundle was introduced
- in Symfony 3.4. Prior, you needed to install the SensioFrameworkExtraBundle.
+ ... ... ... was introduced in Symfony 5.2. Prior to this,
+ ... ... ... ... ... ... ... ... .
-For a deprecation use the ``.. deprecated:: 3.x`` directive:
+For a deprecation use the ``.. deprecated:: 5.x`` directive:
.. code-block:: rst
- .. deprecated:: 3.3
+ .. deprecated:: 5.2
- This technique is discouraged and the ``addClassesToCompile()`` method was
- deprecated in Symfony 3.3 because modern PHP versions make it unnecessary.
+ ... ... ... was deprecated in Symfony 5.2.
-Whenever a new major version of Symfony is released (e.g. 3.0, 4.0, etc),
+Whenever a new major version of Symfony is released (e.g. 6.0, 7.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
-Symfony 4.0 were released today, 3.0 to 3.4 ``versionadded`` and ``deprecated``
-tags would be removed from the new ``4.0`` branch.
+Symfony 6.0 were released today, 5.0 to 5.4 ``versionadded`` and ``deprecated``
+tags would be removed from the new ``6.0`` branch.
.. _reStructuredText: http://docutils.sourceforge.net/rst.html
.. _Sphinx: https://www.sphinx-doc.org/
diff --git a/contributing/documentation/standards.rst b/contributing/documentation/standards.rst
index 08787eb0331..25fac5739fb 100644
--- a/contributing/documentation/standards.rst
+++ b/contributing/documentation/standards.rst
@@ -53,9 +53,7 @@ Code Examples
as well as the `Twig Coding Standards`_;
* The code examples should look real for a web application context. Avoid abstract
or trivial examples (``foo``, ``bar``, ``demo``, etc.);
-* The code should follow the :doc:`Symfony Best Practices `.
- Unless the example requires a custom bundle, make sure to always use the
- ``AppBundle`` bundle to store your code;
+* The code should follow the :doc:`Symfony Best Practices `.
* Use ``Acme`` when the code requires a vendor name;
* Use ``example.com`` as the domain of sample URLs and ``example.org`` and
``example.net`` when additional domains are required. All of these domains are
@@ -132,7 +130,7 @@ Files and Directories
---------------------
* When referencing directories, always add a trailing slash to avoid confusions
- with regular files (e.g. "execute the ``console`` script located at the ``app/``
+ with regular files (e.g. "execute the ``console`` script located at the ``bin/``
directory").
* When referencing file extensions explicitly, you should include a leading dot
for every extension (e.g. "XML files use the ``.xml`` extension").
diff --git a/controller.rst b/controller.rst
index ae905081eab..879c984bfc8 100644
--- a/controller.rst
+++ b/controller.rst
@@ -4,41 +4,11 @@
Controller
==========
-A controller is a PHP function you create that reads information from the Symfony's
+A controller is a PHP function you create that reads information from the
``Request`` object and creates and returns a ``Response`` object. The response could
be an HTML page, JSON, XML, a file download, a redirect, a 404 error or anything
-else you can dream up. The controller executes whatever arbitrary logic
-*your application* needs to render the content of a page.
-
-See how simple this is by looking at a Symfony controller in action.
-This renders a page that prints a lucky (random) number::
-
- // src/AppBundle/Controller/LuckyController.php
- namespace AppBundle\Controller;
-
- use Symfony\Component\HttpFoundation\Response;
- use Symfony\Component\Routing\Annotation\Route;
-
- class LuckyController
- {
- /**
- * @Route("/lucky/number")
- */
- public function numberAction()
- {
- $number = random_int(0, 100);
-
- return new Response(
- 'Lucky number: '.$number.''
- );
- }
- }
-
-But in the real world, your controller will probably do a lot of work in order to
-create the response. It might read information from the request, load a database
-resource, send an email or set information on the user's session.
-But in all cases, the controller will eventually return the ``Response`` object
-that will be delivered back to the client.
+else. The controller executes whatever arbitrary logic *your application* needs
+to render the content of a page.
.. tip::
@@ -51,12 +21,12 @@ that will be delivered back to the client.
A Simple Controller
-------------------
-While a controller can be any PHP callable (a function, method on an object,
+While a controller can be any PHP callable (function, method on an object,
or a ``Closure``), a controller is usually a method inside a controller
class::
- // src/AppBundle/Controller/LuckyController.php
- namespace AppBundle\Controller;
+ // src/Controller/LuckyController.php
+ namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
@@ -64,9 +34,9 @@ class::
class LuckyController
{
/**
- * @Route("/lucky/number/{max}")
+ * @Route("/lucky/number/{max}", name="app_lucky_number")
*/
- public function numberAction($max)
+ public function number($max)
{
$number = random_int(0, $max);
@@ -76,7 +46,7 @@ class::
}
}
-The controller is the ``numberAction()`` method, which lives inside a
+The controller is the ``number()`` method, which lives inside the
controller class ``LuckyController``.
This controller is pretty straightforward:
@@ -88,13 +58,11 @@ This controller is pretty straightforward:
the ``use`` keyword imports the ``Response`` class, which the controller
must return.
-* *line 7*: The class can technically be called anything - but should end in the
- word ``Controller`` (this isn't *required*, but some shortcuts rely on this).
+* *line 7*: The class can technically be called anything, but it's suffixed
+ with ``Controller`` by convention.
-* *line 12*: Each action method in a controller class is suffixed with ``Action``
- (again, this isn't *required*, but some shortcuts rely on this). This method
- is allowed to have a ``$max`` argument thanks to the ``{max}``
- :doc:`wildcard in the route `.
+* *line 12*: The action method is allowed to have a ``$max`` argument thanks to the
+ ``{max}`` :doc:`wildcard in the route `.
* *line 16*: The controller creates and returns a ``Response`` object.
@@ -105,7 +73,8 @@ Mapping a URL to a Controller
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In order to *view* the result of this controller, you need to map a URL to it via
-a route. This was done above with the ``@Route("/lucky/number/{max}")`` annotation.
+a route. This was done above with the ``@Route("/lucky/number/{max}")``
+:ref:`route annotation `.
To see your page, go to this URL in your browser:
@@ -117,24 +86,27 @@ For more information on routing, see :doc:`/routing`.
single: Controller; Base controller class
.. _the-base-controller-class-services:
+.. _the-base-controller-classes-services:
+
+The Base Controller Class & Services
+------------------------------------
-The Base Controller Classes & Services
---------------------------------------
+To aid development, Symfony comes with an optional base controller class called
+:class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController`.
+It can be extended to gain access to helper methods.
-For convenience, Symfony comes with two optional base
-:class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller` and
-:class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController`
-classes. You can extend either to get access to a number of helper methods.
+Add the ``use`` statement atop your controller class and then modify
+``LuckyController`` to extend it:
-Add the ``use`` statement atop the ``Controller`` class and then modify
-``LuckyController`` to extend it::
+.. code-block:: diff
- // src/AppBundle/Controller/LuckyController.php
- namespace AppBundle\Controller;
+ // src/Controller/LuckyController.php
+ namespace App\Controller;
- use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+ + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
- class LuckyController extends Controller
+ - class LuckyController
+ + class LuckyController extends AbstractController
{
// ...
}
@@ -142,33 +114,18 @@ Add the ``use`` statement atop the ``Controller`` class and then modify
That's it! You now have access to methods like :ref:`$this->render() `
and many others that you'll learn about next.
-.. _controller-abstract-versus-controller:
-
-.. tip::
-
- You can extend either ``Controller`` or ``AbstractController``. The difference
- is that when you extend ``AbstractController``, you can't access your services
- via ``$this->get()`` or ``$this->container->get()``, only to a set of common
- Symfony services. This forces you to write more robust code to access services.
-
- Moreover, in Symfony 4.2 ``Controller`` was deprecated in favor of
- ``AbstractController``, so using the latter will make your applications
- future-proof.
-
-.. versionadded:: 3.3
-
- The ``AbstractController`` class was introduced in Symfony 3.3.
-
.. index::
single: Controller; Redirecting
Generating URLs
~~~~~~~~~~~~~~~
-The :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::generateUrl`
+The :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController::generateUrl`
method is just a helper method that generates the URL for a given route::
- $url = $this->generateUrl('blog_show', ['slug' => 'slug-value']);
+ $url = $this->generateUrl('app_lucky_number', ['max' => 10]);
+
+.. _controller-redirect:
Redirecting
~~~~~~~~~~~
@@ -176,16 +133,22 @@ Redirecting
If you want to redirect the user to another page, use the ``redirectToRoute()``
and ``redirect()`` methods::
- public function indexAction()
+ use Symfony\Component\HttpFoundation\RedirectResponse;
+
+ // ...
+ public function index()
{
// redirects to the "homepage" route
return $this->redirectToRoute('homepage');
+ // redirectToRoute is a shortcut for:
+ // return new RedirectResponse($this->generateUrl('homepage'));
+
// does a permanent - 301 redirect
return $this->redirectToRoute('homepage', [], 301);
- // redirects to a route with parameters
- return $this->redirectToRoute('blog_show', ['slug' => 'my-page']);
+ // 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());
@@ -194,27 +157,12 @@ and ``redirect()`` methods::
return $this->redirect('http://symfony.com/doc');
}
-For more information, see the :doc:`Routing article `.
-
.. caution::
The ``redirect()`` method does not check its destination in any way. If you
- redirect to some URL provided by the end-users, your application may be open
+ redirect to a URL provided by end-users, your application may be open
to the `unvalidated redirects security vulnerability`_.
-.. tip::
-
- The ``redirectToRoute()`` method is simply a shortcut that creates a
- ``Response`` object that specializes in redirecting the user. It's
- equivalent to::
-
- use Symfony\Component\HttpFoundation\RedirectResponse;
-
- public function indexAction()
- {
- return new RedirectResponse($this->generateUrl('homepage'));
- }
-
.. index::
single: Controller; Rendering templates
@@ -227,38 +175,26 @@ If you're serving HTML, you'll want to render a template. The ``render()``
method renders a template **and** puts that content into a ``Response``
object for you::
- // renders app/Resources/views/lucky/number.html.twig
+ // renders templates/lucky/number.html.twig
return $this->render('lucky/number.html.twig', ['number' => $number]);
-Templates can also live in deeper sub-directories. Just try to avoid
-creating unnecessarily deep structures::
-
- // renders app/Resources/views/lottery/lucky/number.html.twig
- return $this->render('lottery/lucky/number.html.twig', [
- 'number' => $number,
- ]);
-
-The Symfony templating system and Twig are explained more in the
-:doc:`Creating and Using Templates article `.
+Templating and Twig are explained more in the
+:doc:`Creating and Using Templates article `.
.. index::
single: Controller; Accessing services
.. _controller-accessing-services:
+.. _accessing-other-services:
-Fetching Services as Controller Arguments
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. versionadded:: 3.3
-
- The ability to type-hint a controller argument in order to receive a service
- was introduced in Symfony 3.3.
+Fetching Services
+~~~~~~~~~~~~~~~~~
Symfony comes *packed* with a lot of useful objects, called :doc:`services `.
These are used for rendering templates, sending emails, querying the database and
any other "work" you can think of.
-If you need a service in a controller, just type-hint an argument with its class
+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;
@@ -267,7 +203,7 @@ If you need a service in a controller, just type-hint an argument with its class
/**
* @Route("/lucky/number/{max}")
*/
- public function numberAction($max, LoggerInterface $logger)
+ public function number($max, LoggerInterface $logger)
{
$logger->info('We are logging!');
// ...
@@ -289,20 +225,22 @@ the argument by its name:
.. code-block:: yaml
- # app/config/services.yml
+ # config/services.yaml
services:
# ...
# explicitly configure the service
- AppBundle\Controller\LuckyController:
+ App\Controller\LuckyController:
public: true
bind:
# for any $logger argument, pass this specific service
$logger: '@monolog.logger.doctrine'
+ # for any $projectDir argument, pass this parameter value
+ $projectDir: '%kernel.project_dir%'
.. code-block:: xml
-
+
-
+
+ %kernel.project_dir%
.. code-block:: php
- // app/config/services.php
- use AppBundle\Controller\LuckyController;
+ // config/services.php
+ use App\Controller\LuckyController;
use Symfony\Component\DependencyInjection\Reference;
$container->register(LuckyController::class)
->setPublic(true)
->setBindings([
'$logger' => new Reference('monolog.logger.doctrine'),
+ '$projectDir' => '%kernel.project_dir%'
])
;
-You can also use normal :ref:`constructor injection `
+Like with all services, you can also use regular :ref:`constructor injection `
in your controllers.
-.. caution::
-
- You can *only* pass *services* to your controller arguments in this way. It's not
- possible, for example, to pass a service parameter as a controller argument,
- even by using ``bind``. If you need a parameter, use the ``$this->getParameter('kernel.debug')``
- shortcut or pass the value through your controller's ``__construct()`` method
- and specify its value with ``bind``.
-
For more information about services, see the :doc:`/service_container` article.
-.. _controller-service-arguments-tag:
-
-.. note::
-
- If this isn't working, make sure your controller is registered as a service,
- is :ref:`autoconfigured ` and extends either
- :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller` or
- :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController`. If
- you use the :ref:`services.yml configuration from the Symfony Standard Edition `,
- then your controllers are already registered as services and autoconfigured.
-
- If you're not using the default configuration, you can tag your service manually
- with ``controller.service_arguments``.
-
-.. _accessing-other-services:
-.. _controller-access-services-directly:
+Generating Controllers
+----------------------
-Accessing the Container Directly
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+To save time, you can install `Symfony Maker`_ and tell Symfony to generate a
+new controller class:
-If you extend the base ``Controller`` class, you can access any Symfony service
-via the :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::get`
-method. Here are several common services you might need::
+.. code-block:: terminal
- $templating = $this->get('templating');
+ $ php bin/console make:controller BrandNewController
- $router = $this->get('router');
+ created: src/Controller/BrandNewController.php
+ created: templates/brandnew/index.html.twig
- $mailer = $this->get('mailer');
+If you want to generate an entire CRUD from a Doctrine :doc:`entity `,
+use:
- // you can also fetch parameters
- $someParameter = $this->getParameter('some_parameter');
+.. code-block:: terminal
-If you receive an error like:
+ $ php bin/console make:crud Product
-.. code-block:: text
+ created: src/Controller/ProductController.php
+ created: src/Form/ProductType.php
+ created: templates/product/_delete_form.html.twig
+ created: templates/product/_form.html.twig
+ created: templates/product/edit.html.twig
+ created: templates/product/index.html.twig
+ created: templates/product/new.html.twig
+ created: templates/product/show.html.twig
- You have requested a non-existent service "my_service_id"
+.. versionadded:: 1.2
-Check to make sure the service exists (use :ref:`debug:container `)
-and that it's :ref:`public `.
+ The ``make:crud`` command was introduced in MakerBundle 1.2.
.. index::
single: Controller; Managing errors
@@ -397,23 +320,27 @@ and that it's :ref:`public `.
Managing Errors and 404 Pages
-----------------------------
-When things are not found, you should play well with the HTTP protocol and
-return a 404 response. To do this, you'll throw a special type of exception.
-If you're extending the base ``Controller`` or the base ``AbstractController``
-class, do the following::
+When things are not found, you should return a 404 response. To do this, throw a
+special type of exception::
+
+ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
- public function indexAction()
+ // ...
+ public function index()
{
// retrieve the object from database
$product = ...;
if (!$product) {
throw $this->createNotFoundException('The product does not exist');
+
+ // the above is just a shortcut for:
+ // throw new NotFoundHttpException('The product does not exist');
}
return $this->render(...);
}
-The :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::createNotFoundException`
+The :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController::createNotFoundException`
method is just a shortcut to create a special
:class:`Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException`
object, which ultimately triggers a 404 HTTP response inside Symfony.
@@ -427,15 +354,11 @@ HTTP status code::
throw new \Exception('Something went wrong!');
In every case, an error page is shown to the end user and a full debug
-error page is shown to the developer (i.e. when you're using the ``app_dev.php``
-front controller - see :ref:`page-creation-environments`).
+error page is shown to the developer (i.e. when you're in "Debug" mode - see
+:ref:`page-creation-environments`).
-You'll want to customize the error page your user sees. To do that, see
-the :doc:`/controller/error_pages` article.
-
-.. index::
- single: Controller; The session
- single: Session
+To customize the error page that's shown to the user, see the
+:doc:`/controller/error_pages` article.
.. _controller-request-argument:
@@ -443,13 +366,13 @@ The Request object as a Controller Argument
-------------------------------------------
What if you need to read query parameters, grab a request header or get access
-to an uploaded file? All of that information is stored in Symfony's ``Request``
-object. To get it in your controller, just add it as an argument and
+to an uploaded file? That information is stored in Symfony's ``Request``
+object. To access it in your controller, add it as an argument and
**type-hint it with the Request class**::
use Symfony\Component\HttpFoundation\Request;
- public function indexAction(Request $request, $firstName, $lastName)
+ public function index(Request $request, $firstName, $lastName)
{
$page = $request->query->get('page', 1);
@@ -459,24 +382,29 @@ object. To get it in your controller, just add it as an argument and
:ref:`Keep reading ` for more information about using the
Request object.
+.. index::
+ single: Controller; The session
+ single: Session
+
+.. _session-intro:
+
Managing the Session
--------------------
-Symfony provides a nice session object that you can use to store information
-about the user between requests. By default, Symfony stores the token in a
-cookie and writes the attributes to a file by using native PHP sessions.
-
-.. versionadded:: 3.3
+Symfony provides a session service that you can use to store information
+about the user between requests. Session is enabled by default, but will only be
+started if you read or write from it.
- The ability to request a ``Session`` instance in controllers was introduced
- in Symfony 3.3.
+Session storage and other configuration can be controlled under the
+:ref:`framework.session configuration ` in
+``config/packages/framework.yaml``.
-To retrieve the session, add the :class:`Symfony\\Component\\HttpFoundation\\Session\\SessionInterface`
-type-hint to your argument and Symfony will provide you with a session::
+To get the session, add an argument and type-hint it with
+:class:`Symfony\\Component\\HttpFoundation\\Session\\SessionInterface`::
use Symfony\Component\HttpFoundation\Session\SessionInterface;
- public function indexAction(SessionInterface $session)
+ public function index(SessionInterface $session)
{
// stores an attribute for reuse during a later user request
$session->set('foo', 'bar');
@@ -490,14 +418,13 @@ type-hint to your argument and Symfony will provide you with a session::
Stored attributes remain in the session for the remainder of that user's session.
-.. tip::
-
- Every ``SessionInterface`` implementation is supported. If you have your
- own implementation, type-hint this in the arguments instead.
+For more info, see :doc:`/session`.
.. index::
single: Session; Flash messages
+.. _flash-messages:
+
Flash Messages
~~~~~~~~~~~~~~
@@ -510,7 +437,7 @@ For example, imagine you're processing a :doc:`form ` submission::
use Symfony\Component\HttpFoundation\Request;
- public function updateAction(Request $request)
+ public function update(Request $request)
{
// ...
@@ -534,11 +461,12 @@ and then redirects. The message key (``notice`` in this example) can be anything
you'll use this key to retrieve the message.
In the template of the next page (or even better, in your base layout template),
-read any flash messages from the session using ``app.flashes()``:
+read any flash messages from the session using the ``flashes()`` method provided
+by the :ref:`Twig global app variable `:
.. code-block:: html+twig
- {# app/Resources/views/base.html.twig #}
+ {# templates/base.html.twig #}
{# read and display just one flash message type #}
{% for message in app.flashes('notice') %}
@@ -565,16 +493,9 @@ read any flash messages from the session using ``app.flashes()``:
{% endfor %}
{% endfor %}
-.. versionadded:: 3.3
-
- The ``app.flashes()`` Twig function was introduced in Symfony 3.3. Prior,
- you had to use ``app.session.flashBag()``.
-
-.. note::
-
- It's common to use ``notice``, ``warning`` and ``error`` as the keys of the
- different types of flash messages, but you can use any key that fits your
- needs.
+It's common to use ``notice``, ``warning`` and ``error`` as the keys of the
+different types of flash messages, but you can use any key that fits your
+needs.
.. tip::
@@ -590,13 +511,13 @@ read any flash messages from the session using ``app.flashes()``:
The Request and Response Object
-------------------------------
-As mentioned :ref:`earlier