Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Popa's avatar
Level 1

I need help laravel version migration issue from v5.x to v7.x

I have laravel project built on v5.x I have migrated laravel version from v5.x code to v7.x. but I have issue on Table relationship on v7.x for example, belongsTo and hasOne relationship between User and Country table works on v5.x but it doesn't work on v7.x. It looks like table left join doesn't work after v7.x migration ----------User.php------------------

class User extends Base {
	 public function countries()
    {
        return $this->belongsTo(
            \App\Country::class,
            'country',
            'code'
        );
    }
}}

--------Country.php-----------------

class Country extends Base {
  public function user()
    {
        return $this->hasOne(
            \App\User::class,
            'country',
            'code'
        )->active();
    }
}

By the way, I have Resource controller for self api request query parse on v5.x I think there are some updated function on v7.x

<?php

namespace App\Http\Controllers;

use App\Base;
use App\Contracts\ResourceInterface;
use App\Entity;
use App\Traits\HasFees;
use App\Traits\HasImages;
use App\Traits\HasTransactions;
use Illuminate\Container\Container;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Routing\Route;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\MessageBag;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Log;

/**
 * Class ResourceController
 */
class ResourceController extends BaseController implements ResourceInterface
{
    /**
     * The errors array
     *
     * @var array
     */
    protected $errors = [];

    /**
     * The error messages from the message bag
     *
     * @var MessageBag
     */
    protected $errorMessages = [];

    /**
     * Default number of items used for pagination
     *
     * @var int
     */
    protected $itemsPerPage = 50;

    /**
     * All filters that can be used during a query
     *
     * @var array
     */
    protected $filters = [];

    /**
     * The request to use during the call
     *
     * @var Request
     */
    protected $request = null;

    /**
     * The original request that was received from the call
     *
     * @var Request
     */
    protected $originalRequest = null;

    /**
     * Property that will load the main model
     *
     * @var Model
     */
    protected $model = null;

    /**
     * Property that will load the main resource
     *
     * @var resource
     */
    protected $resource = null;

    /**
     * Flag to indicate if the resource should use collection
     *
     * @var bool
     */
    protected $resourceCollection = false;

    /**
     * Flag to indicate if the query builder has aggregates
     *
     * @var bool
     */
    protected $aggregates = false;

    /**
     * All relations that the loaded model has
     *
     * @var array
     */
    protected $relations = [];

    /**
     * Relation classes that should not have methods skipped if
     * encountered
     *
     * @var array
     */
    protected $relationSkips = [
        HasFees::class => [],
        HasImages::class => [
            'image',
            'images',
        ],
        HasTransactions::class => [
            'transaction',
            'transactions',
        ],
        SoftDeletes::class => [],
    ];

    /**
     * All methods that are not a relation
     *
     * @var array
     */
    protected $nonRelationMethods = [
        'processEntries',
        'shouldUpgradeTier',
        'userRefs',
        'getRecentlyUsedAddresses',
        'getRewardsPaid',
        'getRewardsEligibility',
        'hasContact',
        'getVerificationUserRef',
        'processCardOrder',
        'processRewards',
        'forceDelete',
        'boot',
    ];

    /**
     * The logged in entity
     *
     * @var Entity
     */
    protected $entity;

    /**
     * Load the entity if in session as soon as the controller
     * instantiates
     */
    public function __construct()
    {
        // Load the entity if present
        $this->entity = session('entity');
        $this->request = request();
    }

    /**
     * Method to initiate the main controller and route the
     * proper method
     *
     * @return mixed
     *
     * @throws ModelNotFoundException|\Exception
     */
    public function initiate(Request $request)
    {
        // Reset the flags
        $this->resetFlags();
        // Set the original request
        $this->originalRequest = $request;
        // Get the method
        $method = $this->originalRequest->getMethod();
        // Get route information
        $model = $this->originalRequest->route()->parameter('model');
        $id = $this->originalRequest->route()->parameter('id');
        $override = self::METHOD_MAPPING_OVERRIDES['BACKEND'][$model] ?? [];
        // Load the model
        if (isset(self::RESOURCE_MAPPING['BACKEND'][$model])) {
            if (! $this->loadModel($model, $id)) {
                throw new ModelNotFoundException();
            } elseif (! $this->loadRequest($this->originalRequest)) {
                // Default to original request and set flag
                $this->request = $this->originalRequest;
            }

            if (! $this->loadResource($model)) {
                $this->resource = self::RESOURCE_NAMESPACE.'DefaultResource';
            }

            // If all went fine load all relationships
            $this->loadRelations();
        } else {
            // In case this is a raw override not related to a model,
            // just copy over the request
            $this->request = $this->originalRequest;
            $this->errorMessages = new MessageBag();
        }

        // Call proper method according to request
        $methodParam = 'all';
        // Look for any other parameter besides id and model
        $availableParam = collect($this->originalRequest->route()->parameters())
            ->except('model')->keys()->first();
        if ($availableParam && isset(self::METHOD_MAPPING[$method][$availableParam])) {
            $methodParam = $availableParam;
        }
        if (isset($override[$method][$methodParam])) {
            return $this->{$override[$method][$methodParam]}();
        } else {
            return $this->{self::METHOD_MAPPING[$method][$methodParam]}();
        }
    }

    /**
     * Method to properly reset all flags and start a fresh call
     */
    protected function resetFlags()
    {
        $this->resourceCollection = false;
        $this->model = null;
        $this->relations = [];
        $this->resource = null;
        $this->request = null;
        $this->originalRequest = null;
    }

    /**
     * Method to load the resource for this call
     *
     * @param  string  $model The model that came with the call
     * @return bool
     */
    public function loadResource($model)
    {
        $resource = self::RESOURCE_NAMESPACE.'Backend\'.
            self::RESOURCE_MAPPING['BACKEND'][$model].
            'Resource';
        if (! class_exists($resource)) {
            // If it doesn't exist just return false
            return false;
        } else {
            // Set the resource
            $this->resource = $resource;
        }

        return true;
    }

    /**
     * Method to properly load the request for the model
     *
     * @param  Request  $originalRequest The original request
     * @return bool
     */
    public function loadRequest(Request $originalRequest)
    {
        // Create the name
        $model = $originalRequest->route()->parameter('model');
        $request = self::REQUEST_NAMESPACE.'Backend\'.
                   self::RESOURCE_MAPPING['BACKEND'][$model].
                   'Request';
        if (! class_exists($request)) {
            // If it doesn't exist just return false
            return false;
        } else {
            // Instantiate the request associated with this model
            $this->request = new $request(
                $originalRequest->query->all(),
                $originalRequest->request->all(),
                $originalRequest->attributes->all(),
                $originalRequest->cookies->all(),
                $originalRequest->files->all(),
                $originalRequest->server->all(),
                $originalRequest->getContent()
            );
            // Set all needed parameters from original request
            $this->request->setMethod($originalRequest->getMethod());
            $this->request->setUserResolver($originalRequest->getUserResolver());
            $this->request->setRouteResolver($originalRequest->getRouteResolver());
            $this->request->setRequestFormat($originalRequest->getRequestFormat());
            $this->request->setContainer(Container::getInstance());
            $this->request->setJson($originalRequest->json());
            // Validate the request
            // $this->request->validate();
        }

        return true;
    }

    /**
     * Method to properly load the model on the request
     *
     * @param  string  $model The model that came with the call
     * @param  int  $id The id of the model if any
     * @return bool
     */
    protected function loadModel($model, $id = null)
    {
        // Look for the model
        if ($model && isset(self::RESOURCE_MAPPING['BACKEND'][$model]) &&
            ($model = self::MODEL_NAMESPACE.self::RESOURCE_MAPPING['BACKEND'][$model]) &&
            class_exists($model)) {
            // Make sure that the model properly exists
            if ($id && ! ($this->model = $model::find($id))) {
                // If id parameter was sent and it doesn't exist, bail
                return false;
            } elseif (! $id) {
                // Instantiate the model
                $this->model = new $model();
            }

            return true;
        }

        return false;
    }

    /**
     * Method that will load all relations of the loaded model
     *
     * @param string The model to get the classes
     * @return array
     *
     * @throws \ReflectionException
     */
    protected function loadRelations($externalModel = null)
    {
        $model = $externalModel ?? get_class($this->model);
        $reflection = new \ReflectionClass($model);
        $relations = [];

        foreach ($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
            if ($method->class != $model ||
                ! empty($method->getParameters()) ||
                $method->getName() == __FUNCTION__ ||
                in_array($method->getName(), $this->nonRelationMethods)) {
                continue;
            } else {
                $hasRelationSkipMethod = false;
                foreach ($reflection->getTraits() as $trait) {
                    if (in_array($trait->getName(), array_keys($this->relationSkips))) {
                        foreach ($trait->getMethods(\ReflectionMethod::IS_PUBLIC) as $traitMethod) {
                            if ($traitMethod->getName() == $method->getName() &&
                                ! in_array($traitMethod->getName(), $this->relationSkips[$trait->getName()])) {
                                $hasRelationSkipMethod = true;
                                break;
                            }
                        }
                        if ($hasRelationSkipMethod) {
                            break;
                        }
                    }
                }
                if ($hasRelationSkipMethod) {
                    continue;
                }
            }

            try {
                if ($externalModel) {
                    $invoke = new $externalModel();
                } else {
                    $invoke = $this->model;
                }
                $return = $method->invoke($invoke);

                if ($return instanceof Relation) {
                    $relations[$method->getName()] = [
                        'type' => (new \ReflectionClass($return))->getShortName(),
                        'model' => (new \ReflectionClass($return->getRelated()))->getName(),
                        'table' => $return->getModel()->getTable(),
                        'foreignKey' => method_exists($return, 'getQualifiedForeignKeyName') ?
                            $return->getQualifiedForeignKeyName() :
                            $return->getQualifiedOwnerKeyName(),
                        'localKey' => method_exists($return, 'getQualifiedForeignKey') ?
                            $return->getQualifiedForeignKey() :
                            $return->getQualifiedParentKeyName(),
                    ];
                } elseif (isset($return['relation'])) {
                    $relations[$method->getName()] = $return['relation'];
                }
            } catch (\Exception $e) {
            } catch (\Throwable $t) {
            }
        }

        if ($externalModel) {
            return $relations;
        } else {
            $this->relations = $relations;

            return $this->relations;
        }
    }

    /**
     * Method to process all possible nested relation creation
     * in the call
     */
    protected function processNestedRelationCruds()
    {
        if ($this->model->getErrorMessages()) {
            // We don't want to process relation cruds on a model
            // with errors
            return false;
        }
        // Set flag initially to false
        $hasRelationCrud = false;
        // Look for any relation that may be in the request
        foreach ($this->relations as $method => $relation) {
            if ($this->model->$method instanceof Collection) {
                $exists = $this->model->$method()->get()->count();
            } else {
                $exists = $this->model->$method;
            }
            if (! $exists &&
                ($relationData = $this->request->json($method)) &&
                is_array($relationData)) {
                // Set the flag to true so we refresh model later
                $hasRelationCrud = true;
                if (count($relationData) == count($relationData, COUNT_RECURSIVE)) {
                    // There is a relation in the call try to create it
                    $this->processRelationCrud($relationData, $relation['model'], $method);
                } else {
                    foreach ($relationData as $data) {
                        // There is a relation in the call try to create it
                        $this->processRelationCrud($data, $relation['model'], $method);
                    }
                }
            }
        }

        if ($hasRelationCrud) {
            // If we have found relation data refresh the model
            $this->refreshModel();
        }
    }

    /**
     * Method to properly process a relation crud, it will automatically
     * look for the id if it has any and attempt to update it or insert
     * new one
     */
    protected function processRelationCrud($relationData,
        $relationModel,
        $relationMethod)
    {
        if (isset($relationData['id']) &&
            ($relationModel = $relationModel::find($relationData['id']))) {
            // If it has an id it could be an update try looking for the relation
            $relationModel->update($relationData);
        } else {
            // There is a relation in the call try to create it
            $this->model->$relationMethod()->save(new $relationModel($relationData));
        }
    }

    /**
     * Method to properly apply all wheres in the http query parameters
     *
     * @param  Builder  $query The query being built
     * @return Builder|array
     */
    protected function applyWiths(?Builder &$query = null)
    {
        if ($this->request->get('with') &&
            is_array($this->request->get('with'))) {
            // Apply any possible relation
            $withs = [];
            foreach ($this->request->get('with') as $with) {
                $relations = explode('.', $with);

                if (method_exists($this->model, $relations[0])) {
                    $withs[$with] = function ($query) {
                        $query->latest()->limit($this->itemsPerPage * 10);
                    };
                }
            }

            if (! $query) {
                // If sent a null query, just return withs array
                return $withs;
            }
            // Apply with to query directly
            $query->with($withs);
        }

        return $query ?? [];
    }

    /**
     * Method to fully refresh the model instance and load all relations
     *
     * @param  bool  $loadRelations Flag to indicate if relations should be loaded
     * @return Model|null
     */
    protected function refreshModel($loadRelations = true)
    {
        // Load any withs we may have
        $withs = $this->applyWiths();
        // Refresh the model fully
        $this->model->refresh();
        $this->model->load($withs);

        if (! $withs && $loadRelations) {
            // If flag is set and no withs were sent on parameters
            // load all needed relations
            $this->model->load(array_keys($this->relations));
        }

        return $this->model;
    }

    /**
     * Method to properly map out the proper operator of a
     * query call
     *
     * @param  string  $string The string to map
     * @return string
     */
    protected function getOperator($string)
    {
        switch ($string) {
            case self::SEARCH_OPERATORS['NQ']:
                $operator = '<>';
                break;
            case self::SEARCH_OPERATORS['GT']:
                $operator = '>';
                break;
            case self::SEARCH_OPERATORS['LT']:
                $operator = '<';
                break;
            case self::SEARCH_OPERATORS['GTE']:
                $operator = '>=';
                break;
            case self::SEARCH_OPERATORS['LTE']:
                $operator = '<=';
                break;
            case self::SEARCH_OPERATORS['LIKE']:
                $operator = 'like';
                break;
            case self::SEARCH_OPERATORS['EQ']:
            default:
                $operator = '=';
                break;
        }

        return $operator;
    }

    /**
     * Method to apply any needed ordering
     *
     * @param  Builder  $query The query being built
     */
    protected function applyOrdering(Builder &$query)
    {
        if ($this->request->get('order') &&
            is_array($this->request->get('order'))) {
            // Apply any possible ordering
            foreach ($this->request->get('order') as $attribute => $sort) {
                $query->orderBy($attribute, $sort);
            }
        }
    }

    /**
     * Method to apply all possible selects in the request to the query
     *
     * @param  Builder  $query The query being built
     */
    protected function applySelects(Builder &$query)
    {
        $aggregate = [];
        $selects = $this->request->get('select');

        if ($this->request->get('sum')) {
            $aggregate['field'] = $this->request->get('sum');
            $aggregate['function'] = 'SUM';
        } elseif ($this->request->get('count')) {
            $aggregate['field'] = $this->request->get('count');
            $aggregate['function'] = 'COUNT';
        } elseif ($this->request->get('avg')) {
            $aggregate['field'] = $this->request->get('avg');
            $aggregate['function'] = 'AVG';
        }

        if (isset($aggregate['field']) && isset($aggregate['function'])) {
            // Set flag to false as this won't need to return in a resource
            $this->resourceCollection = false;
            $this->aggregates = true;
            if (strpos($aggregate['field'], "{$this->model->getTable()}.") === false) {
                $alias = $aggregate['field'];
                $aggregate['field'] = "{$this->model->getTable()}.{$aggregate['field']}";
            } else {
                $alias = explode('.', $aggregate['field']);
                $alias = end($alias);
            }
            $rawSelect = DB::raw(
                "{$aggregate['function']}({$aggregate['field']}) AS ".
                "$alias"
            );

            if (is_array($selects)) {
                $selects = array_merge([$rawSelect], $selects);
            } else {
                $selects[] = $rawSelect;
            }
        }

        if ($selects) {
            foreach ($selects as $key => $select) {
                if (is_array($select)) {
                    // Make sure format is correct or skip it as it wont work
                    if (count($select) == 1 && is_string(end($select))) {
                        $selects[$key] = DB::raw(
                            key($select).'('.end($select).') AS '.
                            key($select).$key
                        );
                    } elseif (count($select) == 1 &&
                        key($select) == 'dateFormat' &&
                        isset($select['dateFormat']['field']) &&
                        isset($select['dateFormat']['format'])) {
                        $selects[$key] = DB::raw(
                            "DATE_FORMAT({$select['dateFormat']['field']}, ".
                            "\"{$select['dateFormat']['format']}\") AS ".
                            key($select).$key
                        );
                    } else {
                        // Continue, this format is not correct
                        continue;
                    }
                } elseif (is_string($select)) {
                    $this->parseRelationalAttribute($select, $query);
                    $select = explode('.', $select);
                    if (count($select) > 2) {
                        $select = $select[count($select) - 2].'.'.
                            end($select);
                    } else {
                        $select = implode('.', $select);
                    }
                    $selects[$key] = $select;
                }
            }
            $query->select($selects);
        }
    }

    /**
     * Method that will process the where data before it goes
     * to the query builder
     *
     * @param  array  $data The data coming from the array in the query
     * @param  string  $attribute The attribute being created
     * @param  Builder  $query The query being created
     * @return array
     */
    protected function getWhereData($data, &$attribute, &$query)
    {
        // Parse the attribute
        $this->parseRelationalAttribute($attribute, $query);

        $whereData = [];
        if (is_array($data)) {
            $operator = $this->getOperator(key($data));
            $value = end($data);
            if (in_array($operator, self::WILDCARD_OPERATORS) &&
                strpos($value, '%') === false) {
                $value = "%{$value}%";
            }

            $whereData = [
                'operator' => $this->getOperator(key($data)),
                'value' => $value,
            ];
        }

        return $whereData;
    }

    /**
     * Method to properly apply all wheres in the http query parameters
     *
     * @param  Builder  $query The query being built
     */
    protected function applyWheres(Builder &$query)
    {
        if (($and = $this->request->get('and')) &&
            is_array($this->request->get('and'))) {
            $query->where(function ($query) use ($and) {
                foreach ($and as $attribute => $ands) {
                    foreach ($ands as $data) {
                        if ($data = $this->getWhereData($data, $attribute, $query)) {
                            if (($relationalAttributes = explode('.', $attribute)) &&
                                count($relationalAttributes) > 1 &&
                                count(explode('_', $relationalAttributes[0])) == 1) {
                                $attributeName = array_pop($relationalAttributes);
                                $relationName = implode('.', $relationalAttributes);

                                if ($data['value'] == 'null') {
                                    $query->whereHas($relationName, function ($query) use ($attributeName) {
                                        $query->whereNull($attributeName);
                                    });
                                } elseif ($data['value'] == 'notNull') {
                                    $query->whereHas($relationName, function ($query) use ($attributeName) {
                                        $query->whereNotNull($attributeName);
                                    });
                                } else {
                                    $query->whereHas($relationName, function ($query) use ($data, $attributeName) {
                                        $query->where(
                                            $attributeName,
                                            $data['operator'],
                                            $data['value']
                                        );
                                    });
                                }
                            } else {
                                if ($data['value'] == 'null') {
                                    $query->whereNull($attribute);
                                } elseif ($data['value'] == 'notNull') {
                                    $query->whereNotNull($attribute);
                                } else {
                                    $query->where(
                                        $attribute,
                                        $data['operator'],
                                        $data['value']
                                    );
                                }
                            }
                        }
                    }
                }
            });
        }

        if (($or = $this->request->get('or')) &&
            is_array($this->request->get('or'))) {
            $query->where(function ($query) use ($or) {
                foreach ($or as $attribute => $ors) {
                    foreach ($ors as $data) {
                        if ($data = $this->getWhereData($data, $attribute, $query)) {
                            $query->orWhere(
                                $attribute,
                                $data['operator'],
                                $data['value']
                            );
                        }
                    }
                }
            });
        }

        if (($in = $this->request->get('in')) &&
            is_array($this->request->get('in'))) {
            foreach ($this->request->get('in') as $attribute => $in) {
                $query->whereIn($attribute, $in);
            }
        }

        if (($date = $this->request->get('date')) &&
            is_array($this->request->get('date'))) {
            foreach ($date as $attribute => $dates) {
                foreach ($dates as $operator => $value) {
                    $query->whereDate(
                        $attribute,
                        $this->getOperator($operator),
                        $value
                    );
                }
            }
        }
    }

    /**
     * Method to help apply any filters sent during a request
     *
     * @return Builder
     */
    public function applyFilters(Builder $builder)
    {
        if ($this->filters) {
            foreach ($this->filters as $query => $field) {
                if (request()->has($query)) {
                    $builder->where($field, request()->get($query));
                }
            }
        }

        return $builder;
    }

    /**
     * Method that will build needed groupings for the query
     */
    protected function applyGrouping(Builder &$query)
    {
        if (($group = $this->request->get('group')) &&
            is_array($this->request->get('group'))) {
            foreach ($group as $attribute => $grouping) {
                // Parse the attribute
                $this->parseRelationalAttribute($attribute, $query);
                if (is_array($grouping)) {
                    switch (key($grouping)) {
                        case 'month':
                        case 'year':
                        case 'day':
                        case 'hour':
                        case 'minute':
                        case 'week':
                            $attribute = DB::raw(key($grouping)."({$attribute})");
                            break;
                    }
                }
                // Add the grouping
                $query->groupBy($attribute);

            }
        }
    }

    /**
     * Method that will parse the attribute and check if there is a
     * relation
     *
     * @param  string  $attribute The attribute to parse
     * @param  Builder  $query The query being built
     * @return bool
     */
    protected function parseRelationalAttribute($attribute, Builder &$query)
    {
        $relationalAttributes = explode('.', $attribute);
        if (count($relationalAttributes) > 1) {
            // Pop out the last part as it is the field
            array_pop($relationalAttributes);
            foreach ($relationalAttributes as $key => $relationalAttribute) {
                if ($key > 0) {
                    if (substr($relationalAttributes[$key - 1], -3, 3) == 'ies') {
                        $relationalAttributes[$key - 1] = str_replace(
                            'ies',
                            'y',
                            $relationalAttributes[$key - 1]
                        );
                    } elseif (substr($relationalAttributes[$key - 1], -1, 1) == 's') {
                        $relationalAttributes[$key - 1] = str_replace(
                            's',
                            '',
                            $relationalAttributes[$key - 1]
                        );
                    }
                    $externalModelParts = explode('_', $relationalAttributes[$key - 1]);
                    $externalModel = ucfirst(reset($externalModelParts));
                    array_shift($externalModelParts);
                    foreach ($externalModelParts as $externalModelPart) {
                        $externalModel .= ucfirst($externalModelPart);
                    }
                    $relations = $this->loadRelations('App\'.$externalModel);
                } else {
                    $relations = $this->relations;
                }

                // Set the relation table
                $relationAttribute = null;
                $fromMethod = null;
                foreach ($relations as $method => $relation) {
                    if ($relation['table'] == $relationalAttribute ||
                        $method == $relationalAttribute) {
                        if ($method == $relationalAttribute) {
                            $fromMethod = $method;
                        }
                        $relationAttribute = $relation;
                        break;
                    }
                }

                if ($relationAttribute['type'] == 'Custom') {
                    // Make sure we have joined the local key
                    $this->parseRelationalAttribute(
                        $relationAttribute['localKey'],
                        $query
                    );
                }

                if ($query->getQuery()->joins) {
                    foreach ($query->getQuery()->joins as $join) {
                        if ($join->table == $relationAttribute['table']) {
                            return false;
                        }
                    }
                }

                if ($relationAttribute) {
                    $table = $relationAttribute['table'];
                    $foreignKey = $relationAttribute['foreignKey'];
                    if ($fromMethod) {
                        $table .= " as {$fromMethod}";
                        $foreignKey = explode('.', $foreignKey);
                        $foreignKey = "{$fromMethod}.{$foreignKey[1]}";
                    }
                    // Add the relational attribute table to the query
                    $query->leftJoin(
                        $table,
                        $foreignKey,
                        '=',
                        $relationAttribute['localKey']
                    );
                }
            }
        }

        return true;
    }

    /**
     * Method used to properly set the loaded resource to the response
     *
     * @param  object  $resource The resource to return in the response
     * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
     */
    public function resource($resource)
    {
        if ($this->request->get('withDeleted') &&
            $resource instanceof SoftDeletes) {
            // If parameter set, make so we pull with deleted records
            $resource->withTrashed();
        }

        if ($resource instanceof $this->model &&
            method_exists($resource, 'getErrorMessages')) {
            // Set any errors
            $this->errorMessages = $resource->getErrorMessages();
            $this->errors = $this->errorMessages;
        }

        if ($this->resourceCollection) {
            $resource = $this->resource::collection(
                $resource->paginate($this->itemsPerPage)
            );
        } else {
            if ($this->aggregates || $resource->getQuery()->groups) {
                $resource = collect(
                    DB::connection('mysql')->select(
                        $resource->toSql(),
                        $resource->getBindings()
                    )
                );
            } elseif (! ($resource instanceof $this->model)) {
                $resource = $resource->first();
            }

            $resource = new $this->resource($resource);
        }

        return $this->resourceResponse($resource);
    }

    /**
     * Resource wrapper to standardize responses
     *
     * @param  resource  $resource The resource to wrap
     * @param  int  $status The http status code
     * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
     */
    public function resourceResponse(JsonResource $resource, $status = 200)
    {
        // Catch the origin content array
        // $originalContent = $resource->toResponse(Request::capture())->getOriginalContent();
        $originalContent = (array)$resource->toResponse(Request::capture())->getData();
        $request = $this->request ?? Request::capture();
        if ($this->errorMessages) {
            // Set the errors
            $originalContent['error'] = $this->errorMessages;
        }
        $originalContentData = $originalContent;

        if ($this->model &&
            (method_exists($this->model, 'setDataAttribute') ||
                in_array('data', $this->model->getFillable())) &&
            isset($originalContentData['data'])) {
            // If data is an attribute in the mode through getters or filters
            // clear it out
            unset($originalContentData['data']);
        }

        if (count($originalContent) == count($originalContent, COUNT_RECURSIVE) ||
            ! isset($originalContentData['data'])) {
            if (isset($originalContent['data']) && ! $originalContent['data']) {
                $originalContent = [];
            }
            // If the array is not recursive it means it merged the data wrapper wrong
            // so we need to rewrap it
            $originalContent = [
                'data' => $originalContent,
                'error' => $this->errorMessages ? $this->errorMessages->getMessages() : [],
            ];
            // Clean the original content and do a final check
            unset($originalContent['data']['links']);
            unset($originalContent['data']['meta']);
            if (isset($originalContent->data->data) &&
                count($originalContent['data']) == 1) {
                $originalContent['data'] = $originalContent->data->data;
            }
            if (isset($originalContentData['links'])) {
                $originalContent['links'] = $originalContentData['links'];
            }
            if (isset($originalContentData['meta'])) {
                $originalContent['meta'] = $originalContentData['meta'];
            }
        }

        if (isset($originalContent['data']) &&
            ($request->method() == 'POST' ||
             $request->method() == 'PATCH' ||
             $request->method() == 'PUT')) {
            // If this is a request that touched data, only load relations
            // that has data to the response
            $originalContent['data'] = collect($originalContent['data'])
                ->filter(function ($item, $attribute) {
                    if ((is_array($item) && ! $item) ||
                        ($this->model &&
                         ! in_array($attribute, $this->model->getFillable()) &&
                         $item === null)) {
                        return false;
                    }

                    return true;
                })->toArray();
        }

        // Do a final check to make sure the data is properly set
        if (isset($originalContent->data->data->id) ||
            isset($originalContent->data->data[0]->id)) {
            $originalContent['data'] = $originalContent->data->data;
        }

        $response = array_merge([
            'success' => $this->errors ? false : true,
        ], $originalContent);

        return response($response, $status);
    }

    /**
     * The general response wrapper
     *
     * @param  array  $data The data that should be appended to response
     * @param  int  $status The http status code of the response
     * @return \Illuminate\Http\JsonResponse
     */
    public function response($data = [], $status = 200)
    {
        if ($data instanceof Base) {
            // If this is an instance of base, just make to an array
            $data = $data->toArray();
        } elseif ($this->model &&
                  method_exists($this->model, 'getErrorMessages')) {
            // Set any errors
            $this->errorMessages = $this->model->errorMessages;
            $this->errors = $this->errorMessages->getMessages();
        }

        $json = [
            'success' => $this->getErrors() ? false : true,
        ];

        if ($this->errorMessages &&
            ($errors = $this->errorMessages->getMessages())) {
            $json['error'] = $errors;
        } elseif ($this->getErrors()) {
            $json['error'] = $this->getErrors();
        } elseif (empty($data)) {
            $json['data'] = null;
        } elseif (is_array($data) ||
                  is_object($data)) {
            $json['data'] = $data;
        }

        // Clear errors after sending before sending response
        $this->errors = [];

        return response()->json($json, $status);
    }

    /**
     * Fallback to process an existing model with simple update, query or
     * delete
     *
     * @param  Base  $model The model to process
     * @param  int  $id The id of the resource
     * @param  bool  $delete Flag indicating if a delete should be done
     * @param  bool  $query Flag indicating if only a query should be done
     * @return bool|Base
     */
    protected function processExistingModel(Base $model,
        $id,
        bool $delete = false,
        bool $query = false)
    {
        if ($model = $model::find($id)) {
            if (! $query) {
                // Found the model, try to delete it or update it
                return $delete ?
                       $model->delete() :
                       $this->processModel($model);
            }

            return $model;
        }
        $this->setError(
            'The resource sent was not found',
            1
        );

        return false;
    }

    /**
     * Method that will get the results of models and paginate it
     *
     * @param  Base  $model The model to query for
     * @param  array  $withs All the relations to load
     * @param  bool  $asArray Flag indicating if the result should be
     *                      returned as an array
     * @return mixed
     */
    protected function getResults(Base $model, $withs = [], $asArray = true)
    {
        // Load all possible query parameters
        $filters = $model->getFillable();
        // Set any possible sorting
        $sort = request()->get('sort');
        if (! in_array($sort, $filters)) {
            $sort = 'updated_at';
        }
        $order = request()->get('order');
        if (strtolower($order) != 'asc' &&
            strtolower($order) != 'desc') {
            $order = 'desc';
        }
        // Set the amount of items per page
        $itemsPerPage = request()->get('itemsPerPage');
        if (! $itemsPerPage ||
            $itemsPerPage > $this->itemsPerPage) {
            // We allow to set items per page, but we have a limit of 50
            $itemsPerPage = $this->itemsPerPage;
        }

        $modelClass = get_class($model);
        $query = $modelClass::with($withs)
            ->orderBy($sort, $order);

        if (method_exists($model, 'entity') &&
            $this->getAuthedUser()) {
            $query->whereEntityId($this->entity->id);
        }

        foreach ($filters as $filter) {
            if ($filterValue = request()->get($filter)) {
                // For numbers always equal strings go for like
                $operator = is_numeric($filterValue) ? '=' : 'LIKE';
                $search = $operator == 'LIKE' ? "%{$filterValue}%" : $filterValue;
                $query->where($filter, $operator, $search);
            }
        }

        $results = $query->paginate($itemsPerPage);

        return $asArray ? $results->toArray() : $results;
    }

    /**
     * Get the current logged in user
     *
     * @return Entity
     */
    protected function getAuthedUser()
    {
        return $this->entity = session('entity');
    }

    /**
     * Fallback method created to get the new error format
     * of the model and converted back to the old format
     * (will be removed soon)
     *
     *
     * @return Model|bool
     */
    protected function processModel(Base $model)
    {
        // Fill in the model with data sent on the request
        $model->fill(request()->json()->all());
        if (in_array('entity_id', $model->getFillable()) &&
            $this->getAuthedUser()) {
            // Force the entity id to always be the logged in entity
            $model->entity()->associate($this->entity);
        }

        if (! $model->save()) {
            // Try to save, if it fails register any errors
            if ($model->getErrorMessages()) {
                $errors = 0;
                foreach ($model->getErrorMessages() as $errorMessages) {
                    foreach ($errorMessages as $errorMessage) {
                        $this->setError($errorMessage, $errors);
                        $errors++;
                    }
                }
            }

            // Failed to save the model, bail
            return false;
        }

        // All went good, return the model
        return $model;
    }

    /**
     * Get validation parameters and process validations
     *
     * @param  array  $validations The array of validation rules
     * @return bool
     */
    protected function processValidations(array $validations)
    {
        $validation = Validator::make(
            request()->json()->all(),
            $validations
        );

        if ($validation->fails()) {
            foreach ($validation->getMessageBag()->getMessages() as $errorMessages) {
                $errors = 0;
                foreach ($errorMessages as $errorMessage) {
                    $this->setError($errorMessage, $errors);
                    $errors++;
                }
            }

            return false;
        }

        return true;
    }

    /**
     * Method that will help get data from the request
     *
     * @param  string  $key The key go grab from the request
     * @param  string  $method The method to use while looking for the
     *                       key
     * @return mixed
     */
    protected function request($key = null,
        $method = 'json')
    {
        $request = request();
        if (method_exists(request(), $method)) {
            $request = $request->$method();
        }

        return $key ? $request->get($key) : $request->all();
    }

    /**
     * Method that will get all data from the method selected
     *
     * @param  string  $method The method to get all the data from
     * @return array
     */
    protected function requestAll($method = 'json')
    {
        return $this->request(null, $method);
    }
...
}
0 likes
8 replies
jlrdw's avatar

I suggest just starting a new app with latest version and migrate your:

  • models
  • views
  • controllers
  • custom code / classes

Over to new app. Older versions are outdated and no new security patches.

martinbean's avatar

@popa Belongs to and has one relationships have been in Laravel for many, many versions now. They did not change significantly between versions 5 and 7.

You may find the problem is in your Base class, whatever that is.

Popa's avatar
Level 1

Could you check ResourceController source code? it is from v5.x I think it doesn't work properly on v7.x. I looks like there are grammer issue in source code

<?php

namespace App\Http\Controllers;

use App\Base;
use App\Contracts\ResourceInterface;
use App\Entity;
use App\Traits\HasFees;
use App\Traits\HasImages;
use App\Traits\HasTransactions;
use Illuminate\Container\Container;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Routing\Route;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\MessageBag;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Log;

/**
 * Class ResourceController
 */
class ResourceController extends BaseController implements ResourceInterface
{
    /**
     * The errors array
     *
     * @var array
     */
    protected $errors = [];

    /**
     * The error messages from the message bag
     *
     * @var MessageBag
     */
    protected $errorMessages = [];

    /**
     * Default number of items used for pagination
     *
     * @var int
     */
    protected $itemsPerPage = 50;

    /**
     * All filters that can be used during a query
     *
     * @var array
     */
    protected $filters = [];

    /**
     * The request to use during the call
     *
     * @var Request
     */
    protected $request = null;

    /**
     * The original request that was received from the call
     *
     * @var Request
     */
    protected $originalRequest = null;

    /**
     * Property that will load the main model
     *
     * @var Model
     */
    protected $model = null;

    /**
     * Property that will load the main resource
     *
     * @var resource
     */
    protected $resource = null;

    /**
     * Flag to indicate if the resource should use collection
     *
     * @var bool
     */
    protected $resourceCollection = false;

    /**
     * Flag to indicate if the query builder has aggregates
     *
     * @var bool
     */
    protected $aggregates = false;

    /**
     * All relations that the loaded model has
     *
     * @var array
     */
    protected $relations = [];

    /**
     * Relation classes that should not have methods skipped if
     * encountered
     *
     * @var array
     */
    protected $relationSkips = [
        HasFees::class => [],
        HasImages::class => [
            'image',
            'images',
        ],
        HasTransactions::class => [
            'transaction',
            'transactions',
        ],
        SoftDeletes::class => [],
    ];

    /**
     * All methods that are not a relation
     *
     * @var array
     */
    protected $nonRelationMethods = [
        'processEntries',
        'shouldUpgradeTier',
        'userRefs',
        'getRecentlyUsedAddresses',
        'getRewardsPaid',
        'getRewardsEligibility',
        'hasContact',
        'getVerificationUserRef',
        'processCardOrder',
        'processRewards',
        'forceDelete',
        'boot',
    ];

    /**
     * The logged in entity
     *
     * @var Entity
     */
    protected $entity;

    /**
     * Load the entity if in session as soon as the controller
     * instantiates
     */
    public function __construct()
    {
        // Load the entity if present
        $this->entity = session('entity');
        $this->request = request();
    }

    /**
     * Method to initiate the main controller and route the
     * proper method
     *
     * @return mixed
     *
     * @throws ModelNotFoundException|\Exception
     */
    public function initiate(Request $request)
    {
        // Reset the flags
        $this->resetFlags();
        // Set the original request
        $this->originalRequest = $request;
        // Get the method
        $method = $this->originalRequest->getMethod();
        // Get route information
        $model = $this->originalRequest->route()->parameter('model');
        $id = $this->originalRequest->route()->parameter('id');
        $override = self::METHOD_MAPPING_OVERRIDES['BACKEND'][$model] ?? [];
        // Load the model
        if (isset(self::RESOURCE_MAPPING['BACKEND'][$model])) {
            if (! $this->loadModel($model, $id)) {
                throw new ModelNotFoundException();
            } elseif (! $this->loadRequest($this->originalRequest)) {
                // Default to original request and set flag
                $this->request = $this->originalRequest;
            }

            if (! $this->loadResource($model)) {
                $this->resource = self::RESOURCE_NAMESPACE.'DefaultResource';
            }

            // If all went fine load all relationships
            $this->loadRelations();
        } else {
            // In case this is a raw override not related to a model,
            // just copy over the request
            $this->request = $this->originalRequest;
            $this->errorMessages = new MessageBag();
        }

        // Call proper method according to request
        $methodParam = 'all';
        // Look for any other parameter besides id and model
        $availableParam = collect($this->originalRequest->route()->parameters())
            ->except('model')->keys()->first();
        if ($availableParam && isset(self::METHOD_MAPPING[$method][$availableParam])) {
            $methodParam = $availableParam;
        }
        if (isset($override[$method][$methodParam])) {
            return $this->{$override[$method][$methodParam]}();
        } else {
            return $this->{self::METHOD_MAPPING[$method][$methodParam]}();
        }
    }

    /**
     * Method to properly reset all flags and start a fresh call
     */
    protected function resetFlags()
    {
        $this->resourceCollection = false;
        $this->model = null;
        $this->relations = [];
        $this->resource = null;
        $this->request = null;
        $this->originalRequest = null;
    }

    /**
     * Method to load the resource for this call
     *
     * @param  string  $model The model that came with the call
     * @return bool
     */
    public function loadResource($model)
    {
        $resource = self::RESOURCE_NAMESPACE.'Backend\'.
            self::RESOURCE_MAPPING['BACKEND'][$model].
            'Resource';
        if (! class_exists($resource)) {
            // If it doesn't exist just return false
            return false;
        } else {
            // Set the resource
            $this->resource = $resource;
        }

        return true;
    }

    /**
     * Method to properly load the request for the model
     *
     * @param  Request  $originalRequest The original request
     * @return bool
     */
    public function loadRequest(Request $originalRequest)
    {
        // Create the name
        $model = $originalRequest->route()->parameter('model');
        $request = self::REQUEST_NAMESPACE.'Backend\'.
                   self::RESOURCE_MAPPING['BACKEND'][$model].
                   'Request';
        if (! class_exists($request)) {
            // If it doesn't exist just return false
            return false;
        } else {
            // Instantiate the request associated with this model
            $this->request = new $request(
                $originalRequest->query->all(),
                $originalRequest->request->all(),
                $originalRequest->attributes->all(),
                $originalRequest->cookies->all(),
                $originalRequest->files->all(),
                $originalRequest->server->all(),
                $originalRequest->getContent()
            );
            // Set all needed parameters from original request
            $this->request->setMethod($originalRequest->getMethod());
            $this->request->setUserResolver($originalRequest->getUserResolver());
            $this->request->setRouteResolver($originalRequest->getRouteResolver());
            $this->request->setRequestFormat($originalRequest->getRequestFormat());
            $this->request->setContainer(Container::getInstance());
            $this->request->setJson($originalRequest->json());
            // Validate the request
            // $this->request->validate();
        }

        return true;
    }

    /**
     * Method to properly load the model on the request
     *
     * @param  string  $model The model that came with the call
     * @param  int  $id The id of the model if any
     * @return bool
     */
    protected function loadModel($model, $id = null)
    {
        // Look for the model
        if ($model && isset(self::RESOURCE_MAPPING['BACKEND'][$model]) &&
            ($model = self::MODEL_NAMESPACE.self::RESOURCE_MAPPING['BACKEND'][$model]) &&
            class_exists($model)) {
            // Make sure that the model properly exists
            if ($id && ! ($this->model = $model::find($id))) {
                // If id parameter was sent and it doesn't exist, bail
                return false;
            } elseif (! $id) {
                // Instantiate the model
                $this->model = new $model();
            }

            return true;
        }

        return false;
    }

    /**
     * Method that will load all relations of the loaded model
     *
     * @param string The model to get the classes
     * @return array
     *
     * @throws \ReflectionException
     */
    protected function loadRelations($externalModel = null)
    {
        $model = $externalModel ?? get_class($this->model);
        $reflection = new \ReflectionClass($model);
        $relations = [];

        foreach ($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
            if ($method->class != $model ||
                ! empty($method->getParameters()) ||
                $method->getName() == __FUNCTION__ ||
                in_array($method->getName(), $this->nonRelationMethods)) {
                continue;
            } else {
                $hasRelationSkipMethod = false;
                foreach ($reflection->getTraits() as $trait) {
                    if (in_array($trait->getName(), array_keys($this->relationSkips))) {
                        foreach ($trait->getMethods(\ReflectionMethod::IS_PUBLIC) as $traitMethod) {
                            if ($traitMethod->getName() == $method->getName() &&
                                ! in_array($traitMethod->getName(), $this->relationSkips[$trait->getName()])) {
                                $hasRelationSkipMethod = true;
                                break;
                            }
                        }
                        if ($hasRelationSkipMethod) {
                            break;
                        }
                    }
                }
                if ($hasRelationSkipMethod) {
                    continue;
                }
            }

            try {
                if ($externalModel) {
                    $invoke = new $externalModel();
                } else {
                    $invoke = $this->model;
                }
                $return = $method->invoke($invoke);

                if ($return instanceof Relation) {
                    $relations[$method->getName()] = [
                        'type' => (new \ReflectionClass($return))->getShortName(),
                        'model' => (new \ReflectionClass($return->getRelated()))->getName(),
                        'table' => $return->getModel()->getTable(),
                        'foreignKey' => method_exists($return, 'getQualifiedForeignKeyName') ?
                            $return->getQualifiedForeignKeyName() :
                            $return->getQualifiedOwnerKeyName(),
                        'localKey' => method_exists($return, 'getQualifiedForeignKey') ?
                            $return->getQualifiedForeignKey() :
                            $return->getQualifiedParentKeyName(),
                    ];
                } elseif (isset($return['relation'])) {
                    $relations[$method->getName()] = $return['relation'];
                }
            } catch (\Exception $e) {
            } catch (\Throwable $t) {
            }
        }

        if ($externalModel) {
            return $relations;
        } else {
            $this->relations = $relations;

            return $this->relations;
        }
    }

    /**
     * Method to process all possible nested relation creation
     * in the call
     */
    protected function processNestedRelationCruds()
    {
        if ($this->model->getErrorMessages()) {
            // We don't want to process relation cruds on a model
            // with errors
            return false;
        }
        // Set flag initially to false
        $hasRelationCrud = false;
        // Look for any relation that may be in the request
        foreach ($this->relations as $method => $relation) {
            if ($this->model->$method instanceof Collection) {
                $exists = $this->model->$method()->get()->count();
            } else {
                $exists = $this->model->$method;
            }
            if (! $exists &&
                ($relationData = $this->request->json($method)) &&
                is_array($relationData)) {
                // Set the flag to true so we refresh model later
                $hasRelationCrud = true;
                if (count($relationData) == count($relationData, COUNT_RECURSIVE)) {
                    // There is a relation in the call try to create it
                    $this->processRelationCrud($relationData, $relation['model'], $method);
                } else {
                    foreach ($relationData as $data) {
                        // There is a relation in the call try to create it
                        $this->processRelationCrud($data, $relation['model'], $method);
                    }
                }
            }
        }

        if ($hasRelationCrud) {
            // If we have found relation data refresh the model
            $this->refreshModel();
        }
    }

    /**
     * Method to properly process a relation crud, it will automatically
     * look for the id if it has any and attempt to update it or insert
     * new one
     */
    protected function processRelationCrud($relationData,
        $relationModel,
        $relationMethod)
    {
        if (isset($relationData['id']) &&
            ($relationModel = $relationModel::find($relationData['id']))) {
            // If it has an id it could be an update try looking for the relation
            $relationModel->update($relationData);
        } else {
            // There is a relation in the call try to create it
            $this->model->$relationMethod()->save(new $relationModel($relationData));
        }
    }

    /**
     * Method to properly apply all wheres in the http query parameters
     *
     * @param  Builder  $query The query being built
     * @return Builder|array
     */
    protected function applyWiths(?Builder &$query = null)
    {
        if ($this->request->get('with') &&
            is_array($this->request->get('with'))) {
            // Apply any possible relation
            $withs = [];
            foreach ($this->request->get('with') as $with) {
                $relations = explode('.', $with);

                if (method_exists($this->model, $relations[0])) {
                    $withs[$with] = function ($query) {
                        $query->latest()->limit($this->itemsPerPage * 10);
                    };
                }
            }

            if (! $query) {
                // If sent a null query, just return withs array
                return $withs;
            }
            // Apply with to query directly
            $query->with($withs);
        }

        return $query ?? [];
    }

    /**
     * Method to fully refresh the model instance and load all relations
     *
     * @param  bool  $loadRelations Flag to indicate if relations should be loaded
     * @return Model|null
     */
    protected function refreshModel($loadRelations = true)
    {
        // Load any withs we may have
        $withs = $this->applyWiths();
        // Refresh the model fully
        $this->model->refresh();
        $this->model->load($withs);

        if (! $withs && $loadRelations) {
            // If flag is set and no withs were sent on parameters
            // load all needed relations
            $this->model->load(array_keys($this->relations));
        }

        return $this->model;
    }

    /**
     * Method to properly map out the proper operator of a
     * query call
     *
     * @param  string  $string The string to map
     * @return string
     */
    protected function getOperator($string)
    {
        switch ($string) {
            case self::SEARCH_OPERATORS['NQ']:
                $operator = '<>';
                break;
            case self::SEARCH_OPERATORS['GT']:
                $operator = '>';
                break;
            case self::SEARCH_OPERATORS['LT']:
                $operator = '<';
                break;
            case self::SEARCH_OPERATORS['GTE']:
                $operator = '>=';
                break;
            case self::SEARCH_OPERATORS['LTE']:
                $operator = '<=';
                break;
            case self::SEARCH_OPERATORS['LIKE']:
                $operator = 'like';
                break;
            case self::SEARCH_OPERATORS['EQ']:
            default:
                $operator = '=';
                break;
        }

        return $operator;
    }

    /**
     * Method to apply any needed ordering
     *
     * @param  Builder  $query The query being built
     */
    protected function applyOrdering(Builder &$query)
    {
        if ($this->request->get('order') &&
            is_array($this->request->get('order'))) {
            // Apply any possible ordering
            foreach ($this->request->get('order') as $attribute => $sort) {
                $query->orderBy($attribute, $sort);
            }
        }
    }

    /**
     * Method to apply all possible selects in the request to the query
     *
     * @param  Builder  $query The query being built
     */
    protected function applySelects(Builder &$query)
    {
        $aggregate = [];
        $selects = $this->request->get('select');

        if ($this->request->get('sum')) {
            $aggregate['field'] = $this->request->get('sum');
            $aggregate['function'] = 'SUM';
        } elseif ($this->request->get('count')) {
            $aggregate['field'] = $this->request->get('count');
            $aggregate['function'] = 'COUNT';
        } elseif ($this->request->get('avg')) {
            $aggregate['field'] = $this->request->get('avg');
            $aggregate['function'] = 'AVG';
        }

        if (isset($aggregate['field']) && isset($aggregate['function'])) {
            // Set flag to false as this won't need to return in a resource
            $this->resourceCollection = false;
            $this->aggregates = true;
            if (strpos($aggregate['field'], "{$this->model->getTable()}.") === false) {
                $alias = $aggregate['field'];
                $aggregate['field'] = "{$this->model->getTable()}.{$aggregate['field']}";
            } else {
                $alias = explode('.', $aggregate['field']);
                $alias = end($alias);
            }
            $rawSelect = DB::raw(
                "{$aggregate['function']}({$aggregate['field']}) AS ".
                "$alias"
            );

            if (is_array($selects)) {
                $selects = array_merge([$rawSelect], $selects);
            } else {
                $selects[] = $rawSelect;
            }
        }

        if ($selects) {
            foreach ($selects as $key => $select) {
                if (is_array($select)) {
                    // Make sure format is correct or skip it as it wont work
                    if (count($select) == 1 && is_string(end($select))) {
                        $selects[$key] = DB::raw(
                            key($select).'('.end($select).') AS '.
                            key($select).$key
                        );
                    } elseif (count($select) == 1 &&
                        key($select) == 'dateFormat' &&
                        isset($select['dateFormat']['field']) &&
                        isset($select['dateFormat']['format'])) {
                        $selects[$key] = DB::raw(
                            "DATE_FORMAT({$select['dateFormat']['field']}, ".
                            "\"{$select['dateFormat']['format']}\") AS ".
                            key($select).$key
                        );
                    } else {
                        // Continue, this format is not correct
                        continue;
                    }
                } elseif (is_string($select)) {
                    $this->parseRelationalAttribute($select, $query);
                    $select = explode('.', $select);
                    if (count($select) > 2) {
                        $select = $select[count($select) - 2].'.'.
                            end($select);
                    } else {
                        $select = implode('.', $select);
                    }
                    $selects[$key] = $select;
                }
            }
            $query->select($selects);
        }
    }

    /**
     * Method that will process the where data before it goes
     * to the query builder
     *
     * @param  array  $data The data coming from the array in the query
     * @param  string  $attribute The attribute being created
     * @param  Builder  $query The query being created
     * @return array
     */
    protected function getWhereData($data, &$attribute, &$query)
    {
        // Parse the attribute
        $this->parseRelationalAttribute($attribute, $query);

        $whereData = [];
        if (is_array($data)) {
            $operator = $this->getOperator(key($data));
            $value = end($data);
            if (in_array($operator, self::WILDCARD_OPERATORS) &&
                strpos($value, '%') === false) {
                $value = "%{$value}%";
            }

            $whereData = [
                'operator' => $this->getOperator(key($data)),
                'value' => $value,
            ];
        }

        return $whereData;
    }

    /**
     * Method to properly apply all wheres in the http query parameters
     *
     * @param  Builder  $query The query being built
     */
    protected function applyWheres(Builder &$query)
    {
        if (($and = $this->request->get('and')) &&
            is_array($this->request->get('and'))) {
            $query->where(function ($query) use ($and) {
                foreach ($and as $attribute => $ands) {
                    foreach ($ands as $data) {
                        if ($data = $this->getWhereData($data, $attribute, $query)) {
                            if (($relationalAttributes = explode('.', $attribute)) &&
                                count($relationalAttributes) > 1 &&
                                count(explode('_', $relationalAttributes[0])) == 1) {
                                $attributeName = array_pop($relationalAttributes);
                                $relationName = implode('.', $relationalAttributes);

                                if ($data['value'] == 'null') {
                                    $query->whereHas($relationName, function ($query) use ($attributeName) {
                                        $query->whereNull($attributeName);
                                    });
                                } elseif ($data['value'] == 'notNull') {
                                    $query->whereHas($relationName, function ($query) use ($attributeName) {
                                        $query->whereNotNull($attributeName);
                                    });
                                } else {
                                    $query->whereHas($relationName, function ($query) use ($data, $attributeName) {
                                        $query->where(
                                            $attributeName,
                                            $data['operator'],
                                            $data['value']
                                        );
                                    });
                                }
                            } else {
                                if ($data['value'] == 'null') {
                                    $query->whereNull($attribute);
                                } elseif ($data['value'] == 'notNull') {
                                    $query->whereNotNull($attribute);
                                } else {
                                    $query->where(
                                        $attribute,
                                        $data['operator'],
                                        $data['value']
                                    );
                                }
                            }
                        }
                    }
                }
            });
        }

        if (($or = $this->request->get('or')) &&
            is_array($this->request->get('or'))) {
            $query->where(function ($query) use ($or) {
                foreach ($or as $attribute => $ors) {
                    foreach ($ors as $data) {
                        if ($data = $this->getWhereData($data, $attribute, $query)) {
                            $query->orWhere(
                                $attribute,
                                $data['operator'],
                                $data['value']
                            );
                        }
                    }
                }
            });
        }

        if (($in = $this->request->get('in')) &&
            is_array($this->request->get('in'))) {
            foreach ($this->request->get('in') as $attribute => $in) {
                $query->whereIn($attribute, $in);
            }
        }

        if (($date = $this->request->get('date')) &&
            is_array($this->request->get('date'))) {
            foreach ($date as $attribute => $dates) {
                foreach ($dates as $operator => $value) {
                    $query->whereDate(
                        $attribute,
                        $this->getOperator($operator),
                        $value
                    );
                }
            }
        }
    }

    /**
     * Method to help apply any filters sent during a request
     *
     * @return Builder
     */
    public function applyFilters(Builder $builder)
    {
        if ($this->filters) {
            foreach ($this->filters as $query => $field) {
                if (request()->has($query)) {
                    $builder->where($field, request()->get($query));
                }
            }
        }

        return $builder;
    }

    /**
     * Method that will build needed groupings for the query
     */
    protected function applyGrouping(Builder &$query)
    {
        if (($group = $this->request->get('group')) &&
            is_array($this->request->get('group'))) {
            foreach ($group as $attribute => $grouping) {
                // Parse the attribute
                $this->parseRelationalAttribute($attribute, $query);
                if (is_array($grouping)) {
                    switch (key($grouping)) {
                        case 'month':
                        case 'year':
                        case 'day':
                        case 'hour':
                        case 'minute':
                        case 'week':
                            $attribute = DB::raw(key($grouping)."({$attribute})");
                            break;
                    }
                }
                // Add the grouping
                $query->groupBy($attribute);

            }
        }
    }

    /**
     * Method that will parse the attribute and check if there is a
     * relation
     *
     * @param  string  $attribute The attribute to parse
     * @param  Builder  $query The query being built
     * @return bool
     */
    protected function parseRelationalAttribute($attribute, Builder &$query)
    {
        $relationalAttributes = explode('.', $attribute);
        if (count($relationalAttributes) > 1) {
            // Pop out the last part as it is the field
            array_pop($relationalAttributes);
            foreach ($relationalAttributes as $key => $relationalAttribute) {
                if ($key > 0) {
                    if (substr($relationalAttributes[$key - 1], -3, 3) == 'ies') {
                        $relationalAttributes[$key - 1] = str_replace(
                            'ies',
                            'y',
                            $relationalAttributes[$key - 1]
                        );
                    } elseif (substr($relationalAttributes[$key - 1], -1, 1) == 's') {
                        $relationalAttributes[$key - 1] = str_replace(
                            's',
                            '',
                            $relationalAttributes[$key - 1]
                        );
                    }
                    $externalModelParts = explode('_', $relationalAttributes[$key - 1]);
                    $externalModel = ucfirst(reset($externalModelParts));
                    array_shift($externalModelParts);
                    foreach ($externalModelParts as $externalModelPart) {
                        $externalModel .= ucfirst($externalModelPart);
                    }
                    $relations = $this->loadRelations('App\'.$externalModel);
                } else {
                    $relations = $this->relations;
                }

                // Set the relation table
                $relationAttribute = null;
                $fromMethod = null;
                foreach ($relations as $method => $relation) {
                    if ($relation['table'] == $relationalAttribute ||
                        $method == $relationalAttribute) {
                        if ($method == $relationalAttribute) {
                            $fromMethod = $method;
                        }
                        $relationAttribute = $relation;
                        break;
                    }
                }

                if ($relationAttribute['type'] == 'Custom') {
                    // Make sure we have joined the local key
                    $this->parseRelationalAttribute(
                        $relationAttribute['localKey'],
                        $query
                    );
                }

                if ($query->getQuery()->joins) {
                    foreach ($query->getQuery()->joins as $join) {
                        if ($join->table == $relationAttribute['table']) {
                            return false;
                        }
                    }
                }

                if ($relationAttribute) {
                    $table = $relationAttribute['table'];
                    $foreignKey = $relationAttribute['foreignKey'];
                    if ($fromMethod) {
                        $table .= " as {$fromMethod}";
                        $foreignKey = explode('.', $foreignKey);
                        $foreignKey = "{$fromMethod}.{$foreignKey[1]}";
                    }
                    // Add the relational attribute table to the query
                    $query->leftJoin(
                        $table,
                        $foreignKey,
                        '=',
                        $relationAttribute['localKey']
                    );
                }
            }
        }

        return true;
    }

    /**
     * Method used to properly set the loaded resource to the response
     *
     * @param  object  $resource The resource to return in the response
     * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
     */
    public function resource($resource)
    {
        if ($this->request->get('withDeleted') &&
            $resource instanceof SoftDeletes) {
            // If parameter set, make so we pull with deleted records
            $resource->withTrashed();
        }

        if ($resource instanceof $this->model &&
            method_exists($resource, 'getErrorMessages')) {
            // Set any errors
            $this->errorMessages = $resource->getErrorMessages();
            $this->errors = $this->errorMessages;
        }

        if ($this->resourceCollection) {
            $resource = $this->resource::collection(
                $resource->paginate($this->itemsPerPage)
            );
        } else {
            if ($this->aggregates || $resource->getQuery()->groups) {
                $resource = collect(
                    DB::connection('mysql')->select(
                        $resource->toSql(),
                        $resource->getBindings()
                    )
                );
            } elseif (! ($resource instanceof $this->model)) {
                $resource = $resource->first();
            }

            $resource = new $this->resource($resource);
        }

        return $this->resourceResponse($resource);
    }

    /**
     * Resource wrapper to standardize responses
     *
     * @param  resource  $resource The resource to wrap
     * @param  int  $status The http status code
     * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
     */
    public function resourceResponse(JsonResource $resource, $status = 200)
    {
        // Catch the origin content array
        // $originalContent = $resource->toResponse(Request::capture())->getOriginalContent();
        $originalContent = (array)$resource->toResponse(Request::capture())->getData();
        $request = $this->request ?? Request::capture();
        if ($this->errorMessages) {
            // Set the errors
            $originalContent['error'] = $this->errorMessages;
        }
        $originalContentData = $originalContent;

        if ($this->model &&
            (method_exists($this->model, 'setDataAttribute') ||
                in_array('data', $this->model->getFillable())) &&
            isset($originalContentData['data'])) {
            // If data is an attribute in the mode through getters or filters
            // clear it out
            unset($originalContentData['data']);
        }

        if (count($originalContent) == count($originalContent, COUNT_RECURSIVE) ||
            ! isset($originalContentData['data'])) {
            if (isset($originalContent['data']) && ! $originalContent['data']) {
                $originalContent = [];
            }
            // If the array is not recursive it means it merged the data wrapper wrong
            // so we need to rewrap it
            $originalContent = [
                'data' => $originalContent,
                'error' => $this->errorMessages ? $this->errorMessages->getMessages() : [],
            ];
            // Clean the original content and do a final check
            unset($originalContent['data']['links']);
            unset($originalContent['data']['meta']);
            if (isset($originalContent->data->data) &&
                count($originalContent['data']) == 1) {
                $originalContent['data'] = $originalContent->data->data;
            }
            if (isset($originalContentData['links'])) {
                $originalContent['links'] = $originalContentData['links'];
            }
            if (isset($originalContentData['meta'])) {
                $originalContent['meta'] = $originalContentData['meta'];
            }
        }

        if (isset($originalContent['data']) &&
            ($request->method() == 'POST' ||
             $request->method() == 'PATCH' ||
             $request->method() == 'PUT')) {
            // If this is a request that touched data, only load relations
            // that has data to the response
            $originalContent['data'] = collect($originalContent['data'])
                ->filter(function ($item, $attribute) {
                    if ((is_array($item) && ! $item) ||
                        ($this->model &&
                         ! in_array($attribute, $this->model->getFillable()) &&
                         $item === null)) {
                        return false;
                    }

                    return true;
                })->toArray();
        }

        // Do a final check to make sure the data is properly set
        if (isset($originalContent->data->data->id) ||
            isset($originalContent->data->data[0]->id)) {
            $originalContent['data'] = $originalContent->data->data;
        }

        $response = array_merge([
            'success' => $this->errors ? false : true,
        ], $originalContent);

        return response($response, $status);
    }

    /**
     * The general response wrapper
     *
     * @param  array  $data The data that should be appended to response
     * @param  int  $status The http status code of the response
     * @return \Illuminate\Http\JsonResponse
     */
    public function response($data = [], $status = 200)
    {
        if ($data instanceof Base) {
            // If this is an instance of base, just make to an array
            $data = $data->toArray();
        } elseif ($this->model &&
                  method_exists($this->model, 'getErrorMessages')) {
            // Set any errors
            $this->errorMessages = $this->model->errorMessages;
            $this->errors = $this->errorMessages->getMessages();
        }

        $json = [
            'success' => $this->getErrors() ? false : true,
        ];

        if ($this->errorMessages &&
            ($errors = $this->errorMessages->getMessages())) {
            $json['error'] = $errors;
        } elseif ($this->getErrors()) {
            $json['error'] = $this->getErrors();
        } elseif (empty($data)) {
            $json['data'] = null;
        } elseif (is_array($data) ||
                  is_object($data)) {
            $json['data'] = $data;
        }

        // Clear errors after sending before sending response
        $this->errors = [];

        return response()->json($json, $status);
    }

    /**
     * Fallback to process an existing model with simple update, query or
     * delete
     *
     * @param  Base  $model The model to process
     * @param  int  $id The id of the resource
     * @param  bool  $delete Flag indicating if a delete should be done
     * @param  bool  $query Flag indicating if only a query should be done
     * @return bool|Base
     */
    protected function processExistingModel(Base $model,
        $id,
        bool $delete = false,
        bool $query = false)
    {
        if ($model = $model::find($id)) {
            if (! $query) {
                // Found the model, try to delete it or update it
                return $delete ?
                       $model->delete() :
                       $this->processModel($model);
            }

            return $model;
        }
        $this->setError(
            'The resource sent was not found',
            1
        );

        return false;
    }

    /**
     * Method that will get the results of models and paginate it
     *
     * @param  Base  $model The model to query for
     * @param  array  $withs All the relations to load
     * @param  bool  $asArray Flag indicating if the result should be
     *                      returned as an array
     * @return mixed
     */
    protected function getResults(Base $model, $withs = [], $asArray = true)
    {
        // Load all possible query parameters
        $filters = $model->getFillable();
        // Set any possible sorting
        $sort = request()->get('sort');
        if (! in_array($sort, $filters)) {
            $sort = 'updated_at';
        }
        $order = request()->get('order');
        if (strtolower($order) != 'asc' &&
            strtolower($order) != 'desc') {
            $order = 'desc';
        }
        // Set the amount of items per page
        $itemsPerPage = request()->get('itemsPerPage');
        if (! $itemsPerPage ||
            $itemsPerPage > $this->itemsPerPage) {
            // We allow to set items per page, but we have a limit of 50
            $itemsPerPage = $this->itemsPerPage;
        }

        $modelClass = get_class($model);
        $query = $modelClass::with($withs)
            ->orderBy($sort, $order);

        if (method_exists($model, 'entity') &&
            $this->getAuthedUser()) {
            $query->whereEntityId($this->entity->id);
        }

        foreach ($filters as $filter) {
            if ($filterValue = request()->get($filter)) {
                // For numbers always equal strings go for like
                $operator = is_numeric($filterValue) ? '=' : 'LIKE';
                $search = $operator == 'LIKE' ? "%{$filterValue}%" : $filterValue;
                $query->where($filter, $operator, $search);
            }
        }

        $results = $query->paginate($itemsPerPage);

        return $asArray ? $results->toArray() : $results;
    }

    /**
     * Get the current logged in user
     *
     * @return Entity
     */
    protected function getAuthedUser()
    {
        return $this->entity = session('entity');
    }

    /**
     * Fallback method created to get the new error format
     * of the model and converted back to the old format
     * (will be removed soon)
     *
     *
     * @return Model|bool
     */
    protected function processModel(Base $model)
    {
        // Fill in the model with data sent on the request
        $model->fill(request()->json()->all());
        if (in_array('entity_id', $model->getFillable()) &&
            $this->getAuthedUser()) {
            // Force the entity id to always be the logged in entity
            $model->entity()->associate($this->entity);
        }

        if (! $model->save()) {
            // Try to save, if it fails register any errors
            if ($model->getErrorMessages()) {
                $errors = 0;
                foreach ($model->getErrorMessages() as $errorMessages) {
                    foreach ($errorMessages as $errorMessage) {
                        $this->setError($errorMessage, $errors);
                        $errors++;
                    }
                }
            }

            // Failed to save the model, bail
            return false;
        }

        // All went good, return the model
        return $model;
    }

    /**
     * Get validation parameters and process validations
     *
     * @param  array  $validations The array of validation rules
     * @return bool
     */
    protected function processValidations(array $validations)
    {
        $validation = Validator::make(
            request()->json()->all(),
            $validations
        );

        if ($validation->fails()) {
            foreach ($validation->getMessageBag()->getMessages() as $errorMessages) {
                $errors = 0;
                foreach ($errorMessages as $errorMessage) {
                    $this->setError($errorMessage, $errors);
                    $errors++;
                }
            }

            return false;
        }

        return true;
    }

    /**
     * Method that will help get data from the request
     *
     * @param  string  $key The key go grab from the request
     * @param  string  $method The method to use while looking for the
     *                       key
     * @return mixed
     */
    protected function request($key = null,
        $method = 'json')
    {
        $request = request();
        if (method_exists(request(), $method)) {
            $request = $request->$method();
        }

        return $key ? $request->get($key) : $request->all();
    }

    /**
     * Method that will get all data from the method selected
     *
     * @param  string  $method The method to get all the data from
     * @return array
     */
    protected function requestAll($method = 'json')
    {
        return $this->request(null, $method);
    }

    /**
     * Add errors to response
     */
    public function setError($message, $code)
    {
        $this->errors[] = [
            'code' => $code,
            'message' => $message,
        ];
    }

    /**
     * Get all errors during the run
     *
     * @return array
     */
    public function getErrors()
    {
        return $this->errors;
    }
}



Snapey's avatar

why would you paste all that again, and expect someone to go through it?

Cant say Ive ever needed to rewrite eloquent in any of my projects

I would seriously consider moving your project into a new Laravel 10 framework. Laravel 9 is already unsupported and version 11 is released any day. Moving to 7 is not really any progress.

Popa's avatar
Level 1

due to our project package dependencies, we can't upgrade version more than v7.x

Snapey's avatar

@Popa Then you have a major rewrite on your hands. Being stuck on version 7 is a bad place to be, and to be honest you may as well stay on 5.x

jlrdw's avatar

@Popa You may want to wean yourself away from dependencies and write some of the code yourself.

A suggestion only. But This is one of the problems of depending on external packages.

Please or to participate in this conversation.