Completed
Push — master ( e3df42...7db57b )
by Nikolas
03:09
created

ExecuteResource::collectParameters()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 3
rs 10
cc 1
eloc 2
nc 1
nop 2
1
<?php
2
namespace rtens\domin\delivery\web\root;
3
4
use rtens\domin\Action;
5
use rtens\domin\delivery\ParameterReader;
6
use rtens\domin\delivery\web\HeadElements;
7
use rtens\domin\delivery\web\RequestParameterReader;
8
use rtens\domin\delivery\web\WebApplication;
9
use rtens\domin\delivery\web\WebExecutor;
10
use rtens\domin\delivery\web\WebField;
11
use rtens\domin\execution\ExecutionResult;
12
use rtens\domin\execution\FailedResult;
13
use rtens\domin\execution\MissingParametersResult;
14
use rtens\domin\execution\NoResult;
15
use rtens\domin\execution\RedirectResult;
16
use rtens\domin\execution\RenderedResult;
17
use rtens\domin\Parameter;
18
use watoki\collections\Map;
19
use watoki\curir\cookie\Cookie;
20
use watoki\curir\cookie\CookieStore;
21
use watoki\curir\delivery\WebRequest;
22
use watoki\curir\rendering\PhpRenderer;
23
use watoki\curir\Resource;
24
use watoki\factory\Factory;
25
use watoki\reflect\type\ClassType;
26
27
class ExecuteResource extends Resource {
28
29
    const ACTION_ARG = '__action';
30
    const BREADCRUMB_COOKIE = 'domin_trail';
31
32
    /** @var CookieStore */
33
    private $cookies;
34
35
    /**
36
     * @param Factory $factory <-
37
     * @param WebApplication $app <-
38
     * @param CookieStore $cookies <-
39
     */
40
    public function __construct(Factory $factory, WebApplication $app, CookieStore $cookies) {
41
        parent::__construct($factory);
42
        $this->app = $app;
0 ignored issues
show
Bug introduced by
The property app does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
43
        $this->cookies = $cookies;
44
    }
45
46
    private static function baseHeadElements() {
47
        return [
48
            HeadElements::jquery(),
49
            HeadElements::jqueryUi(), // not actually needed but it needs to be included before bootstrap.js too avoid conflicts
50
            HeadElements::bootstrap(),
51
            HeadElements::bootstrapJs(),
52
        ];
53
    }
54
55
    /**
56
     * @param string $__action
57
     * @param WebRequest $__request <-
58
     * @return array
59
     */
60
    public function doPost($__action, WebRequest $__request) {
61
        return $this->doGet($__action, $__request);
62
    }
63
64
    /**
65
     * @param string $__action
66
     * @param WebRequest $__request <-
67
     * @return array
68
     * @throws \Exception
69
     */
70
    public function doGet($__action, WebRequest $__request) {
71
        $headElements = self::baseHeadElements();
72
        $renderedAction = null;
73
        $caption = 'Error';
74
        $crumbs = [];
75
76
        $reader = new RequestParameterReader($__request);
77
78
        try {
79
            $action = $this->app->actions->getAction($__action);
80
            $caption = $action->caption();
81
82
            $executor = new WebExecutor($this->app->actions, $this->app->fields, $this->app->renderers, $reader);
83
            $result = $executor->execute($__action);
84
85
            if (!($result instanceof RedirectResult)) {
86
                $crumbs = $this->updateCrumbs($__action, $result, $__request, $reader);
87
                $headElements = array_merge($headElements, $executor->getHeadElements());
88
89
                $actionParameter = new Parameter($__action, new ClassType(get_class($action)));
90
                $actionField = $this->app->fields->getField($actionParameter);
91
                if (!($actionField instanceof WebField)) {
92
                    throw new \Exception(get_class($actionField) . " must implement WebField");
93
                }
94
95
                $renderedAction = $actionField->render($actionParameter, $this->readParameters($action, $reader));
96
                $headElements = array_merge($headElements, $actionField->headElements($actionParameter));
97
            }
98
        } catch (\Exception $e) {
99
            $result = new FailedResult($e);
100
        }
101
102
        $resultModel = $this->assembleResult($result, $__request);
103
104
        return array_merge(
105
            [
106
                'name' => $this->app->name,
107
                'menu' => $this->app->menu->render($__request),
108
                'breadcrumbs' => $crumbs ? array_slice($crumbs, 0, -1) : null,
109
                'current' => $crumbs ? array_slice($crumbs, -1)[0]['target'] : null,
110
                'caption' => $caption,
111
                'action' => $renderedAction,
112
                'headElements' => HeadElements::filter($headElements)
113
            ],
114
            $resultModel
115
        );
116
    }
117
118
    private function assembleResult(ExecutionResult $result, WebRequest $request) {
119
        $model = [
120
            'error' => null,
121
            'missing' => null,
122
            'success' => null,
123
            'redirect' => null,
124
            'output' => null
125
        ];
126
127
        if ($result instanceof FailedResult) {
128
            $model['error'] = htmlentities($result->getMessage());
129
        } else if ($result instanceof NoResult) {
130
            $model['success'] = true;
131
            $model['redirect'] = $this->getLastCrumb();
132
        } else if ($result instanceof RenderedResult) {
133
            $model['output'] = $result->getOutput();
134
        } else if ($result instanceof MissingParametersResult) {
135
            $model['missing'] = $result->getParameters();
136
        } else if ($result instanceof RedirectResult) {
137
            $model['success'] = true;
138
            $model['redirect'] = $request->getContext()
139
                ->appended($result->getActionId())
140
                ->withParameters(new Map($result->getParameters()));
141
        }
142
143
        return $model;
144
    }
145
146
    private function readParameters(Action $action, ParameterReader $reader) {
147
        $values = [];
148
149
        foreach ($action->parameters() as $parameter) {
150
            if ($reader->has($parameter)) {
151
                $field = $this->app->fields->getField($parameter);
152
                $values[$parameter->getName()] = $field->inflate($parameter, $reader->read($parameter));
153
            }
154
        }
155
        return $values;
156
    }
157
158
    private function updateCrumbs($actionId, ExecutionResult $result, WebRequest $request, ParameterReader $reader) {
159
        $action = $this->app->actions->getAction($actionId);
160
        $crumbs = $this->readCrumbs();
161
162
        $current = [
163
            'target' => (string)$request->getContext()
164
                ->appended($actionId)
165
                ->withParameters(new Map($this->readRawParameters($action, $reader))),
166
            'caption' => $action->caption()
167
        ];
168
        $newCrumbs = [];
169
        foreach ($crumbs as $crumb) {
170
            if ($crumb == $current) {
171
                break;
172
            }
173
            $newCrumbs[] = $crumb;
174
        }
175
        $newCrumbs[] = $current;
176
        if ($result instanceof RenderedResult) {
177
            $this->saveCrumbs($newCrumbs);
178
        }
179
        return $newCrumbs;
180
    }
181
182
    private function readRawParameters(Action $action, ParameterReader $reader) {
183
        $values = [];
184
185
        foreach ($action->parameters() as $parameter) {
186
            if ($reader->has($parameter)) {
187
                $values[$parameter->getName()] = $reader->read($parameter);
188
            }
189
        }
190
        return $values;
191
    }
192
193
    private function getLastCrumb() {
194
        $crumbs = $this->readCrumbs();
195
        if (!$crumbs) {
196
            return null;
197
        }
198
        return end($crumbs)['target'];
199
    }
200
201
    private function readCrumbs() {
202
        if ($this->cookies->hasKey(self::BREADCRUMB_COOKIE)) {
203
            return $this->cookies->read(self::BREADCRUMB_COOKIE)->payload;
204
        }
205
        return [];
206
    }
207
208
    private function saveCrumbs($crumbs) {
209
        $this->cookies->create(new Cookie($crumbs), self::BREADCRUMB_COOKIE);
210
    }
211
212
    protected function createDefaultRenderer() {
213
        return new PhpRenderer();
214
    }
215
}