PHP語言的靈活性
段譽奇道:“什麼?只這麼一會兒,便使了一十七種不同的武功?” 小說裡面的慕容復博知天下武學,只一會兒功夫便使出了十七種不同的武功,php可以稱之為程式設計界的慕容復,看看php一段Hello World。
<?php
/**
*php7.4 需要安裝swoole擴充套件
*/
class Test{
public static function main() : void{
go(function() {
$arr = ['H', 'e', 'l', 'l', 'o'];
echo array_reduce($arr, fn($s, $e) => $s .= $e);
});
}
}
Test::main();
你是不是看到了Java、Golang、JavaScript的影子,php吸取了很多語言的特性,可函式式,可OOP,作為一門指令碼語言當然也可以程式導向。因為PHP如此靈活再加上沒有JavaEE這樣企業級的標準導致程式碼風格”五花八門”。比如專案早期是這麼建立物件的,每個方法前面一堆的new物件,檔案頭部一長串 use namespace。
<?php
namespace Model\Food;
use Psr\KLogg\Logger;
use Model\User\User;
use Model\Shop\Vip;
use Model\Pay\Pay;
use App\Libs\Http;
... 此處略去一萬行
class Food{
public function createOrder(){
//日誌類建構函式第一個引數日誌目錄,第二個引數除錯
$logger = new Logger('order', 'debug');
$user = new User();
$vip = new Vip();
$pay = new Pay();
$http = new Http();
... 此處略去一萬行
}
- 使用工廠模式優化
某一天專案上了分散式服務,原來的日誌類不好用了,得換另外一個開源的日誌類庫,leader一聲令下,一個個改,所有寫日誌的地方全得修改,程式猿痛苦不跌,而且每次寫業務邏輯都得手動建立物件,既然是建立物件,那麼就用物件建立型的工廠設計模式來生產物件。
<?php
class Model{
public static $_model = [];
/**
*魔術靜態方法可以減少很多工廠方法
*/
public static function __callStatic($name, $argv) {
if (isset(self::$_model[$name])) {
return self::$_model[$name];
}
$name = "\\App\\libs\\" . $name;
self::$_model[$name] = new $name(...$argv);
return self::$_model[$name];
}
public static function logger($path, $level){
if(isset(self::$_model['logger'])){
self::$_model['logger'] = new Psr\KLogg\Logger();
}
return self::$_model['logger'];
}
public static function User(){
if(isset(self::$_model['user'])){
self::$_model['user'] = new \Model\User\User();
}
return self::$_model['user'];
}
......
}
好景不長,當專案不斷迭代,工廠裡的方法會越來越多,每次新增一個物件型別就得在工廠增加一個方法,違背了solid原則裡的開閉原則,其實這個倒是影響不是很大,關鍵是這個工廠類會越來越膨脹,那麼如何把物件的建立和工廠進行解耦呢?本文的主角IOC容器登場了,Laravel的核心就是一個IOC容器。
IOC容器
laravel容器的作用是管理類的依賴和注入的工具, 原來類對外部的依賴需要在本類建立物件,使用了容器後,使得類把建立物件的工作反轉到了容器。
laravel原始碼分析
public/public/index.php入口
bootstrap/app.php
vendor/laravel/framework/src/Illuminate/Foundation/Application.php
這一步會註冊基礎的服務提供者vendor/laravel/framework/src/Illuminate/Routing/RoutingServiceProvider.php
我們看到註冊路由方法 執行了容器例項的singleton方法 vendor/laravel/framework/src/Illuminate/Foundation/Application.php
bind方法重點看這一行,容器的屬性bindings陣列 以繫結的字元為key,閉包為值,閉包裡有建立物件的方法。那這個閉包是什麼時候會執行呢?
由於控制器不是在我們的應用層,這裡我們以Redis為例子來分析下。首先你當然得配置好redis連線配置。
- 自動注入原始碼分析
index.php會首先例項化kernel並執行handle方法, vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
vendor\laravel\framework\src\Illuminate\Foundation\Application.php
bootstrppers陣列裡的服務提供者都會被例項化,並且執行bootstrap方法。
vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/RegisterFacades.php
vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/RegisterProviders.php
config/app.php裡的provoders的建立方法會被自動注入到容器。
config/app.php裡的alials會執行class_alias
vendor\laravel\framework\src\Illuminate\Foundation\AliasLoader.php
例項真正被建立是在呼叫的時候
我們寫一個redis存取的例子,這裡我把config/app.php裡的Redis別名改成MyRedis<?php namespace App\Http\Controllers; class IndexController extends Controller { public function show() { /**@var $redis \Redis **/ /**我們可以直接使用MyRedis別名,不需要引入任何**/ $key = 'test_laravel'; $redis = \MyRedis::connection(); $redis->set($key, 2000000); echo $redis->get($key); } }
那麼redis例項到底是如何進行的呢?
自動執行例項物件原始碼分析
這個類只有一個方法 getFaceeAccessor,我們呼叫的set方法其實是執行了Faced類的__callstaic方法
static::getFacedAccessor()執行的就是 (使用了靜態延遲繫結)重點來了。。。
我們知道這個name的值是MyRedis。。。MyRedis是怎麼例項化的呢? 謎底馬上揭開了。。。
- containner類實現了ArrayAcess介面
原因就在於容器類實現了ArrayAceess介面,使得$staic::$app[$name],呼叫了offsetGet 然後執行了make方法,一切水落石出
知道了laravel的實現,我們就可以自己實現一個簡陋的容器。
- 使用IOC容器優化
容器類
namespace sdk\container; class Container implements \ArrayAccess { /** * @var array 模型例項陣列 */ private $instances = []; /** * @var array 繫結的閉包 */ private $bindings = []; public function isBinded($abstract){ return isset($this->bindings[$abstract]); } public function bind($abstract, $concrete){ if(!$this->isBinded($abstract)){ $this->bindings[$abstract] = $concrete; } return $this; } public function make($abstract){ if($this->bindings[$abstract] instanceof \Closure){ return call_user_func($this->bindings[$abstract]); }else{ return $this->bindings[$abstract]; } } public function setInstances($alias, $instance){ $this->instances[$alias] = $instance; return $instance; } public function getInstances(){ return $this->instances; } /** * @inheritDoc */ public function offsetExists($alias) { return isset($this->instances[$alias]); } /** * @inheritDoc */ public function offsetGet($alias) { if(isset($this->instances[$alias])){ return $this->instances[$alias]; } return $this->make($alias); } /** * @inheritDoc */ public function offsetSet($alias, $value) { $this->instances[$alias] = $value; return $this; } /** * @inheritDoc */ public function offsetUnset($alias) { unset($this->instances[$alias]); return $this; } }
模型類
namespace sdk\container; /** * @method static \sdk\cater\Order caterOrder() * @method static \sdk\cater\Waimai waimai() * @method static \Katzgrau\KLogger\Logger logger() */ class Model { /** * @var array */ private static $cfg = [ 'caterOrder'=>[ \sdk\cater\Order::class, ], 'waimai'=>[ \sdk\cater\Waimai::class, ], 'logger'=>[ \Katzgrau\KLogger\Logger::class, [ SDK_PATH . '/../log/', \Psr\Log\LogLevel::DEBUG, ] ], ]; /** * 是否已經繫結過了 * @var bool */ private static $isBind = false; /** * @var \sdk\container\Container */ private static $app; public static function bind(){ foreach(static::$cfg as $alias=>$value){ $class = $value[0] ?? ''; if(empty($class)){ continue; } $args = $value[1] ?? []; static::$app->bind($alias, function() use($alias, $class, $args){ $ref = new \ReflectionClass($class); return static::$app->setInstances($alias, $ref->newInstanceArgs($args)); }); } } public static function __callStatic($name, $args) { if(static::$app == null){ static::$app = new Container(); } if(static::$isBind == false){ static::bind(); } if(static::$app != null){ return static::$app[$name]; } } }
以後加新的模型方法只需要增加配置以及使IDE具備提示功能的註釋就可以了。
本作品採用《CC 協議》,轉載必須註明作者和本文連結