#依赖注入
依赖注入(DI)的目的是使类免除获取其操作所需的对象的责任(这些对象被称为服务)。 要在他们的实例化上传递这些服务。 我们在这章学到以下几点:
* 依赖注入的原理是什么。
* 如何创建动态和静态DI容器。
* 如何延迟加载服务。
什么是依赖注入?
依赖注入(DI)是没有什么神秘或令人费解的。 它可以被理解为一个自私的句子:“不要寻求任何东西,让别人做它”。现在让我们把它翻译成程序员的演讲。 我们有一个博客里面有一个Article类:
~~~
class Article
{
public $id;
public $title;
public $content;
function save()
{
// 我们将数据保存到数据库中
}
}
~~~
我们可以这样使用这个类
~~~
$article = new Article;
$article->title = '10 Things You Need to Know About Losing Weight';
$article->content = 'Every year millions of people in ...';
$article->save();
~~~
save()方法是将要把文章保存到articles数据表中,它使用Nette\Database很容易实现。但是怎样Article得到数据库连接。 即使用Connection类。
嗯,我们可以将它放入一些全局变量中,如$ GLOBALS ['connection']或类的一些静态成员。 但是你没有听说过使用全局变量是错误的吗? 这是真的,全局变量是邪恶的,静态成员也是一样的。
那么在我们怎样得到数据库连接? DI有答案:“不要寻找任何东西,让别人做它。”换句话说,如果我需要一个数据库,有人给我,这不是我的工作。 哈,它狡猾,亲爱的DI! 我们开始做吧:
~~~
class Article
{
public $id;
public $title;
public $content;
private $connection;
function __construct(Nette\Database\Connection $connection)
{
$this->connection = $connection;
}
function save()
{
$this->connection->query('INSERT INTO articles', [
'title' => $this->title,
'content' => $this->content,
]);
}
}
~~~
使用Article类将稍有改变:
~~~
$article = new Article($connection);
$article->title = ...
$article->content = ...
$article->save();
~~~
你在问,这个代码需要$connection? DI给出一个直接的答案:“让别人做它。”数据库连接将由调用代码的人提供。 等等,等等。 当然你说,不可能使责任的外交。 必须有一个零点。 你是对的。 开始有一个创造者,他没有委托任何东西,他创造了对象。 我们称之为系统容器。 他有一个单独的章节。
以前学过对象编程的人不难看懂,但是没有学过的话有点难了。实际上上面意思是做一个 Article对象,他有一个保存数据方法,但是在保存数据之前要连接数据,但是要怎样连接数据呢,所以我们做一个$connection默认对象,如果有人要使用Article类,那他就要实例化它,程序就会自动注入$connection默认对象。有些象做一个电脑配置单,然后按配置单采购配件,再组装起来,系统就是依赖注入,这样才能成为一台完整电脑出来,但可以按配置单组很多台电脑,每台电脑都有系统。
# 为什么全局变量是错误的呢?
好问题。 Article类无论如何都需要数据库连接。 但从第一个例子,没有显示出来,从哪里和如何得到它。 这样的代码的用户可能会感到惊讶,文章真的节省了,他问:“它在哪里保存?”第二个例子使用DI,代码是自我解释。
想象一下,你正在探索一些支付网关,你写一个例子:
~~~
$cc = new CreditCard('4461510140804839', 12, 2013);
$cc->pay(1000, CreditCard::DOLLARS);
~~~
您运行的代码,与您的卡号,后来你会发现,它真的从您的帐户扣了钱! 震惊你盯着列表和哀叹:“我的钱在哪里,怎么会发生,我没有配对它任何支付网关!”类CreditCard自己做了,发现一个在一些全局变量中词,神秘,如Article得到了数据库连接。 这样的事情你不是从代码中推导出来的,你甚至不知道如何将网关改为另一个,比如测试。
# DI意味着更多的写作
你可以实例化对象,使用DI意味着更多的写作,那就是创建一个Article的实例,你必须处理数据库连接等等。 这是真的,但不要忘记最后一次,当“少写作”花费你$ 1000! 不,我们不想放松。 异常是正确的,我们将添加一个更大:当我们发现需要缓存一些数据,与DI协调,它将需要一个参数与缓存存储库。 这意味着在许多地方调整应用程序:至少在任何地方,其中Article要被实例化。
现在怎么办? 事情有一个解决方案:而不是手动创建Article对象,我们做一个工厂,即。 函数创建这些文章对象。 当Article更改构造函数时,只有工厂必须更新,没有更多。 和在哪里得到工厂在我们的代码? 你知道...让别人做。 :-)
# DI容器和服务
术语“DI容器”是指工厂。 更确切地说,它是一个对象,包含任何数量的工厂,每个服务一个。 什么是服务? 普通对象,比如说Connection实例。 只是用DI容器我们称之为服务。 也许一些顾问发明它使DI看起来很复杂,所以他们可以咨询。
示例可以是创建Article对象的容器,也可以是所需的数据库连接::
~~~
class Container
{
function createConnection()
{
return new Nette\Database\Connection('mysql:', 'root', '***');
}
function createArticle()
{
return new Article($this->createConnection());
}
}
~~~
用法如下:
~~~
$container = new Container;
$article = $container->createArticle();
~~~
优点是显而易见的,我们不需要关心这篇文章究竟是如何实例化的,这是工厂的工作。 无论如何,解决方案仍有两个缺点。 首先,有条目数据连接到代码,所以我们将它们分离成一个变量:
~~~
class Container
{
private $parameters;
function __construct(array $parameters)
{
$this->parameters = $parameters;
}
function createConnection()
{
return new Nette\Database\Connection(
$this->parameters['dsn'],
$this->parameters['user'],
$this->parameters['password']
);
}
function createArticle()
{
return new Article($this->createConnection());
}
}
$container = new Container([
'dsn' => 'mysql:',
'user' => 'root',
'password' => '***',
]);
$article = $container->createArticle();
~~~
更重要的缺点是,总是,当我们要求一个Article创建时,将要开始新的数据库连接。 这需要避免。 我们将添加方法getConnection,它将保留一次创建的服务以供下次使用:
~~~
class Container
{
private $parameters;
private $services = [];
function __construct(array $parameters)
{
$this->parameters = $parameters;
}
function createConnection()
{
return new Nette\Database\Connection(
$this->parameters['dsn'],
$this->parameters['user'],
$this->parameters['password']
);
}
function getConnection()
{
if (!isset($this->services['connection'])) {
$this->services['connection'] = $this->createConnection();
}
return $this->services['connection'];
}
function createArticle()
{
return new Article($this->getConnection());
}
}
~~~
现在我们有全功能DI容器。 如你所见,编写它并不复杂。 值得注意的是,服务单独不知道,这是由一些容器创建的,因此可以创建任何PHP对象的方式。 不用触摸自己的源代码。
# Nette\DI\Container
Nette \ DI \ Container类是通用DI容器的灵活实现。 它自动确保该服务实例只创建一次。
我们可以使我们自己的容器静态,即。 继承这个类,或者当我们添加工厂作为闭包或回调时,它是动态的。
## 静态容器
工厂方法的名称遵循统一的约定,它们包括前缀createService +从首字母大写开始的服务的名称。 如果他们不应该从外部访问,可以降低其受保护的可见性。 请注意,容器已经为用户参数定义了$parameters字段。
~~~
class MyContainer extends Nette\DI\Container
{
protected function createServiceConnection()
{
return new Nette\Database\Connection(
$this->parameters['dsn'],
$this->parameters['user'],
$this->parameters['password']
);
}
protected function createServiceArticle()
{
return new Article($this->getService('connection'));
}
}
~~~
现在我们创建一个容器的实例并传递参数:
~~~
$container = new MyContainer([
'dsn' => 'mysql:',
'user' => 'root',
'password' => '***',
]);
~~~
我们通过调用getService方法获取服务:
~~~
$article = $container->getService('article');
~~~
如前所述,所有服务都只在一个容器中创建一次,但是如果容器总是创建一个Article的新实例,这将更有用。 它可以很容易地实现:而不是服务文章的工厂,我们将创建一个普通的方法createArticle:
~~~
class MyContainer extends Nette\DI\Container
{
function createServiceConnection()
{
return new Nette\Database\Connection(
$this->parameters['dsn'],
$this->parameters['user'],
$this->parameters['password']
);
}
function createArticle()
{
return new Article($this->getService('connection'));
}
}
$container = new MyContainer(...);
$article = $container->createArticle();
~~~
从$ container-> createArticle()的调用很明显,一个新的对象总是被创建。 这是一个程序员的约定。
## 动态容器
我们可以使用addService方法将服务添加到Nette \ DI \ Container甚至运行时。 工厂可以写成PHP回调或闭包。 请注意,容器本身作为参数传递给它们,以便容易地访问参数和其他服务。
~~~
$container = new Nette\DI\Container;
$container->addService('connection', function($container) {
return new Nette\Database\Connection(
$container->parameters['dsn'],
$container->parameters['user'],
$container->parameters['password']
);
});
~~~
当服务创建只包括简单的实例化时,可以直接传递类名作为addService方法的第二个参数。 当我们已经创建了对象时,我们甚至可以传递它。
除了addService,我们还有hasService用于服务存在检查,removeService用于删除它
- Nette简介
- 快速开始
- 入门
- 主页
- 显示文章详细页
- 文章评论
- 创建和编辑帖子
- 权限验证
- 程序员指南
- MVC应用程序和控制器
- URL路由
- Tracy - PHP调试器
- 调试器扩展
- 增强PHP语言
- HTTP请求和响应
- 数据库
- 数据库:ActiveRow
- 数据库和表
- Sessions
- 用户授权和权限
- 配置
- 依赖注入
- 获取依赖关系
- DI容器扩展
- 组件
- 字符串处理
- 数组处理
- HTML元素
- 使用URL
- 表单
- 验证器
- 模板
- AJAX & Snippets
- 发送电子邮件
- 图像操作
- 缓存
- 本土化
- Nette Tester - 单元测试
- 与Travis CI的持续集成
- 分页
- 自动加载
- 文件搜索:Finder
- 原子操作