ok
Direktori : /home2/selectio/www/fms-worksuite/vendor/froiden/laravel-rest-api/src/ |
Current File : /home2/selectio/www/fms-worksuite/vendor/froiden/laravel-rest-api/src/RequestParser.php |
<?php namespace Froiden\RestAPI; use Froiden\RestAPI\Exceptions\Parse\InvalidLimitException; use Froiden\RestAPI\Exceptions\Parse\InvalidFilterDefinitionException; use Froiden\RestAPI\Exceptions\Parse\InvalidOrderingDefinitionException; use Froiden\RestAPI\Exceptions\Parse\MaxLimitException; use Froiden\RestAPI\Exceptions\Parse\NotAllowedToFilterOnThisFieldException; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Support\Str; class RequestParser { /** * Checks if fields are specified correctly */ const FIELDS_REGEX = "/([a-zA-Z0-9\\_\\-\\:\\.\\(\\)]+(?'curly'\\{((?>[^{}]+)|(?&curly))*\\})?+)/"; /** * Extracts fields parts */ const FIELD_PARTS_REGEX = "/([^{.]+)(.limit\\(([0-9]+)\\)|.offset\\(([0-9]+)\\)|.order\\(([A-Za-z_]+)\\))*(\\{((?>[^{}]+)|(?R))*\\})?/i"; /** * Checks if filters are correctly specified */ const FILTER_REGEX = "/(\\((?:[\\s]*(?:and|or)?[\\s]*[\\w\\.]+[\\s]+(?:eq|ne|gt|ge|lt|le|lk)[\\s]+(?:\\\"(?:[^\\\"\\\\]|\\\\.)*\\\"|\\d+(,\\d+)*(\\.\\d+(e\\d+)?)?|null)[\\s]*|(?R))*\\))/i"; /** * Extracts filter parts */ const FILTER_PARTS_REGEX = "/([\\w\\.]+)[\\s]+(?:eq|ne|gt|ge|lt|le|lk)[\\s]+(?:\"(?:[^\"\\\\]|\\\\.)*\"|\\d+(?:,\\d+)*(?:\\.\\d+(?:e\\d+)?)?|null)/i"; /** * Checks if ordering is specified correctly */ const ORDER_FILTER = "/[\\s]*([\\w\\.]+)(?:[\\s](?!,))*(asc|desc|)/i"; /** * Full class reference to model this controller represents * * @var string */ protected $model = null; /** * Table name corresponding to the model this controller is handling * * @var string */ private $table = null; /** * Primary key of the model * * @var string */ private $primaryKey = null; /** * Fields to be returned in response. This does not include relations * * @var array */ private $fields = []; /** * Relations to be included in the response * * @var array */ private $relations = []; /** * Number of results requested per page * * @var int */ private $limit = 10; /** * Offset from where fetching should start * * @var int */ private $offset = 0; /** * Ordering string * * @var int */ private $order = null; /** * Filters to be applied * * @var string */ private $filters = null; /** * Attributes passed in request * * @var array */ private $attributes = []; public function __construct($model) { $this->model = $model; $this->primaryKey = call_user_func([new $this->model(), "getKeyName"]); $this->parseRequest(); } /** * @return array */ public function getFields() { return $this->fields; } /** * @param array $fields */ public function setFields($fields) { $this->fields = $fields; } /** * @return array */ public function getRelations() { return $this->relations; } /** * @param array $relations */ public function setRelations($relations) { $this->relations = $relations; } /** * @return int */ public function getLimit() { return $this->limit; } /** * @return int */ public function getOffset() { return $this->offset; } /** * @return int */ public function getOrder() { return $this->order; } /** * @return string */ public function getFilters() { return $this->filters; } /** * @return array */ public function getAttributes() { return $this->attributes; } /** * Parse request and fill the parameters * @return $this current controller object for chain method calling * @throws InvalidFilterDefinitionException * @throws InvalidOrderingDefinitionException * @throws MaxLimitException */ protected function parseRequest() { if (request()->limit) { if (request()->limit <= 0) { throw new InvalidLimitException(); } else if (request()->limit > config("api.maxLimit")) { throw new MaxLimitException(); } else { $this->limit = request()->limit; } } else { $this->limit = config("api.defaultLimit"); } if (request()->offset) { $this->offset = request()->offset; } else { $this->offset = 0; } $this->extractFields(); $this->extractFilters(); $this->extractOrdering(); $this->loadTableName(); $this->attributes = request()->all(); return $this; } protected function extractFields() { if (request()->fields) { $this->parseFields(request()->fields); } else { // Else, by default, we only return default set of visible fields $fields = call_user_func($this->model."::getDefaultFields"); // We parse the default fields in same way as above so that, if // relations are included in default fields, they also get included $this->parseFields(implode(",", $fields)); } if (!in_array($this->primaryKey, $this->fields)) { $this->fields[] = $this->primaryKey; } } protected function extractFilters() { if (request()->filters) { $filters = "(" . request()->filters . ")"; if (preg_match(RequestParser::FILTER_REGEX, $filters) === 1) { preg_match_all(RequestParser::FILTER_PARTS_REGEX, $filters, $parts); $filterable = call_user_func($this->model . "::getFilterableFields"); foreach ($parts[1] as $column) { if (!in_array($column, $filterable)) { throw new NotAllowedToFilterOnThisFieldException("Applying filter on field \"" . $column . "\" is not allowed"); } } // Convert filter name to sql `column` format $where = preg_replace( [ "/([\\w]+)\\.([\\w]+)[\\s]+(eq|ne|gt|ge|lt|le|lk)/i", "/([\\w]+)[\\s]+(eq|ne|gt|ge|lt|le|lk)/i", ], [ "`$1`.`$2` $3", "`$1` $2", ], $filters ); // convert eq null to is null and ne null to is not null $where = preg_replace( [ "/ne[\\s]+null/i", "/eq[\\s]+null/i" ], [ "is not null", "is null" ], $where ); // Replace operators $where = preg_replace( [ "/[\\s]+eq[\\s]+/i", "/[\\s]+ne[\\s]+/i", "/[\\s]+gt[\\s]+/i", "/[\\s]+ge[\\s]+/i", "/[\\s]+lt[\\s]+/i", "/[\\s]+le[\\s]+/i", "/[\\s]+lk[\\s]+/i" ], [ " = ", " != ", " > ", " >= ", " < ", " <= ", " LIKE " ], $where ); $this->filters = $where; } else { throw new InvalidFilterDefinitionException(); } } } protected function extractOrdering() { if (request()->order) { if (preg_match(RequestParser::ORDER_FILTER, request()->order) === 1) { $order = request()->order; // eg : user.name asc, year desc, age,month $order = preg_replace( [ "/[\\s]*([\\w]+)\\.([\\w]+)(?:[\\s](?!,))*(asc|desc|)/", "/[\\s]*([\\w`\\.]+)(?:[\\s](?!,))*(asc|desc|)/", ], [ "$1`.`$2 $3", // Result: user`.`name asc, year desc, age,month "`$1` $2", // Result: `user`.`name` asc, `year` desc, `age`,`month` ], $order ); $this->order = $order; } else { throw new InvalidOrderingDefinitionException(); } } } /** * Recursively parses fields to extract limit, ordering and their own fields * and adds width relations * * @param $fields */ private function parseFields($fields) { // If fields parameter is set, parse it using regex preg_match_all(static::FIELDS_REGEX, $fields, $matches); if (!empty($matches[0])) { foreach ($matches[0] as $match) { preg_match_all(static::FIELD_PARTS_REGEX, $match, $parts); $fieldName = $parts[1][0]; if (Str::contains($fieldName, ":") || call_user_func($this->model . "::relationExists", $fieldName)) { // If field name has a colon, we assume its a relations // OR // If method with field name exists in the class, we assume its a relation // This is default laravel behavior $limit = ($parts[3][0] == "") ? config("api.defaultLimit") : $parts[3][0]; $offset = ($parts[4][0] == "") ? 0 : $parts[4][0]; $order = ($parts[5][0] == "chronological") ? "chronological" : "reverse_chronological"; if (!empty($parts[7][0])) { $subFields = explode(",", $parts[7][0]); // This indicates if user specified fields for relation or not $userSpecifiedFields = true; } else { $subFields = []; $userSpecifiedFields = false; } $fieldName = str_replace(":", ".", $fieldName); // Check if relation name in modal is in camel case then convert relation name in camel case if(config("api.relation_case", 'snakecase') === 'camelcase'){ $fieldName = Str::camel($fieldName); } if (!isset($this->relations[$fieldName])) { $this->relations[$fieldName] = [ "limit" => $limit, "offset" => $offset, "order" => $order, "fields" => $subFields, "userSpecifiedFields" => $userSpecifiedFields ]; } else { $this->relations[$fieldName]["limit"] = $limit; $this->relations[$fieldName]["offset"] = $offset; $this->relations[$fieldName]["order"] = $order; $this->relations[$fieldName]["fields"] = array_merge($this->relations[$fieldName]["fields"], $subFields); } // We also need to add the relation's foreign key field to select. If we don't, // relations always return null if (Str::contains($fieldName, ".")) { $relationNameParts = explode('.', $fieldName); $model = $this->model; $relation = null; foreach ($relationNameParts as $rp) { $relation = call_user_func([ new $model(), $rp]); $model = $relation->getRelated(); } // Its a multi level relations $fieldParts = explode(".", $fieldName); if ($relation instanceof BelongsTo) { $singular = $relation->getForeignKeyName(); } else if ($relation instanceof HasOne || $relation instanceof HasMany) { $singular = $relation->getForeignKeyName(); } // Unset last element of array unset($fieldParts[count($fieldParts) - 1]); $parent = implode(".", $fieldParts); if ($relation instanceof HasOne || $relation instanceof HasMany) { // For hasMany and HasOne, the foreign key is in current relation table, not in parent $this->relations[$fieldName]["fields"][] = $singular; } else { // The parent might already been set because we cannot rely on order // in which user sends relations in request if (!isset($this->relations[$parent])) { $this->relations[$parent] = [ "limit" => config("api.defaultLimit"), "offset" => 0, "order" => "chronological", "fields" => isset($singular) ? [$singular] : [], "userSpecifiedFields" => true ]; } else { if (isset($singular)) { $this->relations[$parent]["fields"][] = $singular; } } } if ($relation instanceof BelongsTo) { $this->relations[$fieldName]["limit"] = max($this->relations[$fieldName]["limit"], $this->relations[$parent]["limit"]); } else if ($relation instanceof HasMany) { $this->relations[$fieldName]["limit"] = $this->relations[$fieldName]["limit"] * $this->relations[$parent]["limit"]; } } else { $relation = call_user_func([new $this->model(), $fieldName]); if ($relation instanceof HasOne) { $keyField = explode(".", $relation->getQualifiedParentKeyName())[1]; } else if ($relation instanceof BelongsTo) { $keyField = explode(".", $relation->getQualifiedForeignKeyName())[1]; } if (isset($keyField) && !in_array($keyField, $this->fields)) { $this->fields[] = $keyField; } if ($relation instanceof BelongsTo) { $this->relations[$fieldName]["limit"] = max($this->relations[$fieldName]["limit"], $this->limit); } else if ($relation instanceof HasMany) { // Commented out for third level hasmany limit // $this->relations[$fieldName]["limit"] = $this->relations[$fieldName]["limit"] * $this->limit; } } } else { // Else, its a normal field $this->fields[] = $fieldName; } } } } /** * Load table name into the $table property */ private function loadTableName() { $this->table = call_user_func($this->model."::getTableName"); } }