使用Yii2時遇到的實際問題

最近一直在學習Yii2框架,多是一直以來對它的青睞,讓我難以對其它框架再產生興趣,學習中遇到了許多問題,因而把問題和解決辦法也記錄下來,這樣方便之後複習和交流。 ## 目錄 擴展XmlResponseFormatter
在原有的Yii2框架上,新建一個api應用
配置Yii2 request Parser使之能夠經過Yii::$app->request->post()來接收 xml 和 json的數據
使用 TimestampBehavior 來自動填充 created_at 和 updated_atphp

擴展XmlResponseFormatter

在作微信接口測試的時候發現,每次返回數據的時候都是本身寫的 xml 信息而後 echo 出來,今天忽然看到了 Yii::$app->response->format = Response::FORMAT_XML; 原來經過這個就能夠設置返回的數據爲 xml ,固然 response 這個類在 Controller 裏面是沒有加載的,因此首先得加載一下 use yii\web\Response; ,最後把須要返回的數據用數組的形式來返回便可:html

<?php 
// ... ...
use yii\web\Response;

public function actionIndex(){
	// ... ... 原來的邏輯代碼
	Yii::$app->response->format = Response::FORMAT_XML;
	return [
            "ToUserName"=>$postObject->FromUserName,
            "FromUserName"=>$postObject->ToUserName,
            "CreateTime"=>time(),
            "MsgType"=>"music",
            "Music"=>[
                "Title"=>$recognition,
                "Description"=>$decode,
                "MusicUrl"=>$musicurl,
                "HQMusicUrl"=>$musicurl,
            ]
        ];
}

這樣使用以後發現請求獲得的結果是:git

<?xml version="1.0" encoding="UTF-8"?>
<response>
	<ToUserName><SimpleXMLElement><FromUserName><SimpleXMLElement/></FromUserName></SimpleXMLElement></ToUserName>
	<FromUserName><SimpleXMLElement><ToUserName><SimpleXMLElement/></ToUserName></SimpleXMLElement></FromUserName>
	<CreateTime>1416207112</CreateTime>
	<MsgType>music</MsgType>
	<Music>
		<Title>maps maroon5</Title>
		<Description>120976464.mp3?xcode=7ba3137f5fd742bcba7a6f5a2ffb7764172503013bacbdc8</Description>
		<MusicUrl>http://zhangmenshiting.baidu.com/data2/music/120976464/120976464.mp3?xcode=7ba3137f5fd742bcba7a6f5a2ffb7764172503013bacbdc8</MusicUrl>
		<HQMusicUrl>http://zhangmenshiting.baidu.com/data2/music/120976464/120976464.mp3?xcode=7ba3137f5fd742bcba7a6f5a2ffb7764172503013bacbdc8</HQMusicUrl>
	</Music>
</response>

問題就來了,微信須要的格式是前外層以 <xml>...</xml> 來定義的,後來終於在 Response 裏面的 formatters 發現了信息,它裏面定義了每一個類相應的信息,咱們能夠經過手動指定一些信息來覆蓋掉系統默認的。github

Yii::$app->response->formatters = [Response::FORMAT_XML=> ['class'=>yii\web\XmlResponseFormatter', 'rootTag'=>'xml'];

經過這樣設置以後,最外層的 response 終於變成了 xml,又發現了一個問題,那就是個人內容裏面根本就沒有SimpleXMLElement相關的東西,這個怎麼會多出來?回看了一下邏輯代碼發現有:web

$postObject = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);

最後只能在return的時候加上類型轉換爲字符串,這下終於恢復正常了。json

return [
	"ToUserName"=>(string)$postObject->FromUserName,
	"FromUserName"=>(string)$postObject->ToUserName,
	// ...
]

在使用這個的時候有的數據是須要加上 CDataSection(<![CDATA[ ... ]]>) 的,由於否則若是內容裏面帶有了 < 這種就會出問題。這個確實讓我頭疼了好久,首先看了一下源代碼原來的類 XmlResponseFormatter, 確實沒法知足相應的需求,知足不了需求就只能擴展了bootstrap

step1. 在應用下建立一個 component 目錄 step2. 在component目錄下新建一個 MyXmlResponseFormatter.php 的文件 step3. 實現這個類api

<?php

namespace weixin\component;

use yii\web\XmlResponseFormatter;
use DOMElement;
use DOMText;
use yii\helpers\StringHelper;
use yii\base\Arrayable;
use DOMCdataSection;

class MyXmlResponseFormatter extends XmlResponseFormatter{
    public $rootTag = "xml";  // 這裏我就能夠把 rootTag 的默認值修改爲 xml 了
    /**
     * 若是須要使用 CDATA 那就須要把原來的數據轉成數組,而且數組含有如下key
     * ,咱們就把這個節點添加成一個 DOMCdataSection 
     */
    const CDATA = '---cdata---';  // 這個是是否使用CDATA 的下標
     /**
     * @param DOMElement $element
     * @param mixed $data
     */
    protected function buildXml($element, $data)
    {
        if (is_object($data)) {
            // 這裏保持原來的代碼不變
        } elseif (is_array($data)) {
            foreach ($data as $name => $value) {
                if (is_int($name) && is_object($value)) {
                    $this->buildXml($element, $value);
                } elseif (is_array($value) || is_object($value)) {
                    $child = new DOMElement(is_int($name) ? $this->itemTag : $name);
                    $element->appendChild($child);
                    // 主要就是修改這一個點,若是值是一個數組,而且含有 CDATA 的,那麼就直接建立一個 CdataSection 節點,
                    // 而不把它自己看成列表再回調。
                    if(array_key_exists(self::CDATA, $value)){
                        $child->appendChild(new DOMCdataSection((string) $value[0]));
                    }else{
                        $this->buildXml($child, $value);
                    }
                } else {
                    $child = new DOMElement(is_int($name) ? $this->itemTag : $name);
                    $element->appendChild($child);
                    $child->appendChild(new DOMText((string) $value));
                }
            }
        } else {
            $element->appendChild(new DOMText((string) $data));
        }
    }
}

step4. 修改默認的 xml 解析所使用的類爲新建的擴展類數組

Yii::$app->response->formatters = [
	Response::FORMAT_XML=> ['class'=>'weixin\component\MyXmlResponseFormatter']
];

step5. 若是說字符串須要使用 CDATA 的時候須要設置xcode

use weixin\component\MyXmlResponseFormatter as MXRF;

return [
    "ToUserName"=>[$postObj->FromUserName,MXRF::CDATA=>true],
    "FromUserName"=>[$postObj->ToUserName,MXRF::CDATA=>true],
    "CreateTime"=>time(),
    "MsgType"=>"music",
    "Music"=>[
        "Title"=>[$recognition,MXRF::CDATA=>true],
        "Description"=>[$decode,MXRF::CDATA=>true],
        "MusicUrl"=>[$musicurl,MXRF::CDATA=>true],
        "HQMusicUrl"=>[$musicurl,MXRF::CDATA=>true],
    ]
];

通過本次的修改算是對如何修改和擴展Yii2 有了必定的認識。

在原有的Yii2框架上,新建一個api應用

在作東西的時候須要清晰的結構和邏輯,這樣作出來的東西相對來講會比較漂亮,因此爲了api咱們可能得新建一個應用,這裏面全是api相關的程序,我經過Google 「yii2 create new application」,「yii2 add new application」,都沒有找到相要的答案,因而只能開動本身的腦筋了。

$ cp -a environments/dev/frontend environments/dev/api

$ cp -a environments/prod/frontend environments/prod/api

# file: environments/index.php
<?php
// 這裏僅說明了我添加了哪些信息,不須要刪除任何信息,只須要添加。
return [
    'Development' => [
        'setWritable' => [
        	// ... 在原來的後面添加上
            'api/runtime',
            'api/web/assets'
        ],
        'setCookieValidationKey' => [
            // ... 在原來的後面添加上
            'api/config/main-local.php',
        ],
    ],
    'Production' => [
       	// 這裏和上面同樣的添加
    ],
];

建立相應的目錄:
$ mkdir -p api/{assets,config,controllers,models,runtime,web/assets}
$ touch api/{assets,config,controllers,models,runtime,web/assets}/.gitkeep

複製配置文件:
$ cp -a frontend/config/params.php frontend/config/main.php frontend/config/bootstrap.php frontend/config/.gitignore api/config 
$ cp frontend/runtime/.gitignore api/runtime/ 
$ cp frontend/web/.gitignore api/web

# file api/config/main.php

return [
	'id' => 'app-api',
	// ... 
	'controllerNamespace' => 'api\controllers',
]

# file common/config/bootstrap.php
Yii::setAlias('api', dirname(dirname(__DIR__)) . '/api');

// 配置的其它信息看本身的需求而定

$ ./init

新建一個Controller來測試一下:

# file: api/controllers/SiteController.php
<?php
namespace api\controllers;

use yii\web\Controller;

class SiteController extends Controller {
    public $layout = false;

    public function actionIndex(){
        return "test";
    }
}

而後經過瀏覽器訪問相應的地址 http://hostname/api/web/index.php?r=site/index 能出來 test 則表明 ok 啦,以上步驟都是一步步的嘗試和查看源代碼得來的,可能會有不規範的地方,如有不對的地方請到 Github (yii2-usage)上留言。

配置Yii2 request Parser使之能夠經過Yii::$app->request->post()來接收 xml 和 json的數據

你們都知道 Yii2 接收 POST 數據是使用 Yii::$app->request->post();,可是若是發送過來的數據格式是 json 或 xml 的時候,經過這個方法就沒法獲取到數據了,Yii2 這麼強大的組件型框架確定想到了這一點。

對於 json 的解析 Yii2 已經寫好了 [[JsonResponseFormatter]] ,在配置文件裏面配置一下便可使用。

# file app/config/main.php

'components' =>[
	'request' => [
        'parsers' => [
            'application/json' => 'yii\web\JsonParser',
            'text/json' => 'yii\web\JsonParser',
        ],
	],
],

配置好以後訪問提交過來的數據就太簡單啦

# json raw data
{"username": "bob"}

# access data
$post_data = Yii::$app->request->post();
echo $post_data["username"];

# or 
echo Yii::$app->request->post("username");

經過框架找到了 JsonParser 所在的目錄發現了一個接口 [[RequestParserInterface]] ,並在 JsonParser 的同級目錄下未找到 XmlParser 的類,基於 Yii2 組件框架,因而本身來寫一個 Parser 用來解析 xml 數據,只須要實現接口提供的方法便可 [[RequestParserInterface::parse()]] ,這裏最主要的是將 xml 的數據轉換成數組的一個過程,經過 Google 找了不少 「xml to array」,大部分的解析結果我並不滿意,要麼是功能不完整,要麼就是結果不許確,但最終我仍是找到了比較完善的 「xml to array」 的類 xml2array,建立一個類,實現 xml2array 的功能。

# file: common/tools/Xml2Array.php  目錄不存在的話須要建立

<?php
namespace common\tools;

class Xml2Array
{
	// 把那個網站上的方法複製過來,並在方法前面加上 public static 把方法名換成 go 
	// 註釋部分建議也複製過來,這對之後追溯代碼的出處頗有用。
	// 替換以後的基本格式爲:
	public static function go($contents, $get_attributes=1, $priority = 'tag')
	{
		... ... 
	}
}


# file common/components/XmlRequestParser.php
namespace common\components;

use yii\web\RequestParserInterface;
use common\tools\Xml2Array;

class XmlRequestParser implements  RequestParserInterface
{

    public function parse($rawBody, $contentType)
    {
        $content = Xml2Array::go($rawBody);

        return array_pop($content);
    }
}

# file app/config/main.php

'components' =>[
	'request' => [
        'parsers' => [
            'text/xml' => 'common\components\XmlRequestParser',
            'application/xml' => 'common\components\XmlRequestParser',

            'application/json' => 'yii\web\JsonParser',
            'text/json' => 'yii\web\JsonParser',
        ],
	],
],

通過上面的三步以後,就能夠直接訪問提交過來的 xml 數據了。

# raw data
<xml><username><![CDATA[bob]]></username></xml>

# access data
Yii::$app->request->post('username');

這樣無論別人傳過來的數據是 html、json、xml 格式均可以很是方便的獲取了,在和各類接口打交道的時候用上這個能夠方便太多了。

使用 TimestampBehavior 來自動填充 created_at 和 updated_at 的一個坑

Yii2 官方默認提供了一個 TimestampBehavior 來方便咱們來自動填充 created_at 和 updated_at ,它會自動在你插入新數據的時候幫你填充這兩個值爲當前時間,固然你也能夠設置成別的時間,當你更新數據的時候它會自動把 updated_at 改爲最後更新的時間。

我建立了一個 user_weixin 表,而後設置 created_at 和 updated_at 兩個字段爲 datetime 類型,並在相應的 Model 裏面使用上 TimestampBehavior

# file app/models/UserWeixin.php

<?php
 	... ... 
use yii\behaviors\TimestampBehavior;

class UserWeixin extends ActiveRecord {

	public function behaviors(){
		return [TimestampBehavior::className()];
	}
}

而後正常的調用保存數據,發現那兩個字段的值均爲 ‘0000-00-00 00:00:00’,看到這個感受甚是奇怪,去看了一下默認生成的用戶模型,common/models/User.php,發現它也沒有作其它的別的操做就能夠的啊,我這樣爲何不行呢,去看了一下表結構,發現系統建立的 user 表的兩個字段是使用的 int 類型,而不是 datetime,因而把 user_weixin 表的兩個字段也改爲了 int 類型,再測試一次發現好了。

不甘心的我去看了一下 TimestampBehavior 類的註釋,發現確實沒有說明這個問題,因此你們在聲明 created_at 和 updated_at 字段類型的時候須要注意一下

相關文章
相關標籤/搜索