1兼都、基礎(chǔ)知識預(yù)備
1.1 PHP 反射詳解
什么是反射定续?直觀理解就是根據(jù)到達地找到出發(fā)地和來源捂人。比如蓝谨,一個光禿禿的對象,我們可以僅僅通過這個對象就能知道它所屬的類茂卦,擁有哪些方法何什。
PHP的反射是指在PHP運行狀態(tài)過程中,擴展分析PHP程序等龙,導(dǎo)出或提出關(guān)于類处渣、方法伶贰、屬性、參數(shù)等詳細信息罐栈,包括注釋黍衙。這種動態(tài)獲取信息以及動態(tài)調(diào)用對象方法的功能成為反射API
下面是一個PHP反射的一個簡單demo。
詳細可參考PHP docuement http://php.net/manual/zh/book.reflection.php
<?php
class Person{
public $name;
public $gender;
private $age;
public function __construct($name,$gender){
$this->name = $name;
$this->gender = $gender;
}
public function __set($key, $value){
$this->$key = $value;
}
public function __get($key){
if(!isset($this->$key)){
return "$key not exists";
}
return $this->$key;
}
}
//用ReflectionClass得到A的反射類對象荠诬,通過反射類對象可以得到類的各種屬性琅翻,
//包括類名空間,父類柑贞,類名等方椎,使用newInstanceArgs可以傳入構(gòu)造函數(shù)的參數(shù)創(chuàng)建一個新的類的實例
$reflect = new ReflectionClass("Person");
//注入?yún)?shù)
$student = $reflect->newInstanceArgs(["xiaoming", "male"]);
echo $student->name;
echo "\n";
echo $student->gender;
echo "\n";
echo $student->age;
echo "\n";
$student->age = 20;
echo $student->age;
echo "\n";
output:
xiaoming
male
age not exists
20
1.2 依賴
程序的執(zhí)行過程就是方法的調(diào)用過程,有方法調(diào)用,就必然會促使對象和對象之間產(chǎn)生依賴钧嘶,除非一個對象不參與程序的運行棠众,這樣的對象就像一座孤島,與其他對象沒有任何交互有决,但是這樣的對象也就沒有任何存在的價值了闸拿。因此,在我們的程序代碼中疮薇,任何一個對象必然會與其他一個甚至更多個對象產(chǎn)生依賴關(guān)系胸墙。
依賴產(chǎn)生的原因主要有:
- 方法調(diào)用
- 繼承(子類依賴于父類)
- 傳遞參數(shù)(一個類作為參數(shù)傳遞給另外一個類的成員方法時)
- 臨時變量引入
下面有兩段代碼我注。
第一段是直接在代碼中引入了Train類按咒,這時候就形成了依賴。但是假如旅客不想坐火車但骨,想坐飛機励七,只能修改StupidTraveller中的代碼。
第二段代碼使用依賴注入的方式解除了上面StupidTraveller對Train的依賴奔缠。代碼的可擴展性變強了很多掠抬,如果還有其他的交通工具比如Car,只需要將Car注入Traveller類中即可。此處的依賴注入是手動的校哎,Laravel的IOC容器是自動注入的两波。
<?php
class Train{
public function go(){
echo "move by train \n";
}
}
class Airplane{
public function go(){
echo "move by airplane \n";
}
}
class StupidTraveller{
private $trafficTool;
public function __construct(){
$this->trafficTool = new Train();
}
public function travel(){
$this->trafficTool->go();
}
}
$mike = new StupidTraveller();
$mike->travel();
<?php
interface TrafficTool{
public function go();
}
class Train implements TrafficTool{
public function go(){
echo "move by train \n";
}
}
class Airplane implements TrafficTool{
public function go(){
echo "move by airplane \n";
}
}
class Traveller{
private $trafficTool;
public function __construct(TrafficTool $tool){
$this->trafficTool = $tool;
}
public function travel(){
$this->trafficTool->go();
}
}
//在這里進行了依賴注入
$mike = new Traveller(new Train());
$mike->travel();
2、IOC容器
2.1 IOC容器是什么闷哆?
在理解IOC之前腰奋,首先從1.2的例子中理解下什么是DI(Dependency Injection)。Traveller類在初始化的時候注入了TrafficTool類抱怔,這就是一個典型的依賴注入的例子劣坊。依賴注入有兩個好處,一個是解耦屈留,將依賴之間解耦局冰,顯然Traveller類要比StupidTraveller要優(yōu)雅很多测蘑;第二個是由于已經(jīng)解耦了,方便做單元測試康二,尤其是Mock測試碳胳。如果TrafficTool是一個數(shù)據(jù)庫的操作類(打個比方啊)沫勿,這個時候你做單元測試的時候可以直接Mock一個虛擬的數(shù)據(jù)庫類注入到Traveller中去來做單元測試固逗。
IOC(Inversion of Control) 控制反轉(zhuǎn)是一種面對對象編程的設(shè)計原則,用于降低代碼之間的耦合度藕帜。其基本思想是借助第三方實現(xiàn)具有依賴關(guān)系的對象之間的解耦烫罩。
軟件系統(tǒng)在沒有IOC之前,對象之間相互依賴洽故,那么對象A在初始化或者運行到某一個點的時候贝攒,自己必須主動去創(chuàng)建一個對象B,或者使用之間已經(jīng)創(chuàng)建好的對象B。無論是創(chuàng)建還是使用對象B,控制權(quán)都在自己手上时甚。
但是在有了IOC容器后隘弊,這種情況改變了,由于IOC的加入荒适,對象之間的直接聯(lián)系消失了梨熙。在對象A運行時需要對象B的時候,IOC會主動創(chuàng)建一個對象B注入到對象A需要的地方刀诬。這也是Laravel中實現(xiàn)的IOC容器的神奇之處咽扇,自動注入依賴。
2.2 一段比較經(jīng)典的IOC容器的代碼
下面這段代碼源于《laravel框架關(guān)鍵技術(shù)解析》
<?php
// 容器類陕壹,將接口與實現(xiàn)綁定
class Container
{
// 保存與接口綁定的閉包质欲,
// 閉包必須能夠返回接口的實例。
protected $bindings = [];
// 為某個接口綁定一個實現(xiàn)糠馆,有兩種情況:
//
// 第一種是綁定接口的實現(xiàn)的類名嘶伟;
// 第二種是綁定一個閉包,這個閉包應(yīng)該返回接口的實例又碌。
// 不管哪種情況九昧,實例化操作都是調(diào)用 build() 方法完成。
// 第二種情況毕匀,主要是為了能夠在實例化前后進行額外的操作铸鹰,
// 實例化的邏輯就書寫在閉包中。
public function bind($abstract, $concrete = null)
{
// 如果提供的參數(shù)不是閉包期揪,而是一個類掉奄,
// 則構(gòu)建一個閉包,直接調(diào)用 build() 方法進行實例化
if (! $concrete instanceof Closure) {
// 調(diào)用閉包時,傳入的參數(shù)是容器本身姓建,即 $this
$concrete = function ($c) use ($concrete) {
return $c->build($concrete);
};
}
$this->bindings[$abstract] = $concrete;
}
// 生成指定接口的實例
public function make($abstract)
{
// 取出閉包
$concrete = $this->bindings[$abstract];
// 運行閉包诞仓,即可取得一個實例
return $concrete($this);
}
public function build($class)
{
// 取得類的反射
$reflector = new ReflectionClass($class);
// 檢查類是否可實例化
if (! $reflector->isInstantiable()) {
// 如果不能,意味著接口不能正常工作速兔,報錯
echo $message = "Target [$class] is not instantiable";
}
// 取得構(gòu)造函數(shù)的反射
$constructor = $reflector->getConstructor();
// 檢查是否有構(gòu)造函數(shù)
if (is_null($constructor)) {
// 如果沒有墅拭,就說明沒有依賴,直接實例化
return new $class;
}
// 取得包含每個參數(shù)的反射的數(shù)組
$parameters = $constructor->getParameters();
// 返回一個真正的參數(shù)列表涣狗,那些被類型提示的參數(shù)已經(jīng)被注入相應(yīng)的實例
$realParameters = $this->resolveDependencies($parameters);
return $reflector->newInstanceArgs($realParameters);
}
protected function resolveDependencies($parameters)
{
$realParameters = [];
foreach($parameters as $parameter) {
// 如果一個參數(shù)被類型提示為類 foo谍婉,
// 則這個方法將返回類 foo 的反射
$dependency = $parameter->getClass();
if(is_null($dependency)) {
$realParameters[] = NULL;
} else {
$realParameters[] = $this->make($dependency->name);
}
}
return (array)$realParameters;
}
}
下面在用上面的IOC容器來實現(xiàn)先前的Traveller
<?php
require __DIR__ . '/Container.php';
interface TrafficTool
{
public function go();
}
class Train implements TrafficTool
{
public function go()
{
// TODO: Implement go() method.
echo "move by train";
}
}
class Airplane implements TrafficTool
{
public function go()
{
// TODO: Implement go() method.
echo "move by airplane\n";
}
}
class Destination
{
public function mark()
{
echo "I am here";
}
}
class Traveller
{
protected $trafficTool;
protected $destination;
public function __construct(TrafficTool $tool, Destination $destination)
{
$this->trafficTool = $tool;
$this->destination = $destination;
}
public function travel()
{
$this->trafficTool->go();
$this->destination->mark();
}
}
// 實例化容器
$app = new Container();
//綁定某一功能到ioc
//將Train綁定到TrafficTool,abstract 是TrafficTool, concrete是Train
$app->bind('TrafficTool', 'AirPlane');
$app->bind("Destination", "Destination");
$app->bind('travellerA', 'Traveller');
//實例化對象
$tra = $app->make('travellerA');
$tra->travel();
注意$tra = $app->make('travellerA')
;這里并沒有手動的注入依賴TrafficTool。這個依賴在container的resolveDependencies方法中獲取镀钓,最后在$reflector->newInstanceArgs($realParameters)
這行代碼中間進行了注入穗熬。并且這一系列的注入都不是手動的,注入都是自動完成的丁溅。
2.3唤蔗、laravel中的IOC容器
在laravel初始化的地方index.php中有這樣一行代碼
/*
|--------------------------------------------------------------------------
| Turn On The Lights
|--------------------------------------------------------------------------
|
| We need to illuminate PHP development, so let us turn on the lights.
| This bootstraps the framework and gets it ready for use, then it
| will load up this application so that we can run it and send
| the responses back to the browser and delight our users.
|
*/
$app = require_once __DIR__.'/../bootstrap/app.php';
開燈了!?呱汀妓柜!這里的app就是IOC容器,里面已經(jīng)進行了一系列的綁定涯穷。具體的綁定如下棍掐。
<?php
$app = new Illuminate\Foundation\Application(
realpath(__DIR__.'/../')
);
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
return $app;
容器就是在vendor/laravel/framework/src/illuminate/Container/Container.php中實現(xiàn)的,具體的實現(xiàn)代碼要比上面的那個簡單版復(fù)雜很多拷况,但是思想是一樣的作煌。