讀 PHP - Pimple 源碼筆記(上)

也就是閒時爲了寫文章而寫的一篇關於 Pimple 源碼的閱讀筆記。
Pimple 代碼有兩種編碼方式,一種是以 PHP 編寫的,另外一種是以 C 擴展編寫的方式,固然我的能力有限呀,也就看看第一種了。php

Pimple 連接
官網 WebSite
GitHub - Pimple
Pimple 中文版文檔

前提知識

ArrayAccess(數組式訪問)接口

提供像訪問數組同樣訪問對象的能力的接口。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 - 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 在上面已經介紹過了。composer

魔術方法 __invoke()

__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 能夠直接生成結構。

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 分析

一、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
相關文章
相關標籤/搜索