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


Posted in PHP onJanuary 28, 2019

什么是容器

在开发过程中,经常会用到的一个概率就是依赖注入。我们借助依懒注入来解耦代码,选择性的按需加载服务,而这些通常都是借助容器来实现。

容器实现对类的统一管理,并且确保对象实例的唯一性

常用的容器网上有很多,如PHP-DI 、 YII-DI 等各种实现,通常他们要么大而全,要么高度适配特定业务,与实际需要存在冲突。

出于需要,我们自己造一个轻量级的轮子,为了保持规范,我们基于PSR-11 来实现。

PSR-11

PSR 是 php-fig 提供的标准建议,虽然不是官方组织,但是得到广泛认可。PSR-11 提供了容器接口。他包含 ContainerInterface 和 两个异常接口,提供使用建议。

/**
 * Describes the interface of a container that exposes methods to read its entries.
 */
interface ContainerInterface
{
  /**
   * Finds an entry of the container by its identifier and returns it.
   *
   * @param string $id Identifier of the entry to look for.
   *
   * @throws NotFoundExceptionInterface No entry was found for **this** identifier.
   * @throws ContainerExceptionInterface Error while retrieving the entry.
   *
   * @return mixed Entry.
   */
  public function get($id);

  /**
   * Returns true if the container can return an entry for the given identifier.
   * Returns false otherwise.
   *
   * `has($id)` returning true does not mean that `get($id)` will not throw an exception.
   * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
   *
   * @param string $id Identifier of the entry to look for.
   *
   * @return bool
   */
  public function has($id);
}

实现示例

我们先来实现接口中要求的两个方法

abstract class AbstractContainer implements ContainerInterface
{

  protected $resolvedEntries = [];

  /**
   * @var array
   */
  protected $definitions = [];

  public function __construct($definitions = [])
  {
    foreach ($definitions as $id => $definition) {
      $this->injection($id, $definition);
    }
  }

  public function get($id)
  {

    if (!$this->has($id)) {
      throw new NotFoundException("No entry or class found for {$id}");
    }

    $instance = $this->make($id);

    return $instance;
  }

  public function has($id)
  {
    return isset($this->definitions[$id]);
  }

实际我们容器中注入的对象是多种多样的,所以我们单独抽出实例化方法。

public function make($name)
  {
    if (!is_string($name)) {
      throw new \InvalidArgumentException(sprintf(
        'The name parameter must be of type string, %s given',
        is_object($name) ? get_class($name) : gettype($name)
      ));
    }

    if (isset($this->resolvedEntries[$name])) {
      return $this->resolvedEntries[$name];
    }

    if (!$this->has($name)) {
      throw new NotFoundException("No entry or class found for {$name}");
    }

    $definition = $this->definitions[$name];
    $params = [];
    if (is_array($definition) && isset($definition['class'])) {
      $params = $definition;
      $definition = $definition['class'];
      unset($params['class']);
    }

    $object = $this->reflector($definition, $params);

    return $this->resolvedEntries[$name] = $object;
  }

  public function reflector($concrete, array $params = [])
  {
    if ($concrete instanceof \Closure) {
      return $concrete($params);
    } elseif (is_string($concrete)) {
      $reflection = new \ReflectionClass($concrete);
      $dependencies = $this->getDependencies($reflection);
      foreach ($params as $index => $value) {
        $dependencies[$index] = $value;
      }
      return $reflection->newInstanceArgs($dependencies);
    } elseif (is_object($concrete)) {
      return $concrete;
    }
  }

  /**
   * @param \ReflectionClass $reflection
   * @return array
   */
  private function getDependencies($reflection)
  {
    $dependencies = [];
    $constructor = $reflection->getConstructor();
    if ($constructor !== null) {
      $parameters = $constructor->getParameters();
      $dependencies = $this->getParametersByDependencies($parameters);
    }

    return $dependencies;
  }

  /**
   *
   * 获取构造类相关参数的依赖
   * @param array $dependencies
   * @return array $parameters
   * */
  private function getParametersByDependencies(array $dependencies)
  {
    $parameters = [];
    foreach ($dependencies as $param) {
      if ($param->getClass()) {
        $paramName = $param->getClass()->name;
        $paramObject = $this->reflector($paramName);
        $parameters[] = $paramObject;
      } elseif ($param->isArray()) {
        if ($param->isDefaultValueAvailable()) {
          $parameters[] = $param->getDefaultValue();
        } else {
          $parameters[] = [];
        }
      } elseif ($param->isCallable()) {
        if ($param->isDefaultValueAvailable()) {
          $parameters[] = $param->getDefaultValue();
        } else {
          $parameters[] = function ($arg) {
          };
        }
      } else {
        if ($param->isDefaultValueAvailable()) {
          $parameters[] = $param->getDefaultValue();
        } else {
          if ($param->allowsNull()) {
            $parameters[] = null;
          } else {
            $parameters[] = false;
          }
        }
      }
    }
    return $parameters;
  }

如你所见,到目前为止我们只实现了从容器中取出实例,从哪里去提供实例定义呢,所以我们还需要提供一个方水法

/**
   * @param string $id
   * @param string | array | callable $concrete
   * @throws ContainerException
   */
  public function injection($id, $concrete)
  {
    if (is_array($concrete) && !isset($concrete['class'])) {
      throw new ContainerException('数组必须包含类定义');
    }

    $this->definitions[$id] = $concrete;
  }

只有这样吗?对的,有了这些操作我们已经有一个完整的容器了,插箱即用。

不过为了使用方便,我们可以再提供一些便捷的方法,比如数组式访问。

class Container extends AbstractContainer implements \ArrayAccess
{

  public function offsetExists($offset)
  {
    return $this->has($offset);
  }

  public function offsetGet($offset)
  {
    return $this->get($offset);
  }

  public function offsetSet($offset, $value)
  {
    return $this->injection($offset, $value);
  }

  public function offsetUnset($offset)
  {
    unset($this->resolvedEntries[$offset]);
    unset($this->definitions[$offset]);
  }
}

这样我们就拥有了一个功能丰富,使用方便的轻量级容器了,赶快整合到你的项目中去吧。

点击这里查看完整代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

PHP 相关文章推荐
如何正确理解PHP的错误信息
Oct 09 PHP
基于文本的留言簿
Oct 09 PHP
PHP学习笔记之二
Jan 17 PHP
php二维数组排序与默认自然排序的方法介绍
Apr 27 PHP
PHP mysql与mysqli事务使用说明 分享
Aug 17 PHP
PHP中设置一个严格30分钟过期Session面试题的4种答案
Jul 30 PHP
PHP中filter函数校验数据的方法详解
Jul 31 PHP
jQuery向下滚动即时加载内容实现的瀑布流效果
Jan 07 PHP
php PDO异常处理详解
Nov 20 PHP
php从数据库中读取特定的行(实例)
Jun 02 PHP
详解PHP函数 strip_tags 处理字符串缺陷bug
Jun 11 PHP
PHP的new static和new self的区别与使用
Nov 27 PHP
PDO::_construct讲解
Jan 27 #PHP
PDO::commit讲解
Jan 27 #PHP
PDO::beginTransaction讲解
Jan 27 #PHP
PHP的PDO大对象(LOBs)
Jan 27 #PHP
实例讲解PHP中使用命名空间
Jan 27 #PHP
PHP的PDO错误与错误处理
Jan 27 #PHP
实例分析PHP将字符串转换成数字的方法
Jan 27 #PHP
You might like
在PHP中使用与Perl兼容的正则表达式
2006/11/26 PHP
php语言注释,单行注释和多行注释
2018/01/21 PHP
javascript css float属性的特殊写法
2008/11/13 Javascript
兼容FireFox 的 js 日历 支持时间的获取
2009/03/04 Javascript
深入理解JavaScript系列(3) 全面解析Module模式
2012/01/15 Javascript
通过一段代码简单说js中的this的使用
2013/07/23 Javascript
使用js显示当前时间示例
2014/03/02 Javascript
js对象内部访问this修饰的成员函数示例
2014/04/27 Javascript
如何用JS判断两个数字的大小
2016/07/21 Javascript
用Vue写一个分页器的示例代码
2018/04/22 Javascript
springMvc 前端用json的方式向后台传递对象数组方法
2018/08/07 Javascript
vue中引入mxGraph的步骤详解
2019/05/17 Javascript
JavaScript实现的弹出遮罩层特效经典示例【基于jQuery】
2019/07/10 jQuery
vue中使用百度脑图kityminder-core二次开发的实现
2019/09/26 Javascript
python Django模板的使用方法(图文)
2013/11/04 Python
Python3指定路径寻找符合匹配模式文件
2015/05/22 Python
python Django批量导入数据
2016/03/25 Python
shelve  用来持久化任意的Python对象实例代码
2016/10/12 Python
Python实现确认字符串是否包含指定字符串的实例
2018/05/02 Python
python版opencv摄像头人脸实时检测方法
2018/08/03 Python
解决python Jupyter不能导入外部包问题
2020/04/15 Python
Keras预训练的ImageNet模型实现分类操作
2020/07/07 Python
关于django python manage.py startapp 应用名出错异常原因解析
2020/12/15 Python
Python实现网络聊天室的示例代码(支持多人聊天与私聊)
2021/01/27 Python
css3一个简易的 LED 数字时钟实现方法
2020/01/15 HTML / CSS
拉丁舞学习者的自我评价
2013/10/27 职场文书
总账会计岗位职责
2014/03/13 职场文书
《赶海》教学反思
2014/04/20 职场文书
如何写早恋检讨书
2014/09/10 职场文书
2014年车间主任工作总结
2014/12/10 职场文书
2015年前台个人工作总结
2015/04/03 职场文书
任命通知范文
2015/04/21 职场文书
小学四年级班务总结该怎么写?
2019/08/16 职场文书
创业计划书之书店
2019/09/10 职场文书
7个关于Python的经典基础案例
2021/11/07 Python
Java8利用Stream对列表进行去除重复的方法详解
2022/04/14 Java/Android