dependency injection in drupal 8

Post on 15-Jan-2015

3.136 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

DESCRIPTION

 

TRANSCRIPT

Dependency Injection in Drupal 8

By Alexei Gorobetsasgorobets

Why talk about DI?

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

The problems in D7

1. Strong dependencies* Globals: - users

- database- language- paths

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

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.

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

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.

How we want our code to look like?

How it actually looks?

This talk is about

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

Our goal:

Let’s take a wrong approach

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

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.

Let’s try an OO approach

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

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?

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

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

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?

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?

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?

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

For instance to store sessions in DB or files

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

Introducing Dependency Injection

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.

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

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

Ok, where does injection happen?

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

Where should this code be?

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

How frameworks do that?

Dependency Injection Container (DIC)or

Service container

What is a Service?

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

task

Let’s say we have a class

class Mailer{ private $transport;

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

// ...}

How to make it a service?

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

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

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

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

Use Graphviz dumper for this beauty

Symfony terminology

Symfony terminology

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

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

Compiler passes

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.

Tagged services

Event subscribers

Event subscribers

Changing registered service

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

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

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

$slide = new Questions();

$current_slide = new Slide($slide);

top related