Passed
Push — master ( f83aa9...a7daae )
by Alex
03:39
created

CommonApplication::baseFormatter()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 2
nop 1
dl 0
loc 12
rs 9.9666
c 0
b 0
f 0
1
<?php
2
namespace Mezon\Application;
3
4
use Mezon\HtmlTemplate\HtmlTemplate;
5
use Mezon\Rest;
6
use Mezon\Router\Utils;
7
8
// TODO fetch CommonApplication in separate package
9
/**
10
 * Class CommonApplication
11
 *
12
 * @package Mezon
13
 * @subpackage CommonApplication
14
 * @author Dodonov A.A.
15
 * @version v.1.0 (2019/08/07)
16
 * @copyright Copyright (c) 2019, aeon.org
17
 */
18
19
/**
20
 * Common application with any available template
21
 *
22
 * To load routes from the config call $this->load_routes_from_config('./Conf/routes.json');
23
 *
24
 * The format of the *.json config must be like this:
25
 *
26
 * [
27
 * {
28
 * "route" : "/route1" ,
29
 * "callback" : "callback1" ,
30
 * "method" : "POST"
31
 * } ,
32
 * {
33
 * "route" : "/route2" ,
34
 * "callback" : "callback2" ,
35
 * "method" : ["GET" , "POST"]
36
 * }
37
 * ]
38
 */
39
class CommonApplication extends Application
40
{
41
42
    /**
43
     * Application's template
44
     *
45
     * @var HtmlTemplate
46
     */
47
    private $template = false;
48
49
    /**
50
     * Constructor
51
     *
52
     * @param HtmlTemplate $template
53
     *            Template
54
     */
55
    public function __construct(HtmlTemplate $template)
56
    {
57
        parent::__construct();
58
59
        $this->template = $template;
60
61
        $this->getRouter()->setNoProcessorFoundErrorHandler([
62
            $this,
63
            'noRouteFoundErrorHandler'
64
        ]);
65
66
        $this->loadActoinsFromConfig();
67
    }
68
69
    /**
70
     * Method handles 404 errors
71
     *
72
     * @codeCoverageIgnore
73
     */
74
    public function noRouteFoundErrorHandler(): void
75
    {
76
        $this->redirectTo('/404');
77
    }
78
79
    /**
80
     * Method renders common parts of all pages.
81
     *
82
     * @return array List of common parts.
83
     */
84
    public function crossRender(): array
85
    {
86
        return [];
87
    }
88
89
    /**
90
     * Method formats exception object
91
     *
92
     * @param \Exception $e
93
     *            Exception
94
     * @return object Formatted exception object
95
     */
96
    protected function baseFormatter(\Exception $e): object
97
    {
98
        $error = new \stdClass();
99
        $error->message = $e->getMessage();
100
        $error->code = $e->getCode();
101
        $error->call_stack = $this->formatCallStack($e);
102
        if (isset($_SERVER['HTTP_HOST']) && $_SERVER['REQUEST_URI']) {
103
            $error->host = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
104
        } else {
105
            $error->host = 'undefined';
106
        }
107
        return $error;
108
    }
109
110
    /**
111
     * Method processes exception.
112
     *
113
     * @param Rest\Exception $e
114
     *            RestException object.
115
     */
116
    public function handleRestException(Rest\Exception $e): void
117
    {
118
        $error = $this->baseFormatter($e);
119
120
        $error->httpBody = $e->getHttpBody();
121
122
        print('<pre>' . json_encode($error, JSON_PRETTY_PRINT));
123
    }
124
125
    /**
126
     * Method processes exception.
127
     *
128
     * @param \Exception $e
129
     *            Exception object.
130
     */
131
    public function handleException(\Exception $e): void
132
    {
133
        $error = $this->baseFormatter($e);
134
135
        print('<pre>' . json_encode($error, JSON_PRETTY_PRINT));
136
    }
137
138
    /**
139
     * Method sets GET parameter as template var
140
     *
141
     * @param string $fieldName
142
     *            name of the GET parameter
143
     */
144
    protected function setGetVar(string $fieldName): void
145
    {
146
        if (isset($_GET[$fieldName])) {
147
            $this->template->setPageVar($fieldName, $_GET[$fieldName]);
148
        }
149
    }
150
151
    /**
152
     * Running application.
153
     */
154
    public function run(): void
155
    {
156
        try {
157
            $callRouteResult = $this->callRoute();
158
159
            if (is_array($callRouteResult) === false) {
160
                throw (new \Exception('Route was not called properly'));
161
            }
162
163
            $result = array_merge($callRouteResult, $this->crossRender());
0 ignored issues
show
Bug introduced by
It seems like $callRouteResult can also be of type null and string and true; however, parameter $arrays of array_merge() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

163
            $result = array_merge(/** @scrutinizer ignore-type */ $callRouteResult, $this->crossRender());
Loading history...
164
165
            if (is_array($result)) {
0 ignored issues
show
introduced by
The condition is_array($result) is always true.
Loading history...
166
                foreach ($result as $key => $value) {
167
                    $content = $value instanceof ViewInterface ? $value->render() : $value;
168
169
                    $this->template->setPageVar($key, $content);
170
                }
171
            }
172
173
            $this->setGetVar('redirect-to');
174
175
            print($this->template->compile());
176
        } catch (Rest\Exception $e) {
177
            $this->handleRestException($e);
178
        } catch (\Exception $e) {
179
            $this->handleException($e);
180
        }
181
    }
182
183
    /**
184
     * Getting template
185
     *
186
     * @return HtmlTemplate Application's template
187
     * @codeCoverageIgnore
188
     */
189
    public function getTemplate(): HtmlTemplate
190
    {
191
        return $this->template;
192
    }
193
194
    /**
195
     * Setting template
196
     *
197
     * @param HtmlTemplate $template
198
     *            Template
199
     * @codeCoverageIgnore
200
     */
201
    public function setTemplate(HtmlTemplate $template): void
202
    {
203
        $this->template = $template;
204
    }
205
206
    /**
207
     * Does file exists
208
     *
209
     * @param string $fileName
210
     *            file name
211
     * @return bool true if the file exists, false otherwise
212
     */
213
    protected function fileExists(string $fileName): bool
214
    {
215
        return $this->getTemplate()->fileExists($fileName);
216
    }
217
218
    /**
219
     * Method returns localized error message by it's key
220
     *
221
     * @param string $actionMessageCode
222
     *            key of the message
223
     * @return string localized error message by it's key
224
     */
225
    protected function getActionMessage(string $actionMessageCode): string
226
    {
227
        if ($this->fileExists('action-messages.json')) {
228
            $messages = $this->getTemplate()->getFile('action-messages.json');
229
            $messages = json_decode($messages, true);
230
231
            if (isset($messages[$actionMessageCode])) {
232
                return $messages[$actionMessageCode];
233
            } else {
234
                throw (new \Exception('The message with locator "' . $actionMessageCode . '" was not found', - 1));
235
            }
236
        }
237
238
        return '';
239
    }
240
241
    /**
242
     * Method returns action-message or '' if not set
243
     *
244
     * @return string
245
     */
246
    protected function getActionMessageCode(): string
247
    {
248
        return $_GET['action-message'] ?? '';
249
    }
250
251
    /**
252
     * Method sets message variable
253
     *
254
     * @param string $successMessageLocator
255
     *            message code
256
     */
257
    public function setSuccessMessage(string $successMessageLocator): void
258
    {
259
        $this->getTemplate()->setPageVar('action-message', $this->getActionMessage($successMessageLocator));
260
    }
261
262
    /**
263
     * Method sets message variable
264
     *
265
     * @param string $errorMessageLocator
266
     *            message code
267
     */
268
    public function setErrorMessage(string $errorMessageLocator): void
269
    {
270
        $this->getTemplate()->setPageVar('action-message', $this->getActionMessage($errorMessageLocator));
271
    }
272
273
    /**
274
     * Method compiles result record
275
     *
276
     * @param mixed $presenter
277
     *            main area presenter
278
     * @return array result record
279
     */
280
    public function result($presenter = null): void
281
    {
282
        if ($presenter !== null) {
283
            $presenter->run();
284
        }
285
286
        if ($actionMessage = $this->getActionMessageCode()) {
287
            $this->setSuccessMessage($actionMessage);
288
        }
289
    }
290
291
    /**
292
     * Method creates action from JSON config
293
     *
294
     * @param string $path
295
     *            path to JSON config
296
     */
297
    private function createActionFromJsonConfig(string $path): void
298
    {
299
        $method = 'action' . basename($path, '.json');
300
301
        $this->$method = function () use ($path): array {
302
            $result = [];
303
304
            // TODO extract method and put in the ActionBuilder
305
            $presenter = null;
306
            $config = json_decode(file_get_contents($path), true);
307
            $views = [];
308
309
            ActionBuilder::constructOverrideHandler($path, $config);
310
311
            foreach ($config as $key => $value) {
312
                $callback = ActionBuilder::getActionBuilderMethod($this, $key, $value);
313
314
                if ($callback !== null) {
315
                    $callback();
316
                } elseif (is_string($value)) {
317
                    // string content
318
                    $result[$key] = $value;
319
                } elseif ($key === 'presenter') {
320
                    $presenter = new $value['class'](
321
                        isset($value['view']) && isset($views[$value['view']]) ? $views[$value['view']] : null,
322
                        $value['name'],
323
                        $this->getRequestParamsFetcher());
324
                } else {
325
                    ActionBuilder::constructOtherView($this, $result, $key, $value, $views);
326
                }
327
            }
328
329
            $this->result($presenter);
330
331
            return $result;
332
        };
333
334
        $this->loadRoute(
335
            [
336
                'route' => Utils::convertMethodNameToRoute($method),
337
                'callback' => [
338
                    $this,
339
                    $method
340
                ],
341
                'method' => [
342
                    'GET',
343
                    'POST'
344
                ]
345
            ]);
346
    }
347
348
    /**
349
     * Method loads actions from path
350
     *
351
     * @param string $path
352
     */
353
    protected function loadActionsFromDirectory(string $path): void
354
    {
355
        if (file_exists($path)) {
356
            $files = scandir($path);
357
358
            foreach ($files as $file) {
359
                if ($file === '.' || $file === '..') {
360
                    // do nothing
361
                } elseif (is_file($path . '/' . $file) && strpos($file, '.json') !== false) {
362
                    $this->createActionFromJsonConfig($path . '/' . $file);
363
                } elseif (is_dir($path . '/' . $file)) {
364
                    $this->loadActionsFromDirectory($path . '/' . $file);
365
                }
366
            }
367
        }
368
    }
369
370
    /**
371
     * Method loads all actions from ./actions directory
372
     */
373
    private function loadActoinsFromConfig(): void
374
    {
375
        $this->loadActionsFromDirectory($this->getClassPath() . '/Actions/');
376
    }
377
}
378
379