Completed
Push — master ( 04f83a...b71153 )
by Alex
09:15
created

Application::buildRoute()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 4
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
namespace Mezon\Application;
3
4
use Mezon\Transport\RequestParamsInterface;
5
use Mezon\Transport\HttpRequestParams;
6
use Mezon\Router\Router;
7
8
/**
9
 * Class Application
10
 *
11
 * @package Mezon
12
 * @subpackage Application
13
 * @author Dodonov A.A.
14
 * @version v.1.0 (2019/08/13)
15
 * @copyright Copyright (c) 2019, aeon.org
16
 */
17
18
/**
19
 * Base class of the application
20
 */
21
class Application
22
{
23
24
    /**
25
     * Router object
26
     *
27
     * @var Router
28
     */
29
    private $router = null;
30
31
    /**
32
     * Params fetcher
33
     *
34
     * @var HttpRequestParams
35
     */
36
    private $requestParams = null;
37
38
    /**
39
     * Constructor
40
     */
41
    function __construct()
42
    {
43
        // getting application's actions
44
        $this->router = new Router();
45
46
        $this->router->fetchActions($this);
47
48
        $reflector = new \ReflectionClass(get_class($this));
49
        $classPath = dirname($reflector->getFileName());
50
51
        if (file_exists($classPath . '/conf/routes.php')) {
52
            $this->loadRoutesFromConfig($classPath . '/conf/routes.php');
53
        }
54
55
        if (file_exists($classPath . '/conf/routes.json')) {
56
            $this->loadRoutesFromConfig($classPath . '/conf/routes.json');
57
        }
58
    }
59
60
    /**
61
     * Method returns $this->requestParams and creates this object if necessery
62
     *
63
     * @return RequestParamsInterface
64
     */
65
    public function getRequestParamsFetcher(): RequestParamsInterface
66
    {
67
        if ($this->requestParams === null) {
68
            $this->requestParams = new HttpRequestParams($this->router);
69
        }
70
71
        return $this->requestParams;
72
    }
73
74
    /**
75
     * Method calls route and returns it's content
76
     */
77
    protected function callRoute()
78
    {
79
        $route = explode('/', trim(@$_GET['r'], '/'));
80
81
        if ($this->router === null) {
82
            throw (new \Exception('this->Router was not set', - 2));
83
        }
84
85
        return $this->router->callRoute($route);
86
    }
87
88
    /**
89
     * Method loads single route
90
     *
91
     * @param array $route
92
     *            Route settings
93
     */
94
    public function loadRoute(array $route): void
95
    {
96
        if (isset($route['route']) === false) {
97
            throw (new \Exception('Field "route" must be set'));
98
        }
99
        if (isset($route['callback']) === false) {
100
            throw (new \Exception('Field "callback" must be set'));
101
        }
102
103
        $callback = $route['callback'];
104
105
        if (is_array($route['callback'])) {
106
            $route['class'] = $route['callback'][0];
107
            $route['callback'] = $route['callback'][1];
108
        } else {
109
            $class = isset($route['class']) ? new $route['class']() : $this;
110
            $callback = [
111
                $class,
112
                $callback
113
            ];
114
        }
115
        $this->router->addRoute($route['route'], $callback, isset($route['method']) ? $route['method'] : 'GET');
116
    }
117
118
    /**
119
     * Method loads routes
120
     *
121
     * @param array $routes
122
     *            List of routes
123
     */
124
    public function loadRoutes(array $routes): void
125
    {
126
        foreach ($routes as $route) {
127
            $this->loadRoute($route);
128
        }
129
    }
130
131
    /**
132
     * Method loads routes from config file in *.php or *.json format
133
     *
134
     * @param string $configPath
135
     *            Path of the config for routes
136
     */
137
    public function loadRoutesFromConfig(string $configPath): void
138
    {
139
        if (file_exists($configPath)) {
140
            if (substr($configPath, - 5) === '.json') {
141
                // load config from json
142
                $routes = json_decode(file_get_contents($configPath), true);
143
            } else {
144
                // loadconfig from php
145
                $routes = (include ($configPath));
146
            }
147
            $this->loadRoutes($routes);
148
        } else {
149
            throw (new \Exception('Route ' . $configPath . ' was not found', 1));
150
        }
151
    }
152
153
    /**
154
     * Method loads list of configs
155
     *
156
     * @param array $configPaths
157
     *            paths to config files
158
     */
159
    public function loadRoutesFromConfigs(array $configPaths): void
160
    {
161
        foreach ($configPaths as $configPath) {
162
            $this->loadRoutesFromConfig($configPath);
163
        }
164
    }
165
166
    /**
167
     * Method loads routes from the directory
168
     *
169
     * @param string $directory
170
     *            path to the directory. Scanninng is recursive.
171
     */
172
    public function loadRoutesFromDirectory(string $directory): void
173
    {
174
        $paths = scandir($directory);
175
176
        foreach ($paths as $path) {
177
            if (is_file($directory . '/' . $path)) {
178
                $this->loadRoutesFromConfig($directory . '/' . $path);
179
            }
180
        }
181
    }
182
183
    /**
184
     * Method processes exception
185
     *
186
     * @param \Exception $e
187
     *            Exception object to be formatted
188
     */
189
    public function handleException(\Exception $e): void
190
    {
191
        print('<pre>' . $e->getMessage() . '<br/>' . implode('<br/>', $this->formatCallStack($e)));
192
    }
193
194
    /**
195
     * Running application
196
     */
197
    public function run(): void
198
    {
199
        try {
200
            print($this->callRoute());
201
        } catch (\Exception $e) {
202
            $this->handleException($e);
203
        }
204
    }
205
206
    /**
207
     * Allowing to call methods added on the fly
208
     *
209
     * @param string $method
210
     *            Method to be called
211
     * @param array $args
212
     *            Arguments
213
     * @return mixed Result of the call
214
     */
215
    public function __call(string $method, array $args)
216
    {
217
        if (isset($this->$method)) {
218
            $function = $this->$method;
219
220
            return call_user_func_array($function, $args);
221
        } else {
222
            throw (new \Exception('Method ' . $method . ' was not found in the application ' . get_class($this)));
223
        }
224
    }
225
226
    /**
227
     * Method redirects user to another page
228
     *
229
     * @param string $url
230
     *            New page
231
     */
232
    public function redirectTo($url): void
233
    {
234
        // @codeCoverageIgnoreStart
235
        if (isset($_GET['redirect-to'])) {
236
            $url = str_replace('{redirect-to}', urldecode($_GET['redirect-to']), $url);
237
        }
238
239
        header('Location: ' . $url);
240
        exit(0);
241
        // @codeCoverageIgnoreEnd
242
    }
243
244
    /**
245
     * Method validates that route exists
246
     *
247
     * @param string $route
248
     *            route
249
     * @return bool true if the route exists
250
     */
251
    public function routeExists(string $route): bool
252
    {
253
        return $this->router->routeExists($route);
254
    }
255
256
    /**
257
     * Method returns router
258
     *
259
     * @return \Mezon\Router\Router router
260
     */
261
    public function getRouter(): \Mezon\Router\Router
262
    {
263
        return $this->router;
264
    }
265
266
    /**
267
     * Formatting call stack
268
     *
269
     * @param mixed $e
270
     *            Exception object
271
     */
272
    protected function formatCallStack($e): array
273
    {
274
        $stack = $e->getTrace();
275
276
        foreach ($stack as $i => $call) {
277
            $stack[$i] = (@$call['file'] == '' ? 'lambda : ' : @$call['file'] . ' (' . $call['line'] . ') : ') .
278
                (@$call['class'] == '' ? '' : $call['class'] . '->') . $call['function'];
279
        }
280
281
        return $stack;
282
    }
283
284
    /**
285
     * Method builds route data
286
     *
287
     * @param string $route
288
     *            route
289
     * @param string $method
290
     *            HTTP method
291
     * @param object $handler
292
     *            object wich handles request
293
     * @param string $function
294
     *            controller's function name
295
     * @return array built route data
296
     */
297
    public static function buildRoute(string $route, string $method, object $handler, string $function): array
298
    {
299
        return [
300
            'route' => $route,
301
            'method' => $method,
302
            'callback' => [
303
                $handler,
304
                $function
305
            ]
306
        ];
307
    }
308
}
309