diff --git a/src/Builder.php b/src/Builder.php index 90d7eda..08b43f5 100644 --- a/src/Builder.php +++ b/src/Builder.php @@ -21,6 +21,7 @@ class Builder '%NAMESPACE%' => $context->namespace, '%USE%' => ( $uses = $context->renderUses() ) ? "use $uses;" : false, '%CLASSNAME%' => $context->className, + '%PATHNAME%' => $context->viewPath, '%EXTENDS%' => $context->extendFrom ? "extends " . static::TEMPLATE_CLASSNAME_PREFIX . static::generateClassUID($context->extendFrom) : '', '%EXTENDS_TEMPLATE%' => $context->extendFrom, '%CONTENT%' => $compiledSource, diff --git a/src/Builder/ClassTemplate.php b/src/Builder/ClassTemplate.php index d07add7..6e42cc2 100644 --- a/src/Builder/ClassTemplate.php +++ b/src/Builder/ClassTemplate.php @@ -4,7 +4,9 @@ namespace %NAMESPACE%; %USE% +# %PATHNAME% class %CLASSNAME% %EXTENDS% { + public array $blockList = []; public array $sectionList = []; diff --git a/src/Caching/Opcache.php b/src/Caching/Opcache.php index 5970e3e..87319c7 100644 --- a/src/Caching/Opcache.php +++ b/src/Caching/Opcache.php @@ -2,7 +2,8 @@ namespace Picea\Caching; -use Picea\Compiler\Context; +use Picea\Builder, + Picea\Compiler\Context; class Opcache implements Cache { @@ -11,10 +12,15 @@ class Opcache implements Cache { protected string $cachePath = ""; protected array $compiled = []; + + protected ? Context $context; - public function __construct(?string $cachePath = null) + public function __construct(?string $cachePath = null, ? Context $context) { $this->cachePath = $cachePath ?? sys_get_temp_dir(); + $this->context = $context; + + $this->registerNamespace($this->context->namespace); } public function load(string $viewPath, ...$arguments) : object @@ -29,10 +35,6 @@ class Opcache implements Cache { $compiledContext = $this->compiled[$viewPath]; - if ( $compiledContext['extends'] ) { - $this->load($compiledContext['extends'], ...$arguments); - } - $fullName = isset($compiledContext['namespace']) ? "\\{$compiledContext['namespace']}\\{$compiledContext['classname']}" : $compiledContext['classname']; return new $fullName(...$arguments); @@ -40,7 +42,7 @@ class Opcache implements Cache { public function save(Context $context) : bool { - $fileName = $context->compiledClassPath(); + $context->compiledClassPath(); $this->validateCachePath(); file_put_contents($this->cachePath($context->viewPath), $context->compiledSource); @@ -55,9 +57,15 @@ class Opcache implements Cache { if ( file_exists($path = $this->cachePath($viewPath)) ) { $this->compiled[$viewPath] = include($path); + + # if ( $this->compiled[$viewPath]['extends'] ?? false ) { + # $this->compiled($this->compiled[$viewPath]['extends']); + # } + return true; } - + + return false; } @@ -73,8 +81,6 @@ class Opcache implements Cache { throw new \RuntimeException( sprintf("Given cache path `%s` is not writeable by `%s`", $fullPath, static::class) ); - - return false; } return true; @@ -82,6 +88,26 @@ class Opcache implements Cache { protected function cachePath(string $fileName = "") : string { - return implode(DIRECTORY_SEPARATOR, array_filter([ $this->cachePath, $this->cacheFolder, $fileName ? str_replace([ "/", DIRECTORY_SEPARATOR ], "~", $fileName . ".php") : null ])); + return implode(DIRECTORY_SEPARATOR, array_filter([ $this->cachePath, $this->cacheFolder, $fileName ? str_replace([ "/", DIRECTORY_SEPARATOR ], "~", Builder::generateClassName($fileName) . ".php") : null ])); + } + + public function registerNamespace(string $namespace) { + spl_autoload_register(function ($class) use ($namespace) { + $prefix = "$namespace\\"; + + $baseDir = $this->cachePath() . "/"; + + $len = strlen($prefix); + + if (strncmp($prefix, $class, $len) !== 0) { + return; + } + + $file = $baseDir . str_replace('\\', '/', substr($class, $len)) . '.php'; + + if ( file_exists($file) ) { + require $file; + } + }); } } diff --git a/src/Compiler.php b/src/Compiler.php index ae93322..81b1157 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -45,8 +45,12 @@ class Compiler $this->sourceCode = preg_replace_callback($replace, function ($matches) use (&$context) { $matches[2] = trim($matches[2]); + + list($token, $arguments) = array_pad(array_filter(explode(' ', $matches[2], 2), 'strlen'), 2, null); - + + $token = trim($token); + # @TODO Refractor this parts to allows registration to the tag's name if ( $this->tagList[$token] ?? false ) { return $this->tagList[$token]->parse($context, $arguments, $token); diff --git a/src/ControlStructure/BlockToken.php b/src/ControlStructure/BlockToken.php index 5dee4cb..db881e4 100644 --- a/src/ControlStructure/BlockToken.php +++ b/src/ControlStructure/BlockToken.php @@ -4,14 +4,20 @@ namespace Picea\ControlStructure; class BlockToken implements ControlStructure { - public array $token = [ "block", "arguments" ]; + public array $token = [ "arguments", "block", "endblock", "define", "slot", "endslot", "using" ]; public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) { - + static $slotDefinitions = []; + switch($token) { case "block": - return "picea->inlineHtml(\$this, $arguments); ?>"; - + $slotDefinitions[] = $this->slotDefinitions(); + + return ""; + + case "endblock": + return "render(\$___class__template); unset(\$___block); ?>"; + case "arguments": $class = static::class; @@ -26,10 +32,73 @@ class BlockToken implements ControlStructure { throw new \Exception('A block awaiting arguments `$arguments` instead received `' . implode(', ', array_map('gettype', \$inlineVariables)) . '`'); } ?> - PHP; - } + PHP; + + case "define": + list($name, $definition) = array_pad(explode(',', $arguments, 2), 2, ""); + + end($slotDefinitions)->setDefinition(eval("return $name;"), $definition); + + return <<defineSlot($name, function($definition) {}); ?> + PHP; + + case "slot": + $def = end($slotDefinitions); + + list($name, $definition) = array_pad(explode(',', $arguments, 2), 2, ""); + + if ($def->hasDefinitions() ) { + $slotName = eval("return $name;"); + $def->currentSlot = $slotName; + $def->setDefinitionVars($slotName, $definition); + + $definition = $def->printDefinition($slotName); + + return <<printSlot($name, function($definition, array \$___using = []) { extract(\$___using, \EXTR_SKIP); ?> + PHP; + } + else { + return <<slotIsSet($name) || \$___block->setSlot($name, function($definition, array \$___using = []) { extract(\$___using, \EXTR_SKIP); ?> + PHP; + } + + case "endslot": + $def =end($slotDefinitions); + + if ($def->hasDefinitions() ) { + $definition = $def->getCurrentSlotDefinitionVars(); + + if ($definition) { + $definition = "$definition,"; + } + + return <<call(\$this, $definition \$this->using); ?> + PHP; + } + else { + return ""; + } + + case "using": + return "setUsing($arguments); ?>"; + } } + + public function inlineHtml(? object $proxy, string $viewPath, ... $variables) { + return $this->renderHtml($viewPath, [ 'inlineVariables' => $variables ], $proxy); + } + + public static function parseSlotArguments(Callable $method) : array + { + #return + } + + public static function parseArguments(Callable $method, array $arguments) : array { try{ @@ -42,9 +111,128 @@ class BlockToken implements ControlStructure { $parameters = []; foreach((new \ReflectionFunction($method))->getParameters() as $key => $value) { - $parameters[ $value->getName() ] = $arguments[$key] ?? $value->getDefaultValue(); + if ( isset($arguments[$key]) ) { + $parameters[ $value->getName() ] = $arguments[$key]; + } + elseif ( $value->isDefaultValueAvailable() ) { + $parameters[ $value->getName() ] = $value->getDefaultValue(); + } + elseif ( $value->isVariadic() ) { + $parameters[ $value->getName() ] = []; + } } return $parameters; } + + public static function slotDefinitions() { + return new class() { + + public array $definitions = []; + + public array $variables = []; + + public bool $rendering = false; + + public string $currentSlot; + + public function setDefinition(string $name, string $definition) : self + { + $this->definitions[$name] = $definition; + + return $this; + } + + public function setDefinitionVars(string $name, string $variables) : self + { + $this->variables[$name] = $variables; + + return $this; + } + + public function getDefinitionVars(string $name) : string + { + return $this->variables[$name] ?? ""; + } + + public function getCurrentSlotDefinitionVars() : string + { + return $this->getDefinitionVars($this->currentSlot); + } + + public function hasDefinitions() : bool + { + return count($this->definitions) > 0; + } + + public function printDefinition(string $name) : string + { + if ( ! isset($this->definitions[$name]) ) { + throw new \Exception("Slot definition for `$name` was not found. Have you defined it in your block header ?"); + } + + return $this->definitions[$name]; + } + + public function render() : void + { + $this->rendering = true; + } + + }; + } + + public static function instanciateBlock(string $viewPath, ... $arguments) + { + return new class($viewPath, ...$arguments) { + + public bool $rendering = false; + + public string $viewPath; + + public array $using = []; + + public array $arguments = []; + + public array $slots = []; + + public array $definition = []; + + public function __construct(string $viewPath, ...$arguments) { + $this->viewPath = $viewPath; + $this->arguments = $arguments; + } + + public function render(object $classTemplate) : string + { + $this->rendering = true; + + return $classTemplate->picea->inlineHtml($this, $this->viewPath, ...$this->arguments); + } + + public function setSlot(string $name, Callable $method) : void + { + $this->slots[$name] = $method; + } + + public function defineSlot(string $name, Callable $method) : void + { + $this->definition[$name] = $method; + } + + public function slotIsSet(string $name) : bool + { + return ! empty($this->slots[$name]); + } + + public function printSlot(string $name, Callable $default) + { + return $this->slotIsSet($name) ? $this->slots[$name] : $default; + } + + public function setUsing(array $variables) { + $this->using = $variables; + } + }; + } } \ No newline at end of file diff --git a/src/ControlStructure/ElseIfToken.php b/src/ControlStructure/ElseIfToken.php deleted file mode 100644 index 3405492..0000000 --- a/src/ControlStructure/ElseIfToken.php +++ /dev/null @@ -1,13 +0,0 @@ -"; - } - -} diff --git a/src/ControlStructure/ElseToken.php b/src/ControlStructure/ElseToken.php deleted file mode 100644 index cc9ea3d..0000000 --- a/src/ControlStructure/ElseToken.php +++ /dev/null @@ -1,13 +0,0 @@ -"; - } - -} diff --git a/src/ControlStructure/EndCaseToken.php b/src/ControlStructure/EndCaseToken.php deleted file mode 100644 index 4a6a039..0000000 --- a/src/ControlStructure/EndCaseToken.php +++ /dev/null @@ -1,7 +0,0 @@ -"; - } - -} diff --git a/src/ControlStructure/EndSectionToken.php b/src/ControlStructure/EndSectionToken.php deleted file mode 100644 index 8a52bf0..0000000 --- a/src/ControlStructure/EndSectionToken.php +++ /dev/null @@ -1,18 +0,0 @@ -sections) ) { - throw new \RuntimeException("A section closing tag {% endsection %} was found without an opening {% section %} tag"); - } - - $section = array_pop($context->sections); - $build = $context->extendFrom ? "" : "\$___class__template->renderSection({$section['name']});"; - return ""; - } -} diff --git a/src/ControlStructure/EndSwitchToken.php b/src/ControlStructure/EndSwitchToken.php deleted file mode 100644 index f74a929..0000000 --- a/src/ControlStructure/EndSwitchToken.php +++ /dev/null @@ -1,12 +0,0 @@ -"; - } -} diff --git a/src/ControlStructure/EndforToken.php b/src/ControlStructure/EndforToken.php deleted file mode 100644 index 3f62099..0000000 --- a/src/ControlStructure/EndforToken.php +++ /dev/null @@ -1,20 +0,0 @@ -iterationStack)['or'] === false ) { - $output = ""; - } - else { - $output = ""; - } - - array_pop($context->iterationStack); - return $output; - } -} diff --git a/src/ControlStructure/EndforeachToken.php b/src/ControlStructure/EndforeachToken.php deleted file mode 100644 index a8088b4..0000000 --- a/src/ControlStructure/EndforeachToken.php +++ /dev/null @@ -1,20 +0,0 @@ -iterationStack)['or'] === false ) { - $output = ""; - } - else { - $output = ""; - } - - array_pop($context->iterationStack); - return $output; - } -} diff --git a/src/ControlStructure/ForToken.php b/src/ControlStructure/ForToken.php index e662336..0cb66c4 100644 --- a/src/ControlStructure/ForToken.php +++ b/src/ControlStructure/ForToken.php @@ -4,17 +4,32 @@ namespace Picea\ControlStructure; class ForToken implements ControlStructure { - public string $token = "for"; + public array $token = [ "for", "endfor" ]; public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) { - $uid = "$".uniqid("for_"); + switch($token) { + case "for": + $uid = "$".uniqid("for_"); - $context->iterationStack[] = [ - 'or' => false, - 'uid' => $uid, - 'token' => 'endfor', - ]; + $context->iterationStack[] = [ + 'or' => false, + 'uid' => $uid, + 'token' => 'endfor', + ]; - return ""; + return ""; + + case "endfor": + if ( end($context->iterationStack)['or'] === false ) { + $output = ""; + } + else { + $output = ""; + } + + array_pop($context->iterationStack); + + return $output; + } } } diff --git a/src/ControlStructure/ForeachToken.php b/src/ControlStructure/ForeachToken.php index 1403dcd..454a21a 100644 --- a/src/ControlStructure/ForeachToken.php +++ b/src/ControlStructure/ForeachToken.php @@ -4,23 +4,38 @@ namespace Picea\ControlStructure; class ForeachToken implements ControlStructure { - public string $token = "foreach"; + public array $token = [ "foreach", "endforeach" ]; public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) { - $name = "$".uniqid("foreach_"); - - $count = count($context->iterationStack ?? []); - - if ( $count > 0 ) { - $name .= "[" . $context->iterationStack[$count - 1]['uid'] . "]"; - } + switch($token) { + case "foreach": + $name = "$".uniqid("foreach_"); - $context->iterationStack[] = [ - 'or' => false, - 'uid' => $name, - 'token' => 'endforeach', - ]; - - return ""; + $count = count($context->iterationStack ?? []); + + if ( $count > 0 ) { + $name .= "[" . $context->iterationStack[$count - 1]['uid'] . "]"; + } + + $context->iterationStack[] = [ + 'or' => false, + 'uid' => $name, + 'token' => 'endforeach', + ]; + + return ""; + + case "endforeach": + if ( end($context->iterationStack)['or'] === false ) { + $output = ""; + } + else { + $output = ""; + } + + array_pop($context->iterationStack); + + return $output; + } } } diff --git a/src/ControlStructure/IfToken.php b/src/ControlStructure/IfToken.php index 4ef0d6d..9c86446 100644 --- a/src/ControlStructure/IfToken.php +++ b/src/ControlStructure/IfToken.php @@ -4,10 +4,22 @@ namespace Picea\ControlStructure; class IfToken implements ControlStructure { - public string $token = "if"; + public array $token = [ "if", "else", "elseif", "endif" ]; public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) { - return ""; + switch($token) { + case "if": + return ""; + + case "else": + return ""; + + case "elseif": + return ""; + + case "endif": + return ""; + } } } diff --git a/src/ControlStructure/SectionToken.php b/src/ControlStructure/SectionToken.php index be3cdc3..e7ad623 100644 --- a/src/ControlStructure/SectionToken.php +++ b/src/ControlStructure/SectionToken.php @@ -4,9 +4,24 @@ namespace Picea\ControlStructure; class SectionToken implements ControlStructure { - public string $token = "section"; + public array $token = [ "section", "endsection" ]; public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) { + switch($token) { + case "section": + return $this->printSection($context, $arguments); + + case "endsection": + if ( empty($context->sections) ) { + throw new \RuntimeException("A section closing tag {% endsection %} was found without an opening {% section %} tag"); + } + + return $this->printEndSection($context); + } + } + + protected function printSection($context, ?string $arguments) : string + { list($name, $options) = array_pad(explode(',', $arguments, 2), 2, null); if ( $options ?? false ) { @@ -34,4 +49,11 @@ class SectionToken implements ControlStructure { \$___class__template->sectionList[$name]['$action'][] = [ 'order' => $order, 'callback' => function() use (\$picea, \$___class__template, \$___global_variables, \$___variables) { extract(\$___global_variables); extract(\$___variables, \EXTR_OVERWRITE); ?>"; } -} + + protected function printEndSection($context) : string + { + $section = array_pop($context->sections); + $build = $context->extendFrom ? "" : "\$___class__template->renderSection({$section['name']});"; + return ""; + } +} \ No newline at end of file diff --git a/src/ControlStructure/SwitchToken.php b/src/ControlStructure/SwitchToken.php index ad1bd07..a8643f7 100644 --- a/src/ControlStructure/SwitchToken.php +++ b/src/ControlStructure/SwitchToken.php @@ -4,10 +4,31 @@ namespace Picea\ControlStructure; class SwitchToken implements ControlStructure { - public string $token = "switch"; + public array $token = [ "switch", "case", "endswitch" ]; public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) { - $context->switchStack[] = true; - return "switchStack[] = true; + return "switchStack ) { + array_pop($context->switchStack); + } + else { + $output = ""; + + case "endcase": + return ""; + + case "endswitch": + return ""; + } } } diff --git a/src/Extension/LanguageExtension.php b/src/Extension/LanguageExtension.php index d1e8677..75e9397 100644 --- a/src/Extension/LanguageExtension.php +++ b/src/Extension/LanguageExtension.php @@ -9,7 +9,7 @@ class LanguageExtension implements Extension { public array $tokens = [ "lang", "_", "language.set" ]; public string $currentLanguage = ""; - + protected LanguageHandler $languageHandler; public function __construct(Context $context, LanguageHandler $handler) { @@ -38,13 +38,13 @@ class LanguageExtension implements Extension { $context->pushFunction("_", [ $this, 'relativeLang' ]); $context->pushFunction("lang", [ $this, 'absoluteLang' ]); } - - public function relativeLang(string $key, array $variables = []) : string + + public function relativeLang(string $key, array $variables = []) #: array|string { return $this->languageHandler->languageFromKey("{$this->currentLanguage}.{$key}", $variables); } - public function absoluteLang(string $key, array $variables = []) : string + public function absoluteLang(string $key, array $variables = []) #: array|string { return $this->languageHandler->languageFromKey($key, $variables); } diff --git a/src/Extension/LanguageHandler.php b/src/Extension/LanguageHandler.php index 4f97cfd..281e6aa 100644 --- a/src/Extension/LanguageHandler.php +++ b/src/Extension/LanguageHandler.php @@ -2,7 +2,7 @@ namespace Picea\Extension; -interface LanguageHandler -{ - public function languageFromKey(string $key, array $variables = []) : string; +interface LanguageHandler +{ + public function languageFromKey(string $key, array $variables = []); #: array|string; } diff --git a/src/Extension/UrlExtension.php b/src/Extension/UrlExtension.php index d60e205..e906953 100644 --- a/src/Extension/UrlExtension.php +++ b/src/Extension/UrlExtension.php @@ -178,18 +178,9 @@ class UrlExtension implements Extension { else { $variable = $item[1]; } - - /*if ( array_key_exists($variable, $arguments) ) { - $value = $arguments[ $item[1] ]; - } - else if ( isset($default) ) { - $value = $default; - } - else { - throw new \InvalidArgumentException("Argument `$variable` is missing within required route parameter(s)"); - }*/ - + $search[ $item[0] ] = $value; + unset($arguments[ $item[1] ]); } diff --git a/src/Language/DefaultRegistrations.php b/src/Language/DefaultRegistrations.php index 6777fa1..6f9199c 100644 --- a/src/Language/DefaultRegistrations.php +++ b/src/Language/DefaultRegistrations.php @@ -45,29 +45,17 @@ class DefaultRegistrations implements LanguageRegistration public function registerControlStructure(Compiler $compiler) : void { -# @TODO -- a rewrite on the compiler has to be done to accept this kind of functionnality -# $compiler->registerControlStructure(new \Picea\ControlStructure\RawToken()); -# $compiler->registerControlStructure(new \Picea\ControlStructure\EndRawToken()); $compiler->registerControlStructure(new \Picea\ControlStructure\NamespaceToken()); $compiler->registerControlStructure(new \Picea\ControlStructure\UseToken()); $compiler->registerControlStructure(new \Picea\ControlStructure\IfToken()); - $compiler->registerControlStructure(new \Picea\ControlStructure\ElseToken()); - $compiler->registerControlStructure(new \Picea\ControlStructure\ElseIfToken()); - $compiler->registerControlStructure(new \Picea\ControlStructure\EndIfToken()); $compiler->registerControlStructure(new \Picea\ControlStructure\ForeachToken()); $compiler->registerControlStructure(new \Picea\ControlStructure\ForToken()); $compiler->registerControlStructure(new \Picea\ControlStructure\OrToken()); - $compiler->registerControlStructure(new \Picea\ControlStructure\EndforeachToken()); - $compiler->registerControlStructure(new \Picea\ControlStructure\EndforToken()); $compiler->registerControlStructure(new \Picea\ControlStructure\SwitchToken()); - $compiler->registerControlStructure(new \Picea\ControlStructure\CaseToken()); $compiler->registerControlStructure(new \Picea\ControlStructure\DefaultToken()); $compiler->registerControlStructure(new \Picea\ControlStructure\BreakToken()); - $compiler->registerControlStructure(new \Picea\ControlStructure\EndCaseToken()); - $compiler->registerControlStructure(new \Picea\ControlStructure\EndSwitchToken()); $compiler->registerControlStructure(new \Picea\ControlStructure\ExtendsToken()); $compiler->registerControlStructure(new \Picea\ControlStructure\SectionToken()); - $compiler->registerControlStructure(new \Picea\ControlStructure\EndSectionToken()); $compiler->registerControlStructure(new \Picea\ControlStructure\BlockToken()); $compiler->registerControlStructure(new \Picea\ControlStructure\ViewToken()); diff --git a/src/Picea.php b/src/Picea.php index 45daa28..bffa014 100644 --- a/src/Picea.php +++ b/src/Picea.php @@ -127,7 +127,7 @@ class Picea implements LanguageRegistration $tmpFolder = sys_get_temp_dir(); $builder = $this->instanciateBuilder(); $compiledSource = $this->compileSource($source); - list($namespace, $className, $compiledSource) = $builder->build($compiledSource['context'], $compiledSource['source']) ; + list($namespace, $className) = $builder->build($compiledSource['context'], $compiledSource['source']) ; $path = "$tmpFolder/$className.php"; file_put_contents($path, $compiledSource); @@ -141,15 +141,15 @@ class Picea implements LanguageRegistration public function fetchFromCache(string $viewPath, array $variables = [], ?object $proxy = null) : ?object { - if ( $this->debug || ! $this->cache->compiled($viewPath) ) { + if ( ( true === $this->debug ) || (! $this->cache->compiled($viewPath))) { $context = $this->compileView($viewPath); $this->cache->save($context); - + if ( $context->extendFrom ) { $this->fetchFromCache($context->extendFrom, $variables, $proxy); } } - + return $this->cache->load($viewPath, $this, $variables, $proxy); } @@ -210,6 +210,7 @@ class Picea implements LanguageRegistration $context = $builder->build($compiled['context'], $compiled['source']) ; $context->classPath = $tmpFolder . DIRECTORY_SEPARATOR . $context->className . ".php"; + if ( $context->extendFrom ) { $this->compileView($context->extendFrom); }