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 相关文章推荐
VML绘图板②脚本--VMLgraph.js、XMLtool.js
Oct 09 PHP
php桌面中心(一) 创建数据库
Mar 11 PHP
基于PHP Socket配置以及实例的详细介绍
Jun 13 PHP
php 获取今日、昨日、上周、本月的起始时间戳和结束时间戳的方法
Sep 28 PHP
PHP网页游戏学习之Xnova(ogame)源码解读(九)
Jun 24 PHP
thinkphp模板输出技巧汇总
Nov 24 PHP
php获取用户浏览器版本的方法
Jan 03 PHP
PHP中应该避免使用同名变量(拆分临时变量)
Apr 03 PHP
phplist及phpmailer(组合使用)通过gmail发送邮件的配置方法
Mar 30 PHP
iOS+PHP注册登录系统 PHP部分(上)
Dec 26 PHP
PHP进程通信基础之信号量与共享内存通信
Feb 19 PHP
thinkPHP5.0框架环境变量配置方法
Mar 17 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+MYSQL的文章管理系统(二)
2006/10/09 PHP
浅谈php7的重大新特性
2015/10/23 PHP
php7基于递归实现删除空文件夹的方法示例
2017/06/15 PHP
JS实现浏览器菜单命令
2006/09/05 Javascript
jquery post方式传递多个参数值后台以数组的方式进行接收
2013/01/11 Javascript
不使用jquery实现js打字效果示例分享
2014/01/19 Javascript
jquery实现多条件筛选特效代码分享
2015/08/28 Javascript
深入理解JavaScript中的for循环
2017/02/07 Javascript
vue中利用simplemde实现markdown编辑器(增加图片上传功能)
2019/04/29 Javascript
借助云开发实现小程序短信验证码的发送
2020/01/06 Javascript
JS+DIV实现拖动效果
2020/02/11 Javascript
通过实例了解Nodejs模块系统及require机制
2020/07/16 NodeJs
解决vuex数据页面刷新后初始化操作
2020/07/26 Javascript
element-ui中dialog弹窗关闭按钮失效的解决
2020/09/22 Javascript
python 中的列表解析和生成表达式
2011/03/10 Python
Django内容增加富文本功能的实例
2017/10/17 Python
Laravel+Dingo/Api 自定义响应的实现
2019/02/17 Python
python中dict使用方法详解
2019/07/17 Python
Python使用psutil获取进程信息的例子
2019/12/17 Python
Python装饰器原理与基本用法分析
2020/01/07 Python
完美解决jupyter由于无法import新包的问题
2020/05/26 Python
美国复古街头服饰精品店:Need Supply Co.
2017/02/22 全球购物
德国价格合理的品牌商品购物网站:averdo
2019/03/21 全球购物
大学毕业生工作的自我评价
2013/10/01 职场文书
生物化工工艺专业应届生求职信
2013/10/08 职场文书
测绘工程个人的自我评价
2013/11/23 职场文书
小学生期末自我鉴定
2014/01/19 职场文书
售后客服工作职责
2014/06/16 职场文书
师德先进个人事迹材料
2014/12/19 职场文书
鲁迅故里导游词
2015/02/05 职场文书
2015年度员工自我评价范文
2015/03/11 职场文书
网络营销实训总结
2015/08/03 职场文书
小程序后台PHP版本部署运行 LNMP+WNMP
2021/04/01 Servers
CSS3常见动画的实现方式
2021/04/14 HTML / CSS
python opencv旋转图片的使用方法
2021/06/04 Python
Redis调用Lua脚本及使用场景快速掌握
2022/03/16 Redis