功能实现
- 代码优化,注册与容器分离
- 实现契约绑定(method 契约> class 契约> namespace 契约> 绑定 > 自动创建)
- 契约实现可以将接口类与实现类关联
- 添加 full 契约
- 添加 namesapce 契约
- 添加 class 契约
- 添加 method 契约
- 添加类方法执行
代码实现
- 注册类
Register
class Register extends AbsSingleton
{
/**
* string(class) => instance
* @var array
*/
protected $bind = [];
/**
* @var RegisterContact
*/
protected $contact;
/**
* delay to bind
* @var array
*/
protected $delay = [];
// init
protected function init()
{
$this->contact = new RegisterContact();
}
/**
* @param string $abstract
* @param mixed $instance
* @return $this
*/
public function bind(string $abstract, $instance)
{
$this->bind[$abstract] = $instance;
return $this;
}
/**
* @return RegisterContact
*/
public function contact()
{
return $this->contact;
}
/**
* @param string $abstract
* @param Closure $bindDelay
* @return Register
*/
public function delay(string $abstract, Closure $bindDelay)
{
$this->delay[$abstract] = $bindDelay;
return $this;
}
/**
* @return array
*/
public function getBind(): array
{
return $this->bind;
}
/**
* @return RegisterContact
*/
public function getContact(): RegisterContact
{
return $this->contact;
}
/**
* @return array
*/
public function getDelay(): array
{
return $this->delay;
}
}
- 契约注册类
RegisterContact
class RegisterContact
{
/**
* @var array
*/
protected $method, $class, $namespace, $full = [];
/**
* @param string $class
* @param string $method
* @param string $abstract
* @param mixed $instance
* @return $this
*/
public function method(string $class, string $method, string $abstract, $instance)
{
$this->method[$class."@".$method][$abstract] = $instance;
return $this;
}
/**
* @param string $class
* @param string $abstract
* @param mixed $instance
* @return $this
*/
public function class(string $class, string $abstract, $instance)
{
$this->class[$class][$abstract] = $instance;
return $this;
}
/**
* @param string $namespace
* @param string $abstract
* @param mixed $instance
* @return $this
*/
public function namespace(string $namespace, string $abstract, $instance)
{
$this->namespace[$namespace][$abstract] = $instance;
return $this;
}
/**
* @param string $abstract
* @param mixed $instance
* @return $this
*/
public function full(string $abstract, $instance)
{
$this->full[$abstract] = $instance;
return $this;
}
/*---------------------------------------------- has ----------------------------------------------*/
/**
* @param string $class
* @param string $method
* @param string $abstract
* @return bool
*/
public function hasMethodContact(string $class, string $method, string $abstract): bool
{
return isset($this->method[$class."@".$method][$abstract]);
}
/**
* @param string $class
* @param string $abstract
* @return bool
*/
public function hasClassContact(string $class, string $abstract): bool
{
return isset($this->class[$class][$abstract]);
}
/**
* @param string $namespace
* @param string $abstract
* @return bool
*/
public function hasNamespaceContact(string $namespace, string $abstract): bool
{
return isset($this->namespace[$namespace][$abstract]);
}
/**
* @param string $abstract
* @return bool
*/
public function hasFullContact(string $abstract): bool
{
return isset($this->full[$abstract]);
}
/**
* @param string $namespace
* @param string $class
* @param string $method
* @param string $abstract
* @return bool
*/
public function hasContact(string $namespace, string $class, string $method, string $abstract): bool
{
return $this->hasMethodContact($class, $method, $abstract) ||
$this->hasClassContact($class, $abstract) ||
$this->hasNamespaceContact($namespace, $abstract) ||
$this->hasFullContact($abstract);
}
/*---------------------------------------------- getIn ----------------------------------------------*/
/**
* @param string $class
* @param string $method
* @param string $abstract
* @return mixed|null
*/
public function getInMethod(string $class, string $method, string $abstract)
{
return $this->method[$class."@".$method][$abstract] ?? null;
}
/**
* @param string $class
* @param string $abstract
* @return mixed|null
*/
public function getInClass(string $class, string $abstract)
{
return $this->class[$class][$abstract] ?? null;
}
/**
* @param string $namespace
* @param string $abstract
* @return mixed|null
*/
public function getInNamespace(string $namespace, string $abstract)
{
return $this->namespace[$namespace][$abstract] ?? null;
}
/**
* @param string $abstract
* @return mixed|null
*/
public function getInFull(string $abstract)
{
return $this->full[$abstract] ?? null;
}
/**
* @param string $namespace
* @param string $class
* @param string $method
* @param string $abstract
* @return mixed|null
*/
public function getContact(string $namespace, string $class, string $method,string $abstract)
{
return $this->getInMethod($class, $method, $abstract) ??
$this->getInClass($class, $abstract) ??
$this->getInNamespace($namespace, $abstract) ??
$this->getInFull($abstract);
}
/*---------------------------------------------- get ----------------------------------------------*/
/**
* @return array
*/
public function getMethod(): array
{
return $this->method;
}
/**
* @return array
*/
public function getClass(): array
{
return $this->class;
}
/**
* @return array
*/
public function getNamespace(): array
{
return $this->namespace;
}
/**
* @return array
*/
public function getFull(): array
{
return $this->full;
}
}
- 容器
Container
use Closure;
use Exception;
use Psr\Container\ContainerExceptionInterface;
use ReflectionClass;
use ReflectionException;
use ReflectionParameter;
class Container extends AbsSingleton
{
/**
* @var Register
*/
protected $register;
/**
* singleton init
*/
protected function init()
{
$this->register = Register::getInstance();
}
/**
* @return Register
*/
public static function register()
{
return static::getInstance()->register;
}
/**
* @param object/string $abstract
* @param string $method
* @param array $params
* @return mixed
* @throws ReflectionException
*/
public function call($abstract, $method, $params = [])
{
$refClass = new ReflectionClass($abstract);
// check
$refClass->hasMethod($method) || (function() use ($refClass, $method){
throw new class("Uncaught Error: Call to undefined method " . $refClass->getName() . "::$method .") extends Exception implements ContainerExceptionInterface{};
})();
$reflectionMethod = $refClass->getMethod($method);
$reflectionMethod->isPublic() || (function () use ($method) {
throw new class("$method must be public.") extends Exception implements ContainerExceptionInterface {};
})();
$params = array_map(function (ReflectionParameter $param) use ($refClass, $method) {
if (($result = $this->paramsHandle($param)) instanceof ReflectionClass) {
$className = $param->getClass()->getName();
$contact = $this->register->contact();
if ($contact->hasMethodContact($refClass->getName(), $method, $className)) {
return $contact->getInMethod($refClass->getName(), $method, $className);
} elseif ($contact->hasClassContact($refClass->getName(), $className)) {
return $contact->getInClass($refClass->getName(), $className);
} elseif ($contact->hasNamespaceContact($refClass->getNamespaceName(), $className)) {
return $contact->getInNamespace($refClass->getNamespaceName(), $className);
} elseif ($contact->hasFullContact($className)) {
return $contact->getInFull($className);
} else {
return $this->create($param->getClass()->getName());
}
} else {
return $result;
}
}, $reflectionMethod->getParameters());
return $abstract->{$method}(... $params);
}
/**
* create a new object
* @param string $abstract
* @return Closure|object
* @throws ReflectionException
* @throws Exception
*/
public function create($abstract)
{
return $this->createInstance($abstract);
}
/**
* create new instance
* @param string $abstract
* @param array $tmp
* @return Closure|object
* @throws ReflectionException
* @throws Exception
*/
protected function createInstance(string $abstract, array $tmp = [])
{
// todo: flag 1: check
if (isset($this->register->getBind()[$abstract])) {
return $this->register->getBind()[$abstract];
} else if (isset($tmp[$abstract])) {
return $tmp[$abstract];
} else if (array_key_exists($abstract, $tmp)) {
throw new class('can not create a circular dependencies class object.') extends Exception implements ContainerExceptionInterface{ };
} else {
$tmp[$abstract] = null;
}
// todo: flag 2: get method && parse params
$refClass = new ReflectionClass($abstract);
if ($refClass->isInterface() || $refClass->isAbstract()) {
throw new class('can not create a class in interface or abstract.') extends Exception implements ContainerExceptionInterface{ };
} elseif ($refClass->getName() === Closure::class) {
return function () {};
}
if ($refClass->hasMethod('__construct')) {
$reflectionMethod = $refClass->getMethod('__construct');
$reflectionMethod->isPublic() || (function () {
throw new class("can not automatically create the class object, __construct must be public.") extends Exception implements ContainerExceptionInterface {};
})();
$_constructParams = array_map(function (ReflectionParameter $param) use ($refClass, $abstract, $tmp) {
if (($result = $this->paramsHandle($param)) instanceof ReflectionClass) {
$className = $param->getClass()->getName();
$contact = $this->register->contact();
if ($contact->hasClassContact($refClass->getName(), $className)) {
return $contact->getInClass($refClass->getName(), $className);
} elseif ($contact->hasNamespaceContact($refClass->getNamespaceName(), $className)) {
return $contact->getInNamespace($refClass->getNamespaceName(), $className);
} elseif ($contact->hasFullContact($className)) {
return $contact->getInFull($className);
} else {
return $this->createInstance($param->getClass()->getName(), $tmp);
}
} else {
return $result;
}
}, $reflectionMethod->getParameters());
}
// todo: flag 3: make instance && bind to tmp
return $tmp[$abstract] = $refClass->newInstance(... ($_constructParams ?? []));
}
/**
* @param ReflectionParameter $param
* @return mixed|ReflectionClass|null
* @throws ReflectionException
*/
private function paramsHandle(ReflectionParameter $param)
{
if ($param->getClass()) {
return $param->getClass();
} elseif ($param->isDefaultValueAvailable()) {
return $param->getDefaultValue();
} elseif ($param->getType()) {
return [
'string' => '',
'int' => 0,
'array' => [],
'bool' => false,
'float' => 0.0,
'iterable' => [],
'callable' => function() {}
][$param->getType()->getName()] ?? null;
} else {
return null;
}
}
}
1. 将基础数据类型判断先做分离(方便后期会做基础数据的动态映射)
2. 分离绑定注册(方便拓展以及管理实例绑定)
3. 修改类自动创建参数解析顺序
3.1 在createInstance
中先判断是否存在绑定
// todo: flag 1: check
if (isset($this->register->getBind()[$abstract])) {
return $this->register->getBind()[$abstract];
} else if (isset($tmp[$abstract])) {
return $tmp[$abstract];
} else if (array_key_exists($abstract, $tmp)) {
throw new class('can not create a circular dependencies class object.') extends Exception implements ContainerExceptionInterface{ };
} else {
$tmp[$abstract] = null;
}
3.2 解析构造参数,非基础数据类型或者mixed
类型的根据(类/接口)名从契约注册器中依次检查,否则执行递归创建该实例
if (($result = $this->paramsHandle($param)) instanceof ReflectionClass) {
$className = $param->getClass()->getName();
$contact = $this->register->contact();
if ($contact->hasClassContact($refClass->getName(), $className)) {
return $contact->getInClass($refClass->getName(), $className);
} elseif ($contact->hasNamespaceContact($refClass->getNamespaceName(), $className)) {
return $contact->getInNamespace($refClass->getNamespaceName(), $className);
} elseif ($contact->hasFullContact($className)) {
return $contact->getInFull($className);
} else {
return $this->createInstance($param->getClass()->getName(), $tmp);
}
} else {
return $result;
}
4. 添加call
执行方法
4.1 检查获取执行的方法参数实例列表
$params = array_map(function (ReflectionParameter $param) use ($refClass, $method) {
if (($result = $this->paramsHandle($param)) instanceof ReflectionClass) {
$className = $param->getClass()->getName();
$contact = $this->register->contact();
if ($contact->hasMethodContact($refClass->getName(), $method, $className)) {
return $contact->getInMethod($refClass->getName(), $method, $className);
} elseif ($contact->hasClassContact($refClass->getName(), $className)) {
return $contact->getInClass($refClass->getName(), $className);
} elseif ($contact->hasNamespaceContact($refClass->getNamespaceName(), $className)) {
return $contact->getInNamespace($refClass->getNamespaceName(), $className);
} elseif ($contact->hasFullContact($className)) {
return $contact->getInFull($className);
} else {
return $this->create($param->getClass()->getName());
}
} else {
return $result;
}
}, $reflectionMethod->getParameters());
4.2 执行类方法
return $abstract->{$method}(... $params);
示例
interface InterfaceContact { public function show(); }
namespace TTT; // 假设命名空间仅作用于TA
class TA
{
public function test(InterfaceContact $contact)
{
return $contact->show();
}
}
class TB
{
public function test(InterfaceContact $contact)
{
return $contact->show();
}
public function test(InterfaceContact $contact)
{
return '2:' . (string)$contact->show();
}
}
class TNew
{
protected $contact;
public function __construct(InterfaceContact $contact)
{
$this->contact = $contact;
}
public function show()
{
return $this->contact->show();
}
}
class Full implements InterfaceContact
{
public function show()
{
return 'class Full';
}
}
class A implements InterfaceContact
{
public function show()
{
return 'class A';
}
}
class B implements InterfaceContact
{
public function show()
{
return 'class B';
}
}
class C implements InterfaceContact
{
public function show()
{
return 'class C';
}
}
class D implements InterfaceContact
{
public function show()
{
return 'class D';
}
}
$container = Container::getInstance();
$contact = Container::register()->contact(); // 契约
$bind = Container::register()->bind(); // 绑定
$ta = $container->create(TA::class);
$tb = $container->create(TB::class);
// 直接执行Ta的`test`方法将抛出异常,因为没有契约绑定到InterfaceContact接口类
$container->call($ta, 'test');
// 同样create将会报错
$container->create(TNew::class);
$contact->full(InterfaceContact::class, new Full());
$container->call($ta, 'test'); // 返回 class Full
$container->call($tb, 'test'); // 返回 class Full
// 绑定到namespace契约
$contact->namespace('TTT', InterfaceContact::class, new A())
$container->call($ta, 'test'); // 返回 class A, 因为TA正属于TTT命名空间下
$container->call($tb, 'test'); // 返回 class Full
$contact->class(TB::class, InterfaceContact::class, new B());
$container->call($ta, 'test'); // 返回 class A, 因为TA正属于TTT命名空间下
$container->call($tb, 'test'); // 执行test1, 返回 class B
$container->call($tb, 'test2'); // 执行test2, 返回 class B
$contact->method(TB::class, 'test2', InterfaceContact::class, new C());
$container->call($ta, 'test'); // 返回 class A, 因为TA正属于TTT命名空间下
$container->call($tb, 'test'); // 执行test1, 返回 class B
$container->call($tb, 'test2'); // 执行test2, 返回 class C
// 同理,自动创建也遵循这些规则
$tn = $container->create(TNew::class);
$tn->show(); // 返回class Full
网友评论