168

I want to know if it is possible to add new methods to a resource controller in Laravel and how you do it.

I know that these methods are the default (index, create, store, edit, update, destroy). Now I want to add additional methods and routes to the same controller.

Is that possible?

Joseph Silber
  • 214,931
  • 59
  • 362
  • 292
user2403824
  • 1,693
  • 2
  • 11
  • 4

12 Answers12

328

Just add a route to that method separately, before you register the resource:

Route::get('foo/bar', 'FooController@bar');
Route::resource('foo', 'FooController');
Joseph Silber
  • 214,931
  • 59
  • 362
  • 292
  • 16
    Note that your new methods have to go *above* the `::resource` otherwise you get an error message, "No query results for model". – mpen Mar 30 '14 at 18:11
  • How would you pass a parameter like {id}? Currently I've coded my custom method inline in routes.php (like the example here https://laravel.com/docs/5.1/routing#route-parameters). Ideally I'd like to pass the parameter to run in FooController. – ATutorMe Jan 08 '16 at 07:13
  • 1
    Found the problem - wasn't in the Route syntax. Was actually in some custom Middleware that I'm using! Route::get('foo/{id}/bar', 'FooController@bar'); will pass the id to bar($id) method. Thanks! – ATutorMe Jan 08 '16 at 07:41
  • Can someone explain why the custom route should go above the resource route ??? I've done some tests and seems to have no diference between putting above or below... – Ricardo Vigatti Jun 16 '16 at 14:21
  • 4
    @RicardoVigatti - The resource registers this route: `Route::get('foo/{id}', ...)`. This swallows all routes starting with `foo` and having one additional segments, including `foo/bar`. – Joseph Silber Jun 17 '16 at 01:14
  • @Joseph Silber Maybe you can help me. Look at this : https://stackoverflow.com/questions/49594340/how-can-i-implement-resource-controllers-if-i-use-many-get-on-the-laravel – moses toh Apr 01 '18 at 03:08
  • Any way to add N number of method separately. Do i need to add each method separately in routes? – Govind Totla Jan 31 '19 at 08:30
  • Much appreciated. I had the extra route AFTER the resource routes, and I couldn't figure out why it wasn't working. – HartleySan Mar 22 '19 at 17:04
40

Yeah, It's possible..

In my case I add method : data to handle request for /data.json in HTTP POST method.

This what I did.

First we extends Illuminate\Routing\ResourceRegistrar to add new method data

<?php

namespace App\MyCustom\Routing;

use Illuminate\Routing\ResourceRegistrar as OriginalRegistrar;

class ResourceRegistrar extends OriginalRegistrar
{
    // add data to the array
    /**
     * The default actions for a resourceful controller.
     *
     * @var array
     */
    protected $resourceDefaults = ['index', 'create', 'store', 'show', 'edit', 'update', 'destroy', 'data'];


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

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

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

After that, make your new ServiceProvider or use AppServiceProvider instead.

In method boot, add this code :

    public function boot()
    {
        $registrar = new \App\MyCustom\Routing\ResourceRegistrar($this->app['router']);

        $this->app->bind('Illuminate\Routing\ResourceRegistrar', function () use ($registrar) {
            return $registrar;
        });
    }

then :

add to your route :

Route::resource('test', 'TestController');

Check by php artisan route:list And you will find new method 'data'

Mathieu Ferre
  • 4,246
  • 1
  • 14
  • 31
35

I just did that, to add a GET "delete" method.

After creating your files, you just need to add

'AntonioRibeiro\Routing\ExtendedRouterServiceProvider',

to 'providers' in your app/config.php

Edit the Route alias in this same file:

'Route'           => 'Illuminate\Support\Facades\Route',

changing it to

'Route'           => 'AntonioRibeiro\Facades\ExtendedRouteFacade',

And make sure those files are being autoloaded, they must be in some directory that you have in your composer.json ("autoload" section).

Then you just need to:

Route::resource('users', 'UsersController');

And this (look at the last line) is the result if you run php artisan routes:

routes list Those are my source files:

ExtendedRouteFacade.pas

<?php namespace AntonioRibeiro\Facades;

use Illuminate\Support\Facades\Facade as IlluminateFacade;

class ExtendedRouteFacade extends IlluminateFacade {

    /**
     * Determine if the current route matches a given name.
     *
     * @param  string  $name
     * @return bool
     */
    public static function is($name)
    {
        return static::$app['router']->currentRouteNamed($name);
    }

    /**
     * Determine if the current route uses a given controller action.
     *
     * @param  string  $action
     * @return bool
     */
    public static function uses($action)
    {
        return static::$app['router']->currentRouteUses($action);
    }

    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor() { return 'router'; }

}

ExtendedRouter.pas

<?php namespace AntonioRibeiro\Routing;

class ExtendedRouter extends \Illuminate\Routing\Router {

    protected $resourceDefaults = array('index', 'create', 'store', 'show', 'edit', 'update', 'destroy', 'delete');

    /**
     * Add the show method for a resourceful route.
     *
     * @param  string  $name
     * @param  string  $base
     * @param  string  $controller
     * @return void
     */
    protected function addResourceDelete($name, $base, $controller)
    {
        $uri = $this->getResourceUri($name).'/{'.$base.'}/destroy';

        return $this->get($uri, $this->getResourceAction($name, $controller, 'delete'));
    }

}

ExtendedRouteServiceProvider.pas

<?php namespace AntonioRibeiro\Routing;

use Illuminate\Support\ServiceProvider;

class ExtendedRouterServiceProvider extends ServiceProvider {

    /**
     * Indicates if loading of the provider is deferred.
     *
     * @var bool
     */
    protected $defer = true;

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->app['router'] = $this->app->share(function() { return new ExtendedRouter($this->app); });
    }

    /**
     * Get the services provided by the provider.
     *
     * @return array
     */
    public function provides()
    {
        return array('router');
    }

}
Antonio Carlos Ribeiro
  • 86,191
  • 22
  • 213
  • 204
16
Route::resource('foo', 'FooController');
Route::controller('foo', 'FooController');

Give this a try .Put you extra methods like getData() etc etc .. This worked for me to keep route.php clean

Hassan Jamal
  • 694
  • 9
  • 11
  • Yas this works . Laravel 5.1 Documentation did't mention that Route::resource and Route::controller can be used together rather they mentioned about Supplementing Resource Controllers. So this is confused. But your ans proved that these are can be used together – Amir Nov 08 '15 at 20:20
  • I am sorry Hassan jamal, these two are not working together. So now how can I set a custom post method in a controller if I use only Route::resource – Amir Nov 08 '15 at 21:15
3

Using Laravel >5 Find the web.php file in routes folder add your methods

You can use route::resource to route all these methods index, show, store, update, destroy in your controller in one line

Route::get('foo/bar', 'NameController@bar');
Route::resource('foo', 'NameController');
Mazen Embaby
  • 1,255
  • 11
  • 18
2

Previously I defined my route as:

Route::get('foo/bar', 'FooController@bar');
Route::resource('foo', 'FooController');

It gave the error:

route foo.bar is not defined

And then after some Googling I added name

Route::get('foo/bar', 'FooController@bar')->name('foo.bar');

And it worked fine.

David Buck
  • 3,752
  • 35
  • 31
  • 35
  • It works. In recent version of Laravel (e.g - 10.x) you can do this - `Route::get('foo/bar', [FooController::class, 'bar'])->name('foo.bar');` – Tariqul Islam Sep 01 '23 at 13:29
2

I solve by

Create a custom router file that extends the BaseRouter

// src/app/Custom/Router.php


<?php

namespace App\Custom;

use Illuminate\Routing\Router as BaseRouter;
use Illuminate\Support\Str;

class Router extends BaseRouter
{
    public function customResource($name, $controller, array $options = [])
    {
        $model = Str::singular($name); // this is optional, i need it for Route Model Binding

        $this
            ->get( // set the http methods
                $name .'/{' . $model . '}/audit',
                $controller . '@audit'
            )->name($name . '.audit');

        return $this->resource($name, $controller, $options);
    }
}

Then register at src/bootstrap/app.php

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

And use it on /routes/web.php

Route::customResource(
    'entries',
    'EntryController'
);
Dev Lim
  • 21
  • 2
1

Just add a new method and a route to that method.

In your controller:

public function foo($bar=“default”)
{
      //do stuff
}

And in your web routes

Route::get(“foo/{$bar}”, “MyController@foo”);

Just be sure the method in the controller is public.

Patrick Lumenus
  • 1,412
  • 1
  • 15
  • 29
1

As of Laravel 9 this appears to have been made much simpler. The following works fine, even if you add the new method after the Resource controller was created.

Route::get('/foo/bar', [FooController::class, 'bar']);

Route::resource('/foo', FooController::class);

Just be sure to include the new method on top, as the remaining /foo/[any] namespace will be reserved for the Resource controller on that line. As is the convention for Laravel routes.

22289d
  • 81
  • 1
  • 7
1

I'm new to Laravel, but as of Laravel 10, I was able to group resource and custom route all together like that. And you can add as many custom route as you would like into that group.

Route::controller(FooController::class)->group(function () {
    Route::resource('foos', FooController::class);
    // The route to add
    Route::get('yourroute/{foo}', 'test')->name('foos.test');
})->middleware(['auth', 'verified']);

If you run artisan route:list you can validate it is working as expected.

Marc
  • 11,341
  • 3
  • 27
  • 30
0

you can add your macros, in my case it looks like this.

use Illuminate\Routing\Router;

Router::macro('crudResource', function ($name, $controller, array $options = []) {
    $only = ['index', 'show', 'store', 'update', 'destroy', 'restore'];

    if (isset($options['except'])) {
        $only = array_diff($only, (array)$options['except']);
    }

    if (in_array('restore', $only)) {
        $this->put($name . '/restore/{' . ($model = Str::singular($name)) . '}', [$controller, 'restore'])
            ->name($name . '.restore')
            ->can('delete', $model)
            ->withTrashed();
    }

    return $this->resource($name, $controller, array_merge([
                                                               'only' => $only,
                                                           ], $options))
        ->withTrashed(['index', 'show']);
});

Route

Route::crudResource('initial_balance_banks', InitialBalanceBankController::class);
Route::crudResource('bank_movements', BankMovementController::class)->except('restore');
Route::crudResource('outcoming_bank_orders', OutcomingBankOrderController::class, ['except' => ['restore']]);

enter image description here

-3

This works pretty good too. No need to add more routes just use show method of the resource controller like this :

public function show($name){

 switch ($name){
   case 'foo':
    $this -> foo();
    break;
   case 'bar':
    $this ->bar();
    break; 
  defautlt:
    abort(404,'bad request');
    break;
 }

}
public function foo(){}
publcc function bar(){}

I use the default to throw custom error page.

mdamia
  • 4,447
  • 1
  • 24
  • 23