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 相关文章推荐
通过html表格发电子邮件
Oct 09 PHP
基于mysql的bbs设计(三)
Oct 09 PHP
PHP 中文处理技巧
Apr 25 PHP
基于initPHP的框架介绍
Apr 18 PHP
php实现webservice实例
Nov 06 PHP
Codeigniter检测表单post数据的方法
Mar 21 PHP
了解PHP的返回引用和局部静态变量
Jun 04 PHP
php图片添加水印例子
Jul 20 PHP
CI(CodeIgniter)框架实现图片上传的方法
Mar 24 PHP
PHP正则匹配操作简单示例【preg_match_all应用】
Jul 10 PHP
php tpl模板引擎定义与使用示例
Aug 09 PHP
PHP Web表单生成器案例分析
Jun 02 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
利用curl 多线程 模拟 并发的详解
2013/06/14 PHP
php找出指定范围内回文数且平方根也是回文数的方法
2015/03/23 PHP
php提高网站效率的技巧
2015/09/29 PHP
PHP实现执行外部程序的方法详解
2017/08/17 PHP
JQuery教学之性能优化
2014/05/14 Javascript
node.js实现BigPipe详解
2014/12/05 Javascript
每天一篇javascript学习小结(RegExp对象)
2015/11/17 Javascript
jquery zTree异步加载简单实例讲解
2016/02/25 Javascript
AngularJS 整理一些优化的小技巧
2016/08/18 Javascript
轻松实现js弹框显示选项
2016/09/13 Javascript
node.js实现复制文本到剪切板的功能
2017/01/23 Javascript
jquery使用iscorll实现上拉、下拉加载刷新
2017/10/26 jQuery
node.js多个异步过程中判断执行是否完成的解决方案
2017/12/10 Javascript
微信小程序实现验证码获取倒计时效果
2018/02/08 Javascript
Vue 全局loading组件实例详解
2018/05/29 Javascript
JavaScript寄生组合式继承原理与用法分析
2019/01/11 Javascript
移动端自适应flexible.js的使用方法(不用三大框架,仅写一个单html页面使用)推荐
2019/04/02 Javascript
vue中的inject学习教程
2019/04/24 Javascript
JS面向对象编程基础篇(一) 对象和构造函数实例详解
2020/03/03 Javascript
python文件操作整理汇总
2014/10/21 Python
Python利用Beautiful Soup模块创建对象详解
2017/03/27 Python
python使用Apriori算法进行关联性解析
2017/12/21 Python
Python socket实现简单聊天室
2018/04/01 Python
python版本的仿windows计划任务工具
2018/04/30 Python
Numpy的简单用法小结
2019/08/28 Python
python实现word文档批量转成自定义格式的excel文档的思路及实例代码
2020/02/21 Python
美国优质宠物用品购买网站:Muttropolis
2020/02/17 全球购物
心理健康课教学反思
2014/02/13 职场文书
《手指教学》反思
2014/02/14 职场文书
《金孔雀轻轻跳》教学反思
2014/04/20 职场文书
文明城市创建标语
2014/06/16 职场文书
银行员工犯错检讨书
2014/09/16 职场文书
厉行节约工作总结
2015/08/12 职场文书
2019同学聚会主持词
2019/05/06 职场文书
2019奶茶店创业计划书范本,值得你借鉴
2019/08/14 职场文书
解决Git推送错误non-fast-forward的方法
2022/06/25 Servers