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下过滤html代码的函数 提高程序安全性
Mar 02 PHP
PHP开发负载均衡指南
Jul 17 PHP
深入PHP运行环境配置的详解
Jun 04 PHP
浅谈web上存漏洞及原理分析、防范方法(安全文件上存方法)
Jun 29 PHP
php 下载保存文件保存到本地的两种实现方法
Aug 12 PHP
php实现mysql封装类示例
May 07 PHP
PHP计算百度地图两个GPS坐标之间距离的方法
Jan 09 PHP
分享一个Laravel好用的Cache宏
Mar 02 PHP
PHP基于yii框架实现生成ICO图标
Nov 13 PHP
Yii2中设置与获取别名的函数(setAlias和getAlias)用法分析
Jul 25 PHP
PHP查询大量数据内存耗尽问题的解决方法
Oct 28 PHP
php实现替换手机号中间数字为*号及隐藏IP最后几位的方法
Nov 16 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引用地址改变变量值的问题
2012/03/23 PHP
div li的多行多列 无刷新分页示例代码
2013/10/16 PHP
jquery.alert 弹出式复选框实现代码
2009/06/15 Javascript
Jquery实现简单的动画效果代码
2012/03/18 Javascript
禁用Enter键表单自动提交实现代码
2014/05/22 Javascript
20个实用的JavaScript技巧分享
2014/11/28 Javascript
Node.js事件循环(Event Loop)和线程池详解
2015/01/28 Javascript
jQuery 选择同时包含两个class的元素的实现方法
2016/06/01 Javascript
基于jQuery实现滚动刷新效果
2017/01/09 Javascript
JavaScript优化以及前段开发小技巧
2017/02/02 Javascript
Easyui和zTree两种方式分别实现树形下拉框
2017/08/04 Javascript
Javascript网页抢红包外挂实现分享
2018/01/11 Javascript
webpack+react+antd脚手架优化的方法
2018/04/02 Javascript
Vue实现用户自定义字段显示数据的方法
2018/08/28 Javascript
vscode vue 文件模板的配置方法
2019/07/23 Javascript
Angular8基础应用之表单及其验证
2019/08/11 Javascript
vue视频播放插件vue-video-player的具体使用方法
2019/11/08 Javascript
vue实现短信验证码登录功能(流程详解)
2019/12/10 Javascript
python刷投票的脚本实现代码
2014/11/08 Python
关于Python元祖,列表,字典,集合的比较
2017/01/06 Python
python机器学习实战之树回归详解
2017/12/20 Python
2018年Python值得关注的开源库、工具和开发者(总结篇)
2018/01/04 Python
在python里协程使用同步锁Lock的实例
2019/02/19 Python
python通过txt文件批量安装依赖包的实现步骤
2019/08/13 Python
pytorch程序异常后删除占用的显存操作
2020/01/13 Python
Python可以用来做什么
2020/11/23 Python
HTML5 画布canvas使用方法
2016/03/18 HTML / CSS
Nike香港官网:Nike HK
2019/03/23 全球购物
Intersport西班牙:在线体育商店
2019/11/06 全球购物
Linux如何压缩可执行文件
2013/10/21 面试题
表彰先进集体通报
2014/01/12 职场文书
咖啡蛋糕店创业计划书
2014/01/28 职场文书
妇女工作先进事迹
2014/08/17 职场文书
展览会邀请函
2015/02/02 职场文书
村党组织公开承诺书
2015/04/30 职场文书
七年级数学教学反思
2016/02/17 职场文书