Skip to content
Merged
4 changes: 4 additions & 0 deletions src/ViewTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -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}.
Expand Down Expand Up @@ -90,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;
}
Expand Down
50 changes: 50 additions & 0 deletions tests/ViewTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -253,6 +255,54 @@ 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';
$baseRenderer = new PhpTemplateRenderer();

// Create a renderer that adds a marker to identify which renderer was used
$phpRenderer = new class ($baseRenderer) implements TemplateRendererInterface {
public function __construct(private readonly PhpTemplateRenderer $baseRenderer)
{
}

public function render(ViewInterface $view, string $template, array $parameters): string
{
return '[php]' . $this->baseRenderer->render($view, $template, $parameters);
}
};

$bladePhpRenderer = new class ($baseRenderer) implements TemplateRendererInterface {
public function __construct(private readonly PhpTemplateRenderer $baseRenderer)
{
}

public function render(ViewInterface $view, string $template, array $parameters): string
{
return '[blade.php]' . $this->baseRenderer->render($view, $template, $parameters);
}
};

// 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);
Expand Down
Loading