Skip to content

Updates to DI config for 3.3 #7807

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
May 5, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8433fc1
[WIP] Updates to DI config for 3.3
weaverryan Apr 15, 2017
2d11347
more tweaks
weaverryan Apr 28, 2017
105801c
adding note about autoconfigure
weaverryan Apr 28, 2017
049df7d
Adding details and usages of fetching the service as a controller arg
weaverryan Apr 28, 2017
9e84572
last tweaks from feedback
weaverryan Apr 28, 2017
c45daf4
fixing build problem
weaverryan Apr 28, 2017
2636bea
bad link
weaverryan Apr 28, 2017
9ab27f0
Add xml files
GuilhemN Apr 28, 2017
0e48bd8
[WIP] Updates to DI config for 3.3
weaverryan Apr 15, 2017
6e6ed94
more tweaks
weaverryan Apr 28, 2017
70178d1
adding note about autoconfigure
weaverryan Apr 28, 2017
45500b3
Adding details and usages of fetching the service as a controller arg
weaverryan Apr 28, 2017
759e9b2
last tweaks from feedback
weaverryan Apr 28, 2017
6de83e2
fixing build problem
weaverryan Apr 28, 2017
89e12de
bad link
weaverryan Apr 28, 2017
443aec2
Merge pull request #7857 from GuilhemN/patch-1
weaverryan May 2, 2017
bc7088d
Merge remote-tracking branch 'origin/di-3.3-changes' into di-3.3-changes
weaverryan May 2, 2017
ee27765
Adding versionadded
weaverryan May 2, 2017
5452c61
Adding section about public: false
weaverryan May 2, 2017
2229fd3
Merge remote-tracking branch 'origin/master' into di-3.3-changes
weaverryan May 2, 2017
cac3c6c
Merge remote-tracking branch 'origin/master' into di-3.3-changes
weaverryan May 5, 2017
12c4944
Tweaks after amazing review from @GuilhemN and @xabbuh
weaverryan May 5, 2017
22adfbd
removing duplicate target
weaverryan May 5, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 137 additions & 62 deletions controller.rst
Original file line number Diff line number Diff line change
Expand Up @@ -116,29 +116,15 @@ For more information on routing, see :doc:`/routing`.
.. index::
single: Controller; Base controller class

.. _anchor-name:
:ref:`The Base Controller Classes & Services <the-base-controller-class-services>`
.. _the-base-controller-class-services:

The Base Controller Classes & Services
--------------------------------------

For convenience, Symfony comes with two optional base
:class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller` and
:class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController`
classes.
If you extend one or the other, this won't change anything about how your
controller works, but you'll get access to a number of **helper methods**.

The base ``Controller`` also allows you to access the **service container** (see :ref:`controller-accessing-services`): an
array-like object that gives you access to every useful object in the
system. These useful objects are called **services**, and Symfony ships
with a service object that can render Twig templates, another that can
log messages and many more.

On the other hand, the ``AbstractController`` prevents you from accessing the
**service container**. When you need an external dependency, this forces you to
write a code more robust as you have to explicitly define your dependencies by
using :doc:`the controller as a service </controller/service>`.
classes. You can extend either to get access to a number of `helper methods`_.

Add the ``use`` statement atop the ``Controller`` class and then modify
``LuckyController`` to extend it::
Expand All @@ -153,11 +139,19 @@ Add the ``use`` statement atop the ``Controller`` class and then modify
// ...
}

Helper methods are just shortcuts to using core Symfony functionality
that's available to you with or without the use of the base
controller classes. A great way to see the core functionality in
action is to look in the
:class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller` class.
That's it! You now have access to methods like :ref:`$this->render() <controller-rendering-templates>`
and many others that you'll learn about next.

.. tip::

You can extend either ``Controller`` or ``AbstractController``. The difference
is that when you extend ``AbstractController``, you can't access services directly
via ``$this->get()`` or ``$this->container->get()``. This forces you to write
more robust code to access services. But if you *do* need direct access to the
container, using ``Controller`` is fine.

.. versionadded:: 3.3
The ``AbstractController`` class was added in Symfony 3.3.

.. index::
single: Controller; Redirecting
Expand Down Expand Up @@ -244,15 +238,121 @@ The Symfony templating system and Twig are explained more in the

.. _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 added in Symfony 3.3.

Symfony comes *packed* with a lot of useful objects, called :doc:`services </service_container>`.
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
(or interface) name. Symfony will automatically pass you the service you need::

use Psr\Log\LoggerInterface
// ...

/**
* @Route("/lucky/number/{max}")
*/
public function numberAction($max, LoggerInterface $logger)
{
$logger->info('We are logging!');
// ...
}

Awesome!

What other services can you type-hint? To see them, use the ``debug:container`` console
command:

.. code-block:: terminal

$ php bin/console debug:container --types

Symfony comes packed with a lot of useful objects, called *services*. These
are used for rendering templates, sending emails, querying the database and
any other "work" you can think of. When you install a new bundle, it probably
brings in even *more* services.
If you need control over the *exact* value of an argument, you can override your
controller's service config:

When extending the base ``Controller`` class, you can access any Symfony service
.. configuration-block::

.. code-block:: yaml

# app/config/services.yml
services:
# ...

# explicitly configure the service
AppBundle\Controller\LuckyController:
public: true
tags:
# add multiple tags to controller multiple args
- name: controller.service_arguments
action: numberAction
argument: logger
# pass this specific service id
id: monolog.logger.doctrine
Copy link
Contributor

@GuilhemN GuilhemN May 2, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as a reminder for me, I think it could be nice to be able to use bindings here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also like bindings... we'll see how everything works out for in reality in Symfony 3.3 :). For the most part, bindings and arguments accomplish the same task (in a slightly different way). But it's interesting that bindings could also work to help with controller args - I hadn't really thought about that... seems like one use-case that makes bindings actually a bit better.


.. code-block:: xml

<!-- app/config/services.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">

<services>
<!-- ... -->

<!-- Explicitly configure the service -->
<service id="AppBundle\Controller\LuckyController" public="true">
<tag
name="controller.service_arguments"
action="numberAction"
argument="logger"
id="monolog.logger.doctrine"
/>
</service>
</services>
</container>

.. code-block:: php

// app/config/services.php
use AppBundle\Controller\LuckyController;

$container->register(LuckyController::class)
->setPublic(true)
->addTag('controller.service_arguments', [
'action' => 'numberAction',
'argument' => 'logger',
'id' => 'monolog.logger.doctrine',
])
;

You can of course also use normal :ref:`constructor injection <services-constructor-injection>`
in your controllers.

For more information about services, see the :doc:`/service_container` article.

.. note::
If this isn't working, make sure your controller is registered as a service,
is :ref:`autoconfigured <services-autoconfigure>` and extends either
:class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller` or
:class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController`. Or,
you can tag your service manually with ``controller.service_arguments``. All
of this is done for you in a fresh Symfony install.

.. _accessing-other-services:
.. _controller-access-services-directly:

Accessing the Container Directly
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If you extend the base ``Controller`` class, you can access any Symfony service
via the :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::get`
method. Here are several common services you might need::

Expand All @@ -262,23 +362,17 @@ method. Here are several common services you might need::

$mailer = $this->get('mailer');

What other services exist? To list all services, use the ``debug:container``
console command:

.. code-block:: terminal
// you can also fetch parameters
$someParameter = $this->getParameter('some_parameter');

$ php bin/console debug:container
If you receive an eror like:

For more information, see the :doc:`/service_container` article.

.. tip::
.. code-block:: text

To get a :ref:`container configuration parameter <config-parameter-intro>`,
use the
:method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::getParameter`
method::
You have requested a non-existent service "my_service_id"

$from = $this->getParameter('app.mailer.from');
Check to make sure the service exists (use :ref:`debug:container <container-debug-container>`)
and that it's :ref:`public <container-public>`.

.. index::
single: Controller; Managing errors
Expand Down Expand Up @@ -355,7 +449,6 @@ Symfony provides a nice session object that you can use to store information
about the user between requests. By default, Symfony stores the attributes in a
cookie by using native PHP sessions.


.. versionadded:: 3.3
The ability to request a ``Session`` instance in controllers was introduced
in Symfony 3.3.
Expand Down Expand Up @@ -418,20 +511,6 @@ For example, imagine you're processing a :doc:`form </forms>` submission::
return $this->render(...);
}

.. tip::

As a developer, you might prefer not to extend the ``Controller``. To
use the flash message functionality, you can request the flash bag from
the :class:`Symfony\\Component\\HttpFoundation\\Session\\Session`::
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this also be removed in the controller as a service article then?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to re-read and re-think that article in its entirety... since controllers as services are a first-class citizen (does it make sense to still have a separate article?)


use Symfony\Component\HttpFoundation\Session\Session;

public function indexAction(Session $session)
{
// getFlashBag is not available in the SessionInterface and requires the Session
$flashBag = $session->getFlashBag();
}

After processing the request, the controller sets a flash message in the session
and then redirects. The message key (``notice`` in this example) can be anything:
you'll use this key to retrieve the message.
Expand Down Expand Up @@ -636,12 +715,7 @@ and it's a PHP function where you can do anything in order to return the
final ``Response`` object that will be returned to the user.

To make life easier, you'll probably extend the base ``Controller`` class because
this gives two things:

A) Shortcut methods (like ``render()`` and ``redirectToRoute()``);

B) Access to *all* of the useful objects (services) in the system via the
:ref:`get() <controller-accessing-services>` method.
this gives access to shortcut methods (like ``render()`` and ``redirectToRoute()``).

In other articles, you'll learn how to use specific services from inside your controller
that will help you persist and fetch objects from a database, process form submissions,
Expand All @@ -666,4 +740,5 @@ Learn more about Controllers

controller/*

.. _`helper methods`: https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about document the trait in the reference section instead?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why, but the API docs for the trait are terrible, basically empty :/. It's actually a problem because I would like to link to some methods in the trait via :method in a few places - http://api.symfony.com/master/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.html

.. _`unvalidated redirects security vulnerability`: https://www.owasp.org/index.php/Open_redirect
7 changes: 7 additions & 0 deletions doctrine.rst
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,7 @@ a controller, this is pretty easy. Add the following method to the
// ...
use AppBundle\Entity\Product;
use Symfony\Component\HttpFoundation\Response;
use Doctrine\ORM\EntityManagerInterface;

// ...
public function createAction()
Expand All @@ -568,6 +569,12 @@ a controller, this is pretty easy. Add the following method to the
return new Response('Saved new product with id '.$product->getId());
}

// you can also receive the $em as an argument
public function editAction(EntityManagerInterface $em)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also add this to the Fetching Objects from the Database section? It doesn't seem obvious to me.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll update the whole Doctrine section in another PR with more of this fancy type-based stuff :)

{
// ...
}

.. note::

If you're following along with this example, you'll need to create a
Expand Down
8 changes: 6 additions & 2 deletions email.rst
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ The Swift Mailer library works by creating, configuring and then sending
of the message and is accessible via the ``mailer`` service. Overall, sending
an email is pretty straightforward::

public function indexAction($name)
public function indexAction($name, \Swift_Mailer $mailer)
{
$message = \Swift_Message::newInstance()
->setSubject('Hello Email')
Expand All @@ -125,7 +125,11 @@ an email is pretty straightforward::
)
*/
;
$this->get('mailer')->send($message);

$mailer->send($message);

// or, you can also fetch the mailer service this way
// $this->get('mailer')->send($message);

return $this->render(...);
}
Expand Down
8 changes: 6 additions & 2 deletions logging.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ Logging a Message
To log a message, fetch the ``logger`` service from the container in
your controller::

public function indexAction()
use Psr\Log\LoggerInterface;

public function indexAction(LoggerInterface $logger)
{
$logger = $this->get('logger');
// alternative way of getting the logger
// $logger = $this->get('logger');

$logger->info('I just got the logger');
$logger->error('An error occurred');

Expand Down
Loading