Completed
Push — master ( dbc4d9...4d3a24 )
by Thierry
01:44
created

CallableObject   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 320
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
dl 0
loc 320
rs 9.1199
c 0
b 0
f 0
wmc 41
lcom 1
cbo 4

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A getRegisteredObject() 0 24 4
A getClassName() 0 5 1
A getName() 0 11 2
A getJsName() 0 4 1
A getNamespace() 0 5 1
A getPath() 0 5 1
A getMethods() 0 20 5
C configure() 0 39 12
B getScript() 0 35 7
A isClass() 0 4 1
A hasMethod() 0 4 2
A call() 0 14 3

How to fix   Complexity   

Complex Class

Complex classes like CallableObject often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CallableObject, and based on these observations, apply Extract Interface, too.

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 $registeredObject = null;
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
    }
97
98
    /**
99
     * Return the registered callable object
100
     *
101
     * @return object
102
     */
103
    public function getRegisteredObject()
104
    {
105
        if($this->registeredObject == null)
106
        {
107
            $di = jaxon()->di();
108
            // Use the Reflection class to get the parameters of the constructor
109
            if(($constructor = $this->reflectionClass->getConstructor()) != null)
110
            {
111
                $parameters = $constructor->getParameters();
112
                $parameterInstances = [];
113
                foreach($parameters as $parameter)
114
                {
115
                    // Get the parameter instance from the DI
116
                    $parameterInstances[] = $di->get($parameter->getClass()->getName());
117
                }
118
                $this->registeredObject = $this->reflectionClass->newInstanceArgs($parameterInstances);
119
            }
120
            else
121
            {
122
                $this->registeredObject = $this->reflectionClass->newInstance();
123
            }
124
        }
125
        return $this->registeredObject;
126
    }
127
128
    /**
129
     * Return the class name of this callable object, without the namespace if any
130
     *
131
     * @return string
132
     */
133
    public function getClassName()
134
    {
135
        // Get the class name without the namespace.
136
        return $this->reflectionClass->getShortName();
137
    }
138
139
    /**
140
     * Return the name of this callable object
141
     *
142
     * @return string
143
     */
144
    public function getName()
145
    {
146
        // The class name without the namespace.
147
        $name = $this->reflectionClass->getShortName();
148
        // Append the classpath to the name
149
        if(($this->classpath))
150
        {
151
            $name = $this->classpath . '\\' . $name;
152
        }
153
        return $name;
154
    }
155
156
    /**
157
     * Return the javascript name of this callable object
158
     *
159
     * @return string
160
     */
161
    public function getJsName()
162
    {
163
        return str_replace('\\', $this->separator, $this->getName());
164
    }
165
166
    /**
167
     * Return the namespace of this callable object
168
     *
169
     * @return string
170
     */
171
    public function getNamespace()
172
    {
173
        // The namespace the class was registered with.
174
        return $this->namespace;
175
    }
176
177
    /**
178
     * Return the class path of this callable object
179
     *
180
     * @return string
181
     */
182
    public function getPath()
183
    {
184
        // The class path without the trailing separator.
185
        return $this->classpath;
186
    }
187
188
    /**
189
     * Return a list of methods of the callable object to export to javascript
190
     *
191
     * @return array
192
     */
193
    public function getMethods()
194
    {
195
        $aReturn = [];
196
        foreach($this->reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $xMethod)
197
        {
198
            $sMethodName = $xMethod->getShortName();
199
            // Don't take magic __call, __construct, __destruct methods
200
            if(strlen($sMethodName) > 2 && substr($sMethodName, 0, 2) == '__')
201
            {
202
                continue;
203
            }
204
            // Don't take excluded methods
205
            if(in_array($sMethodName, $this->aProtectedMethods))
206
            {
207
                continue;
208
            }
209
            $aReturn[] = $sMethodName;
210
        }
211
        return $aReturn;
212
    }
213
214
    /**
215
     * Set configuration options / call options for each method
216
     *
217
     * @param string        $sMethod            The name of the method
218
     * @param string        $sName                The name of the configuration option
219
     * @param string        $sValue                The value of the configuration option
220
     *
221
     * @return void
222
     */
223
    public function configure($sMethod, $sName, $sValue)
224
    {
225
        // Set the namespace
226
        if($sName == 'namespace')
227
        {
228
            if($sValue != '')
229
                $this->namespace = $sValue;
230
            return;
231
        }
232
        // Set the classpath
233
        if($sName == 'classpath')
234
        {
235
            if($sValue != '')
236
                $this->classpath = trim($sValue, '\\');
237
            return;
238
        }
239
        // Set the separator
240
        if($sName == 'separator')
241
        {
242
            if($sValue == '_' || $sValue == '.')
243
                $this->separator = $sValue;
244
            return;
245
        }
246
        // Set the excluded methods
247
        if($sName == 'protected')
248
        {
249
            if(is_array($sValue))
250
                $this->aProtectedMethods = array_merge($this->aProtectedMethods, $sValue);
251
            elseif(is_string($sValue))
252
                $this->aProtectedMethods[] = $sValue;
253
            return;
254
        }
255
256
        if(!isset($this->aConfiguration[$sMethod]))
257
        {
258
            $this->aConfiguration[$sMethod] = [];
259
        }
260
        $this->aConfiguration[$sMethod][$sName] = $sValue;
261
    }
262
263
    /**
264
     * Generate client side javascript code for calls to all methods exposed by this callable object
265
     *
266
     * @return string
267
     */
268
    public function getScript()
269
    {
270
        $sJaxonPrefix = $this->getOption('core.prefix.class');
271
        // "\" are replaced with the configured separator in the generated javascript code.
272
        $sClass = str_replace('\\', $this->separator, $this->getName());
273
        $aMethods = [];
274
275
        // Common options to be set on all methods
276
        $aCommonConfig = array_key_exists('*', $this->aConfiguration) ? $this->aConfiguration['*'] : [];
277
        foreach($this->reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $xMethod)
278
        {
279
            $sMethodName = $xMethod->getShortName();
280
            // Don't export magic __call, __construct, __destruct methods
281
            if(strlen($sMethodName) > 0 && substr($sMethodName, 0, 2) == '__')
282
            {
283
                continue;
284
            }
285
            // Don't export "protected" methods
286
            if(in_array($sMethodName, $this->aProtectedMethods))
287
            {
288
                continue;
289
            }
290
            // Specific options for this method
291
            $aMethodConfig = array_key_exists($sMethodName, $this->aConfiguration) ?
292
                array_merge($aCommonConfig, $this->aConfiguration[$sMethodName]) : $aCommonConfig;
293
            $aMethod = array('name' => $sMethodName, 'config' => $aMethodConfig);
294
            $aMethods[] = $aMethod;
295
        }
296
297
        return $this->render('jaxon::support/object.js', array(
298
            'sPrefix' => $sJaxonPrefix,
299
            'sClass' => $sClass,
300
            'aMethods' => $aMethods,
301
        ));
302
    }
303
304
    /**
305
     * Check if the specified class name matches the class name of the registered callable object
306
     *
307
     * @return boolean
308
     */
309
    public function isClass($sClass)
310
    {
311
        return ($this->reflectionClass->getName() === $sClass);
312
    }
313
314
    /**
315
     * Check if the specified method name is one of the methods of the registered callable object
316
     *
317
     * @param string        $sMethod            The name of the method to check
318
     *
319
     * @return boolean
320
     */
321
    public function hasMethod($sMethod)
322
    {
323
        return $this->reflectionClass->hasMethod($sMethod) || $this->reflectionClass->hasMethod('__call');
324
    }
325
326
    /**
327
     * Call the specified method of the registered callable object using the specified array of arguments
328
     *
329
     * @param string        $sMethod            The name of the method to call
330
     * @param array         $aArgs              The arguments to pass to the method
331
     *
332
     * @return void
333
     */
334
    public function call($sMethod, $aArgs)
335
    {
336
        if(!$this->hasMethod($sMethod))
337
        {
338
            return;
339
        }
340
        $reflectionMethod = $this->reflectionClass->getMethod($sMethod);
341
        $registeredObject = $this->getRegisteredObject();
342
        $response = $reflectionMethod->invokeArgs($registeredObject, $aArgs);
343
        if(($response))
344
        {
345
            $this->getResponseManager()->append($response);
346
        }
347
    }
348
}
349