1. 云栖社区>
  2. PHP教程>
  3. 正文

使用Yii2时遇到的实际问题

作者:用户 来源:互联网 时间:2017-12-01 16:54:30

新建记录

使用Yii2时遇到的实际问题 - 摘要: 本文讲的是使用Yii2时遇到的实际问题, 最近一直在学习Yii2框架,可能是一直以来对它的青睐,让我难以对其它框架再产生兴趣,学习中遇到了许多问题,于是把问题和解决办法也记录下来,这样方便以后复习和交流。 目录 扩展XmlResponseFormatter 在原有的Yii2框架

最近一直在学习Yii2框架,可能是一直以来对它的青睐,让我难以对其它框架再产生兴趣,学习中遇到了许多问题,于是把问题和解决办法也记录下来,这样方便以后复习和交流。

目录

  • 扩展XmlResponseFormatter
  • 在原有的Yii2框架上,新建一个api应用
  • 配置Yii2 request Parser使之可以通过Yii::$app->request->post()来接收 xml 和 json的数据

扩展XmlResponseFormatter

在做微信接口测试的时候发现,每次返回数据的时候都是自己写的 xml 信息然后 echo 出来,今天突然看到了Yii::$app->response->format = Response::FORMAT_XML;原来通过这个就可以设置返回的数据为 xml ,当然response这个类在 Controller 里面是没有加载的,所以首先得加载一下use yii/web/Response;,最后把需要返回的数据用数组的形式来返回即可:

<?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,			]		];}

这样使用之后发现请求得到的结果是:

<?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发现了信息,它里面定义了每个类相应的信息,我们可以通过手动指定一些信息来覆盖掉系统默认的。

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

通过这样设置之后,最外层的 response 终于变成了 xml,又发现了一个问题,那就是我的内容里面根本就没有SimpleXMLElement相关的东西,这个怎么会多出来。回看了一下逻辑代码发现有:

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

最后只能在return的时候加上类型转换为字符串,这下终于恢复正常了。

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

在使用这个的时候有的数据是需要加上 CDataSection(<![CDATA[ ... ]]>) 的,因为不然如果内容里面带有了<这种就会出问题。这个确实让我头疼了很久,首先看了一下源代码原来的类XmlResponseFormatter, 确实无法满足相应的需求,满足不了需求就只能扩展了

step1. 在应用下创建一个 component 目录

step2. 在component目录下新建一个 MyXmlResponseFormatter.php 的文件

step3. 实现这个类

<?phpnamespace 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 的时候需要设置

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.phpreturn [	'id' => 'app-api',	// ...	'controllerNamespace' => 'api/controllers',]# file common/config/bootstrap.phpYii::setAlias('api', dirname(dirname(__DIR__)) . '/api');// 配置的其它信息看自己的需求而定

$ ./init

新建一个Controller来测试一下:

# file: api/controllers/SiteController.php<?phpnamespace 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();,但是如果发送过来的数据格式是jsonxml的时候,通过这个方法就无法获取到数据了,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"];# orecho 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目录不存在的话需要创建<?phpnamespace common/tools;class Xml2Array{	// 把那个网站上的方法复制过来,并在方法前面加上 public static 把方法名换成 go	// 注释部分建议也复制过来,这对以后追溯代码的出处很有用。	// 替换之后的基本格式为:	public static function go($contents, $get_attributes=1, $priority = 'tag')	{		... ...	}}# file common/components/XmlRequestParser.phpnamespace common/components;use yii/web/RequestParserInterface;use common/tools/Xml2Array;class XmlRequestParser implementsRequestParserInterface{	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 dataYii::$app->request->post('username');

这样不管别人传过来的数据是 html、json、xml 格式都可以非常方便的获取了,在和各种接口打交道的时候用上这个可以方便太多了。

以上是云栖社区小编为您精心准备的的内容,在云栖社区的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索新建 记录 ,以便于您获取更多的相关知识。