Created
August 10, 2016 09:44
-
-
Save mdmunir/e7e4f7ee11f67daff87ef496d6b58775 to your computer and use it in GitHub Desktop.
Serializer for API Rest
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?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; | |
| } | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?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; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?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