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

Mastering the Illuminate Container

Mastering the Illuminate Container

An introduction to Laravel's IOC/DI service container.

Matt Stauffer

March 08, 2017
Tweet

More Decks by Matt Stauffer

Other Decks in Programming

Transcript

  1. MASTERING the
    ILLUMINATE
    CONTAINER

    View Slide

  2. View Slide

  3. WHAT IS THE CONTAINER?

    View Slide

  4. WHAT IS THE CONTAINER?
    WHAT IS LARAVEL?

    View Slide

  5. WHAT IS THE CONTAINER?
    THE CONTAINER IS THE BACKBONE OF EVERY
    LARAVEL APPLICATION.

    View Slide

  6. Routing.
    Middleware.
    Requests.
    Constructor injection.
    Facades.

    View Slide

  7. OK, IT'S
    ✨Magic✨...
    BUT WHAT DOES IT DO?

    View Slide

  8. NAMING THE CONTAINER
    A container by any other name...
    ▸ The container
    ▸ Application
    ▸ IOC Container
    ▸ DI Container

    View Slide

  9. WHAT DO
    "DI" and "IOC"
    MEAN?

    View Slide

  10. Dependency Injection: When one object supplies ("injects") the
    dependencies of another object.

    View Slide

  11. Inversion of control: Defining (once) at the framework level how to
    implement specific features or code paths instead of (multiple times)
    in the code.

    View Slide

  12. So if the container enables DI and IOC...

    View Slide

  13. THE CONTAINER
    IS THE glue

    View Slide

  14. A QUICK INTRODUCTION TO
    BINDING and RESOLVING
    WITH THE CONTAINER

    View Slide

  15. Getting an instance of the container:
    $container = app();

    View Slide

  16. Resolving an instance out of the container:
    $container = app();
    $logger = $container->make(Illuminate\Log\Writer::class);
    // or
    $logger = app()->make(Illuminate\Log\Writer::class);
    drop instance.. Or even shorter... :

    View Slide

  17. Shortcut to resolving a instance out of the container:
    $logger = app(Illuminate\Log\Writer::class);

    View Slide

  18. BUT WAIT—
    WHY IS THIS ANY BETTER THAN
    new Thing?

    View Slide

  19. namespace Illuminate\Foundation;
    class Application extends Container
    {
    public function make($key)
    {
    return new $key;
    }
    }

    View Slide

  20. class MyClass
    {
    public function doThing()
    {
    echo 'abc';
    }
    }
    $instance = new MyClass;
    $instance->doThing();

    View Slide

  21. class MyClass
    {
    public function doThing()
    {
    echo 'abc';
    }
    }
    $instance = app(MyClass::class);
    $instance->doThing();
    At this point not actually providing any value. But...

    View Slide

  22. WHAT ABOUT DEPENDENCIES?

    View Slide

  23. class MyClass
    {
    protected $otherThing;
    public function __construct(OtherThing $otherThing)
    {
    $this->otherThing = $otherThing;
    }
    }
    $instance = new MyClass;

    View Slide

  24. View Slide

  25. class MyClass
    {
    protected $otherThing;
    public function __construct(OtherThing $otherThing)
    {
    $this->otherThing = $otherThing;
    }
    }
    $instance = new MyClass(new OtherThing);

    View Slide

  26. class MyClass
    {
    protected $otherThing;
    public function __construct(OtherThing $otherThing)
    {
    $this->otherThing = $otherThing;
    }
    }
    $instance = app(MyClass::class);
    // No error!

    View Slide

  27. AUTOWIRING

    View Slide

  28. AUTOWIRING
    A framework/package's ability to instantiate a class without being
    given explicit instructions from the user of how to instantiate that
    class.
    If a class has dependencies, an autowiring framework will use reflection
    to determine and provide its dependencies.

    View Slide

  29. namespace Illuminate\Foundation;
    class Application
    {
    public function make($key)
    {
    $dependencies = $this->getConstructorDependenciesFor($key);
    return new $key(... $dependencies);
    }
    }
    It now uses reflection to figure out the constructor dependencies.

    View Slide

  30. View Slide

  31. WHAT IF I DON'T KNOW IT'S A TURTLE?
    (What if the dependencies aren't typehinted?)

    View Slide

  32. class MyService
    {
    private $apiKey;
    public function __construct($apiKey)
    {
    $this->apiKey = $apiKey;
    }
    }
    $service = app(MyService::class);

    View Slide

  33. View Slide

  34. MANUAL BINDING

    View Slide

  35. Types of manual binding:
    ▸ bind
    ▸ singleton
    ▸ instance
    ▸ alias

    View Slide

  36. BIND
    // Binding a class to the container
    app()->bind(MyService::class, function () {
    return new MyService('0h5oh1kn09da');
    });
    // Now, we can use it:
    $service = app(MyService::class);

    View Slide

  37. USING $app WITHIN A BINDING CLOSURE
    class MyService
    {
    public function __construct(Thing $thing, Other $other, $apiKey)
    { ... }
    }
    app()->bind(MyService::class, function ($app) {
    return new MyService(
    $app->make(Thing::class),
    $app->make(Other::class),
    'api1o2n34'
    );
    });

    View Slide

  38. BIND (AND AUTOWIRING) GENERATES NEW INSTANCES
    class OnlyWantOne
    {
    public function __construct()
    {
    $this->id = str_random();
    }
    }
    $one = app(OnlyWantOne::class);
    $two = app(OnlyWantOne::class);
    dd($one->id, $two->id);
    // "metkZ7bRlSjEv8rP", "2ufdDRgcjep9DEFL"

    View Slide

  39. BIND (AND AUTOWIRING) GENERATES NEW INSTANCES
    class OnlyWantOne
    { ... }
    app()->bind(OnlyWantOne::class, function () {
    return new OnlyWantOne;
    });
    $one = app(OnlyWantOne::class);
    $two = app(OnlyWantOne::class);
    dd($one->id, $two->id);
    // "CtjkZ1b91jKf68Fq", "4B3fDmUcA5p9DnPz"

    View Slide

  40. SINGLETON RUNS ONLY ONCE
    class OnlyWantOne
    { ... }
    app()->singleton(OnlyWantOne::class, function () {
    return new OnlyWantOne;
    });
    $one = app(OnlyWantOne::class);
    $two = app(OnlyWantOne::class);
    dd($one->id, $two->id);
    // "RvSk6APVA2d8C3Yx", "RvSk6APVA2d8C3Yx"

    View Slide

  41. INSTANCE ALWAYS RETURNS THE GIVEN INSTANCE
    class OnlyWantOne
    { ... }
    $original = new OnlyWantOne;
    app()->instance(OnlyWantOne::class, $original);
    $one = app(OnlyWantOne::class);
    $two = app(OnlyWantOne::class);
    dd($one->id, $two->id, $original->id);
    // "zLgaLFnW7AD5ZIGM", "zLgaLFnW7AD5ZIGM", "zLgaLFnW7AD5ZIGM"

    View Slide

  42. ALIAS BINDS A STRING KEY TO ANOTHER STRING KEY
    app()->alias(OnlyWantOne::class, 'owo');
    $one = app('owo');
    echo get_class($one);
    // OnlyWantOne

    View Slide

  43. ALIAS FREEDOM
    We can really bind to any arbitrary string.
    Therefore interfaces are bindable too.
    app()->alias(
    PostmarkMailer::class,
    Mailer::class // Interface
    );
    app()->alias(
    MagicService::class,
    'my-favorite-interface-thing-or-whatever' // silly string
    );

    View Slide

  44. LARAVEL'S SHORT ALIASES
    app('logger');
    // Same as
    app(Illuminate\Log\Writer::class);

    View Slide

  45. LARAVEL'S SHORT ALIASES (CONTINUED)

    View Slide

  46. LARAVEL'S SHORT ALIASES (CONTINUED)
    Each alias is pointing to a real class instantiated to a shortcut.
    // Illuminate/Foundation/Application
    public function registerCoreContainerAliases()
    {
    $aliases = [
    'app' => [
    \Illuminate\Foundation\Application::class,
    \Illuminate\Contracts\Container\Container::class,
    \Illuminate\Contracts\Foundation\Application::class
    ], // ...
    ];
    }

    View Slide

  47. COMMON USES of AUTOMATIC DEPENDENCY INJECTION
    ▸ Requesting classes from the framework (like Request, Logger,
    Mailer) in controllers and other user-facing code
    ▸ Route model binding
    ▸ Form Requests
    ▸ Relying on constructor injection more confidently in custom classes

    View Slide

  48. REQUESTING CLASSES FROM THE FRAMEWORK (LIKE
    REQUEST, LOGGER, MAILER) IN CONTROLLERS AND OTHER
    USER-FACING CODE
    // PostsController
    public function store(Request $request)
    {
    $this->validate($request, [ ... ]);
    Post::create($request->only(['title', 'body']));
    return back();
    }

    View Slide

  49. ROUTE MODEL BINDING
    Route::get('/posts/{post}', function (Post $post) {
    return $post;
    });

    View Slide

  50. FORM REQUESTS
    // PostsController
    public function update(UpdatePost $request)
    {
    // do stuff
    }
    // UpdatePost
    public function rules()
    {
    return [
    'title' => 'required|unique:posts|max:255',
    'body' => 'required',
    ];
    }

    View Slide

  51. RELYING ON CONSTRUCTOR INJECTION MORE CONFIDENTLY
    IN CUSTOM CLASSES
    namespace Symposium\JoindIn;
    // ... imports
    class Client
    {
    // ... class properties
    public function __construct(Client $guzzle, Logger $logger, Cache $cache)
    {
    // ... assign as class properties
    }
    }

    View Slide

  52. BONUS GOODY: CALLING METHODS
    app()->call([$object, $method]);
    app()->call('ClassName@method');
    // e.g.
    app()->call([$this, 'doThingWithDependencies']);
    app()->call('Synchronizer@activate');
    app()->call('SuperClient@get', ['/posts/2']);

    View Slide

  53. SERVICE
    PROVIDERS

    View Slide

  54. SERVICE PROVIDERS: WHAT ARE THEY?
    Central location to bind services for use by your application's code,
    usually grouped by functionality; the place where all of Laravel's
    bootstrapping takes place.

    View Slide

  55. THE ANATOMY OF A SERVICE PROVIDER
    ▸ register: binding into the container
    ▸ boot: called after all registrations are complete; everything else
    here (registering event listeners, routes, or any other functionality)
    ▸ defer?: can we avoid running this service provider if certain
    bindings aren't requested?
    ▸ provides: if deferred, which bindings' requests should trigger
    running this service provider?

    View Slide

  56. SERVICE PROVIDERS (AN EXAMPLE)
    class BroadcastServiceProvider extends ServiceProvider
    {
    protected $defer = true;
    public function register()
    {
    $this->app->singleton(BroadcastManager::class, ...);
    $this->app->singleton(BroadcasterContract::class, ...);
    $this->app->alias(BroadcastManager::class, BroadcastingFactory::class);
    }
    public function provides()
    {
    return [
    BroadcastManager::class,
    BroadcastingFactory::class,
    BroadcasterContract::class,
    ];
    }
    }

    View Slide

  57. ASIDE:
    APP SERVICE PROVIDER

    View Slide

  58. PACKAGES and SERVICE
    PROVIDERS

    View Slide

  59. Many packages say to add two things in config/app.php, right?
    1. service provider, 2. alias

    View Slide

  60. transition time...

    View Slide

  61. FACADES

    View Slide

  62. !FACADES"

    View Slide

  63. FACADES

    View Slide

  64. INTERLUDE: FACADE DRAMA

    View Slide

  65. FACADES
    Route::get();
    Log::info();
    DB::statement();
    Auth::user();

    View Slide

  66. WHAT IS A FACADE?
    A convenience class
    providing static access
    to non-static methods
    on classes resolved out of the container.

    View Slide

  67. WHAT DEFINES A FACADE?
    class Cache extends Facade
    {
    protected static function getFacadeAccessor()
    {
    return 'cache';
    }
    }
    Cache::get();
    // same as:
    app('cache')->get();

    View Slide

  68. CREATING YOUR OWN FACADE
    1. Bind your class to the container
    2. Define the facade class and point it to your bound class name
    3. Profit, ostensibly

    View Slide

  69. CREATING YOUR OWN FACADE (CONTINUED)
    Bind your class to the container
    app()->bind('twitter', function () {
    // ...
    });

    View Slide

  70. CREATING YOUR OWN FACADE (CONTINUED)
    Define the facade class and point it to your bound class name
    class Twitter extends Facade
    {
    protected static function getFacadeAccessor()
    {
    return 'twitter';
    }
    }

    View Slide

  71. CREATING YOUR OWN FACADE (CONTINUED)
    Profit, ostensibly
    !

    View Slide

  72. REAL TIME FACADES
    Prepend Facades\ to your namespace when you import your class
    and now use it as a Facade backed by that class.
    class Thing
    {
    public function do()
    {
    // ...
    }
    }
    Facades\Thing::do();

    View Slide

  73. TESTING and the
    CONTAINER

    View Slide

  74. TESTING AND THE CONTAINER
    Dependency injection aids tests; the container aids DI. Swap in a mock
    or a spy easily.
    // in code
    Route::get('whatever', function (MyService $service) {
    return $service->get('whatever');
    });
    // in test
    $mock = Mockery::mock('MyService');
    app()->instance('MyService', $mock);

    View Slide

  75. TESTING AND THE CONTAINER (CONTINUED)
    It works for Facades too:
    // in code
    Route::get('whatever', function () {
    return Twitter::post('here is my status!');
    });
    // in test
    $mock = Mockery::mock(MyTwitterClient::class);
    app()->instance('twitter', $mock);

    View Slide

  76. FACADE-SPECIFIC MOCKING AND SPIES
    Cache::shouldReceive('get')
    ->with('key')
    ->andReturn('value');

    View Slide

  77. MISCELLANY
    Binding arbitrary values
    app()->bind('countries', function () {
    return [
    'Afghanistan',
    // ...
    ];
    });
    dd(app('countries'));

    View Slide

  78. MISCELLANY
    Array-access
    $app = app(); // or, more likely, injected
    $logger = $app['logger'];
    $app['mailer'] = new MailgunMailer(...);
    $app['countries'] = ['Afghanistan'];

    View Slide

  79. MISCELLANY
    Contextual binding
    $app->when(Newsletter::class)
    ->needs(Mailer::class)
    ->give(MailgunMailer::class);
    $app->when(Notifier::class)
    ->needs(Mailer::class)
    ->give(PostmarkMailer::class);

    View Slide

  80. CONCLUSION

    View Slide