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 网页过期时间的控制代码
Jun 29 PHP
Discuz 6.0+ 批量注册用户名
Sep 13 PHP
非常好用的两个PHP函数 serialize()和unserialize()
Feb 04 PHP
mysql,mysqli,PDO的各自不同介绍
Sep 19 PHP
处理(php-cgi.exe - FastCGI 进程超过了配置的请求超时时限)的问题
Jul 03 PHP
详解php中空字符串和0之间的关系
Oct 23 PHP
PHP生成word文档的三种实现方式
Nov 14 PHP
php使用Jpgraph创建折线图效果示例
Feb 15 PHP
YII中Ueditor富文本编辑器文件和图片上传的配置图文教程
Mar 15 PHP
php实现数字补零的方法总结
Sep 12 PHP
PHP中quotemeta()函数的用法讲解
Apr 04 PHP
PHP如何防止用户重复提交表单
Dec 09 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中=赋值操作符对不同数据类型的不同行为
2011/01/02 PHP
解析func_num_args与func_get_args函数的使用
2013/06/24 PHP
推荐一款MAC OS X 下php集成开发环境mamp
2014/11/08 PHP
MacOS 安装 PHP的图片裁剪扩展Tclip
2015/03/25 PHP
利用PHP抓取百度阅读的方法示例
2016/12/18 PHP
简单几行JS Code实现IE邮件转发新浪微博
2013/07/03 Javascript
JS+css 图片自动缩放自适应大小
2013/08/08 Javascript
点击button获取text内容并改变样式的js实现
2014/09/09 Javascript
Bootstrap的基本应用要点浅析
2016/12/19 Javascript
详解从Node.js的child_process模块来学习父子进程之间的通信
2017/03/27 Javascript
详解vue数据渲染出现闪烁问题
2017/06/29 Javascript
angularjs获取到My97DatePicker选中的值方法
2018/10/02 Javascript
js 闭包深入理解与实例分析
2020/03/19 Javascript
Vue中computed及watch区别实例解析
2020/08/01 Javascript
[48:48]VGJ.T vs Liquid 2018国际邀请赛小组赛BO2 第二场 8.19
2018/08/21 DOTA
跟老齐学Python之开始真正编程
2014/09/12 Python
python实现从网络下载文件并获得文件大小及类型的方法
2015/04/28 Python
Python黑魔法@property装饰器的使用技巧解析
2016/06/16 Python
利用Python实现Windows定时关机功能
2017/03/21 Python
python pandas实现excel转为html格式的方法
2018/10/23 Python
Python/ArcPy遍历指定目录中的MDB文件方法
2018/10/27 Python
Python 窗体(tkinter)按钮 位置实例
2019/06/13 Python
Python算法的时间复杂度和空间复杂度(实例解析)
2019/11/19 Python
AUC计算方法与Python实现代码
2020/02/28 Python
Python实现Keras搭建神经网络训练分类模型教程
2020/06/12 Python
python xlsxwriter模块的使用
2020/12/24 Python
css3 响应式媒体查询的示例代码
2019/09/25 HTML / CSS
Kathmandu英国网站:新西兰户外运动品牌
2017/03/27 全球购物
学前教育教师求职自荐信
2013/09/22 职场文书
函授本科自我鉴定
2013/11/03 职场文书
函授大学生自我鉴定
2014/02/05 职场文书
副职竞争上岗演讲稿
2014/05/12 职场文书
上班时间打瞌睡检讨书
2014/09/26 职场文书
学习党的群众路线剖析材料
2014/10/09 职场文书
小学语文教师竞聘演讲稿范文
2019/08/09 职场文书
导游词之峨眉乐山/兵马俑/北京故宫御花园
2019/09/03 职场文书