关于php:如何使用-php-写一个类似于-laravel-框架的服务容器

36次阅读

共计 4541 个字符,预计需要花费 12 分钟才能阅读完成。

如何应用 php 写一个相似于 laravel 框架的服务容器?

这篇文章可能文字不会太多,毕竟说再多都还不如间接看代码来的切实 😀,以下我会把外围的代码都先贴出来,外面都有比较完善的正文信息,能够对着看。另外如果本人测试的话,能够间接下载我的源码,对于如何测试,源码中都有示例代码。

  • Gitee 地址
  • GitHub 地址

以下是实现容器的外围代码


<?php
/**
 * 实现一个简略的 php 容器
 *
 * Created by PhpStorm
 * User: Alex
 * Date: 2021-08-03 17:51
 * E-mail: <276558492@qq.com>
 */

class Container
{

    /**
     * 以后全局可用的容器 (如果有)
     *
     * @var static
     */
    private static $instance;

    /**
     * 容器的绑定
     *
     * @var array[]
     */
    private $bindings = [];

    /**
     * 容器的共享实例
     *
     * @var object[]
     */
    private $instances = [];

    public function __construct()
    {$this->instances[Container::class] = $this;
    }

    public static function getInstance()
    {if (is_null(self::$instance)) {self::$instance = new self;}

        self::$instance->instances[Container::class] = self::$instance;

        return self::$instance;
    }

    /**
     * 在容器中注册共享绑定
     *
     * @param $abstract
     * @param $concrete
     */
    public function singleton($abstract, $concrete)
    {$this->bind($abstract, $concrete, true);
    }

    /**
     * 向容器注册绑定
     *
     * @param $abstract
     * @param $concrete
     * @param false $shared
     */
    public function bind($abstract, $concrete, $shared = false)
    {if ($concrete instanceof Closure) {$this->bindings[$abstract] = compact('concrete', 'shared');
        } else {if (! is_string($concrete) || ! class_exists($concrete)) {throw new InvalidArgumentException('Argument 2 must be callback or class.');
            }
        }

        $this->bindings[$abstract] = compact('concrete', 'shared');

    }

    /**
     * 将现有实例注册为容器中的共享实例
     *
     * @param string $abstract
     * @param mixed $instance
     * @return mixed
     */
    public function instance($abstract, $instance)
    {$this->instances[$abstract] = $instance;

        return $instance;
    }

    /**
     * 从容器解析给定类型
     *
     * @param string $abstract  指标类的名称
     * @param array $parameters  实例化指标类时所须要的参数(非对象类型束缚参数数组)* @return mixed|object
     */
    public function make(string $abstract, array $parameters = [])
    {if (! isset($this->instances[$abstract]) && ! isset($this->bindings[$abstract])) {if (! class_exists($abstract)) throw new InvalidArgumentException("Target class [$abstract] does not exist.");
        }

        if (isset($this->instances[$abstract])) {return $this->instances[$abstract];
        }

        try {if (isset($this->bindings[$abstract])) {$concrete = $this->bindings[$abstract]['concrete'];
                if (is_callable($concrete)) {$instance = $this->resolveCallable($concrete, $parameters);
                } else {$instance = $this->resolveClass($concrete, $parameters);
                }
            } else {$instance = $this->resolveClass($abstract, $parameters);
            }

            if (isset($this->bindings[$abstract]) && $this->bindings[$abstract]['shared']) {$this->instances[$abstract] = $instance;
            }

            return $instance;
        } catch (\Exception $exception) {echo($exception->getMessage() . PHP_EOL);
            print_r($exception->getTraceAsString());
        }

    }

    /**
     * 解决回调函数时的依赖
     *
     * @param callable $callbackName  指标回调函数
     * @param array $realArgs
     * @return mixed
     * @throws ReflectionException
     */
    private function resolveCallable(callable $callbackName, array $realArgs = [])
    {$reflector = new ReflectionFunction($callbackName);

        // 获取回调函数的参数列表
        $parameters = $reflector->getParameters();
        $list = [];
        if (count($parameters) > 0) {$list = $this->resolveDependencies($parameters, $realArgs);
        }

        // 调用函数参数
        return $reflector->invokeArgs($list);
    }

    /**
     * 解决对象时的依赖
     *
     * @param string|object $className  指标类的名称
     * @param array $realArgs
     * @return object  指标类对应的实例对象
     * @throws ReflectionException
     */
    private function resolveClass($className, array $realArgs = [])
    {
        try {
            // 对指标类进行反射(解析其办法、属性)$reflector = new ReflectionClass($className);
        } catch (ReflectionException $e) {throw new RuntimeException("Target class [$className] does not exist.", 0, $e);
        }

        if (! $reflector->isInstantiable()) {  // 查看类是否能够实例化
            throw new RuntimeException("Target class [$className] is not instantiable.");
        }

        // 获取指标类的构造函数,当类不存在构造函数时返回 null
        $constructor = $reflector->getConstructor();
        // 没有构造函数,则间接实例化
        if (is_null($constructor)) {
            // return new $className;  // 或者也能够间接这样去实例化,因为指标类没有构造函数,不须要传参数
            return $reflector->newInstance();}

        // 获取构造函数的参数列表
        $parameters = $constructor->getParameters();
        // 递归解析构造函数的参数
        $list = $this->resolveDependencies($parameters, $realArgs);

        // 从给出的参数创立一个新的类实例
        return $reflector->newInstanceArgs($list);
    }

    /**
     * 递归解析依赖树
     *
     * @param array $dependencies  指标类的结构函数参数列表
     * @param array $parameters  实例化指标类时的其余参数(非类型提醒参数)* @return array  实例化指标类时构造函数所需的所有参数
     */
    private function resolveDependencies(array $dependencies, array $parameters = [])
    {
        // 用于存储所有的参数
        $results = [];

        foreach ($dependencies as $dependency) {

            // 获取类型提醒类
            $obj = $dependency->getClass();

            // 如果类为 null,则示意依赖项是字符串或其余类型
            if (is_null($obj)) {$parameterName = $dependency->getName();  // 获取参数的名称

                // 查看参数是否有默认值
                if (! $dependency->isDefaultValueAvailable()) {if (! isset($parameters[$parameterName])) {throw new RuntimeException($parameterName . 'has no value');
                    } else {$results[] = $parameters[$parameterName];
                    }
                } else {  // 参数有默认值的时候
                    if (isset($parameters[$parameterName])) {$results[] = $parameters[$parameterName];
                    } else {$results[] = $dependency->getDefaultValue();  // 获取参数的默认值}
                }

            } else {  // 类型提醒确定是一个类时,则须要递归解决依赖项
                $objName = $obj->getName();  // 获取依赖项的类名
                if (! class_exists($objName)) {throw new RuntimeException('Unable to load class:' . $objName);
                } else {$results[] = $this->make($objName);
                }
            }

        }

        return $results;
    }

}


正文完
 0