最近一直在学习Yii2框架,多是一直以来对它的青睐,让我难以对其它框架再产生兴趣,学习中遇到了许多问题,因而把问题和解决办法也记录下来,这样方便之后复习和交流。 ## 目录 扩展XmlResponseFormatter
在原有的Yii2框架上,新建一个api应用
配置Yii2 request Parser使之能够经过Yii::$app->request->post()来接收 xml 和 json的数据
使用 TimestampBehavior 来自动填充 created_at 和 updated_atphp
在作微信接口测试的时候发现,每次返回数据的时候都是本身写的 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 有了必定的认识。
在作东西的时候须要清晰的结构和逻辑,这样作出来的东西相对来讲会比较漂亮,因此为了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
接收 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 格式均可以很是方便的获取了,在和各类接口打交道的时候用上这个能够方便太多了。
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 字段类型的时候须要注意一下。