Dependency injection in Drupal 8

3,664 views

Published on

Published in: Technology, Business
0 Comments
2 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
3,664
On SlideShare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
15
Comments
0
Likes
2
Embeds 0
No embeds

No notes for slide

Dependency injection in Drupal 8

  1. 1. Dependency Injection in Drupal 8 By Alexei Gorobets asgorobets
  2. 2. Why talk about DI? * Drupal 8 is coming… * DI is a commonly used pattern in software design
  3. 3. The problems in D7 1. Strong dependencies * Globals: - users - database - language - paths * EntityFieldQuery still assumes SQL in property queries * Views assumes SQL database.
  4. 4. The problems in D7 2. No way to reuse. * Want to change a small part of contrib code? Copy the callback entirely and replace it with your code.
  5. 5. The problems in D7 3. A lot of clutter happening. * Use of preprocess to do calculations. * Excessive use of alter hooks for some major replacements. * PHP in template files? Huh =)
  6. 6. The problems in D7 4. How can we test something? * Pass dummy DB connection? NO. * Create mock objects? NO. * Want a simple test class? Bootstrap Drupal first.
  7. 7. How we want our code to look like?
  8. 8. How it actually looks?
  9. 9. This talk is about * DI Design Pattern * DI in Symfony * DI in Drupal 8
  10. 10. Our goal:
  11. 11. Let’s take a wrong approach
  12. 12. Some procedural code function foo_bar($foo) { global $user; if ($user->uid != 0) { $nodes = db_query("SELECT * FROM {node}")->fetchAll(); } // Load module file that has the function. module_load_include('inc', cool_module, 'cool.admin'); node_make_me_look_cool(NOW()); }
  13. 13. Some procedural code function foo_bar($foo) { global $user; if ($user->uid != 0) { $nodes = db_query("SELECT * FROM {node}")->fetchAll(); } // Load module file that has the function. module_load_include('inc', cool_module, 'cool.admin'); node_make_me_look_cool(NOW()); } * foo_bar knows about the user object. * node_make_me_look_cool needs a function from another module. * You need bootstrapped Drupal to use module_load_include, or at least include the file that declares it. * foo_bar assumes some default database connection.
  14. 14. Let’s try an OO approach
  15. 15. User class uses sessions to store language class SessionStorage { function __construct($cookieName = 'PHP_SESS_ID') { session_name($cookieName); session_start(); } function set($key, $value) { $_SESSION[$key] = $value; } function get($key) { return $_SESSION[$key]; } // ... } class User { protected $storage; function __construct() { $this->storage = new SessionStorage(); } function setLanguage($language) { $this->storage->set('language', $language); } function getLanguage() { return $this->storage->get('language'); } // ... }
  16. 16. Working with User $user = new User(); $user->setLanguage('fr'); $user_language = $user->getLanguage(); Working with such code is damn easy. What could possibly go wrong here?
  17. 17. What if? What if we want to change a session cookie name?
  18. 18. What if? What if we want to change a session cookie name? class User { function __construct() { $this->storage = new SessionStorage('SESSION_ID'); } // ... } Hardcode the name in User’s constructor
  19. 19. What if? class User { function __construct() { $this->storage = new SessionStorage (STORAGE_SESSION_NAME); } // ... } define('STORAGE_SESSION_NAME', 'SESSION_ID'); Define a constant What if we want to change a session cookie name?
  20. 20. What if? class User { function __construct($sessionName) { $this->storage = new SessionStorage($sessionName); } // ... } $user = new User('SESSION_ID'); Send cookie name as User argument What if we want to change a session cookie name?
  21. 21. What if? class User { function __construct($storageOptions) { $this->storage = new SessionStorage($storageOptions ['session_name']); } // ... } $user = new User(array('session_name' => 'SESSION_ID')); Send an array of options as User argument What if we want to change a session cookie name?
  22. 22. What if? What if we want to change a session storage? For instance to store sessions in DB or files
  23. 23. What if? What if we want to change a session storage? For instance to store sessions in DB or files You need to change User class
  24. 24. Introducing Dependency Injection
  25. 25. Here it is: class User { function __construct($storage) { $this->storage = $storage; } // ... } Instead of instantiating the storage in User, let’s just pass it from outside.
  26. 26. Here it is: class User { function __construct($storage) { $this->storage = $storage; } // ... } Instead of instantiating the storage in User, let’s just pass it from outside. $storage = new SessionStorage('SESSION_ID'); $user = new User($storage);
  27. 27. Types of injections: class User { function __construct($storage) { $this->storage = $storage; } } class User { function setSessionStorage($storage) { $this->storage = $storage; } } class User { public $sessionStorage; } $user->sessionStorage = $storage; Constructor injection Setter injection Property injection
  28. 28. Ok, where does injection happen? $storage = new SessionStorage('SESSION_ID'); $user = new User($storage); Where should this code be?
  29. 29. Ok, where does injection happen? $storage = new SessionStorage('SESSION_ID'); $user = new User($storage); Where should this code be? * Manual injection * Injection in a factory class * Using a container/injector
  30. 30. How frameworks do that? Dependency Injection Container (DIC) or Service container
  31. 31. What is a Service?
  32. 32. Service is any PHP object that performs some sort of "global" task
  33. 33. Let’s say we have a class class Mailer { private $transport; public function __construct($transport) { $this->transport = $transport; } // ... }
  34. 34. How to make it a service?
  35. 35. Using YAML parameters: mailer.class: Mailer mailer.transport: sendmail services: mailer: class: "%mailer.class%" arguments: ["%my_mailer.transport%"]
  36. 36. Using YAML parameters: mailer.class: Mailer mailer.transport: sendmail services: mailer: class: "%mailer.class%" arguments: ["%my_mailer.transport%"] Loading a service from yml file. require_once '/PATH/TO/sfServiceContainerAutoloader.php'; sfServiceContainerAutoloader::register(); $sc = new sfServiceContainerBuilder(); $loader = new sfServiceContainerLoaderFileYaml($sc); $loader->load('/somewhere/services.yml');
  37. 37. Use PHP Dumper to create PHP file once $name = 'Project'.md5($appDir.$isDebug.$environment).'ServiceContainer'; $file = sys_get_temp_dir().'/'.$name.'.php'; if (!$isDebug && file_exists($file)) { require_once $file; $sc = new $name(); } else { // build the service container dynamically $sc = new sfServiceContainerBuilder(); $loader = new sfServiceContainerLoaderFileXml($sc); $loader->load('/somewhere/container.xml'); if (!$isDebug) { $dumper = new sfServiceContainerDumperPhp($sc); file_put_contents($file, $dumper->dump(array('class' => $name)); } }
  38. 38. Use Graphviz dumper for this beauty
  39. 39. Symfony terminology
  40. 40. Symfony terminology
  41. 41. Compile the container There is no reason to pull configurations on every request. We just compile the container in a PHP class with hardcoded methods for each service. container->compile();
  42. 42. Compiler passes Compiler passes are classes that process the container, giving you an opportunity to manipulate existing service definitions. Use them to: ● Specify a different class for a given serviceid ● Process“tagged”services
  43. 43. Compiler passes
  44. 44. Tagged services There is a way to tag services for further processing in compiler passes. This technique is used to register event subscribers to Symfony’s event dispatcher.
  45. 45. Tagged services
  46. 46. Event subscribers
  47. 47. Event subscribers
  48. 48. Changing registered service
  49. 49. 1. Write OO code and get wired into the container. 2. In case of legacy procedural code you can use: Drupal::service(‘some_service’); Example: Drupal 7: $path = $_GET['q'] Drupal 8: $request = Drupal::service('request'); $path = $request->attributes->get('_system_path'); Ways to use core’s services
  50. 50. ● 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/CoreServiceProvider. php ● Compiler pass classes are in: core/lib/Drupal/Core/DependencyInjection/ Compiler/ Where the magic happens?
  51. 51. ● Define module services in : mymodule/mymodule.services.yml ● Compiler passes get added in: mymodule/lib/Drupal/mymodule/Mymodule ServiceProvider.php ● All classes including Compiler class classes live in: mymodule/lib/Drupal/mymodule What about modules?
  52. 52. Resources: ● Fabien Potencier’s “What is Dependency Injection” series ● Symfony Service Container ● Symfony Dependency Injection Component ● WSCCI Conversion Guide ● Change notice: Use Dependency Injection to handle global PHP objects
  53. 53. $slide = new Questions(); $current_slide = new Slide($slide);

×