Skip to content

Commit 0b7ef7a

Browse files
endroidampagent
andauthored
Add opt-in priority for the generated QR code route (#96)
Preserve existing route precedence by default while allowing applications to place the QR code route ahead of catch-all routes. Keep explicit routes and two-argument loader construction unchanged. Fixes #95 Amp-Thread-ID: https://ampcode.com/threads/T-01a135b3-5c83-75eb-aef1-0c8b4f44f342 Co-authored-by: Amp <amp@ampcode.com>
1 parent fb1c784 commit 0b7ef7a

6 files changed

Lines changed: 84 additions & 2 deletions

File tree

‎README.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,18 @@ endroid_qr_code:
3939
4040
This makes the route available at `/my-custom-prefix/{builder}/{data}`.
4141

42+
The generated route has priority `0` by default, preserving the existing route order.
43+
If an application catch-all route intercepts QR code URLs, set a higher priority:
44+
45+
```yaml
46+
endroid_qr_code:
47+
route_priority: 1
48+
```
49+
50+
Higher priorities are matched first, so choose a value above your catch-all's priority.
51+
This only affects the automatically registered route; an explicitly defined
52+
`qr_code_generate` route is left unchanged.
53+
4254
## Builder Configuration
4355

4456
The bundle makes use of builders to create QR codes. The default parameters

‎src/EndroidQrCodeBundle.php‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public function configure(DefinitionConfigurator $definition): void
4949

5050
$children->scalarNode('route_prefix')->defaultValue('/qr-code');
5151
$children->booleanNode('route_enabled')->defaultTrue();
52+
$children->integerNode('route_priority')->defaultValue(0);
5253
$children
5354
->arrayNode('builders')
5455
->useAttributeAsKey('name')
@@ -106,7 +107,7 @@ public function loadExtension(array $config, ContainerConfigurator $configurator
106107
// This ensures routes work on all Symfony versions without user configuration
107108
$services
108109
->set('endroid_qr_code.route_loader', QrCodeRouteLoader::class)
109-
->args(['', $routePrefix])
110+
->args(['', $routePrefix, $config['route_priority'] ?? 0])
110111
->tag('routing.route_loader');
111112
}
112113

‎src/Routing/QrCodeRouteLoader.php‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
public function __construct(
1515
private string $originalResource,
1616
private string $routePrefix,
17+
private int $routePriority = 0,
1718
) {}
1819

1920
public function __invoke(LoaderInterface $loader, ?string $_env): RouteCollection
@@ -30,7 +31,7 @@ public function __invoke(LoaderInterface $loader, ?string $_env): RouteCollectio
3031
['data' => '[\w\W]+'],
3132
);
3233
$route->setOption('utf8', true);
33-
$collection->add('qr_code_generate', $route);
34+
$collection->add('qr_code_generate', $route, $this->routePriority);
3435
}
3536

3637
return $collection;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Endroid\QrCodeBundle\Tests\Routing;
6+
7+
use Endroid\QrCodeBundle\Routing\QrCodeRouteLoader;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use PHPUnit\Framework\TestCase;
10+
use Symfony\Component\Config\Loader\LoaderInterface;
11+
use Symfony\Component\Routing\Matcher\UrlMatcher;
12+
use Symfony\Component\Routing\RequestContext;
13+
use Symfony\Component\Routing\Route;
14+
use Symfony\Component\Routing\RouteCollection;
15+
16+
final class QrCodeRouteLoaderTest extends TestCase
17+
{
18+
#[DataProvider('providePriorities')]
19+
public function testRoutePriority(?int $priority, int $catchAllPriority, string $expectedRoute): void
20+
{
21+
$routes = new RouteCollection();
22+
$routes->add('catch_all', new Route('/{slug}', [], ['slug' => '.*']), $catchAllPriority);
23+
24+
$loader = $this->createStub(LoaderInterface::class);
25+
$loader->method('load')->willReturn($routes);
26+
27+
$routeLoader = $priority === null
28+
? new QrCodeRouteLoader('app', '/2fa/qr')
29+
: new QrCodeRouteLoader('app', '/2fa/qr', $priority);
30+
$matcher = new UrlMatcher($routeLoader($loader, null), new RequestContext());
31+
32+
self::assertSame($expectedRoute, $matcher->match('/2fa/qr/default/test')['_route']);
33+
self::assertSame('catch_all', $matcher->match('/other/page')['_route']);
34+
}
35+
36+
public static function providePriorities(): iterable
37+
{
38+
yield 'omitted priority preserves application precedence' => [null, 0, 'catch_all'];
39+
yield 'equal priority preserves application precedence' => [0, 0, 'catch_all'];
40+
yield 'higher priority beats catch-all' => [1, 0, 'qr_code_generate'];
41+
yield 'higher application priority still wins' => [1, 2, 'catch_all'];
42+
yield 'negative priorities are respected' => [-2, -1, 'catch_all'];
43+
}
44+
45+
public function testExplicitRouteIsPreserved(): void
46+
{
47+
$routes = new RouteCollection();
48+
$routes->add('catch_all', new Route('/{slug}', [], ['slug' => '.*']));
49+
$routes->add('qr_code_generate', new Route('/manual/{data}'), 1);
50+
51+
$loader = $this->createStub(LoaderInterface::class);
52+
$loader->method('load')->willReturn($routes);
53+
54+
$routeLoader = new QrCodeRouteLoader('app', '/2fa/qr', -1);
55+
$matcher = new UrlMatcher($routeLoader($loader, null), new RequestContext());
56+
57+
self::assertSame('qr_code_generate', $matcher->match('/manual/test')['_route']);
58+
self::assertSame('catch_all', $matcher->match('/2fa/qr/default/test')['_route']);
59+
}
60+
}

‎tests/application/config/packages/endroid_qr_code.yaml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
endroid_qr_code:
2+
route_priority: 1
23
builders:
34
default:
45
writer: Endroid\QrCode\Writer\PngWriter

‎tests/application/config/routes/endroid_qr_code_twig.yaml‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,10 @@ endroid_qr_code_twig_invalid_option:
77
path: /twig-invalid-option
88
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
99
defaults: { template: 'twig-invalid-option.html.twig' }
10+
11+
# The QR code route must outrank an earlier catch-all when configured to do so.
12+
catch_all:
13+
path: /{slug}
14+
requirements: { slug: '.*' }
15+
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
16+
defaults: { template: 'twig.html.twig' }

0 commit comments

Comments
 (0)