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 @@ ...@@ -6,6 +6,7 @@
*/ */
namespace yii; namespace yii;
use yii\base\Arrayable;
use yii\base\Exception; use yii\base\Exception;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use yii\base\InvalidParamException; use yii\base\InvalidParamException;
...@@ -636,6 +637,31 @@ class YiiBase ...@@ -636,6 +637,31 @@ class YiiBase
{ {
return get_object_vars($object); 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( YiiBase::$aliases = array(
......
...@@ -19,10 +19,6 @@ use yii\web\HttpException; ...@@ -19,10 +19,6 @@ use yii\web\HttpException;
abstract class Application extends Module 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. * @var string the application name.
*/ */
public $name = 'My Application'; public $name = 'My Application';
...@@ -144,11 +140,7 @@ abstract class Application extends Module ...@@ -144,11 +140,7 @@ abstract class Application extends Module
public function run() public function run()
{ {
$response = $this->handle($this->getRequest()); $response = $this->handle($this->getRequest());
$response->send();
$event = new ResponseEvent($response);
$this->trigger(self::EVENT_RESPONSE, $event);
$event->response->send();
return $response->exitStatus; return $response->exitStatus;
} }
...@@ -422,10 +414,6 @@ abstract class Application extends Module ...@@ -422,10 +414,6 @@ abstract class Application extends Module
*/ */
public function handleFatalError() public function handleFatalError()
{ {
$error = error_get_last();
if (ErrorException::isFatalError($error)) {
unset($this->_memoryReserve);
// load ErrorException manually here because autoloading them will not work // load ErrorException manually here because autoloading them will not work
// when error occurs while autoloading a class // when error occurs while autoloading a class
if (!class_exists('\\yii\\base\\Exception', false)) { if (!class_exists('\\yii\\base\\Exception', false)) {
...@@ -434,6 +422,11 @@ abstract class Application extends Module ...@@ -434,6 +422,11 @@ abstract class Application extends Module
if (!class_exists('\\yii\\base\\ErrorException', false)) { if (!class_exists('\\yii\\base\\ErrorException', false)) {
require_once(__DIR__ . '/ErrorException.php'); require_once(__DIR__ . '/ErrorException.php');
} }
$error = error_get_last();
if (ErrorException::isFatalError($error)) {
unset($this->_memoryReserve);
$exception = new ErrorException($error['message'], $error['type'], $error['type'], $error['file'], $error['line']); $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 // use error_log because it's too late to use Yii log
error_log($exception); error_log($exception);
......
...@@ -8,15 +8,16 @@ ...@@ -8,15 +8,16 @@
namespace yii\base; 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> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @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; ...@@ -9,7 +9,6 @@ namespace yii\base;
use Yii; use Yii;
use yii\web\HttpException; use yii\web\HttpException;
use yii\web\Response;
/** /**
* ErrorHandler handles uncaught PHP errors and exceptions. * ErrorHandler handles uncaught PHP errors and exceptions.
...@@ -90,31 +89,41 @@ class ErrorHandler extends Component ...@@ -90,31 +89,41 @@ class ErrorHandler extends Component
$useErrorView = !YII_DEBUG || $exception instanceof UserException; $useErrorView = !YII_DEBUG || $exception instanceof UserException;
$response = Yii::$app->getResponse();
if ($useErrorView && $this->errorAction !== null) { if ($useErrorView && $this->errorAction !== null) {
$result = Yii::$app->runAction($this->errorAction); $result = Yii::$app->runAction($this->errorAction);
if ($result instanceof Response) { if ($result instanceof Response) {
$response = $result; $response = $result;
} else { } else {
$response = new Response; $response->setContent($result);
$response->content = $result;
} }
} else { } elseif ($response->format === \yii\web\Response::FORMAT_HTML) {
$response = new Response;
if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') { if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') {
// AJAX request // AJAX request
$response->content = Yii::$app->renderException($exception); $response->setContent(Yii::$app->renderException($exception));
} else { } else {
// if there is an error during error rendering it's useful to // if there is an error during error rendering it's useful to
// display PHP error in debug mode instead of a blank screen // display PHP error in debug mode instead of a blank screen
if (YII_DEBUG) { if (YII_DEBUG) {
ini_set('display_errors', 1); ini_set('display_errors', 1);
} }
$file = $useErrorView ? $this->errorView : $this->exceptionView; $file = $useErrorView ? $this->errorView : $this->exceptionView;
$response->content = $this->renderFile($file, array( $response->setContent($this->renderFile($file, array(
'exception' => $exception, '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) { if ($exception instanceof HttpException) {
......
...@@ -13,7 +13,7 @@ namespace yii\base; ...@@ -13,7 +13,7 @@ namespace yii\base;
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
class Exception extends \Exception class Exception extends \Exception implements Arrayable
{ {
/** /**
* @return string the user-friendly name of this exception * @return string the user-friendly name of this exception
...@@ -22,4 +22,18 @@ class Exception extends \Exception ...@@ -22,4 +22,18 @@ class Exception extends \Exception
{ {
return \Yii::t('yii', '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 @@ ...@@ -7,10 +7,10 @@
namespace yii\base; namespace yii\base;
use Yii;
use ArrayObject; use ArrayObject;
use ArrayIterator; use ArrayIterator;
use yii\helpers\Inflector; use yii\helpers\Inflector;
use yii\helpers\Json;
use yii\validators\RequiredValidator; use yii\validators\RequiredValidator;
use yii\validators\Validator; use yii\validators\Validator;
...@@ -42,7 +42,7 @@ use yii\validators\Validator; ...@@ -42,7 +42,7 @@ use yii\validators\Validator;
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @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 * @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 ...@@ -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]]. * 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 @@ ...@@ -8,14 +8,13 @@
namespace yii\base; namespace yii\base;
use Yii; use Yii;
use yii\helpers\Json;
/** /**
* @include @yii/base/Object.md * @include @yii/base/Object.md
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
class Object implements Jsonable class Object implements Arrayable
{ {
/** /**
* @return string the fully qualified name of this class. * @return string the fully qualified name of this class.
...@@ -221,12 +220,13 @@ class Object implements Jsonable ...@@ -221,12 +220,13 @@ class Object implements Jsonable
} }
/** /**
* Returns the JSON representation of this object. * Converts the object into an array.
* The default implementation will return all public member variables. * The default implementation will return all public property values as an array.
* @return string the JSON representation of this object. * 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; ...@@ -14,12 +14,24 @@ namespace yii\base;
class Response extends Component 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. * @var integer the exit status. Exit statuses should be in the range 0 to 254.
* The status 0 means the program terminates successfully. * The status 0 means the program terminates successfully.
*/ */
public $exitStatus = 0; 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() public function send()
{ {
$this->trigger(self::EVENT_SEND, new ResponseEvent($this));
} }
} }
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
namespace yii\helpers\base; namespace yii\helpers\base;
use yii\base\InvalidParamException; use yii\base\InvalidParamException;
use yii\base\Jsonable; use yii\base\Arrayable;
use yii\web\JsExpression; use yii\web\JsExpression;
/** /**
...@@ -91,9 +91,8 @@ class Json ...@@ -91,9 +91,8 @@ class Json
$token = '!{[' . count($expressions) . ']}!'; $token = '!{[' . count($expressions) . ']}!';
$expressions['"' . $token . '"'] = $data->expression; $expressions['"' . $token . '"'] = $data->expression;
return $token; return $token;
} elseif ($data instanceof Jsonable) {
return $data->toJson();
} else { } else {
$data = $data instanceof Arrayable ? $data->toArray() : get_object_vars($data);
$result = array(); $result = array();
foreach ($data as $key => $value) { foreach ($data as $key => $value) {
if (is_array($value) || is_object($value)) { if (is_array($value) || is_object($value)) {
......
...@@ -71,7 +71,9 @@ class Application extends \yii\base\Application ...@@ -71,7 +71,9 @@ class Application extends \yii\base\Application
return $result; return $result;
} else { } else {
$response = $this->getResponse(); $response = $this->getResponse();
$response->content = $result; if ($result !== null) {
$response->setContent($result);
}
return $response; return $response;
} }
} catch (InvalidRouteException $e) { } catch (InvalidRouteException $e) {
......
...@@ -51,4 +51,15 @@ class HttpException extends UserException ...@@ -51,4 +51,15 @@ class HttpException extends UserException
return 'Error'; 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 @@ ...@@ -8,6 +8,7 @@
namespace yii\web; namespace yii\web;
use Yii; use Yii;
use yii\base\InvalidConfigException;
use yii\web\HttpException; use yii\web\HttpException;
use yii\base\InvalidParamException; use yii\base\InvalidParamException;
use yii\helpers\FileHelper; use yii\helpers\FileHelper;
...@@ -23,6 +24,21 @@ use yii\helpers\StringHelper; ...@@ -23,6 +24,21 @@ use yii\helpers\StringHelper;
*/ */
class Response extends \yii\base\Response 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. * @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 * 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 ...@@ -33,10 +49,6 @@ class Response extends \yii\base\Response
/** /**
* @var string * @var string
*/ */
public $content;
/**
* @var string
*/
public $statusText; public $statusText;
/** /**
* @var string the version of the HTTP protocol to use. If not set, it will be determined via `$_SERVER['SERVER_PROTOCOL']`, * @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 ...@@ -133,6 +145,9 @@ class Response extends \yii\base\Response
$this->version = '1.1'; $this->version = '1.1';
} }
} }
if ($this->charset === null) {
$this->charset = Yii::$app->charset;
}
} }
/** /**
...@@ -172,7 +187,7 @@ class Response extends \yii\base\Response ...@@ -172,7 +187,7 @@ class Response extends \yii\base\Response
public function renderJson($data) public function renderJson($data)
{ {
$this->getHeaders()->set('Content-Type', 'application/json'); $this->getHeaders()->set('Content-Type', 'application/json');
$this->content = Json::encode($data); $this->setContent(Json::encode($data));
$this->send(); $this->send();
} }
...@@ -180,16 +195,16 @@ class Response extends \yii\base\Response ...@@ -180,16 +195,16 @@ class Response extends \yii\base\Response
{ {
$this->getHeaders()->set('Content-Type', 'text/javascript'); $this->getHeaders()->set('Content-Type', 'text/javascript');
$data = Json::encode($data); $data = Json::encode($data);
$this->content = "$callbackName($data);"; $this->setContent("$callbackName($data);");
$this->send(); $this->send();
} }
/** /**
* Sends the response to the client. * Sends the response to the client.
* @return boolean true if the response was sent
*/ */
public function send() public function send()
{ {
parent::send();
$this->sendHeaders(); $this->sendHeaders();
$this->sendContent(); $this->sendContent();
$this->clear(); $this->clear();
...@@ -199,8 +214,8 @@ class Response extends \yii\base\Response ...@@ -199,8 +214,8 @@ class Response extends \yii\base\Response
{ {
$this->_headers = null; $this->_headers = null;
$this->_statusCode = null; $this->_statusCode = null;
$this->_content = null;
$this->statusText = null; $this->statusText = null;
$this->content = null;
} }
/** /**
...@@ -253,7 +268,7 @@ class Response extends \yii\base\Response ...@@ -253,7 +268,7 @@ class Response extends \yii\base\Response
*/ */
protected function sendContent() protected function sendContent()
{ {
echo $this->content; echo $this->getContent();
} }
/** /**
...@@ -304,10 +319,10 @@ class Response extends \yii\base\Response ...@@ -304,10 +319,10 @@ class Response extends \yii\base\Response
if ($begin !=0 || $end != $contentLength - 1) { if ($begin !=0 || $end != $contentLength - 1) {
$this->setStatusCode(206); $this->setStatusCode(206);
$headers->set('Content-Range', "bytes $begin-$end/$contentLength"); $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 { } else {
$this->setStatusCode(200); $this->setStatusCode(200);
$this->content = $content; $this->setContent($content, self::FORMAT_RAW);
} }
$this->send(); $this->send();
...@@ -628,4 +643,44 @@ class Response extends \yii\base\Response ...@@ -628,4 +643,44 @@ class Response extends \yii\base\Response
{ {
return in_array($this->getStatusCode(), array(201, 204, 304)); 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