Skip to content

Commit

Permalink
Merge pull request #3401 from kitsunet/bugfix/3400-compile-route-attr…
Browse files Browse the repository at this point in the history
…ibutes

BUGFIX: Static compile attribute routes
  • Loading branch information
mhsdesign authored Oct 14, 2024
2 parents 7136a1f + ab6c0a9 commit 5797a1b
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 16 deletions.
46 changes: 35 additions & 11 deletions Neos.Flow/Classes/Mvc/Routing/AttributeRoutesProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ final class AttributeRoutesProvider implements RoutesProviderInterface
* @param array<string> $classNames
*/
public function __construct(
public readonly ReflectionService $reflectionService,
public readonly ObjectManagerInterface $objectManager,
public readonly array $classNames,
) {
Expand All @@ -78,9 +77,7 @@ public function __construct(
public function getRoutes(): Routes
{
$routes = [];
$annotatedClasses = $this->reflectionService->getClassesContainingMethodsAnnotatedWith(Flow\Route::class);

foreach ($annotatedClasses as $className) {
foreach (static::compileRoutesConfiguration($this->objectManager) as $className => $routesForClass) {
$includeClassName = false;
foreach ($this->classNames as $classNamePattern) {
if (fnmatch($classNamePattern, $className, FNM_NOESCAPE)) {
Expand All @@ -92,12 +89,37 @@ public function getRoutes(): Routes
continue;
}

$routes = [...$routes, ...$routesForClass];
}

$routes = array_map(static fn (array $routeConfiguration): Route => Route::fromConfiguration($routeConfiguration), $routes);
return Routes::create(...$routes);
}

/**
* @param ObjectManagerInterface $objectManager
* @return array<string, array<int, mixed>>
* @throws InvalidActionNameException
* @throws InvalidControllerException
* @throws \Neos\Flow\Utility\Exception
* @throws \Neos\Utility\Exception\FilesException
* @throws \ReflectionException
*/
#[Flow\CompileStatic]
public static function compileRoutesConfiguration(ObjectManagerInterface $objectManager): array
{
$reflectionService = $objectManager->get(ReflectionService::class);

$routesByClassName = [];
$annotatedClasses = $reflectionService->getClassesContainingMethodsAnnotatedWith(Flow\Route::class);

foreach ($annotatedClasses as $className) {
if (!in_array(ActionController::class, class_parents($className), true)) {
throw new InvalidControllerException('TODO: Currently #[Flow\Route] is only supported for ActionController. See https://github.com/neos/flow-development-collection/issues/3335.');
}

$controllerObjectName = $this->objectManager->getCaseSensitiveObjectName($className);
$controllerPackageKey = $this->objectManager->getPackageKeyByObjectName($controllerObjectName);
$controllerObjectName = $objectManager->getCaseSensitiveObjectName($className);
$controllerPackageKey = $objectManager->getPackageKeyByObjectName($controllerObjectName);
$controllerPackageNamespace = str_replace('.', '\\', $controllerPackageKey);
if (!str_ends_with($className, 'Controller')) {
throw new InvalidControllerException('Only for controller classes');
Expand All @@ -109,17 +131,18 @@ public function getRoutes(): Routes
$controllerName = substr($localClassName, 11);
$subPackage = null;
} elseif (str_contains($localClassName, '\\Controller\\')) {
list($subPackage, $controllerName) = explode('\\Controller\\', $localClassName);
[$subPackage, $controllerName] = explode('\\Controller\\', $localClassName);
} else {
throw new InvalidControllerException('Unknown controller pattern');
}

$annotatedMethods = $this->reflectionService->getMethodsAnnotatedWith($className, Flow\Route::class);
$routesByClassName[$className] = [];
$annotatedMethods = $reflectionService->getMethodsAnnotatedWith($className, Flow\Route::class);
foreach ($annotatedMethods as $methodName) {
if (!str_ends_with($methodName, 'Action')) {
throw new InvalidActionNameException('Only for action methods');
}
$annotations = $this->reflectionService->getMethodAnnotations($className, $methodName, Flow\Route::class);
$annotations = $reflectionService->getMethodAnnotations($className, $methodName, Flow\Route::class);
foreach ($annotations as $annotation) {
if ($annotation instanceof Flow\Route) {
$controller = substr($controllerName, 0, -10);
Expand All @@ -139,11 +162,12 @@ public function getRoutes(): Routes
$annotation->defaults ?? []
)
];
$routes[] = Route::fromConfiguration($configuration);
$routesByClassName[$className][] = $configuration;
}
}
}
}
return Routes::create(...$routes);

return $routesByClassName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,10 @@
namespace Neos\Flow\Mvc\Routing;

use Neos\Flow\ObjectManagement\ObjectManagerInterface;
use Neos\Flow\Reflection\ReflectionService;

class AttributeRoutesProviderFactory implements RoutesProviderFactoryInterface
{
public function __construct(
public readonly ReflectionService $reflectionService,
public readonly ObjectManagerInterface $objectManager,
) {
}
Expand All @@ -31,7 +29,6 @@ public function __construct(
public function createRoutesProvider(array $options): RoutesProviderInterface
{
return new AttributeRoutesProvider(
$this->reflectionService,
$this->objectManager,
$options['classNames'] ?? [],
);
Expand Down
20 changes: 18 additions & 2 deletions Neos.Flow/Tests/Unit/Mvc/Routing/AttributeRoutesProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,12 @@ public function setUp(): void
$this->mockReflectionService = $this->createMock(ReflectionService::class);
$this->mockObjectManager = $this->createMock(ObjectManagerInterface::class);

$this->mockObjectManager->expects(self::any())
->method('get')
->with(ReflectionService::class)
->willReturn($this->mockReflectionService);

$this->annotationRoutesProvider = new Routing\AttributeRoutesProvider(
$this->mockReflectionService,
$this->mockObjectManager,
['Vendor\\Example\\Controller\\*']
);
Expand Down Expand Up @@ -136,10 +140,22 @@ class ExampleController extends \Neos\Flow\Mvc\Controller\ActionController {
*/
public function annotationsOutsideClassNamesAreIgnored(): void
{
$controllerclassName = 'Neos\Flow\Mvc\Controller\StandardController';

$this->mockObjectManager->expects(self::once())
->method('getCaseSensitiveObjectName')
->with($controllerclassName)
->willReturn($controllerclassName);

$this->mockObjectManager->expects(self::once())
->method('getPackageKeyByObjectName')
->with($controllerclassName)
->willReturn('Neos.Flow');

$this->mockReflectionService->expects($this->once())
->method('getClassesContainingMethodsAnnotatedWith')
->with(Flow\Route::class)
->willReturn(['Vendor\Other\Controller\ExampleController']);
->willReturn([$controllerclassName]);

$this->assertEquals(Routes::empty(), $this->annotationRoutesProvider->getRoutes());
}
Expand Down

0 comments on commit 5797a1b

Please sign in to comment.