Commit fad63544 by Qiang Xue

Fixes #2655: Arrayable and ArrayableTrait are incompatible for some PHP versions.

parent 6d97ded6
...@@ -8,7 +8,14 @@ ...@@ -8,7 +8,14 @@
namespace yii\base; namespace yii\base;
/** /**
* Arrayable should be implemented by classes that need to be represented in array format. * Arrayable is the interface that should be implemented by classes who want to support customizable representation of their instances.
*
* For example, if a class implements Arrayable, by calling [[toArray()]], an instance of this class
* can be turned into an array (including all its embedded objects) which can then be further transformed easily
* into other formats, such as JSON, XML.
*
* The methods [[fields()]] and [[extraFields()]] allow the implementing classes to customize how and which of their data
* should be formatted and put into the result of [[toArray()]].
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
...@@ -16,8 +23,68 @@ namespace yii\base; ...@@ -16,8 +23,68 @@ namespace yii\base;
interface Arrayable interface Arrayable
{ {
/** /**
* Returns the list of fields that should be returned by default by [[toArray()]] when no specific fields are specified.
*
* A field is a named element in the returned array by [[toArray()]].
*
* This method should return an array of field names or field definitions.
* If the former, the field name will be treated as an object property name whose value will be used
* as the field value. If the latter, the array key should be the field name while the array value should be
* the corresponding field definition which can be either an object property name or a PHP callable
* returning the corresponding field value. The signature of the callable should be:
*
* ```php
* function ($field, $model) {
* // return field value
* }
* ```
*
* For example, the following code declares four fields:
*
* - `email`: the field name is the same as the property name `email`;
* - `firstName` and `lastName`: the field names are `firstName` and `lastName`, and their
* values are obtained from the `first_name` and `last_name` properties;
* - `fullName`: the field name is `fullName`. Its value is obtained by concatenating `first_name`
* and `last_name`.
*
* ```php
* return [
* 'email',
* 'firstName' => 'first_name',
* 'lastName' => 'last_name',
* 'fullName' => function () {
* return $this->first_name . ' ' . $this->last_name;
* },
* ];
* ```
*
* @return array the list of field names or field definitions.
* @see toArray()
*/
public function fields();
/**
* Returns the list of additional fields that can be returned by [[toArray()]] in addition to those listed in [[fields()]].
*
* This method is similar to [[fields()]] except that the list of fields declared
* by this method are not returned by default by [[toArray()]]. Only when a field in the list
* is explicitly requested, will it be included in the result of [[toArray()]].
*
* @return array the list of expandable field names or field definitions. Please refer
* to [[fields()]] on the format of the return value.
* @see toArray()
* @see fields()
*/
public function extraFields();
/**
* Converts the object into an array. * Converts the object into an array.
* @return array the array representation of this object *
* @param array $fields the fields that the output array should contain. Fields not specified
* in [[fields()]] will be ignored. If this parameter is empty, all fields as specified in [[fields()]] will be returned.
* @param array $expand the additional fields that the output array should contain.
* Fields not specified in [[extraFields()]] will be ignored. If this parameter is empty, no extra fields
* will be returned.
* @param boolean $recursive whether to recursively return array representation of embedded objects.
* @return array the array representation of the object
*/ */
public function toArray(); public function toArray(array $fields = [], array $expand = [], $recursive = true);
} }
...@@ -13,6 +13,10 @@ use yii\web\Link; ...@@ -13,6 +13,10 @@ use yii\web\Link;
use yii\web\Linkable; use yii\web\Linkable;
/** /**
* ArrayableTrait provides a common implementation of the [[Arrayable]] interface.
*
* ArrayableTrait implements [[toArray()]] by respecting the field definitions as declared
* in [[fields()]] and [[extraFields()]].
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
......
...@@ -15,7 +15,7 @@ use Yii; ...@@ -15,7 +15,7 @@ use Yii;
* @author Alexander Makarov <sam@rmcreative.ru> * @author Alexander Makarov <sam@rmcreative.ru>
* @since 2.0 * @since 2.0
*/ */
class ErrorException extends \ErrorException implements Arrayable class ErrorException extends \ErrorException
{ {
/** /**
* Constructs the exception. * Constructs the exception.
...@@ -93,32 +93,4 @@ class ErrorException extends \ErrorException implements Arrayable ...@@ -93,32 +93,4 @@ class ErrorException extends \ErrorException implements Arrayable
]; ];
return isset($names[$this->getCode()]) ? $names[$this->getCode()] : 'Error'; return isset($names[$this->getCode()]) ? $names[$this->getCode()] : 'Error';
} }
/**
* Returns the array representation of this object.
* @return array the array representation of this object.
*/
public function toArray()
{
return $this->toArrayRecursive($this);
}
/**
* Returns the array representation of the exception and all previous exceptions recursively.
* @param \Exception $exception object
* @return array the array representation of the exception.
*/
protected function toArrayRecursive($exception)
{
$array = [
'type' => get_class($exception),
'name' => $exception instanceof self ? $exception->getName() : 'Exception',
'message' => $exception->getMessage(),
'code' => $exception->getCode(),
];
if (($prev = $exception->getPrevious()) !== null) {
$array['previous'] = $this->toArrayRecursive($prev);
}
return $array;
}
} }
...@@ -119,15 +119,8 @@ class ErrorHandler extends Component ...@@ -119,15 +119,8 @@ class ErrorHandler extends Component
'exception' => $exception, 'exception' => $exception,
]); ]);
} }
} elseif ($exception instanceof Arrayable) {
$response->data = $exception->toArray();
} else { } else {
$response->data = [ $response->data = $this->convertExceptionToArray($exception);
'type' => get_class($exception),
'name' => 'Exception',
'message' => $exception->getMessage(),
'code' => $exception->getCode(),
];
} }
if ($exception instanceof HttpException) { if ($exception instanceof HttpException) {
...@@ -140,6 +133,25 @@ class ErrorHandler extends Component ...@@ -140,6 +133,25 @@ class ErrorHandler extends Component
} }
/** /**
* Converts an exception into an array.
* @param \Exception $exception the exception being converted
* @return array the array representation of the exception.
*/
protected function convertExceptionToArray($exception)
{
$array = [
'type' => get_class($exception),
'name' => $exception instanceof \yii\base\Exception || $exception instanceof \yii\base\ErrorException ? $exception->getName() : 'Exception',
'message' => $exception->getMessage(),
'code' => $exception->getCode(),
];
if (($prev = $exception->getPrevious()) !== null) {
$array['previous'] = $this->convertExceptionToArray($prev);
}
return $array;
}
/**
* Converts special characters to HTML entities. * Converts special characters to HTML entities.
* @param string $text to encode. * @param string $text to encode.
* @return string encoded original text. * @return string encoded original text.
......
...@@ -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 implements Arrayable class Exception extends \Exception
{ {
/** /**
* @return string the user-friendly name of this exception * @return string the user-friendly name of this exception
...@@ -22,32 +22,4 @@ class Exception extends \Exception implements Arrayable ...@@ -22,32 +22,4 @@ class Exception extends \Exception implements Arrayable
{ {
return 'Exception'; return 'Exception';
} }
/**
* Returns the array representation of this object.
* @return array the array representation of this object.
*/
public function toArray()
{
return $this->toArrayRecursive($this);
}
/**
* Returns the array representation of the exception and all previous exceptions recursively.
* @param \Exception $exception object
* @return array the array representation of the exception.
*/
protected function toArrayRecursive($exception)
{
$array = [
'type' => get_class($exception),
'name' => $exception instanceof self ? $exception->getName() : 'Exception',
'message' => $exception->getMessage(),
'code' => $exception->getCode(),
];
if (($prev = $exception->getPrevious()) !== null) {
$array['previous'] = $this->toArrayRecursive($prev);
}
return $array;
}
} }
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
namespace yii\data; namespace yii\data;
use Yii; use Yii;
use yii\base\Arrayable;
use yii\base\Object; use yii\base\Object;
use yii\web\Link; use yii\web\Link;
use yii\web\Linkable; use yii\web\Linkable;
...@@ -68,7 +67,7 @@ use yii\web\Request; ...@@ -68,7 +67,7 @@ use yii\web\Request;
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
class Pagination extends Object implements Linkable, Arrayable class Pagination extends Object implements Linkable
{ {
const LINK_NEXT = 'next'; const LINK_NEXT = 'next';
const LINK_PREV = 'prev'; const LINK_PREV = 'prev';
...@@ -317,19 +316,6 @@ class Pagination extends Object implements Linkable, Arrayable ...@@ -317,19 +316,6 @@ class Pagination extends Object implements Linkable, Arrayable
} }
/** /**
* @inheritdoc
*/
public function toArray()
{
return [
'totalCount' => $this->totalCount,
'pageCount' => $this->getPageCount(),
'currentPage' => $this->getPage(),
'perPage' => $this->getPageSize(),
];
}
/**
* Returns the value of the specified query parameter. * Returns the value of the specified query parameter.
* This method returns the named parameter value from [[params]]. Null is returned if the value does not exist. * This method returns the named parameter value from [[params]]. Null is returned if the value does not exist.
* @param string $name the parameter name * @param string $name the parameter name
......
...@@ -91,7 +91,11 @@ class BaseJson ...@@ -91,7 +91,11 @@ class BaseJson
} elseif ($data instanceof Arrayable) { } elseif ($data instanceof Arrayable) {
$data = $data->toArray(); $data = $data->toArray();
} else { } else {
$data = get_object_vars($data); $result = [];
foreach ($data as $name => $value) {
$result[$name] = $value;
}
$data = $result;
} }
if ($data === []) { if ($data === []) {
......
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
namespace yii\i18n; namespace yii\i18n;
use Yii; use Yii;
use yii\base\Arrayable;
use yii\base\Component; use yii\base\Component;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
...@@ -104,11 +103,7 @@ class I18N extends Component ...@@ -104,11 +103,7 @@ class I18N extends Component
*/ */
public function format($message, $params, $language) public function format($message, $params, $language)
{ {
if ($params instanceof Arrayable) { $params = (array)$params;
$params = $params->toArray();
} else {
$params = (array)$params;
}
if ($params === []) { if ($params === []) {
return $message; return $message;
} }
......
...@@ -169,14 +169,33 @@ class Serializer extends Component ...@@ -169,14 +169,33 @@ class Serializer extends Component
$this->collectionEnvelope => $models, $this->collectionEnvelope => $models,
]; ];
if ($pagination !== false) { if ($pagination !== false) {
$result['_links'] = Link::serialize($pagination->getLinks()); return array_merge($result, $this->serializePagination($pagination));
$result['_meta'] = $pagination->toArray(); } else {
return $result;
} }
return $result;
} }
} }
/** /**
* Serializes a pagination into an array.
* @param Pagination $pagination
* @return array the array representation of the pagination
* @see addPaginationHeader()
*/
protected function serializePagination($pagination)
{
return [
'_links' => Link::serialize($pagination->getLinks(true)),
'_meta' => [
'totalCount' => $pagination->totalCount,
'pageCount' => $pagination->getPageCount(),
'currentPage' => $pagination->getPage(),
'perPage' => $pagination->getPageSize(),
],
];
}
/**
* Adds HTTP headers about the pagination to the response. * Adds HTTP headers about the pagination to the response.
* @param Pagination $pagination * @param Pagination $pagination
*/ */
......
...@@ -9,7 +9,6 @@ namespace yii\web; ...@@ -9,7 +9,6 @@ namespace yii\web;
use Yii; use Yii;
use ArrayIterator; use ArrayIterator;
use yii\base\Arrayable;
use yii\base\InvalidCallException; use yii\base\InvalidCallException;
use yii\base\Object; use yii\base\Object;
...@@ -23,7 +22,7 @@ use yii\base\Object; ...@@ -23,7 +22,7 @@ use yii\base\Object;
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
class CookieCollection extends Object implements \IteratorAggregate, \ArrayAccess, \Countable, Arrayable class CookieCollection extends Object implements \IteratorAggregate, \ArrayAccess, \Countable
{ {
/** /**
* @var boolean whether this collection is read only. * @var boolean whether this collection is read only.
...@@ -167,17 +166,6 @@ class CookieCollection extends Object implements \IteratorAggregate, \ArrayAcces ...@@ -167,17 +166,6 @@ class CookieCollection extends Object implements \IteratorAggregate, \ArrayAcces
} }
/** /**
* Returns the collection as a PHP array.
* @return array the array representation of the collection.
* The array keys are cookie names, and the array values are the corresponding
* cookie objects.
*/
public function toArray()
{
return $this->_cookies;
}
/**
* Returns whether there is a cookie with the specified name. * Returns whether there is a cookie with the specified name.
* This method is required by the SPL interface `ArrayAccess`. * This method is required by the SPL interface `ArrayAccess`.
* It is implicitly called when you use something like `isset($collection[$name])`. * It is implicitly called when you use something like `isset($collection[$name])`.
......
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
namespace yii\web; namespace yii\web;
use Yii; use Yii;
use yii\base\Arrayable;
use yii\base\Object; use yii\base\Object;
use ArrayIterator; use ArrayIterator;
...@@ -22,7 +21,7 @@ use ArrayIterator; ...@@ -22,7 +21,7 @@ use ArrayIterator;
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAccess, \Countable, Arrayable class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAccess, \Countable
{ {
/** /**
* @var array the headers in this collection (indexed by the header names) * @var array the headers in this collection (indexed by the header names)
...@@ -161,16 +160,6 @@ class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAcces ...@@ -161,16 +160,6 @@ class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAcces
} }
/** /**
* Returns the collection as a PHP array.
* @return array the array representation of the collection.
* The array keys are header names, and the array values are the corresponding header values.
*/
public function toArray()
{
return $this->_headers;
}
/**
* Returns whether there is a header with the specified name. * Returns whether there is a header with the specified name.
* This method is required by the SPL interface `ArrayAccess`. * This method is required by the SPL interface `ArrayAccess`.
* It is implicitly called when you use something like `isset($collection[$name])`. * It is implicitly called when you use something like `isset($collection[$name])`.
......
...@@ -58,15 +58,4 @@ class HttpException extends UserException ...@@ -58,15 +58,4 @@ 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;
}
} }
...@@ -7,7 +7,6 @@ ...@@ -7,7 +7,6 @@
namespace yii\web; namespace yii\web;
use yii\base\Arrayable;
use yii\base\Object; use yii\base\Object;
/** /**
...@@ -16,7 +15,7 @@ use yii\base\Object; ...@@ -16,7 +15,7 @@ use yii\base\Object;
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
class Link extends Object implements Arrayable class Link extends Object
{ {
/** /**
* The self link. * The self link.
...@@ -53,13 +52,6 @@ class Link extends Object implements Arrayable ...@@ -53,13 +52,6 @@ class Link extends Object implements Arrayable
*/ */
public $hreflang; public $hreflang;
/**
* @inheritdoc
*/
public function toArray()
{
return array_filter((array)$this);
}
/** /**
* Serializes a list of links into proper array format. * Serializes a list of links into proper array format.
...@@ -71,7 +63,7 @@ class Link extends Object implements Arrayable ...@@ -71,7 +63,7 @@ class Link extends Object implements Arrayable
foreach ($links as $rel => $link) { foreach ($links as $rel => $link) {
if (is_array($link)) { if (is_array($link)) {
foreach ($link as $i => $l) { foreach ($link as $i => $l) {
$link[$i] = $l instanceof self ? $l->toArray() : ['href' => $l]; $link[$i] = $l instanceof self ? array_filter((array)$l) : ['href' => $l];
} }
$links[$rel] = $link; $links[$rel] = $link;
} elseif (!$link instanceof self) { } elseif (!$link instanceof self) {
......
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
namespace yii\web; namespace yii\web;
use Yii; use Yii;
use yii\base\Arrayable;
use yii\base\Component; use yii\base\Component;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use yii\base\InvalidParamException; use yii\base\InvalidParamException;
...@@ -72,7 +71,7 @@ use yii\base\InvalidParamException; ...@@ -72,7 +71,7 @@ use yii\base\InvalidParamException;
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Countable, Arrayable class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Countable
{ {
/** /**
* @var string the name of the session variable that stores the flash message data. * @var string the name of the session variable that stores the flash message data.
...@@ -600,15 +599,6 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co ...@@ -600,15 +599,6 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
} }
/** /**
* @return array the list of all session variables in array
*/
public function toArray()
{
$this->open();
return $_SESSION;
}
/**
* Updates the counters for flash messages and removes outdated flash messages. * Updates the counters for flash messages and removes outdated flash messages.
* This method should only be called once in [[init()]]. * This method should only be called once in [[init()]].
*/ */
......
<?php
namespace yiiunit\framework\base;
use yiiunit\TestCase;
use yii\base\UserException;
use yii\base\InvalidCallException;
class ExceptionTest extends TestCase
{
public function testToArrayWithPrevious()
{
$e = new InvalidCallException('bar', 0 ,new InvalidCallException('foo'));
$array = $e->toArray();
$this->assertEquals('bar', $array['message']);
$this->assertEquals('foo', $array['previous']['message']);
$e = new InvalidCallException('bar', 0, new UserException('foo'));
$array = $e->toArray();
$this->assertEquals('bar', $array['message']);
$this->assertEquals('foo', $array['previous']['message']);
}
}
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