Completed
Push — master ( 022616...175363 )
by Alex
08:53
created

Application::__call()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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