Passed
Push — main ( 31af8b...652a21 )
by Thierry
04:07
created

ComponentOptions::isProtectedMethod()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 2
eloc 2
c 2
b 0
f 0
nc 2
nop 1
dl 0
loc 4
rs 10
1
<?php
2
3
/**
4
 * ComponentOptions.php
5
 *
6
 * Options of a callable object.
7
 *
8
 * @package jaxon-core
9
 * @author Thierry Feuzeu <[email protected]>
10
 * @copyright 2024 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\Request\CallableClass;
16
17
use function array_merge;
18
use function array_unique;
19
use function explode;
20
use function in_array;
21
use function is_array;
22
use function is_string;
23
use function json_encode;
24
use function substr;
25
use function str_replace;
26
use function trim;
27
28
class ComponentOptions
29
{
30
    /**
31
     * Check if the js code for this object must be generated
32
     *
33
     * @var bool
34
     */
35
    private $bExcluded = false;
36
37
    /**
38
     * The character to use as separator in javascript class names
39
     *
40
     * @var string
41
     */
42
    private $sSeparator = '.';
43
44
    /**
45
     * A list of methods of the user registered callable object the library must not export to javascript
46
     *
47
     * @var array
48
     */
49
    private $aProtectedMethods = [];
50
51
    /**
52
     * A list of methods to call before processing the request
53
     *
54
     * @var array
55
     */
56
    private $aBeforeMethods = [];
57
58
    /**
59
     * A list of methods to call after processing the request
60
     *
61
     * @var array
62
     */
63
    private $aAfterMethods = [];
64
65
    /**
66
     * The javascript class options
67
     *
68
     * @var array
69
     */
70
    private $aJsOptions = [];
71
72
    /**
73
     * The DI options
74
     *
75
     * @var array
76
     */
77
    private $aDiOptions = [];
78
79
    /**
80
     * The constructor
81
     *
82
     * @param array $aOptions
83
     * @param bool $bExcluded
84
     * @param array $aProtectedMethods
85
     * @param array $aProperties
86
     */
87
    public function __construct(array $aOptions, bool $bExcluded,
88
        array $aProtectedMethods, array $aProperties)
89
    {
90
        $this->bExcluded = $bExcluded || (bool)($aOptions['excluded'] ?? false);
91
        if($this->bExcluded)
92
        {
93
            return;
94
        }
95
96
        $sSeparator = $aOptions['separator'];
97
        if($sSeparator === '_' || $sSeparator === '.')
98
        {
99
            $this->sSeparator = $sSeparator;
100
        }
101
        $this->addProtectedMethods($aOptions['protected']);
102
        $this->addProtectedMethods($aProtectedMethods);
103
104
        foreach($aOptions['functions'] as $sNames => $aFunctionOptions)
105
        {
106
            // Names are in a comma-separated list.
107
            $aFunctionNames = explode(',', $sNames);
108
            foreach($aFunctionNames as $sFunctionName)
109
            {
110
                $this->addFunctionOptions($sFunctionName, $aFunctionOptions);
111
            }
112
        }
113
        foreach($aProperties as $sFunctionName => $aFunctionOptions)
114
        {
115
            $this->addFunctionOptions($sFunctionName, $aFunctionOptions);
116
        }
117
    }
118
119
    /**
120
     * @param array|string $xMethods
121
     *
122
     * @return void
123
     */
124
    private function addProtectedMethods(array|string $xMethods): void
125
    {
126
        if(!is_array($xMethods))
0 ignored issues
show
introduced by
The condition is_array($xMethods) is always true.
Loading history...
127
        {
128
            $this->aProtectedMethods[trim((string)$xMethods)] = true;
129
            return;
130
        }
131
132
        foreach($xMethods as $sMethod)
133
        {
134
            $this->aProtectedMethods[trim((string)$sMethod)] = true;
135
        }
136
    }
137
138
    /**
139
     * @param string $sMethodName
140
     *
141
     * @return bool
142
     */
143
    public function isProtectedMethod(string $sMethodName): bool
144
    {
145
        return isset($this->aProtectedMethods['*']) ||
146
            isset($this->aProtectedMethods[$sMethodName]);
147
    }
148
149
    /**
150
     * Check if the js code for this object must be generated
151
     *
152
     * @return bool
153
     */
154
    public function excluded(): bool
155
    {
156
        return $this->bExcluded;
157
    }
158
159
    /**
160
     * @return string
161
     */
162
    public function separator(): string
163
    {
164
        return $this->sSeparator;
165
    }
166
167
    /**
168
     * @return array
169
     */
170
    public function beforeMethods(): array
171
    {
172
        return $this->aBeforeMethods;
173
    }
174
175
    /**
176
     * @return array
177
     */
178
    public function afterMethods(): array
179
    {
180
        return $this->aAfterMethods;
181
    }
182
183
    /**
184
     * @return array
185
     */
186
    public function diOptions(): array
187
    {
188
        return $this->aDiOptions;
189
    }
190
191
    /**
192
     * @return array
193
     */
194
    public function jsOptions(): array
195
    {
196
        return $this->aJsOptions;
197
    }
198
199
    /**
200
     * Set hook methods
201
     *
202
     * @param array $aHookMethods    The array of hook methods
203
     * @param string|array $xValue    The value of the configuration option
204
     *
205
     * @return void
206
     */
207
    private function setHookMethods(array &$aHookMethods, $xValue): void
208
    {
209
        foreach($xValue as $sCalledMethod => $xMethodToCall)
210
        {
211
            if(!isset($aHookMethods[$sCalledMethod]))
212
            {
213
                $aHookMethods[$sCalledMethod] = [];
214
            }
215
            if(is_array($xMethodToCall))
216
            {
217
                $aHookMethods[$sCalledMethod] = array_merge($aHookMethods[$sCalledMethod], $xMethodToCall);
218
            }
219
            elseif(is_string($xMethodToCall))
220
            {
221
                $aHookMethods[$sCalledMethod][] = $xMethodToCall;
222
            }
223
        }
224
    }
225
226
    /**
227
     * @param array $aDiOptions
228
     */
229
    private function addDiOption(array $aDiOptions): void
230
    {
231
        $this->aDiOptions = array_merge($this->aDiOptions, $aDiOptions);
232
    }
233
234
    /**
235
     * Set configuration options / call options for each method
236
     *
237
     * @param string $sName    The name of the configuration option
238
     * @param string|array $xValue    The value of the configuration option
239
     *
240
     * @return void
241
     */
242
    private function addOption(string $sName, $xValue): void
243
    {
244
        switch($sName)
245
        {
246
        // Set the methods to call before processing the request
247
        case '__before':
248
            $this->setHookMethods($this->aBeforeMethods, $xValue);
249
            break;
250
        // Set the methods to call after processing the request
251
        case '__after':
252
            $this->setHookMethods($this->aAfterMethods, $xValue);
253
            break;
254
        // Set the attributes to inject in the callable object
255
        case '__di':
256
            $this->addDiOption($xValue);
0 ignored issues
show
Bug introduced by
It seems like $xValue can also be of type string; however, parameter $aDiOptions of Jaxon\Plugin\Request\Cal...tOptions::addDiOption() does only seem to accept array, 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

256
            $this->addDiOption(/** @scrutinizer ignore-type */ $xValue);
Loading history...
257
            break;
258
        default:
259
            break;
260
        }
261
    }
262
263
    /**
264
     * @param string $sFunctionName
265
     * @param string $sOptionName
266
     * @param mixed $xOptionValue
267
     *
268
     * @return void
269
     */
270
    private function _addJsArrayOption(string $sFunctionName, string $sOptionName, $xOptionValue): void
271
    {
272
        if(is_string($xOptionValue))
273
        {
274
            $xOptionValue = [$xOptionValue];
275
        }
276
        if(!is_array($xOptionValue))
277
        {
278
            return; // Do not save.
279
        }
280
281
        $aOptions = $this->aJsOptions[$sFunctionName][$sOptionName] ?? [];
282
        $this->aJsOptions[$sFunctionName][$sOptionName] = array_merge($aOptions, $xOptionValue);
283
    }
284
285
    /**
286
     * @param string $sFunctionName
287
     * @param string $sOptionName
288
     * @param mixed $xOptionValue
289
     *
290
     * @return void
291
     */
292
    private function _setJsOption(string $sFunctionName, string $sOptionName, $xOptionValue): void
293
    {
294
        $this->aJsOptions[$sFunctionName][$sOptionName] = $xOptionValue;
295
    }
296
297
    /**
298
     * @param string $sFunctionName
299
     * @param string $sOptionName
300
     * @param mixed $xOptionValue
301
     *
302
     * @return void
303
     */
304
    private function addJsOption(string $sFunctionName, string $sOptionName, $xOptionValue): void
305
    {
306
        switch($sOptionName)
307
        {
308
        case 'excluded':
309
            if((bool)$xOptionValue)
310
            {
311
                $this->addProtectedMethods($sFunctionName);
312
            }
313
            break;
314
        // For databags and callbacks, all the value are merged in a single array.
315
        case 'bags':
316
        case 'callback':
317
            $this->_addJsArrayOption($sFunctionName, $sOptionName, $xOptionValue);
318
            return;
319
        // For all the other options, only the last value is kept.
320
        default:
321
            $this->_setJsOption($sFunctionName, $sOptionName, $xOptionValue);
322
        }
323
    }
324
325
    /**
326
     * @param string $sFunctionName
327
     * @param array $aFunctionOptions
328
     *
329
     * @return void
330
     */
331
    private function addFunctionOptions(string $sFunctionName, array $aFunctionOptions): void
332
    {
333
        foreach($aFunctionOptions as $sOptionName => $xOptionValue)
334
        {
335
            substr($sOptionName, 0, 2) === '__' ?
336
                // Options for PHP classes. They start with "__".
337
                $this->addOption($sOptionName, [$sFunctionName => $xOptionValue]) :
338
                // Options for javascript code.
339
                $this->addJsOption($sFunctionName, $sOptionName, $xOptionValue);
340
        }
341
    }
342
343
    /**
344
     * @param string $sMethodName
345
     *
346
     * @return array
347
     */
348
    public function getMethodOptions(string $sMethodName): array
349
    {
350
        // First take the common options.
351
        $aOptions = array_merge($this->aJsOptions['*'] ?? []); // Clone the array
352
        // Then add the method options.
353
        $aMethodOptions = $this->aJsOptions[$sMethodName] ?? [];
354
        foreach($aMethodOptions as $sOptionName => $xOptionValue)
355
        {
356
            // For databags and callbacks, merge the values in a single array.
357
            // For all the other options, keep the last value.
358
            $aOptions[$sOptionName] = !in_array($sOptionName, ['bags', 'callback']) ?
359
                $xOptionValue :
360
                array_unique(array_merge($aOptions[$sOptionName] ?? [], $xOptionValue));
361
        }
362
        // Since callbacks are js object names, they need a special formatting.
363
        if(isset($aOptions['callback']))
364
        {
365
            $aOptions['callback'] = str_replace('"', '', json_encode($aOptions['callback']));
366
        }
367
        return $aOptions;
368
    }
369
}
370