Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Dependency Injection and the Symfony2 Service Container

Dependency Injection and the Symfony2 Service Container

Dependency injection and the service container are core parts of Symfony2's philosophy. Whilst it is easy to start getting services from the container, a better grasp of dependency injection will allow developers to make the most of this powerful feature.This talk starts by covering the basics of dependency injection and the different types of injection. It then moves from low level dependency injection onto using the service container for full scale application configuration. It continues with how to configure services using Symfony2's service container and the advantages of doing this in allowing application level wiring together of classes. Finally it takes a look at the tools available in Symfony2 for getting an overview of service use and interaction within in an application.

Ab44158da0498db70754ee8061e69c31?s=128

Richard Miller

October 22, 2011
Tweet

Transcript

  1. Dependency Injection and the Symfony2 Service Container

  2. Richard Miller • http://www.limethinking.co.uk • http://miller.limethinking.co.uk • @mr_r_miller

  3. What is Dependency Injection?

  4. class ProductController { public function addAction() { //validate the POST

    variables $productSaver = new ProductSaver(); $productSaver->save($product); } //-- }
  5. class ProductSaver { public function save($product) { $mapper = new

    DataMapper(); $mapper->save($product); $emailNotifier = new EmailNotifier(); $emailNotifier->notify($product); } }
  6. class EmailNotifier { public function notify($product) { $mail = new

    Mail(); $mail->addTo('productmanager@example.com'); $mail->addCC('deputyproductmanager@example.com'); //create message body using $product $mailer = new Mailer(); $mailer->send($mail); } }
  7. Problems with EmailNotifier • Not reusable • Change of email

    addresses • Change of mailer • Impossible to unit test • Any test will create an actual Mailer and send the email
  8. public function notify($product) { $mail = new Mail(); $mail->addTo($this->toAddress); $mail->addCC('deputyproductmanager@example.com');

    //create message body using $product $mailer = new Mailer(); $mailer->send($mail); } protected $toAddress; public function __construct($toAddress) { $this->toAddress = $toAddress; } $mail->addTo($this->toAddress);
  9. protected $toAddress; protected $ccAddresses = array(); public function __construct($toAddress) {

    $this->toAddress = $toAddress; } public function setCcAddress($ccAddress) { $this->ccAddresses[] = $ccAdresss; } public function notify($product) { $mail = new Mail(); $mail->addTo($this->toAddress); foreach($this->ccAddresses as $ccAddress) { $mail->addCC($ccAddress); } //create message body using $product $mailer = new Mailer(); $mailer->send($mail); } protected $ccAddresses = array(); public function addCcAddress($ccAddress) { $this->ccAddresses[] = $ccAdresss; } foreach($this->ccAddresses as $ccAddress) { $mail->addCC($ccAddress); }
  10. Constructor Injection • Fixed at instantiation • Guaranteed to have

    been injected
  11. Setter Injection • Good for optional dependencies • Good for

    collections of dependencies
  12. protected $toAddress; protected $ccAddresses = array(); protected $mailer; public function

    __construct($toAddress, MailerInterface $mailer) { $this->toAddress = $toAddress; $this->mailer = $mailer; } public function addCcAddress($ccAddress) { $this->ccAddresses[] = $ccAdresss; } public function notify($product) { $mail = new Mail(); $mail->addTo($this->toAddress); foreach($this->ccAddresses as $ccAddress) { $mail->addCC($ccAddress); } //create message body using $product $this->mailer->send($mail); } protected $mailer; MailerInterface $mailer $this->mailer = $mailer; $this->mailer->send($mail);
  13. $settings = Settings::fetch(); switch($settings->get('mailer)) { case 'smtp': $mailer = new

    SMTPMailer(); break; case 'sendmail': $mailer = new SendmailMailer(); break; case 'test': $mailer = new TestMailer(); break; case 'basic': default: $mailer = new Mailer(); break; } $mailer->send($mail);
  14. protected $toAddress; protected $ccAddresses = array(); protected $mailer; public function

    __construct($toAddress, MailerInterface $mailer) { $this->toAddress = $toAddress; $this->mailer = $mailer; } public function addCcAddress($ccAddress) { $this->ccAddresses[] = $ccAdresss; } public function notify($product) { $mail = new Mail(); $mail->addTo($this->toAddress); foreach($this->ccAddresses as $ccAddress) { $mail->addCC($ccAddress); } //create message body using $product $this->mailer->send($mail); } protected $mailer; MailerInterface $mailer $this->mailer = $mailer; $this->mailer->send($mail);
  15. Type Hinting • Ensures object with correct interface injected •

    Hint should be Interface not Implementation • Helps IDE with auto-completion
  16. protected $toAddress; protected $ccAddresses = array(); protected $mailer; public function

    __construct($toAddress, MailerInterface $mailer) { $this->toAddress = $toAddress; $this->mailer = $mailer; } public function addCcAddress($ccAddress) { $this->ccAddresses[] = $ccAdresss; } public function notify($product) { $mail = new Mail(); $mail->addTo($this->toAddress); foreach($this->ccAddresses as $ccAddress) { $mail->addCC($ccAddress); } //create message body using $product $this->mailer->send($mail); }
  17. protected $toAddress; protected $ccAddresses = array(); protected $mailer; public function

    __construct($toAddress, MailerInterface $mailer) { $this->toAddress = $toAddress; $this->mailer = $mailer; } public function addCcAddress($ccAddress) { $this->ccAddresses[] = $ccAdresss; } public function notify($product) { $mail = MailFactory::get(); $mail->addTo($this->toAddress); foreach($this->ccAddresses as $ccAddress) { $mail->addCC($ccAddress); } //create message body using $product $this->mailer->send($mail); } $mail = MailFactory::get();
  18. //-- protected $mailFactory; public function __construct($toAddress, MailerInterface $mailer, MailFactoryInterface $mailFactory

    ) { $this->toAddress = $toAddress; $this->mailer = $mailer; $this->mailFactory = $mailFactory; } //-- public function notify($product) { $mail = $this->mailFactory->get(); $mail->addTo($this->toAddress); foreach($this->ccAddresses as $ccAddress) { $mail->addCC($ccAddress); } //create message body using $product $this->mailer->send($mail); } protected $mailFactory; MailFactoryInterface $mailFactory $this->mailFactory = $mailFactory; $mail = $this->mailFactory->get();
  19. Where does the creation of dependencies go ?

  20. class ProductSaver { public function save($product) { $mapper = new

    DataMapper(); $mapper->save($product); $emailNotifier = new EmailNotifier(); $emailNotifier->notify($product); } }
  21. public function save($product) { $mapper = new DataMapper(); $mapper->save($product); $emailNotifier

    = new EmailNotifier('productmanager@example.com', new Mailer, new MailFactory ); $emailNotifier->setCcAddress('deputyproductmanager@example.com'); $emailNotifier->setCcAddress('salesteam@example.com'); $emailNotifier->notify($product); } $emailNotifier = new EmailNotifier('productmanager@example.com', new Mailer, new MailFactory ); $emailNotifier->addCcAddress('deputyproductmanager@example.com'); $emailNotifier->addCcAddress('salesteam@example.com');
  22. protected $notifiers = array(); public function addNotifier(NotifierInterface $notifier) { $this->notifiers[]

    = $notifier; } public function save($product) { $mapper = new DataMapper(); $mapper->save($product); foreach($this->notifiers as $notifier) { $notifier->notify($product); } } protected $notifiers = array(); public function addNotifier(NotifierInterface $notifier) { $this->notifiers[] = $notifier; } foreach($this->notifiers as $notifier) { $notifier->notify($product); }
  23. protected $notifiers = array(); protected $mapper; public function __construct(DataMapperInterface $mapper)

    { $this->mapper = $mapper; } //-- public function save($product) { $this->mapper->save($product); foreach($this->notifiers as $notifier) { $notifier->notify($product); } } protected $mapper; $this->mapper->save($product); public function __construct(DataMapperInterface $mapper) { $this->mapper = $mapper; }
  24. Problem solved?

  25. class ProductController { public function addAction() { //validate the POST

    variables $productSaver = new ProductSaver(); $productSaver->save($product); } //-- }
  26. public function addAction() { //validate the POST variables $emailNotifier =

    new EmailNotifier('productmanager@example.com', new Mailer, new MailFactory ); $emailNotifier->setCcAddress('deputyproductmanager@example.com'); $emailNotifier->setCcAddress('salesteam@example.com'); $mapper = new DataMapper(); $productSaver = new ProductSaver($emailNotifier, $mapper); $productSaver->setNotifier($emailNotifier); $productSaver->save($product); } $emailNotifier = new EmailNotifier('productmanager@example.com', new Mailer, new MailFactory ); $emailNotifier->addCcAddress('deputyproductmanager@example.com'); $emailNotifier->addCcAddress('salesteam@example.com'); $mapper = new DataMapper(); $productSaver = new ProductSaver($mapper); $productSaver->addNotifier($emailNotifier);
  27. protected $productSaver; public function __construct($productSaver) { $this->productSaver = $productSaver; }

    public function addAction() { //validate the POST variables $this->productSaver->save($product); }
  28. $emailNotifier = new EmailNotifier('productmanager@example.com', new Mailer, new MailFactory ); $emailNotifier->addCcAddress('deputyproductmanager@example.com');

    $emailNotifier->addCcAddress('salesteam@example.com'); $mapper = new DataMapper(); $productSaver = new ProductSaver($mapper); $productSaver->addNotifier($emailNotifier); $controller = new ProductController($productSaver);
  29. Advantages to separate wiring • Set up config differently for

    each app • No config code in classes • No need to maintain different versions for different apps • Just inject object with different functionality
  30. Disadvantages to manual wiring • Large unwieldy bootstrap file •

    Everything set up whether used or not • Repetitive code
  31. The Symfony2 Service Container to the rescue • Creates and

    configures services • Allows configuration with XML, YAML and PHP • Only creates used services
  32. <services> <service id="mailer" class="NameSpace\Of\Mailer"/> <service id="mailFactory" class="NameSpace\Of\MailFactory"/> </services>

  33. <services> <service id="emailNotifer" class="NameSpace\Of\EmailNotifer"> <argument>productmanager@example.com</argument> <argument type="service" id="mailer" /> <argument

    type="service" id="mailFactory" /> </service> <service id="mailer" class="NameSpace\Of\Mailer"/> <service id="mailFactory" class="NameSpace\Of\MailFactory"/> </services>
  34. <services> <service id="emailNotifer" class="NameSpace\Of\EmailNotifer"> <argument>productmanager@example.com</argument> <argument type="service" id="mailer" /> <argument

    type="service" id="mailFactory" /> <call method="setCcAddress"> <argument>deputyproductmanager@example.com</argument> </call> <call method="setCcAddress"> <argument>salesteam@example.com</argument> </call> </service> <service id="mailer" class="NameSpace\Of\Mailer"/> <service id="mailFactory" class="NameSpace\Of\MailFactory"/> </services> <call method="addCcAddress"> <argument>deputyproductmanager@example.com</argument> </call> <call method="addCcAddress"> <argument>salesteam@example.com</argument> </call>
  35. services: emailNotifer: class: NameSpace\Of\EmailNotifer arguments: [productmanager@example.com, @mailer, @mailFactory] calls: -

    [ addCcAddress, [ deputyproductmanager@example.com ] ] - [ addCcAddress, [ salesteam@example.com ] ] mailer: class: NameSpace\Of\Mailer mailFactory: class: NameSpace\Of\MailFactory
  36. $container->setDefinition('emailNotifer', new Definition( 'NameSpace\Of\EmailNotifer', array( 'productmanager@example.com', new Reference('mailer'), new Reference('mailFactory')

    ) ))->addMethodCall('addCcAddress', array( 'deputyproductmanager@example.com' ))->addMethodCall('addCcAddress', array( 'salesteam@example.com' )); $container->setDefinition('mailer', new Definition( 'NameSpace\Of\Mailer' )); $container->setDefinition('mailFactory', new Definition( 'NameSpace\Of\MailFactory' ));
  37. How do we actually use the container?

  38. $productController = $container->get('productController');

  39. class ProductController extends Controller { public function addAction() { //validate

    the POST variables $productSaver = $this->container->get('productSaver'); $productSaver->save($product); } //-- }
  40. A Criticism of Dependency Injection • It's difficult to navigate

    code
  41. Tools to help • container:debug command • JMSDebuggingBundle

  42. php app/console container:debug [container] Public services Service Id Class Name

    doctrine Symfony\Bundle\DoctrineBundle\Registry doctrine.dbal.default_connection Doctrine\DBAL\Connection doctrine.odm.mongodb.cache.array Doctrine\Common\Cache\ArrayCache #--
  43. php app/console container:debug assetic.asset_manager [container] Information for service assetic.asset_manager Service

    Id assetic.asset_manager Class Assetic\Factory\LazyAssetManager Tags - Scope container Public yes
  44. JMSDebuggingBundle • http://github.com/schmittjoh/JMSDebuggingBun dle

  45. None
  46. None
  47. None
  48. None
  49. None
  50. Conclusion • Class level advantages • Promotes re-usability • Allows

    true unit testing • App level advantages • Allows easy configuration of application • Easy to implement new features by dropping in new objects • No need to maintain multiple code bases
  51. More • Cookbook articles - http://symfony.com/doc/current/cookbook/ • http://miller.limethinking.co.uk • @mr_r_miller

    • http://joind.in/3704