Engine::prepareDirs()   A
last analyzed

Complexity

Conditions 4
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 5
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 9
ccs 6
cts 6
cp 1
crap 4
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Conia\Boiler;
6
7
use Conia\Boiler\Exception\LookupException;
8
use Conia\Boiler\Exception\UnexpectedValueException;
9
10
/**
11
 * @psalm-api
12
 *
13
 * @psalm-type DirsInput = non-empty-string|list<non-empty-string>|array<non-empty-string, non-empty-string>
14
 * @psalm-type Dirs = list<string>|array<non-empty-string, non-empty-string>
15
 */
16
class Engine
17
{
18
    use RegistersMethod;
19
20
    /** @var Dirs */
0 ignored issues
show
Bug introduced by
The type Conia\Boiler\Dirs was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
21
    protected readonly array $dirs;
22
23
    /**
24
     * @psalm-param DirsInput $dirs
25
     * @psalm-param list<class-string> $whitelist
26
     */
27 61
    public function __construct(
28
        string|array $dirs,
29
        protected readonly array $defaults = [],
30
        protected readonly array $whitelist = [],
31
        protected readonly bool $autoescape = true,
32
    ) {
33 61
        $this->dirs = $this->prepareDirs($dirs);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->prepareDirs($dirs) of type array is incompatible with the declared type Conia\Boiler\Dirs of property $dirs.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
Bug introduced by
The property dirs is declared read-only in Conia\Boiler\Engine.
Loading history...
34 58
        $this->customMethods = new CustomMethods();
35
    }
36
37
    /** @psalm-param non-empty-string $path */
38 44
    public function template(string $path): Template
39
    {
40 44
        if (!preg_match('/^[\\w\\.\/:_-]+$/u', $path)) {
41 2
            throw new UnexpectedValueException('The template path is invalid or empty');
42
        }
43
44 42
        $template = new Template($this->getFile($path), new Sections(), $this);
45 36
        $template->setCustomMethods($this->customMethods);
46
47 36
        return $template;
48
    }
49
50
    /** @psalm-param non-empty-string $path */
51 44
    public function render(
52
        string $path,
53
        array $context = [],
54
        ?bool $autoescape = null
55
    ): string {
56 44
        if (is_null($autoescape)) {
57
            // Use the engine's default value if nothing is passed
58 43
            $autoescape = $this->autoescape;
59
        }
60
61 44
        $template = $this->template($path);
62
63 36
        return $template->render(array_merge($this->defaults, $context), $this->whitelist, $autoescape);
64
    }
65
66
    /**
67
     * @psalm-param non-empty-string $path
68
     *
69
     * @psalm-return non-empty-string
70
     */
71 47
    public function getFile(string $path): string
72
    {
73 47
        [$namespace, $file] = $this->getSegments($path);
74
75 44
        if ($namespace) {
76 3
            $dir = $this->dirs[$namespace] ?? '';
77 3
            $templatePath = $this->validateFile($this->dirs[$namespace], $file);
78
        } else {
79 42
            $templatePath = false;
80
81 42
            foreach ($this->dirs as $dir) {
82 42
                if ($templatePath = $this->validateFile($dir, $file)) {
83 38
                    break;
84
                }
85
            }
86
        }
87
88 44
        if (isset($dir) && $templatePath && is_file($templatePath)) {
89 40
            if (!str_starts_with($templatePath, (string)$dir)) {
90 1
                throw new LookupException(
91 1
                    'Template resides outside of root directory: ' . $templatePath
92 1
                );
93
            }
94
95 39
            return $templatePath;
96
        }
97
98 7
        throw new LookupException('Template not found: ' . $path);
99
    }
100
101
    /** @psalm-param non-empty-string $path */
102 1
    public function exists(string $path): bool
103
    {
104
        try {
105 1
            $this->getFile($path);
106
107 1
            return true;
108 1
        } catch (LookupException) {
109 1
            return false;
110
        }
111
    }
112
113
    /**
114
     * @psalm-param DirsInput $dirs
115
     *
116
     * @return Dirs
117
     */
118 61
    protected function prepareDirs(string|array $dirs): array
119
    {
120 61
        if (is_string($dirs)) {
0 ignored issues
show
introduced by
The condition is_string($dirs) is always false.
Loading history...
121 21
            return [realpath($dirs) ?: throw new LookupException('Template directory does not exist ' . $dirs)];
122
        }
123
124 40
        return array_map(
0 ignored issues
show
Bug Best Practice introduced by
The expression return array_map(functio...) { /* ... */ }, $dirs) returns the type array which is incompatible with the documented return type Conia\Boiler\Dirs.
Loading history...
125 40
            fn ($dir) => realpath($dir) ?: throw new LookupException('Template directory does not exist ' . $dir),
126 40
            $dirs
127 40
        );
128
    }
129
130 44
    protected function validateFile(string $dir, string $file): string|false
131
    {
132 44
        $path = $dir . DIRECTORY_SEPARATOR . $file;
133
134 44
        if ($realpath = realpath($path . '.php')) {
135 39
            return $realpath;
136
        }
137
138 9
        return realpath($path);
139
    }
140
141
    /** @return list{non-empty-string|null, non-empty-string} */
0 ignored issues
show
Bug introduced by
The type Conia\Boiler\list was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
142 47
    protected function getSegments(string $path): array
143
    {
144 47
        if (strpos($path, ':') === false) {
145 42
            $path = trim($path);
146 42
            assert(!empty($path));
147
148 42
            return [null, $path];
0 ignored issues
show
Bug Best Practice introduced by
The expression return array(null, $path) returns the type array<integer,null|string> which is incompatible with the documented return type Conia\Boiler\list.
Loading history...
149
        }
150 6
        $segments = array_map(fn ($s) => trim($s), explode(':', $path));
151
152 6
        if (count($segments) == 2) {
153 5
            if (!empty($segments[0]) && !empty($segments[1])) {
154 3
                return [$segments[0], $segments[1]];
0 ignored issues
show
Bug Best Practice introduced by
The expression return array($segments[0], $segments[1]) returns the type array which is incompatible with the documented return type Conia\Boiler\list.
Loading history...
155
            }
156
157 2
            throw new LookupException(
158 2
                "Invalid template format: '{$path}'. " .
159 2
                    "Use 'namespace:template/path or template/path'."
160 2
            );
161
        } else {
162 1
            throw new LookupException(
163 1
                "Invalid template format: '{$path}'. " .
164 1
                    "Use 'namespace:template/path or template/path'."
165 1
            );
166
        }
167
    }
168
}
169