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
10 changes: 6 additions & 4 deletions src/router/src/DispatcherFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,20 @@ class DispatcherFactory extends BaseDispatcherFactory

public function __construct(protected ContainerInterface $container)
{
$this->routes = $container->get(RouteFileCollector::class)
->getRouteFiles();
$this->initAnnotationRoute(AnnotationCollector::list());
}

public function initRoutes()
public function initRoutes(): void
{
$this->initialized = true;

MiddlewareManager::$container = [];

foreach ($this->routes as $route) {
// Fetch route files at initialization time
// Ensures routes added via loadRoutesFrom() in service providers are included
$routes = $this->container->get(RouteFileCollector::class)->getRouteFiles();

foreach ($routes as $route) {
if (file_exists($route)) {
require $route;
}
Expand Down
54 changes: 54 additions & 0 deletions tests/Router/DispatcherFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@
*/
class DispatcherFactoryTest extends TestCase
{
public static function setUpBeforeClass(): void
{
parent::setUpBeforeClass();

if (! defined('BASE_PATH')) {
define('BASE_PATH', dirname(__DIR__, 2));
}
}

public function testGetRouter()
{
if (! defined('BASE_PATH')) {
Expand Down Expand Up @@ -66,6 +75,51 @@ public function testInitConfigRoute()
$dispatcherFactory->initRoutes('http');
}

/**
* Test that routes added to RouteFileCollector AFTER DispatcherFactory
* construction are still loaded when initRoutes() is called.
*
* This simulates the loadRoutesFrom() pattern where service providers
* add route files during boot(), after DispatcherFactory may have been
* constructed.
*/
public function testRoutesAddedAfterConstructionAreLoaded()
{
if (! defined('BASE_PATH')) {
$this->markTestSkipped('skip it because DispatcherFactory in hyperf is dirty.');
}

/** @var MockInterface|RouteCollector */
$routeCollector = Mockery::mock(RouteCollector::class);

// Initial route from foo.php
$routeCollector->shouldReceive('get')->with('/foo', 'Handler::Foo')->once();

// Late-added route from late.php - this MUST be loaded
$routeCollector->shouldReceive('get')->with('/late', 'Handler::Late')->once();

// Create RouteFileCollector with only the initial route
$routeFileCollector = new RouteFileCollector([
__DIR__ . '/routes/foo.php',
]);

$container = $this->getContainer([
HyperfRouteCollector::class => fn () => $routeCollector,
RouteFileCollector::class => fn () => $routeFileCollector,
]);

// Create DispatcherFactory - this captures route files in constructor
$dispatcherFactory = new DispatcherFactory($container);
$container->define(Router::class, fn () => new Router($dispatcherFactory));

// Simulate service provider adding routes AFTER DispatcherFactory construction
// This is what loadRoutesFrom() does
$routeFileCollector->addRouteFile(__DIR__ . '/routes/late.php');

// Now trigger route loading - late.php should be included
$dispatcherFactory->getRouter('http');
}

private function getContainer(array $bindings = []): Container
{
$container = new Container(
Expand Down
7 changes: 7 additions & 0 deletions tests/Router/routes/late.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

declare(strict_types=1);

use Hypervel\Router\Router;

Router::get('/late', 'Handler::Late');