Completed
Push — master ( db0467...89ffe1 )
by Vitaly
50s
created

Module   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 222
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 20
Bugs 4 Features 8
Metric Value
wmc 29
c 20
b 4
f 8
lcom 1
cbo 8
dl 0
loc 222
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
C findGenericDefaultAction() 0 25 7
B init() 0 36 2
A afterCompress() 0 5 1
A isAsynchronousRequest() 0 4 2
C parseParameters() 0 24 7
C router() 0 75 10
1
<?php
2
namespace samsonphp\router;
3
4
use samson\core\SamsonLocale;
5
use samsonframework\core\SystemInterface;
6
use samsonframework\routing\Core;
7
use samsonframework\routing\generator\Structure;
8
use samsonframework\routing\Route;
9
use samsonphp\event\Event;
10
11
/**
12
 * SamsonPHP Routing module implementation.
13
 *
14
 * @package samsonphp\router
15
 */
16
class Module extends \samson\core\CompressableExternalModule
0 ignored issues
show
Deprecated Code introduced by
The class samson\core\CompressableExternalModule has been deprecated with message: Just implement samsonframework\core\CompressInterface

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
17
{
18
    const EVENT_ROUTE_FOUND = 'router.route.found';
19
20
    /** @var string Module identifier */
21
    public $id = 'router';
22
23
    /** @var string Default controller module identifier */
24
    public $defaultModule = 'main';
25
26
    /** @var string Path to routing logic cache file */
27
    protected $cacheFile;
28
29
    /** @var string Current URL path */
30
    protected $requestURI;
31
32
    /**
33
     * Old generic "main_page" route callback searcher to match old logic.
34
     *
35
     * @return Route Default application route "/"
36
     */
37
    protected function findGenericDefaultAction()
38
    {
39
        $callback = null;
40
        // Set pointer to module
41
        $module = &$this->system->module_stack[$this->defaultModule];
0 ignored issues
show
Bug introduced by
Accessing module_stack on the interface samsonframework\core\SystemInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
42
        // If callback is passed  - function name
43
        if (is_callable($this->defaultModule)) {
44
            // Use it as main controller callback
45
            $callback = $this->defaultModule;
46
            // Consider as module identifier is passed
47
        } elseif ($module !== null) {
48
            // Try to find module universal controller action
49
            if (method_exists($module, self::CTR_BASE)) {
50
                $callback = $module->id . '#' . self::CTR_BASE;
51
            } else if (method_exists($module, self::CTR_CACHE_BASE)) {
52
                $callback = $module->id . '#' . self::CTR_CACHE_BASE;
53
            } elseif (method_exists($module, self::CTR_UNI)) {
54
                $callback = $module->id . '#' . self::CTR_UNI;
55
            } elseif (method_exists($module, self::CTR_CACHE_UNI)) {
56
                $callback = $module->id . '#' . self::CTR_CACHE_UNI;
57
            }
58
        }
59
60
        return new Route('/', $callback, 'main_page');
0 ignored issues
show
Bug introduced by
It seems like $callback defined by null on line 39 can also be of type null; however, samsonframework\routing\Route::__construct() does only seem to accept callable, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
61
    }
62
63
    /**
64
     * Module initialization.
65
     *
66
     * @param array $params Initialization parameters collection
67
     * @return bool Initialization result
68
     */
69
    public function init(array $params = array())
70
    {
71
        //[PHPCOMPRESSOR(remove,start)]
72
        // Create SamsonPHP routing table from loaded modules
73
        $rg = new GenericRouteGenerator($this->system->module_stack);
0 ignored issues
show
Bug introduced by
Accessing module_stack on the interface samsonframework\core\SystemInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
74
75
        // Generate web-application routes
76
        $routes = $rg->generate();
77
        $routes->add($this->findGenericDefaultAction());
78
79
        // Create cache marker
80
        $this->cacheFile = $routes->hash().'.php';
81
        // If we need to refresh cache
82
        if ($this->cache_refresh($this->cacheFile)) {
83
            $generator = new Structure($routes, new \samsonphp\generator\Generator());
84
            // Generate routing logic function
85
            $routerLogic = $generator->generate();
86
87
            // Store router logic in cache
88
            file_put_contents($this->cacheFile, '<?php '."\n".$routerLogic);
89
        }
90
91
        require($this->cacheFile);
92
        //[PHPCOMPRESSOR(remove,end)]
93
94
        // This should be change to receive path as a parameter on initialization
95
        $pathParts = explode(Route::DELIMITER, $_SERVER['REQUEST_URI']);
96
        SamsonLocale::parseURL($pathParts);
97
        $this->requestURI = implode(Route::DELIMITER, $pathParts);
98
99
        // Subscribe to samsonphp\core routing event
100
        \samsonphp\event\Event::subscribe('core.routing', array($this, 'router'));
101
102
        // Continue initialization
103
        return parent::init($params);
104
    }
105
106
    /** @see \samson\core\CompressableExternalModule::afterCompress() */
107
    public function afterCompress(&$obj = null, array &$code = null)
108
    {
109
        // Compress generated php code
110
        $obj->compress_php($this->cacheFile, $this, $code, '');
111
    }
112
113
    /**
114
     * Define if HTTP request is asynchronous.
115
     *
116
     * @return bool True if request is asynchronous
117
     */
118
    public function isAsynchronousRequest()
119
    {
120
        return isset($_SERVER['HTTP_SJSASYNC']) || isset($_POST['SJSASYNC']);
121
    }
122
123
    /**
124
     * Parse route parameters received from router logic function.
125
     *
126
     * @param callable $callback Route instance
127
     * @param array $receivedParameters Collection of parsed parameters
128
     * @return array Collection of route callback needed parameters
129
     */
130
    protected function parseParameters($callback, array $receivedParameters)
131
    {
132
        $parameters = array();
133
        // Parse callback signature and get parameters list
134
        if (is_callable($callback)) {
135
            $reflectionMethod = is_array($callback)
136
                ? new \ReflectionMethod($callback[0], $callback[1])
137
                : new \ReflectionFunction($callback);
138
            foreach ($reflectionMethod->getParameters() as $parameter) {
139
                $parameters[] = $parameter->getName();
140
            }
141
        }
142
143
        // Gather parsed route parameters in correct order
144
        $foundParameters = array();
145
        foreach ($parameters as $name) {
146
            // Add to parameters collection
147
            $parameterValue = &$receivedParameters[$name];
148
            if (isset($parameterValue) && isset($parameterValue{0})) {
149
                $foundParameters[] = $parameterValue;
150
            }
151
        }
152
        return $foundParameters;
153
    }
154
155
    /**
156
     * SamsonPHP core.routing event handler
157
     *
158
     * @param SystemInterface $core Pointer to core object
159
     * @param mixed $result Return value as routing result
160
     * @return bool Routing result
161
     */
162
    public function router(SystemInterface &$core, &$result)
163
    {
164
        //elapsed('Start routing');
165
        // Flag for matching SamsonPHP asynchronous requests
166
        $async = $this->isAsynchronousRequest();
167
        // Get HTTP request path
168
        $path = $this->requestURI;//$_SERVER['REQUEST_URI'];
169
        // Get HTTP request method
170
        $method = $_SERVER['REQUEST_METHOD'];
171
        // Prepend HTTP request type, true - asynchronous
172
        $method = ($async ? GenericRouteGenerator::ASYNC_PREFIX : '').$method;
173
174
        $result = false;
175
176
        // Remove first slash if present, add method to path, remove GET params, remove trailing slash
177
        $path = rtrim(ltrim(strtok($path, '?'), '/'), '/');
178
179
        /** @var mixed $routeMetadata Dispatching result route metadata */
180
        if (is_array($routeMetadata = call_user_func(Core::ROUTING_LOGIC_FUNCTION, $path, $method))) {
181
            // Check found route
182
            if (count($routeMetadata) === 3) {
183
                // Get callback info
184
                list($module, $method) = explode("#", $routeMetadata[2]);
185
                // Get module
186
                $module = $core->module($module);
187
                // Create callback
188
                $callback = array($module, $method);
189
    
190
                // Trigger found route event
191
                Event::fire(self::EVENT_ROUTE_FOUND, array(&$module, $callback));
192
    
193
                // Check if we have vaild callback
194
                if (is_callable($callback)) {
195
                    // Get object from callback and set it as current active core module
196
                    $core->active($module);
197
                    // Routing result
198
                    $result = call_user_func_array(
199
                        $callback,
200
                        $this->parseParameters($callback, $routeMetadata[1])
201
                    );
202
    
203
                    // If this is cached method
204
                    if (stripos($method, self::CACHE_PREFIX) !== false) {
205
                        // perform caching
206
                        $core->cached();
207
                    }
208
    
209
                    // If this route is asynchronous
210
                    if ($async) {
211
                        // Set async response
212
                        $core->async(true);
213
    
214
                        // If controller action has failed
215
                        if (!isset($result['status']) || !$result['status']) {
216
                            $result['message'] = "\n" . 'Event failed: ' . $routeMetadata[0];
217
                            $result['status'] = 0;
218
                        }
219
    
220
                        // Encode event result as json object
221
                        echo json_encode($result, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
222
    
223
                        // Mark as successful
224
                        $result = true;
225
                    }
226
                }
227
228
                // If no result is passed - consider success
229
                $result = $result !== false ? true : $result;
230
            }
231
        }
232
233
        //elapsed('Finished routing');
234
        // Return true or false depending on $result
235
        return $result;
236
    }
237
}
238