资讯专栏INFORMATION COLUMN

读 PHP - Pimple 源码笔记(上)

cfanr / 727人阅读

摘要:也就是闲时为了写文章而写的一篇关于源码的阅读笔记。是标准库的缩写,一组旨在解决标准问题的接口和类的集合。提供了一套标准的数据结构,一组遍历对象的迭代器,一组接口,一组标准的异常,一系列用于处理文件的类,提供了一组函数,具体可以查看文档。

也就是闲时为了写文章而写的一篇关于 Pimple 源码的阅读笔记。
Pimple 代码有两种编码方式,一种是以 PHP 编写的,另一种是以 C 扩展编写的方式,当然个人能力有限呀,也就看看第一种了。

Pimple 链接  
官网 WebSite
GitHub - Pimple
Pimple 中文版文档
前提知识 ArrayAccess(数组式访问)接口

提供像访问数组一样访问对象的能力的接口。

http://php.net/manual/zh/clas...

一个 Class 只要实现以下规定的 4 个接口,就可以是像操作数组一样操作 Object 了。

ArrayAccess {
    /* 方法 */
    abstract public boolean offsetExists ( mixed $offset )
    abstract public mixed offsetGet ( mixed $offset )
    abstract public void offsetSet ( mixed $offset , mixed $value )
    abstract public void offsetUnset ( mixed $offset )
}

伪代码如下

class A implements ArrayAccess {
    // 实现了 4 个接口
}

$a = new A();

// 可以这么操作
$a["x"] = "x"; // 对应 offsetSet  
echo $a["x"]; // 对应 offsetGet  
var_dump(isset($a["x"])); // 对应 offsetExists  
unset($a["x"]); // 对应 offsetUnset  

特别说明,只支持上面四种操作,千万别以为实现了 ArrayAccess,就可以使用 foreach 了,要实现循环 = 迭代,要实现 Iterator(迭代器)接口,其实 PHP 定义了很多 预定义接口 有空可以看看。

SPL - SplObjectStorage SPL

SPL 是 Standard PHP Library(PHP标准库)的缩写,一组旨在解决标准问题的接口和类的集合。SPL 提供了一套标准的数据结构,一组遍历对象的迭代器,一组接口,一组标准的异常,一系列用于处理文件的类,提供了一组函数,具体可以查看文档。

SplObjectStorage

SplObjectStorage 是 SPL 标准库中的数据结构对象容器,用来存储一组对象,特别是当你需要唯一标识对象的时候 。

SplObjectStorage implements Countable , Iterator , Serializable , ArrayAccess {

    /* 
     * 向 SplObjectStorage 添加一个 object,$data 是可选参数
     * 因为 SplObjectStorage 实现了 ArrayAccess 的接口,所以可以通过数组的形式访问,这里相当于设置 object 为数组的 key ,data 是对应的 value,默认 data 是 null
     */
    public void attach ( object $object [, mixed $data = NULL ] )
    
    /* 
     * 检查 SplObjectStorage 是否包含 object ,相当于 isset 判断
     */
    public bool contains ( object $object )
    
    /* 
     * 从 SplObjectStorage 移除 object ,相当于 unset 
     */
    public void detach ( object $object )
    // 其他接口定义可以自行查看文档
    
}

SplObjectStorage 实现了 Countable、Iterator、Serializable、ArrayAccess 四个接口,可实现统计、迭代、序列化、数组式访问等功能,其中 Iterator 和 ArrayAccess 在上面已经介绍过了。

魔术方法 __invoke()

__invoke() 当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。

看一个例子吧,一目了然。


读源码
目录接口
pimple
├── CHANGELOG
├── LICENSE
├── README.rst
├── composer.json
├── ext // C 扩展,不展开
│   └── pimple
├── phpunit.xml.dist
└── src
    └── Pimple
        ├── Container.php
        ├── Exception // 异常类定义,不展开
        ├── Psr11
        │   ├── Container.php
        │   └── ServiceLocator.php
        ├── ServiceIterator.php
        ├── ServiceProviderInterface.php
        └── Tests // 测试文件,不展开

PS, Markdown 写目录格式真是麻烦,后来找了一个工具 tree 可以直接生成结构。

Container.php
class Container implements ArrayAccess
{
    private $values = array(); // 存储 value 的数组
    private $factories; // 存储工厂方法的对象,是 SplObjectStorage 的实例
    private $protected; // 存储保护方法的对象,是 SplObjectStorage 的实例
    
    // 存储被冻结的服务,新设置一个 service 的时候,可以在还没有调用这个 service 的时候,覆盖原先设置,这时不算冻结
    // 一旦调用了这个 service 之后,就会存入 $frozen 数组,如果这时还想重新覆盖这个 service 会报错,判断逻辑在 offsetSet 实现。
    private $frozen = array(); 
    
    private $raw = array(); // 存储 service 原始设置内容,用于 ::raw() 方法读取 
    private $keys = array(); // 存储 key
    
    public function __construct(array $values = array())
    {
        $this->factories = new SplObjectStorage();
        $this->protected = new SplObjectStorage();

        foreach ($values as $key => $value) {
            $this->offsetSet($key, $value);
        }
    }
    
    public function offsetSet($id, $value){}
    public function offsetGet($id){}
    public function offsetExists($id){}
    public function offsetUnset($id){}
    public function factory($callable){}
    public function protect($callable){}
    public function raw($id){}
    public function extend($id, $callable){}
    public function keys(){}
    public function register(ServiceProviderInterface $provider, array $values = array()){}
}

Container 实现了 ArrayAccess 接口,这就可以理解为什么可以通过数组的方式定义服务了。

重要的 function 分析

1、offsetSet、offsetExists、offsetUnset 主要实现 ArrayAccess 的接口很容易看懂
2、factory、protect 主要逻辑是判断传入的 $callable 是否有 __invoke ,如果有的话,通过 SplObjectStorage::attach,存储 object 中
3、raw 获取设置的原始内容
4、key 获取所有的 key
5、register() 注册一些通用的 service

6、offsetGet()

    public function offsetGet($id)
    {
        if (!isset($this->keys[$id])) {    // 如果没有设置过,报错
            throw new UnknownIdentifierException($id);
        }
        
        if (
            isset($this->raw[$id])  // raw 里已经有值,一般来说就是之前已经获取过一次实例,再次获取的时候,就返回相同的值
            || !is_object($this->values[$id]) // 对应的 value 不是 object ,而是一个普通的值
            || isset($this->protected[$this->values[$id]]) // 存在于 protected 中
            || !method_exists($this->values[$id], "__invoke") // 对应的 value 不是闭包
        ) {
            return $this->values[$id]; // 返回 values 数组里的值
        }

        if (isset($this->factories[$this->values[$id]])) { // 如果工厂方法里面设置了相关方法
            return $this->values[$id]($this); // 直接调用这个方法,传入参数($this),也就是匿名函数中可以访问当前实例的其他服务
        }

        $raw = $this->values[$id];
        $val = $this->values[$id] = $raw($this); // 初始化一般的 service ,传入($this) ,以后再调用都获取相同的实例
        $this->raw[$id] = $raw; // 把原始内容存入 raw 数组

        $this->frozen[$id] = true; // 在初始化之后冻结这个 key ,不能被覆盖

        return $val;
    }

7、extend()

扩展一个 service,如果已经被冻结了,也不能被扩展。
与上文说的直接覆盖还是有区别的,直接覆盖就是完全不管之前定义的 service ,使用 extend 是可以在原始定义上做出修改

    public function extend($id, $callable)
    {
        // ... 一些判断逻辑省略
        
        // 如果是 protected 的 service 还不被支持 extend 
        if (isset($this->protected[$this->values[$id]])) {
            @	rigger_error(sprintf("How Pimple behaves when extending protected closures will be fixed in Pimple 4. Are you sure "%s" should be protected?", $id), E_USER_DEPRECATED);
        }

        if (!is_object($callable) || !method_exists($callable, "__invoke")) {
            throw new ExpectedInvokableException("Extension service definition is not a Closure or invokable object.");
        }

        $factory = $this->values[$id];

        // 主要是这两行代码
        $extended = function ($c) use ($callable, $factory) {
            return $callable($factory($c), $c);
        };

        if (isset($this->factories[$factory])) {
            $this->factories->detach($factory);
            $this->factories->attach($extended);
        }

        return $this[$id] = $extended;
    }
未完待续。

还有一篇,主要关于 PSR11 兼容性的。

原创文章,欢迎转载。转载请注明出处,谢谢。
原文链接地址:http://dryyun.com/2018/04/18/...
作者: dryyun
发表日期: 2018-04-18 14:36:40

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/28653.html

相关文章

  • PHP - Pimple 源码笔记(下)

    摘要:服务容器接口是的简写,由组织制定的规范,是开发的实践标准。实现的容器类源码很简单,主要是传入变量,然后设置这个两个方法。原创文章,欢迎转载。原文链接地址作者发表日期 接着上篇 还有一些内容没有写,上篇已经把关于 Pimple 最主要的代码分析了一下,这篇主要是关于 PSR-11 兼容性的分析。 PSR-11 服务容器接口 PSR PSR 是 PHP Standard Recommend...

    KunMinX 评论0 收藏0
  • Pimple相关的源码

    摘要:已经有了非常好的的相关解析,建议先看下一个简单的依赖注入容器读源码笔记上读源码笔记下这里通过例子补充下核心方法的说明相关的类型服务类似单例工厂服务多个实例参数仅仅是保存一些变量保护参数匿名函数都会被认为服务,但是如果仅仅是想作为一个 已经有了非常好的Pimple的相关解析,建议先看下:Pimple - 一个简单的 PHP 依赖注入容器读 PHP - Pimple 源码笔记(上)读 PH...

    MSchumi 评论0 收藏0
  • Pimple - 一个简单的 PHP 依赖注入容器

    摘要:服务通过匿名函数定义,返回一个对象的实例定义一些服务请注意,匿名函数可以访问当前容器实例,从而允许引用其他服务或参数。如果要为所有调用返回不同的实例,请使用方法包装你的匿名函数。 链接 官网 WebSite GitHub - Pimple 这是 Pimple 3.x 的文档。如果你正在使用 Pimple 1.x ,请查看 Pimple 1.x 文档。阅读 Pimple 1.x ...

    wemall 评论0 收藏0
  • PHP容器--Pimple运行流程浅析

    摘要:实际上,闭包和匿名函数是伪装成函数的对象。容器流程浅析是社区中比较流行的容器。服务提供者服务提供者是连接容器与具体功能实现类的桥梁。服务提供者需要实现接口所有服务提供者必须实现接口方法。但已经完成了服务提供者的注册工作。 需要具备的知识点 闭包 闭包和匿名函数在PHP5.3.0中引入的。 闭包是指:创建时封装周围状态的函数。即使闭包所处的环境不存在了,闭包中封装的状态依然存在。 理论上...

    RobinTang 评论0 收藏0
  • Pimple Containter 容器使用实例代码

    摘要:安装代码加载并实例化参数存储单例存储非单例存储存储匿名函数获取匿名函数服务提供者文档官网文档 Pimple/Container 安装 composer require pimple/pimple: ^3.0 代码 加载并实例化 require __DIR__ . /vendor/autoload.php; $pc = new PimpleContainer(); 参数存储 $pc[ap...

    maochunguang 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<