Passed
Push — master ( 72c793...7f699f )
by Kirill
03:22
created

ViewManager   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 179
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 47
c 1
b 0
f 0
dl 0
loc 179
rs 10
wmc 22

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 18 4
A reset() 0 12 3
A render() 0 3 1
A get() 0 13 4
A addDependency() 0 3 1
A getContext() 0 3 1
A findEngine() 0 9 3
A getEngines() 0 3 1
A compile() 0 13 3
A addEngine() 0 9 1
1
<?php
2
3
/**
4
 * Spiral Framework.
5
 *
6
 * @license   MIT
7
 * @author    Anton Titov (Wolfy-J)
8
 */
9
10
declare(strict_types=1);
11
12
namespace Spiral\Views;
13
14
use Spiral\Core\FactoryInterface;
15
use Spiral\Views\Config\ViewsConfig;
16
use Spiral\Views\Exception\ViewException;
17
18
final class ViewManager implements ViewsInterface
19
{
20
    /** @var ViewsConfig */
21
    private $config;
22
23
    /** @var ViewContext */
24
    private $context;
25
26
    /** @var LoaderInterface */
27
    private $loader;
28
29
    /** @var ViewCache|null */
30
    private $cache;
31
32
    /** @var EngineInterface[] */
33
    private $engines;
34
35
    /**
36
     * @param ViewsConfig      $config
37
     * @param FactoryInterface $factory
38
     */
39
    public function __construct(ViewsConfig $config, FactoryInterface $factory)
40
    {
41
        $this->config = $config;
42
        $this->context = new ViewContext();
43
        $this->loader = $factory->make(ViewLoader::class, [
44
            'namespaces' => $config->getNamespaces()
45
        ]);
46
47
        foreach ($this->config->getDependencies() as $dependency) {
48
            $this->addDependency($dependency->resolve($factory));
49
        }
50
51
        foreach ($this->config->getEngines() as $engine) {
52
            $this->addEngine($engine->resolve($factory));
53
        }
54
55
        if ($this->config->isCacheEnabled()) {
56
            $this->cache = new ViewCache();
57
        }
58
    }
59
60
    /**
61
     * Attach new view context dependency.
62
     *
63
     * @param DependencyInterface $dependency
64
     */
65
    public function addDependency(DependencyInterface $dependency): void
66
    {
67
        $this->context = $this->context->withDependency($dependency);
68
    }
69
70
    /**
71
     * @return ContextInterface
72
     */
73
    public function getContext(): ContextInterface
74
    {
75
        return $this->context;
76
    }
77
78
    /**
79
     * Attach new view engine.
80
     *
81
     * @param EngineInterface $engine
82
     */
83
    public function addEngine(EngineInterface $engine): void
84
    {
85
        $this->engines[] = $engine->withLoader($this->loader);
86
87
        uasort($this->engines, function (EngineInterface $a, EngineInterface $b) {
88
            return strcmp($a->getLoader()->getExtension(), $b->getLoader()->getExtension());
89
        });
90
91
        $this->engines = array_values($this->engines);
92
    }
93
94
    /**
95
     * Get all associated view engines.
96
     *
97
     * @return EngineInterface[]
98
     */
99
    public function getEngines(): array
100
    {
101
        return $this->engines;
102
    }
103
104
    /**
105
     * Compile one of multiple cache versions for a given view path.
106
     *
107
     * @param string $path
108
     *
109
     * @throws ViewException
110
     */
111
    public function compile(string $path): void
112
    {
113
        if ($this->cache !== null) {
114
            $this->cache->resetPath($path);
115
        }
116
117
        $engine = $this->findEngine($path);
118
119
        // Rotate all possible context variants and warm up cache
120
        $generator = new ContextGenerator($this->context);
121
        foreach ($generator->generate() as $context) {
122
            $engine->reset($path, $context);
123
            $engine->compile($path, $context);
124
        }
125
    }
126
127
    /**
128
     * Reset view cache for a given path. Identical to compile method by effect but faster.
129
     *
130
     * @param string $path
131
     */
132
    public function reset(string $path): void
133
    {
134
        if ($this->cache !== null) {
135
            $this->cache->resetPath($path);
136
        }
137
138
        $engine = $this->findEngine($path);
139
140
        // Rotate all possible context variants and warm up cache
141
        $generator = new ContextGenerator($this->context);
142
        foreach ($generator->generate() as $context) {
143
            $engine->reset($path, $context);
144
        }
145
    }
146
147
    /**
148
     * Get view from one of the associated engines.
149
     *
150
     * @param string $path
151
     * @return ViewInterface
152
     *
153
     * @throws ViewException
154
     */
155
    public function get(string $path): ViewInterface
156
    {
157
        if ($this->cache !== null && $this->cache->has($this->context, $path)) {
158
            return $this->cache->get($this->context, $path);
159
        }
160
161
        $view = $this->findEngine($path)->get($path, $this->context);
162
163
        if ($this->cache !== null) {
164
            $this->cache->set($this->context, $path, $view);
165
        }
166
167
        return $view;
168
    }
169
170
    /**
171
     * @param string $path
172
     * @param array  $data
173
     * @return string
174
     *
175
     * @throws ViewException
176
     */
177
    public function render(string $path, array $data = []): string
178
    {
179
        return $this->get($path)->render($data);
180
    }
181
182
    /**
183
     * @param string $path
184
     * @return EngineInterface
185
     *
186
     * @throws ViewException
187
     */
188
    protected function findEngine(string $path): EngineInterface
189
    {
190
        foreach ($this->engines as $engine) {
191
            if ($engine->getLoader()->exists($path)) {
192
                return $engine;
193
            }
194
        }
195
196
        throw new ViewException("Unable to detect view engine for `{$path}`.");
197
    }
198
}
199