From d489c3783c73c594ea42f7f42ec5202f5a02ee0a Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 26 Dec 2025 23:33:56 +0100 Subject: [PATCH 01/37] cs --- src/Application/LinkGenerator.php | 2 +- src/Application/MicroPresenter.php | 4 ++-- src/Application/Routers/RouteList.php | 2 +- src/Application/UI/AccessPolicy.php | 3 +++ src/Application/UI/Component.php | 7 ++++--- src/Application/UI/ParameterConverter.php | 4 ++++ src/Bridges/ApplicationDI/ApplicationExtension.php | 1 + src/Bridges/ApplicationLatte/SnippetRuntime.php | 2 +- src/Bridges/ApplicationLatte/TemplateFactory.php | 6 +++--- src/Bridges/ApplicationLatte/UIExtension.php | 2 +- 10 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/Application/LinkGenerator.php b/src/Application/LinkGenerator.php index 122fdfc7b..13877f6ec 100644 --- a/src/Application/LinkGenerator.php +++ b/src/Application/LinkGenerator.php @@ -273,7 +273,7 @@ public function requestToUrl(Request $request, ?bool $relative = false): string if ($relative) { $hostUrl = $this->refUrl->getHostUrl() . '/'; - if (strncmp($url, $hostUrl, strlen($hostUrl)) === 0) { + if (str_starts_with($url, $hostUrl)) { $url = substr($url, strlen($hostUrl) - 1); } } diff --git a/src/Application/MicroPresenter.php b/src/Application/MicroPresenter.php index b68eea59e..f0312f828 100644 --- a/src/Application/MicroPresenter.php +++ b/src/Application/MicroPresenter.php @@ -115,7 +115,7 @@ public function createTemplate(?string $class = null, ?callable $latteFactory = { $latte = $latteFactory ? $latteFactory() - : $this->getContext()->getByType(Nette\Bridges\ApplicationLatte\LatteFactory::class)->create(); + : $this->context->getByType(Nette\Bridges\ApplicationLatte\LatteFactory::class)->create(); $template = $class ? new $class : new Nette\Bridges\ApplicationLatte\DefaultTemplate($latte); @@ -146,7 +146,7 @@ public function redirectUrl(string $url, int $httpCode = Http\IResponse::S302_Fo * Throws HTTP error. * @throws Nette\Application\BadRequestException */ - public function error(string $message = '', int $httpCode = Http\IResponse::S404_NotFound): void + public function error(string $message = '', int $httpCode = Http\IResponse::S404_NotFound): never { throw new Application\BadRequestException($message, $httpCode); } diff --git a/src/Application/Routers/RouteList.php b/src/Application/Routers/RouteList.php index 463589ee5..74b25363a 100644 --- a/src/Application/Routers/RouteList.php +++ b/src/Application/Routers/RouteList.php @@ -51,7 +51,7 @@ protected function completeParameters(array $params): ?array public function constructUrl(array $params, Nette\Http\UrlScript $refUrl): ?string { if ($this->module) { - if (strncmp($params[self::PresenterKey], $this->module, strlen($this->module)) !== 0) { + if (!str_starts_with($params[self::PresenterKey], $this->module)) { return null; } diff --git a/src/Application/UI/AccessPolicy.php b/src/Application/UI/AccessPolicy.php index 4c4ae29b4..0d13df955 100644 --- a/src/Application/UI/AccessPolicy.php +++ b/src/Application/UI/AccessPolicy.php @@ -62,6 +62,9 @@ private function getAttributes(): array } + /** + * @param Requires[] $attrs + */ private function applyInternalRules(array $attrs, Component $component): array { if ( diff --git a/src/Application/UI/Component.php b/src/Application/UI/Component.php index e4feedfe2..e14ac4361 100644 --- a/src/Application/UI/Component.php +++ b/src/Application/UI/Component.php @@ -102,7 +102,7 @@ protected function validateParent(Nette\ComponentModel\IContainer $parent): void */ protected function tryCall(string $method, array $params): bool { - $rc = $this->getReflection(); + $rc = static::getReflection(); if (!$rc->hasMethod($method)) { return false; } elseif (!$rc->hasCallableMethod($method)) { @@ -149,7 +149,7 @@ public static function getReflection(): ComponentReflection */ public function loadState(array $params): void { - $reflection = $this->getReflection(); + $reflection = static::getReflection(); foreach ($reflection->getParameters() as $name => $meta) { if (isset($params[$name])) { // nulls are ignored if (!ParameterConverter::convertType($params[$name], $meta['type'])) { @@ -183,6 +183,7 @@ public function saveState(array &$params): void /** * @internal used by presenter + * @param array $params */ public function saveStatePartial(array &$params, ComponentReflection $reflection): void { @@ -260,7 +261,7 @@ final public function getParameterId(string $name): string */ public function signalReceived(string $signal): void { - if (!$this->tryCall($this->formatSignalMethod($signal), $this->params)) { + if (!$this->tryCall(static::formatSignalMethod($signal), $this->params)) { $class = static::class; throw new BadSignalException("There is no handler for signal '$signal' in class $class."); } diff --git a/src/Application/UI/ParameterConverter.php b/src/Application/UI/ParameterConverter.php index c41935be4..4a699c20f 100644 --- a/src/Application/UI/ParameterConverter.php +++ b/src/Application/UI/ParameterConverter.php @@ -22,6 +22,9 @@ final class ParameterConverter { use Nette\StaticClass; + /** + * @param array $args + */ public static function toArguments(\ReflectionFunctionAbstract $method, array $args): array { $res = []; @@ -60,6 +63,7 @@ public static function toArguments(\ReflectionFunctionAbstract $method, array $a /** * Converts list of arguments to named parameters & check types. + * @param array $supplemental * @param \ReflectionParameter[] $missing arguments * @throws InvalidLinkException * @internal diff --git a/src/Bridges/ApplicationDI/ApplicationExtension.php b/src/Bridges/ApplicationDI/ApplicationExtension.php index d05e39b19..476472cd0 100644 --- a/src/Bridges/ApplicationDI/ApplicationExtension.php +++ b/src/Bridges/ApplicationDI/ApplicationExtension.php @@ -25,6 +25,7 @@ */ final class ApplicationExtension extends Nette\DI\CompilerExtension { + /** @var string[] */ private readonly array $scanDirs; private int $invalidLinkMode; private array $checked = []; diff --git a/src/Bridges/ApplicationLatte/SnippetRuntime.php b/src/Bridges/ApplicationLatte/SnippetRuntime.php index 71867a384..a3429f297 100644 --- a/src/Bridges/ApplicationLatte/SnippetRuntime.php +++ b/src/Bridges/ApplicationLatte/SnippetRuntime.php @@ -65,7 +65,7 @@ public function enter(string $name, string $type): void $this->stack[] = [$name, $obStarted]; if ($name !== '') { - $this->control->redrawControl($name, false); + $this->control->redrawControl($name, redraw: false); } } diff --git a/src/Bridges/ApplicationLatte/TemplateFactory.php b/src/Bridges/ApplicationLatte/TemplateFactory.php index f60226a3c..676b31da6 100644 --- a/src/Bridges/ApplicationLatte/TemplateFactory.php +++ b/src/Bridges/ApplicationLatte/TemplateFactory.php @@ -32,7 +32,7 @@ public function __construct( private readonly ?Nette\Caching\Storage $cacheStorage = null, $templateClass = null, ) { - if ($templateClass && (!class_exists($templateClass) || !is_a($templateClass, Template::class, true))) { + if ($templateClass && (!class_exists($templateClass) || !is_a($templateClass, Template::class, allow_string: true))) { throw new Nette\InvalidArgumentException("Class $templateClass does not implement " . Template::class . ' or it does not exist.'); } @@ -139,8 +139,8 @@ private function setupLatte2( } if ($presenter) { - $latte->addFunction('isLinkCurrent', [$presenter, 'isLinkCurrent']); - $latte->addFunction('isModuleCurrent', [$presenter, 'isModuleCurrent']); + $latte->addFunction('isLinkCurrent', $presenter->isLinkCurrent(...)); + $latte->addFunction('isModuleCurrent', $presenter->isModuleCurrent(...)); } $latte->addFilter('modifyDate', fn($time, $delta, $unit = null) => $time diff --git a/src/Bridges/ApplicationLatte/UIExtension.php b/src/Bridges/ApplicationLatte/UIExtension.php index 965d83c03..2805dff96 100644 --- a/src/Bridges/ApplicationLatte/UIExtension.php +++ b/src/Bridges/ApplicationLatte/UIExtension.php @@ -94,7 +94,7 @@ public function getPasses(): array { return [ 'snippetRendering' => $this->snippetRenderingPass(...), - 'applyLinkBase' => [Nodes\LinkBaseNode::class, 'applyLinkBasePass'], + 'applyLinkBase' => Nodes\LinkBaseNode::applyLinkBasePass(...), ]; } From 183d712b681c6fbdc4ddbb0aad648b8125928a0c Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 29 Dec 2025 23:18:13 +0100 Subject: [PATCH 02/37] updated .gitattributes & .gitignore --- .gitattributes | 16 ++++++++-------- .gitignore | 1 + 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.gitattributes b/.gitattributes index 9670e954e..e1bccc4bf 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,9 +1,9 @@ -.gitattributes export-ignore -.gitignore export-ignore -.github export-ignore -ncs.* export-ignore -phpstan.neon export-ignore -tests/ export-ignore +.gitattributes export-ignore +.github/ export-ignore +.gitignore export-ignore +ncs.* export-ignore +phpstan*.neon export-ignore +tests/ export-ignore -*.sh eol=lf -*.php* diff=php linguist-language=PHP +*.php* diff=php +*.sh text eol=lf diff --git a/.gitignore b/.gitignore index de4a392c3..d49bcd46e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /vendor /composer.lock +tests/lock From 6df3bb29f36d6c5e981df5426f6127443e5b0ef0 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 26 Dec 2025 23:37:40 +0100 Subject: [PATCH 03/37] promoted readonly properties --- src/Application/Application.php | 16 ++++------------ src/Application/Responses/FileResponse.php | 14 ++++---------- src/Application/Responses/ForwardResponse.php | 9 +++------ src/Application/Responses/JsonResponse.php | 12 ++++-------- src/Application/Responses/RedirectResponse.php | 12 ++++-------- src/Application/Responses/TextResponse.php | 9 +++------ src/Application/Routers/CliRouter.php | 8 +++----- 7 files changed, 25 insertions(+), 55 deletions(-) diff --git a/src/Application/Application.php b/src/Application/Application.php index be96dd49c..22b5673a9 100644 --- a/src/Application/Application.php +++ b/src/Application/Application.php @@ -48,22 +48,14 @@ class Application /** @var Request[] */ private array $requests = []; private ?IPresenter $presenter = null; - private Nette\Http\IRequest $httpRequest; - private Nette\Http\IResponse $httpResponse; - private IPresenterFactory $presenterFactory; - private Router $router; public function __construct( - IPresenterFactory $presenterFactory, - Router $router, - Nette\Http\IRequest $httpRequest, - Nette\Http\IResponse $httpResponse, + private readonly IPresenterFactory $presenterFactory, + private readonly Router $router, + private readonly Nette\Http\IRequest $httpRequest, + private readonly Nette\Http\IResponse $httpResponse, ) { - $this->httpRequest = $httpRequest; - $this->httpResponse = $httpResponse; - $this->presenterFactory = $presenterFactory; - $this->router = $router; } diff --git a/src/Application/Responses/FileResponse.php b/src/Application/Responses/FileResponse.php index f45b74bb8..4c98f3d9d 100644 --- a/src/Application/Responses/FileResponse.php +++ b/src/Application/Responses/FileResponse.php @@ -19,26 +19,20 @@ final class FileResponse implements Nette\Application\Response { public bool $resuming = true; - private string $file; - private string $contentType; - private string $name; - private bool $forceDownload; + private readonly string $name; public function __construct( - string $file, + private readonly string $file, ?string $name = null, - ?string $contentType = null, - bool $forceDownload = true, + private readonly string $contentType = 'application/octet-stream', + private readonly bool $forceDownload = true, ) { if (!is_file($file) || !is_readable($file)) { throw new Nette\Application\BadRequestException("File '$file' doesn't exist or is not readable."); } - $this->file = $file; $this->name = $name ?? basename($file); - $this->contentType = $contentType ?? 'application/octet-stream'; - $this->forceDownload = $forceDownload; } diff --git a/src/Application/Responses/ForwardResponse.php b/src/Application/Responses/ForwardResponse.php index 9e68c16ca..23675669c 100644 --- a/src/Application/Responses/ForwardResponse.php +++ b/src/Application/Responses/ForwardResponse.php @@ -17,12 +17,9 @@ */ final class ForwardResponse implements Nette\Application\Response { - private Nette\Application\Request $request; - - - public function __construct(Nette\Application\Request $request) - { - $this->request = $request; + public function __construct( + private readonly Nette\Application\Request $request, + ) { } diff --git a/src/Application/Responses/JsonResponse.php b/src/Application/Responses/JsonResponse.php index 1feb0d6f4..998435b42 100644 --- a/src/Application/Responses/JsonResponse.php +++ b/src/Application/Responses/JsonResponse.php @@ -17,14 +17,10 @@ */ final class JsonResponse implements Nette\Application\Response { - private mixed $payload; - private string $contentType; - - - public function __construct(mixed $payload, ?string $contentType = null) - { - $this->payload = $payload; - $this->contentType = $contentType ?? 'application/json'; + public function __construct( + private readonly mixed $payload, + private readonly string $contentType = 'application/json', + ) { } diff --git a/src/Application/Responses/RedirectResponse.php b/src/Application/Responses/RedirectResponse.php index 1ab884f1a..9974b0c8c 100644 --- a/src/Application/Responses/RedirectResponse.php +++ b/src/Application/Responses/RedirectResponse.php @@ -18,14 +18,10 @@ */ final class RedirectResponse implements Nette\Application\Response { - private string $url; - private int $httpCode; - - - public function __construct(string $url, int $httpCode = Http\IResponse::S302_Found) - { - $this->url = $url; - $this->httpCode = $httpCode; + public function __construct( + private readonly string $url, + private readonly int $httpCode = Http\IResponse::S302_Found, + ) { } diff --git a/src/Application/Responses/TextResponse.php b/src/Application/Responses/TextResponse.php index e21fb5168..ac486472d 100644 --- a/src/Application/Responses/TextResponse.php +++ b/src/Application/Responses/TextResponse.php @@ -17,12 +17,9 @@ */ final class TextResponse implements Nette\Application\Response { - private mixed $source; - - - public function __construct(mixed $source) - { - $this->source = $source; + public function __construct( + private readonly mixed $source, + ) { } diff --git a/src/Application/Routers/CliRouter.php b/src/Application/Routers/CliRouter.php index 7e261fdf0..589fc898b 100644 --- a/src/Application/Routers/CliRouter.php +++ b/src/Application/Routers/CliRouter.php @@ -20,12 +20,10 @@ final class CliRouter implements Nette\Routing\Router { private const PresenterKey = 'action'; - private array $defaults; - - public function __construct(array $defaults = []) - { - $this->defaults = $defaults; + public function __construct( + private readonly array $defaults = [], + ) { } From c9cae3e299a6bbd71d0fbf4e542ec244d0230f96 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 29 Dec 2025 00:34:07 +0100 Subject: [PATCH 04/37] improved tests --- .github/workflows/tests.yml | 6 +- tests/UI/Component.signals.phpt | 174 +++++++++++++++++ tests/UI/Multiplier.phpt | 77 ++++++++ tests/UI/Presenter.canonicalize.phpt | 230 ++++++++++++++++++++++ tests/UI/Presenter.flashMessage.phpt | 257 +++++++++++++++++++++++++ tests/UI/Presenter.lifecycle.phpt | 248 ++++++++++++++++++++++++ tests/UI/Presenter.redirects.phpt | 241 +++++++++++++++++++++++ tests/UI/Presenter.storeRequest().phpt | 137 +++++++++++++ tests/UI/Requires.combinations.phpt | 255 ++++++++++++++++++++++++ 9 files changed, 1622 insertions(+), 3 deletions(-) create mode 100644 tests/UI/Component.signals.phpt create mode 100644 tests/UI/Multiplier.phpt create mode 100644 tests/UI/Presenter.canonicalize.phpt create mode 100644 tests/UI/Presenter.flashMessage.phpt create mode 100644 tests/UI/Presenter.lifecycle.phpt create mode 100644 tests/UI/Presenter.redirects.phpt create mode 100644 tests/UI/Requires.combinations.phpt diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 60067d232..9f017cec9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,7 +20,7 @@ jobs: coverage: none - run: composer install --no-progress --prefer-dist - - run: vendor/bin/tester tests -s -C + - run: composer tester - if: failure() uses: actions/upload-artifact@v4 with: @@ -39,7 +39,7 @@ jobs: coverage: none - run: composer update --no-progress --prefer-dist --prefer-lowest --prefer-stable - - run: vendor/bin/tester tests -s -C + - run: composer tester code_coverage: @@ -53,7 +53,7 @@ jobs: coverage: none - run: composer install --no-progress --prefer-dist - - run: vendor/bin/tester -p phpdbg tests -s -C --coverage ./coverage.xml --coverage-src ./src + - run: composer tester -- -p phpdbg --coverage ./coverage.xml --coverage-src ./src - run: wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.4.3/php-coveralls.phar - env: COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/tests/UI/Component.signals.phpt b/tests/UI/Component.signals.phpt new file mode 100644 index 000000000..5a96b87a3 --- /dev/null +++ b/tests/UI/Component.signals.phpt @@ -0,0 +1,174 @@ +signalCalled = true; + } +} + + +class ParentControl extends Application\UI\Control +{ + public bool $signalCalled = false; + + + public function handleUpdate(): void + { + $this->signalCalled = true; + } + + + protected function createComponentNested(): NestedControl + { + return new NestedControl; + } +} + + +test('signal routing to nested component', function () { + $presenter = new class extends Application\UI\Presenter { + protected function createComponentParent(): ParentControl + { + return new ParentControl; + } + + + public function renderDefault(): void + { + $this->terminate(); + } + }; + + $presenter->injectPrimary( + new Http\Request(new Http\UrlScript, cookies: [Http\Helpers::StrictCookieName => 1]), + new Http\Response, + new Application\PresenterFactory, + new Application\Routers\SimpleRouter, + ); + $presenter->autoCanonicalize = false; + + $presenter->run(new Application\Request('Test', 'GET', [ + 'action' => 'default', + 'do' => 'parent-nested-refresh', + ])); + + $parent = $presenter['parent']; + $nested = $parent['nested']; + + Assert::type(ParentControl::class, $parent); + Assert::type(NestedControl::class, $nested); + Assert::false($parent->signalCalled); + Assert::true($nested->signalCalled); +}); + + +class ParameterControl extends Application\UI\Control +{ + public ?int $receivedParam = null; + + + public function handleClick(int $id): void + { + $this->receivedParam = $id; + } +} + + +test('signal with parameters', function () { + $presenter = new class extends Application\UI\Presenter { + protected function createComponentParam(): ParameterControl + { + return new ParameterControl; + } + + + public function renderDefault(): void + { + $this->terminate(); + } + }; + + $presenter->injectPrimary( + new Http\Request(new Http\UrlScript, cookies: [Http\Helpers::StrictCookieName => 1]), + new Http\Response, + new Application\PresenterFactory, + new Application\Routers\SimpleRouter, + ); + $presenter->autoCanonicalize = false; + + $presenter->run(new Application\Request('Test', 'GET', [ + 'action' => 'default', + 'do' => 'param-click', + 'param-id' => '42', + ])); + + $control = $presenter['param']; + + Assert::type(ParameterControl::class, $control); + Assert::same(42, $control->receivedParam); +}); + + +testException('invalid signal name throws exception', function () { + $presenter = new class extends Application\UI\Presenter { + public function renderDefault(): void + { + $this->terminate(); + } + }; + + $presenter->injectPrimary( + new Http\Request(new Http\UrlScript, cookies: [Http\Helpers::StrictCookieName => 1]), + new Http\Response, + new Application\PresenterFactory, + new Application\Routers\SimpleRouter, + ); + $presenter->autoCanonicalize = false; + + $presenter->run(new Application\Request('Test', 'GET', [ + 'action' => 'default', + 'do' => 'nonexistent', + ])); +}, Application\UI\BadSignalException::class); + + +testException('signal to nonexistent component throws exception', function () { + $presenter = new class extends Application\UI\Presenter { + public function renderDefault(): void + { + $this->terminate(); + } + }; + + $presenter->injectPrimary( + new Http\Request(new Http\UrlScript, cookies: [Http\Helpers::StrictCookieName => 1]), + new Http\Response, + new Application\PresenterFactory, + new Application\Routers\SimpleRouter, + ); + $presenter->autoCanonicalize = false; + + $presenter->run(new Application\Request('Test', 'GET', [ + 'action' => 'default', + 'do' => 'nonexistent-click', + ])); +}, Application\UI\BadSignalException::class); diff --git a/tests/UI/Multiplier.phpt b/tests/UI/Multiplier.phpt new file mode 100644 index 000000000..6838b91d9 --- /dev/null +++ b/tests/UI/Multiplier.phpt @@ -0,0 +1,77 @@ +getComponent('item1'); + $component2 = $multiplier->getComponent('item2'); + + Assert::type(TestControl::class, $component1); + Assert::type(TestControl::class, $component2); + Assert::same('item1', $component1->id); + Assert::same('item2', $component2->id); + Assert::same(['item1', 'item2'], $calls); +}); + + +test('same name returns cached component', function () { + $calls = 0; + $multiplier = new Application\UI\Multiplier(function ($name) use (&$calls) { + $calls++; + return new TestControl($name); + }); + + $component1 = $multiplier->getComponent('item1'); + $component2 = $multiplier->getComponent('item1'); + + Assert::same($component1, $component2); + Assert::same(1, $calls); +}); + + +test('factory receives component name and parent', function () { + $receivedName = null; + $receivedParent = null; + + $multiplier = new Application\UI\Multiplier(function ($name, $parent) use (&$receivedName, &$receivedParent) { + $receivedName = $name; + $receivedParent = $parent; + return new TestControl($name); + }); + + $multiplier->getComponent('test'); + + Assert::same('test', $receivedName); + Assert::same($multiplier, $receivedParent); +}); diff --git a/tests/UI/Presenter.canonicalize.phpt b/tests/UI/Presenter.canonicalize.phpt new file mode 100644 index 000000000..306f873ba --- /dev/null +++ b/tests/UI/Presenter.canonicalize.phpt @@ -0,0 +1,230 @@ +terminate(); + } +} + + +test('autoCanonicalize redirects to remove default persistent parameter', function () { + $presenter = new CanonicalPresenter; + $presenter->injectPrimary( + new Http\Request(new Http\UrlScript('http://localhost/index.php?id=0&presenter=Canonical&action=default')), + new Http\Response, + new Application\PresenterFactory, + new Application\Routers\SimpleRouter, + ); + $presenter->autoCanonicalize = true; + + $response = $presenter->run(new Application\Request('Canonical', 'GET', [ + 'action' => 'default', + 'id' => 0, + ])); + + Assert::type(Application\Responses\RedirectResponse::class, $response); + Assert::notContains('id=0', $response->getUrl()); +}); + + +test('autoCanonicalize disabled does not redirect', function () { + $presenter = new CanonicalPresenter; + $presenter->injectPrimary( + new Http\Request(new Http\UrlScript('http://localhost/index.php?extra=param&id=5&presenter=Canonical&action=default')), + new Http\Response, + new Application\PresenterFactory, + new Application\Routers\SimpleRouter, + ); + $presenter->autoCanonicalize = false; + + $response = $presenter->run(new Application\Request('Canonical', 'GET', [ + 'action' => 'default', + 'id' => 5, + ])); + + Assert::type(Application\Responses\VoidResponse::class, $response); +}); + + +test('canonicalize() does not redirect on AJAX request', function () { + $presenter = createPresenter(CanonicalPresenter::class, headers: ['X-Requested-With' => 'XMLHttpRequest']); + $presenter->autoCanonicalize = true; + + $response = $presenter->run(new Application\Request('Canonical', 'GET', [ + 'action' => 'default', + 'id' => 5, + ])); + + Assert::type(Application\Responses\VoidResponse::class, $response); +}); + + +test('canonicalize() does not redirect on POST request', function () { + $presenter = createPresenter(CanonicalPresenter::class, post: ['data' => 'value']); + $presenter->autoCanonicalize = true; + + $response = $presenter->run(new Application\Request('Canonical', 'POST', [ + 'action' => 'default', + 'id' => 5, + ])); + + Assert::type(Application\Responses\VoidResponse::class, $response); +}); + + +class ManualCanonicalPresenter extends Application\UI\Presenter +{ + public function actionDefault(): void + { + $this->canonicalize(); + } + + + public function renderDefault(): void + { + $this->terminate(); + } +} + + +test('manual canonicalize call works', function () { + $presenter = new ManualCanonicalPresenter; + $presenter->injectPrimary( + new Http\Request(new Http\UrlScript('http://localhost/index.php?extra=param&presenter=ManualCanonical&action=default')), + new Http\Response, + new Application\PresenterFactory, + new Application\Routers\SimpleRouter, + ); + $presenter->autoCanonicalize = false; + + $response = $presenter->run(new Application\Request('ManualCanonical', 'GET', [ + 'action' => 'default', + ])); + + Assert::type(Application\Responses\RedirectResponse::class, $response); +}); + + +class VaryingPresenter extends Application\UI\Presenter +{ + public function actionDefault(): void + { + } + + + public function renderDefault(): void + { + $this->terminate(); + } +} + + +test('canonicalize() uses 301 for non-varying requests', function () { + $presenter = new VaryingPresenter; + $presenter->injectPrimary( + new Http\Request(new Http\UrlScript('http://localhost/index.php?extra=param&presenter=Varying&action=default')), + new Http\Response, + new Application\PresenterFactory, + new Application\Routers\SimpleRouter, + ); + $presenter->autoCanonicalize = true; + + $response = $presenter->run(new Application\Request('Varying', 'GET', [ + 'action' => 'default', + ])); + + Assert::type(Application\Responses\RedirectResponse::class, $response); + Assert::same(Http\IResponse::S301_MovedPermanently, $response->getCode()); +}); + + +test('canonicalize() uses 302 for varying requests', function () { + $presenter = new VaryingPresenter; + $presenter->injectPrimary( + new Http\Request(new Http\UrlScript('http://localhost/index.php?extra=param&presenter=Varying&action=default')), + new Http\Response, + new Application\PresenterFactory, + new Application\Routers\SimpleRouter, + ); + $presenter->autoCanonicalize = true; + + $request = new Application\Request('Varying', 'GET', ['action' => 'default']); + $request->setFlag($request::VARYING, true); + + $response = $presenter->run($request); + + Assert::type(Application\Responses\RedirectResponse::class, $response); + Assert::same(Http\IResponse::S302_Found, $response->getCode()); +}); + + +class DestinationCanonicalPresenter extends Application\UI\Presenter +{ + public function actionDefault(): void + { + $this->canonicalize('other'); + } + + + public function actionOther(): void + { + } + + + public function renderDefault(): void + { + $this->terminate(); + } + + + public function renderOther(): void + { + $this->terminate(); + } +} + + +test('canonicalize() with different destination redirects', function () { + $presenter = new DestinationCanonicalPresenter; + $presenter->injectPrimary( + new Http\Request(new Http\UrlScript('http://localhost/index.php?presenter=DestinationCanonical&action=default')), + new Http\Response, + new Application\PresenterFactory, + new Application\Routers\SimpleRouter, + ); + $presenter->autoCanonicalize = false; + + $response = $presenter->run(new Application\Request('DestinationCanonical', 'GET', [ + 'action' => 'default', + ])); + + Assert::type(Application\Responses\RedirectResponse::class, $response); + Assert::contains('action=other', $response->getUrl()); +}); diff --git a/tests/UI/Presenter.flashMessage.phpt b/tests/UI/Presenter.flashMessage.phpt new file mode 100644 index 000000000..9f5bee7da --- /dev/null +++ b/tests/UI/Presenter.flashMessage.phpt @@ -0,0 +1,257 @@ +terminate(); + } +} + + +class FlashPresenter extends Application\UI\Presenter +{ + public function actionDefault() + { + $this->flashMessage('Test message'); + } + + + public function renderDefault() + { + $this->terminate(); + } +} + + +test('flash message is stored in session', function () { + $sessionSection = Mockery::mock(Http\SessionSection::class); + $sessionSection->shouldReceive('get')->andReturn([]); + $sessionSection->shouldReceive('set')->once(); + + $session = Mockery::mock(Http\Session::class); + $session->shouldReceive('getSection')->andReturn($sessionSection); + $session->shouldReceive('hasSection')->andReturn(true); + + $latte = Mockery::mock(Latte\Engine::class); + $latte->shouldIgnoreMissing(); + $templateFactory = Mockery::mock(Application\UI\TemplateFactory::class); + $templateFactory->shouldReceive('createTemplate')->andReturn(new Nette\Bridges\ApplicationLatte\DefaultTemplate($latte)); + + $presenter = new TestPresenter; + $presenter->injectPrimary( + new Http\Request(new Http\UrlScript), + new Http\Response, + session: $session, + templateFactory: $templateFactory, + ); + $presenter->autoCanonicalize = false; + + $presenter->run(new Application\Request('Test', 'GET', ['action' => 'default'])); + + $flash = $presenter->flashMessage('Test message'); + Assert::type(stdClass::class, $flash); + Assert::same('Test message', $flash->message); + Assert::same('info', $flash->type); +}); + + +test('flash message with custom type', function () { + $sessionSection = Mockery::mock(Http\SessionSection::class); + $sessionSection->shouldReceive('get')->andReturn([]); + $sessionSection->shouldReceive('set')->once(); + + $session = Mockery::mock(Http\Session::class); + $session->shouldReceive('getSection')->andReturn($sessionSection); + $session->shouldReceive('hasSection')->andReturn(true); + + $latte = Mockery::mock(Latte\Engine::class); + $latte->shouldIgnoreMissing(); + $templateFactory = Mockery::mock(Application\UI\TemplateFactory::class); + $templateFactory->shouldReceive('createTemplate')->andReturn(new Nette\Bridges\ApplicationLatte\DefaultTemplate($latte)); + + $presenter = new TestPresenter; + $presenter->injectPrimary( + new Http\Request(new Http\UrlScript), + new Http\Response, + session: $session, + templateFactory: $templateFactory, + ); + $presenter->autoCanonicalize = false; + + $presenter->run(new Application\Request('Test', 'GET', ['action' => 'default'])); + + $flash = $presenter->flashMessage('Error occurred', 'error'); + Assert::same('Error occurred', $flash->message); + Assert::same('error', $flash->type); +}); + + +test('multiple flash messages are stored', function () { + $messages = []; + $sessionSection = Mockery::mock(Http\SessionSection::class); + $sessionSection->shouldReceive('get')->andReturnUsing(function () use (&$messages) { + return $messages; + }); + $sessionSection->shouldReceive('set')->andReturnUsing(function ($id, $value) use (&$messages) { + $messages = $value; + }); + + $session = Mockery::mock(Http\Session::class); + $session->shouldReceive('getSection')->andReturn($sessionSection); + $session->shouldReceive('hasSection')->andReturn(true); + + $latte = Mockery::mock(Latte\Engine::class); + $latte->shouldIgnoreMissing(); + $templateFactory = Mockery::mock(Application\UI\TemplateFactory::class); + $templateFactory->shouldReceive('createTemplate')->andReturn(new Nette\Bridges\ApplicationLatte\DefaultTemplate($latte)); + + $presenter = new TestPresenter; + $presenter->injectPrimary( + new Http\Request(new Http\UrlScript), + new Http\Response, + session: $session, + templateFactory: $templateFactory, + ); + $presenter->autoCanonicalize = false; + + $presenter->run(new Application\Request('Test', 'GET', ['action' => 'default'])); + + $presenter->flashMessage('First message', 'info'); + $presenter->flashMessage('Second message', 'error'); + $presenter->flashMessage('Third message', 'success'); + + Assert::count(3, $messages); + Assert::same('First message', $messages[0]->message); + Assert::same('info', $messages[0]->type); + Assert::same('Second message', $messages[1]->message); + Assert::same('error', $messages[1]->type); + Assert::same('Third message', $messages[2]->message); + Assert::same('success', $messages[2]->type); +}); + + +test('flash message is available in template', function () { + $messages = []; + $sessionSection = Mockery::mock(Http\SessionSection::class); + $sessionSection->shouldReceive('get')->andReturnUsing(function () use (&$messages) { + return $messages; + }); + $sessionSection->shouldReceive('set')->andReturnUsing(function ($id, $value) use (&$messages) { + $messages = $value; + }); + + $session = Mockery::mock(Http\Session::class); + $session->shouldReceive('getSection')->andReturn($sessionSection); + $session->shouldReceive('hasSection')->andReturn(true); + + $latte = Mockery::mock(Latte\Engine::class); + $latte->shouldIgnoreMissing(); + $templateFactory = Mockery::mock(Application\UI\TemplateFactory::class); + $templateFactory->shouldReceive('createTemplate')->andReturn(new Nette\Bridges\ApplicationLatte\DefaultTemplate($latte)); + + $presenter = new TestPresenter; + $presenter->injectPrimary( + new Http\Request(new Http\UrlScript), + new Http\Response, + session: $session, + templateFactory: $templateFactory, + ); + $presenter->autoCanonicalize = false; + + $presenter->run(new Application\Request('Test', 'GET', ['action' => 'default'])); + + $presenter->flashMessage('Template message'); + + $template = $presenter->getTemplate(); + Assert::count(1, $template->flashes); + Assert::same('Template message', $template->flashes[0]->message); +}); + + +test('flash session expires after presenter run', function () { + $expirationSet = false; + $sessionSection = Mockery::mock(Http\SessionSection::class); + $sessionSection->shouldReceive('get')->andReturn([]); + $sessionSection->shouldReceive('set')->once(); + $sessionSection->shouldReceive('setExpiration')->once()->with('30 seconds')->andReturnUsing(function () use (&$expirationSet, $sessionSection) { + $expirationSet = true; + return $sessionSection; + }); + + $session = Mockery::mock(Http\Session::class); + $session->shouldReceive('getSection')->andReturn($sessionSection); + $session->shouldReceive('hasSection')->andReturn(true); + + $latte = Mockery::mock(Latte\Engine::class); + $latte->shouldIgnoreMissing(); + $templateFactory = Mockery::mock(Application\UI\TemplateFactory::class); + $templateFactory->shouldReceive('createTemplate')->andReturn(new Nette\Bridges\ApplicationLatte\DefaultTemplate($latte)); + + $presenter = new FlashPresenter; + $presenter->injectPrimary( + new Http\Request(new Http\UrlScript), + new Http\Response, + session: $session, + templateFactory: $templateFactory, + ); + $presenter->autoCanonicalize = false; + + $presenter->run(new Application\Request('Flash', 'GET', ['action' => 'default'])); + + Assert::true($expirationSet); +}); + + +test('flash message accepts stdClass object', function () { + $sessionSection = Mockery::mock(Http\SessionSection::class); + $sessionSection->shouldReceive('get')->andReturn([]); + $sessionSection->shouldReceive('set')->once(); + + $session = Mockery::mock(Http\Session::class); + $session->shouldReceive('getSection')->andReturn($sessionSection); + $session->shouldReceive('hasSection')->andReturn(true); + + $latte = Mockery::mock(Latte\Engine::class); + $latte->shouldIgnoreMissing(); + $templateFactory = Mockery::mock(Application\UI\TemplateFactory::class); + $templateFactory->shouldReceive('createTemplate')->andReturn(new Nette\Bridges\ApplicationLatte\DefaultTemplate($latte)); + + $presenter = new TestPresenter; + $presenter->injectPrimary( + new Http\Request(new Http\UrlScript), + new Http\Response, + session: $session, + templateFactory: $templateFactory, + ); + $presenter->autoCanonicalize = false; + + $presenter->run(new Application\Request('Test', 'GET', ['action' => 'default'])); + + $customFlash = (object) [ + 'message' => 'Custom message', + 'type' => 'warning', + 'custom' => 'data', + ]; + $flash = $presenter->flashMessage($customFlash); + + Assert::same($customFlash, $flash); + Assert::same('Custom message', $flash->message); + Assert::same('warning', $flash->type); + Assert::same('data', $flash->custom); +}); diff --git a/tests/UI/Presenter.lifecycle.phpt b/tests/UI/Presenter.lifecycle.phpt new file mode 100644 index 000000000..c3cc21ab4 --- /dev/null +++ b/tests/UI/Presenter.lifecycle.phpt @@ -0,0 +1,248 @@ +called[] = 'startup'; + } + + + public function actionDefault(): void + { + $this->called[] = 'actionDefault'; + } + + + protected function beforeRender(): void + { + parent::beforeRender(); + $this->called[] = 'beforeRender'; + } + + + public function renderDefault(): void + { + $this->called[] = 'renderDefault'; + $this->terminate(); + } + + + protected function afterRender(): void + { + parent::afterRender(); + $this->called[] = 'afterRender'; + } + + + protected function shutdown(Application\Response $response): void + { + parent::shutdown($response); + $this->called[] = 'shutdown'; + } +} + + +test('lifecycle methods are called in correct order', function () { + $latte = Mockery::mock(Latte\Engine::class); + $latte->shouldIgnoreMissing(); + $templateFactory = Mockery::mock(Application\UI\TemplateFactory::class); + $templateFactory->shouldReceive('createTemplate')->andReturn(new Nette\Bridges\ApplicationLatte\DefaultTemplate($latte)); + + $presenter = new LifecyclePresenter; + $presenter->injectPrimary( + new Http\Request(new Http\UrlScript), + new Http\Response, + templateFactory: $templateFactory, + ); + $presenter->autoCanonicalize = false; + + $presenter->run(new Application\Request('Lifecycle', 'GET', ['action' => 'default'])); + + Assert::same( + ['startup', 'actionDefault', 'beforeRender', 'renderDefault', 'shutdown'], + $presenter->called, + ); +}); + + +test('onStartup event is fired before startup', function () { + $eventCalled = false; + + $latte = Mockery::mock(Latte\Engine::class); + $latte->shouldIgnoreMissing(); + $templateFactory = Mockery::mock(Application\UI\TemplateFactory::class); + $templateFactory->shouldReceive('createTemplate')->andReturn(new Nette\Bridges\ApplicationLatte\DefaultTemplate($latte)); + + $presenter = new LifecyclePresenter; + $presenter->onStartup[] = function () use (&$eventCalled, $presenter) { + $eventCalled = true; + Assert::same([], $presenter->called); + }; + + $presenter->injectPrimary( + new Http\Request(new Http\UrlScript), + new Http\Response, + templateFactory: $templateFactory, + ); + $presenter->autoCanonicalize = false; + + $presenter->run(new Application\Request('Lifecycle', 'GET', ['action' => 'default'])); + + Assert::true($eventCalled); +}); + + +test('onRender event is fired after beforeRender', function () { + $eventCalled = false; + + $latte = Mockery::mock(Latte\Engine::class); + $latte->shouldIgnoreMissing(); + $templateFactory = Mockery::mock(Application\UI\TemplateFactory::class); + $templateFactory->shouldReceive('createTemplate')->andReturn(new Nette\Bridges\ApplicationLatte\DefaultTemplate($latte)); + + $presenter = new LifecyclePresenter; + $presenter->onRender[] = function () use (&$eventCalled, $presenter) { + $eventCalled = true; + Assert::same(['startup', 'actionDefault', 'beforeRender'], $presenter->called); + }; + + $presenter->injectPrimary( + new Http\Request(new Http\UrlScript), + new Http\Response, + templateFactory: $templateFactory, + ); + $presenter->autoCanonicalize = false; + + $presenter->run(new Application\Request('Lifecycle', 'GET', ['action' => 'default'])); + + Assert::true($eventCalled); +}); + + +test('onShutdown event is fired before shutdown', function () { + $eventCalled = false; + + $latte = Mockery::mock(Latte\Engine::class); + $latte->shouldIgnoreMissing(); + $templateFactory = Mockery::mock(Application\UI\TemplateFactory::class); + $templateFactory->shouldReceive('createTemplate')->andReturn(new Nette\Bridges\ApplicationLatte\DefaultTemplate($latte)); + + $presenter = new LifecyclePresenter; + $presenter->onShutdown[] = function () use (&$eventCalled, $presenter) { + $eventCalled = true; + Assert::same(['startup', 'actionDefault', 'beforeRender', 'renderDefault'], $presenter->called); + }; + + $presenter->injectPrimary( + new Http\Request(new Http\UrlScript), + new Http\Response, + templateFactory: $templateFactory, + ); + $presenter->autoCanonicalize = false; + + $presenter->run(new Application\Request('Lifecycle', 'GET', ['action' => 'default'])); + + Assert::true($eventCalled); +}); + + +class SignalPresenter extends Application\UI\Presenter +{ + public array $called = []; + + + protected function startup(): void + { + parent::startup(); + $this->called[] = 'startup'; + } + + + public function handleRefresh(): void + { + $this->called[] = 'handleRefresh'; + } + + + public function actionDefault(): void + { + $this->called[] = 'actionDefault'; + } + + + public function renderDefault(): void + { + $this->called[] = 'renderDefault'; + $this->terminate(); + } +} + + +test('signal is called after action', function () { + $presenter = createPresenter(SignalPresenter::class, cookies: [Http\Helpers::StrictCookieName => 1]); + + $presenter->run(new Application\Request('Signal', 'GET', [ + 'action' => 'default', + 'do' => 'refresh', + ])); + + Assert::same(['startup', 'actionDefault', 'handleRefresh', 'renderDefault'], $presenter->called); +}); + + +class RedirectPresenter extends Application\UI\Presenter +{ + public array $called = []; + + + public function actionDefault(): void + { + $this->called[] = 'actionDefault'; + $this->redirect('other'); + } + + + public function actionOther(): void + { + $this->called[] = 'actionOther'; + } + + + public function renderOther(): void + { + $this->called[] = 'renderOther'; + $this->terminate(); + } +} + + +test('redirect terminates current lifecycle', function () { + $presenter = createPresenter(RedirectPresenter::class); + + $response = $presenter->run(new Application\Request('Redirect', 'GET', ['action' => 'default'])); + + Assert::type(Application\Responses\RedirectResponse::class, $response); + Assert::same(['actionDefault'], $presenter->called); + Assert::notContains('renderDefault', $presenter->called); + Assert::notContains('renderOther', $presenter->called); +}); diff --git a/tests/UI/Presenter.redirects.phpt b/tests/UI/Presenter.redirects.phpt new file mode 100644 index 000000000..dd6eaef32 --- /dev/null +++ b/tests/UI/Presenter.redirects.phpt @@ -0,0 +1,241 @@ +redirect('other', ['page' => 2]); + } + + + public function actionOther(): void + { + $this->terminate(); + } +} + + +test('redirect() creates RedirectResponse with 302 code', function () { + $presenter = createPresenter(RedirectPresenter::class); + + $response = $presenter->run(new Application\Request('Redirect', 'GET', ['action' => 'default'])); + + Assert::type(Application\Responses\RedirectResponse::class, $response); + Assert::same(Http\IResponse::S302_Found, $response->getCode()); +}); + + +class PermanentRedirectPresenter extends Application\UI\Presenter +{ + public function actionDefault(): void + { + $this->redirectPermanent('other'); + } + + + public function actionOther(): void + { + $this->terminate(); + } +} + + +test('redirectPermanent() creates RedirectResponse with 301 code', function () { + $presenter = createPresenter(PermanentRedirectPresenter::class); + + $response = $presenter->run(new Application\Request('PermanentRedirect', 'GET', ['action' => 'default'])); + + Assert::type(Application\Responses\RedirectResponse::class, $response); + Assert::same(Http\IResponse::S301_MovedPermanently, $response->getCode()); +}); + + +class ForwardPresenter extends Application\UI\Presenter +{ + public function actionDefault(): void + { + $this->forward('other'); + } + + + public function actionOther(): void + { + $this->terminate(); + } +} + + +test('forward() creates ForwardResponse', function () { + $presenter = createPresenter(ForwardPresenter::class); + + $response = $presenter->run(new Application\Request('Forward', 'GET', ['action' => 'default'])); + + Assert::type(Application\Responses\ForwardResponse::class, $response); +}); + + +class PersistentParamsPresenter extends Application\UI\Presenter +{ + #[Persistent] + public int $page = 1; + + #[Persistent] + public string $lang = 'en'; + + + public function actionDefault(): void + { + $this->redirect('other'); + } + + + public function actionOther(): void + { + $this->terminate(); + } +} + + +test('redirect() preserves persistent parameters', function () { + $presenter = new PersistentParamsPresenter; + $presenter->injectPrimary( + new Http\Request(new Http\UrlScript('http://localhost')), + new Http\Response, + new Application\PresenterFactory, + new Application\Routers\SimpleRouter, + ); + $presenter->autoCanonicalize = false; + + $response = $presenter->run(new Application\Request('PersistentParams', 'GET', [ + 'action' => 'default', + 'page' => 5, + 'lang' => 'cs', + ])); + + Assert::type(Application\Responses\RedirectResponse::class, $response); + $url = $response->getUrl(); + Assert::contains('page=5', $url); + Assert::contains('lang=cs', $url); +}); + + +class OverrideParamsPresenter extends Application\UI\Presenter +{ + #[Persistent] + public int $page = 1; + + #[Persistent] + public string $lang = 'en'; + + + public function actionDefault(): void + { + $this->redirect('other', ['page' => 10, 'lang' => 'de']); + } + + + public function actionOther(): void + { + $this->terminate(); + } +} + + +test('redirect() can override persistent parameters', function () { + $presenter = new OverrideParamsPresenter; + $presenter->injectPrimary( + new Http\Request(new Http\UrlScript('http://localhost')), + new Http\Response, + new Application\PresenterFactory, + new Application\Routers\SimpleRouter, + ); + $presenter->autoCanonicalize = false; + + $response = $presenter->run(new Application\Request('OverrideParams', 'GET', [ + 'action' => 'default', + 'page' => 5, + 'lang' => 'cs', + ])); + + Assert::type(Application\Responses\RedirectResponse::class, $response); + $url = $response->getUrl(); + Assert::contains('page=10', $url); + Assert::contains('lang=de', $url); +}); + + +class UrlRedirectPresenter extends Application\UI\Presenter +{ + public function actionDefault(): void + { + $this->redirectUrl('https://example.com/path'); + } +} + + +test('redirectUrl() creates RedirectResponse to external URL', function () { + $presenter = new UrlRedirectPresenter; + $presenter->injectPrimary( + new Http\Request(new Http\UrlScript('http://localhost')), + new Http\Response, + ); + $presenter->autoCanonicalize = false; + + $response = $presenter->run(new Application\Request('UrlRedirect', 'GET', ['action' => 'default'])); + + Assert::type(Application\Responses\RedirectResponse::class, $response); + Assert::same('https://example.com/path', $response->getUrl()); +}); + + +class PostRedirectPresenter extends Application\UI\Presenter +{ + public function actionDefault(): void + { + if ($this->getRequest()->getMethod() === 'POST') { + $this->redirect('success'); + } + } + + + public function actionSuccess(): void + { + $this->terminate(); + } + + + public function renderDefault(): void + { + $this->terminate(); + } +} + + +test('POST request redirect uses 302 code', function () { + $presenter = createPresenter(PostRedirectPresenter::class, post: ['data' => 'value']); + + $response = $presenter->run(new Application\Request('PostRedirect', 'POST', ['action' => 'default'])); + + Assert::type(Application\Responses\RedirectResponse::class, $response); + Assert::same(Http\IResponse::S302_Found, $response->getCode()); +}); diff --git a/tests/UI/Presenter.storeRequest().phpt b/tests/UI/Presenter.storeRequest().phpt index 50ca0cd76..79b6ff802 100644 --- a/tests/UI/Presenter.storeRequest().phpt +++ b/tests/UI/Presenter.storeRequest().phpt @@ -128,3 +128,140 @@ test('request storage without user context', function () { Assert::same($key, $storedKey); Assert::same([null, $applicationRequest], $storedValue); }); + + +test('restoreRequest() restores stored request', function () { + $storedData = [ + 'test_id', + new Application\Request('Test', 'POST', ['action' => 'edit', 'id' => 5], ['name' => 'John']), + ]; + + $sessionSection = Mockery::mock(Http\SessionSection::class); + $sessionSection->shouldReceive('get')->with('abc123')->andReturn($storedData); + $sessionSection->shouldReceive('remove')->with('abc123')->once(); + + $session = Mockery::mock(Http\Session::class); + $session->shouldReceive('getSection')->andReturn($sessionSection); + + $user = Mockery::mock(Nette\Security\User::class); + $user->shouldReceive('getId')->andReturn('test_id'); + + $presenter = new TestPresenter; + $presenter->injectPrimary( + new Http\Request(new Http\UrlScript), + new Http\Response, + new Application\PresenterFactory, + new Application\Routers\SimpleRouter, + $session, + $user, + ); + + $presenter->autoCanonicalize = false; + $presenter->run(new Application\Request('Test', 'GET', ['action' => 'default'])); + + Assert::exception( + fn() => $presenter->restoreRequest('abc123'), + Application\AbortException::class, + ); + + // Verified that get/remove were called (via Mockery expectations) +}); + + +test('restoreRequest() ignores request with wrong user', function () { + $storedData = [ + 'different_user', + new Application\Request('Test', 'POST', ['action' => 'edit']), + ]; + + $sessionSection = Mockery::mock(Http\SessionSection::class); + $sessionSection->shouldReceive('get')->with('abc123')->andReturn($storedData); + $sessionSection->shouldReceive('remove')->never(); + + $session = Mockery::mock(Http\Session::class); + $session->shouldReceive('getSection')->andReturn($sessionSection); + + $user = Mockery::mock(Nette\Security\User::class); + $user->shouldReceive('getId')->andReturn('test_id'); + + $presenter = new TestPresenter; + $presenter->injectPrimary( + new Http\Request(new Http\UrlScript), + new Http\Response, + null, + new Application\Routers\SimpleRouter, + $session, + $user, + ); + + $presenter->autoCanonicalize = false; + $presenter->run(new Application\Request('Test', 'GET', ['action' => 'default'])); + + $presenter->restoreRequest('abc123'); + + // Request should not be restored due to user mismatch +}); + + +test('restoreRequest() handles missing request', function () { + $sessionSection = Mockery::mock(Http\SessionSection::class); + $sessionSection->shouldReceive('get')->with('nonexistent')->andReturn(null); + $sessionSection->shouldReceive('remove')->never(); + + $session = Mockery::mock(Http\Session::class); + $session->shouldReceive('getSection')->andReturn($sessionSection); + + $presenter = new TestPresenter; + $presenter->injectPrimary( + new Http\Request(new Http\UrlScript), + new Http\Response, + null, + new Application\Routers\SimpleRouter, + $session, + ); + + $presenter->autoCanonicalize = false; + $presenter->run(new Application\Request('Test', 'GET', ['action' => 'default'])); + + $presenter->restoreRequest('nonexistent'); + + // Should handle gracefully without error +}); + + +test('restoreRequest() accepts request without user when stored without user', function () { + $storedData = [ + null, + new Application\Request('Test', 'POST', ['action' => 'edit']), + ]; + + $sessionSection = Mockery::mock(Http\SessionSection::class); + $sessionSection->shouldReceive('get')->with('abc123')->andReturn($storedData); + $sessionSection->shouldReceive('remove')->with('abc123')->once(); + + $session = Mockery::mock(Http\Session::class); + $session->shouldReceive('getSection')->andReturn($sessionSection); + + $user = Mockery::mock(Nette\Security\User::class); + $user->shouldReceive('getId')->andReturn('test_id'); + + $presenter = new TestPresenter; + $presenter->injectPrimary( + new Http\Request(new Http\UrlScript), + new Http\Response, + new Application\PresenterFactory, + new Application\Routers\SimpleRouter, + $session, + $user, + ); + + $presenter->autoCanonicalize = false; + $presenter->run(new Application\Request('Test', 'GET', ['action' => 'default'])); + + Assert::exception( + fn() => $presenter->restoreRequest('abc123'), + Application\AbortException::class, + ); + + // Request stored without user (null) should be restorable by any user +}); diff --git a/tests/UI/Requires.combinations.phpt b/tests/UI/Requires.combinations.phpt new file mode 100644 index 000000000..2bb4bba18 --- /dev/null +++ b/tests/UI/Requires.combinations.phpt @@ -0,0 +1,255 @@ +terminate(); + } +} + + +test('POST + AJAX requirement both satisfied', function () { + $presenter = createPresenter( + PostAjaxPresenter::class, + method: Http\Request::Post, + headers: ['X-Requested-With' => 'XMLHttpRequest'], + ); + + Assert::noError( + fn() => $presenter->run(new Application\Request('Test', Http\Request::Post)), + ); +}); + + +test('POST requirement satisfied but AJAX missing', function () { + $presenter = createPresenter( + PostAjaxPresenter::class, + method: Http\Request::Post, + ); + + Assert::exception( + fn() => $presenter->run(new Application\Request('Test', Http\Request::Post)), + Application\BadRequestException::class, + 'AJAX request is required by PostAjaxPresenter', + ); +}); + + +test('AJAX requirement satisfied but wrong HTTP method', function () { + $presenter = createPresenter( + PostAjaxPresenter::class, + headers: ['X-Requested-With' => 'XMLHttpRequest'], + ); + + Assert::exception( + fn() => $presenter->run(new Application\Request('Test', Http\Request::Get)), + Application\BadRequestException::class, + 'Method GET is not allowed by PostAjaxPresenter', + ); +}); + + +#[Requires(sameOrigin: true, methods: 'POST')] +class CsrfPostPresenter extends Application\UI\Presenter +{ + public function actionDefault(): never + { + $this->terminate(); + } +} + + +test('sameOrigin + POST both satisfied', function () { + $presenter = createPresenter( + CsrfPostPresenter::class, + method: Http\Request::Post, + cookies: [Http\Helpers::StrictCookieName => 1], + ); + + Assert::noError( + fn() => $presenter->run(new Application\Request('Test', Http\Request::Post)), + ); +}); + + +test('sameOrigin satisfied but wrong method', function () { + $presenter = createPresenter( + CsrfPostPresenter::class, + cookies: [Http\Helpers::StrictCookieName => 1], + ); + + Assert::exception( + fn() => $presenter->run(new Application\Request('Test', Http\Request::Get)), + Application\BadRequestException::class, + 'Method GET is not allowed by CsrfPostPresenter', + ); +}); + + +test('POST satisfied but sameOrigin violated', function () { + $presenter = createPresenter( + CsrfPostPresenter::class, + method: Http\Request::Post, + ); + + // Without the strict cookie, request is considered cross-origin and redirected + $response = $presenter->run(new Application\Request('Test', Http\Request::Post)); + Assert::type(Application\Responses\RedirectResponse::class, $response); +}); + + +class MultiRequiresPresenter extends Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + #[Requires(ajax: true)] + public function actionDefault(): never + { + $this->terminate(); + } +} + + +test('multiple Requires attributes on same action', function () { + $presenter = createPresenter( + MultiRequiresPresenter::class, + method: Http\Request::Post, + headers: ['X-Requested-With' => 'XMLHttpRequest'], + ); + + Assert::noError( + fn() => $presenter->run(new Application\Request('Test', Http\Request::Post, ['action' => 'default'])), + ); +}); + + +test('multiple Requires - first satisfied, second violated', function () { + $presenter = createPresenter( + MultiRequiresPresenter::class, + method: Http\Request::Post, + ); + + Assert::exception( + fn() => $presenter->run(new Application\Request('Test', Http\Request::Post, ['action' => 'default'])), + Application\BadRequestException::class, + 'AJAX request is required by MultiRequiresPresenter::actionDefault()', + ); +}); + + +test('multiple Requires - second satisfied, first violated', function () { + $presenter = createPresenter( + MultiRequiresPresenter::class, + headers: ['X-Requested-With' => 'XMLHttpRequest'], + ); + + Assert::exception( + fn() => $presenter->run(new Application\Request('Test', Http\Request::Get, ['action' => 'default'])), + Application\BadRequestException::class, + 'Method GET is not allowed by MultiRequiresPresenter::actionDefault()', + ); +}); + + +#[Requires(ajax: true)] +class AjaxPresenterWithAction extends Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionEdit(): never + { + $this->terminate(); + } + + + public function actionDefault(): never + { + $this->terminate(); + } +} + + +test('class-level + method-level requirements combine', function () { + $presenter = createPresenter( + AjaxPresenterWithAction::class, + method: Http\Request::Post, + headers: ['X-Requested-With' => 'XMLHttpRequest'], + ); + + Assert::noError( + fn() => $presenter->run(new Application\Request('Test', Http\Request::Post, ['action' => 'edit'])), + ); +}); + + +test('class-level requirement satisfied, method-level violated', function () { + $presenter = createPresenter( + AjaxPresenterWithAction::class, + headers: ['X-Requested-With' => 'XMLHttpRequest'], + ); + + Assert::exception( + fn() => $presenter->run(new Application\Request('Test', Http\Request::Get, ['action' => 'edit'])), + Application\BadRequestException::class, + 'Method GET is not allowed by AjaxPresenterWithAction::actionEdit()', + ); +}); + + +test('method-level requirement satisfied, class-level violated', function () { + $presenter = createPresenter( + AjaxPresenterWithAction::class, + method: Http\Request::Post, + ); + + Assert::exception( + fn() => $presenter->run(new Application\Request('Test', Http\Request::Post, ['action' => 'edit'])), + Application\BadRequestException::class, + 'AJAX request is required by AjaxPresenterWithAction', + ); +}); + + +#[Requires(forward: true)] +class ForwardPresenter extends Application\UI\Presenter +{ + public function actionDefault(): never + { + $this->terminate(); + } +} + + +test('forward requirement satisfied', function () { + $presenter = createPresenter(ForwardPresenter::class); + + Assert::noError( + fn() => $presenter->run(new Application\Request('Test', Application\Request::FORWARD)), + ); +}); + + +test('forward requirement violated', function () { + $presenter = createPresenter(ForwardPresenter::class); + + Assert::exception( + fn() => $presenter->run(new Application\Request('Test', Http\Request::Get)), + Application\BadRequestException::class, + 'Forwarded request is required by ForwardPresenter', + ); +}); From c1f8c022c1089115440741890d0d9d46d3aabd01 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 8 Jan 2026 23:39:11 +0100 Subject: [PATCH 05/37] improved phpDoc --- src/Application/Helpers.php | 2 +- src/Application/Routers/CliRouter.php | 2 ++ src/Application/Routers/Route.php | 6 +++++- src/Application/Routers/RouteList.php | 3 +++ src/Application/UI/AccessPolicy.php | 3 ++- src/Application/UI/Component.php | 6 ++++++ src/Application/UI/ComponentReflection.php | 3 +++ src/Application/UI/Link.php | 2 ++ src/Application/UI/ParameterConverter.php | 1 + src/Application/UI/Presenter.php | 6 ++++++ src/Bridges/ApplicationDI/ApplicationExtension.php | 2 ++ src/Bridges/ApplicationLatte/Template.php | 3 +++ src/Bridges/ApplicationTracy/RoutingPanel.php | 4 ++++ 13 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/Application/Helpers.php b/src/Application/Helpers.php index 51b266cea..ca6a4e869 100644 --- a/src/Application/Helpers.php +++ b/src/Application/Helpers.php @@ -34,7 +34,7 @@ public static function splitName(string $name): array /** - * return string[] + * @return array */ public static function getClassesAndTraits(string $class): array { diff --git a/src/Application/Routers/CliRouter.php b/src/Application/Routers/CliRouter.php index 589fc898b..c02cfb8e3 100644 --- a/src/Application/Routers/CliRouter.php +++ b/src/Application/Routers/CliRouter.php @@ -22,6 +22,7 @@ final class CliRouter implements Nette\Routing\Router public function __construct( + /** @var array */ private readonly array $defaults = [], ) { } @@ -99,6 +100,7 @@ public function constructUrl(array $params, Nette\Http\UrlScript $refUrl): ?stri /** * Returns default values. + * @return array */ public function getDefaults(): array { diff --git a/src/Application/Routers/Route.php b/src/Application/Routers/Route.php index fbc85f3bf..2ea5a8086 100644 --- a/src/Application/Routers/Route.php +++ b/src/Application/Routers/Route.php @@ -98,6 +98,7 @@ public function match(Nette\Http\IRequest $httpRequest): ?array /** * Constructs absolute URL from array. + * @param array $params */ public function constructUrl(array $params, Nette\Http\UrlScript $refUrl): ?string { @@ -121,7 +122,10 @@ public function constructUrl(array $params, Nette\Http\UrlScript $refUrl): ?stri } - /** @internal */ + /** + * @return array + * @internal + */ public function getConstantParameters(): array { $res = parent::getConstantParameters(); diff --git a/src/Application/Routers/RouteList.php b/src/Application/Routers/RouteList.php index 74b25363a..b7c17fd18 100644 --- a/src/Application/Routers/RouteList.php +++ b/src/Application/Routers/RouteList.php @@ -33,6 +33,8 @@ public function __construct(?string $module = null) /** * Support for modules. + * @param array $params + * @return array|null */ protected function completeParameters(array $params): ?array { @@ -47,6 +49,7 @@ protected function completeParameters(array $params): ?array /** * Constructs absolute URL from array. + * @param array $params */ public function constructUrl(array $params, Nette\Http\UrlScript $refUrl): ?string { diff --git a/src/Application/UI/AccessPolicy.php b/src/Application/UI/AccessPolicy.php index 0d13df955..b478d3ed6 100644 --- a/src/Application/UI/AccessPolicy.php +++ b/src/Application/UI/AccessPolicy.php @@ -63,7 +63,8 @@ private function getAttributes(): array /** - * @param Requires[] $attrs + * @param Attributes\Requires[] $attrs + * @return Attributes\Requires[] */ private function applyInternalRules(array $attrs, Component $component): array { diff --git a/src/Application/UI/Component.php b/src/Application/UI/Component.php index e14ac4361..a649bb1ad 100644 --- a/src/Application/UI/Component.php +++ b/src/Application/UI/Component.php @@ -29,6 +29,8 @@ abstract class Component extends Nette\ComponentModel\Container implements Signa /** @var array Occurs when component is attached to presenter */ public array $onAnchor = []; + + /** @var array */ protected array $params = []; @@ -99,6 +101,7 @@ protected function validateParent(Nette\ComponentModel\IContainer $parent): void /** * Calls public method if exists. + * @param array $params */ protected function tryCall(string $method, array $params): bool { @@ -146,6 +149,7 @@ public static function getReflection(): ComponentReflection /** * Loads state information. + * @param array $params */ public function loadState(array $params): void { @@ -174,6 +178,7 @@ public function loadState(array $params): void /** * Saves state information for next request. + * @param array $params */ public function saveState(array &$params): void { @@ -235,6 +240,7 @@ final public function getParameter(string $name): mixed /** * Returns component parameters. + * @return array */ final public function getParameters(): array { diff --git a/src/Application/UI/ComponentReflection.php b/src/Application/UI/ComponentReflection.php index e8454ff4e..2d58ae9ee 100644 --- a/src/Application/UI/ComponentReflection.php +++ b/src/Application/UI/ComponentReflection.php @@ -115,6 +115,9 @@ public function getPersistentComponents(): array } + /** + * @return string[] names of public properties with #[TemplateVariable] attribute + */ public function getTemplateVariables(Control $control): array { $res = []; diff --git a/src/Application/UI/Link.php b/src/Application/UI/Link.php index 68eea2d1c..e974b9745 100644 --- a/src/Application/UI/Link.php +++ b/src/Application/UI/Link.php @@ -19,6 +19,7 @@ final class Link public function __construct( private readonly Component $component, private readonly string $destination, + /** @var array */ private array $params = [], ) { } @@ -63,6 +64,7 @@ public function getParameter(string $key): mixed /** * Returns link parameters. + * @return array */ public function getParameters(): array { diff --git a/src/Application/UI/ParameterConverter.php b/src/Application/UI/ParameterConverter.php index 4a699c20f..b453848c0 100644 --- a/src/Application/UI/ParameterConverter.php +++ b/src/Application/UI/ParameterConverter.php @@ -24,6 +24,7 @@ final class ParameterConverter /** * @param array $args + * @return mixed[] */ public static function toArguments(\ReflectionFunctionAbstract $method, array $args): array { diff --git a/src/Application/UI/Presenter.php b/src/Application/UI/Presenter.php index 7c4af760a..bcd78c375 100644 --- a/src/Application/UI/Presenter.php +++ b/src/Application/UI/Presenter.php @@ -96,7 +96,11 @@ abstract class Presenter extends Control implements Application\IPresenter public array $allowedMethods = ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH']; private ?Nette\Application\Request $request = null; private ?Nette\Application\Response $response = null; + + /** @var array> */ private array $globalParams = []; + + /** @var array */ private array $globalState; private ?array $globalStateSinces; private string $action = ''; @@ -912,6 +916,7 @@ public static function getPersistentComponents(): array /** * Saves state information for all subcomponents to $this->globalState. + * @return array */ public function getGlobalState(?string $forClass = null): array { @@ -1061,6 +1066,7 @@ private function initGlobalParameters(): void /** * Pops parameters for specified component. + * @return array * @internal */ final public function popGlobalParameters(string $id): array diff --git a/src/Bridges/ApplicationDI/ApplicationExtension.php b/src/Bridges/ApplicationDI/ApplicationExtension.php index 476472cd0..cd1660279 100644 --- a/src/Bridges/ApplicationDI/ApplicationExtension.php +++ b/src/Bridges/ApplicationDI/ApplicationExtension.php @@ -28,6 +28,8 @@ final class ApplicationExtension extends Nette\DI\CompilerExtension /** @var string[] */ private readonly array $scanDirs; private int $invalidLinkMode; + + /** @var array */ private array $checked = []; diff --git a/src/Bridges/ApplicationLatte/Template.php b/src/Bridges/ApplicationLatte/Template.php index cf7cef940..8b2a3024b 100644 --- a/src/Bridges/ApplicationLatte/Template.php +++ b/src/Bridges/ApplicationLatte/Template.php @@ -36,6 +36,7 @@ final public function getLatte(): Latte\Engine /** * Renders template to output. + * @param array $params */ public function render(?string $file = null, array $params = []): void { @@ -46,6 +47,7 @@ public function render(?string $file = null, array $params = []): void /** * Renders template to output. + * @param array $params */ public function renderToString(?string $file = null, array $params = []): string { @@ -126,6 +128,7 @@ final public function getFile(): ?string /** * Returns array of all parameters. + * @return array */ final public function getParameters(): array { diff --git a/src/Bridges/ApplicationTracy/RoutingPanel.php b/src/Bridges/ApplicationTracy/RoutingPanel.php index bbfd16777..869ed2a03 100644 --- a/src/Bridges/ApplicationTracy/RoutingPanel.php +++ b/src/Bridges/ApplicationTracy/RoutingPanel.php @@ -20,7 +20,10 @@ */ final class RoutingPanel implements Tracy\IBarPanel { + /** @var array{path: string, domain: ?string, module: string, routes: array} */ private array $routes; + + /** @var array|null */ private ?array $matched = null; @@ -66,6 +69,7 @@ public function getPanel(): string } + /** @return array{path: string, domain: ?string, module: string, routes: array} */ private function analyse(Routing\RouteList $router, ?Nette\Http\IRequest $httpRequest): array { $res = [ From cb089771c29ef494f2b0a08cacc0a44d7679a7fb Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 24 Nov 2025 01:02:05 +0100 Subject: [PATCH 06/37] opened 3.3-dev --- composer.json | 2 +- readme.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 68fb11e72..7bdcd026c 100644 --- a/composer.json +++ b/composer.json @@ -58,7 +58,7 @@ }, "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "3.3-dev" } } } diff --git a/readme.md b/readme.md index d8e52ef86..41ddf658c 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,7 @@ Nette Application MVC ===================== [![Downloads this Month](https://img.shields.io/packagist/dm/nette/application.svg)](https://packagist.org/packages/nette/application) -[![Tests](https://github.com/nette/application/actions/workflows/tests.yml/badge.svg?branch=v3.2)](https://github.com/nette/application/actions) +[![Tests](https://github.com/nette/application/actions/workflows/tests.yml/badge.svg?branch=v3.3)](https://github.com/nette/application/actions) [![Latest Stable Version](https://poser.pugx.org/nette/application/v/stable)](https://github.com/nette/application/releases) [![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/application/blob/master/license.md) From 2b0603ae216a328fba5af8ca4e659b40e607f2bd Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 24 Nov 2025 01:03:38 +0100 Subject: [PATCH 07/37] requires PHP 8.2 --- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 4 ++-- composer.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 396244b28..be22f1473 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: 8.2 coverage: none - run: composer install --no-progress --prefer-dist diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9f017cec9..b82345087 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['8.1', '8.2', '8.3', '8.4', '8.5'] + php: ['8.2', '8.3', '8.4', '8.5'] fail-fast: false @@ -35,7 +35,7 @@ jobs: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: - php-version: 8.1 + php-version: 8.2 coverage: none - run: composer update --no-progress --prefer-dist --prefer-lowest --prefer-stable diff --git a/composer.json b/composer.json index 7bdcd026c..28c3c61d7 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": "8.1 - 8.5", + "php": "8.2 - 8.5", "nette/component-model": "^3.1", "nette/http": "^3.3.2", "nette/routing": "^3.1.1", From 40ba57ec92108608e01d3c9041f905a8a2ac3b30 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 29 Nov 2025 12:53:11 +0100 Subject: [PATCH 08/37] composer: increased dependencies versions --- composer.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 28c3c61d7..8f7468c08 100644 --- a/composer.json +++ b/composer.json @@ -16,10 +16,10 @@ ], "require": { "php": "8.2 - 8.5", - "nette/component-model": "^3.1", + "nette/component-model": "^3.2", "nette/http": "^3.3.2", "nette/routing": "^3.1.1", - "nette/utils": "^4.0" + "nette/utils": "^4.1" }, "suggest": { "nette/forms": "Allows to use Nette\\Application\\UI\\Form", @@ -32,7 +32,7 @@ "nette/robot-loader": "^4.0", "nette/security": "^3.2", "latte/latte": "^2.10.2 || ^3.0.18", - "tracy/tracy": "^2.9", + "tracy/tracy": "^2.11", "mockery/mockery": "^1.6@stable", "phpstan/phpstan-nette": "^2.0@stable", "jetbrains/phpstorm-attributes": "^1.2" @@ -43,7 +43,7 @@ "nette/forms": "<3.2", "nette/schema": "<1.3", "latte/latte": "<2.7.1 || >=3.0.0 <3.0.18 || >=3.2", - "tracy/tracy": "<2.9" + "tracy/tracy": "<2.11" }, "autoload": { "classmap": ["src/"], From 84755e9a9a07068ee26ad0f200329d345bb48bc8 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 24 Sep 2021 14:06:27 +0200 Subject: [PATCH 09/37] deprecated some magic properties (BC break) --- src/Application/Request.php | 10 +++++----- src/Application/UI/Component.php | 4 ++-- src/Application/UI/Presenter.php | 10 +++++----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Application/Request.php b/src/Application/Request.php index 72f46f181..3640bfb81 100644 --- a/src/Application/Request.php +++ b/src/Application/Request.php @@ -16,11 +16,11 @@ /** * Presenter request. * - * @property string $presenterName - * @property array $parameters - * @property array $post - * @property array $files - * @property string|null $method + * @property-deprecated string $presenterName + * @property-deprecated array $parameters + * @property-deprecated array $post + * @property-deprecated array $files + * @property-deprecated string|null $method */ final class Request { diff --git a/src/Application/UI/Component.php b/src/Application/UI/Component.php index a649bb1ad..f3f08f318 100644 --- a/src/Application/UI/Component.php +++ b/src/Application/UI/Component.php @@ -20,8 +20,8 @@ * other child components, and interact with user. Components have properties * for storing their status, and responds to user command. * - * @property-read Presenter $presenter - * @property-read bool $linkCurrent + * @property-deprecated Presenter $presenter + * @property-deprecated bool $linkCurrent */ abstract class Component extends Nette\ComponentModel\Container implements SignalReceiver, StatePersistent, \ArrayAccess { diff --git a/src/Application/UI/Presenter.php b/src/Application/UI/Presenter.php index bcd78c375..0757172de 100644 --- a/src/Application/UI/Presenter.php +++ b/src/Application/UI/Presenter.php @@ -23,12 +23,12 @@ /** * Presenter component represents a webpage instance. It converts Request to Response. * - * @property-read Nette\Application\Request $request - * @property-read string $action - * @property string $view - * @property string|bool $layout + * @property-deprecated Nette\Application\Request $request + * @property-deprecated string $action + * @property-deprecated string $view + * @property-deprecated string|bool $layout * @property-read \stdClass $payload - * @property-read Nette\Http\Session $session + * @property-deprecated Nette\Http\Session $session * @property-read Nette\Security\User $user */ abstract class Presenter extends Control implements Application\IPresenter From e5218fc888d0c59513d131c9532ac7be40c817e5 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 23 Nov 2024 15:54:47 +0100 Subject: [PATCH 10/37] used annotation #Deprecated --- src/Application/UI/ComponentReflection.php | 2 +- src/Application/UI/Control.php | 5 +---- src/Application/UI/Form.php | 2 +- src/Application/UI/MethodReflection.php | 8 ++------ src/Application/UI/Presenter.php | 9 ++++++--- 5 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/Application/UI/ComponentReflection.php b/src/Application/UI/ComponentReflection.php index 2d58ae9ee..2ac96e47a 100644 --- a/src/Application/UI/ComponentReflection.php +++ b/src/Application/UI/ComponentReflection.php @@ -231,7 +231,7 @@ public function getMethods($filter = -1): array } - /** @deprecated */ + #[\Deprecated] public static function combineArgs(\ReflectionFunctionAbstract $method, array $args): array { return ParameterConverter::toArguments($method, $args); diff --git a/src/Application/UI/Control.php b/src/Application/UI/Control.php index 24484dc3d..7e06433d7 100644 --- a/src/Application/UI/Control.php +++ b/src/Application/UI/Control.php @@ -79,10 +79,7 @@ protected function checkTemplateClass(string $class): ?string } - /** - * Descendant can override this method to customize template compile-time filters. - * @deprecated - */ + #[\Deprecated] public function templatePrepareFilters(Template $template): void { } diff --git a/src/Application/UI/Form.php b/src/Application/UI/Form.php index a5be276de..0d096fbd9 100644 --- a/src/Application/UI/Form.php +++ b/src/Application/UI/Form.php @@ -98,7 +98,7 @@ public function isAnchored(): bool } - /** @deprecated use allowCrossOrigin() */ + #[\Deprecated('use allowCrossOrigin()')] public function disableSameSiteProtection(): void { $this->allowCrossOrigin(); diff --git a/src/Application/UI/MethodReflection.php b/src/Application/UI/MethodReflection.php index f576deeaf..9ca18cf8b 100644 --- a/src/Application/UI/MethodReflection.php +++ b/src/Application/UI/MethodReflection.php @@ -15,18 +15,14 @@ */ final class MethodReflection extends \ReflectionMethod { - /** - * Has method specified annotation? - */ + #[\Deprecated] public function hasAnnotation(string $name): bool { return (bool) ComponentReflection::parseAnnotation($this, $name); } - /** - * Returns an annotation value. - */ + #[\Deprecated] public function getAnnotation(string $name): mixed { $res = ComponentReflection::parseAnnotation($this, $name); diff --git a/src/Application/UI/Presenter.php b/src/Application/UI/Presenter.php index 0757172de..d104f0c9b 100644 --- a/src/Application/UI/Presenter.php +++ b/src/Application/UI/Presenter.php @@ -818,23 +818,26 @@ public function lastModified( } - /** @deprecated @internal */ + #[\Deprecated] protected function createRequest(Component $component, string $destination, array $args, string $mode): ?string { + trigger_error(__METHOD__ . '() is deprecated', E_USER_DEPRECATED); return $this->linkGenerator->link($destination, $args, $component, $mode); } - /** @deprecated @internal */ + #[\Deprecated] public static function parseDestination(string $destination): array { + trigger_error(__METHOD__ . '() is deprecated', E_USER_DEPRECATED); return LinkGenerator::parseDestination($destination); } - /** @deprecated @internal */ + #[\Deprecated] protected function requestToUrl(Application\Request $request, ?bool $relative = null): string { + trigger_error(__METHOD__ . '() is deprecated', E_USER_DEPRECATED); return $this->linkGenerator->requestToUrl($request, $relative ?? !$this->absoluteUrls); } From 723c85555615c46dfe9568db3f1db30b8fe47bfc Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 15 Oct 2021 18:14:52 +0200 Subject: [PATCH 11/37] removed compatibility for old class names - inteface Nette\Application\IRouter replaced by Nette\Routing\Router --- src/Application/Routers/Route.php | 5 +--- src/Application/Routers/RouteList.php | 5 +--- src/Application/Routers/SimpleRouter.php | 3 --- src/Application/UI/Component.php | 5 +--- src/Application/UI/ComponentReflection.php | 5 +--- src/compatibility-intf.php | 11 --------- src/compatibility.php | 28 ---------------------- 7 files changed, 4 insertions(+), 58 deletions(-) delete mode 100644 src/compatibility.php diff --git a/src/Application/Routers/Route.php b/src/Application/Routers/Route.php index 2ea5a8086..530f965b0 100644 --- a/src/Application/Routers/Route.php +++ b/src/Application/Routers/Route.php @@ -10,7 +10,7 @@ namespace Nette\Application\Routers; use Nette; -use function interface_exists, is_string, lcfirst, preg_replace, rawurlencode, str_replace, strlen, strncmp, strrpos, strtolower, strtr, substr, ucwords; +use function is_string, lcfirst, preg_replace, rawurlencode, str_replace, strlen, strncmp, strrpos, strtolower, strtr, substr, ucwords; /** @@ -192,6 +192,3 @@ public static function path2presenter(string $s): string return $s; } } - - -interface_exists(Nette\Application\IRouter::class); diff --git a/src/Application/Routers/RouteList.php b/src/Application/Routers/RouteList.php index b7c17fd18..7481ec812 100644 --- a/src/Application/Routers/RouteList.php +++ b/src/Application/Routers/RouteList.php @@ -11,7 +11,7 @@ use JetBrains\PhpStorm\Language; use Nette; -use function count, interface_exists, is_int, is_string, strlen, strncmp, substr; +use function count, is_int, is_string, strlen, strncmp, substr; /** @@ -143,6 +143,3 @@ public function offsetUnset($index): void $this->modify($index, null); } } - - -interface_exists(Nette\Application\IRouter::class); diff --git a/src/Application/Routers/SimpleRouter.php b/src/Application/Routers/SimpleRouter.php index ac77ea453..c8e8c49b3 100644 --- a/src/Application/Routers/SimpleRouter.php +++ b/src/Application/Routers/SimpleRouter.php @@ -39,6 +39,3 @@ public function __construct(array|string $defaults = []) parent::__construct($defaults); } } - - -interface_exists(Nette\Application\IRouter::class); diff --git a/src/Application/UI/Component.php b/src/Application/UI/Component.php index f3f08f318..0b78419dd 100644 --- a/src/Application/UI/Component.php +++ b/src/Application/UI/Component.php @@ -10,7 +10,7 @@ namespace Nette\Application\UI; use Nette; -use function array_key_exists, array_slice, class_exists, func_get_arg, func_get_args, func_num_args, get_debug_type, is_array, link, method_exists, sprintf, trigger_error; +use function array_key_exists, array_slice, func_get_arg, func_get_args, func_num_args, get_debug_type, is_array, link, method_exists, sprintf, trigger_error; /** @@ -386,6 +386,3 @@ public function error(string $message = '', int $httpCode = Nette\Http\IResponse throw new Nette\Application\BadRequestException($message, $httpCode); } } - - -class_exists(PresenterComponent::class); diff --git a/src/Application/UI/ComponentReflection.php b/src/Application/UI/ComponentReflection.php index 2ac96e47a..4fd9b5374 100644 --- a/src/Application/UI/ComponentReflection.php +++ b/src/Application/UI/ComponentReflection.php @@ -11,7 +11,7 @@ use Nette\Application\Attributes; use Nette\Utils\Reflection; -use function array_fill_keys, array_filter, array_key_exists, array_merge, class_exists, end, preg_match_all, preg_quote, preg_split, strtolower; +use function array_fill_keys, array_filter, array_key_exists, array_merge, end, preg_match_all, preg_quote, preg_split, strtolower; use const PREG_SPLIT_NO_EMPTY; @@ -237,6 +237,3 @@ public static function combineArgs(\ReflectionFunctionAbstract $method, array $a return ParameterConverter::toArguments($method, $args); } } - - -class_exists(PresenterComponentReflection::class); diff --git a/src/compatibility-intf.php b/src/compatibility-intf.php index 5bee25bb1..0d5b2f8ff 100644 --- a/src/compatibility-intf.php +++ b/src/compatibility-intf.php @@ -9,17 +9,6 @@ namespace Nette\Application; -use Nette; - -if (false) { - /** @deprecated use Nette\Routing\Router */ - interface IRouter extends Nette\Routing\Router - { - } -} elseif (!interface_exists(IRouter::class)) { - class_alias(Nette\Routing\Router::class, IRouter::class); -} - if (false) { /** @deprecated use Nette\Application\Response */ interface IResponse extends Response diff --git a/src/compatibility.php b/src/compatibility.php deleted file mode 100644 index cf55aa4bc..000000000 --- a/src/compatibility.php +++ /dev/null @@ -1,28 +0,0 @@ - Date: Tue, 23 Dec 2025 23:28:10 +0100 Subject: [PATCH 12/37] removed deprecated stuff --- src/Application/UI/Component.php | 9 ++------- src/Application/UI/Form.php | 9 ++------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/Application/UI/Component.php b/src/Application/UI/Component.php index 0b78419dd..24220d45f 100644 --- a/src/Application/UI/Component.php +++ b/src/Application/UI/Component.php @@ -37,14 +37,9 @@ abstract class Component extends Nette\ComponentModel\Container implements Signa /** * Returns the presenter where this component belongs to. */ - public function getPresenter(): ?Presenter + public function getPresenter(): Presenter { - if (func_num_args()) { - trigger_error(__METHOD__ . '() parameter $throw is deprecated, use getPresenterIfExists()', E_USER_DEPRECATED); - $throw = func_get_arg(0); - } - - return $this->lookup(Presenter::class, throw: $throw ?? true); + return $this->lookup(Presenter::class); } diff --git a/src/Application/UI/Form.php b/src/Application/UI/Form.php index 0d096fbd9..f9d85049f 100644 --- a/src/Application/UI/Form.php +++ b/src/Application/UI/Form.php @@ -62,14 +62,9 @@ protected function validateParent(Nette\ComponentModel\IContainer $parent): void /** * Returns the presenter where this component belongs to. */ - final public function getPresenter(): ?Presenter + final public function getPresenter(): Presenter { - if (func_num_args()) { - trigger_error(__METHOD__ . '() parameter $throw is deprecated, use getPresenterIfExists()', E_USER_DEPRECATED); - $throw = func_get_arg(0); - } - - return $this->lookup(Presenter::class, throw: $throw ?? true); + return $this->lookup(Presenter::class); } From 9e86c8e3ba5cb2b9545ec4a4ef0416ae798d160b Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 19 Dec 2025 12:21:21 +0100 Subject: [PATCH 13/37] removed support for Latte 2 --- composer.json | 4 +- src/Bridges/ApplicationDI/LatteExtension.php | 71 ++----- .../ApplicationLatte/SnippetBridge.php | 95 --------- src/Bridges/ApplicationLatte/Template.php | 12 +- .../ApplicationLatte/TemplateFactory.php | 59 +----- src/Bridges/ApplicationLatte/UIMacros.php | 187 ------------------ src/Bridges/ApplicationLatte/UIRuntime.php | 84 -------- tests/Bridges.DI/LatteExtension.2.phpt | 122 ------------ tests/Bridges.DI/LatteExtension.basic.phpt | 4 - .../Bridges.DI/LatteExtension.translator.phpt | 4 - .../ControlMock.php | 0 .../Template.getParentName().phpt | 4 - .../TemplateFactory.customTemplate.phpt | 4 - .../TemplateFactory.onCreate.phpt | 4 - .../TemplateFactory.templateVariables.84.phpt | 0 .../TemplateFactory.templateVariables.phpt | 0 .../UIExtension.filters.absoluteUrl.phpt | 4 - .../UIExtension.filters.phpt | 4 - .../UIExtension.nonce.control.phpt | 4 - .../UIExtension.nonce.presenter.phpt | 4 - .../expected/ifCurrent.php | 0 .../expected/isLinkCurrent.php | 0 .../expected/n-snippet.block.php | 0 .../expected/n-snippet.dynamic.php | 0 .../expected/n-snippet.php | 0 .../expected/renderSnippets.html | 0 .../expected/snippet.dynamic.php | 0 .../expected/snippet.dynamic2.php | 0 .../expected/snippet.php | 0 .../isLinkCurrent().phpt | 4 - .../n-snippet.2.phpt | 4 - .../n-snippet.block.phpt | 4 - .../n-snippet.dynamic.phpt | 4 - .../n-snippet.phpt | 3 - .../renderSnippets.phpt | 4 - .../renderSnippets2.phpt | 4 - .../renderSnippets3.phpt | 4 - .../renderSnippets4.phpt | 4 - .../renderSnippets5.phpt | 4 - .../renderSnippets6.phpt | 4 - .../renderSnippets7.phpt | 4 - .../templates/include3.latte | 0 .../templates/snippet-include.latte | 0 .../templates/snippet-included.latte | 0 .../{control}.2.phpt | 4 - .../{control}.3.phpt | 3 - .../{control}.phpt | 4 - .../{ifCurrent}.phpt | 4 - .../{linkBase}.phpt | 4 - .../{link}.2.phpt | 4 - .../{link}.phpt | 4 - .../{snippet}.dynamic.phpt | 4 - .../{snippet}.phpt | 4 - .../Template.getParentName().phpt | 58 ------ .../TemplateFactory.customTemplate.phpt | 75 ------- .../TemplateFactory.filters.phpt | 47 ----- .../TemplateFactory.nonce.control.phpt | 39 ---- .../TemplateFactory.nonce.presenter.phpt | 42 ---- .../TemplateFactory.onCompile.phpt | 80 -------- .../TemplateFactory.onCreate.phpt | 36 ---- tests/Bridges.Latte2/UIMacros.control.2.phpt | 86 -------- tests/Bridges.Latte2/UIMacros.control.3.phpt | 51 ----- tests/Bridges.Latte2/UIMacros.control.phpt | 88 --------- .../UIMacros.isLinkCurrent.phpt | 54 ----- tests/Bridges.Latte2/UIMacros.link.2.phpt | 122 ------------ tests/Bridges.Latte2/UIMacros.link.phpt | 68 ------- .../UIMacros.renderSnippets.phpt | 97 --------- .../UIMacros.renderSnippets2.phpt | 84 -------- .../UIMacros.renderSnippets3.phpt | 73 ------- .../UIMacros.renderSnippets4.phpt | 54 ----- .../UIMacros.renderSnippets5.phpt | 59 ------ .../UIMacros.renderSnippets6.phpt | 45 ----- .../expected/UIMacros.isLinkCurrent.php | 22 --- .../expected/UIMacros.renderSnippets.html | 20 -- tests/Bridges.Latte3/templates/include3.latte | 1 - .../templates/snippet-include.latte | 25 --- .../templates/snippet-included.latte | 1 - 77 files changed, 21 insertions(+), 2058 deletions(-) delete mode 100644 src/Bridges/ApplicationLatte/SnippetBridge.php delete mode 100644 src/Bridges/ApplicationLatte/UIMacros.php delete mode 100644 src/Bridges/ApplicationLatte/UIRuntime.php delete mode 100644 tests/Bridges.DI/LatteExtension.2.phpt rename tests/{Bridges.Latte3 => Bridges.Latte}/ControlMock.php (100%) rename tests/{Bridges.Latte3 => Bridges.Latte}/Template.getParentName().phpt (95%) rename tests/{Bridges.Latte3 => Bridges.Latte}/TemplateFactory.customTemplate.phpt (94%) rename tests/{Bridges.Latte3 => Bridges.Latte}/TemplateFactory.onCreate.phpt (89%) rename tests/{Bridges.Latte3 => Bridges.Latte}/TemplateFactory.templateVariables.84.phpt (100%) rename tests/{Bridges.Latte3 => Bridges.Latte}/TemplateFactory.templateVariables.phpt (100%) rename tests/{Bridges.Latte3 => Bridges.Latte}/UIExtension.filters.absoluteUrl.phpt (94%) rename tests/{Bridges.Latte3 => Bridges.Latte}/UIExtension.filters.phpt (88%) rename tests/{Bridges.Latte3 => Bridges.Latte}/UIExtension.nonce.control.phpt (87%) rename tests/{Bridges.Latte3 => Bridges.Latte}/UIExtension.nonce.presenter.phpt (89%) rename tests/{Bridges.Latte3 => Bridges.Latte}/expected/ifCurrent.php (100%) rename tests/{Bridges.Latte3 => Bridges.Latte}/expected/isLinkCurrent.php (100%) rename tests/{Bridges.Latte3 => Bridges.Latte}/expected/n-snippet.block.php (100%) rename tests/{Bridges.Latte3 => Bridges.Latte}/expected/n-snippet.dynamic.php (100%) rename tests/{Bridges.Latte3 => Bridges.Latte}/expected/n-snippet.php (100%) rename tests/{Bridges.Latte3 => Bridges.Latte}/expected/renderSnippets.html (100%) rename tests/{Bridges.Latte3 => Bridges.Latte}/expected/snippet.dynamic.php (100%) rename tests/{Bridges.Latte3 => Bridges.Latte}/expected/snippet.dynamic2.php (100%) rename tests/{Bridges.Latte3 => Bridges.Latte}/expected/snippet.php (100%) rename tests/{Bridges.Latte3 => Bridges.Latte}/isLinkCurrent().phpt (91%) rename tests/{Bridges.Latte3 => Bridges.Latte}/n-snippet.2.phpt (93%) rename tests/{Bridges.Latte3 => Bridges.Latte}/n-snippet.block.phpt (83%) rename tests/{Bridges.Latte3 => Bridges.Latte}/n-snippet.dynamic.phpt (82%) rename tests/{Bridges.Latte3 => Bridges.Latte}/n-snippet.phpt (85%) rename tests/{Bridges.Latte3 => Bridges.Latte}/renderSnippets.phpt (95%) rename tests/{Bridges.Latte3 => Bridges.Latte}/renderSnippets2.phpt (93%) rename tests/{Bridges.Latte3 => Bridges.Latte}/renderSnippets3.phpt (93%) rename tests/{Bridges.Latte3 => Bridges.Latte}/renderSnippets4.phpt (91%) rename tests/{Bridges.Latte3 => Bridges.Latte}/renderSnippets5.phpt (93%) rename tests/{Bridges.Latte3 => Bridges.Latte}/renderSnippets6.phpt (91%) rename tests/{Bridges.Latte3 => Bridges.Latte}/renderSnippets7.phpt (98%) rename tests/{Bridges.Latte2 => Bridges.Latte}/templates/include3.latte (100%) rename tests/{Bridges.Latte2 => Bridges.Latte}/templates/snippet-include.latte (100%) rename tests/{Bridges.Latte2 => Bridges.Latte}/templates/snippet-included.latte (100%) rename tests/{Bridges.Latte3 => Bridges.Latte}/{control}.2.phpt (94%) rename tests/{Bridges.Latte3 => Bridges.Latte}/{control}.3.phpt (90%) rename tests/{Bridges.Latte3 => Bridges.Latte}/{control}.phpt (94%) rename tests/{Bridges.Latte3 => Bridges.Latte}/{ifCurrent}.phpt (88%) rename tests/{Bridges.Latte3 => Bridges.Latte}/{linkBase}.phpt (90%) rename tests/{Bridges.Latte3 => Bridges.Latte}/{link}.2.phpt (95%) rename tests/{Bridges.Latte3 => Bridges.Latte}/{link}.phpt (92%) rename tests/{Bridges.Latte3 => Bridges.Latte}/{snippet}.dynamic.phpt (87%) rename tests/{Bridges.Latte3 => Bridges.Latte}/{snippet}.phpt (85%) delete mode 100644 tests/Bridges.Latte2/Template.getParentName().phpt delete mode 100644 tests/Bridges.Latte2/TemplateFactory.customTemplate.phpt delete mode 100644 tests/Bridges.Latte2/TemplateFactory.filters.phpt delete mode 100644 tests/Bridges.Latte2/TemplateFactory.nonce.control.phpt delete mode 100644 tests/Bridges.Latte2/TemplateFactory.nonce.presenter.phpt delete mode 100644 tests/Bridges.Latte2/TemplateFactory.onCompile.phpt delete mode 100644 tests/Bridges.Latte2/TemplateFactory.onCreate.phpt delete mode 100644 tests/Bridges.Latte2/UIMacros.control.2.phpt delete mode 100644 tests/Bridges.Latte2/UIMacros.control.3.phpt delete mode 100644 tests/Bridges.Latte2/UIMacros.control.phpt delete mode 100644 tests/Bridges.Latte2/UIMacros.isLinkCurrent.phpt delete mode 100644 tests/Bridges.Latte2/UIMacros.link.2.phpt delete mode 100644 tests/Bridges.Latte2/UIMacros.link.phpt delete mode 100644 tests/Bridges.Latte2/UIMacros.renderSnippets.phpt delete mode 100644 tests/Bridges.Latte2/UIMacros.renderSnippets2.phpt delete mode 100644 tests/Bridges.Latte2/UIMacros.renderSnippets3.phpt delete mode 100644 tests/Bridges.Latte2/UIMacros.renderSnippets4.phpt delete mode 100644 tests/Bridges.Latte2/UIMacros.renderSnippets5.phpt delete mode 100644 tests/Bridges.Latte2/UIMacros.renderSnippets6.phpt delete mode 100644 tests/Bridges.Latte2/expected/UIMacros.isLinkCurrent.php delete mode 100644 tests/Bridges.Latte2/expected/UIMacros.renderSnippets.html delete mode 100644 tests/Bridges.Latte3/templates/include3.latte delete mode 100644 tests/Bridges.Latte3/templates/snippet-include.latte delete mode 100644 tests/Bridges.Latte3/templates/snippet-included.latte diff --git a/composer.json b/composer.json index 8f7468c08..bc3eeddc6 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "nette/forms": "^3.2", "nette/robot-loader": "^4.0", "nette/security": "^3.2", - "latte/latte": "^2.10.2 || ^3.0.18", + "latte/latte": "^3.0.18", "tracy/tracy": "^2.11", "mockery/mockery": "^1.6@stable", "phpstan/phpstan-nette": "^2.0@stable", @@ -42,7 +42,7 @@ "nette/di": "<3.2", "nette/forms": "<3.2", "nette/schema": "<1.3", - "latte/latte": "<2.7.1 || >=3.0.0 <3.0.18 || >=3.2", + "latte/latte": "<3.0.18 || >=3.2", "tracy/tracy": "<2.11" }, "autoload": { diff --git a/src/Bridges/ApplicationDI/LatteExtension.php b/src/Bridges/ApplicationDI/LatteExtension.php index efc3d22ca..c2661d306 100644 --- a/src/Bridges/ApplicationDI/LatteExtension.php +++ b/src/Bridges/ApplicationDI/LatteExtension.php @@ -15,7 +15,7 @@ use Nette\DI\Definitions\Statement; use Nette\Schema\Expect; use Tracy; -use function class_exists, explode, is_string, str_contains, version_compare; +use function class_exists, is_string; /** @@ -34,7 +34,6 @@ public function getConfigSchema(): Nette\Schema\Schema { return Expect::structure([ 'debugger' => Expect::anyOf(true, false, 'all'), - 'macros' => Expect::arrayOf('string'), 'extensions' => Expect::arrayOf('string|Nette\DI\Definitions\Statement'), 'templateClass' => Expect::string(), 'strictTypes' => Expect::bool(false), @@ -54,40 +53,30 @@ public function loadConfiguration(): void $config = $this->config; $builder = $this->getContainerBuilder(); - $latteFactory = $builder->addFactoryDefinition($this->prefix('latteFactory')) + $builder->addFactoryDefinition($this->prefix('latteFactory')) ->setImplement(ApplicationLatte\LatteFactory::class) ->getResultDefinition() ->setFactory(Latte\Engine::class) ->addSetup('setTempDirectory', [$this->tempDir]) ->addSetup('setAutoRefresh', [$this->debugMode]) - ->addSetup('setStrictTypes', [$config->strictTypes]); - - if (version_compare(Latte\Engine::VERSION, '3', '<')) { - foreach ($config->macros as $macro) { - $this->addMacro($macro); - } - } else { - $latteFactory->addSetup('setStrictParsing', [$config->strictParsing]) + ->addSetup('setStrictTypes', [$config->strictTypes]) + ->addSetup('setStrictParsing', [$config->strictParsing]) ->addSetup('enablePhpLinter', [$config->phpLinter]) - ->addSetup('setLocale', [$config->locale]); - - $builder->getDefinition($this->prefix('latteFactory')) - ->getResultDefinition() + ->addSetup('setLocale', [$config->locale]) ->addSetup('?', [$builder::literal('func_num_args() && $service->addExtension(new Nette\Bridges\ApplicationLatte\UIExtension(func_get_arg(0)))')]); - if ($builder->getByType(Nette\Caching\Storage::class)) { - $this->addExtension(new Statement(Nette\Bridges\CacheLatte\CacheExtension::class)); - } - if (class_exists(Nette\Bridges\FormsLatte\FormsExtension::class)) { - $this->addExtension(new Statement(Nette\Bridges\FormsLatte\FormsExtension::class)); - } + if ($builder->getByType(Nette\Caching\Storage::class)) { + $this->addExtension(new Statement(Nette\Bridges\CacheLatte\CacheExtension::class)); + } + if (class_exists(Nette\Bridges\FormsLatte\FormsExtension::class)) { + $this->addExtension(new Statement(Nette\Bridges\FormsLatte\FormsExtension::class)); + } - foreach ($config->extensions as $extension) { - if ($extension === Latte\Essential\TranslatorExtension::class) { - $extension = new Statement($extension, [new Nette\DI\Definitions\Reference(Nette\Localization\Translator::class)]); - } - $this->addExtension($extension); + foreach ($config->extensions as $extension) { + if ($extension === Latte\Essential\TranslatorExtension::class) { + $extension = new Statement($extension, [new Nette\DI\Definitions\Reference(Nette\Localization\Translator::class)]); } + $this->addExtension($extension); } $builder->addDefinition($this->prefix('templateFactory')) @@ -130,40 +119,12 @@ public static function initLattePanel( $control = $template->getLatte()->getProviders()['uiControl'] ?? null; if ($all || $control instanceof Nette\Application\UI\Presenter) { $name = $all && $control ? (new \ReflectionObject($control))->getShortName() : ''; - if (version_compare(Latte\Engine::VERSION, '3', '<')) { - $bar->addPanel(new Latte\Bridges\Tracy\LattePanel($template->getLatte(), $name)); - } else { - $template->getLatte()->addExtension(new Latte\Bridges\Tracy\TracyExtension($name)); - } + $template->getLatte()->addExtension(new Latte\Bridges\Tracy\TracyExtension($name)); } }; } - public function addMacro(string $macro): void - { - $builder = $this->getContainerBuilder(); - $definition = $builder->getDefinition($this->prefix('latteFactory'))->getResultDefinition(); - - if (($macro[0] ?? null) === '@') { - if (str_contains($macro, '::')) { - [$macro, $method] = explode('::', $macro); - } else { - $method = 'install'; - } - - $definition->addSetup('?->onCompile[] = function ($engine) { ?->' . $method . '($engine->getCompiler()); }', ['@self', $macro]); - - } else { - if (!str_contains($macro, '::') && class_exists($macro)) { - $macro .= '::install'; - } - - $definition->addSetup('?->onCompile[] = function ($engine) { ' . $macro . '($engine->getCompiler()); }', ['@self']); - } - } - - public function addExtension(Statement|string $extension): void { $extension = is_string($extension) diff --git a/src/Bridges/ApplicationLatte/SnippetBridge.php b/src/Bridges/ApplicationLatte/SnippetBridge.php deleted file mode 100644 index f5d62ef80..000000000 --- a/src/Bridges/ApplicationLatte/SnippetBridge.php +++ /dev/null @@ -1,95 +0,0 @@ -control = $control; - } - - - public function isSnippetMode(): bool - { - return (bool) $this->control->snippetMode; - } - - - public function setSnippetMode($snippetMode) - { - $this->control->snippetMode = $snippetMode; - } - - - public function needsRedraw($name): bool - { - return $this->control->isControlInvalid($name); - } - - - public function markRedrawn($name): void - { - if ($name !== '') { - $this->control->redrawControl($name, false); - } - } - - - public function getHtmlId($name): string - { - return $this->control->getSnippetId($name); - } - - - public function addSnippet($name, $content): void - { - if (!isset($this->payload)) { - $this->payload = $this->control->getPresenter()->getPayload(); - } - - $this->payload->snippets[$this->control->getSnippetId($name)] = $content; - } - - - public function renderChildren(): void - { - $queue = [$this->control]; - do { - foreach (array_shift($queue)->getComponents() as $child) { - if ($child instanceof Renderable) { - if ($child->isControlInvalid()) { - $child->snippetMode = true; - $child->render(); - $child->snippetMode = false; - } - } elseif ($child instanceof Nette\ComponentModel\IContainer) { - $queue[] = $child; - } - } - } while ($queue); - } -} diff --git a/src/Bridges/ApplicationLatte/Template.php b/src/Bridges/ApplicationLatte/Template.php index 8b2a3024b..c5d429a64 100644 --- a/src/Bridges/ApplicationLatte/Template.php +++ b/src/Bridges/ApplicationLatte/Template.php @@ -11,7 +11,6 @@ use Latte; use Nette; -use function version_compare; /** @@ -93,16 +92,7 @@ public function addFunction(string $name, callable $callback): static */ public function setTranslator(?Nette\Localization\Translator $translator, ?string $language = null): static { - if (version_compare(Latte\Engine::VERSION, '3', '<')) { - $this->latte->addFilter( - 'translate', - fn(Latte\Runtime\FilterInfo $fi, ...$args): string => $translator === null - ? $args[0] - : $translator->translate(...$args), - ); - } else { - $this->latte->addExtension(new Latte\Essential\TranslatorExtension($translator, $language)); - } + $this->latte->addExtension(new Latte\Essential\TranslatorExtension($translator, $language)); return $this; } diff --git a/src/Bridges/ApplicationLatte/TemplateFactory.php b/src/Bridges/ApplicationLatte/TemplateFactory.php index 676b31da6..c0a133347 100644 --- a/src/Bridges/ApplicationLatte/TemplateFactory.php +++ b/src/Bridges/ApplicationLatte/TemplateFactory.php @@ -9,10 +9,9 @@ namespace Nette\Bridges\ApplicationLatte; -use Latte; use Nette; use Nette\Application\UI; -use function array_unshift, class_exists, is_a, iterator_to_array, preg_match, preg_replace, property_exists, rtrim, version_compare; +use function class_exists, is_a, preg_replace, property_exists, rtrim; /** @@ -29,7 +28,6 @@ public function __construct( private readonly LatteFactory $latteFactory, private readonly ?Nette\Http\IRequest $httpRequest = null, private readonly ?Nette\Security\User $user = null, - private readonly ?Nette\Caching\Storage $cacheStorage = null, $templateClass = null, ) { if ($templateClass && (!class_exists($templateClass) || !is_a($templateClass, Template::class, allow_string: true))) { @@ -55,9 +53,7 @@ public function createTemplate(?UI\Control $control = null, ?string $class = nul $latte = $this->latteFactory->create($control); $template = new $class($latte); - if (version_compare(Latte\Engine::VERSION, '3', '<')) { - $this->setupLatte2($latte, $control, $template); - } elseif (!Nette\Utils\Arrays::some($latte->getExtensions(), fn($e) => $e instanceof UIExtension)) { + if (!Nette\Utils\Arrays::some($latte->getExtensions(), fn($e) => $e instanceof UIExtension)) { $latte->addExtension(new UIExtension($control)); } @@ -97,55 +93,4 @@ private function injectDefaultVariables(Template $template, ?UI\Control $control } } } - - - private function setupLatte2( - Latte\Engine $latte, - ?UI\Control $control, - Template $template, - ): void - { - if ($latte->onCompile instanceof \Traversable) { - $latte->onCompile = iterator_to_array($latte->onCompile); - } - - array_unshift($latte->onCompile, function (Latte\Engine $latte) use ($control, $template): void { - if ($this->cacheStorage) { - $latte->getCompiler()->addMacro('cache', new Nette\Bridges\CacheLatte\CacheMacro); - } - - UIMacros::install($latte->getCompiler()); - if (class_exists(Nette\Bridges\FormsLatte\FormMacros::class)) { - Nette\Bridges\FormsLatte\FormMacros::install($latte->getCompiler()); - } - - $control?->templatePrepareFilters($template); - }); - - $latte->addProvider('cacheStorage', $this->cacheStorage); - - $presenter = $control?->getPresenterIfExists(); - if ($control) { - $latte->addProvider('uiControl', $control); - $latte->addProvider('uiPresenter', $presenter); - $latte->addProvider('snippetBridge', new SnippetBridge($control)); - if ($presenter) { - $header = $presenter->getHttpResponse()->getHeader('Content-Security-Policy') - ?: $presenter->getHttpResponse()->getHeader('Content-Security-Policy-Report-Only'); - } - - $nonce = $presenter && preg_match('#\s\'nonce-([\w+/]+=*)\'#', (string) $header, $m) ? $m[1] : null; - $latte->addProvider('uiNonce', $nonce); - } - - if ($presenter) { - $latte->addFunction('isLinkCurrent', $presenter->isLinkCurrent(...)); - $latte->addFunction('isModuleCurrent', $presenter->isModuleCurrent(...)); - } - - $latte->addFilter('modifyDate', fn($time, $delta, $unit = null) => $time - ? Nette\Utils\DateTime::from($time)->modify($delta . $unit) - : null); - - } } diff --git a/src/Bridges/ApplicationLatte/UIMacros.php b/src/Bridges/ApplicationLatte/UIMacros.php deleted file mode 100644 index 131c30075..000000000 --- a/src/Bridges/ApplicationLatte/UIMacros.php +++ /dev/null @@ -1,187 +0,0 @@ -addMacro('control', [$me, 'macroControl']); - - $me->addMacro('href', null, null, fn(MacroNode $node, PhpWriter $writer): string => ' ?> href="macroLink($node, $writer) . ' ?>"addMacro('plink', [$me, 'macroLink']); - $me->addMacro('link', [$me, 'macroLink']); - $me->addMacro('ifCurrent', [$me, 'macroIfCurrent'], '}'); // deprecated; use n:class="$presenter->linkCurrent ? ..." - $me->addMacro('extends', [$me, 'macroExtends']); - $me->addMacro('layout', [$me, 'macroExtends']); - $me->addMacro('nonce', null, null, 'echo $this->global->uiNonce ? " nonce=\"{$this->global->uiNonce}\"" : "";'); - $me->addMacro('templatePrint', [$me, 'macroTemplatePrint'], null, null, self::ALLOWED_IN_HEAD); - } - - - /** - * Initializes before template parsing. - */ - public function initialize(): void - { - $this->extends = false; - } - - - /** - * Finishes template parsing. - * @return array(prolog, epilog) - */ - public function finalize() - { - if ($this->printTemplate) { - return ["Nette\\Bridges\\ApplicationLatte\\UIRuntime::printClass(\$this, $this->printTemplate); exit;"]; - } - - return [$this->extends . 'Nette\Bridges\ApplicationLatte\UIRuntime::initialize($this, $this->parentName, $this->blocks);']; - } - - - /********************* macros ****************d*g**/ - - - /** - * {control name[:method] [params]} - */ - public function macroControl(MacroNode $node, PhpWriter $writer) - { - if ($node->context !== [Latte\Compiler::CONTENT_HTML, Latte\Compiler::CONTEXT_HTML_TEXT]) { - $escapeMod = Latte\Helpers::removeFilter($node->modifiers, 'noescape') ? '' : '|escape'; - } - - if ($node->modifiers) { - trigger_error('Modifiers are deprecated in ' . $node->getNotation(), E_USER_DEPRECATED); - } - - $node->modifiers .= $escapeMod ?? ''; - - $words = $node->tokenizer->fetchWords(); - if (!$words) { - throw new CompileException('Missing control name in {control}'); - } - - $name = $writer->formatWord($words[0]); - $method = ucfirst($words[1] ?? ''); - $method = Strings::match($method, '#^\w*$#D') - ? "render$method" - : "{\"render$method\"}"; - - $tokens = $node->tokenizer; - $pos = $tokens->position; - $wrap = false; - while ($tokens->nextToken()) { - if ($tokens->isCurrent('=>', '(expand)') && !$tokens->depth) { - $wrap = true; - break; - } - } - - $tokens->position = $pos; - $param = $wrap ? $writer->formatArray() : $writer->formatArgs(); - - return "/* line $node->startLine */ " - . ($name[0] === '$' ? "if (is_object($name)) \$_tmp = $name; else " : '') - . '$_tmp = $this->global->uiControl->getComponent(' . $name . '); ' - . 'if ($_tmp instanceof Nette\Application\UI\Renderable) $_tmp->redrawControl(null, false); ' - . ($node->modifiers === '' - ? "\$_tmp->$method($param);" - : $writer->write( - "ob_start(fn() => null); \$_tmp->$method($param); \$ʟ_fi = new LR\\FilterInfo(%var); echo %modifyContent(ob_get_clean());", - Latte\Engine::CONTENT_HTML, - ) - ); - } - - - /** - * {link destination [,] [params]} - * {plink destination [,] [params]} - * n:href="destination [,] [params]" - */ - public function macroLink(MacroNode $node, PhpWriter $writer) - { - $node->modifiers = preg_replace('#\|safeurl\s*(?=\||$)#Di', '', $node->modifiers); - return $writer->using($node, $this->getCompiler()) - ->write( - 'echo %escape(%modify(' - . ($node->name === 'plink' ? '$this->global->uiPresenter' : '$this->global->uiControl') - . '->link(%node.word, %node.array?)))' - . ($node->startLine ? " /* line $node->startLine */;" : ';'), - ); - } - - - /** - * {ifCurrent destination [,] [params]} - */ - public function macroIfCurrent(MacroNode $node, PhpWriter $writer) - { - if ($node->modifiers) { - throw new CompileException('Modifiers are not allowed in ' . $node->getNotation()); - } - - return $writer->write( - $node->args - ? 'if ($this->global->uiPresenter->isLinkCurrent(%node.word, %node.array?)) {' - : 'if ($this->global->uiPresenter->getLastCreatedRequestFlag("current")) {', - ); - } - - - /** - * {extends auto} - */ - public function macroExtends(MacroNode $node, PhpWriter $writer) - { - if ($node->modifiers || $node->parentNode || $node->args !== 'auto') { - return $this->extends = false; - } - - $this->extends = $writer->write('$this->parentName = $this->global->uiPresenter->findLayoutTemplateFile();'); - } - - - /** - * {templatePrint [parentClass | default]} - */ - public function macroTemplatePrint(MacroNode $node): void - { - if ($node->modifiers) { - throw new CompileException('Modifiers are not allowed in ' . $node->getNotation()); - } - - $this->printTemplate = var_export($node->tokenizer->fetchWord() ?: null, true); - } -} diff --git a/src/Bridges/ApplicationLatte/UIRuntime.php b/src/Bridges/ApplicationLatte/UIRuntime.php deleted file mode 100644 index ace3af72d..000000000 --- a/src/Bridges/ApplicationLatte/UIRuntime.php +++ /dev/null @@ -1,84 +0,0 @@ -global; - $blocks = array_filter(array_keys($blocks), fn(string $s): bool => $s[0] !== '_'); - if ( - $parentName === null - && $blocks - && !$template->getReferringTemplate() - && ($providers->uiControl ?? null) instanceof Nette\Application\UI\Presenter - ) { - $parentName = $providers->uiControl->findLayoutTemplateFile(); - } - } - - - public static function printClass(Latte\Runtime\Template $template, ?string $parent = null): void - { - $blueprint = new Latte\Runtime\Blueprint; - $name = 'Template'; - $params = $template->getParameters(); - $control = $params['control'] ?? $params['presenter'] ?? null; - if ($control) { - $name = preg_replace('#(Control|Presenter)$#', '', $control::class) . 'Template'; - unset($params[$control instanceof Presenter ? 'control' : 'presenter']); - } - - if ($parent) { - if (!class_exists($parent)) { - $blueprint->printHeader("{templatePrint}: Class '$parent' doesn't exist."); - return; - } - - $params = array_diff_key($params, get_class_vars($parent)); - } - - $funcs = array_diff_key((array) $template->global->fn, (new Latte\Runtime\Defaults)->getFunctions()); - unset($funcs['isLinkCurrent'], $funcs['isModuleCurrent']); - - $namespace = new Php\PhpNamespace(Php\Helpers::extractNamespace($name)); - $class = $namespace->addClass(Php\Helpers::extractShortName($name)); - $class->setExtends($parent ?: Template::class); - if (!$parent) { - $class->addTrait(Nette\SmartObject::class); - } - - $blueprint->addProperties($class, $params, true); - $blueprint->addFunctions($class, $funcs); - - $end = $blueprint->printCanvas(); - $blueprint->printHeader('Native types'); - $blueprint->printCode((string) $namespace); - - $blueprint->addProperties($class, $params, false); - - $blueprint->printHeader('phpDoc types'); - $blueprint->printCode((string) $namespace); - echo $end; - } -} diff --git a/tests/Bridges.DI/LatteExtension.2.phpt b/tests/Bridges.DI/LatteExtension.2.phpt deleted file mode 100644 index 8144950b6..000000000 --- a/tests/Bridges.DI/LatteExtension.2.phpt +++ /dev/null @@ -1,122 +0,0 @@ -')) { - Tester\Environment::skip('Test for Latte 2'); -} - - -class LoremIpsumMacros extends Latte\Macros\MacroSet -{ - public static function install(Latte\Compiler $compiler) - { - $me = new static($compiler); - $me->addMacro('lorem', 'lorem'); - Notes::add($me::class); - } -} - - -class IpsumLoremMacros extends Latte\Macros\MacroSet -{ - public static function install(Latte\Compiler $compiler) - { - $me = new static($compiler); - $me->addMacro('ipsum', 'ipsum'); - Notes::add($me::class); - } -} - - -class FooMacros extends Latte\Macros\MacroSet -{ - public static function install(Latte\Compiler $compiler) - { - $me = new static($compiler); - $me->addMacro('foo', 'foo'); - Notes::add($me::class); - } -} - - -class NonStaticMacrosFactory -{ - private string $parameter; - - - public function __construct($parameter) - { - $this->parameter = $parameter; - } - - - public function install(Latte\Compiler $compiler) - { - $macros = new Latte\Macros\MacroSet($compiler); - $macros->addMacro('foo', 'foo ' . $this->parameter); - Notes::add(static::class . '::install'); - } - - - public function create(Latte\Compiler $compiler) - { - $macros = new Latte\Macros\MacroSet($compiler); - $macros->addMacro('foo2', 'foo ' . $this->parameter); - Notes::add(static::class . '::create'); - } -} - - -class AnotherExtension extends Nette\DI\CompilerExtension -{ - public function beforeCompile() - { - foreach ($this->compiler->getExtensions(Nette\Bridges\ApplicationDI\LatteExtension::class) as $extension) { - $extension->addMacro('FooMacros::install'); - } - } -} - - -$loader = new DI\Config\Loader; -$config = $loader->load(Tester\FileMock::create(' -latte: - macros: - - LoremIpsumMacros - - IpsumLoremMacros::install - - @macroFactory - - @macroFactory::create - -services: - macroFactory: NonStaticMacrosFactory(foo) -', 'neon')); - -$compiler = new DI\Compiler; -$compiler->addExtension('latte', new Nette\Bridges\ApplicationDI\LatteExtension('', false)); -$compiler->addExtension('another', new AnotherExtension); -$code = $compiler->addConfig($config)->compile(); -eval($code); - -$container = new Container; - - -Assert::type(Nette\Bridges\ApplicationLatte\LatteFactory::class, $container->getService('nette.latteFactory')); -$container->getService('nette.latteFactory')->create()->setLoader(new Latte\Loaders\StringLoader)->compile(''); - -Assert::same([ - 'LoremIpsumMacros', - 'IpsumLoremMacros', - 'NonStaticMacrosFactory::install', - 'NonStaticMacrosFactory::create', - 'FooMacros', -], Notes::fetch()); diff --git a/tests/Bridges.DI/LatteExtension.basic.phpt b/tests/Bridges.DI/LatteExtension.basic.phpt index 09dc0d292..34ea641d5 100644 --- a/tests/Bridges.DI/LatteExtension.basic.phpt +++ b/tests/Bridges.DI/LatteExtension.basic.phpt @@ -11,10 +11,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - class MyExtension extends Latte\Extension { diff --git a/tests/Bridges.DI/LatteExtension.translator.phpt b/tests/Bridges.DI/LatteExtension.translator.phpt index f555b460e..031312f30 100644 --- a/tests/Bridges.DI/LatteExtension.translator.phpt +++ b/tests/Bridges.DI/LatteExtension.translator.phpt @@ -11,10 +11,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - class Translator implements Nette\Localization\Translator { diff --git a/tests/Bridges.Latte3/ControlMock.php b/tests/Bridges.Latte/ControlMock.php similarity index 100% rename from tests/Bridges.Latte3/ControlMock.php rename to tests/Bridges.Latte/ControlMock.php diff --git a/tests/Bridges.Latte3/Template.getParentName().phpt b/tests/Bridges.Latte/Template.getParentName().phpt similarity index 95% rename from tests/Bridges.Latte3/Template.getParentName().phpt rename to tests/Bridges.Latte/Template.getParentName().phpt index 598fbdb97..a75350763 100644 --- a/tests/Bridges.Latte3/Template.getParentName().phpt +++ b/tests/Bridges.Latte/Template.getParentName().phpt @@ -6,10 +6,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - Tester\Environment::bypassFinals(); $presenter = Mockery::mock(Nette\Application\UI\Presenter::class) diff --git a/tests/Bridges.Latte3/TemplateFactory.customTemplate.phpt b/tests/Bridges.Latte/TemplateFactory.customTemplate.phpt similarity index 94% rename from tests/Bridges.Latte3/TemplateFactory.customTemplate.phpt rename to tests/Bridges.Latte/TemplateFactory.customTemplate.phpt index e231cdb34..4df04c5a7 100644 --- a/tests/Bridges.Latte3/TemplateFactory.customTemplate.phpt +++ b/tests/Bridges.Latte/TemplateFactory.customTemplate.phpt @@ -14,10 +14,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - Tester\Environment::bypassFinals(); diff --git a/tests/Bridges.Latte3/TemplateFactory.onCreate.phpt b/tests/Bridges.Latte/TemplateFactory.onCreate.phpt similarity index 89% rename from tests/Bridges.Latte3/TemplateFactory.onCreate.phpt rename to tests/Bridges.Latte/TemplateFactory.onCreate.phpt index ed6e9b61c..987e86aea 100644 --- a/tests/Bridges.Latte3/TemplateFactory.onCreate.phpt +++ b/tests/Bridges.Latte/TemplateFactory.onCreate.phpt @@ -14,10 +14,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - test('', function () { $engine = new Latte\Engine; diff --git a/tests/Bridges.Latte3/TemplateFactory.templateVariables.84.phpt b/tests/Bridges.Latte/TemplateFactory.templateVariables.84.phpt similarity index 100% rename from tests/Bridges.Latte3/TemplateFactory.templateVariables.84.phpt rename to tests/Bridges.Latte/TemplateFactory.templateVariables.84.phpt diff --git a/tests/Bridges.Latte3/TemplateFactory.templateVariables.phpt b/tests/Bridges.Latte/TemplateFactory.templateVariables.phpt similarity index 100% rename from tests/Bridges.Latte3/TemplateFactory.templateVariables.phpt rename to tests/Bridges.Latte/TemplateFactory.templateVariables.phpt diff --git a/tests/Bridges.Latte3/UIExtension.filters.absoluteUrl.phpt b/tests/Bridges.Latte/UIExtension.filters.absoluteUrl.phpt similarity index 94% rename from tests/Bridges.Latte3/UIExtension.filters.absoluteUrl.phpt rename to tests/Bridges.Latte/UIExtension.filters.absoluteUrl.phpt index 6b1b6b63f..8b728ec3f 100644 --- a/tests/Bridges.Latte3/UIExtension.filters.absoluteUrl.phpt +++ b/tests/Bridges.Latte/UIExtension.filters.absoluteUrl.phpt @@ -10,10 +10,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - Tester\Environment::bypassFinals(); diff --git a/tests/Bridges.Latte3/UIExtension.filters.phpt b/tests/Bridges.Latte/UIExtension.filters.phpt similarity index 88% rename from tests/Bridges.Latte3/UIExtension.filters.phpt rename to tests/Bridges.Latte/UIExtension.filters.phpt index 952f5a760..0dc89f8cd 100644 --- a/tests/Bridges.Latte3/UIExtension.filters.phpt +++ b/tests/Bridges.Latte/UIExtension.filters.phpt @@ -10,10 +10,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - $latte = new Latte\Engine; $latte->addExtension(new Nette\Bridges\ApplicationLatte\UIExtension(null)); diff --git a/tests/Bridges.Latte3/UIExtension.nonce.control.phpt b/tests/Bridges.Latte/UIExtension.nonce.control.phpt similarity index 87% rename from tests/Bridges.Latte3/UIExtension.nonce.control.phpt rename to tests/Bridges.Latte/UIExtension.nonce.control.phpt index 9248da8af..cf822c0ab 100644 --- a/tests/Bridges.Latte3/UIExtension.nonce.control.phpt +++ b/tests/Bridges.Latte/UIExtension.nonce.control.phpt @@ -11,10 +11,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - $response = Mockery::mock(Nette\Http\IResponse::class); $response->shouldReceive('getHeader')->with('Content-Security-Policy')->andReturn("hello 'nonce-abcd123==' world"); diff --git a/tests/Bridges.Latte3/UIExtension.nonce.presenter.phpt b/tests/Bridges.Latte/UIExtension.nonce.presenter.phpt similarity index 89% rename from tests/Bridges.Latte3/UIExtension.nonce.presenter.phpt rename to tests/Bridges.Latte/UIExtension.nonce.presenter.phpt index 2f2a8d254..15f0d212d 100644 --- a/tests/Bridges.Latte3/UIExtension.nonce.presenter.phpt +++ b/tests/Bridges.Latte/UIExtension.nonce.presenter.phpt @@ -11,10 +11,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - Tester\Environment::bypassFinals(); $response = Mockery::mock(Nette\Http\IResponse::class); diff --git a/tests/Bridges.Latte3/expected/ifCurrent.php b/tests/Bridges.Latte/expected/ifCurrent.php similarity index 100% rename from tests/Bridges.Latte3/expected/ifCurrent.php rename to tests/Bridges.Latte/expected/ifCurrent.php diff --git a/tests/Bridges.Latte3/expected/isLinkCurrent.php b/tests/Bridges.Latte/expected/isLinkCurrent.php similarity index 100% rename from tests/Bridges.Latte3/expected/isLinkCurrent.php rename to tests/Bridges.Latte/expected/isLinkCurrent.php diff --git a/tests/Bridges.Latte3/expected/n-snippet.block.php b/tests/Bridges.Latte/expected/n-snippet.block.php similarity index 100% rename from tests/Bridges.Latte3/expected/n-snippet.block.php rename to tests/Bridges.Latte/expected/n-snippet.block.php diff --git a/tests/Bridges.Latte3/expected/n-snippet.dynamic.php b/tests/Bridges.Latte/expected/n-snippet.dynamic.php similarity index 100% rename from tests/Bridges.Latte3/expected/n-snippet.dynamic.php rename to tests/Bridges.Latte/expected/n-snippet.dynamic.php diff --git a/tests/Bridges.Latte3/expected/n-snippet.php b/tests/Bridges.Latte/expected/n-snippet.php similarity index 100% rename from tests/Bridges.Latte3/expected/n-snippet.php rename to tests/Bridges.Latte/expected/n-snippet.php diff --git a/tests/Bridges.Latte3/expected/renderSnippets.html b/tests/Bridges.Latte/expected/renderSnippets.html similarity index 100% rename from tests/Bridges.Latte3/expected/renderSnippets.html rename to tests/Bridges.Latte/expected/renderSnippets.html diff --git a/tests/Bridges.Latte3/expected/snippet.dynamic.php b/tests/Bridges.Latte/expected/snippet.dynamic.php similarity index 100% rename from tests/Bridges.Latte3/expected/snippet.dynamic.php rename to tests/Bridges.Latte/expected/snippet.dynamic.php diff --git a/tests/Bridges.Latte3/expected/snippet.dynamic2.php b/tests/Bridges.Latte/expected/snippet.dynamic2.php similarity index 100% rename from tests/Bridges.Latte3/expected/snippet.dynamic2.php rename to tests/Bridges.Latte/expected/snippet.dynamic2.php diff --git a/tests/Bridges.Latte3/expected/snippet.php b/tests/Bridges.Latte/expected/snippet.php similarity index 100% rename from tests/Bridges.Latte3/expected/snippet.php rename to tests/Bridges.Latte/expected/snippet.php diff --git a/tests/Bridges.Latte3/isLinkCurrent().phpt b/tests/Bridges.Latte/isLinkCurrent().phpt similarity index 91% rename from tests/Bridges.Latte3/isLinkCurrent().phpt rename to tests/Bridges.Latte/isLinkCurrent().phpt index 1ca37dcb3..277027dc9 100644 --- a/tests/Bridges.Latte3/isLinkCurrent().phpt +++ b/tests/Bridges.Latte/isLinkCurrent().phpt @@ -10,10 +10,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - Tester\Environment::bypassFinals(); $presenter = Mockery::mock(Nette\Application\UI\Presenter::class); diff --git a/tests/Bridges.Latte3/n-snippet.2.phpt b/tests/Bridges.Latte/n-snippet.2.phpt similarity index 93% rename from tests/Bridges.Latte3/n-snippet.2.phpt rename to tests/Bridges.Latte/n-snippet.2.phpt index 983c46ce5..a5cfc2a20 100644 --- a/tests/Bridges.Latte3/n-snippet.2.phpt +++ b/tests/Bridges.Latte/n-snippet.2.phpt @@ -6,10 +6,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - $latte = new Latte\Engine; $latte->setLoader(new Latte\Loaders\StringLoader); $latte->addExtension(new Nette\Bridges\ApplicationLatte\UIExtension(null)); diff --git a/tests/Bridges.Latte3/n-snippet.block.phpt b/tests/Bridges.Latte/n-snippet.block.phpt similarity index 83% rename from tests/Bridges.Latte3/n-snippet.block.phpt rename to tests/Bridges.Latte/n-snippet.block.phpt index 107dd6ebc..27a83c2f7 100644 --- a/tests/Bridges.Latte3/n-snippet.block.phpt +++ b/tests/Bridges.Latte/n-snippet.block.phpt @@ -6,10 +6,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - $latte = new Latte\Engine; $latte->setLoader(new Latte\Loaders\StringLoader); diff --git a/tests/Bridges.Latte3/n-snippet.dynamic.phpt b/tests/Bridges.Latte/n-snippet.dynamic.phpt similarity index 82% rename from tests/Bridges.Latte3/n-snippet.dynamic.phpt rename to tests/Bridges.Latte/n-snippet.dynamic.phpt index ab2827bca..a794e2d0c 100644 --- a/tests/Bridges.Latte3/n-snippet.dynamic.phpt +++ b/tests/Bridges.Latte/n-snippet.dynamic.phpt @@ -6,10 +6,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - $latte = new Latte\Engine; $latte->setLoader(new Latte\Loaders\StringLoader); diff --git a/tests/Bridges.Latte3/n-snippet.phpt b/tests/Bridges.Latte/n-snippet.phpt similarity index 85% rename from tests/Bridges.Latte3/n-snippet.phpt rename to tests/Bridges.Latte/n-snippet.phpt index c9ff1b893..ac2247eec 100644 --- a/tests/Bridges.Latte3/n-snippet.phpt +++ b/tests/Bridges.Latte/n-snippet.phpt @@ -6,9 +6,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} class Test { diff --git a/tests/Bridges.Latte3/renderSnippets.phpt b/tests/Bridges.Latte/renderSnippets.phpt similarity index 95% rename from tests/Bridges.Latte3/renderSnippets.phpt rename to tests/Bridges.Latte/renderSnippets.phpt index 675995784..4d113171f 100644 --- a/tests/Bridges.Latte3/renderSnippets.phpt +++ b/tests/Bridges.Latte/renderSnippets.phpt @@ -11,10 +11,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - class InnerControl extends Nette\Application\UI\Control { diff --git a/tests/Bridges.Latte3/renderSnippets2.phpt b/tests/Bridges.Latte/renderSnippets2.phpt similarity index 93% rename from tests/Bridges.Latte3/renderSnippets2.phpt rename to tests/Bridges.Latte/renderSnippets2.phpt index 31522dbee..1a092d470 100644 --- a/tests/Bridges.Latte3/renderSnippets2.phpt +++ b/tests/Bridges.Latte/renderSnippets2.phpt @@ -11,10 +11,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - class InnerControl extends Nette\Application\UI\Control { diff --git a/tests/Bridges.Latte3/renderSnippets3.phpt b/tests/Bridges.Latte/renderSnippets3.phpt similarity index 93% rename from tests/Bridges.Latte3/renderSnippets3.phpt rename to tests/Bridges.Latte/renderSnippets3.phpt index dd11322d4..65a621014 100644 --- a/tests/Bridges.Latte3/renderSnippets3.phpt +++ b/tests/Bridges.Latte/renderSnippets3.phpt @@ -11,10 +11,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - class TestControl extends Nette\Application\UI\Control { diff --git a/tests/Bridges.Latte3/renderSnippets4.phpt b/tests/Bridges.Latte/renderSnippets4.phpt similarity index 91% rename from tests/Bridges.Latte3/renderSnippets4.phpt rename to tests/Bridges.Latte/renderSnippets4.phpt index f01809640..b300c03bc 100644 --- a/tests/Bridges.Latte3/renderSnippets4.phpt +++ b/tests/Bridges.Latte/renderSnippets4.phpt @@ -11,10 +11,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - class TestPresenter extends Nette\Application\UI\Presenter { diff --git a/tests/Bridges.Latte3/renderSnippets5.phpt b/tests/Bridges.Latte/renderSnippets5.phpt similarity index 93% rename from tests/Bridges.Latte3/renderSnippets5.phpt rename to tests/Bridges.Latte/renderSnippets5.phpt index fdbd86159..340e36cf6 100644 --- a/tests/Bridges.Latte3/renderSnippets5.phpt +++ b/tests/Bridges.Latte/renderSnippets5.phpt @@ -11,10 +11,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - class TestPresenter extends Nette\Application\UI\Presenter { diff --git a/tests/Bridges.Latte3/renderSnippets6.phpt b/tests/Bridges.Latte/renderSnippets6.phpt similarity index 91% rename from tests/Bridges.Latte3/renderSnippets6.phpt rename to tests/Bridges.Latte/renderSnippets6.phpt index f264135e7..a9cda7417 100644 --- a/tests/Bridges.Latte3/renderSnippets6.phpt +++ b/tests/Bridges.Latte/renderSnippets6.phpt @@ -11,10 +11,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - class TestPresenter extends Nette\Application\UI\Presenter { diff --git a/tests/Bridges.Latte3/renderSnippets7.phpt b/tests/Bridges.Latte/renderSnippets7.phpt similarity index 98% rename from tests/Bridges.Latte3/renderSnippets7.phpt rename to tests/Bridges.Latte/renderSnippets7.phpt index fb7d7c175..e6792e74f 100644 --- a/tests/Bridges.Latte3/renderSnippets7.phpt +++ b/tests/Bridges.Latte/renderSnippets7.phpt @@ -9,10 +9,6 @@ Tester\Environment::bypassFinals(); require __DIR__ . '/ControlMock.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - $dataSets = [ diff --git a/tests/Bridges.Latte2/templates/include3.latte b/tests/Bridges.Latte/templates/include3.latte similarity index 100% rename from tests/Bridges.Latte2/templates/include3.latte rename to tests/Bridges.Latte/templates/include3.latte diff --git a/tests/Bridges.Latte2/templates/snippet-include.latte b/tests/Bridges.Latte/templates/snippet-include.latte similarity index 100% rename from tests/Bridges.Latte2/templates/snippet-include.latte rename to tests/Bridges.Latte/templates/snippet-include.latte diff --git a/tests/Bridges.Latte2/templates/snippet-included.latte b/tests/Bridges.Latte/templates/snippet-included.latte similarity index 100% rename from tests/Bridges.Latte2/templates/snippet-included.latte rename to tests/Bridges.Latte/templates/snippet-included.latte diff --git a/tests/Bridges.Latte3/{control}.2.phpt b/tests/Bridges.Latte/{control}.2.phpt similarity index 94% rename from tests/Bridges.Latte3/{control}.2.phpt rename to tests/Bridges.Latte/{control}.2.phpt index bb2cb9171..7086c7fd1 100644 --- a/tests/Bridges.Latte3/{control}.2.phpt +++ b/tests/Bridges.Latte/{control}.2.phpt @@ -10,10 +10,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - class MockComponent { diff --git a/tests/Bridges.Latte3/{control}.3.phpt b/tests/Bridges.Latte/{control}.3.phpt similarity index 90% rename from tests/Bridges.Latte3/{control}.3.phpt rename to tests/Bridges.Latte/{control}.3.phpt index fe9d4052f..59a41fd48 100644 --- a/tests/Bridges.Latte3/{control}.3.phpt +++ b/tests/Bridges.Latte/{control}.3.phpt @@ -10,9 +10,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} $control = new class { public function render() diff --git a/tests/Bridges.Latte3/{control}.phpt b/tests/Bridges.Latte/{control}.phpt similarity index 94% rename from tests/Bridges.Latte3/{control}.phpt rename to tests/Bridges.Latte/{control}.phpt index 9a6e1f4a6..ed3772729 100644 --- a/tests/Bridges.Latte3/{control}.phpt +++ b/tests/Bridges.Latte/{control}.phpt @@ -10,10 +10,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - $latte = new Latte\Engine; $latte->setLoader(new Latte\Loaders\StringLoader); diff --git a/tests/Bridges.Latte3/{ifCurrent}.phpt b/tests/Bridges.Latte/{ifCurrent}.phpt similarity index 88% rename from tests/Bridges.Latte3/{ifCurrent}.phpt rename to tests/Bridges.Latte/{ifCurrent}.phpt index 3c9ee133e..0be5f05e2 100644 --- a/tests/Bridges.Latte3/{ifCurrent}.phpt +++ b/tests/Bridges.Latte/{ifCurrent}.phpt @@ -6,10 +6,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - Tester\Environment::bypassFinals(); $latte = new Latte\Engine; diff --git a/tests/Bridges.Latte3/{linkBase}.phpt b/tests/Bridges.Latte/{linkBase}.phpt similarity index 90% rename from tests/Bridges.Latte3/{linkBase}.phpt rename to tests/Bridges.Latte/{linkBase}.phpt index a1e9c0c34..78d6f66cf 100644 --- a/tests/Bridges.Latte3/{linkBase}.phpt +++ b/tests/Bridges.Latte/{linkBase}.phpt @@ -10,10 +10,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - $latte = new Latte\Engine; $latte->setLoader(new Latte\Loaders\StringLoader); diff --git a/tests/Bridges.Latte3/{link}.2.phpt b/tests/Bridges.Latte/{link}.2.phpt similarity index 95% rename from tests/Bridges.Latte3/{link}.2.phpt rename to tests/Bridges.Latte/{link}.2.phpt index 816283b42..7fc270038 100644 --- a/tests/Bridges.Latte3/{link}.2.phpt +++ b/tests/Bridges.Latte/{link}.2.phpt @@ -10,10 +10,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - class MockControl { diff --git a/tests/Bridges.Latte3/{link}.phpt b/tests/Bridges.Latte/{link}.phpt similarity index 92% rename from tests/Bridges.Latte3/{link}.phpt rename to tests/Bridges.Latte/{link}.phpt index e2457c9a7..9d8e296e6 100644 --- a/tests/Bridges.Latte3/{link}.phpt +++ b/tests/Bridges.Latte/{link}.phpt @@ -10,10 +10,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - $latte = new Latte\Engine; $latte->setLoader(new Latte\Loaders\StringLoader); diff --git a/tests/Bridges.Latte3/{snippet}.dynamic.phpt b/tests/Bridges.Latte/{snippet}.dynamic.phpt similarity index 87% rename from tests/Bridges.Latte3/{snippet}.dynamic.phpt rename to tests/Bridges.Latte/{snippet}.dynamic.phpt index 3b2068ed0..1eb5aacdd 100644 --- a/tests/Bridges.Latte3/{snippet}.dynamic.phpt +++ b/tests/Bridges.Latte/{snippet}.dynamic.phpt @@ -6,10 +6,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - $latte = new Latte\Engine; $latte->setLoader(new Latte\Loaders\StringLoader); diff --git a/tests/Bridges.Latte3/{snippet}.phpt b/tests/Bridges.Latte/{snippet}.phpt similarity index 85% rename from tests/Bridges.Latte3/{snippet}.phpt rename to tests/Bridges.Latte/{snippet}.phpt index bf9022dce..5afa062ef 100644 --- a/tests/Bridges.Latte3/{snippet}.phpt +++ b/tests/Bridges.Latte/{snippet}.phpt @@ -6,10 +6,6 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -if (version_compare(Latte\Engine::VERSION, '3', '<')) { - Tester\Environment::skip('Test for Latte 3'); -} - $latte = new Latte\Engine; $latte->setLoader(new Latte\Loaders\StringLoader); diff --git a/tests/Bridges.Latte2/Template.getParentName().phpt b/tests/Bridges.Latte2/Template.getParentName().phpt deleted file mode 100644 index 1253c4a4e..000000000 --- a/tests/Bridges.Latte2/Template.getParentName().phpt +++ /dev/null @@ -1,58 +0,0 @@ -')) { - Tester\Environment::skip('Test for Latte 2'); -} - - -$presenter = Mockery::mock(Nette\Application\UI\Presenter::class) - ->shouldReceive('findLayoutTemplateFile')->andReturn('layout.latte') - ->mock(); - -$latte = new Latte\Engine; -$latte->setLoader(new Latte\Loaders\StringLoader); -$latte->addProvider('uiControl', $presenter); -UIMacros::install($latte->getCompiler()); - -$template = $latte->createTemplate(''); -$template->prepare(); -Assert::null($template->getParentName()); - -$template = $latte->createTemplate('{block}...{/block}'); -$template->prepare(); -Assert::null($template->getParentName()); - -$template = $latte->createTemplate('{block name}...{/block}'); -$template->prepare(); -Assert::same('layout.latte', $template->getParentName()); - -$template = $latte->createTemplate('{extends "file.latte"} {block name}...{/block}'); -$template->prepare(); -Assert::same('file.latte', $template->getParentName()); - -$template = $latte->createTemplate('{extends "file.latte"}'); -$template->prepare(); -Assert::same('file.latte', $template->getParentName()); - -$template = $latte->createTemplate( - '{extends $file} {block name}...{/block}', - ['file' => 'file.latte'], -); -$template->prepare(); -Assert::same('file.latte', $template->getParentName()); - -$template = $latte->createTemplate('{extends none}'); -$template->prepare(); -Assert::null($template->getParentName()); - -$latte->addProvider('uiPresenter', $presenter); -$template = $latte->createTemplate('{extends auto}'); -$template->prepare(); -Assert::same('layout.latte', $template->getParentName()); diff --git a/tests/Bridges.Latte2/TemplateFactory.customTemplate.phpt b/tests/Bridges.Latte2/TemplateFactory.customTemplate.phpt deleted file mode 100644 index 011d5b52f..000000000 --- a/tests/Bridges.Latte2/TemplateFactory.customTemplate.phpt +++ /dev/null @@ -1,75 +0,0 @@ -')) { - Tester\Environment::skip('Test for Latte 2'); -} - -Tester\Environment::bypassFinals(); - - -class TemplateMock extends Template -{ - private $file = 'ko'; - - - public function render(?string $file = null, array $params = []): void - { - echo strrev($this->file); - } - - - public function setFile(string $file): static - { - $this->file = $file; - return $this; - } - - - public function getFile(): string - { - return $this->file; - } -} - - -test('', function () { - $latteFactory = Mockery::mock(LatteFactory::class); - $latteFactory->shouldReceive('create')->andReturn(new Latte\Engine); - $factory = new TemplateFactory($latteFactory); - Assert::type(Template::class, $factory->createTemplate()); -}); - -Assert::exception(function () { - $factory = new TemplateFactory(Mockery::mock(LatteFactory::class), null, null, null, stdClass::class); -}, Nette\InvalidArgumentException::class, 'Class stdClass does not implement Nette\Bridges\ApplicationLatte\Template or it does not exist.'); - - -test('', function () { - $latteFactory = Mockery::mock(LatteFactory::class); - $latteFactory->shouldReceive('create')->andReturn(new Latte\Engine); - $factory = new TemplateFactory($latteFactory, null, null, null, TemplateMock::class); - $template = $factory->createTemplate(); - Assert::type(TemplateMock::class, $template); - Assert::type(UI\Template::class, $template); - ob_start(); - $template->render(); - Assert::same('ok', ob_get_clean()); - $template->setFile('bla'); - ob_start(); - $template->render(); - Assert::same('alb', ob_get_clean()); -}); diff --git a/tests/Bridges.Latte2/TemplateFactory.filters.phpt b/tests/Bridges.Latte2/TemplateFactory.filters.phpt deleted file mode 100644 index 57e879543..000000000 --- a/tests/Bridges.Latte2/TemplateFactory.filters.phpt +++ /dev/null @@ -1,47 +0,0 @@ -')) { - Tester\Environment::skip('Test for Latte 2'); -} - - -class LatteFactory implements Nette\Bridges\ApplicationLatte\LatteFactory -{ - private $engine; - - - public function __construct(Latte\Engine $engine) - { - $this->engine = $engine; - } - - - public function create(): Latte\Engine - { - return $this->engine; - } -} - -$factory = new TemplateFactory(new LatteFactory(new Latte\Engine)); -$latte = $factory->createTemplate()->getLatte(); - - -setlocale(LC_TIME, 'C'); -date_default_timezone_set('Europe/Prague'); - -Assert::null($latte->invokeFilter('modifyDate', [null, null])); -Assert::same('1978-01-24 11:40:00', (string) $latte->invokeFilter('modifyDate', [254_400_000, '+1 day'])); -Assert::same('1978-05-06 00:00:00', (string) $latte->invokeFilter('modifyDate', ['1978-05-05', '+1 day'])); -Assert::same('1978-05-06 00:00:00', (string) $latte->invokeFilter('modifyDate', [new DateTime('1978-05-05'), '1day'])); -Assert::same('1978-01-22 11:40:00', (string) $latte->invokeFilter('modifyDate', [254_400_000, -1, 'day'])); diff --git a/tests/Bridges.Latte2/TemplateFactory.nonce.control.phpt b/tests/Bridges.Latte2/TemplateFactory.nonce.control.phpt deleted file mode 100644 index 850629b36..000000000 --- a/tests/Bridges.Latte2/TemplateFactory.nonce.control.phpt +++ /dev/null @@ -1,39 +0,0 @@ -')) { - Tester\Environment::skip('Test for Latte 2'); -} - -$latte = new Latte\Engine; - -$latteFactory = Mockery::mock(ApplicationLatte\LatteFactory::class); -$latteFactory->shouldReceive('create')->andReturn($latte); - -$response = Mockery::mock(Nette\Http\IResponse::class); -$response->shouldReceive('getHeader')->with('Content-Security-Policy')->andReturn("hello 'nonce-abcd123==' world"); - -$control = Mockery::mock(UI\Control::class); -$control->shouldReceive('getPresenter')->andReturn(null); -$control->shouldIgnoreMissing(); - -$factory = new ApplicationLatte\TemplateFactory($latteFactory); -$factory->createTemplate($control); - -$latte->setLoader(new Latte\Loaders\StringLoader); - -Assert::match( - '', - $latte->renderToString(''), -); diff --git a/tests/Bridges.Latte2/TemplateFactory.nonce.presenter.phpt b/tests/Bridges.Latte2/TemplateFactory.nonce.presenter.phpt deleted file mode 100644 index 1ef2df0e8..000000000 --- a/tests/Bridges.Latte2/TemplateFactory.nonce.presenter.phpt +++ /dev/null @@ -1,42 +0,0 @@ -')) { - Tester\Environment::skip('Test for Latte 2'); -} - -Tester\Environment::bypassFinals(); - -$latte = new Latte\Engine; - -$latteFactory = Mockery::mock(ApplicationLatte\LatteFactory::class); -$latteFactory->shouldReceive('create')->andReturn($latte); - -$response = Mockery::mock(Nette\Http\IResponse::class); -$response->shouldReceive('getHeader')->with('Content-Security-Policy')->andReturn("hello 'nonce-abcd123==' world"); - -$presenter = Mockery::mock(UI\Presenter::class); -$presenter->shouldReceive('getPresenterIfExists')->andReturn($presenter); -$presenter->shouldReceive('getHttpResponse')->andReturn($response); -$presenter->shouldIgnoreMissing(); - -$factory = new ApplicationLatte\TemplateFactory($latteFactory); -$factory->createTemplate($presenter); - -$latte->setLoader(new Latte\Loaders\StringLoader); - -Assert::match( - '', - $latte->renderToString(''), -); diff --git a/tests/Bridges.Latte2/TemplateFactory.onCompile.phpt b/tests/Bridges.Latte2/TemplateFactory.onCompile.phpt deleted file mode 100644 index b60ebd8e1..000000000 --- a/tests/Bridges.Latte2/TemplateFactory.onCompile.phpt +++ /dev/null @@ -1,80 +0,0 @@ -')) { - Tester\Environment::skip('Test for Latte 2'); -} - - -test('', function () { - $engine = new Latte\Engine; - $latteFactory = Mockery::mock(LatteFactory::class); - $latteFactory->shouldReceive('create')->andReturn($engine); - $factory = new TemplateFactory($latteFactory, new Http\Request(new Http\UrlScript('http://nette.org'))); - $engine->onCompile[] = $callback = function () {}; - - $factory->createTemplate(); - - Assert::type('array', $engine->onCompile); - Assert::type(Closure::class, $engine->onCompile[0]); // prepended by TemplateFactory - Assert::same($callback, $engine->onCompile[1]); // our callback -}); - - -test('', function () { - $engine = new Latte\Engine; - $latteFactory = Mockery::mock(LatteFactory::class); - $latteFactory->shouldReceive('create')->andReturn($engine); - $factory = new TemplateFactory($latteFactory, new Http\Request(new Http\UrlScript('http://nette.org'))); - $engine->onCompile = new ArrayIterator([$callback = function () {}]); - - $factory->createTemplate(); - - Assert::type('array', $engine->onCompile); - Assert::type(Closure::class, $engine->onCompile[0]); // prepended by TemplateFactory - Assert::same($callback, $engine->onCompile[1]); // our callback -}); - - -test('', function () { - class Event implements IteratorAggregate - { - public $events; - - - public function __construct($events) - { - $this->events = $events; - } - - - public function getIterator(): ArrayIterator - { - return new ArrayIterator($this->events); - } - } - - $engine = new Latte\Engine; - $latteFactory = Mockery::mock(LatteFactory::class); - $latteFactory->shouldReceive('create')->andReturn($engine); - $factory = new TemplateFactory($latteFactory, new Http\Request(new Http\UrlScript('http://nette.org'))); - $engine->onCompile = new Event([$callback = function () {}]); - - $factory->createTemplate(); - - Assert::type('array', $engine->onCompile); - Assert::type(Closure::class, $engine->onCompile[0]); // prepended by TemplateFactory - Assert::same($callback, $engine->onCompile[1]); // our callback -}); diff --git a/tests/Bridges.Latte2/TemplateFactory.onCreate.phpt b/tests/Bridges.Latte2/TemplateFactory.onCreate.phpt deleted file mode 100644 index 8f86ef42d..000000000 --- a/tests/Bridges.Latte2/TemplateFactory.onCreate.phpt +++ /dev/null @@ -1,36 +0,0 @@ -')) { - Tester\Environment::skip('Test for Latte 2'); -} - - -test('', function () { - $engine = new Latte\Engine; - $latteFactory = Mockery::mock(LatteFactory::class); - $latteFactory->shouldReceive('create')->andReturn($engine); - $factory = new TemplateFactory($latteFactory, new Http\Request(new Http\UrlScript('http://nette.org'))); - $factory->onCreate[] = $callback = function (Template $template) { - $template->add('foo', 'bar'); - }; - - $template = $factory->createTemplate(); - - Assert::type('array', $factory->onCreate); - Assert::same($callback, $factory->onCreate[0]); // our callback - Assert::equal('bar', $template->foo); -}); diff --git a/tests/Bridges.Latte2/UIMacros.control.2.phpt b/tests/Bridges.Latte2/UIMacros.control.2.phpt deleted file mode 100644 index 9449e00e3..000000000 --- a/tests/Bridges.Latte2/UIMacros.control.2.phpt +++ /dev/null @@ -1,86 +0,0 @@ -')) { - Tester\Environment::skip('Test for Latte 2'); -} - - -class MockComponent -{ - public function getComponent($name) - { - Notes::add(__METHOD__); - Notes::add(func_get_args()); - return new MockControl; - } -} - - -class MockControl -{ - public function __call($name, $args) - { - Notes::add(__METHOD__); - Notes::add(func_get_args()); - } -} - - -$latte = new Latte\Engine; -$latte->setLoader(new Latte\Loaders\StringLoader); -UIMacros::install($latte->getCompiler()); - -$latte->addProvider('uiControl', new MockComponent); -$params['form'] = new MockControl; -$params['name'] = 'form'; - -$latte->renderToString(' -{control \'name\'} - -{control form} - -{control form:test} - -{control $form:test} - -{control $name:test} - -{control $name:$name} - -{control form var1} - -{control form var1, 1, 2} - -{control form var1 => 5, 1, 2} -', $params); - -Assert::same([ - 'MockComponent::getComponent', ['name'], - 'MockControl::__call', ['render', []], - 'MockComponent::getComponent', ['form'], - 'MockControl::__call', ['render', []], - 'MockComponent::getComponent', ['form'], - 'MockControl::__call', ['renderTest', []], - 'MockControl::__call', ['renderTest', []], - 'MockComponent::getComponent', ['form'], - 'MockControl::__call', ['renderTest', []], - 'MockComponent::getComponent', ['form'], - 'MockControl::__call', ['renderform', []], - 'MockComponent::getComponent', ['form'], - 'MockControl::__call', ['render', ['var1']], - 'MockComponent::getComponent', ['form'], - 'MockControl::__call', ['render', ['var1', 1, 2]], - 'MockComponent::getComponent', ['form'], - 'MockControl::__call', ['render', [['var1' => 5, 0 => 1, 1 => 2]]], -], Notes::fetch()); diff --git a/tests/Bridges.Latte2/UIMacros.control.3.phpt b/tests/Bridges.Latte2/UIMacros.control.3.phpt deleted file mode 100644 index f5d6e23d2..000000000 --- a/tests/Bridges.Latte2/UIMacros.control.3.phpt +++ /dev/null @@ -1,51 +0,0 @@ -')) { - Tester\Environment::skip('Test for Latte 2'); -} - - -$latte = new Latte\Engine; -$latte->setLoader(new Latte\Loaders\StringLoader); -UIMacros::install($latte->getCompiler()); -$latte->addProvider('uiControl', new class { - public function render() - { - echo '<>&'; - } - - - public function __call($name, $args) - { - return new self; - } -}); - -Assert::exception(function () use ($latte) { - $latte->renderToString('
&', - $latte->renderToString('
', - $latte->renderToString('
'), -); - -Assert::exception(function () use ($latte) { - $latte->renderToString(''); -}, Latte\RuntimeException::class, 'Filters: unable to convert content type HTML to HTMLCSS'); diff --git a/tests/Bridges.Latte2/UIMacros.control.phpt b/tests/Bridges.Latte2/UIMacros.control.phpt deleted file mode 100644 index 76f8e52f3..000000000 --- a/tests/Bridges.Latte2/UIMacros.control.phpt +++ /dev/null @@ -1,88 +0,0 @@ -')) { - Tester\Environment::skip('Test for Latte 2'); -} - - -$latte = new Latte\Engine; -$latte->setLoader(new Latte\Loaders\StringLoader); -UIMacros::install($latte->getCompiler()); - -// {control ...} -Assert::match( - '%A% $this->global->uiControl->getComponent("form");%A%->render();%A%', - $latte->compile('{control form}'), -); - -@Assert::match( - <<<'XX' - %A% - /* %a% */ $_tmp = $this->global->uiControl->getComponent("form"); - if ($_tmp instanceof Nette\Application\UI\Renderable) $_tmp->redrawControl(null, false); - ob_start(fn() => null); - $_tmp->render(); - $ʟ_fi = new LR\FilterInfo('html'); - echo $this->filters->filterContent('filter', $ʟ_fi, ob_get_clean()); - %A% - XX, - $latte->compile('{control form|filter}'), -); // @deprecated - -Assert::match( - <<<'XX' - %A% - /* %a% */ if (is_object($form)) $_tmp = $form; - else $_tmp = $this->global->uiControl->getComponent($form); - if ($_tmp instanceof Nette\Application\UI\Renderable) $_tmp->redrawControl(null, false); - $_tmp->render(); - %A% - XX, - $latte->compile('{control $form}'), -); - -Assert::match( - '%A% $this->global->uiControl->getComponent("form");%A%->renderType();%A%', - $latte->compile('{control form:type}'), -); - -Assert::match( - '%A% $this->global->uiControl->getComponent("form");%A%->{"render$type"}();%A%', - $latte->compile('{control form:$type}'), -); - -Assert::match( - '%A% $this->global->uiControl->getComponent("form");%A%->renderType(\'param\');%A%', - $latte->compile('{control form:type param}'), -); - -Assert::match( - '%A% $this->global->uiControl->getComponent("form");%A%->render(array_merge([], $params, []));%A%', - $latte->compile('{control form (expand) $params}'), -); - -Assert::match( - '%A% $this->global->uiControl->getComponent("form");%A%->renderType([\'param\' => 123]);%A%', - $latte->compile('{control form:type param => 123}'), -); - -Assert::match( - '%A% $this->global->uiControl->getComponent("form");%A%->renderType([\'param\' => 123]);%A%', - $latte->compile('{control form:type, param => 123}'), -); - -Assert::match( - '%A% $this->global->uiControl->getComponent("form");%A%->renderType(param: 123);%A%', - $latte->compile('{control form:type, param: 123}'), -); diff --git a/tests/Bridges.Latte2/UIMacros.isLinkCurrent.phpt b/tests/Bridges.Latte2/UIMacros.isLinkCurrent.phpt deleted file mode 100644 index 579a8fd4b..000000000 --- a/tests/Bridges.Latte2/UIMacros.isLinkCurrent.phpt +++ /dev/null @@ -1,54 +0,0 @@ -')) { - Tester\Environment::skip('Test for Latte 2'); -} - -Tester\Environment::bypassFinals(); - -$latte = new Latte\Engine; - -$latteFactory = Mockery::mock(Nette\Bridges\ApplicationLatte\LatteFactory::class); -$latteFactory->shouldReceive('create')->andReturn($latte); - -$presenter = Mockery::mock(Nette\Application\UI\Presenter::class); -$presenter->shouldReceive('getPresenterIfExists')->andReturn($presenter); -$presenter->shouldReceive('getHttpResponse')->andReturn((Mockery::mock(Nette\Http\IResponse::class))->shouldIgnoreMissing()); -$presenter->shouldIgnoreMissing(); - -$factory = new Nette\Bridges\ApplicationLatte\TemplateFactory($latteFactory); -$factory->createTemplate($presenter); - -$latte->setLoader(new Latte\Loaders\StringLoader); - -Assert::matchFile( - __DIR__ . '/expected/UIMacros.isLinkCurrent.php', - $latte->compile( - <<<'XX' - n:href before n:class - - n:href after n:class - - href before n:class - - href after n:class - - {ifCurrent}empty{/ifCurrent} - - {ifCurrent default}default{/ifCurrent} - - custom function - - XX, - ), -); diff --git a/tests/Bridges.Latte2/UIMacros.link.2.phpt b/tests/Bridges.Latte2/UIMacros.link.2.phpt deleted file mode 100644 index 83a441c31..000000000 --- a/tests/Bridges.Latte2/UIMacros.link.2.phpt +++ /dev/null @@ -1,122 +0,0 @@ -')) { - Tester\Environment::skip('Test for Latte 2'); -} - - -class MockControl -{ - public function link($destination, $args = []) - { - if (!is_array($args)) { - $args = array_slice(func_get_args(), 1); - } - - array_unshift($args, $destination); - return 'link:' . strtr(json_encode($args), '"', "'"); - } -} - - -class MockPresenter extends MockControl -{ - public function link($destination, $args = []) - { - if (!is_array($args)) { - $args = array_slice(func_get_args(), 1); - } - - array_unshift($args, $destination); - return 'plink:' . strtr(json_encode($args), '"', "'"); - } - - - public function isAjax() - { - return false; - } -} - - -$latte = new Latte\Engine; -$latte->setLoader(new Latte\Loaders\StringLoader); -UIMacros::install($latte->getCompiler()); - -$latte->addProvider('uiControl', new MockControl); -$latte->addProvider('uiPresenter', new MockPresenter); -$params['action'] = 'login'; -$params['arr'] = ['link' => 'login', 'param' => 123]; - -Assert::match(<<<'EOD' - plink:['Homepage:'] - - plink:['Homepage:'] - - plink:['Homepage:action'] - - plink:['Homepage:action'] - - plink:['Homepage:action',10,20,'{one}&two'] - - plink:['Homepage:action#hash',10,20,'{one}&two'] - - plink:['#hash'] - - plink:[':',10] - - plink:{'0':'default','1':10,'a':20,'b':30} - - link:['login'] - - - - - - - - - - - EOD, strtr($latte->renderToString(<<<'EOD' - {plink Homepage:} - - {plink Homepage: } - - {plink Homepage:action } - - {plink 'Homepage:action' } - - {plink Homepage:action 10, 20, '{one}&two'} - - {plink Homepage:action#hash 10, 20, '{one}&two'} - - {plink #hash} - - {plink : 10 } - - {plink default 10, 'a' => 20, 'b' => 30} - - {link $action} - - - - - - - - - - - EOD, $params), [''' => "'", ''' => "'", '{' => '{'])); diff --git a/tests/Bridges.Latte2/UIMacros.link.phpt b/tests/Bridges.Latte2/UIMacros.link.phpt deleted file mode 100644 index 52fda3fb0..000000000 --- a/tests/Bridges.Latte2/UIMacros.link.phpt +++ /dev/null @@ -1,68 +0,0 @@ -')) { - Tester\Environment::skip('Test for Latte 2'); -} - - -$latte = new Latte\Engine; -$latte->setLoader(new Latte\Loaders\StringLoader); -UIMacros::install($latte->getCompiler()); - -// {link ...} -Assert::contains( - '$this->global->uiControl->link("p")', - $latte->compile('{link p}'), -); -Assert::contains( - '($this->filters->filter)($this->global->uiControl->link("p"))', - $latte->compile('{link p|filter}'), -); -Assert::contains( - '$this->global->uiControl->link("p:a")', - $latte->compile('{link p:a}'), -); -Assert::contains( - '$this->global->uiControl->link($dest)', - $latte->compile('{link $dest}'), -); -Assert::contains( - '$this->global->uiControl->link($p:$a)', - $latte->compile('{link $p:$a}'), -); -Assert::contains( - '$this->global->uiControl->link("$p:$a")', - $latte->compile('{link "$p:$a"}'), -); -Assert::contains( - '$this->global->uiControl->link("p:a")', - $latte->compile('{link "p:a"}'), -); -Assert::contains( - '$this->global->uiControl->link(\'p:a\')', - $latte->compile('{link \'p:a\'}'), -); - -Assert::contains( - '$this->global->uiControl->link("p", [\'param\'])', - $latte->compile('{link p param}'), -); -Assert::contains( - '$this->global->uiControl->link("p", [\'param\' => 123])', - $latte->compile('{link p param => 123}'), -); -Assert::contains( - '$this->global->uiControl->link("p", [\'param\' => 123])', - $latte->compile('{link p, param => 123}'), -); diff --git a/tests/Bridges.Latte2/UIMacros.renderSnippets.phpt b/tests/Bridges.Latte2/UIMacros.renderSnippets.phpt deleted file mode 100644 index 71c354140..000000000 --- a/tests/Bridges.Latte2/UIMacros.renderSnippets.phpt +++ /dev/null @@ -1,97 +0,0 @@ -')) { - Tester\Environment::skip('Test for Latte 2'); -} - - -class InnerControl extends Nette\Application\UI\Control -{ - public function render() - { - $latte = new Latte\Engine; - UIMacros::install($latte->getCompiler()); - $latte->addProvider('uiPresenter', $this->getPresenter()); - $latte->addProvider('uiControl', $this); - $latte->addProvider('snippetBridge', new Nette\Bridges\ApplicationLatte\SnippetBridge($this)); - $params['say'] = 'Hello'; - $latte->render(__DIR__ . '/templates/snippet-included.latte', $params); - } -} - -class TestPresenter extends Nette\Application\UI\Presenter -{ - public function createComponentMulti() - { - return new Nette\Application\UI\Multiplier(function () { - $control = new InnerControl; - $control->redrawControl(); - return $control; - }); - } - - - public function render() - { - $latte = new Latte\Engine; - UIMacros::install($latte->getCompiler()); - $latte->addProvider('snippetBridge', new Nette\Bridges\ApplicationLatte\SnippetBridge($this)); - $latte->render(__DIR__ . '/templates/snippet-include.latte'); - } -} - - -$presenter = new TestPresenter; -$presenter->snippetMode = true; -$presenter->redrawControl(); -$presenter['multi-1']->redrawControl(); -$presenter->render(); -Assert::same([ - 'snippets' => [ - 'snippet--hello' => 'Hello', - 'snippet--include' => "

Included file #3 (A, B)

\n", - 'snippet--array-1' => 'Value 1', - 'snippet--array-2' => 'Value 2', - 'snippet--array-3' => 'Value 3', - 'snippet--array2-1' => 'Value 1', - 'snippet--array2-2' => 'Value 2', - 'snippet--array2-3' => 'Value 3', - 'snippet--includeSay' => 'Hello include snippet', - 'snippet--nested1' => "\t
Foo
\n", - 'snippet-multi-1-includeSay' => 'Hello', - ], -], (array) $presenter->payload); - - - -$presenter = new TestPresenter; -$presenter->snippetMode = true; -$presenter->redrawControl('hello'); -$presenter->redrawControl('array'); -$presenter->render(); - -Assert::same([ - 'snippets' => [ - 'snippet--hello' => 'Hello', - 'snippet--array-1' => 'Value 1', - 'snippet--array-2' => 'Value 2', - 'snippet--array-3' => 'Value 3', - ], -], (array) $presenter->payload); - -$presenter = new TestPresenter; -ob_start(); -$presenter->render(); -$content = ob_get_clean(); -Assert::matchFile(__DIR__ . '/expected/UIMacros.renderSnippets.html', $content); diff --git a/tests/Bridges.Latte2/UIMacros.renderSnippets2.phpt b/tests/Bridges.Latte2/UIMacros.renderSnippets2.phpt deleted file mode 100644 index 58a72d170..000000000 --- a/tests/Bridges.Latte2/UIMacros.renderSnippets2.phpt +++ /dev/null @@ -1,84 +0,0 @@ -')) { - Tester\Environment::skip('Test for Latte 2'); -} - - -class InnerControl extends Nette\Application\UI\Control -{ - public function render() - { - $this->renderA(); - $this->renderB(); - } - - - public function renderA() - { - $latte = new Latte\Engine; - $latte->setLoader(new Latte\Loaders\StringLoader); - UIMacros::install($latte->getCompiler()); - $latte->addProvider('uiPresenter', $this->getPresenter()); - $latte->addProvider('uiControl', $this); - $latte->addProvider('snippetBridge', new SnippetBridge($this)); - $params['say'] = 'Hello'; - $latte->render('{snippet testA}{$say}{/snippet}', $params); - } - - - public function renderB() - { - $latte = new Latte\Engine; - $latte->setLoader(new Latte\Loaders\StringLoader); - UIMacros::install($latte->getCompiler()); - $latte->addProvider('uiPresenter', $this->getPresenter()); - $latte->addProvider('uiControl', $this); - $latte->addProvider('snippetBridge', new SnippetBridge($this)); - $params['say'] = 'world'; - $latte->render('{snippet testB}{$say}{/snippet}', $params); - } -} - -class TestPresenter extends Nette\Application\UI\Presenter -{ - public function createComponentMulti() - { - return new Nette\Application\UI\Multiplier(fn() => new InnerControl); - } - - - public function render() - { - $latte = new Latte\Engine; - $latte->setLoader(new Latte\Loaders\StringLoader); - UIMacros::install($latte->getCompiler()); - $latte->addProvider('uiControl', $this); - $latte->addProvider('snippetBridge', new SnippetBridge($this)); - $latte->render(''); - } -} - - -$presenter = new TestPresenter; -$presenter->snippetMode = true; -$presenter['multi-1']->redrawControl(); -$presenter->render(); -Assert::same([ - 'snippets' => [ - 'snippet-multi-1-testA' => 'Hello', - 'snippet-multi-1-testB' => 'world', - ], -], (array) $presenter->payload); diff --git a/tests/Bridges.Latte2/UIMacros.renderSnippets3.phpt b/tests/Bridges.Latte2/UIMacros.renderSnippets3.phpt deleted file mode 100644 index 3dc72672b..000000000 --- a/tests/Bridges.Latte2/UIMacros.renderSnippets3.phpt +++ /dev/null @@ -1,73 +0,0 @@ -')) { - Tester\Environment::skip('Test for Latte 2'); -} - - -class TestControl extends Nette\Application\UI\Control -{ - public function render() - { - $latte = new Latte\Engine; - $latte->setLoader(new Latte\Loaders\StringLoader); - UIMacros::install($latte->getCompiler()); - $latte->addProvider('uiPresenter', $this->getPresenter()); - $latte->addProvider('uiControl', $this); - $latte->addProvider('snippetBridge', new Nette\Bridges\ApplicationLatte\SnippetBridge($this)); - $latte->render('{snippet foo}hello{/snippet}'); - } -} - -class TestPresenter extends Nette\Application\UI\Presenter -{ - public function createComponentTest() - { - return new TestControl; - } - - - public function render() - { - $latte = new Latte\Engine; - $latte->setLoader(new Latte\Loaders\StringLoader); - UIMacros::install($latte->getCompiler()); - $latte->addProvider('uiControl', $this); - $latte->addProvider('snippetBridge', new Nette\Bridges\ApplicationLatte\SnippetBridge($this)); - $latte->render('{snippet foo}{control test}{/snippet}'); - } -} - - -$presenter = new TestPresenter; -$presenter->snippetMode = true; -$presenter->redrawControl('foo'); -$presenter['test']->redrawControl('foo'); -$presenter->render(); -Assert::same([ - 'snippets' => [ - 'snippet--foo' => '
hello
', - ], -], (array) $presenter->payload); - - -$presenter = new TestPresenter; -$presenter->snippetMode = true; -$presenter['test']->redrawControl('foo'); -$presenter->render(); -Assert::same([ - 'snippets' => [ - 'snippet-test-foo' => 'hello', - ], -], (array) $presenter->payload); diff --git a/tests/Bridges.Latte2/UIMacros.renderSnippets4.phpt b/tests/Bridges.Latte2/UIMacros.renderSnippets4.phpt deleted file mode 100644 index 917404369..000000000 --- a/tests/Bridges.Latte2/UIMacros.renderSnippets4.phpt +++ /dev/null @@ -1,54 +0,0 @@ -')) { - Tester\Environment::skip('Test for Latte 2'); -} - - - -class TestPresenter extends Nette\Application\UI\Presenter -{ - public function render() - { - $latte = new Latte\Engine; - $latte->setLoader(new Latte\Loaders\StringLoader); - UIMacros::install($latte->getCompiler()); - $latte->addProvider('uiControl', $this); - $latte->addProvider('snippetBridge', new SnippetBridge($this)); - $latte->render('{snippet foo}{php $presenter->renderFoo()}{/snippet}', ['presenter' => $this]); - } - - - public function renderFoo() - { - $latte = new Latte\Engine; - $latte->setLoader(new Latte\Loaders\StringLoader); - UIMacros::install($latte->getCompiler()); - $latte->addProvider('uiControl', $this); - $latte->addProvider('snippetBridge', new SnippetBridge($this)); - $latte->render('Hello'); - } -} - - -$presenter = new TestPresenter; -$presenter->snippetMode = true; -$presenter->redrawControl('foo'); -$presenter->render(); -Assert::same([ - 'snippets' => [ - 'snippet--foo' => 'Hello', - ], -], (array) $presenter->payload); diff --git a/tests/Bridges.Latte2/UIMacros.renderSnippets5.phpt b/tests/Bridges.Latte2/UIMacros.renderSnippets5.phpt deleted file mode 100644 index a915657fe..000000000 --- a/tests/Bridges.Latte2/UIMacros.renderSnippets5.phpt +++ /dev/null @@ -1,59 +0,0 @@ -')) { - Tester\Environment::skip('Test for Latte 2'); -} - - - -class TestPresenter extends Nette\Application\UI\Presenter -{ - public function render(string $template) - { - $latte = new Latte\Engine; - $latte->setLoader(new Latte\Loaders\StringLoader); - UIMacros::install($latte->getCompiler()); - $latte->addProvider('uiControl', $this); - $latte->addProvider('snippetBridge', new SnippetBridge($this)); - $latte->render($template, ['presenter' => $this]); - } -} - - -$presenter = new TestPresenter; -$presenter->snippetMode = true; -$presenter->redrawControl('foo'); -$presenter->render('
Hello
'); -Assert::same([ - 'snippets' => [ - 'snippet--foo' => 'Hello', - ], -], (array) $presenter->payload); - - -$presenter = new TestPresenter; -$presenter->snippetMode = true; -$presenter->redrawControl('foo'); -Assert::exception(function () use ($presenter) { - $presenter->render('
Hello
'); -}, Latte\CompileException::class, 'Cannot combine HTML attribute id with n:snippet.'); - - -$presenter = new TestPresenter; -$presenter->snippetMode = true; -$presenter->redrawControl('foo'); -Assert::exception(function () use ($presenter) { - $presenter->render('
Hello
'); -}, Latte\CompileException::class, 'Cannot combine HTML attribute id with n:snippet.'); diff --git a/tests/Bridges.Latte2/UIMacros.renderSnippets6.phpt b/tests/Bridges.Latte2/UIMacros.renderSnippets6.phpt deleted file mode 100644 index dc4e32a25..000000000 --- a/tests/Bridges.Latte2/UIMacros.renderSnippets6.phpt +++ /dev/null @@ -1,45 +0,0 @@ -')) { - Tester\Environment::skip('Test for Latte 2'); -} - - -class TestPresenter extends Nette\Application\UI\Presenter -{ - public function render(string $template) - { - $latte = new Latte\Engine; - $latte->setLoader(new Latte\Loaders\StringLoader); - UIMacros::install($latte->getCompiler()); - $latte->addProvider('snippetBridge', new Nette\Bridges\ApplicationLatte\SnippetBridge($this)); - $latte->onCompile[] = function ($latte) { - $latte->getCompiler()->getMacros()['snippet'][0]->snippetAttribute = 'data-snippet'; - }; - $latte->render($template); - } -} - - -$presenter = new TestPresenter; -ob_start(); -$presenter->render('
hello
'); -$content = ob_get_clean(); -Assert::same('
hello
', $content); - - -$presenter = new TestPresenter; -Assert::exception(function () use ($presenter) { - $presenter->render('
hello
'); -}, Latte\CompileException::class, 'Cannot combine HTML attribute data-snippet with n:snippet.'); diff --git a/tests/Bridges.Latte2/expected/UIMacros.isLinkCurrent.php b/tests/Bridges.Latte2/expected/UIMacros.isLinkCurrent.php deleted file mode 100644 index 15ef390af..000000000 --- a/tests/Bridges.Latte2/expected/UIMacros.isLinkCurrent.php +++ /dev/null @@ -1,22 +0,0 @@ -%A%n:href before n:class - -n:href after n:class - -href before n:class - -href after n:class - -'; - if ($this->global->uiPresenter->getLastCreatedRequestFlag("current")) { - echo 'empty'; - } - echo ' - -'; - if ($this->global->uiPresenter->isLinkCurrent("default")) { - echo 'default'; - } - echo ' - -global->fn->isLinkCurrent)('default') ? 'current' : null%A%>custom function -%A% diff --git a/tests/Bridges.Latte2/expected/UIMacros.renderSnippets.html b/tests/Bridges.Latte2/expected/UIMacros.renderSnippets.html deleted file mode 100644 index 3ff3683ed..000000000 --- a/tests/Bridges.Latte2/expected/UIMacros.renderSnippets.html +++ /dev/null @@ -1,20 +0,0 @@ -
Hello
world! - -

Included file #3 (A, B)

-
- -
Value 1
-
Value 2
-
Value 3
-
- -
Value 1
-
Value 2
-
Value 3
- - -
Hello include snippet
- - -
Foo
-
diff --git a/tests/Bridges.Latte3/templates/include3.latte b/tests/Bridges.Latte3/templates/include3.latte deleted file mode 100644 index 14caf4cb4..000000000 --- a/tests/Bridges.Latte3/templates/include3.latte +++ /dev/null @@ -1 +0,0 @@ -

Included file #3 ({$localvar}, {$hello})

diff --git a/tests/Bridges.Latte3/templates/snippet-include.latte b/tests/Bridges.Latte3/templates/snippet-include.latte deleted file mode 100644 index 8a1ec964d..000000000 --- a/tests/Bridges.Latte3/templates/snippet-include.latte +++ /dev/null @@ -1,25 +0,0 @@ -{snippet hello}Hello{/snippet} world! - -{snippet include} - {include include3.latte localvar => 'A', hello => 'B'} -{/snippet} - -{snippet array} - {foreach [1, 2, 3] as $id} - {snippet "array-$id"}Value {$id}{/snippet} - {/foreach} -{/snippet} - -{snippetArea array2} - {foreach [1, 2, 3] as $id} - {snippet "array2-$id"}Value {$id}{/snippet} - {/foreach} -{/snippetArea} - -{snippetArea foo} - {include snippet-included.latte say => 'Hello include snippet'} -{/snippetArea} - -{snippet nested1} - {snippet nested2}Foo{/snippet} -{/snippet} diff --git a/tests/Bridges.Latte3/templates/snippet-included.latte b/tests/Bridges.Latte3/templates/snippet-included.latte deleted file mode 100644 index a392b6227..000000000 --- a/tests/Bridges.Latte3/templates/snippet-included.latte +++ /dev/null @@ -1 +0,0 @@ -{snippet includeSay}{$say}{/snippet} From 8ac5dc3b5961720fb343364b713d2a52bc97ecb0 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 23 Dec 2025 23:26:12 +0100 Subject: [PATCH 14/37] removed deprecated {ifCurrent} --- .../ApplicationLatte/Nodes/IfCurrentNode.php | 70 ------------------- src/Bridges/ApplicationLatte/UIExtension.php | 1 - tests/Bridges.Latte/{ifCurrent}.phpt | 31 -------- 3 files changed, 102 deletions(-) delete mode 100644 src/Bridges/ApplicationLatte/Nodes/IfCurrentNode.php delete mode 100644 tests/Bridges.Latte/{ifCurrent}.phpt diff --git a/src/Bridges/ApplicationLatte/Nodes/IfCurrentNode.php b/src/Bridges/ApplicationLatte/Nodes/IfCurrentNode.php deleted file mode 100644 index dd1c6813f..000000000 --- a/src/Bridges/ApplicationLatte/Nodes/IfCurrentNode.php +++ /dev/null @@ -1,70 +0,0 @@ -position->line})", E_USER_DEPRECATED); - $node = $tag->node = new static; - if (!$tag->parser->isEnd()) { - $node->destination = $tag->parser->parseUnquotedStringOrExpression(); - $tag->parser->stream->tryConsume(','); - $node->args = $tag->parser->parseArguments(); - } - - [$node->content] = yield; - return $node; - } - - - public function print(PrintContext $context): string - { - return $this->destination - ? $context->format( - 'if ($this->global->uiPresenter->isLinkCurrent(%node, %args?)) { %node } ', - $this->destination, - $this->args, - $this->content, - ) - : $context->format( - 'if ($this->global->uiPresenter->getLastCreatedRequestFlag("current")) { %node } ', - $this->content, - ); - } - - - public function &getIterator(): \Generator - { - if ($this->destination) { - yield $this->destination; - yield $this->args; - } - yield $this->content; - } -} diff --git a/src/Bridges/ApplicationLatte/UIExtension.php b/src/Bridges/ApplicationLatte/UIExtension.php index 2805dff96..6851803ff 100644 --- a/src/Bridges/ApplicationLatte/UIExtension.php +++ b/src/Bridges/ApplicationLatte/UIExtension.php @@ -80,7 +80,6 @@ public function getTags(): array 'plink' => Nodes\LinkNode::create(...), 'link' => Nodes\LinkNode::create(...), 'linkBase' => Nodes\LinkBaseNode::create(...), - 'ifCurrent' => Nodes\IfCurrentNode::create(...), 'templatePrint' => Nodes\TemplatePrintNode::create(...), 'snippet' => Nodes\SnippetNode::create(...), 'snippetArea' => Nodes\SnippetAreaNode::create(...), diff --git a/tests/Bridges.Latte/{ifCurrent}.phpt b/tests/Bridges.Latte/{ifCurrent}.phpt deleted file mode 100644 index 0be5f05e2..000000000 --- a/tests/Bridges.Latte/{ifCurrent}.phpt +++ /dev/null @@ -1,31 +0,0 @@ -shouldReceive('getPresenterIfExists')->andReturn($presenter); -$presenter->shouldReceive('getHttpResponse')->andReturn((Mockery::mock(Nette\Http\IResponse::class))->shouldIgnoreMissing()); -$presenter->shouldIgnoreMissing(); - -$latte = new Latte\Engine; -$latte->setLoader(new Latte\Loaders\StringLoader); -$latte->addExtension(new Nette\Bridges\ApplicationLatte\UIExtension($presenter)); - -Assert::matchFile( - __DIR__ . '/expected/ifCurrent.php', - @$latte->compile( // is deprecated - <<<'XX' - {ifCurrent}empty{/ifCurrent} - - {ifCurrent default}default{/ifCurrent} - XX, - ), -); From 9638728cdd998e47044f6349169ac28b6915beb3 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 24 Nov 2025 04:19:06 +0100 Subject: [PATCH 15/37] requires Latte 3.1 --- composer.json | 4 ++-- .../ApplicationLatte/Nodes/ControlNode.php | 8 +++---- .../ApplicationLatte/Nodes/LinkNode.php | 1 - .../Bridges.Latte/expected/isLinkCurrent.php | 20 ++++++++-------- .../expected/n-snippet.block.php | 14 +++++------ .../expected/n-snippet.dynamic.php | 10 ++++---- tests/Bridges.Latte/expected/n-snippet.php | 23 ++++++++++--------- .../expected/snippet.dynamic.php | 10 ++++---- .../expected/snippet.dynamic2.php | 10 ++++---- tests/Bridges.Latte/expected/snippet.php | 22 +++++++++--------- tests/Bridges.Latte/{control}.3.phpt | 2 +- tests/Bridges.Latte/{control}.phpt | 22 ++++++++---------- 12 files changed, 70 insertions(+), 76 deletions(-) diff --git a/composer.json b/composer.json index bc3eeddc6..51ec77a45 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "nette/forms": "^3.2", "nette/robot-loader": "^4.0", "nette/security": "^3.2", - "latte/latte": "^3.0.18", + "latte/latte": "^3.1", "tracy/tracy": "^2.11", "mockery/mockery": "^1.6@stable", "phpstan/phpstan-nette": "^2.0@stable", @@ -42,7 +42,7 @@ "nette/di": "<3.2", "nette/forms": "<3.2", "nette/schema": "<1.3", - "latte/latte": "<3.0.18 || >=3.2", + "latte/latte": "<3.1 || >=3.2", "tracy/tracy": "<2.11" }, "autoload": { diff --git a/src/Bridges/ApplicationLatte/Nodes/ControlNode.php b/src/Bridges/ApplicationLatte/Nodes/ControlNode.php index 2a28c4640..bd05f3979 100644 --- a/src/Bridges/ApplicationLatte/Nodes/ControlNode.php +++ b/src/Bridges/ApplicationLatte/Nodes/ControlNode.php @@ -64,11 +64,9 @@ public static function create(Tag $tag): static } $modifier = $tag->parser->parseModifier(); - foreach ($modifier->filters as $filter) { - match ($filter->name->name) { - 'noescape' => $node->escape = false, - default => throw new Latte\CompileException('Only modifier |noescape is allowed here.', $tag->position), - }; + $node->escape = $modifier->removeFilter('noescape') ? false : null; + if ($modifier->filters) { + throw new Latte\CompileException('Only modifier |noescape is allowed here.', reset($modifier->filters)->position); } return $node; diff --git a/src/Bridges/ApplicationLatte/Nodes/LinkNode.php b/src/Bridges/ApplicationLatte/Nodes/LinkNode.php index e4320167b..aba74a25d 100644 --- a/src/Bridges/ApplicationLatte/Nodes/LinkNode.php +++ b/src/Bridges/ApplicationLatte/Nodes/LinkNode.php @@ -40,7 +40,6 @@ public static function create(Tag $tag): ?static $node->args = $tag->parser->parseArguments(); $node->modifier = $tag->parser->parseModifier(); $node->modifier->escape = true; - $node->modifier->check = false; $node->mode = $tag->name; if ($tag->isNAttribute()) { diff --git a/tests/Bridges.Latte/expected/isLinkCurrent.php b/tests/Bridges.Latte/expected/isLinkCurrent.php index bfb0c72d3..145117989 100644 --- a/tests/Bridges.Latte/expected/isLinkCurrent.php +++ b/tests/Bridges.Latte/expected/isLinkCurrent.php @@ -1,32 +1,32 @@ %A% echo 'isLinkCurrent() ? 'current' : null])) ? ' class="' . LR\%a%Attr(implode(" ", array_unique($ʟ_tmp))) . '"' : "" /* %a% */; + echo ($ʟ_tmp = array_filter([$presenter->isLinkCurrent() ? 'current' : null])) ? ' class="' . LR\HtmlHelpers::escapeAttr(implode(" ", array_unique($ʟ_tmp))) . '"' : "" /* pos 1:21 */; echo '>n:href before n:class isLinkCurrent() ? 'current' : null])) ? ' class="' . LR\%a%Attr(implode(" ", array_unique($ʟ_tmp))) . '"' : "" /* %a% */; + echo ($ʟ_tmp = array_filter([$presenter->isLinkCurrent() ? 'current' : null])) ? ' class="' . LR\HtmlHelpers::escapeAttr(implode(" ", array_unique($ʟ_tmp))) . '"' : "" /* pos 3:4 */; echo '>n:href after n:class isLinkCurrent() ? 'current' : null])) ? ' class="' . LR\%a%Attr(implode(" ", array_unique($ʟ_tmp))) . '"' : "" /* %a% */; + echo ($ʟ_tmp = array_filter([$presenter->isLinkCurrent() ? 'current' : null])) ? ' class="' . LR\HtmlHelpers::escapeAttr(implode(" ", array_unique($ʟ_tmp))) . '"' : "" /* pos 5:26 */; echo '>href before n:class isLinkCurrent() ? 'current' : null])) ? ' class="' . LR\%a%Attr(implode(" ", array_unique($ʟ_tmp))) . '"' : "" /* %a% */; + echo ($ʟ_tmp = array_filter([$presenter->isLinkCurrent() ? 'current' : null])) ? ' class="' . LR\HtmlHelpers::escapeAttr(implode(" ", array_unique($ʟ_tmp))) . '"' : "" /* pos 7:4 */; echo ' href="'; - echo LR\%a%Attr($this->global->uiControl->link('default')) /* %a% */; + echo LR\HtmlHelpers::escapeAttr($this->global->uiControl->link('default')) /* pos 7:58 */; echo '">href after n:class global->fn->isLinkCurrent)(%a%'default') ? 'current' : null])) ? ' class="' . LR\%a%Attr(implode(" ", array_unique($ʟ_tmp))) . '"' : "" /* %a% */; + echo ($ʟ_tmp = array_filter([($this->global->fn->isLinkCurrent)($this, 'default') ? 'current' : null])) ? ' class="' . LR\HtmlHelpers::escapeAttr(implode(" ", array_unique($ʟ_tmp))) . '"' : "" /* pos 9:4 */; echo '>custom function '; %A% diff --git a/tests/Bridges.Latte/expected/n-snippet.block.php b/tests/Bridges.Latte/expected/n-snippet.block.php index c2a978759..156b09d4d 100644 --- a/tests/Bridges.Latte/expected/n-snippet.block.php +++ b/tests/Bridges.Latte/expected/n-snippet.block.php @@ -11,12 +11,12 @@ final class Template%a% extends Latte\Runtime\Template public function main(array $ʟ_args): void { %A% - $this->renderBlock('block1', get_defined_vars()) /* %a% */; + $this->renderBlock('block1', get_defined_vars()) /* pos %d%:26 */; echo ' '; echo '
'; - $this->renderBlock('outer', [], null, 'snippet') /* %a% */; + $this->renderBlock('outer', [], null, 'snippet') /* pos %d%:1 */; echo '
'; } @@ -31,7 +31,7 @@ public function blockBlock1(array $ʟ_args): void echo ''; - $this->renderBlock('snippet', [], null, 'snippet') /* %a% */; + $this->renderBlock('snippet', [], null, 'snippet') /* pos %d%:6 */; echo '
'; } @@ -44,7 +44,7 @@ public function blockSnippet(array $ʟ_args): void extract($ʟ_args); unset($ʟ_args); - $this->global->snippetDriver->enter('snippet', 'static') /* %a% */; + $this->global->snippetDriver->enter('snippet', 'static') /* pos %d%:6 */; try { echo ' static @@ -63,12 +63,12 @@ public function blockOuter(array $ʟ_args): void extract($ʟ_args); unset($ʟ_args); - $this->global->snippetDriver->enter('outer', 'static') /* %a% */; + $this->global->snippetDriver->enter('outer', 'static') /* pos %d%:1 */; try { echo ' begin '; - $this->renderBlock('block2', get_defined_vars()) /* %a% */; + $this->renderBlock('block2', get_defined_vars()) /* pos %d%:30 */; echo 'end '; @@ -88,7 +88,7 @@ public function blockBlock2(array $ʟ_args): void echo ''; - $this->global->snippetDriver->enter($ʟ_nm, 'dynamic') /* %a% */; + $this->global->snippetDriver->enter($ʟ_nm, 'dynamic') /* pos %d%:6 */; try { echo ' dynamic diff --git a/tests/Bridges.Latte/expected/n-snippet.dynamic.php b/tests/Bridges.Latte/expected/n-snippet.dynamic.php index 2383a6003..815837a6e 100644 --- a/tests/Bridges.Latte/expected/n-snippet.dynamic.php +++ b/tests/Bridges.Latte/expected/n-snippet.dynamic.php @@ -12,7 +12,7 @@ public function main(array $ʟ_args): void %A% echo ' '; echo '
'; - $this->renderBlock('outer1', [], null, 'snippet') /* %a% */; + $this->renderBlock('outer1', [], null, 'snippet') /* pos 1:2 */; echo '
'; } @@ -30,18 +30,18 @@ public function blockOuter1(array $ʟ_args): void extract($ʟ_args); unset($ʟ_args); - $this->global->snippetDriver->enter('outer1', 'static') /* %a% */; + $this->global->snippetDriver->enter('outer1', 'static') /* pos 1:2 */; try { echo "\n"; - foreach ([1, 2, 3] as $id) /* %a% */ { + foreach ([1, 2, 3] as $id) /* pos 2:2 */ { echo ' '; - $this->global->snippetDriver->enter($ʟ_nm, 'dynamic') /* %a% */; + $this->global->snippetDriver->enter($ʟ_nm, 'dynamic') /* pos 3:8 */; try { echo ' #'; - echo LR\%a%Text($id) /* %a% */; + echo LR\HtmlHelpers::escapeText($id) /* pos 4:6 */; echo ' '; diff --git a/tests/Bridges.Latte/expected/n-snippet.php b/tests/Bridges.Latte/expected/n-snippet.php index fcd2af5e2..399056402 100644 --- a/tests/Bridges.Latte/expected/n-snippet.php +++ b/tests/Bridges.Latte/expected/n-snippet.php @@ -13,25 +13,26 @@ public function main(array $ʟ_args): void echo '
'; - $this->renderBlock('outer', [], null, 'snippet') /* %a% */; + $this->renderBlock('outer', [], null, 'snippet') /* pos %d%:20 */; echo '
renderBlock('gallery', [], null, 'snippet') /* %a% */; + echo LR\HtmlHelpers::formatListAttribute(' class', 'class') /* pos %d%:36 */; + echo '>'; + $this->renderBlock('gallery', [], null, 'snippet') /* pos %d%:7 */; echo '
'; - $this->renderBlock('script', [], null, 'snippet') /* %a% */; + $this->renderBlock('script', [], null, 'snippet') /* pos %d%:10 */; echo ' '; - $this->renderBlock('hello', [], null, 'snippet') /* %a% */; + $this->renderBlock('hello', [], null, 'snippet') /* pos %d%:10 */; echo ' '; } @@ -44,7 +45,7 @@ public function blockOuter(array $ʟ_args): void extract($ʟ_args); unset($ʟ_args); - $this->global->snippetDriver->enter('outer', 'static') /* %a% */; + $this->global->snippetDriver->enter('outer', 'static') /* pos %d%:20 */; try { echo '

Outer

@@ -63,7 +64,7 @@ public function blockGallery(array $ʟ_args): void extract($ʟ_args); unset($ʟ_args); - $this->global->snippetDriver->enter('gallery', 'static') /* %a% */; + $this->global->snippetDriver->enter('gallery', 'static') /* pos %d%:7 */; try { } finally { $this->global->snippetDriver->leave(); @@ -78,9 +79,9 @@ public function blockScript(array $ʟ_args): void extract($ʟ_args); unset($ʟ_args); - $this->global->snippetDriver->enter('script', 'static') /* %a% */; + $this->global->snippetDriver->enter('script', 'static') /* pos %d%:10 */; try { - echo LR\%a%::escapeJs('x') /* %a% */; + echo LR\Helpers::escapeJs('x') /* pos %d%:29 */; } finally { $this->global->snippetDriver->leave(); @@ -95,9 +96,9 @@ public function blockHello(array $ʟ_args): void extract($ʟ_args); unset($ʟ_args); - $this->global->snippetDriver->enter('hello', 'static') /* %a% */; + $this->global->snippetDriver->enter('hello', 'static') /* pos %d%:10 */; try { - echo LR\%a%::escapeJs('y') /* %a% */; + echo LR\Helpers::escapeJs('y') /* pos %d%:32 */; } finally { $this->global->snippetDriver->leave(); diff --git a/tests/Bridges.Latte/expected/snippet.dynamic.php b/tests/Bridges.Latte/expected/snippet.dynamic.php index c52e4b68a..27dbe6818 100644 --- a/tests/Bridges.Latte/expected/snippet.dynamic.php +++ b/tests/Bridges.Latte/expected/snippet.dynamic.php @@ -12,7 +12,7 @@ public function main(array $ʟ_args): void %A% echo ' '; echo '
'; - $this->renderBlock('outer', [], null, 'snippet') /* %a% */; + $this->renderBlock('outer', [], null, 'snippet') /* pos %d%:2 */; echo '
'; } @@ -30,17 +30,17 @@ public function blockOuter(array $ʟ_args): void extract($ʟ_args); unset($ʟ_args); - $this->global->snippetDriver->enter('outer', 'static') /* %a% */; + $this->global->snippetDriver->enter('outer', 'static') /* pos %d%:2 */; try { echo "\n"; - foreach ([1, 2, 3] as $id) /* %a% */ { + foreach ([1, 2, 3] as $id) /* pos %d%:2 */ { echo ' '; echo '
'; - $this->global->snippetDriver->enter($ʟ_nm, 'dynamic') /* %a% */; + $this->global->snippetDriver->enter($ʟ_nm, 'dynamic') /* pos %d%:3 */; try { echo ' #'; - echo LR\%a%Text($id) /* %a% */; + echo LR\HtmlHelpers::escapeText($id) /* pos %d%:6 */; echo ' '; diff --git a/tests/Bridges.Latte/expected/snippet.dynamic2.php b/tests/Bridges.Latte/expected/snippet.dynamic2.php index b2d33178b..339c016a9 100644 --- a/tests/Bridges.Latte/expected/snippet.dynamic2.php +++ b/tests/Bridges.Latte/expected/snippet.dynamic2.php @@ -12,7 +12,7 @@ public function main(array $ʟ_args): void %A% echo ' '; echo '
'; - $this->renderBlock('outer', [], null, 'snippet') /* %a% */; + $this->renderBlock('outer', [], null, 'snippet') /* pos %d%:2 */; echo '
'; } @@ -30,17 +30,17 @@ public function blockOuter(array $ʟ_args): void extract($ʟ_args); unset($ʟ_args); - $this->global->snippetDriver->enter('outer', 'static') /* %a% */; + $this->global->snippetDriver->enter('outer', 'static') /* pos %d%:2 */; try { echo "\n"; - foreach ([1, 2, 3] as $id) /* %a% */ { + foreach ([1, 2, 3] as $id) /* pos %d%:2 */ { echo ' '; echo '
'; - $this->global->snippetDriver->enter($ʟ_nm, 'dynamic') /* %a% */; + $this->global->snippetDriver->enter($ʟ_nm, 'dynamic') /* pos %d%:3 */; try { echo ' #'; - echo LR\%a%Text($id) /* %a% */; + echo LR\HtmlHelpers::escapeText($id) /* pos %d%:6 */; echo ' '; diff --git a/tests/Bridges.Latte/expected/snippet.php b/tests/Bridges.Latte/expected/snippet.php index 014f2d5ef..7baf9c070 100644 --- a/tests/Bridges.Latte/expected/snippet.php +++ b/tests/Bridges.Latte/expected/snippet.php @@ -12,32 +12,32 @@ public function main(array $ʟ_args): void %A% echo ' '; echo '
'; - $this->renderBlock('', [], null, 'snippet') /* %a% */; + $this->renderBlock('', [], null, 'snippet') /* pos %d%:2 */; echo '
'; echo '
'; - $this->renderBlock('outer', [], null, 'snippet') /* %a% */; + $this->renderBlock('outer', [], null, 'snippet') /* pos %d%:2 */; echo '
@'; - if (true) /* %a% */ { + if (true) /* pos %d%:3 */ { echo ' Hello World @'; } echo ' '; echo '
'; - $this->renderBlock('title', [], null, 'snippet') /* %a% */; + $this->renderBlock('title', [], null, 'snippet') /* pos %d%:2 */; echo '
'; echo '
'; - $this->renderBlock('title2', [], null, 'snippet') /* %a% */; + $this->renderBlock('title2', [], null, 'snippet') /* pos %d%:2 */; echo '
'; } @@ -49,7 +49,7 @@ public function block1(array $ʟ_args): void extract($ʟ_args); unset($ʟ_args); - $this->global->snippetDriver->enter('', 'static') /* %a% */; + $this->global->snippetDriver->enter('', 'static') /* pos %d%:2 */; try { echo ' @@ -68,13 +68,13 @@ public function blockOuter(array $ʟ_args): void extract($ʟ_args); unset($ʟ_args); - $this->global->snippetDriver->enter('outer', 'static') /* %a% */; + $this->global->snippetDriver->enter('outer', 'static') /* pos %d%:2 */; try { echo ' Outer '; echo '
'; - $this->renderBlock('inner', [], null, 'snippet') /* %a% */; + $this->renderBlock('inner', [], null, 'snippet') /* pos %d%:3 */; echo '
/Outer '; @@ -92,7 +92,7 @@ public function blockInner(array $ʟ_args): void extract($ʟ_args); unset($ʟ_args); - $this->global->snippetDriver->enter('inner', 'static') /* %a% */; + $this->global->snippetDriver->enter('inner', 'static') /* pos %d%:3 */; try { echo 'Inner'; @@ -109,7 +109,7 @@ public function blockTitle(array $ʟ_args): void extract($ʟ_args); unset($ʟ_args); - $this->global->snippetDriver->enter('title', 'static') /* %a% */; + $this->global->snippetDriver->enter('title', 'static') /* pos %d%:2 */; try { echo 'Title 1'; @@ -126,7 +126,7 @@ public function blockTitle2(array $ʟ_args): void extract($ʟ_args); unset($ʟ_args); - $this->global->snippetDriver->enter('title2', 'static') /* %a% */; + $this->global->snippetDriver->enter('title2', 'static') /* pos %d%:2 */; try { echo 'Title 2'; diff --git a/tests/Bridges.Latte/{control}.3.phpt b/tests/Bridges.Latte/{control}.3.phpt index 59a41fd48..b610ba991 100644 --- a/tests/Bridges.Latte/{control}.3.phpt +++ b/tests/Bridges.Latte/{control}.3.phpt @@ -49,5 +49,5 @@ Assert::same( Assert::exception( fn() => $latte->renderToString(''), Latte\RuntimeException::class, - 'Filters: unable to convert content type HTML to HTML/%a?%CSS', + 'Filters: unable to convert content type HTML to HTML/RAW/CSS', ); diff --git a/tests/Bridges.Latte/{control}.phpt b/tests/Bridges.Latte/{control}.phpt index ed3772729..678527d9b 100644 --- a/tests/Bridges.Latte/{control}.phpt +++ b/tests/Bridges.Latte/{control}.phpt @@ -21,7 +21,7 @@ Assert::match( %A% $ʟ_tmp = $this->global->uiControl->getComponent('form'); if ($ʟ_tmp instanceof Nette\Application\UI\Renderable) $ʟ_tmp->redrawControl(null, false); - $ʟ_tmp->render() /* %a% */; + $ʟ_tmp->render() /* pos 1:1 */; %A% XX, $latte->compile('{control form}'), @@ -32,7 +32,7 @@ Assert::match( %A% if (!is_object($ʟ_tmp = $form)) $ʟ_tmp = $this->global->uiControl->getComponent($ʟ_tmp); if ($ʟ_tmp instanceof Nette\Application\UI\Renderable) $ʟ_tmp->redrawControl(null, false); - $ʟ_tmp->render() /* %a% */; + $ʟ_tmp->render() /* pos 1:1 */; %A% XX, $latte->compile('{control $form}'), @@ -41,9 +41,7 @@ Assert::match( Assert::match( <<<'XX' %A% - $ʟ_tmp = $this->global->uiControl->getComponent('form'); - %A% - $ʟ_tmp->renderType() /* %a% */; + $ʟ_tmp->renderType() /* pos 1:1 */; %A% XX, $latte->compile('{control form:type}'), @@ -52,9 +50,7 @@ Assert::match( Assert::match( <<<'XX' %A% - $ʟ_tmp = $this->global->uiControl->getComponent('form'); - %A% - $ʟ_tmp->{'render' . $type}() /* %a% */; + $ʟ_tmp->{'render' . $type}() /* pos 1:1 */; %A% XX, $latte->compile('{control form:$type}'), @@ -63,7 +59,7 @@ Assert::match( Assert::match( <<<'XX' %A% - $ʟ_tmp->renderType('param') /* %a% */; + $ʟ_tmp->renderType('param') /* pos 1:1 */; %A% XX, $latte->compile('{control form:type param}'), @@ -72,7 +68,7 @@ Assert::match( Assert::match( <<<'XX' %A% - $ʟ_tmp->render(...$params) /* %a% */; + $ʟ_tmp->render(...$params) /* pos 1:1 */; %A% XX, $latte->compile('{control form (expand) $params}'), @@ -81,7 +77,7 @@ Assert::match( Assert::match( <<<'XX' %A% - $ʟ_tmp->renderType(['param' => 123]) /* %a% */; + $ʟ_tmp->renderType(['param' => 123]) /* pos 1:1 */; %A% XX, $latte->compile('{control form:type param => 123}'), @@ -90,7 +86,7 @@ Assert::match( Assert::match( <<<'XX' %A% - $ʟ_tmp->renderType(param: 123) /* %a% */; + $ʟ_tmp->renderType(param: 123) /* pos 1:1 */; %A% XX, $latte->compile('{control form:type, param: 123}'), @@ -99,7 +95,7 @@ Assert::match( Assert::match( <<<'XX' %A% - $ʟ_tmp->renderType(param: 123) /* %a% */; + $ʟ_tmp->renderType(param: 123) /* pos 1:1 */; %A% XX, $latte->compile('{control form:type, param: 123}'), From 1ad2c23c636d7ce04f239ff1e58a32032abcd6ea Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 30 Nov 2025 23:10:23 +0100 Subject: [PATCH 16/37] LatteFactory: $control is passed to create() (BC break) --- src/Bridges/ApplicationDI/LatteExtension.php | 2 +- src/Bridges/ApplicationLatte/LatteFactory.php | 3 ++- src/Bridges/ApplicationLatte/TemplateFactory.php | 5 ----- tests/Bridges.DI/LatteExtension.basic.phpt | 14 -------------- tests/Bridges.DI/LatteExtension.translator.phpt | 2 +- 5 files changed, 4 insertions(+), 22 deletions(-) diff --git a/src/Bridges/ApplicationDI/LatteExtension.php b/src/Bridges/ApplicationDI/LatteExtension.php index c2661d306..b7939d848 100644 --- a/src/Bridges/ApplicationDI/LatteExtension.php +++ b/src/Bridges/ApplicationDI/LatteExtension.php @@ -63,7 +63,7 @@ public function loadConfiguration(): void ->addSetup('setStrictParsing', [$config->strictParsing]) ->addSetup('enablePhpLinter', [$config->phpLinter]) ->addSetup('setLocale', [$config->locale]) - ->addSetup('?', [$builder::literal('func_num_args() && $service->addExtension(new Nette\Bridges\ApplicationLatte\UIExtension(func_get_arg(0)))')]); + ->addSetup('addExtension', [new Statement(ApplicationLatte\UIExtension::class, [$builder::literal('$control')])]); if ($builder->getByType(Nette\Caching\Storage::class)) { $this->addExtension(new Statement(Nette\Bridges\CacheLatte\CacheExtension::class)); diff --git a/src/Bridges/ApplicationLatte/LatteFactory.php b/src/Bridges/ApplicationLatte/LatteFactory.php index e6d2c8842..71e1ced0c 100644 --- a/src/Bridges/ApplicationLatte/LatteFactory.php +++ b/src/Bridges/ApplicationLatte/LatteFactory.php @@ -10,11 +10,12 @@ namespace Nette\Bridges\ApplicationLatte; use Latte; +use Nette\Application\UI\Control; interface LatteFactory { - function create(/*?Control $control = null*/): Latte\Engine; + function create(?Control $control = null): Latte\Engine; } diff --git a/src/Bridges/ApplicationLatte/TemplateFactory.php b/src/Bridges/ApplicationLatte/TemplateFactory.php index c0a133347..2fba1dc06 100644 --- a/src/Bridges/ApplicationLatte/TemplateFactory.php +++ b/src/Bridges/ApplicationLatte/TemplateFactory.php @@ -52,11 +52,6 @@ public function createTemplate(?UI\Control $control = null, ?string $class = nul $latte = $this->latteFactory->create($control); $template = new $class($latte); - - if (!Nette\Utils\Arrays::some($latte->getExtensions(), fn($e) => $e instanceof UIExtension)) { - $latte->addExtension(new UIExtension($control)); - } - $this->injectDefaultVariables($template, $control); Nette\Utils\Arrays::invoke($this->onCreate, $template); diff --git a/tests/Bridges.DI/LatteExtension.basic.phpt b/tests/Bridges.DI/LatteExtension.basic.phpt index 34ea641d5..a3a13d48f 100644 --- a/tests/Bridges.DI/LatteExtension.basic.phpt +++ b/tests/Bridges.DI/LatteExtension.basic.phpt @@ -59,20 +59,6 @@ Assert::type(Nette\Bridges\ApplicationLatte\LatteFactory::class, $container->get $latte = $container->getService('nette.latteFactory')->create(); $extensions = Assert::with($latte, fn() => $this->extensions); -Assert::equal([ - new Latte\Essential\CoreExtension, - new Latte\Sandbox\SandboxExtension, - new Nette\Bridges\FormsLatte\FormsExtension, - new MyExtension, - new MyExtension(1), - new MyExtension(2), - new MyExtension, -], $extensions); - -// UIExtension is added -$latte = $container->getService('nette.latteFactory')->create(null); -$extensions = Assert::with($latte, fn() => $this->extensions); - Assert::equal([ new Latte\Essential\CoreExtension, new Latte\Sandbox\SandboxExtension, diff --git a/tests/Bridges.DI/LatteExtension.translator.phpt b/tests/Bridges.DI/LatteExtension.translator.phpt index 031312f30..2a9555ff3 100644 --- a/tests/Bridges.DI/LatteExtension.translator.phpt +++ b/tests/Bridges.DI/LatteExtension.translator.phpt @@ -39,4 +39,4 @@ $container = new Container; $latte = $container->getService('nette.latteFactory')->create(); $extensions = Assert::with($latte, fn() => $this->extensions); -Assert::equal(new Latte\Essential\TranslatorExtension(new Translator), $extensions[3]); +Assert::equal(new Latte\Essential\TranslatorExtension(new Translator), $extensions[4]); From e6c5179b335b98bf517ea5a0f91ab3d02e55f207 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 15 Oct 2021 18:19:42 +0200 Subject: [PATCH 17/37] RouteList: array access is deprecated --- src/Application/Routers/RouteList.php | 10 ++++++++++ tests/Bridges.DI/RoutingExtension.basic.phpt | 7 ++++--- tests/Routers/RouteList.basic.phpt | 3 +-- tests/Routers/RouteList.modules.phpt | 5 ++--- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/Application/Routers/RouteList.php b/src/Application/Routers/RouteList.php index 7481ec812..92f0d73d7 100644 --- a/src/Application/Routers/RouteList.php +++ b/src/Application/Routers/RouteList.php @@ -99,6 +99,13 @@ public function getModule(): ?string */ public function offsetSet($index, $router): void { + if ($router instanceof Route) { + trigger_error('Usage `$router[] = new Route(...)` is deprecated, use `$router->addRoute(...)`.', E_USER_DEPRECATED); + } else { + $class = getclass($router); + trigger_error("Usage `\$router[] = new $class` is deprecated, use `\$router->add(new $class)`.", E_USER_DEPRECATED); + } + if ($index === null) { $this->add($router); } else { @@ -113,6 +120,7 @@ public function offsetSet($index, $router): void */ public function offsetGet($index): Nette\Routing\Router { + trigger_error('Usage `$route = $router[...]` is deprecated, use `$router->getRouters()`.', E_USER_DEPRECATED); if (!$this->offsetExists($index)) { throw new Nette\OutOfRangeException('Offset invalid or out of range'); } @@ -126,6 +134,7 @@ public function offsetGet($index): Nette\Routing\Router */ public function offsetExists($index): bool { + trigger_error('Usage `isset($router[...])` is deprecated.', E_USER_DEPRECATED); return is_int($index) && $index >= 0 && $index < count($this->getRouters()); } @@ -136,6 +145,7 @@ public function offsetExists($index): bool */ public function offsetUnset($index): void { + trigger_error('Usage `unset($router[$index])` is deprecated, use `$router->modify($index, null)`.', E_USER_DEPRECATED); if (!$this->offsetExists($index)) { throw new Nette\OutOfRangeException('Offset invalid or out of range'); } diff --git a/tests/Bridges.DI/RoutingExtension.basic.phpt b/tests/Bridges.DI/RoutingExtension.basic.phpt index a8e13d1a6..0b6f57a72 100644 --- a/tests/Bridges.DI/RoutingExtension.basic.phpt +++ b/tests/Bridges.DI/RoutingExtension.basic.phpt @@ -31,9 +31,10 @@ test('', function () { $container = new Container1; $router = $container->getService('router'); Assert::type(Nette\Application\Routers\RouteList::class, $router); - Assert::same('index.php', $router[0]->getMask()); - Assert::same('item/', $router[1]->getMask()); + $routes = $router->getRouters(); + Assert::same('index.php', $routes[0]->getMask()); + Assert::same('item/', $routes[1]->getMask()); Assert::type(Nette\Application\Routers\RouteList::class, $router); - Assert::type(Nette\Application\Routers\Route::class, $router[0]); + Assert::type(Nette\Application\Routers\Route::class, $routes[0]); }); diff --git a/tests/Routers/RouteList.basic.phpt b/tests/Routers/RouteList.basic.phpt index 22afeda6c..b4783db95 100644 --- a/tests/Routers/RouteList.basic.phpt +++ b/tests/Routers/RouteList.basic.phpt @@ -6,7 +6,6 @@ declare(strict_types=1); -use Nette\Application\Routers\Route; use Nette\Application\Routers\RouteList; use Tester\Assert; @@ -17,7 +16,7 @@ require __DIR__ . '/Route.php'; $list = new RouteList; -$list[] = new Route('//'); +$list->addRoute('//'); Assert::same('http://example.com/front.homepage/', testRouteOut($list, ['presenter' => 'Front:Homepage'])); diff --git a/tests/Routers/RouteList.modules.phpt b/tests/Routers/RouteList.modules.phpt index db2aff0d2..ff1e15193 100644 --- a/tests/Routers/RouteList.modules.phpt +++ b/tests/Routers/RouteList.modules.phpt @@ -6,7 +6,6 @@ declare(strict_types=1); -use Nette\Application\Routers\Route; use Nette\Application\Routers\RouteList; @@ -16,12 +15,12 @@ require __DIR__ . '/Route.php'; $list = new RouteList; -$list[] = new Route('auth/[/]', [ +$list->addRoute('auth/[/]', [ 'module' => 'Auth', 'presenter' => 'Homepage', 'action' => 'default', ]); -$list[] = new Route('[/]', [ +$list->addRoute('[/]', [ 'module' => 'Default', 'presenter' => 'Homepage', 'action' => 'default', From 757795140a01cc95269b4eaf6706fe20e4f606d7 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 21 Jun 2022 15:27:13 +0200 Subject: [PATCH 18/37] Component::getParameter() $default is deprecated --- src/Application/UI/Component.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Application/UI/Component.php b/src/Application/UI/Component.php index 24220d45f..383b84f98 100644 --- a/src/Application/UI/Component.php +++ b/src/Application/UI/Component.php @@ -227,6 +227,7 @@ public function saveStatePartial(array &$params, ComponentReflection $reflection final public function getParameter(string $name): mixed { if (func_num_args() > 1) { + trigger_error(__METHOD__ . '() parameter $default is deprecated, use operator ??', E_USER_DEPRECATED); $default = func_get_arg(1); } return $this->params[$name] ?? $default ?? null; From 54d9c6fabdd615df9c8ac3b52f67a94aae259128 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 19 Apr 2024 19:20:51 +0200 Subject: [PATCH 19/37] LinkGenerator: query part in link is deprecated --- src/Application/LinkGenerator.php | 1 + tests/UI/LinkGenerator.parseDestination().phpt | 4 ++-- tests/UI/Presenter.link().phpt | 12 ++++-------- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/Application/LinkGenerator.php b/src/Application/LinkGenerator.php index 13877f6ec..3ec9709bd 100644 --- a/src/Application/LinkGenerator.php +++ b/src/Application/LinkGenerator.php @@ -245,6 +245,7 @@ public static function parseDestination(string $destination): array } if (!empty($matches['query'])) { + trigger_error("Link format is obsolete, use arguments instead of query string in '$destination'.", E_USER_DEPRECATED); parse_str(substr($matches['query'], 1), $args); } diff --git a/tests/UI/LinkGenerator.parseDestination().phpt b/tests/UI/LinkGenerator.parseDestination().phpt index 5132e99a4..82655a85b 100644 --- a/tests/UI/LinkGenerator.parseDestination().phpt +++ b/tests/UI/LinkGenerator.parseDestination().phpt @@ -48,7 +48,7 @@ Assert::same([ 'signal' => false, 'args' => [], 'fragment' => '#fragment', -], LinkGenerator::parseDestination('a:b?#fragment')); +], @LinkGenerator::parseDestination('a:b?#fragment')); // deprecated Assert::same([ 'absolute' => false, @@ -56,7 +56,7 @@ Assert::same([ 'signal' => false, 'args' => ['a' => 'b', 'c' => 'd'], 'fragment' => '#fragment', -], LinkGenerator::parseDestination('a:b?a=b&c=d#fragment')); +], @LinkGenerator::parseDestination('a:b?a=b&c=d#fragment')); // deprecated Assert::exception( fn() => LinkGenerator::parseDestination(''), diff --git a/tests/UI/Presenter.link().phpt b/tests/UI/Presenter.link().phpt index b6b129d28..b1b3d6e6b 100644 --- a/tests/UI/Presenter.link().phpt +++ b/tests/UI/Presenter.link().phpt @@ -90,15 +90,11 @@ class TestPresenter extends Application\UI\Presenter Assert::same('/index.php?a=1&action=product&presenter=Test', $this->link('product', ['a' => 1])); Assert::same('#error: Passed more parameters than method TestPresenter::actionParams() expects.', $this->link('params', 1, 2, 3, 4, 5)); - // special url - Assert::same('/index.php?x=1&y=2&action=product&presenter=Test', $this->link('product?x=1&y=2')); - Assert::same('/index.php?x=1&y=2&action=product&presenter=Test#fragment', $this->link('product?x=1&y=2#fragment')); - // absolute - Assert::same('http://localhost/index.php?x=1&y=2&action=product&presenter=Test#fragment', $this->link('//product?x=1&y=2#fragment')); + Assert::same('http://localhost/index.php?x=1&y=2&action=product&presenter=Test#fragment', $this->link('//product#fragment', ['x' => 1, 'y' => 2])); $this->absoluteUrls = true; - Assert::same('http://localhost/index.php?x=1&y=2&action=product&presenter=Test#fragment', $this->link('product?x=1&y=2#fragment')); - Assert::same('http://localhost/index.php?x=1&y=2&action=product&presenter=Test#fragment', $this->link('//product?x=1&y=2#fragment')); + Assert::same('http://localhost/index.php?x=1&y=2&action=product&presenter=Test#fragment', $this->link('product#fragment', ['x' => 1, 'y' => 2])); + Assert::same('http://localhost/index.php?x=1&y=2&action=product&presenter=Test#fragment', $this->link('//product#fragment', ['x' => 1, 'y' => 2])); $this->absoluteUrls = false; // persistent params @@ -142,7 +138,7 @@ class TestPresenter extends Application\UI\Presenter Assert::same('/index.php?mycontrol-x=1&mycontrol-y=0&action=default&do=mycontrol-click&presenter=Test', $this['mycontrol']->link('click', true, false)); Assert::same('/index.php?action=default&do=mycontrol-click&presenter=Test', $this['mycontrol']->link('click', null, '')); Assert::same('#error: Passed more parameters than method TestControl::handleClick() expects.', $this['mycontrol']->link('click', 1, 2, 3)); - Assert::same('http://localhost/index.php?mycontrol-x=1&mycontrol-round=1&action=default&presenter=Test#frag', $this['mycontrol']->link('//this?x=1&round=1#frag')); + Assert::same('http://localhost/index.php?mycontrol-x=1&mycontrol-round=1&action=default&presenter=Test#frag', $this['mycontrol']->link('//this#frag', ['x' => 1, 'round' => 1])); Assert::same('/index.php?mycontrol-x=1&mycontrol-y=2&action=default&do=mycontrol-click&presenter=Test', $this->link('mycontrol:click!', ['x' => 1, 'y' => 2, 'round' => 0])); Assert::same(['mycontrol-x' => 1, 'mycontrol-y' => 2, 'mycontrol-round' => null, 'mycontrol-order' => null, 'pint' => null, 'parr' => null, 'pbool' => null, 'action' => 'default', 'do' => 'mycontrol-click'], $this->getLastCreatedRequest()->getParameters()); From 317146b692959d912248a6cfeac58eecf3577bdb Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 28 Apr 2024 14:29:04 +0200 Subject: [PATCH 20/37] PresenterFactoryCallback: refreshes page instead of creating presenters dynamically (BC break) TODO: scanDir must be active --- .../ApplicationDI/ApplicationExtension.php | 2 +- .../PresenterFactoryCallback.php | 23 ++++--------------- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/src/Bridges/ApplicationDI/ApplicationExtension.php b/src/Bridges/ApplicationDI/ApplicationExtension.php index cd1660279..e73b52be4 100644 --- a/src/Bridges/ApplicationDI/ApplicationExtension.php +++ b/src/Bridges/ApplicationDI/ApplicationExtension.php @@ -100,7 +100,7 @@ public function loadConfiguration(): void ->setType(Nette\Application\IPresenterFactory::class) ->setFactory(Nette\Application\PresenterFactory::class, [new Definitions\Statement( Nette\Bridges\ApplicationDI\PresenterFactoryCallback::class, - [1 => $this->invalidLinkMode, $touch ?? null], + [1 => $touch ?? null], )]); if ($config->mapping) { diff --git a/src/Bridges/ApplicationDI/PresenterFactoryCallback.php b/src/Bridges/ApplicationDI/PresenterFactoryCallback.php index 5142af3b1..9d1f1d2dd 100644 --- a/src/Bridges/ApplicationDI/PresenterFactoryCallback.php +++ b/src/Bridges/ApplicationDI/PresenterFactoryCallback.php @@ -21,7 +21,6 @@ final class PresenterFactoryCallback { public function __construct( private readonly Nette\DI\Container $container, - private readonly int $invalidLinkMode, private readonly ?string $touchToRefresh, ) { } @@ -41,25 +40,13 @@ public function __invoke(string $class): Nette\Application\IPresenter return $this->container->createService($services[0]); } - if ($this->touchToRefresh) { + if ($this->touchToRefresh && class_exists($class)) { touch($this->touchToRefresh); + echo 'Class ' . htmlspecialchars($class) . ' was not found in DI container.

If you just created this presenter, it should be enough to refresh the page. It will happen automatically in 5 seconds.

Otherwise, please check the configuration of your DI container.'; + header('Refresh: 5'); + exit; } - try { - $presenter = $this->container->createInstance($class); - $this->container->callInjects($presenter); - } catch (Nette\DI\MissingServiceException | Nette\DI\ServiceCreationException $e) { - if ($this->touchToRefresh && class_exists($class)) { - throw new \Exception("Refresh your browser. New presenter $class was found.", 0, $e); - } - - throw $e; - } - - if ($presenter instanceof Nette\Application\UI\Presenter && !$presenter->invalidLinkMode) { - $presenter->invalidLinkMode = $this->invalidLinkMode; - } - - return $presenter; + throw new Nette\Application\InvalidPresenterException("No services of type $class found."); } } From bbf29265c3aa4ea594350e23f557e22541900a7a Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 15 Apr 2024 01:38:08 +0200 Subject: [PATCH 21/37] PresenterFactory: default mapping is App\Presentation\*\**Presenter (BC break) --- src/Application/PresenterFactory.php | 2 +- .../PresenterFactory.formatPresenterClass.phpt | 18 +++++++++--------- tests/Routers/LinkGenerator.phpt | 8 +++++--- tests/Routers/link-aliases.phpt | 1 + 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/Application/PresenterFactory.php b/src/Application/PresenterFactory.php index 063ff2ccb..68972780a 100644 --- a/src/Application/PresenterFactory.php +++ b/src/Application/PresenterFactory.php @@ -20,7 +20,7 @@ class PresenterFactory implements IPresenterFactory { /** @var array[] of module => splited mask */ private array $mapping = [ - '*' => ['', '*Module\\', '*Presenter'], + '*' => ['App\Presentation\\', '*\\', '**Presenter'], 'Nette' => ['NetteModule\\', '*\\', '*Presenter'], ]; diff --git a/tests/Application/PresenterFactory.formatPresenterClass.phpt b/tests/Application/PresenterFactory.formatPresenterClass.phpt index ef4842e03..c5314e343 100644 --- a/tests/Application/PresenterFactory.formatPresenterClass.phpt +++ b/tests/Application/PresenterFactory.formatPresenterClass.phpt @@ -21,11 +21,11 @@ test('complex mapping with multiple wildcards', function () { 'Foo3' => 'My\App\*Mod\*Presenter', ]); - Assert::same('FooPresenter', $factory->formatPresenterClass('Foo')); - Assert::same('FooModule\BarPresenter', $factory->formatPresenterClass('Foo:Bar')); - Assert::same('FooModule\BarModule\BazPresenter', $factory->formatPresenterClass('Foo:Bar:Baz')); + Assert::same('App\Presentation\Foo\FooPresenter', $factory->formatPresenterClass('Foo')); + Assert::same('App\Presentation\Foo\Bar\BarPresenter', $factory->formatPresenterClass('Foo:Bar')); + Assert::same('App\Presentation\Foo\Bar\Baz\BazPresenter', $factory->formatPresenterClass('Foo:Bar:Baz')); - Assert::same('Foo2Presenter', $factory->formatPresenterClass('Foo2')); + Assert::same('App\Presentation\Foo2\Foo2Presenter', $factory->formatPresenterClass('Foo2')); Assert::same('App2\BarPresenter', $factory->formatPresenterClass('Foo2:Bar')); Assert::same('App2\Bar\BazPresenter', $factory->formatPresenterClass('Foo2:Bar:Baz')); @@ -44,7 +44,7 @@ test('simple wildcard mapping', function () { 'Foo3' => 'My\App\*Presenter', ]); - Assert::same('Foo2Presenter', $factory->formatPresenterClass('Foo2')); + Assert::same('App\Presentation\Foo2\Foo2Presenter', $factory->formatPresenterClass('Foo2')); Assert::same('App2\BarPresenter', $factory->formatPresenterClass('Foo2:Bar')); Assert::same('App2\BarModule\BazPresenter', $factory->formatPresenterClass('Foo2:Bar:Baz')); @@ -61,11 +61,11 @@ test('multi-segment wildcard expansion', function () { 'Foo3' => 'My\App\*Mod\**Presenter', ]); - Assert::same('FooPresenter', $factory->formatPresenterClass('Foo')); - Assert::same('FooModule\BarPresenter', $factory->formatPresenterClass('Foo:Bar')); - Assert::same('FooModule\BarModule\BazPresenter', $factory->formatPresenterClass('Foo:Bar:Baz')); + Assert::same('App\Presentation\Foo\FooPresenter', $factory->formatPresenterClass('Foo')); + Assert::same('App\Presentation\Foo\Bar\BarPresenter', $factory->formatPresenterClass('Foo:Bar')); + Assert::same('App\Presentation\Foo\Bar\Baz\BazPresenter', $factory->formatPresenterClass('Foo:Bar:Baz')); - Assert::same('Foo2Presenter', $factory->formatPresenterClass('Foo2')); + Assert::same('App\Presentation\Foo2\Foo2Presenter', $factory->formatPresenterClass('Foo2')); Assert::same('App2\Bar\BarPresenter', $factory->formatPresenterClass('Foo2:Bar')); Assert::same('App2\Bar\Baz\BazPresenter', $factory->formatPresenterClass('Foo2:Bar:Baz')); diff --git a/tests/Routers/LinkGenerator.phpt b/tests/Routers/LinkGenerator.phpt index d82ea37a8..d211d9c14 100644 --- a/tests/Routers/LinkGenerator.phpt +++ b/tests/Routers/LinkGenerator.phpt @@ -6,7 +6,9 @@ declare(strict_types=1); -namespace { +namespace App\Presentation\Homepage { + + use Nette; require __DIR__ . '/../bootstrap.php'; @@ -24,7 +26,7 @@ namespace { } -namespace ModuleModule { +namespace App\Presentation\Module\My { use Nette; @@ -77,7 +79,7 @@ namespace { testException('invalid action parameter propagation', function () use ($pf) { $generator = new LinkGenerator(new Routers\Route('/', 'Homepage:'), new Http\UrlScript('http://nette.org/en/'), $pf); $generator->link('Homepage:missing', [10]); - }, Nette\Application\UI\InvalidLinkException::class, "Unable to pass parameters to action 'Homepage:missing', missing corresponding method HomepagePresenter::renderMissing()."); + }, Nette\Application\UI\InvalidLinkException::class, "Unable to pass parameters to action 'Homepage:missing', missing corresponding method App\\Presentation\\Homepage\\HomepagePresenter::renderMissing()."); test('URL generation without PresenterFactory', function () { diff --git a/tests/Routers/link-aliases.phpt b/tests/Routers/link-aliases.phpt index 0f2b75324..2180bd526 100644 --- a/tests/Routers/link-aliases.phpt +++ b/tests/Routers/link-aliases.phpt @@ -20,6 +20,7 @@ class TestPresenter extends Application\UI\Presenter $factory = new PresenterFactory; +$factory->setMapping(['*' => '*Presenter']); $factory->setAliases([ 'a' => 'Test:a', 'b' => 'Test:b', From 04dc948035fb48d1210f9ba6d15cebe4abb5082b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radovan=20Kep=C3=A1k?= Date: Tue, 25 Nov 2025 01:15:32 +0100 Subject: [PATCH 22/37] Application: refactor to return Response from processRequest() BC break (#350) Changed Application::processRequest to return a Response object instead of sending it directly. Updated run() to call send() on the returned Response. This improves testability and separation of concerns by decoupling request processing from response sending. --- src/Application/Application.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Application/Application.php b/src/Application/Application.php index 22b5673a9..858ae0e71 100644 --- a/src/Application/Application.php +++ b/src/Application/Application.php @@ -66,7 +66,8 @@ public function run(): void { try { Arrays::invoke($this->onStartup, $this); - $this->processRequest($this->createInitialRequest()); + $this->processRequest($this->createInitialRequest()) + ->send($this->httpRequest, $this->httpResponse); Arrays::invoke($this->onShutdown, $this); } catch (\Throwable $e) { @@ -74,7 +75,8 @@ public function run(): void Arrays::invoke($this->onError, $this, $e); if ($this->catchExceptions && ($req = $this->createErrorRequest($e))) { try { - $this->processRequest($req); + $this->processRequest($req) + ->send($this->httpRequest, $this->httpResponse); Arrays::invoke($this->onShutdown, $this, $e); return; @@ -113,7 +115,7 @@ public function createInitialRequest(): Request } - public function processRequest(Request $request): void + public function processRequest(Request $request): Response { process: if (count($this->requests) > $this->maxLoop) { @@ -148,7 +150,7 @@ public function processRequest(Request $request): void } Arrays::invoke($this->onResponse, $this, $response); - $response->send($this->httpRequest, $this->httpResponse); + return $response; } From 8ab32e4bb7a13a9b0b3bf79aeb883dcae803eebd Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 19 Apr 2024 18:59:59 +0200 Subject: [PATCH 23/37] @annotations are deprecated (BC break) --- src/Application/UI/ComponentReflection.php | 20 +++++++++++-------- src/Application/UI/MethodReflection.php | 2 ++ .../ComponentReflection.getParameters().phpt | 2 +- ...tReflection.getPersistentComponents().phpt | 13 ------------ .../ComponentReflection.parseAnnotation.phpt | 16 +++++++-------- tests/UI/Presenter.link().persistent.phpt | 17 ++++++++-------- tests/UI/Presenter.link().phpt | 19 +++++++++--------- tests/UI/fixtures/ParamPresenter.php | 2 +- 8 files changed, 43 insertions(+), 48 deletions(-) diff --git a/src/Application/UI/ComponentReflection.php b/src/Application/UI/ComponentReflection.php index 4fd9b5374..dcb879b69 100644 --- a/src/Application/UI/ComponentReflection.php +++ b/src/Application/UI/ComponentReflection.php @@ -29,7 +29,7 @@ final class ComponentReflection extends \ReflectionClass /** - * Returns array of class properties that are public and have attribute #[Persistent] or #[Parameter] or annotation @persistent. + * Returns array of class properties that are public and have attribute #[Persistent] or #[Parameter]. * @return array */ public function getParameters(): array @@ -77,7 +77,7 @@ public function getParameters(): array /** - * Returns array of persistent properties. They are public and have attribute #[Persistent] or annotation @persistent. + * Returns array of persistent properties. They are public and have attribute #[Persistent]. * @return array */ public function getPersistentParams(): array @@ -172,6 +172,7 @@ public function getSignalMethod(string $signal): ?\ReflectionMethod /** * Returns an annotation value. + * @deprecated */ public static function parseAnnotation(\Reflector $ref, string $name): ?array { @@ -189,22 +190,25 @@ public static function parseAnnotation(\Reflector $ref, string $name): ?array } } + $alt = match ($name) { + 'persistent' => '#[Nette\Application\Attributes\Persistent]', + 'deprecated' => '#[Nette\Application\Attributes\Deprecated]', + 'crossOrigin' => '#[Nette\Application\Attributes\Request(sameOrigin: false)]', + default => 'alternative' + }; + trigger_error("Annotation @$name is deprecated, use $alt (used in " . Reflection::toString($ref) . ')', E_USER_DEPRECATED); return $res; } - /** - * Has class specified annotation? - */ + #[\Deprecated] public function hasAnnotation(string $name): bool { return (bool) self::parseAnnotation($this, $name); } - /** - * Returns an annotation value. - */ + #[\Deprecated] public function getAnnotation(string $name): mixed { $res = self::parseAnnotation($this, $name); diff --git a/src/Application/UI/MethodReflection.php b/src/Application/UI/MethodReflection.php index 9ca18cf8b..45461f8cc 100644 --- a/src/Application/UI/MethodReflection.php +++ b/src/Application/UI/MethodReflection.php @@ -18,6 +18,7 @@ final class MethodReflection extends \ReflectionMethod #[\Deprecated] public function hasAnnotation(string $name): bool { + trigger_error(__METHOD__ . '() is deprecated', E_USER_DEPRECATED); return (bool) ComponentReflection::parseAnnotation($this, $name); } @@ -25,6 +26,7 @@ public function hasAnnotation(string $name): bool #[\Deprecated] public function getAnnotation(string $name): mixed { + trigger_error(__METHOD__ . '() is deprecated', E_USER_DEPRECATED); $res = ComponentReflection::parseAnnotation($this, $name); return $res ? end($res) : null; } diff --git a/tests/UI/ComponentReflection.getParameters().phpt b/tests/UI/ComponentReflection.getParameters().phpt index 5567ab142..ca70724af 100644 --- a/tests/UI/ComponentReflection.getParameters().phpt +++ b/tests/UI/ComponentReflection.getParameters().phpt @@ -16,7 +16,7 @@ class OnePresenter extends Presenter public static $no1; public $no2; - /** @persistent */ + #[Persistent] public $yes1; #[Persistent, Parameter] diff --git a/tests/UI/ComponentReflection.getPersistentComponents().phpt b/tests/UI/ComponentReflection.getPersistentComponents().phpt index 2654e06d9..febe683f3 100644 --- a/tests/UI/ComponentReflection.getPersistentComponents().phpt +++ b/tests/UI/ComponentReflection.getPersistentComponents().phpt @@ -14,14 +14,6 @@ class NonePresenter extends Presenter } -/** - * @persistent(a, b) - */ -class AnnotationPresenter extends Presenter -{ -} - - #[Persistent('a', 'b')] class AttributePresenter extends Presenter { @@ -39,11 +31,6 @@ class MethodPresenter extends AttributePresenter Assert::same([], NonePresenter::getReflection()->getPersistentComponents()); -Assert::same([ - 'a' => ['since' => AnnotationPresenter::class], - 'b' => ['since' => AnnotationPresenter::class], -], AnnotationPresenter::getReflection()->getPersistentComponents()); - Assert::same([ 'a' => ['since' => AttributePresenter::class], 'b' => ['since' => AttributePresenter::class], diff --git a/tests/UI/ComponentReflection.parseAnnotation.phpt b/tests/UI/ComponentReflection.parseAnnotation.phpt index 278b5e7d1..71a614e90 100644 --- a/tests/UI/ComponentReflection.parseAnnotation.phpt +++ b/tests/UI/ComponentReflection.parseAnnotation.phpt @@ -34,11 +34,11 @@ class TestClass $rc = new ReflectionClass('TestClass'); -Assert::same(['value ="Johno\'s addendum"', 'mode=True', true, true], Reflection::parseAnnotation($rc, 'title')); -Assert::null(Reflection::parseAnnotation($rc, 'public')); -Assert::null(Reflection::parseAnnotation($rc, 'private')); -Assert::same(['item 1'], Reflection::parseAnnotation($rc, 'components')); -Assert::same([true, false, null], Reflection::parseAnnotation($rc, 'persistent')); -Assert::same([true], Reflection::parseAnnotation($rc, 'renderable')); -Assert::same(['loggedIn'], Reflection::parseAnnotation($rc, 'Secured\User')); -Assert::null(Reflection::parseAnnotation($rc, 'missing')); +Assert::same(['value ="Johno\'s addendum"', 'mode=True', true, true], @Reflection::parseAnnotation($rc, 'title')); +Assert::null(@Reflection::parseAnnotation($rc, 'public')); +Assert::null(@Reflection::parseAnnotation($rc, 'private')); +Assert::same(['item 1'], @Reflection::parseAnnotation($rc, 'components')); +Assert::same([true, false, null], @Reflection::parseAnnotation($rc, 'persistent')); +Assert::same([true], @Reflection::parseAnnotation($rc, 'renderable')); +Assert::same(['loggedIn'], @Reflection::parseAnnotation($rc, 'Secured\User')); +Assert::null(@Reflection::parseAnnotation($rc, 'missing')); diff --git a/tests/UI/Presenter.link().persistent.phpt b/tests/UI/Presenter.link().persistent.phpt index 548f122f2..f181adce7 100644 --- a/tests/UI/Presenter.link().persistent.phpt +++ b/tests/UI/Presenter.link().persistent.phpt @@ -7,6 +7,7 @@ declare(strict_types=1); use Nette\Application; +use Nette\Application\Attributes\Persistent; use Nette\Http; use Tester\Assert; @@ -23,13 +24,13 @@ function sortParams(array $params): array trait PersistentParam1 { - /** @persistent */ + #[Persistent] public $t1; } trait PersistentParam2A { - /** @persistent */ + #[Persistent] public $t2; } @@ -40,7 +41,7 @@ trait PersistentParam2B trait PersistentParam3 { - /** @persistent */ + #[Persistent] public $t3; } @@ -48,7 +49,7 @@ class BasePresenter extends Application\UI\Presenter { use PersistentParam1; - /** @persistent */ + #[Persistent] public $p1; } @@ -57,7 +58,7 @@ class TestPresenter extends BasePresenter { use PersistentParam2B; - /** @persistent */ + #[Persistent] public $p2; @@ -92,10 +93,10 @@ class SecondPresenter extends BasePresenter { use PersistentParam3; - /** @persistent */ + #[Persistent] public $p1 = 20; - /** @persistent */ + #[Persistent] public $p3; } @@ -108,7 +109,7 @@ class ThirdPresenter extends BasePresenter class FourthPresenter extends BasePresenter { - #[Application\Attributes\Persistent] + #[Persistent] public $p1; } diff --git a/tests/UI/Presenter.link().phpt b/tests/UI/Presenter.link().phpt index b1b3d6e6b..38407c6dd 100644 --- a/tests/UI/Presenter.link().phpt +++ b/tests/UI/Presenter.link().phpt @@ -7,6 +7,7 @@ declare(strict_types=1); use Nette\Application; +use Nette\Application\Attributes\Persistent; use Nette\Http; use Tester\Assert; @@ -16,10 +17,10 @@ require __DIR__ . '/../bootstrap.php'; class TestControl extends Application\UI\Control { - /** @persistent array */ + #[Persistent] public $order = []; - /** @persistent int */ + #[Persistent] public $round = 0; @@ -50,22 +51,22 @@ class TestControl extends Application\UI\Control class TestPresenter extends Application\UI\Presenter { - /** @persistent */ + #[Persistent] public $p; - /** @persistent */ + #[Persistent] public $pint = 10; - /** @persistent */ + #[Persistent] public $parr = []; - /** @persistent */ + #[Persistent] public $pbool = true; - /** @persistent */ + #[Persistent] public array $parrn; - /** @persistent */ + #[Persistent] public ?bool $pbooln = null; @@ -290,7 +291,7 @@ class TestPresenter extends Application\UI\Presenter class OtherPresenter extends TestPresenter { - /** @persistent */ + #[Persistent] public $p = 20; } diff --git a/tests/UI/fixtures/ParamPresenter.php b/tests/UI/fixtures/ParamPresenter.php index 453a78c34..21e04cca4 100644 --- a/tests/UI/fixtures/ParamPresenter.php +++ b/tests/UI/fixtures/ParamPresenter.php @@ -4,7 +4,7 @@ class ParamPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ + #[Nette\Application\Attributes\Persistent] public $bool = true; From a9b1ccd030f0f507af9f9b565bc5fc3640e81590 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 1 Dec 2025 00:07:11 +0100 Subject: [PATCH 24/37] added Presenter::completeTemplate() --- src/Application/UI/Presenter.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Application/UI/Presenter.php b/src/Application/UI/Presenter.php index d104f0c9b..7f4306f4c 100644 --- a/src/Application/UI/Presenter.php +++ b/src/Application/UI/Presenter.php @@ -481,13 +481,22 @@ public function setLayout(string|bool $layout): static public function sendTemplate(?Template $template = null): void { $template ??= $this->getTemplate(); + $this->completeTemplate($template); + $this->sendResponse(new Responses\TextResponse($template)); + } + + + /** + * Completes template parameters and file before rendering. + */ + protected function completeTemplate(Template $template): void + { foreach ($this->getReflection()->getTemplateVariables($this) as $name) { $template->$name ??= $this->$name; } if ($template->getFile() === null) { $template->setFile($this->findTemplateFile()); } - $this->sendResponse(new Responses\TextResponse($template)); } From 65c8ba6a4b507d98257979bee46a057f425ada33 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 27 Dec 2025 19:48:19 +0100 Subject: [PATCH 25/37] added CLAUDE.md --- .gitattributes | 1 + CLAUDE.md | 578 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 579 insertions(+) create mode 100644 CLAUDE.md diff --git a/.gitattributes b/.gitattributes index e1bccc4bf..ed8103553 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,7 @@ .gitattributes export-ignore .github/ export-ignore .gitignore export-ignore +CLAUDE.md export-ignore ncs.* export-ignore phpstan*.neon export-ignore tests/ export-ignore diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..e7e35a8bc --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,578 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Nette Application is a full-stack component-based MVC framework library for PHP. It provides the core application layer for the Nette Framework, handling presenters, components, routing, templating integration, and request/response cycles. + +**Key characteristics:** +- PHP library package (not a full application) +- Supports PHP 8.2 - 8.5 +- Component-based architecture with hierarchical component trees +- Tight integration with Latte templating engine +- Dependency injection through Nette DI bridges + +## Essential Commands + +### Running Tests + +```bash +# Run all tests +vendor/bin/tester tests -s -C + +# Run specific test directory +vendor/bin/tester tests/Application -s -C +vendor/bin/tester tests/UI -s -C + +# Run single test file +php tests/Application/Presenter.twoDomains.phpt +``` + +**Important:** Tests use Nette Tester with `.phpt` extension (148 test files total). + +### Code Quality + +```bash +# Run PHPStan static analysis (level 5) +composer phpstan +``` + +## Architecture + +### Source Structure + +``` +src/ +├── Application/ Main application layer +│ ├── UI/ Presenter & component system +│ │ ├── Presenter.php Base presenter class +│ │ ├── Component.php Base component class +│ │ ├── Control.php Renderable component +│ │ └── Form.php Form component integration +│ ├── Routers/ Routing implementations +│ │ ├── Route.php Standard route +│ │ ├── RouteList.php Route collection +│ │ ├── SimpleRouter.php +│ │ └── CliRouter.php CLI routing +│ ├── Responses/ Response types +│ │ ├── JsonResponse.php +│ │ ├── TextResponse.php +│ │ ├── RedirectResponse.php +│ │ ├── ForwardResponse.php +│ │ └── FileResponse.php +│ ├── Application.php Front controller +│ ├── Request.php Application request +│ ├── LinkGenerator.php URL generation +│ └── PresenterFactory.php +└── Bridges/ Framework integrations + ├── ApplicationDI/ DI container integration + │ ├── ApplicationExtension.php + │ ├── LatteExtension.php + │ └── RoutingExtension.php + ├── ApplicationLatte/ Latte template engine integration + │ ├── TemplateFactory.php + │ ├── TemplateGenerator.php Auto-generates template classes + │ ├── UIExtension.php + │ └── Nodes/ Latte syntax nodes + └── ApplicationTracy/ Tracy debugger integration +``` + +### Key Architectural Concepts + +#### Component Hierarchy + +The framework uses hierarchical component trees where: +- **Presenter** is the root component (extends `Control`, implements `IPresenter`) +- **Control** components can contain child components +- **Component** base class provides parameter persistence and signal handling +- Components use `lookup()` to find ancestors (e.g., `$this->lookup(Presenter::class)`) + +#### Request-Response Cycle + +1. `Application` receives HTTP request via router +2. `PresenterFactory` creates presenter instance +3. `Presenter::run()` processes the request: + - Calls lifecycle methods (`startup()`, `beforeRender()`, `render*()`, `shutdown()`) + - Handles signals (AJAX/component interactions) + - Resolves action/view + - Creates template and renders response +4. Returns `Response` object (Text/Json/Redirect/Forward/File/Void) + +#### Template Integration + +**TemplateGenerator** (new in v3.3): +- Automatically generates typed template classes from presenters/controls +- Creates `{PresenterName}Template` classes with proper property types +- Updates presenter phpDoc with `@property-read` annotations +- Enables full IDE support in `.latte` files via `{templateType}` declaration + +**Bridge Pattern:** +- `Bridges\ApplicationLatte` provides Latte integration +- `Bridges\ApplicationDI` provides DI container extensions +- `Bridges\ApplicationTracy` provides debugging integration + +#### Link Generation + +- `link()` and `n:href` generate URLs to presenter actions/signals +- `LinkGenerator` handles absolute URL generation +- Invalid link handling modes: Silent/Warning/Exception/Textual +- Special syntax: `this`, `//absolute`, `:Module:Presenter:action` + +### Test Structure + +Tests mirror source structure: +``` +tests/ +├── Application/ Application class tests +├── Bridges.DI/ DI extension tests +├── Bridges.Latte/ Latte integration tests (snippets, templates) +├── Bridges.Latte3/ Latte 3.x specific tests +├── Routers/ Router tests +├── Responses/ Response type tests +├── UI/ Presenter & component tests +└── bootstrap.php Test environment setup +``` + +**Test conventions:** +- Use `.phpt` extension (Nette Tester format) +- Use `test()` function for test cases with descriptive names +- Use `testException()` for exception-only tests +- Use `Assert::*` for assertions +- Mockery for mocking dependencies +- Temporary files go to `tests/tmp/{pid}/` + +## Development Guidelines + +### Coding Standards + +- Every PHP file must start with `declare(strict_types=1);` +- Follow Nette Coding Standard (based on PSR-12) +- Use tabs for indentation +- Return type declarations on separate line from closing brace: + ```php + public function example(string $param): + { + // method body + } + ``` + +### Documentation Style + +- Use phpDoc for public APIs +- Document `@property-read` for magic properties +- Document array types: `@return string[]` +- Mark deprecated features with `@deprecated` and explanation +- Interfaces use marker comments for method purposes + +### Presenter Lifecycle + +Presenters follow a strict lifecycle when processing requests. Methods are called sequentially, all optional: + +``` +__construct() + ↓ +startup() ← Always call parent::startup() + ↓ +action() ← Called BEFORE render, can change view/redirect + ↓ +handle() ← Processes signals (AJAX requests, component interactions) + ↓ +beforeRender() ← Common template setup + ↓ +render() ← Prepare data for template + ↓ +afterRender() ← Rarely used + ↓ +shutdown() ← Cleanup +``` + +**Important notes:** +- `action()` executes before `render()` - can change which template renders +- Parameters from URL are automatically passed and type-checked +- Missing/invalid parameters trigger 404 error +- Calling `redirect()`, `error()`, `sendJson()` etc. terminates lifecycle immediately (throws `AbortException`) +- If no response method called, presenter automatically renders template + +### Persistent Parameters + +Persistent parameters maintain state across requests automatically via URL. + +**Declaration:** +```php +use Nette\Application\Attributes\Persistent; + +class ProductPresenter extends Nette\Application\UI\Presenter +{ + #[Persistent] + public string $lang = 'en'; // Must be public, specify type +} +``` + +**How they work:** +- Value automatically included in all generated links +- Transferred across different actions of same presenter +- Can be transferred across presenters if defined in common ancestor/trait +- Changed via `n:href="Product:show lang: cs"` or reset via `lang: null` + +**Validation:** +Override `loadState()` to validate values from URL: +```php +public function loadState(array $params): void +{ + parent::loadState($params); // Sets $this->lang + if (!in_array($this->lang, ['en', 'cs'])) { + $this->error(); // 404 if invalid + } +} +``` + +**Never trust URL parameters** - always validate as users can modify them. + +### Signals & Hollywood Style + +Signals handle user interactions on the current page (sorting, AJAX updates, form submissions). + +**Hollywood Style Philosophy:** +Instead of asking "was button clicked?", tell framework "when button clicked, call this method". Framework calls you back - "Don't call us, we'll call you." + +**Signal Declaration:** +```php +public function handleClick(int $x, int $y): void +{ + // Process the signal + $this->redrawControl(); // Mark for AJAX re-render +} +``` + +**Signal URLs:** +- Created with exclamation mark: `n:href="click! $x, $y"` +- Always called on current presenter/action +- Cannot signal to different presenter +- Format: `?do={signal}` or `?do={component}-{signal}` for component signals + +**In components**, `link()` and `n:href` default to signals (no `!` needed): +```php +// In component template +refresh // Calls handleRefresh() signal +``` + +### Component Factory Pattern + +Components are created lazily via factory methods in presenters. + +**Basic Factory:** +```php +protected function createComponentPoll(): PollControl +{ + return new PollControl; +} +``` + +**Accessing components:** +```php +$poll = $this->getComponent('poll'); // or $this['poll'] +``` + +**Factory is called automatically:** +- First time component is accessed +- Only if actually needed +- Not called during AJAX if component not used + +**Components with Dependencies:** +Use generated factory interface pattern: +```php +// Define interface +interface PollControlFactory +{ + public function create(int $pollId): PollControl; +} + +// Register in config/services.neon +// Nette DI automatically implements interface + +// Use in presenter +class PollPresenter extends Nette\Application\UI\Presenter +{ + public function __construct( + private PollControlFactory $pollControlFactory, + ) { + } + + protected function createComponentPollControl(): PollControl + { + return $this->pollControlFactory->create($pollId: 1); + } +} +``` + +**Multiplier for dynamic components:** +```php +protected function createComponentShopForm(): Multiplier +{ + return new Multiplier(function (string $itemId) { + $form = new Nette\Application\UI\Form; + // ... configure form for $itemId + return $form; + }); +} + +// In template: {control "shopForm-$item->id"} +``` + +### Bidirectional Routing + +Router converts URLs ↔ Presenter:action pairs in both directions. + +**Key concept:** URLs are never hardcoded. Change entire URL structure by modifying router only. + +**Route definition:** +```php +$router = new RouteList; +$router->addRoute('rss.xml', 'Feed:rss'); +$router->addRoute('article/', 'Article:view'); +$router->addRoute('/[/]', 'Home:default'); +``` + +**Route order is critical:** +- Evaluated top to bottom for both matching and generating +- List specific routes before general routes +- First matching route wins + +**Route features:** +- Parameters: ``, `` (with regex validation) +- Optional sequences: `[/]name` +- Default values: `` +- Filters & translations: Czech URLs like `/produkt` → `Product` presenter +- Wildcards: `%domain%`, `%basePath%` +- Modules: `->withModule('Admin')` +- Subdomains: `->withDomain('admin.example.com')` + +**Canonization:** +Framework prevents duplicate content by redirecting alternative URLs to canonical URL (first matching route). Automatic 301 redirect for SEO. + +### AJAX & Snippets + +Snippets update page parts without full reload. + +**Workflow:** +1. Mark element as snippet in template: `{snippet header}...{/snippet}` +2. Invalidate snippet in signal handler: `$this->redrawControl('header')` +3. Nette renders only changed snippets, sends as JSON +4. Naja.js library updates DOM automatically + +**Template syntax:** +```latte +{snippet header} +

Hello {$user->name}

+{/snippet} + +{* Or using n:snippet attribute *} +
+

Hello {$user->name}

+
+``` + +**Signal handler:** +```php +public function handleLogin(string $user): void +{ + $this->user = $user; + $this->redrawControl('header'); // Invalidate specific snippet + // or $this->redrawControl() to invalidate all snippets +} +``` + +**Dynamic snippets with snippetArea:** +```latte +
    + {foreach $items as $id => $item} +
  • {$item}
  • + {/foreach} +
+``` + +```php +$this->redrawControl('itemsContainer'); // Must invalidate parent area +$this->redrawControl('item-1'); // And specific snippet +``` + +**Client-side (Naja.js):** +```html + +Click me +
...
+``` + +**Sending custom data:** +```php +public function handleDelete(int $id): void +{ + // ... + if ($this->isAjax()) { + $this->payload->message = 'Deleted successfully'; + } +} +``` + +### Template Lookup + +Framework automatically finds templates - no need to specify paths. + +**Action template lookup:** +``` +Presentation/Home/HomePresenter.php +Presentation/Home/default.latte ← Found automatically +``` + +**Alternative structure:** +``` +Presenters/HomePresenter.php +Presenters/templates/Home.default.latte ← or +Presenters/templates/Home/default.latte ← or +``` + +**Layout template lookup:** +``` +Presentation/@layout.latte ← Common for all +Presentation/Home/@layout.latte ← Specific for Home +``` + +**Override in code:** +```php +$this->setView('otherView'); // Change view +$this->template->setFile('/path/to/file.latte'); // Explicit path +$this->setLayout('layoutAdmin'); // Different layout +$this->setLayout(false); // No layout +``` + +**Type-safe templates:** +```php +/** + * @property-read ArticleTemplate $template + */ +class ArticlePresenter extends Nette\Application\UI\Presenter +{ +} + +class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template +{ + public Article $article; + public User $user; +} +``` + +In template: +```latte +{templateType App\Presentation\Article\ArticleTemplate} +{* Now full IDE autocomplete for $article, $user *} +``` + +### Link Syntax + +Links to presenters/actions use special syntax instead of URLs. + +**In templates:** +```latte +detail +detail in EN +refresh current page +current page with changed param + +{* Absolute URL *} +absolute + +{* Module navigation *} +absolute to module + +{* Signals (notice the !) *} +signal + +{* Fragment *} +jump to #main +``` + +**In presenter code:** +```php +$url = $this->link('Product:show', $id); +$url = $this->link('Product:show', [$id, 'lang' => 'en']); + +// Redirects +$this->redirect('Product:show', $id); // 302/303 +$this->redirectPermanent('Product:show', $id); // 301 +$this->redirectUrl('https://example.com'); +$this->forward('Product:show'); // No HTTP redirect +``` + +**Link checking:** +```latte +{if isLinkCurrent('Product:show')}active{/if} +
  • ...
  • +``` + +**Invalid links:** +Configured via `Presenter::$invalidLinkMode`: +- `InvalidLinkSilent` - returns `#` +- `InvalidLinkWarning` - logs E_USER_WARNING (production default) +- `InvalidLinkTextual` - shows error in link text (dev default) +- `InvalidLinkException` - throws exception + +### Flash Messages + +Flash messages survive redirects and stay for 30 seconds (for page refresh tolerance). + +```php +public function handleDelete(int $id): void +{ + // ... delete item + $this->flashMessage('Item deleted successfully.'); + $this->redirect('this'); +} +``` + +In template: +```latte +{foreach $flashes as $flash} +
    {$flash->message}
    +{/foreach} +``` + +With type: +```php +$this->flashMessage('Error occurred', 'error'); +$this->flashMessage('Success', 'success'); +``` + +### Application Configuration + +Key configuration options in `config/common.neon`: + +```neon +application: + errorPresenter: Error # 4xx and 5xx errors + # Or separate error presenters: + errorPresenter: + 4xx: Error4xx + 5xx: Error5xx + + silentLinks: false # Suppress invalid link warnings in dev + + mapping: # Presenter name → class mapping + *: App\*Module\Presentation\*Presenter + + aliases: # Short aliases for links + home: Front:Home:default + admin: Admin:Dashboard:default + +latte: + strictTypes: false # Add declare(strict_types=1) to templates + strictParsing: false # Strict parser mode + locale: cs_CZ # Locale for filters +``` + +### Breaking Changes + +This is v3.3 branch. Recent BC breaks include: +- `Application::processRequest()` now returns `Response` (not void) +- `@annotations` deprecated in favor of PHP 8 attributes +- Various deprecation notices for old APIs From 1267b1b920b53cc84b32a0da506f3d0b58a57443 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 14 Dec 2023 22:21:35 +0100 Subject: [PATCH 26/37] opened 4.0-dev --- composer.json | 2 +- readme.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 51ec77a45..a0135a8d8 100644 --- a/composer.json +++ b/composer.json @@ -58,7 +58,7 @@ }, "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "4.0-dev" } } } diff --git a/readme.md b/readme.md index 41ddf658c..b4b61840e 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,7 @@ Nette Application MVC ===================== [![Downloads this Month](https://img.shields.io/packagist/dm/nette/application.svg)](https://packagist.org/packages/nette/application) -[![Tests](https://github.com/nette/application/actions/workflows/tests.yml/badge.svg?branch=v3.3)](https://github.com/nette/application/actions) +[![Tests](https://github.com/nette/application/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/nette/application/actions) [![Latest Stable Version](https://poser.pugx.org/nette/application/v/stable)](https://github.com/nette/application/releases) [![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/application/blob/master/license.md) From 595eb3690cc17f36ba2ef96ce3086f3332f04c07 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 8 Feb 2024 21:39:57 +0100 Subject: [PATCH 27/37] added type hints (BC break) --- src/Application/UI/Component.php | 8 +++---- src/Application/UI/Presenter.php | 21 +++++++------------ .../ApplicationLatte/TemplateFactory.php | 2 +- tests/UI/Component.redirect().phpt | 2 +- 4 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/Application/UI/Component.php b/src/Application/UI/Component.php index 383b84f98..2b56884e4 100644 --- a/src/Application/UI/Component.php +++ b/src/Application/UI/Component.php @@ -339,10 +339,9 @@ public function isLinkCurrent(?string $destination = null, $args = []): bool * Redirect to another presenter, action or signal. * @param string $destination in format "[//] [[[module:]presenter:]action | signal! | this] [#fragment]" * @param array|mixed $args - * @return never * @throws Nette\Application\AbortException */ - public function redirect(string $destination, $args = []): void + public function redirect(string $destination, $args = []): never { $args = func_num_args() < 3 && is_array($args) ? $args @@ -357,10 +356,9 @@ public function redirect(string $destination, $args = []): void * Permanently redirects to presenter, action or signal. * @param string $destination in format "[//] [[[module:]presenter:]action | signal! | this] [#fragment]" * @param array|mixed $args - * @return never * @throws Nette\Application\AbortException */ - public function redirectPermanent(string $destination, $args = []): void + public function redirectPermanent(string $destination, $args = []): never { $args = func_num_args() < 3 && is_array($args) ? $args @@ -377,7 +375,7 @@ public function redirectPermanent(string $destination, $args = []): void * Throws HTTP error. * @throws Nette\Application\BadRequestException */ - public function error(string $message = '', int $httpCode = Nette\Http\IResponse::S404_NotFound): void + public function error(string $message = '', int $httpCode = Nette\Http\IResponse::S404_NotFound): never { throw new Nette\Application\BadRequestException($message, $httpCode); } diff --git a/src/Application/UI/Presenter.php b/src/Application/UI/Presenter.php index 7f4306f4c..2a0795049 100644 --- a/src/Application/UI/Presenter.php +++ b/src/Application/UI/Presenter.php @@ -476,9 +476,8 @@ public function setLayout(string|bool $layout): static /** * @throws Nette\Application\AbortException - * @return never */ - public function sendTemplate(?Template $template = null): void + public function sendTemplate(?Template $template = null): never { $template ??= $this->getTemplate(); $this->completeTemplate($template); @@ -662,9 +661,8 @@ public function isAjax(): bool /** * Sends AJAX payload to the output. * @throws Nette\Application\AbortException - * @return never */ - public function sendPayload(): void + public function sendPayload(): never { $this->sendResponse(new Responses\JsonResponse($this->getPayload())); } @@ -673,9 +671,8 @@ public function sendPayload(): void /** * Sends JSON data to the output. * @throws Nette\Application\AbortException - * @return never */ - public function sendJson(mixed $data): void + public function sendJson(mixed $data): never { $this->sendResponse(new Responses\JsonResponse($data)); } @@ -687,9 +684,8 @@ public function sendJson(mixed $data): void /** * Sends response and terminates presenter. * @throws Nette\Application\AbortException - * @return never */ - public function sendResponse(Application\Response $response): void + public function sendResponse(Application\Response $response): never { $this->response = $response; $this->terminate(); @@ -699,9 +695,8 @@ public function sendResponse(Application\Response $response): void /** * Correctly terminates presenter. * @throws Nette\Application\AbortException - * @return never */ - public function terminate(): void + public function terminate(): never { throw new Application\AbortException; } @@ -711,9 +706,8 @@ public function terminate(): void * Forward to another presenter or action. * @param array|mixed $args * @throws Nette\Application\AbortException - * @return never */ - public function forward(string|Nette\Application\Request $destination, $args = []): void + public function forward(string|Nette\Application\Request $destination, $args = []): never { if ($destination instanceof Application\Request) { $this->sendResponse(new Responses\ForwardResponse($destination)); @@ -730,9 +724,8 @@ public function forward(string|Nette\Application\Request $destination, $args = [ /** * Redirect to another URL and ends presenter execution. * @throws Nette\Application\AbortException - * @return never */ - public function redirectUrl(string $url, ?int $httpCode = null): void + public function redirectUrl(string $url, ?int $httpCode = null): never { if ($this->isAjax()) { $this->getPayload()->redirect = $url; diff --git a/src/Bridges/ApplicationLatte/TemplateFactory.php b/src/Bridges/ApplicationLatte/TemplateFactory.php index 2fba1dc06..70a828ea5 100644 --- a/src/Bridges/ApplicationLatte/TemplateFactory.php +++ b/src/Bridges/ApplicationLatte/TemplateFactory.php @@ -43,7 +43,7 @@ public function __construct( * @param class-string|null $class * @return T */ - public function createTemplate(?UI\Control $control = null, ?string $class = null): UI\Template + public function createTemplate(?UI\Control $control = null, ?string $class = null): Template { $class ??= $this->templateClass; if (!is_a($class, Template::class, allow_string: true)) { diff --git a/tests/UI/Component.redirect().phpt b/tests/UI/Component.redirect().phpt index e60a89939..3afc6a9a7 100644 --- a/tests/UI/Component.redirect().phpt +++ b/tests/UI/Component.redirect().phpt @@ -24,7 +24,7 @@ class TestPresenter extends Application\UI\Presenter } - public function sendResponse(Application\Response $response): void + public function sendResponse(Application\Response $response): never { parent::sendResponse($this->response = $response); } From 774a477c0438e90f15aab09f4914bb16a5923b86 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 12 May 2024 20:35:11 +0200 Subject: [PATCH 28/37] Presenter::handleInvalidLink() -> processInvalidLink() (BC break) --- src/Application/UI/Component.php | 2 +- src/Application/UI/Presenter.php | 2 +- src/Bridges/ApplicationDI/ApplicationExtension.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Application/UI/Component.php b/src/Application/UI/Component.php index 2b56884e4..39ba8c7c8 100644 --- a/src/Application/UI/Component.php +++ b/src/Application/UI/Component.php @@ -297,7 +297,7 @@ public function link(string $destination, $args = []): string return $this->getPresenter()->getLinkGenerator()->link($destination, $args, $this, 'link'); } catch (InvalidLinkException $e) { - return $this->getPresenter()->handleInvalidLink($e); + return $this->getPresenter()->processInvalidLink($e); } } diff --git a/src/Application/UI/Presenter.php b/src/Application/UI/Presenter.php index 2a0795049..eb04eb1b0 100644 --- a/src/Application/UI/Presenter.php +++ b/src/Application/UI/Presenter.php @@ -848,7 +848,7 @@ protected function requestToUrl(Application\Request $request, ?bool $relative = * Invalid link handler. Descendant can override this method to change default behaviour. * @throws InvalidLinkException */ - protected function handleInvalidLink(InvalidLinkException $e): string + protected function processInvalidLink(InvalidLinkException $e): string { if ($this->invalidLinkMode & self::InvalidLinkException) { throw $e; diff --git a/src/Bridges/ApplicationDI/ApplicationExtension.php b/src/Bridges/ApplicationDI/ApplicationExtension.php index e73b52be4..8dca9c25e 100644 --- a/src/Bridges/ApplicationDI/ApplicationExtension.php +++ b/src/Bridges/ApplicationDI/ApplicationExtension.php @@ -279,7 +279,7 @@ private function checkPresenter(string $class): void $re = $class::formatActionMethod('') . '.|' . $class::formatRenderMethod('') . '.|' . $class::formatSignalMethod('') . '.'; foreach ($rc->getMethods() as $rm) { - if (preg_match("#^(?!handleInvalidLink)($re)#", $rm->getName()) && (!$rm->isPublic() || $rm->isStatic())) { + if (preg_match("#^$re#", $rm->getName()) && (!$rm->isPublic() || $rm->isStatic())) { throw new Nette\InvalidStateException(sprintf('Method %s: this method must be public non-static.', Reflection::toString($rm))); } elseif (preg_match('#^createComponent.#', $rm->getName()) && ($rm->isPrivate() || $rm->isStatic())) { throw new Nette\InvalidStateException(sprintf('Method %s: this method must be non-private non-static.', Reflection::toString($rm))); From 6de8e5766df40733e396be806e52a69a34b55edd Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 24 Oct 2022 19:12:55 +0200 Subject: [PATCH 29/37] Revert "UI\PresenterComponent: removed references created by loadState() for persistent parameters. [Closes nette/nette#703][Closes nette/nette#703][Closes #69]" (possible BC break) This reverts commit cda17f460d020b0f042364d4e140742022a7e94d. See https://forum.nette.org/cs/35528-stejne-pojmenovany-parametr-akce-presenteru-a-persistentni-odlisne-chovani-v-nette-2-0-oproti-aktualnimu#p221742 BC break: Property must be nullable, ie: #[Persistent] public ?int $foo --- src/Application/UI/Component.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Application/UI/Component.php b/src/Application/UI/Component.php index 39ba8c7c8..ada0ae62f 100644 --- a/src/Application/UI/Component.php +++ b/src/Application/UI/Component.php @@ -161,9 +161,9 @@ public function loadState(array $params): void )); } - $this->$name = $params[$name]; + $this->$name = &$params[$name]; } else { - $params[$name] = $this->$name ?? null; + $params[$name] = &$this->$name; } } @@ -240,7 +240,7 @@ final public function getParameter(string $name): mixed */ final public function getParameters(): array { - return $this->params; + return array_map(fn($item) => $item, $this->params); } From bf409030a189fa657276d83b580136d3040a6313 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sat, 20 Apr 2024 02:43:19 +0200 Subject: [PATCH 30/37] Component: method checkRequirements() is called for createComponent() methods (BC break) --- src/Application/UI/Component.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Application/UI/Component.php b/src/Application/UI/Component.php index ada0ae62f..0f1b128c5 100644 --- a/src/Application/UI/Component.php +++ b/src/Application/UI/Component.php @@ -72,7 +72,9 @@ public function getUniqueId(): string protected function createComponent(string $name): ?Nette\ComponentModel\IComponent { if (method_exists($this, $method = 'createComponent' . $name)) { - (new AccessPolicy(new \ReflectionMethod($this, $method)))->checkAccess($this); + $rm = new \ReflectionMethod($this, $method); + (new AccessPolicy($rm))->checkAccess($this); + $this->checkRequirements($rm); } $res = parent::createComponent($name); if ($res && !$res instanceof SignalReceiver && !$res instanceof StatePersistent) { From 20edeebc634851920cb527be63f94bcbda2119fc Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 7 Apr 2024 04:33:34 +0200 Subject: [PATCH 31/37] Component: only UI components can be added to presenter/component (BC break) WIP --- src/Application/UI/Component.php | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/Application/UI/Component.php b/src/Application/UI/Component.php index 0f1b128c5..a44093ea1 100644 --- a/src/Application/UI/Component.php +++ b/src/Application/UI/Component.php @@ -69,6 +69,20 @@ public function getUniqueId(): string } + public function addComponent( + Nette\ComponentModel\IComponent $component, + ?string $name, + ?string $insertBefore = null, + ): static + { + if (!$component instanceof SignalReceiver && !$component instanceof StatePersistent) { + throw new Nette\InvalidStateException("Component '$name' of type " . get_debug_type($component) . ' is not intended to be used in the Presenter.'); + } + + return parent::addComponent($component, $name, $insertBefore = null); + } + + protected function createComponent(string $name): ?Nette\ComponentModel\IComponent { if (method_exists($this, $method = 'createComponent' . $name)) { @@ -76,13 +90,7 @@ protected function createComponent(string $name): ?Nette\ComponentModel\ICompone (new AccessPolicy($rm))->checkAccess($this); $this->checkRequirements($rm); } - $res = parent::createComponent($name); - if ($res && !$res instanceof SignalReceiver && !$res instanceof StatePersistent) { - $type = $res::class; - trigger_error("It seems that component '$name' of type $type is not intended to be used in the Presenter."); - } - - return $res; + return parent::createComponent($name); } From 6db23d26e7dd2f304ccd5a22db9753a944736384 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 20 Oct 2021 01:53:14 +0200 Subject: [PATCH 32/37] Component::link() & etc uses variadic parameter --- src/Application/UI/Component.php | 52 ++++++++++++++++---------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/Application/UI/Component.php b/src/Application/UI/Component.php index a44093ea1..1b4f26d5f 100644 --- a/src/Application/UI/Component.php +++ b/src/Application/UI/Component.php @@ -10,7 +10,7 @@ namespace Nette\Application\UI; use Nette; -use function array_key_exists, array_slice, func_get_arg, func_get_args, func_num_args, get_debug_type, is_array, link, method_exists, sprintf, trigger_error; +use function array_key_exists, count, func_get_arg, func_num_args, get_debug_type, is_array, link, method_exists, sprintf, trigger_error; /** @@ -295,15 +295,15 @@ public static function formatSignalMethod(string $signal): string /** * Generates URL to presenter, action or signal. * @param string $destination in format "[//] [[[module:]presenter:]action | signal! | this] [#fragment]" - * @param array|mixed $args + * @param mixed ...$args * @throws InvalidLinkException */ - public function link(string $destination, $args = []): string + public function link(string $destination, ...$args): string { try { - $args = func_num_args() < 3 && is_array($args) - ? $args - : array_slice(func_get_args(), 1); + $args = count($args) === 1 && is_array($args[0] ?? null) + ? $args[0] + : $args; return $this->getPresenter()->getLinkGenerator()->link($destination, $args, $this, 'link'); } catch (InvalidLinkException $e) { @@ -315,13 +315,13 @@ public function link(string $destination, $args = []): string /** * Returns destination as Link object. * @param string $destination in format "[//] [[[module:]presenter:]action | signal! | this] [#fragment]" - * @param array|mixed $args + * @param mixed ...$args */ - public function lazyLink(string $destination, $args = []): Link + public function lazyLink(string $destination, ...$args): Link { - $args = func_num_args() < 3 && is_array($args) - ? $args - : array_slice(func_get_args(), 1); + $args = count($args) === 1 && is_array($args[0] ?? null) + ? $args[0] + : $args; return new Link($this, $destination, $args); } @@ -329,15 +329,15 @@ public function lazyLink(string $destination, $args = []): Link /** * Determines whether it links to the current page. * @param ?string $destination in format "[[[module:]presenter:]action | signal! | this]" - * @param array|mixed $args + * @param mixed ...$args * @throws InvalidLinkException */ - public function isLinkCurrent(?string $destination = null, $args = []): bool + public function isLinkCurrent(?string $destination = null, ...$args): bool { if ($destination !== null) { - $args = func_num_args() < 3 && is_array($args) - ? $args - : array_slice(func_get_args(), 1); + $args = count($args) === 1 && is_array($args[0] ?? null) + ? $args[0] + : $args; $this->getPresenter()->getLinkGenerator()->createRequest($this, $destination, $args, 'test'); } @@ -348,14 +348,14 @@ public function isLinkCurrent(?string $destination = null, $args = []): bool /** * Redirect to another presenter, action or signal. * @param string $destination in format "[//] [[[module:]presenter:]action | signal! | this] [#fragment]" - * @param array|mixed $args + * @param mixed ...$args * @throws Nette\Application\AbortException */ - public function redirect(string $destination, $args = []): never + public function redirect(string $destination, ...$args): never { - $args = func_num_args() < 3 && is_array($args) - ? $args - : array_slice(func_get_args(), 1); + $args = count($args) === 1 && is_array($args[0] ?? null) + ? $args[0] + : $args; $presenter = $this->getPresenter(); $presenter->saveGlobalState(); $presenter->redirectUrl($presenter->getLinkGenerator()->link($destination, $args, $this, 'redirect')); @@ -365,14 +365,14 @@ public function redirect(string $destination, $args = []): never /** * Permanently redirects to presenter, action or signal. * @param string $destination in format "[//] [[[module:]presenter:]action | signal! | this] [#fragment]" - * @param array|mixed $args + * @param mixed ...$args * @throws Nette\Application\AbortException */ - public function redirectPermanent(string $destination, $args = []): never + public function redirectPermanent(string $destination, ...$args): never { - $args = func_num_args() < 3 && is_array($args) - ? $args - : array_slice(func_get_args(), 1); + $args = count($args) === 1 && is_array($args[0] ?? null) + ? $args[0] + : $args; $presenter = $this->getPresenter(); $presenter->redirectUrl( $presenter->getLinkGenerator()->link($destination, $args, $this, 'redirect'), From 0d7b1ebe17a41fc9652fcee1228f600f5be62033 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 12 Dec 2023 00:11:10 +0100 Subject: [PATCH 33/37] Presenter: removed constructor (BC break!) --- src/Application/UI/Presenter.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Application/UI/Presenter.php b/src/Application/UI/Presenter.php index eb04eb1b0..32752f7f2 100644 --- a/src/Application/UI/Presenter.php +++ b/src/Application/UI/Presenter.php @@ -120,11 +120,6 @@ abstract class Presenter extends Control implements Application\IPresenter private readonly LinkGenerator $linkGenerator; - public function __construct() - { - } - - final public function getRequest(): ?Application\Request { return $this->request; From 91e78d6aa69ef7f735895738704d50e65bd5927a Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 30 Nov 2025 00:07:16 +0100 Subject: [PATCH 34/37] removed support for @annotations (BC break) --- src/Application/LinkGenerator.php | 4 +- src/Application/UI/AccessPolicy.php | 1 - src/Application/UI/ComponentReflection.php | 76 +------------------ src/Application/UI/MethodReflection.php | 33 -------- .../ComponentReflection.parseAnnotation.phpt | 44 ----------- 5 files changed, 4 insertions(+), 154 deletions(-) delete mode 100644 src/Application/UI/MethodReflection.php delete mode 100644 tests/UI/ComponentReflection.parseAnnotation.phpt diff --git a/src/Application/LinkGenerator.php b/src/Application/LinkGenerator.php index 3ec9709bd..efc3e6d88 100644 --- a/src/Application/LinkGenerator.php +++ b/src/Application/LinkGenerator.php @@ -302,9 +302,7 @@ private function validateLinkTarget( { if ($mode !== 'forward' && !(new UI\AccessPolicy($element))->isLinkable()) { throw new UI\InvalidLinkException("Link to forbidden $message from '{$presenter->getName()}:{$presenter->getAction()}'."); - } elseif ($presenter?->invalidLinkMode - && (UI\ComponentReflection::parseAnnotation($element, 'deprecated') || $element->getAttributes(Attributes\Deprecated::class)) - ) { + } elseif ($presenter?->invalidLinkMode && $element->getAttributes(Attributes\Deprecated::class)) { trigger_error("Link to deprecated $message from '{$presenter->getName()}:{$presenter->getAction()}'.", E_USER_DEPRECATED); } } diff --git a/src/Application/UI/AccessPolicy.php b/src/Application/UI/AccessPolicy.php index b478d3ed6..7ac5bd8b2 100644 --- a/src/Application/UI/AccessPolicy.php +++ b/src/Application/UI/AccessPolicy.php @@ -71,7 +71,6 @@ private function applyInternalRules(array $attrs, Component $component): array if ( $this->element instanceof \ReflectionMethod && str_starts_with($this->element->getName(), $component::formatSignalMethod('')) - && !ComponentReflection::parseAnnotation($this->element, 'crossOrigin') && !Nette\Utils\Arrays::some($attrs, fn($attr) => $attr->sameOrigin === false) ) { $attrs[] = new Attributes\Requires(sameOrigin: true); diff --git a/src/Application/UI/ComponentReflection.php b/src/Application/UI/ComponentReflection.php index dcb879b69..4296731cf 100644 --- a/src/Application/UI/ComponentReflection.php +++ b/src/Application/UI/ComponentReflection.php @@ -44,10 +44,7 @@ public function getParameters(): array foreach ($this->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) { if ($prop->isStatic()) { continue; - } elseif ( - self::parseAnnotation($prop, 'persistent') - || $prop->getAttributes(Attributes\Persistent::class) - ) { + } elseif ($prop->getAttributes(Attributes\Persistent::class)) { $params[$prop->getName()] = [ 'def' => $prop->hasDefaultValue() ? $prop->getDefaultValue() : null, 'type' => ParameterConverter::getType($prop), @@ -88,7 +85,7 @@ public function getPersistentParams(): array /** * Returns array of persistent components. They are tagged with class-level attribute - * #[Persistent] or annotation @persistent or returned by Presenter::getPersistentComponents(). + * #[Persistent] or returned by Presenter::getPersistentComponents(). * @return array */ public function getPersistentComponents(): array @@ -100,9 +97,7 @@ public function getPersistentComponents(): array } $attrs = $this->getAttributes(Attributes\Persistent::class); - $names = $attrs - ? $attrs[0]->getArguments() - : (array) self::parseAnnotation($this, 'persistent'); + $names = $attrs ? $attrs[0]->getArguments() : []; $names = array_merge($names, $class::getPersistentComponents()); $components = array_fill_keys($names, ['since' => $class]); @@ -170,71 +165,6 @@ public function getSignalMethod(string $signal): ?\ReflectionMethod } - /** - * Returns an annotation value. - * @deprecated - */ - public static function parseAnnotation(\Reflector $ref, string $name): ?array - { - if (!preg_match_all('#[\s*]@' . preg_quote($name, '#') . '(?:\(\s*([^)]*)\s*\)|\s|$)#', (string) $ref->getDocComment(), $m)) { - return null; - } - - $tokens = ['true' => true, 'false' => false, 'null' => null]; - $res = []; - foreach ($m[1] as $s) { - foreach (preg_split('#\s*,\s*#', $s, -1, PREG_SPLIT_NO_EMPTY) ?: ['true'] as $item) { - $res[] = array_key_exists($tmp = strtolower($item), $tokens) - ? $tokens[$tmp] - : $item; - } - } - - $alt = match ($name) { - 'persistent' => '#[Nette\Application\Attributes\Persistent]', - 'deprecated' => '#[Nette\Application\Attributes\Deprecated]', - 'crossOrigin' => '#[Nette\Application\Attributes\Request(sameOrigin: false)]', - default => 'alternative' - }; - trigger_error("Annotation @$name is deprecated, use $alt (used in " . Reflection::toString($ref) . ')', E_USER_DEPRECATED); - return $res; - } - - - #[\Deprecated] - public function hasAnnotation(string $name): bool - { - return (bool) self::parseAnnotation($this, $name); - } - - - #[\Deprecated] - public function getAnnotation(string $name): mixed - { - $res = self::parseAnnotation($this, $name); - return $res ? end($res) : null; - } - - - public function getMethod($name): MethodReflection - { - return new MethodReflection($this->getName(), $name); - } - - - /** - * @return MethodReflection[] - */ - public function getMethods($filter = -1): array - { - foreach ($res = parent::getMethods($filter) as $key => $val) { - $res[$key] = new MethodReflection($this->getName(), $val->getName()); - } - - return $res; - } - - #[\Deprecated] public static function combineArgs(\ReflectionFunctionAbstract $method, array $args): array { diff --git a/src/Application/UI/MethodReflection.php b/src/Application/UI/MethodReflection.php deleted file mode 100644 index 45461f8cc..000000000 --- a/src/Application/UI/MethodReflection.php +++ /dev/null @@ -1,33 +0,0 @@ - Date: Mon, 22 Jan 2024 00:20:18 +0100 Subject: [PATCH 35/37] uses nette/routing 4.0 --- src/Application/Routers/RouteList.php | 4 ++-- src/Bridges/ApplicationTracy/RoutingPanel.php | 2 +- tests/Routers/RouteList.addRoute.phpt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Application/Routers/RouteList.php b/src/Application/Routers/RouteList.php index 92f0d73d7..6fbea06fe 100644 --- a/src/Application/Routers/RouteList.php +++ b/src/Application/Routers/RouteList.php @@ -69,10 +69,10 @@ public function addRoute( #[Language('TEXT')] string $mask, array|string|\Closure $metadata = [], - int|bool $oneWay = 0, + bool $oneWay = false, ): static { - $this->add(new Route($mask, $metadata), (int) $oneWay); + $this->add(new Route($mask, $metadata), $oneWay); return $this; } diff --git a/src/Bridges/ApplicationTracy/RoutingPanel.php b/src/Bridges/ApplicationTracy/RoutingPanel.php index 869ed2a03..aeb38e72b 100644 --- a/src/Bridges/ApplicationTracy/RoutingPanel.php +++ b/src/Bridges/ApplicationTracy/RoutingPanel.php @@ -89,7 +89,7 @@ private function analyse(Routing\RouteList $router, ?Nette\Http\IRequest $httpRe continue; } - $matched = $flags[$i] & $router::ONE_WAY ? 'oneway' : 'no'; + $matched = empty($flags[$i]['oneWay']) ? 'no' : 'oneway'; $params = $e = null; try { if ( diff --git a/tests/Routers/RouteList.addRoute.phpt b/tests/Routers/RouteList.addRoute.phpt index fd6ab423a..068f66805 100644 --- a/tests/Routers/RouteList.addRoute.phpt +++ b/tests/Routers/RouteList.addRoute.phpt @@ -12,7 +12,7 @@ require __DIR__ . '/Route.php'; $list = new RouteList; $list->addRoute('foo', ['presenter' => 'foo'], RouteList::ONE_WAY); -$list->addRoute('bar', ['presenter' => 'bar'], RouteList::ONE_WAY); +$list->addRoute('bar', ['presenter' => 'bar'], oneWay: true); $list->addRoute('hello', ['presenter' => 'hello']); From 0852e8ea23a95e64a99f57c3e00370c810482841 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 19 Apr 2024 19:01:35 +0200 Subject: [PATCH 36/37] removed {templatePrint} --- .../Nodes/TemplatePrintNode.php | 61 ------------------- src/Bridges/ApplicationLatte/UIExtension.php | 1 - 2 files changed, 62 deletions(-) delete mode 100644 src/Bridges/ApplicationLatte/Nodes/TemplatePrintNode.php diff --git a/src/Bridges/ApplicationLatte/Nodes/TemplatePrintNode.php b/src/Bridges/ApplicationLatte/Nodes/TemplatePrintNode.php deleted file mode 100644 index 151913a07..000000000 --- a/src/Bridges/ApplicationLatte/Nodes/TemplatePrintNode.php +++ /dev/null @@ -1,61 +0,0 @@ -getParameters(), ' . PhpHelpers::dump($this->template ?? Template::class) . '); exit;'; - } - - - public static function printClass(array $params, string $parentClass): void - { - $bp = new Latte\Essential\Blueprint; - if (!method_exists($bp, 'generateTemplateClass')) { - throw new \LogicException("Please update 'latte/latte' to version 3.0.15 or newer."); - } - - $control = $params['control'] ?? $params['presenter'] ?? null; - $name = 'Template'; - if ($control instanceof UI\Control) { - $name = preg_replace('#(Control|Presenter)$#', '', $control::class) . 'Template'; - unset($params[$control instanceof UI\Presenter ? 'control' : 'presenter']); - } - $class = $bp->generateTemplateClass($params, $name, $parentClass); - $code = (string) $class->getNamespace(); - - $bp->printBegin(); - $bp->printCode($code); - - if ($control instanceof UI\Control) { - $file = dirname((new \ReflectionClass($control))->getFileName()) . '/' . $class->getName() . '.php'; - if (file_exists($file)) { - echo "unsaved, file {$bp->clickableFile($file)} already exists"; - } else { - echo "saved to file {$bp->clickableFile($file)}"; - file_put_contents($file, "printEnd(); - exit; - } -} diff --git a/src/Bridges/ApplicationLatte/UIExtension.php b/src/Bridges/ApplicationLatte/UIExtension.php index 6851803ff..ac07a7587 100644 --- a/src/Bridges/ApplicationLatte/UIExtension.php +++ b/src/Bridges/ApplicationLatte/UIExtension.php @@ -80,7 +80,6 @@ public function getTags(): array 'plink' => Nodes\LinkNode::create(...), 'link' => Nodes\LinkNode::create(...), 'linkBase' => Nodes\LinkBaseNode::create(...), - 'templatePrint' => Nodes\TemplatePrintNode::create(...), 'snippet' => Nodes\SnippetNode::create(...), 'snippetArea' => Nodes\SnippetAreaNode::create(...), 'layout' => $this->createExtendsNode(...), From b2f31872b59f1fab27b9ab830ca8ee1feeb315a7 Mon Sep 17 00:00:00 2001 From: Makr Date: Thu, 12 Mar 2026 15:19:47 +0100 Subject: [PATCH 37/37] Update RouteList.php fix getclass -> get_class --- src/Application/Routers/RouteList.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Application/Routers/RouteList.php b/src/Application/Routers/RouteList.php index 6fbea06fe..f77a81922 100644 --- a/src/Application/Routers/RouteList.php +++ b/src/Application/Routers/RouteList.php @@ -102,7 +102,7 @@ public function offsetSet($index, $router): void if ($router instanceof Route) { trigger_error('Usage `$router[] = new Route(...)` is deprecated, use `$router->addRoute(...)`.', E_USER_DEPRECATED); } else { - $class = getclass($router); + $class = get_class($router); trigger_error("Usage `\$router[] = new $class` is deprecated, use `\$router->add(new $class)`.", E_USER_DEPRECATED); }