Passed
Branch 6.0 (c154c1)
by Olivier
11:35
created

PHPEngine::render()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 26
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 14
c 1
b 0
f 0
nc 7
nop 3
dl 0
loc 26
rs 9.4888
1
<?php
2
3
/*
4
 * This file is part of the ICanBoogie package.
5
 *
6
 * (c) Olivier Laviale <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace ICanBoogie\Render;
13
14
use ArrayObject;
15
use InvalidArgumentException;
16
use Stringable;
17
use Throwable;
18
19
use function extract;
20
use function get_debug_type;
21
use function is_array;
22
use function is_object;
23
use function is_string;
24
use function ob_end_clean;
25
use function ob_get_clean;
26
use function ob_start;
27
28
/**
29
 * Renders PHP templates.
30
 */
31
final class PHPEngine implements Engine
32
{
33
    private const FORBIDDEN_VAR_THIS = 'this';
34
35
    /**
36
     * @throws Throwable
37
     */
38
    public function render(string $template_pathname, mixed $content, array $variables): string
39
    {
40
        if (isset($variables[self::FORBIDDEN_VAR_THIS])) {
41
            throw new InvalidArgumentException("The usage of 'this' is forbidden in variables.");
42
        }
43
44
        $f = function ($__TEMPLATE_PATHNAME__, $__VARIABLES__) {
45
            extract($__VARIABLES__);
46
47
            require $__TEMPLATE_PATHNAME__;
48
        };
49
50
        if ($content) {
51
            $f = $f->bindTo($this->ensure_is_object($content));
52
        }
53
54
        ob_start();
55
56
        try {
57
            $f($template_pathname, [ self::VAR_TEMPLATE_PATHNAME => $template_pathname ] + $variables);
58
59
            return ob_get_clean() ?: "";
60
        } catch (Throwable $e) {
61
            ob_end_clean();
62
63
            throw $e;
64
        }
65
    }
66
67
    /**
68
     * Ensures that a value is an object.
69
     *
70
     * - `value` is an object, value is returned.
71
     * - `value` is an array, an `ArrayObject` instance is returned.
72
     * - Otherwise `value` is cast into a string and a {@link String} instance is returned.
73
     */
74
    private function ensure_is_object(mixed $value): object
75
    {
76
        if (is_object($value)) {
77
            return $value;
78
        }
79
80
        if (is_array($value)) {
81
            return new ArrayObject($value);
82
        }
83
84
        if (is_string($value)) {
85
            return new class ($value) implements Stringable {
86
                public function __construct(
87
                    private readonly string $value
88
                ) {
89
                }
90
91
                public function __toString(): string
92
                {
93
                    return $this->value;
94
                }
95
            };
96
        }
97
98
        throw new InvalidArgumentException("Don't know what to do with: " . get_debug_type($value) . ".");
99
    }
100
}
101