From 2162fe8fe4afb49bf98745cb22aed2b25e3cfa99 Mon Sep 17 00:00:00 2001 From: Sebastian Michaelsen Date: Mon, 2 Feb 2026 15:13:12 +0100 Subject: [PATCH 1/3] chore: declare compatibility with TYPO3 v14 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3e6601e..f5802c1 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,7 @@ "description": "Easy way to route rest-like URLs to your code", "license": "GPL-2.0-only", "require": { - "typo3/cms-core": "^13.4" + "typo3/cms-core": "^13.4 || ^14.0" }, "require-dev": { "phpstan/phpstan": "^2.1", From 529542181e4eec40b585adf470cdb2b52192762f Mon Sep 17 00:00:00 2001 From: Amir Arends Date: Tue, 3 Mar 2026 10:42:52 +0100 Subject: [PATCH 2/3] test: introduce unit tests for key components --- .github/workflows/ci.yaml | 41 ++++ .../Middleware/AppRoutesMiddlewareTest.php | 205 ++++++++++++++++++ .../Service/ResponseCachingServiceTest.php | 81 +++++++ Tests/Unit/Service/RouterTest.php | 91 ++++++++ .../Service/RoutesConfigurationLoaderTest.php | 68 ++++++ .../Unit/ViewHelpers/RouteViewHelperTest.php | 58 +++++ composer.json | 12 +- phpunit.xml | 18 ++ 8 files changed, 572 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci.yaml create mode 100644 Tests/Unit/Middleware/AppRoutesMiddlewareTest.php create mode 100644 Tests/Unit/Service/ResponseCachingServiceTest.php create mode 100644 Tests/Unit/Service/RouterTest.php create mode 100644 Tests/Unit/Service/RoutesConfigurationLoaderTest.php create mode 100644 Tests/Unit/ViewHelpers/RouteViewHelperTest.php create mode 100644 phpunit.xml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..c0e7c38 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,41 @@ +name: CI + +on: + pull_request: + +jobs: + tests: + name: Tests (PHP ${{ matrix.php-version }}) + runs-on: ubuntu-latest + strategy: + matrix: + php-version: ['8.4'] + steps: + - uses: actions/checkout@v4 + + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + tools: composer:v2 + + - run: composer install --no-progress + + - run: composer test + + tests-lowest: + name: Tests lowest deps (PHP ${{ matrix.php-version }}) + runs-on: ubuntu-latest + strategy: + matrix: + php-version: ['8.4'] + steps: + - uses: actions/checkout@v4 + + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + tools: composer:v2 + + - run: composer update --prefer-lowest --no-progress + + - run: composer test diff --git a/Tests/Unit/Middleware/AppRoutesMiddlewareTest.php b/Tests/Unit/Middleware/AppRoutesMiddlewareTest.php new file mode 100644 index 0000000..81b6acb --- /dev/null +++ b/Tests/Unit/Middleware/AppRoutesMiddlewareTest.php @@ -0,0 +1,205 @@ +context = new Context(); + $this->frontendInitialization = self::createStub(FrontendInitialization::class); + $this->responseCachingService = self::createStub(ResponseCachingService::class); + $this->router = self::createStub(Router::class); + + $this->subject = new AppRoutesMiddleware( + $this->context, + $this->frontendInitialization, + $this->responseCachingService, + $this->router, + ); + } + + #[Test] + public function noRouteMatchDelegatesToNextHandler(): void + { + $urlMatcher = self::createStub(UrlMatcher::class); + $urlMatcher->method('match')->willThrowException(new ResourceNotFoundException()); + $this->router->method('getUrlMatcher')->willReturn($urlMatcher); + + $expectedResponse = new Response(); + $nextHandler = self::createStub(RequestHandlerInterface::class); + $nextHandler->method('handle')->willReturn($expectedResponse); + + $request = new ServerRequest('https://example.com/unknown-path'); + $response = $this->subject->process($request, $nextHandler); + + self::assertSame($expectedResponse, $response); + } + + #[Test] + public function etagMatchReturns304(): void + { + $etag = '"abc123"'; + $request = (new ServerRequest('https://example.com/api/test')) + ->withHeader('If-None-Match', $etag); + + $originalResponse = (new Response()) + ->withHeader('ETag', $etag) + ->withStatus(200); + + $reflection = new \ReflectionMethod($this->subject, 'replaceWithNotModifiedResponse'); + $result = $reflection->invoke($this->subject, $request, $originalResponse); + + self::assertSame(304, $result->getStatusCode()); + self::assertSame('', (string)$result->getBody()); + } + + #[Test] + public function etagMismatchKeepsOriginalResponse(): void + { + $request = (new ServerRequest('https://example.com/api/test')) + ->withHeader('If-None-Match', '"old-etag"'); + + $originalResponse = (new Response()) + ->withHeader('ETag', '"new-etag"') + ->withStatus(200); + + $reflection = new \ReflectionMethod($this->subject, 'replaceWithNotModifiedResponse'); + $result = $reflection->invoke($this->subject, $request, $originalResponse); + + self::assertSame(200, $result->getStatusCode()); + } + + #[Test] + public function nonOkResponseSkipsEtagCheck(): void + { + $request = (new ServerRequest('https://example.com/api/test')) + ->withHeader('If-None-Match', '"abc123"'); + + $originalResponse = (new Response()) + ->withHeader('ETag', '"abc123"') + ->withStatus(404); + + $reflection = new \ReflectionMethod($this->subject, 'replaceWithNotModifiedResponse'); + $result = $reflection->invoke($this->subject, $request, $originalResponse); + + self::assertSame(404, $result->getStatusCode()); + } + + #[Test] + public function cacheHitServesFromCache(): void + { + $cachedResponse = new Response(); + $request = new ServerRequest('https://example.com/api/test', 'GET'); + + $this->responseCachingService->method('has')->willReturn(true); + $this->responseCachingService->method('isCacheable')->willReturn(true); + $this->responseCachingService->method('serveFromCache')->willReturn($cachedResponse); + + $response = $this->subject->handleRequestCached( + ['cache' => true], + $request, + ); + + self::assertSame($cachedResponse, $response); + } + + #[Test] + public function initializeNeededFrontendComponentsSetsPageParts(): void + { + $pageInformation = new PageInformation(); + $pageInformation->setPageRecord(['tstamp' => 1000, 'SYS_LASTCHANGED' => 2000]); + + $language = self::createStub(SiteLanguage::class); + $this->frontendInitialization->method('getLanguage')->willReturn($language); + $this->frontendInitialization->method('createPageInformation')->willReturn($pageInformation); + + $site = self::createStub(SiteInterface::class); + $site->method('getRootPageId')->willReturn(1); + + $request = (new ServerRequest('https://example.com/api/test')) + ->withAttribute('site', $site); + + $reflection = new \ReflectionMethod($this->subject, 'initializeNeededFrontendComponents'); + $result = $reflection->invoke($this->subject, [], $request); + + $pageParts = $result->getAttribute('frontend.page.parts'); + self::assertInstanceOf(PageParts::class, $pageParts); + } + + #[Test] + public function pagePartsLastChangedUsesSysLastChangedWhenHigher(): void + { + $pageInformation = new PageInformation(); + $pageInformation->setPageRecord(['tstamp' => 1000, 'SYS_LASTCHANGED' => 2000]); + + $language = self::createStub(SiteLanguage::class); + $this->frontendInitialization->method('getLanguage')->willReturn($language); + $this->frontendInitialization->method('createPageInformation')->willReturn($pageInformation); + + $site = self::createStub(SiteInterface::class); + $site->method('getRootPageId')->willReturn(1); + + $request = (new ServerRequest('https://example.com/api/test')) + ->withAttribute('site', $site); + + $reflection = new \ReflectionMethod($this->subject, 'initializeNeededFrontendComponents'); + $result = $reflection->invoke($this->subject, [], $request); + + $pageParts = $result->getAttribute('frontend.page.parts'); + self::assertSame(2000, $pageParts->getLastChanged()); + } + + #[Test] + public function pagePartsLastChangedUsesTstampWhenHigher(): void + { + $pageInformation = new PageInformation(); + $pageInformation->setPageRecord(['tstamp' => 3000, 'SYS_LASTCHANGED' => 1000]); + + $language = self::createStub(SiteLanguage::class); + $this->frontendInitialization->method('getLanguage')->willReturn($language); + $this->frontendInitialization->method('createPageInformation')->willReturn($pageInformation); + + $site = self::createStub(SiteInterface::class); + $site->method('getRootPageId')->willReturn(1); + + $request = (new ServerRequest('https://example.com/api/test')) + ->withAttribute('site', $site); + + $reflection = new \ReflectionMethod($this->subject, 'initializeNeededFrontendComponents'); + $result = $reflection->invoke($this->subject, [], $request); + + $pageParts = $result->getAttribute('frontend.page.parts'); + self::assertSame(3000, $pageParts->getLastChanged()); + } +} diff --git a/Tests/Unit/Service/ResponseCachingServiceTest.php b/Tests/Unit/Service/ResponseCachingServiceTest.php new file mode 100644 index 0000000..57cd4bd --- /dev/null +++ b/Tests/Unit/Service/ResponseCachingServiceTest.php @@ -0,0 +1,81 @@ +cache = self::createStub(FrontendInterface::class); + $cacheManager = self::createStub(CacheManager::class); + $cacheManager->method('getCache')->willReturn($this->cache); + + $this->subject = new ResponseCachingService($cacheManager); + } + + #[Test] + public function isCacheableReturnsTrueForGetRequest(): void + { + $request = new ServerRequest('https://example.com/api/test', 'GET'); + self::assertTrue($this->subject->isCacheable($request)); + } + + #[Test] + public function isCacheableReturnsTrueForHeadRequest(): void + { + $request = new ServerRequest('https://example.com/api/test', 'HEAD'); + self::assertTrue($this->subject->isCacheable($request)); + } + + #[Test] + public function isCacheableReturnsFalseForPostRequest(): void + { + $request = new ServerRequest('https://example.com/api/test', 'POST'); + self::assertFalse($this->subject->isCacheable($request)); + } + + #[Test] + public function isCacheableReturnsFalseForPutRequest(): void + { + $request = new ServerRequest('https://example.com/api/test', 'PUT'); + self::assertFalse($this->subject->isCacheable($request)); + } + + #[Test] + public function isCacheableReturnsFalseForDeleteRequest(): void + { + $request = new ServerRequest('https://example.com/api/test', 'DELETE'); + self::assertFalse($this->subject->isCacheable($request)); + } + + #[Test] + public function hasDelegatesToCache(): void + { + $this->cache->method('has')->willReturn(true); + self::assertTrue($this->subject->has('testKey')); + } + + #[Test] + public function hasReturnsFalseForMissingKey(): void + { + $this->cache->method('has')->willReturn(false); + self::assertFalse($this->subject->has('missingKey')); + } +} diff --git a/Tests/Unit/Service/RouterTest.php b/Tests/Unit/Service/RouterTest.php new file mode 100644 index 0000000..5278961 --- /dev/null +++ b/Tests/Unit/Service/RouterTest.php @@ -0,0 +1,91 @@ +routeFilesLoader = self::createStub(RoutesConfigurationLoader::class); + $this->runtimeCache = self::createStub(FrontendInterface::class); + $cacheManager->method('getCache')->willReturn($this->runtimeCache); + + $this->subject = new Router($cacheManager, $this->routeFilesLoader); + } + + #[Test] + public function getRoutesReturnsPopulatedRouteCollection(): void + { + $this->runtimeCache->method('has')->willReturn(false); + + $this->routeFilesLoader->method('getRoutesConfiguration')->willReturn([ + 'testApp' => [ + 'prefix' => '/api/v1', + 'routes' => [ + [ + 'name' => 'list', + 'path' => '/items', + 'defaults' => ['handler' => 'TestHandler'], + ], + [ + 'name' => 'detail', + 'path' => '/items/{id}', + 'defaults' => ['handler' => 'TestHandler'], + ], + ], + ], + ]); + + $routes = $this->subject->getRoutes(); + + self::assertInstanceOf(RouteCollection::class, $routes); + self::assertCount(2, $routes); + self::assertNotNull($routes->get('testApp.list')); + self::assertNotNull($routes->get('testApp.detail')); + self::assertSame('/api/v1/items', $routes->get('testApp.list')->getPath()); + self::assertSame('/api/v1/items/{id}', $routes->get('testApp.detail')->getPath()); + } + + #[Test] + public function getRoutesReturnsCachedRouteCollection(): void + { + $cachedRoutes = new RouteCollection(); + $this->runtimeCache->method('has')->willReturn(true); + $this->runtimeCache->method('get')->willReturn($cachedRoutes); + + $routes = $this->subject->getRoutes(); + + self::assertSame($cachedRoutes, $routes); + } + + #[Test] + public function getRoutesHandlesEmptyConfiguration(): void + { + $this->runtimeCache->method('has')->willReturn(false); + $this->routeFilesLoader->method('getRoutesConfiguration')->willReturn([]); + + $routes = $this->subject->getRoutes(); + + self::assertCount(0, $routes); + } +} diff --git a/Tests/Unit/Service/RoutesConfigurationLoaderTest.php b/Tests/Unit/Service/RoutesConfigurationLoaderTest.php new file mode 100644 index 0000000..2047bea --- /dev/null +++ b/Tests/Unit/Service/RoutesConfigurationLoaderTest.php @@ -0,0 +1,68 @@ +cache = self::createStub(FrontendInterface::class); + $this->packageManager = self::createStub(PackageManager::class); + $this->yamlFileLoader = self::createStub(YamlFileLoader::class); + } + + private function createSubject(): RoutesConfigurationLoader + { + $cacheManager = self::createStub(CacheManager::class); + $cacheManager->method('getCache')->willReturn($this->cache); + + return new RoutesConfigurationLoader($cacheManager, $this->packageManager, $this->yamlFileLoader); + } + + #[Test] + public function returnsCachedConfiguration(): void + { + $expectedConfig = ['testApp' => ['routes' => []]]; + $this->cache->method('has')->willReturn(true); + $this->cache->method('get')->willReturn($expectedConfig); + + $subject = $this->createSubject(); + $result = $subject->getRoutesConfiguration(); + + self::assertSame($expectedConfig, $result); + } + + #[Test] + public function returnsSameResultOnSecondCall(): void + { + $expectedConfig = ['testApp' => ['routes' => []]]; + $this->cache->method('has')->willReturn(true); + $this->cache->method('get')->willReturn($expectedConfig); + + $subject = $this->createSubject(); + $first = $subject->getRoutesConfiguration(); + $second = $subject->getRoutesConfiguration(); + + self::assertSame($first, $second); + } +} diff --git a/Tests/Unit/ViewHelpers/RouteViewHelperTest.php b/Tests/Unit/ViewHelpers/RouteViewHelperTest.php new file mode 100644 index 0000000..b591546 --- /dev/null +++ b/Tests/Unit/ViewHelpers/RouteViewHelperTest.php @@ -0,0 +1,58 @@ +method('generate')->willReturn('https://example.com/api/order/42'); + + $router = self::createStub(Router::class); + $router->method('getUrlGenerator')->willReturn($urlGenerator); + + $subject = new RouteViewHelper($router); + $subject->initializeArguments(); + $subject->setArguments([ + 'routeName' => 'myApp.order', + 'parameters' => ['orderUid' => '42'], + ]); + + $result = $subject->render(); + + self::assertSame('https://example.com/api/order/42', $result); + } + + #[Test] + public function defaultsToEmptyParametersArray(): void + { + $urlGenerator = self::createStub(UrlGenerator::class); + $urlGenerator->method('generate')->willReturn('https://example.com/api/list'); + + $router = self::createStub(Router::class); + $router->method('getUrlGenerator')->willReturn($urlGenerator); + + $subject = new RouteViewHelper($router); + $subject->initializeArguments(); + $subject->setArguments([ + 'routeName' => 'myApp.list', + 'parameters' => [], + ]); + + $result = $subject->render(); + + self::assertSame('https://example.com/api/list', $result); + } +} diff --git a/composer.json b/composer.json index f5802c1..bfad6b3 100644 --- a/composer.json +++ b/composer.json @@ -8,11 +8,14 @@ }, "require-dev": { "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^11.0", "typo3/cms-frontend": "^13.4", - "typo3/cms-lowlevel": "^13.4" + "typo3/cms-lowlevel": "^13.4", + "typo3/testing-framework": "^9.0" }, "scripts": { - "phpstan": "phpstan analyze -c phpstan.neon --memory-limit=1G" + "phpstan": "phpstan analyze -c phpstan.neon --memory-limit=1G", + "test": "phpunit -c phpunit.xml" }, "config": { "allow-plugins": { @@ -25,6 +28,11 @@ "Sinso\\AppRoutes\\": "Classes" } }, + "autoload-dev": { + "psr-4": { + "Sinso\\AppRoutes\\Tests\\": "Tests" + } + }, "extra": { "typo3/cms": { "extension-key": "app_routes" diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..6005f75 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,18 @@ + + + + + Tests/Unit + + + + + Classes + + + From 729d53a7cd1761447b2ebc1ea94ff001f21fc941 Mon Sep 17 00:00:00 2001 From: Amir Arends Date: Tue, 3 Mar 2026 10:38:25 +0100 Subject: [PATCH 3/3] fix!: remove TSFE initialization BREAKING CHANGE: TSFE is no longer initialized by the middleware. --- .github/workflows/{ci.yaml => unit.yaml} | 2 +- .../AppRoutesProvider.php | 12 ++----- Classes/Middleware/AppRoutesMiddleware.php | 13 +++++--- Classes/Service/FrontendInitialization.php | 31 ------------------ Classes/Service/RoutesConfigurationLoader.php | 32 +++++-------------- Readme.md | 3 +- composer.json | 6 ++-- 7 files changed, 24 insertions(+), 75 deletions(-) rename .github/workflows/{ci.yaml => unit.yaml} (98%) diff --git a/.github/workflows/ci.yaml b/.github/workflows/unit.yaml similarity index 98% rename from .github/workflows/ci.yaml rename to .github/workflows/unit.yaml index c0e7c38..e13c600 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/unit.yaml @@ -1,4 +1,4 @@ -name: CI +name: Unit on: pull_request: diff --git a/Classes/ConfigurationModuleProvider/AppRoutesProvider.php b/Classes/ConfigurationModuleProvider/AppRoutesProvider.php index 1b20979..afae0ad 100644 --- a/Classes/ConfigurationModuleProvider/AppRoutesProvider.php +++ b/Classes/ConfigurationModuleProvider/AppRoutesProvider.php @@ -9,15 +9,9 @@ class AppRoutesProvider extends AbstractProvider { - /** - * @var RoutesConfigurationLoader - */ - protected $routesConfigurationLoader; - - public function __construct(RoutesConfigurationLoader $routesConfigurationLoader) - { - $this->routesConfigurationLoader = $routesConfigurationLoader; - } + public function __construct( + private readonly RoutesConfigurationLoader $routesConfigurationLoader, + ) {} public function getConfiguration(): array { diff --git a/Classes/Middleware/AppRoutesMiddleware.php b/Classes/Middleware/AppRoutesMiddleware.php index 749710e..002c266 100644 --- a/Classes/Middleware/AppRoutesMiddleware.php +++ b/Classes/Middleware/AppRoutesMiddleware.php @@ -20,6 +20,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Frontend\Aspect\PreviewAspect; use TYPO3\CMS\Frontend\Page\PageInformation; +use TYPO3\CMS\Frontend\Page\PageParts; class AppRoutesMiddleware implements MiddlewareInterface { @@ -89,7 +90,7 @@ protected function initializeNeededFrontendComponents(array $parameters, ServerR $site = $request->getAttribute('site'); // set PageArguments as routing attribute - $keysToRemove = ['handler', 'requiresTsfe', 'requiresTypoScript', 'cache', 'L', '_route']; + $keysToRemove = ['handler', 'requiresTypoScript', 'cache', 'L', '_route']; $remainingArguments = array_diff_key($request->getQueryParams(), array_flip($keysToRemove)); $request = $request->withAttribute('routing', new PageArguments($site->getRootPageId(), '0', [], [], $remainingArguments)); @@ -107,11 +108,13 @@ protected function initializeNeededFrontendComponents(array $parameters, ServerR $pageInformation = $this->frontendInitialization->createPageInformation($request); $request = $request->withAttribute('frontend.page.information', $pageInformation); - // TSFE - if ($parameters['requiresTsfe'] ?? false) { - $tsfe = $this->frontendInitialization->createTyposcriptFrontendController($request); - $request = $request->withAttribute('frontend.controller', $tsfe); + $pageParts = new PageParts(); + $lastChanged = (int)$pageInformation->getPageRecord()['tstamp']; + if ($lastChanged < (int)$pageInformation->getPageRecord()['SYS_LASTCHANGED']) { + $lastChanged = (int)$pageInformation->getPageRecord()['SYS_LASTCHANGED']; } + $pageParts->setLastChanged($lastChanged); + $request = $request->withAttribute('frontend.page.parts', $pageParts); // TypoScript if ($parameters['requiresTypoScript'] ?? false) { diff --git a/Classes/Service/FrontendInitialization.php b/Classes/Service/FrontendInitialization.php index cc58628..38fc7d0 100644 --- a/Classes/Service/FrontendInitialization.php +++ b/Classes/Service/FrontendInitialization.php @@ -9,14 +9,11 @@ use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend; use TYPO3\CMS\Core\Routing\PageArguments; use TYPO3\CMS\Core\Site\Entity\NullSite; -use TYPO3\CMS\Core\Site\Entity\SiteInterface; use TYPO3\CMS\Core\Site\Entity\SiteLanguage; use TYPO3\CMS\Core\Site\SiteFinder; use TYPO3\CMS\Core\TypoScript\FrontendTypoScript; use TYPO3\CMS\Core\TypoScript\FrontendTypoScriptFactory; use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; -use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; use TYPO3\CMS\Frontend\Page\PageInformation; use TYPO3\CMS\Frontend\Page\PageInformationFactory; @@ -29,34 +26,6 @@ public function __construct( private readonly PhpFrontend $typoScriptCache, ) {} - public function createTyposcriptFrontendController(ServerRequestInterface $request): TypoScriptFrontendController - { - $pageInformation = $this->pageInformationFactory->create($request); - - $controller = GeneralUtility::makeInstance(TypoScriptFrontendController::class); - $controller->initializePageRenderer($request); - $controller->initializeLanguageService($request); - $controller->id = $pageInformation->getId(); - $controller->page = $pageInformation->getPageRecord(); - $controller->contentPid = $pageInformation->getContentFromPid(); - $controller->rootLine = $pageInformation->getRootLine(); - $controller->config['rootLine'] = $pageInformation->getLocalRootLine(); - $controller->register['SYS_LASTCHANGED'] = (int)$pageInformation->getPageRecord()['tstamp']; - if ($controller->register['SYS_LASTCHANGED'] < (int)$pageInformation->getPageRecord()['SYS_LASTCHANGED']) { - $controller->register['SYS_LASTCHANGED'] = (int)$pageInformation->getPageRecord()['SYS_LASTCHANGED']; - } - - $contentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class); - $contentObjectRenderer->start($pageInformation->getPageRecord(), 'pages'); - $controller->cObj = $contentObjectRenderer; - - $request = $request->withAttribute('frontend.controller', $controller); - $GLOBALS['TYPO3_REQUEST'] = $request; - $GLOBALS['TSFE'] = $controller; - - return $controller; - } - public function createPageInformation(ServerRequestInterface $request): PageInformation { return $this->pageInformationFactory->create($request); diff --git a/Classes/Service/RoutesConfigurationLoader.php b/Classes/Service/RoutesConfigurationLoader.php index 9df3dfb..4291154 100644 --- a/Classes/Service/RoutesConfigurationLoader.php +++ b/Classes/Service/RoutesConfigurationLoader.php @@ -13,36 +13,20 @@ class RoutesConfigurationLoader { public const APP_ROUTES_YAML_PATH = 'Configuration/AppRoutes.yaml'; - /** - * @var FrontendInterface - */ - protected $cache; + private readonly FrontendInterface $cache; + private array $routesConfiguration; - /** - * @var PackageManager - */ - protected $packageManager; - - /** - * @var array - */ - protected $routesConfiguration; - - /** - * @var YamlFileLoader - */ - protected $yamlFileLoader; - - public function __construct(CacheManager $cacheManager, PackageManager $packageManager, YamlFileLoader $yamlFileLoader) - { + public function __construct( + CacheManager $cacheManager, + private readonly PackageManager $packageManager, + private readonly YamlFileLoader $yamlFileLoader, + ) { $this->cache = $cacheManager->getCache('app_routes'); - $this->packageManager = $packageManager; - $this->yamlFileLoader = $yamlFileLoader; } public function getRoutesConfiguration(): array { - if (!is_array($this->routesConfiguration)) { + if (!isset($this->routesConfiguration)) { $this->loadRoutesConfiguration(); } return $this->routesConfiguration; diff --git a/Readme.md b/Readme.md index e00625b..96bc003 100644 --- a/Readme.md +++ b/Readme.md @@ -41,8 +41,7 @@ Everything that is available as YAML configuration option in `symfony/routing` s This package offers these additional options: * `defaults.cache: true` - If true, then responses are cached (see more details below). (default: `false`) -* `defaults.requiresTsfe: true` - If true, then the `frontend.controller` request attribute will be initialized before your handler is called (default: `false`). -* `defaults.TypoScript: true` - If true, then the `frontend.typoscript` request attribute will be initialized before your handler is called (default: `false`). +* `defaults.requiresTypoScript: true` - If true, then the `frontend.typoscript` request attribute will be initialized before your handler is called (default: `false`). ### Generate Route URLs diff --git a/composer.json b/composer.json index bfad6b3..87da838 100644 --- a/composer.json +++ b/composer.json @@ -4,13 +4,13 @@ "description": "Easy way to route rest-like URLs to your code", "license": "GPL-2.0-only", "require": { - "typo3/cms-core": "^13.4 || ^14.0" + "typo3/cms-core": "^14.0" }, "require-dev": { "phpstan/phpstan": "^2.1", "phpunit/phpunit": "^11.0", - "typo3/cms-frontend": "^13.4", - "typo3/cms-lowlevel": "^13.4", + "typo3/cms-frontend": "^14.0", + "typo3/cms-lowlevel": "^14.0", "typo3/testing-framework": "^9.0" }, "scripts": {