Recursively parses fields to extract limit, ordering and their own fields
and adds width relations

@param $fields
963640
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);

                    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->getForeignKey();
                        }
                        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->getQualifiedForeignKey())[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) {
                            $this->relations[$fieldName]["limit"] = $this->relations[$fieldName]["limit"] * $this->limit;
                        }
                    }

                }
                else {
                    // Else, its a normal field
                    $this->fields[] = $fieldName;
                }
            }
        }
    }
494492
protected function _order($fields)
    {
        $direction = 'ASC';

        $result = [];
        foreach ($fields as $field => $value) {
            if (!is_int($field)) {
                $result[$field] = $value;
                continue;
            }
            if (preg_match('/^(.*?)\s+((?:a|de)sc)$/i', $value, $match)) {
                $value = $match[1];
                $dir = $match[2];
            } else {
                $dir = $direction;
            }
            $result[$value] = $dir;
        }
        return $result;
    }
1345187
protected function _resolveFields($fields)
    {
        if (is_string($fields)) {
            $fields = [$fields];
        }

        if (!is_array($fields)) {
            throw new Exception('Invalid "fields" configuration.');
        }

        $types = array_keys(Type::map());

        foreach ($fields as $field => $type) {
            if (is_numeric($field) && is_string($type)) {
                unset($fields[$field]);
                $field = $type;
                $type = self::DEFAULT_TYPE;
                $fields[$field] = $type;
            }

            if (!in_array($type, $types)) {
                throw new Exception(sprintf('The field "%s" mapped type "%s" was not found.', $field, $type));
            }
        }

        return $fields;
    }
1140473
protected function parseFields($params)
    {
        foreach (explode(',', $params) as $field) {
            // Only add the fields that are on the base resource
            if (! Str::contains($field, '.')) {
                $this->fields[] = trim($field);
            } else {
                $this->additionalFields[] = trim($field);
            }
        }
    }
1330503
private static function setFields($fields) {
        $fieldsString = '';
        $i = 0;
        foreach($fields as $key => $val) {
            $fieldsString .= ($i > 0 ? ",\n  " : '') . self::$_ops->addBackticks($key) .
                             self::$_ops->operandi($val, true, true);
            $i++;
        }
        return $fieldsString;
    }
823083
public function addFields(array $fields)
    {
        array_walk(
            $fields,
            function ($field, $key) {
                $this->addField($key, $field);
            }
        );
    }
631902
public function buildOrderBy(array $fields)
    {
        $this->parseSelectPath();
        foreach ($fields as $field) {
            if (!is_array($field)) {
                $field = [$field];
            }
            $property = array_shift($field);
            if (!in_array($property, $this->aliasMapping) && isset($this->aliasMapping[$property])) {
                $property = $this->aliasMapping[$property];
            }
            $direction = $field ? array_shift($field) : 'ASC';
            $function = $field ? array_shift($field) : null;
            if ($function) {
                $property = sprintf("%s(%s)", $function, $property);
            }
            $this->addOrderBy($property, $direction);
        }

        return $this;
    }
152637
public function addFields($fields)
    {
        if (!is_array($fields)) {
            $fields = array_map(function ($f) {
                return trim($f);
            }, explode(',', $fields));
        }

        $this->fields = array_merge($this->fields, $fields);

        return $this;
    }
573484
private static function handleSelectandi($fields) {
        $i = 0;
        $res = '';
        foreach($fields as $what) {
            $res .= ($i > 0 ? ', ' : '') . self::$_ops->selectandi($what);
            $i++;
        }
        return $res;
    }
89155
public function parse($fields)
    {
        if ( ! $fields) return [];

        // name:string, age:integer
        // name:string(10,2), age:integer
        $fields = preg_split('/\s?,\s/', $fields);

        $parsed = [];

        foreach($fields as $index => $field)
        {
            // Example:
            // name:string:nullable => ['name', 'string', 'nullable']
            // name:string(15):nullable
            $chunks = preg_split('/\s?:\s?/', $field, null);

            // The first item will be our property
            $property = array_shift($chunks);

            // The next will be the schema type
            $type = array_shift($chunks);

            $args = null;

            // See if args were provided, like:
            // name:string(10)
            if (preg_match('/(.+?)\(([^)]+)\)/', $type, $matches))
            {
                $type = $matches[1];
                $args = $matches[2];
            }

            // Finally, anything that remains will
            // be our decorators
            $decorators = $chunks;

            $parsed[$index] = ['field' => $property, 'type' => $type];

            if (isset($args)) $parsed[$index]['args'] = $args;
            if ($decorators) $parsed[$index]['decorators'] = $decorators;
        }

        return $parsed;
    }
256850
protected function _loadFieldData($fields) {
        if (!is_array($fields)) $fields= array($fields);
        else $fields= array_values($fields);
        $criteria= $this->xpdo->newQuery($this->_class, $this->getPrimaryKey());
        $criteria->select($fields);
        if ($rows= xPDOObject :: _loadRows($this->xpdo, $this->_class, $criteria)) {
            $row= $rows->fetch(\PDO::FETCH_ASSOC);
            $rows->closeCursor();
            $this->fromArray($row, '', false, true);
            $this->_lazy= array_diff($this->_lazy, $fields);
        }
    }
915269
private function prepareFields(array $fields)
    {
        $preparedFields = [];

        foreach ($fields as $field) {
            $preparedFields[$field->getName()] = $field;
        }

        return $preparedFields;
    }
868494
public static function all($fields = null)
    {
        static::applySchema();

        $table = static::getTable();
        $writer = static::getDatabase()->getWriter();

        $limit = '';
        if (isset($fields['limit'])) {
            $limit = 'LIMIT '.$fields['limit'];
            unset($fields['limit']);
        }

        $order = '';
        if (isset($fields['order'])) {
            $limit = 'ORDER BY '.$writer->orderBy($fields['order']);
            unset($fields['order']);
        }

        if (isset($fields['fields'])) {
            $fields = array_merge($fields, $fields['fields']);
            unset($fields['fields']);
        }

        $join = '';
        $class = static::getClassName();

        //
        $selectFields = $writer->selectFields($fields, $class, $join);

        //
        $sql = "SELECT {$selectFields} "
             ."FROM {$table} AS {$class} "
             ." {$join} "
             ." {$order} "
             ." {$limit} ";

        $results = static::fetch(
            $sql,
            null,
            false,
            is_string($fields),
            is_null($fields)
        );

        return $results;
    }
596913
public static function cleanFields($fields)
    {
        $fields = parent::cleanFields($fields);

        //If config is requested add id and type as they are need to pull config.
        if (in_array('config', $fields)) {
            $fields[] = 'id';
            $fields[] = 'type';
        }

        //Removing config from field list as it is not a real column in the table.
        if (in_array('config', $fields)) {
            $key = array_keys($fields, 'config');
            unset($fields[$key[0]]);
        }

        return $fields;
    }
657561
public function parseField($fields){
        if(empty($fields)) {
            $fields    = array();
        }
        if(is_string($fields)) {
            $_fields    = explode(',',$fields);
            $fields     = array();
            foreach ($_fields as $f)
                $fields[$f] = true;
        }elseif(is_array($fields)) {
            $_fields    = $fields;
            $fields     = array();
            foreach ($_fields as $f=>$v) {
                if(is_numeric($f))
                    $fields[$v] = true;
                else
                    $fields[$f] = $v ? true : false;
            }
        }
        return $fields;
    }
408518
public function fields($fields) {

        if ( is_array($fields) ) {
            $this->fields = '`'.implode("`, `", $fields).'`';
        } else if ( is_string($fields) ) {
            $this->fields = $fields;
        } else {
            $this->fields = '*';
        }
        return $this;
    }
963903
protected function parse()
    {
        $this->parseParam('sort');
        $this->parseParam('fields');
        $this->parseParam('limit');
        if ($this->isEloquentBuilder()) {
            $this->parseParam('with');
        }
        $this->parseFilter();
    }