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类
Nov 27 PHP
php str_pad 函数使用详解
Jan 13 PHP
php实现utf-8和GB2312编码相互转换函数代码
Feb 07 PHP
解析php入库和出库
Jun 25 PHP
php上传图片存入数据库示例分享
Mar 11 PHP
smarty内置函数section的用法
Jan 22 PHP
php结合正则批量抓取网页中邮箱地址
May 19 PHP
php中使用GD库做验证码
Mar 31 PHP
PHP实现基于图的深度优先遍历输出1,2,3...n的全排列功能
Nov 10 PHP
Yii2.0 RESTful API 基础配置教程详解
Dec 26 PHP
laravel dingo API返回自定义错误信息的实例
Sep 29 PHP
Aliyun Linux 编译安装 php7.3 tengine2.3.2 mysql8.0 redis5的过程详解
Oct 20 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检测字符串是否为UTF8编码的常用方法
2014/11/21 PHP
Codeigniter的dom类用法实例
2015/06/26 PHP
CodeIgniter辅助之第三方类库third_party用法分析
2016/01/20 PHP
tp5框架无刷新分页实现方法分析
2019/09/26 PHP
laravel Model 执行事务的实现
2019/10/10 PHP
jquery限制输入字数,并提示剩余字数实现代码
2012/12/24 Javascript
使用JavaScript动态设置样式实现代码及演示动画
2013/01/25 Javascript
jQuery创建平滑的页面滚动(顶部或底部)
2013/02/26 Javascript
使用js对select动态添加和删除OPTION示例代码
2013/08/12 Javascript
jQuery操作input值的各种方法总结
2013/11/21 Javascript
对JavaScript中this指针的新理解分享
2015/01/31 Javascript
jQuery遍历页面所有CheckBox查看是否被选中的方法
2015/04/14 Javascript
jQuery实用技巧必备(中)
2015/11/03 Javascript
浅谈javascript中关于日期和时间的基础知识
2016/07/13 Javascript
jquery点击展示与隐藏更多内容
2016/12/03 Javascript
selenium 与 chrome 进行qq登录并发邮件操作实例详解
2017/04/06 Javascript
原生javascript实现分页效果
2017/04/21 Javascript
vue使用自定义事件的表单输入组件用法详解【日期组件与货币组件】
2020/06/01 Javascript
vue3为什么要用proxy替代defineProperty
2020/10/19 Javascript
python内置函数:lambda、map、filter简单介绍
2017/11/16 Python
Python基于whois模块简单识别网站域名及所有者的方法
2018/04/23 Python
Python运维之获取系统CPU信息的实现方法
2018/06/11 Python
django-rest-swagger对API接口注释的方法
2019/08/29 Python
Flask之pipenv虚拟环境的实现
2019/11/26 Python
python爬虫中采集中遇到的问题整理
2020/11/27 Python
什么是JNDI的上下文?如何初始化JNDI上下文
2012/03/10 面试题
幼儿园保育员辞职信
2014/01/12 职场文书
干部现实表现材料
2014/02/13 职场文书
小小的船教学反思
2014/02/21 职场文书
教师新年寄语
2014/04/03 职场文书
大学开学计划书
2014/04/30 职场文书
暑假学习心得体会
2014/09/02 职场文书
党员个人查摆剖析材料
2014/10/16 职场文书
2014乡党委副书记党建工作汇报材料
2014/11/02 职场文书
2014年政工师工作总结
2014/12/18 职场文书
解决Oracle数据库用户密码过期
2022/05/11 Oracle