Passed
Push — main ( d93426...8fe145 )
by Thierry
05:54
created

CallableObject::getJsParam()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
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_merge;
35
use function call_user_func;
36
use function is_array;
37
use function is_string;
38
use function str_replace;
39
40
class CallableObject
41
{
42
    /**
43
     * The user registered component
44
     *
45
     * @var mixed
46
     */
47
    private $xComponent = null;
48
49
    /**
50
     * The target of the Jaxon call
51
     *
52
     * @var Target
53
     */
54
    private $xTarget;
55
56
    /**
57
     * The class constructor
58
     *
59
     * @param ComponentContainer $cdi
60
     * @param Container $di
61
     * @param ReflectionClass $xReflectionClass
62
     * @param ComponentOptions $xOptions
63
     */
64
    public function __construct(protected ComponentContainer $cdi,
65
        protected Container $di, private ReflectionClass $xReflectionClass,
66
        private ComponentOptions $xOptions)
67
    {}
68
69
    /**
70
     * @param string|null $sMethodName
71
     *
72
     * @return bool
73
     */
74
    public function excluded(?string $sMethodName = null): bool
75
    {
76
        return $sMethodName === null ? $this->xOptions->excluded() :
77
            !$this->xOptions->isPublicMethod($sMethodName);
78
    }
79
80
    /**
81
     * Get the name of the registered PHP class
82
     *
83
     * @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...
84
     */
85
    public function getClassName(): string
86
    {
87
        return $this->xReflectionClass->getName();
88
    }
89
90
    /**
91
     * Get the name of the corresponding javascript class
92
     *
93
     * @return string
94
     */
95
    public function getJsName(): string
96
    {
97
        return str_replace('\\', $this->xOptions->separator(), $this->getClassName());
98
    }
99
100
    /**
101
     * Get the name of the javascript parameter in the ajax request
102
     *
103
     * @return string
104
     */
105
    public function getJsParam(): string
106
    {
107
        return str_replace('\\', '.', $this->getClassName());
108
    }
109
110
    /**
111
     * Get the js options of the component
112
     *
113
     * @return array
114
     */
115
    public function getOptions(): array
116
    {
117
        return $this->xOptions->jsOptions();
118
    }
119
120
    /**
121
     * Return a list of methods of the component to export to javascript
122
     *
123
     * @return array
124
     */
125
    public function getCallableMethods(): array
126
    {
127
        return $this->xOptions->getCallableMethods();
128
    }
129
130
    /**
131
     * Check if the specified method name is one of the methods of the component
132
     *
133
     * @param string $sMethod    The name of the method to check
134
     *
135
     * @return bool
136
     */
137
    public function hasMethod(string $sMethod): bool
138
    {
139
        return $this->xReflectionClass->hasMethod($sMethod);
140
    }
141
142
    /**
143
     * Call the specified method of the component using the specified array of arguments
144
     *
145
     * @param string $sMethod    The method name
146
     * @param array $aArgs    The method arguments
147
     * @param bool $bAccessible    If false, only calls to public method are allowed
148
     *
149
     * @return void
150
     * @throws ReflectionException
151
     */
152
    private function callMethod(string $sMethod, array $aArgs, bool $bAccessible): void
153
    {
154
        $reflectionMethod = $this->xReflectionClass->getMethod($sMethod);
155
        // Make it possible to call protected methods
156
        $reflectionMethod->setAccessible($bAccessible);
157
        $reflectionMethod->invokeArgs($this->xComponent, $aArgs);
158
    }
159
160
    /**
161
     * Call the specified method of the component using the specified array of arguments
162
     *
163
     * @param array $aHookMethods    The method config options
164
     *
165
     * @return void
166
     * @throws ReflectionException
167
     */
168
    private function callHookMethods(array $aHookMethods): void
169
    {
170
        $sMethod = $this->xTarget->getMethodName();
171
        // The hooks defined at method level are merged with those defined at class level.
172
        $aMethods = array_merge($aHookMethods['*'] ?? [], $aHookMethods[$sMethod] ?? []);
173
        foreach($aMethods as $xKey => $xValue)
174
        {
175
            $sHookName = $xValue;
176
            $aHookArgs = [];
177
            if(is_string($xKey))
178
            {
179
                $sHookName = $xKey;
180
                $aHookArgs = is_array($xValue) ? $xValue : [$xValue];
181
            }
182
            $this->callMethod($sHookName, $aHookArgs, true);
183
        }
184
    }
185
186
    /**
187
     * @param object $xComponent
188
     * @param string $sAttr
189
     * @param object $xDiValue
190
     * @param-closure-this object $cSetter
191
     *
192
     * @return void
193
     */
194
    private function setDiAttribute($xComponent, string $sAttr, $xDiValue, Closure $cSetter): void
195
    {
196
        // Allow the setter to access protected attributes.
197
        $cSetter = $cSetter->bindTo($xComponent, $xComponent);
198
        call_user_func($cSetter, $sAttr, $xDiValue);
199
    }
200
201
    /**
202
     * @param mixed $xComponent
203
     * @param array $aDiOptions
204
     *
205
     * @return void
206
     */
207
    private function setDiAttributes($xComponent, array $aDiOptions): void
208
    {
209
        // Set the protected attributes of the object
210
        $cSetter = function($sAttr, $xDiValue) {
211
            // $this here is related to the registered object instance.
212
            // Warning: dynamic properties will be deprecated in PHP8.2.
213
            $this->$sAttr = $xDiValue;
214
        };
215
        foreach($aDiOptions as $sAttr => $sClass)
216
        {
217
            $this->setDiAttribute($xComponent, $sAttr, $this->di->get($sClass), $cSetter);
218
        }
219
    }
220
221
    /**
222
     * @param mixed $xComponent
223
     *
224
     * @return void
225
     */
226
    public function setDiClassAttributes($xComponent): void
227
    {
228
        $aDiOptions = $this->xOptions->diOptions();
229
        $this->setDiAttributes($xComponent, $aDiOptions['*'] ?? []);
230
    }
231
232
    /**
233
     * @param mixed $xComponent
234
     * @param string $sMethodName
235
     *
236
     * @return void
237
     */
238
    public function setDiMethodAttributes($xComponent, string $sMethodName): void
239
    {
240
        $aDiOptions = $this->xOptions->diOptions();
241
        $this->setDiAttributes($xComponent, $aDiOptions[$sMethodName] ?? []);
242
    }
243
244
    /**
245
     * Call the specified method of the component using the specified array of arguments
246
     *
247
     * @param Target $xTarget The target of the Jaxon call
248
     *
249
     * @return void
250
     * @throws ReflectionException
251
     * @throws SetupException
252
     */
253
    public function call(Target $xTarget): void
254
    {
255
        $this->xTarget = $xTarget;
256
        $this->xComponent = $this->cdi->getTargetComponent($this->getClassName(), $xTarget);
257
258
        // Methods to call before processing the request
259
        $this->callHookMethods($this->xOptions->beforeMethods());
260
261
        // Call the request method
262
        $this->callMethod($xTarget->getMethodName(), $this->xTarget->args(), false);
263
264
        // Methods to call after processing the request
265
        $this->callHookMethods($this->xOptions->afterMethods());
266
    }
267
}
268