Completed
Push — master ( 4604a2...31c42c )
by Radu
02:05
created

AbstractApplication::haltHttp()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 23
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 17
c 1
b 0
f 0
dl 0
loc 23
rs 9.7
cc 4
nc 4
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace WebServCo\Framework;
6
7
use WebServCo\Framework\Exceptions\ApplicationException;
8
use WebServCo\Framework\Exceptions\NotFoundException;
9
use WebServCo\Framework\Helpers\PhpHelper;
10
use WebServCo\Framework\Interfaces\ResponseInterface;
11
12
abstract class AbstractApplication
13
{
14
    use \WebServCo\Framework\Traits\ExposeLibrariesTrait;
15
16
    protected string $projectNamespace;
17
    protected string $projectPath;
18
19
    abstract protected function getCliOutput(\Throwable $throwable): string;
20
21
    abstract protected function getHttpOutput(\Throwable $throwable): string;
22
23
    abstract protected function getResponse(): ResponseInterface;
24
25
    public function __construct(string $publicPath, ?string $projectPath, ?string $projectNamespace)
26
    {
27
        // If no custom namespace is set, use "Project".
28
        $this->projectNamespace = $projectNamespace ?? 'Project';
29
30
        // Make sure path ends with a slash.
31
        $publicPath = \rtrim($publicPath, \DIRECTORY_SEPARATOR) . \DIRECTORY_SEPARATOR;
32
33
        if (!\is_readable($publicPath . 'index.php')) {
34
            throw new \WebServCo\Framework\Exceptions\ApplicationException('Public web path is not readable.');
35
        }
36
37
        // If no custom project path is set, use parent directory of public.
38
        $projectPath ??= (string) \realpath(\sprintf('%s..', $publicPath));
39
40
        // Make sure path ends with a slash.
41
        $this->projectPath = \rtrim($projectPath, \DIRECTORY_SEPARATOR) . \DIRECTORY_SEPARATOR;
42
43
        // Add environment settings.
44
45
        \WebServCo\Framework\Environment\Setting::set('APP_PATH_WEB', $publicPath);
46
        \WebServCo\Framework\Environment\Setting::set('APP_PATH_PROJECT', $this->projectPath);
47
        \WebServCo\Framework\Environment\Setting::set('APP_PATH_LOG', \sprintf('%svar/log/', $this->projectPath));
48
    }
49
50
    /**
51
     * Runs the application.
52
     */
53
    final public function run(): void
54
    {
55
        try {
56
            ErrorHandler::set();
57
58
            \register_shutdown_function([$this, 'shutdown']);
59
60
            $this->loadEnvironmentSettings();
61
62
            $response = $this->getResponse();
63
64
            $statusCode = $response->send();
65
66
            //$this->shutdown(null, true, PhpHelper::isCli() ? $statusCode : 0);
67
            $this->shutdown(null, true, $statusCode);
68
        } catch (\Throwable $e) {
69
            $this->shutdown($e, true);
70
        }
71
    }
72
73
    /**
74
     * Finishes the execution of the Application.
75
     *
76
     * This method is also registered as a shutdown handler.
77
     */
78
    final public function shutdown(?\Throwable $exception = null, bool $manual = false, int $statusCode = 0): void
79
    {
80
        $hasError = $this->handleErrors($exception);
81
        if ($hasError) {
0 ignored issues
show
introduced by
The condition $hasError is always true.
Loading history...
82
            $statusCode = 1;
83
        }
84
85
        if (!$manual) { // if shutdown handler
86
            /**
87
             * Warning: this part will always be executed,
88
             * independent of the outcome of the script.
89
             */
90
            ErrorHandler::restore();
91
        }
92
        exit($statusCode);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
93
    }
94
95
    final protected function execute(): ResponseInterface
96
    {
97
        $classType = PhpHelper::isCli()
98
            ? 'Command'
99
            : 'Controller';
100
        $target = $this->request()->getTarget();
101
        // \WebServCo\Framework\Objects\Route
102
        $route = $this->router()->getRoute(
103
            $target,
104
            $this->router()->setting('routes'),
105
            $this->request()->getArgs(),
106
        );
107
108
        $className = \sprintf("\\%s\\Domain\\%s\\%s", $this->projectNamespace, $route->class, $classType);
109
        if (!\class_exists($className)) {
110
            if ('Controller' !== $classType) {
111
                throw new NotFoundException(
112
                    \sprintf('No matching %s found. Target: "%s"', $classType, $target),
113
                );
114
            }
115
            // Class type is "Controller", so check for 404 route
116
            // throws \WebServCo\Framework\Exceptions\NotFoundException
117
            $route = $this->router()->getFourOhfourRoute();
118
            $className = \sprintf("\\%s\\Domain\\%s\\%s", $this->projectNamespace, $route->class, $classType);
119
        }
120
121
        $object = new $className();
122
        $parent = \get_parent_class($object);
123
        if (\method_exists((string) $parent, $route->method) || !\is_callable([$className, $route->method])) {
124
            throw new NotFoundException(\sprintf('No matching Action found. Target: "%s".', $target));
125
        }
126
        $callable = [$object, $route->method];
127
        if (!\is_callable($callable)) {
128
            throw new ApplicationException(\sprintf('Method not found. Target: "%s"', $target));
129
        }
130
131
        return \call_user_func_array($callable, $route->arguments);
132
    }
133
134
    /**
135
     * Handle Errors.
136
     */
137
    final protected function handleErrors(?\Throwable $exception = null): bool
138
    {
139
        $throwable = \WebServCo\Framework\Helpers\ErrorObjectHelper::get($exception);
140
        if ($throwable instanceof \Throwable) {
0 ignored issues
show
introduced by
$throwable is always a sub-type of Throwable.
Loading history...
141
            return $this->halt($throwable);
142
        }
143
        return false;
144
    }
145
146
    final protected function halt(\Throwable $throwable): bool
147
    {
148
        $exceptionLogger = new \WebServCo\Framework\Log\ExceptionLogger();
149
        $exceptionLogger->log($throwable);
150
        return \WebServCo\Framework\Helpers\PhpHelper::isCli()
151
            ? $this->haltCli($throwable)
152
            : $this->haltHttp($throwable);
153
    }
154
155
    final protected function haltCli(\Throwable $throwable): bool
156
    {
157
        $output = $this->getCliOutput($throwable);
158
        $response = new \WebServCo\Framework\Cli\Response($output, 1);
159
        $response->send();
160
        return true;
161
    }
162
163
    final protected function haltHttp(\Throwable $throwable): bool
164
    {
165
        $output = $this->getHttpOutput($throwable);
166
167
        $code = $throwable->getCode();
168
        switch ($code) {
169
            case 404:
170
                $statusCode = 404; //not found
171
                break;
172
            case 500: //application
173
            case 0: //default
174
            default:
175
                $statusCode = 500;
176
                break;
177
        }
178
179
        $response = new \WebServCo\Framework\Http\Response(
180
            $output,
181
            $statusCode,
182
            ['Content-Type' => ['text/html']],
183
        );
184
        $response->send();
185
        return true;
186
    }
187
188
    protected function loadEnvironmentSettings(): bool
189
    {
190
        return \WebServCo\Framework\Environment\Settings::load($this->projectPath);
191
    }
192
}
193