Timeline Taxi Out now: my sci-fi novel Timeline Taxi is published!

Abstract resources in Laravel Nova

Heads up!

This is a guest post, written by Bruno Falcao. If you're interested in writing guest posts yourselves, you can find all information on how to over here.

One of the major coding architecture strategies I use when building a complex Laravel Nova project is the ability to have an abstract resource class.

Before starting by the way, if you want deep dive in Nova, I suggest you subscribe to updates in my upcoming course Mastering Nova that will be released this mid-summer!


An abstract resource class will inherit the base Resource class. This allows you to override specific methods to add functionality on your real resource classes.

In the end, any method that you improve in your custom base class, will be available on your model resources. I'll show you how to create the abstract resource, and then we'll at concrete improvements.

We start by creating a file AbstractResource.php inside app/Nova, like this:

app/
  Nova/
     AbstractResource.php

At first, the AbstractResource looks like this:

namespace App\Nova;

abstract class AbstractResource extends Resource
{
}

Next, in your Resource classes just inherit from this abstract Resource instead of the Nova Resource one:

namespace App\Nova;

use App\Nova\AbstractResource;

class Review extends AbstractResource
{
    //
}

So, let's look at some examples of improvements you can add to your new abstract resource.

# Default sorting

On your abstract Resource write this code:

public static function indexQuery(NovaRequest $request, $query)
{
    $uriKey = static::uriKey();
    
    if (($request->orderByDirection ?? null) !== null) {
        return $query;
    }
    
    if (! empty(static::$indexDefaultOrder)) {
        $query->getQuery()->orders = [];

        return $query->orderBy(
            key(static::$indexDefaultOrder), 
            reset(static::$indexDefaultOrder)
        );
    }
}

Then on your model resource:

public static $indexDefaultOrder = ['email' => 'asc'];

This will sort your index query by "email, asc" in case there is not a pre-selected sorting order.

# Search relationships

If you have a relationship field, you might have seen that you cannot use it to search on your Resource search field. In that case, you can use the titasgailius/search-relations package.

To install it, just import it via Composer:

composer require titasgailius/search-relations

Then in your Abstract Resource, you can add it like:

use Titasgailius\SearchRelations\SearchesRelations;

abstract class AbstractResource extends Resource
{
    use SearchesRelations;
}

Henceforth, on your model resources, you can simply add:

public static $searchRelations = [
    'user' => ['username', 'email'],
];

where the key is your relationship name, and then an array of searchable column values. Therefore you can now search on your relationship columns!

# Sharing Cards, Lenses, Actions and Filters

Let's say you would like to have a generic card that shows information about when was the last time your current resource was updated, and some other extra information regarding your resource; or an action that actually will change status on models that share a status_type column.

All of this functionality can be shared between model resources.

As an example, let's say you want to add a new Card to all of the model resources that share your abstract resource, you can do it like:

public function cards(Request $request){
    return [
        new ResourceInformation(
            $this->getCurrentResourceInstance($this->getModelInstance())
        ),
    ];
}

protected function getModelInstance()
{
    $resourceKey = explode('/', request()->path())[1];

    $resourceClass = Nova::resourceForKey($resourceKey);

    return new $resourceClass::$model;
}

and in your model Resource:

public function cards(Request $request)
{
    return array_merge(
        [/* your cards */], 
        parent::cards($request),
    );
}

# Disable 'trashed' behavior

The BelongsTo field already has an option to remove the checkbox 'With Trashed' (basically not to show trashed items), but what if want to remove it from any other relationship operation (e.g.: BelongsToMany)?

You just need to apply the following code in your abstract resource:

use Illuminate\Support\Facades\Gate;

/**
 * Based the trashed behavior on a new policy called trashedAny()
 *
 * @return boolean
 */
public static function softDeletes()
{
    // Is this resource authorized on trashedAny?
    if (static::authorizable()) {
        if (! method_exists(
            Gate::getPolicyFor(static::newModel()),
            'trashedAny'
        )) {
            return true;
        }       

        return Gate::check('trashedAny', static::class));
    };

    return parent::softDeletes();
}

in this example, all you have to do is to define a policy for your model, and then create a new method called trashedAny(User $user), as example:

public function trashedAny(User $user)
{
    return false;
}

These were examples that can trigger your thoughts about how to leverage Abstract Resources on your Nova projects.

And if I was able to convince you :) I suggest you subscribe to updates in my upcoming course Mastering Nova that will be released this mid-summer!

Best, Bruno