也就是闲时为了写文章而写的一篇关于 Pimple 源码的阅读笔记。 Pimple 代码有两种编码方式,一种是以 PHP 编写的,另一种是以 C 扩展编写的方式,当然个人能力有限呀,也就看看第一种了。
Pimple 链接官网 WebSite GitHub - Pimple Pimple 中文版文档
前提知识 ArrayAccess(数组式访问)接口 提供像访问数组一样访问对象的能力的接口。
http://php.net/manual/zh/class.arrayaccess.php
一个 Class 只要实现以下规定的 4 个接口,就可以是像操作数组一样操作 Object 了。
1 2 3 4 5 6 7 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 ) }
伪代码如下1 2 3 4 5 6 7 8 9 10 11 12 class A implements \ArrayAccess { } $a = new A(); $a['x' ] = 'x' ; echo $a['x' ]; var_dump(isset ($a['x' ])); unset ($a['x' ]);
特别说明,只支持上面四种操作,千万别以为实现了 ArrayAccess,就可以使用 foreach 了,要实现循环 = 迭代,要实现 Iterator(迭代器)接口 ,其实 PHP 定义了很多 预定义接口 有空可以看看。
SPL - SplObjectStorage SPL SPL 是 Standard PHP Library(PHP标准库)的缩写,一组旨在解决标准问题的接口和类的集合。SPL 提供了一套标准的数据结构,一组遍历对象的迭代器,一组接口,一组标准的异常,一系列用于处理文件的类,提供了一组函数,具体可以查看文档。
SplObjectStorage SplObjectStorage 是 SPL 标准库中的数据结构对象容器,用来存储一组对象,特别是当你需要唯一标识对象的时候 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 SplObjectStorage implements Countable , Iterator , Serializable , ArrayAccess { public void attach ( object $object [, mixed $data = NULL ] ) public bool contains ( object $object ) public void detach ( object $object ) }
SplObjectStorage 实现了 Countable 、Iterator、Serializable、ArrayAccess 四个接口,可实现统计、迭代、序列化、数组式访问等功能,其中 Iterator 和 ArrayAccess 在上面已经介绍过了。
魔术方法 __invoke() __invoke() 当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。
看一个例子吧,一目了然。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php class CallableClass { function __invoke ($x) { var_dump($x); } } $obj = new CallableClass; $obj(5 ); var_dump(is_callable($obj));
读源码 目录接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Container implements \ArrayAccess { private $values = array (); private $factories; private $protected; private $frozen = array (); private $raw = array (); private $keys = array (); 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()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public function offsetGet ($id) { if (!isset ($this ->keys[$id])) { throw new UnknownIdentifierException($id); } if ( isset ($this ->raw[$id]) || !\is_object($this ->values[$id]) || isset ($this ->protected[$this ->values[$id]]) || !\method_exists($this ->values[$id], '__invoke' ) ) { return $this ->values[$id]; } if (isset ($this ->factories[$this ->values[$id]])) { return $this ->values[$id]($this ); } $raw = $this ->values[$id]; $val = $this ->values[$id] = $raw($this ); $this ->raw[$id] = $raw; $this ->frozen[$id] = true ; return $val; }
7、extend()
扩展一个 service,如果已经被冻结了,也不能被扩展。 与上文说的直接覆盖还是有区别的,直接覆盖就是完全不管之前定义的 service ,使用 extend 是可以在原始定义上做出修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public function extend ($id, $callable) { if (isset ($this ->protected[$this ->values[$id]])) { @\trigger_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 兼容性的。