Completed
Push — master ( 7b173b...f911b9 )
by Thierry
01:48
created

CallableObject::makeCallable()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 4
nop 0
dl 0
loc 24
rs 9.536
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * CallableObject.php - Jaxon callable object
5
 *
6
 * This class stores a reference to an object whose methods can be called from
7
 * the client via an Jaxon request
8
 *
9
 * The Jaxon plugin manager will call <CallableObject->getClientScript> so that
10
 * stub functions can be generated and sent to the browser.
11
 *
12
 * @package jaxon-core
13
 * @author Jared White
14
 * @author J. Max Wilson
15
 * @author Joseph Woolley
16
 * @author Steffen Konerow
17
 * @author Thierry Feuzeu <[email protected]>
18
 * @copyright Copyright (c) 2005-2007 by Jared White & J. Max Wilson
19
 * @copyright Copyright (c) 2008-2010 by Joseph Woolley, Steffen Konerow, Jared White  & J. Max Wilson
20
 * @copyright 2016 Thierry Feuzeu <[email protected]>
21
 * @license https://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
22
 * @link https://github.com/jaxon-php/jaxon-core
23
 */
24
25
namespace Jaxon\Request\Support;
26
27
use Jaxon\Request\Request;
28
29
class CallableObject
30
{
31
    use \Jaxon\Utils\Traits\Config;
32
    use \Jaxon\Utils\Traits\Manager;
33
    use \Jaxon\Utils\Traits\Template;
34
35
    /**
36
     * A reference to the callable object the user has registered
37
     *
38
     * @var object
39
     */
40
    private $callableObject;
41
42
    /**
43
     * The reflection class of the user registered callable object
44
     *
45
     * @var \ReflectionClass
46
     */
47
    private $reflectionClass;
48
49
    /**
50
     * A list of methods of the user registered callable object the library must not export to javascript
51
     *
52
     * @var array
53
     */
54
    private $aProtectedMethods;
55
56
    /**
57
     * The namespace where the callable object class is defined
58
     *
59
     * @var string
60
     */
61
    private $namespace = '';
62
63
    /**
64
     * The path to the directory where the callable object class is defined, starting from the namespace root
65
     *
66
     * @var string
67
     */
68
    private $classpath = '';
69
70
    /**
71
     * The character to use as separator in javascript class names
72
     *
73
     * @var string
74
     */
75
    private $separator = '.';
76
77
    /**
78
     * An associative array that will contain configuration options for zero or more of the objects methods
79
     *
80
     * These configuration options will define the call options for each request.
81
     * The call options will be passed to the client browser when the function stubs are generated.
82
     *
83
     * @var array
84
     */
85
    private $aConfiguration;
86
87
    /**
88
     * The class constructor
89
     *
90
     * @param string            $sCallable               The callable object instance or class name
91
     *
92
     */
93
    public function __construct($sCallable)
94
    {
95
        $this->reflectionClass = new \ReflectionClass($sCallable);
96
        $this->callableObject = null;
97
        $this->aConfiguration = [];
98
        // By default, no method is "protected"
99
        $this->aProtectedMethods = [];
100
    }
101
102
    /**
103
     * Set a user registered callable object.
104
     *
105
     * @return void
106
     */
107
    private function makeCallable()
108
    {
109
        // Create an instance of the registered class
110
        if($this->callableObject == null)
111
        {
112
            $di = jaxon()->di();
113
            // Use the Reflection class to get the parameters of the constructor
114
            if(($constructor = $this->reflectionClass->getConstructor()) != null)
115
            {
116
                $parameters = $constructor->getParameters();
117
                $parameterInstances = [];
118
                foreach($parameters as $parameter)
119
                {
120
                    // Get the parameter instance from the DI
121
                    $parameterInstances[] = $di->get($parameter->getClass()->getName());
122
                }
123
                $this->callableObject = $this->reflectionClass->newInstanceArgs($parameterInstances);
124
            }
125
            else
126
            {
127
                $this->callableObject = $this->reflectionClass->newInstance();
128
            }
129
        }
130
    }
131
132
    /**
133
     * Return the registered callable object
134
     *
135
     * @return object
136
     */
137
    public function getRegisteredObject()
138
    {
139
        if($this->callableObject == null)
140
        {
141
            $this->makeCallable();
142
        }
143
        return $this->callableObject;
144
    }
145
146
    /**
147
     * Return the class name of this callable object, without the namespace if any
148
     *
149
     * @return string
150
     */
151
    public function getClassName()
152
    {
153
        // Get the class name without the namespace.
154
        return $this->reflectionClass->getShortName();
155
    }
156
157
    /**
158
     * Return the name of this callable object
159
     *
160
     * @return string
161
     */
162
    public function getName()
163
    {
164
        // The class name without the namespace.
165
        $name = $this->reflectionClass->getShortName();
166
        // Append the classpath to the name
167
        if(($this->classpath))
168
        {
169
            $name = $this->classpath . '\\' . $name;
170
        }
171
        return $name;
172
    }
173
174
    /**
175
     * Return the javascript name of this callable object
176
     *
177
     * @return string
178
     */
179
    public function getJsName()
180
    {
181
        return str_replace('\\', $this->separator, $this->getName());
182
    }
183
184
    /**
185
     * Return the namespace of this callable object
186
     *
187
     * @return string
188
     */
189
    public function getNamespace()
190
    {
191
        // The namespace the class was registered with.
192
        return $this->namespace;
193
    }
194
195
    /**
196
     * Return the class path of this callable object
197
     *
198
     * @return string
199
     */
200
    public function getPath()
201
    {
202
        // The class path without the trailing separator.
203
        return $this->classpath;
204
    }
205
206
    /**
207
     * Return a list of methods of the callable object to export to javascript
208
     *
209
     * @return array
210
     */
211
    public function getMethods()
212
    {
213
        $aReturn = [];
214
        foreach($this->reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $xMethod)
215
        {
216
            $sMethodName = $xMethod->getShortName();
217
            // Don't take magic __call, __construct, __destruct methods
218
            if(strlen($sMethodName) > 2 && substr($sMethodName, 0, 2) == '__')
219
            {
220
                continue;
221
            }
222
            // Don't take excluded methods
223
            if(in_array($sMethodName, $this->aProtectedMethods))
224
            {
225
                continue;
226
            }
227
            $aReturn[] = $sMethodName;
228
        }
229
        return $aReturn;
230
    }
231
232
    /**
233
     * Set configuration options / call options for each method
234
     *
235
     * @param string        $sMethod            The name of the method
236
     * @param string        $sName                The name of the configuration option
237
     * @param string        $sValue                The value of the configuration option
238
     *
239
     * @return void
240
     */
241
    public function configure($sMethod, $sName, $sValue)
242
    {
243
        // Set the namespace
244
        if($sName == 'namespace')
245
        {
246
            if($sValue != '')
247
                $this->namespace = $sValue;
248
            return;
249
        }
250
        // Set the classpath
251
        if($sName == 'classpath')
252
        {
253
            if($sValue != '')
254
                $this->classpath = trim($sValue, '\\');
255
            return;
256
        }
257
        // Set the separator
258
        if($sName == 'separator')
259
        {
260
            if($sValue == '_' || $sValue == '.')
261
                $this->separator = $sValue;
262
            return;
263
        }
264
        // Set the excluded methods
265
        if($sName == 'protected')
266
        {
267
            if(is_array($sValue))
268
                $this->aProtectedMethods = array_merge($this->aProtectedMethods, $sValue);
269
            elseif(is_string($sValue))
270
                $this->aProtectedMethods[] = $sValue;
271
            return;
272
        }
273
274
        if(!isset($this->aConfiguration[$sMethod]))
275
        {
276
            $this->aConfiguration[$sMethod] = [];
277
        }
278
        $this->aConfiguration[$sMethod][$sName] = $sValue;
279
    }
280
281
    /**
282
     * Generate client side javascript code for calls to all methods exposed by this callable object
283
     *
284
     * @return string
285
     */
286
    public function getScript()
287
    {
288
        $sJaxonPrefix = $this->getOption('core.prefix.class');
289
        // "\" are replaced with the configured separator in the generated javascript code.
290
        $sClass = str_replace('\\', $this->separator, $this->getName());
291
        $aMethods = [];
292
293
        // Common options to be set on all methods
294
        $aCommonConfig = array_key_exists('*', $this->aConfiguration) ? $this->aConfiguration['*'] : [];
295
        foreach($this->reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $xMethod)
296
        {
297
            $sMethodName = $xMethod->getShortName();
298
            // Don't export magic __call, __construct, __destruct methods
299
            if(strlen($sMethodName) > 0 && substr($sMethodName, 0, 2) == '__')
300
            {
301
                continue;
302
            }
303
            // Don't export "protected" methods
304
            if(in_array($sMethodName, $this->aProtectedMethods))
305
            {
306
                continue;
307
            }
308
            // Specific options for this method
309
            $aMethodConfig = array_key_exists($sMethodName, $this->aConfiguration) ?
310
                array_merge($aCommonConfig, $this->aConfiguration[$sMethodName]) : $aCommonConfig;
311
            $aMethod = array('name' => $sMethodName, 'config' => $aMethodConfig);
312
            $aMethods[] = $aMethod;
313
        }
314
315
        return $this->render('jaxon::support/object.js', array(
316
            'sPrefix' => $sJaxonPrefix,
317
            'sClass' => $sClass,
318
            'aMethods' => $aMethods,
319
        ));
320
    }
321
322
    /**
323
     * Check if the specified class name matches the class name of the registered callable object
324
     *
325
     * @return boolean
326
     */
327
    public function isClass($sClass)
328
    {
329
        return ($this->reflectionClass->getName() === $sClass);
330
    }
331
332
    /**
333
     * Check if the specified method name is one of the methods of the registered callable object
334
     *
335
     * @param string        $sMethod            The name of the method to check
336
     *
337
     * @return boolean
338
     */
339
    public function hasMethod($sMethod)
340
    {
341
        return $this->reflectionClass->hasMethod($sMethod) || $this->reflectionClass->hasMethod('__call');
342
    }
343
344
    /**
345
     * Call the specified method of the registered callable object using the specified array of arguments
346
     *
347
     * @param string        $sMethod            The name of the method to call
348
     * @param array         $aArgs              The arguments to pass to the method
349
     *
350
     * @return void
351
     */
352
    public function call($sMethod, $aArgs)
353
    {
354
        if(!$this->hasMethod($sMethod))
355
        {
356
            return;
357
        }
358
        $reflectionMethod = $this->reflectionClass->getMethod($sMethod);
359
        $callableObject = $this->getRegisteredObject();
360
        $response = $reflectionMethod->invokeArgs($callableObject, $aArgs);
361
        if(($response))
362
        {
363
            $this->getResponseManager()->append($response);
364
        }
365
    }
366
}
367