netdjw
netdjw

Reputation: 6007

How to extend Laravel's basic route resource in Laravel 6?

I'm using Laravel as an API with many endpoints and many controllers. I'm using Route::resource() method to define REST endpoints, but in most cases I need to add one more endpoint and now my code is looking like this:

Route::get('product/list', 'ProductController@all');
Route::resource('product', 'ProductController');

Route::get('property/list', 'PropertyController@all');
Route::resource('property', 'PropertyController');

Route::get('customer/list', 'CustomerController@all');
Route::resource('customer', 'CustomerController');

...and this sample keeps coming up, over and over again. I think there needs to be a practical and better way to define this {resource}/list URI in one place. Now I need to repeat this sample:

Route::get('{resource}/list', 'NameOfController@all');

Is there a better solution to define this endpoint only once and be available in every controllers? Can I avoid somehow the repetition?

Upvotes: 1

Views: 3829

Answers (2)

Chin Leung
Chin Leung

Reputation: 14941

If you take a look at the resource method in the Illuminate\Routing\Router class, you are going to see this:

if ($this->container && $this->container->bound(ResourceRegistrar::class)) {
    $registrar = $this->container->make(ResourceRegistrar::class);
} else {
    $registrar = new ResourceRegistrar($this);
}

Which means you can bind a ResourceRegistrar to overwrite the default one provided by Laravel. Therefore, to achieve what you want, you could first make a new class, for instance, app/ResourceRegistrar.php, which would extends the Illuminate\Routing\ResourceRegistrar and add a default 'list':

<?php

namespace App;

use Illuminate\Routing\ResourceRegistrar as BaseResourceRegistrar;

class ResourceRegistrar extends BaseResourceRegistrar
{
    protected $resourceDefaults = [
        'index', 'create', 'store', 'show', 'edit', 'update', 'destroy', 'list',
    ];

    /**
     * Add the list method for a resourceful route.
     *
     * @param  string  $name
     * @param  string  $base
     * @param  string  $controller
     * @param  array   $options
     * @return \Illuminate\Routing\Route
     */
    public function addResourceList($name, $base, $controller, $options)
    {
        $uri = $this->getResourceUri($name).'/all';

        $action = $this->getResourceAction($name, $controller, 'list', $options);

        return $this->router->get($uri, $action);
    }
}

And then, you can simply bind the registrar in your AppServiceProvider:

<?php

namespace App\Providers;

use App\ResourceRegistrar;
use Illuminate\Routing\Router;
use Illuminate\Routing\ResourceRegistrar as BaseResourceRegistrar;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        $this->app->bind(BaseResourceRegistrar::class, ResourceRegistrar::class);
    }
}

And you can register your route like you used to without adding the extra line:

Route::resource('product', 'ProductController');
Route::resource('property', 'PropertyController');
Route::resource('customer', 'CustomerController');

Then if you run the php artisan route:list, you should see the {resource}/list route.

Upvotes: 4

IGP
IGP

Reputation: 15909

I made it. You'll need to extend the registrar

1/2 Create Router class

# I made mine in app/Overrides/Router.php
<?php

namespace App\Overrides;

use Illuminate\Routing\Router as BaseRouter;

class Router extends BaseRouter
{
    // You can call it however you want. These are the params you need to pass the original resource() method.
    public function listResource($name, $controller, array $options = [])
    {
        // What make a get route and then a normal resource route you'll be able to call optional methods on.
        $this->get($name.'/all', $controller.'@all')->name($name.'.all');
        return $this->resource($name, $controller, $options);
    }   
}

2/2 Bind it in bootstrap/app.php

$app->singleton('router', function ($app) {
    return new \App\Overrides\Router($app['events'], $app);
});

And you're done.

Now for the example:

Route::listResource('users', 'UserController');
> php artisan r:l
+--------+-----------+----------------------+---------------+---------------------------------------------+--------------+
| Domain | Method    | URI                  | Name          | Action                                      | Middleware   |
+--------+-----------+----------------------+---------------+---------------------------------------------+--------------+
|        | GET|HEAD  | users                | users.index   | App\Http\Controllers\UserController@index   | web          |
|        | POST      | users                | users.store   | App\Http\Controllers\UserController@store   | web          |
|        | GET|HEAD  | users/create         | users.create  | App\Http\Controllers\UserController@create  | web          |
|        | GET|HEAD  | users/list           | users.list    | App\Http\Controllers\UserController@all     | web          |
|        | GET|HEAD  | users/{user}         | users.show    | App\Http\Controllers\UserController@show    | web          |
|        | PUT|PATCH | users/{user}         | users.update  | App\Http\Controllers\UserController@update  | web          |
|        | DELETE    | users/{user}         | users.destroy | App\Http\Controllers\UserController@destroy | web          |
|        | GET|HEAD  | users/{user}/edit    | users.edit    | App\Http\Controllers\UserController@edit    | web          |
+--------+-----------+----------------------+---------------+---------------------------------------------+--------------+


Upvotes: 3

Related Questions