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 第二节 数据类型之数组
Apr 28 PHP
最常用的8款PHP调试工具
Jul 06 PHP
PHP实现自动登入google play下载app report的方法
Sep 23 PHP
php实现两表合并成新表并且有序排列的方法
Dec 05 PHP
PHP获取某个月最大天数(最后一天)的方法
Jul 29 PHP
Symfony2安装的方法(2种方法)
Feb 04 PHP
CI框架AR操作(数组形式)实现插入多条sql数据的方法
May 18 PHP
php微信公众平台开发之微信群发信息
Sep 13 PHP
PHP实现多级分类生成树的方法示例
Feb 07 PHP
php获取网站根目录物理路径的几种方法(推荐)
Mar 04 PHP
Laravel关联模型中过滤结果为空的结果集(has和with区别)
Oct 18 PHP
Laravel使用swoole实现websocket主动消息推送的方法介绍
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可逆加密/解密函数分享
2012/09/25 PHP
深入解析fsockopen与pfsockopen的区别
2013/07/05 PHP
php使用Jpgraph绘制饼状图的方法
2015/06/10 PHP
PHP实现获取中英文首字母
2015/06/19 PHP
js的with语句使用方法
2007/09/21 Javascript
js 刷新页面的代码小结 推荐
2010/04/02 Javascript
js正则表达式中test,exec,match方法的区别说明
2014/01/29 Javascript
jQuery实现自动与手动切换的滚动新闻特效代码分享
2015/08/27 Javascript
jQuery+AJAX实现遮罩层登录验证界面(附源码)
2020/09/13 Javascript
jquery判断类型是不是number类型的实例代码
2016/10/07 Javascript
微信小程序 input输入框详解及简单实例
2017/01/10 Javascript
Django+Vue跨域环境配置详解
2018/07/06 Javascript
VueJS 组件参数名命名与组件属性转化问题
2018/12/03 Javascript
基于element-ui组件手动实现单选和上传功能
2018/12/06 Javascript
Python实现合并字典的方法
2015/07/07 Python
Python3安装Pymongo详细步骤
2017/05/26 Python
python实现对excel进行数据剔除操作实例
2017/12/07 Python
python清除字符串中间空格的实例讲解
2018/05/11 Python
python 制作自定义包并安装到系统目录的方法
2018/10/27 Python
Python数据类型之List列表实例详解
2019/05/08 Python
selenium2.0中常用的python函数汇总
2019/08/05 Python
python3 selenium自动化测试 强大的CSS定位方法
2019/08/23 Python
PyTorch使用cpu加载模型运算方式
2020/01/13 Python
python中sklearn的pipeline模块实例详解
2020/05/21 Python
浅谈tensorflow模型保存为pb的各种姿势
2020/05/25 Python
python爬虫今日热榜数据到txt文件的源码
2021/02/23 Python
深入浅析css3 中display box使用方法
2015/11/25 HTML / CSS
伦敦最著名的老字号百货公司:Selfridges(塞尔福里奇百货)
2016/07/25 全球购物
Melijoe时尚童装德国官网:Melijoe德国
2016/09/03 全球购物
墨西哥购物网站:Elektra
2020/01/21 全球购物
到底Java是如何传递参数的?是by value或by reference?
2012/07/13 面试题
毕业生欢送会主持词
2014/03/31 职场文书
购房协议书范本
2014/10/02 职场文书
冰峪沟导游词
2015/02/09 职场文书
爱国主义影片观后感
2015/06/18 职场文书
商务信函英语问候语
2015/11/10 职场文书