Your SlideShare is downloading. ×
0

Dependency Injection in Drupal 8

5,378

Published on

Published in: Technology, Business

Transcript of "Dependency Injection in Drupal 8"

  1. 1. Dependency Injection in Drupal 8
  2. 2. Intro● Rudimentary understanding of OOP assumed● Big changes in D8
  3. 3. Agenda● DI as a design pattern● DI from a framework perspective● Symfony-style DI● DI in Drupal 8
  4. 4. Agenda● DI as a design pattern● DI from a framework perspective● Symfony-style DI● DI in Drupal 8Why?How?
  5. 5. Why Dependency Injection?
  6. 6. Goal: we want to write codethat is...✔Clutter-free✔Reusable✔Testable
  7. 7. Doing It Wrong1. An example in procedural code
  8. 8. function my_module_func($val1, $val2) {module_load_include(module_x, inc);$val1 = module_x_process_val($val1);return $val1 + $val2;}
  9. 9. function my_module_func($val1, $val2) {module_load_include(module_x, inc);$val1 = module_x_process_val($val1);return $val1 + $val2;}✗ Clutter-free✗ Reusable✗ Testable
  10. 10. Doing It Wrong1. An example in procedural code2. An example in Object Oriented code
  11. 11. class Notifier {private $mailer;public function __construct() {$this->mailer = new Mailer();}public function notify() {...$this->mailer->send($from, $to, $msg);...}}
  12. 12. class Notifier {private $mailer;public function __construct() {$this->mailer = new Mailer(sendmail);}public function notify() {...$this->mailer->send($from, $to, $msg);...}}
  13. 13. class Notifier {private $mailer;public function __construct(Mailer $m) {$this->mailer = $m;}public function notify() {...$this->mailer->send($from, $to, $msg);...}}
  14. 14. class Notifier {private $mailer;public function __construct(Mailer $m) {$this->mailer = $m;}public function notify() {...$this->mailer->send($from, $to, $msg);...}}$mailer = new Mailer();$notifier = new Notifier($mailer);
  15. 15. Goal: we want to write codethat is...✔Clutter-free✔Reusable✔Testable
  16. 16. Goal: we want to write codethat is...✔Clutter-free✔Reusable✔TestableIgnorant
  17. 17. The less your code knows, the morereusable it is.
  18. 18. class Notifier {private $mailer;public function __construct(Mailer $m) {$this->mailer = $m;}public function notify() {...$this->mailer->send($from, $to, $msg);...}}$mailer = new Mailer();$notifier = new Notifier($mailer);
  19. 19. class Notifier {private $mailer;public function__construct(MailerInterface $m) {$this->mailer = $m;}public function notify() {...$this->mailer->send($from, $to, $msg);...}}$mailer = new SpecialMailer();$notifier = new Notifier($mailer);
  20. 20. class Notifier {private $mailer;public function__construct(MailerInterface $m) {$this->mailer = $m;}public function notify() {...$this->mailer->send($from, $to, $msg);...}}$mailer = new SpecialMailer();$notifier = new Notifier($mailer);Constructor Injection
  21. 21. Dependency InjectionDeclaratively express dependencies in theclass definition rather than instantiating themin the class itself.
  22. 22. Constructor Injection is notthe only form of DI
  23. 23. Setter Injection
  24. 24. class Notifier {private $mailer;public functionsetMailer(MailerInterface $m) {$this->mailer = $m;}public function notify() {...$this->mailer->send($msg);}}$mailer = new Mailer();$notifier = new Notifier();$notifier->setMailer($mailer);
  25. 25. class Notifier {private $mailer;public functionsetMailer(MailerInterface $m) {$this->mailer = $m;}public function notify() {...$this->mailer->send($msg);}}$mailer = new Mailer();$notifier = new Notifier();$notifier->setMailer($mailer);Setter Injection
  26. 26. Interface InjectionLike setter injection, except there is aninterface for each dependencys settermethod.Very verboseNot very common
  27. 27. Dependency Injection==Inversion of Control
  28. 28. “Dont call us,well call you!”(The Hollywood Principle)
  29. 29. class Notifier {private $mailer;public function__construct(MailerInterface $m) {$this->mailer = $m;}public function notify() {...$this->mailer->send($from, $to, $msg);...}}$mailer = new Mailer();$notifier = new Notifier($mailer);
  30. 30. class Notifier {private $mailer;public function__construct(MailerInterface $m) {$this->mailer = $m;}public function notify() {...$this->mailer->send($from, $to, $msg);...}}$mailer = new Mailer();$notifier = new Notifier($mailer);?
  31. 31. Where does injection happen?
  32. 32. Where does injection happen?➔ Manual injection➔ Use a factory➔ Use a container / injector
  33. 33. Using DI in a FrameworkDependency Injector==Dependency Injection Container (DIC)==IoC Container==Service Container
  34. 34. The Service Container➔ Assumes responsibility for constructingobject graphs (i.e. instantiating yourclasses with their dependencies)➔ Uses configuration data to know how todo this➔ Allows infrastructure logic to be keptseparate from application logic
  35. 35. Objects as ServicesA service is an object that provides some kind ofglobally useful functionality
  36. 36. Examples of Services➔ Cache Backend➔ Logger➔ Mailer➔ URL Generator
  37. 37. Examples of Non-Services➔ Product➔ Blog post➔ Email message
  38. 38. Source: Dependency Injection by Dhanji R. Prasanna, published by Manning
  39. 39. Source: Dependency Injection by Dhanji R. Prasanna, published by Manning(Service Definitions)(Service Definitions)(Control Flow)(Control Flow)
  40. 40. Source: Dependency Injection by Dhanji R. Prasanna, published by ManningGettingGetting““wired in”wired in”
  41. 41. Sample configuration<services...><service id=”notifier” class=”Notifier”><constructor-arg ref=”emailer” /></service><service id=”emailer” class=”Mailer”><constructor-arg ref=”spell_checker” /></service><service id=”spell_checker”class=”SpellChecker” /></services>
  42. 42. How does it work?➔ Service keys map to service definitions➔ Definitions specify which class toinstantiate and what its dependenciesare➔ Dependencies are specified asreferences to other services (usingservice keys)➔ $container->getService(some_service)
  43. 43. ScopeThe scope of a service is the context under whichthe service key refers to the same instance.
  44. 44. Symfonys Dependency InjectionComponent
  45. 45. Symfonys DI Component➔ Service keys are strings, e.g. some_service➔ Service definitions, in addition to specifyingwhich class to instantiate and whatconstructor arguments to pass in, allow you tospecify additional methods to call on theobject after instantiation
  46. 46. Symfonys DI Component➔ Default scope: container➔ Can be configured in PHP, XML or YAML➔ Can be “compiled” down to plain PHP
  47. 47. Some Symfony Terminology
  48. 48. “Compiling” the containerIts too expensive to parse configuration onevery request.Parse once and put the result into a PHP classthat hardcodes a method for each service.
  49. 49. “Compiling” the containerClass service_container extends Container {/*** Gets the example service.*/protected function getExampleService(){return $this->services[example] = newSomeNamespaceSomeClass();}}
  50. 50. “Synthetic” ServicesA synthetic service is one that is not instantiatedby the container – the container just gets toldabout it so it can then treat it as a service whenanything has a dependency on it.
  51. 51. Compiler passesCompiler passes are classes that process thecontainer, giving you an opportunity tomanipulate existing service definitions.Use them to:● Specify a different class for a given service id● Process “tagged” services
  52. 52. Tagged ServicesYou can add tags to your services when youdefine them. This flags them for some kind ofspecial processing (in a compiler pass).For example, this mechanism is used to registerevent subscribers (services tagged withevent_subscriber) to Symfonys eventdispatcher
  53. 53. BundlesBundles are Symfonys answer to plugins ormodules, i.e. prepackaged sets of functionalityimplementing a particular feature, e.g. a blog.Each bundle includes a class implementing theBundleInterface which allows it to interact withthe container, e.g. to add compiler passes.
  54. 54. Symfonys Event Dispatcherplays an important role in theapplication flow.
  55. 55. Symfonys Event Dispatcher➔ Dispatcher dispatches events such asKernel::Request➔ Can be used to dispatch any kind of customevent➔ Event listeners are registered to thedispatcher and notified when an event fires➔ Event subscribers are classes that providemultiple event listeners for different events
  56. 56. Symfonys Event Dispatcher➔ A compiler pass registers all subscribers tothe dispatcher, using their service IDs➔ The dispatcher holds a reference to theservice container➔ Can therefore instantiate “subscriberservices” with their dependencies
  57. 57. class RegisterKernelListenersPass implementsCompilerPassInterface {public function process(ContainerBuilder $container) {$definition = $container->getDefinition(event_dispatcher);$services = $container->findTaggedServiceIds(event_subscriber);foreach ($services as $id => $attributes) {$definition->addMethodCall(addSubscriberService,array($id, $class));}}}A compiler pass iterates over thetagged services
  58. 58. class CoreBundle extends Bundle {public function build(ContainerBuilder $container){...// Compiler pass for event subscribers.$container->addCompilerPass(newRegisterKernelListenersPass());...}}Register the compiler pass
  59. 59. Dependency Injection in Drupal 8
  60. 60. Some D8 Services➔ The default DB connection (database)➔ The module handler (module_handler)➔ The HTTP request object (request)
  61. 61. Services:database:class: DrupalCoreDatabaseConnectionfactory_class: DrupalCoreDatabaseDatabasefactory_method: getConnectionarguments: [default]path.alias_whitelist:class: DrupalCorePathAliasWhitelisttags:- { name: needs_destruction }language_manager:class: DrupalCoreLanguageLanguageManagerpath.alias_manager:class: DrupalCorePathAliasManagerarguments: [@database,@path.alias_whitelist, @language_manager]
  62. 62. class AliasManager implements AliasManagerInterface {...public function __construct(Connection $connection,AliasWhitelist $whitelist, LanguageManager$language_manager) {$this->connection = $connection;$this->languageManager = $language_manager;$this->whitelist = $whitelist;}...}AliasManagers Constructor
  63. 63. 2 ways you can use coresservices1. From procedural code, using a helper:Drupal::service(some_service)2. Write OO code and get wired into thecontainer
  64. 64. Drupals Application Flow
  65. 65. Get wired in as an eventsubscriber
  66. 66. class MySubscriber implementsEventSubscriberInterface {static function getSubscribedEvents() {$events[KernelEvents::REQUEST][] =array(onKernelRequest, 200);return $events;}public function onKernelRequest(GetResponseEvent$event) {...}}1. ImplementEventSubscriberInterface
  67. 67. Services:...my_subscriber:class: DrupalmymoduleMySubscribertags:- { name: event_subscriber }...2. Write a service definition andadd the event_subscriber tag
  68. 68. How to get your controllerwired in?
  69. 69. Controllers as Services?➔ Controllers have dependencies on services➔ Whether they should be directly wired intothe container is a hotly debated topic in theSymfony community➔ Recommended way in D8 is not to makecontrollers themselves services but toimplement a special interface that has afactory method which accepts the container➔ See book module for an example!
  70. 70. Dont inject the container!Ever.(Unless you absolutely must)
  71. 71. Where does it all happen?➔ The Drupal Kernel:core/lib/Drupal/Core/DrupalKernel.php➔ Services are defined in:core/core.services.yml➔ Compiler passes get added in:core/lib/Drupal/Core/CoreBundle.php➔ Compiler pass classes live here:core/lib/Drupal/Core/DependencyInjection/Compiler/...
  72. 72. What about modules?➔ Services are defined in:mymodule/mymodule.services.yml➔ Compiler passes get added in:mymodule/lib/Drupal/mymodule/MymoduleBundle.php➔ All classes, including compiler pass classes,must live inmymodule/lib/Drupal/mymodule/
  73. 73. Easy testability with DI andPHPUnit
  74. 74. PHPUnit Awesomeness// Create a language manager stub.$language_manager = $this->getMock(DrupalCoreLanguageLanguageManager);$language_manager->expects($this->any())->method(getLanguage)->will($this->returnValue((object) array(langcode => en,name => English)));
  75. 75. PHPUnit Awesomeness// Create an alias manager stub.$alias_manager = $this->getMockBuilder(DrupalCorePathAliasManager)->disableOriginalConstructor()->getMock();$alias_manager->expects($this->any())->method(getSystemPath)->will($this->returnValueMap(array(foo => user/1,bar => node/1,)));
  76. 76. ResourcesThese slides and a list of resources on DI andits use in Symfony and Drupal are available athttp://katbailey.github.io/
  77. 77. Questions?
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×