Commit a951e1c8 by Qiang Xue

Replaced Jsonable with Arrayable.

Added support for different response formats. Support for error response in different formats.
parent 075a6b65
......@@ -6,6 +6,7 @@
*/
namespace yii;
use yii\base\Arrayable;
use yii\base\Exception;
use yii\base\InvalidConfigException;
use yii\base\InvalidParamException;
......@@ -636,6 +637,31 @@ class YiiBase
{
return get_object_vars($object);
}
/**
* Converts the object into an array.
* @param object|array $object the object to be converted into an array
* @param boolean $recursive whether to recursively converts properties which are objects into arrays.
* @return array the array representation of the object
*/
public static function toArray($object, $recursive = true)
{
if ($object instanceof Arrayable) {
$object = $object->toArray();
if (!$recursive) {
return $object;
}
}
$result = array();
foreach ($object as $key => $value) {
if ($recursive && (is_array($value) || is_object($value))) {
$result[$key] = static::toArray($value, true);
} else {
$result[$key] = $value;
}
}
return $result;
}
}
YiiBase::$aliases = array(
......
......@@ -19,10 +19,6 @@ use yii\web\HttpException;
abstract class Application extends Module
{
/**
* @event ResponseEvent an event that is triggered after [[handle()]] returns a response.
*/
const EVENT_RESPONSE = 'response';
/**
* @var string the application name.
*/
public $name = 'My Application';
......@@ -144,11 +140,7 @@ abstract class Application extends Module
public function run()
{
$response = $this->handle($this->getRequest());
$event = new ResponseEvent($response);
$this->trigger(self::EVENT_RESPONSE, $event);
$event->response->send();
$response->send();
return $response->exitStatus;
}
......@@ -422,18 +414,19 @@ abstract class Application extends Module
*/
public function handleFatalError()
{
// load ErrorException manually here because autoloading them will not work
// when error occurs while autoloading a class
if (!class_exists('\\yii\\base\\Exception', false)) {
require_once(__DIR__ . '/Exception.php');
}
if (!class_exists('\\yii\\base\\ErrorException', false)) {
require_once(__DIR__ . '/ErrorException.php');
}
$error = error_get_last();
if (ErrorException::isFatalError($error)) {
unset($this->_memoryReserve);
// load ErrorException manually here because autoloading them will not work
// when error occurs while autoloading a class
if (!class_exists('\\yii\\base\\Exception', false)) {
require_once(__DIR__ . '/Exception.php');
}
if (!class_exists('\\yii\\base\\ErrorException', false)) {
require_once(__DIR__ . '/ErrorException.php');
}
$exception = new ErrorException($error['message'], $error['type'], $error['type'], $error['file'], $error['line']);
// use error_log because it's too late to use Yii log
error_log($exception);
......
......@@ -8,15 +8,16 @@
namespace yii\base;
/**
* Jsonable should be implemented by classes that need to be represented in JSON format.
* Arrayable should be implemented by classes that need to be represented in array format.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
interface Jsonable
interface Arrayable
{
/**
* @return string the JSON representation of this object
* Converts the object into an array.
* @return array the array representation of this object
*/
public function toJson();
public function toArray();
}
......@@ -9,7 +9,6 @@ namespace yii\base;
use Yii;
use yii\web\HttpException;
use yii\web\Response;
/**
* ErrorHandler handles uncaught PHP errors and exceptions.
......@@ -90,31 +89,41 @@ class ErrorHandler extends Component
$useErrorView = !YII_DEBUG || $exception instanceof UserException;
$response = Yii::$app->getResponse();
if ($useErrorView && $this->errorAction !== null) {
$result = Yii::$app->runAction($this->errorAction);
if ($result instanceof Response) {
$response = $result;
} else {
$response = new Response;
$response->content = $result;
$response->setContent($result);
}
} else {
$response = new Response;
} elseif ($response->format === \yii\web\Response::FORMAT_HTML) {
if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') {
// AJAX request
$response->content = Yii::$app->renderException($exception);
$response->setContent(Yii::$app->renderException($exception));
} else {
// if there is an error during error rendering it's useful to
// display PHP error in debug mode instead of a blank screen
if (YII_DEBUG) {
ini_set('display_errors', 1);
}
$file = $useErrorView ? $this->errorView : $this->exceptionView;
$response->content = $this->renderFile($file, array(
$response->setContent($this->renderFile($file, array(
'exception' => $exception,
));
)));
}
} else {
if ($exception instanceof Exception) {
$content = $exception->toArray();
} else {
$content = array(
'type' => get_class($exception),
'name' => 'Exception',
'message' => $exception->getMessage(),
'code' => $exception->getCode(),
);
}
$response->setContent($content);
}
if ($exception instanceof HttpException) {
......
......@@ -13,7 +13,7 @@ namespace yii\base;
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Exception extends \Exception
class Exception extends \Exception implements Arrayable
{
/**
* @return string the user-friendly name of this exception
......@@ -22,4 +22,18 @@ class Exception extends \Exception
{
return \Yii::t('yii', 'Exception');
}
/**
* Returns the array representation of this object.
* @return array the array representation of this object.
*/
public function toArray()
{
return array(
'type' => get_class($this),
'name' => $this->getName(),
'message' => $this->getMessage(),
'code' => $this->getCode(),
);
}
}
......@@ -7,10 +7,10 @@
namespace yii\base;
use Yii;
use ArrayObject;
use ArrayIterator;
use yii\helpers\Inflector;
use yii\helpers\Json;
use yii\validators\RequiredValidator;
use yii\validators\Validator;
......@@ -42,7 +42,7 @@ use yii\validators\Validator;
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Model extends Component implements \IteratorAggregate, \ArrayAccess, Jsonable
class Model extends Component implements \IteratorAggregate, \ArrayAccess
{
/**
* @event ModelEvent an event raised at the beginning of [[validate()]]. You may set
......@@ -639,13 +639,13 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess, Jsona
}
/**
* Returns the JSON representation of this object.
* Converts the object into an array.
* The default implementation will return [[attributes]].
* @return string the JSON representation of this object.
* @return array the array representation of the object
*/
public function toJson()
public function toArray()
{
return Json::encode($this->getAttributes());
return $this->getAttributes();
}
/**
......
......@@ -8,14 +8,13 @@
namespace yii\base;
use Yii;
use yii\helpers\Json;
/**
* @include @yii/base/Object.md
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Object implements Jsonable
class Object implements Arrayable
{
/**
* @return string the fully qualified name of this class.
......@@ -221,12 +220,13 @@ class Object implements Jsonable
}
/**
* Returns the JSON representation of this object.
* The default implementation will return all public member variables.
* @return string the JSON representation of this object.
* Converts the object into an array.
* The default implementation will return all public property values as an array.
* However, if the object is traversable, it will return the data obtained by the data iteration.
* @return array the array representation of the object
*/
public function toJson()
public function toArray()
{
return Json::encode(Yii::getObjectVars($this));
return Yii::toArray($this, false);
}
}
......@@ -14,12 +14,24 @@ namespace yii\base;
class Response extends Component
{
/**
* @event ResponseEvent an event that is triggered by [[send()]] before it sends the response to client.
* You may respond to this event to modify the response before it is sent out.
*/
const EVENT_SEND = 'send';
/**
* @var integer the exit status. Exit statuses should be in the range 0 to 254.
* The status 0 means the program terminates successfully.
*/
public $exitStatus = 0;
/**
* Sends the response to client.
* This method will trigger the [[EVENT_SEND]] event. Please make sure you call
* the parent implementation first if you override this method.
*/
public function send()
{
$this->trigger(self::EVENT_SEND, new ResponseEvent($this));
}
}
......@@ -8,7 +8,7 @@
namespace yii\helpers\base;
use yii\base\InvalidParamException;
use yii\base\Jsonable;
use yii\base\Arrayable;
use yii\web\JsExpression;
/**
......@@ -91,9 +91,8 @@ class Json
$token = '!{[' . count($expressions) . ']}!';
$expressions['"' . $token . '"'] = $data->expression;
return $token;
} elseif ($data instanceof Jsonable) {
return $data->toJson();
} else {
$data = $data instanceof Arrayable ? $data->toArray() : get_object_vars($data);
$result = array();
foreach ($data as $key => $value) {
if (is_array($value) || is_object($value)) {
......
......@@ -71,7 +71,9 @@ class Application extends \yii\base\Application
return $result;
} else {
$response = $this->getResponse();
$response->content = $result;
if ($result !== null) {
$response->setContent($result);
}
return $response;
}
} catch (InvalidRouteException $e) {
......
......@@ -51,4 +51,15 @@ class HttpException extends UserException
return 'Error';
}
}
/**
* Returns the array representation of this object.
* @return array the array representation of this object.
*/
public function toArray()
{
$array = parent::toArray();
$array['status'] = $this->statusCode;
return $array;
}
}
......@@ -8,6 +8,7 @@
namespace yii\web;
use Yii;
use yii\base\InvalidConfigException;
use yii\web\HttpException;
use yii\base\InvalidParamException;
use yii\helpers\FileHelper;
......@@ -23,6 +24,21 @@ use yii\helpers\StringHelper;
*/
class Response extends \yii\base\Response
{
const FORMAT_RAW = 'raw';
const FORMAT_HTML = 'html';
const FORMAT_JSON = 'json';
const FORMAT_JSONP = 'jsonp';
const FORMAT_XML = 'xml';
/**
* @var string the response format.
*/
public $format = self::FORMAT_HTML;
/**
* @var string the charset of the text response. If not set, it will use
* the value of [[Application::charset]].
*/
public $charset;
/**
* @var integer the HTTP status code that should be used when redirecting in AJAX mode.
* This is used by [[redirect()]]. A 2xx code should normally be used for this purpose
......@@ -33,10 +49,6 @@ class Response extends \yii\base\Response
/**
* @var string
*/
public $content;
/**
* @var string
*/
public $statusText;
/**
* @var string the version of the HTTP protocol to use. If not set, it will be determined via `$_SERVER['SERVER_PROTOCOL']`,
......@@ -133,6 +145,9 @@ class Response extends \yii\base\Response
$this->version = '1.1';
}
}
if ($this->charset === null) {
$this->charset = Yii::$app->charset;
}
}
/**
......@@ -172,7 +187,7 @@ class Response extends \yii\base\Response
public function renderJson($data)
{
$this->getHeaders()->set('Content-Type', 'application/json');
$this->content = Json::encode($data);
$this->setContent(Json::encode($data));
$this->send();
}
......@@ -180,16 +195,16 @@ class Response extends \yii\base\Response
{
$this->getHeaders()->set('Content-Type', 'text/javascript');
$data = Json::encode($data);
$this->content = "$callbackName($data);";
$this->setContent("$callbackName($data);");
$this->send();
}
/**
* Sends the response to the client.
* @return boolean true if the response was sent
*/
public function send()
{
parent::send();
$this->sendHeaders();
$this->sendContent();
$this->clear();
......@@ -199,8 +214,8 @@ class Response extends \yii\base\Response
{
$this->_headers = null;
$this->_statusCode = null;
$this->_content = null;
$this->statusText = null;
$this->content = null;
}
/**
......@@ -253,7 +268,7 @@ class Response extends \yii\base\Response
*/
protected function sendContent()
{
echo $this->content;
echo $this->getContent();
}
/**
......@@ -304,10 +319,10 @@ class Response extends \yii\base\Response
if ($begin !=0 || $end != $contentLength - 1) {
$this->setStatusCode(206);
$headers->set('Content-Range', "bytes $begin-$end/$contentLength");
$this->content = StringHelper::substr($content, $begin, $end - $begin + 1);
$this->setContent(StringHelper::substr($content, $begin, $end - $begin + 1), self::FORMAT_RAW);
} else {
$this->setStatusCode(200);
$this->content = $content;
$this->setContent($content, self::FORMAT_RAW);
}
$this->send();
......@@ -628,4 +643,44 @@ class Response extends \yii\base\Response
{
return in_array($this->getStatusCode(), array(201, 204, 304));
}
private $_content;
public function getContent()
{
return $this->_content;
}
public function setContent($value, $format = null)
{
if ($format !== null) {
$this->format = $format;
}
$this->_content = $this->formatContent($value, $format);
}
protected function formatContent($data, $format)
{
switch ($this->format) {
case self::FORMAT_RAW:
return $data;
case self::FORMAT_HTML:
$this->getHeaders()->setDefault('Content-Type', 'text/html; charset=' . $this->charset);
return $data;
case self::FORMAT_JSON:
$this->getHeaders()->set('Content-Type', 'application/json');
return Json::encode($data);
case self::FORMAT_JSONP:
$this->getHeaders()->set('Content-Type', 'text/javascript');
if (is_array($data) && isset($data['data'], $data['callback'])) {
return sprintf('%s(%s);', $data['callback'], Json::encode($data['data']));
} else {
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
default:
throw new InvalidConfigException("Unsupported response format: $format");
}
}
}
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