Passed
Branch main (08d999)
by Sammy
03:02
created

ORMController::form_model()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 1
c 1
b 0
f 1
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace HexMakina\kadro\Controllers;
4
5
use HexMakina\TightORM\Interfaces\ModelInterface;
6
use HexMakina\LeMarchand\LeMarchand;
7
8
abstract class ORMController extends KadroController implements Interfaces\ORMControllerInterface
9
{
10
    protected $model_class_name = null;
11
    protected $model_type = null;
12
13
    protected $load_model = null;
14
    protected $form_model = null;
15
16
17
    public function add_errors($errors)
18
    {
19
        foreach ($errors as $err) {
20
            if (is_array($err)) {
21
                $this->add_error(array_unshift($err), array_unshift($err));
22
            } else {
23
                $this->add_error($err);
24
            }
25
        }
26
    }
27
28
    public function load_model(): ?ModelInterface
29
    {
30
        return $this->load_model;
31
    }
32
33
    public function formModel(ModelInterface $setter = null): ModelInterface
34
    {
35
      if(!is_null($setter)){
36
        $this->form_model = $setter;
37
      }
38
      elseif(is_null($this->form_model)){
39
        $reflection = new \ReflectionClass($this->modelClassName());
40
        $this->form_model = $reflection->newInstanceWithoutConstructor(); //That's it!
41
      }
42
      return $this->form_model;
43
    }
44
45
    // shortcut to model_type
46
    public function modelType(): string
47
    {
48
      // have to go through the model to garantee model_type existence via interface
49
      if(is_null($this->model_type))
50
        $this->model_type = get_class($this->formModel())::model_type();
51
52
      return $this->model_type;
53
    }
54
55
    public function modelPrefix($suffix=null): string
56
    {
57
      $ret = $this->modelType();
58
59
      if(!is_null($suffix))
60
        $ret .= '_'.$suffix;
61
62
      return $ret;
63
    }
64
65
    public function prepare()
66
    {
67
        parent::prepare();
68
69
        $this->model_type = $this->modelClassName()::model_type();
70
71
        $pk_values = [];
72
73
        if ($this->router()->submits()) {
74
            $this->formModel()->import($this->sanitize_post_data($this->router()->submitted()));
75
            $pk_values = $this->modelClassName()::table()->primary_keys_match($this->router()->submitted());
76
77
            $this->load_model = $this->modelClassName()::exists($pk_values);
78
        } elseif ($this->router()->requests()) {
79
            $pk_values = $this->modelClassName()::table()->primary_keys_match($this->router()->params());
80
81
            if (!is_null($this->load_model = $this->modelClassName()::exists($pk_values))) {
82
                $this->formModel(clone $this->load_model);
83
            }
84
        }
85
        // TODO restore model history
86
        // if (!is_null($this->load_model) && is_subclass_of($this->load_model, '\HexMakina\Tracer\TraceableInterface') && $this->load_model->traceable()) {
87
        //   // $traces = $this->tracer()->traces_by_model($this->load_model);
88
        //     $traces = $this->load_model->traces();
89
        //     //$this->tracer()->history_by_model($this->load_model);
90
        //     $this->viewport('load_model_history', $traces ?? []);
91
        // }
92
    }
93
94
    public function has_load_model()
95
    {
96
        return !empty($this->load_model);
97
    }
98
99
    // ----------- META -----------
100
101
    // CoC class name by
102
    // 1. replacing namespace Controllers by Models
103
    // 2. removing the Controller from classname
104
    // overwrite this behavior by setting the model_class_name at controller construction
105
    public function modelClassName(): string
106
    {
107
        if (is_null($this->model_class_name)) {
108
            preg_match(LeMarchand::RX_CLASS_NAME, get_called_class(), $m);
0 ignored issues
show
Bug introduced by
The constant HexMakina\LeMarchand\LeMarchand::RX_CLASS_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
109
            $this->model_class_name = $this->box($m[1].'Class');
110
        }
111
112
        return $this->model_class_name;
113
    }
114
115
    public function table_name(): string
116
    {
117
        return $this->modelClassName()::table_name();
118
    }
119
120
    public function model_type_to_label($model = null)
121
    {
122
        $model = $model ?? $this->load_model ?? $this->formModel();
123
        return $this->l(sprintf('MODEL_%s_INSTANCE', get_class($model)::model_type()));
124
    }
125
    public function field_name_to_label($model, $field_name)
126
    {
127
        $model = $model ?? $this->load_model ?? $this->formModel();
128
        return $this->l(sprintf('MODEL_%s_FIELD_%s', (get_class($model))::model_type(), $field_name));
129
    }
130
131
    public function dashboard()
132
    {
133
        $this->listing(); //default dashboard is a listing
134
    }
135
136
    public function listing($model = null, $filters = [], $options = [])
137
    {
138
        $class_name = is_null($model) ? $this->modelClassName() : get_class($model);
139
140
        if (!isset($filters['date_start'])) {
141
            $filters['date_start'] = $this->box('StateAgent')->filters('date_start');
142
        }
143
        if (!isset($filters['date_stop'])) {
144
            $filters['date_stop'] = $this->box('StateAgent')->filters('date_stop');
145
        }
146
147
        $listing = $this->modelClassName()::filter($filters);
148
149
        $this->viewport_listing($class_name, $listing, $this->find_template($this->box('template_engine'), __FUNCTION__));
150
    }
151
152
    public function viewport_listing($class_name, $listing, $listing_template)
153
    {
154
        $listing_fields = [];
155
        if (empty($listing)) {
156
            foreach ($class_name::table()->columns() as $column) {
157
                if (!$column->is_auto_incremented() && !$column->is_hidden()) {
158
                    $listing_fields[$column->name()] = $this->l(sprintf('MODEL_%s_FIELD_%s', $class_name::model_type(), $column->name()));
159
                }
160
            }
161
        } else {
162
            $current = current($listing);
163
            if (is_object($current)) {
164
                $current = get_object_vars($current);
165
            }
166
167
            foreach (array_keys($current) as $field) {
168
                $listing_fields[$field] = $this->l(sprintf('MODEL_%s_FIELD_%s', $class_name::model_type(), $field));
169
            }
170
        }
171
172
        $this->viewport('listing', $listing);
173
        $this->viewport('listing_title', $this->l(sprintf('MODEL_%s_INSTANCES', $class_name::model_type())));
174
        $this->viewport('listing_fields', $listing_fields);
175
        $this->viewport('listing_template', $listing_template);
176
177
        $this->viewport('route_new', $this->router()->prehop($class_name::model_type() . '_new'));
178
        $this->viewport('route_export', $this->router()->prehop($class_name::model_type() . '_export'));
179
    }
180
181
    public function copy()
182
    {
183
        $this->formModel($this->load_model->copy());
184
185
        $this->route_back($this->load_model);
186
        $this->edit();
187
    }
188
189
    public function edit()
190
    {
191
    }
192
193
    public function save()
194
    {
195
        $model = $this->persist_model($this->formModel());
196
197
        if (empty($this->errors())) {
198
            $this->route_back($model);
199
        } else {
200
            $this->edit();
201
            return 'edit';
202
        }
203
    }
204
205
    public function persist_model($model): ?ModelInterface
206
    {
207
        $this->errors = $model->save($this->operator()->operator_id()); // returns [errors]
208
        if (empty($this->errors())) {
209
            $this->logger()->nice($this->l('CRUDITES_INSTANCE_ALTERED', [$this->l('MODEL_' . get_class($model)::model_type() . '_INSTANCE')]));
210
            return $model;
211
        }
212
        foreach ($this->errors() as $field => $error_msg) {
213
            $this->logger()->warning($this->l($error_msg, [$field]));
214
        }
215
216
        return null;
217
    }
218
219
    public function before_edit()
220
    {
221
        if (!is_null($this->router()->params('id')) && is_null($this->load_model)) {
222
            $this->logger()->warning($this->l('CRUDITES_ERR_INSTANCE_NOT_FOUND', [$this->l('MODEL_' . $this->modelClassName()::model_type() . '_INSTANCE')]));
223
            $this->router()->hop($this->modelClassName()::model_type());
224
        }
225
    }
226
227
    public function before_save()
228
    {
229
        return [];
230
    }
231
232
  // default: hop to altered object
233
    public function after_save()
234
    {
235
        $this->router()->hop($this->route_back());
236
    }
237
238
    public function destroy_confirm()
239
    {
240
        if (is_null($this->load_model)) {
241
            $this->logger()->warning($this->l('CRUDITES_ERR_INSTANCE_NOT_FOUND', [$this->l('MODEL_' . $this->model_type . '_INSTANCE')]));
242
            $this->router()->hop($this->model_type);
243
        }
244
245
        $this->before_destroy();
246
247
        return 'destroy';
248
    }
249
250
    public function before_destroy() // default: checks for load_model and immortality, hops back to object on failure
251
    {
252
        if (is_null($this->load_model)) {
253
            $this->logger()->warning($this->l('CRUDITES_ERR_INSTANCE_NOT_FOUND', [$this->l('MODEL_' . $this->model_type . '_INSTANCE')]));
254
            $this->router()->hop($this->model_type);
255
        } elseif ($this->load_model->immortal()) {
256
            $this->logger()->warning($this->l('CRUDITES_ERR_INSTANCE_IS_IMMORTAL', [$this->l('MODEL_' . $this->model_type . '_INSTANCE')]));
257
            $this->router()->hop($this->route_model($this->load_model));
258
        }
259
    }
260
261
    public function destroy()
262
    {
263
        if (!$this->router()->submits()) {
264
            throw new \Exception('KADRO_ROUTER_MUST_SUBMIT');
265
        }
266
267
        if ($this->load_model->destroy($this->operator()->operator_id()) === false) {
268
            $this->logger()->info($this->l('CRUDITES_ERR_INSTANCE_IS_UNDELETABLE', ['' . $this->load_model]));
269
            $this->route_back($this->load_model);
270
        } else {
271
            $this->logger()->nice($this->l('CRUDITES_INSTANCE_DESTROYED', [$this->l('MODEL_' . $this->model_type . '_INSTANCE')]));
272
            $this->route_back($this->model_type);
273
        }
274
    }
275
276
    public function after_destroy()
277
    {
278
        $this->router()->hop($this->route_back());
279
    }
280
281
    public function conclude()
282
    {
283
        $this->viewport('errors', $this->errors());
284
        $this->viewport('form_model_type', $this->model_type);
285
        $this->viewport('form_model', $this->formModel());
286
287
        if (isset($this->load_model)) {
288
            $this->viewport('load_model', $this->load_model);
289
        }
290
291
292
    }
293
294
    public function collection_to_csv($collection, $filename)
295
    {
296
      // TODO use Format/File/CSV class to generate file
297
        $file_path = $this->box('settings.export.directory') . $filename . '.csv';
298
        $fp = fopen($file_path, 'w');
299
300
        $header = false;
301
302
        foreach ($collection as $line) {
303
            $line = get_object_vars($line);
304
            if ($header === false) {
305
                fputcsv($fp, array_keys($line));
306
                $header = true;
307
            }
308
            fputcsv($fp, $line);
309
        }
310
        fclose($fp);
311
312
        return $file_path;
313
    }
314
315
    public function export()
316
    {
317
        $format = $this->router()->params('format');
318
        switch ($format) {
319
            case null:
320
                $filename = $this->model_type;
321
                $collection = $this->modelClassName()::listing();
322
                $file_path = $this->collection_to_csv($collection, $filename);
323
                $this->router()->send_file($file_path);
324
                break;
325
326
            case 'xlsx':
327
                $report_controller = $this->box('HexMakina\koral\Controllers\ReportController');
328
                return $report_controller->collection($this->modelClassName());
329
        }
330
    }
331
332
    public function route_new(ModelInterface $model): string
333
    {
334
        return $this->router()->prehop(get_class($model)::model_type() . '_new');
335
    }
336
337
    public function route_list(ModelInterface $model): string
338
    {
339
        return $this->router()->prehop(get_class($model)::model_type());
340
    }
341
342
    public function route_model(ModelInterface $model): string
343
    {
344
        $route_params = [];
345
346
        $route_name = get_class($model)::model_type() . '_';
347
        if ($model->is_new()) {
348
            $route_name .= 'new';
349
        } else {
350
            $route_name .= 'default';
351
            $route_params = ['id' => $model->get_id()];
352
        }
353
        $res = $this->router()->prehop($route_name, $route_params);
354
        return $res;
355
    }
356
357
    public function route_factory($route = null, $route_params = []): string
358
    {
359
        if (is_null($route) && $this->router()->submits()) {
360
            $route = $this->formModel();
361
        }
362
363
        if (!is_null($route) && is_subclass_of($route, '\HexMakina\TightORM\Interfaces\ModelInterface')) {
364
            $route = $this->route_model($route);
365
        }
366
367
        return parent::route_factory($route, $route_params);
368
    }
369
370
    private function sanitize_post_data($post_data = [])
371
    {
372
        foreach ($this->modelClassName()::table()->columns() as $col) {
373
            if ($col->type()->is_boolean()) {
374
                $post_data[$col->name()] = !empty($post_data[$col->name()]);
375
            }
376
        }
377
378
        return $post_data;
379
    }
380
381
    // overriding displaycontroller
382
    protected function template_base()
383
    {
384
        return $this->modelClassName()::model_type();
385
    }
386
}
387