Search code examples
laravellaravel-routinglaravel-6

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?


Solution

  • 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.