Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
Section
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
7 / 7
9
100.00% covered (success)
100.00%
1 / 1
 prepend
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 append
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 setValue
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 wraps
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 compare
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 divergence
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Celema\Boiler;
6
7/** @internal */
8final class Section
9{
10    /** @var list<string> in call order */
11    private array $prepended = [];
12
13    /** @var list<array{list<int>, string}> position and content of each addition, in call order */
14    private array $appended = [];
15
16    /** The main content, null until `section()` captures it, even when empty. */
17    private ?string $value = null;
18
19    public function prepend(string $content): void
20    {
21        $this->prepended[] = $content;
22    }
23
24    /** @param list<int> $position as numbered by Sections */
25    public function append(string $content, array $position): void
26    {
27        $this->appended[] = [$position, $content];
28    }
29
30    /**
31     * The additions of a layout stay closer to the main content than those of
32     * the template it wraps: page prepends, layout prepends, main content,
33     * layout appends, page appends. Call order gives that for prepends, as a
34     * layout renders after the template it wraps; appends are reordered. The
35     * appends of an included template and of its own layouts stay together at
36     * the place of the include. The default stands in for main content that
37     * was never captured.
38     */
39    public function get(string $default = ''): string
40    {
41        $appended = $this->appended;
42        usort(
43            $appended,
44            /**
45             * @param array{list<int>, string} $a
46             * @param array{list<int>, string} $b
47             */
48            static fn(array $a, array $b): int => self::compare($a[0], $b[0]),
49        );
50
51        return implode('', $this->prepended) . ($this->value ?? $default) . implode('', array_column($appended, 1));
52    }
53
54    public function setValue(string $value): void
55    {
56        $this->value = $value;
57    }
58
59    /**
60     * Whether the template at position $a wraps the one at $b: it is a
61     * layout of that template or of a template that included it, or a
62     * template that such a layout included. That is the case when the
63     * positions first differ in a layout level, as $a must render later and
64     * layout levels only grow while a template renders. Positions as
65     * numbered by Sections.
66     *
67     * @param list<int> $a
68     * @param list<int> $b
69     */
70    public static function wraps(array $a, array $b): bool
71    {
72        return (self::divergence($a, $b) % 2) === 0;
73    }
74
75    /**
76     * Orders two appends at the first difference of their positions: a
77     * higher layout level first, otherwise call order.
78     *
79     * @param list<int> $a
80     * @param list<int> $b
81     */
82    private static function compare(array $a, array $b): int
83    {
84        $index = self::divergence($a, $b);
85
86        if (($index % 2) === 0) {
87            return $b[$index] <=> $a[$index];
88        }
89
90        return $a[$index] <=> $b[$index];
91    }
92
93    /**
94     * The index of the first difference of two positions. Even parts are
95     * layout levels, odd parts call numbers. Call numbers are unique, so two
96     * positions always differ before either ends.
97     *
98     * @param list<int> $a
99     * @param list<int> $b
100     */
101    private static function divergence(array $a, array $b): int
102    {
103        $index = 0;
104
105        while ($a[$index] === $b[$index]) {
106            $index++;
107        }
108
109        return $index;
110    }
111}