Completed
Pull Request — master (#7)
by Pavlo
03:11
created

Module::init()   C

Complexity

Conditions 8
Paths 8

Size

Total Lines 63
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 13
Bugs 2 Features 4
Metric Value
c 13
b 2
f 4
dl 0
loc 63
rs 6.8825
cc 8
eloc 25
nc 8
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 bool Use automatic locale resolving with browser response headers */
27
    public $browserLocaleRedirect = false;
28
29
    /** @var string Path to routing logic cache file */
30
    protected $cacheFile;
31
32
    /** @var string Current URL path */
33
    protected $requestURI;
34
35
    /**
36
     * Old generic "main_page" route callback searcher to match old logic.
37
     *
38
     * @return Route Default application route "/"
39
     */
40
    protected function findGenericDefaultAction()
41
    {
42
        $callback = null;
43
        // Set pointer to module
44
        $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...
45
        // If callback is passed  - function name
46
        if (is_callable($this->defaultModule)) {
47
            // Use it as main controller callback
48
            $callback = $this->defaultModule;
49
            // Consider as module identifier is passed
50
        } elseif ($module !== null) {
51
            // Try to find module universal controller action
52
            if (method_exists($module, self::CTR_BASE)) {
53
                $callback = $module->id . '#' . self::CTR_BASE;
54
            } else if (method_exists($module, self::CTR_CACHE_BASE)) {
55
                $callback = $module->id . '#' . self::CTR_CACHE_BASE;
56
            } elseif (method_exists($module, self::CTR_UNI)) {
57
                $callback = $module->id . '#' . self::CTR_UNI;
58
            } elseif (method_exists($module, self::CTR_CACHE_UNI)) {
59
                $callback = $module->id . '#' . self::CTR_CACHE_UNI;
60
            }
61
        }
62
63
        return new Route('/', $callback, 'main_page');
0 ignored issues
show
Bug introduced by
It seems like $callback defined by null on line 42 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...
64
    }
65
66
    /**
67
     * Module initialization.
68
     *
69
     * @param array $params Initialization parameters collection
70
     * @return bool Initialization result
71
     */
72
    public function init(array $params = array())
73
    {
74
        //[PHPCOMPRESSOR(remove,start)]
75
        // Create SamsonPHP routing table from loaded modules
76
        $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...
77
78
        // Generate web-application routes
79
        $routes = $rg->generate();
80
        $routes->add($this->findGenericDefaultAction());
81
82
        // Create cache marker
83
        $this->cacheFile = $routes->hash().'.php';
84
        // If we need to refresh cache
85
        if ($this->cache_refresh($this->cacheFile)) {
86
            $generator = new Structure($routes, new \samsonphp\generator\Generator());
87
            // Generate routing logic function
88
            $routerLogic = $generator->generate();
89
90
            // Store router logic in cache
91
            file_put_contents($this->cacheFile, '<?php '."\n".$routerLogic);
92
        }
93
94
        require($this->cacheFile);
95
        //[PHPCOMPRESSOR(remove,end)]
96
97
        // Set locale resolver mode
98
        SamsonLocale::$leaveDefaultLocale = $this->browserLocaleRedirect;
99
100
        // This should be change to receive path as a parameter on initialization
101
        $pathParts = array_values(array_filter(explode(Route::DELIMITER, $_SERVER['REQUEST_URI'])));
102
         // Parse URL and store locale found bug
103
        $localeFound = SamsonLocale::parseURL($pathParts, $this->browserLocaleRedirect);
0 ignored issues
show
Unused Code introduced by
The call to SamsonLocale::parseURL() has too many arguments starting with $this->browserLocaleRedirect.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
104
        // Gather URL path parts with removed locale placeholder
105
        $this->requestURI = implode(Route::DELIMITER, $pathParts);
106
107
        // Get localization data
108
        $current = SamsonLocale::current();
109
        $default = SamsonLocale::$defaultLocale;
110
111
        // Browser agent language detection logic
112
        if ($this->browserLocaleRedirect && !$localeFound) {
113
            // Redirect to browser language
114
            $lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);
115
            // Is browser language supported by application
116
            $langSupport = in_array($lang, SamsonLocale::get(), true);
117
118
            /**
119
             * If browser language header is supported by our web-application and we are already not on that locale
120
             * and current locale is not default.
121
             */
122
            if ($current === $default && $current !== $lang && $langSupport) {
123
                header('Location: http://'.$_SERVER['HTTP_HOST'].'/'.$lang.'/'.$this->requestURI);exit;
0 ignored issues
show
Coding Style introduced by
It is generally recommended to place each PHP statement on a line by itself.

Let’s take a look at an example:

// Bad
$a = 5; $b = 6; $c = 7;

// Good
$a = 5;
$b = 6;
$c = 7;
Loading history...
124
            } elseif (!$langSupport) {
125
                SamsonLocale::$leaveDefaultLocale = false;
126
            }
127
        }
128
129
        // Subscribe to samsonphp\core routing event
130
        \samsonphp\event\Event::subscribe('core.routing', array($this, 'router'));
131
132
        // Continue initialization
133
        return parent::init($params);
134
    }
135
136
    /** @see \samson\core\CompressableExternalModule::afterCompress() */
137
    public function afterCompress(&$obj = null, array &$code = null)
138
    {
139
        // Compress generated php code
140
        $obj->compress_php($this->cacheFile, $this, $code, '');
141
    }
142
143
    /**
144
     * Define if HTTP request is asynchronous.
145
     *
146
     * @return bool True if request is asynchronous
147
     */
148
    public function isAsynchronousRequest()
149
    {
150
        return isset($_SERVER['HTTP_SJSASYNC']) || isset($_POST['SJSASYNC']);
151
    }
152
153
    /**
154
     * Parse route parameters received from router logic function.
155
     *
156
     * @param callable $callback Route instance
157
     * @param array $receivedParameters Collection of parsed parameters
158
     * @return array Collection of route callback needed parameters
159
     */
160
    protected function parseParameters($callback, array $receivedParameters)
161
    {
162
        $parameters = array();
163
        // Parse callback signature and get parameters list
164
        if (is_callable($callback)) {
165
            $reflectionMethod = is_array($callback)
166
                ? new \ReflectionMethod($callback[0], $callback[1])
167
                : new \ReflectionFunction($callback);
168
            foreach ($reflectionMethod->getParameters() as $parameter) {
169
                $parameters[] = $parameter->getName();
170
            }
171
        }
172
173
        // Gather parsed route parameters in correct order
174
        $foundParameters = array();
175
        foreach ($parameters as $name) {
176
            // Add to parameters collection
177
            $parameterValue = &$receivedParameters[$name];
178
            if (isset($parameterValue) && isset($parameterValue{0})) {
179
                $foundParameters[] = $parameterValue;
180
            }
181
        }
182
        return $foundParameters;
183
    }
184
185
    /**
186
     * SamsonPHP core.routing event handler
187
     *
188
     * @param SystemInterface $core Pointer to core object
189
     * @param mixed $result Return value as routing result
190
     * @return bool Routing result
191
     */
192
    public function router(SystemInterface &$core, &$result)
193
    {
194
        //elapsed('Start routing');
195
        // Flag for matching SamsonPHP asynchronous requests
196
        $async = $this->isAsynchronousRequest();
197
        // Get HTTP request path
198
        $path = $this->requestURI;//$_SERVER['REQUEST_URI'];
199
        // Get HTTP request method
200
        $method = $_SERVER['REQUEST_METHOD'];
201
        // Prepend HTTP request type, true - asynchronous
202
        $method = ($async ? GenericRouteGenerator::ASYNC_PREFIX : '').$method;
203
204
        $result = false;
205
206
        // Remove first slash if present, add method to path, remove GET params, remove trailing slash
207
        $path = rtrim(ltrim(strtok($path, '?'), '/'), '/');
208
209
        /** @var mixed $routeMetadata Dispatching result route metadata */
210
        if (is_array($routeMetadata = call_user_func(Core::ROUTING_LOGIC_FUNCTION, $path, $method))) {
211
            // Check found route
212
            if (count($routeMetadata) === 3) {
213
                // Get callback info
214
                list($module, $method) = explode("#", $routeMetadata[2]);
215
                // Get module
216
                $module = $core->module($module);
217
                // Create callback
218
                $callback = array($module, $method);
219
    
220
                // Trigger found route event
221
                Event::fire(self::EVENT_ROUTE_FOUND, array(&$module, $callback));
222
    
223
                // Check if we have vaild callback
224
                if (is_callable($callback)) {
225
                    // Get object from callback and set it as current active core module
226
                    $core->active($module);
227
                    // Routing result
228
                    $result = call_user_func_array(
229
                        $callback,
230
                        $this->parseParameters($callback, $routeMetadata[1])
231
                    );
232
    
233
                    // If this is cached method
234
                    if (stripos($method, self::CACHE_PREFIX) !== false) {
235
                        // perform caching
236
                        $core->cached();
237
                    }
238
    
239
                    // If this route is asynchronous
240
                    if ($async) {
241
                        // Set async response
242
                        $core->async(true);
243
    
244
                        // If controller action has failed
245
                        if (!isset($result['status']) || !$result['status']) {
246
                            $result['message'] = "\n" . 'Event failed: ' . $routeMetadata[0];
247
                            $result['status'] = 0;
248
                        }
249
    
250
                        // Encode event result as json object
251
                        echo json_encode($result, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
252
    
253
                        // Mark as successful
254
                        $result = true;
255
                    }
256
                }
257
258
                // If no result is passed - consider success
259
                $result = $result !== false ? true : $result;
260
            }
261
        }
262
263
        //elapsed('Finished routing');
264
        // Return true or false depending on $result
265
        return $result;
266
    }
267
}
268