Blame | Letzte Änderung | Log anzeigen | RSS feed
<?phpnamespace Illuminate\Foundation\Console;use Closure;use Illuminate\Console\Command;use Illuminate\Contracts\Routing\UrlGenerator;use Illuminate\Routing\Route;use Illuminate\Routing\Router;use Illuminate\Routing\ViewController;use Illuminate\Support\Arr;use Illuminate\Support\Str;use ReflectionClass;use ReflectionFunction;use Symfony\Component\Console\Attribute\AsCommand;use Symfony\Component\Console\Input\InputOption;use Symfony\Component\Console\Terminal;#[AsCommand(name: 'route:list')]class RouteListCommand extends Command{/*** The console command name.** @var string*/protected $name = 'route:list';/*** The name of the console command.** This name is used to identify the command during lazy loading.** @var string|null** @deprecated*/protected static $defaultName = 'route:list';/*** The console command description.** @var string*/protected $description = 'List all registered routes';/*** The router instance.** @var \Illuminate\Routing\Router*/protected $router;/*** The table headers for the command.** @var string[]*/protected $headers = ['Domain', 'Method', 'URI', 'Name', 'Action', 'Middleware'];/*** The terminal width resolver callback.** @var \Closure|null*/protected static $terminalWidthResolver;/*** The verb colors for the command.** @var array*/protected $verbColors = ['ANY' => 'red','GET' => 'blue','HEAD' => '#6C7280','OPTIONS' => '#6C7280','POST' => 'yellow','PUT' => 'yellow','PATCH' => 'yellow','DELETE' => 'red',];/*** Create a new route command instance.** @param \Illuminate\Routing\Router $router* @return void*/public function __construct(Router $router){parent::__construct();$this->router = $router;}/*** Execute the console command.** @return void*/public function handle(){$this->router->flushMiddlewareGroups();if (! $this->router->getRoutes()->count()) {return $this->components->error("Your application doesn't have any routes.");}if (empty($routes = $this->getRoutes())) {return $this->components->error("Your application doesn't have any routes matching the given criteria.");}$this->displayRoutes($routes);}/*** Compile the routes into a displayable format.** @return array*/protected function getRoutes(){$routes = collect($this->router->getRoutes())->map(function ($route) {return $this->getRouteInformation($route);})->filter()->all();if (($sort = $this->option('sort')) !== null) {$routes = $this->sortRoutes($sort, $routes);} else {$routes = $this->sortRoutes('uri', $routes);}if ($this->option('reverse')) {$routes = array_reverse($routes);}return $this->pluckColumns($routes);}/*** Get the route information for a given route.** @param \Illuminate\Routing\Route $route* @return array*/protected function getRouteInformation(Route $route){return $this->filterRoute(['domain' => $route->domain(),'method' => implode('|', $route->methods()),'uri' => $route->uri(),'name' => $route->getName(),'action' => ltrim($route->getActionName(), '\\'),'middleware' => $this->getMiddleware($route),'vendor' => $this->isVendorRoute($route),]);}/*** Sort the routes by a given element.** @param string $sort* @param array $routes* @return array*/protected function sortRoutes($sort, array $routes){return Arr::sort($routes, function ($route) use ($sort) {return $route[$sort];});}/*** Remove unnecessary columns from the routes.** @param array $routes* @return array*/protected function pluckColumns(array $routes){return array_map(function ($route) {return Arr::only($route, $this->getColumns());}, $routes);}/*** Display the route information on the console.** @param array $routes* @return void*/protected function displayRoutes(array $routes){$routes = collect($routes);$this->output->writeln($this->option('json') ? $this->asJson($routes) : $this->forCli($routes));}/*** Get the middleware for the route.** @param \Illuminate\Routing\Route $route* @return string*/protected function getMiddleware($route){return collect($this->router->gatherRouteMiddleware($route))->map(function ($middleware) {return $middleware instanceof Closure ? 'Closure' : $middleware;})->implode("\n");}/*** Determine if the route has been defined outside of the application.** @param \Illuminate\Routing\Route $route* @return bool*/protected function isVendorRoute(Route $route){if ($route->action['uses'] instanceof Closure) {$path = (new ReflectionFunction($route->action['uses']))->getFileName();} elseif (is_string($route->action['uses']) &&str_contains($route->action['uses'], 'SerializableClosure')) {return false;} elseif (is_string($route->action['uses'])) {if ($this->isFrameworkController($route)) {return false;}$path = (new ReflectionClass($route->getControllerClass()))->getFileName();} else {return false;}return str_starts_with($path, base_path('vendor'));}/*** Determine if the route uses a framework controller.** @param \Illuminate\Routing\Route $route* @return bool*/protected function isFrameworkController(Route $route){return in_array($route->getControllerClass(), ['\Illuminate\Routing\RedirectController','\Illuminate\Routing\ViewController',], true);}/*** Filter the route by URI and / or name.** @param array $route* @return array|null*/protected function filterRoute(array $route){if (($this->option('name') && ! Str::contains((string) $route['name'], $this->option('name'))) ||($this->option('path') && ! Str::contains($route['uri'], $this->option('path'))) ||($this->option('method') && ! Str::contains($route['method'], strtoupper($this->option('method')))) ||($this->option('domain') && ! Str::contains((string) $route['domain'], $this->option('domain'))) ||($this->option('except-vendor') && $route['vendor']) ||($this->option('only-vendor') && ! $route['vendor'])) {return;}if ($this->option('except-path')) {foreach (explode(',', $this->option('except-path')) as $path) {if (str_contains($route['uri'], $path)) {return;}}}return $route;}/*** Get the table headers for the visible columns.** @return array*/protected function getHeaders(){return Arr::only($this->headers, array_keys($this->getColumns()));}/*** Get the column names to show (lowercase table headers).** @return array*/protected function getColumns(){return array_map('strtolower', $this->headers);}/*** Parse the column list.** @param array $columns* @return array*/protected function parseColumns(array $columns){$results = [];foreach ($columns as $column) {if (str_contains($column, ',')) {$results = array_merge($results, explode(',', $column));} else {$results[] = $column;}}return array_map('strtolower', $results);}/*** Convert the given routes to JSON.** @param \Illuminate\Support\Collection $routes* @return string*/protected function asJson($routes){return $routes->map(function ($route) {$route['middleware'] = empty($route['middleware']) ? [] : explode("\n", $route['middleware']);return $route;})->values()->toJson();}/*** Convert the given routes to regular CLI output.** @param \Illuminate\Support\Collection $routes* @return array*/protected function forCli($routes){$routes = $routes->map(fn ($route) => array_merge($route, ['action' => $this->formatActionForCli($route),'method' => $route['method'] == 'GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS' ? 'ANY' : $route['method'],'uri' => $route['domain'] ? ($route['domain'].'/'.ltrim($route['uri'], '/')) : $route['uri'],]),);$maxMethod = mb_strlen($routes->max('method'));$terminalWidth = $this->getTerminalWidth();$routeCount = $this->determineRouteCountOutput($routes, $terminalWidth);return $routes->map(function ($route) use ($maxMethod, $terminalWidth) {['action' => $action,'domain' => $domain,'method' => $method,'middleware' => $middleware,'uri' => $uri,] = $route;$middleware = Str::of($middleware)->explode("\n")->filter()->whenNotEmpty(fn ($collection) => $collection->map(fn ($middleware) => sprintf(' %s⇂ %s', str_repeat(' ', $maxMethod), $middleware)))->implode("\n");$spaces = str_repeat(' ', max($maxMethod + 6 - mb_strlen($method), 0));$dots = str_repeat('.', max($terminalWidth - mb_strlen($method.$spaces.$uri.$action) - 6 - ($action ? 1 : 0), 0));$dots = empty($dots) ? $dots : " $dots";if ($action && ! $this->output->isVerbose() && mb_strlen($method.$spaces.$uri.$action.$dots) > ($terminalWidth - 6)) {$action = substr($action, 0, $terminalWidth - 7 - mb_strlen($method.$spaces.$uri.$dots)).'…';}$method = Str::of($method)->explode('|')->map(fn ($method) => sprintf('<fg=%s>%s</>', $this->verbColors[$method] ?? 'default', $method),)->implode('<fg=#6C7280>|</>');return [sprintf(' <fg=white;options=bold>%s</> %s<fg=white>%s</><fg=#6C7280>%s %s</>',$method,$spaces,preg_replace('#({[^}]+})#', '<fg=yellow>$1</>', $uri),$dots,str_replace(' ', ' › ', $action ?? ''),), $this->output->isVerbose() && ! empty($middleware) ? "<fg=#6C7280>$middleware</>" : null];})->flatten()->filter()->prepend('')->push('')->push($routeCount)->push('')->toArray();}/*** Get the formatted action for display on the CLI.** @param array $route* @return string*/protected function formatActionForCli($route){['action' => $action, 'name' => $name] = $route;if ($action === 'Closure' || $action === ViewController::class) {return $name;}$name = $name ? "$name " : null;$rootControllerNamespace = $this->laravel[UrlGenerator::class]->getRootControllerNamespace()?? ($this->laravel->getNamespace().'Http\\Controllers');if (str_starts_with($action, $rootControllerNamespace)) {return $name.substr($action, mb_strlen($rootControllerNamespace) + 1);}$actionClass = explode('@', $action)[0];if (class_exists($actionClass) && str_starts_with((new ReflectionClass($actionClass))->getFilename(), base_path('vendor'))) {$actionCollection = collect(explode('\\', $action));return $name.$actionCollection->take(2)->implode('\\').' '.$actionCollection->last();}return $name.$action;}/*** Determine and return the output for displaying the number of routes in the CLI output.** @param \Illuminate\Support\Collection $routes* @param int $terminalWidth* @return string*/protected function determineRouteCountOutput($routes, $terminalWidth){$routeCountText = 'Showing ['.$routes->count().'] routes';$offset = $terminalWidth - mb_strlen($routeCountText) - 2;$spaces = str_repeat(' ', $offset);return $spaces.'<fg=blue;options=bold>Showing ['.$routes->count().'] routes</>';}/*** Get the terminal width.** @return int*/public static function getTerminalWidth(){return is_null(static::$terminalWidthResolver)? (new Terminal)->getWidth(): call_user_func(static::$terminalWidthResolver);}/*** Set a callback that should be used when resolving the terminal width.** @param \Closure|null $resolver* @return void*/public static function resolveTerminalWidthUsing($resolver){static::$terminalWidthResolver = $resolver;}/*** Get the console command options.** @return array*/protected function getOptions(){return [['json', null, InputOption::VALUE_NONE, 'Output the route list as JSON'],['method', null, InputOption::VALUE_OPTIONAL, 'Filter the routes by method'],['name', null, InputOption::VALUE_OPTIONAL, 'Filter the routes by name'],['domain', null, InputOption::VALUE_OPTIONAL, 'Filter the routes by domain'],['path', null, InputOption::VALUE_OPTIONAL, 'Only show routes matching the given path pattern'],['except-path', null, InputOption::VALUE_OPTIONAL, 'Do not display the routes matching the given path pattern'],['reverse', 'r', InputOption::VALUE_NONE, 'Reverse the ordering of the routes'],['sort', null, InputOption::VALUE_OPTIONAL, 'The column (domain, method, uri, name, action, middleware) to sort by', 'uri'],['except-vendor', null, InputOption::VALUE_NONE, 'Do not display routes defined by vendor packages'],['only-vendor', null, InputOption::VALUE_NONE, 'Only display routes defined by vendor packages'],];}}