美文网首页PHP
(五)从零编写PHP容器-实现契约,添加方法执行

(五)从零编写PHP容器-实现契约,添加方法执行

作者: FinalZero | 来源:发表于2020-02-19 17:49 被阅读0次

项目源码

功能实现

  1. 代码优化,注册与容器分离
  2. 实现契约绑定(method 契约> class 契约> namespace 契约> 绑定 > 自动创建)
    • 契约实现可以将接口类与实现类关联
    • 添加 full 契约
    • 添加 namesapce 契约
    • 添加 class 契约
    • 添加 method 契约
  3. 添加类方法执行

代码实现

  • 注册类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

相关文章

网友评论

    本文标题:(五)从零编写PHP容器-实现契约,添加方法执行

    本文链接:https://www.haomeiwen.com/subject/zlgafhtx.html