diff --git a/Neos.Flow/Classes/Annotations/Route.php b/Neos.Flow/Classes/Annotations/Route.php new file mode 100644 index 0000000000..09159d72f1 --- /dev/null +++ b/Neos.Flow/Classes/Annotations/Route.php @@ -0,0 +1,40 @@ +routesProvider->getRoutes()[$index - 1] ?? null; + $route = iterator_to_array($this->routesProvider->getRoutes()->getIterator())[$index - 1] ?? null; if ($route === null) { $this->outputLine('Route %d was not found!', [$index]); $this->outputLine('Run ./flow routing:list to show all registered routes'); diff --git a/Neos.Flow/Classes/Mvc/Routing/AnnotationRoutesProvider.php b/Neos.Flow/Classes/Mvc/Routing/AnnotationRoutesProvider.php new file mode 100644 index 0000000000..bf7e07bb01 --- /dev/null +++ b/Neos.Flow/Classes/Mvc/Routing/AnnotationRoutesProvider.php @@ -0,0 +1,85 @@ +reflectionService->getClassesContainingMethodsAnnotatedWith(Flow\Route::class); + foreach ($annotatedClasses as $className) { + $controllerObjectName = $this->objectManager->getCaseSensitiveObjectName($className); + $controllerPackageKey = $this->objectManager->getPackageKeyByObjectName($controllerObjectName); + $controllerPackageNamespace = str_replace('.', '\\', $controllerPackageKey); + if (!str_ends_with($className, 'Controller')) { + throw new \Exception('only for controller classes'); + } + if (!str_starts_with($className, $controllerPackageNamespace . '\\')) { + throw new \Exception('only for classes in package namespace'); + } + + $localClassName = substr($className, strlen($controllerPackageNamespace) + 1); + + if (str_starts_with($localClassName, 'Controller\\')) { + $controllerName = substr($localClassName, 11); + $subPackage = null; + } elseif (str_contains($localClassName, '\\Controller\\')) { + list ($subPackage, $controllerName) = explode('\\Controller\\', $localClassName); + } else { + throw new \Exception('unknown controller pattern'); + } + + $annotatedMethods = $this->reflectionService->getMethodsAnnotatedWith($className, Flow\Route::class); + // @todo remove once reflectionService handles multiple annotations properly + $annotatedMethods = array_unique($annotatedMethods); + foreach ($annotatedMethods as $methodName) { + if (!str_ends_with($methodName, 'Action')) { + throw new \Exception('only for action methods'); + } + $annotations = $this->reflectionService->getMethodAnnotations($className, $methodName, Flow\Route::class); + foreach ($annotations as $annotation) { + if ($annotation instanceof Flow\Route) { + $configuration = [ + 'uriPattern' => $annotation->uriPattern, + 'defaults' => Arrays::arrayMergeRecursiveOverrule( + [ + '@package' => $controllerPackageKey, + '@subpackage' => $subPackage, + '@controller' => substr($controllerName, 0, -10), + '@action' => substr($methodName, 0, -6), + '@format' => 'html' + ], + $annotation->defaults ?? [] + ) + ]; + if ($annotation->name !== null) { + $configuration['name'] = $annotation->name; + } + if ($annotation->httpMethods !== null) { + $configuration['httpMethods'] = $annotation->httpMethods; + } + $routes[] = Route::fromConfiguration($configuration); + } + } + } + } + return Routes::create(...$routes); + } +} diff --git a/Neos.Flow/Classes/Mvc/Routing/CombinedRoutesProvider.php b/Neos.Flow/Classes/Mvc/Routing/CombinedRoutesProvider.php new file mode 100644 index 0000000000..1aaa18f4e2 --- /dev/null +++ b/Neos.Flow/Classes/Mvc/Routing/CombinedRoutesProvider.php @@ -0,0 +1,17 @@ +annotationRoutesProvider->getRoutes()->merge($this->configurationRoutesProvider->getRoutes()); + } +} diff --git a/Neos.Flow/Configuration/Objects.yaml b/Neos.Flow/Configuration/Objects.yaml index 4f116f465d..53cd98eacc 100644 --- a/Neos.Flow/Configuration/Objects.yaml +++ b/Neos.Flow/Configuration/Objects.yaml @@ -258,7 +258,7 @@ Neos\Flow\Mvc\Routing\RouterInterface: className: Neos\Flow\Mvc\Routing\Router Neos\Flow\Mvc\Routing\RoutesProviderInterface: - className: Neos\Flow\Mvc\Routing\ConfigurationRoutesProvider + className: Neos\Flow\Mvc\Routing\CombinedRoutesProvider Neos\Flow\Mvc\Routing\RouterCachingService: properties: