Dependency Injection in PHP. Create your own DI container.

blog / PHP

By my opinion one of the biggest problems in programming are dependencies. If you want to have a good well written application you should avoid dependencies between your modules/classes. There is a design pattern which could help and it's called Dependency Injection (DI). It allows you to inject objects into a class, instead of creating them there.

The files of that article could be found here https://github.com/krasimir/php-dependency-injection.

Actually I'm sure that you already used dependency injection in your code. And I bet that you are doing that pretty often. You just don't know that it is called dependency injection. For example let's take the following example:


class Person {
private $skills;
public function __construct() {
$this->skills = array("PHP", "HTML", "CSS", "JavaScript");
}
public function totalSkills() {
return count($this->skills);
}
}
$p = new Person();
echo $p->totalSkills();

There are three big problems with the class above.

    • When we want to add some new skill we should edit the class, which is not good. Every class should be written as a black box. I.e. you should be able to fully operate with the class only by using its public properties and variables.
    • What if we want to set different skills for the different instances of class Person. It is currently not possible, because the private property $skills is created inside the constructor of the class.
    • What if we have something more then an array. For example another object and that object has its own needs. Then we should send the needed things to Person class so we can initialize our variable.

The solution of the problem could be something like:


class Person {
private $skills;
public function __construct($skills) {
$this->skills = $skills;
}
public function totalSkills() {
return count($this->skills);
}
}
$mySkills = array("PHP", "HTML", "CSS", "JavaScript");
$p = new Person($mySkills);
echo $p->totalSkills();

I.e. passing the skills from outside the class. We could say that we injected an object in class Person. The example is really simple, but it perfectly illustrates how helpful could be this approach.

In the example above we used injection to make our class looks better. We solved two problems, but another one comes. Now our class depends on the provided $skills variable. In our case that's just an array, but it could be something very complex or even worst - you could have several very complex objects and some of them could have another dependencies, which also have dependencies and so on. If that's the case then you should pass their dependencies too and your class will become bigger and bigger. You need something that will handle the dependencies for you and that thing is called Dependency Injection container. In the example below I'll show you how you can create and use DI container in PHP.

Let's imagine that we have a MVC written application and we want to show the users in our system.
We have a view which displays information to the visiter:


class View {
public function show($str) {
echo "<p>".$str."</p>";
}
}

A model which delivers the users' information:


class UsersModel {
public function get() {
return array(
(object) array("firstName" => "John", "lastName" => "Doe"),
(object) array("firstName" => "Mark", "lastName" => "Black")
);
}
}

A navigation of the page which uses the view to show main menu links:


class Navigation {
private $view;
public function __construct() {
$this->view = new View();
}
public function show() {
$this->view->show('
<a href="#" title="Home">Home</a> |
<a href="#" title="Home">Products</a> |
<a href="#" title="Home">Contacts</a>
');
}
}

A class which shows the content of the page:


class Content {
private $title;
private $view;
private $usersModel; public function __construct($title) {
$this->title = $title;
$this->view = new View();
$this->usersModel = new UsersModel();
}
public function show() {
$users = $this->usersModel->get();
$this->view->show($this->title);
foreach($users as $user) {
$this->view->show($user->firstName." ".$user->lastName);
}
}
}

Controller which combines everything:


class PageController {
public function show() {
$navigation = new Navigation();
$content = new Content("Content title!");
$navigation->show();
$content->show();
}
}

At the end - running the controller:


$page = new PageController();
$page->show();

The result:


<p>
<a href="#" title="Home">Home</a> |
<a href="#" title="Home">Products</a> |
<a href="#" title="Home">Contacts</a>
</p>
<p>Content title!</p>
<p>John Doe</p>
<p>Mark Black</p>

Basically nothing wrong with the code. It works, shows the navigation, the title and the users. The problem here is that the classes are configure by themselfs and a lot of dependencies are created. For example if we want to use the Content with another model or another view we should make changes inside the constructor of the class. All the objects are tightly connected to each other and it is difficult to use them out of the current context.

The role of the DI container that we are going to create is to initializes objects instead of you and inject the dependencies which are required by the new object. We will start with the mapping mechanism. I.e. the setting of rules which will define what to be injected and where. Here is how the class looks in its first form:


class DI {
public static function getInstanceOf($className, $arguments = null);
public static function mapValue($key, $value);
public static function mapClass($key, $value, $arguments = null);
public static function mapClassAsSingleton($key, $value, $arguments = null);
}
    • getInstanceOf - creates instance of type $className by passing $argumentsto the class's constructor.
    • mapValue - associate $key with $value. The value could be anything - array, string or object. The key is used by the container to find out what exactly to inject.
    • mapClass - same as mapValue, but class name should be passed as a value. We can also send $arguments to class's constructor.
    • mapClassAsSingleton - same as mapClass, but once the class is created its instance is returned during the next injection.

Here is the mapping code:


class DI { private static $map; private static function addToMap($key, $obj) {
if(self::$map === null) {
self::$map = (object) array();
}
self::$map->$key = $obj;
}
public static function mapValue($key, $value) {
self::addToMap($key, (object) array(
"value" => $value,
"type" => "value"
));
}
public static function mapClass($key, $value, $arguments = null) {
self::addToMap($key, (object) array(
"value" => $value,
"type" => "class",
"arguments" => $arguments
));
}
public static function mapClassAsSingleton($key, $value, $arguments = null) {
self::addToMap($key, (object) array(
"value" => $value,
"type" => "classSingleton",
"instance" => null,
"arguments" => $arguments
));
} }

So far so good. Now we have to find some way to write instructions to the container. These instructions should be placed inside the classes which will be used during the injection. The other programming languages offer various ways to handle this. For example in ActionScript we could use meta tags, but in PHP we don't have any simple and elegant solution. There are several implementations of Dependency Injection in PHP. Some of them are reading your php file as a plain text and parsing its content. I decided to use ReflectionClass and to put everything in the class's docs. For example:


/**
* @Inject view
*/
class Navigation {
public function show() {
$this->view->show(...);
}
}

When you want to have an object of type Navigation you should call the getInstanceOf method of the container. The line @Inject view means that you will have a public property injected called view. The value of view depends of your mapping rules. In the example below $view is an instance of View class.
The code of getInstanceOf:


public static function getInstanceOf($className, $arguments = null) { // checking if the class exists
if(!class_exists($className)) {
throw new Exception("DI: missing class '".$className."'.");
} // initialized the ReflectionClass
$reflection = new ReflectionClass($className); // creating an instance of the class
if($arguments === null || count($arguments) == 0) {
$obj = new $className;
} else {
if(!is_array($arguments)) {
$arguments = array($arguments);
}
$obj = $reflection->newInstanceArgs($arguments);
} // injecting
if($doc = $reflection->getDocComment()) {
$lines = explode("\n", $doc);
foreach($lines as $line) {
if(count($parts = explode("@Inject", $line)) > 1) {
$parts = explode(" ", $parts[1]);
if(count($parts) > 1) {
$key = $parts[1];
$key = str_replace("\n", "", $key);
$key = str_replace("\r", "", $key);
if(isset(self::$map->$key)) {
switch(self::$map->$key->type) {
case "value":
$obj->$key = self::$map->$key->value;
break;
case "class":
$obj->$key = self::getInstanceOf(self::$map->$key->value, self::$map->$key->arguments);
break;
case "classSingleton":
if(self::$map->$key->instance === null) {
$obj->$key = self::$map->$key->instance = self::getInstanceOf(self::$map->$key->value, self::$map->$key->arguments);
} else {
$obj->$key = self::$map->$key->instance;
}
break;
}
}
}
}
}
} // return the created instance
return $obj; }

The interesting part is the usage of the ReflectionClass on line 18 (creating the new instance) and line 22 (getting the docs of the class). The rest of the code is just parsing the documentation text line by line and injecting the objects as a public properties.

Now, when everything is done we can transform our little project in something more modular.
Here are the mapping rules that we have to set:


DI::mapClass("navigation", "Navigation");
DI::mapClass("content", "Content", array("Content title!"));
DI::mapClass("view", "View");
DI::mapClassAsSingleton("usersModel", "UsersModel");

The view and the model remain the same.


class View {
public function show($str) {
echo "<p>".$str."</p>";
}
}
class UsersModel {
public function get() {
return array(
(object) array("firstName" => "John", "lastName" => "Doe"),
(object) array("firstName" => "Mark", "lastName" => "Black")
);
}
}

We can inject the view inside the Navigation instead of creating it there:


/**
* @Inject view
*/
class Navigation {
public function show() {
$this->view->show('
<a href="#" title="Home">Home</a> |
<a href="#" title="Home">Products</a> |
<a href="#" title="Home">Contacts</a>
');
}
}

The situation with the Content class is the same. Injecting the view and the model:


/**
* @Inject usersModel
* @Inject view
*/
class Content {
private $title;
public function __construct($title) {
$this->title = $title;
}
public function show() {
$this->users = $this->usersModel->get();
$this->view->show($this->title);
foreach($this->users as $user) {
$this->view->show($user->firstName." ".$user->lastName);
}
}
}

Injecting the navigation and the content into the controller:


/**
* @Inject navigation
* @Inject content
*/
class PageController {
public function show() {
$this->navigation->show();
$this->content->show();
}
}

And at the end initializing and running the controller:


$page = DI::getInstanceOf("PageController");
$page->show();

The benefits of that transformations are a lot. For example if some of our classe uses the model and if we are wondering where exactly is the problem - into the model's data or into the class, we can easily test by placing a dummy model which always return corret data. We don't have to deal with very complex classes tree and searching where is every object coming from.

Of course this implementation of dependency injection container is not perfect at all. Could be improved a lot, but it is good as a start. Let me know if you have any suggestions.

The files of that article could be found here https://github.com/krasimir/php-dependency-injection.

【转】dependency injection 的例子的更多相关文章

  1. Ninject学习(一) - Dependency Injection By Hand

    大体上是把官网上的翻译下而已. http://www.ninject.90iogjkdcrorg/wiki.html Dependency Injection By Hand So what's Ni ...

  2. [转载][翻译] IoC 容器和 Dependency Injection 模式

    原文地址:Inversion of Control Containers and the Dependency Injection pattern 中文翻译版本是网上的PDF文档,发布在这里仅为方便查 ...

  3. Dependency Injection

    Inversion of Control - Dependency Injection - Dependency Lookup loose coupling/maintainability/ late ...

  4. 【译】Dependency Injection with Autofac

    先说下为什么翻译这篇文章,既定的方向是架构,然后为了学习架构就去学习一些架构模式.设计思想. 突然有一天发现依赖注入这种技能.为了使得架构可测试.易维护.可扩展,需要架构设计为松耦合类型,简单的说也就 ...

  5. 依赖注入 | Dependency Injection

    原文链接: Angular Dependency Injection翻译人员: 铁锚翻译时间: 2014年02月10日说明: 译者认为,本文中所有名词性的"依赖" 都可以理解为 & ...

  6. 黄聪:Microsoft Enterprise Library 5.0 系列教程(八) Unity Dependency Injection and Interception

    原文:黄聪:Microsoft Enterprise Library 5.0 系列教程(八) Unity Dependency Injection and Interception 依赖注入容器Uni ...

  7. Srping - bean的依赖注入(Dependency injection)

    目录 1 概述 2 两种基本的依赖注入方式 2.1 构造函数方式 2.2Setter方式 3 其他依赖注入功能 3.1 <ref/>标签引用不同范围的bean 3.2 内部bean 3.3 ...

  8. 听 Fabien Potencier 谈Symfony2 之 《What is Dependency Injection ?》

    听 Fabien Potencier 谈Symfony2 之 <What is Dependency Injection ?>   什么是依赖注入?从PHP实现角度来分析依赖注入,因为PH ...

  9. Benefits of Using the Spring Framework Dependency Injection 依赖注入 控制反转

    小结: 1. Dependency Injection is merely one concrete example of Inversion of Control. 依赖注入是仅仅是控制反转的一个具 ...

随机推荐

  1. PHP数组转对象,对象转数组

    废话不多,直接上代码: <?php class object_array{ //数组转对象 public static function array_to_object($e){ if(gett ...

  2. 创建jenkins任务

    前提条件 整个持续集成中用的相关的应用: gitlab (代码管理) maven(项目管理) 这些应用我们暂时全部放在了一个服务器上. 安装maven: CentOS 6.3 安装Maven3(就一步 ...

  3. 【随机化】Petrozavodsk Summer Training Camp 2016 Day 5: Petr Mitrichev Contest 14, Saturday, August 27, 2016 Problem I. Vier

    给你一个1~n的排列,让你找出4个下标a b c d,满足 (a+b)%n=(c+d)%n (w(a)+w(b))%n=(w(c)+w(d))%n,并且是非平凡解. 发现对于每个数i,找出两个数和为其 ...

  4. 【欧拉回路】【欧拉路径】【Fleury算法】CDOJ1634 记得小苹初见,两重心字罗衣

    Fleury算法看这里 http://hihocoder.com/problemset/problem/1181 把每个点看成边,每个横纵坐标看成一个点,得到一个无向图. 如果新图中每个点的度都是偶数 ...

  5. 【最大流】BZOJ1305-[CQOI2009]dance跳舞

    [题目大意] 一次舞会有n个男孩和n个女孩.每首曲子开始时,所有男孩和女孩恰好配成n对跳交谊舞.每个男孩都不会和同一个女孩跳两首(或更多)舞曲.有一些男孩女孩相互喜欢,而其他相互不喜欢(不会“单向喜欢 ...

  6. 小H的硬币游戏

    题目大意: 有n个物品排成一排,每个物品都有自己的价值,你每次可以从中挑选两个距离为k的物品取走,问最大的收益. (如果原来两个物品中间有物品被取走,距离不变) 思路: 贪心. 先按照每个物品的位置m ...

  7. JDK源码学习笔记——LinkedList

    一.类定义 public class LinkedList<E> extends AbstractSequentialList<E> implements List<E& ...

  8. 浅谈js对象及对象属性

    对象: ECMA-262把对象定义为 :无序属性的集合,其属性可以包含基本值,对象或者函数. 严格来讲,这就相当于说对象是一组没有特定顺序的值.对象的每一个属性或方法都有一个名字,而每个名字都映射到一 ...

  9. MATLAB/Octave warning: mx_el_eq: automatic broadcasting operation applied 错误分析

    在进行对一个mXn的矩阵与mX1的矩阵进行==比较时,原意是想让mXn的矩阵的每一行分别与mX1的矩阵每一行进行比较,得到的结果虽然是对的,但会报一个warning: mx_el_eq: automa ...

  10. Windows使用OpenVPN客户端连接

    前提: 1.上官网下载Windows客户端:https://openvpn.net/index.php/open-source/downloads.html 安装: 1.默认安装,选择easy-rsa ...