Completed
Push — master ( f8712b...8ebca4 )
by Jared
04:10
created

Application::handle()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 3
1
<?php
2
3
/**
4
 * @author Jared King <[email protected]>
5
 *
6
 * @see http://jaredtking.com
7
 *
8
 * @copyright 2015 Jared King
9
 * @license MIT
10
 */
11
12
namespace Infuse;
13
14
use Infuse\Middleware\DispatchMiddleware;
15
use Pimple\Container;
16
use SplStack;
17
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
18
use Symfony\Component\HttpKernel\HttpKernelInterface;
19
20
class Application extends Container implements HttpKernelInterface
21
{
22
    const ENV_PRODUCTION = 'production';
23
    const ENV_DEVELOPMENT = 'development';
24
    const ENV_TEST = 'test';
25
26
    /**
27
     * @staticvar array
28
     */
29
    protected static $baseConfig = [
30
        'app' => [
31
            'title' => 'Infuse',
32
            'ssl' => false,
33
            'port' => 80,
34
        ],
35
        'services' => [
36
            // these services are required but can be overriden
37
            'exception_handler' => 'Infuse\Services\ExceptionHandler',
38
            'locale' => 'Infuse\Services\Locale',
39
            'logger' => 'Infuse\Services\Logger',
40
            'method_not_allowed_handler' => 'Infuse\Services\MethodNotAllowedHandler',
41
            'not_found_handler' => 'Infuse\Services\NotFoundHandler',
42
            'php_error_handler' => 'Infuse\Services\PhpErrorHandler',
43
            'router' => 'Infuse\Services\Router',
44
            'route_resolver' => 'Infuse\Services\RouteResolver',
45
            'view_engine' => 'Infuse\Services\ViewEngine',
46
        ],
47
        'i18n' => [
48
            'locale' => 'en',
49
        ],
50
        'console' => [
51
            'commands' => [],
52
        ],
53
    ];
54
55
    /**
56
     * @staticvar Application
57
     */
58
    private static $default;
59
60
    /**
61
     * @var SplStack
62
     */
63
    private $middleware;
64
65
    /**
66
     * @param array  $settings
67
     * @param string $environment
68
     */
69
    public function __construct(array $settings = [], $environment = self::ENV_DEVELOPMENT)
70
    {
71
        parent::__construct();
72
73
        if (!defined('INFUSE_BASE_DIR')) {
74
            die('INFUSE_BASE_DIR has not been defined!');
75
        }
76
77
        /* Load Configuration */
78
79
        $configWithDirs = [
80
            'dirs' => [
81
                'app' => INFUSE_BASE_DIR.'/app',
82
                'assets' => INFUSE_BASE_DIR.'/assets',
83
                'public' => INFUSE_BASE_DIR.'/public',
84
                'temp' => INFUSE_BASE_DIR.'/temp',
85
                'views' => INFUSE_BASE_DIR.'/views',
86
            ],
87
        ];
88
89
        $settings = array_replace_recursive(
90
            static::$baseConfig,
91
            $configWithDirs,
92
            $settings);
93
94
        $config = new Config($settings);
95
        $this['config'] = $config;
96
        $this['environment'] = $environment;
97
98
        /* Base URL */
99
100
        $this['base_url'] = function () use ($config) {
101
            $url = (($config->get('app.ssl')) ? 'https' : 'http').'://';
102
            $url .= $config->get('app.hostname');
103
            $port = $config->get('app.port');
104
            $url .= ((!in_array($port, [0, 80, 443])) ? ':'.$port : '').'/';
105
106
            return $url;
107
        };
108
109
        /* Services  */
110
111
        foreach ($config->get('services') as $name => $class) {
112
            $this[$name] = new $class($this);
113
        }
114
115
        // set the last created app instance
116
        self::$default = $this;
117
    }
118
119
    /**
120
     * Gets the last created Application instance.
121
     *
122
     * @return Application
123
     */
124
    public static function getDefault()
125
    {
126
        return self::$default;
127
    }
128
129
    ////////////////////////
130
    // ROUTING
131
    ////////////////////////
132
133
    /**
134
     * Adds a handler to the routing table for a given GET route.
135
     *
136
     * @param string $route   path pattern
137
     * @param mixed  $handler route handler
138
     *
139
     * @return self
140
     */
141
    public function get($route, $handler)
142
    {
143
        $this['router']->get($route, $handler);
144
145
        return $this;
146
    }
147
148
    /**
149
     * Adds a handler to the routing table for a given POST route.
150
     *
151
     * @param string $route   path pattern
152
     * @param mixed  $handler route handler
153
     *
154
     * @return self
155
     */
156
    public function post($route, $handler)
157
    {
158
        $this['router']->post($route, $handler);
159
160
        return $this;
161
    }
162
163
    /**
164
     * Adds a handler to the routing table for a given PUT route.
165
     *
166
     * @param string $route   path pattern
167
     * @param mixed  $handler route handler
168
     *
169
     * @return self
170
     */
171
    public function put($route, $handler)
172
    {
173
        $this['router']->put($route, $handler);
174
175
        return $this;
176
    }
177
178
    /**
179
     * Adds a handler to the routing table for a given DELETE route.
180
     *
181
     * @param string $route   path pattern
182
     * @param mixed  $handler route handler
183
     *
184
     * @return self
185
     */
186
    public function delete($route, $handler)
187
    {
188
        $this['router']->delete($route, $handler);
189
190
        return $this;
191
    }
192
193
    /**
194
     * Adds a handler to the routing table for a given PATCH route.
195
     *
196
     * @param string $route   path pattern
197
     * @param mixed  $handler route handler
198
     *
199
     * @return self
200
     */
201
    public function patch($route, $handler)
202
    {
203
        $this['router']->patch($route, $handler);
204
205
        return $this;
206
    }
207
208
    /**
209
     * Adds a handler to the routing table for a given OPTIONS route.
210
     *
211
     * @param string $route   path pattern
212
     * @param mixed  $handler route handler
213
     *
214
     * @return self
215
     */
216
    public function options($route, $handler)
217
    {
218
        $this['router']->options($route, $handler);
219
220
        return $this;
221
    }
222
223
    /**
224
     * Adds a handler to the routing table for a given route.
225
     *
226
     * @param string $method  HTTP method
227
     * @param string $route   path pattern
228
     * @param mixed  $handler route handler
229
     *
230
     * @return self
231
     */
232
    public function map($method, $route, $handler)
233
    {
234
        $this['router']->map($method, $route, $handler);
235
236
        return $this;
237
    }
238
239
    ////////////////////////
240
    // REQUESTS
241
    ////////////////////////
242
243
    /**
244
     * Runs the application.
245
     *
246
     * @return self
247
     */
248
    public function run()
249
    {
250
        $req = Request::createFromGlobals();
251
252
        $this->handleRequest($req)->send();
253
254
        return $this;
255
    }
256
257
    /**
258
     * Builds a response to an incoming request by routing
259
     * it through the application.
260
     *
261
     * @param Request $req
262
     *
263
     * @return Response
264
     */
265
    public function handleRequest(Request $req)
266
    {
267
        // set host name from request if not already set
268
        $config = $this['config'];
269
        if (!$config->get('app.hostname')) {
270
            $config->set('app.hostname', $req->host());
271
        }
272
273
        $res = new Response();
274
        try {
275
            // determine route by dispatching to router
276
            $routeInfo = $this['router']->dispatch($req->method(), $req->path());
277
            $this['routeInfo'] = $routeInfo;
278
279
            // set any route arguments on the request
280
            if (isset($routeInfo[2])) {
281
                $req->setParams($routeInfo[2]);
282
            }
283
284
            // the dispatch middleware is the final step
285
            $dispatch = new DispatchMiddleware();
286
            $dispatch->setApp($this);
287
            $this->middleware($dispatch);
288
289
            // the request is handled by returning the response
290
            // generated by the middleware chain
291
            return $this->runMiddleware($req, $res);
292
        } catch (\Exception $e) {
293
            return $this['exception_handler']($req, $res, $e);
294
        } catch (\Error $e) {
295
            return $this['php_error_handler']($req, $res, $e);
296
        }
297
    }
298
299
    ////////////////////////
300
    // MIDDLEWARE
301
    ////////////////////////
302
303
    /**
304
     * Adds middleware to the application.
305
     *
306
     * The middleware must be callable / invokable with the following
307
     * method signature:
308
     *    middlewareFn(Request $req, Response $res, callable $next)
309
     *
310
     * Middleware is called in LIFO order. Each middleware step
311
     * is expected to return a Response object. In order to continue
312
     * processing the middleware chain then call $next($req, $res).
313
     * If a middleware is to be the final step then simply return
314
     * a Response object instead of calling $next.
315
     *
316
     * @param callable $middleware
317
     *
318
     * @return self
319
     */
320
    public function middleware(callable $middleware)
321
    {
322
        $this->initMiddleware()->unshift($middleware);
323
324
        return $this;
325
    }
326
327
    /**
328
     * Runs the middleware chain.
329
     *
330
     * @param Request  $req
331
     * @param Response $res
332
     *
333
     * @return Response $res
334
     */
335
    public function runMiddleware(Request $req, Response $res)
336
    {
337
        $this->initMiddleware()->rewind();
338
339
        return $this->nextMiddleware($req, $res);
340
    }
341
342
    /**
343
     * Initializes the middleware stack.
344
     *
345
     * @return SplStack
346
     */
347
    private function initMiddleware()
348
    {
349
        // only need to initialize once
350
        if (!$this->middleware) {
351
            $this->middleware = new SplStack();
352
        }
353
354
        return $this->middleware;
355
    }
356
357
    /**
358
     * Calls the next middleware in the chain.
359
     * DO NOT call directly.
360
     *
361
     * @param Request  $req
362
     * @param Response $res
363
     *
364
     * @return Response
365
     */
366
    public function nextMiddleware(Request $req, Response $res)
367
    {
368
        $middleware = $this->middleware->current();
369
370
        // base case - no middleware left
371
        if (!$middleware) {
372
            return $res;
373
        }
374
375
        // otherwise, call next middleware
376
        $this->middleware->next();
377
378
        return $middleware($req, $res, [$this, 'nextMiddleware']);
379
    }
380
381
    ////////////////////////
382
    // Console
383
    ////////////////////////
384
385
    /**
386
     * Gets a console instance for this application.
387
     *
388
     * @return \Infuse\Console\Application
389
     */
390
    public function getConsole()
391
    {
392
        return new Console\Application($this);
393
    }
394
395
    ////////////////////////
396
    // Magic Methods
397
    ////////////////////////
398
399
    public function __get($k)
400
    {
401
        return $this[$k];
402
    }
403
404
    public function __isset($k)
405
    {
406
        return isset($this[$k]);
407
    }
408
409
    ////////////////////////
410
    // HttpKernelInterface
411
    ////////////////////////
412
413
    public function handle(SymfonyRequest $request, $type = self::MASTER_REQUEST, $catch = true)
414
    {
415
        $bridge = new SymfonyHttpBridge();
416
        $req = $bridge->convertSymfonyRequest($request);
417
418
        return $bridge->convertInfuseResponse($this->handleRequest($req));
419
    }
420
}
421