Completed
Push — master ( 9de2bb...0e3488 )
by Thierry
04:07
created

CallableObject   B

Complexity

Total Complexity 42

Size/Duplication

Total Lines 258
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
dl 0
loc 258
rs 8.295
c 0
b 0
f 0
wmc 42
lcom 1
cbo 7

9 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 25 5
A getName() 0 4 1
C register() 0 44 14
A generateHash() 0 10 2
B getScript() 0 29 5
B canProcessRequest() 0 16 6
B processRequest() 0 26 5
A getCallableObject() 0 8 2
A getRegisteredObject() 0 6 2

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 plugin
5
 *
6
 * This class registers user defined callable objects, generates client side javascript code,
7
 * and calls their methods on user request
8
 *
9
 * @package jaxon-core
10
 * @author Jared White
11
 * @author J. Max Wilson
12
 * @author Joseph Woolley
13
 * @author Steffen Konerow
14
 * @author Thierry Feuzeu <[email protected]>
15
 * @copyright Copyright (c) 2005-2007 by Jared White & J. Max Wilson
16
 * @copyright Copyright (c) 2008-2010 by Joseph Woolley, Steffen Konerow, Jared White  & J. Max Wilson
17
 * @copyright 2016 Thierry Feuzeu <[email protected]>
18
 * @license https://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
19
 * @link https://github.com/jaxon-php/jaxon-core
20
 */
21
22
namespace Jaxon\Request\Plugin;
23
24
use Jaxon\Jaxon;
25
use Jaxon\Plugin\Request as RequestPlugin;
26
27
class CallableObject extends RequestPlugin
28
{
29
    use \Jaxon\Utils\Traits\Config;
30
    use \Jaxon\Utils\Traits\Manager;
31
    use \Jaxon\Utils\Traits\Validator;
32
    use \Jaxon\Utils\Traits\Translator;
33
34
    /**
35
     * The registered callable objects
36
     *
37
     * @var array
38
     */
39
    protected $aCallableObjects;
40
41
    /**
42
     * The classpaths of the registered callable objects
43
     *
44
     * @var array
45
     */
46
    protected $aClassPaths;
47
48
    /**
49
     * The value of the class parameter of the incoming Jaxon request
50
     *
51
     * @var string
52
     */
53
    protected $sRequestedClass;
54
    
55
    /**
56
     * The value of the method parameter of the incoming Jaxon request
57
     *
58
     * @var string
59
     */
60
    protected $sRequestedMethod;
61
62
    public function __construct()
0 ignored issues
show
Coding Style introduced by
__construct uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
__construct uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
63
    {
64
        $this->aCallableObjects = array();
65
        $this->aClassPaths = array();
66
67
        $this->sRequestedClass = null;
68
        $this->sRequestedMethod = null;
69
70
        if(!empty($_GET['jxncls']))
71
        {
72
            $this->sRequestedClass = $_GET['jxncls'];
73
        }
74
        if(!empty($_GET['jxnmthd']))
75
        {
76
            $this->sRequestedMethod = $_GET['jxnmthd'];
77
        }
78
        if(!empty($_POST['jxncls']))
79
        {
80
            $this->sRequestedClass = $_POST['jxncls'];
81
        }
82
        if(!empty($_POST['jxnmthd']))
83
        {
84
            $this->sRequestedMethod = $_POST['jxnmthd'];
85
        }
86
    }
87
88
    /**
89
     * Return the name of this plugin
90
     *
91
     * @return string
92
     */
93
    public function getName()
94
    {
95
        return 'CallableObject';
96
    }
97
98
    /**
99
     * Register a user defined callable object
100
     *
101
     * @param array         $aArgs                An array containing the callable object specification
102
     *
103
     * @return array
104
     */
105
    public function register($aArgs)
106
    {
107
        if(count($aArgs) > 1)
108
        {
109
            $sType = $aArgs[0];
110
111
            if($sType == Jaxon::CALLABLE_OBJECT)
112
            {
113
                $xCallableObject = $aArgs[1];
114
115
                if(!is_object($xCallableObject) && !is_string($xCallableObject))
116
                {
117
                    throw new \Jaxon\Exception\Error($this->trans('errors.objects.instance'));
118
                }
119
                if(is_string($xCallableObject) && !class_exists($xCallableObject))
120
                {
121
                    throw new \Jaxon\Exception\Error($this->trans('errors.objects.instance'));
122
                }
123
                if(!($xCallableObject instanceof \Jaxon\Request\Support\CallableObject))
124
                {
125
                    $xCallableObject = new \Jaxon\Request\Support\CallableObject($xCallableObject);
126
                }
127
                if(count($aArgs) > 2 && is_array($aArgs[2]))
128
                {
129
                    foreach($aArgs[2] as $sKey => $aValue)
130
                    {
131
                        foreach($aValue as $sName => $sValue)
132
                        {
133
                            if($sName == 'classpath' && $sValue != '')
134
                                $this->aClassPaths[] = $sValue;
135
                            $xCallableObject->configure($sKey, $sName, $sValue);
136
                        }
137
                    }
138
                }
139
                // Replace all separators ('.' and '_') with antislashes.
140
                $sClassName = str_replace(['.', '_'], ['\\', '\\'], $xCallableObject->getName());
141
                $this->aCallableObjects[trim($sClassName, '\\')] = $xCallableObject;
142
143
                return true;
144
            }
145
        }
146
147
        return false;
148
    }
149
150
    /**
151
     * Generate a hash for the registered callable objects
152
     *
153
     * @return string
154
     */
155
    public function generateHash()
156
    {
157
        $sHash = '';
158
        foreach($this->aCallableObjects as $xCallableObject)
159
        {
160
            $sHash .= $xCallableObject->getName();
161
            $sHash .= implode('|', $xCallableObject->getMethods());
162
        }
163
        return md5($sHash);
164
    }
165
166
    /**
167
     * Generate client side javascript code for the registered callable objects
168
     *
169
     * @return string
170
     */
171
    public function getScript()
172
    {
173
        $sJaxonPrefix = $this->getOption('core.prefix.class');
174
        // Generate code for javascript classes declaration
175
        $code = '';
176
        $classes = array();
177
        foreach($this->aClassPaths as $sClassPath)
178
        {
179
            $offset = 0;
180
            $sClassPath .= '.Null'; // This is a sentinel. The last token is not processed in the while loop.
181
            while(($dotPosition = strpos($sClassPath, '.', $offset)) !== false)
182
            {
183
                $class = substr($sClassPath, 0, $dotPosition);
184
                // Generate code for this class
185
                if(!array_key_exists($class, $classes))
186
                {
187
                    $code .= "$sJaxonPrefix$class = {};\n";
188
                    $classes[$class] = $class;
189
                }
190
                $offset = $dotPosition + 1;
191
            }
192
        }
193
194
        foreach($this->aCallableObjects as $xCallableObject)
195
        {
196
            $code .= $xCallableObject->getScript();
197
        }
198
        return $code;
199
    }
200
201
    /**
202
     * Check if this plugin can process the incoming Jaxon request
203
     *
204
     * @return boolean
205
     */
206
    public function canProcessRequest()
207
    {
208
        // Check the validity of the class name
209
        if(($this->sRequestedClass) && !$this->validateClass($this->sRequestedClass))
210
        {
211
            $this->sRequestedClass = null;
212
            $this->sRequestedMethod = null;
213
        }
214
        // Check the validity of the method name
215
        if(($this->sRequestedMethod) && !$this->validateMethod($this->sRequestedMethod))
216
        {
217
            $this->sRequestedClass = null;
218
            $this->sRequestedMethod = null;
219
        }
220
        return ($this->sRequestedClass != null && $this->sRequestedMethod != null);
221
    }
222
223
    /**
224
     * Process the incoming Jaxon request
225
     *
226
     * @return boolean
227
     */
228
    public function processRequest()
229
    {
230
        if(!$this->canProcessRequest())
231
            return false;
232
233
        $aArgs = $this->getRequestManager()->process();
234
235
        // Register an instance of the requested class, if it isn't yet
236
        if(!($xCallableObject = $this->getCallableObject($this->sRequestedClass)))
237
        {
238
            $this->getPluginManager()->registerClass($this->sRequestedClass);
239
            $xCallableObject = $this->getCallableObject($this->sRequestedClass);
240
        }
241
242
        // Find the requested method
243
        if(!$xCallableObject || !$xCallableObject->hasMethod($this->sRequestedMethod))
244
        {
245
            // Unable to find the requested object or method
246
            throw new \Jaxon\Exception\Error($this->trans('errors.objects.invalid',
247
                array('class' => $this->sRequestedClass, 'method' => $this->sRequestedMethod)));
248
        }
249
250
        // Call the requested method
251
        $xCallableObject->call($this->sRequestedMethod, $aArgs);
252
        return true;
253
    }
254
255
    /**
256
     * Find a callable object by class name
257
     *
258
     * @param string        $sClassName            The class name of the callable object
259
     *
260
     * @return object
261
     */
262
    public function getCallableObject($sClassName)
263
    {
264
        // Replace all separators ('.' and '_') with antislashes, and remove the antislashes
265
        // at the beginning and the end of the class name.
266
        $sClassName = trim(str_replace(['.', '_'], ['\\', '\\'], (string)$sClassName), '\\');
267
        return array_key_exists($sClassName, $this->aCallableObjects) ?
268
            $this->aCallableObjects[$sClassName] : null;
269
    }
270
271
    /**
272
     * Find a user registered callable object by class name
273
     *
274
     * @param string        $sClassName            The class name of the callable object
275
     *
276
     * @return object
277
     */
278
    public function getRegisteredObject($sClassName)
279
    {
280
        // Get the corresponding callable object
281
        $xCallableObject = $this->getCallableObject($sClassName);
282
        return ($xCallableObject) ? $xCallableObject->getRegisteredObject() : null;
283
    }
284
}
285