Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
66 / 66
100.00% covered (success)
100.00%
1 / 1
CRAP
100.00% covered (success)
100.00%
1 / 1
Bootstrap
100.00% covered (success)
100.00%
66 / 66
100.00% covered (success)
100.00%
1 / 1
23
100.00% covered (success)
100.00%
1 / 1
 run
100.00% covered (success)
100.00%
66 / 66
100.00% covered (success)
100.00%
1 / 1
23
1<?php
2
3namespace Miniframe\Core;
4
5use Miniframe\Response\InternalServerErrorResponse;
6use Miniframe\Response\NotFoundResponse;
7use RuntimeException;
8
9/**
10 * This is the framework bootstrapper
11 */
12class Bootstrap
13{
14    /**
15     * Executes the framework
16     *
17     * @param string $projectFolder Root folder of the project.
18     *
19     * @return integer
20     */
21    public function run(string $projectFolder): int
22    {
23        $projectFolder = rtrim((string)realpath($projectFolder), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
24        $thrown = false;
25        $postProcessors = array();
26        try {
27            // Load config
28            $config = new Config($projectFolder . 'config', $projectFolder);
29            if ($config->has('framework', 'config')) {
30                $configClassName = $config->get('framework', 'config');
31                if (!is_string($configClassName)) {
32                    throw new RuntimeException('Invalid config value: ' . var_export($configClassName, true));
33                }
34                if (!class_exists($configClassName)) {
35                    throw new RuntimeException($configClassName . ' can\'t be found');
36                }
37                if (!in_array(Config::class, class_parents($configClassName))) {
38                    throw new RuntimeException($configClassName . ' isn\'t a valid config class');
39                }
40                $config = new $configClassName($projectFolder . 'config', $projectFolder);
41            }
42            Registry::register('config', $config);
43
44            // Fetch request and loads middlewares
45            $request = Request::getActual();
46            $routers = array();
47            if ($config->has('framework', 'middleware')) { // @phpstan-ignore-line
48                foreach ($config->get('framework', 'middleware') as $key => $FQCN) { // @phpstan-ignore-line
49                    if (!class_exists($FQCN)) {
50                        throw new RuntimeException('Middleware ' . $FQCN . ' not found');
51                    }
52                    if (!is_subclass_of($FQCN, AbstractMiddleware::class)) {
53                        throw new RuntimeException(
54                            'Middleware ' . $FQCN . ' does not extend ' . AbstractMiddleware::class
55                        );
56                    }
57                    if (is_numeric($key)) { // When a value is defined as "middleware[] = FQCN", the key is numeric
58                        $key = $FQCN;
59                    }
60                    $middleware = new $FQCN($request, $config); /* @var $middleware AbstractMiddleware */
61                    Registry::register($key, $middleware);
62                    $routers = array_merge($routers, $middleware->getRouters());
63                    $postProcessors = array_merge($postProcessors, $middleware->getPostProcessors());
64                }
65            }
66            if (count($routers) == 0) {
67                throw new RuntimeException(
68                    'No routers found. Please define a router middleware. See '
69                    . 'https://bitbucket.org/miniframe/core/src/master/README.md'
70                    . ' for more.'
71                );
72            }
73
74            // Tries all routers to find if we can serve a page
75            $callable = null;
76            foreach ($routers as $router) {
77                $callable = call_user_func($router);
78                if ($callable !== null) {
79                    break;
80                }
81            }
82            if ($callable === null) {
83                throw new NotFoundResponse();
84            }
85
86            // Executes the callable and validates it's response
87            $response = call_user_func($callable);
88            if (!is_object($response) || !is_a($response, Response::class)) {
89                throw new RuntimeException(
90                    'Bad response type: ' . (is_object($response) ? get_class($response) : gettype($response))
91                );
92            }
93        } catch (Response $exception) {
94            $response = $exception;
95            $thrown = true;
96        } catch (\Throwable $exception) {
97            $response = new InternalServerErrorResponse('', 0, $exception);
98            $thrown = true;
99        }
100
101        // Post-processing, in a separate try/catch block, just to be sure
102        try {
103            $postProcessors = array_reverse($postProcessors);
104            foreach ($postProcessors as $postProcessor) {
105                $response = call_user_func($postProcessor, $response, $thrown);
106                if (!is_a($response, Response::class)) {
107                    throw new RuntimeException('One of the PostProcessors doesn\'t return a Response object');
108                }
109            }
110        } catch (\Throwable $exception) {
111            $response = new InternalServerErrorResponse('', 0, $exception);
112        }
113
114        // Output result
115        http_response_code($response->getResponseCode());
116        foreach ($response->getHeaders() as $header) {
117            header($header['header'], $header['replace']);
118        }
119        echo $response->render();
120        return $response->getExitCode();
121    }
122}