Mastering the Illuminate Container

Mastering the Illuminate Container

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

60187fe0ab07ea5a46572a3ab05f61dd?s=128

Matt Stauffer

March 08, 2017
Tweet

Transcript

  1. MASTERING the ILLUMINATE CONTAINER

  2. None
  3. WHAT IS THE CONTAINER?

  4. WHAT IS THE CONTAINER? WHAT IS LARAVEL?

  5. WHAT IS THE CONTAINER? THE CONTAINER IS THE BACKBONE OF

    EVERY LARAVEL APPLICATION.
  6. Routing. Middleware. Requests. Constructor injection. Facades.

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

  8. NAMING THE CONTAINER A container by any other name... ▸

    The container ▸ Application ▸ IOC Container ▸ DI Container
  9. WHAT DO "DI" and "IOC" MEAN?

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

    another object.
  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.
  12. So if the container enables DI and IOC...

  13. THE CONTAINER IS THE glue

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

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

  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... :
  17. Shortcut to resolving a instance out of the container: $logger

    = app(Illuminate\Log\Writer::class);
  18. BUT WAIT— WHY IS THIS ANY BETTER THAN new Thing?

  19. <?php namespace Illuminate\Foundation; class Application extends Container { public function

    make($key) { return new $key; } }
  20. class MyClass { public function doThing() { echo 'abc'; }

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

    } $instance = app(MyClass::class); $instance->doThing(); At this point not actually providing any value. But...
  22. WHAT ABOUT DEPENDENCIES?

  23. class MyClass { protected $otherThing; public function __construct(OtherThing $otherThing) {

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

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

    $this->otherThing = $otherThing; } } $instance = app(MyClass::class); // No error!
  27. AUTOWIRING

  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.
  29. <?php 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.
  30. None
  31. WHAT IF I DON'T KNOW IT'S A TURTLE? (What if

    the dependencies aren't typehinted?)
  32. <?php class MyService { private $apiKey; public function __construct($apiKey) {

    $this->apiKey = $apiKey; } } $service = app(MyService::class);
  33. None
  34. MANUAL BINDING

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

    ▸ alias
  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);
  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' ); });
  38. BIND (AND AUTOWIRING) GENERATES NEW INSTANCES <?php class OnlyWantOne {

    public function __construct() { $this->id = str_random(); } } $one = app(OnlyWantOne::class); $two = app(OnlyWantOne::class); dd($one->id, $two->id); // "metkZ7bRlSjEv8rP", "2ufdDRgcjep9DEFL"
  39. BIND (AND AUTOWIRING) GENERATES NEW INSTANCES <?php 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"
  40. SINGLETON RUNS ONLY ONCE <?php 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"
  41. INSTANCE ALWAYS RETURNS THE GIVEN INSTANCE <?php 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"
  42. ALIAS BINDS A STRING KEY TO ANOTHER STRING KEY app()->alias(OnlyWantOne::class,

    'owo'); $one = app('owo'); echo get_class($one); // OnlyWantOne
  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 );
  44. LARAVEL'S SHORT ALIASES app('logger'); // Same as app(Illuminate\Log\Writer::class);

  45. LARAVEL'S SHORT ALIASES (CONTINUED)

  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 ], // ... ]; }
  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
  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(); }
  49. ROUTE MODEL BINDING Route::get('/posts/{post}', function (Post $post) { return $post;

    });
  50. FORM REQUESTS // PostsController public function update(UpdatePost $request) { //

    do stuff } // UpdatePost public function rules() { return [ 'title' => 'required|unique:posts|max:255', 'body' => 'required', ]; }
  51. RELYING ON CONSTRUCTOR INJECTION MORE CONFIDENTLY IN CUSTOM CLASSES <?php

    namespace Symposium\JoindIn; // ... imports class Client { // ... class properties public function __construct(Client $guzzle, Logger $logger, Cache $cache) { // ... assign as class properties } }
  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']);
  53. SERVICE PROVIDERS

  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.
  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?
  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, ]; } }
  57. ASIDE: APP SERVICE PROVIDER

  58. PACKAGES and SERVICE PROVIDERS

  59. Many packages say to add two things in config/app.php, right?

    1. service provider, 2. alias
  60. transition time...

  61. FACADES

  62. !FACADES"

  63. FACADES

  64. INTERLUDE: FACADE DRAMA

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

  66. WHAT IS A FACADE? A convenience class providing static access

    to non-static methods on classes resolved out of the container.
  67. WHAT DEFINES A FACADE? class Cache extends Facade { protected

    static function getFacadeAccessor() { return 'cache'; } } Cache::get(); // same as: app('cache')->get();
  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
  69. CREATING YOUR OWN FACADE (CONTINUED) Bind your class to the

    container app()->bind('twitter', function () { // ... });
  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'; } }
  71. CREATING YOUR OWN FACADE (CONTINUED) Profit, ostensibly !

  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();
  73. TESTING and the CONTAINER

  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);
  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);
  76. FACADE-SPECIFIC MOCKING AND SPIES Cache::shouldReceive('get') ->with('key') ->andReturn('value');

  77. MISCELLANY Binding arbitrary values app()->bind('countries', function () { return [

    'Afghanistan', // ... ]; }); dd(app('countries'));
  78. MISCELLANY Array-access $app = app(); // or, more likely, injected

    $logger = $app['logger']; $app['mailer'] = new MailgunMailer(...); $app['countries'] = ['Afghanistan'];
  79. MISCELLANY Contextual binding $app->when(Newsletter::class) ->needs(Mailer::class) ->give(MailgunMailer::class); $app->when(Notifier::class) ->needs(Mailer::class) ->give(PostmarkMailer::class);

  80. CONCLUSION