Passed
Push — main ( 87fed9...27211a )
by Thierry
04:04
created

CallableObject::getJsName()   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_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 js options of the component
102
     *
103
     * @return array
104
     */
105
    public function getOptions(): array
106
    {
107
        return $this->xOptions->jsOptions();
108
    }
109
110
    /**
111
     * Return a list of methods of the component to export to javascript
112
     *
113
     * @return array
114
     */
115
    public function getCallableMethods(): array
116
    {
117
        return $this->xOptions->getCallableMethods();
118
    }
119
120
    /**
121
     * Check if the specified method name is one of the methods of the component
122
     *
123
     * @param string $sMethod    The name of the method to check
124
     *
125
     * @return bool
126
     */
127
    public function hasMethod(string $sMethod): bool
128
    {
129
        return $this->xReflectionClass->hasMethod($sMethod);
130
    }
131
132
    /**
133
     * Call the specified method of the component using the specified array of arguments
134
     *
135
     * @param string $sMethod    The method name
136
     * @param array $aArgs    The method arguments
137
     * @param bool $bAccessible    If false, only calls to public method are allowed
138
     *
139
     * @return void
140
     * @throws ReflectionException
141
     */
142
    private function callMethod(string $sMethod, array $aArgs, bool $bAccessible): void
143
    {
144
        $reflectionMethod = $this->xReflectionClass->getMethod($sMethod);
145
        // Make it possible to call protected methods
146
        $reflectionMethod->setAccessible($bAccessible);
147
        $reflectionMethod->invokeArgs($this->xComponent, $aArgs);
148
    }
149
150
    /**
151
     * Call the specified method of the component using the specified array of arguments
152
     *
153
     * @param array $aHookMethods    The method config options
154
     *
155
     * @return void
156
     * @throws ReflectionException
157
     */
158
    private function callHookMethods(array $aHookMethods): void
159
    {
160
        $sMethod = $this->xTarget->getMethodName();
161
        // The hooks defined at method level are merged with those defined at class level.
162
        $aMethods = array_merge($aHookMethods['*'] ?? [], $aHookMethods[$sMethod] ?? []);
163
        foreach($aMethods as $xKey => $xValue)
164
        {
165
            $sHookName = $xValue;
166
            $aHookArgs = [];
167
            if(is_string($xKey))
168
            {
169
                $sHookName = $xKey;
170
                $aHookArgs = is_array($xValue) ? $xValue : [$xValue];
171
            }
172
            $this->callMethod($sHookName, $aHookArgs, true);
173
        }
174
    }
175
176
    /**
177
     * @param object $xComponent
178
     * @param string $sAttr
179
     * @param object $xDiValue
180
     * @param-closure-this object $cSetter
181
     *
182
     * @return void
183
     */
184
    private function setDiAttribute($xComponent, string $sAttr, $xDiValue, Closure $cSetter): void
185
    {
186
        // Allow the setter to access protected attributes.
187
        $cSetter = $cSetter->bindTo($xComponent, $xComponent);
188
        call_user_func($cSetter, $sAttr, $xDiValue);
189
    }
190
191
    /**
192
     * @param mixed $xComponent
193
     * @param array $aDiOptions
194
     *
195
     * @return void
196
     */
197
    private function setDiAttributes($xComponent, array $aDiOptions): void
198
    {
199
        // Set the protected attributes of the object
200
        $cSetter = function($sAttr, $xDiValue) {
201
            // $this here is related to the registered object instance.
202
            // Warning: dynamic properties will be deprecated in PHP8.2.
203
            $this->$sAttr = $xDiValue;
204
        };
205
        foreach($aDiOptions as $sAttr => $sClass)
206
        {
207
            $this->setDiAttribute($xComponent, $sAttr, $this->di->get($sClass), $cSetter);
208
        }
209
    }
210
211
    /**
212
     * @param mixed $xComponent
213
     *
214
     * @return void
215
     */
216
    public function setDiClassAttributes($xComponent): void
217
    {
218
        $aDiOptions = $this->xOptions->diOptions();
219
        $this->setDiAttributes($xComponent, $aDiOptions['*'] ?? []);
220
    }
221
222
    /**
223
     * @param mixed $xComponent
224
     * @param string $sMethodName
225
     *
226
     * @return void
227
     */
228
    public function setDiMethodAttributes($xComponent, string $sMethodName): void
229
    {
230
        $aDiOptions = $this->xOptions->diOptions();
231
        $this->setDiAttributes($xComponent, $aDiOptions[$sMethodName] ?? []);
232
    }
233
234
    /**
235
     * Call the specified method of the component using the specified array of arguments
236
     *
237
     * @param Target $xTarget The target of the Jaxon call
238
     *
239
     * @return void
240
     * @throws ReflectionException
241
     * @throws SetupException
242
     */
243
    public function call(Target $xTarget): void
244
    {
245
        $this->xTarget = $xTarget;
246
        $this->xComponent = $this->cdi->getTargetComponent($this->getClassName(), $xTarget);
247
248
        // Methods to call before processing the request
249
        $this->callHookMethods($this->xOptions->beforeMethods());
250
251
        // Call the request method
252
        $this->callMethod($xTarget->getMethodName(), $this->xTarget->args(), false);
253
254
        // Methods to call after processing the request
255
        $this->callHookMethods($this->xOptions->afterMethods());
256
    }
257
}
258