淺析easyswoole源碼Di原理與實現
簡介:Dependency Injection 依賴註入
EasySwoole實現了簡單版的IOC,使用 IOC 容器可以很方便的存儲/獲取資源,實現解耦。
使用依賴註入,最重要的一點好處就是有效的分離了對象和它所需要的外部資源,使得它們松散耦合,有利於功能復用,更重要的是使得程序的整個體系結構變得非常靈活。
在我們的日常開發中,創建對象的操作隨處可見以至於對其十分熟悉的同時又感覺十分繁瑣,每次需要對象都需要親手將其new出來,甚至某些情況下由於壞編程習慣還會造成對象無法被回收,這是相當糟糕的。但更為嚴重的是,我們一直倡導的松耦合,少***原則,這種情況下變得一無是處。於是前輩們開始謀求改變這種編程陋習,考慮如何使用編碼更加解耦合,由此而來的解決方案是面向接口的編程。
--摘自《searswoole開發文檔》
本文將解析並實現下列功能。
- 1、singleton模式的巧妙實現。
- 2、通過Di方式實現簡單的容器,方便存取資源,從而達到解耦的目的。
代碼實現
文件結構
- \client.php 功能調用入口
- \AbstractInterface\Singleton.php 單例抽象接口
- \Lib\Redis.php redis服務類
- \Service\Di.php 依賴註入實現簡單Ioc容器
單例實現過程解讀
client.php
<?php /** * Created by PhpStorm. * User: zrj * Date: 19-1-14 * Time: 下午4:39 */ namespace App; define(‘APP_ROOT‘, dirname(__FILE__)); use Exception; use Yaconf; require_once APP_ROOT . ‘/Lib/Redis.php‘; require_once APP_ROOT . ‘/Service/Di.php‘; use App\Lib\Redis; use App\Service\Di; try { if (!Yaconf::has(‘easyswoole_api‘)) throw new Exception(‘項目配置ini文件不存在‘); $redisConfig = Yaconf::get(‘easyswoole_api‘)[‘redis‘] ?? []; if (empty($redisConfig)) throw new Exception(‘項目redis配置不存在‘); $config = [ ‘host‘ => (string)$redisConfig[‘host‘] ?? ‘‘, ‘port‘ => (int)$redisConfig[‘port‘] ?? ‘‘, ‘timeout‘ => (string)$redisConfig[‘timeout‘] ?? ‘‘, ‘password‘ => (string)$redisConfig[‘password‘] ?? ‘‘, ‘database‘ => (int)$redisConfig[‘database‘] ?? ‘‘, ]; //直接訪問 //Redis::getInstance($config)->set(‘hello‘, ‘world‘); //echo Redis::getInstance($config)->get(‘hello‘); //Di::getInstance()->clear(); //註入容器 //--方法一、註入實例 //Di::getInstance()->set(‘redis‘, \App\Lib\Redis::getInstance($config)); //--方法二、容器內部實例化(支持單例模式) Di::getInstance()->set(‘redis‘, \App\Lib\Redis::class, $config); //通過容器獲取實例 $redisInstance = Di::getInstance()->get(‘redis‘); $redisInstance->set(‘dear‘, ‘hui‘); echo $redisInstance->get(‘hello‘) . PHP_EOL; echo $redisInstance->get(‘dear‘) . PHP_EOL; } catch (\Exception $e) { //throw $e; echo $e->getMessage(); }
\AbstractInterface\Singleton.php 單例抽象接口
namespace App\AbstractInterface; #聲明為特性 Trait Singleton { private static $instance; public static function getInstance(...$args) { if(!isset(self::$instance)){ //通過static靜態綁定來new綁定的對象 self::$instance=new static(...$args); } return self::$instance; } }
\Lib\Redis.php redis服務類
namespace App\Lib;
require_once APP_ROOT . ‘/AbstractInterface/Singleton.php‘;
use App\AbstractInterface\Singleton;
class Redis
{
//這裏通過使用特性來擴展Redis類的能力
//借助Singleton中的getInstance來new本類,創建redis連接實例
//Redis::getInstance(...)
use Singleton;
private $connectInstance = null;
/**
* Redis constructor.
* @param array $redisConfig
* @throws \Exception
*/
private function __construct(array $redisConfig)
{
try {
//通過yaconf讀取配置ini
if (!extension_loaded(‘redis‘)) throw new \Exception(‘Redis extension is not install‘);
$this->connectInstance = new \Redis();
$result = $this->connectInstance->connect($redisConfig[‘host‘], $redisConfig[‘port‘], $redisConfig[‘timeout‘]);
$this->connectInstance->auth($redisConfig[‘password‘]);
$this->connectInstance->select($redisConfig[‘database‘]);
if ($result === false) throw new \Exception(‘Connect redis server fail‘);
} catch (\Exception $e) {
throw $e;
}
}
public function __call($name, $arguments)
{
return $this->connectInstance->$name(...$arguments);
}
}
Redis類引入Singleton特性,輕松實現單例子。
//通過Redis::getInstance就能獲取實例
Redis::getInstance($config)->set(‘hello‘, ‘world‘);
echo Redis::getInstance($config)->get(‘hello‘);
依賴註入的實現
\Service\Di.php 依賴註入實現簡單Ioc容器
<?php
/**
* Created by PhpStorm.
* User: zrj
* Date: 19-1-14
* Time: 下午5:06
*/
namespace App\Service;
require_once APP_ROOT . ‘/AbstractInterface/Singleton.php‘;
require_once APP_ROOT . ‘/Lib/Redis.php‘;
use App\AbstractInterface\Singleton;
//use App\Lib\Redis;
class Di
{
use Singleton;
private $container = [];
//註入容器
public function set(string $key, $obj, array $arg = []): void
{
if (count($arg) == 1 && is_array($arg[0])) {
$arg = $arg[0];
}
$this->container[$key] = [
‘obj‘ => $obj,
‘params‘ => $arg
];
}
public function get(string $key)
{
if (!isset($this->container[$key])) return null;
$result = $this->container[$key];
if (is_object($result[‘obj‘])) {
return $result[‘obj‘];
} elseif (is_callable($result[‘obj‘])) {// 檢測參數是否為合法的可調用結構(function,class method)
return $result[‘obj‘];
} elseif (is_string($result[‘obj‘]) && class_exists($result[‘obj‘])) {
//通過反射方式獲取實例化對象
$reflection = new \ReflectionClass($result[‘obj‘]);
$constructor = $reflection->getConstructor();
$instance = null;
if ($constructor->isPublic()) {//直接實例化
$instance = $reflection->newInstanceArgs($result[‘params‘]);
} elseif ($constructor->isPrivate()) {//支持單例模式
if ($reflection->hasMethod(‘getInstance‘)) {
$instance = $result[‘obj‘]::getInstance($result[‘params‘]);
}
}
$this->container[$key][‘obj‘] = $instance;
return $instance;
} else {
return $result[‘obj‘];
}
}
public function clear(): void
{
$this->container = [];
}
public function delete($key): void
{
unset($this->container[$key]);
}
}
在client.php中使用Di的使用
//向容器中註入實例
//Di::getInstance()->set(‘redis‘, \App\Lib\Redis::getInstance($config));
Di::getInstance()->set(‘redis‘, \App\Lib\Redis::class, $config);//通過容器獲取實例
$redisInstance = Di::getInstance()->get(‘redis‘);$redisInstance->set(‘dear‘, ‘hui‘);
echo $redisInstance->get(‘hello‘) . PHP_EOL;
echo $redisInstance->get(‘dear‘) . PHP_EOL;
總結
本案例涉及知識點:
1.通過Yaconf擴展高效獲取配置信息
2.static靜態綁定、trait特性
3.singleton模式的實現
4.Di依賴註入
附:Yaconf配置文件(easyswoole_api.ini)格式
redis.host = ‘192.168.1.99‘
redis.port = 6379
redis.password = ‘helloworld‘
redis.database = 12
redis.timeout = 3
淺析easyswoole源碼Di原理與實現