Passed
Push — main ( 82104f...ee6fb2 )
by Thierry
05:16
created

CodeGenerator::renderCodes()   C

Complexity

Conditions 9
Paths 256

Size

Total Lines 40
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 22
nc 256
nop 2
dl 0
loc 40
rs 6.5222
c 1
b 0
f 0
1
<?php
2
3
/**
4
 * CodeGenerator.php
5
 *
6
 * Generate HTML, CSS and Javascript code for Jaxon.
7
 *
8
 * @package jaxon-core
9
 * @author Thierry Feuzeu <[email protected]>
10
 * @copyright 2016 Thierry Feuzeu <[email protected]>
11
 * @license https://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
12
 * @link https://github.com/jaxon-php/jaxon-core
13
 */
14
15
namespace Jaxon\Plugin\Code;
16
17
use Jaxon\App\Config\ConfigManager;
18
use Jaxon\Di\Container;
19
use Jaxon\Utils\Http\UriException;
20
use Jaxon\Utils\Template\TemplateEngine;
21
22
use function array_map;
23
use function count;
24
use function implode;
25
use function md5;
26
use function usort;
27
28
class CodeGenerator
29
{
30
    /**
31
     * @var AssetManager|null
32
     */
33
    private $xAssetManager = null;
34
35
    /**
36
     * @var array<string>
37
     */
38
    protected $aCodeGenerators = [];
39
40
    /**
41
     * @var Code|null
42
     */
43
    protected $xCode = null;
44
45
    /**
46
     * @var string
47
     */
48
    private const SEPARATOR = "\n\n";
49
50
    /**
51
     * @var int
52
     */
53
    private const TYPE_CSS_CODE = 1;
54
55
    /**
56
     * @var int
57
     */
58
    private const TYPE_JS_CODE = 2;
59
60
    /**
61
     * @var int
62
     */
63
    private const TYPE_CODE = 3;
64
65
    /**
66
     * The constructor
67
     *
68
     * @param string $sVersion
69
     * @param Container $di
70
     * @param TemplateEngine $xTemplateEngine
71
     * @param ConfigManager $xConfigManager
72
     */
73
    public function __construct(private string $sVersion, private Container $di,
74
        private TemplateEngine $xTemplateEngine, private ConfigManager $xConfigManager)
75
    {
76
        // The Jaxon library config is on top.
77
        $this->addJsCodeGenerator(ConfigScriptGenerator::class, 0);
78
        // The ready script comes after.
79
        $this->addJsCodeGenerator(ReadyScriptGenerator::class, 2);
80
    }
81
82
    /**
83
     * @return AssetManager
84
     */
85
    private function asset(): AssetManager
86
    {
87
        // Cannot be injected because of dependency loop.
88
        return $this->xAssetManager ??= $this->di->getAssetManager();
89
    }
90
91
    /**
92
     * Add a code generator to the list
93
     *
94
     * @param string $sClassName    The code generator class
95
     * @param int $nPriority    The desired priority, used to order the plugins
96
     *
97
     * @return void
98
     */
99
    public function addCssCodeGenerator(string $sClassName, int $nPriority): void
100
    {
101
        $this->aCodeGenerators[] = [$sClassName, self::TYPE_CSS_CODE, $nPriority];
102
    }
103
104
    /**
105
     * Add a code generator to the list
106
     *
107
     * @param string $sClassName    The code generator class
108
     * @param int $nPriority    The desired priority, used to order the plugins
109
     *
110
     * @return void
111
     */
112
    public function addJsCodeGenerator(string $sClassName, int $nPriority): void
113
    {
114
        $this->aCodeGenerators[] = [$sClassName, self::TYPE_JS_CODE, $nPriority];
115
    }
116
117
    /**
118
     * Add a code generator to the list
119
     *
120
     * @param string $sClassName    The code generator class
121
     * @param int $nPriority    The desired priority, used to order the plugins
122
     *
123
     * @return void
124
     */
125
    public function addCodeGenerator(string $sClassName, int $nPriority): void
126
    {
127
        $this->aCodeGenerators[] = [$sClassName, self::TYPE_CODE, $nPriority];
128
    }
129
130
    /**
131
     * Generate a hash for all the javascript code generated by the library
132
     *
133
     * @return string
134
     */
135
    public function getHash(): string
136
    {
137
        $aHashes = array_map(fn(array $aGenerator) =>
138
            $this->di->g($aGenerator[0])->getHash(), $this->aCodeGenerators);
139
        $aHashes[] = $this->sVersion;
140
        return md5(implode('', $aHashes));
141
    }
142
143
    /**
144
     * Render a template in the 'plugins' subdir
145
     *
146
     * @param string $sTemplate    The template filename
147
     * @param array $aVars    The template variables
148
     *
149
     * @return string
150
     */
151
    private function renderTemplate(string $sTemplate, array $aVars = []): string
152
    {
153
        return $this->xTemplateEngine->render("jaxon::plugins/$sTemplate", $aVars);
154
    }
155
156
    /**
157
     * @param array $aCssFile
158
     *
159
     * @return string
160
     */
161
    private function renderCssTag(array $aCssFile): string
162
    {
163
        return $this->renderTemplate('include.css', [
164
            'sUrl' => $aCssFile['uri'],
165
            'sOptions' => $this->asset()->makeFileOptions($aCssFile),
166
        ]);
167
    }
168
169
    /**
170
     * @param array $aJsFile
171
     *
172
     * @return string
173
     */
174
    private function renderJsTag(array $aJsFile): string
175
    {
176
        return $this->renderTemplate('include.js', [
177
            'sUrl' => $aJsFile['uri'],
178
            'sOptions' => $this->asset()->makeFileOptions($aJsFile),
179
        ]);
180
    }
181
182
    /**
183
     * Render a template in the 'plugins' subdir
184
     *
185
     * @param string $sTemplate    The template filename
186
     * @param array $aVars    The template variables
187
     *
188
     * @return string
189
     */
190
    private function render(string $sTemplate, array $aVars = []): string
191
    {
192
        $aVars['sJsOptions'] = $this->asset()->getJsOptions();
193
        return $this->xTemplateEngine->render("jaxon::plugins/$sTemplate", $aVars);
194
    }
195
196
    /**
197
     * Generate the Jaxon CSS ans js codes
198
     *
199
     * @return void
200
     * @throws UriException
201
     */
202
    private function generateCodes(): void
203
    {
204
        if($this->xCode !== null)
205
        {
206
            return;
207
        }
208
209
        $this->xCode = new Code();
210
        // We need the library to have been bootstrapped.
211
        $this->di->getBootstrap()->onBoot();
212
213
        // Sort the code generators by ascending priority
214
        usort($this->aCodeGenerators, fn(array $aGen1, array $aGen2) => $aGen1[2] - $aGen2[2]);
215
216
        // Load the Jaxon lib js files, before the other libs js files.
217
        $this->xCode->addJsTag(trim($this->renderTemplate('includes.js', [
218
            'aUrls' => $this->asset()->getJsLibFiles(),
219
            'sOptions' => $this->asset()->getJsOptions(),
220
        ])));
221
222
        $renderJsTag = fn(array $aJsFile) => $this->renderJsTag($aJsFile);
223
        $renderCssTag = fn(array $aCssFile) => $this->renderCssTag($aCssFile);
224
        foreach($this->aCodeGenerators as [$sClassName, $nType])
225
        {
226
            $xGenerator = $this->di->g($sClassName);
227
            $bIncludeAssets = $this->asset()->shallIncludeAssets($xGenerator);
228
229
            switch($nType)
230
            {
231
            case self::TYPE_CSS_CODE:
232
                $this->xCode->mergeCssCode($xGenerator, $renderCssTag, $bIncludeAssets);
233
                break;
234
            case self::TYPE_JS_CODE:
235
                $this->xCode->mergeJsCode($xGenerator, $renderJsTag, $bIncludeAssets);
236
                break;
237
            case self::TYPE_CODE:
238
                $this->xCode->mergeCode($xGenerator, $bIncludeAssets);
239
            }
240
        }
241
    }
242
243
    /**
244
     * Get the HTML tags to include Jaxon CSS code and files into the page
245
     *
246
     * @return string
247
     * @throws UriException
248
     */
249
    public function getCss(): string
250
    {
251
        $this->generateCodes();
252
253
        $aTags = $this->xCode->cssTags();
254
        $aCodes = $this->xCode->cssCodes();
255
        if(count($aCodes) === 0)
256
        {
257
            return implode(self::SEPARATOR, $aTags);
258
        }
259
260
        $cGetHash = fn() => $this->getHash();
261
        $cGetCode = fn() => implode(self::SEPARATOR, $aCodes);
262
        $sUrl = $this->asset()->createCssFiles($cGetHash, $cGetCode);
263
        // Wrap the js code into the corresponding HTML tag.
264
        $aTags[] = $sUrl !== '' ?
265
            // The generated code is saved to a file. Render the corresponding URL.
266
            $this->renderTemplate('include.css', [
267
                'sUrl' => $sUrl,
268
                'sOptions' => $this->asset()->getCssOptions(),
269
            ]) :
270
            // Otherwise, render the code.
271
            $this->renderTemplate('wrapper.css', [
272
                'sCode' => $cGetCode(),
273
                'sOptions' => $this->asset()->getCssOptions(),
274
            ]);
275
276
        return implode(self::SEPARATOR, $aTags);
277
    }
278
279
    /**
280
     * Get the HTML tags to include Jaxon javascript files into the page
281
     *
282
     * @return string
283
     * @throws UriException
284
     */
285
    public function getJs(): string
286
    {
287
        $this->generateCodes();
288
289
        return implode(self::SEPARATOR, $this->xCode->jsTags());
290
    }
291
292
    /**
293
     * Get the javascript code to be sent to the browser
294
     *
295
     * @param bool $bIncludeJs Also get the JS files
296
     * @param bool $bIncludeCss Also get the CSS files
297
     *
298
     * @return string
299
     * @throws UriException
300
     */
301
    public function getScript(bool $bIncludeJs, bool $bIncludeCss): string
302
    {
303
        $this->generateCodes();
304
305
        $aTags = [];
306
        if($bIncludeCss)
307
        {
308
            $aTags[] = $this->getCss();
309
        }
310
        if($bIncludeJs)
311
        {
312
            $aTags[] = $this->getJs();
313
        }
314
315
        $aJsOptions = $this->asset()->getJsOptions();
316
317
        $aJsCodes = $this->xCode->jsCodes();
318
        if(count($this->xCode->jsCodes()) > 0)
319
        {
320
            $cGetHash = fn() => $this->getHash();
321
            $cGetCode = fn() => implode(self::SEPARATOR, $aJsCodes);
322
            $sUrl = $this->asset()->createJsFiles($cGetHash, $cGetCode);
323
            // Wrap the js code into the corresponding HTML tag.
324
            $aTags[] = $sUrl !== '' ?
325
                // The generated code is saved to a file. Render the corresponding URL.
326
                $this->render('include.js', [
327
                    'sUrl' => $sUrl,
328
                    'sOptions' => $aJsOptions,
329
                ]) :
330
                // Otherwise, render the code.
331
                $this->render('wrapper.js', [
332
                    'sCode' => $cGetCode(),
333
                    'sOptions' => $aJsOptions,
334
                ]);
335
        }
336
337
        return implode(self::SEPARATOR, $aTags);
338
    }
339
}
340