Yii2.0 初識 RESTful Serializer

當RESTful API響應中包含一個資源時,該資源需要序列化成一個字符串。 Yii將這個過程分成兩步,首先,資源會被yii\rest\Serializer轉換成數組, 然後,該數組會通過yii\web\ResponseFormatterInterface根據請求格式(如JSON, XML)被序列化成字符串。當開發一個資源類時應重點關注第一步。


所以打開Yii框架下的vendor/yiisoft/yii2/rest/Serializer.php

    public $fieldsParam = 'fields';
   
    public $expandParam = 'expand';
    
    public $totalCountHeader = 'X-Pagination-Total-Count';
   
    public $pageCountHeader = 'X-Pagination-Page-Count';
   
    public $currentPageHeader = 'X-Pagination-Current-Page';
    
    public $perPageHeader = 'X-Pagination-Per-Page';
    
    public $collectionEnvelope;
   
    public $linksEnvelope = '_links';
    
    public $metaEnvelope = '_meta';
    
    public $request;
   
    public $response;

變量$fieldsParam對應的是是RESTful風格中的yii\base\Model::fields()或者yii\db\ActiveRecord::fields()方法的返回值結果爲一個鍵值對數組

例如:

public function fields(){        return [                // 字段名和屬性名相同        'id',        // 字段名爲"email", 對應的屬性名爲"email_address"        'email' => 'email_address',               // 字段名爲"name", 值由一個PHP回調函數定義       'name' => function ($model)            return $model->first_name . ' ' . $model->last_name;        },    ];}

那麼對應的當該資源被請求時返回的將是:

[        // 字段名和屬性名相同        'id',        // 字段名爲"email", 對應的屬性名爲"email_address"        'email' => 'email_address',        // 字段名爲"name", 值由一個PHP回調函數定義        'name' => function ($model) {            return $model->first_name . ' ' . $model->last_name;        },    ]

如果對應的模型層中沒有實現yii\base\Model::fields()或者yii\db\ActiveRecord::fields()方法,則會默認返回所有定義的字段。

變量$expandParam對應的是yii\db\Model::extraFields() 和 yii\db\ActiveRecord::extraFields() 其中前者返回空值,後者返回關聯模型中定義的字段鍵值對數組,實現方法與yii\base\Model::fields()或者yii\db\ActiveRecord::fields()相同。

變量$totalCountHeader當請求資源時如果設置了分頁,那麼$totalCountHeader對應資源請求中的總的記錄數量。

變量$pageCountHeader當請求資源時如果設置了分頁,那麼$pageCountHeader對應資源請求中的分頁總數。

變量$currentPageHeader當請求資源時如果設置了分頁,那麼$currentPageHeader對應資源請求中的當前的頁碼。

變量$perPageHeader當請求資源時如果設置了分頁,那麼$perPageHeader對應資源請求中每一頁的記錄數量。

變量$collectionEnvelope,當發出一個資源請求,其中包括了對分頁的要求或者對於關聯操作進行的設置,那麼$collectionEnvelope就會有對應的值。

首先是返回的關聯操作,統一的是以資源請求的url形式進行返回的,聲明的時候會有譬如最典型的返回一個self鏈接:

class User extends ActiveRecord implements Linkable{    public function getLinks()    {        return [            Link::REL_SELF => Url::to(['user/view', 'id' => $this->id], true),        ];    }}

那麼在我們得到的資源中將會有:

{    "id": 100,    "email": "[email protected]",    // ...    "_links" => {        "self": {            "href": "https://example.com/users/100"        }    }}

增加了一個_links標記 其中包括了我們在添加的yii\db\ActiveRecord::getLinks()中添加的url,而這個標籤內的內容恰好是變量$linksEnvelope對應的,如果設置了分頁那麼類似_links標記,還會增加一個_meta標記由$metaEnvelope對應:

'_meta' => {    // meta information as returned by Pagination::toArray()
    'totalCount' => 100,
    'pageCount' => 5,
    'currentPage' => 1,
    'perPage' => 20,
}

對於變量的解釋就到這裏,接下來是對方法部分的解釋。

init()方法:

 public function init()
 {
     if ($this->request === null) {
         $this->request = Yii::$app->getRequest();
     }
     if ($this->response === null) {
         $this->response = Yii::$app->getResponse();
     }
 }

每次初始化時會初始化$request和$response的值,這兩個變量的實現參考vendor/yiisoft/yii2/web/Request.php

和vendor/yiisoft/yii2/web/Respense.php 就是對數據的接收的返回的解析和封裝,這裏不做贅述。


serialize()方法:

    public function serialize($data)
    {
        if ($data instanceof Model && $data->hasErrors()) {
            return $this->serializeModelErrors($data);
        } elseif ($data instanceof Arrayable) {
            return $this->serializeModel($data);
        } elseif ($data instanceof DataProviderInterface) {
            return $this->serializeDataProvider($data);
        } else {
            return $data;
        }
    }

這一步是對數據進行序列化,將數據格式統一處理爲標準的RESTful的數據格式,便於開發者操作處理。

首先看是第一個條件,判斷了$data是否爲符合要求的數據,此處調用的yii\base\Model::hasErrors()方法的代碼如下:

    public function hasErrors($attribute = null)
    {
        return $attribute === null ? !empty($this->_errors) : isset($this->_errors[$attribute]);
    }

當傳入數據沒有屬性,或者數據有屬性是否存在不符(通過數據層中實現的rules()方法檢驗)。

如果數據符合要求則會進入下面的序列化階段,如果傳入的數據爲鍵值對那麼使用serializeModel()方法:

protected function serializeModel($model)
    {
        if ($this->request->getIsHead()) {
            return null;
        } else {
            list ($fields, $expand) = $this->getRequestedFields();
            return $model->toArray($fields, $expand);
        }
    }

首先用yii2/web/Request::getIsHead()方法來判斷當前的請求是否爲HEAD請求是的話不返回信息,如果不是則通過getRequestedFields()方法對數據進行序列化,代碼如下:

    protected function getRequestedFields()
    {
        $fields = $this->request->get($this->fieldsParam);
        $expand = $this->request->get($this->expandParam);


        return [
            preg_split('/\s*,\s*/', $fields, -1, PREG_SPLIT_NO_EMPTY),
            preg_split('/\s*,\s*/', $expand, -1, PREG_SPLIT_NO_EMPTY),
        ];
    }

首先調用yii2/web/Request::get()方法

    public function get($name = null, $defaultValue = null)
    {
        if ($name === null) {
            return $this->getQueryParams();
        } else {
            return $this->getQueryParam($name, $defaultValue);
        }
    }
    public function getQueryParam($name, $defaultValue = null)     {         $params = $this->getQueryParams();         return isset($params[$name]) ? $params[$name] : $defaultValue;     }

去掉請求中的空格並且返回非空部分作爲標籤名。然後由serialize()返回該值。


如果傳入的數據類型是根據DataProviderInterface接口實現的,那麼則需要根據傳入的分頁信息對數據進行處理。

關於Yii的Arrayable接口,詳情請見官方文檔




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章