From 3e1f1416d04e241e13545a400e1d82068dac6f72 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sat, 15 Apr 2017 10:56:55 -0400 Subject: [PATCH 1/4] Rewriting the service container docs --- service_container.rst | 564 +++++++++++++++++------------------- service_container/calls.rst | 69 +++++ 2 files changed, 328 insertions(+), 305 deletions(-) create mode 100644 service_container/calls.rst diff --git a/service_container.rst b/service_container.rst index a21448dedb4..bab4aade28c 100644 --- a/service_container.rst +++ b/service_container.rst @@ -5,22 +5,22 @@ Service Container ================= -A modern PHP application is full of objects. One object may facilitate the -delivery of email messages while another may allow you to persist information -into a database. In your application, you may create an object that manages -your product inventory, or another object that processes data from a third-party -API. The point is that a modern application does many things and is organized -into many objects that handle each task. - -This article is about a special PHP object in Symfony that helps -you instantiate, organize and retrieve the many objects of your application. -This object, called a service container, will allow you to standardize and -centralize the way objects are constructed in your application. The container -makes your life easier, is super fast, and emphasizes an architecture that -promotes reusable and decoupled code. Since all core Symfony classes -use the container, you'll learn how to extend, configure and use any object -in Symfony. In large part, the service container is the biggest contributor -to the speed and extensibility of Symfony. +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. +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! + +In Symfony, these useful objects are called **services** and each service lives inside +a very special object called the **service container**. If you have the service container, +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 @@ -28,69 +28,68 @@ 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. -.. tip:: +Fetching and using Services +--------------------------- - If you want to know a lot more after reading this article, check out - the :doc:`DependencyInjection component documentation `. +The moment you start a Symfony app, the 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:: -.. index:: - single: Service Container; What is a service? + // src/AppBundle/Controller/ProductController.php + namespace AppBundle\Controller; -What is a Service? ------------------- + use Symfony\Bundle\FrameworkBundle\Controller\Controller; -Put simply, a service is any PHP object that performs some sort of "global" -task. It's a purposefully-generic name used in computer science to describe an -object that's created for a specific purpose (e.g. delivering emails). Each -service is used throughout your application whenever you need the specific -functionality it provides. You don't have to do anything special to make a -service: simply write a PHP class with some code that accomplishes a specific -task. Congratulations, you've just created a service! + class ProductController extends Controller + { + /** + * @Route("/products") + */ + public function listAction() + { + $logger = $this->container->get('logger'); + $logger->info('Look! I just used a service'); -.. note:: + // ... + } + } - As a rule, a PHP object is a service if it is used globally in your - application. A single ``Mailer`` service is used globally to send - email messages whereas the many ``Message`` objects that it delivers - are *not* services. Similarly, a ``Product`` object is not a service, - but an object that persists ``Product`` objects to a database *is* a service. - -So what's the big deal then? The advantage of thinking about "services" is -that you begin to think about separating each piece of functionality in your -application into a series of services. Since each service does just one job, -you can easily access each service and use its functionality wherever you -need it. Each service can also be more easily tested and configured since -it's separated from the other functionality in your application. This idea -is called `service-oriented architecture`_ and is not unique to Symfony -or even PHP. Structuring your application around a set of independent service -classes is a well-known and trusted object-oriented best-practice. These skills -are key to being a good developer in almost any language. +``logger`` is a unique key for the logger object. What other services are available? +Find out by running: -.. index:: - single: Service Container; What is a service container? +.. code-block:: terminal -What is a Service Container? ----------------------------- + php bin/console debug:container -A service container (or *dependency injection container*) is simply a PHP -object that manages the instantiation of services (i.e. objects). +This is just a *small* sample of the output: -For example, suppose you have a simple PHP class that delivers email messages. -Without a service container, you must manually create the object whenever -you need it:: +Service ID Class name +------------------------------- ------------------------------------------------------------------- +doctrine Doctrine\Bundle\DoctrineBundle\Registry +filesystem Symfony\Component\Filesystem\Filesystem +form.factory Symfony\Component\Form\FormFactory +logger Symfony\Bridge\Monolog\Logger +request_stack Symfony\Component\HttpFoundation\RequestStack +router Symfony\Bundle\FrameworkBundle\Routing\Router +security.authorization_checker Symfony\Component\Security\Core\Authorization\AuthorizationChecker +security.password_encoder Symfony\Component\Security\Core\Encoder\UserPasswordEncoder +session Symfony\Component\HttpFoundation\Session\Session +translator Symfony\Component\Translation\DataCollectorTranslator +twig Twig_Environment +validator Symfony\Component\Validator\Validator\ValidatorInterface +------------------------------- ------------------------------------------------------------------- - use AppBundle\Mailer; +Throughout the docs, you'll see how to see the many different services that live +in the container. - $mailer = new Mailer('sendmail'); - $mailer->send('ryan@example.com', ...); +.. 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. If during a request + you never use the ``validator`` service, the container will never create it. -This is easy enough. The imaginary ``Mailer`` class allows you to configure -the method used to deliver the email messages (e.g. ``sendmail``, ``smtp``, etc). -But what if you wanted to use the mailer service somewhere else? You certainly -don't want to repeat the mailer configuration *every* time you need to use -the ``Mailer`` object. What if you needed to change the ``transport`` from -``sendmail`` to ``smtp`` everywhere in the application? You'd need to hunt -down every place you create a ``Mailer`` service and change it. .. index:: single: Service Container; Configuring services @@ -100,80 +99,42 @@ down every place you create a ``Mailer`` service and change it. Creating/Configuring Services in the Container ---------------------------------------------- -A better answer is to let the service container create the ``Mailer`` object -for you. In order for this to work, you must *teach* the container how to -create the ``Mailer`` service. This is done via configuration, which can -be specified in YAML, XML or PHP: - -.. include:: /_includes/service_container/_my_mailer.rst.inc - -.. note:: - - When Symfony initializes, it builds the service container using the - application configuration (``app/config/config.yml`` by default). The - exact file that's loaded is dictated by the ``AppKernel::registerContainerConfiguration()`` - method, which loads an environment-specific configuration file (e.g. - ``config_dev.yml`` for the ``dev`` environment or ``config_prod.yml`` - for ``prod``). +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:: -An instance of the ``AppBundle\Mailer`` class is now available via the service -container. The container is available in any traditional Symfony controller -where you can access the services of the container via the ``get()`` shortcut -method:: + // src/AppBundle/Service/MessageGenerator.php + namespace AppBundle\Service; - class HelloController extends Controller + class MessageGenerator { - // ... - - public function sendEmailAction() + public function getHappyMessage() { - // ... - $mailer = $this->get('app.mailer'); - $mailer->send('ryan@foobar.net', ...); - } - } - -When you ask for the ``app.mailer`` service from the container, the container -constructs the object and returns it. This is another major advantage of -using the service container. Namely, a service is *never* constructed until -it's needed. If you define a service and never use it on a request, the service -is never created. This saves memory and increases the speed of your application. -This also means that there's very little or no performance hit for defining -lots of services. Services that are never used are never constructed. + $messages = [ + 'You did it! You updated the system! Amazing!', + 'That was one of the coolest updates I\'ve seen all day!', + 'Great work! Keep going!', + ]; -As a bonus, the ``Mailer`` service is only created once and the same -instance is returned each time you ask for the service. This is almost always -the behavior you'll need (it's more flexible and powerful), but you'll learn -later how you can configure a service that has multiple instances in the -:doc:`/service_container/scopes` article. - -.. note:: + $index = array_rand($messages); - In this example, the controller extends Symfony's base Controller, which - gives you access to the service container itself. You can then use the - ``get()`` method to locate and retrieve the ``app.mailer`` service from - the service container. - -.. _service-container-parameters: - -Service Parameters ------------------- + return $messages[$index]; + } + } -The creation of new services (i.e. objects) via the container is pretty -straightforward. Parameters make defining services more organized and flexible: +Congratulations! You've just created your first service class. Next, you can *teach* +the service container *how* to instantiate it: .. configuration-block:: .. code-block:: yaml # app/config/services.yml - parameters: - app.mailer.transport: sendmail - services: - app.mailer: - class: AppBundle\Mailer - arguments: ['%app.mailer.transport%'] + app.message_generator: + class: AppBundle\Service\MessageGenerator + arguments: [] .. code-block:: xml @@ -184,13 +145,8 @@ straightforward. Parameters make defining services more organized and flexible: xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - - sendmail - - - - %app.mailer.transport% + @@ -198,120 +154,152 @@ straightforward. Parameters make defining services more organized and flexible: .. code-block:: php // app/config/services.php - use AppBundle\Mailer; + use AppBundle\Service\MessageGenerator; use Symfony\Component\DependencyInjection\Definition; - $container->setParameter('app.mailer.transport', 'sendmail'); - - $container->setDefinition('app.mailer', new Definition( - Mailer::class, - array('%app.mailer.transport%') + $container->setDefinition('app.message_generator', new Definition( + MessageGenerator::class, + array() )); -The end result is exactly the same as before - the difference is only in -*how* you defined the service. By enclosing the ``app.mailer.transport`` -string with percent (``%``) signs, the container knows to look for a parameter -with that name. When the container is built, it looks up the value of each -parameter and uses it in the service definition. +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:: -.. note:: + public function newAction() + { + // ... - If you want to use a string that starts with an ``@`` sign as a parameter - value (e.g. a very safe mailer password) in a YAML file, you need to escape - it by adding another ``@`` sign (this only applies to the YAML format): + // the container will instantiate a new MessageGenerator() + $messageGenerator = $this->container->get('app.message_generator'); - .. code-block:: yaml + // or use this shorter synax + // $messageGenerator = $this->get('app.message_generator'); - # app/config/parameters.yml - parameters: - # This will be parsed as string '@securepass' - mailer_password: '@@securepass' + $message = $messageGenerator->getHappyMessage(); + $this->addFlash('success', $message); + // ... + } -.. note:: +When you ask for the ``app.message_generator`` 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 +you memory and increasing the speed of your app. This also means that there's almost +no performance hit for defining a lot of services. - The percent sign inside a parameter or argument, as part of the string, must - be escaped with another percent sign: +As a bonus, the ``app.message_generator`` service is only created *once*: the same +instance is returned each time you ask for it. - .. code-block:: xml +Injecting Services/Config into a Service +---------------------------------------- - http://symfony.com/?foo=%%s&bar=%%d +What if you want to use the ``logger`` service from within ``MessageGenerator``? +Your service does *not* have a ``$this->container`` property - that's a special power +only controllers have. -The purpose of parameters is to feed information into services. Of course -there was nothing wrong with defining the service without using any parameters. -Parameters, however, have several advantages: +Instead, you should create a ``__construct()`` method, add a ``$logger`` argument +and set it on a ``$logger`` property:: -* separation and organization of all service "options" under a single - ``parameters`` key; + // src/AppBundle/Service/MessageGenerator.php + // ... -* parameter values can be used in multiple service definitions; + use Psr\Log\LoggerInterface; -* when creating a service in a bundle (this follows shortly), using parameters - allows the service to be easily customized in your application. + class MessageGenerator + { + private $logger; -The choice of using or not using parameters is up to you. High-quality -third-party bundles will *always* use parameters as they make the service -stored in the container more configurable. For the services in your application, -however, you may not need the flexibility of parameters. + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } -Array Parameters -~~~~~~~~~~~~~~~~ + public function getHappyMessage() + { + $this->logger->info('About to find a happy message!'); + // ... + } + } -Parameters can also contain array values. See :ref:`component-di-parameters-array`. +.. tip:: -.. index:: - single: Service Container; Referencing services + The ``LoggerInterface`` type-hint in the ``__construct()`` method is optional, + but a good idea. You can find the right type-hint by reading the docs for the + service or by using the ``php bin/console debug:container`` console command. -Referencing (Injecting) Services --------------------------------- +Next, tell the container the service has a constructor argument: -So far, the original ``app.mailer`` service is simple: it takes just one argument -in its constructor, which is easily configurable. As you'll see, the real -power of the container is realized when you need to create a service that -depends on one or more other services in the container. +.. configuration-block:: -As an example, suppose you have a new service, ``NewsletterManager``, -that helps to manage the preparation and delivery of an email message to -a collection of addresses. Of course the ``app.mailer`` service is already -really good at delivering email messages, so you'll use it inside ``NewsletterManager`` -to handle the actual delivery of the messages. This pretend class might look -something like this:: + .. code-block:: yaml - // src/AppBundle/Newsletter/NewsletterManager.php - namespace AppBundle\Newsletter; + # app/config/services.yml + services: + app.message_generator: + class: AppBundle\Service\MessageGenerator + arguments: ['@logger'] - use AppBundle\Mailer; + .. code-block:: xml - class NewsletterManager - { - protected $mailer; + + + - public function __construct(Mailer $mailer) - { - $this->mailer = $mailer; - } + + + + + + - // ... - } + .. code-block:: php + + // app/config/services.php + use AppBundle\Service\MessageGenerator; + use Symfony\Component\DependencyInjection\Definition; + use Symfony\Component\DependencyInjection\Reference; + + $container->setDefinition('app.message_generator', new Definition( + MessageGenerator::class, + array(new Reference('logger')) + )); -Without using the service container, you can create a new ``NewsletterManager`` -fairly easily from inside a controller:: +The ``arguments`` key holds an array of all of the constructor arguments to the +service (just 1 so far). The ``@`` symbol before ``@logger`` is special: it tells +Symfony to pass the *service* named ``logger``. - use AppBundle\Newsletter\NewsletterManager; +You can pass anything as arguments. For example, suppose you want to make your +class a bit more configurable:: + // src/AppBundle/Service/MessageGenerator.php // ... - public function sendNewsletterAction() + use Psr\Log\LoggerInterface; + + class MessageGenerator { - $mailer = $this->get('app.mailer'); - $newsletter = new NewsletterManager($mailer); - // ... + private $logger; + private $loggingEnabled; + + public function __construct(LoggerInterface $logger, $loggingEnabled) + { + $this->logger = $logger; + $this->loggingEnabled = $loggingEnabled; + } + + public function getHappyMessage() + { + if ($this->loggingEnabled) { + $this->logger->info('About to find a happy message!'); + } + // ... + } } -This approach is fine, but what if you decide later that the ``NewsletterManager`` -class needs a second or third constructor argument? What if you decide to -refactor your code and rename the class? In both cases, you'd need to find every -place where the ``NewsletterManager`` is instantiated and modify it. Of course, -the service container gives you a much more appealing option: +The class now has a *second* constructor argument. No problem, just update your +service config: .. configuration-block:: @@ -319,12 +307,9 @@ the service container gives you a much more appealing option: # app/config/services.yml services: - app.mailer: - # ... - - app.newsletter_manager: - class: AppBundle\Newsletter\NewsletterManager - arguments: ['@app.mailer'] + app.message_generator: + class: AppBundle\Service\MessageGenerator + arguments: ['@logger', true] .. code-block:: xml @@ -336,12 +321,9 @@ the service container gives you a much more appealing option: http://symfony.com/schema/dic/services/services-1.0.xsd"> - - - - - - + + + true @@ -349,69 +331,39 @@ the service container gives you a much more appealing option: .. code-block:: php // app/config/services.php - use AppBundle\Newsletter\NewsletterManager; + use AppBundle\Service\MessageGenerator; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; - $container->setDefinition('app.mailer', ...); - - $container->setDefinition('app.newsletter_manager', new Definition( - NewsletterManager::class, - array(new Reference('app.mailer')) + $container->setDefinition('app.message_generator', new Definition( + MessageGenerator::class, + array(new Reference('logger'), true) )); -In YAML, the special ``@app.mailer`` syntax tells the container to look for -a service named ``app.mailer`` and to pass that object into the constructor -of ``NewsletterManager``. In this case, however, the specified service ``app.mailer`` -must exist. If it does not, an exception will be thrown. You can mark your -dependencies as optional - this will be discussed in the next section. - -Using references is a very powerful tool that allows you to create independent service -classes with well-defined dependencies. In this example, the ``app.newsletter_manager`` -service needs the ``app.mailer`` service in order to function. When you define -this dependency in the service container, the container takes care of all -the work of instantiating the classes. - -Optional Dependencies: Setter Injection -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Injecting dependencies into the constructor in this manner is an excellent -way of ensuring that the dependency is available to use. If you have optional -dependencies for a class, then "setter injection" may be a better option. This -means injecting the dependency using a method call rather than through the -constructor. The class would look like this:: +You can even leverage :doc:`environments ` to control +this new value in different situations. - namespace AppBundle\Newsletter; - - use AppBundle\Mailer; - - class NewsletterManager - { - protected $mailer; - - public function setMailer(Mailer $mailer) - { - $this->mailer = $mailer; - } +.. _service-container-parameters: - // ... - } +Service Parameters +------------------ -Injecting the dependency by the setter method just needs a change of syntax: +In addition to holding service objects, the container also holds configuration, +called ``parameters``. To create a parameter, add it under the ``parameters`` key +and reference it with the ``%parameter_name%`` syntax: .. configuration-block:: .. code-block:: yaml # app/config/services.yml - services: - app.mailer: - # ... + parameters: + enable_generator_logging: true - app.newsletter_manager: - class: AppBundle\Newsletter\NewsletterManager - calls: - - [setMailer, ['@app.mailer']] + services: + app.message_generator: + class: AppBundle\Service\MessageGenerator + arguments: ['@logger', '%enable_generator_logging%'] .. code-block:: xml @@ -423,14 +375,13 @@ Injecting the dependency by the setter method just needs a change of syntax: http://symfony.com/schema/dic/services/services-1.0.xsd"> - - - + + true + - - - - + + + %enable_generator_logging% @@ -438,46 +389,49 @@ Injecting the dependency by the setter method just needs a change of syntax: .. code-block:: php // app/config/services.php - use AppBundle\Newsletter\NewsletterManager; + use AppBundle\Service\MessageGenerator; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; - $container->setDefinition('app.mailer', ...); + $container->setParameter('enable_generator_logging', true); - $definition = new Definition(NewsletterManager::class); - $definition->addMethodCall('setMailer', array( - new Reference('app.mailer'), + $container->setDefinition('app.message_generator', new Definition( + MessageGenerator::class, + array(new Reference('logger'), '%enable_generator_logging%') )); - $container->setDefinition('app.newsletter_manager', $definition); -.. note:: +Actually, once you define a parameter, it can be referenced via this ``%parameter_name%`` +syntax in *any* other service configuration file - like ``config.yml``. Many parameters +are defined in a :ref:`parameters.yml file `. - The approaches presented in this section are called "constructor injection" - and "setter injection". The Symfony service container also supports - "property injection". +You can also fetch parameters directly from the container:: -.. tip:: + public function newAction() + { + // ... + + $isLoggingEnabled = $this->container + ->getParameter('enable_generator_logging'); + // ... + } + +.. note:: - The YAML configuration format supports an expanded syntax which may be - useful when the YAML contents are long and complex: + If you use a string that starts with ``@`` or ``%``, you need to escape it by + adding another ``@`` or ``%``: .. code-block:: yaml - # app/config/services.yml - services: - # traditional syntax - app.newsletter_manager: - class: AppBundle\Newsletter\NewsletterManager - calls: - - [setMailer, ['@app.mailer']] - - # expanded syntax - app.newsletter_manager: - class: AppBundle\Newsletter\NewsletterManager - calls: - - method: setMailer - arguments: - - '@app.mailer' + # app/config/parameters.yml + parameters: + # This will be parsed as string '@securepass' + mailer_password: '@@securepass' + + # Parsed as http://symfony.com/?foo=%s&bar=%d + url_pattern: 'http://symfony.com/?foo=%%s&bar=%%d' + +For more info about parameters, see :doc:`/service_container/parameters`. + Learn more ---------- diff --git a/service_container/calls.rst b/service_container/calls.rst new file mode 100644 index 00000000000..f14292c2cf5 --- /dev/null +++ b/service_container/calls.rst @@ -0,0 +1,69 @@ +.. index:: + single: DependencyInjection; Method Calls + +Service Method Calls and Setter Injection +========================================= + +Usually, you'll want to inject your dependencies via the constructor. But sometimes, +especially if a dependency is optional, you may want to use "setter injection". For +example:: + + namespace AppBundle\Service; + + use Psr\Log\LoggerInterface; + + class MessageGenerator + { + private $logger; + + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + } + + // ... + } + +To configure the container to call the ``setLogger`` method, use the ``calls`` key: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/services.yml + services: + app.message_generator: + # ... + calls: + - method: setLogger + arguments: + - '@logger' + + .. code-block:: xml + + + + + + + + + + + + + + + + .. code-block:: php + + // app/config/services.php + use AppBundle\Service\MessageGenerator; + use Symfony\Component\DependencyInjection\Definition; + use Symfony\Component\DependencyInjection\Reference; + + $container->register('app.message_generator', 'AppBundle\Service\MessageGenerator') + ->addMethodCall('setLogger', array(new Reference('logger'))); From e70f3afd67ebf89ff362317c61240d9f199936a9 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sat, 15 Apr 2017 11:02:04 -0400 Subject: [PATCH 2/4] proofread wip --- service_container.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/service_container.rst b/service_container.rst index bab4aade28c..8780dee040a 100644 --- a/service_container.rst +++ b/service_container.rst @@ -55,7 +55,7 @@ something? No problem:: } } -``logger`` is a unique key for the logger object. What other services are available? +``logger`` is a unique key for the ``Logger`` object. What other services are available? Find out by running: .. code-block:: terminal @@ -80,7 +80,7 @@ twig Twig_Environment validator Symfony\Component\Validator\Validator\ValidatorInterface ------------------------------- ------------------------------------------------------------------- -Throughout the docs, you'll see how to see the many different services that live +Throughout the docs, you'll see how to use the many different services that live in the container. .. sidebar:: Container: Lazy-loaded for speed @@ -90,7 +90,6 @@ in the container. instantiate a service until (and unless) you ask for it. If during a request you never use the ``validator`` service, the container will never create it. - .. index:: single: Service Container; Configuring services From 9d08b17fc8ebf4101e0f4e76454b0837ed69fe00 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sat, 15 Apr 2017 11:07:06 -0400 Subject: [PATCH 3/4] more tweaks --- service_container.rst | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/service_container.rst b/service_container.rst index 8780dee040a..707694be5c3 100644 --- a/service_container.rst +++ b/service_container.rst @@ -87,8 +87,9 @@ in the container. 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. If during a request - you never use the ``validator`` service, the container will never create it. + 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 @@ -183,7 +184,7 @@ When you ask for the ``app.message_generator`` 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 you memory and increasing the speed of your app. This also means that there's almost -no performance hit for defining a lot of services. +no performance overhead for defining a lot of services. As a bonus, the ``app.message_generator`` service is only created *once*: the same instance is returned each time you ask for it. @@ -192,7 +193,7 @@ Injecting Services/Config into a Service ---------------------------------------- What if you want to use the ``logger`` service from within ``MessageGenerator``? -Your service does *not* have a ``$this->container`` property - that's a special power +Your service does *not* have a ``$this->container`` property: that's a special power only controllers have. Instead, you should create a ``__construct()`` method, add a ``$logger`` argument @@ -222,7 +223,7 @@ and set it on a ``$logger`` property:: .. tip:: The ``LoggerInterface`` type-hint in the ``__construct()`` method is optional, - but a good idea. You can find the right type-hint by reading the docs for the + 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. Next, tell the container the service has a constructor argument: @@ -265,11 +266,14 @@ Next, tell the container the service has a constructor argument: array(new Reference('logger')) )); +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. + The ``arguments`` key holds an array of all of the constructor arguments to the -service (just 1 so far). The ``@`` symbol before ``@logger`` is special: it tells +service (just 1 so far). The ``@`` symbol before ``@logger`` is important: it tells Symfony to pass the *service* named ``logger``. -You can pass anything as arguments. For example, suppose you want to make your +But you can pass anything as arguments. For example, suppose you want to make your class a bit more configurable:: // src/AppBundle/Service/MessageGenerator.php @@ -399,7 +403,7 @@ and reference it with the ``%parameter_name%`` syntax: array(new Reference('logger'), '%enable_generator_logging%') )); -Actually, once you define a parameter, it can be referenced via this ``%parameter_name%`` +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 are defined in a :ref:`parameters.yml file `. @@ -431,7 +435,6 @@ You can also fetch parameters directly from the container:: For more info about parameters, see :doc:`/service_container/parameters`. - Learn more ---------- From b3e65bd08340bc014445f767cf1ec6d6ae540f70 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sat, 15 Apr 2017 11:11:50 -0400 Subject: [PATCH 4/4] Tweaks --- service_container.rst | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/service_container.rst b/service_container.rst index 707694be5c3..e11c236acad 100644 --- a/service_container.rst +++ b/service_container.rst @@ -60,25 +60,26 @@ Find out by running: .. code-block:: terminal - php bin/console debug:container + $ php bin/console debug:container This is just a *small* sample of the output: +=============================== ======================================================================= Service ID Class name -------------------------------- ------------------------------------------------------------------- -doctrine Doctrine\Bundle\DoctrineBundle\Registry -filesystem Symfony\Component\Filesystem\Filesystem -form.factory Symfony\Component\Form\FormFactory -logger Symfony\Bridge\Monolog\Logger -request_stack Symfony\Component\HttpFoundation\RequestStack -router Symfony\Bundle\FrameworkBundle\Routing\Router -security.authorization_checker Symfony\Component\Security\Core\Authorization\AuthorizationChecker -security.password_encoder Symfony\Component\Security\Core\Encoder\UserPasswordEncoder -session Symfony\Component\HttpFoundation\Session\Session -translator Symfony\Component\Translation\DataCollectorTranslator -twig Twig_Environment -validator Symfony\Component\Validator\Validator\ValidatorInterface -------------------------------- ------------------------------------------------------------------- +=============================== ======================================================================= +doctrine ``Doctrine\Bundle\DoctrineBundle\Registry`` +filesystem ``Symfony\Component\Filesystem\Filesystem`` +form.factory ``Symfony\Component\Form\FormFactory`` +logger ``Symfony\Bridge\Monolog\Logger`` +request_stack ``Symfony\Component\HttpFoundation\RequestStack`` +router ``Symfony\Bundle\FrameworkBundle\Routing\Router`` +security.authorization_checker ``Symfony\Component\Security\Core\Authorization\AuthorizationChecker`` +security.password_encoder ``Symfony\Component\Security\Core\Encoder\UserPasswordEncoder`` +session ``Symfony\Component\HttpFoundation\Session\Session`` +translator ``Symfony\Component\Translation\DataCollectorTranslator`` +twig ``Twig_Environment`` +validator ``Symfony\Component\Validator\Validator\ValidatorInterface`` +=============================== ======================================================================= Throughout the docs, you'll see how to use the many different services that live in the container.