x Sorry, these patches are not available anymore due to data migration. Please run a fresh inspection.
Completed
Push — master ( 3b9aa0...812432 )
by Anton
03:38
created

StemplerLoader::getSourceContext()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * Spiral, Core Components
4
 *
5
 * @author Wolfy-J
6
 */
7
8
namespace Spiral\Stempler;
9
10
use Spiral\Files\FileManager;
11
use Spiral\Files\FilesInterface;
12
use Spiral\Stempler\Exceptions\LoaderException;
13
14
/**
15
 * Simple loader with ability to use multiple namespaces. Copy from TwigLoader.
16
 */
17
class StemplerLoader implements LoaderInterface
18
{
19
    const DEFAULT_NAMESPACE = 'default';
20
    const FILE_EXTENSION    = 'php';
21
22
    /**
23
     * Path chunks.
24
     */
25
    const VIEW_FILENAME  = 0;
26
    const VIEW_NAMESPACE = 1;
27
    const VIEW_NAME      = 2;
28
29
    /**
30
     * Available view namespaces associated with their directories.
31
     *
32
     * @var array
33
     */
34
    protected $namespaces = [];
35
36
    /**
37
     * @var FilesInterface
38
     */
39
    protected $files = null;
40
41
    /**
42
     * @param array          $namespaces
43
     * @param FilesInterface $files
44
     */
45
    public function __construct(array $namespaces, FilesInterface $files = null)
46
    {
47
        $this->namespaces = $namespaces;
48
        $this->files = $files ?? new FileManager();
49
    }
50
51
    /**
52
     * {@inheritdoc}
53
     */
54
    public function getSourceContext(string $path): SourceContextInterface
55
    {
56
        return new SourceContext(
57
            $this->locateView($path)[self::VIEW_FILENAME]
58
        );
59
    }
60
61
    /**
62
     * Locate view filename based on current loader settings.
63
     *
64
     * @param string $path
65
     *
66
     * @return array [namespace, name]
67
     *
68
     * @throws LoaderException
69
     */
70
    protected function locateView(string $path): array
71
    {
72
        //Making sure requested name is valid
73
        $this->validatePath($path);
74
75
        list($namespace, $filename) = $this->parsePath($path);
76
77
        foreach ($this->namespaces[$namespace] as $directory) {
78
            //Seeking for view filename
79
            if ($this->files->exists($directory . $filename)) {
80
                return [
81
                    self::VIEW_FILENAME  => $directory . $filename,
82
                    self::VIEW_NAMESPACE => $namespace,
83
                    self::VIEW_NAME      => $this->fetchName($filename)
84
                ];
85
            }
86
        }
87
88
        throw new LoaderException("Unable to locate view '{$filename}' in namespace '{$namespace}'");
89
    }
90
91
    /**
92
     * Fetch namespace and filename from view name or force default values.
93
     *
94
     * @param string $path
95
     *
96
     * @return array
97
     *
98
     * @throws LoaderException
99
     */
100
    protected function parsePath(string $path): array
101
    {
102
        //Cutting extra symbols (see Twig)
103
        $filename = preg_replace('#/{2,}#', '/', str_replace('\\', '/', (string)$path));
104
105
        if (strpos($filename, '.') === false) {
106
            //Forcing default extension
107
            $filename .= '.' . static::FILE_EXTENSION;
108
        }
109
110
        if (strpos($filename, ':') !== false) {
111
            return explode(':', $filename);
112
        }
113
114
        //Let's force default namespace
115
        return [static::DEFAULT_NAMESPACE, $filename];
116
    }
117
118
    /**
119
     * Make sure view filename is OK. Same as in twig.
120
     *
121
     * @param string $path
122
     *
123
     * @throws LoaderException
124
     */
125
    protected function validatePath(string $path)
126
    {
127
        if (false !== strpos($path, "\0")) {
128
            throw new LoaderException('A template name cannot contain NUL bytes');
129
        }
130
131
        $path = ltrim($path, '/');
132
        $parts = explode('/', $path);
133
        $level = 0;
134
        foreach ($parts as $part) {
135
            if ('..' === $part) {
136
                --$level;
137
            } elseif ('.' !== $part) {
138
                ++$level;
139
            }
140
141
            if ($level < 0) {
142
                throw new LoaderException(sprintf(
143
                    'Looks like you try to load a template outside configured directories (%s)',
144
                    $path
145
                ));
146
            }
147
        }
148
    }
149
150
    /**
151
     * Resolve view name based on filename (depends on current extension settings).
152
     *
153
     * @param string $filename
154
     *
155
     * @return string
156
     */
157
    protected function fetchName(string $filename): string
158
    {
159
        return substr($filename, 0, -1 * (1 + strlen(static::FILE_EXTENSION)));
160
    }
161
}