Commit 5a8e4b64 by Qiang Xue

Added XmlResponseFormatter.

parent fa198518
......@@ -132,7 +132,7 @@ class Controller extends Component
* the route will start from the application; otherwise, it will start from the parent module of this controller.
* @param string $route the route to be handled, e.g., 'view', 'comment/view', '/admin/comment/view'.
* @param array $params the parameters to be passed to the action.
* @return integer the status code returned by the action execution. 0 means normal, and other values mean abnormal.
* @return mixed the result of the action
* @see runAction
* @see forward
*/
......
......@@ -34,6 +34,12 @@ class Response extends \yii\base\Response
*/
public $format = self::FORMAT_HTML;
/**
* @var array the formatters for converting data into the response content of the specified [[format]].
* The array keys are the format names, and the array values are the corresponding configurations
* for creating the formatter objects.
*/
public $formatters;
/**
* @var string the charset of the text response. If not set, it will use
* the value of [[Application::charset]].
*/
......@@ -134,7 +140,9 @@ class Response extends \yii\base\Response
*/
private $_headers;
/**
* Initializes this component.
*/
public function init()
{
if ($this->version === null) {
......@@ -157,6 +165,13 @@ class Response extends \yii\base\Response
return $this->_statusCode;
}
/**
* Sets the response status code.
* This method will set the corresponding status text if `$text` is null.
* @param integer $value the status code
* @param string $text the status text. If not set, it will be set automatically based on the status code.
* @throws InvalidParamException if the status code is invalid.
*/
public function setStatusCode($value, $text = null)
{
$this->_statusCode = (int)$value;
......@@ -194,9 +209,13 @@ class Response extends \yii\base\Response
$this->clear();
}
/**
* Clears the headers, cookies, content, status code of the response.
*/
public function clear()
{
$this->_headers = null;
$this->_cookies = null;
$this->_statusCode = null;
$this->_content = null;
$this->statusText = null;
......@@ -643,8 +662,9 @@ class Response extends \yii\base\Response
* The existing content will be overwritten.
* Depending on the value of [[format]], the data will be properly formatted.
* @param mixed $data the data that needs to be converted into the response content.
* @param string $format the format of the response. The following formats are
* supported by the default implementation:
* @param string $format the format of the response. The [[formatters]] property specifies
* the supported formats and the corresponding formatters. Additionally, the following formats are
* supported if they are not found in [[formatters]]:
*
* - [[FORMAT_RAW]]: the data will be treated as the response content without any conversion.
* No extra HTTP header will be added.
......@@ -656,8 +676,8 @@ class Response extends \yii\base\Response
* header will be set as "text/javascript". Note that in this case `$data` must be an array
* with "data" and "callback" elements. The former refers to the actual data to be sent,
* while the latter refers to the name of the JavaScript callback.
* - [[FORMAT_XML]]: the data will be converted into XML format, and the "Content-Type"
* header will be set as "application/xml" if no previous "Content-Type" header is set.
* - [[FORMAT_XML]]: the data will be converted into XML format. Please refer to [[XmlResponseFormatter]]
* for more details.
*/
public function setContent($data, $format = null)
{
......@@ -667,8 +687,28 @@ class Response extends \yii\base\Response
$this->_content = $this->formatContent($data, $format);
}
/**
* Formats the given data as the specified format.
* @param mixed $data the data to be formatted.
* @param string $format the format to use.
* @return string the formatting result.
* @throws InvalidParamException if `$format` is not supported
* @throws InvalidConfigException if the formatter for the specified format is invalid
*/
protected function formatContent($data, $format)
{
if (isset($this->formatters[$format])) {
$formatter = $this->formatters[$format];
if (!is_object($formatter)) {
$formatter = Yii::createObject($formatter);
}
if ($formatter instanceof ResponseFormatter) {
return $formatter->format($this, $data);
} else {
throw new InvalidConfigException("The '$format' response formatter is invalid. It must implement the ResponseFormatter interface.");
}
}
switch ($this->format) {
case self::FORMAT_RAW:
return $data;
......@@ -686,7 +726,7 @@ class Response extends \yii\base\Response
throw new InvalidParamException("The 'jsonp' response requires that the data be an array consisting of both 'data' and 'callback' elements.");
}
case self::FORMAT_XML:
// todo
return Yii::createObject('yii\web\XmlResponseFormatter')->format($this, $data);
default:
throw new InvalidConfigException("Unsupported response format: $format");
}
......
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\web;
/**
* ResponseFormatter specifies the interface needed to format data for a Web response object.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
interface ResponseFormatter
{
/**
* Formats the given data for the response.
* @param Response $response the response object that will accept the formatted result
* @param mixed $data the data to be formatted
* @return string the formatted result
*/
function format($response, $data);
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\web;
use DOMDocument;
use DOMElement;
use DOMText;
use yii\base\Arrayable;
use yii\base\Component;
use yii\helpers\StringHelper;
/**
* XmlResponseFormatter formats the given data into an XML response content.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class XmlResponseFormatter extends Component implements ResponseFormatter
{
/**
* @var string the Content-Type header for the response
*/
public $contentType = 'application/xml';
/**
* @var string the XML version
*/
public $version = '1.0';
/**
* @var string the XML encoding. If not set, it will use the value of [[Response::charset]].
*/
public $encoding;
/**
* @var string the name of the root element.
*/
public $rootTag = 'response';
/**
* @var string the name of the elements that represent the array elements with numeric keys.
*/
public $itemTag = 'item';
/**
* Formats the given data for the response.
* @param Response $response the response object that will accept the formatted result
* @param mixed $data the data to be formatted
* @return string the formatted result
*/
public function format($response, $data)
{
$response->getHeaders()->set('Content-Type', $this->contentType);
$dom = new DOMDocument($this->version, $this->encoding === null ? $response->charset : $this->encoding);
$root = new DOMElement($this->rootTag);
$dom->appendChild($root);
$this->buildXml($root, $data);
return $dom->saveXML();
}
/**
* @param DOMElement $element
* @param mixed $data
*/
protected function buildXml($element, $data)
{
if (is_object($data)) {
$child = new DOMElement(StringHelper::basename(get_class($data)));
$element->appendChild($child);
if ($data instanceof Arrayable) {
$this->buildXml($child, $data->toArray());
} else {
$array = array();
foreach ($data as $name => $value) {
$array[$name] = $value;
}
$this->buildXml($child, $array);
}
} 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);
$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));
}
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yiiunit\framework\web;
use yii\base\Object;
use yii\web\Response;
use yii\web\XmlResponseFormatter;
class Post extends Object
{
public $id;
public $title;
public function __construct($id, $title)
{
$this->id = $id;
$this->title = $title;
}
}
/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class XmlResponseFormatterTest extends \yiiunit\TestCase
{
/**
* @var Response
*/
public $response;
/**
* @var XmlResponseFormatter
*/
public $formatter;
protected function setUp()
{
$this->mockApplication();
$this->response = new Response;
$this->formatter = new XmlResponseFormatter;
}
public function testFormatScalars()
{
$head = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
$xml = $head . "<response></response>\n";
$this->assertEquals($xml, $this->formatter->format($this->response, null));
$xml = $head . "<response>1</response>\n";
$this->assertEquals($xml, $this->formatter->format($this->response, 1));
$xml = $head . "<response>abc</response>\n";
$this->assertEquals($xml, $this->formatter->format($this->response, 'abc'));
$xml = $head . "<response>1</response>\n";
$this->assertEquals($xml, $this->formatter->format($this->response, true));
}
public function testFormatArrays()
{
$head = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
$xml = $head . "<response/>\n";
$this->assertEquals($xml, $this->formatter->format($this->response, array()));
$xml = $head . "<response><item>1</item><item>abc</item></response>\n";
$this->assertEquals($xml, $this->formatter->format($this->response, array(1, 'abc')));
$xml = $head . "<response><a>1</a><b>abc</b></response>\n";
$this->assertEquals($xml, $this->formatter->format($this->response, array(
'a' => 1,
'b' => 'abc',
)));
$xml = $head . "<response><item>1</item><item>abc</item><item><item>2</item><item>def</item></item><item>1</item></response>\n";
$this->assertEquals($xml, $this->formatter->format($this->response, array(
1,
'abc',
array(2, 'def'),
true,
)));
$xml = $head . "<response><a>1</a><b>abc</b><c><item>2</item><item>def</item></c><item>1</item></response>\n";
$this->assertEquals($xml, $this->formatter->format($this->response, array(
'a' => 1,
'b' => 'abc',
'c' => array(2, 'def'),
true,
)));
}
public function testFormatObjects()
{
$head = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
$xml = $head . "<response><Post><id>123</id><title>abc</title></Post></response>\n";
$this->assertEquals($xml, $this->formatter->format($this->response, new Post(123, 'abc')));
$xml = $head . "<response><Post><id>123</id><title>abc</title></Post><Post><id>456</id><title>def</title></Post></response>\n";
$this->assertEquals($xml, $this->formatter->format($this->response, array(
new Post(123, 'abc'),
new Post(456, 'def'),
)));
$xml = $head . "<response><Post><id>123</id><title>abc</title></Post><a><Post><id>456</id><title>def</title></Post></a></response>\n";
$this->assertEquals($xml, $this->formatter->format($this->response, array(
new Post(123, 'abc'),
'a' => new Post(456, 'def'),
)));
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment