Yii2 framework backup
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

335 lines
10 KiB

<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\data;
use Yii;
use yii\base\Component;
use yii\base\Model;
use yii\helpers\StringHelper;
/**
* ModelSerializer converts a model or a list of models into an array representation with selected fields.
*
* Used together with [[\yii\web\ResponseFormatterInterface]], ModelSerializer can be used to serve model data
* in JSON or XML format for REST APIs.
*
* ModelSerializer provides two methods [[export()]] and [[exportAll()]] to convert model(s) into array(s).
* The former works for a single model, while the latter for an array of models.
* During conversion, it will check which fields are requested and only provide valid fields (as declared
* in [[fields()]] and [[expand()]]) in the array result.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ModelSerializer extends Component
{
/**
* @var string the model class that this API is serving. If not set, it will be initialized
* as the class of the model(s) being exported by [[export()]] or [[exportAll()]].
*/
public $modelClass;
/**
* @var mixed the context information. If not set, it will be initialized as the "user" application component.
* You can use the context information to conditionally control which fields can be returned for a model.
*/
public $context;
/**
* @var array|string an array or a string of comma separated field names representing
* which fields should be returned. Only fields declared in [[fields()]] will be respected.
* If this property is empty, all fields declared in [[fields()]] will be returned.
*/
public $fields;
/**
* @var array|string an array or a string of comma separated field names representing
* which fields should be returned in addition to those declared in [[fields()]].
* Only fields declared in [[expand()]] will be respected.
*/
public $expand;
/**
* @var integer the error code to be used in the result of [[exportErrors()]].
*/
public $validationErrorCode = 1024;
/**
* @var string the error message to be used in the result of [[exportErrors()]].
*/
public $validationErrorMessage = 'Validation Failed';
/**
* @var array a list of serializer classes indexed by their corresponding model classes.
* This property is used by [[createSerializer()]] to serialize embedded objects.
* @see createSerializer()
*/
public $serializers = [];
/**
* @var array a list of paths or path aliases specifying how to look for a serializer class
* given a model class. If the base name of a model class is `Xyz`, the corresponding
* serializer class being looked for would be `XyzSerializer` under each of the paths listed here.
*/
public $serializerPaths = ['@app/serializers'];
/**
* @var array the loaded serializer objects indexed by the model class names
*/
private $_serializers = [];
/**
* @inheritdoc
*/
public function init()
{
parent::init();
if ($this->context === null && Yii::$app) {
$this->context = Yii::$app->user;
}
}
/**
* Exports a model object by converting it into an array based on the specified fields.
* @param Model $model the model being exported
* @return array the exported data
*/
public function export($model)
{
if ($this->modelClass === null) {
$this->modelClass = get_class($model);
}
$fields = $this->resolveFields($this->fields, $this->expand);
return $this->exportObject($model, $fields);
}
/**
* Exports an array of model objects by converting it into an array based on the specified fields.
* @param Model[] $models the models being exported
* @return array the exported data
*/
public function exportAll(array $models)
{
if (empty($models)) {
return [];
}
if ($this->modelClass === null) {
$this->modelClass = get_class(reset($models));
}
$fields = $this->resolveFields($this->fields, $this->expand);
$result = [];
foreach ($models as $model) {
$result[] = $this->exportObject($model, $fields);
}
return $result;
}
/**
* Exports the model validation errors.
* @param Model $model
* @return array
*/
public function exportErrors($model)
{
$result = [
'code' => $this->validationErrorCode,
'message' => $this->validationErrorMessage,
'errors' => [],
];
foreach ($model->getFirstErrors() as $name => $message) {
$result['errors'][] = [
'field' => $name,
'message' => $message,
];
}
return $result;
}
/**
* Returns a list of fields that can be returned to end users.
*
* These are the fields that should be returned by default when a user does not explicitly specify which
* fields to return for a model. If the user explicitly which fields to return, only the fields declared
* in this method can be returned. All other fields will be ignored.
*
* By default, this method returns [[Model::attributes()]], which are the attributes defined by a model.
*
* You may override this method to select which fields can be returned or define new fields based
* on model attributes.
*
* The value returned by this method should be an array of field definitions. The array keys
* are the field names, and the array values are the corresponding attribute names or callbacks
* returning field values. If a field name is the same as the corresponding attribute name,
* you can use the field name without a key.
*
* @return array field name => attribute name or definition
*/
protected function fields()
{
if (is_subclass_of($this->modelClass, Model::className())) {
/** @var Model $model */
$model = new $this->modelClass;
return $model->attributes();
} else {
return array_keys(get_class_vars($this->modelClass));
}
}
/**
* Returns a list of additional fields that can be returned to end users.
*
* The default implementation returns an empty array. You may override this method to return
* a list of additional fields that can be returned to end users. Please refer to [[fields()]]
* on the format of the return value.
*
* You usually override this method by returning a list of relation names.
*
* @return array field name => attribute name or definition
*/
protected function expand()
{
return [];
}
/**
* Filters the data to be exported to end user.
* The default implementation does nothing. You may override this method to remove
* certain fields from the data being exported based on the [[context]] information.
* You may also use this method to add some common fields, such as class name, to the data.
* @param array $data the data being exported
* @return array the filtered data
*/
protected function filter($data)
{
return $data;
}
/**
* Returns the serializer for the specified model class.
* @param string $modelClass fully qualified model class name
* @return static the serializer
*/
protected function getSerializer($modelClass)
{
if (!isset($this->_serializers[$modelClass])) {
$this->_serializers[$modelClass] = $this->createSerializer($modelClass);
}
return $this->_serializers[$modelClass];
}
/**
* Creates a serializer object for the specified model class.
*
* This method tries to create an appropriate serializer using the following algorithm:
*
* - Check if [[serializers]] specifies the serializer class for the model class and
* create an instance of it if available;
* - Search for a class named `XyzSerializer` under the paths specified by [[serializerPaths]],
* where `Xyz` stands for the model class.
* - If both of the above two strategies fail, simply return an instance of `ModelSerializer`.
*
* @param string $modelClass the model class
* @return ModelSerializer the new model serializer
*/
protected function createSerializer($modelClass)
{
if (isset($this->serializers[$modelClass])) {
$config = $this->serializers[$modelClass];
if (!is_array($config)) {
$config = ['class' => $config];
}
} else {
$className = StringHelper::basename($modelClass) . 'Serializer';
foreach ($this->serializerPaths as $path) {
$path = Yii::getAlias($path);
if (is_file($path . "/$className.php")) {
$config = ['class' => $className];
break;
}
}
}
if (!isset($config)) {
$config = ['class' => __CLASS__];
}
$config['modelClass'] = $modelClass;
$config['context'] = $this->context;
return Yii::createObject($config);
}
/**
* Returns the fields of the model that need to be returned to end user
* @param string|array $fields an array or a string of comma separated field names representing
* which fields should be returned.
* @param string|array $expand an array or a string of comma separated field names representing
* which additional fields should be returned.
* @return array field name => field definition (attribute name or callback)
*/
protected function resolveFields($fields, $expand)
{
if (!is_array($fields)) {
$fields = preg_split('/\s*,\s*/', $fields, -1, PREG_SPLIT_NO_EMPTY);
}
if (!is_array($expand)) {
$expand = preg_split('/\s*,\s*/', $expand, -1, PREG_SPLIT_NO_EMPTY);
}
$result = [];
foreach ($this->fields() as $field => $definition) {
if (is_integer($field)) {
$field = $definition;
}
if (empty($fields) || in_array($field, $fields, true)) {
$result[$field] = $definition;
}
}
if (empty($expand)) {
return $result;
}
foreach ($this->expand() as $field => $definition) {
if (is_integer($field)) {
$field = $definition;
}
if (in_array($field, $expand, true)) {
$result[$field] = $definition;
}
}
return $result;
}
/**
* Exports an object by converting it into an array based on the given field definitions.
* @param object $model the model being exported
* @param array $fields field definitions (field name => field definition)
* @return array the exported model data
*/
protected function exportObject($model, $fields)
{
$data = [];
foreach ($fields as $field => $attribute) {
if (is_string($attribute)) {
$value = $model->$attribute;
} else {
$value = call_user_func($attribute, $model, $field);
}
if (is_object($value)) {
$value = $this->getSerializer(get_class($value))->export($value);
} elseif (is_array($value)) {
foreach ($value as $i => $v) {
if (is_object($v)) {
$value[$i] = $this->getSerializer(get_class($v))->export($v);
}
// todo: array of array
}
}
$data[$field] = $value;
}
return $this->filter($data);
}
}