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边学边教》(02.Apache+PHP环境配置――下篇)
Dec 13 PHP
PHP6 先修班 JSON实例代码
Aug 23 PHP
PHP stream_context_create()作用和用法分析
Mar 29 PHP
深入php常用函数的使用汇总
Jun 08 PHP
php配合jquery实现增删操作具体实例
Dec 12 PHP
codeigniter教程之上传视频并使用ffmpeg转flv示例
Feb 13 PHP
全面解读PHP的Yii框架中的日志功能
Mar 17 PHP
PHP的Yii框架中View视图的使用进阶
Mar 29 PHP
PHP动态地创建属性和方法, 对象的复制, 对象的比较,加载指定的文件,自动加载类文件,命名空间
May 06 PHP
Laravel框架实现的批量删除功能示例
Jan 16 PHP
PHP设计模式之组合模式定义与应用示例
Feb 01 PHP
php实现记事本案例
Oct 20 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/12/10 PHP
服务器迁移php版本不同可能诱发的问题
2015/12/22 PHP
关于ThinkPHP中的异常处理详解
2018/05/11 PHP
JavaScript实现计算字符串中出现次数最多的字符和出现的次数
2015/03/12 Javascript
Javascript实现div的toggle效果实例分析
2015/06/09 Javascript
理解js对象继承的N种模式
2016/01/25 Javascript
JavaScript实现简洁的俄罗斯方块完整实例
2016/03/01 Javascript
解决wx.onMenuShareTimeline出现的问题
2016/08/16 Javascript
jQuery Validate让普通按钮触发表单验证的方法
2016/12/15 Javascript
addEventListener()与removeEventListener()解析
2017/04/20 Javascript
VUE 更好的 ajax 上传处理 axios.js实现代码
2017/05/10 Javascript
JS实现获取word文档内容并输出显示到html页面示例
2018/06/23 Javascript
详解三种方式解决vue中v-html元素中标签样式
2018/11/22 Javascript
解决layer.msg 不居中 ifram中的问题
2019/09/05 Javascript
JS实现简单日历特效
2020/01/03 Javascript
python开发之基于thread线程搜索本地文件的方法
2015/11/11 Python
python微信跳一跳游戏辅助代码解析
2018/01/29 Python
解决python3 网络请求路径包含中文的问题
2018/05/10 Python
python集合比较(交集,并集,差集)方法详解
2018/09/13 Python
opencv python统计及绘制直方图的方法
2019/01/21 Python
pandas中ix的使用详细讲解
2020/03/09 Python
HTML5新特性之语义化标签
2017/10/31 HTML / CSS
西班牙自行车和跑步商店:Alltricks
2018/07/07 全球购物
三星印度官网:Samsung印度
2019/08/03 全球购物
优秀应届生推荐信
2013/11/09 职场文书
竞选班干部的演讲稿
2014/04/24 职场文书
四风问题对照检查材料
2014/09/22 职场文书
2014感恩节演讲稿大全
2014/10/11 职场文书
文员岗位职责范本
2015/04/16 职场文书
音乐课外活动总结
2015/05/09 职场文书
离婚案件被告代理词
2015/05/23 职场文书
追讨欠款律师函
2015/06/24 职场文书
村主任当选感言
2015/08/01 职场文书
幼师必备:幼儿园期末教师评语50条
2019/11/01 职场文书
Pytorch distributed 多卡并行载入模型操作
2021/06/05 Python
从结婚开始的恋爱故事。小说《我的美好婚事》TV动画化决定
2022/04/07 日漫