diff --git a/components/dependency_injection/parentservices.rst b/components/dependency_injection/parentservices.rst index e5a44e2a30e..530c078fe27 100644 --- a/components/dependency_injection/parentservices.rst +++ b/components/dependency_injection/parentservices.rst @@ -56,11 +56,14 @@ The service config for these classes would look something like this: # ... newsletter_manager.class: NewsletterManager greeting_card_manager.class: GreetingCardManager + services: my_mailer: # ... + my_email_formatter: # ... + newsletter_manager: class: "%newsletter_manager.class%" calls: @@ -82,12 +85,14 @@ The service config for these classes would look something like this: - + - + + + @@ -96,10 +101,12 @@ The service config for these classes would look something like this: + + @@ -108,29 +115,34 @@ The service config for these classes would look something like this: .. code-block:: php - use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; // ... $container->setParameter('newsletter_manager.class', 'NewsletterManager'); $container->setParameter('greeting_card_manager.class', 'GreetingCardManager'); - $container->setDefinition('my_mailer', ...); - $container->setDefinition('my_email_formatter', ...); - $container->setDefinition('newsletter_manager', new Definition( - '%newsletter_manager.class%' - ))->addMethodCall('setMailer', array( - new Reference('my_mailer') - ))->addMethodCall('setEmailFormatter', array( - new Reference('my_email_formatter') - )); - $container->setDefinition('greeting_card_manager', new Definition( - '%greeting_card_manager.class%' - ))->addMethodCall('setMailer', array( - new Reference('my_mailer') - ))->addMethodCall('setEmailFormatter', array( - new Reference('my_email_formatter') - )); + $container->register('my_mailer', ...); + $container->register('my_email_formatter', ...); + + $container + ->register('newsletter_manager', '%newsletter_manager.class%') + ->addMethodCall('setMailer', array( + new Reference('my_mailer'), + )) + ->addMethodCall('setEmailFormatter', array( + new Reference('my_email_formatter'), + )) + ; + + $container + ->register('greeting_card_manager', '%greeting_card_manager.class%') + ->addMethodCall('setMailer', array( + new Reference('my_mailer'), + )) + ->addMethodCall('setEmailFormatter', array( + new Reference('my_email_formatter'), + )) + ; There is a lot of repetition in both the classes and the configuration. This means that if you changed, for example, the ``Mailer`` of ``EmailFormatter`` @@ -180,15 +192,9 @@ a parent for a service. .. code-block:: yaml - parameters: - # ... - newsletter_manager.class: NewsletterManager - greeting_card_manager.class: GreetingCardManager + # ... services: - my_mailer: - # ... - my_email_formatter: - # ... + # ... mail_manager: abstract: true calls: @@ -200,34 +206,33 @@ a parent for a service. parent: mail_manager greeting_card_manager: - class: "%greeting_card_manager.class%" + class: "%greeting_card_manager.class%" parent: mail_manager .. code-block:: xml - - - NewsletterManager - GreetingCardManager - - + - - - - - - + + - - + + + + .. code-block:: php @@ -237,29 +242,26 @@ a parent for a service. use Symfony\Component\DependencyInjection\Reference; // ... - $container->setParameter('newsletter_manager.class', 'NewsletterManager'); - $container->setParameter('greeting_card_manager.class', 'GreetingCardManager'); - - $container->setDefinition('my_mailer', ...); - $container->setDefinition('my_email_formatter', ...); - $container->setDefinition('mail_manager', new Definition( - ))->setAbstract( - true - )->addMethodCall('setMailer', array( - new Reference('my_mailer') - ))->addMethodCall('setEmailFormatter', array( - new Reference('my_email_formatter') - )); - $container->setDefinition('newsletter_manager', new DefinitionDecorator( - 'mail_manager' - ))->setClass( - '%newsletter_manager.class%' - ); - $container->setDefinition('greeting_card_manager', new DefinitionDecorator( - 'mail_manager' - ))->setClass( - '%greeting_card_manager.class%' - ); + + $mailManager = new Definition(); + $mailManager + ->setAbstract(true); + ->addMethodCall('setMailer', array( + new Reference('my_mailer'), + )) + ->addMethodCall('setEmailFormatter', array( + new Reference('my_email_formatter'), + )) + ; + $container->setDefinition('mail_manager', $mailManager); + + $newsletterManager = new DefinitionDecorator('mail_manager'); + $newsletterManager->setClass('%newsletter_manager.class%'); + $container->setDefinition('newsletter_manager', $newsletterManager); + + $greetingCardManager = new DefinitionDecorator('mail_manager'); + $greetingCardManager->setClass('%greeting_card_manager.class%'); + $container->setDefinition('greeting_card_manager', $greetingCardManager); In this context, having a ``parent`` service implies that the arguments and method calls of the parent service should be used for the child services. @@ -289,6 +291,13 @@ would cause an exception to be raised for a non-abstract service. In order for parent dependencies to resolve, the ``ContainerBuilder`` must first be compiled. See :doc:`/components/dependency_injection/compilation` for more details. + +.. tip:: + + In the examples shown, the classes sharing the same configuration also extend + from the same parent in PHP. This does not need to be the case though, you can + extract common parts of similar service definitions into a parent service without + also extending a parent class in PHP. Overriding Parent Dependencies ------------------------------ @@ -303,19 +312,14 @@ to the ``NewsletterManager`` class, the config would look like this: .. code-block:: yaml - parameters: - # ... - newsletter_manager.class: NewsletterManager - greeting_card_manager.class: GreetingCardManager + # ... services: - my_mailer: - # ... + # ... my_alternative_mailer: # ... - my_email_formatter: - # ... + mail_manager: - abstract: true + abstract: true calls: - [setMailer, ["@my_mailer"]] - [setEmailFormatter, ["@my_email_formatter"]] @@ -332,36 +336,37 @@ to the ``NewsletterManager`` class, the config would look like this: .. code-block:: xml - - - NewsletterManager - GreetingCardManager - + - - - - - - - + + + + - + + - + + .. code-block:: php @@ -371,150 +376,42 @@ to the ``NewsletterManager`` class, the config would look like this: use Symfony\Component\DependencyInjection\Reference; // ... - $container->setParameter('newsletter_manager.class', 'NewsletterManager'); - $container->setParameter('greeting_card_manager.class', 'GreetingCardManager'); - - $container->setDefinition('my_mailer', ...); $container->setDefinition('my_alternative_mailer', ...); - $container->setDefinition('my_email_formatter', ...); - $container->setDefinition('mail_manager', new Definition( - ))->setAbstract( - true - )->addMethodCall('setMailer', array( - new Reference('my_mailer') - ))->addMethodCall('setEmailFormatter', array( - new Reference('my_email_formatter') - )); - $container->setDefinition('newsletter_manager', new DefinitionDecorator( - 'mail_manager' - ))->setClass( - '%newsletter_manager.class%' - )->addMethodCall('setMailer', array( - new Reference('my_alternative_mailer') - )); - $container->setDefinition('greeting_card_manager', new DefinitionDecorator( - 'mail_manager' - ))->setClass( - '%greeting_card_manager.class%' - ); + + $mailManager = new Definition(); + $mailManager + ->setAbstract(true); + ->addMethodCall('setMailer', array( + new Reference('my_mailer'), + )) + ->addMethodCall('setEmailFormatter', array( + new Reference('my_email_formatter'), + )) + ; + $container->setDefinition('mail_manager', $mailManager); + + $newsletterManager = new DefinitionDecorator('mail_manager'); + $newsletterManager->setClass('%newsletter_manager.class%'); + ->addMethodCall('setMailer', array( + new Reference('my_alternative_mailer'), + )) + ; + $container->setDefinition('newsletter_manager', $newsletterManager); + + $greetingCardManager = new DefinitionDecorator('mail_manager'); + $greetingCardManager->setClass('%greeting_card_manager.class%'); + $container->setDefinition('greeting_card_manager', $greetingCardManager); The ``GreetingCardManager`` will receive the same dependencies as before, but the ``NewsletterManager`` will be passed the ``my_alternative_mailer`` instead of the ``my_mailer`` service. -Collections of Dependencies ---------------------------- - -It should be noted that the overridden setter method in the previous example -is actually called twice - once per the parent definition and once per the -child definition. In the previous example, that was fine, since the second -``setMailer`` call replaces mailer object set by the first call. - -In some cases, however, this can be a problem. For example, if the overridden -method call involves adding something to a collection, then two objects will -be added to that collection. The following shows such a case, if the parent -class looks like this:: - - abstract class MailManager - { - protected $filters; - - public function setFilter($filter) - { - $this->filters[] = $filter; - } - - // ... - } - -If you had the following config: - -.. configuration-block:: - - .. code-block:: yaml - - parameters: - # ... - newsletter_manager.class: NewsletterManager - services: - my_filter: - # ... - another_filter: - # ... - mail_manager: - abstract: true - calls: - - [setFilter, ["@my_filter"]] - - newsletter_manager: - class: "%newsletter_manager.class%" - parent: mail_manager - calls: - - [setFilter, ["@another_filter"]] - - .. code-block:: xml - - - - NewsletterManager - - - - - - - - - - - - - - - - - - - - - - .. code-block:: php - - use Symfony\Component\DependencyInjection\Definition; - use Symfony\Component\DependencyInjection\DefinitionDecorator; - use Symfony\Component\DependencyInjection\Reference; - - // ... - $container->setParameter('newsletter_manager.class', 'NewsletterManager'); - $container->setParameter('mail_manager.class', 'MailManager'); - - $container->setDefinition('my_filter', ...); - $container->setDefinition('another_filter', ...); - $container->setDefinition('mail_manager', new Definition( - ))->setAbstract( - true - )->addMethodCall('setFilter', array( - new Reference('my_filter') - )); - $container->setDefinition('newsletter_manager', new DefinitionDecorator( - 'mail_manager' - ))->setClass( - '%newsletter_manager.class%' - )->addMethodCall('setFilter', array( - new Reference('another_filter') - )); - -In this example, the ``setFilter`` of the ``newsletter_manager`` service -will be called twice, resulting in the ``$filters`` array containing both -``my_filter`` and ``another_filter`` objects. This is great if you just want -to add additional filters to the subclasses. If you want to replace the filters -passed to the subclass, removing the parent setting from the config will -prevent the base class from calling ``setFilter``. - -.. tip:: +.. caution:: - In the examples shown there is a similar relationship between the parent - and child services and the underlying parent and child classes. This does - not need to be the case though, you can extract common parts of similar - service definitions into a parent service without also inheriting a parent - class. + You can't override method calls. When you defined new method calls in the child + service, it'll be added to the current set of configured method calls. This means + it works perfectly when the setter overrides the current property, but it doesn't + work as expected when the setter appends it to the existing data (e.g. an + ``addFilters()`` method). + In those cases, the only solution is to *not* extend the parent service and configuring + the service just like you did before knowing this feature.