Completed
Push — master ( 92fe32...b0dd29 )
by Philip
06:20
created

Controller   C

Complexity

Total Complexity 58

Size/Duplication

Total Lines 488
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 0%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 58
lcom 1
cbo 10
dl 0
loc 488
ccs 0
cts 292
cp 0
rs 6.3005
c 3
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A modifyFilesAndSetFlashBag() 0 14 3
A setValidationFailedFlashes() 0 7 2
C modifyEntity() 0 33 7
A getAfterDeleteRedirectParameters() 0 15 3
C buildUpListFilter() 0 25 8
A getNotFoundPage() 0 9 1
A __construct() 0 8 1
A setLocaleAndCheckEntity() 0 9 2
A create() 0 7 1
B showList() 0 46 4
B show() 0 35 5
A edit() 0 10 2
B delete() 0 30 5
A renderFile() 0 11 4
A deleteFile() 0 17 4
A staticFile() 0 23 3
A setLocale() 0 14 3

How to fix   Complexity   

Complex Class

Complex classes like Controller often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Controller, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the CRUDlex package.
5
 *
6
 * (c) Philip Lehmann-Böhm <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace CRUDlex;
13
14
use League\Flysystem\FilesystemInterface;
15
use League\Flysystem\Util\MimeType;
16
use Symfony\Component\HttpFoundation\RedirectResponse;
17
use Symfony\Component\HttpFoundation\Request;
18
use Symfony\Component\HttpFoundation\Response;
19
use Symfony\Component\HttpFoundation\Session\SessionInterface;
20
use Symfony\Component\HttpFoundation\StreamedResponse;
21
use Symfony\Component\Translation\TranslatorInterface;
22
use Twig_Environment;
23
24
25
/**
26
 * Default implementation of the ControllerInterface.
27
 */
28
class Controller implements ControllerInterface {
29
30
    /**
31
     * Holds the filesystme.
32
     * @var FilesystemInterface
33
     */
34
    protected $filesystem;
35
36
    /**
37
     * Holds the session.
38
     * @var SessionInterface
39
     */
40
    protected $session;
41
42
    /**
43
     * Holds the translator.
44
     * @var TranslatorInterface
45
     */
46
    protected $translator;
47
48
    /**
49
     * Holds the service.
50
     * @var Service
51
     */
52
    protected $service;
53
54
    /**
55
     * Holds the Twig instance.
56
     * @var Twig_Environment
57
     */
58
    protected $twig;
59
60
    /**
61
     * Postprocesses the entity after modification by handling the uploaded
62
     * files and setting the flash.
63
     *
64
     * @param Request $request
65
     * the current request
66
     * @param AbstractData $crudData
67
     * the data instance of the entity
68
     * @param Entity $instance
69
     * the entity
70
     * @param string $entity
71
     * the name of the entity
72
     * @param string $mode
73
     * whether to 'edit' or to 'create' the entity
74
     *
75
     * @return null|\Symfony\Component\HttpFoundation\RedirectResponse
76
     * the HTTP response of this modification
77
     */
78
    protected function modifyFilesAndSetFlashBag(Request $request, AbstractData $crudData, Entity $instance, $entity, $mode)
79
    {
80
        $id          = $instance->get('id');
81
        $fileHandler = new FileHandler($this->filesystem, $crudData->getDefinition());
82
        $result      = $mode == 'edit' ? $fileHandler->updateFiles($crudData, $request, $instance, $entity) : $fileHandler->createFiles($crudData, $request, $instance, $entity);
83
        if (!$result) {
84
            return null;
85
        }
86
        $this->session->getFlashBag()->add('success', $this->translator->trans('crudlex.'.$mode.'.success', [
87
            '%label%' => $crudData->getDefinition()->getLabel(),
88
            '%id%' => $id
89
        ]));
90
        return new RedirectResponse($this->service->generateURL('crudShow', ['entity' => $entity, 'id' => $id]));
91
    }
92
93
    /**
94
     * Sets the flashes of a failed entity modification.
95
     *
96
     * @param boolean $optimisticLocking
97
     * whether the optimistic locking failed
98
     * @param string $mode
99
     * the modification mode, either 'create' or 'edit'
100
     */
101
    protected function setValidationFailedFlashes($optimisticLocking, $mode)
102
    {
103
        $this->session->getFlashBag()->add('danger', $this->translator->trans('crudlex.'.$mode.'.error'));
104
        if ($optimisticLocking) {
105
            $this->session->getFlashBag()->add('danger', $this->translator->trans('crudlex.edit.locked'));
106
        }
107
    }
108
109
    /**
110
     * Validates and saves the new or updated entity and returns the appropriate HTTP
111
     * response.
112
     *
113
     * @param Request $request
114
     * the current request
115
     * @param AbstractData $crudData
116
     * the data instance of the entity
117
     * @param Entity $instance
118
     * the entity
119
     * @param string $entity
120
     * the name of the entity
121
     * @param boolean $edit
122
     * whether to edit (true) or to create (false) the entity
123
     *
124
     * @return Response
0 ignored issues
show
Documentation introduced by
Should the return type not be RedirectResponse|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
125
     * the HTTP response of this modification
126
     */
127
    protected function modifyEntity(Request $request, AbstractData $crudData, Entity $instance, $entity, $edit)
128
    {
129
        $fieldErrors = [];
130
        $mode        = $edit ? 'edit' : 'create';
131
        if ($request->getMethod() == 'POST') {
132
            $instance->populateViaRequest($request);
133
            $validator  = new EntityValidator($instance);
134
            $validation = $validator->validate($crudData, intval($request->get('version')));
135
136
            $fieldErrors = $validation['errors'];
137
            if (!$validation['valid']) {
138
                $optimisticLocking = isset($fieldErrors['version']);
139
                $this->setValidationFailedFlashes($optimisticLocking, $mode);
140
            } else {
141
                $modified = $edit ? $crudData->update($instance) : $crudData->create($instance);
142
                $response = $modified ? $this->modifyFilesAndSetFlashBag($request, $crudData, $instance, $entity, $mode) : false;
143
                if ($response) {
144
                    return $response;
145
                }
146
                $this->session->getFlashBag()->add('danger', $this->translator->trans('crudlex.'.$mode.'.failed'));
147
            }
148
        }
149
150
        return $this->twig->render($this->service->getTemplate('template', 'form', $entity), [
151
            'crud' => $this->service,
152
            'crudEntity' => $entity,
153
            'crudData' => $crudData,
154
            'entity' => $instance,
155
            'mode' => $mode,
156
            'fieldErrors' => $fieldErrors,
157
            'layout' => $this->service->getTemplate('layout', $mode, $entity)
158
        ]);
159
    }
160
161
    /**
162
     * Gets the parameters for the redirection after deleting an entity.
163
     *
164
     * @param Request $request
165
     * the current request
166
     * @param string $entity
167
     * the entity name
168
     * @param string $redirectPage
169
     * reference, where the page to redirect to will be stored
170
     *
171
     * @return array<string,string>
172
     * the parameters of the redirection, entity and id
173
     */
174
    protected function getAfterDeleteRedirectParameters(Request $request, $entity, &$redirectPage)
175
    {
176
        $redirectPage       = 'crudList';
177
        $redirectParameters = ['entity' => $entity];
178
        $redirectEntity     = $request->get('redirectEntity');
179
        $redirectId         = $request->get('redirectId');
180
        if ($redirectEntity && $redirectId) {
181
            $redirectPage       = 'crudShow';
182
            $redirectParameters = [
183
                'entity' => $redirectEntity,
184
                'id' => $redirectId
185
            ];
186
        }
187
        return $redirectParameters;
188
    }
189
190
    /**
191
     * Builds up the parameters of the list page filters.
192
     *
193
     * @param Request $request
194
     * the current request
195
     * @param EntityDefinition $definition
196
     * the current entity definition
197
     * @param array &$filter
198
     * will hold a map of fields to request parameters for the filters
199
     * @param boolean $filterActive
200
     * reference, will be true if at least one filter is active
201
     * @param array $filterToUse
202
     * reference, will hold a map of fields to integers (0 or 1) which boolean filters are active
203
     * @param array $filterOperators
204
     * reference, will hold a map of fields to operators for AbstractData::listEntries()
205
     */
206
    protected function buildUpListFilter(Request $request, EntityDefinition $definition, &$filter, &$filterActive, &$filterToUse, &$filterOperators)
207
    {
208
        foreach ($definition->getFilter() as $filterField) {
209
            $type                 = $definition->getType($filterField);
210
            $filter[$filterField] = $request->get('crudFilter'.$filterField);
211
            if ($filter[$filterField]) {
212
                $filterActive                  = true;
213
                $filterToUse[$filterField]     = $filter[$filterField];
214
                $filterOperators[$filterField] = '=';
215
                if ($type === 'boolean') {
216
                    $filterToUse[$filterField] = $filter[$filterField] == 'true' ? 1 : 0;
217
                } else if ($type === 'reference') {
218
                    $filter[$filterField] = ['id' => $filter[$filterField]];
219
                } else if ($type === 'many') {
220
                    $filter[$filterField] = array_map(function($value) {
221
                        return ['id' => $value];
222
                    }, $filter[$filterField]);
223
                    $filterToUse[$filterField] = $filter[$filterField];
224
                } else if (in_array($type, ['text', 'multiline', 'fixed'])) {
225
                    $filterToUse[$filterField]     = '%'.$filter[$filterField].'%';
226
                    $filterOperators[$filterField] = 'LIKE';
227
                }
228
            }
229
        }
230
    }
231
232
    /**
233
     * Generates the not found page.
234
     *
235
     * @param string $error
236
     * the cause of the not found error
237
     *
238
     * @return Response
239
     * the rendered not found page with the status code 404
240
     */
241
    protected function getNotFoundPage($error)
242
    {
243
        return new Response($this->twig->render('@crud/notFound.twig', [
244
            'crud' => $this->service,
245
            'error' => $error,
246
            'crudEntity' => '',
247
            'layout' => $this->service->getTemplate('layout', '', '')
248
        ]), 404);
249
    }
250
251
    /**
252
     * Controller constructor.
253
     *
254
     * @param Service $service
255
     * the CRUDlex service
256
     * @param FilesystemInterface $filesystem
257
     * the used filesystem
258
     * @param Twig_Environment $twig
259
     * the Twig environment
260
     * @param SessionInterface $session
261
     * the session service
262
     * @param TranslatorInterface $translator
263
     * the translation service
264
     */
265
    public function __construct(Service $service, FilesystemInterface $filesystem, Twig_Environment $twig, SessionInterface $session, TranslatorInterface $translator)
266
    {
267
        $this->service    = $service;
268
        $this->filesystem = $filesystem;
269
        $this->twig       = $twig;
270
        $this->session    = $session;
271
        $this->translator = $translator;
272
    }
273
274
    /**
275
     * {@inheritdoc}
276
     */
277
    public function setLocaleAndCheckEntity(Request $request)
278
    {
279
        $locale = $this->translator->getLocale();
280
        $this->service->setLocale($locale);
281
        if (!$this->service->getData($request->get('entity'))) {
282
            return $this->getNotFoundPage($this->translator->trans('crudlex.entityNotFound'));
283
        }
284
        return null;
285
    }
286
287
    /**
288
     * {@inheritdoc}
289
     */
290
    public function create(Request $request, $entity)
291
    {
292
        $crudData = $this->service->getData($entity);
293
        $instance = $crudData->createEmpty();
294
        $instance->populateViaRequest($request);
295
        return $this->modifyEntity($request, $crudData, $instance, $entity, false);
296
    }
297
298
    /**
299
     * {@inheritdoc}
300
     */
301
    public function showList(Request $request, $entity)
302
    {
303
        $crudData   = $this->service->getData($entity);
304
        $definition = $crudData->getDefinition();
305
306
        $filter          = [];
307
        $filterActive    = false;
308
        $filterToUse     = [];
309
        $filterOperators = [];
310
        $this->buildUpListFilter($request, $definition, $filter, $filterActive, $filterToUse, $filterOperators);
311
312
        $pageSize = $definition->getPageSize();
313
        $total    = $crudData->countBy($definition->getTable(), $filterToUse, $filterOperators, true);
314
        $page     = abs(intval($request->get('crudPage', 0)));
315
        $maxPage  = intval($total / $pageSize);
316
        if ($total % $pageSize == 0) {
317
            $maxPage--;
318
        }
319
        if ($page > $maxPage) {
320
            $page = $maxPage;
321
        }
322
        $skip = $page * $pageSize;
323
324
        $sortField            = $request->get('crudSortField', $definition->getInitialSortField());
325
        $sortAscendingRequest = $request->get('crudSortAscending');
326
        $sortAscending        = $sortAscendingRequest !== null ? $sortAscendingRequest === 'true' : $definition->isInitialSortAscending();
327
328
        $entities = $crudData->listEntries($filterToUse, $filterOperators, $skip, $pageSize, $sortField, $sortAscending);
329
330
        return $this->twig->render($this->service->getTemplate('template', 'list', $entity), [
331
            'crud' => $this->service,
332
            'crudEntity' => $entity,
333
            'crudData' => $crudData,
334
            'definition' => $definition,
335
            'entities' => $entities,
336
            'pageSize' => $pageSize,
337
            'maxPage' => $maxPage,
338
            'page' => $page,
339
            'total' => $total,
340
            'filter' => $filter,
341
            'filterActive' => $filterActive,
342
            'sortField' => $sortField,
343
            'sortAscending' => $sortAscending,
344
            'layout' => $this->service->getTemplate('layout', 'list', $entity)
345
        ]);
346
    }
347
348
    /**
349
     * {@inheritdoc}
350
     */
351
    public function show($entity, $id)
352
    {
353
        $crudData = $this->service->getData($entity);
354
        $instance = $crudData->get($id);
355
        if (!$instance) {
356
            return $this->getNotFoundPage($this->translator->trans('crudlex.instanceNotFound'));
357
        }
358
        $definition = $crudData->getDefinition();
359
360
        $childrenLabelFields = $definition->getChildrenLabelFields();
361
        $children            = [];
362
        if (count($childrenLabelFields) > 0) {
363
            foreach ($definition->getChildren() as $child) {
364
                $childField      = $child[1];
365
                $childEntity     = $child[2];
366
                $childLabelField = array_key_exists($childEntity, $childrenLabelFields) ? $childrenLabelFields[$childEntity] : 'id';
367
                $childCrud       = $this->service->getData($childEntity);
368
                $children[]      = [
369
                    $childCrud->getDefinition()->getLabel(),
370
                    $childEntity,
371
                    $childLabelField,
372
                    $childCrud->listEntries([$childField => $instance->get('id')]),
373
                    $childField
374
                ];
375
            }
376
        }
377
378
        return $this->twig->render($this->service->getTemplate('template', 'show', $entity), [
379
            'crud' => $this->service,
380
            'crudEntity' => $entity,
381
            'entity' => $instance,
382
            'children' => $children,
383
            'layout' => $this->service->getTemplate('layout', 'show', $entity)
384
        ]);
385
    }
386
387
    /**
388
     * {@inheritdoc}
389
     */
390
    public function edit(Request $request, $entity, $id)
391
    {
392
        $crudData = $this->service->getData($entity);
393
        $instance = $crudData->get($id);
394
        if (!$instance) {
395
            return $this->getNotFoundPage($this->translator->trans('crudlex.instanceNotFound'));
396
        }
397
398
        return $this->modifyEntity($request, $crudData, $instance, $entity, true);
399
    }
400
401
    /**
402
     * {@inheritdoc}
403
     */
404
    public function delete(Request $request, $entity, $id)
405
    {
406
        $crudData = $this->service->getData($entity);
407
        $instance = $crudData->get($id);
408
        if (!$instance) {
409
            return $this->getNotFoundPage($this->translator->trans('crudlex.instanceNotFound'));
410
        }
411
412
        $fileHandler  = new FileHandler($this->filesystem, $crudData->getDefinition());
413
        $filesDeleted = $fileHandler->deleteFiles($crudData, $instance, $entity);
414
        $deleted      = $filesDeleted ? $crudData->delete($instance) : AbstractData::DELETION_FAILED_EVENT;
415
416
        if ($deleted === AbstractData::DELETION_FAILED_EVENT) {
417
            $this->session->getFlashBag()->add('danger', $this->translator->trans('crudlex.delete.failed'));
418
            return new RedirectResponse($this->service->generateURL('crudShow', ['entity' => $entity, 'id' => $id]));
419
        } elseif ($deleted === AbstractData::DELETION_FAILED_STILL_REFERENCED) {
420
            $this->session->getFlashBag()->add('danger', $this->translator->trans('crudlex.delete.error', [
421
                '%label%' => $crudData->getDefinition()->getLabel()
422
            ]));
423
            return new RedirectResponse($this->service->generateURL('crudShow', ['entity' => $entity, 'id' => $id]));
424
        }
425
426
        $redirectPage       = 'crudList';
427
        $redirectParameters = $this->getAfterDeleteRedirectParameters($request, $entity, $redirectPage);
428
429
        $this->session->getFlashBag()->add('success', $this->translator->trans('crudlex.delete.success', [
430
            '%label%' => $crudData->getDefinition()->getLabel()
431
        ]));
432
        return new RedirectResponse($this->service->generateURL($redirectPage, $redirectParameters));
433
    }
434
435
    /**
436
     * {@inheritdoc}
437
     */
438
    public function renderFile($entity, $id, $field)
439
    {
440
        $crudData   = $this->service->getData($entity);
441
        $instance   = $crudData->get($id);
442
        $definition = $crudData->getDefinition();
443
        if (!$instance || $definition->getType($field) != 'file' || !$instance->get($field)) {
444
            return $this->getNotFoundPage($this->translator->trans('crudlex.instanceNotFound'));
445
        }
446
        $fileHandler = new FileHandler($this->filesystem, $definition);
447
        return $fileHandler->renderFile($instance, $entity, $field);
448
    }
449
450
    /**
451
     * {@inheritdoc}
452
     */
453
    public function deleteFile($entity, $id, $field)
454
    {
455
        $crudData = $this->service->getData($entity);
456
        $instance = $crudData->get($id);
457
        if (!$instance) {
458
            return $this->getNotFoundPage($this->translator->trans('crudlex.instanceNotFound'));
459
        }
460
        $fileHandler = new FileHandler($this->filesystem, $crudData->getDefinition());
461
        if (!$crudData->getDefinition()->getField($field, 'required', false) && $fileHandler->deleteFile($crudData, $instance, $entity, $field)) {
462
            $instance->set($field, '');
463
            $crudData->update($instance);
464
            $this->session->getFlashBag()->add('success', $this->translator->trans('crudlex.file.deleted'));
465
        } else {
466
            $this->session->getFlashBag()->add('danger', $this->translator->trans('crudlex.file.notDeleted'));
467
        }
468
        return new RedirectResponse($this->service->generateURL('crudShow', ['entity' => $entity, 'id' => $id]));
469
    }
470
471
    /**
472
     * {@inheritdoc}
473
     */
474
    public function staticFile(Request $request)
475
    {
476
        $fileParam = str_replace('..', '', $request->get('file'));
477
        $file      = __DIR__.'/../static/'.$fileParam;
478
        if (!$fileParam || !file_exists($file)) {
479
            return $this->getNotFoundPage($this->translator->trans('crudlex.resourceNotFound'));
480
        }
481
482
        $mimeType = MimeType::detectByFilename($file);
483
        $size     = filesize($file);
484
485
        $streamedFileResponse = new StreamedFileResponse();
486
        $response             = new StreamedResponse($streamedFileResponse->getStreamedFileFunction($file), 200, [
487
            'Content-Type' => $mimeType,
488
            'Content-Disposition' => 'attachment; filename="'.basename($file).'"',
489
            'Content-length' => $size
490
        ]);
491
492
        $response->setETag(filemtime($file))->setPublic()->isNotModified($request);
493
        $response->send();
494
495
        return $response;
496
    }
497
498
    /**
499
     * {@inheritdoc}
500
     */
501
    public function setLocale(Request $request, $locale)
502
    {
503
504
        if (!in_array($locale, $this->service->getLocales())) {
505
            return $this->getNotFoundPage('Locale '.$locale.' not found.');
506
        }
507
508
        $manageI18n = $this->service->isManageI18n();
509
        if ($manageI18n) {
510
            $this->session->set('locale', $locale);
511
        }
512
        $redirect = $request->get('redirect');
513
        return new RedirectResponse($redirect);
514
    }
515
}
516