Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
TemplateFunctions
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
3 / 3
9
100.00% covered (success)
100.00%
1 / 1
 run
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 wrapper
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 load
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
6
1<?php
2
3declare(strict_types=1);
4
5namespace Celema\Boiler;
6
7use Celema\Boiler\Exception\LogicException;
8use Closure;
9use ReflectionFunction;
10
11/**
12 * Backs the global template functions `u()` and `w()`: loads them and keeps
13 * the wrappers of the renders in progress.
14 *
15 * @internal
16 */
17final class TemplateFunctions
18{
19    /** @var list<Contract\Wrapper> the innermost render's last */
20    private static array $wrappers = [];
21    private static bool $loaded = false;
22
23    /**
24     * Runs a render, during which `w()` wraps values with its wrapper.
25     *
26     * @param Closure(): string $render
27     */
28    public static function run(Contract\Wrapper $wrapper, Closure $render): string
29    {
30        self::load();
31        self::$wrappers[] = $wrapper;
32
33        try {
34            return $render();
35        } finally {
36            array_pop(self::$wrappers);
37        }
38    }
39
40    /** The wrapper of the innermost render in progress, if any. */
41    public static function wrapper(): ?Contract\Wrapper
42    {
43        return self::$wrappers === [] ? null : self::$wrappers[array_key_last(self::$wrappers)];
44    }
45
46    private static function load(): void
47    {
48        if (self::$loaded) {
49            return;
50        }
51
52        $file = __DIR__ . '/functions.php';
53        $defined = false;
54
55        foreach (['u', 'w'] as $name) {
56            if (!function_exists($name)) {
57                continue;
58            }
59
60            $origin = (string) new ReflectionFunction($name)->getFileName();
61
62            // Preloading may have loaded Boiler's own file already.
63            if (realpath($origin) !== $file) {
64                throw new LogicException(
65                    "Boiler's template function {$name}() clashes with the global function {$name}() in {$origin}",
66                );
67            }
68
69            $defined = true;
70        }
71
72        if (!$defined) {
73            require $file;
74        }
75
76        self::$loaded = true;
77    }
78}