Completed
Push — master ( c8fa0d...fd022a )
by Vitaly
03:04
created

Module::parseParameters()   C

Complexity

Conditions 7
Paths 9

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 24
rs 6.7273
cc 7
eloc 14
nc 9
nop 2
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
10
/**
11
 * SamsonPHP Routing module implementation.
12
 *
13
 * @package samsonphp\router
14
 */
15
class Module extends \samson\core\CompressableExternalModule
16
{
17
    /** @var string Module identifier */
18
    public $id = 'router';
19
20
    /** @var string Default controller module identifier */
21
    public $defaultModule = 'main';
22
23
    /** @var string Path to routing logic cache file */
24
    protected $cacheFile;
25
26
    /** @var string Current URL path */
27
    protected $requestURI;
28
29
    /**
30
     * Old generic "main_page" route callback searcher to match old logic.
31
     *
32
     * @return Route Default application route "/"
33
     */
34
    protected function findGenericDefaultAction()
35
    {
36
        $callback = null;
37
        // If callback is passed  - function name
38
        if (is_callable($this->defaultModule)) {
39
            // Use it as main controller callback
40
            $callback = $this->defaultModule;
41
            // Consider as module identifier is passed
42
        } elseif (isset($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...
43
            // Try to find module universal controller action
44
            $callback = $this->system->module_stack[$this->defaultModule]->id.'#'.self::CTR_UNI;
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
        }
46
47
        return new Route('/', $callback, 'main_page');
0 ignored issues
show
Bug introduced by
It seems like $callback defined by null on line 36 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...
48
    }
49
50
    /**
51
     * Module initialization.
52
     *
53
     * @param array $params Initialization parameters collection
54
     * @return bool Initialization result
55
     */
56
    public function init(array $params = array())
57
    {
58
        //[PHPCOMPRESSOR(remove,start)]
59
        // Create SamsonPHP routing table from loaded modules
60
        $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...
61
62
        // Generate web-application routes
63
        $routes = $rg->generate();
64
        $routes->add($this->findGenericDefaultAction());
65
66
        // Create cache marker
67
        $this->cacheFile = $routes->hash().'.php';
68
        // If we need to refresh cache
69
        if ($this->cache_refresh($this->cacheFile)) {
70
            $generator = new Structure($routes, new \samsonphp\generator\Generator());
71
            // Generate routing logic function
72
            $routerLogic = $generator->generate();
73
74
            // Store router logic in cache
75
            file_put_contents($this->cacheFile, '<?php '."\n".$routerLogic);
76
        }
77
78
        require($this->cacheFile);
79
        //[PHPCOMPRESSOR(remove,end)]
80
81
        // This should be change to receive path as a parameter on initialization
82
        $pathParts = explode(Route::DELIMITER, $_SERVER['REQUEST_URI']);
83
        SamsonLocale::parseURL($pathParts);
84
        $this->requestURI = implode(Route::DELIMITER, $pathParts);
85
86
        // Subscribe to samsonphp\core routing event
87
        \samsonphp\event\Event::subscribe('core.routing', array($this, 'router'));
88
89
        // Continue initialization
90
        return parent::init($params);
91
    }
92
93
    /** @see \samson\core\CompressableExternalModule::afterCompress() */
94
    public function afterCompress(&$obj = null, array &$code = null)
95
    {
96
        // Compress generated php code
97
        $obj->compress_php($this->cacheFile, $this, $code, '');
98
    }
99
100
    /**
101
     * Define if HTTP request is asynchronous.
102
     *
103
     * @return bool True if request is asynchronous
104
     */
105
    public function isAsynchronousRequest()
106
    {
107
        return $_SERVER['HTTP_ACCEPT'] == '*/*'
108
        || isset($_SERVER['HTTP_SJSASYNC'])
109
        || isset($_POST['SJSASYNC']);
110
    }
111
112
    /**
113
     * Parse route parameters received from router logic function.
114
     *
115
     * @param callable $callback Route instance
116
     * @param array $receivedParameters Collection of parsed parameters
117
     * @return array Collection of route callback needed parameters
118
     */
119
    protected function parseParameters($callback, array $receivedParameters)
120
    {
121
        $parameters = array();
122
        // Parse callback signature and get parameters list
123
        if (is_callable($callback)) {
124
            $reflectionMethod = is_array($callback)
125
                ? new \ReflectionMethod($callback[0], $callback[1])
126
                : new \ReflectionFunction($callback);
127
            foreach ($reflectionMethod->getParameters() as $parameter) {
128
                $parameters[] = $parameter->getName();
129
            }
130
        }
131
132
        // Gather parsed route parameters in correct order
133
        $foundParameters = array();
134
        foreach ($parameters as $name) {
135
            // Add to parameters collection
136
            $parameterValue = &$receivedParameters[$name];
137
            if (isset($parameterValue) && isset($parameterValue{0})) {
138
                $foundParameters[] = $parameterValue;
139
            }
140
        }
141
        return $foundParameters;
142
    }
143
144
    /**
145
     * SamsonPHP core.routing event handler
146
     *
147
     * @param SystemInterface $core Pointer to core object
148
     * @param mixed $result Return value as routing result
149
     * @return bool Routing result
150
     */
151
    public function router(SystemInterface &$core, &$result)
152
    {
153
        //elapsed('Start routing');
154
        // Flag for matching SamsonPHP asynchronous requests
155
        $async = $this->isAsynchronousRequest();
156
        // Get HTTP request path
157
        $path = $this->requestURI;//$_SERVER['REQUEST_URI'];
158
        // Get HTTP request method
159
        $method = $_SERVER['REQUEST_METHOD'];
160
        // Prepend HTTP request type, true - asynchronous
161
        $method = ($async ? GenericRouteGenerator::ASYNC_PREFIX : '').$method;
162
163
        $result = false;
164
165
        // Remove first slash if present, add method to path, remove GET params, remove trailing slash
166
        $path = rtrim(strtok(ltrim($path, '/'), '?'), '/');
167
168
        /** @var mixed $routeMetadata Dispatching result route metadata */
169
        if (is_array($routeMetadata = call_user_func(Core::ROUTING_LOGIC_FUNCTION, $path, $method))) {
170
            // Get callback info
171
            list($module, $method) = explode("#", $routeMetadata[2]);
172
            // Get module
173
            $module = $core->module($module);
174
            // Create callback
175
            $callback = array($module, $method);
176
177
            // Check if we have valid callback
178
            if (is_callable($callback)) {
179
                // Routing result
180
                $result = call_user_func_array(
181
                    $callback,
182
                    $this->parseParameters($callback, $routeMetadata[1])
183
                );
184
185
                // Get object from callback and set it as current active core module
186
                $core->active($module);
187
188
                // If this is cached method
189
                if (stripos($method, self::CACHE_PREFIX) !== false) {
190
                    // perform caching
191
                    $core->cached();
192
                }
193
194
                // If this route is asynchronous
195
                if ($async) {
196
                    // Set async response
197
                    $core->async(true);
198
199
                    // If controller action has failed
200
                    if (!isset($result['status']) || !$result['status']) {
201
                        $result['message'] = "\n" . 'Event failed: ' . $routeMetadata[0];
202
                        $result['status'] = 0;
203
                    }
204
205
                    // Encode event result as json object
206
                    echo json_encode($result, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
207
208
                    // Mark as successful
209
                    $result = true;
210
                }
211
            }
212
213
            // If no result is passed - consider success
214
            $result = $result !== false ? true : $result;
215
        }
216
217
        //elapsed('Finished routing');
218
        // Return true or false depending on $result
219
        return $result;
220
    }
221
}
222