Skip to content

Instantly share code, notes, and snippets.

@mdmunir
Created August 10, 2016 09:44
Show Gist options
  • Select an option

  • Save mdmunir/e7e4f7ee11f67daff87ef496d6b58775 to your computer and use it in GitHub Desktop.

Select an option

Save mdmunir/e7e4f7ee11f67daff87ef496d6b58775 to your computer and use it in GitHub Desktop.
Serializer for API Rest
<?php
namespace classes\data;
use yii\base\Model;
/**
* Description of Helper
*
* @author Misbahul D Munir <misbahuldmunir@gmail.com>
* @since 1.0
*/
class Helper
{
/**
* Serializes a model object.
* @param mixed $object
* @param array $expands Expand field(s)
* @param array $excepts Excluded field(s) from result
*
* @return array the array representation of the model
*/
public function serializeObject($object, $expands = [], $excepts = [])
{
$expands = static::resolveExpand($expands);
$excepts = static::resolveExpand($excepts);
return static::serializeObjectRecursive($object, $expands, $excepts);
}
/**
* Serializes a set of models.
* @param array $models
* @param array $expands Expand field(s)
* @param array $excepts Excluded field(s) from result
* @return array the array representation of the models
*/
public static function serializeModels(array $models, $expands = [], $excepts = [])
{
$expands = static::resolveExpand($expands);
$excepts = static::resolveExpand($excepts);
foreach ($models as $i => $model) {
$models[$i] = static::serializeObjectRecursive($model, $expands, $excepts);
}
return $models;
}
/**
* Serializes a model object.
* @param mixed $object
* @param array $expands Expand field(s)
* @param array $excepts Excluded field(s) from result
*
* @return array the array representation of the model
*/
protected static function serializeObjectRecursive($object, $expands = [], $excepts = [])
{
if (is_object($object)) {
if ($object instanceof Model) {
$data = $object->attributes;
} else {
$data = [];
foreach ($object as $key => $value) {
$data[$key] = $value;
}
}
foreach (array_keys($expands) as $field) {
if (!array_key_exists($field, $data)) {
$data[$field] = $object->$field;
}
}
} else {
$data = $object;
}
foreach ($excepts as $field => $child) {
if (empty($child) && $field != '*') {
unset($data[$field]);
} elseif ($field == '*') {
foreach ($child as $field) {
unset($data[$field]);
}
}
}
foreach ($data as $key => $value) {
if (is_array($value) || is_object($value)) {
if (is_int($key)) {
$itemExpands = $expands;
$itemExcepts = $excepts;
} else {
$itemExpands = isset($expands[$key]) ? static::resolveExpand($expands[$key]) : [];
$itemExcepts = isset($excepts[$key]) ? static::resolveExpand($excepts[$key]) : [];
if (isset($excepts['*'])) {
foreach ($excepts['*'] as $field) {
$itemExcepts['*'][] = $field;
}
}
}
$data[$key] = static::serializeObjectRecursive($value, $itemExpands, $itemExcepts);
}
}
return $data;
}
/**
*
* @param array $expands
* @return array Description
*/
public static function resolveExpand(array $expands, $olds = [])
{
foreach ($expands as $field) {
$fields = explode('.', $field, 2);
$olds[$fields[0]][] = isset($fields[1]) ? $fields[1] : false;
}
return array_map('array_filter', $olds);
}
/**
* @param QueryInterface $query
* @return string
*/
public static function resolveAlias($query)
{
if (!empty($query->join) || !empty($query->joinWith) || !empty($query->with)) {
if (!empty($query->from)) {
foreach ($query->from as $alias => $table) {
if (is_string($alias)) {
return $alias;
} elseif (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $table, $matches)) {
return $matches[2];
} else {
return $table;
}
}
} elseif (isset($query->modelClass)) {
$class = $query->modelClass;
return $class::tableName();
}
}
return '';
}
/**
* Applying filter to query
* @param QueryInterface $query Query to be applied
* @param array $params parameter filter
* @param array $fieldMap Mapping search param to database column
* @param string $alias default alias for unscoped field
*/
public static function applyFilter($query, $params = [], $fieldMap = [], $alias = null)
{
if (empty($params) || !is_array($params)) {
return;
}
if ($alias === null) {
$alias = static::resolveAlias($query);
}
$alias = empty($alias) ? '' : rtrim($alias, '.') . '.';
$opMap = [
'=' => 'LIKE',
'!=' => 'NOT LIKE',
'==' => '=',
'!==' => '<>',
'contain' => 'LIKE',
'between' => '[]'
];
foreach ($params as $field => $value) {
if (empty($value)) {
continue;
}
if (is_array($value)) {
if (empty($value['value'])) {
continue;
}
if (isset($value['field'])) {
$field = $value['field'];
}
$op = isset($value['operator']) ? strtolower($value['operator']) : '=';
$value = $value['value'];
} else {
$op = '=';
}
if (isset($fieldMap[$field])) {
$field = $fieldMap[$field];
}
if (strpos($field, '.') === false) {
$field = $alias . $field;
}
$operator = isset($opMap[$op]) ? $opMap[$op] : $op;
switch ($operator) {
case '[]':
case '![]':
$v1 = isset($value[0]) ? $value[0] : '';
$v2 = isset($value[1]) ? $value[1] : '';
if ($v1 !== '' && $v2 !== '') {
$query->andWhere([$operator == '[]' ? 'BETWEEN' : 'NOT BETWEEN', $field, $v1, $v2]);
} elseif ($v1 !== '' && $v2 === '') {
$query->andWhere([$operator == '[]' ? '>=' : '<', $field, $v1]);
} elseif ($v1 === '' && $v2 !== '') {
$query->andWhere([$operator == '[]' ? '<=' : '>', $field, $v2]);
}
break;
default:
$query->andWhere([$operator, $field, $value]);
break;
}
}
}
}
<?php
namespace classes\data;
use yii\base\Behavior;
/**
* Description of ProxySerializer
*
* @author Misbahul D Munir <misbahuldmunir@gmail.com>
* @since 1.0
*/
class ProxySerializer extends Behavior
{
/**
* @var SerializeFilter
*/
public $serializer;
/**
* Get serialize object
* @return SerializeFilter
*/
public function getSerializer()
{
return $this->serializer;
}
}
<?php
namespace classes\data;
use Yii;
use yii\base\Model;
use yii\db\QueryInterface;
use yii\web\Request;
use yii\web\Response;
use yii\base\ActionFilter;
/**
* Description of SerializeFilter
*
* @author Misbahul D Munir <misbahuldmunir@gmail.com>
* @since 1.0
*/
class SerializeFilter extends ActionFilter
{
/**
* @var array list of supported response formats. The keys are MIME types (e.g. `application/json`)
* while the values are the corresponding formats (e.g. `html`, `json`) which must be supported
* as declared in [[\yii\web\Response::formatters]].
*
* If this property is empty or not set, response format negotiation will be skipped.
*/
public $formats = [
'application/json' => 'json',
];
/**
* @var string Table alias for unaliased column
*/
public $alias;
/**
* @var array Maping field from query param to database field
*/
public $fieldMap = [];
/**
* @var Request the current request. If not set, the `request` application component will be used.
*/
public $request;
/**
* @var Response the response to be sent. If not set, the `response` application component will be used.
*/
public $response;
/**
* @var array expand field for serialize object
*/
public $expands = [];
/**
* @var array except field for serialize object
*/
public $excepts = [];
/**
* @var string
*/
public $expandParam = 'expands';
/**
* @var string key for meta data
*/
public $metaEnvelope;
/**
* @var string key for total result
*/
public $totalKey = 'total';
/**
* @var string Key for retured array data
*/
public $dataEnvelope;
/**
* @var type Key for single data
*/
public $singleEnvelope;
/**
* @inheritdoc
*/
public function init()
{
$this->request = $this->request ? : Yii::$app->getRequest();
$this->response = $this->response ? : Yii::$app->getResponse();
}
/**
* @inheritdoc
*/
public function beforeAction($action)
{
if ($this->request->getMethod() === 'OPTIONS') {
$options = ['GET', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];
$this->response->setStatusCode(405);
$this->response
->getHeaders()->set('Allow', implode(', ', $options));
return false;
}
/* @var $action \yii\base\Action */
if ($action->controller !== $this->owner) {
$action->controller->attachBehavior('dProxySerializer', new ProxySerializer([
'serializer' => $this
]));
}
if ($this->expandParam && ($expands = $this->request->get($this->expandParam))) {
$this->expands = array_merge(preg_split('/\s*,\s*/', $expands, -1, PREG_SPLIT_NO_EMPTY), $this->expands);
}
$this->negotiate($this->request, $this->response);
return true;
}
/**
* @inheritdoc
*/
public function afterAction($action, $result)
{
/* @var $action \yii\base\Action */
$result = $this->serialize($result);
$action->controller->detachBehavior('dProxySerializer');
return $result;
}
/**
* Negotiates the response format.
* @param Request $request
* @param Response $response
*/
protected function negotiate($request, $response)
{
$types = $request->getAcceptableContentTypes();
foreach ($types as $type => $params) {
if (isset($this->formats[$type])) {
$response->format = $this->formats[$type];
$response->acceptMimeType = $type;
$response->acceptParams = $params;
return;
}
}
}
/**
* Return it self
* @return static
*/
public function getSerializeFilter()
{
return $this;
}
/**
* Serializes the given data into a format that can be easily turned into other formats.
* This method mainly converts the objects of recognized types into array representation.
* It will not do conversion for unknown object types or non-object data.
* The default implementation will handle [[Model]] and [[DataProviderInterface]].
* You may override this method to support more object types.
* @param mixed $data the data to be serialized.
* @return mixed the converted data.
*/
public function serialize($data)
{
if ($data instanceof Model && $data->hasErrors()) {
return $this->serializeModelError($data);
} elseif ($data instanceof Model) {
return $this->serializeModel($data);
} elseif ($data instanceof QueryInterface) {
return $this->serializeQuery($data);
}
return $data;
}
/**
* Serialize model error
* @param Model $model
*/
protected function serializeModelError($model)
{
$this->response->setStatusCode(422, 'Data Validation Failed.');
return $model->getFirstErrors();
}
/**
* Serialize model error
* @param Model $model
*/
protected function serializeModel($model)
{
if ($this->request->getIsHead()) {
return null;
}
$model = Helper::serializeObject($model, $this->expands, $this->excepts);
return $this->formatModel($model, $this->getMetaObject());
}
/**
* Serializes a query.
* @param QueryInterface $query
* @return array the array representation of the data provider.
*/
protected function serializeQuery($query)
{
if ($this->alias === null) {
$this->alias = Helper::resolveAlias($query);
}
if (($sorting = $this->getSorting()) !== false) {
$query->orderBy($sorting);
}
$meta = [];
if (($pagination = $this->getPagination()) !== false) {
list($limit, $page) = $pagination;
$total = $query->count();
$query->limit($limit)->offset(($page - 1) * $limit);
$meta = [
'total' => $total,
'perSize' => $limit,
'currentPage' => $page,
];
$this->addPaginationHeader($meta);
}
if ($this->request->getIsHead()) {
return null;
}
$models = Helper::serializeModels($query->all(), $this->expands, $this->excepts);
return $this->formatModels($models, $this->getMetaObject($meta));
}
/**
* Send pagination headers
* @param array $meta
*/
protected function addPaginationHeader($meta)
{
$this->response->getHeaders()
->add('X-Pagination-Total-Count', $meta['total'])
->add('X-Pagination-Per-Page', $meta['perSize'])
->add('X-Pagination-Current-Page', $meta['currentPage']);
if ($meta['perSize'] < 1) {
$pageCount = $meta['total'] > 0 ? 1 : 0;
} else {
$pageCount = (int) ((($meta['total'] > 0 ? (int) $meta['total'] : 0) + $meta['perSize'] - 1) / $meta['perSize']);
}
$this->response->getHeaders()->add('X-Pagination-Total-Page', $pageCount);
}
/**
* Get meta object
* @param array $values
* @return array
*/
protected function getMetaObject(array $values = [])
{
return array_merge(['status' => 'success'], $values);
}
/**
* Format returned array model
* @param array $model
* @param array $meta
* @return array
*/
protected function formatModel($model, array $meta = [])
{
if ($this->singleEnvelope) {
if ($this->metaEnvelope && !empty($meta)) {
$result = [$this->metaEnvelope = $meta];
} else {
$result = $meta;
}
return array_merge($result, [$this->singleEnvelope => $model]);
}
return $model;
}
/**
* Format returned array model
* @param array $models
* @param array $meta
* @return array
*/
protected function formatModels($models, array $meta = [])
{
if ($this->dataEnvelope) {
if ($this->metaEnvelope && !empty($meta)) {
$result = [$this->metaEnvelope = $meta];
} else {
$result = $meta;
}
return array_merge($result, [$this->dataEnvelope => $models]);
}
return $models;
}
/**
* Get limit offset
* @return array|boolean
*/
protected function getPagination()
{
return false;
}
/**
* Get sorting
* @return array|boolean
*/
protected function getSorting()
{
return false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment