Created
June 27, 2023 21:40
-
-
Save janklan/9ab38ee8c3dd7a0e781d729e4bc29c23 to your computer and use it in GitHub Desktop.
A Symfony Command that checks if there are any routes without explicit requirements
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Command\Maintenance; | |
use Symfony\Component\Console\Attribute\AsCommand; | |
use Symfony\Component\Console\Command\Command; | |
use Symfony\Component\Console\Input\InputInterface; | |
use Symfony\Component\Console\Output\OutputInterface; | |
use Symfony\Component\Console\Style\SymfonyStyle; | |
use Symfony\Component\Routing\RouterInterface; | |
#[AsCommand( | |
name: 'app:maintenance:route-requirements', | |
description: 'Finds all routes where the placeholder requirements are not set', | |
)] | |
class RouteRequirementsCommand extends Command | |
{ | |
public function __construct(private readonly RouterInterface $router) | |
{ | |
parent::__construct(); | |
} | |
/** | |
* Every route with placeholders should define proper requirements so that we don't accept any value at all times. | |
* | |
* This command is part of the CI/CD pipeline to make sure if a new placeholder is added without requirements, | |
* the pipeline fails. | |
*/ | |
protected function execute(InputInterface $input, OutputInterface $output): int | |
{ | |
$io = new SymfonyStyle($input, $output); | |
$routeCollection = $this->router->getRouteCollection(); | |
$result = []; | |
foreach ($routeCollection->all() as $name => $route) { | |
// Skip what's not ours | |
if (null === $route->getDefault('_controller')) { | |
continue; | |
} | |
if (!str_starts_with($route->getDefault('_controller'), 'App') && !str_starts_with($route->getDefault('_controller'), 'Cognetiq')) { | |
continue; | |
} | |
// Find routes that define at least one {placeholder} | |
if (preg_match_all('/\{([^\}]+)\}/', $route->getPath(), $placeholders)) { | |
// Ignore requirements that are not relevant to that route (requirements can be set in yaml, or at the class level too) | |
$relevantRequirements = array_intersect_key($route->getRequirements(), array_flip($placeholders[1])); | |
$missingRequirements = array_diff_key(array_flip($placeholders[1]), $relevantRequirements); | |
// We don't care about this one | |
unset($missingRequirements['_format']); | |
if (!empty($missingRequirements)) { | |
$result[] = [$name, implode(', ', array_flip($missingRequirements))]; | |
} | |
} | |
} | |
ksort($result); | |
if (empty($result)) { | |
$io->success('All route placeholders requirements defined.'); | |
return Command::SUCCESS; | |
} | |
$io->error('Some routes have placeholders without explicit requirements.'); | |
$io->table( | |
['Route Name', 'Offending placeholder(s)'], | |
$result, | |
); | |
return Command::FAILURE; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I found it helpful to define requirements to all route parameters, so I thought I could share this with whoever might find it.