Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,18 @@ endroid_qr_code:

This makes the route available at `/my-custom-prefix/{builder}/{data}`.

The generated route has priority `0` by default, preserving the existing route order.
If an application catch-all route intercepts QR code URLs, set a higher priority:

```yaml
endroid_qr_code:
route_priority: 1
```

Higher priorities are matched first, so choose a value above your catch-all's priority.
This only affects the automatically registered route; an explicitly defined
`qr_code_generate` route is left unchanged.

## Builder Configuration

The bundle makes use of builders to create QR codes. The default parameters
Expand Down
3 changes: 2 additions & 1 deletion src/EndroidQrCodeBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public function configure(DefinitionConfigurator $definition): void

$children->scalarNode('route_prefix')->defaultValue('/qr-code');
$children->booleanNode('route_enabled')->defaultTrue();
$children->integerNode('route_priority')->defaultValue(0);
$children
->arrayNode('builders')
->useAttributeAsKey('name')
Expand Down Expand Up @@ -106,7 +107,7 @@ public function loadExtension(array $config, ContainerConfigurator $configurator
// This ensures routes work on all Symfony versions without user configuration
$services
->set('endroid_qr_code.route_loader', QrCodeRouteLoader::class)
->args(['', $routePrefix])
->args(['', $routePrefix, $config['route_priority'] ?? 0])
->tag('routing.route_loader');
}

Expand Down
3 changes: 2 additions & 1 deletion src/Routing/QrCodeRouteLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
public function __construct(
private string $originalResource,
private string $routePrefix,
private int $routePriority = 0,
) {}

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

return $collection;
Expand Down
60 changes: 60 additions & 0 deletions tests/Routing/QrCodeRouteLoaderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace Endroid\QrCodeBundle\Tests\Routing;

use Endroid\QrCodeBundle\Routing\QrCodeRouteLoader;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

final class QrCodeRouteLoaderTest extends TestCase
{
#[DataProvider('providePriorities')]
public function testRoutePriority(?int $priority, int $catchAllPriority, string $expectedRoute): void
{
$routes = new RouteCollection();
$routes->add('catch_all', new Route('/{slug}', [], ['slug' => '.*']), $catchAllPriority);

$loader = $this->createStub(LoaderInterface::class);
$loader->method('load')->willReturn($routes);

$routeLoader = $priority === null
? new QrCodeRouteLoader('app', '/2fa/qr')
: new QrCodeRouteLoader('app', '/2fa/qr', $priority);
$matcher = new UrlMatcher($routeLoader($loader, null), new RequestContext());

self::assertSame($expectedRoute, $matcher->match('/2fa/qr/default/test')['_route']);
self::assertSame('catch_all', $matcher->match('/other/page')['_route']);
}

public static function providePriorities(): iterable
{
yield 'omitted priority preserves application precedence' => [null, 0, 'catch_all'];
yield 'equal priority preserves application precedence' => [0, 0, 'catch_all'];
yield 'higher priority beats catch-all' => [1, 0, 'qr_code_generate'];
yield 'higher application priority still wins' => [1, 2, 'catch_all'];
yield 'negative priorities are respected' => [-2, -1, 'catch_all'];
}

public function testExplicitRouteIsPreserved(): void
{
$routes = new RouteCollection();
$routes->add('catch_all', new Route('/{slug}', [], ['slug' => '.*']));
$routes->add('qr_code_generate', new Route('/manual/{data}'), 1);

$loader = $this->createStub(LoaderInterface::class);
$loader->method('load')->willReturn($routes);

$routeLoader = new QrCodeRouteLoader('app', '/2fa/qr', -1);
$matcher = new UrlMatcher($routeLoader($loader, null), new RequestContext());

self::assertSame('qr_code_generate', $matcher->match('/manual/test')['_route']);
self::assertSame('catch_all', $matcher->match('/2fa/qr/default/test')['_route']);
}
}
1 change: 1 addition & 0 deletions tests/application/config/packages/endroid_qr_code.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
endroid_qr_code:
route_priority: 1
builders:
default:
writer: Endroid\QrCode\Writer\PngWriter
Expand Down
7 changes: 7 additions & 0 deletions tests/application/config/routes/endroid_qr_code_twig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,10 @@ endroid_qr_code_twig_invalid_option:
path: /twig-invalid-option
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
defaults: { template: 'twig-invalid-option.html.twig' }

# The QR code route must outrank an earlier catch-all when configured to do so.
catch_all:
path: /{slug}
requirements: { slug: '.*' }
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
defaults: { template: 'twig.html.twig' }
Loading