From e91cf46f85e7d9acc6b8e2d0a8809cce4b785713 Mon Sep 17 00:00:00 2001 From: Maxim Akimov Date: Tue, 18 Mar 2025 16:32:20 +0200 Subject: [PATCH] v 1.0.5: feature - empty template file calls errorDispatcher --- .../Template/FileModelTemplateResolver.php | 20 +++---- .../Template/FileTemplateContentProvider.php | 41 +++++++++++++ .../TemplateRendererWithFileTemplate.php | 20 +++---- private-classes/View/ViewNamespace.php | 7 +++ .../FileTemplateContentProviderInterface.php | 10 ++++ src/View/ViewTemplateRenderer.php | 8 ++- .../FileModelTemplateProviderTest.php | 60 ++++++++----------- .../TemplateRendererWithFileTemplateTest.php | 15 ++++- 8 files changed, 118 insertions(+), 63 deletions(-) create mode 100644 private-classes/Template/FileTemplateContentProvider.php create mode 100644 src/Interfaces/Template/FileTemplateContentProviderInterface.php diff --git a/private-classes/Template/FileModelTemplateResolver.php b/private-classes/Template/FileModelTemplateResolver.php index d83aea6..50ee05c 100644 --- a/private-classes/Template/FileModelTemplateResolver.php +++ b/private-classes/Template/FileModelTemplateResolver.php @@ -7,6 +7,7 @@ use Prosopo\Views\Interfaces\Model\ModelNameResolverInterface; use Prosopo\Views\Interfaces\Model\ModelNamespaceResolverInterface; use Prosopo\Views\Interfaces\Model\TemplateModelInterface; +use Prosopo\Views\Interfaces\Template\FileTemplateContentProviderInterface; use Prosopo\Views\Interfaces\Template\ModelTemplateResolverInterface; /** @@ -19,6 +20,7 @@ final class FileModelTemplateResolver implements ModelTemplateResolverInterface private string $namespace; private string $extension; private bool $isFileBasedTemplate; + private FileTemplateContentProviderInterface $fileTemplateContentProvider; private ModelNameResolverInterface $modelNameProvider; private ModelNamespaceResolverInterface $modelNamespaceProvider; @@ -27,12 +29,15 @@ public function __construct( string $templatesRootPath, string $extension, bool $isFileBasedTemplate, + FileTemplateContentProviderInterface $fileTemplateContentProvider, ModelNamespaceResolverInterface $modelNamespaceProvider, ModelNameResolverInterface $modelNameProvider ) { $this->templatesRootPath = $templatesRootPath; $this->namespace = $namespace; $this->extension = $extension; + + $this->fileTemplateContentProvider = $fileTemplateContentProvider; $this->isFileBasedTemplate = $isFileBasedTemplate; $this->modelNameProvider = $modelNameProvider; $this->modelNamespaceProvider = $modelNamespaceProvider; @@ -49,22 +54,11 @@ public function resolveModelTemplate(TemplateModelInterface $model): string $relativeTemplatePath = $this->getRelativeTemplatePath($relativeModelNamespace, $modelName); - $absoluteTemplatePath = $this->getAbsoluteTemplatePath($relativeTemplatePath); - return $this->isFileBasedTemplate ? + return $this->isFileBasedTemplate ? $absoluteTemplatePath : - $this->getFileContent($absoluteTemplatePath); - } - - protected function getFileContent(string $file): string - { - if (! file_exists($file)) { - return ''; - } - - // @phpcs:ignore - return (string)file_get_contents($file); + $this->fileTemplateContentProvider->getFileTemplateContent($absoluteTemplatePath); } protected function getAbsoluteTemplatePath(string $relativeTemplatePath): string diff --git a/private-classes/Template/FileTemplateContentProvider.php b/private-classes/Template/FileTemplateContentProvider.php new file mode 100644 index 0000000..dd2e9c8 --- /dev/null +++ b/private-classes/Template/FileTemplateContentProvider.php @@ -0,0 +1,41 @@ +errorEventName = $errorEventName; + $this->eventDispatcher = $eventDispatcher; + } + + public function getFileTemplateContent(string $file): string + { + if (file_exists($file)) { + // @phpcs:ignore + return (string)file_get_contents($file); + } + + $this->eventDispatcher->dispatchEvent($this->errorEventName, [ + 'error' => 'Template file does not exist', + 'file' => $file, + ]); + + return ''; + } +} diff --git a/private-classes/Template/TemplateRendererWithFileTemplate.php b/private-classes/Template/TemplateRendererWithFileTemplate.php index 730ada2..7743b63 100644 --- a/private-classes/Template/TemplateRendererWithFileTemplate.php +++ b/private-classes/Template/TemplateRendererWithFileTemplate.php @@ -4,6 +4,7 @@ namespace Prosopo\Views\PrivateClasses\Template; +use Prosopo\Views\Interfaces\Template\FileTemplateContentProviderInterface; use Prosopo\Views\Interfaces\Template\TemplateRendererInterface; /** @@ -12,26 +13,21 @@ */ final class TemplateRendererWithFileTemplate implements TemplateRendererInterface { + private FileTemplateContentProviderInterface $fileTemplateContentProvider; private TemplateRendererInterface $templateRenderer; - public function __construct(TemplateRendererInterface $templateRenderer) - { + public function __construct( + FileTemplateContentProviderInterface $fileTemplateContentProvider, + TemplateRendererInterface $templateRenderer + ) { + $this->fileTemplateContentProvider = $fileTemplateContentProvider; $this->templateRenderer = $templateRenderer; } public function renderTemplate(string $template, array $variables = []): string { - $template = $this->getFileContent($template); + $template = $this->fileTemplateContentProvider->getFileTemplateContent($template); return $this->templateRenderer->renderTemplate($template, $variables); } - - protected function getFileContent(string $file): string - { - if (! file_exists($file)) { - return ''; - } - - return (string) file_get_contents($file); - } } diff --git a/private-classes/View/ViewNamespace.php b/private-classes/View/ViewNamespace.php index dd8d33b..23aeff0 100644 --- a/private-classes/View/ViewNamespace.php +++ b/private-classes/View/ViewNamespace.php @@ -22,6 +22,7 @@ ModelRendererWithEventDetails}; use Prosopo\Views\PrivateClasses\EventDispatcher; use Prosopo\Views\PrivateClasses\Template\FileModelTemplateResolver; +use Prosopo\Views\PrivateClasses\Template\FileTemplateContentProvider; use Prosopo\Views\PrivateClasses\Template\TemplateRendererWithModelsRender; use Prosopo\Views\View\ViewNamespaceConfig; use Prosopo\Views\View\ViewNamespaceModules; @@ -81,6 +82,11 @@ public function __construct( new ModelNameResolver(new ObjectClassReader()) : $modelNameProvider; + $fileTemplateContentProvider = new FileTemplateContentProvider( + $templateErrorEventName, + $eventDispatcher, + ); + $modelTemplateResolver = $modules->getModelTemplateResolver(); $modelTemplateResolver = null === $modelTemplateResolver ? new FileModelTemplateResolver( @@ -88,6 +94,7 @@ public function __construct( $config->getTemplatesRootPath(), $config->getTemplateFileExtension(), $config->fileBasedTemplates(), + $fileTemplateContentProvider, $modelNamespaceProvider, $modelNameProvider ) : diff --git a/src/Interfaces/Template/FileTemplateContentProviderInterface.php b/src/Interfaces/Template/FileTemplateContentProviderInterface.php new file mode 100644 index 0000000..da7c8af --- /dev/null +++ b/src/Interfaces/Template/FileTemplateContentProviderInterface.php @@ -0,0 +1,10 @@ +fileBasedTemplates()) { - $templateRenderer = new TemplateRendererWithFileTemplate($templateRenderer); + $fileTemplateContentProvider = new FileTemplateContentProvider( + $errorEventName, + $eventDispatcher, + ); + + $templateRenderer = new TemplateRendererWithFileTemplate($fileTemplateContentProvider, $templateRenderer); } $templateRenderer = new TemplateRendererWithCustomEscape( diff --git a/tests/pest/Unit/Template/FileModelTemplateProviderTest.php b/tests/pest/Unit/Template/FileModelTemplateProviderTest.php index 94c4193..5b5ff8b 100644 --- a/tests/pest/Unit/Template/FileModelTemplateProviderTest.php +++ b/tests/pest/Unit/Template/FileModelTemplateProviderTest.php @@ -10,6 +10,7 @@ use Prosopo\Views\Interfaces\Model\ModelNameResolverInterface; use Prosopo\Views\Interfaces\Model\ModelNamespaceResolverInterface; use Prosopo\Views\Interfaces\Model\TemplateModelInterface; +use Prosopo\Views\Interfaces\Template\FileTemplateContentProviderInterface; use Prosopo\Views\PrivateClasses\Template\FileModelTemplateResolver; class FileModelTemplateProviderTest extends TestCase @@ -21,11 +22,13 @@ public function testGetTemplateReturnsFileContent(): void $templateModel = Mockery::mock(TemplateModelInterface::class); $modelNamespaceProviderMock = Mockery::mock(ModelNamespaceResolverInterface::class); $modelNameProviderMock = Mockery::mock(ModelNameResolverInterface::class); + $fileTemplateContentProviderMock = Mockery::mock(FileTemplateContentProviderInterface::class); $provider = new FileModelTemplateResolver( 'App\\Views', vfsStream::url('templates'), '.blade.php', false, + $fileTemplateContentProviderMock, $modelNamespaceProviderMock, $modelNameProviderMock ); @@ -34,6 +37,8 @@ public function testGetTemplateReturnsFileContent(): void $result = fn() => $provider->resolveModelTemplate($templateModel); // then + $fileTemplateContentProviderMock->shouldReceive('getFileTemplateContent') + ->andReturn('View Content'); $modelNamespaceProviderMock->shouldReceive('resolveModelNamespace') ->once() ->with($templateModel) @@ -56,11 +61,13 @@ public function testGetFileBasedTemplateReturnsFileName(): void $templateModel = Mockery::mock(TemplateModelInterface::class); $modelNamespaceProviderMock = Mockery::mock(ModelNamespaceResolverInterface::class); $modelNameProviderMock = Mockery::mock(ModelNameResolverInterface::class); + $fileTemplateContentProviderMock = Mockery::mock(FileTemplateContentProviderInterface::class); $provider = new FileModelTemplateResolver( 'App\\Views', vfsStream::url('templates'), '.blade.php', true, + $fileTemplateContentProviderMock, $modelNamespaceProviderMock, $modelNameProviderMock ); @@ -69,6 +76,8 @@ public function testGetFileBasedTemplateReturnsFileName(): void $result = fn() => $provider->resolveModelTemplate($templateModel); // then + $fileTemplateContentProviderMock->shouldReceive('getFileTemplateContent') + ->andReturn('View Content'); $modelNamespaceProviderMock->shouldReceive('resolveModelNamespace') ->once() ->with($templateModel) @@ -84,41 +93,6 @@ public function testGetFileBasedTemplateReturnsFileName(): void Mockery::close(); } - public function testGetTemplateReturnsEmptyStringForMissingFile(): void - { - // given - vfsStream::setup('templates'); - $templateModel = Mockery::mock(TemplateModelInterface::class); - $modelNamespaceProviderMock = Mockery::mock(ModelNamespaceResolverInterface::class); - $modelNameProviderMock = Mockery::mock(ModelNameResolverInterface::class); - $provider = new FileModelTemplateResolver( - 'App\\Views', - vfsStream::url('templates'), - '.blade.php', - false, - $modelNamespaceProviderMock, - $modelNameProviderMock - ); - - // when - $result = fn() => $provider->resolveModelTemplate($templateModel); - - // then - $modelNamespaceProviderMock->shouldReceive('resolveModelNamespace') - ->once() - ->with($templateModel) - ->andReturn('App\\Views'); - $modelNameProviderMock->shouldReceive('resolveModelName') - ->once() - ->with($templateModel) - ->andReturn('SampleView'); - - $this->assertSame('', $result()); - - // apply - Mockery::close(); - } - public function testGetFileBasedTemplateReturnsPathForMissingFile(): void { // given @@ -126,11 +100,13 @@ public function testGetFileBasedTemplateReturnsPathForMissingFile(): void $templateModel = Mockery::mock(TemplateModelInterface::class); $modelNamespaceProviderMock = Mockery::mock(ModelNamespaceResolverInterface::class); $modelNameProviderMock = Mockery::mock(ModelNameResolverInterface::class); + $fileTemplateContentProviderMock = Mockery::mock(FileTemplateContentProviderInterface::class); $provider = new FileModelTemplateResolver( 'App\\Views', vfsStream::url('templates'), '.blade.php', true, + $fileTemplateContentProviderMock, $modelNamespaceProviderMock, $modelNameProviderMock ); @@ -161,11 +137,13 @@ public function testGetTemplateHandlesCamelCaseConversion(): void $templateModel = Mockery::mock(TemplateModelInterface::class); $modelNamespaceProviderMock = Mockery::mock(ModelNamespaceResolverInterface::class); $modelNameProviderMock = Mockery::mock(ModelNameResolverInterface::class); + $fileTemplateContentProviderMock = Mockery::mock(FileTemplateContentProviderInterface::class); $provider = new FileModelTemplateResolver( 'App\\Views', vfsStream::url('templates'), '.blade.php', false, + $fileTemplateContentProviderMock, $modelNamespaceProviderMock, $modelNameProviderMock ); @@ -174,6 +152,8 @@ public function testGetTemplateHandlesCamelCaseConversion(): void $result = fn() => $provider->resolveModelTemplate($templateModel); // then + $fileTemplateContentProviderMock->shouldReceive('getFileTemplateContent') + ->andReturn('Camel Case Content'); $modelNamespaceProviderMock->shouldReceive('resolveModelNamespace') ->once() ->with($templateModel) @@ -196,11 +176,13 @@ public function testGetFileBasedTemplateHandlesCamelCaseConversion(): void $templateModel = Mockery::mock(TemplateModelInterface::class); $modelNamespaceProviderMock = Mockery::mock(ModelNamespaceResolverInterface::class); $modelNameProviderMock = Mockery::mock(ModelNameResolverInterface::class); + $fileTemplateContentProviderMock = Mockery::mock(FileTemplateContentProviderInterface::class); $provider = new FileModelTemplateResolver( 'App\\Views', vfsStream::url('templates'), '.blade.php', true, + $fileTemplateContentProviderMock, $modelNamespaceProviderMock, $modelNameProviderMock ); @@ -218,6 +200,7 @@ public function testGetFileBasedTemplateHandlesCamelCaseConversion(): void ->with($templateModel) ->andReturn('SomeCamelCaseView'); + $this->assertSame(vfsStream::url('templates/some-camel-case-view.blade.php'), $result()); // apply @@ -231,11 +214,13 @@ public function testGetTemplateHandlesNestedNamespaces(): void $templateModel = Mockery::mock(TemplateModelInterface::class); $modelNamespaceProviderMock = Mockery::mock(ModelNamespaceResolverInterface::class); $modelNameProviderMock = Mockery::mock(ModelNameResolverInterface::class); + $fileTemplateContentProviderMock = Mockery::mock(FileTemplateContentProviderInterface::class); $provider = new FileModelTemplateResolver( 'App\\Views', vfsStream::url('templates'), '.blade.php', false, + $fileTemplateContentProviderMock, $modelNamespaceProviderMock, $modelNameProviderMock ); @@ -244,6 +229,8 @@ public function testGetTemplateHandlesNestedNamespaces(): void $result = fn() => $provider->resolveModelTemplate($templateModel); // then + $fileTemplateContentProviderMock->shouldReceive('getFileTemplateContent') + ->andReturn('Dashboard Content'); $modelNamespaceProviderMock->shouldReceive('resolveModelNamespace') ->once() ->with($templateModel) @@ -258,6 +245,7 @@ public function testGetTemplateHandlesNestedNamespaces(): void // apply Mockery::close(); } + public function testGetFileBasedTemplateHandlesNestedNamespaces(): void { // given @@ -265,11 +253,13 @@ public function testGetFileBasedTemplateHandlesNestedNamespaces(): void $templateModel = Mockery::mock(TemplateModelInterface::class); $modelNamespaceProviderMock = Mockery::mock(ModelNamespaceResolverInterface::class); $modelNameProviderMock = Mockery::mock(ModelNameResolverInterface::class); + $fileTemplateContentProviderMock = Mockery::mock(FileTemplateContentProviderInterface::class); $provider = new FileModelTemplateResolver( 'App\\Views', vfsStream::url('templates'), '.blade.php', true, + $fileTemplateContentProviderMock, $modelNamespaceProviderMock, $modelNameProviderMock ); diff --git a/tests/pest/Unit/Template/TemplateRendererWithFileTemplateTest.php b/tests/pest/Unit/Template/TemplateRendererWithFileTemplateTest.php index 5c244bc..786e601 100644 --- a/tests/pest/Unit/Template/TemplateRendererWithFileTemplateTest.php +++ b/tests/pest/Unit/Template/TemplateRendererWithFileTemplateTest.php @@ -7,6 +7,7 @@ use Mockery; use org\bovigo\vfs\vfsStream; use PHPUnit\Framework\TestCase; +use Prosopo\Views\Interfaces\Template\FileTemplateContentProviderInterface; use Prosopo\Views\Interfaces\Template\TemplateRendererInterface; use Prosopo\Views\PrivateClasses\Template\TemplateRendererWithFileTemplate; @@ -20,12 +21,17 @@ public function testRenderTemplateReadsFileAndRendersContent(): void ]); $templateFilePath = vfsStream::url('templates/template.blade.php'); $templateRendererMock = Mockery::mock(TemplateRendererInterface::class); - $renderer = new TemplateRendererWithFileTemplate($templateRendererMock); + $fileTemplateContentProviderMock = Mockery::mock(FileTemplateContentProviderInterface::class); + $renderer = new TemplateRendererWithFileTemplate($fileTemplateContentProviderMock, $templateRendererMock); // when $result = fn()=> $renderer->renderTemplate($templateFilePath, ['key' => 'value']); // then + $fileTemplateContentProviderMock->shouldReceive('getFileTemplateContent') + ->once() + ->andReturn('
{{ $key }}
'); + $templateRendererMock->shouldReceive('renderTemplate') ->once() ->with('
{{ $key }}
', ['key' => 'value']) @@ -43,12 +49,17 @@ public function testRenderTemplateReturnsEmptyStringForMissingFile(): void vfsStream::setup('templates'); $missingTemplateFilePath = vfsStream::url('templates/missing-template.blade.php'); $templateRendererMock = Mockery::mock(TemplateRendererInterface::class); - $renderer = new TemplateRendererWithFileTemplate($templateRendererMock); + $fileTemplateContentProviderMock = Mockery::mock(FileTemplateContentProviderInterface::class); + $renderer = new TemplateRendererWithFileTemplate($fileTemplateContentProviderMock, $templateRendererMock); // when $result = fn() => $renderer->renderTemplate($missingTemplateFilePath, ['key' => 'value']); // then + $fileTemplateContentProviderMock->shouldReceive('getFileTemplateContent') + ->once() + ->andReturn(''); + $templateRendererMock->shouldReceive('renderTemplate') ->once() ->with('', ['key' => 'value'])