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. NAMING THE CONTAINER A container by any other name... ▸

    The container ▸ Application ▸ IOC Container ▸ DI Container
  2. Inversion of control: Defining (once) at the framework level how

    to implement specific features or code paths instead of (multiple times) in the code.
  3. 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... :
  4. class MyClass { public function doThing() { echo 'abc'; }

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

    } $instance = app(MyClass::class); $instance->doThing(); At this point not actually providing any value. But...
  6. class MyClass { protected $otherThing; public function __construct(OtherThing $otherThing) {

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

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

    $this->otherThing = $otherThing; } } $instance = app(MyClass::class); // No error!
  9. 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.
  10. <?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.
  11. WHAT IF I DON'T KNOW IT'S A TURTLE? (What if

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

    $this->apiKey = $apiKey; } } $service = app(MyService::class);
  13. 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);
  14. 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' ); });
  15. 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"
  16. 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"
  17. 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"
  18. 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"
  19. ALIAS BINDS A STRING KEY TO ANOTHER STRING KEY app()->alias(OnlyWantOne::class,

    'owo'); $one = app('owo'); echo get_class($one); // OnlyWantOne
  20. 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 );
  21. 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 ], // ... ]; }
  22. 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
  23. 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(); }
  24. FORM REQUESTS // PostsController public function update(UpdatePost $request) { //

    do stuff } // UpdatePost public function rules() { return [ 'title' => 'required|unique:posts|max:255', 'body' => 'required', ]; }
  25. 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 } }
  26. 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']);
  27. 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.
  28. 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?
  29. 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, ]; } }
  30. WHAT IS A FACADE? A convenience class providing static access

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

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

    container app()->bind('twitter', function () { // ... });
  34. 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'; } }
  35. 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();
  36. 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);
  37. 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);
  38. MISCELLANY Array-access $app = app(); // or, more likely, injected

    $logger = $app['logger']; $app['mailer'] = new MailgunMailer(...); $app['countries'] = ['Afghanistan'];