diff --git a/phpcs.xml b/phpcs.xml index 3812f13..38f6968 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -5,11 +5,6 @@ src/ tests/ - - \/FileInfoTest\/.*?$ - \/TemplateFileTest\/.*?$ - \/TemplateEngineTest\/.*?$ - diff --git a/phpstan.neon b/phpstan.neon index 9f21248..59d953b 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -5,10 +5,6 @@ parameters: - src - tests ignoreErrors: - - - message: '#^Variable \$\w+ might not be defined.$#' - paths: - - tests/src/TemplateEngineTest/* - message: '#^Call to an undefined method DanBettles\\Marigold\\OutputHelper\\Html5OutputHelper::create[A-Z]#' path: tests/src/OutputHelper/Html5OutputHelperTest.php @@ -17,6 +13,8 @@ parameters: path: tests/src/OutputHelper/Html5OutputHelperTest.php count: 1 - - message: "#^Anonymous function has an unused use \\$__layout\\.$#" - count: 1 - path: src/TemplateEngine.php + message: '#^Variable \$\w+ might not be defined.$#' + path: tests/src/PhpTest/* + - + message: '#^Variable \$\w+ might not be defined.$#' + path: tests/src/TemplateEngine/EngineTest/* diff --git a/src/Exception/FileTypeNotSupportedException.php b/src/Exception/FileTypeNotSupportedException.php index 7005161..6582724 100644 --- a/src/Exception/FileTypeNotSupportedException.php +++ b/src/Exception/FileTypeNotSupportedException.php @@ -11,6 +11,7 @@ use const null; +// @todo Remove this? class FileTypeNotSupportedException extends RuntimeException { /** diff --git a/src/File/FileInfo.php b/src/FileInfo.php similarity index 96% rename from src/File/FileInfo.php rename to src/FileInfo.php index d73695b..a6d7269 100755 --- a/src/File/FileInfo.php +++ b/src/FileInfo.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace DanBettles\Marigold\File; +namespace DanBettles\Marigold; use SplFileInfo; diff --git a/src/OutputHelper/OutputHelperInterface.php b/src/OutputHelper/OutputHelperInterface.php index 292784c..3963940 100644 --- a/src/OutputHelper/OutputHelperInterface.php +++ b/src/OutputHelper/OutputHelperInterface.php @@ -4,6 +4,7 @@ namespace DanBettles\Marigold\OutputHelper; +// @todo Remove this. interface OutputHelperInterface { } diff --git a/src/Php.php b/src/Php.php new file mode 100755 index 0000000..a566925 --- /dev/null +++ b/src/Php.php @@ -0,0 +1,54 @@ + $context + * @return mixed + * @throws FileNotFoundException If the PHP file does not exist. + */ + public function executeFile( + string $pathname, + array $context = [], + ?string &$output = null + ) { + if (!is_file($pathname)) { + throw new FileNotFoundException($pathname); + } + + return (static function ( + string $__FILE__, + array $context, + &$__OUTPUT__ + ) { + ob_start(); + + try { + extract($context); + unset($context); + + $__RESPONSE__ = require $__FILE__; + + $__OUTPUT__ = ob_get_contents(); + + return $__RESPONSE__; + } finally { + ob_end_clean(); + } + })($pathname, $context, $output); + } +} diff --git a/src/ServiceFactory.php b/src/ServiceFactory.php index f2c50f9..bb52ae6 100644 --- a/src/ServiceFactory.php +++ b/src/ServiceFactory.php @@ -18,7 +18,7 @@ class ServiceFactory { /** - * @var array + * @phpstan-var array */ private array $config; @@ -28,7 +28,7 @@ class ServiceFactory private array $services = []; /** - * @param array $config + * @phpstan-param array $config */ public function __construct(array $config) { @@ -87,7 +87,7 @@ public function get(string $id): object } /** - * @param array $config + * @phpstan-param array $config */ private function setConfig(array $config): self { @@ -96,7 +96,7 @@ private function setConfig(array $config): self } /** - * @return array $config + * @phpstan-return array $config */ public function getConfig(): array { diff --git a/src/TemplateEngine.php b/src/TemplateEngine.php deleted file mode 100755 index 71bc01c..0000000 --- a/src/TemplateEngine.php +++ /dev/null @@ -1,122 +0,0 @@ -setTemplateFileLoader($templateFileLoader); - } - - /** - * @param string|TemplateFile $pathnameOrTemplateFile - * @param array $variables - * @throws FileNotFoundException If the template file does not exist. - * @throws FileTypeNotSupportedException If the file does not appear to contain PHP. - */ - public function render($pathnameOrTemplateFile, array $variables = []): string - { - $templateFile = null; - $templateFilePathname = null; - - if ($pathnameOrTemplateFile instanceof TemplateFile) { - $templateFile = $pathnameOrTemplateFile; - $templateFilePathname = $templateFile->getPathname(); - } else { - if ($this->getTemplateFileLoader()) { - $templateFile = $this->getTemplateFileLoader()->findTemplate($pathnameOrTemplateFile); - - $templateFilePathname = null === $templateFile - ? $pathnameOrTemplateFile - : $templateFile->getPathname() - ; - } else { - $templateFile = new TemplateFile($pathnameOrTemplateFile); - $templateFilePathname = $templateFile->getPathname(); - } - } - - if ( - null === $templateFile - || !$templateFile->isFile() - ) { - throw new FileNotFoundException("The template file `{$templateFilePathname}` does not exist."); - } - - if (!in_array($templateFile->getExtension(), self::VALID_FILE_EXTENSIONS)) { - throw new FileTypeNotSupportedException($templateFile->getExtension(), self::VALID_FILE_EXTENSIONS); - } - - $__FILE__ = $templateFile->getPathname(); - $__VARS__ = $variables; - - $__layout = null; - - $output = (static function () use ($__FILE__, $__VARS__, &$__layout): string { - ob_start(); - - try { - extract($__VARS__); - unset($__VARS__); // (Aiming to expose as little as possible.) - - require $__FILE__; - - $output = ob_get_contents(); - - /** @var string */ - return $output; - } finally { - ob_end_clean(); - } - })(); - - /** @var string|null $__layout */ - if (null !== $__layout) { - $variables['__contentForLayout'] = $output; - - return $this->render($__layout, $variables); - } - - return $output; - } - - private function setTemplateFileLoader(?TemplateFileLoader $loader): self - { - $this->templateFileLoader = $loader; - return $this; - } - - public function getTemplateFileLoader(): ?TemplateFileLoader - { - return $this->templateFileLoader; - } -} diff --git a/src/TemplateEngine/Engine.php b/src/TemplateEngine/Engine.php new file mode 100755 index 0000000..7064b63 --- /dev/null +++ b/src/TemplateEngine/Engine.php @@ -0,0 +1,116 @@ +setPhp($php) + ->setTemplateFileLoader($templateFileLoader) + ; + } + + /** + * Inside the template being rendered, variables can be found in the `$input` collection. The value of a variable + * called "foo" could be accessed with `$input['foo']`, for example. + * + * The `$output` object provides: + * - `include()`, which allows to include other files; + * - and `insertInto()`/`wrapWith()`, which causes the output of the template to be inserted into another template. + * + * @param string|SplFileInfo $pathnameOrFileInfo + * @param array $variables + * @throws FileNotFoundException If the template file could not be found. + */ + public function render( + $pathnameOrFileInfo, + array $variables = [] + ): string { + $templateFile = $this->getTemplateFileLoader()->findTemplate($pathnameOrFileInfo); + + if (null === $templateFile) { + throw new FileNotFoundException((string) $pathnameOrFileInfo); + } + + $output = new OutputFacade($this); + + $renderedOutput = null; + + $this->getPhp()->executeFile( + $templateFile->getPathname(), + [ + 'input' => $variables, + 'output' => $output, + ], + $renderedOutput + ); + + if ($output->getWrapperArgs()) { + // @phpstan-ignore-next-line + list( + $wrapperPathnameOrTemplateFile, + $wrapperTargetVarName, + $wrapperVariables + ) = $output->getWrapperArgs(); + + /** @var array */ + $wrapperVariables = array_replace($wrapperVariables, [ + ($wrapperTargetVarName) => $renderedOutput, + ]); + + return (new self($this->getPhp(), $this->getTemplateFileLoader())) + ->render($wrapperPathnameOrTemplateFile, $wrapperVariables) + ; + } + + return $renderedOutput; + } + + private function setPhp(Php $php): self + { + $this->php = $php; + return $this; + } + + public function getPhp(): Php + { + return $this->php; + } + + private function setTemplateFileLoader(TemplateFileLoader $loader): self + { + $this->templateFileLoader = $loader; + return $this; + } + + public function getTemplateFileLoader(): TemplateFileLoader + { + return $this->templateFileLoader; + } + + /** + * Factory method, for convenience. + */ + public static function create(TemplateFileLoader $loader): self + { + return new self(new Php(), $loader); + } +} diff --git a/src/TemplateEngine/OutputFacade.php b/src/TemplateEngine/OutputFacade.php new file mode 100755 index 0000000..d725152 --- /dev/null +++ b/src/TemplateEngine/OutputFacade.php @@ -0,0 +1,96 @@ +setEngine($engine) + ->setWrapperArgs(null) + ; + } + + /** + * @param string|SplFileInfo $pathnameOrFileInfo + * @param array $variables + */ + public function include( + $pathnameOrFileInfo, + array $variables = [] + ): string { + return $this->getEngine()->render($pathnameOrFileInfo, $variables); + } + + private function setEngine(Engine $engine): self + { + $this->engine = $engine; + return $this; + } + + public function getEngine(): Engine + { + return $this->engine; + } + + /** + * @param array{int: string|SplFileInfo, int: string, int: mixed[]}|null $args + */ + private function setWrapperArgs(?array $args): self + { + $this->wrapperArgs = $args; + return $this; + } + + /** + * @param string|SplFileInfo $pathnameOrFileInfo + * @param array $variables + */ + public function wrapWith( + $pathnameOrFileInfo, + string $targetVarName, + array $variables = [] + ): self { + /** @var array{int: string|SplFileInfo, int: string, int: mixed[]} */ + $wrapperArgs = func_get_args(); + return $this->setWrapperArgs($wrapperArgs); + } + + /** + * Alias for `wrapWith()`. + * + * @param string|SplFileInfo $pathnameOrFileInfo + * @param array $variables + */ + public function insertInto( + $pathnameOrFileInfo, + string $targetVarName, + array $variables = [] + ): self { + return $this->wrapWith($pathnameOrFileInfo, $targetVarName, $variables); + } + + /** + * @return array{int: string|SplFileInfo, int: string, int: mixed[]}|null + */ + public function getWrapperArgs(): ?array + { + return $this->wrapperArgs; + } +} diff --git a/src/File/TemplateFile.php b/src/TemplateEngine/TemplateFile.php similarity index 73% rename from src/File/TemplateFile.php rename to src/TemplateEngine/TemplateFile.php index 86285ce..2ddc52c 100755 --- a/src/File/TemplateFile.php +++ b/src/TemplateEngine/TemplateFile.php @@ -2,13 +2,12 @@ declare(strict_types=1); -namespace DanBettles\Marigold\File; +namespace DanBettles\Marigold\TemplateEngine; -use RangeException; +use DanBettles\Marigold\FileInfo; use function array_slice; use function count; -use function file_exists; use function strtolower; use const null; @@ -17,20 +16,10 @@ class TemplateFile extends FileInfo { private ?string $outputFormat; - /** - * @throws RangeException If the filename does not point at a file. - */ public function __construct(string $filename) { parent::__construct($filename); - if ( - file_exists($this->getPathname()) - && !$this->isFile() - ) { - throw new RangeException("The filename `{$this->getPathname()}` does not point at a file."); - } - $maxExtensions = 2; $extensions = array_slice($this->getExtensions(), -$maxExtensions); @@ -42,6 +31,14 @@ public function __construct(string $filename) $this->setOutputFormat($outputFormat); } + /** + * Returns `true` if the file really is a template file, or `false` otherwise. + */ + public function isValid(): bool + { + return $this->isFile(); + } + private function setOutputFormat(?string $format): self { // Normalize. diff --git a/src/TemplateFileLoader.php b/src/TemplateEngine/TemplateFileLoader.php similarity index 57% rename from src/TemplateFileLoader.php rename to src/TemplateEngine/TemplateFileLoader.php index 3de0005..1c68270 100644 --- a/src/TemplateFileLoader.php +++ b/src/TemplateEngine/TemplateFileLoader.php @@ -2,12 +2,16 @@ declare(strict_types=1); -namespace DanBettles\Marigold; +namespace DanBettles\Marigold\TemplateEngine; -use DanBettles\Marigold\File\TemplateFile; +use InvalidArgumentException; use RangeException; +use SplFileInfo; use function is_dir; +use function is_string; +use function strlen; +use function substr; use const DIRECTORY_SEPARATOR; use const null; @@ -27,13 +31,36 @@ public function __construct(array $templateDirs) $this->setTemplateDirs($templateDirs); } - public function findTemplate(string $basenameOrPathname): ?TemplateFile + /** + * @param string|SplFileInfo $pathnameOrFileInfo + * @throws InvalidArgumentException If the pathname is invalid. + */ + public function findTemplate($pathnameOrFileInfo): ?TemplateFile { + $pathnameOrBasename = $pathnameOrFileInfo instanceof SplFileInfo + ? $pathnameOrFileInfo->getPathname() + : $pathnameOrFileInfo + ; + + if (!is_string($pathnameOrBasename) || !strlen($pathnameOrBasename)) { + throw new InvalidArgumentException('The pathname is invalid.'); + } + + if (DIRECTORY_SEPARATOR === substr($pathnameOrBasename, 0, 1)) { + $templateFile = new TemplateFile($pathnameOrBasename); + + // We're assuming the pathname is absolute, so there's no need to check in the template directories. + return $templateFile->isValid() + ? $templateFile + : null + ; + } + foreach ($this->getTemplateDirs() as $templatesDir) { - $templateFilePathname = $templatesDir . DIRECTORY_SEPARATOR . $basenameOrPathname; + $templateFilePathname = $templatesDir . DIRECTORY_SEPARATOR . $pathnameOrBasename; $templateFile = new TemplateFile($templateFilePathname); - if ($templateFile->isFile()) { + if ($templateFile->isValid()) { return $templateFile; } } diff --git a/tests/src/Exception/FileNotFoundExceptionTest.php b/tests/src/Exception/FileNotFoundExceptionTest.php index 9def06e..45163ba 100755 --- a/tests/src/Exception/FileNotFoundExceptionTest.php +++ b/tests/src/Exception/FileNotFoundExceptionTest.php @@ -17,7 +17,7 @@ public function testIsARuntimeexception(): void public function testCanBeThrown(): void { - $pathname = $this->createFixturePathname('non_existent.php'); + $pathname = $this->createFixturePathname('non_existent.file'); $this->expectException(FileNotFoundException::class); $this->expectExceptionMessage("The file `{$pathname}` does not exist."); diff --git a/tests/src/Exception/FileTypeNotSupportedExceptionTest.php b/tests/src/Exception/FileTypeNotSupportedExceptionTest.php index 194e7e9..f334489 100755 --- a/tests/src/Exception/FileTypeNotSupportedExceptionTest.php +++ b/tests/src/Exception/FileTypeNotSupportedExceptionTest.php @@ -18,7 +18,7 @@ public function testIsARuntimeexception(): void public function testCanBeThrownWithOnlyTheNameOfTheInvalidFileType(): void { $this->expectException(FileTypeNotSupportedException::class); - $this->expectExceptionMessage("The file-type `foo` is not supported."); + $this->expectExceptionMessage('The file-type `foo` is not supported.'); throw new FileTypeNotSupportedException('foo'); } diff --git a/tests/src/File/FileInfoTest/hello_world.JSON.php b/tests/src/File/FileInfoTest/hello_world.JSON.php deleted file mode 100644 index f623638..0000000 --- a/tests/src/File/FileInfoTest/hello_world.JSON.php +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tests/src/File/FileInfoTest/hello_world.html.php b/tests/src/File/FileInfoTest/hello_world.html.php deleted file mode 100644 index 56530d4..0000000 --- a/tests/src/File/FileInfoTest/hello_world.html.php +++ /dev/null @@ -1 +0,0 @@ -Hello, World!

' ?> \ No newline at end of file diff --git a/tests/src/File/FileInfoTest/hello_world.php b/tests/src/File/FileInfoTest/hello_world.php deleted file mode 100644 index b83816f..0000000 --- a/tests/src/File/FileInfoTest/hello_world.php +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tests/src/File/TemplateFileTest/hello_world.JSON.php b/tests/src/File/TemplateFileTest/hello_world.JSON.php deleted file mode 100644 index f623638..0000000 --- a/tests/src/File/TemplateFileTest/hello_world.JSON.php +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tests/src/File/TemplateFileTest/hello_world.html.php b/tests/src/File/TemplateFileTest/hello_world.html.php deleted file mode 100644 index 56530d4..0000000 --- a/tests/src/File/TemplateFileTest/hello_world.html.php +++ /dev/null @@ -1 +0,0 @@ -Hello, World!

' ?> \ No newline at end of file diff --git a/tests/src/File/TemplateFileTest/hello_world.php b/tests/src/File/TemplateFileTest/hello_world.php deleted file mode 100644 index b83816f..0000000 --- a/tests/src/File/TemplateFileTest/hello_world.php +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tests/src/File/FileInfoTest.php b/tests/src/FileInfoTest.php similarity index 96% rename from tests/src/File/FileInfoTest.php rename to tests/src/FileInfoTest.php index 207b27b..39a7178 100755 --- a/tests/src/File/FileInfoTest.php +++ b/tests/src/FileInfoTest.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace DanBettles\Marigold\Tests\File; +namespace DanBettles\Marigold\Tests; use DanBettles\Marigold\AbstractTestCase; -use DanBettles\Marigold\File\FileInfo; +use DanBettles\Marigold\FileInfo; use SplFileInfo; class FileInfoTest extends AbstractTestCase diff --git a/tests/src/File/FileInfoTest/.hello_world b/tests/src/FileInfoTest/.hello_world similarity index 100% rename from tests/src/File/FileInfoTest/.hello_world rename to tests/src/FileInfoTest/.hello_world diff --git a/tests/src/File/FileInfoTest/hello_world b/tests/src/FileInfoTest/hello_world similarity index 100% rename from tests/src/File/FileInfoTest/hello_world rename to tests/src/FileInfoTest/hello_world diff --git a/tests/src/File/FileInfoTest/hello_world. b/tests/src/FileInfoTest/hello_world. similarity index 100% rename from tests/src/File/FileInfoTest/hello_world. rename to tests/src/FileInfoTest/hello_world. diff --git a/tests/src/FileInfoTest/hello_world.JSON.php b/tests/src/FileInfoTest/hello_world.JSON.php new file mode 100644 index 0000000..73d19b5 --- /dev/null +++ b/tests/src/FileInfoTest/hello_world.JSON.php @@ -0,0 +1,4 @@ +Hello, World!

'; diff --git a/tests/src/FileInfoTest/hello_world.php b/tests/src/FileInfoTest/hello_world.php new file mode 100644 index 0000000..db0db83 --- /dev/null +++ b/tests/src/FileInfoTest/hello_world.php @@ -0,0 +1,4 @@ +assertTrue($executeFile->isPublic()); + $this->assertFalse($executeFile->isStatic()); + } + + public function testExecutefileExecutesAPhpFileAndReturnsTheReturnValue(): void + { + $returnValue = (new Php())->executeFile($this->createFixturePathname('returns_a_value.php')); + + $this->assertSame([ + 'foo' => 'bar', + ], $returnValue); + } + + public function testFilesExecutedByExecutefileDoNotHaveAccessToItsParameters(): void + { + $phpFilePathname = $this->createFixturePathname('returns_defined_vars.php'); + $returnValue = (new Php())->executeFile($phpFilePathname); + + $this->assertSame([ + '__FILE__' => $phpFilePathname, + '__OUTPUT__' => '', + ], $returnValue); + } + + public function testFilesExecutedByExecutefileDoNotHaveAccessToTheObjectToWhichItBelongs(): void + { + $this->expectError(); + $this->expectErrorMessage('Using $this when not in object context'); + + (new Php())->executeFile($this->createFixturePathname('attempts_to_access_this.php')); + } + + public function testExecutefileExecutesAPhpFileAndPassesBackAnyOutput(): void + { + $output = null; + $something = (new Php())->executeFile($this->createFixturePathname('hello_world.php'), [], $output); + + $this->assertSame('Hello, World!', $output); + $this->assertSame(1, $something); + } + + public function testExecutefileExecutesAPhpFileUsingTheSpecifiedContext(): void + { + $output = null; + + (new Php())->executeFile($this->createFixturePathname('hello_name.php'), [ + 'name' => 'Dan', + ], $output); + + $this->assertSame('Hello, Dan!', $output); + } + + public function testExecutefileThrowsAnExceptionIfThePhpFileDoesNotExist(): void + { + $nonExistentFile = $this->createFixturePathname('non_existent.file'); + + $this->expectException(FileNotFoundException::class); + $this->expectExceptionMessage("`{$nonExistentFile}`"); + + (new Php())->executeFile($nonExistentFile); + } +} diff --git a/tests/src/PhpTest/attempts_to_access_this.php b/tests/src/PhpTest/attempts_to_access_this.php new file mode 100644 index 0000000..83be35e --- /dev/null +++ b/tests/src/PhpTest/attempts_to_access_this.php @@ -0,0 +1,4 @@ + 'bar', +]; diff --git a/tests/src/PhpTest/returns_defined_vars.php b/tests/src/PhpTest/returns_defined_vars.php new file mode 100644 index 0000000..eb825cb --- /dev/null +++ b/tests/src/PhpTest/returns_defined_vars.php @@ -0,0 +1,3 @@ +assertSame('/foo/123/bar/456', $path); } - public function testGeneratepathDoesNotRequireParameterValues(): void + public function testParameterValuesDoNotHaveToBePassedToGeneratepath(): void { $routes = [ 'posts' => [ diff --git a/tests/src/ServiceFactoryTest.php b/tests/src/ServiceFactoryTest.php index 88d4f0e..8e4bee6 100755 --- a/tests/src/ServiceFactoryTest.php +++ b/tests/src/ServiceFactoryTest.php @@ -50,7 +50,7 @@ public function providesConfig(): array /** * @dataProvider providesConfig * @phpstan-param class-string $expectedClassName - * @param array $config + * @phpstan-param array $config */ public function testGetReturnsTheServiceWithTheSpecifiedId( string $expectedClassName, @@ -70,7 +70,7 @@ public function testGetReturnsTheServiceWithTheSpecifiedId( public function testGetThrowsAnExceptionIfThereIsNoServiceWithTheSpecifiedId(): void { $this->expectException(OutOfBoundsException::class); - $this->expectExceptionMessage("There is no service with the ID `non_existent_service`."); + $this->expectExceptionMessage('There is no service with the ID `non_existent_service`.'); (new ServiceFactory([]))->get('non_existent_service'); } @@ -93,7 +93,7 @@ public function testGetThrowsAnExceptionIfAServiceClassDoesNotExist(): void public function testGetThrowsAnExceptionIfAServiceFactoryClosureDoesNotReturnAnObject(): void { $this->expectException(RangeException::class); - $this->expectExceptionMessage("The factory for service `closure` does not return an object."); + $this->expectExceptionMessage('The factory for service `closure` does not return an object.'); (new ServiceFactory([ 'closure' => function () { @@ -131,7 +131,7 @@ public function providesInvalidConfig(): array /** * @dataProvider providesInvalidConfig - * @param array $invalidConfig + * @phpstan-param array $invalidConfig */ public function testGetThrowsAnExceptionIfAServiceConfigurationDoesNotResolveToAnObject( array $invalidConfig, diff --git a/tests/src/TemplateEngine/EngineTest.php b/tests/src/TemplateEngine/EngineTest.php new file mode 100755 index 0000000..7650e5d --- /dev/null +++ b/tests/src/TemplateEngine/EngineTest.php @@ -0,0 +1,264 @@ +getFixturesDir()]); + $engine = new Engine($php, $loader); + + $this->assertSame($php, $engine->getPhp()); + $this->assertSame($loader, $engine->getTemplateFileLoader()); + } + + /** + * If we know the engine is using `TemplateFileLoader` in conjunction with `executeFile()` then we can be confident + * in its behaviour. + */ + public function testRenderReturnsTheRenderedOutputOfTheTemplate(): void + { + $fixturesDir = $this->createFixturePathname(__FUNCTION__); + $templateFileBasename = 'hello_world.txt.php'; + $templateFilePathname = "{$fixturesDir}/{$templateFileBasename}"; + $templateFile = new TemplateFile($templateFilePathname); + + $this->assertFileDoesNotExist($templateFile->getPathname()); + + $templateVars = [ + 'name' => 'Dan', + ]; + + $expectedOutput = 'Hello, Dan!'; + + $phpMock = $this + ->getMockBuilder(Php::class) + ->onlyMethods(['executeFile']) + ->disableArgumentCloning() + ->getMock() + ; + + $phpMock + ->expects($this->once()) + ->method('executeFile') + ->with( + $templateFilePathname, + $this->callback(function ($variables) use ($templateVars) { + return is_array($variables) + && ['input', 'output'] == array_keys($variables) + && $templateVars === $variables['input'] + && $variables['output'] instanceof OutputFacade + ; + }), + null + ) + ->willReturnCallback(function ($pathname, $context, &$output) use ($expectedOutput) { + $output = $expectedOutput; + + return 1; + }) + ; + + $loaderMock = $this + ->getMockBuilder(TemplateFileLoader::class) + ->onlyMethods(['findTemplate']) + ->setConstructorArgs([ + [$fixturesDir], + ]) + ->getMock() + ; + + $loaderMock + ->expects($this->once()) + ->method('findTemplate') + ->with($templateFileBasename) + ->willReturn($templateFile) + ; + + /** @var Php $phpMock */ + /** @var TemplateFileLoader $loaderMock */ + $actualOutput = (new Engine($phpMock, $loaderMock)) + ->render( + $templateFileBasename, + $templateVars + ) + ; + + $this->assertSame($expectedOutput, $actualOutput); + } + + /** + * Here we care only that the `SplFileInfo` is passed unadulterated to the `TemplateFileLoader`. + */ + public function testRenderAcceptsAFileInfoObject(): void + { + $fixturesDir = $this->createFixturePathname(__FUNCTION__); + $templateFileBasename = 'hello_world.txt.php'; + $templateFilePathname = "{$fixturesDir}/{$templateFileBasename}"; + $templateFile = new TemplateFile($templateFilePathname); + $splFileInfo = new SplFileInfo($templateFileBasename); + + $this->assertFileDoesNotExist($templateFile->getPathname()); + + $phpMock = $this + ->getMockBuilder(Php::class) + ->onlyMethods(['executeFile']) + ->disableArgumentCloning() + ->getMock() + ; + + $phpMock + ->method('executeFile') + ->willReturnCallback(function ($pathname, $context, &$output) { + $output = ''; + + return 1; + }) + ; + + $loaderMock = $this + ->getMockBuilder(TemplateFileLoader::class) + ->onlyMethods(['findTemplate']) + ->setConstructorArgs([ + [$fixturesDir], + ]) + ->getMock() + ; + + $loaderMock + ->expects($this->once()) + ->method('findTemplate') + ->with($splFileInfo) + ->willReturn($templateFile) + ; + + /** @var Php $phpMock */ + /** @var TemplateFileLoader $loaderMock */ + (new Engine($phpMock, $loaderMock)) + ->render( + $splFileInfo + ) + ; + } + + public function testVarsDoNotHaveToBePassedToRender(): void + { + $renderMethod = new ReflectionMethod(Engine::class, 'render'); + $variablesParam = $renderMethod->getParameters()[1]; + + $this->assertTrue($variablesParam->isOptional()); + } + + /** @return array> */ + public function providesNonExistentTemplateFiles(): array + { + $fixturesDir = $this->createFixturePathname('testRenderThrowsAnExceptionIfTheFileDoesNotExist'); + $templateFileBasename = 'non_existent.file'; + $templateFilePathname = "{$fixturesDir}/{$templateFileBasename}"; + + return [ + [ + $templateFilePathname, + $fixturesDir, + $templateFilePathname, + ], + [ + $templateFilePathname, + $fixturesDir, + new SplFileInfo($templateFilePathname), + ], + ]; + } + + /** + * @dataProvider providesNonExistentTemplateFiles + * @param string|SplFileInfo $templateFile + */ + public function testRenderThrowsAnExceptionIfTheFileDoesNotExist( + string $expectedPathname, + string $fixturesDir, + $templateFile + ): void { + $this->expectException(FileNotFoundException::class); + $this->expectExceptionMessage("`{$expectedPathname}`"); + + $php = new Php(); + $loader = new TemplateFileLoader([$fixturesDir]); + + (new Engine($php, $loader)) + ->render( + $templateFile + ) + ; + } + + public function testCreateReturnsANewInstance(): void + { + $createMethod = new ReflectionMethod(Engine::class, 'create'); + + $this->assertTrue($createMethod->isPublic()); + $this->assertTrue($createMethod->isStatic()); + + $loaderMock = $this + ->getMockBuilder(TemplateFileLoader::class) + ->disableOriginalConstructor() + ->getMock() + ; + + /** @var TemplateFileLoader $loaderMock */ + $templateEngine = Engine::create($loaderMock); + + $this->assertInstanceOf(Engine::class, $templateEngine); + + $this->assertInstanceOf(Php::class, $templateEngine->getPhp()); + $this->assertSame($loaderMock, $templateEngine->getTemplateFileLoader()); + } + + public function testTemplatesCanIncludeOtherFiles(): void + { + $loader = new TemplateFileLoader([$this->createFixturePathname(__FUNCTION__)]); + + $actualOutput = (new Engine(new Php(), $loader)) + ->render('parent.php') + ; + + $this->assertSame(<<createFixturePathname(__FUNCTION__)]); + + $actualOutput = (new Engine(new Php(), $loader)) + ->render('wrapped.php') + ; + + $this->assertSame(<<include('other_template.php'); +// phpcs:ignore +echo $output->include('not_a_template.txt'); diff --git a/tests/src/TemplateEngine/EngineTest/testTemplatesCanInstructTheEngineToWrapTheirOutputWithAnotherTemplate/wrapped.php b/tests/src/TemplateEngine/EngineTest/testTemplatesCanInstructTheEngineToWrapTheirOutputWithAnotherTemplate/wrapped.php new file mode 100644 index 0000000..59ec75a --- /dev/null +++ b/tests/src/TemplateEngine/EngineTest/testTemplatesCanInstructTheEngineToWrapTheirOutputWithAnotherTemplate/wrapped.php @@ -0,0 +1,7 @@ +wrapWith('wrapper.php', 'contentForLayout', [ + 'title' => 'Test Wrapping', +]); +?> +Wrapped content. \ No newline at end of file diff --git a/tests/src/TemplateEngine/EngineTest/testTemplatesCanInstructTheEngineToWrapTheirOutputWithAnotherTemplate/wrapper.php b/tests/src/TemplateEngine/EngineTest/testTemplatesCanInstructTheEngineToWrapTheirOutputWithAnotherTemplate/wrapper.php new file mode 100644 index 0000000..76c5784 --- /dev/null +++ b/tests/src/TemplateEngine/EngineTest/testTemplatesCanInstructTheEngineToWrapTheirOutputWithAnotherTemplate/wrapper.php @@ -0,0 +1,5 @@ +# + + + +End of wrapper. \ No newline at end of file diff --git a/tests/src/TemplateEngine/OutputFacadeTest.php b/tests/src/TemplateEngine/OutputFacadeTest.php new file mode 100755 index 0000000..a7400f1 --- /dev/null +++ b/tests/src/TemplateEngine/OutputFacadeTest.php @@ -0,0 +1,144 @@ +createFixturePathname(__FUNCTION__)]); + $engine = new Engine(new Php(), $loader); + + $facade = new OutputFacade($engine); + + $this->assertSame($engine, $facade->getEngine()); + $this->assertNull($facade->getWrapperArgs()); + } + + /** @return array> */ + public function providesTheRenderedOutputOfFiles(): array + { + return [ + [ + 'Lorem ipsum dolor.', + [ + 'single.file', + // (No variables.) + ], + ], + [ + 'Sit amet, consectetur.', + [ + 'single.file', + ['foo' => 'bar', 'baz' => 'qux'], + ], + ], + [ + 'Adipiscing elit, sed.', + [ + new SplFileInfo('single.file'), + // (No variables.) + ], + ], + ]; + } + + /** + * Here we care only that: + * - input is passed unadulterated to `Engine::render()`; + * - and `OutputFacade::include()` returns the unadulterated output from `Engine::render()`. + * + * @dataProvider providesTheRenderedOutputOfFiles + * @param array{0: string|SplFileInfo, 1?: mixed[]} $args + */ + public function testIncludeReturnsTheRenderedOutputOfTheFile( + string $expectedOutput, + array $args + ): void { + $engineMock = $this + ->getMockBuilder(Engine::class) + ->onlyMethods(['render']) + ->disableOriginalConstructor() + ->getMock() + ; + + $engineMock + ->expects($this->once()) + ->method('render') + ->with(...$args) + ->willReturn($expectedOutput) + ; + + /** @var Engine $engineMock */ + $actualOutput = (new OutputFacade($engineMock)) + ->include(...$args) + ; + + $this->assertSame($expectedOutput, $actualOutput); + } + + public function testWrapwithSetsTheWrapperArgs(): void + { + $facade = new OutputFacade($this->createStub(Engine::class)); + + $something = $facade->wrapWith('wrapper.php', 'varInWrapper', [ + 'foo' => 'bar', + 'baz' => 'qux', + ]); + + $this->assertSame([ + 'wrapper.php', 'varInWrapper', [ + 'foo' => 'bar', + 'baz' => 'qux', + ], + ], $facade->getWrapperArgs()); + + $this->assertSame($facade, $something); + } + + public function testVarsDoNotHaveToBePassedToWrapwith(): void + { + $wrapwithMethod = new ReflectionMethod(OutputFacade::class, 'wrapWith'); + $variablesParam = $wrapwithMethod->getParameters()[2]; + + $this->assertTrue($variablesParam->isOptional()); + } + + public function testInsertintoIsAnAliasForWrapwith(): void + { + $pathnameOrFileInfo = 'wrapper.php'; + $targetVarName = 'varInWrapper'; + + $variables = [ + 'foo' => 'bar', + 'baz' => 'qux', + ]; + + $facadeMock = $this + ->getMockBuilder(OutputFacade::class) + ->onlyMethods(['wrapWith']) + ->disableOriginalConstructor() + ->getMock() + ; + + $facadeMock + ->expects($this->once()) + ->method('wrapWith') + ->with($pathnameOrFileInfo, $targetVarName, $variables) + ->willReturn($facadeMock) + ; + + /** @var OutputFacade $facadeMock */ + $facadeMock->insertInto($pathnameOrFileInfo, $targetVarName, $variables); + } +} diff --git a/tests/src/TemplateFileLoaderTest/testThrowsAnExceptionIfADirectoryDoesNotExist/.gitignore b/tests/src/TemplateEngine/OutputFacadeTest/testIsInstantiable/.gitignore similarity index 100% rename from tests/src/TemplateFileLoaderTest/testThrowsAnExceptionIfADirectoryDoesNotExist/.gitignore rename to tests/src/TemplateEngine/OutputFacadeTest/testIsInstantiable/.gitignore diff --git a/tests/src/TemplateEngine/TemplateFileLoaderTest.php b/tests/src/TemplateEngine/TemplateFileLoaderTest.php new file mode 100755 index 0000000..09cd025 --- /dev/null +++ b/tests/src/TemplateEngine/TemplateFileLoaderTest.php @@ -0,0 +1,163 @@ +createFixturePathname(__FUNCTION__); + + $paths = [ + "{$fixturesDir}/defaults", + "{$fixturesDir}/overrides", + ]; + + $loader = new TemplateFileLoader($paths); + + $this->assertEquals($paths, $loader->getTemplateDirs()); + } + + // @todo Review this. + public function testThrowsAnExceptionIfTheArrayOfDirectoryPathsIsEmpty(): void + { + $this->expectException(RangeException::class); + $this->expectExceptionMessage('The array of directory paths is empty.'); + + new TemplateFileLoader([]); + } + + public function testThrowsAnExceptionIfADirectoryDoesNotExist(): void + { + $nonExistentDir = $this->createFixturePathname(__FUNCTION__ . '/non_existent/'); + + $this->expectException(RangeException::class); + $this->expectExceptionMessage("The directory `{$nonExistentDir}` does not exist."); + + new TemplateFileLoader([ + $nonExistentDir, + ]); + } + + public function testFindtemplateReturnsTheFirstMatchingTemplateFile(): void + { + $fixturesDir = $this->createFixturePathname(__FUNCTION__); + + $paths = [ + "{$fixturesDir}/overrides", + "{$fixturesDir}/defaults", + ]; + + $overridesBeforeDefaultsLoader = new TemplateFileLoader($paths); + /** @var TemplateFile */ + $overrideTemplateFile = $overridesBeforeDefaultsLoader->findTemplate('empty.php'); + + $this->assertInstanceOf(TemplateFile::class, $overrideTemplateFile); + $this->assertSame("{$fixturesDir}/overrides/empty.php", $overrideTemplateFile->getPathname()); + + $reversedPaths = array_reverse($paths); + + $defaultsBeforeOverridesLoader = new TemplateFileLoader($reversedPaths); + /** @var TemplateFile */ + $defaultTemplateFile = $defaultsBeforeOverridesLoader->findTemplate('empty.php'); + + $this->assertInstanceOf(TemplateFile::class, $defaultTemplateFile); + $this->assertSame("{$fixturesDir}/defaults/empty.php", $defaultTemplateFile->getPathname()); + } + + public function testFindtemplateReturnsNullIfTheTemplateFileDoesNotExist(): void + { + $fixturesDir = $this->createFixturePathname(__FUNCTION__); + $loader = new TemplateFileLoader([$fixturesDir]); + + $nonExistentFile = "{$fixturesDir}/non_existent.file"; + $this->assertFileDoesNotExist($nonExistentFile); + + $this->assertNull($loader->findTemplate(basename($nonExistentFile))); // Relative path. + $this->assertNull($loader->findTemplate($nonExistentFile)); // Absolute path. + + $notATemplateFile = "{$fixturesDir}/not_a_template_file/"; + $this->assertDirectoryExists($notATemplateFile); + + $this->assertNull($loader->findTemplate(basename($notATemplateFile))); // Relative path. Exists but not a file. + $this->assertNull($loader->findTemplate($notATemplateFile)); // Absolute path. Exists but not a file. + } + + /** @return array> */ + public function providesInvalidPathnames(): array + { + return [ + [ + '', + ], + [ + new SplFileInfo(''), + ], + [ + null, + ], + ]; + } + + /** + * @dataProvider providesInvalidPathnames + * @param string|SplFileInfo $pathnameOrFileInfo + */ + public function testFindtemplateThrowsAnExceptionIfThePathnameIsInvalid($pathnameOrFileInfo): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The pathname is invalid.'); + + (new TemplateFileLoader([$this->createFixturePathname(__FUNCTION__)])) + ->findTemplate($pathnameOrFileInfo) + ; + } + + public function testFindtemplatePrioritisesAbsolutePathnames(): void + { + $fixturesDir = $this->createFixturePathname(__FUNCTION__); + $expectedPathname = "{$fixturesDir}/empty.php"; + + /** @var TemplateFile */ + $templateFileFromPathname = (new TemplateFileLoader([$fixturesDir])) + ->findTemplate($expectedPathname) + ; + + $this->assertInstanceOf(TemplateFile::class, $templateFileFromPathname); + $this->assertSame($expectedPathname, $templateFileFromPathname->getPathname()); + + /** @var TemplateFile */ + $templateFileFromFileInfo = (new TemplateFileLoader([$fixturesDir])) + ->findTemplate(new SplFileInfo($expectedPathname)) + ; + + $this->assertInstanceOf(TemplateFile::class, $templateFileFromFileInfo); + $this->assertSame($expectedPathname, $templateFileFromFileInfo->getPathname()); + } + + public function testFindtemplateAcceptsAFileInfoObject(): void + { + $fixturesDir = $this->createFixturePathname(__FUNCTION__); + $splFileInfo = new SplFileInfo('empty.php'); + + /** @var TemplateFile */ + $templateFile = (new TemplateFileLoader([$fixturesDir]))->findTemplate($splFileInfo); + + $this->assertInstanceOf(TemplateFile::class, $templateFile); + $this->assertSame("{$fixturesDir}/empty.php", $templateFile->getPathname()); + } +} diff --git a/tests/src/TemplateEngineTest/empty_file.php b/tests/src/TemplateEngine/TemplateFileLoaderTest/testFindtemplateAcceptsAFileInfoObject/empty.php similarity index 100% rename from tests/src/TemplateEngineTest/empty_file.php rename to tests/src/TemplateEngine/TemplateFileLoaderTest/testFindtemplateAcceptsAFileInfoObject/empty.php diff --git a/tests/src/TemplateFileLoaderTest/testFindtemplateReturnsTheFirstMatchingTemplateFile/defaults/hello_world.html.php b/tests/src/TemplateEngine/TemplateFileLoaderTest/testFindtemplatePrioritisesAbsolutePathnames/empty.php similarity index 100% rename from tests/src/TemplateFileLoaderTest/testFindtemplateReturnsTheFirstMatchingTemplateFile/defaults/hello_world.html.php rename to tests/src/TemplateEngine/TemplateFileLoaderTest/testFindtemplatePrioritisesAbsolutePathnames/empty.php diff --git a/tests/src/TemplateFileLoaderTest/testFindtemplateReturnsTheFirstMatchingTemplateFile/overrides/hello_world.html.php b/tests/src/TemplateEngine/TemplateFileLoaderTest/testFindtemplateReturnsNullIfTheTemplateFileDoesNotExist/not_a_template_file/.gitignore similarity index 100% rename from tests/src/TemplateFileLoaderTest/testFindtemplateReturnsTheFirstMatchingTemplateFile/overrides/hello_world.html.php rename to tests/src/TemplateEngine/TemplateFileLoaderTest/testFindtemplateReturnsNullIfTheTemplateFileDoesNotExist/not_a_template_file/.gitignore diff --git a/tests/src/TemplateEngine/TemplateFileLoaderTest/testFindtemplateReturnsTheFirstMatchingTemplateFile/defaults/empty.php b/tests/src/TemplateEngine/TemplateFileLoaderTest/testFindtemplateReturnsTheFirstMatchingTemplateFile/defaults/empty.php new file mode 100644 index 0000000..e69de29 diff --git a/tests/src/TemplateEngine/TemplateFileLoaderTest/testFindtemplateReturnsTheFirstMatchingTemplateFile/overrides/empty.php b/tests/src/TemplateEngine/TemplateFileLoaderTest/testFindtemplateReturnsTheFirstMatchingTemplateFile/overrides/empty.php new file mode 100644 index 0000000..e69de29 diff --git a/tests/src/TemplateEngine/TemplateFileLoaderTest/testFindtemplateThrowsAnExceptionIfThePathnameIsInvalid/.gitignore b/tests/src/TemplateEngine/TemplateFileLoaderTest/testFindtemplateThrowsAnExceptionIfThePathnameIsInvalid/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/tests/src/TemplateEngine/TemplateFileLoaderTest/testIsConstructedUsingOneOrMoreDirectoryPaths/defaults/.gitignore b/tests/src/TemplateEngine/TemplateFileLoaderTest/testIsConstructedUsingOneOrMoreDirectoryPaths/defaults/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/tests/src/TemplateEngine/TemplateFileLoaderTest/testIsConstructedUsingOneOrMoreDirectoryPaths/overrides/.gitignore b/tests/src/TemplateEngine/TemplateFileLoaderTest/testIsConstructedUsingOneOrMoreDirectoryPaths/overrides/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/tests/src/TemplateEngine/TemplateFileLoaderTest/testThrowsAnExceptionIfADirectoryDoesNotExist/.gitignore b/tests/src/TemplateEngine/TemplateFileLoaderTest/testThrowsAnExceptionIfADirectoryDoesNotExist/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/tests/src/File/TemplateFileTest.php b/tests/src/TemplateEngine/TemplateFileTest.php similarity index 62% rename from tests/src/File/TemplateFileTest.php rename to tests/src/TemplateEngine/TemplateFileTest.php index 8edd65e..9f65b17 100755 --- a/tests/src/File/TemplateFileTest.php +++ b/tests/src/TemplateEngine/TemplateFileTest.php @@ -2,16 +2,19 @@ declare(strict_types=1); -namespace DanBettles\Marigold\Tests\File; +namespace DanBettles\Marigold\Tests\TemplateEngine; use DanBettles\Marigold\AbstractTestCase; -use DanBettles\Marigold\File\FileInfo; -use DanBettles\Marigold\File\TemplateFile; -use RangeException; +use DanBettles\Marigold\FileInfo; +use DanBettles\Marigold\TemplateEngine\TemplateFile; +use function array_map; +use function array_merge; use function file_exists; +use const false; use const null; +use const true; class TemplateFileTest extends AbstractTestCase { @@ -52,7 +55,7 @@ public function providesExistentFileMetadata(): array } /** @dataProvider providesExistentFileMetadata */ - public function testIsConstructedWithThePathnameOfATemplateFile( + public function testIsConstructedUsingThePathnameOfAFile( ?string $ignore, string $pathname ): void { @@ -61,23 +64,39 @@ public function testIsConstructedWithThePathnameOfATemplateFile( $this->assertSame($pathname, $templateFile->getPathname()); } - public function testCanBeConstructedFromThePathnameOfAFileThatDoesNotExist(): void + public function testCanBeConstructedUsingThePathnameOfAFileThatDoesNotExist(): void { - $templateFilePathname = $this->createFixturePathname('non_existent.php'); + $templateFilePathname = $this->createFixturePathname('non_existent.file'); $this->assertFalse(file_exists($templateFilePathname)); new TemplateFile($templateFilePathname); } - public function testThrowsAnExceptionIfThePathnameDoesNotPointAtAFile(): void + /** @return array> */ + public function providesValidTemplateFiles(): array { - $dir = $this->getFixturesDir(); - - $this->expectException(RangeException::class); - $this->expectExceptionMessage("The filename `{$dir}` does not point at a file."); + $validTemplateFiles = array_map(function (array $args): array { + return [ + true, + $args[1], + ]; + }, $this->providesExistentFileMetadata()); + + return array_merge($validTemplateFiles, [ + [ + false, + $this->createFixturePathname('not_a_template_file/'), + ], + ]); + } - new TemplateFile($dir); + /** @dataProvider providesValidTemplateFiles */ + public function testIsvalidReturnsTrueIfTheReferencedObjectReallyIsATemplateFile( + bool $valid, + string $templateFilePathname + ): void { + $this->assertSame($valid, (new TemplateFile($templateFilePathname))->isValid()); } /** @dataProvider providesExistentFileMetadata */ diff --git a/tests/src/File/TemplateFileTest/.hello_world b/tests/src/TemplateEngine/TemplateFileTest/.hello_world similarity index 100% rename from tests/src/File/TemplateFileTest/.hello_world rename to tests/src/TemplateEngine/TemplateFileTest/.hello_world diff --git a/tests/src/File/TemplateFileTest/hello_world b/tests/src/TemplateEngine/TemplateFileTest/hello_world similarity index 100% rename from tests/src/File/TemplateFileTest/hello_world rename to tests/src/TemplateEngine/TemplateFileTest/hello_world diff --git a/tests/src/File/TemplateFileTest/hello_world. b/tests/src/TemplateEngine/TemplateFileTest/hello_world. similarity index 100% rename from tests/src/File/TemplateFileTest/hello_world. rename to tests/src/TemplateEngine/TemplateFileTest/hello_world. diff --git a/tests/src/TemplateEngine/TemplateFileTest/hello_world.JSON.php b/tests/src/TemplateEngine/TemplateFileTest/hello_world.JSON.php new file mode 100644 index 0000000..73d19b5 --- /dev/null +++ b/tests/src/TemplateEngine/TemplateFileTest/hello_world.JSON.php @@ -0,0 +1,4 @@ +Hello, World!

'; diff --git a/tests/src/TemplateEngine/TemplateFileTest/hello_world.php b/tests/src/TemplateEngine/TemplateFileTest/hello_world.php new file mode 100644 index 0000000..db0db83 --- /dev/null +++ b/tests/src/TemplateEngine/TemplateFileTest/hello_world.php @@ -0,0 +1,4 @@ +createFixturePathname('non_existent.php'); - - $this->expectException(FileNotFoundException::class); - $this->expectExceptionMessage("The template file `{$templateFilePathname}` does not exist."); - - (new TemplateEngine())->render( - $templateFilePathname, - [] - ); - } - - public function testRenderThrowsAnExceptionIfTheFileDoesNotAppearToContainPhp(): void - { - $this->expectException(FileTypeNotSupportedException::class); - $this->expectExceptionMessage('The file-type `txt` is not supported. Supported types: php; '); - - (new TemplateEngine())->render( - $this->createFixturePathname('hello_world.txt'), - [] - ); - } - - /** @return array> */ - public function providesRenderedTemplateOutput(): array - { - return [ - [ - '', - $this->createFixturePathname('empty_file.php'), - [], - ], - [ - 'Hello, World!', - $this->createFixturePathname('hello_world.php'), - [], - ], - [ - << -
  • Buddha
  • -
  • Dharma
  • -
  • Sangha
  • - - END, - $this->createFixturePathname('the_three_jewels.php'), - ['jewels' => ['Buddha', 'Dharma', 'Sangha']], - ], - [ - 'Hello, World!', - $this->createFixturePathname('hello_world.phtml'), - [], - ], - [ - 'Hello, World!', - $this->createFixturePathname('hello_world.php5'), - [], - ], - [ - 'Hello, World!', - $this->createFixturePathname('hello_world.php4'), - [], - ], - [ - 'Hello, World!', - $this->createFixturePathname('hello_world.php3'), - [], - ], - [ - 'Hello, World!', - $this->createFixturePathname('hello_world.phps'), - [], - ], - ]; - } - - /** - * @dataProvider providesRenderedTemplateOutput - * @param array $templateVars - */ - public function testRenderReturnsTheRenderedOutputOfTheTemplate( - string $expectedOutput, - string $templateFilePathname, - array $templateVars - ): void { - ob_start(); - - try { - $output = (new TemplateEngine())->render( - $templateFilePathname, - $templateVars - ); - - $this->assertSame(0, ob_get_length()); - $this->assertSame($expectedOutput, $output); - } finally { - ob_end_clean(); - } - } - - public function testRenderDoesNotRequireVars(): void - { - $output = (new TemplateEngine())->render( - $this->createFixturePathname('hello_world.php') - ); - - $this->assertSame('Hello, World!', $output); - } - - public function testTemplateFilesDoNotHaveAccessToThis(): void - { - $this->expectError(); - $this->expectErrorMessage('Using $this when not in object context'); - - (new TemplateEngine()) - ->render($this->createFixturePathname('does_not_contain_var_this.php')) - ; - } - - public function testRenderCanAcceptATemplatefileInsteadOfAPathname(): void - { - $templateFile = new TemplateFile($this->createFixturePathname('hello_world.php')); - - $output = (new TemplateEngine())->render( - $templateFile - ); - - $this->assertSame('Hello, World!', $output); - } - - public function testRenderThrowsAnExceptionIfTheTemplatefilePointsAtAFileThatDoesNotExist(): void - { - $templateFilePathname = $this->createFixturePathname('non_existent.php'); - $templateFile = new TemplateFile($templateFilePathname); - - $this->expectException(FileNotFoundException::class); - $this->expectExceptionMessage("The template file `{$templateFilePathname}` does not exist."); - - (new TemplateEngine())->render( - $templateFile, - [] - ); - } - - public function testCanUseATemplatefileloader(): void - { - $loader = new TemplateFileLoader([$this->getFixturesDir()]); - $engine = new TemplateEngine($loader); - $output = $engine->render('hello_world.php'); - - $this->assertSame('Hello, World!', $output); - } - - public function testRenderThrowsAnExceptionIfTheTemplateFileLoadedUsingTheTemplatefileloaderDoesNotExist(): void - { - $templateFileBasename = 'non_existent.php'; - - $this->expectException(FileNotFoundException::class); - $this->expectExceptionMessage("The template file `{$templateFileBasename}` does not exist."); - - $loader = new TemplateFileLoader([$this->getFixturesDir()]); - - (new TemplateEngine($loader))->render( - $templateFileBasename - ); - } - - public function testRenderCanAutomaticallyInsertTheRenderedOutputOfATemplateIntoALayout(): void - { - $templatesDir = $this->createFixturePathname(__FUNCTION__); - $loader = new TemplateFileLoader([$templatesDir]); - $engine = new TemplateEngine($loader); - - $output = $engine->render('content.html.php', [ - 'message' => 'Hello, World!', - ]); - - $this->assertSame(<<Hello, World!

    - END, $output); - } -} diff --git a/tests/src/TemplateEngineTest/does_not_contain_var_this.php b/tests/src/TemplateEngineTest/does_not_contain_var_this.php deleted file mode 100644 index e1eab3b..0000000 --- a/tests/src/TemplateEngineTest/does_not_contain_var_this.php +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tests/src/TemplateEngineTest/hello_world.php b/tests/src/TemplateEngineTest/hello_world.php deleted file mode 100644 index b83816f..0000000 --- a/tests/src/TemplateEngineTest/hello_world.php +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tests/src/TemplateEngineTest/hello_world.php3 b/tests/src/TemplateEngineTest/hello_world.php3 deleted file mode 100644 index b83816f..0000000 --- a/tests/src/TemplateEngineTest/hello_world.php3 +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tests/src/TemplateEngineTest/hello_world.php4 b/tests/src/TemplateEngineTest/hello_world.php4 deleted file mode 100644 index b83816f..0000000 --- a/tests/src/TemplateEngineTest/hello_world.php4 +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tests/src/TemplateEngineTest/hello_world.php5 b/tests/src/TemplateEngineTest/hello_world.php5 deleted file mode 100644 index b83816f..0000000 --- a/tests/src/TemplateEngineTest/hello_world.php5 +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tests/src/TemplateEngineTest/hello_world.phps b/tests/src/TemplateEngineTest/hello_world.phps deleted file mode 100644 index b83816f..0000000 --- a/tests/src/TemplateEngineTest/hello_world.phps +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tests/src/TemplateEngineTest/hello_world.phtml b/tests/src/TemplateEngineTest/hello_world.phtml deleted file mode 100644 index b83816f..0000000 --- a/tests/src/TemplateEngineTest/hello_world.phtml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tests/src/TemplateEngineTest/hello_world.txt b/tests/src/TemplateEngineTest/hello_world.txt deleted file mode 100644 index b45ef6f..0000000 --- a/tests/src/TemplateEngineTest/hello_world.txt +++ /dev/null @@ -1 +0,0 @@ -Hello, World! \ No newline at end of file diff --git a/tests/src/TemplateEngineTest/testRenderCanAutomaticallyInsertTheRenderedOutputOfATemplateIntoALayout/content.html.php b/tests/src/TemplateEngineTest/testRenderCanAutomaticallyInsertTheRenderedOutputOfATemplateIntoALayout/content.html.php deleted file mode 100644 index 4afcf60..0000000 --- a/tests/src/TemplateEngineTest/testRenderCanAutomaticallyInsertTheRenderedOutputOfATemplateIntoALayout/content.html.php +++ /dev/null @@ -1,2 +0,0 @@ - -

    \ No newline at end of file diff --git a/tests/src/TemplateEngineTest/testRenderCanAutomaticallyInsertTheRenderedOutputOfATemplateIntoALayout/layout.html.php b/tests/src/TemplateEngineTest/testRenderCanAutomaticallyInsertTheRenderedOutputOfATemplateIntoALayout/layout.html.php deleted file mode 100644 index de64476..0000000 --- a/tests/src/TemplateEngineTest/testRenderCanAutomaticallyInsertTheRenderedOutputOfATemplateIntoALayout/layout.html.php +++ /dev/null @@ -1,6 +0,0 @@ - - -
  • - - \ No newline at end of file diff --git a/tests/src/TemplateFileLoaderTest.php b/tests/src/TemplateFileLoaderTest.php deleted file mode 100755 index fed738e..0000000 --- a/tests/src/TemplateFileLoaderTest.php +++ /dev/null @@ -1,84 +0,0 @@ -createFixturePathname(__FUNCTION__); - - $paths = [ - "{$fixturesDir}/defaults", - "{$fixturesDir}/overrides", - ]; - - $loader = new TemplateFileLoader($paths); - - $this->assertEquals($paths, $loader->getTemplateDirs()); - } - - public function testThrowsAnExceptionIfTheArrayOfDirectoryPathsIsEmpty(): void - { - $this->expectException(RangeException::class); - $this->expectExceptionMessage('The array of directory paths is empty.'); - - new TemplateFileLoader([]); - } - - public function testThrowsAnExceptionIfADirectoryDoesNotExist(): void - { - $nonExistentDir = $this->createFixturePathname(__FUNCTION__ . "/non_existent"); - - $this->expectException(RangeException::class); - $this->expectExceptionMessage("The directory `{$nonExistentDir}` does not exist."); - - new TemplateFileLoader([ - $nonExistentDir, - ]); - } - - public function testFindtemplateReturnsTheFirstMatchingTemplateFile(): void - { - $fixturesDir = $this->createFixturePathname(__FUNCTION__); - - $paths = [ - "{$fixturesDir}/overrides", - "{$fixturesDir}/defaults", - ]; - - $overridesBeforeDefaultsLoader = new TemplateFileLoader($paths); - /** @var TemplateFile */ - $overrideTemplateFile = $overridesBeforeDefaultsLoader->findTemplate('hello_world.html.php'); - - $this->assertInstanceOf(TemplateFile::class, $overrideTemplateFile); - $this->assertSame("{$fixturesDir}/overrides/hello_world.html.php", $overrideTemplateFile->getPathname()); - - $reversedPaths = array_reverse($paths); - - $defaultsBeforeOverridesLoader = new TemplateFileLoader($reversedPaths); - /** @var TemplateFile */ - $defaultTemplateFile = $defaultsBeforeOverridesLoader->findTemplate('hello_world.html.php'); - - $this->assertInstanceOf(TemplateFile::class, $defaultTemplateFile); - $this->assertSame("{$fixturesDir}/defaults/hello_world.html.php", $defaultTemplateFile->getPathname()); - } - - public function testFindtemplateReturnsNullIfTheTemplateFileDoesNotExist(): void - { - $loader = new TemplateFileLoader([ - $this->createFixturePathname(__FUNCTION__), - ]); - - $this->assertNull($loader->findTemplate('non_existent.php')); - } -}