weoyk / note

0 stars 0 forks source link

php反射 | 而废不能半途 #40

Open weoyk opened 2 days ago

weoyk commented 2 days ago

PHP反射

反射就是让你拥有剖析类、函数的能力

反射并不会对你实现业务有任何影响

但是你如果想写出结构优雅的程序,想写出维护性和扩展性都很高的程序

学习反射是 必不可少 的。

目录

PHP内置了一组反射类来实现类、方法以及参数的解析

常用的有:

这些类大多数都继承于 ReflectionFunctionAbstract 类,该类常用的方法有:

详情参考 : http://php.net/manual/zh/class.reflectionfunctionabstract.php

ReflectionClass

传入实例化的类或者类名都可以。

常用方法:

ReflectionParameter

常用方法:

详情参考 : http://php.net/manual/zh/class.reflectionparameter.php

ReflectionMethod

传入类名和方法名

常用方法:

详情参考 :http://php.net/manual/zh/class.reflectionmethod.php

实例化类

传入类名和初始化参数,使用相关的反射类实现类的实例化,包括参数依赖注入。

示例:

class People 
{
    protected $name;

    protected $phone;

    public function __construct(Phone $phone, $name = '小明')
    {
        $this->phone = $phone;
        $this->name = $name;
    }

    public function has()
    {
        echo $this->name . '拥有' . $phone->name;
    }
}

class Phone 
{
    protected $name;

    public function __construct($phoneName = 'iphoneX')
    {
        $this->name = $phoneName;
    }
}

$phone = new Phone('小米手机');
$people = new People($phone);
$people->has();     

使用反射类相关知识封装方法 make() 用来实例化类,

$phone = new Phone('小米手机');
$people = new People($phone);
$people->has();     

则上面实例化类的代码只需要一行代码就可以。

make('People', ['phoneName' => '小米手机','name' => '爱学习的小明']);

make() 方法代码如下:


function make($class, $vars = []) {
    if (!class_exists($class)) {
        echo '类不存在';
        exit;
    }

    $ref = new ReflectionClass($class);    

    if (!$ref->isInstantiable()) {         
        echo $class . '不可以实例化';
        exit; 
    }

    $construct = $ref->getConstructor();
    if (is_null($construct)) {

        return new $class;
    }

    $parmeters = $construct->getParameters();

    $resolveParams = is_null($parmeters) ? [] : injectionParameter($parmeters, $vars); 

    return $ref->newInstanceArgs($resolveParams);
}

依赖注入

injectionParameters() :解析参数,拼凑所需的参数


function injectionParameter(array $parmeters, $vars = [])
{
    $resolveParams = [];
    foreach ($parmeters as $k => $v) {

        $name = $v->getName();

        if (isset($vars[$name])) {
            $resolveParams[] = $vars[$name];
            continue;
        } 

        $default = $v->isDefaultValueAvailable() ? $v->getDefaultValue() : null;

        if (!is_null($default)) {
            $resolveParams[] = $default;
            continue;   
        }

        if ($v->getClass()) {
            $resolveParams[] = make($v->getClass()->getName(), $vars);
            continue;
        }

        echo $name . "不能为空";
        exit;
    }

    return $resolveParams;
}

注入依赖最核心方法就是通过getClass 获取参数类型提示

ReflectionParamters->getClass() :获取参数类型提示

在该参数没有默认值且有设置类型提示时

递归去调用 make() 方法将类实例化出来后注入到参数里

调用类方法

action():传入实例化的类,对应的方法名之后执行该方法

function action($class, $method, $vars = [])
{
    if (!method_exists($class, $method)) {
        echo '方法不存在';
        exit;
    }
    $ref = new ReflectionMethod($class, $method);
    $parameters = $ref->getParameters();
    $resolveParams = is_null($parameters) ? [] : injectionParameter($parameters, $vars);

    return $ref->invoke($class, ...$resolveParams);
}

代码示例

<?php
class Bag 
{
    public $name;

    public function __construct($bagName = '书包')
    {
        $this->name = $bagName;
    }
}

class Pencil 
{
    public $name;

    public function __construct($pencilName = '铅笔')
    {
        $this->name = $pencilName;
    }
}

class People
{
    public $bag;

    public $name;

    public function __construct(Bag $bag, $peopleName = 'test')
    {
        $this->bag = $bag;
        $this->name = $peopleName;
    }

    public function bag()
    {
        echo $this->name . ' has a ' . $this->bag->bag;
    }

    public function has(Pencil $pencil, $say = '')
    {
        echo "hello, I'm is {$this->name}." . "<br />";
        echo "I has {$pencil->name}." . "<br />";
        echo "I has {$this->bag->name}." . "<br />";
        if ($say) {
            echo $this->name . "say {$say}";
        }
    }
}

function make($class, $vars = []) {
    if (!class_exists($class)) {
        echo '类不存在';
        exit;
    }

    $ref = new ReflectionClass($class);    

    if (!$ref->isInstantiable()) {         
        echo $class . '不可以实例化';
        exit; 
    }

    $construct = $ref->getConstructor();
    if (is_null($construct)) {
        return new $class;
    }
    $parmeters = $construct->getParameters();
    $resolveParams = is_null($parmeters) ? [] : injectionParameter($parmeters, $vars); 

    return $ref->newInstanceArgs($resolveParams);
}

function action($class, $method, $vars = [])
{
    if (!method_exists($class, $method)) {
        echo '方法不存在';
        exit;
    }
    $ref = new ReflectionMethod($class, $method);
    $parameters = $ref->getParameters();
    $resolveParams = is_null($parameters) ? [] : injectionParameter($parameters, $vars);

    return $ref->invoke($class, ...$resolveParams);
}

function injectionParameter(array $parmeters, $vars = [])
{
    $resolveParams = [];
    foreach ($parmeters as $k => $v) {

        $name = $v->getName();

        if (isset($vars[$name])) {
            $resolveParams[] = $vars[$name];
            continue;
        } 

        $default = $v->isDefaultValueAvailable() ? $v->getDefaultValue() : null;

        if (!is_null($default)) {
            $resolveParams[] = $default;
            continue;   
        }

        if ($v->getClass()) {
            $resolveParams[] = make($v->getClass()->getName(), $vars);
            continue;
        }

        echo $name . "不能为空";
        exit;
    }

    return $resolveParams;
}          

$people = make('People', [
    'peopleName' => '小明',
    'pencilName' => '2B铅笔',
    'bagName' => '自定义书包',
]);
action($people, 'has');

总结 :

make() : 实例化一个类,假如构造函数有依赖的时候实例化并将实例注入到类里

injectionParameter() :解析参数,当参数没有传值,也没有默认值,但是有类型提示的情况下。实例化该类实例并注入到参数中返回。

action():执行类里面的一个方法,假如方法参数里依赖于其他类的话将类实例化并注入到方法的参数中。

该示例模拟了框架常用的一个依赖注入的功能。

通过路由解析到实际执行的类和方法,然后执行该方法。

上面的示例就大概模仿了去执行路由对应的动作这个流程。