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 zip文件解压类代码
Dec 02 PHP
memcached 和 mysql 主从环境下php开发代码详解
May 16 PHP
php cookie名使用点号(句号)会被转换
Oct 23 PHP
PHP stream_context_create()函数的使用示例
May 12 PHP
win7系统配置php+Apache+mysql环境的方法
Aug 21 PHP
ThinkPHP在Cli模式下使用模板引擎的方法
Sep 25 PHP
PHP中使用foreach()遍历二维数组的简单实例
Jun 13 PHP
php 5.4 全新的代码复用Trait详解
Jan 05 PHP
PHP基于ORM方式操作MySQL数据库实例
Jun 21 PHP
PHP实现基于栈的后缀表达式求值功能
Nov 10 PHP
在云虚拟主机部署thinkphp5项目的步骤详解
Dec 21 PHP
原生php实现excel文件读写的方法分析
Apr 25 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中substr()函数参数说明及用法实例
2014/11/15 PHP
PHP文件读写操作相关函数总结
2014/11/18 PHP
php实现通用的信用卡验证类
2015/03/24 PHP
基于JQuery的密码强度验证代码
2010/03/01 Javascript
js 创建快捷方式的代码(fso)
2010/11/19 Javascript
js限制textarea每行输入字符串长度的代码
2012/10/31 Javascript
jQuery.buildFragment使用方法及思路分析
2013/01/07 Javascript
在firefox和Chrome下关闭浏览器窗口无效的解决方法
2014/01/16 Javascript
extjs 时间范围选择自动判断的实现代码
2014/06/24 Javascript
取得元素的左和上偏移量的方法
2014/09/17 Javascript
JS实现点击文字对应DIV层不停闪动效果的方法
2015/03/02 Javascript
Yii2使用Bootbox插件实现自定义弹窗
2015/04/02 Javascript
jquery模拟alert的弹窗插件
2015/07/31 Javascript
jQuery实现仿腾讯迷你首页选项卡效果代码
2015/09/17 Javascript
vuejs通过filterBy、orderBy实现搜索筛选、降序排序数据
2020/10/26 Javascript
详解nodejs微信jssdk后端接口
2017/05/25 NodeJs
Angular4项目中添加i18n国际化插件ngx-translate的步骤详解
2017/07/02 Javascript
Angularjs 事件指令详细整理
2017/07/27 Javascript
高性能的javascript之加载顺序与执行原理篇
2018/01/14 Javascript
详解React native fetch遇到的坑
2018/08/30 Javascript
js使用formData实现批量上传
2020/03/27 Javascript
PWA介绍及快速上手搭建一个PWA应用的方法
2019/01/27 Javascript
Vue 幸运大转盘实现思路详解
2019/05/06 Javascript
JavaScript数值类型知识汇总
2019/11/17 Javascript
Python学习笔记(一)(基础入门之环境搭建)
2014/06/05 Python
跟老齐学Python之正规地说一句话
2014/09/28 Python
Python实现一个简单的MySQL类
2015/01/07 Python
Bottle框架中的装饰器类和描述符应用详解
2017/10/28 Python
Python加载数据的5种不同方式(收藏)
2020/11/13 Python
瑞典Happy Socks美国官网:购买色彩斑斓的快乐袜子
2016/10/19 全球购物
三星印度官网:Samsung印度
2019/08/03 全球购物
一些关于MySql加速和优化的面试题
2014/01/30 面试题
大学毕业生的自我鉴定
2013/11/30 职场文书
八年级英语教学反思
2014/01/09 职场文书
高中校园广播稿
2014/10/21 职场文书
浅谈如何提高PHP代码质量之端到端集成测试
2021/05/28 PHP