Passed
Push — v3 ( 94080a...a3db2e )
by Andrew
31:21 queued 18:20
created

Manifest   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 470
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 173
dl 0
loc 470
ccs 0
cts 244
cp 0
rs 6.96
c 1
b 0
f 0
wmc 53

17 Methods

Rating   Name   Duplication   Size   Complexity  
A getModuleEntry() 0 23 4
A getFile() 0 3 1
A invalidateCaches() 0 5 1
A getFileContents() 0 39 5
B getModule() 0 30 8
A getCssModuleTags() 0 15 3
A getFileFromUri() 0 17 5
A getCssRelPreloadPolyfill() 0 3 1
A getJsModuleTags() 0 21 5
A getCssInlineTags() 0 9 2
A getJsonFile() 0 3 1
B getManifestFile() 0 33 6
A combinePaths() 0 24 3
A getFileFromManifest() 0 17 3
A getSafariNomoduleFix() 0 3 1
A jsonFileDecode() 0 3 1
A reportError() 0 7 3

How to fix   Complexity   

Complex Class

Complex classes like Manifest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Manifest, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Twigpack plugin for Craft CMS 3.x
4
 *
5
 * Twigpack is the conduit between Twig and webpack, with manifest.json &
6
 * webpack-dev-server HMR support
7
 *
8
 * @link      https://nystudio107.com/
9
 * @copyright Copyright (c) 2018 nystudio107
10
 */
11
12
namespace nystudio107\seomatic\helpers;
13
14
use Craft;
15
use craft\helpers\Json as JsonHelper;
16
use craft\helpers\UrlHelper as CraftUrlHelper;
17
18
use yii\base\Exception;
19
use yii\caching\TagDependency;
20
use yii\web\NotFoundHttpException;
21
22
/**
23
 * @author    nystudio107
0 ignored issues
show
Coding Style introduced by
The tag in position 1 should be the @package tag
Loading history...
Coding Style introduced by
Content of the @author tag must be in the form "Display Name <[email protected]>"
Loading history...
24
 * @package   Twigpack
25
 * @since     1.0.0
26
 */
27
class Manifest
28
{
29
    // Constants
30
    // =========================================================================
31
32
    const CACHE_KEY = 'twigpack-seomatic';
33
    const CACHE_TAG = 'twigpack-seomatic';
34
35
    const DEVMODE_CACHE_DURATION = 1;
36
37
    const SUPPRESS_ERRORS_FOR_MODULES = [
38
        'styles.js',
39
        'commons.js',
40
        'vendors.js',
41
        'vendors.css',
42
    ];
43
44
    // Protected Static Properties
45
    // =========================================================================
46
47
    /**
48
     * @var array
49
     */
50
    protected static $files;
51
52
    /**
53
     * @var bool
54
     */
55
    protected static $isHot = false;
56
57
    // Public Static Methods
58
    // =========================================================================
59
60
    /**
61
     * @param array  $config
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
62
     * @param string $moduleName
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
63
     * @param bool   $async
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
64
     *
65
     * @return string
66
     * @throws NotFoundHttpException
67
     */
68
    public static function getCssModuleTags(array $config, string $moduleName, bool $async): string
69
    {
70
        $legacyModule = self::getModule($config, $moduleName, 'legacy', true);
71
        if ($legacyModule === null) {
0 ignored issues
show
introduced by
The condition $legacyModule === null is always false.
Loading history...
72
            return '';
73
        }
74
        $lines = [];
75
        if ($async) {
76
            $lines[] = "<link rel=\"preload\" href=\"{$legacyModule}\" as=\"style\" onload=\"this.onload=null;this.rel='stylesheet'\" />";
77
            $lines[] = "<noscript><link rel=\"stylesheet\" href=\"{$legacyModule}\"></noscript>";
78
        } else {
79
            $lines[] = "<link rel=\"stylesheet\" href=\"{$legacyModule}\" />";
80
        }
81
82
        return implode("\r\n", $lines);
83
    }
84
85
    /**
86
     * @param string $path
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
87
     *
88
     * @return string
89
     */
90
    public static function getCssInlineTags(string $path): string
91
    {
92
        $result = self::getFile($path);
93
        if ($result) {
94
            $result = "<style>\r\n".$result."</style>\r\n";
95
            return $result;
96
        }
97
98
        return '';
99
    }
100
101
    /**
102
     * Returns the uglified loadCSS rel=preload Polyfill as per:
103
     * https://github.com/filamentgroup/loadCSS#how-to-use-loadcss-recommended-example
104
     *
105
     * @return string
106
     */
107
    public static function getCssRelPreloadPolyfill(): string
108
    {
109
        return <<<EOT
110
<script>
111
/*! loadCSS. [c]2017 Filament Group, Inc. MIT License */
112
!function(t){"use strict";t.loadCSS||(t.loadCSS=function(){});var e=loadCSS.relpreload={};if(e.support=function(){var e;try{e=t.document.createElement("link").relList.supports("preload")}catch(t){e=!1}return function(){return e}}(),e.bindMediaToggle=function(t){var e=t.media||"all";function a(){t.media=e}t.addEventListener?t.addEventListener("load",a):t.attachEvent&&t.attachEvent("onload",a),setTimeout(function(){t.rel="stylesheet",t.media="only x"}),setTimeout(a,3e3)},e.poly=function(){if(!e.support())for(var a=t.document.getElementsByTagName("link"),n=0;n<a.length;n++){var o=a[n];"preload"!==o.rel||"style"!==o.getAttribute("as")||o.getAttribute("data-loadcss")||(o.setAttribute("data-loadcss",!0),e.bindMediaToggle(o))}},!e.support()){e.poly();var a=t.setInterval(e.poly,500);t.addEventListener?t.addEventListener("load",function(){e.poly(),t.clearInterval(a)}):t.attachEvent&&t.attachEvent("onload",function(){e.poly(),t.clearInterval(a)})}"undefined"!=typeof exports?exports.loadCSS=loadCSS:t.loadCSS=loadCSS}("undefined"!=typeof global?global:this);
113
</script>
114
EOT;
115
    }
116
117
    /**
118
     * @param array  $config
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
119
     * @param string $moduleName
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
120
     * @param bool   $async
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
121
     *
122
     * @return null|string
123
     * @throws NotFoundHttpException
124
     */
125
    public static function getJsModuleTags(array $config, string $moduleName, bool $async)
126
    {
127
        $legacyModule = self::getModule($config, $moduleName, 'legacy');
128
        if ($legacyModule === null) {
0 ignored issues
show
introduced by
The condition $legacyModule === null is always false.
Loading history...
129
            return '';
130
        }
131
        if ($async) {
132
            $modernModule = self::getModule($config, $moduleName, 'modern');
133
            if ($modernModule === null) {
0 ignored issues
show
introduced by
The condition $modernModule === null is always false.
Loading history...
134
                return '';
135
            }
136
        }
137
        $lines = [];
138
        if ($async) {
139
            $lines[] = "<script type=\"module\" src=\"{$modernModule}\"></script>";
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $modernModule does not seem to be defined for all execution paths leading up to this point.
Loading history...
140
            $lines[] = "<script nomodule src=\"{$legacyModule}\"></script>";
141
        } else {
142
            $lines[] = "<script src=\"{$legacyModule}\"></script>";
143
        }
144
145
        return implode("\r\n", $lines);
146
    }
147
148
    /**
149
     * Safari 10.1 supports modules, but does not support the `nomodule`
150
     * attribute - it will load <script nomodule> anyway. This snippet solve
151
     * this problem, but only for script tags that load external code, e.g.:
152
     * <script nomodule src="nomodule.js"></script>
153
     *
154
     * Again: this will **not* # prevent inline script, e.g.:
155
     * <script nomodule>alert('no modules');</script>.
156
     *
157
     * This workaround is possible because Safari supports the non-standard
158
     * 'beforeload' event. This allows us to trap the module and nomodule load.
159
     *
160
     * Note also that `nomodule` is supported in later versions of Safari -
161
     * it's just 10.1 that omits this attribute.
162
     *
163
     * c.f.: https://gist.github.com/samthor/64b114e4a4f539915a95b91ffd340acc
164
     *
165
     * @return string
166
     */
167
    public static function getSafariNomoduleFix(): string
168
    {
169
        return <<<EOT
170
<script>
171
!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()},!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();
172
</script>
173
EOT;
174
    }
175
176
    /**
177
     * Return the URI to a module
178
     *
179
     * @param array  $config
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
180
     * @param string $moduleName
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
181
     * @param string $type
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
182
     * @param bool   $soft
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
183
     *
184
     * @return null|string
185
     * @throws NotFoundHttpException
186
     */
187
    public static function getModule(array $config, string $moduleName, string $type = 'modern', bool $soft = false)
188
    {
189
        // Get the module entry
190
        $module = self::getModuleEntry($config, $moduleName, $type, $soft);
191
        if ($module !== null) {
192
            $prefix = self::$isHot
193
                ? $config['devServer']['publicPath']
194
                : $config['server']['publicPath'];
195
            // If the module isn't a full URL, prefix it
196
            if (!CraftUrlHelper::isAbsoluteUrl($module)) {
197
                $module = self::combinePaths($prefix, $module);
198
            }
199
            // Resolve any aliases
200
            $alias = Craft::getAlias($module, false);
201
            if ($alias) {
202
                $module = $alias;
203
            }
204
            // Make sure it's a full URL
205
            if (!CraftUrlHelper::isAbsoluteUrl($module) && !is_file($module)) {
0 ignored issues
show
Bug introduced by
It seems like $module can also be of type true; however, parameter $url of craft\helpers\UrlHelper::isAbsoluteUrl() 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

205
            if (!CraftUrlHelper::isAbsoluteUrl(/** @scrutinizer ignore-type */ $module) && !is_file($module)) {
Loading history...
Bug introduced by
It seems like $module can also be of type true; however, parameter $filename of is_file() 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

205
            if (!CraftUrlHelper::isAbsoluteUrl($module) && !is_file(/** @scrutinizer ignore-type */ $module)) {
Loading history...
206
                try {
207
                    $module = CraftUrlHelper::siteUrl($module);
0 ignored issues
show
Bug introduced by
It seems like $module can also be of type true; however, parameter $path of craft\helpers\UrlHelper::siteUrl() 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

207
                    $module = CraftUrlHelper::siteUrl(/** @scrutinizer ignore-type */ $module);
Loading history...
208
                } catch (Exception $e) {
209
                    Craft::error($e->getMessage(), __METHOD__);
210
                }
211
            }
212
        } else {
213
            $module = '';
214
        }
215
216
        return $module;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $module also could return the type true which is incompatible with the documented return type null|string.
Loading history...
217
    }
218
219
    /**
220
     * Return a module's raw entry from the manifest
221
     *
222
     * @param array  $config
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
223
     * @param string $moduleName
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
224
     * @param string $type
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
225
     * @param bool   $soft
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
226
     *
227
     * @return null|string
228
     * @throws NotFoundHttpException
229
     */
230
    public static function getModuleEntry(array $config, string $moduleName, string $type = 'modern', bool $soft = false)
231
    {
232
        $module = null;
233
        // Get the manifest file
234
        $manifest = self::getManifestFile($config, $type);
235
        if ($manifest !== null) {
236
            // Make sure it exists in the manifest
237
            if (empty($manifest[$moduleName])) {
238
                // Don't report errors for any files in SUPPRESS_ERRORS_FOR_MODULES
239
                if (!in_array($moduleName, self::SUPPRESS_ERRORS_FOR_MODULES)) {
240
                    self::reportError(Craft::t(
241
                        'seomatic',
242
                        'Module does not exist in the manifest: {moduleName}',
243
                        ['moduleName' => $moduleName]
244
                    ), $soft);
245
                }
246
247
                return null;
248
            }
249
            $module = $manifest[$moduleName];
250
        }
251
252
        return $module;
253
    }
254
255
    /**
256
     * Return a JSON-decoded manifest file
257
     *
258
     * @param array  $config
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
259
     * @param string $type
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
260
     *
261
     * @return null|array
262
     * @throws NotFoundHttpException
263
     */
264
    public static function getManifestFile(array $config, string $type = 'modern')
265
    {
266
        $manifest = null;
267
        // Determine whether we should use the devServer for HMR or not
268
        $devMode = Craft::$app->getConfig()->getGeneral()->devMode;
269
        self::$isHot = ($devMode && $config['useDevServer']);
270
        // Try to get the manifest
271
        while ($manifest === null) {
272
            $manifestPath = self::$isHot
273
                ? $config['devServer']['manifestPath']
274
                : $config['server']['manifestPath'];
275
            // Normalize the path
276
            $path = self::combinePaths($manifestPath, $config['manifest'][$type]);
277
            $manifest = self::getJsonFile($path);
278
            // If the manifest isn't found, and it was hot, fall back on non-hot
279
            if ($manifest === null) {
280
                // We couldn't find a manifest; throw an error
281
                self::reportError(Craft::t(
282
                    'seomatic',
283
                    'Manifest file not found at: {manifestPath}',
284
                    ['manifestPath' => $manifestPath]
285
                ), true);
286
                if (self::$isHot) {
287
                    // Try again, but not with home module replacement
288
                    self::$isHot = false;
289
                } else {
290
                    // Give up and return null
291
                    return null;
292
                }
293
            }
294
        }
295
296
        return $manifest;
297
    }
298
299
    /**
300
     * Returns the contents of a file from a URI path
301
     *
302
     * @param string $path
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
303
     *
304
     * @return string
305
     */
306
    public static function getFile(string $path): string
307
    {
308
        return self::getFileFromUri($path, null) ?? '';
309
    }
310
311
    /**
312
     * @param array  $config
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
313
     * @param string $fileName
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
314
     * @param string $type
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
315
     *
316
     * @return string
317
     */
318
    public static function getFileFromManifest(array $config, string $fileName, string $type = 'legacy'): string
319
    {
320
        try {
321
            $path = self::getModuleEntry($config, $fileName, $type, true);
322
        } catch (NotFoundHttpException $e) {
323
            Craft::error($e->getMessage(), __METHOD__);
324
        }
325
        if ($path !== null) {
326
            $path = self::combinePaths(
327
                $config['localFiles']['basePath'],
328
                $path
329
            );
330
331
            return self::getFileFromUri($path, null) ?? '';
332
        }
333
334
        return '';
335
    }
336
337
    /**
338
     * Return the contents of a JSON file from a URI path
339
     *
340
     * @param string $path
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
341
     *
342
     * @return null|array
343
     */
344
    protected static function getJsonFile(string $path)
345
    {
346
        return self::getFileFromUri($path, [self::class, 'jsonFileDecode']);
347
    }
348
349
    /**
350
     * Invalidate all of the manifest caches
351
     */
352
    public static function invalidateCaches()
353
    {
354
        $cache = Craft::$app->getCache();
355
        TagDependency::invalidate($cache, self::CACHE_TAG);
356
        Craft::info('All manifest caches cleared', __METHOD__);
357
    }
358
359
    // Protected Static Methods
360
    // =========================================================================
361
362
    /**
363
     * Return the contents of a file from a URI path
364
     *
365
     * @param string        $path
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
366
     * @param callable|null $callback
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
367
     *
368
     * @return null|mixed
369
     */
370
    protected static function getFileFromUri(string $path, callable $callback = null)
371
    {
372
        // Resolve any aliases
373
        $alias = Craft::getAlias($path, false);
374
        if ($alias) {
375
            $path = $alias;
376
        }
377
        // Make sure it's a full URL
378
        if (!CraftUrlHelper::isAbsoluteUrl($path) && !is_file($path)) {
0 ignored issues
show
Bug introduced by
It seems like $path can also be of type true; however, parameter $filename of is_file() 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

378
        if (!CraftUrlHelper::isAbsoluteUrl($path) && !is_file(/** @scrutinizer ignore-type */ $path)) {
Loading history...
Bug introduced by
It seems like $path can also be of type true; however, parameter $url of craft\helpers\UrlHelper::isAbsoluteUrl() 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

378
        if (!CraftUrlHelper::isAbsoluteUrl(/** @scrutinizer ignore-type */ $path) && !is_file($path)) {
Loading history...
379
            try {
380
                $path = CraftUrlHelper::siteUrl($path);
0 ignored issues
show
Bug introduced by
It seems like $path can also be of type true; however, parameter $path of craft\helpers\UrlHelper::siteUrl() 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

380
                $path = CraftUrlHelper::siteUrl(/** @scrutinizer ignore-type */ $path);
Loading history...
381
            } catch (Exception $e) {
382
                Craft::error($e->getMessage(), __METHOD__);
383
            }
384
        }
385
386
        return self::getFileContents($path, $callback);
0 ignored issues
show
Bug introduced by
It seems like $path can also be of type true; however, parameter $path of nystudio107\seomatic\hel...fest::getFileContents() 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

386
        return self::getFileContents(/** @scrutinizer ignore-type */ $path, $callback);
Loading history...
387
    }
388
389
    /**
390
     * Return the contents of a file from the passed in path
391
     *
392
     * @param string   $path
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
393
     * @param callable $callback
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
394
     *
395
     * @return null|mixed
396
     */
397
    protected static function getFileContents(string $path, callable $callback = null)
398
    {
399
        // Return the memoized manifest if it exists
400
        if (!empty(self::$files[$path])) {
401
            return self::$files[$path];
402
        }
403
        // Create the dependency tags
404
        $dependency = new TagDependency([
405
            'tags' => [
406
                self::CACHE_TAG,
407
                self::CACHE_TAG.$path,
408
            ],
409
        ]);
410
        // Set the cache duration based on devMode
411
        $cacheDuration = Craft::$app->getConfig()->getGeneral()->devMode
412
            ? self::DEVMODE_CACHE_DURATION
413
            : null;
414
        // Get the result from the cache, or parse the file
415
        $cache = Craft::$app->getCache();
416
        $file = $cache->getOrSet(
417
            self::CACHE_KEY.$path,
418
            function () use ($path, $callback) {
419
                $result = null;
420
                $contents = @file_get_contents($path);
421
                if ($contents) {
422
                    $result = $contents;
423
                    if ($callback) {
424
                        $result = $callback($result);
425
                    }
426
                }
427
428
                return $result;
429
            },
430
            $cacheDuration,
431
            $dependency
432
        );
433
        self::$files[$path] = $file;
434
435
        return $file;
436
    }
437
438
    /**
439
     * Combined the passed in paths, whether file system or URL
440
     *
441
     * @param string ...$paths
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
442
     *
443
     * @return string
444
     */
445
    protected static function combinePaths(string ...$paths): string
446
    {
447
        $last_key = \count($paths) - 1;
448
        array_walk($paths, function (&$val, $key) use ($last_key) {
449
            switch ($key) {
450
                case 0:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
451
                    $val = rtrim($val, '/ ');
452
                    break;
453
                case $last_key:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
454
                    $val = ltrim($val, '/ ');
455
                    break;
456
                default:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
457
                    $val = trim($val, '/ ');
458
                    break;
459
            }
460
        });
461
462
        $first = array_shift($paths);
463
        $last = array_pop($paths);
464
        $paths = array_filter($paths);
465
        array_unshift($paths, $first);
466
        $paths[] = $last;
467
468
        return implode('/', $paths);
469
    }
470
471
    /**
472
     * @param string $error
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
473
     * @param bool   $soft
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
474
     *
475
     * @throws NotFoundHttpException
476
     */
477
    protected static function reportError(string $error, $soft = false)
478
    {
479
        $devMode = Craft::$app->getConfig()->getGeneral()->devMode;
480
        if ($devMode && !$soft) {
481
            throw new NotFoundHttpException($error);
482
        }
483
        Craft::error($error, __METHOD__);
484
    }
485
486
    // Private Static Methods
487
    // =========================================================================
488
489
    /**
490
     * @param $string
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
491
     *
492
     * @return mixed
493
     */
494
    private static function jsonFileDecode($string)
0 ignored issues
show
Coding Style introduced by
Private method name "Manifest::jsonFileDecode" must be prefixed with an underscore
Loading history...
495
    {
496
        return JsonHelper::decodeIfJson($string);
497
    }
498
}
499