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图片上传程序
Mar 27 PHP
PHP+jquery实时显示网站在线人数的方法
Jan 04 PHP
详解php的socket通信
Aug 11 PHP
PHP中子类重载父类的方法【parent::方法名】
May 06 PHP
php通过文件头判断格式的方法
May 28 PHP
Laravel中的Blade模板引擎示例详解
Oct 10 PHP
yii2中关于加密解密的那些事儿
Jun 12 PHP
PHP操作Redis数据库常用方法示例
Aug 25 PHP
PHP5.5基于mysqli连接MySQL数据库和读取数据操作实例详解
Feb 16 PHP
php中的钩子理解及应用实例分析
Aug 30 PHP
ThinkPHP5.0框架实现切换数据库的方法分析
Oct 30 PHP
php实现对短信验证码发送次数的限制实例讲解
Mar 04 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快递单号查询接口使用示例
2014/05/05 PHP
Win2003+apache+PHP+SqlServer2008 配置生产环境
2014/07/29 PHP
PHP的伪随机数与真随机数详解
2015/05/27 PHP
PHP实现的数据对象映射模式详解
2019/03/20 PHP
加载jQuery后$冲突的解决办法
2010/07/09 Javascript
在VS2008中使用jQuery智能感应的方法
2010/12/30 Javascript
关闭浏览器输入框自动补齐 兼容IE,FF,Chrome等主流浏览器
2014/02/11 Javascript
基于Jquery easyui 选中特定的tab
2015/11/17 Javascript
JavaScript 模块的循环加载实现方法
2015/12/13 Javascript
动态生成的DOM不会触发onclick事件的原因及解决方法
2016/08/06 Javascript
使用BootStrapValidator完成前端输入验证
2016/09/28 Javascript
浅谈jQuery before和insertBefore的区别
2016/12/04 Javascript
JS 在数组指定位置插入/删除数据的方法
2017/01/12 Javascript
jQuery动态追加页面数据以及事件委托详解
2017/05/06 jQuery
AngularJS实现表格的增删改查(仅限前端)
2017/07/04 Javascript
Angular移动端页面input无法输入的解决方法
2017/11/14 Javascript
vue将时间戳转换成自定义时间格式的方法
2018/03/02 Javascript
vue+高德地图写地图选址组件的方法
2019/05/18 Javascript
简单了解微信小程序的目录结构
2019/07/01 Javascript
简单谈谈Python中的几种常见的数据类型
2017/02/10 Python
Python中turtle作图示例
2017/11/15 Python
python中将字典形式的数据循环插入Excel
2018/01/16 Python
python实现图书馆研习室自动预约功能
2018/04/27 Python
python数字图像处理实现直方图与均衡化
2018/05/04 Python
Python networkx包的实现
2020/02/14 Python
解决Python数据可视化中文部分显示方块问题
2020/05/16 Python
如何用Python编写一个电子考勤系统
2021/02/08 Python
印度排名第一的蛋糕、鲜花和礼品送货:Winni
2019/08/02 全球购物
如何写自我评价?自我评价写什么好?
2014/03/14 职场文书
2014年医院十一国庆节活动方案
2014/09/15 职场文书
大学生入党积极分子自我评价
2014/09/20 职场文书
教师培训学习心得体会
2016/01/21 职场文书
Python制作表白爱心合集
2022/01/22 Python
Django + Taro 前后端分离项目实现企业微信登录功能
2022/04/07 Python
python中 .npy文件的读写操作实例
2022/04/14 Python
Jmerte 分布式压测及分布式压测配置
2022/04/30 Java/Android