也就是閒時爲了寫文章而寫的一篇關於 Pimple 源碼的閱讀筆記。
Pimple 代碼有兩種編碼方式,一種是以 PHP 編寫的,另外一種是以 C 擴展編寫的方式,固然我的能力有限呀,也就看看第一種了。php
Pimple 連接
官網 WebSite
GitHub - Pimple
Pimple 中文版文檔
提供像訪問數組同樣訪問對象的能力的接口。git
http://php.net/manual/zh/clas...
一個 Class 只要實現如下規定的 4 個接口,就能夠是像操做數組同樣操做 Object 了。github
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 ) }
僞代碼以下json
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 是 Standard PHP Library(PHP標準庫)的縮寫,一組旨在解決標準問題的接口和類的集合。SPL 提供了一套標準的數據結構,一組遍歷對象的迭代器,一組接口,一組標準的異常,一系列用於處理文件的類,提供了一組函數,具體能夠查看文檔。數據結構
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 在上面已經介紹過了。composer
__invoke() 當嘗試以調用函數的方式調用一個對象時,__invoke() 方法會被自動調用。ide
看一個例子吧,一目瞭然。函數
<?php class CallableClass { function __invoke($x) { var_dump($x); } } $obj = new CallableClass; $obj(5); var_dump(is_callable($obj)); //output //int(5) //bool(true)
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 能夠直接生成結構。
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 接口,這就能夠理解爲何能夠經過數組的方式定義服務了。
一、offsetSet、offsetExists、offsetUnset 主要實現 ArrayAccess 的接口很容易看懂
二、factory、protect 主要邏輯是判斷傳入的 $callable 是否有 __invoke ,若是有的話,經過 SplObjectStorage::attach,存儲 object 中
三、raw 獲取設置的原始內容
四、key 獲取全部的 key
五、register() 註冊一些通用的 service
六、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; }
七、extend()
擴展一個 service,若是已經被凍結了,也不能被擴展。
與上文說的直接覆蓋仍是有區別的,直接覆蓋就是徹底無論以前定義的 service ,使用 extend 是能夠在原始定義上作出修改
public function extend($id, $callable) { // ... 一些判斷邏輯省略 // 若是是 protected 的 service 還不被支持 extend 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 兼容性的。
原創文章,歡迎轉載。轉載請註明出處,謝謝。
原文連接地址: http://dryyun.com/2018/04/18/...
做者: dryyun 發表日期: 2018-04-18 14:36:40