Dependency Injection Container

Video loading...

  • 0:00
    Dependency Injection and the Art of Services and Containers
  • 0:02
    Dependency Injection Container with Leanna Pelham
  • 0:09
    Our project now has services, and an interface
  • 0:12
    and is fully using dependency injection.
  • 0:15
    Nice work.
  • 0:16
    One of the downsides of dependency injection
  • 0:18
    is that all the complexity of creating and configuring objects
  • 0:22
    is now your job.
  • 0:23
    This isn't so bad since it happens in one place
  • 0:26
    and gives you so much control, but it is something we can improve.
  • 0:30
    If you want to make this easier, the tool you need
  • 0:33
    is called a Dependency Injection Container.
  • 0:37
    A lot of DI containers exist in PHP, but let's use Composer to grab
  • 0:41
    the simplest one of all, called Pimple.
  • 0:48
    Add a require key to composer.json to include the library.
  • 1:04
    Make sure you've downloaded Composer and then
  • 1:06
    run php composer.phar install to download Pimple.
  • 1:14
    Pimple is both powerful and tiny, kind of
  • 1:17
    like having one on prom night.
  • 1:19
    It is just a single file taking up around 200 lines.
  • 1:23
    That's one reason I love it.
  • 1:30
    Create a new Pimple container.
  • 1:32
    This is an object, of course, but it looks and acts like an array
  • 1:35
    that we store all of our service objects on.
  • 1:38
    Start by adding the SmtpMailer object under a key called mailer.
  • 1:43
    Instead of setting it directly, wrap it
  • 1:45
    in a call to share and in an anonymous function.
  • 1:49
    We'll talk about this more in a second,
  • 1:51
    but just return the mailer object from the function for now.
  • 2:01
    To access the SmtpMailer object, use the array syntax again.
  • 2:08
    It's that simple.
  • 2:09
    Run the application to spam-- I mean, send
  • 2:12
    great opportunities-- to our friends.
  • 2:15
    We haven't fully seen the awesomeness of the container yet,
  • 2:18
    but there are already some cool things happening.
  • 2:21
    First, wrapping the instantiation of the mailer service
  • 2:24
    in an anonymous function makes its creation lazy.
  • 2:28
    This means that the object isn't created until much later when we
  • 2:31
    reference the mailer service and ask the container to give it to us.
  • 2:36
    And if we never reference mailer, it's
  • 2:38
    never created at all, saving us time and memory.
  • 2:42
    Second, using the share method means that no matter how many times we
  • 2:45
    ask for the mailer service, it only creates it once.
  • 2:49
    Each call returns the original object.
  • 2:52
    This is a very common property of a service.
  • 2:54
    You only ever need just one.
  • 2:57
    If we need to send many emails, we don't need many mailers.
  • 3:01
    We just need the one and then we'll call send on it many times.
  • 3:05
    This also makes our code faster and less
  • 3:07
    memory intensive since the container guarantees
  • 3:10
    that we only have one mailer.
  • 3:13
    This is another detail that we don't need to worry about.
  • 3:19
    Let's keep going and add our other services to the container.
  • 3:22
    But first, I'll add some comments to separate which part of our code
  • 3:26
    is building the container and which part
  • 3:28
    is our actual application code.
  • 3:35
    Let's add FriendHarvester to the container next.
  • 3:52
    That's easy, except that we somehow need access to the PDO object
  • 3:57
    and the container itself so we can get to required dependencies.
  • 4:01
    Fortunately, the anonymous function is passed in arguments,
  • 4:04
    which is the Pimple container itself.
  • 4:07
    To fix the missing PDO object, just make it a service as well.
  • 4:26
    Now we can easily update the FriendHarvester
  • 4:28
    service configuration to use it.
  • 4:30
    With the new FriendHarvester service,
  • 4:32
    update the application code to just grab it out of the container.
  • 4:45
    Now that all three of our services are in the container,
  • 4:47
    you can start to see the power that this gives us.
  • 4:50
    All of the logic of exactly which objects depend on which
  • 4:53
    other object is abstracted away into the container itself.
  • 4:57
    Whenever we need to use the service, we just reference it.
  • 5:00
    We don't care how it's created or what dependencies it may have.
  • 5:04
    It's all handled elsewhere, and if constructor arguments for a service
  • 5:08
    like the mailer change later, we only
  • 5:10
    need to update one spot in our code.
  • 5:13
    Nobody else knows or cares about this change.
  • 5:17
    Remember also that the services are constructed lazily.
  • 5:20
    When we ask for the FriendHarvester, the PDO and mailer services
  • 5:23
    haven't been instantiated yet.
  • 5:26
    Fortunately, the container is smart enough
  • 5:28
    to create them first and then pass them
  • 5:30
    into the FriendHarvester constructor.
  • 5:32
    All of that happens automatically behind the scenes.
  • 5:36
    But a container can hold more than just services.
  • 5:39
    It can house our configuration as well.
  • 5:44
    Create a new key on the container called database.dsn.
  • 5:48
    Set it to our configuration and then use it
  • 5:50
    when we're creating the PDO object.
  • 5:52
    We're not using the share method or the anonymous function
  • 5:55
    because this is just a scalar value.
  • 5:58
    And we don't need to worry about the lazy loading stuff.
  • 6:11
    We can do the same thing with the SMTP configuration parameters.
  • 6:16
    Notice that the name I'm giving to each of these parameters
  • 6:18
    isn't important at all.
  • 6:20
    I'm just inventing a sane pattern and using the name where I need it.
  • 6:43
    When we're all done, the application works exactly as before.
  • 6:47
    What we've gained is the ability to keep all of our configuration
  • 6:50
  • 6:51
    This would make it very easy to change your database
  • 6:54
    to use MySQL or change the SMTP password.
  • 6:59
    Now that we have this flexibility let's move the configuration
  • 7:02
    and service building into separate files altogether.
  • 7:05
    Create a new app directory and config.php and services.php files.
  • 7:20
    Require each of these from the app.php script right
  • 7:23
    after creating the container.
  • 7:37
    Next, move the configuration logic into config.php
  • 7:40
    and all the services into services.php.
  • 8:03
    Be sure to update the sqlite database path
  • 8:05
    and config.php since we just moved this file.
  • 8:13
  • 8:13
    We now have configuration, service building,
  • 8:16
    and our actual application code all separated into different files.
  • 8:26
    Notice how clear our actual app code is now.
  • 8:29
    It's just one line to get out a service and another to use it.
  • 8:33
    If this were a web application, this would live in a controller.
  • 8:37
    You'll often hear that you should have
  • 8:38
    skinny controllers and a fat model.
  • 8:41
    And whether you realize it or not, we've just seen that in practice.
  • 8:45
    When we started, app.php held all of our logic.
  • 8:49
    After refactoring into services and using a service container,
  • 8:53
    app.php is skinny.
  • 8:55
    The fat model refers to moving all of your logic
  • 8:57
    into separate single purpose classes, which are sometimes
  • 9:00
    referred to collectively as the model.
  • 9:03
    Another term for this is service-oriented architecture.
  • 9:06
    In the real world, you may not always have skinny controllers,
  • 9:10
    but always keep this philosophy in your mind.
  • 9:13
    The more skinnier your controllers, the more readable, reusable,
  • 9:17
    testable, and maintainable that code will be.
  • 9:20
    What's better?
  • 9:21
    A 300 line long chunk of code or five lines that
  • 9:24
    use a few well-named and small service objects?
  • 9:28
    One of the downsides to using a container
  • 9:30
    is that your IDE and other developers
  • 9:32
    don't exactly know what type of object a service may be.
  • 9:36
    There's no perfect answer to this since a container
  • 9:39
    is very dynamic by nature.
  • 9:42
    But what you can do is use the PHP documentation whenever possible
  • 9:46
    to explicitly say what type of object something is.
  • 9:50
    For example, after fetching the FriendHarvester service,
  • 9:53
    you can use a single line comment to tell your IDE and other developers
  • 9:56
    exactly what type of object we're getting back.
  • 10:02
    This gives us IDE autocomplete on the FriendHarvester variable.
  • 10:07
    Another common tactic is to create an object or a subclass container
  • 10:11
    and add specific methods that return different services
  • 10:14
    and have proper PHP Doc on them.
  • 10:17
    I won't show it here, but imagine we've subclassed the Pimple class
  • 10:21
    and added a getFriendHarvester method which has a proper at return
  • 10:24
    PHP Doc on it.

Dependency Injection Container


Our project now has services, an interface, and is fully using dependency injection. Nice work! One of the downsides of DI is that all the complexity of creating and configuring objects is now your job. This isn’t so bad since it all happens in one place and gives you so much control, but it is something we can improve! If you want to make this easier, the tool you need is called a dependency injection container. A lot of DI containers exist in PHP, but we're going to use Composer to grab the simplest one of all, called Pimple. (If you are unfamiliar with Composer, you should watch The Wonderful World of Composer tutorial.)

Log in or sign up to download companion files.
Additional resources: 

The Wonderful World of Composer
Composer download
To learn how to apply these concepts in Drupal 8 module development, check out the Module Development Essentials series, starting with Understand the Service Container.