• 售前

  • 售后

热门帖子
入门百科

PHP实现一个轻量级容器的方法

[复制链接]
铁血_斩蛇 显示全部楼层 发表于 2021-10-26 13:45:59 |阅读模式 打印 上一主题 下一主题
什么是容器

在开发过程中,常常会用到的一个概率就是依赖注入。我们借助依懒注入来解耦代码,选择性的按需加载服务,而这些通常都是借助容器来实现。
容器实现对类的统一管理,并且确保对象实例的唯一性

常用的容器网上有许多,如PHP-DI 、 YII-DI 等各种实现,通常他们要么大而全,要么高度适配特定业务,与现实需要存在冲突。
出于需要,我们本身造一个轻量级的轮子,为了保持规范,我们基于PSR-11 来实现。
PSR-11

PSR 是 php-fig 提供的尺度建议,固然不是官方构造,但是得到广泛认可。PSR-11 提供了容器接口。他包含 ContainerInterface 和 两个非常接口,提供使用建议。
  1. /**
  2. * Describes the interface of a container that exposes methods to read its entries.
  3. */
  4. interface ContainerInterface
  5. {
  6.   /**
  7.    * Finds an entry of the container by its identifier and returns it.
  8.    *
  9.    * @param string $id Identifier of the entry to look for.
  10.    *
  11.    * @throws NotFoundExceptionInterface No entry was found for **this** identifier.
  12.    * @throws ContainerExceptionInterface Error while retrieving the entry.
  13.    *
  14.    * @return mixed Entry.
  15.    */
  16.   public function get($id);
  17.   /**
  18.    * Returns true if the container can return an entry for the given identifier.
  19.    * Returns false otherwise.
  20.    *
  21.    * `has($id)` returning true does not mean that `get($id)` will not throw an exception.
  22.    * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
  23.    *
  24.    * @param string $id Identifier of the entry to look for.
  25.    *
  26.    * @return bool
  27.    */
  28.   public function has($id);
  29. }
复制代码
实现示例

我们先来实现接口中要求的两个方法
  1. abstract class AbstractContainer implements ContainerInterface
  2. {
  3.   protected $resolvedEntries = [];
  4.   /**
  5.    * @var array
  6.    */
  7.   protected $definitions = [];
  8.   public function __construct($definitions = [])
  9.   {
  10.     foreach ($definitions as $id => $definition) {
  11.       $this->injection($id, $definition);
  12.     }
  13.   }
  14.   public function get($id)
  15.   {
  16.     if (!$this->has($id)) {
  17.       throw new NotFoundException("No entry or class found for {$id}");
  18.     }
  19.     $instance = $this->make($id);
  20.     return $instance;
  21.   }
  22.   public function has($id)
  23.   {
  24.     return isset($this->definitions[$id]);
  25.   }
复制代码
现实我们容器中注入的对象是多种多样的,以是我们单独抽出实例化方法。
  1. public function make($name)
  2.   {
  3.     if (!is_string($name)) {
  4.       throw new \InvalidArgumentException(sprintf(
  5.         'The name parameter must be of type string, %s given',
  6.         is_object($name) ? get_class($name) : gettype($name)
  7.       ));
  8.     }
  9.     if (isset($this->resolvedEntries[$name])) {
  10.       return $this->resolvedEntries[$name];
  11.     }
  12.     if (!$this->has($name)) {
  13.       throw new NotFoundException("No entry or class found for {$name}");
  14.     }
  15.     $definition = $this->definitions[$name];
  16.     $params = [];
  17.     if (is_array($definition) && isset($definition['class'])) {
  18.       $params = $definition;
  19.       $definition = $definition['class'];
  20.       unset($params['class']);
  21.     }
  22.     $object = $this->reflector($definition, $params);
  23.     return $this->resolvedEntries[$name] = $object;
  24.   }
  25.   public function reflector($concrete, array $params = [])
  26.   {
  27.     if ($concrete instanceof \Closure) {
  28.       return $concrete($params);
  29.     } elseif (is_string($concrete)) {
  30.       $reflection = new \ReflectionClass($concrete);
  31.       $dependencies = $this->getDependencies($reflection);
  32.       foreach ($params as $index => $value) {
  33.         $dependencies[$index] = $value;
  34.       }
  35.       return $reflection->newInstanceArgs($dependencies);
  36.     } elseif (is_object($concrete)) {
  37.       return $concrete;
  38.     }
  39.   }
  40.   /**
  41.    * @param \ReflectionClass $reflection
  42.    * @return array
  43.    */
  44.   private function getDependencies($reflection)
  45.   {
  46.     $dependencies = [];
  47.     $constructor = $reflection->getConstructor();
  48.     if ($constructor !== null) {
  49.       $parameters = $constructor->getParameters();
  50.       $dependencies = $this->getParametersByDependencies($parameters);
  51.     }
  52.     return $dependencies;
  53.   }
  54.   /**
  55.    *
  56.    * 获取构造类相关参数的依赖
  57.    * @param array $dependencies
  58.    * @return array $parameters
  59.    * */
  60.   private function getParametersByDependencies(array $dependencies)
  61.   {
  62.     $parameters = [];
  63.     foreach ($dependencies as $param) {
  64.       if ($param->getClass()) {
  65.         $paramName = $param->getClass()->name;
  66.         $paramObject = $this->reflector($paramName);
  67.         $parameters[] = $paramObject;
  68.       } elseif ($param->isArray()) {
  69.         if ($param->isDefaultValueAvailable()) {
  70.           $parameters[] = $param->getDefaultValue();
  71.         } else {
  72.           $parameters[] = [];
  73.         }
  74.       } elseif ($param->isCallable()) {
  75.         if ($param->isDefaultValueAvailable()) {
  76.           $parameters[] = $param->getDefaultValue();
  77.         } else {
  78.           $parameters[] = function ($arg) {
  79.           };
  80.         }
  81.       } else {
  82.         if ($param->isDefaultValueAvailable()) {
  83.           $parameters[] = $param->getDefaultValue();
  84.         } else {
  85.           if ($param->allowsNull()) {
  86.             $parameters[] = null;
  87.           } else {
  88.             $parameters[] = false;
  89.           }
  90.         }
  91.       }
  92.     }
  93.     return $parameters;
  94.   }
复制代码
如你所见,到现在为止我们只实现了从容器中取出实例,从哪里去提供实例定义呢,以是我们还需要提供一个方水法
  1. /**
  2.    * @param string $id
  3.    * @param string | array | callable $concrete
  4.    * @throws ContainerException
  5.    */
  6.   public function injection($id, $concrete)
  7.   {
  8.     if (is_array($concrete) && !isset($concrete['class'])) {
  9.       throw new ContainerException('数组必须包含类定义');
  10.     }
  11.     $this->definitions[$id] = $concrete;
  12.   }
复制代码
只有这样吗?对的,有了这些利用我们已经有一个完备的容器了,插箱即用。
不过为了使用方便,我们可以再提供一些便捷的方法,比如数组式访问。
  1. class Container extends AbstractContainer implements \ArrayAccess
  2. {
  3.   public function offsetExists($offset)
  4.   {
  5.     return $this->has($offset);
  6.   }
  7.   public function offsetGet($offset)
  8.   {
  9.     return $this->get($offset);
  10.   }
  11.   public function offsetSet($offset, $value)
  12.   {
  13.     return $this->injection($offset, $value);
  14.   }
  15.   public function offsetUnset($offset)
  16.   {
  17.     unset($this->resolvedEntries[$offset]);
  18.     unset($this->definitions[$offset]);
  19.   }
  20. }
复制代码
这样我们就拥有了一个功能丰富,使用方便的轻量级容器了,赶快整合到你的项目中去吧。
点击这里查看完备代码

以上就是本文的全部内容,渴望对大家的学习有所资助,也渴望大家多多支持草根技能分享。

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作