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作的文本留言本的例子(五)
Oct 09 PHP
mysql建立外键
Nov 25 PHP
php自定义函数之递归删除文件及目录
Aug 08 PHP
php中让上传的文件大小在上传前就受限制的两种解决方法
Jun 24 PHP
PHP 下载文件时自动添加bom头的方法实例
Jan 10 PHP
php广告加载类用法实例
Sep 23 PHP
php创建多级目录的方法
Mar 24 PHP
php自定义分页类完整实例
Dec 25 PHP
Symfony2框架学习笔记之HTTP Cache用法详解
Mar 18 PHP
利用Laravel生成Gravatar头像地址的优雅方法
Dec 30 PHP
使用swoole 定时器变更超时未支付订单状态的解决方案
Jul 24 PHP
PhpStorm连接服务器并实现自动上传功能
Dec 09 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
MySQL中create table语句的基本语法是
2007/01/15 PHP
php生成SessionID和图片校验码的思路和实现代码
2009/03/10 PHP
php 安全过滤函数代码
2011/05/07 PHP
phpword插件导出word文件时中文乱码问题处理方案
2014/08/19 PHP
jquery简单体验
2007/01/10 Javascript
JavaScript实现网页图片等比例缩放实现代码及调用方式
2013/02/25 Javascript
获取数组中最大最小值方法js代码(自写)
2013/08/12 Javascript
javascript设计模式之解释器模式详解
2014/06/05 Javascript
Javascript获取background属性中url的值
2016/10/17 Javascript
Windows下快速搭建NodeJS本地服务器的步骤
2017/08/09 NodeJs
jquery对table做排序操作的实例演示
2017/08/10 jQuery
详解JavaScript之ES5的继承
2020/07/08 Javascript
[42:20]Secret vs Liquid 2019国际邀请赛小组赛 BO2 第二场 8.15
2019/08/17 DOTA
合并百度影音的离线数据( with python 2.3)
2015/08/04 Python
python通过getopt模块如何获取执行的命令参数详解
2017/12/29 Python
python中cPickle类使用方法详解
2018/08/27 Python
windows下cx_Freeze生成Python可执行程序的详细步骤
2018/10/09 Python
Python面向对象程序设计OOP深入分析【构造函数,组合类,工具类等】
2019/01/05 Python
PyQt QListWidget修改列表项item的行高方法
2019/06/20 Python
Python json转字典字符方法实例解析
2020/04/13 Python
Python基于requests实现模拟上传文件
2020/04/21 Python
一款超酷的js+css3实现的3D标签云特效兼容ie7/8/9
2013/11/18 HTML / CSS
PUMA官方商城:世界领先的运动品牌之一
2016/11/16 全球购物
加拿大最大的五金、家居装修和园艺产品商店:RONA
2017/01/27 全球购物
伦敦剧院门票:From The Box Office
2018/06/30 全球购物
比利时的在线灯具店:Lampen24.be
2019/07/01 全球购物
意大利自行车商店:Cingolani Bike Shop
2019/09/03 全球购物
电大本科自我鉴定
2014/02/05 职场文书
工业自动化专业自荐信范文
2014/04/10 职场文书
高中综合实践活动总结
2014/07/07 职场文书
英语课前三分钟演讲稿(6篇)
2014/09/13 职场文书
优秀教师先进材料
2014/12/16 职场文书
单位证明范文
2015/06/18 职场文书
办公室主任岗位竞聘书
2015/09/15 职场文书
丧事酒宴答谢词
2015/09/30 职场文书
Python开发五子棋小游戏
2022/04/28 Python