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 @@
-= '"Hello, World!"' ?>
\ 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 @@
-= 'Hello, World!' ?>
\ 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 @@
-= '"Hello, World!"' ?>
\ 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 @@
-= 'Hello, World!' ?>
\ 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 @@
+# = $input['title'] ?>
+
+= $input['contentForLayout'] ?>
+
+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 @@
-= 'Hello, World!' ?>
\ 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 @@
-= 'Hello, World!' ?>
\ 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 @@
-= 'Hello, World!' ?>
\ 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 @@
-= 'Hello, World!' ?>
\ 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 @@
-= 'Hello, World!' ?>
\ 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 @@
-= 'Hello, World!' ?>
\ 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 @@
-
-= $message ?>
\ 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 @@
-
-
- = $name ?>
-
-
\ 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'));
- }
-}