Skip to content
Open
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
43 changes: 42 additions & 1 deletion src/RouteRegistrar.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,39 @@ public function registerDirectory(string | array $directories, array $patterns =

$files = (new Finder())->files()->in($directories)->name($patterns)->notName($notPatterns)->sortByName();

collect($files)->each(fn (SplFileInfo $file) => $this->registerFile($file));
// Collect all groups from all files first
$allGroups = collect();

foreach ($files as $file) {
$className = $this->fullQualifiedClassNameFromFile($file);

if (! class_exists($className)) {
continue;
}

$class = new ReflectionClass($className);
$classRouteAttributes = new ClassRouteAttributes($class);
$groups = $classRouteAttributes->groups();

foreach ($groups as $group) {
$allGroups->push([
'class' => $class,
'classRouteAttributes' => $classRouteAttributes,
'group' => $group,
]);
}
}

// Sort all groups globally - domain groups first, then non-domain groups
$sortedGroups = $allGroups->sortByDesc(function ($item) {
return !empty($item['group']['domain'] ?? null);
});

// Process all groups in the correct order
foreach ($sortedGroups as $item) {
$router = $this->router;
$router->group($item['group'], fn () => $this->registerRoutes($item['class'], $item['classRouteAttributes']));
}
}

public function registerFile(string | SplFileInfo $path): void
Expand Down Expand Up @@ -119,6 +151,15 @@ protected function processAttributes(string $className): void

$groups = $classRouteAttributes->groups();

// Note: When called from registerDirectory, groups are already globally sorted
// This sorting is only for individual registerClass calls
usort($groups, function (array $group1, array $group2) {
$domain1 = !empty($group1['domain'] ?? null);
$domain2 = !empty($group2['domain'] ?? null);

return $domain2 <=> $domain1; // Domain routes come first
});

foreach ($groups as $group) {
$router = $this->router;
$router->group($group, fn () => $this->registerRoutes($class, $classRouteAttributes));
Expand Down
54 changes: 54 additions & 0 deletions tests/AttributeTests/DomainAttributeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Spatie\RouteAttributes\Tests\AttributeTests;

use Spatie\RouteAttributes\Tests\TestCase;
use Spatie\RouteAttributes\Tests\TestClasses\Controllers\DomainOrderTestController;
use Spatie\RouteAttributes\Tests\TestClasses\Controllers\DomainTestController;

class DomainAttributeTest extends TestCase
Expand All @@ -28,4 +29,57 @@ public function it_can_apply_a_domain_on_the_url_of_every_method()
domain: 'my-subdomain.localhost'
);
}

/** @test */
public function it_registers_domain_files_before_non_domain_files()
{
// Use registerDirectory to test file-level domain ordering
$this->routeRegistrar->registerDirectory($this->getTestPath('TestClasses/Controllers'));
$routes = collect($this->getRouteCollection()->getRoutes());

// Find all domain routes and non-domain routes
$domainRoutes = $routes->filter(fn($route) => $route->getDomain() !== null);
$nonDomainRoutes = $routes->filter(fn($route) => $route->getDomain() === null);

// Get the last index of domain routes and first index of non-domain routes
$allRoutes = $routes->values();

// Find the last domain route index
$lastDomainIndex = null;
foreach ($domainRoutes as $domainRoute) {
$index = $allRoutes->search($domainRoute);
if ($lastDomainIndex === null || $index > $lastDomainIndex) {
$lastDomainIndex = $index;
}
}

// Find the first non-domain route index
$firstNonDomainIndex = null;
foreach ($nonDomainRoutes as $nonDomainRoute) {
$index = $allRoutes->search($nonDomainRoute);
if ($firstNonDomainIndex === null || $index < $firstNonDomainIndex) {
$firstNonDomainIndex = $index;
}
}

// All domain routes should come before all non-domain routes
$this->assertLessThan(
$firstNonDomainIndex,
$lastDomainIndex,
'All domain routes should be registered before all non-domain routes',
);
}

/** @test */
public function it_registers_domain_routes_before_other_routes_in_domain_order_test_controller()
{
$this->routeRegistrar->registerClass(DomainOrderTestController::class);

$routes = $this->assertRegisteredRoutesCount(4)->getRouteCollection()->getRoutes();

$this->assertNotNull($routes[0]->domain());
$this->assertNotNull($routes[1]->domain());
$this->assertNull($routes[2]->domain());
$this->assertNull($routes[3]->domain());
}
}
10 changes: 9 additions & 1 deletion tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,15 @@ public function setUp(): void
->useRootNamespace('Spatie\RouteAttributes\Tests\\');
}

public function getTestPath(string $directory = null): string
protected function defineEnvironment($app)
{
// Disable automatic filesystem serving routes that TestBench adds in Laravel 12
$app['config']->set('filesystems.disks.local.serve', false);

return parent::defineEnvironment($app);
}

public function getTestPath(?string $directory = null): string
{
return __DIR__ . ($directory ? DIRECTORY_SEPARATOR . $directory : '');
}
Expand Down
22 changes: 22 additions & 0 deletions tests/TestClasses/Controllers/DomainOrderTestController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Spatie\RouteAttributes\Tests\TestClasses\Controllers;

use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Group;
use Spatie\RouteAttributes\Attributes\Post;

#[Group(prefix: 'my-second-prefix')]
#[Group(domain: 'domain.localhost', prefix: 'my-prefix')]
class DomainOrderTestController
{
#[Get('my-get-method')]
public function myGetMethod()
{
}

#[Post('my-post-method')]
public function myPostMethod()
{
}
}