[Feature Request]: Analyze cached routes
Feature Description
I have a situation like #1126 where I define some routes in a macro. Since it's complex to analyze routes defined in a macro, maybe analyze the cached route file: bootstrap/cache/route-v7.php. This file contains all the routes defined in macro and I can setup a file watcher to run artisan route:cache after route files modified.
Could you show the macro? We already have some solution for this case.
Mine is complex. it may look like resource route but it has some differences.
Route::macro( 'myEditor', function ($name, $controller, $options = []) {
Route::name( $name . '.' )->prefix( $name )->controller( $controller )->group( function () use ($name, $options) {
$variableName = $options['variableName'] ?? Str::singular( $name );
Route::get( '/', 'index' )->name( 'index' );
Route::post( '/', 'store' )->name( 'store' );
Route::post( '/delete', 'destroy' )->name( 'destroy' );
Route::match( ['put', 'patch'], '/{' . $variableName . '}', 'update' )->name( 'update' );
} );
} );
Right now as a workaround i created a command to find routes created by macros and put in a helper file for laravel-idea to pick them up. This method needs two classes:
MacroRouter class
place the following code in app/Classes/MacroRouter.php:
<?php
namespace App\Classes;
use Illuminate\Routing\Route;
use Illuminate\Routing\Router;
class MacroRouter extends Router
{
/**
* @var array|Route[][]
*/
protected(set) array $macroRouteDefinitions = [];
public function __call($method, $parameters) {
if ( ! static::hasMacro( $method ) ) {
return parent::__call( $method, $parameters );
}
$before = $this->getRoutes()->getRoutes();
$result = $this->macroCall( $method, $parameters );
$after = $this->getRoutes()->getRoutes();
$new = array_diff( array_keys( $after ), array_keys( $before ) );
if ( ! array_key_exists( $method, $this->macroRouteDefinitions ) ) {
$this->macroRouteDefinitions[$method] = [];
}
foreach ($new as $item) {
$this->macroRouteDefinitions[$method][] = $after[$item];
}
return $result;
}
}
AutoCompleteForMacroRoutesCommand class
place the following code to commands directory:
<?php
namespace App\Console\Commands;
use Closure;
use Illuminate\Console\Command;
use Illuminate\Filesystem\Filesystem;
class AutoCompleteForMacroRoutesCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'route:autocomplete';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
/**
* Execute the console command.
*/
public function handle(Filesystem $files) {
$macroRoutes = app( 'router' )->macroRouteDefinitions;
$file = '<?php' . PHP_EOL;
foreach ($macroRoutes as $macro => $routes) {
$file .= "\n// Created by '$macro' macro.\n";
foreach ($routes as $route) {
$methods = array_diff( $route->methods(), ['HEAD'] );
$method = count( $methods ) === 1 ? strtolower( reset( $methods ) ) : 'match';
$file .= "Route::$method(";
if ( $method === 'match' ) {
$file .= "['" . implode( "', '", $methods ) . "'], ";
}
$file .= "'" . $route->uri() . "', ";
$action = $route->getAction();
if ( isset( $action['uses'] ) && $action['uses'] instanceof Closure ) {
// For Closures, we can only represent them as a generic closure stub
$file .= 'function () { /* ... */ })';
}
elseif ( is_string( $action['uses'] ) ) {
// Controller@method or named string handler
$file .= "'{$action['uses']}')";
}
elseif ( is_array( $action['uses'] ) && isset( $action['uses'][0] ) && isset( $action['uses'][1] ) ) {
// [Controller::class, 'method'] format
$controller = is_object( $action['uses'][0] ) ? get_class( $action['uses'][0] ) : $action['uses'][0];
$file .= "['$controller', '{$action['uses'][1]}'])";
}
else {
// Fallback for unexpected actions
$file .= '/* UNKNOWN ACTION */)';
}
// Name
if ( $name = $route->getName() ) {
$file .= "->name('$name')";
}
// Middleware
if ( $middleware = $route->gatherMiddleware() ) {
$file .= "->middleware(['" . implode( "', '", $middleware ) . "'])";
}
// Where (Parameter constraints)
if ( $wheres = $route->wheres ) {
foreach ($wheres as $key => $value) {
$file .= "->where('$key', '$value')";
}
}
$file .= ";\n";
}
}
$helperPath = base_path( '_ide_helper' );
if ( ! $files->isDirectory( $helperPath ) ) {
$files->makeDirectory( $helperPath );
}
$helperPath .= '/macro_routes.php';
$files->put( $helperPath, $file );
$this->line( "File created/updated at: $helperPath:1" );
}
}
After that, this should be added to RouteServiceProvider's boot method:
if ( app()->runningConsoleCommand( 'route:autocomplete' ) ) {
$this->app->bind(
Router::class,
MacroRouter::class
);
$this->app->singleton( 'router', function ($app) {
return new MacroRouter( $app['events'], $app );
} );
Route::clearResolvedInstance( 'router' );
}
Then setup file watcher on routes directory to run artisan route:autocomplete.