Mike Rockétt

Avoiding repetition in Laravel Eloquent Resources

Making resource classes just a bit easier to work with.

Published 13 March 2021 in Laravel

One more tip on Eloquent Resources: repeating id, created_at and updated_at is a chore. The chances of these columns ever being renamed are pretty much zero, but typing it out every time you create a new resource is, well, a pain.

I’ve adopted a simple abstract class that takes care of this automatically. It makes for a cleaner resource class, uses a new method to make things a little clearer, and adds a helper for only including elements when they are set.

Here goes:

<?php

namespace App\Resources;

use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Http\Resources\MissingValue;

abstract class ModelResource extends JsonResource
{
  public Request $request;
  public bool $identifier = true;
  public bool $timestamps = true;

  public function toArray($request): array
  {
    $this->request = $request;

    return array_merge([
      $this->mergeWhen($this->identifier, [
        'id' => $this->id,
      ]),

      $this->mergeWhen($this->timestamps, [
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
      ]),

      $this->mergeWhen(
        $this->timestamps && in_array(
          SoftDeletes::class,
          class_uses($this->resource)
        ),
        ['deleted_at' => $this->deleted_at]
      ),
    ], $this->transformTo());
  }

  public function whenSet(string $attribute): MissingValue|mixed
  {
    return $this->when(
      isset($this->resource->$attribute),
      $this->resource->$attribute,
    );
  }

  abstract function transformTo(): array;
}

The ability to turn the ID and timestamps on and off is done through simple properties on the implementing resource class.

Classes that extend this need to implement the transformTo method, and may access the request from the class instance itself (as opposed to an argument).

The whenSet method is simply a shortcut to using isset on a resource attribute – similar to whenLoaded, but for attributes that might not be present. Ever done a withCount or loadCount and then wondered what the cleanest approach to excluding the counts when they’re not present would be? isset is seemingly the way, and this helps make it a little more trivial.

(Note: the method shown here is returning a union type and uses mixed – this is only supported in PHP 8, so remove it if you haven’t upgraded yet.)

Usage is simple and predictable. Create a new resource that extends ModelResource, and then call it as usual.

<?php

namespace App\Resources;

use App\Resources\ModelResource;

class UserModelResource extends ModelResource
{
  public function transformTo(): array
  {
    return [
      'name' => $this->name,
      'avatar_url' => $this->avatar_url,
      'state' => $this->state,
      'posts_count' => $this->whenSet('posts_count'),
    ];
  }
}

If you need to turn off IDs and timestamps, simply add the following properties to the class:

public bool $identifier = false;
public bool $timestamps = false;

When you need access to the request, it’s available in $this->request, right there next to $this->resource.