ViewRenderer   A
last analyzed

Complexity

Total Complexity 25

Size/Duplication

Total Lines 289
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 65
c 1
b 0
f 0
dl 0
loc 289
rs 10
wmc 25

13 Methods

Rating   Name   Duplication   Size   Complexity  
A render() 0 27 3
A shareValues() 0 7 2
A share() 0 4 1
A addRenderer() 0 15 2
A set() 0 4 1
A store() 0 7 2
A addNamespace() 0 8 1
A setDefaultNamespace() 0 3 1
A getNamespaceRenderer() 0 8 2
A setDefaultRenderer() 0 5 1
A __construct() 0 4 1
B addNamespaces() 0 28 7
A getRenderer() 0 4 1
1
<?php
2
3
namespace Jaxon\App\View;
4
5
use Jaxon\Di\Container;
6
use Jaxon\Utils\Config\Config;
7
8
use Closure;
9
10
use function array_filter;
11
use function array_merge;
12
use function is_array;
13
use function rtrim;
14
use function strlen;
15
use function strrpos;
16
use function substr;
17
18
class ViewRenderer
19
{
20
    /**
21
     * @var Container
22
     */
23
    protected $di;
24
25
    /**
26
     * The view data store
27
     *
28
     * @var Store
29
     */
30
    protected $xStore = null;
31
32
    /**
33
     * The view data store
34
     *
35
     * @var Store
36
     */
37
    protected $xEmptyStore = null;
38
39
    /**
40
     * The view namespaces
41
     *
42
     * @var array
43
     */
44
    protected $aNamespaces = [];
45
46
    /**
47
     * The default namespace
48
     *
49
     * @var string
50
     */
51
    protected $sDefaultNamespace = 'jaxon';
52
53
    /**
54
     * The view global data
55
     *
56
     * @var array
57
     */
58
    protected $aViewData = [];
59
60
    /**
61
     * The class constructor
62
     *
63
     * @param Container $di
64
     */
65
    public function __construct(Container $di)
66
    {
67
        $this->di = $di;
68
        $this->xEmptyStore = new Store();
69
    }
70
71
    /**
72
     * Add a view namespace, and set the corresponding renderer.
73
     *
74
     * @param string $sNamespace    The namespace name
75
     * @param string $sDirectory    The namespace directory
76
     * @param string $sExtension    The extension to append to template names
77
     * @param string $sRenderer    The corresponding renderer name
78
     *
79
     * @return void
80
     */
81
    public function addNamespace(string $sNamespace, string $sDirectory, string $sExtension, string $sRenderer)
82
    {
83
        $aNamespace = [
84
            'directory' => $sDirectory,
85
            'extension' => $sExtension,
86
            'renderer' => $sRenderer,
87
        ];
88
        $this->aNamespaces[$sNamespace] = $aNamespace;
89
    }
90
91
    /**
92
     * Set the view namespaces.
93
     *
94
     * @param Config $xAppConfig    The config options provided in the library
95
     * @param Config|null $xUserConfig    The config options provided in the app section of the global config file.
96
     *
97
     * @return void
98
     */
99
    public function addNamespaces(Config $xAppConfig, ?Config $xUserConfig = null)
100
    {
101
        if(empty($aNamespaces = $xAppConfig->getOptionNames('views')))
102
        {
103
            return;
104
        }
105
        $sPackage = $xAppConfig->getOption('package', '');
106
        foreach($aNamespaces as $sNamespace => $sOption)
107
        {
108
            // Save the namespace
109
            $aNamespace = $xAppConfig->getOption($sOption);
110
            $aNamespace['package'] = $sPackage;
111
            if(!isset($aNamespace['renderer']))
112
            {
113
                $aNamespace['renderer'] = 'jaxon'; // 'jaxon' is the default renderer.
114
            }
115
116
            // If the lib config has defined a template option, then its value must be
117
            // read from the app config.
118
            if($xUserConfig !== null && isset($aNamespace['template']) && is_array($aNamespace['template']))
119
            {
120
                $sTemplateOption = $xAppConfig->getOption($sOption . '.template.option');
121
                $sTemplateDefault = $xAppConfig->getOption($sOption . '.template.default');
122
                $sTemplate = $xUserConfig->getOption($sTemplateOption, $sTemplateDefault);
0 ignored issues
show
Bug introduced by
It seems like $sTemplateOption can also be of type null; however, parameter $sName of Jaxon\Utils\Config\Config::getOption() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

122
                $sTemplate = $xUserConfig->getOption(/** @scrutinizer ignore-type */ $sTemplateOption, $sTemplateDefault);
Loading history...
123
                $aNamespace['directory'] = rtrim($aNamespace['directory'], '/') . '/' . $sTemplate;
124
            }
125
126
            $this->aNamespaces[$sNamespace] = $aNamespace;
127
        }
128
    }
129
130
    /**
131
     * Get the view renderer
132
     *
133
     * @param string $sId    The unique identifier of the view renderer
134
     *
135
     * @return ViewInterface
136
     */
137
    public function getRenderer(string $sId): ViewInterface
138
    {
139
        // Return the view renderer with the given id
140
        return $this->di->g('jaxon.app.view.' . $sId);
141
    }
142
143
    /**
144
     * Add a view renderer with an id
145
     *
146
     * @param string $sId    The unique identifier of the view renderer
147
     * @param Closure $xClosure    A closure to create the view instance
148
     *
149
     * @return void
150
     */
151
    public function addRenderer(string $sId, Closure $xClosure)
152
    {
153
        // Return the initialized view renderer
154
        $this->di->set('jaxon.app.view.' . $sId, function($di) use($sId, $xClosure) {
155
            // Get the defined renderer
156
            $xRenderer = $xClosure($di);
157
            // Init the renderer with the template namespaces
158
            $aNamespaces = array_filter($this->aNamespaces, function($aNamespace) use($sId) {
159
                return $aNamespace['renderer'] === $sId;
160
            });
161
            foreach($aNamespaces as $sNamespace => $aNamespace)
162
            {
163
                $xRenderer->addNamespace($sNamespace, $aNamespace['directory'], $aNamespace['extension']);
164
            }
165
            return $xRenderer;
166
        });
167
    }
168
169
    /**
170
     * Add a view renderer with an id
171
     *
172
     * @param string $sId    The unique identifier of the view renderer
173
     * @param string $sExtension    The extension to append to template names
174
     * @param Closure $xClosure    A closure to create the view instance
175
     *
176
     * @return void
177
     */
178
    public function setDefaultRenderer(string $sId, string $sExtension, Closure $xClosure)
179
    {
180
        $this->setDefaultNamespace($sId);
181
        $this->addNamespace($sId, '', $sExtension, $sId);
182
        $this->addRenderer($sId, $xClosure);
183
    }
184
185
    /**
186
     * Get the view renderer for a given namespace
187
     *
188
     * @param string $sNamespace    The namespace name
189
     *
190
     * @return ViewInterface|null
191
     */
192
    public function getNamespaceRenderer(string $sNamespace): ?ViewInterface
193
    {
194
        if(!isset($this->aNamespaces[$sNamespace]))
195
        {
196
            return null;
197
        }
198
        // Return the view renderer with the configured id
199
        return $this->getRenderer($this->aNamespaces[$sNamespace]['renderer']);
200
    }
201
202
    /**
203
     * Set the default namespace
204
     *
205
     * @param string $sDefaultNamespace
206
     */
207
    public function setDefaultNamespace(string $sDefaultNamespace): void
208
    {
209
        $this->sDefaultNamespace = $sDefaultNamespace;
210
    }
211
212
    /**
213
     * Get the current store or create a new store
214
     *
215
     * @return Store
216
     */
217
    protected function store(): Store
218
    {
219
        if(!$this->xStore)
220
        {
221
            $this->xStore = new Store();
222
        }
223
        return $this->xStore;
224
    }
225
226
    /**
227
     * Make a piece of data available for the rendered view
228
     *
229
     * @param string $sName    The data name
230
     * @param mixed $xValue    The data value
231
     *
232
     * @return ViewRenderer
233
     */
234
    public function set(string $sName, $xValue): ViewRenderer
235
    {
236
        $this->store()->with($sName, $xValue);
237
        return $this;
238
    }
239
240
    /**
241
     * Make a piece of data available for all views
242
     *
243
     * @param string $sName    The data name
244
     * @param mixed $xValue    The data value
245
     *
246
     * @return ViewRenderer
247
     */
248
    public function share(string $sName, $xValue): ViewRenderer
249
    {
250
        $this->aViewData[$sName] = $xValue;
251
        return $this;
252
    }
253
254
    /**
255
     * Make an array of data available for all views
256
     *
257
     * @param array $aValues    The data values
258
     *
259
     * @return ViewRenderer
260
     */
261
    public function shareValues(array $aValues): ViewRenderer
262
    {
263
        foreach($aValues as $sName => $xValue)
264
        {
265
            $this->share($sName, $xValue);
266
        }
267
        return $this;
268
    }
269
270
    /**
271
     * Render a view using a store
272
     *
273
     * The store returned by this function will later be used with the make() method to render the view.
274
     *
275
     * @param string $sViewName    The view name
276
     * @param array $aViewData    The view data
277
     *
278
     * @return Store   A store populated with the view data
279
     */
280
    public function render(string $sViewName, array $aViewData = []): Store
281
    {
282
        $xStore = $this->store();
283
        // Get the default view namespace
284
        $sNamespace = $this->sDefaultNamespace;
285
        // Get the namespace from the view name
286
        $nSeparatorPosition = strrpos($sViewName, '::');
287
        if($nSeparatorPosition !== false)
288
        {
289
            $sNamespace = substr($sViewName, 0, $nSeparatorPosition);
290
            $sViewName = substr($sViewName, $nSeparatorPosition + 2);
291
        }
292
293
        $xRenderer = $this->getNamespaceRenderer($sNamespace);
294
        if(!$xRenderer)
295
        {
296
            // Cannot render a view if there's no renderer corresponding to the namespace.
297
            return $this->xEmptyStore;
298
        }
299
300
        $xStore->setData(array_merge($this->aViewData, $aViewData))
301
            ->setView($xRenderer, $sNamespace, $sViewName);
302
303
        // Set the store to null so a new store will be created for the next view.
304
        $this->xStore = null;
305
        // Return the store
306
        return $xStore;
307
    }
308
}
309