diff --git a/src/Phaseolies/Http/Controllers/Controller.php b/src/Phaseolies/Http/Controllers/Controller.php index 1ff6bfb..9de3bf6 100644 --- a/src/Phaseolies/Http/Controllers/Controller.php +++ b/src/Phaseolies/Http/Controllers/Controller.php @@ -108,38 +108,34 @@ class Controller extends View */ protected const MAX_COMPILE_RETRIES = 3; + /** + * Counter for tracking solo block uniqueness + * + * @var int + */ + protected int $soloCounter = 0; + + /** + * Tracks which solo blocks have already rendered + * + * @var array + */ + protected array $soloStack = []; + /** * Constructor to initialize the template engine with default settings */ public function __construct() { parent::__construct(); - - // Set the file extension for template files (changed to .odo.php) $this->setFileExtension('.odo.php'); - - // Set the directory where view files are stored $this->setViewFolder('resources/views' . DIRECTORY_SEPARATOR); - - // Set the directory where cached files are stored $this->setCacheFolder('storage/framework/views' . DIRECTORY_SEPARATOR); - - // Create the cache folder if it doesn't exist $this->createCacheFolder(); - - // Set the directory where cached files are stored $this->setSymlinkPathFolder('storage/app/public' . DIRECTORY_SEPARATOR); - - // Create the cache folder if it doesn't exist $this->createPublicSymlinkFolder(); - - // Set the format for echoing variables in templates $this->setEchoFormat('$this->e(%s)'); - - // Initialize arrays for blocks, block stacks, and loop stacks $this->loopStacks = []; - - // Load custom syntax from config if available $this->loadCustomSyntax(); } diff --git a/src/Phaseolies/Support/Odo/OdoCondition.php b/src/Phaseolies/Support/Odo/OdoCondition.php index 9777d3c..a859d15 100644 --- a/src/Phaseolies/Support/Odo/OdoCondition.php +++ b/src/Phaseolies/Support/Odo/OdoCondition.php @@ -541,4 +541,111 @@ protected function compileEndscopenot(): string { return ""; } + + /** + * Usage: #blank($variable) + * Renders block when variable is empty, null, or an empty array/string + * + * @param mixed $variable + * @return string + */ + protected function compileBlank($variable): string + { + return ""; + } + + /** + * Usage: #endblank + * + * @return string + */ + protected function compileEndblank(): string + { + return ""; + } + + /** + * Usage: #notblank($variable) + * Renders block when variable is NOT empty + * + * @param mixed $variable + * @return string + */ + protected function compileNotblank($variable): string + { + return ""; + } + + /** + * Usage: #endnotblank + * + * @return string + */ + protected function compileEndnotblank(): string + { + return ""; + } + + /** + * Usage: #solo + * Renders its content only once, even if inside a loop + * + * @return string + */ + protected function compileSolo(): string + { + return "soloCounter ?? $this->soloCounter = 0) . "'])): \$__solo['" . $this->soloCounter++ . "'] = true; ?>"; + } + + /** + * Usage: #endsolo + * + * @return string + */ + protected function compileEndsolo(): string + { + return ""; + } + + /** + * Usage: #inject('stack-name') + * Pushes content into a named stack + * + * @param string $expression + * @return string + */ + protected function compileInject($expression): string + { + if (isset($expression[0]) && '(' === $expression[0]) { + $expression = substr($expression, 1, -1); + } + + return "startInject({$expression}); ?>"; + } + + /** + * Usage: #endinject + * + * @return string + */ + protected function compileEndinject(): string + { + return "endInject(); ?>"; + } + + /** + * Usage: #slot('stack-name') + * Outputs all content pushed into a named stack + * + * @param string $expression + * @return string + */ + protected function compileSlot($expression): string + { + if (isset($expression[0]) && '(' === $expression[0]) { + $expression = substr($expression, 1, -1); + } + + return "renderSlot({$expression}); ?>"; + } } diff --git a/src/Phaseolies/Support/View/View.php b/src/Phaseolies/Support/View/View.php index 2b7e282..faebe72 100644 --- a/src/Phaseolies/Support/View/View.php +++ b/src/Phaseolies/Support/View/View.php @@ -44,12 +44,25 @@ class View extends Factory /** * Stack of currently rendering view names. - * Useful for debugging and internal state tracking. * * @var array */ protected $renderStack = []; + /** + * Named stacks for inject/slot system + * + * @var array + */ + protected array $stacks = []; + + /** + * Currently open inject stack name + * + * @var string|null + */ + protected ?string $currentInject = null; + public function __construct() { $this->factory = app('view'); @@ -262,6 +275,74 @@ private function isAssoc(array $array): bool return array_keys($array) !== range(0, count($array) - 1); } + /** + * Start capturing content for a named inject stack + * + * @param string $name + * @return void + */ + public function startInject(string $name): void + { + if (empty($name)) { + throw new \InvalidArgumentException('Inject stack name must not be empty'); + } + + $this->currentInject = trim($name, "'\""); + ob_start(); + } + + /** + * End capturing and push content into the named stack + * + * @return void + */ + public function endInject(): void + { + if ($this->currentInject === null) { + throw new \RuntimeException('Cannot end inject — no inject block is open'); + } + + $content = ob_get_clean(); + $name = $this->currentInject; + + if (!isset($this->stacks[$name])) { + $this->stacks[$name] = []; + } + + $this->stacks[$name][] = $content; + $this->currentInject = null; + } + + /** + * Render all content pushed into a named stack + * + * @param string $name + * @return string + */ + public function renderSlot(string $name): string + { + $name = trim($name, "'\""); + + if (!isset($this->stacks[$name])) { + return ''; + } + + return implode('', $this->stacks[$name]); + } + + /** + * Check if a stack has any content + * + * @param string $name + * @return bool + */ + public function hasSlot(string $name): bool + { + $name = trim($name, "'\""); + + return !empty($this->stacks[$name]); + } + /** * Clean the block state * @@ -272,6 +353,8 @@ public function flush() $this->blocks = []; $this->blockStacks = []; $this->parents = []; + $this->stacks = []; + $this->currentInject = null; } public function __destruct()