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

CallableObject::getOptions()   A

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
nc 1
nop 0
dl 0
loc 3
rs 10
c 1
b 0
f 0
1
<?php
2
3
/**
4
 * CallableObject.php
5
 *
6
 * Jaxon callable object
7
 *
8
 * This class stores a reference to a component whose methods can be called from
9
 * the client via a Jaxon request
10
 *
11
 * @package jaxon-core
12
 * @author Jared White
13
 * @author J. Max Wilson
14
 * @author Joseph Woolley
15
 * @author Steffen Konerow
16
 * @author Thierry Feuzeu <[email protected]>
17
 * @copyright Copyright (c) 2005-2007 by Jared White & J. Max Wilson
18
 * @copyright Copyright (c) 2008-2010 by Joseph Woolley, Steffen Konerow, Jared White  & J. Max Wilson
19
 * @copyright 2016 Thierry Feuzeu <[email protected]>
20
 * @license https://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
21
 * @link https://github.com/jaxon-php/jaxon-core
22
 */
23
24
namespace Jaxon\Plugin\Request\CallableClass;
25
26
use Jaxon\Di\ComponentContainer;
27
use Jaxon\Di\Container;
28
use Jaxon\Exception\SetupException;
29
use Jaxon\Request\Target;
30
use Closure;
31
use ReflectionClass;
32
use ReflectionException;
33
34
use function array_map;
35
use function array_merge;
36
use function call_user_func;
37
use function is_array;
38
use function is_string;
39
use function json_encode;
40
use function str_replace;
41
42
class CallableObject
43
{
44
    /**
45
     * The user registered component
46
     *
47
     * @var mixed
48
     */
49
    private $xComponent = null;
50
51
    /**
52
     * The target of the Jaxon call
53
     *
54
     * @var Target
55
     */
56
    private $xTarget;
57
58
    /**
59
     * The class constructor
60
     *
61
     * @param ComponentContainer $cdi
62
     * @param Container $di
63
     * @param ReflectionClass $xReflectionClass
64
     * @param ComponentOptions $xOptions
65
     */
66
    public function __construct(protected ComponentContainer $cdi,
67
        protected Container $di, private ReflectionClass $xReflectionClass,
68
        private ComponentOptions $xOptions)
69
    {}
70
71
    /**
72
     * @param string|null $sMethodName
73
     *
74
     * @return bool
75
     */
76
    public function excluded(?string $sMethodName = null): bool
77
    {
78
        return $sMethodName === null ? $this->xOptions->excluded() :
79
            $this->xOptions->isProtectedMethod($sMethodName);
80
    }
81
82
    /**
83
     * Get the name of the registered PHP class
84
     *
85
     * @return class-string
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
86
     */
87
    public function getClassName(): string
88
    {
89
        return $this->xReflectionClass->getName();
90
    }
91
92
    /**
93
     * Get the name of the corresponding javascript class
94
     *
95
     * @return string
96
     */
97
    public function getJsName(): string
98
    {
99
        return str_replace('\\', $this->xOptions->separator(), $this->getClassName());
100
    }
101
102
    /**
103
     * Get the js options of the component
104
     *
105
     * @return array
106
     */
107
    public function getOptions(): array
108
    {
109
        return $this->xOptions->jsOptions();
110
    }
111
112
    /**
113
     * Return a list of methods of the component to export to javascript
114
     *
115
     * @return array
116
     */
117
    public function getCallableMethods(): array
118
    {
119
        // Get the method options, and convert each of them to
120
        // a string to be displayed in the js script template.
121
        $fGetOption = function($sMethodName) {
122
            return array_map(function($xOption) {
123
                return is_array($xOption) ? json_encode($xOption) : $xOption;
124
            }, $this->xOptions->getMethodOptions($sMethodName));
125
        };
126
        $aMethods = $this->cdi->getPublicMethods($this->xReflectionClass);
127
        $aMethods = array_filter($aMethods, fn($sMethodName) =>
128
            !$this->xOptions->isProtectedMethod($sMethodName));
129
130
        return array_map(fn($sMethodName) => [
131
            'name' => $sMethodName,
132
            'config' => $fGetOption($sMethodName),
133
        ], $aMethods);
134
    }
135
136
    /**
137
     * Check if the specified method name is one of the methods of the component
138
     *
139
     * @param string $sMethod    The name of the method to check
140
     *
141
     * @return bool
142
     */
143
    public function hasMethod(string $sMethod): bool
144
    {
145
        return $this->xReflectionClass->hasMethod($sMethod);
146
    }
147
148
    /**
149
     * Call the specified method of the component using the specified array of arguments
150
     *
151
     * @param string $sMethod    The method name
152
     * @param array $aArgs    The method arguments
153
     * @param bool $bAccessible    If false, only calls to public method are allowed
154
     *
155
     * @return void
156
     * @throws ReflectionException
157
     */
158
    private function callMethod(string $sMethod, array $aArgs, bool $bAccessible): void
159
    {
160
        $reflectionMethod = $this->xReflectionClass->getMethod($sMethod);
161
        // Make it possible to call protected methods
162
        $reflectionMethod->setAccessible($bAccessible);
163
        $reflectionMethod->invokeArgs($this->xComponent, $aArgs);
164
    }
165
166
    /**
167
     * Call the specified method of the component using the specified array of arguments
168
     *
169
     * @param array $aHookMethods    The method config options
170
     *
171
     * @return void
172
     * @throws ReflectionException
173
     */
174
    private function callHookMethods(array $aHookMethods): void
175
    {
176
        $sMethod = $this->xTarget->getMethodName();
177
        // The hooks defined at method level are merged with those defined at class level.
178
        $aMethods = array_merge($aHookMethods['*'] ?? [], $aHookMethods[$sMethod] ?? []);
179
        foreach($aMethods as $xKey => $xValue)
180
        {
181
            $sHookName = $xValue;
182
            $aHookArgs = [];
183
            if(is_string($xKey))
184
            {
185
                $sHookName = $xKey;
186
                $aHookArgs = is_array($xValue) ? $xValue : [$xValue];
187
            }
188
            $this->callMethod($sHookName, $aHookArgs, true);
189
        }
190
    }
191
192
    /**
193
     * @param object $xComponent
194
     * @param string $sAttr
195
     * @param object $xDiValue
196
     * @param-closure-this object $cSetter
197
     *
198
     * @return void
199
     */
200
    private function setDiAttribute($xComponent, string $sAttr, $xDiValue, Closure $cSetter): void
201
    {
202
        // Allow the setter to access protected attributes.
203
        $cSetter = $cSetter->bindTo($xComponent, $xComponent);
204
        call_user_func($cSetter, $sAttr, $xDiValue);
205
    }
206
207
    /**
208
     * @param mixed $xComponent
209
     * @param array $aDiOptions
210
     *
211
     * @return void
212
     */
213
    private function setDiAttributes($xComponent, array $aDiOptions): void
214
    {
215
        // Set the protected attributes of the object
216
        $cSetter = function($sAttr, $xDiValue) {
217
            // $this here is related to the registered object instance.
218
            // Warning: dynamic properties will be deprecated in PHP8.2.
219
            $this->$sAttr = $xDiValue;
220
        };
221
        foreach($aDiOptions as $sAttr => $sClass)
222
        {
223
            $this->setDiAttribute($xComponent, $sAttr, $this->di->get($sClass), $cSetter);
224
        }
225
    }
226
227
    /**
228
     * @param mixed $xComponent
229
     *
230
     * @return void
231
     */
232
    public function setDiClassAttributes($xComponent): void
233
    {
234
        $aDiOptions = $this->xOptions->diOptions();
235
        $this->setDiAttributes($xComponent, $aDiOptions['*'] ?? []);
236
    }
237
238
    /**
239
     * @param mixed $xComponent
240
     * @param string $sMethodName
241
     *
242
     * @return void
243
     */
244
    public function setDiMethodAttributes($xComponent, string $sMethodName): void
245
    {
246
        $aDiOptions = $this->xOptions->diOptions();
247
        $this->setDiAttributes($xComponent, $aDiOptions[$sMethodName] ?? []);
248
    }
249
250
    /**
251
     * Call the specified method of the component using the specified array of arguments
252
     *
253
     * @param Target $xTarget The target of the Jaxon call
254
     *
255
     * @return void
256
     * @throws ReflectionException
257
     * @throws SetupException
258
     */
259
    public function call(Target $xTarget): void
260
    {
261
        $this->xTarget = $xTarget;
262
        $this->xComponent = $this->cdi->getTargetComponent($this->getClassName(), $xTarget);
263
264
        // Methods to call before processing the request
265
        $this->callHookMethods($this->xOptions->beforeMethods());
266
267
        // Call the request method
268
        $this->callMethod($xTarget->getMethodName(), $this->xTarget->args(), false);
269
270
        // Methods to call after processing the request
271
        $this->callHookMethods($this->xOptions->afterMethods());
272
    }
273
}
274