Skip to content

Instantly share code, notes, and snippets.

@jhoff
Last active July 31, 2023 03:42
Show Gist options
  • Save jhoff/c487d1ced8beb9748451c32645bd7c6b to your computer and use it in GitHub Desktop.
Save jhoff/c487d1ced8beb9748451c32645bd7c6b to your computer and use it in GitHub Desktop.
Removable Laravel Routes

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

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 and Stack Overflow for examples.

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:

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.

<?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'], '\\')]);
}
}
}
<?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)
);
};
}
}
<?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'));
}
}
@promanapeople
Copy link

Thank you for this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment