dependency injection in drupal 8

57
Dependency Injection in Drupal 8 By Alexei Gorobets asgorobets

Upload: alexei-gorobets

Post on 15-Jan-2015

3.133 views

Category:

Technology


1 download

DESCRIPTION

 

TRANSCRIPT

Page 1: Dependency injection in Drupal 8

Dependency Injection in Drupal 8

By Alexei Gorobetsasgorobets

Page 2: Dependency injection in Drupal 8

Why talk about DI?

* Drupal 8 is coming…* DI is a commonly used pattern in software design

Page 3: Dependency injection in Drupal 8

The problems in D7

1. Strong dependencies* Globals: - users

- database- language- paths

* EntityFieldQuery still assumes SQL in property queries* Views assumes SQL database.

Page 4: Dependency injection in Drupal 8

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.

Page 5: Dependency injection in Drupal 8

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 =)

Page 6: Dependency injection in Drupal 8

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.

Page 7: Dependency injection in Drupal 8

How we want our code to look like?

Page 8: Dependency injection in Drupal 8
Page 9: Dependency injection in Drupal 8

How it actually looks?

Page 10: Dependency injection in Drupal 8
Page 11: Dependency injection in Drupal 8

This talk is about

* DI Design Pattern* DI in Symfony* DI in Drupal 8

Page 12: Dependency injection in Drupal 8

Our goal:

Page 13: Dependency injection in Drupal 8

Let’s take a wrong approach

Page 14: Dependency injection in Drupal 8

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());}

Page 15: Dependency injection in Drupal 8

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.

Page 16: Dependency injection in Drupal 8

Let’s try an OO approach

Page 17: Dependency injection in Drupal 8

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'); } // ...}

Page 18: Dependency injection in Drupal 8

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?

Page 19: Dependency injection in Drupal 8

What if?What if we want to change a session cookie name?

Page 20: Dependency injection in Drupal 8

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

Page 21: Dependency injection in Drupal 8

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?

Page 22: Dependency injection in Drupal 8

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?

Page 23: Dependency injection in Drupal 8

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?

Page 24: Dependency injection in Drupal 8

What if?What if we want to change a session storage?

For instance to store sessions in DB or files

Page 25: Dependency injection in Drupal 8

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

Page 26: Dependency injection in Drupal 8

Introducing Dependency Injection

Page 27: Dependency injection in Drupal 8
Page 28: Dependency injection in Drupal 8

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.

Page 29: Dependency injection in Drupal 8

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);

Page 30: Dependency injection in Drupal 8

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

Page 31: Dependency injection in Drupal 8

Ok, where does injection happen?

$storage = new SessionStorage('SESSION_ID');$user = new User($storage);

Where should this code be?

Page 32: Dependency injection in Drupal 8

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

Page 33: Dependency injection in Drupal 8

How frameworks do that?

Dependency Injection Container (DIC)or

Service container

Page 34: Dependency injection in Drupal 8

What is a Service?

Page 35: Dependency injection in Drupal 8

Service is any PHP object that performs some sort of "global"

task

Page 36: Dependency injection in Drupal 8

Let’s say we have a class

class Mailer{ private $transport;

public function __construct($transport) { $this->transport = $transport; }

// ...}

Page 37: Dependency injection in Drupal 8

How to make it a service?

Page 38: Dependency injection in Drupal 8

Using YAMLparameters: mailer.class: Mailer mailer.transport: sendmail

services: mailer: class: "%mailer.class%" arguments: ["%my_mailer.transport%"]

Page 39: Dependency injection in Drupal 8

Using YAMLparameters: 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');

Page 40: Dependency injection in Drupal 8

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)); }}

Page 41: Dependency injection in Drupal 8

Use Graphviz dumper for this beauty

Page 42: Dependency injection in Drupal 8

Symfony terminology

Page 43: Dependency injection in Drupal 8

Symfony terminology

Page 44: Dependency injection in Drupal 8

Compile the containerThere 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();

Page 45: Dependency injection in Drupal 8

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

Page 46: Dependency injection in Drupal 8

Compiler passes

Page 47: Dependency injection in Drupal 8

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.

Page 48: Dependency injection in Drupal 8

Tagged services

Page 49: Dependency injection in Drupal 8

Event subscribers

Page 50: Dependency injection in Drupal 8

Event subscribers

Page 51: Dependency injection in Drupal 8

Changing registered service

Page 52: Dependency injection in Drupal 8

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

Page 53: Dependency injection in Drupal 8

● 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?

Page 54: Dependency injection in Drupal 8

● Define module services in : mymodule/mymodule.services.yml

● Compiler passes get added in: mymodule/lib/Drupal/mymodule/MymoduleServiceProvider.php

● All classes including Compiler class classes live in: mymodule/lib/Drupal/mymodule

What about modules?

Page 56: Dependency injection in Drupal 8

$slide = new Questions();

$current_slide = new Slide($slide);

Page 57: Dependency injection in Drupal 8