CallableObjectOptions::beforeMethods()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
/**
4
 * CallableObjectOptions.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 Jaxon\App\AbstractCallable;
18
use Jaxon\App\Metadata\MetadataInterface;
19
use ReflectionClass;
20
21
use function array_merge;
22
use function array_unique;
23
use function is_array;
24
use function is_string;
25
use function substr;
26
use function trim;
27
28
class CallableObjectOptions
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 MetadataInterface|null $xMetadata
84
     */
85
    public function __construct(array $aOptions, ?MetadataInterface $xMetadata)
86
    {
87
        $this->bExcluded = ($xMetadata?->isExcluded() ?? false) || (bool)($aOptions['excluded'] ?? false);
88
        if($this->bExcluded)
89
        {
90
            return;
91
        }
92
93
        $sSeparator = $aOptions['separator'];
94
        if($sSeparator === '_' || $sSeparator === '.')
95
        {
96
            $this->sSeparator = $sSeparator;
97
        }
98
        $this->addProtectedMethods($aOptions['protected']);
99
        $this->addProtectedMethods($xMetadata?->getProtectedMethods() ?? []);
100
101
        foreach($aOptions['functions'] as $sNames => $aFunctionOptions)
102
        {
103
            $aFunctionNames = explode(',', $sNames); // Names are in comma-separated list.
104
            foreach($aFunctionNames as $sFunctionName)
105
            {
106
                $this->addFunctionOptions($sFunctionName, $aFunctionOptions);
107
            }
108
        }
109
        foreach($xMetadata?->getProperties() ?? [] as $sFunctionName => $aFunctionOptions)
110
        {
111
            $this->addFunctionOptions($sFunctionName, $aFunctionOptions);
112
        }
113
    }
114
115
    /**
116
     * @param mixed $xMethods
117
     *
118
     * @return void
119
     */
120
    private function addProtectedMethods($xMethods)
121
    {
122
        if(!is_array($xMethods))
123
        {
124
            $this->aProtectedMethods[trim((string)$xMethods)] = true;
125
            return;
126
        }
127
        foreach($xMethods as $sMethod)
128
        {
129
            $this->aProtectedMethods[trim((string)$sMethod)] = true;
130
        }
131
    }
132
133
    /**
134
     * Check if the js code for this object must be generated
135
     *
136
     * @return bool
137
     */
138
    public function excluded(): bool
139
    {
140
        return $this->bExcluded;
141
    }
142
143
    /**
144
     * @return string
145
     */
146
    public function separator(): string
147
    {
148
        return $this->sSeparator;
149
    }
150
151
    /**
152
     * @param ReflectionClass $xReflectionClass
153
     * @param string $sMethodName
154
     *
155
     * @return bool
156
     */
157
    public function isProtectedMethod(ReflectionClass $xReflectionClass, string $sMethodName): bool
158
    {
159
        return isset($this->aProtectedMethods[$sMethodName]) || isset($this->aProtectedMethods['*']) ||
160
            ($xReflectionClass->isSubclassOf(AbstractCallable::class) && $sMethodName === 'paginator');
161
    }
162
163
    /**
164
     * @return array
165
     */
166
    public function beforeMethods(): array
167
    {
168
        return $this->aBeforeMethods;
169
    }
170
171
    /**
172
     * @return array
173
     */
174
    public function afterMethods(): array
175
    {
176
        return $this->aAfterMethods;
177
    }
178
179
    /**
180
     * @return array
181
     */
182
    public function diOptions(): array
183
    {
184
        return $this->aDiOptions;
185
    }
186
187
    /**
188
     * @return array
189
     */
190
    public function jsOptions(): array
191
    {
192
        return $this->aJsOptions;
193
    }
194
195
    /**
196
     * Set hook methods
197
     *
198
     * @param array $aHookMethods    The array of hook methods
199
     * @param string|array $xValue    The value of the configuration option
200
     *
201
     * @return void
202
     */
203
    private function setHookMethods(array &$aHookMethods, $xValue)
204
    {
205
        foreach($xValue as $sCalledMethod => $xMethodToCall)
206
        {
207
            if(!isset($aHookMethods[$sCalledMethod]))
208
            {
209
                $aHookMethods[$sCalledMethod] = [];
210
            }
211
            if(is_array($xMethodToCall))
212
            {
213
                $aHookMethods[$sCalledMethod] = array_merge($aHookMethods[$sCalledMethod], $xMethodToCall);
214
            }
215
            elseif(is_string($xMethodToCall))
216
            {
217
                $aHookMethods[$sCalledMethod][] = $xMethodToCall;
218
            }
219
        }
220
    }
221
222
    /**
223
     * @param array $aDiOptions
224
     */
225
    private function addDiOption(array $aDiOptions)
226
    {
227
        $this->aDiOptions = array_merge($this->aDiOptions, $aDiOptions);
228
    }
229
230
    /**
231
     * Set configuration options / call options for each method
232
     *
233
     * @param string $sName    The name of the configuration option
234
     * @param string|array $xValue    The value of the configuration option
235
     *
236
     * @return void
237
     */
238
    private function addOption(string $sName, $xValue)
239
    {
240
        switch($sName)
241
        {
242
        // Set the methods to call before processing the request
243
        case '__before':
244
            $this->setHookMethods($this->aBeforeMethods, $xValue);
245
            break;
246
        // Set the methods to call after processing the request
247
        case '__after':
248
            $this->setHookMethods($this->aAfterMethods, $xValue);
249
            break;
250
        // Set the attributes to inject in the callable object
251
        case '__di':
252
            $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

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