Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save janklan/b43e37602fb79abe4f9d98669f847fe7 to your computer and use it in GitHub Desktop.
Save janklan/b43e37602fb79abe4f9d98669f847fe7 to your computer and use it in GitHub Desktop.
A Symfony command that looks for routes without declared placeholder requirements
<?php
namespace App\Command\Security;
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:security: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