TemplateContext.php
PHP
Path: src/Template/TemplateContext.php
<?php
namespace mini\Template;
/**
* Template context - the $this object available in templates
*
* Provides template inheritance via:
* - $this->extend() - extend a parent layout
* - $this->block() / $this->end() - define blocks
* - $this->show() - output blocks in parent templates
*
* Example usage within templates:
* ```php
* <?php $this->extend('layout.php'); ?>
* <?php $this->block('title', 'Home'); ?>
* <?php $this->block('content'); ?><p>Hello!</p><?php $this->end(); ?>
* ```
*/
final class TemplateContext
{
public ?string $layout = null;
public array $blocks = [];
private array $stack = [];
private ?string $defaultLayout = null;
/**
* Set the default layout (called from viewstart processing)
*/
public function setDefaultLayout(?string $layout): void
{
$this->defaultLayout = $layout;
}
/**
* Mark this template as extending a parent layout
*
* Must be called once, before defining any blocks.
*
* @param string|null $file Parent template filename (uses default if null)
* @throws \LogicException If called twice, after blocks, or no layout available
*/
public function extend(?string $file = null): void
{
if ($this->layout !== null) {
throw new \LogicException('extend() called twice - only one parent layout allowed');
}
if ($this->blocks) {
throw new \LogicException('extend() must be called before defining blocks');
}
$this->layout = $file ?? $this->defaultLayout ?? throw new \LogicException(
'No layout specified and no $layout default set'
);
}
/**
* Define a block (dual-use: inline or buffered)
*
* Inline mode: $block('name', 'inline content')
* Buffered mode: $block('name'); ... $end();
*
* @param string $name Block name
* @param string|null $value Optional inline value
*/
public function block(string $name, ?string $value = null): void
{
if ($value !== null) {
$this->blocks[$name] = $value;
return;
}
$this->stack[] = $name;
ob_start();
}
/**
* End a buffered block started with block()
*
* @throws \LogicException If no block was started
*/
public function end(): void
{
if (!$this->stack) {
throw new \LogicException('No block started');
}
$name = array_pop($this->stack);
$this->blocks[$name] = ob_get_clean();
}
/**
* Output a block in parent templates
*
* @param string $name Block name
* @param string $default Default content if block not defined
*/
public function show(string $name, string $default = ''): void
{
echo $this->blocks[$name] ?? $default;
}
/**
* Include a template file with $this bound to this context
*
* @param string $__file Absolute path to template file
* @param array $__vars Variables to extract into template scope
*/
public function include(string $__file, array $__vars): void
{
extract($__vars, EXTR_SKIP);
require $__file;
}
/**
* Check for unclosed blocks after template execution
*
* @throws \LogicException If any blocks were started but not closed
*/
public function assertNoUnclosedBlocks(): void
{
if ($this->stack) {
$unclosed = implode(', ', $this->stack);
throw new \LogicException("Unclosed block(s): $unclosed - missing \$this->end() call(s)");
}
}
}