Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
Blocks
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
3 / 3
7
100.00% covered (success)
100.00%
1 / 1
 open
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 close
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 assertClosed
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Celema\Boiler;
6
7use Celema\Boiler\Exception\LogicException;
8use Closure;
9
10/**
11 * The capture blocks that a template's current render has opened and not
12 * yet closed, innermost last: sections and components.
13 *
14 * Every template render has its own stack, so a template can only close the
15 * blocks it opened itself. Blocks may nest: a section can contain a component,
16 * another section, or an include whose template opens and closes its own blocks.
17 *
18 * @internal
19 */
20final class Blocks
21{
22    /** @var list<Block> */
23    private array $open = [];
24
25    /** @param Closure(string, Location): void $close receives the captured output and where the block opened */
26    public function open(string $kind, string $name, Location $location, Closure $close): void
27    {
28        ob_start();
29        $this->open[] = new Block($kind, $name, $location, $close);
30    }
31
32    /**
33     * Closes the innermost block. With a name, it must be that block's name.
34     */
35    public function close(?string $name = null): void
36    {
37        $block = end($this->open);
38
39        if ($block === false) {
40            throw new LogicException('No open section or component to close');
41        }
42
43        if ($name !== null && $name !== $block->name) {
44            throw new LogicException("`end('{$name}')` does not match the open {$block->kind} `{$block->name}`");
45        }
46
47        array_pop($this->open);
48        ($block->close)((string) ob_get_clean(), $block->location);
49    }
50
51    public function assertClosed(): void
52    {
53        $block = $this->open[0] ?? null;
54
55        if ($block === null) {
56            return;
57        }
58
59        throw new LogicException(
60            "Unclosed {$block->kind} `{$block->name}` at {$block->location}",
61            location: $block->location,
62        );
63    }
64}