From 42d190dd6350b1625f87b9563591f4c1201d26ec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 06:13:58 +0000 Subject: [PATCH 1/8] Initial plan From eed66990ddb7cfc83a3393c5f9ff312e38cfb2af Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 06:21:42 +0000 Subject: [PATCH 2/8] Sort renderer extensions by length to prioritize specific extensions Co-authored-by: samdark <47294+samdark@users.noreply.github.com> --- src/ViewTrait.php | 8 +++++++- tests/ViewTest.php | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/ViewTrait.php b/src/ViewTrait.php index 11c19b9e7..feeb29af0 100644 --- a/src/ViewTrait.php +++ b/src/ViewTrait.php @@ -25,7 +25,9 @@ use function is_file; use function pathinfo; use function str_ends_with; +use function strlen; use function substr; +use function uksort; /** * `ViewTrait` could be used as a base implementation of {@see ViewInterface}. @@ -447,7 +449,11 @@ public function render(string $view, array $parameters = []): string $renderer = null; $fileName = basename($viewFile); - foreach ($this->renderers as $extension => $candidateRenderer) { + // Sort extensions by length in descending order to match more specific extensions first + $sortedRenderers = $this->renderers; + uksort($sortedRenderers, static fn (string $a, string $b): int => strlen($b) <=> strlen($a)); + + foreach ($sortedRenderers as $extension => $candidateRenderer) { if ($extension === '') { continue; } diff --git a/tests/ViewTest.php b/tests/ViewTest.php index cb7283ee9..6a1e11821 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -253,6 +253,45 @@ public function testDoubleExtensionRenderer(): void ); } + /** + * Test that longer extensions are matched before shorter ones when there are overlapping extensions. + * @link https://github.com/yiisoft/view/pull/291#discussion_r2663151134 + */ + public function testOverlappingExtensionRendererPriority(): void + { + $filename = 'test'; + + // Create a renderer that adds a marker to identify which renderer was used + $phpRenderer = new class () extends PhpTemplateRenderer { + public function render($view, $template, array $params = []): string + { + return '[php]' . parent::render($view, $template, $params); + } + }; + + $bladePhpRenderer = new class () extends PhpTemplateRenderer { + public function render($view, $template, array $params = []): string + { + return '[blade.php]' . parent::render($view, $template, $params); + } + }; + + // Register both "php" and "blade.php" renderers + $view = $this + ->createViewWithBasePath($this->tempDirectory) + ->withContext($this->createContext($this->tempDirectory)) + ->withRenderers([ + 'php' => $phpRenderer, + 'blade.php' => $bladePhpRenderer, + ]); + + file_put_contents("$this->tempDirectory/$filename.blade.php", 'content'); + + // The blade.php renderer should be used because it's more specific (longer extension) + $result = $view->render($filename); + $this->assertStringStartsWith('[blade.php]', $result); + } + public function testLocalize(): void { $view = $this->createViewWithBasePath($this->tempDirectory); From ccf5b2025b8825224818cf7c5f6b89239860662c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 06:23:45 +0000 Subject: [PATCH 3/8] Optimize: Sort renderers once in withRenderers instead of on every render Co-authored-by: samdark <47294+samdark@users.noreply.github.com> --- src/ViewTrait.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/ViewTrait.php b/src/ViewTrait.php index feeb29af0..bc6fabe92 100644 --- a/src/ViewTrait.php +++ b/src/ViewTrait.php @@ -92,6 +92,8 @@ public function withBasePath(string|null $basePath): static public function withRenderers(array $renderers): static { $new = clone $this; + // Sort by extension length (descending) to match more specific extensions first + uksort($renderers, static fn (string $a, string $b): int => strlen($b) <=> strlen($a)); $new->renderers = $renderers; return $new; } @@ -449,11 +451,7 @@ public function render(string $view, array $parameters = []): string $renderer = null; $fileName = basename($viewFile); - // Sort extensions by length in descending order to match more specific extensions first - $sortedRenderers = $this->renderers; - uksort($sortedRenderers, static fn (string $a, string $b): int => strlen($b) <=> strlen($a)); - - foreach ($sortedRenderers as $extension => $candidateRenderer) { + foreach ($this->renderers as $extension => $candidateRenderer) { if ($extension === '') { continue; } From dcf72deda19861b0bf51a38745f2f3baaa622b2f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 06:40:36 +0000 Subject: [PATCH 4/8] Fix: Use TemplateRendererInterface instead of extending final PhpTemplateRenderer Co-authored-by: samdark <47294+samdark@users.noreply.github.com> --- tests/ViewTest.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/ViewTest.php b/tests/ViewTest.php index 6a1e11821..c6582edd2 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -16,11 +16,13 @@ use Yiisoft\View\Event\View\PageEnd; use Yiisoft\View\Exception\ViewNotFoundException; use Yiisoft\View\PhpTemplateRenderer; +use Yiisoft\View\TemplateRendererInterface; use Yiisoft\View\Tests\TestSupport\TestHelper; use Yiisoft\View\Tests\TestSupport\TestTrait; use Yiisoft\View\Theme; use Yiisoft\View\View; use Yiisoft\View\ViewContextInterface; +use Yiisoft\View\ViewInterface; use function crc32; use function dechex; @@ -262,17 +264,17 @@ public function testOverlappingExtensionRendererPriority(): void $filename = 'test'; // Create a renderer that adds a marker to identify which renderer was used - $phpRenderer = new class () extends PhpTemplateRenderer { - public function render($view, $template, array $params = []): string + $phpRenderer = new class () implements TemplateRendererInterface { + public function render(ViewInterface $view, string $template, array $parameters): string { - return '[php]' . parent::render($view, $template, $params); + return '[php]' . (new PhpTemplateRenderer())->render($view, $template, $parameters); } }; - $bladePhpRenderer = new class () extends PhpTemplateRenderer { - public function render($view, $template, array $params = []): string + $bladePhpRenderer = new class () implements TemplateRendererInterface { + public function render(ViewInterface $view, string $template, array $parameters): string { - return '[blade.php]' . parent::render($view, $template, $params); + return '[blade.php]' . (new PhpTemplateRenderer())->render($view, $template, $parameters); } }; From 0d51cbe5278a5c9aede8a62f1e00126bf2f294a3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 06:46:29 +0000 Subject: [PATCH 5/8] Optimize test: Reuse PhpTemplateRenderer instance instead of creating new ones Co-authored-by: samdark <47294+samdark@users.noreply.github.com> --- tests/ViewTest.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/tests/ViewTest.php b/tests/ViewTest.php index c6582edd2..33e3b5489 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -262,19 +262,28 @@ public function testDoubleExtensionRenderer(): void public function testOverlappingExtensionRendererPriority(): void { $filename = 'test'; + $baseRenderer = new PhpTemplateRenderer(); // Create a renderer that adds a marker to identify which renderer was used - $phpRenderer = new class () implements TemplateRendererInterface { + $phpRenderer = new class ($baseRenderer) implements TemplateRendererInterface { + public function __construct(private PhpTemplateRenderer $baseRenderer) + { + } + public function render(ViewInterface $view, string $template, array $parameters): string { - return '[php]' . (new PhpTemplateRenderer())->render($view, $template, $parameters); + return '[php]' . $this->baseRenderer->render($view, $template, $parameters); } }; - $bladePhpRenderer = new class () implements TemplateRendererInterface { + $bladePhpRenderer = new class ($baseRenderer) implements TemplateRendererInterface { + public function __construct(private PhpTemplateRenderer $baseRenderer) + { + } + public function render(ViewInterface $view, string $template, array $parameters): string { - return '[blade.php]' . (new PhpTemplateRenderer())->render($view, $template, $parameters); + return '[blade.php]' . $this->baseRenderer->render($view, $template, $parameters); } }; From 4f58ea56b7816d528d0553843392d91f8262c264 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 06:47:35 +0000 Subject: [PATCH 6/8] Cleanup: Remove empty constructor bodies from anonymous classes Co-authored-by: samdark <47294+samdark@users.noreply.github.com> --- tests/ViewTest.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/ViewTest.php b/tests/ViewTest.php index 33e3b5489..30c0a0c56 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -266,9 +266,7 @@ public function testOverlappingExtensionRendererPriority(): void // Create a renderer that adds a marker to identify which renderer was used $phpRenderer = new class ($baseRenderer) implements TemplateRendererInterface { - public function __construct(private PhpTemplateRenderer $baseRenderer) - { - } + public function __construct(private PhpTemplateRenderer $baseRenderer) {} public function render(ViewInterface $view, string $template, array $parameters): string { @@ -277,9 +275,7 @@ public function render(ViewInterface $view, string $template, array $parameters) }; $bladePhpRenderer = new class ($baseRenderer) implements TemplateRendererInterface { - public function __construct(private PhpTemplateRenderer $baseRenderer) - { - } + public function __construct(private PhpTemplateRenderer $baseRenderer) {} public function render(ViewInterface $view, string $template, array $parameters): string { From ff485e5f1060652a53296c8d82a643c2ab785692 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Tue, 6 Jan 2026 06:47:42 +0000 Subject: [PATCH 7/8] Apply fixes from StyleCI --- tests/ViewTest.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/ViewTest.php b/tests/ViewTest.php index 30c0a0c56..33e3b5489 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -266,7 +266,9 @@ public function testOverlappingExtensionRendererPriority(): void // Create a renderer that adds a marker to identify which renderer was used $phpRenderer = new class ($baseRenderer) implements TemplateRendererInterface { - public function __construct(private PhpTemplateRenderer $baseRenderer) {} + public function __construct(private PhpTemplateRenderer $baseRenderer) + { + } public function render(ViewInterface $view, string $template, array $parameters): string { @@ -275,7 +277,9 @@ public function render(ViewInterface $view, string $template, array $parameters) }; $bladePhpRenderer = new class ($baseRenderer) implements TemplateRendererInterface { - public function __construct(private PhpTemplateRenderer $baseRenderer) {} + public function __construct(private PhpTemplateRenderer $baseRenderer) + { + } public function render(ViewInterface $view, string $template, array $parameters): string { From 1796fdb6be4b2b041b638a09f82e951ab3feea1c Mon Sep 17 00:00:00 2001 From: samdark <47294+samdark@users.noreply.github.com> Date: Tue, 6 Jan 2026 06:48:17 +0000 Subject: [PATCH 8/8] Apply Rector changes (CI) --- tests/ViewTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ViewTest.php b/tests/ViewTest.php index 33e3b5489..f00d54eff 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -266,7 +266,7 @@ public function testOverlappingExtensionRendererPriority(): void // Create a renderer that adds a marker to identify which renderer was used $phpRenderer = new class ($baseRenderer) implements TemplateRendererInterface { - public function __construct(private PhpTemplateRenderer $baseRenderer) + public function __construct(private readonly PhpTemplateRenderer $baseRenderer) { } @@ -277,7 +277,7 @@ public function render(ViewInterface $view, string $template, array $parameters) }; $bladePhpRenderer = new class ($baseRenderer) implements TemplateRendererInterface { - public function __construct(private PhpTemplateRenderer $baseRenderer) + public function __construct(private readonly PhpTemplateRenderer $baseRenderer) { }