文章出自 Inn的博客 : https://www.gog5.cn/archives/8/
第一次開通博客,歡迎小夥伴們點贊、交換友情連接php
建議先看看 IoC基礎,寫的很好。sql
Inversion of Control,即「控制反轉」,不是什麼技術,而是一種設計思想。Ioc意味着將你設計好的對象交給容器控制,而不是傳統的在你的對象內部直接控制。傳統程序設計,咱們直接在對象內部經過new進行建立對象,是程序主動去建立依賴對象;而IoC是有專門一個容器來建立這些對象,即由Ioc容器來控制對象的建立。thinkphp
傳統應用程序是由咱們本身在對象中主動控制去直接獲取依賴對象,也就是正轉,而反轉則是由容器來幫忙建立及注入依賴對象。爲什麼是反轉?由於由容器幫咱們查找及注入依賴對象,對象只是被動的接受依賴對象,依賴對象的獲取被反轉了。數據庫
<?php interface Database { public function insert(); } class Mysql implements Database { public function insert() { echo 'Mysql insert'; } } class Oracle implements Database { public function insert() { echo 'Oracle insert'; } } class App { protected $database; public function __construct() { $this->database = new Mysql(); } public function insertLog() { $this->database->insert(); } } $app = new App(); $app->insertLog();
上面的代碼實現了寫入數據庫日誌功能,看起來沒什麼問題,可是若是我要把Mysql改爲Oracle數據庫,那就得從新修改App類了。實際上這並無達到解耦。咱們能夠稍做修改。數組
class App { protected $database; public function __construct(Database $database) { $this->database = $database; } public function insertLog() { $this->database->insert(); } } $app = new App(new Oracle()); $app->insertLog();
如今經過構造函數傳遞參數就能夠實現修改數據庫了,由外部注入依賴對象,App對象只是被動的接受依賴對象,因此是反轉;哪些方面反轉了?依賴對象的獲取被反轉了,咱們能夠稱其爲控制反轉。app
DI—Dependency Injection,即「依賴注入」:是組件之間依賴關係由容器在運行期決定,形象的說,即由容器動態的將某個依賴關係注入到組件之中。依賴注入的目的並不是爲軟件系統帶來更多功能,而是爲了提高組件重用的頻率,併爲系統搭建一個靈活、可擴展的平臺。經過依賴注入機制,咱們只須要經過簡單的配置,而無需任何代碼就可指定目標須要的資源,完成自身的業務邏輯,而不須要關心具體的資源來自何處,由誰實現。函數
理解DI的關鍵是:「誰依賴誰,爲何須要依賴,誰注入誰,注入了什麼」,那咱們來深刻分析一下:ui
咱們來看看thinkphp6的獲取容器中的實例方法this
$arrayItem = app('org\utils\ArrayItem');
這裏實際上在底層調用了think\Container
的invokeClass
方法spa
public function invokeClass(string $class, array $vars = []) { try { $reflect = new ReflectionClass($class); } catch (ReflectionException $e) { throw new ClassNotFoundException('class not exists: ' . $class, $class, $e); } if ($reflect->hasMethod('__make')) { $method = $reflect->getMethod('__make'); if ($method->isPublic() && $method->isStatic()) { $args = $this->bindParams($method, $vars); return $method->invokeArgs(null, $args); } } $constructor = $reflect->getConstructor(); $args = $constructor ? $this->bindParams($constructor, $vars) : []; $object = $reflect->newInstanceArgs($args); $this->invokeAfter($class, $object); return $object; }
原來tp是用反射來實現依賴注入的,咱們看到這行,正是實例化了一個反射類。
$reflect = new ReflectionClass($class);
反射指在PHP運行狀態中,擴展分析PHP程序,導出或提取出關於類,方法,屬性,參數等詳細信息,包括註釋。這種動態獲取信息以及動態調用對象方法的功能稱爲反射API,反射類的方法有:
https://www.php.net/manual/zh/book.reflection.php
ReflectionClass::__construct — 初始化 ReflectionClass 類 ReflectionClass::export — 導出一個類 ReflectionClass::getConstant — 獲取定義過的一個常量 ReflectionClass::getConstants — 獲取一組常量 ReflectionClass::getConstructor — 獲取類的構造函數 ReflectionClass::getDefaultProperties — 獲取默認屬性 ReflectionClass::getDocComment — 獲取文檔註釋 ReflectionClass::getEndLine — 獲取最後一行的行數 ReflectionClass::getExtension — 根據已定義的類獲取所在擴展的 ReflectionExtension 對象 ReflectionClass::getExtensionName — 獲取定義的類所在的擴展的名稱 ReflectionClass::getFileName — 獲取定義類的文件名 ReflectionClass::getInterfaceNames — 獲取接口(interface)名稱 ReflectionClass::getInterfaces — 獲取接口 ReflectionClass::getMethod — 獲取一個類方法的 ReflectionMethod。 ReflectionClass::getMethods — 獲取方法的數組 ReflectionClass::getModifiers — 獲取類的修飾符 ReflectionClass::getName — 獲取類名 ReflectionClass::getNamespaceName — 獲取命名空間的名稱 ReflectionClass::getParentClass — 獲取父類 ReflectionClass::getProperties — 獲取一組屬性 ReflectionClass::getProperty — 獲取類的一個屬性的 ReflectionProperty ReflectionClass::getReflectionConstant — Gets a ReflectionClassConstant for a class's constant ReflectionClass::getReflectionConstants — Gets class constants ReflectionClass::getShortName — 獲取短名 ReflectionClass::getStartLine — 獲取起始行號 ReflectionClass::getStaticProperties — 獲取靜態(static)屬性 ReflectionClass::getStaticPropertyValue — 獲取靜態(static)屬性的值 ReflectionClass::getTraitAliases — 返回 trait 別名的一個數組 ReflectionClass::getTraitNames — 返回這個類所使用 traits 的名稱的數組 ReflectionClass::getTraits — 返回這個類所使用的 traits 數組 ReflectionClass::hasConstant — 檢查常量是否已經定義 ReflectionClass::hasMethod — 檢查方法是否已定義 ReflectionClass::hasProperty — 檢查屬性是否已定義 ReflectionClass::implementsInterface — 接口的實現 ReflectionClass::inNamespace — 檢查是否位於命名空間中 ReflectionClass::isAbstract — 檢查類是不是抽象類(abstract) ReflectionClass::isAnonymous — 檢查類是不是匿名類 ReflectionClass::isCloneable — 返回了一個類是否可複製 ReflectionClass::isFinal — 檢查類是否聲明爲 final ReflectionClass::isInstance — 檢查類的實例 ReflectionClass::isInstantiable — 檢查類是否可實例化 ReflectionClass::isInterface — 檢查類是不是一個接口(interface) ReflectionClass::isInternal — 檢查類是否由擴展或核心在內部定義 ReflectionClass::isIterable — Check whether this class is iterable ReflectionClass::isIterateable — 檢查是否可迭代(iterateable) ReflectionClass::isSubclassOf — 檢查是否爲一個子類 ReflectionClass::isTrait — 返回了是否爲一個 trait ReflectionClass::isUserDefined — 檢查是否由用戶定義的 ReflectionClass::newInstance — 從指定的參數建立一個新的類實例 ReflectionClass::newInstanceArgs — 從給出的參數建立一個新的類實例。 ReflectionClass::newInstanceWithoutConstructor — 建立一個新的類實例而不調用它的構造函數 ReflectionClass::setStaticPropertyValue — 設置靜態屬性的值 ReflectionClass::__toString — 返回 ReflectionClass 對象字符串的表示形式。
知道反射類的基本方法了,咱們來實現一個不用new
對應class就能夠實例化對象的方法:
function make($class) { //創建這個類的反射類 $reflect = new ReflectionClass($class); //拿到構造方法 $constructor = $reflect->getConstructor(); //沒有構造方法,直接返回實例 if (is_null($constructor)) { return $reflect->newInstance(); } //獲取構造方法的參數 $parameters = $constructor->getParameters(); //傳入參數實例化class return $reflect->newInstanceArgs($parameters); } $std = new stdClass(); var_dump($std); $std2 = make('stdClass'); var_dump($std2);
上面的$std
和 $std2
對象都是同樣的。可是咱們還沒實現依賴注入,接着再來改造一下。
... class App { protected $database; public function __construct(Oracle $database) { $this->database = $database; } public function insertLog() { $this->database->insert(); } } function make($class) { $reflect = new ReflectionClass($class); $constructor = $reflect->getConstructor(); if (is_null($constructor)) { return $reflect->newInstance(); } //返回構造函數裏的依賴實例 $instances = getParameters($constructor); return $reflect->newInstanceArgs($instances); } function getParameters($constructor) { $parameters = $constructor->getParameters(); $dependencies = []; foreach ($parameters as $parameter) { //調用make方法遞歸獲取實例 //$parameter->getClass()->name 得到類型提示類名,好比 function Test(App $app) ,就是App $dependencies[] = make($parameter->getClass()->name); } return $dependencies; } $app = make('App'); $app->insertLog();
上面程序裏IoC容器自動實例化了Oracle類,並注入到App類裏,因此咱們不須要手動傳入,咱們能夠稱其爲依賴注入。
上面的代碼仍是沒有徹底達到解偶。若是不少頁面的構造裏都注入了Mysql,可是有一天要把系統換成Oracle記錄日誌,那咱們豈不是所有頁面都要改爲Oracle?
並且以前課程 淺談thinkphp的服務容器 (https://www.gog5.cn/archives/6/)
咱們尚未實現IoC和DI。
因此會結合上一節 淺談thinkphp的服務容器 實現依賴注入。
interface Database { public function insert(); } class Mysql implements Database { public function insert() { echo 'Mysql insert'; } } class Oracle implements Database { public function insert() { echo 'Oracle insert'; } } class App { protected $database; public function __construct(Database $database) { $this->database = $database; } public function insertLog() { $this->database->insert(); } } class Container { protected $bind = []; /** * @param string $abstract 類標識、接口 * @param string $concrete 要綁定的類 */ public function bind($abstract, $concrete) { $this->bind[$abstract] = $this->build($concrete); } public function make($abstract) { if (!isset($this->bind[$abstract])) { $this->bind($abstract, $abstract); } return $this->bind[$abstract]; } //至關於以前的make方法 public function build($abstract) { $reflect = new ReflectionClass($abstract); $constructor = $reflect->getConstructor(); if (is_null($constructor)) { return $reflect->newInstance(); } $instances = $this->getParameters($constructor); return $reflect->newInstanceArgs($instances); } protected function getParameters($constructor) { $parameters = $constructor->getParameters(); $dependencies = []; foreach ($parameters as $parameter) { $dependencies[] = $this->make($parameter->getClass()->name); } return $dependencies; } } $container = new Container(); $container->bind('Database', 'Mysql'); $app = $container->make('App'); $app->insertLog();
咱們把 App
構造的類提示從Oracle
類改爲 Database
接口,這樣就可讓容器決定注入數據庫類了:
public function __construct(Database $database)
怎麼讓容器決定注入的類,bind
方法裏,先是把Mysql
類實例化,而後映射到Database
接口。 make
解析實例的時候,會把Database
換成Mysql
實例,注入到參數裏。
$container->bind('Database', 'Mysql');
之後須要更換數據庫時候,只要在綁定容器的地方,把映射類替換掉,全部頁面也都生效了。
$container->bind('Database', 'Oracle');
到這裏容器、依賴注入、控制反轉也就講完了。