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.

Richard Miller

October 22, 2011
Tweet

More Decks by Richard Miller

Other Decks in Programming

Transcript

  1. Dependency Injection and
    the Symfony2 Service
    Container

    View Slide

  2. Richard Miller

    http://www.limethinking.co.uk

    http://miller.limethinking.co.uk

    @mr_r_miller

    View Slide

  3. What is Dependency Injection?

    View Slide

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

    View Slide

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

    View Slide

  6. 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);
    }
    }

    View Slide

  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

    View Slide

  8. 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);

    View Slide

  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);
    }

    View Slide

  10. Constructor Injection

    Fixed at instantiation

    Guaranteed to have been injected

    View Slide

  11. Setter Injection

    Good for optional dependencies

    Good for collections of dependencies

    View Slide

  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);

    View Slide

  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);

    View Slide

  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);

    View Slide

  15. Type Hinting

    Ensures object with correct interface injected

    Hint should be Interface not Implementation

    Helps IDE with auto-completion

    View Slide

  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);
    }

    View Slide

  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();

    View Slide

  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();

    View Slide

  19. Where does the creation of dependencies go ?

    View Slide

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

    View Slide

  21. 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]');

    View Slide

  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);
    }

    View Slide

  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;
    }

    View Slide

  24. Problem solved?

    View Slide

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

    View Slide

  26. 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);

    View Slide

  27. protected $productSaver;
    public function __construct($productSaver)
    {
    $this->productSaver = $productSaver;
    }
    public function addAction()
    {
    //validate the POST variables
    $this->productSaver->save($product);
    }

    View Slide

  28. $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);

    View Slide

  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

    View Slide

  30. Disadvantages to manual wiring

    Large unwieldy bootstrap file

    Everything set up whether used or not

    Repetitive code

    View Slide

  31. The Symfony2 Service Container to
    the rescue

    Creates and configures services

    Allows configuration with XML, YAML and PHP

    Only creates used services

    View Slide


  32. class="NameSpace\Of\Mailer"/>
    class="NameSpace\Of\MailFactory"/>

    View Slide


  33. class="NameSpace\Of\EmailNotifer">
    [email protected]



    class="NameSpace\Of\Mailer"/>
    class="NameSpace\Of\MailFactory"/>

    View Slide


  34. class="NameSpace\Of\EmailNotifer">
    [email protected]



    [email protected]


    [email protected]


    class="NameSpace\Of\Mailer"/>
    class="NameSpace\Of\MailFactory"/>


    [email protected]


    [email protected]

    View Slide

  35. 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

    View Slide

  36. $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'
    ));

    View Slide

  37. How do we actually use the container?

    View Slide

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

    View Slide

  39. class ProductController extends Controller
    {
    public function addAction()
    {
    //validate the POST variables
    $productSaver = $this->container->get('productSaver');
    $productSaver->save($product);
    }
    //--
    }

    View Slide

  40. A Criticism of Dependency Injection

    It's difficult to navigate code

    View Slide

  41. Tools to help

    container:debug command

    JMSDebuggingBundle

    View Slide

  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
    #--

    View Slide

  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

    View Slide

  44. JMSDebuggingBundle

    http://github.com/schmittjoh/JMSDebuggingBun
    dle

    View Slide

  45. View Slide

  46. View Slide

  47. View Slide

  48. View Slide

  49. View Slide

  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

    View Slide

  51. More

    Cookbook articles -
    http://symfony.com/doc/current/cookbook/

    http://miller.limethinking.co.uk

    @mr_r_miller

    http://joind.in/3704

    View Slide