diff --git a/composer.json b/composer.json index 9160a98..11822e3 100644 --- a/composer.json +++ b/composer.json @@ -12,10 +12,10 @@ } ], "require": { - "php": ">8.0", + "php": ">8.0.1", "ext-mbstring": "*", - "toolkit/fsutil":"~1.0", - "toolkit/stdlib":"~1.0" + "toolkit/fsutil":"~2.0", + "toolkit/stdlib":"~2.0" }, "autoload": { "psr-4": { diff --git a/src/AbstractTemplate.php b/src/AbstractTemplate.php index add4c63..de15c8d 100644 --- a/src/AbstractTemplate.php +++ b/src/AbstractTemplate.php @@ -62,7 +62,7 @@ abstract class AbstractTemplate implements TemplateInterface * * @return static */ - public static function new(array $config = []): self + public static function new(array $config = []): static { return new static($config); } @@ -82,7 +82,7 @@ public function __construct(array $config = []) * * @return $this */ - public function configThis(callable $fn): self + public function configThis(callable $fn): static { $fn($this); return $this; @@ -242,7 +242,7 @@ public function setGlobalVars(array $globalVars): void * * @return AbstractTemplate */ - public function setPathResolver(callable $pathResolver): self + public function setPathResolver(callable $pathResolver): static { $this->pathResolver = $pathResolver; return $this; diff --git a/src/Compiler/AbstractCompiler.php b/src/Compiler/AbstractCompiler.php index 337da23..4228bad 100644 --- a/src/Compiler/AbstractCompiler.php +++ b/src/Compiler/AbstractCompiler.php @@ -62,19 +62,36 @@ abstract class AbstractCompiler implements CompilerInterface /** * custom directive, control statement token. * - * eg: implement include() + * ----- + * eg: implements: `include('parts/header.tpl')` + * + * ```php + * $compiler->addDirective('include', function(string $body, string $name): string { + * // $name : 'include' + * // $body : "('parts/header.tpl')" + * // do something... + * }); + * ``` * * @var array{string, callable(string, string): string} */ public array $customDirectives = []; + /** + * @return static + */ + public static function new(): static + { + return new static(); + } + /** * @param string $open * @param string $close * * @return $this */ - public function setOpenCloseTag(string $open, string $close): self + public function setOpenCloseTag(string $open, string $close): static { $this->openTag = $open; $this->closeTag = $close; @@ -173,18 +190,18 @@ public function addFilter(string $name, string $callExpr): self * @param string $name * @param callable $handler * - * @return $this + * @return static */ - public function addDirective(string $name, callable $handler): self + public function addDirective(string $name, callable $handler): static { $this->customDirectives[$name] = $handler; return $this; } /** - * @return $this + * @return static */ - public function disableEchoFilter(): self + public function disableEchoFilter(): static { $this->echoFilterFunc = self::RAW_OUTPUT; return $this; diff --git a/src/Compiler/LineCompiler.php b/src/Compiler/LineCompiler.php index a21897f..4597da9 100644 --- a/src/Compiler/LineCompiler.php +++ b/src/Compiler/LineCompiler.php @@ -29,14 +29,6 @@ class LineCompiler extends AbstractCompiler */ private bool $insideTag = false; - /** - * @return static - */ - public static function new(): self - { - return new self(); - } - /** * compile template contents to raw PHP template codes * diff --git a/src/Compiler/PregCompiler.php b/src/Compiler/PregCompiler.php index 04deef0..2e45326 100644 --- a/src/Compiler/PregCompiler.php +++ b/src/Compiler/PregCompiler.php @@ -26,21 +26,13 @@ class PregCompiler extends AbstractCompiler private string $openTagE = '\{\{'; private string $closeTagE = '\}\}'; - /** - * @return static - */ - public static function new(): self - { - return new self(); - } - /** * @param string $open * @param string $close * * @return $this */ - public function setOpenCloseTag(string $open, string $close): self + public function setOpenCloseTag(string $open, string $close): static { parent::setOpenCloseTag($open, $close); @@ -113,11 +105,6 @@ public function parseCodeBlock(string $block): string return self::PHP_TAG_OPEN . ' } ' . self::PHP_TAG_CLOSE; } - // comments block. `{{# comments #}}` - // if ($block[0] === '#' && str_ends_with($block, '#')) { - // return ''; - // } - $isInline = !str_contains($trimmed, "\n"); $kwPattern = Token::getBlockNamePattern(); diff --git a/src/Contract/CompilerInterface.php b/src/Contract/CompilerInterface.php index b8a2672..0b958dd 100644 --- a/src/Contract/CompilerInterface.php +++ b/src/Contract/CompilerInterface.php @@ -15,12 +15,20 @@ interface CompilerInterface * * @return $this */ - public function setOpenCloseTag(string $open, string $close): self; + public function setOpenCloseTag(string $open, string $close): static; /** * @return $this */ - public function disableEchoFilter(): self; + public function disableEchoFilter(): static; + + /** + * @param string $name + * @param callable $handler + * + * @return static + */ + public function addDirective(string $name, callable $handler): static; /** * compile template contents to raw PHP template codes diff --git a/src/EasyTemplate.php b/src/EasyTemplate.php index 1e64272..5504319 100644 --- a/src/EasyTemplate.php +++ b/src/EasyTemplate.php @@ -79,9 +79,10 @@ protected function init(CompilerInterface $compiler): void { // add built-in filters $this->addFilters([ - 'upper' => 'strtoupper', - 'lower' => 'strtolower', - 'nl' => function ($str): string { + 'upper' => 'strtoupper', + 'lower' => 'strtolower', + 'escape' => 'htmlspecialchars', + 'nl' => function ($str): string { return $str . "\n"; }, ]); diff --git a/src/Extended/ExtendLoader.php b/src/Extended/ExtendLoader.php new file mode 100644 index 0000000..d605022 --- /dev/null +++ b/src/Extended/ExtendLoader.php @@ -0,0 +1,10 @@ +addDirective('extend', function (string $tplName) { + return ExtendLoader::new()->handle($tplName); + }) + ->addDirective('extends', function () { + + }) + ->addDirective('block', function (string $body) { + $this->currentBlock = trim($body, '() '); + + return sprintf('$this->startBlock(%s);', $this->currentBlock); + }) + ->addDirective('endblock', function () { + return '$this->endBlock();'; + }); + } + + protected function startBlock(string $name): void + { + + } + + protected function endBlock(): void + { + + } +} \ No newline at end of file diff --git a/src/Extended/LayoutTemplate.php b/src/Extended/LayoutTemplate.php new file mode 100644 index 0000000..e0acc55 --- /dev/null +++ b/src/Extended/LayoutTemplate.php @@ -0,0 +1,27 @@ +addDirective('layout', function (string $tplName) { + return ExtendLoader::new()->handle(); + }) + ->addDirective('contents', function () { + return '$this->contents();'; + }); + } +} diff --git a/test/Compiler/PregCompilerTest.php b/test/Compiler/PregCompilerTest.php index 648921a..c38aaae 100644 --- a/test/Compiler/PregCompilerTest.php +++ b/test/Compiler/PregCompilerTest.php @@ -177,67 +177,6 @@ public function testCompile_comments():void } } - public function testCompile_customDirective():void - { - $p = new PregCompiler(); - $p->addDirective('include', function (string $body, string $name) { - return '$this->' . $name . $body; - }); - - $tests = [ - ['{{ include("header.tpl") }}', 'include("header.tpl") ?>'], - ['{{ include("header.tpl", [ - "key1" => "value1", -]) }}', 'include("header.tpl", [ - "key1" => "value1", -]) -?>'], - ]; - foreach ($tests as [$in, $out]) { - $this->assertEquals($out, $p->compile($in)); - } - } - - public function testCompile_has_comments():void - { - $p = new PregCompiler(); - - $str = <<<'TXT' -{{# comments #}} hello -TXT; - $out = $p->compile($str); - $this->assertStringNotContainsString('{{#', $out); - $this->assertStringNotContainsString('comments', $out); - $this->assertEquals(' hello', $out); - - $str = <<<'TXT' -{{# multi - line - comments -#}} hello -TXT; - $out = $p->compile($str); - $this->assertStringNotContainsString('{{#', $out); - $this->assertStringNotContainsString('comments', $out); - $this->assertEquals(' hello', $out); - - $str = <<<'TXT' -{{ foreach ($vars as $var): }} -{{# -comments - newInfo.incr{{ $var | ucfirst }}({{ $var }}); -#}} -public void {{ $var | ucfirst }}(Integer value) { - {{ $var }} += value; -} -{{ endforeach }} -TXT; - $out = $p->compile($str); - $this->assertStringNotContainsString('{{#', $out); - $this->assertStringNotContainsString('comments', $out); - } - public function testCompile_if_block():void { $p = new PregCompiler(); @@ -339,4 +278,65 @@ public function testCompile_ml_define():void ,$compiled); } + public function testCompile_customDirective_include():void + { + $p = new PregCompiler(); + $p->addDirective('include', function (string $body, string $name) { + return '$this->' . $name . $body; + }); + + $tests = [ + ['{{ include("header.tpl") }}', 'include("header.tpl") ?>'], + ['{{ include("header.tpl", [ + "key1" => "value1", +]) }}', 'include("header.tpl", [ + "key1" => "value1", +]) +?>'], + ]; + foreach ($tests as [$in, $out]) { + $this->assertEquals($out, $p->compile($in)); + } + } + + public function testCompile_has_comments():void + { + $p = new PregCompiler(); + + $str = <<<'TXT' +{{# comments #}} hello +TXT; + $out = $p->compile($str); + $this->assertStringNotContainsString('{{#', $out); + $this->assertStringNotContainsString('comments', $out); + $this->assertEquals(' hello', $out); + + $str = <<<'TXT' +{{# multi + line + comments +#}} hello +TXT; + $out = $p->compile($str); + $this->assertStringNotContainsString('{{#', $out); + $this->assertStringNotContainsString('comments', $out); + $this->assertEquals(' hello', $out); + + $str = <<<'TXT' +{{ foreach ($vars as $var): }} +{{# +comments + newInfo.incr{{ $var | ucfirst }}({{ $var }}); +#}} +public void {{ $var | ucfirst }}(Integer value) { + {{ $var }} += value; +} +{{ endforeach }} +TXT; + $out = $p->compile($str); + $this->assertStringNotContainsString('{{#', $out); + $this->assertStringNotContainsString('comments', $out); + } + } diff --git a/test/EasyTemplateTest.php b/test/EasyTemplateTest.php index d3bcf18..ea1dc9a 100644 --- a/test/EasyTemplateTest.php +++ b/test/EasyTemplateTest.php @@ -240,7 +240,7 @@ public function testRender_foreach(): void public function testRender_include_file(): void { $t = new EasyTemplate([ - 'tplDir' => __DIR__ . '/testdata', + 'tplDir' => __DIR__ . '/testdata/includes', ]); $result = $t->renderFile('home', ['name' => 'inhere']); diff --git a/test/testdata/extends/home.php b/test/testdata/extends/home.php new file mode 100644 index 0000000..56f652a --- /dev/null +++ b/test/testdata/extends/home.php @@ -0,0 +1,12 @@ + +extends('layouts/layout.php') ?> + +block('body') ?> +on home: block body; +endblock() ?> + +block('footer') ?> +on home: block footer; +endblock() ?> + + diff --git a/test/testdata/extends/home.tpl b/test/testdata/extends/home.tpl new file mode 100644 index 0000000..f6d161b --- /dev/null +++ b/test/testdata/extends/home.tpl @@ -0,0 +1,10 @@ + +{{ extends('layouts/layout.tpl') }} + +{{ block 'body' }} +on home: block body; +{{ endblock; }} + +{{ block 'footer' }} +on home: block footer; +{{ endblock; }} diff --git a/test/testdata/extends/layouts/layout.php b/test/testdata/extends/layouts/layout.php new file mode 100644 index 0000000..b286cbc --- /dev/null +++ b/test/testdata/extends/layouts/layout.php @@ -0,0 +1,20 @@ +this is an layout file. + +--- head + +block('header') ?> +on layout: block header; +endblock(); ?> + +--- body + +block('body') ?> +on layout: block body; +endblock(); ?> + +--- footer + +block('footer') ?> +on layout: block footer; +endblock(); ?> + diff --git a/test/testdata/extends/layouts/layout.tpl b/test/testdata/extends/layouts/layout.tpl new file mode 100644 index 0000000..68adbd7 --- /dev/null +++ b/test/testdata/extends/layouts/layout.tpl @@ -0,0 +1,14 @@ +{{ block 'header' }} + on layout: block header; +{{ endblock }} + +this is an layout file. + +{{ block 'body' }} + on layout: block body; +{{ endblock }} + +{{ block 'footer' }} + on layout: block footer; +{{ endblock }} + diff --git a/test/testdata/home.tpl b/test/testdata/includes/home.tpl similarity index 100% rename from test/testdata/home.tpl rename to test/testdata/includes/home.tpl diff --git a/test/testdata/parts/header.tpl b/test/testdata/includes/parts/header.tpl similarity index 100% rename from test/testdata/parts/header.tpl rename to test/testdata/includes/parts/header.tpl