看Laravel的IoC容器文档只是介绍实例,但是没有说原理,之前用MVC框架都没有在意这个概念,无意中在phalcon的文档中看到这个详细的介绍,感觉豁然开朗,复制粘贴过来,主要是好久没有写东西了,现在确实很懒变得!
首先,我们假设,我们要开发一个组件命名为SomeComponent。这个组件中现在将要注入一个数据库连接。
在这个例子中,数据库连接在component中被创建,这种方法是不切实际的,这样做的话,我们将不能改变数据库连接参数及数据库类型等一些参数。
~~~
~~~
<?php
class SomeComponent
{
/**
* The instantiation of the connection is hardcoded inside
* the component so is difficult to replace it externally
* or change its behavior
*/
public function someDbTask()
{
$connection = new Connection(array(
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo"
));
// ...
}
}
$some = new SomeComponent();
$some->someDbTask();
~~~
~~~
为了解决上面所说的问题,我们需要在使用前创建一个外部连接,并注入到容器中。就目前而言,这看起来是一个很好的解决方案:
~~~
~~~
<?php
class SomeComponent
{
protected $_connection;
/**
* Sets the connection externally
*/
public function setConnection($connection)
{
$this->_connection = $connection;
}
public function someDbTask()
{
$connection = $this->_connection;
// ...
}
}
$some = new SomeComponent();
//Create the connection
$connection = new Connection(array(
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo"
));
//Inject the connection in the component
$some->setConnection($connection);
$some->someDbTask();
~~~
~~~
现在我们来考虑一个问题,我们在应用程序中的不同地方使用此组件,将多次创建数据库连接。使用一种类似全局注册表的方式,从这获得一个数据库连接实例,而不是使用一次就创建一次。
~~~
~~~
<?php
class Registry
{
/**
* Returns the connection
*/
public static function getConnection()
{
return new Connection(array(
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo"
));
}
}
class SomeComponent
{
protected $_connection;
/**
* Sets the connection externally
*/
public function setConnection($connection){
$this->_connection = $connection;
}
public function someDbTask()
{
$connection = $this->_connection;
// ...
}
}
$some = new SomeComponent();
//Pass the connection defined in the registry
$some->setConnection(Registry::getConnection());
$some->someDbTask();
~~~
~~~
现在,让我们来想像一下,我们必须在组件中实现两个方法,首先需要创建一个新的数据库连接,第二个总是获得一个共享连接:
~~~
~~~
<?php
class Registry
{
protected static $_connection;
/**
* Creates a connection
*/
protected static function _createConnection()
{
return new Connection(array(
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo"
));
}
/**
* Creates a connection only once and returns it
*/
public static function getSharedConnection()
{
if (self::$_connection===null){
$connection = self::_createConnection();
self::$_connection = $connection;
}
return self::$_connection;
}
/**
* Always returns a new connection
*/
public static function getNewConnection()
{
return self::_createConnection();
}
}
class SomeComponent
{
protected $_connection;
/**
* Sets the connection externally
*/
public function setConnection($connection){
$this->_connection = $connection;
}
/**
* This method always needs the shared connection
*/
public function someDbTask()
{
$connection = $this->_connection;
// ...
}
/**
* This method always needs a new connection
*/
public function someOtherDbTask($connection)
{
}
}
$some = new SomeComponent();
//This injects the shared connection
$some->setConnection(Registry::getSharedConnection());
$some->someDbTask();
//Here, we always pass a new connection as parameter
$some->someOtherDbTask(Registry::getConnection());
~~~
~~~
到此为止,我们已经看到了如何使用依赖注入解决我们的问题。不是在代码内部创建依赖关系,而是让其作为一个参数传递,这使得我们的程序更容易维护,降低程序代码的耦合度,实现一种松耦合。但是从长远来看,这种形式的依赖注入也有一些缺点。
例如,如果组件中有较多的依赖关系,我们需要创建多个setter方法传递,或创建构造函数进行传递。另外,每次使用组件时,都需要创建依赖组件,使代码维护不太易,我们编写的代码可能像这样:
~~~
~~~
<?php
//Create the dependencies or retrieve them from the registry
$connection = new Connection();
$session = new Session();
$fileSystem = new FileSystem();
$filter = new Filter();
$selector = new Selector();
//Pass them as constructor parameters
$some = new SomeComponent($connection, $session, $fileSystem, $filter, $selector);
// ... or using setters
$some->setConnection($connection);
$some->setSession($session);
$some->setFileSystem($fileSystem);
$some->setFilter($filter);
$some->setSelector($selector);
~~~
~~~
我想,我们不得不在应用程序的许多地方创建这个对象。如果你不需要依赖的组件后,我们又要去代码注入部分移除构造函数中的参数或者是setter方法。为了解决这个问题,我们再次返回去使用一个全局注册表来创建组件。但是,在创建对象之前,它增加了一个新的抽象层:
~~~
~~~
<?php
class SomeComponent
{
// ...
/**
* Define a factory method to create SomeComponent instances injecting its dependencies
*/
public static function factory()
{
$connection = new Connection();
$session = new Session();
$fileSystem = new FileSystem();
$filter = new Filter();
$selector = new Selector();
return new self($connection, $session, $fileSystem, $filter, $selector);
}
}
~~~
~~~
这一刻,我们好像回到了问题的开始,我们正在创建组件内部的依赖,我们每次都在修改以及找寻一种解决问题的办法,但这都不是很好的做法。
一种实用和优雅的来解决这些问题,是使用容器的依赖注入,像我们在前面看到的,容器作为全局注册表,使用容器的依赖注入做为一种桥梁来解决依赖可以使我们的代码耦合度更低,很好的降低了组件的复杂性:
~~~
~~~
<?php
class SomeComponent
{
protected $_di;
public function __construct($di)
{
$this->_di = $di;
}
public function someDbTask()
{
// Get the connection service
// Always returns a new connection
$connection = $this->_di->get('db');
}
public function someOtherDbTask()
{
// Get a shared connection service,
// this will return the same connection everytime
$connection = $this->_di->getShared('db');
//This method also requires a input filtering service
$filter = $this->_db->get('filter');
}
}
$di = new Phalcon\DI();
//Register a "db" service in the container
$di->set('db', function(){
return new Connection(array(
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo"
));
});
//Register a "filter" service in the container
$di->set('filter', function(){
return new Filter();
});
//Register a "session" service in the container
$di->set('session', function(){
return new Session();
});
//Pass the service container as unique parameter
$some = new SomeComponent($di);
$some->someTask();
~~~
~~~
现在,该组件只有访问某种service的时候才需要它,如果它不需要,它甚至不初始化,以节约资源。该组件是高度解耦。他们的行为,或者说他们的任何其他方面都不会影响到组件本身。
我们的实现办法¶
Phalcon\DI 是一个实现了服务的依赖注入功能的组件,它本身也是一个容器。
由于Phalcon高度解耦,Phalcon\DI 是框架用来集成其他组件的必不可少的部分,开发人员也可以使用这个组件依赖注入和管理应用程序中不同类文件的实例。
基本上,这个组件实现了 Inversion of Control 模式。基于此,对象不再以构造函数接收参数或者使用setter的方式来实现注入,而是直接请求服务的依赖注入。这就大大降低了整体程序的复杂性,因为只有一个方法用以获得所需要的一个组件的依赖关系。
此外,这种模式增强了代码的可测试性,从而使它不容易出错。
在容器中注册服务¶
框架本身或开发人员都可以注册服务。当一个组件A要求调用组件B(或它的类的一个实例),可以从容器中请求调用组件B,而不是创建组件B的一个实例。
这种工作方式为我们提供了许多优点:
我们可以更换一个组件,从他们本身或者第三方轻松创建。
在组件发布之前,我们可以充分的控制对象的初始化,并对对象进行各种设置。
我们可以使用统一的方式从组件得到一个结构化的全局实例
服务可以通过以下几种方式注入到容器:
~~~
~~~
<?php
//Create the Dependency Injector Container
$di = new Phalcon\DI();
//By its class name
$di->set("request", 'Phalcon\Http\Request');
//Using an anonymous function, the instance will lazy loaded
$di->set("request", function(){
return new Phalcon\Http\Request();
});
//Registering directly an instance
$di->set("request", new Phalcon\Http\Request());
//Using an array definition
$di->set("request", array(
"className" => 'Phalcon\Http\Request'
));
~~~
~~~
在上面的例子中,当向框架请求访问一个请求数据时,它将首先确定容器中是否存在这个”reqeust”名称的服务。
容器会反回一个请求数据的实例,开发人员最终得到他们想要的组件。
在上面示例中的每一种方法都有优缺点,具体使用哪一种,由开发过程中的特定场景来决定的。
用一个字符串来设定一个服务非常简单,但缺少灵活性。设置服务时,使用数组则提供了更多的灵活性,而且可以使用较复杂的代码。lambda函数是两者之间一个很好的平衡,但也可能导致更多的维护管理成本。
Phalcon\DI 提供服务的延迟加载。除非开发人员在注入服务的时候直接实例化一个对象,然后存存储到容器中。在容器中,通过数组,字符串等方式存储的服务都将被延迟加载,即只有在请求对象的时候才被初始化。
~~~
~~~
<?php
//Register a service "db" with a class name and its parameters
$di->set("db", array(
"className" => "Phalcon\Db\Adapter\Pdo\Mysql",
"parameters" => array(
"parameter" => array(
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "blog"
)
)
));
//Using an anonymous function
$di->set("db", function(){
return new Phalcon\Db\Adapter\Pdo\Mysql(array(
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "blog"
));
});
~~~
~~~
以上这两种服务的注册方式产生相同的结果。然后,通过数组定义的,在后面需要的时候,你可以修改服务参数:
~~~
~~~
<?php
$di->setParameter("db", 0, array(
"host" => "localhost",
"username" => "root",
"password" => "secret"
));
~~~
~~~
从容器中获得服务的最简单方式就是使用”get”方法,它将从容器中返回一个新的实例:
~~~
~~~
<?php $request = $di->get("request");
~~~
~~~
或者通过下面这种魔术方法的形式调用:
~~~
~~~
<?php
$request = $di->getRequest();
//Phalcon\DI 同时允许服务重用,为了得到一个已经实例化过的服务,可以使用 getShared() 方法的形式来获得服务。
~~~
~~~
具体的 Phalcon\Http\Request 请求示例:
~~~
~~~
<?php
$request = $di->getShared("request");
~~~
~~~
参数还可以在请求的时候通过将一个数组参数传递给构造函数的方式:
~~~
~~~
<?php
$component = $di->get("MyComponent", array("some-parameter", "other"));
~~~
~~~
原文地址:[](http://www.yuansir-web.com/?p=1012&preview=true)[http://www.yuansir-web.com/?p=1012&preview=true](http://www.yuansir-web.com/?p=1012&preview=true)
- 前言
- 发行说明/L5新特性
- 升级向导
- 升级到 5.0.16
- 从 4.2 升级到 5.0
- 从 4.1 升级到 4.2
- 从 4.1.x 升级到 4.1.29
- 从 4.1.25 升级到 4.1.26
- 从 4.0 升级到 4.1
- 贡献向导
- 环境配置
- 安装
- 配置
- 基本功能
- 路由
- 基本路由
- CSRF 保护
- 方法欺骗
- 路由参数
- 命名路由
- 路由群组
- 路由模型绑定
- 抛出 404 错误
- 中间件
- 建立中间件
- 注册中间件
- 可终止中间件
- 控制器
- 基础控制器
- 控制器中间件
- 隐式控制器
- RESTful 资源控制器
- 请求
- 取得请求实例
- 取得输入数据
- 旧输入数据
- Cookies
- 上传文件
- 其他的请求信息
- 响应
- 基本响应
- 重定向
- 其他响应
- 响应宏
- 系统架构
- 服务提供者
- 基本提供者例子
- 注册提供者
- 缓载提供者
- 服务容器
- 基本用法
- 将接口绑定到实现
- 上下文绑定
- 标签
- 实际应用
- 容器事件
- 参考:理解PHP 依赖注入|Laravel IoC容器
- Contracts
- 为什么用 Contracts
- Contract 参考
- 如何使用 Contracts
- Facades
- 实际用法
- 建立 Facades
- 模拟 Facades
- Facade 类参考
- 请求的生命周期
- 生命周期概要
- 聚焦于服务提供者
- 应用程序结构
- 根目录
- App 目录
- 为应用程序配置命名空间
- 系统服务
- 认证
- 用户认证
- 取得经过认证的用户
- 保护路由
- HTTP 基本认证
- 忘记密码与重设
- 第三方登陆认证
- 交易
- 配置文件
- 订购方案
- 一次性付款
- Single Charges
- 免信用卡试用
- 订购转换
- 订购数量
- 取消订购
- 恢复订购
- 确认订购状态
- 处理失败订阅
- 处理其它 Stripe Webhooks
- 收据
- 缓存
- 配置
- 缓存用法
- 递增与递减
- 缓存标签
- 缓存事件
- 数据库缓存
- 集合
- Command Bus
- 建立命令
- 调用命令
- 命令队列
- 命令管道
- 核心扩展
- 管理者和工厂
- 缓存
- Session
- 认证
- 基于服务容器的扩展
- Laravel Elixir
- 安装与配置
- 使用方式
- Gulp
- Custom Tasks and Extensions
- 加密
- Envoy 任务执行器
- 安装
- 执行任务
- 多服务器
- 并行执行
- 任务宏
- 通知
- 更新 Envoy
- 错误与日志
- 配置
- 错误处理
- HTTP 异常
- 日志
- 事件
- 基本用法
- 事件处理队列
- 事件订阅者
- 文件系统与云存储
- 配置文件
- 基本用法
- 自定义文件系统
- 哈希
- 基本用法
- 辅助方法
- 数组
- 路径
- 路由
- 字符串
- 网址(URL)
- 其他
- 本地化
- 语言文件
- 基本用法
- 复数
- 验证
- 覆写扩展包的语言文件
- 邮件
- 配置
- 基本用法
- 内嵌附件
- 邮件队列
- 邮件与本地端开发
- 扩展包开发
- 视图
- 语言
- 配置文件
- 公共资源
- 发布分类文件
- 路由
- 分页
- 配置
- 使用
- 追加分页链接
- 转换至 JSON
- 队列
- 设置
- 基本用法
- 队列闭包
- 执行一个队列监听
- 常驻队列处理器
- 推送队列
- 已失败的工作
- 会话
- 配置
- 使用 Session
- 暂存数据(Flash Data)
- 数据库 Sessions
- Session 驱动
- 模板
- Blade 模板
- Blade 控制语法结构
- Blade 扩展
- 参考:@section与@yield 介绍
- 单元测试
- 定义并执行测试
- 测试环境
- 从测试调用路由
- 模拟 Facades
- 框架 Assertions
- 辅助方法
- 重置应用程序
- 表单验证
- 基本用法
- 控制器验证
- 表单请求验证
- 使用错误信息
- 错误信息 & 视图
- 可用验证规则
- 条件验证规则
- 自定义错误信息
- 自定义验证规则
- 数据库
- 使用基础
- 配置
- 读取/写入连接
- 执行查找
- 数据库事务处理
- 获取连接
- 日志记录
- 查询构造器
- Selects
- Joins
- 高级 Wheres
- 聚合
- 原生表达式
- 添加
- 更新
- 删除
- Unions
- 悲观锁定 (Pessimistic Locking)
- Eloquent ORM
- 基本用法
- 批量赋值
- 新增,更新,删除
- 软删除
- 时间戳
- 范围查询
- Global Scopes
- 关联
- 关联查询
- 预载入
- 新增关联模型
- 更新上层时间戳
- 使用枢纽表
- 集合
- 获取器和修改器
- 日期转换器
- 属性类型转换
- 模型事件
- 模型观察者
- 模型 URL 生成
- 转换成数组 / JSON
- 结构生成器
- 建立与删除数据表
- 加入字段
- 修改字段
- 修改字段名称
- 移除字段
- 检查是否存在
- 加入索引
- 外键
- 移除索引
- 移除时间戳记和软删除
- 保存引擎
- 迁移和数据填充
- 建立迁移文件
- 执行迁移
- 回滚迁移
- 数据填充
- Redis
- 配置
- 使用方式
- 管道
- 开发包
- Confide 用户身份认证
- Entrust 权限管理
- Shoppingcart 购物车
- Genertators 代码生成工具
- IDE Helper IDE助手
- Artisan 命令行工具
- 概览
- 用法
- 在命令行接口以外的地方调用命令
- 定时调用 Artisan 命令
- 开发
- 建立自定义命令
- 注册自定义命令
- CoverPage