Engine::resolvePath()   A
last analyzed

Complexity

Conditions 6
Paths 10

Size

Total Lines 32
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 6.0585

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 16
c 1
b 0
f 0
nc 10
nop 1
dl 0
loc 32
ccs 15
cts 17
cp 0.8824
crap 6.0585
rs 9.1111
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Setono\PhpTemplates\Engine;
6
7
use const E_USER_DEPRECATED;
8
use const PATHINFO_EXTENSION;
9
use function Safe\ob_end_clean;
10
use function Safe\preg_match;
11
use function Safe\sprintf;
12
use Setono\PhpTemplates\Exception\InvalidPathException;
13
use Setono\PhpTemplates\Exception\InvalidTemplateFormatException;
14
use Setono\PhpTemplates\Exception\RenderingException;
15
use Setono\PhpTemplates\Exception\TemplateNotFoundException;
16
use SplPriorityQueue;
17
use Throwable;
18
19
final class Engine implements EngineInterface
20
{
21
    /** @var string */
22
    private const TEMPLATE_FORMAT_REGEX = '^@([^/]+)/(.+\..+)';
23
24
    /** @var SplPriorityQueue */
25
    private $paths;
26
27 9
    public function __construct(array $paths = [])
28
    {
29 9
        $this->paths = new SplPriorityQueue();
30
31 9
        foreach ($paths as $idx => $value) {
32 6
            $priority = 0;
33 6
            $path = $value;
34
35
            // if the index is a string it is the namespace and the value is the priority
36 6
            if (is_string($idx)) {
37 1
                $path = $idx;
38 1
                $priority = $value;
39
            }
40 6
            $this->addPath($path, $priority);
41
        }
42 8
    }
43
44 8
    public function addPath(string $path, int $priority = 0): void
45
    {
46 8
        $path = rtrim($path, '/');
47
48 8
        if (!is_dir($path) || !is_readable($path)) {
49 1
            throw new InvalidPathException($path);
50
        }
51
52 7
        $this->paths->insert($path, $priority);
53 7
    }
54
55 8
    public function render(string $template, array $context = []): string
56
    {
57 8
        $template = $this->resolvePath($template);
58
59
        $inc = static function (): void {
60 5
            extract(func_get_arg(1));
61 5
            include func_get_arg(0);
62 5
        };
63
64
        return self::obWrap($template, static function () use ($inc, $template, $context): void {
65 5
            $inc($template, $context);
66 5
        });
67
    }
68
69 8
    private function resolvePath(string $template): string
70
    {
71
        // this is a BC layer for when a user render a template without an extension
72 8
        if (pathinfo($template, PATHINFO_EXTENSION) === '') {
73
            @trigger_error('Not adding an extension to the template name is deprecated since v1.1', E_USER_DEPRECATED);
74
            $template .= '.php';
75
        }
76
77 8
        if (preg_match(sprintf('#%s#', self::TEMPLATE_FORMAT_REGEX), $template, $matches) !== 1) {
78 1
            throw new InvalidTemplateFormatException($template);
79
        }
80
81 7
        [, $namespace, $filename] = $matches;
82
83 7
        $checkedPaths = [];
84
85 7
        foreach ($this->paths as $path) {
86 7
            $checkedPaths[] = $path;
87
88 7
            if (!is_dir($path . '/' . $namespace)) {
89 1
                continue;
90
            }
91
92 6
            $resolvedPath = $path . '/' . $namespace . '/' . $filename;
93 6
            if (!file_exists($resolvedPath)) {
94 1
                continue;
95
            }
96
97 5
            return $resolvedPath;
98
        }
99
100 2
        throw new TemplateNotFoundException($template, $checkedPaths);
101
    }
102
103 5
    private static function obWrap(string $template, callable $wrap): string
104
    {
105 5
        $level = ob_get_level();
106
107
        try {
108 5
            ob_start();
109 5
            $wrap();
110
111 4
            return (string) ob_get_clean();
112 1
        } catch (Throwable $e) {
113 1
            while (ob_get_level() > $level) {
114 1
                ob_end_clean();
115
            }
116
117 1
            throw new RenderingException($template, $e);
118
        }
119
    }
120
}
121