diff --git a/meta/definitions.php b/meta/definitions.php index 009b0b0..2045a18 100644 --- a/meta/definitions.php +++ b/meta/definitions.php @@ -20,7 +20,10 @@ return [ }, Context::class => function($c) { - return new BaseContext($c->get(Lean\Lean::class)->getPiceaContext()); + return new BaseContext( + defaultNamespace: $c->get(Lean\Lean::class)->getPiceaContext(), + declare: getenv('PICEA_DECLARE') ?? 'declare(strict_types=1)' + ); }, Compiler::class => autowire(Compiler::class), diff --git a/src/Builder.php b/src/Builder.php index 990880a..fd72786 100644 --- a/src/Builder.php +++ b/src/Builder.php @@ -22,6 +22,7 @@ class Builder $replace = [ '%NAMESPACE%' => $context->namespace, '%USE%' => ( $uses = $context->renderUses() ) ? $uses : false, + '%DECLARE%' => $context->declare ? rtrim($context->declare, ';') . ';' : "", '%CLASSNAME%' => $context->className, '%PATHNAME%' => $path($context->viewPath), '%FULLPATH%' => $path($context->filePath), diff --git a/src/Builder/ClassTemplate.php b/src/Builder/ClassTemplate.php index 02e0d69..0231fc0 100644 --- a/src/Builder/ClassTemplate.php +++ b/src/Builder/ClassTemplate.php @@ -1,4 +1,4 @@ -namespace = $defaultNamespace; } diff --git a/src/Compiler/Context.php b/src/Compiler/Context.php index 09db1e7..9eaab46 100644 --- a/src/Compiler/Context.php +++ b/src/Compiler/Context.php @@ -6,36 +6,6 @@ use Picea\Compiler; abstract class Context { - public string $namespace = ""; - - public string $extendFrom = ""; - - public string $className = ""; - - public string $compiledSource = ""; - - public ? string $sectionAction = null; - - public string $viewPath = ""; - - public string $filePath = ""; - - public string $classPath = ""; - - public array $switchStack = []; - - public array $iterationStack = []; - - public array $useStack = []; - - public array $sections = []; - - public int $functions = 0; - - public array $functionStack = []; - - public array $hooks = []; - public Compiler $compiler; public function hook(string $path, Callable $callback) : self diff --git a/src/ControlStructure/BlockToken.php b/src/ControlStructure/BlockToken.php index 63433ed..05c828d 100644 --- a/src/ControlStructure/BlockToken.php +++ b/src/ControlStructure/BlockToken.php @@ -2,16 +2,21 @@ namespace Picea\ControlStructure; +use http\Exception\InvalidArgumentException; + class BlockToken implements ControlStructure { - public array $token = [ "arguments", "block", "endblock", "/block", "define", "slot", "endslot", "/slot", "using" ]; + public array $token = [ "arguments", "block", "endblock", "/block", "define", "slot", "endslot", "/slot", "using", "public", "private", "protected", "set" ]; public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string { static $depth = 0; + static $slotDepth = 0; static $slotDefinitions = []; + static $slotName = []; + $class= static::class; - # dump($depth, $token, $arguments, $slotDefinitions); + # dump($slotDepth, $token, $arguments, $slotDefinitions); switch($token) { case "block": @@ -26,8 +31,6 @@ class BlockToken implements ControlStructure { return "render(\$___class__template); unset(\$___block); ?>"; case "arguments": - $class = static::class; - return <<iterationStack ?? []) ? ",". implode(', ', array_filter(array_column($context->iterationStack, 'uid'), fn($e) => strpos($e, '[') === false)) : null; + $slotDepth++; if ($def->hasDefinitions() ) { - $slotName = eval("return $name;"); - $def->currentSlot = $slotName; - $def->setDefinitionVars($slotName, $definition); + $slotName[] = eval("return $name;"); + $def->currentSlot = end($slotName); + $def->setDefinitionVars(end($slotName), $definition); - $definition = $def->printDefinition($slotName); + $definition = $def->printDefinition(end($slotName)); if ($definition) { $definition .= ","; } return <<printSlot($name, function($definition array \$___using = []) use (\$picea $loops) { extract(\$___using, \EXTR_SKIP); ?> + printSlot($name, function($definition array \$___using = []) use (\$picea, \$___class__template $loops) { extract(\$___using, \EXTR_SKIP); ?> PHP; } else { @@ -82,15 +86,17 @@ class BlockToken implements ControlStructure { } return <<slotIsSet($name) || (\$___block ?? \$this)->setSlot($name, function($definition array \$___using = []) use (\$picea $loops) { extract(\$___using, \EXTR_SKIP); ?> + slotIsSet($name) || (\$___block ?? \$this)->setSlot($name, function($definition array \$___using = []) use (\$picea, \$___class__template $loops) { extract(\$___using, \EXTR_SKIP); ?> PHP; } case "/slot": case "endslot": + $slotDepth--; $def = ( $slotDefinitions[$depth] ?? end($slotDefinitions) ); if ($def->hasDefinitions() ) { + $def->currentSlot = array_pop($slotName); $definition = $def->getCurrentSlotDefinitionVars(); if ($definition) { @@ -105,9 +111,21 @@ class BlockToken implements ControlStructure { return ""; } + case "public": + case "private": + case "protected": + $arguments = rtrim($arguments, ';'); + return "createProperty(\\$class::parseProperty('$token', fn($arguments) => func_get_arg(0))) ?>"; + + case "set": + return "setPropertyValue({$arguments}); ?>"; + + # DEPRECATED ; should be removed, to be superseded by 'set' instead case "using": return "setUsing($arguments); ?>"; } + + throw new \LogicException("Unhandled token ? Should never have been here !"); } public function inlineHtml(? object $proxy, string $viewPath, ... $variables) { @@ -119,7 +137,6 @@ class BlockToken implements ControlStructure { #return } - public static function parseArguments(Callable $method, array $arguments) : array { try{ @@ -131,7 +148,6 @@ class BlockToken implements ControlStructure { $parameters = []; - foreach((new \ReflectionFunction($method))->getParameters() as $key => $value) { $name = $value->getName(); @@ -158,6 +174,26 @@ class BlockToken implements ControlStructure { return $parameters; } + public static function parseProperty(string $token, \Closure $method) : BlockToken\Property + { + foreach((new \ReflectionFunction($method))->getParameters() as $value) { + $property = new BlockToken\Property( + name: $value->getName(), + scope: BlockToken\PropertyScopeEnum::from($token), + setter: $method, + ); + + if ($value->isDefaultValueAvailable()) { + $property->value = $value->getDefaultValue(); + } + + # For now, we only allow one declaration per token, might be revised at another moment... + break; + } + + return $property ?? throw new \LogicException("No parameters found in property declaration."); + } + public static function slotDefinitions() { return new class() { @@ -230,6 +266,10 @@ class BlockToken implements ControlStructure { public array $definition = []; + protected array $properties = []; + + private array $definedPropertyValues = []; + public function __construct(string $viewPath, ...$arguments) { $this->viewPath = $viewPath; $this->arguments = $arguments; @@ -261,14 +301,51 @@ class BlockToken implements ControlStructure { return ! empty($this->slots[$name]); } - public function printSlot(string $name, Callable $default) + public function printSlot(string $name, Callable $default) : mixed { return $this->slotIsSet($name) ? $this->slots[$name] : $default; } - public function setUsing(array $variables) { + public function setUsing(array $variables) : void + { $this->using = $variables; } + + public function setPropertyValue(array $vars) : void + { + foreach($vars as $key => $value) { + $this->definedPropertyValues[$key] = $value; + } + } + + public function createProperty(BlockToken\Property $property) : void + { + if (isset($this->properties[$property->name])) { + throw new \InvalidArgumentException("Given property '{$property->name}' is already defined somewhere else in this block."); + } + + $this->properties[$property->name] = $property; + + if (isset($this->definedPropertyValues[$property->name])) { + if ($property->scope !== BlockToken\PropertyScopeEnum::Public) { + throw new \InvalidArgumentException( + sprintf("Trying to set property %s but it's scope (%s) makes it impossible", $property->name, $property->scope->name) + ); + } + + $this->__set($property->name, $this->definedPropertyValues[$property->name]); + } + } + + public function __set(string $name, mixed $value) : void + { + $this->properties[$name]->value = call_user_func($this->properties[$name]->setter, $value); + } + + public function __get(string $name) : mixed + { + return $this->properties[$name]->value; + } }; } } \ No newline at end of file diff --git a/src/ControlStructure/BlockToken/Property.php b/src/ControlStructure/BlockToken/Property.php new file mode 100644 index 0000000..049c1a6 --- /dev/null +++ b/src/ControlStructure/BlockToken/Property.php @@ -0,0 +1,14 @@ +"; } - public function handleTitle(? string $set = null, ...$arguments) : ? string + public function handleTitle(null|\Stringable|string $set = null, ...$arguments) : ? string { if ( $set === null ) { return $this->title; @@ -31,6 +31,7 @@ class TitleExtension implements Extension, FunctionExtension { else { # Fixed a bug where template inheritance was rewriting title if ( empty($this->title) ) { + $set = (string) $set; $this->title = $arguments ? sprintf($set, ...$arguments) : $set; } } diff --git a/src/Extension/UrlExtension.php b/src/Extension/UrlExtension.php index afe3c5f..8788560 100644 --- a/src/Extension/UrlExtension.php +++ b/src/Extension/UrlExtension.php @@ -138,9 +138,9 @@ PATTERN; } # src: https://stackoverflow.com/a/14550919 - public static function slug(string $text, string $separator = '-') : string + public static function slug(\Stringable|string $text, string $separator = '-') : string { - $clean = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $text); + $clean = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', (string) $text); $clean = preg_replace("/[^a-zA-Z0-9\/_| -]/", '', $clean); return preg_replace("/[\/_| -]+/", $separator, strtolower(trim($clean, '-')));