From 8433fc14063621147646ac483f1e0b61f933c096 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sat, 15 Apr 2017 13:14:45 -0400 Subject: [PATCH 01/19] [WIP] Updates to DI config for 3.3 --- service_container.rst | 296 ++++++++++++++++++++++-------------------- 1 file changed, 157 insertions(+), 139 deletions(-) diff --git a/service_container.rst b/service_container.rst index 934b08485ea..25eb3441c8e 100644 --- a/service_container.rst +++ b/service_container.rst @@ -133,68 +133,61 @@ the service container *how* to instantiate it: # app/config/services.yml services: - app.message_generator: - class: AppBundle\Service\MessageGenerator - arguments: [] + # configures defaults for all services in this file + _defaults: + autowire: true + autoconfigure: true + + # registers all classes in the dir(s) as services + AppBundle\: + resource: '../../src/AppBundle/{Service}' .. code-block:: xml - - - - - - - - + TODO .. code-block:: php // app/config/services.php - use AppBundle\Service\MessageGenerator; - use Symfony\Component\DependencyInjection\Definition; + TODO - $container->setDefinition('app.message_generator', new Definition( - MessageGenerator::class, - array() - )); +That's it! Thanks to the ``AppBundle\`` line and ``resource`` key below it, all +classes in the ``src/AppBundle/Service`` directory will automatically be added to +the container. Each service's "key" is simply its class name. You can use it immediately +inside your controller:: -That's it! Your service - with the unique key ``app.message_generator`` - is now -available in the container. You can use it immediately inside your controller:: + use AppBundle\Service\MessageGenerator; public function newAction() { // ... // the container will instantiate a new MessageGenerator() - $messageGenerator = $this->container->get('app.message_generator'); + $messageGenerator = $this->container->get(MessageGenerator::class); // or use this shorter synax - // $messageGenerator = $this->get('app.message_generator'); + // $messageGenerator = $this->get(MessageGenerator::class); $message = $messageGenerator->getHappyMessage(); $this->addFlash('success', $message); // ... } -When you ask for the ``app.message_generator`` service, the container constructs +When you ask for the ``MessageGenerator::class`` service, the container constructs a new ``MessageGenerator`` object and returns it. If you never ask for the -``app.message_generator`` service during a request, it's *never* constructed, saving +``MessageGenerator::class`` service during a request, it's *never* constructed, saving you memory and increasing the speed of your app. This also means that there's almost no performance overhead for defining a lot of services. -As a bonus, the ``app.message_generator`` service is only created *once*: the same +As a bonus, the ``MessageGenerator::class`` service is only created *once*: the same instance is returned each time you ask for it. .. caution:: - Service ids are case-insensitive (e.g. ``app.message_generator`` and ``APP.Message_Generator`` - refer to the same service). But this was deprecated in Symfony 3.3. Starting - in 4.0, service ids will be case sensitive. + Service ids are case-insensitive (e.g. ``AppBundle\Service\MessageGenerator`` + and ``appbundle\service\messagegenerator`` refer to the same service). But this + was deprecated in Symfony 3.3. Starting in 4.0, service ids will be case sensitive. Injecting Services/Config into a Service ---------------------------------------- @@ -227,13 +220,64 @@ and set it on a ``$logger`` property:: } } +That's it! The container will *automatically* know to pass the ``logger`` service +when instantiating the ``MessageGenerator``? How does it know to do this? The key +is the ``LoggerInterface`` type-hint in your ``__construct()`` method and the +``autowire: true`` config in ``services.yml``. When you type-hint an argument, the +container will automatically find the matching service. If it can't or there is any +ambuiguity, you'll see a clear exception suggesting how to fix it. + +Be sure to read more about :doc:`autowiring `. + .. tip:: - The ``LoggerInterface`` type-hint in the ``__construct()`` method is optional, - but a good idea. You can find the correct type-hint by reading the docs for the - service or by using the ``php bin/console debug:container`` console command. + How do you know to use ``LoggerInterface`` for the type-hint? The best way to + know this is by reading the docs for whatever service you're using. You can + also use the ``php bin/console debug:container`` console command to get a hint + to the class name for a service. + +Handling Multiple Services +-------------------------- -Next, tell the container the service has a constructor argument: +Suppose you also want to email some site administrator each time a site update is +made. To do that, you create a new class:: + + // src/AppBundle/Updates/SiteUpdateManager.php + namespace AppBundle\Updates; + + use AppBundle\Service\MessageGenerator; + + class SiteUpdateManager + { + private $messageGenerator; + private $mailer; + + public function __construct(MessageGenerator $messageGenerator, \Swift_Mailer $mailer) + { + $this->messageGenerator = $messageGenerator; + $this->mailer = $mailer; + } + + public function notifyOfSiteUpdate() + { + $happyMessage = $this->messageGenerator->getHappyMessage(); + + $message = \Swift_Message::newInstance() + ->setSubject('Site update just happened!') + ->setFrom('admin@example.com') + ->setTo('manager@example.com') + ->addPart( + 'Someone just updated the site. We told them: '.$happyMessage + ); + $this->mailer->send($message); + + return $message; + } + } + +This uses the ``MessageGenerator`` *and* the ``Swift_Mailer`` service. To register +this as a new service in the container, simply tell your configuration to load from +the new ``Updates`` sub-directory: .. configuration-block:: @@ -241,75 +285,86 @@ Next, tell the container the service has a constructor argument: # app/config/services.yml services: - app.message_generator: - class: AppBundle\Service\MessageGenerator - arguments: ['@logger'] + # ... + + # registers all classes in Services & Updates directories + AppBundle\: + resource: '../../src/AppBundle/{Service,Updates}' .. code-block:: xml - - - - - - - - - + TODO .. code-block:: php // app/config/services.php - use AppBundle\Service\MessageGenerator; - use Symfony\Component\DependencyInjection\Definition; - use Symfony\Component\DependencyInjection\Reference; + TODO - $container->setDefinition('app.message_generator', new Definition( - MessageGenerator::class, - array(new Reference('logger')) - )); +Now, you can use the service immediately:: -That's it! The container now knows to pass the ``logger`` service as an argument -when it instantiates the ``MessageGenerator``. This is called dependency injection. + use AppBundle\Updates\SiteUpdateManager; -The ``arguments`` key holds an array of all of the constructor arguments to the -service (just 1 so far). The ``@`` symbol before ``@logger`` is important: it tells -Symfony to pass the *service* named ``logger``. + public function newAction() + { + // ... -But you can pass anything as arguments. For example, suppose you want to make your -class a bit more configurable:: + $siteUpdateManager = $this->container->get(SiteUpdateManager::class); - // src/AppBundle/Service/MessageGenerator.php - // ... + $message = $siteUpdateManager->notifyOfSiteUpdate(); + $this->addFlash('success', $message); + // ... + } - use Psr\Log\LoggerInterface; +Just like before, when you ask for the ``SiteUpdateManager`` service, the container +will automatically instantiate it for you. By reading the type-hints on the ``__construct()`` +method in that class, it takes care of passing the correct services as arguments. +All of that is taken care of for you. - class MessageGenerator +Manually Wiring Arguments +------------------------- + +There are a few cases when an argument to a service cannot be autowired. For example, +suppose you want to make the admin email configurable: + +.. code-block:: diff + + // src/AppBundle/Updates/SiteUpdateManager.php + // ... + + class SiteUpdateManager { - private $logger; - private $loggingEnabled; + // ... + + private $adminEmail; - public function __construct(LoggerInterface $logger, $loggingEnabled) + - public function __construct(MessageGenerator $messageGenerator, \Swift_Mailer $mailer) + + public function __construct(MessageGenerator $messageGenerator, \Swift_Mailer $mailer, $adminEmail) { - $this->logger = $logger; - $this->loggingEnabled = $loggingEnabled; + // ... + + $this->adminEmail = $adminEmail; } - public function getHappyMessage() + public function notifyOfSiteUpdate() { - if ($this->loggingEnabled) { - $this->logger->info('About to find a happy message!'); - } + // ... + + $message = \Swift_Message::newInstance() + // ... + - ->setTo('manager@example.com') + + ->setTo($this->adminEmail) + // ... + ; // ... } } -The class now has a *second* constructor argument. No problem, just update your -service config: +If you make this change and refresh, you'll see an error: + + Cannot autowire service "AppBundle\Updates\SiteUpdateManager": argument "$adminEmail" + of method "__construct()" must have a type-hint or be given a value explicitly. + +That makes sense! There is no way that the container knows what value you want to +pass here. No problem! In your configuration, you can explicitly set this argument: .. configuration-block:: @@ -317,41 +372,29 @@ service config: # app/config/services.yml services: - app.message_generator: - class: AppBundle\Service\MessageGenerator - arguments: ['@logger', true] + # ... + + # same as before + AppBundle\: + resource: '../../src/AppBundle/{Service,Updates}' + + # explicitly configure the service + AppBundle\Updates\SiteUpdateManager: + arguments: + $adminEmail: 'manager@example.com' .. code-block:: xml - - - - - - - true - - - + TODO .. code-block:: php // app/config/services.php - use AppBundle\Service\MessageGenerator; - use Symfony\Component\DependencyInjection\Definition; - use Symfony\Component\DependencyInjection\Reference; + TODO - $container->setDefinition('app.message_generator', new Definition( - MessageGenerator::class, - array(new Reference('logger'), true) - )); - -You can even leverage :doc:`environments ` to control -this new value in different situations. +Thanks to this, the container will pass ``manager@example.com`` as the third argument +to ``__construct`` when creating the ``SiteUpdateManager`` service. .. _service-container-parameters: @@ -368,47 +411,22 @@ and reference it with the ``%parameter_name%`` syntax: # app/config/services.yml parameters: - enable_generator_logging: true + admin_email: manager@example.com services: - app.message_generator: - class: AppBundle\Service\MessageGenerator - arguments: ['@logger', '%enable_generator_logging%'] + # ... + + AppBundle\Updates\SiteUpdateManager: + arguments: + $adminEmail: '%admin_email%' .. code-block:: xml - - - - - - - true - - - - - %enable_generator_logging% - - - + TODO .. code-block:: php - // app/config/services.php - use AppBundle\Service\MessageGenerator; - use Symfony\Component\DependencyInjection\Definition; - use Symfony\Component\DependencyInjection\Reference; - - $container->setParameter('enable_generator_logging', true); - - $container->setDefinition('app.message_generator', new Definition( - MessageGenerator::class, - array(new Reference('logger'), '%enable_generator_logging%') - )); + TODO Actually, once you define a parameter, it can be referenced via the ``%parameter_name%`` syntax in *any* other service configuration file - like ``config.yml``. Many parameters @@ -420,8 +438,8 @@ You can also fetch parameters directly from the container:: { // ... - $isLoggingEnabled = $this->container - ->getParameter('enable_generator_logging'); + $adminEmail = $this->container + ->getParameter('admin_email'); // ... } From 2d1134791115e21ad52db21521b58687c4a698de Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Fri, 28 Apr 2017 12:27:52 -0400 Subject: [PATCH 02/19] more tweaks --- service_container.rst | 146 ++++++++++++++++++++++++++++-------------- 1 file changed, 98 insertions(+), 48 deletions(-) diff --git a/service_container.rst b/service_container.rst index 25eb3441c8e..43b29b3692c 100644 --- a/service_container.rst +++ b/service_container.rst @@ -6,7 +6,7 @@ Service Container ================= Your application is *full* of useful objects: one "Mailer" object might help you -deliver email messages while another object might help you save things to the database. +send email messages while another object might help you save things to the database. Almost *everything* that your app "does" is actually done by one of these objects. And each time you install a new bundle, you get access to even more! @@ -17,21 +17,13 @@ then you can fetch a service by using that service's id:: $logger = $container->get('logger'); $entityManager = $container->get('doctrine.entity_manager'); -The container is the *heart* of Symfony: it allows you to standardize and centralize -the way objects are constructed. It makes your life easier, is super fast, and emphasizes -an architecture that promotes reusable and decoupled code. It's also a big reason -that Symfony is so fast and extensible! - -Finally, configuring and using the service container is easy. By the end -of this article, you'll be comfortable creating your own objects via the -container and customizing objects from any third-party bundle. You'll begin -writing code that is more reusable, testable and decoupled, simply because -the service container makes writing good code so easy. +The container allows you to centralize the way objects are constructed. It makes +your life easier, promotes a strong architecture and is super fast! Fetching and using Services --------------------------- -The moment you start a Symfony app, the container *already* contains many services. +The moment you start a Symfony app, your container *already* contains many services. These are like *tools*, waiting for you to take advantage of them. In your controller, you have access to the container via ``$this->container``. Want to :doc:`log ` something? No problem:: @@ -86,11 +78,10 @@ in the container. .. sidebar:: Container: Lazy-loaded for speed - If the container holds so many useful objects (services), does that mean those - objects are instantiated on *every* request? No! The container is lazy: it doesn't - instantiate a service until (and unless) you ask for it. For example, if you - never use the ``validator`` service during a request, the container will never - instantiate it. + Wait! Are all the services (objects) instantiated on *every* request? No! The + container is lazy: it doesn't instantiate a service until (and unless) you ask + for it. For example, if you never use the ``validator`` service during a request, + the container will never instantiate it. .. index:: single: Service Container; Configuring services @@ -100,10 +91,9 @@ in the container. Creating/Configuring Services in the Container ---------------------------------------------- -You can also leverage the container to organize your *own* code into services. For -example, suppose you want to show your users a random, happy message every time -they do something. If you put this code in your controller, it can't be re-used. -Instead, you decide to create a new class:: +You can also organize your *own* code into services. For example, suppose you need +to show your users a random, happy message. If you put this code in your controller, +it can't be re-used. Instead, you decide to create a new class:: // src/AppBundle/Service/MessageGenerator.php namespace AppBundle\Service; @@ -133,14 +123,13 @@ the service container *how* to instantiate it: # app/config/services.yml services: - # configures defaults for all services in this file _defaults: autowire: true autoconfigure: true - # registers all classes in the dir(s) as services + # load services from whatever directories you want (you can update this!) AppBundle\: - resource: '../../src/AppBundle/{Service}' + resource: '../../src/AppBundle/{Service,EventDispatcher,Twig,Form}' .. code-block:: xml @@ -153,9 +142,10 @@ the service container *how* to instantiate it: TODO That's it! Thanks to the ``AppBundle\`` line and ``resource`` key below it, all -classes in the ``src/AppBundle/Service`` directory will automatically be added to -the container. Each service's "key" is simply its class name. You can use it immediately -inside your controller:: +classes in the ``src/AppBundle/Service`` directory (and a few other directories) +will automatically be added to the container. + +Each service's "key" is its class name. You can use it immediately inside your controller:: use AppBundle\Service\MessageGenerator; @@ -175,10 +165,8 @@ inside your controller:: } When you ask for the ``MessageGenerator::class`` service, the container constructs -a new ``MessageGenerator`` object and returns it. If you never ask for the -``MessageGenerator::class`` service during a request, it's *never* constructed, saving -you memory and increasing the speed of your app. This also means that there's almost -no performance overhead for defining a lot of services. +a new ``MessageGenerator`` object and returns it. But if you never ask for the service, +it's *never* constructed: saving memory and speed. As a bonus, the ``MessageGenerator::class`` service is only created *once*: the same instance is returned each time you ask for it. @@ -221,25 +209,26 @@ and set it on a ``$logger`` property:: } That's it! The container will *automatically* know to pass the ``logger`` service -when instantiating the ``MessageGenerator``? How does it know to do this? The key -is the ``LoggerInterface`` type-hint in your ``__construct()`` method and the -``autowire: true`` config in ``services.yml``. When you type-hint an argument, the -container will automatically find the matching service. If it can't or there is any -ambuiguity, you'll see a clear exception suggesting how to fix it. +when instantiating the ``MessageGenerator``? How does it know to do this? +:doc:`Autowiring `. The key is the ``LoggerInterface`` +type-hint in your ``__construct()`` method and the ``autowire: true`` config in +``services.yml``. When you type-hint an argument, the container will automatically +find the matching service. If it can't or there is any ambuiguity, you'll see a clear +exception suggesting how to fix it. Be sure to read more about :doc:`autowiring `. .. tip:: - How do you know to use ``LoggerInterface`` for the type-hint? The best way to - know this is by reading the docs for whatever service you're using. You can - also use the ``php bin/console debug:container`` console command to get a hint + How should you know to use ``LoggerInterface`` for the type-hint? The best way + is by reading the docs for whatever feature you're using. You can also use the + ``php bin/console debug:container`` console command to get a hint to the class name for a service. Handling Multiple Services -------------------------- -Suppose you also want to email some site administrator each time a site update is +Suppose you also want to email a site administrator each time a site update is made. To do that, you create a new class:: // src/AppBundle/Updates/SiteUpdateManager.php @@ -289,7 +278,7 @@ the new ``Updates`` sub-directory: # registers all classes in Services & Updates directories AppBundle\: - resource: '../../src/AppBundle/{Service,Updates}' + resource: '../../src/AppBundle/{Service,Updates,EventDispatcher,Twig,Form}' .. code-block:: xml @@ -316,16 +305,15 @@ Now, you can use the service immediately:: // ... } -Just like before, when you ask for the ``SiteUpdateManager`` service, the container -will automatically instantiate it for you. By reading the type-hints on the ``__construct()`` -method in that class, it takes care of passing the correct services as arguments. -All of that is taken care of for you. +Thanks to autowiring and your type-hints in ``__construct()``, the container creates +the ``SiteUpdateManager`` object and passes it the correct arguments. In most cases, +this works perfectly. Manually Wiring Arguments ------------------------- -There are a few cases when an argument to a service cannot be autowired. For example, -suppose you want to make the admin email configurable: +But there are a few cases when an argument to a service cannot be autowired. For +example, suppose you want to make the admin email configurable: .. code-block:: diff @@ -394,7 +382,8 @@ pass here. No problem! In your configuration, you can explicitly set this argume TODO Thanks to this, the container will pass ``manager@example.com`` as the third argument -to ``__construct`` when creating the ``SiteUpdateManager`` service. +to ``__construct`` when creating the ``SiteUpdateManager`` service. The other arguments +will still be autowired. .. _service-container-parameters: @@ -460,6 +449,67 @@ You can also fetch parameters directly from the container:: For more info about parameters, see :doc:`/service_container/parameters`. +Choose a Specific Service +------------------------- + +The ``MessageGenerator`` service created earlier requires a ``LoggerInterface`` argument:: + + // src/AppBundle/Service/MessageGenerator.php + // ... + + use Psr\Log\LoggerInterface; + + class MessageGenerator + { + private $logger; + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } + // ... + } + +However, there are *multiple* services in the container that implement ``LoggerInterface``, +such as ``logger``, ``monolog.logger.request``, ``monolog.logger.php``, etc. How +does the container know which one to use? + +In these situations, the container is usually configured to automatically choose +one of the services - ``logger`` in this case (read more about why in :ref:`service-autowiring-alias`). +But, you can control this and pass in a different logger: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/services.yml + services: + # ... same code as before + + # explicitly configure the service + AppBundle\Service\MessageGenerator: + arguments: + $logger: '@monolog.logger.request' + + .. code-block:: xml + + + TODO + + .. code-block:: php + + // app/config/services.php + TODO + +This tells the container that the ``$logger`` argument to ``_construct`` should use +service whose id is ``monolog.logger.request``. + +.. tip:: + + The ``@`` symbol is important: that's what tells the container you want to pass + the *service* whose id is ``monolog.logger.request``, and not just the *string* + ``monolog.logger.request``. + Learn more ---------- From 105801c521eb71e8640402abb2c46198007d338c Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Fri, 28 Apr 2017 12:37:20 -0400 Subject: [PATCH 03/19] adding note about autoconfigure --- service_container.rst | 65 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/service_container.rst b/service_container.rst index 43b29b3692c..67867c0ba38 100644 --- a/service_container.rst +++ b/service_container.rst @@ -123,6 +123,7 @@ the service container *how* to instantiate it: # app/config/services.yml services: + # default configuration for services in *this* file _defaults: autowire: true autoconfigure: true @@ -510,6 +511,70 @@ service whose id is ``monolog.logger.request``. the *service* whose id is ``monolog.logger.request``, and not just the *string* ``monolog.logger.request``. +The autoconfigure Option +------------------------ + +Above, we've set ``autoconfigure: true`` in the ``_defaults`` section so that it +applies to all services defined in that file. With this setting, the container will +automatically apply certain configuration to your services, based on your service's +*class*. The is mostly used to *auto-tag* your services. + +For example, to create a Twig Extension, you need to create a class, register it +as a service, and :doc:`tag ` it with ``twig.extension``: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/services.yml + services: + # ... + + AppBundle\Twig\MyTwigExtension: + tags: [twig.extension] + + .. code-block:: xml + + + TODO + + .. code-block:: php + + // app/config/services.php + TODO + +But, with ``autoconfigure: true``, you don't need the tag. In fact, all you need +is this: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/services.yml + services: + _defaults: + autowire: true + autoconfigure: true + + # load your service from the Twig directory + AppBundle\: + resource: '../../src/AppBundle/{Service,EventDispatcher,Twig,Form}' + + .. code-block:: xml + + + TODO + + .. code-block:: php + + // app/config/services.php + TODO + +That's it! The container will find your class in the ``Twig/`` directory and register +it as a service. Then ``autoconfigure`` will add the ``twig.extension`` tag *for* +you, because your class implements ``Twig_ExtensionInterface``. And thanks to ``autowire``, +you can even add ``__construct()`` arguments without any configuration. + Learn more ---------- From 049df7d38732f4c16fe91232f906a41a5fbf8144 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Fri, 28 Apr 2017 13:18:54 -0400 Subject: [PATCH 04/19] Adding details and usages of fetching the service as a controller arg --- controller.rst | 60 +++++++++++++++++++++++++------------------ doctrine.rst | 7 +++++ email.rst | 8 ++++-- logging.rst | 8 ++++-- service_container.rst | 2 ++ 5 files changed, 56 insertions(+), 29 deletions(-) diff --git a/controller.rst b/controller.rst index efba9be7e27..cea9d1bf662 100644 --- a/controller.rst +++ b/controller.rst @@ -148,6 +148,11 @@ that's available to you with or without the use of the base action is to look in the :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller` class. +.. tip:: + If you know what you're doing, you can alternatively extend + :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController`. It + has all the same shortcuts, but does not have a ```$this->container`` property. + .. index:: single: Controller; Redirecting @@ -236,12 +241,11 @@ The Symfony templating system and Twig are explained more in the Accessing other Services ~~~~~~~~~~~~~~~~~~~~~~~~ -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. +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. -When extending the base controller class, you can access any Symfony service +When extending the base ``Controller`` class, you can access any Symfony service via the :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::get` method of the ``Controller`` class. Here are several common services you might need:: @@ -252,6 +256,9 @@ need:: $mailer = $this->get('mailer'); + // you can also fetch parameters + $someParameter = $this->getParameter('some_parameter'); + What other services exist? To list all services, use the ``debug:container`` console command: @@ -261,14 +268,31 @@ console command: For more information, see the :doc:`/service_container` article. -.. tip:: +Services as Controller Arguments +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can also tell Symfony to pass your a service as a controller argument by type-hinting +it:: - To get a :ref:`container configuration parameter `, - use the - :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::getParameter` - method:: + use Psr\Log\LoggerInterface + // ... + + /** + * @Route("/lucky/number/{max}") + */ + public function numberAction($max, LoggerInterface $logger) + { + $logger->info('We are logging!'); - $from = $this->getParameter('app.mailer.from'); + // ... + } + +.. note:: + If this isn't working, make sure your controller is registered as a service, + :ref:`autoconfigured ` and extends either + :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller` or + :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController`. Or, + you can tag it manually with ``controller.service_arguments``. .. index:: single: Controller; Managing errors @@ -407,20 +431,6 @@ For example, imagine you're processing a :doc:`form ` 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`:: - - 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. diff --git a/doctrine.rst b/doctrine.rst index 398aa521b02..29c9fcedca1 100644 --- a/doctrine.rst +++ b/doctrine.rst @@ -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() @@ -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) + { + // ... + } + .. note:: If you're following along with this example, you'll need to create a diff --git a/email.rst b/email.rst index eabb1a155af..593cfa22151 100644 --- a/email.rst +++ b/email.rst @@ -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') @@ -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 in this way + // $this->get('mailer')->send($message); return $this->render(...); } diff --git a/logging.rst b/logging.rst index 0c7ebd789c9..e46398b44de 100644 --- a/logging.rst +++ b/logging.rst @@ -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'); diff --git a/service_container.rst b/service_container.rst index 67867c0ba38..85b02d0a56e 100644 --- a/service_container.rst +++ b/service_container.rst @@ -511,6 +511,8 @@ service whose id is ``monolog.logger.request``. the *service* whose id is ``monolog.logger.request``, and not just the *string* ``monolog.logger.request``. +.. _services-autoconfigure: + The autoconfigure Option ------------------------ From 9e845728745419d8ffab4b8abad6f87149d94d38 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Fri, 28 Apr 2017 13:22:40 -0400 Subject: [PATCH 05/19] last tweaks from feedback --- service_container.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/service_container.rst b/service_container.rst index 85b02d0a56e..4c8078ee188 100644 --- a/service_container.rst +++ b/service_container.rst @@ -142,9 +142,9 @@ the service container *how* to instantiate it: // app/config/services.php TODO -That's it! Thanks to the ``AppBundle\`` line and ``resource`` key below it, all -classes in the ``src/AppBundle/Service`` directory (and a few other directories) -will automatically be added to the container. +That's it! Thanks to the ``AppBundle\`` line and ``resource`` key below it, a service +will be registered for each class in the ``src/AppBundle/Service`` directory (and +the other directories listed). Each service's "key" is its class name. You can use it immediately inside your controller:: @@ -210,11 +210,11 @@ and set it on a ``$logger`` property:: } That's it! The container will *automatically* know to pass the ``logger`` service -when instantiating the ``MessageGenerator``? How does it know to do this? +when instantiating the ``MessageGenerator``. How does it know to do this? :doc:`Autowiring `. The key is the ``LoggerInterface`` type-hint in your ``__construct()`` method and the ``autowire: true`` config in ``services.yml``. When you type-hint an argument, the container will automatically -find the matching service. If it can't or there is any ambuiguity, you'll see a clear +find the matching service. If it can't or there is any ambiguity, you'll see a clear exception suggesting how to fix it. Be sure to read more about :doc:`autowiring `. From c45daf4223691c6f921c41e33b9f23e5d1d30953 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Fri, 28 Apr 2017 13:42:31 -0400 Subject: [PATCH 06/19] fixing build problem --- service_container/autowiring.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/service_container/autowiring.rst b/service_container/autowiring.rst index c113a02fbac..2f1ed9c944b 100644 --- a/service_container/autowiring.rst +++ b/service_container/autowiring.rst @@ -224,6 +224,8 @@ the ``TransformerInterface`` and injects it automatically. Even when using interfaces (and you should), building the service graph and refactoring the project is easier than with standard definitions. +.. _service-autowiring-alias: + Dealing with Multiple Implementations of the Same Type ------------------------------------------------------ From 2636bea4550d8a8254f167c63e55604d589817f7 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Fri, 28 Apr 2017 13:48:47 -0400 Subject: [PATCH 07/19] bad link --- service_container.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service_container.rst b/service_container.rst index 4c8078ee188..14150161afa 100644 --- a/service_container.rst +++ b/service_container.rst @@ -522,7 +522,7 @@ automatically apply certain configuration to your services, based on your servic *class*. The is mostly used to *auto-tag* your services. For example, to create a Twig Extension, you need to create a class, register it -as a service, and :doc:`tag ` it with ``twig.extension``: +as a service, and :doc:`tag ` it with ``twig.extension``: .. configuration-block:: From 9ab27f03a263c207031b14b235b34ef0785825da Mon Sep 17 00:00:00 2001 From: Guilhem Niot Date: Fri, 28 Apr 2017 22:52:53 +0200 Subject: [PATCH 08/19] Add xml files --- service_container.rst | 113 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 106 insertions(+), 7 deletions(-) diff --git a/service_container.rst b/service_container.rst index 14150161afa..88d5990d849 100644 --- a/service_container.rst +++ b/service_container.rst @@ -135,7 +135,20 @@ the service container *how* to instantiate it: .. code-block:: xml - TODO + + + + + + + + + + + .. code-block:: php @@ -284,7 +297,19 @@ the new ``Updates`` sub-directory: .. code-block:: xml - TODO + + + + + + + + + + .. code-block:: php @@ -375,7 +400,24 @@ pass here. No problem! In your configuration, you can explicitly set this argume .. code-block:: xml - TODO + + + + + + + + + + + + %admin_email% + + + .. code-block:: php @@ -412,7 +454,25 @@ and reference it with the ``%parameter_name%`` syntax: .. code-block:: xml - TODO + + + + + + manager@example.com + + + + + + + %admin_email% + + + .. code-block:: php @@ -495,7 +555,21 @@ But, you can control this and pass in a different logger: .. code-block:: xml - TODO + + + + + + + + + + + + .. code-block:: php @@ -538,7 +612,20 @@ as a service, and :doc:`tag ` it with ``twig.extension` .. code-block:: xml - TODO + + + + + + + + + + + .. code-block:: php @@ -565,7 +652,19 @@ is this: .. code-block:: xml - TODO + + + + + + + + + + .. code-block:: php From 0e48bd872931e5b71f138d42aa128b53c22e7d30 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sat, 15 Apr 2017 13:14:45 -0400 Subject: [PATCH 09/19] [WIP] Updates to DI config for 3.3 --- service_container.rst | 296 ++++++++++++++++++++++-------------------- 1 file changed, 157 insertions(+), 139 deletions(-) diff --git a/service_container.rst b/service_container.rst index 934b08485ea..25eb3441c8e 100644 --- a/service_container.rst +++ b/service_container.rst @@ -133,68 +133,61 @@ the service container *how* to instantiate it: # app/config/services.yml services: - app.message_generator: - class: AppBundle\Service\MessageGenerator - arguments: [] + # configures defaults for all services in this file + _defaults: + autowire: true + autoconfigure: true + + # registers all classes in the dir(s) as services + AppBundle\: + resource: '../../src/AppBundle/{Service}' .. code-block:: xml - - - - - - - - + TODO .. code-block:: php // app/config/services.php - use AppBundle\Service\MessageGenerator; - use Symfony\Component\DependencyInjection\Definition; + TODO - $container->setDefinition('app.message_generator', new Definition( - MessageGenerator::class, - array() - )); +That's it! Thanks to the ``AppBundle\`` line and ``resource`` key below it, all +classes in the ``src/AppBundle/Service`` directory will automatically be added to +the container. Each service's "key" is simply its class name. You can use it immediately +inside your controller:: -That's it! Your service - with the unique key ``app.message_generator`` - is now -available in the container. You can use it immediately inside your controller:: + use AppBundle\Service\MessageGenerator; public function newAction() { // ... // the container will instantiate a new MessageGenerator() - $messageGenerator = $this->container->get('app.message_generator'); + $messageGenerator = $this->container->get(MessageGenerator::class); // or use this shorter synax - // $messageGenerator = $this->get('app.message_generator'); + // $messageGenerator = $this->get(MessageGenerator::class); $message = $messageGenerator->getHappyMessage(); $this->addFlash('success', $message); // ... } -When you ask for the ``app.message_generator`` service, the container constructs +When you ask for the ``MessageGenerator::class`` service, the container constructs a new ``MessageGenerator`` object and returns it. If you never ask for the -``app.message_generator`` service during a request, it's *never* constructed, saving +``MessageGenerator::class`` service during a request, it's *never* constructed, saving you memory and increasing the speed of your app. This also means that there's almost no performance overhead for defining a lot of services. -As a bonus, the ``app.message_generator`` service is only created *once*: the same +As a bonus, the ``MessageGenerator::class`` service is only created *once*: the same instance is returned each time you ask for it. .. caution:: - Service ids are case-insensitive (e.g. ``app.message_generator`` and ``APP.Message_Generator`` - refer to the same service). But this was deprecated in Symfony 3.3. Starting - in 4.0, service ids will be case sensitive. + Service ids are case-insensitive (e.g. ``AppBundle\Service\MessageGenerator`` + and ``appbundle\service\messagegenerator`` refer to the same service). But this + was deprecated in Symfony 3.3. Starting in 4.0, service ids will be case sensitive. Injecting Services/Config into a Service ---------------------------------------- @@ -227,13 +220,64 @@ and set it on a ``$logger`` property:: } } +That's it! The container will *automatically* know to pass the ``logger`` service +when instantiating the ``MessageGenerator``? How does it know to do this? The key +is the ``LoggerInterface`` type-hint in your ``__construct()`` method and the +``autowire: true`` config in ``services.yml``. When you type-hint an argument, the +container will automatically find the matching service. If it can't or there is any +ambuiguity, you'll see a clear exception suggesting how to fix it. + +Be sure to read more about :doc:`autowiring `. + .. tip:: - The ``LoggerInterface`` type-hint in the ``__construct()`` method is optional, - but a good idea. You can find the correct type-hint by reading the docs for the - service or by using the ``php bin/console debug:container`` console command. + How do you know to use ``LoggerInterface`` for the type-hint? The best way to + know this is by reading the docs for whatever service you're using. You can + also use the ``php bin/console debug:container`` console command to get a hint + to the class name for a service. + +Handling Multiple Services +-------------------------- -Next, tell the container the service has a constructor argument: +Suppose you also want to email some site administrator each time a site update is +made. To do that, you create a new class:: + + // src/AppBundle/Updates/SiteUpdateManager.php + namespace AppBundle\Updates; + + use AppBundle\Service\MessageGenerator; + + class SiteUpdateManager + { + private $messageGenerator; + private $mailer; + + public function __construct(MessageGenerator $messageGenerator, \Swift_Mailer $mailer) + { + $this->messageGenerator = $messageGenerator; + $this->mailer = $mailer; + } + + public function notifyOfSiteUpdate() + { + $happyMessage = $this->messageGenerator->getHappyMessage(); + + $message = \Swift_Message::newInstance() + ->setSubject('Site update just happened!') + ->setFrom('admin@example.com') + ->setTo('manager@example.com') + ->addPart( + 'Someone just updated the site. We told them: '.$happyMessage + ); + $this->mailer->send($message); + + return $message; + } + } + +This uses the ``MessageGenerator`` *and* the ``Swift_Mailer`` service. To register +this as a new service in the container, simply tell your configuration to load from +the new ``Updates`` sub-directory: .. configuration-block:: @@ -241,75 +285,86 @@ Next, tell the container the service has a constructor argument: # app/config/services.yml services: - app.message_generator: - class: AppBundle\Service\MessageGenerator - arguments: ['@logger'] + # ... + + # registers all classes in Services & Updates directories + AppBundle\: + resource: '../../src/AppBundle/{Service,Updates}' .. code-block:: xml - - - - - - - - - + TODO .. code-block:: php // app/config/services.php - use AppBundle\Service\MessageGenerator; - use Symfony\Component\DependencyInjection\Definition; - use Symfony\Component\DependencyInjection\Reference; + TODO - $container->setDefinition('app.message_generator', new Definition( - MessageGenerator::class, - array(new Reference('logger')) - )); +Now, you can use the service immediately:: -That's it! The container now knows to pass the ``logger`` service as an argument -when it instantiates the ``MessageGenerator``. This is called dependency injection. + use AppBundle\Updates\SiteUpdateManager; -The ``arguments`` key holds an array of all of the constructor arguments to the -service (just 1 so far). The ``@`` symbol before ``@logger`` is important: it tells -Symfony to pass the *service* named ``logger``. + public function newAction() + { + // ... -But you can pass anything as arguments. For example, suppose you want to make your -class a bit more configurable:: + $siteUpdateManager = $this->container->get(SiteUpdateManager::class); - // src/AppBundle/Service/MessageGenerator.php - // ... + $message = $siteUpdateManager->notifyOfSiteUpdate(); + $this->addFlash('success', $message); + // ... + } - use Psr\Log\LoggerInterface; +Just like before, when you ask for the ``SiteUpdateManager`` service, the container +will automatically instantiate it for you. By reading the type-hints on the ``__construct()`` +method in that class, it takes care of passing the correct services as arguments. +All of that is taken care of for you. - class MessageGenerator +Manually Wiring Arguments +------------------------- + +There are a few cases when an argument to a service cannot be autowired. For example, +suppose you want to make the admin email configurable: + +.. code-block:: diff + + // src/AppBundle/Updates/SiteUpdateManager.php + // ... + + class SiteUpdateManager { - private $logger; - private $loggingEnabled; + // ... + + private $adminEmail; - public function __construct(LoggerInterface $logger, $loggingEnabled) + - public function __construct(MessageGenerator $messageGenerator, \Swift_Mailer $mailer) + + public function __construct(MessageGenerator $messageGenerator, \Swift_Mailer $mailer, $adminEmail) { - $this->logger = $logger; - $this->loggingEnabled = $loggingEnabled; + // ... + + $this->adminEmail = $adminEmail; } - public function getHappyMessage() + public function notifyOfSiteUpdate() { - if ($this->loggingEnabled) { - $this->logger->info('About to find a happy message!'); - } + // ... + + $message = \Swift_Message::newInstance() + // ... + - ->setTo('manager@example.com') + + ->setTo($this->adminEmail) + // ... + ; // ... } } -The class now has a *second* constructor argument. No problem, just update your -service config: +If you make this change and refresh, you'll see an error: + + Cannot autowire service "AppBundle\Updates\SiteUpdateManager": argument "$adminEmail" + of method "__construct()" must have a type-hint or be given a value explicitly. + +That makes sense! There is no way that the container knows what value you want to +pass here. No problem! In your configuration, you can explicitly set this argument: .. configuration-block:: @@ -317,41 +372,29 @@ service config: # app/config/services.yml services: - app.message_generator: - class: AppBundle\Service\MessageGenerator - arguments: ['@logger', true] + # ... + + # same as before + AppBundle\: + resource: '../../src/AppBundle/{Service,Updates}' + + # explicitly configure the service + AppBundle\Updates\SiteUpdateManager: + arguments: + $adminEmail: 'manager@example.com' .. code-block:: xml - - - - - - - true - - - + TODO .. code-block:: php // app/config/services.php - use AppBundle\Service\MessageGenerator; - use Symfony\Component\DependencyInjection\Definition; - use Symfony\Component\DependencyInjection\Reference; + TODO - $container->setDefinition('app.message_generator', new Definition( - MessageGenerator::class, - array(new Reference('logger'), true) - )); - -You can even leverage :doc:`environments ` to control -this new value in different situations. +Thanks to this, the container will pass ``manager@example.com`` as the third argument +to ``__construct`` when creating the ``SiteUpdateManager`` service. .. _service-container-parameters: @@ -368,47 +411,22 @@ and reference it with the ``%parameter_name%`` syntax: # app/config/services.yml parameters: - enable_generator_logging: true + admin_email: manager@example.com services: - app.message_generator: - class: AppBundle\Service\MessageGenerator - arguments: ['@logger', '%enable_generator_logging%'] + # ... + + AppBundle\Updates\SiteUpdateManager: + arguments: + $adminEmail: '%admin_email%' .. code-block:: xml - - - - - - - true - - - - - %enable_generator_logging% - - - + TODO .. code-block:: php - // app/config/services.php - use AppBundle\Service\MessageGenerator; - use Symfony\Component\DependencyInjection\Definition; - use Symfony\Component\DependencyInjection\Reference; - - $container->setParameter('enable_generator_logging', true); - - $container->setDefinition('app.message_generator', new Definition( - MessageGenerator::class, - array(new Reference('logger'), '%enable_generator_logging%') - )); + TODO Actually, once you define a parameter, it can be referenced via the ``%parameter_name%`` syntax in *any* other service configuration file - like ``config.yml``. Many parameters @@ -420,8 +438,8 @@ You can also fetch parameters directly from the container:: { // ... - $isLoggingEnabled = $this->container - ->getParameter('enable_generator_logging'); + $adminEmail = $this->container + ->getParameter('admin_email'); // ... } From 6e6ed94a154d9cae034f78c97fe55f380bccc5d7 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Fri, 28 Apr 2017 12:27:52 -0400 Subject: [PATCH 10/19] more tweaks --- service_container.rst | 146 ++++++++++++++++++++++++++++-------------- 1 file changed, 98 insertions(+), 48 deletions(-) diff --git a/service_container.rst b/service_container.rst index 25eb3441c8e..43b29b3692c 100644 --- a/service_container.rst +++ b/service_container.rst @@ -6,7 +6,7 @@ Service Container ================= Your application is *full* of useful objects: one "Mailer" object might help you -deliver email messages while another object might help you save things to the database. +send email messages while another object might help you save things to the database. Almost *everything* that your app "does" is actually done by one of these objects. And each time you install a new bundle, you get access to even more! @@ -17,21 +17,13 @@ then you can fetch a service by using that service's id:: $logger = $container->get('logger'); $entityManager = $container->get('doctrine.entity_manager'); -The container is the *heart* of Symfony: it allows you to standardize and centralize -the way objects are constructed. It makes your life easier, is super fast, and emphasizes -an architecture that promotes reusable and decoupled code. It's also a big reason -that Symfony is so fast and extensible! - -Finally, configuring and using the service container is easy. By the end -of this article, you'll be comfortable creating your own objects via the -container and customizing objects from any third-party bundle. You'll begin -writing code that is more reusable, testable and decoupled, simply because -the service container makes writing good code so easy. +The container allows you to centralize the way objects are constructed. It makes +your life easier, promotes a strong architecture and is super fast! Fetching and using Services --------------------------- -The moment you start a Symfony app, the container *already* contains many services. +The moment you start a Symfony app, your container *already* contains many services. These are like *tools*, waiting for you to take advantage of them. In your controller, you have access to the container via ``$this->container``. Want to :doc:`log ` something? No problem:: @@ -86,11 +78,10 @@ in the container. .. sidebar:: Container: Lazy-loaded for speed - If the container holds so many useful objects (services), does that mean those - objects are instantiated on *every* request? No! The container is lazy: it doesn't - instantiate a service until (and unless) you ask for it. For example, if you - never use the ``validator`` service during a request, the container will never - instantiate it. + Wait! Are all the services (objects) instantiated on *every* request? No! The + container is lazy: it doesn't instantiate a service until (and unless) you ask + for it. For example, if you never use the ``validator`` service during a request, + the container will never instantiate it. .. index:: single: Service Container; Configuring services @@ -100,10 +91,9 @@ in the container. Creating/Configuring Services in the Container ---------------------------------------------- -You can also leverage the container to organize your *own* code into services. For -example, suppose you want to show your users a random, happy message every time -they do something. If you put this code in your controller, it can't be re-used. -Instead, you decide to create a new class:: +You can also organize your *own* code into services. For example, suppose you need +to show your users a random, happy message. If you put this code in your controller, +it can't be re-used. Instead, you decide to create a new class:: // src/AppBundle/Service/MessageGenerator.php namespace AppBundle\Service; @@ -133,14 +123,13 @@ the service container *how* to instantiate it: # app/config/services.yml services: - # configures defaults for all services in this file _defaults: autowire: true autoconfigure: true - # registers all classes in the dir(s) as services + # load services from whatever directories you want (you can update this!) AppBundle\: - resource: '../../src/AppBundle/{Service}' + resource: '../../src/AppBundle/{Service,EventDispatcher,Twig,Form}' .. code-block:: xml @@ -153,9 +142,10 @@ the service container *how* to instantiate it: TODO That's it! Thanks to the ``AppBundle\`` line and ``resource`` key below it, all -classes in the ``src/AppBundle/Service`` directory will automatically be added to -the container. Each service's "key" is simply its class name. You can use it immediately -inside your controller:: +classes in the ``src/AppBundle/Service`` directory (and a few other directories) +will automatically be added to the container. + +Each service's "key" is its class name. You can use it immediately inside your controller:: use AppBundle\Service\MessageGenerator; @@ -175,10 +165,8 @@ inside your controller:: } When you ask for the ``MessageGenerator::class`` service, the container constructs -a new ``MessageGenerator`` object and returns it. If you never ask for the -``MessageGenerator::class`` service during a request, it's *never* constructed, saving -you memory and increasing the speed of your app. This also means that there's almost -no performance overhead for defining a lot of services. +a new ``MessageGenerator`` object and returns it. But if you never ask for the service, +it's *never* constructed: saving memory and speed. As a bonus, the ``MessageGenerator::class`` service is only created *once*: the same instance is returned each time you ask for it. @@ -221,25 +209,26 @@ and set it on a ``$logger`` property:: } That's it! The container will *automatically* know to pass the ``logger`` service -when instantiating the ``MessageGenerator``? How does it know to do this? The key -is the ``LoggerInterface`` type-hint in your ``__construct()`` method and the -``autowire: true`` config in ``services.yml``. When you type-hint an argument, the -container will automatically find the matching service. If it can't or there is any -ambuiguity, you'll see a clear exception suggesting how to fix it. +when instantiating the ``MessageGenerator``? How does it know to do this? +:doc:`Autowiring `. The key is the ``LoggerInterface`` +type-hint in your ``__construct()`` method and the ``autowire: true`` config in +``services.yml``. When you type-hint an argument, the container will automatically +find the matching service. If it can't or there is any ambuiguity, you'll see a clear +exception suggesting how to fix it. Be sure to read more about :doc:`autowiring `. .. tip:: - How do you know to use ``LoggerInterface`` for the type-hint? The best way to - know this is by reading the docs for whatever service you're using. You can - also use the ``php bin/console debug:container`` console command to get a hint + How should you know to use ``LoggerInterface`` for the type-hint? The best way + is by reading the docs for whatever feature you're using. You can also use the + ``php bin/console debug:container`` console command to get a hint to the class name for a service. Handling Multiple Services -------------------------- -Suppose you also want to email some site administrator each time a site update is +Suppose you also want to email a site administrator each time a site update is made. To do that, you create a new class:: // src/AppBundle/Updates/SiteUpdateManager.php @@ -289,7 +278,7 @@ the new ``Updates`` sub-directory: # registers all classes in Services & Updates directories AppBundle\: - resource: '../../src/AppBundle/{Service,Updates}' + resource: '../../src/AppBundle/{Service,Updates,EventDispatcher,Twig,Form}' .. code-block:: xml @@ -316,16 +305,15 @@ Now, you can use the service immediately:: // ... } -Just like before, when you ask for the ``SiteUpdateManager`` service, the container -will automatically instantiate it for you. By reading the type-hints on the ``__construct()`` -method in that class, it takes care of passing the correct services as arguments. -All of that is taken care of for you. +Thanks to autowiring and your type-hints in ``__construct()``, the container creates +the ``SiteUpdateManager`` object and passes it the correct arguments. In most cases, +this works perfectly. Manually Wiring Arguments ------------------------- -There are a few cases when an argument to a service cannot be autowired. For example, -suppose you want to make the admin email configurable: +But there are a few cases when an argument to a service cannot be autowired. For +example, suppose you want to make the admin email configurable: .. code-block:: diff @@ -394,7 +382,8 @@ pass here. No problem! In your configuration, you can explicitly set this argume TODO Thanks to this, the container will pass ``manager@example.com`` as the third argument -to ``__construct`` when creating the ``SiteUpdateManager`` service. +to ``__construct`` when creating the ``SiteUpdateManager`` service. The other arguments +will still be autowired. .. _service-container-parameters: @@ -460,6 +449,67 @@ You can also fetch parameters directly from the container:: For more info about parameters, see :doc:`/service_container/parameters`. +Choose a Specific Service +------------------------- + +The ``MessageGenerator`` service created earlier requires a ``LoggerInterface`` argument:: + + // src/AppBundle/Service/MessageGenerator.php + // ... + + use Psr\Log\LoggerInterface; + + class MessageGenerator + { + private $logger; + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } + // ... + } + +However, there are *multiple* services in the container that implement ``LoggerInterface``, +such as ``logger``, ``monolog.logger.request``, ``monolog.logger.php``, etc. How +does the container know which one to use? + +In these situations, the container is usually configured to automatically choose +one of the services - ``logger`` in this case (read more about why in :ref:`service-autowiring-alias`). +But, you can control this and pass in a different logger: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/services.yml + services: + # ... same code as before + + # explicitly configure the service + AppBundle\Service\MessageGenerator: + arguments: + $logger: '@monolog.logger.request' + + .. code-block:: xml + + + TODO + + .. code-block:: php + + // app/config/services.php + TODO + +This tells the container that the ``$logger`` argument to ``_construct`` should use +service whose id is ``monolog.logger.request``. + +.. tip:: + + The ``@`` symbol is important: that's what tells the container you want to pass + the *service* whose id is ``monolog.logger.request``, and not just the *string* + ``monolog.logger.request``. + Learn more ---------- From 70178d1ead135cfd5e54dbfeab0e360eee2887a5 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Fri, 28 Apr 2017 12:37:20 -0400 Subject: [PATCH 11/19] adding note about autoconfigure --- service_container.rst | 65 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/service_container.rst b/service_container.rst index 43b29b3692c..67867c0ba38 100644 --- a/service_container.rst +++ b/service_container.rst @@ -123,6 +123,7 @@ the service container *how* to instantiate it: # app/config/services.yml services: + # default configuration for services in *this* file _defaults: autowire: true autoconfigure: true @@ -510,6 +511,70 @@ service whose id is ``monolog.logger.request``. the *service* whose id is ``monolog.logger.request``, and not just the *string* ``monolog.logger.request``. +The autoconfigure Option +------------------------ + +Above, we've set ``autoconfigure: true`` in the ``_defaults`` section so that it +applies to all services defined in that file. With this setting, the container will +automatically apply certain configuration to your services, based on your service's +*class*. The is mostly used to *auto-tag* your services. + +For example, to create a Twig Extension, you need to create a class, register it +as a service, and :doc:`tag ` it with ``twig.extension``: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/services.yml + services: + # ... + + AppBundle\Twig\MyTwigExtension: + tags: [twig.extension] + + .. code-block:: xml + + + TODO + + .. code-block:: php + + // app/config/services.php + TODO + +But, with ``autoconfigure: true``, you don't need the tag. In fact, all you need +is this: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/services.yml + services: + _defaults: + autowire: true + autoconfigure: true + + # load your service from the Twig directory + AppBundle\: + resource: '../../src/AppBundle/{Service,EventDispatcher,Twig,Form}' + + .. code-block:: xml + + + TODO + + .. code-block:: php + + // app/config/services.php + TODO + +That's it! The container will find your class in the ``Twig/`` directory and register +it as a service. Then ``autoconfigure`` will add the ``twig.extension`` tag *for* +you, because your class implements ``Twig_ExtensionInterface``. And thanks to ``autowire``, +you can even add ``__construct()`` arguments without any configuration. + Learn more ---------- From 45500b3398dd49a36a6bb0cc86b154f4a2ebb2fb Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Fri, 28 Apr 2017 13:18:54 -0400 Subject: [PATCH 12/19] Adding details and usages of fetching the service as a controller arg --- controller.rst | 58 +++++++++++++++++++++++++------------------ doctrine.rst | 7 ++++++ email.rst | 8 ++++-- logging.rst | 8 ++++-- service_container.rst | 2 ++ 5 files changed, 55 insertions(+), 28 deletions(-) diff --git a/controller.rst b/controller.rst index 926ec77d234..73812f25288 100644 --- a/controller.rst +++ b/controller.rst @@ -159,6 +159,11 @@ controller classes. A great way to see the core functionality in action is to look in the :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller` class. +.. tip:: + If you know what you're doing, you can alternatively extend + :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController`. It + has all the same shortcuts, but does not have a ```$this->container`` property. + .. index:: single: Controller; Redirecting @@ -247,10 +252,9 @@ The Symfony templating system and Twig are explained more in the Accessing other Services ~~~~~~~~~~~~~~~~~~~~~~~~ -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. +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. When extending the base ``Controller`` class, you can access any Symfony service via the :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::get` @@ -262,6 +266,9 @@ method. Here are several common services you might need:: $mailer = $this->get('mailer'); + // you can also fetch parameters + $someParameter = $this->getParameter('some_parameter'); + What other services exist? To list all services, use the ``debug:container`` console command: @@ -271,14 +278,31 @@ console command: For more information, see the :doc:`/service_container` article. -.. tip:: +Services as Controller Arguments +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can also tell Symfony to pass your a service as a controller argument by type-hinting +it:: - To get a :ref:`container configuration parameter `, - use the - :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::getParameter` - method:: + use Psr\Log\LoggerInterface + // ... + + /** + * @Route("/lucky/number/{max}") + */ + public function numberAction($max, LoggerInterface $logger) + { + $logger->info('We are logging!'); - $from = $this->getParameter('app.mailer.from'); + // ... + } + +.. note:: + If this isn't working, make sure your controller is registered as a service, + :ref:`autoconfigured ` and extends either + :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller` or + :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController`. Or, + you can tag it manually with ``controller.service_arguments``. .. index:: single: Controller; Managing errors @@ -418,20 +442,6 @@ For example, imagine you're processing a :doc:`form ` 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`:: - - 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. diff --git a/doctrine.rst b/doctrine.rst index 398aa521b02..29c9fcedca1 100644 --- a/doctrine.rst +++ b/doctrine.rst @@ -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() @@ -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) + { + // ... + } + .. note:: If you're following along with this example, you'll need to create a diff --git a/email.rst b/email.rst index eabb1a155af..593cfa22151 100644 --- a/email.rst +++ b/email.rst @@ -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') @@ -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 in this way + // $this->get('mailer')->send($message); return $this->render(...); } diff --git a/logging.rst b/logging.rst index 0c7ebd789c9..e46398b44de 100644 --- a/logging.rst +++ b/logging.rst @@ -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'); diff --git a/service_container.rst b/service_container.rst index 67867c0ba38..85b02d0a56e 100644 --- a/service_container.rst +++ b/service_container.rst @@ -511,6 +511,8 @@ service whose id is ``monolog.logger.request``. the *service* whose id is ``monolog.logger.request``, and not just the *string* ``monolog.logger.request``. +.. _services-autoconfigure: + The autoconfigure Option ------------------------ From 759e9b2305e8247147f9946c3763930587553dcc Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Fri, 28 Apr 2017 13:22:40 -0400 Subject: [PATCH 13/19] last tweaks from feedback --- service_container.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/service_container.rst b/service_container.rst index 85b02d0a56e..4c8078ee188 100644 --- a/service_container.rst +++ b/service_container.rst @@ -142,9 +142,9 @@ the service container *how* to instantiate it: // app/config/services.php TODO -That's it! Thanks to the ``AppBundle\`` line and ``resource`` key below it, all -classes in the ``src/AppBundle/Service`` directory (and a few other directories) -will automatically be added to the container. +That's it! Thanks to the ``AppBundle\`` line and ``resource`` key below it, a service +will be registered for each class in the ``src/AppBundle/Service`` directory (and +the other directories listed). Each service's "key" is its class name. You can use it immediately inside your controller:: @@ -210,11 +210,11 @@ and set it on a ``$logger`` property:: } That's it! The container will *automatically* know to pass the ``logger`` service -when instantiating the ``MessageGenerator``? How does it know to do this? +when instantiating the ``MessageGenerator``. How does it know to do this? :doc:`Autowiring `. The key is the ``LoggerInterface`` type-hint in your ``__construct()`` method and the ``autowire: true`` config in ``services.yml``. When you type-hint an argument, the container will automatically -find the matching service. If it can't or there is any ambuiguity, you'll see a clear +find the matching service. If it can't or there is any ambiguity, you'll see a clear exception suggesting how to fix it. Be sure to read more about :doc:`autowiring `. From 6de83e2fcafc748a3b61964e93af001925b9e495 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Fri, 28 Apr 2017 13:42:31 -0400 Subject: [PATCH 14/19] fixing build problem --- service_container/autowiring.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/service_container/autowiring.rst b/service_container/autowiring.rst index c113a02fbac..2f1ed9c944b 100644 --- a/service_container/autowiring.rst +++ b/service_container/autowiring.rst @@ -224,6 +224,8 @@ the ``TransformerInterface`` and injects it automatically. Even when using interfaces (and you should), building the service graph and refactoring the project is easier than with standard definitions. +.. _service-autowiring-alias: + Dealing with Multiple Implementations of the Same Type ------------------------------------------------------ From 89e12dee07ea5438033d29eeefa4ac83b54cf49f Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Fri, 28 Apr 2017 13:48:47 -0400 Subject: [PATCH 15/19] bad link --- service_container.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service_container.rst b/service_container.rst index 4c8078ee188..14150161afa 100644 --- a/service_container.rst +++ b/service_container.rst @@ -522,7 +522,7 @@ automatically apply certain configuration to your services, based on your servic *class*. The is mostly used to *auto-tag* your services. For example, to create a Twig Extension, you need to create a class, register it -as a service, and :doc:`tag ` it with ``twig.extension``: +as a service, and :doc:`tag ` it with ``twig.extension``: .. configuration-block:: From ee2776566da98c0ba360e9ca8f3cde9c835b7cda Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 2 May 2017 06:59:11 -0400 Subject: [PATCH 16/19] Adding versionadded --- controller.rst | 7 +++++++ service_container.rst | 15 +++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/controller.rst b/controller.rst index 08ea406af1f..a49e9d37417 100644 --- a/controller.rst +++ b/controller.rst @@ -149,6 +149,9 @@ and many others that you'll learn about next. via ``$this->get()`` or ``$this->container->get()``. This forces you to write more robust code to access services, but if you're not use, use ``Controller``. +.. versionadded:: 3.3 + The ``AbstractController`` class was added in Symfony 3.3. + .. index:: single: Controller; Redirecting @@ -237,6 +240,10 @@ The Symfony templating system and Twig are explained more in the Fetching Services as Arguments ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. versionadded:: 3.3 + The ability to type-hint an 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 `. These are used for rendering templates, sending emails, querying the database and any other "work" you can think of. diff --git a/service_container.rst b/service_container.rst index 3d2b8151656..c3235f976f6 100644 --- a/service_container.rst +++ b/service_container.rst @@ -43,6 +43,9 @@ service's class or interface name. Want to :doc:`log ` something? No p // ... } +.. versionadded:: 3.3 + The ability to type-hint a service in order to receive it was added in Symfony 3.3. + .. _container-debug-container: What other services are available? Find out by running: @@ -177,6 +180,10 @@ the service container *how* to instantiate it: // app/config/services.php // _defaults and loading entire directories is not possible with PHP configuration +.. versionadded:: 3.3 + The ``_defaults`` key and ability to load services from a directory were added + in Symfony 3.3. + That's it! Thanks to the ``AppBundle\`` line and ``resource`` key below it, a service will be registered for each class in the ``src/AppBundle/Service`` directory (and the other directories listed). @@ -462,6 +469,11 @@ pass here. No problem! In your configuration, you can explicitly set this argume ->setPublic(false) ->setArgument('$adminEmail', 'manager@example.com'); +.. versionadded:: 3.3 + The ability to configure an argument by its name (``$adminEmail``) was added + in Symfony 3.3. Previously, you could configure it only by its index (``2`` in + this case). + Thanks to this, the container will pass ``manager@example.com`` as the third argument to ``__construct`` when creating the ``SiteUpdateManager`` service. The other arguments will still be autowired. @@ -641,6 +653,9 @@ service whose id is ``monolog.logger.request``. The autoconfigure Option ------------------------ +.. versionadded:: 3.3 + The ``autoconfigure`` option was added in Symfony 3.3. + Above, we've set ``autoconfigure: true`` in the ``_defaults`` section so that it applies to all services defined in that file. With this setting, the container will automatically apply certain configuration to your services, based on your service's From 5452c617d2686463354d1a0491ca2e69bf1da9c2 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 2 May 2017 07:07:48 -0400 Subject: [PATCH 17/19] Adding section about public: false --- service_container.rst | 79 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/service_container.rst b/service_container.rst index c3235f976f6..7191fe2a8ed 100644 --- a/service_container.rst +++ b/service_container.rst @@ -748,6 +748,85 @@ it as a service. Then ``autoconfigure`` will add the ``twig.extension`` tag *for you, because your class implements ``Twig_ExtensionInterface``. And thanks to ``autowire``, you can even add constructor arguments without any configuration. +.. _container-public: + +Public Versus Private Services +------------------------------ + +Thanks to the ``_defaults`` section in ``services.yml``, every service defined in +this file is ``public: false`` by default: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/services.yml + services: + # default configuration for services in *this* file + _defaults: + # ... + public: false + + .. code-block:: xml + + + + + + + + + + + +What does this mean? When a service is **not** public, you cannot access it directly +from the container:: + + use AppBundle\Service\MessageGenerator; + + public function newAction(MessageGenerator $messageGenerator) + { + // type-hinting it as an argument DOES work + + // but accessing it directly from the container does NOT Work + $this->container->get(MessageGenerator::class); + } + +Usually, this is ok: there are better ways to access a service. But, if you *do* +need to make your service public, just override this setting: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/services.yml + services: + # ... same code as before + + # explicitly configure the service + AppBundle\Service\MessageGenerator: + public: true + + .. code-block:: xml + + + + + + + + + + + + + Learn more ---------- From 12c49444c8be79f3988875fd619f958a0ff85e07 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Fri, 5 May 2017 06:56:27 -0400 Subject: [PATCH 18/19] Tweaks after amazing review from @GuilhemN and @xabbuh --- controller.rst | 36 +++++++++++++++++++-------------- email.rst | 2 +- service_container.rst | 47 ++++++++++++++++++++++++++----------------- 3 files changed, 51 insertions(+), 34 deletions(-) diff --git a/controller.rst b/controller.rst index e64325ef360..14980f27055 100644 --- a/controller.rst +++ b/controller.rst @@ -147,7 +147,8 @@ and many others that you'll learn about next. 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're not use, use ``Controller``. + 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. @@ -238,19 +239,19 @@ The Symfony templating system and Twig are explained more in the .. _controller-accessing-services: .. _accessing-other-services: -Fetching Services as Arguments -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Fetching Services as Controller Arguments +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 3.3 - The ability to type-hint an argument in order to receive a service was added - in Symfony 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 `. These are used for rendering templates, sending emails, querying the database and any other "work" you can think of. -If you need a service, just type-hint an argument with its class (or interface) name. -Symfony will automatically pass you the service you need:: +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 // ... @@ -266,16 +267,15 @@ Symfony will automatically pass you the service you need:: Awesome! -What other services exist? Each page of the documentation will reveal more and more -services you can use. To list *all* services, use the ``debug:container`` console +What other services can you type-hint? To see them, use the ``debug:container`` console command: .. code-block:: terminal - $ php bin/console debug:container + $ php bin/console debug:container --types -If need to control the *exact* value of an argument, you can override your controller's -service config: +If you need control over the *exact* value of an argument, you can override your +controller's service config: .. configuration-block:: @@ -334,14 +334,18 @@ service config: ]) ; +You can of course also use normal :ref:`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, - :ref:`autoconfigured ` and extends either + is :ref:`autoconfigured ` 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``. + 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: @@ -349,7 +353,7 @@ For more information about services, see the :doc:`/service_container` article. Accessing the Container Directly ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -If extending the base ``Controller`` class, you can access any Symfony service +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:: @@ -364,6 +368,8 @@ method. Here are several common services you might need:: If you receive an eror like: +.. code-block:: text + You have requested a non-existent service "my_service_id" Check to make sure the service exists (use :ref:`debug:container `) diff --git a/email.rst b/email.rst index 593cfa22151..e573bd3bbdc 100644 --- a/email.rst +++ b/email.rst @@ -128,7 +128,7 @@ an email is pretty straightforward:: $mailer->send($message); - // or, you can also fetch the mailer service in this way + // or, you can also fetch the mailer service this way // $this->get('mailer')->send($message); return $this->render(...); diff --git a/service_container.rst b/service_container.rst index bd3c4153ecf..ea756eb7836 100644 --- a/service_container.rst +++ b/service_container.rst @@ -25,7 +25,7 @@ Fetching and using Services The moment you start a Symfony app, your container *already* contains many services. These are like *tools*, waiting for you to take advantage of them. In your controller, -you can "ask" for a service from the container by type-hinting an argument wit the +you can "ask" for a service from the container by type-hinting an argument with the service's class or interface name. Want to :doc:`log ` something? No problem:: // src/AppBundle/Controller/ProductController.php @@ -155,7 +155,7 @@ the service container *how* to instantiate it: # loads services from whatever directories you want (you can update this!) AppBundle\: - resource: '../../src/AppBundle/{Service,EventDispatcher,Twig,Form}' + resource: '../../src/AppBundle/{Service,Command,Form,EventSubscriber,Twig,Security}' .. code-block:: xml @@ -171,7 +171,7 @@ the service container *how* to instantiate it: - + @@ -179,6 +179,12 @@ the service container *how* to instantiate it: // app/config/services.php // _defaults and loading entire directories is not possible with PHP configuration + // you need to define your servicess one-by-one + use AppBundle/Service/MessageGenerator; + + $container->autowire(MessageGenerator::class) + ->setAutoconfigured(true) + ->setPublic(false); .. versionadded:: 3.3 The ``_defaults`` key and ability to load services from a directory were added @@ -228,7 +234,7 @@ be its class name in this case:: } } -However, this only works if you set your service to be :ref:`public `. +However, this only works if you make your service :ref:`public `. .. caution:: @@ -236,10 +242,12 @@ However, this only works if you set your service to be :ref:`public container->get()``. @@ -272,8 +280,8 @@ when instantiating the ``MessageGenerator``. How does it know to do this? :doc:`Autowiring `. The key is the ``LoggerInterface`` type-hint in your ``__construct()`` method and the ``autowire: true`` config in ``services.yml``. When you type-hint an argument, the container will automatically -find the matching service. If it can't or there is any ambiguity, you'll see a clear -exception with a helpful suggestion. +find the matching service. If it can't, you'll see a clear exception with a helpful +suggestion. Be sure to read more about :doc:`autowiring `. @@ -281,8 +289,8 @@ Be sure to read more about :doc:`autowiring `. How should you know to use ``LoggerInterface`` for the type-hint? The best way is by reading the docs for whatever feature you're using. You can also use the - ``php bin/console debug:container`` console command to get a hint - to the class name for a service. + ``php bin/console debug:container --types`` console command to get a list of + available type-hints. Handling Multiple Services ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -337,7 +345,7 @@ the new ``Updates`` sub-directory: # registers all classes in Services & Updates directories AppBundle\: - resource: '../../src/AppBundle/{Service,Updates,EventDispatcher,Twig,Form}' + resource: '../../src/AppBundle/{Service,Updates,Command,Form,EventSubscriber,Twig,Security}' .. code-block:: xml @@ -352,7 +360,7 @@ the new ``Updates`` sub-directory: - + @@ -370,7 +378,7 @@ Now, you can use the service immediately:: } Thanks to autowiring and your type-hints in ``__construct()``, the container creates -the ``SiteUpdateManager`` object and passes it the correct arguments. In most cases, +the ``SiteUpdateManager`` object and passes it the correct argument. In most cases, this works perfectly. Manually Wiring Arguments @@ -428,7 +436,7 @@ pass here. No problem! In your configuration, you can explicitly set this argume # same as before AppBundle\: - resource: '../../src/AppBundle/{Service,Updates}' + resource: '../../src/AppBundle/{Service,Updates,Command,Form,EventSubscriber,Twig,Security}' # explicitly configure the service AppBundle\Updates\SiteUpdateManager: @@ -448,7 +456,7 @@ pass here. No problem! In your configuration, you can explicitly set this argume - + @@ -526,6 +534,8 @@ and reference it with the ``%parameter_name%`` syntax: .. code-block:: php + // app/config/services.php + use AppBundle\Updates\SiteUpdateManager; $container->setParameter('admin_email', 'manager@example.com'); $container->autowire(SiteUpdateManager::class) @@ -659,7 +669,7 @@ The autoconfigure Option Above, we've set ``autoconfigure: true`` in the ``_defaults`` section so that it applies to all services defined in that file. With this setting, the container will automatically apply certain configuration to your services, based on your service's -*class*. The is mostly used to *auto-tag* your services. +*class*. This is mostly used to *auto-tag* your services. For example, to create a Twig Extension, you need to create a class, register it as a service, and :doc:`tag ` it with ``twig.extension``: @@ -702,7 +712,8 @@ as a service, and :doc:`tag ` it with ``twig.extension` ->addTag('twig.extension'); But, with ``autoconfigure: true``, you don't need the tag. In fact, all you need -to do is load your service from the ``Twig`` directory: +to do is load your service from the ``Twig`` directory, which is already loaded +by default in a fresh Symfony install: .. configuration-block:: @@ -716,7 +727,7 @@ to do is load your service from the ``Twig`` directory: # load your services from the Twig directory AppBundle\: - resource: '../../src/AppBundle/{Service,EventDispatcher,Twig,Form}' + resource: '../../src/AppBundle/{Service,Updates,Command,Form,EventSubscriber,Twig,Security}' .. code-block:: xml @@ -731,7 +742,7 @@ to do is load your service from the ``Twig`` directory: - + From 22adfbd62405073500ce1ff9b0a5d799c8a6e39b Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Fri, 5 May 2017 07:00:09 -0400 Subject: [PATCH 19/19] removing duplicate target --- controller.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/controller.rst b/controller.rst index 14980f27055..c93bcb0e49c 100644 --- a/controller.rst +++ b/controller.rst @@ -237,7 +237,6 @@ The Symfony templating system and Twig are explained more in the single: Controller; Accessing services .. _controller-accessing-services: -.. _accessing-other-services: Fetching Services as Controller Arguments ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~