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

Dependency Injection and the Symfony2 Service C...

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.

Richard Miller

October 22, 2011
Tweet

More Decks by Richard Miller

Other Decks in Programming

Transcript

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

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

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

    Mail(); $mail->addTo('[email protected]'); $mail->addCC('[email protected]'); //create message body using $product $mailer = new Mailer(); $mailer->send($mail); } }
  4. 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
  5. public function notify($product) { $mail = new Mail(); $mail->addTo($this->toAddress); $mail->addCC('[email protected]');

    //create message body using $product $mailer = new Mailer(); $mailer->send($mail); } protected $toAddress; public function __construct($toAddress) { $this->toAddress = $toAddress; } $mail->addTo($this->toAddress);
  6. 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); }
  7. 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);
  8. $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);
  9. 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);
  10. Type Hinting • Ensures object with correct interface injected •

    Hint should be Interface not Implementation • Helps IDE with auto-completion
  11. 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); }
  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 = 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();
  13. //-- 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();
  14. class ProductSaver { public function save($product) { $mapper = new

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

    = new EmailNotifier('[email protected]', new Mailer, new MailFactory ); $emailNotifier->setCcAddress('[email protected]'); $emailNotifier->setCcAddress('[email protected]'); $emailNotifier->notify($product); } $emailNotifier = new EmailNotifier('[email protected]', new Mailer, new MailFactory ); $emailNotifier->addCcAddress('[email protected]'); $emailNotifier->addCcAddress('[email protected]');
  16. 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); }
  17. 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; }
  18. class ProductController { public function addAction() { //validate the POST

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

    new EmailNotifier('[email protected]', new Mailer, new MailFactory ); $emailNotifier->setCcAddress('[email protected]'); $emailNotifier->setCcAddress('[email protected]'); $mapper = new DataMapper(); $productSaver = new ProductSaver($emailNotifier, $mapper); $productSaver->setNotifier($emailNotifier); $productSaver->save($product); } $emailNotifier = new EmailNotifier('[email protected]', new Mailer, new MailFactory ); $emailNotifier->addCcAddress('[email protected]'); $emailNotifier->addCcAddress('[email protected]'); $mapper = new DataMapper(); $productSaver = new ProductSaver($mapper); $productSaver->addNotifier($emailNotifier);
  20. protected $productSaver; public function __construct($productSaver) { $this->productSaver = $productSaver; }

    public function addAction() { //validate the POST variables $this->productSaver->save($product); }
  21. $emailNotifier = new EmailNotifier('[email protected]', new Mailer, new MailFactory ); $emailNotifier->addCcAddress('[email protected]');

    $emailNotifier->addCcAddress('[email protected]'); $mapper = new DataMapper(); $productSaver = new ProductSaver($mapper); $productSaver->addNotifier($emailNotifier); $controller = new ProductController($productSaver);
  22. 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
  23. Disadvantages to manual wiring • Large unwieldy bootstrap file •

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

    configures services • Allows configuration with XML, YAML and PHP • Only creates used services
  25. <services> <service id="emailNotifer" class="NameSpace\Of\EmailNotifer"> <argument>[email protected]</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>
  26. <services> <service id="emailNotifer" class="NameSpace\Of\EmailNotifer"> <argument>[email protected]</argument> <argument type="service" id="mailer" /> <argument

    type="service" id="mailFactory" /> <call method="setCcAddress"> <argument>[email protected]</argument> </call> <call method="setCcAddress"> <argument>[email protected]</argument> </call> </service> <service id="mailer" class="NameSpace\Of\Mailer"/> <service id="mailFactory" class="NameSpace\Of\MailFactory"/> </services> <call method="addCcAddress"> <argument>[email protected]</argument> </call> <call method="addCcAddress"> <argument>[email protected]</argument> </call>
  27. services: emailNotifer: class: NameSpace\Of\EmailNotifer arguments: [[email protected], @mailer, @mailFactory] calls: -

    [ addCcAddress, [ [email protected] ] ] - [ addCcAddress, [ [email protected] ] ] mailer: class: NameSpace\Of\Mailer mailFactory: class: NameSpace\Of\MailFactory
  28. $container->setDefinition('emailNotifer', new Definition( 'NameSpace\Of\EmailNotifer', array( '[email protected]', new Reference('mailer'), new Reference('mailFactory')

    ) ))->addMethodCall('addCcAddress', array( '[email protected]' ))->addMethodCall('addCcAddress', array( '[email protected]' )); $container->setDefinition('mailer', new Definition( 'NameSpace\Of\Mailer' )); $container->setDefinition('mailFactory', new Definition( 'NameSpace\Of\MailFactory' ));
  29. class ProductController extends Controller { public function addAction() { //validate

    the POST variables $productSaver = $this->container->get('productSaver'); $productSaver->save($product); } //-- }
  30. 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 #--
  31. 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
  32. 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