laravel-json-api / laravel

JSON:API for Laravel applications
MIT License
523 stars 43 forks source link

Avoid loading attribute mutators when using sparse fieldset #241

Open freestyledork opened 1 year ago

freestyledork commented 1 year ago

I know the current version only filters the output for sparse fields. I was wondering if there is an easy fix to avoid loading mutator attributes. I have a few attributes on my models with some complex logic that are getting ran every request when it's not needed resulting in a lot of extra database hits and performance slowdowns. I've tried messing with eager loads, but that gets messy quick. I am open to other solutions, perhaps proxy models? I would prefer keep the calls clean if at all possible.

Any advice is appreciated. Thanks

freestyledork commented 1 year ago

I attempted to come up with a temporary solution for not loading sparse fields. Extending the JsonApiResource and using custom resource classes I can effectively avoid loading unrelated fields. This hasn't been tested in all scenarios. I am not sure if it will work in all combination of available options, but for my initial needs, it's sufficient. Perhaps a head start of some logic needed for v4.

Create a JsonApiResource class extending the package base class to add a couple helper methods.

    /**
     * Extract sparse field sets from the provided request.
     *
     * @param  Request  $request
     *
     * @return FieldSets|null
     */
    protected function extractSparseFieldSets($request): ?FieldSets
    {
        if ($request instanceof QueryParameters) {
            return $request->sparseFieldSets();
        }

        if ($request->query->has('fields')) {
            return FieldSets::fromArray($request->query('fields') ?: []);
        }

        return null;
    }

    protected function getDefaultAttributes($request): array
    {
        $sparseFields      = $this->extractSparseFieldSets($request);
        $fields            = $sparseFields?->fields()[$this->type] ?? [];
        $usingSparseFields = count($fields) > 0;

        $schemaAttributes = [];
        foreach ($this->schema->attributes() as $attr) {
            if ($attr instanceof SerializableAttribute && $attr->isNotHidden($request)) {
                $fieldName = $attr->serializedFieldName();
                if ( ! $usingSparseFields || in_array($fieldName, $fields, true)) {
                    $schemaAttributes[$fieldName] = $attr->serialize($this->resource);
                }
            }
        }

        return $schemaAttributes;
    }

Then from every custom models resource class I just call the $this-getDefaultAttributes($request) from the attributes function.

@lindyhopchris Do you see any obvious issues with this solution?

lindyhopchris commented 10 months ago

Hey! Sorry for not replying to this earlier. I see no problem with that as a temporary solution. I'm planning a permanent solution for the sparse fieldsets as part of all the upgrades I'm working on at the moment.