TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Backend\Command;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use TYPO3\CMS\Backend\Module\ModuleRegistry;
|
||||
use TYPO3\CMS\Backend\Routing\Router;
|
||||
use TYPO3\CMS\Core\Attribute\AsNonSchedulableCommand;
|
||||
|
||||
/**
|
||||
* Debug backend routes, including module routes, AJAX routes and regular routes.
|
||||
* Similar to Symfony's debug:router command.
|
||||
* @internal only for development purposes
|
||||
*/
|
||||
#[AsCommand('debug:backend:routes', 'Debugging: List all registered backend routes (only for development purpose)')]
|
||||
#[AsNonSchedulableCommand]
|
||||
class DebugBackendRoutesCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Router $router,
|
||||
private readonly ModuleRegistry $moduleRegistry,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->addOption(
|
||||
'json',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Output routes in JSON format'
|
||||
)
|
||||
->addOption(
|
||||
'filter',
|
||||
'f',
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'Filter routes by name (supports partial matching)'
|
||||
)
|
||||
->addOption(
|
||||
'limit',
|
||||
'l',
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'Limit routes by type: ajax, module, or route'
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$jsonOutput = $input->getOption('json');
|
||||
$filter = $input->getOption('filter');
|
||||
$limit = $input->getOption('limit');
|
||||
|
||||
// Validate limit option
|
||||
if ($limit !== null) {
|
||||
$limit = strtolower($limit);
|
||||
if (!in_array($limit, ['ajax', 'module', 'route'], true)) {
|
||||
$io->error('Invalid limit type. Valid options are: ajax, module, route');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// Collect all routes
|
||||
$routes = $this->collectAllRoutes($filter, $limit);
|
||||
|
||||
if (empty($routes)) {
|
||||
$messages = [];
|
||||
if ($filter) {
|
||||
$messages[] = 'filter: ' . $filter;
|
||||
}
|
||||
if ($limit) {
|
||||
$messages[] = 'type: ' . $limit;
|
||||
}
|
||||
if ($messages) {
|
||||
$io->warning('No routes found matching ' . implode(', ', $messages));
|
||||
} else {
|
||||
$io->warning('No routes found.');
|
||||
}
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
// Sort routes by name
|
||||
ksort($routes);
|
||||
|
||||
if ($jsonOutput) {
|
||||
$this->outputJson($routes, $output);
|
||||
} else {
|
||||
$this->outputTable($routes, $io, $output);
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all routes from Router (including AJAX routes) and Module routes
|
||||
*
|
||||
* @return array<string, array{name: string, method: string, path: string, target: string, type: string, options: array}>
|
||||
*/
|
||||
private function collectAllRoutes(?string $filter, ?string $limitType): array
|
||||
{
|
||||
$routes = [];
|
||||
|
||||
// Build a set of module identifiers for quick lookup
|
||||
$moduleIdentifiers = [];
|
||||
foreach ($this->moduleRegistry->getModules() as $module) {
|
||||
if ($module->hasParentModule() || $module->isStandalone()) {
|
||||
$moduleIdentifiers[$module->getIdentifier()] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Get all routes from Router (includes regular routes, AJAX routes, and module routes)
|
||||
// Note: Routes can be either TYPO3 Route or Symfony Route objects
|
||||
foreach ($this->router->getRoutes() as $routeName => $route) {
|
||||
if ($filter && !str_contains((string)$routeName, $filter)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine route type
|
||||
$type = 'Route';
|
||||
if (str_starts_with((string)$routeName, 'ajax_')) {
|
||||
$type = 'Ajax';
|
||||
} elseif (isset($moduleIdentifiers[(string)$routeName])) {
|
||||
$type = 'Module';
|
||||
}
|
||||
|
||||
// Apply limit filter if specified
|
||||
if ($limitType !== null) {
|
||||
$typeNormalized = strtolower($type);
|
||||
if ($typeNormalized !== $limitType) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Get methods - works for both Symfony and TYPO3 Route objects
|
||||
$methods = $route->getMethods();
|
||||
$methodString = empty($methods) ? 'ANY' : implode('|', $methods);
|
||||
|
||||
// Get options - works for both route types
|
||||
$options = method_exists($route, 'getOptions') ? $route->getOptions() : [];
|
||||
$target = $options['target'] ?? $options['_controller'] ?? '-';
|
||||
|
||||
$routes[$routeName] = [
|
||||
'name' => (string)$routeName,
|
||||
'method' => $methodString,
|
||||
'path' => $route->getPath(),
|
||||
'target' => $target,
|
||||
'type' => $type,
|
||||
'options' => $options,
|
||||
];
|
||||
}
|
||||
|
||||
return $routes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output routes as JSON
|
||||
*/
|
||||
private function outputJson(array $routes, OutputInterface $output): void
|
||||
{
|
||||
$jsonData = [];
|
||||
foreach ($routes as $route) {
|
||||
$jsonData[] = [
|
||||
'name' => $route['name'],
|
||||
'method' => $route['method'],
|
||||
'path' => $route['path'],
|
||||
'target' => $route['target'],
|
||||
'type' => $route['type'],
|
||||
'options' => $route['options'],
|
||||
];
|
||||
}
|
||||
|
||||
$output->writeln((string)json_encode($jsonData, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
}
|
||||
|
||||
/**
|
||||
* Output routes as formatted table (similar to Symfony's debug:router)
|
||||
*/
|
||||
private function outputTable(array $routes, SymfonyStyle $io, OutputInterface $output): void
|
||||
{
|
||||
$table = new Table($output);
|
||||
$table->setHeaders(['Name', 'Method', 'Path', 'Target', 'Type']);
|
||||
$rows = [];
|
||||
foreach ($routes as $route) {
|
||||
$rows[] = [
|
||||
$route['name'],
|
||||
$route['method'],
|
||||
$route['path'],
|
||||
$this->formatTarget($route['target']),
|
||||
$route['type'],
|
||||
];
|
||||
}
|
||||
|
||||
$table->setRows($rows);
|
||||
$table->render();
|
||||
|
||||
$io->newLine();
|
||||
$io->writeln(sprintf('<info>%d</info> routes found', count($routes)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the target for display (shorten class names)
|
||||
*/
|
||||
private function formatTarget(string $target): string
|
||||
{
|
||||
// Shorten TYPO3 class names for better readability
|
||||
$target = str_replace('TYPO3\\CMS\\', '', $target);
|
||||
|
||||
// Limit length if too long
|
||||
if (strlen($target) > 80) {
|
||||
return substr($target, 0, 77) . '...';
|
||||
}
|
||||
|
||||
return $target;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user