Skip to content

Instantly share code, notes, and snippets.

@jhoff
Last active July 10, 2025 15:47
Show Gist options
  • Select an option

  • Save jhoff/c487d1ced8beb9748451c32645bd7c6b to your computer and use it in GitHub Desktop.

Select an option

Save jhoff/c487d1ced8beb9748451c32645bd7c6b to your computer and use it in GitHub Desktop.

Revisions

  1. jhoff renamed this gist Mar 11, 2020. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  2. jhoff revised this gist Mar 10, 2020. 1 changed file with 5 additions and 1 deletion.
    6 changes: 5 additions & 1 deletion README.md
    Original file line number Diff line number Diff line change
    @@ -2,7 +2,11 @@

    This is a Laravel Router mixin that was originally written for a Laravel Spark app that allows you to remove previously defined routes that are hard coded in a package.

    We no longer need it, but thought the code might be useful to someone so here it is:
    We no longer need it, but thought the code might be useful to someone so here it is

    ## Disclaimer

    This **should not** be used to modify the authentication routes that come with Laravel out if the box. There is a much easier way to do this. See the [documentation](https://laravel.com/docs/5.8/authentication#included-routing) and [Stack Overflow](https://stackoverflow.com/questions/58645640/remove-specific-route-from-login-laravel-6) for examples.

    ## Installation

  3. jhoff created this gist Mar 10, 2020.
    50 changes: 50 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,50 @@
    # Removable Laravel Routes

    This is a Laravel Router mixin that was originally written for a Laravel Spark app that allows you to remove previously defined routes that are hard coded in a package.

    We no longer need it, but thought the code might be useful to someone so here it is:

    ## Installation

    Create an `app/Routing` folder containing `RemovableRoutesMixin.php` and `RemovableRouteCollection.php`.

    In the `boot` method of `app/Providers/RouteServiceProvider.php`, add the RemovableRoutesMixin to the Router:

    ```php

    use Illuminate\Support\Facades\Route;
    use App\Routing\RemovableRoutesMixin;

    ...

    public function boot()
    {
    Route::mixin(new RemovableRoutesMixin());

    ...
    }
    ```

    ## Usage

    In your routes files ( ex: `routes/web.php` ) you can now call any of the following methods to remove any registered routes:

    ```
    // Remove any route by verb
    Route::remove('GET', 'foobar');
    // Remove a route using a verb specific method
    Route::removeGet('foobar');
    Route::removePost('foobar');
    Route::removePut('foobar');
    Route::removePatch('foobar');
    Route::removeDelete('foobar');
    Route::removeOptions('foobar');
    // Remove a route using any verb
    Route::removeAny('foobar');
    ```

    ## Testing

    Optionally, a unit test class is provided as well. Save `RemovableRoutesTest.php` to the `tests/Unit` folder.
    80 changes: 80 additions & 0 deletions RemovableRouteCollection.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,80 @@
    <?php

    namespace App\Routing;

    use Illuminate\Support\Arr;
    use Illuminate\Routing\Route;
    use Illuminate\Routing\RouteCollection;

    class RemovableRouteCollection extends RouteCollection
    {
    /**
    * Clone a base route collection into an removable instance
    *
    * @param \Illuminate\Routing\RouteCollection $base
    * @return \App\Routing\RouteCollection
    */
    public static function cloneFrom(RouteCollection $base)
    {
    $clone = new static();

    $clone->routes = $base->routes;
    $clone->allRoutes = $base->allRoutes;
    $clone->nameList = $base->nameList;
    $clone->actionList = $base->actionList;

    return $clone;
    }

    /**
    * Remove a Route instance from the collection by uri for any method
    *
    * @param mixed $methods
    * @param string $uri
    *
    * @return static
    */
    public function remove($methods, string $uri)
    {
    foreach ($this->routes as $method => $routes) {
    if (! in_array($method, Arr::wrap($methods))) {
    continue;
    }

    foreach ($routes as $domainAndUri => $route) {
    if (trim($uri, '/') !== $route->uri()) {
    continue;
    }

    $this->removeRoute($route, $method, $domainAndUri);
    }
    }

    return $this;
    }

    /**
    * Remove all matching routes for the given method
    *
    * @param \Illuminate\Routing\Route $route
    * @param string $method
    * @param string $domainAndUri
    *
    * @return void
    */
    protected function removeRoute(Route $route, string $method, string $domainAndUri)
    {
    unset($this->routes[$method][$domainAndUri]);
    unset($this->allRoutes[$method . $domainAndUri]);

    if ($name = $route->getName()) {
    unset($this->nameList[$name]);
    }

    $action = $route->getAction();

    if (isset($action['controller'])) {
    unset($this->actionList[trim($action['controller'], '\\')]);
    }
    }
    }
    134 changes: 134 additions & 0 deletions RemovableRoutesMixin.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,134 @@
    <?php

    namespace App\Routing;

    use Illuminate\Routing\Router;
    use App\Routing\RemovableRouteCollection;

    /**
    * This is an Illuminate\Routing\Router mixin ( see Macroables ) that enables
    * the hard removal of routes that are pre-defined by Spark or other packages.
    * This is done both for route organizing and security purposes.
    */
    class RemovableRoutesMixin
    {
    /**
    * Remove a route using the provided method and uri
    *
    * @return closure
    */
    public function remove()
    {
    return function (string $method, string $uri) {
    $this->setRoutes(
    RemovableRouteCollection::cloneFrom($this->routes)
    ->remove(strtoupper($method), $uri)
    );
    };
    }

    /**
    * Remove a Get route using the provided uri
    *
    * @return closure
    */
    public function removeGet()
    {
    return function (string $uri) {
    $this->setRoutes(
    RemovableRouteCollection::cloneFrom($this->routes)
    ->remove('GET', $uri)
    );
    };
    }

    /**
    * Remove a Post route using the provided uri
    *
    * @return closure
    */
    public function removePost()
    {
    return function (string $uri) {
    $this->setRoutes(
    RemovableRouteCollection::cloneFrom($this->routes)
    ->remove('POST', $uri)
    );
    };
    }

    /**
    * Remove a Put route using the provided uri
    *
    * @return closure
    */
    public function removePut()
    {
    return function (string $uri) {
    $this->setRoutes(
    RemovableRouteCollection::cloneFrom($this->routes)
    ->remove('PUT', $uri)
    );
    };
    }

    /**
    * Remove a Patch route using the provided uri
    *
    * @return closure
    */
    public function removePatch()
    {
    return function (string $uri) {
    $this->setRoutes(
    RemovableRouteCollection::cloneFrom($this->routes)
    ->remove('PATCH', $uri)
    );
    };
    }

    /**
    * Remove a Delete route using the provided uri
    *
    * @return closure
    */
    public function removeDelete()
    {
    return function (string $uri) {
    $this->setRoutes(
    RemovableRouteCollection::cloneFrom($this->routes)
    ->remove('DELETE', $uri)
    );
    };
    }

    /**
    * Remove a Options route using the provided uri
    *
    * @return closure
    */
    public function removeOptions()
    {
    return function (string $uri) {
    $this->setRoutes(
    RemovableRouteCollection::cloneFrom($this->routes)
    ->remove('OPTIONS', $uri)
    );
    };
    }

    /**
    * Remove routes using any method that matches the provided uri
    *
    * @return closure
    */
    public function removeAny()
    {
    return function ($uri) {
    $this->setRoutes(
    RemovableRouteCollection::cloneFrom($this->routes)
    ->remove(Router::$verbs, $uri)
    );
    };
    }
    }
    170 changes: 170 additions & 0 deletions RemovableRoutesTest.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,170 @@
    <?php

    namespace Tests\Unit;

    use Illuminate\Routing\Router;
    use PHPUnit\Framework\TestCase;
    use Illuminate\Events\Dispatcher;
    use App\Routing\RemovableRoutesMixin;

    class RemovableRoutesTest extends TestCase
    {
    /**
    * @test
    */
    public function mixinAddsMethodsToRoute()
    {
    $router = new Router(new Dispatcher());

    $this->assertTrue(method_exists(Router::class, 'mixin'));

    $router->mixin(new RemovableRoutesMixin());

    foreach (get_class_methods(RemovableRoutesMixin::class) as $method) {
    $this->assertTrue(Router::hasMacro($method));
    }
    }

    /**
    * @test
    */
    public function routesCanBeRemovedWithMethod()
    {
    $router = new Router(new Dispatcher());
    $router->mixin(new RemovableRoutesMixin());

    $this->assertFalse($router->has('foobar'));
    $this->assertFalse($router->has('bazqux'));
    $this->assertFalse($router->has('quxcorge'));
    $this->assertFalse($router->has('corgegrault'));
    $this->assertFalse($router->has('graultgarply'));
    $this->assertFalse($router->has('garplywaldo'));

    $router->get('foobar')->name('foobar');
    $router->post('bazqux')->name('bazqux');
    $router->put('quxcorge')->name('quxcorge');
    $router->patch('corgegrault')->name('corgegrault');
    $router->delete('graultgarply')->name('graultgarply');
    $router->options('garplywaldo')->name('garplywaldo');

    $router->getRoutes()->refreshNameLookups();

    $this->assertTrue($router->has('foobar'));
    $this->assertTrue($router->has('bazqux'));
    $this->assertTrue($router->has('quxcorge'));
    $this->assertTrue($router->has('corgegrault'));
    $this->assertTrue($router->has('graultgarply'));
    $this->assertTrue($router->has('garplywaldo'));

    $router->remove('get', 'foobar');
    $router->remove('post', 'bazqux');
    $router->remove('put', 'quxcorge');
    $router->remove('patch', 'corgegrault');
    $router->remove('delete', 'graultgarply');
    $router->remove('options', 'garplywaldo');

    $this->assertFalse($router->has('foobar'));
    $this->assertFalse($router->has('bazqux'));
    $this->assertFalse($router->has('quxcorge'));
    $this->assertFalse($router->has('corgegrault'));
    $this->assertFalse($router->has('graultgarply'));
    $this->assertFalse($router->has('garplywaldo'));
    }

    /**
    * @test
    */
    public function routesCanBeRemovedUsingSpecificHelper()
    {
    $router = new Router(new Dispatcher());
    $router->mixin(new RemovableRoutesMixin());

    $this->assertFalse($router->has('foobar'));
    $this->assertFalse($router->has('bazqux'));
    $this->assertFalse($router->has('quxcorge'));
    $this->assertFalse($router->has('corgegrault'));
    $this->assertFalse($router->has('graultgarply'));
    $this->assertFalse($router->has('garplywaldo'));

    $router->get('foobar')->name('foobar');
    $router->post('bazqux')->name('bazqux');
    $router->put('quxcorge')->name('quxcorge');
    $router->patch('corgegrault')->name('corgegrault');
    $router->delete('graultgarply')->name('graultgarply');
    $router->options('garplywaldo')->name('garplywaldo');

    $router->getRoutes()->refreshNameLookups();

    $this->assertTrue($router->has('foobar'));
    $this->assertTrue($router->has('bazqux'));
    $this->assertTrue($router->has('quxcorge'));
    $this->assertTrue($router->has('corgegrault'));
    $this->assertTrue($router->has('graultgarply'));
    $this->assertTrue($router->has('garplywaldo'));

    $router->removeGet('foobar');
    $router->removePost('bazqux');
    $router->removePut('quxcorge');
    $router->removePatch('corgegrault');
    $router->removeDelete('graultgarply');
    $router->removeOptions('garplywaldo');

    $this->assertFalse($router->has('foobar'));
    $this->assertFalse($router->has('bazqux'));
    $this->assertFalse($router->has('quxcorge'));
    $this->assertFalse($router->has('corgegrault'));
    $this->assertFalse($router->has('graultgarply'));
    $this->assertFalse($router->has('garplywaldo'));
    }

    /**
    * @test
    */
    public function routesCanBeRemovedUsingDeleteAny()
    {
    $router = new Router(new Dispatcher());
    $router->mixin(new RemovableRoutesMixin());

    $this->assertFalse($router->has('barbaz'));
    $this->assertFalse($router->has('foobar'));
    $this->assertFalse($router->has('bazqux'));
    $this->assertFalse($router->has('quxcorge'));
    $this->assertFalse($router->has('corgegrault'));
    $this->assertFalse($router->has('graultgarply'));
    $this->assertFalse($router->has('garplywaldo'));

    $router->any('barbaz')->name('barbaz');
    $router->get('foobar')->name('foobar');
    $router->post('foobar')->name('bazqux');
    $router->put('quxcorge')->name('quxcorge');
    $router->patch('corgegrault')->name('corgegrault');
    $router->delete('graultgarply')->name('graultgarply');
    $router->options('garplywaldo')->name('garplywaldo');

    $router->getRoutes()->refreshNameLookups();

    $this->assertTrue($router->has('barbaz'));
    $this->assertTrue($router->has('foobar'));
    $this->assertTrue($router->has('bazqux'));
    $this->assertTrue($router->has('quxcorge'));
    $this->assertTrue($router->has('corgegrault'));
    $this->assertTrue($router->has('graultgarply'));
    $this->assertTrue($router->has('garplywaldo'));

    $router->removeAny('barbaz');
    $router->removeAny('foobar');
    $router->removeAny('bazqux');
    $router->removeAny('quxcorge');
    $router->removeAny('corgegrault');
    $router->removeAny('graultgarply');
    $router->removeAny('garplywaldo');

    $this->assertFalse($router->has('barbaz'));
    $this->assertFalse($router->has('foobar'));
    $this->assertFalse($router->has('bazqux'));
    $this->assertFalse($router->has('quxcorge'));
    $this->assertFalse($router->has('corgegrault'));
    $this->assertFalse($router->has('graultgarply'));
    $this->assertFalse($router->has('garplywaldo'));
    }
    }