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 生成静态页面的办法与实现代码详细版
Feb 15 PHP
PHP跨时区(UTC时间)应用解决方案
Jan 11 PHP
PHP APC的安装与使用详解
Jun 13 PHP
php截取字符串之截取utf8或gbk编码的中英文字符串示例
Mar 12 PHP
php导出csv数据在浏览器中输出提供下载或保存到文件的示例
Apr 24 PHP
php实现微信公众平台账号自定义菜单类
Dec 02 PHP
php从文件夹随机读取文件的方法
Jun 01 PHP
PHP如何使用Memcached
Apr 05 PHP
Yii统计不同类型邮箱数量的方法
Oct 18 PHP
Zend Framework前端控制器用法示例
Dec 11 PHP
PHP调用接口用post方法传送json数据的实例
May 31 PHP
PHP Beanstalkd消息队列的安装与使用方法实例详解
Feb 21 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
杏林同学录(六)
2006/10/09 PHP
基于php权限分配的实现代码
2013/04/28 PHP
php实现的九九乘法口诀表简洁版
2014/07/28 PHP
PHP实现Google plus的好友拖拽分组效果
2016/10/21 PHP
php中通过eval实现字符串格式的计算公式
2017/03/18 PHP
laravel5.0在linux下解决.htaccess无效和去除index.php的问题
2019/10/16 PHP
破解Session cookie的方法
2006/07/28 Javascript
javascript setTimeout()传递函数参数(包括传递对象参数)
2010/04/07 Javascript
jQuery LigerUI 使用教程入门篇
2012/01/18 Javascript
Jquery多选框互相内容交换的实例代码
2013/07/04 Javascript
JS中的构造函数详细解析
2014/03/10 Javascript
js中的setInterval和setTimeout使用实例
2014/05/09 Javascript
Jquery插件分享之气泡形提示控件grumble.js
2014/05/20 Javascript
搭建pomelo 开发环境
2014/06/24 Javascript
jquery 获取 outerHtml 包含当前节点本身的代码
2014/10/30 Javascript
JavaScript实现防止网页被嵌入Frame框架的代码分享
2014/12/29 Javascript
深入理解JavaScript系列(27):设计模式之建造者模式详解
2015/03/03 Javascript
JQuery实现样式设置、追加、移除与切换的方法
2015/06/11 Javascript
聊一聊Vue.js过渡效果
2016/09/07 Javascript
详解Angularjs在控制器(controller.js)中使用过滤器($filter)格式化日期/时间实例
2017/02/17 Javascript
Bootstrap3.3.7导航栏下拉菜单鼠标滑过展开效果
2017/10/31 Javascript
vscode中eslint插件的配置(prettier配置无效)
2019/09/10 Javascript
python基础教程之分支、循环简单用法
2016/06/16 Python
python SSH模块登录,远程机执行shell命令实例解析
2018/01/12 Python
Python(TensorFlow框架)实现手写数字识别系统的方法
2018/05/29 Python
python使用knn实现特征向量分类
2018/12/26 Python
在Python中获取操作系统的进程信息
2019/08/27 Python
2021年的Python 时间轴和即将推出的功能详解
2020/07/27 Python
CSS3 绘制BMW logo实的现代码
2013/04/25 HTML / CSS
鲜为人知的HTML5语音合成功能
2019/05/17 HTML / CSS
英国时尚家具、家居饰品及礼品商店:Graham & Green
2016/09/15 全球购物
服装销售人员求职自我评价
2013/09/26 职场文书
高三自我鉴定
2013/10/23 职场文书
2014年业务工作总结
2014/11/17 职场文书
2015年化验员工作总结
2015/04/10 职场文书
Pytorch 如何实现常用正则化
2021/05/27 Python