在開始之前要明確一個概念,不管是設計模式,還是依賴注入等等,都是為了實現模塊化.所謂模塊化就是希望一個軟件是由很多子模塊組成的,這些模塊之間的依賴程度盡量的低,也就是如果系統中不需要某一個功能,那么只要移除這個功能所對應的模塊就可以了.
那么,我們今天要說的服務容器就是為了實現上面的功能.你應該聽過,Laravel中的服務容器其本質上是一個IoC容器,但是好像隊IoC又不是很了解,講來講去優點很多,功能很強勁.但是不懂原理怎么用都不踏實啊.所以,這里我們自己來實現一個IoC容器,洞察其本質.
> 在開始之前,先說明一點,閱讀本篇文章至少要保證有一下的基礎知識:
>
> * php反射用法
>
> * 閉包的use用法
>
> 如果不懂上面的內容,請先補充.避免閱讀代碼時候產生的不適感.
Container.php
~~~
<?php
class Container
{
//用于提供實例的回調函數,真正的容器還會裝實例等其他內容.從而實現單列等高級功能
public $binding = [];
//綁定接口和生成相應實例的回調函數
public function bind($abstract, $concrete = null, $shared = false)
{
if (!$concrete instanceof Closure) {
//如果提供的參數不是回調函數則產生默認的回調函數
$concrete = $this->getClosure($abstract, $concrete);
}
$this->binding[$abstract] = compact('concrete', 'shared');
}
//默認生成的實例的回調函數
protected function getClosure($abstract, $concrete)
{
//生成實例的回調函數,$c一般為IOC容器對象,在調用回調生成實例時提供
//即build函數中的$concrete($this)
return function ($c) use ($abstract, $concrete) {
$method = ($abstract == $concrete) ? 'build' : 'make';
//調用的是容器的build或make方法生成的實例
return $c->$method($concrete);
};
}
//生成實例對象,首先要解決接口和要實例化類之間的依賴關系
public function make($abstract)
{
$concrete = $this->getConcrete($abstract);
if ($this->isBuildable($concrete, $abstract)) {
$object = $this->build($concrete);
} else {
$object = $this->make($concrete);
}
return $object;
}
public function isBuildable($concrete, $abstract)
{
return $concrete === $abstract || $concrete instanceof Closure;
}
//獲取綁定的回調函數
protected function getConcrete($abstract)
{
if (!isset($this->binding[$abstract])) {
return $abstract;
}
return $this->binding[$abstract]['concrete'];
}
//實例化對象
public function build($concrete) {
if($concrete instanceof Closure) {
return $concrete($this);
}
//反射...
$reflector = new ReflectionClass($concrete);
if(!$reflector->isInstantiable()) {
echo $message = "Target [$concrete] is not instantiable";
}
//獲取要實例化對象的構造函數
$constructor = $reflector->getConstructor();
//沒有定義構造函數,只有默認的構造函數,說明構造函數參數個數為空
if(is_null($constructor)) {
return new $concrete;
}
//獲取構造函數所需要的所有參數
$dependencies = $constructor->getParameters();
$instances = $this->getDependencies($dependencies);
//從給出的數組參數在中實例化對象
return $reflector->newInstanceArgs($instances);
}
/**
* 獲取構建類所需要的所有依賴,級構造函數所需要的參數 ,
*/
protected function getDependencies($paramters) {
$dependencies = [];
foreach ($paramters as $paramter) {
//獲取到參數名稱.
$dep = $paramter->getClass();
if(is_null($dep)){
$dependencies = null;
}else{
$dependencies[] = $this->resolveClass($paramter);
}
}
return (array)$dependencies;
}
/**
* 實例化 構造函數中所需要的參數.
*/
protected function resolveClass(ReflectionParameter $parameter) {
$name = $parameter->getClass()->name;
return $this->make($name);
}
}
~~~
這就是一個IoC容器的實現代碼.乍一看,很麻煩.其實真的蠻麻煩的 =_=,如果是第一次接觸的話,并不是那么好消化,這里再給出使用IoC容器的代碼
~~~
<?php
require __DIR__ . '/Container.php';
interface TrafficTool
{
public function go();
}
class Train implements TrafficTool
{
public function go()
{
echo "train....";
}
}
class Leg implements TrafficTool
{
public function go()
{
echo "leg..";
}
}
class Traveller
{
/**
* @var Leg|null|Train
* 旅行工具
*/
protected $_trafficTool;
public function __construct(TrafficTool$trafficTool)
{
$this->_trafficTool = $trafficTool;
}
public function visitTibet()
{
$this->_trafficTool->go();
}
}
//實例化IoC容器
$app = new Container();
//綁定某一功能到IoC
$app->bind('TrafficTool', 'Train');
$app->bind('travellerA', 'Traveller');
// 實例化對象
$tra = $app->make('travellerA');
$tra->visitTibet();
~~~
運行例子發現會輸出:train...這個例子假設旅行者去青藏旅行,可以坐火車(train)或者走路(leg)去青藏.
好了,其實這樣子本篇文章就可以結束了,因為所有的答案都在IoC容器的實現中, 但是為了可以更好的理解上面的代碼,我們繼續往下分析.
首先,希望你可以運行一下上面的代碼,雖然簡單的運行代碼并不會幫助你理解代碼,但是一個可以運行的例子會讓人比較踏實,能夠更有把握的理解代碼.
在深入每一行代碼之前,我們從整體上來分析,IoC解決了一個什么問題?簡單點說,就是我們再實例化對象的時候不用使用new了,有了IoC容器之后,我們調用make函數就可以實例化出一個對象了.然而,你發現,Traveller的構造函數是需要一個參數的,可是我們好像并沒有提供這個參數?
這就是IoC強大之處了, 調用make實例化對象的時候,容器會使用反射功能,去分析我們要實例化對象的構造函數,獲取構造函數所需的每個參數,然后分別去實例化這些參數,如果實例化這些參數也要參數,那么就再去實例化參數的參數.....=_=.到最后成功實例化我們所需要的traveller了.在Container的build函數就是使用反射來實例化對象.
但是,有一個問題了,IoC容器怎么知道實例化Traveller的時候需要的參數train,而不是leg?
其實,IoC容器什么都不知道,IoC會實例化哪些對象都是通過bind函數告訴IoC的,上面的例子兩次調用bind函數,就是告訴Ioc可以實例化的對象有Train和Traveller. 再通俗講就是:當需要當我們需要TrafficTool這個服務的時候去實例化Train這個類,需要一個travellerA的旅行者的時候去實例化Traveller類.而Train這個就是travellerA就是去青藏的方式. 這樣子如果想要走路去青藏的話只要把$app->bind('Visit', 'Train');改為$app->bind('Visit', 'Leg');就可以.
可是,這上面的這些有什么意義?直接$tra = new Traveller($trafficTool)來實例化對象好像也沒有什么不好的.
>使用new來實例化對象的時候,會產生依賴.比如上面$tra = new Traveller($trafficTool),這說明我們要創建一個Traveller之前得有一個$trafficTool,即Traveller依賴于trafficTool.當使用new來實例化Traveller的時候,Traveller和trafficTool之間就產生了耦合.這樣,這兩個組件就沒辦法分開了.
而使用IoC是怎么解決這個問題的,之前說過,如果想要如果想要走路去青藏的話只要把$app->bind('Visit', 'Train');改為$app->bind('Visit', 'Leg');就可以.這樣子,使用何種方式去青藏,我們可以自由的選擇.
我們站在Laravel框架設計者的角度去想,設計者肯定希望一個框架提供的功能越多越好,但是又要保證強大的同時又不會限制使用者.最好可以保證使用者想實現什么奇怪的需求都可以.那么功能強大但是又不局限的最好方法就是什么都不做,提供一個強大的IoC容器.所有需要實現的功能都變成一個個服務,需要什么服務就把服務注冊(即調用bind函數)到IoC中,然后讓IoC去管理依賴.
開發者想到一個變態的需求:走路去青藏,那么只要你實現了走路去青藏這個功能,然后把這個功能當做一個服務注冊到IoC中,以后你需要這個服務的時候IoC就幫你實例化這個服務.當開發者回歸正常之后覺得還是坐火車去吧,于是不注冊走路這個功能,實現坐火車的功能,然后注冊這個功能.下次IoC實例化的時候就是實例化坐火車這個功能了.
好了,剩下的部分就是一行一行的閱讀Container的代碼了,Laravel框架中的服務容器代碼也是這個樣子,只是功能更加強悍.但是核心是一樣的,上面的代碼懂了以后再使用Laravel框架就會更加游刃有余了.
文章雖短.但是內容很多.尤其是代碼,雖然可能只是短短的一個例子,但是包含了很多內容.值得好好分析,這里放個彩蛋:Traveller中構造函數參數類似為TrafficTool,是一個接口.但是實例化的是Train.這里體現了設計模式的一個原則
> 面對接口編程,而不是面對實現編程.
- 配置
- composer安裝
- composer用法
- composer版本約束表達
- phpstorm
- sftp文件同步
- php類型約束
- laradock
- 配置文件緩存詳解
- git
- 自定義函數
- 核心概念
- IOC
- 服務提供者
- Facade
- 契約
- 生命周期
- 路由
- 請求
- 命名路由
- 路由分組
- 資源路由
- 控制器路由
- 響應宏
- 響應
- Command
- 創建命令
- 定時任務
- console路由
- 執行用戶自定義的定時任務
- artisan命令
- 中間件
- 創建中間件
- 使用中間件
- 前置和后置
- 詳細介紹
- 訪問次數限制
- 為 VerifyCsrfToken 添加過濾條件
- 單點登錄
- 事件
- 創建
- ORM
- 簡介
- DB類
- 配置
- CURD
- queryScope和setAttribute
- 查看sql執行過程
- 關聯關系
- 一對一
- 一對多
- 多對多
- 遠程關聯
- 多態一對多
- 多態多對多
- 關聯數據庫的調用
- withDefault
- 跨模型更新時間戳
- withCount,withSum ,withAvg, withMax,withMin
- SQL常見操作
- 模型事件
- 模型事件詳解
- 模型事件與 Observer
- deleted 事件未被觸發
- model validation
- ORM/代碼片段
- Repository模式
- 多重where語句
- 中間表類型轉換
- Collection集合
- 新增的一些方法
- 常見用法
- 求和例子
- 機場登機例子
- 計算github活躍度
- 轉化評論格式
- 計算營業額
- 創建lookup數組
- 重新組織出表和字段關系并且字段排序
- 重構循環
- 其他例子
- 其他問題一
- 去重
- 第二個數組按第一個數組的鍵值排序
- 搜索ES
- 安裝
- 表單
- Request
- sessiom
- Response
- Input
- 表單驗證
- 簡介
- Validator
- Request類
- 接口中的表單驗證
- Lumen 中自定義表單驗證返回消息
- redis
- 廣播事件
- 發布訂閱
- 隊列
- 守護進程
- redis隊列的坑
- beanstalkd
- rabbitmq
- redis隊列
- 日志模塊
- 錯誤
- 日志詳解
- 數據填充與遷移
- 生成數據
- 數據填充seed
- migrate
- 常見錯誤
- Blade模板
- 流程控制
- 子視圖
- URL
- 代碼片段
- Carbon時間類
- 一些用法
- 郵件
- 分頁
- 加密解密
- 緩存
- 文件上傳
- 優化
- 隨記
- 嵌套評論
- 判斷字符串是否是合法的 json 字符串
- 單元測試
- 計算出兩個日期的diff
- 自定義一個類文件讓composer加載
- 時間加減
- 對象數組互轉方法
- 用戶停留過久自動退出登錄
- optional 輔助方法
- 文件下載
- Api
- Dingo api
- auth.basic
- api_token
- Jwt-Auth
- passport
- Auth
- Authentication 和 Authorization
- Auth Facade
- 授權策略
- Gates
- composer包
- debug包
- idehelp包
- image處理
- 驗證碼
- jq插件
- 第三方登錄
- 第三方支付
- log顯示包
- 微信包
- xss過濾
- Excel包
- MongoDB
- php操作
- 聚合查詢
- 發送帶附件郵件
- 中文轉拼音包
- clockwork網頁調試
- emoji表情
- symfony組件
- swooletw/laravel-swoole
- 常見問題
- 跨域問題
- Laravel隊列優先級的一個坑
- cache:clear清除緩存問題
- .env無法讀取
- 源碼相關基礎知識
- __set和__get
- 依賴注入、控制反轉和依賴倒置原則
- 控制反轉容器(Ioc Container)
- 深入服務容器
- call_user_func
- compact
- 中間件簡易實現
- array_reduce
- 中間件實現代碼
- Pipeline管道操作
- composer自動加載
- redis延時隊列
- 了解laravel redis隊列
- cli
- 源碼解讀
- Facade分析
- Facade源碼分析
- IOC服務容器
- 中間件原理
- 依賴注入淺析
- 微信
- 微信公眾號
- 常用接收消息
- 6大接收接口
- 常用被動回復消息
- 接口調用憑證
- 自定義菜單
- 新增素材
- 客服消息
- 二維碼
- 微信語音
- LBS定位
- 網頁授權
- JSSDK
- easywechat
- 小程序
- 小程序配置app.json