Passed
Push — master ( bb67c8...cd2c1d )
by Arnaud
14:35 queued 11:07
created

ActionConfiguration   F

Complexity

Total Complexity 73

Size/Duplication

Total Lines 489
Duplicated Lines 0 %

Test Coverage

Coverage 99.18%

Importance

Changes 5
Bugs 1 Features 0
Metric Value
eloc 213
c 5
b 1
f 0
dl 0
loc 489
ccs 241
cts 243
cp 0.9918
rs 2.56
wmc 73

39 Methods

Rating   Name   Duplication   Size   Complexity  
A getMenus() 0 3 1
A getLoadStrategy() 0 3 1
A getFields() 0 3 1
A getPageParameter() 0 3 1
A getActionClass() 0 3 1
A getRouteParameters() 0 3 1
A getPath() 0 3 1
A getDateFormat() 0 3 1
A getTemplate() 0 3 1
A getOrder() 0 3 1
A getIcon() 0 3 1
A getName() 0 3 1
A getMaxPerPage() 0 7 2
A getForm() 0 3 1
A getPermissions() 0 3 1
A getTranslationCatalog() 0 7 2
A getCriteria() 0 3 1
A getExport() 0 3 1
A getPager() 0 7 2
A isPaginationEnabled() 0 9 2
A getFilters() 0 3 1
A getRepositoryMethod() 0 3 1
A getTitle() 0 3 1
A getTranslationPattern() 0 7 2
A getController() 0 3 1
A getRoute() 0 3 1
A isTranslationEnabled() 0 3 1
A getFormOptions() 0 3 1
A getAdminName() 0 3 1
A getFormNormalizer() 0 21 5
A getLoadStrategyNormalizer() 0 16 4
A getRouteParametersNormalizer() 0 12 4
A getCriteriaNormalizer() 0 17 3
B getPathNormalizer() 0 51 8
A getFieldsNormalizer() 0 14 3
B configureOptions() 0 97 1
A getOrderNormalizer() 0 10 5
A getFiltersNormalizer() 0 23 3
A getTitleNormalizer() 0 16 3

How to fix   Complexity   

Complex Class

Complex classes like ActionConfiguration 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.

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 ActionConfiguration, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace LAG\AdminBundle\Configuration;
4
5
use Closure;
6
use JK\Configuration\Configuration;
7
use LAG\AdminBundle\Admin\Action;
8
use LAG\AdminBundle\Admin\AdminInterface;
9
use LAG\AdminBundle\Controller\AdminAction;
10
use LAG\AdminBundle\Exception\Exception;
11
use LAG\AdminBundle\Form\Type\AdminType;
12
use LAG\AdminBundle\Form\Type\DeleteType;
13
use Symfony\Component\Form\Extension\Core\Type\TextType;
14
use Symfony\Component\OptionsResolver\Options;
15
use Symfony\Component\OptionsResolver\OptionsResolver;
16
use function Symfony\Component\String\u;
17
18
class ActionConfiguration extends Configuration
19
{
20 33
    protected function configureOptions(OptionsResolver $resolver): void
21
    {
22
        $resolver
23
            // Main info
24 33
            ->setRequired('name')
25 33
            ->setAllowedTypes('name', 'string')
26 33
            ->setRequired('admin_name')
27 33
            ->setAllowedTypes('admin_name', 'string')
28 33
            ->setDefault('title', null)
29 33
            ->setAllowedTypes('title', ['string', 'null'])
30 33
            ->setNormalizer('title', $this->getTitleNormalizer())
31 33
            ->setDefault('icon', null)
32 33
            ->setAllowedTypes('icon', ['string', 'null'])
33 33
            ->setDefault('action_class', Action::class)
34 33
            ->setAllowedTypes('action_class', 'string')
35 33
            ->setRequired('template')
36 33
            ->setAllowedTypes('template', 'string')
37
38
            // Routing
39 33
            ->setDefault('controller', AdminAction::class)
40 33
            ->setAllowedTypes('controller', 'string')
41 33
            ->setRequired('route')
42 33
            ->setAllowedTypes('route', 'string')
43 33
            ->setDefault('route_parameters', [])
44 33
            ->setAllowedTypes('route_parameters', 'array')
45 33
            ->setNormalizer('route_parameters', $this->getRouteParametersNormalizer())
46 33
            ->setDefault('path', null)
47 33
            ->setAllowedTypes('path', ['string', 'null'])
48 33
            ->setNormalizer('path', $this->getPathNormalizer())
49
50
            // Fields
51 33
            ->setRequired('fields')
52 33
            ->setAllowedTypes('fields', 'array')
53 33
            ->setNormalizer('fields', $this->getFieldsNormalizer())
54
55
            // Filter and orders
56 33
            ->setDefault('order', [])
57 33
            ->setAllowedTypes('order', 'array')
58 33
            ->setNormalizer('order', $this->getOrderNormalizer())
59 33
            ->setDefault('criteria', [])
60 33
            ->setAllowedTypes('criteria', 'array')
61 33
            ->setNormalizer('criteria', $this->getCriteriaNormalizer())
62 33
            ->setDefault('filters', [])
63 33
            ->setAllowedTypes('filters', 'array')
64 33
            ->setNormalizer('filters', $this->getFiltersNormalizer())
65
66
            // Security
67 33
            ->setDefault('permissions', ['ROLE_ADMIN'])
68 33
            ->setAllowedTypes('permissions', 'array')
69
70
            // Export
71 33
            ->setDefault('export', ['csv', 'xml', 'yaml'])
72 33
            ->setAllowedTypes('export', 'array')
73
74
            // Data
75 33
            ->setDefault('load_strategy', null)
76 33
            ->setAllowedValues('load_strategy', [
77 33
                null,
78
                AdminInterface::LOAD_STRATEGY_NONE,
79
                AdminInterface::LOAD_STRATEGY_UNIQUE,
80
                AdminInterface::LOAD_STRATEGY_MULTIPLE,
81
            ])
82 33
            ->setNormalizer('load_strategy', $this->getLoadStrategyNormalizer())
83 33
            ->setDefault('repository_method', null)
84 33
            ->setAllowedTypes('repository_method', ['string', 'null'])
85
86
            // Pagination
87 33
            ->setDefault('pager', 'pagerfanta')
88 33
            ->setAllowedValues('pager', ['pagerfanta', false])
89 33
            ->setDefault('max_per_page', 25)
90 33
            ->setAllowedTypes('max_per_page', 'integer')
91 33
            ->setDefault('page_parameter', 'page')
92 33
            ->setAllowedTypes('page_parameter', 'string')
93
94 33
            ->setDefault('date_format', 'Y-m-d')
95 33
            ->setAllowedTypes('date_format', 'string')
96
97
            // Form
98 33
            ->setDefault('form', null)
99 33
            ->setAllowedTypes('form', ['string', 'null', 'boolean'])
100 33
            ->setNormalizer('form', $this->getFormNormalizer())
101 33
            ->setDefault('form_options', [])
102 33
            ->setAllowedTypes('form_options', 'array')
103
104
            // Menus
105 33
            ->setDefault('menus', [])
106 33
            ->setAllowedTypes('menus', 'array')
107
108
            // Translation
109 33
            ->setDefault('translation', function (OptionsResolver $translationResolver) {
110
                $translationResolver
111 32
                    ->setDefault('enabled', true)
112 32
                    ->setAllowedTypes('enabled', 'boolean')
113 32
                    ->setDefault('pattern', 'admin.{admin}.{key}')
114 32
                    ->setAllowedTypes('pattern', 'string')
115 32
                    ->setDefault('catalog', 'admin')
116 32
                    ->setAllowedTypes('catalog', 'string')
117
                ;
118 33
            })
119
        ;
120 33
    }
121
122 3
    public function getName(): string
123
    {
124 3
        return $this->getString('name');
125
    }
126
127 3
    public function getAdminName(): string
128
    {
129 3
        return $this->getString('admin_name');
130
    }
131
132 1
    public function getTitle(): string
133
    {
134 1
        return $this->getString('title');
135
    }
136
137 1
    public function getIcon(): ?string
138
    {
139 1
        return $this->get('icon');
140
    }
141
142 1
    public function getActionClass(): string
143
    {
144 1
        return $this->getString('action_class');
145
    }
146
147 3
    public function getTemplate(): string
148
    {
149 3
        return $this->get('template');
150
    }
151
152 1
    public function getController(): string
153
    {
154 1
        return $this->getString('controller');
155
    }
156
157 1
    public function getRoute(): string
158
    {
159 1
        return $this->getString('route');
160
    }
161
162 3
    public function getRouteParameters(): array
163
    {
164 3
        return $this->get('route_parameters');
165
    }
166
167 5
    public function getPath(): string
168
    {
169 5
        return $this->getString('path');
170
    }
171
172 4
    public function getFields(): array
173
    {
174 4
        return $this->get('fields');
175
    }
176
177 1
    public function getOrder(): array
178
    {
179 1
        return $this->get('order');
180
    }
181
182 1
    public function getCriteria(): array
183
    {
184 1
        return $this->get('criteria');
185
    }
186
187 3
    public function getFilters(): array
188
    {
189 3
        return $this->get('filters');
190
    }
191
192 1
    public function getPermissions(): array
193
    {
194 1
        return $this->get('permissions');
195
    }
196
197 1
    public function getExport(): array
198
    {
199 1
        return $this->get('export');
200
    }
201
202 6
    public function getLoadStrategy(): string
203
    {
204 6
        return $this->getString('load_strategy');
205
    }
206
207 3
    public function isPaginationEnabled(): bool
208
    {
209 3
        $pager = $this->get('pager');
210
211 3
        if ($pager === false) {
212 2
            return false;
213
        }
214
215 1
        return true;
216
    }
217
218 2
    public function getPager(): string
219
    {
220 2
        if (!$this->isPaginationEnabled()) {
221 1
            throw new Exception('The pagination is not enabled');
222
        }
223
224 1
        return $this->getString('pager');
225
    }
226
227 2
    public function getMaxPerPage(): int
228
    {
229 2
        if (!$this->isPaginationEnabled()) {
230 1
            throw new Exception('The pagination is not enabled');
231
        }
232
233 1
        return $this->getInt('max_per_page');
234
    }
235
236 1
    public function getPageParameter(): string
237
    {
238 1
        return $this->getString('page_parameter');
239
    }
240
241 1
    public function getDateFormat(): string
242
    {
243 1
        return $this->getString('date_format');
244
    }
245
246 10
    public function getForm(): ?string
247
    {
248 10
        return $this->get('form');
249
    }
250
251 3
    public function getFormOptions(): array
252
    {
253 3
        return $this->get('form_options');
254
    }
255
256 1
    public function getMenus(): array
257
    {
258 1
        return $this->get('menus');
259
    }
260
261 3
    public function isTranslationEnabled(): bool
262
    {
263 3
        return $this->get('translation')['enabled'];
264
    }
265
266 2
    public function getTranslationPattern(): string
267
    {
268 2
        if (!$this->isTranslationEnabled()) {
269 1
            throw new Exception('The translation is not enabled');
270
        }
271
272 1
        return $this->get('translation')['pattern'];
273
    }
274
275 2
    public function getTranslationCatalog(): string
276
    {
277 2
        if (!$this->isTranslationEnabled()) {
278 1
            throw new Exception('The translation is not enabled');
279
        }
280
281 1
        return $this->get('translation')['catalog'];
282
    }
283
284
    public function getRepositoryMethod(): ?string
285
    {
286
        return $this->get('repository_method');
287
    }
288
289 33
    private function getTitleNormalizer(): Closure
290
    {
291 33
        return function (Options $options, $value) {
292 32
            if ($value === null) {
293 32
                if ($options->offsetGet('translation')['enabled']) {
294 30
                    $value = u($options->offsetGet('translation')['pattern'])
295 30
                        ->replace('{admin}', $options->offsetGet('admin_name'))
296 30
                        ->replace('{key}', $options->offsetGet('name'))
297 30
                        ->toString()
298
                    ;
299
                } else {
300 2
                    $value = u($options->offsetGet('name'))->title()->toString();
301
                }
302
            }
303
304 32
            return $value;
305 33
        };
306
    }
307
308 33
    private function getPathNormalizer(): Closure
309
    {
310 33
        return function (Options $options, $value) {
311 32
            if ($value !== null) {
312 29
                $path = u($value);
313
314 29
                if ($path->endsWith('/')) {
315 1
                    $path = $path->slice(0, -1);
316
                }
317
318 29
                return $path->toString();
319
            }
320 3
            $loadStrategy = $options->offsetGet('load_strategy');
321 3
            $path = u($options->offsetGet('admin_name'))
322 3
                ->snake()
323 3
                ->replace('_', '-')
324
            ;
325
326 3
            if (!$path->endsWith('s')) {
327 3
                $path = $path->append('s');
328
329 3
                if ($path->endsWith('ys')) {
330 2
                    $path = $path->before('ys')->append('ies');
331
                }
332
            }
333 3
            $snakeActionName = u($options->offsetGet('name'))
334 3
                ->snake()
335 3
                ->replace('_', '-')
336
            ;
337
338
            // Edit the the default action. It is not append to the path (ex: articles/{id} for edit,
339
            // articles/{id}/delete for delete)
340 3
            if ($loadStrategy === AdminInterface::LOAD_STRATEGY_UNIQUE) {
341 1
                $path = $path->append('/{id}');
342
343 1
                if ($options->offsetGet('name') !== 'edit') {
344
                    $path = $path
345 1
                        ->append('/')
346 1
                        ->append($snakeActionName)
347
                    ;
348
                }
349
            }
350
351 3
            if ($loadStrategy === AdminInterface::LOAD_STRATEGY_NONE) {
352
                $path = $path
353 1
                    ->append('/')
354 1
                    ->append($snakeActionName)
355
                ;
356
            }
357
358 3
            return $path->toString();
359 33
        };
360
    }
361
362
    /**
363
     * Return the field normalizer. It will transform null configuration into array to allow field type guessing
364
     * working.
365
     */
366 33
    private function getFieldsNormalizer(): Closure
367
    {
368 33
        return function (Options $options, $fields) {
369 31
            $normalizedFields = [];
370
371 31
            foreach ($fields as $name => $field) {
372 3
                if ($field === null) {
373 1
                    $field = [];
374
                }
375
376 3
                $normalizedFields[$name] = $field;
377
            }
378
379 31
            return $normalizedFields;
380 33
        };
381
    }
382
383
    /**
384
     * Return the order normalizer. It will check if the order value passed are valid.
385
     */
386 33
    private function getOrderNormalizer(): Closure
387
    {
388 33
        return function (Options $options, $order) {
389 32
            foreach ($order as $field => $sort) {
390 1
                if (!\is_string($sort) || !\is_string($field) || !\in_array(strtolower($sort), ['asc', 'desc'])) {
391 1
                    throw new Exception('Order value should be an array of string (["field" => $key]), got '.\gettype($sort));
392
                }
393
            }
394
395 31
            return $order;
396 33
        };
397
    }
398
399 33
    private function getLoadStrategyNormalizer(): Closure
400
    {
401 33
        return function (Options $options, $value) {
402 31
            if ($value !== null) {
403 2
                return $value;
404
            }
405
406 29
            if ($options->offsetGet('name') == 'create') {
407 3
                $value = AdminInterface::LOAD_STRATEGY_NONE;
408 26
            } elseif ($options->offsetGet('name') == 'list') {
409 2
                $value = AdminInterface::LOAD_STRATEGY_MULTIPLE;
410
            } else {
411 24
                $value = AdminInterface::LOAD_STRATEGY_UNIQUE;
412
            }
413
414 29
            return $value;
415 33
        };
416
    }
417
418
    /**
419
     * Return the criteria normalizer. It will add the id parameters for the edit and delete actions if no value is
420
     * provided.
421
     */
422 33
    private function getCriteriaNormalizer(): Closure
423
    {
424 33
        return function (Options $options, $value) {
425 31
            if (!$value) {
426
                $idActions = [
427 31
                    'edit',
428
                    'delete',
429
                ];
430
431 31
                if (\in_array($options->offsetGet('name'), $idActions)) {
432
                    $value = [
433 5
                        'id',
434
                    ];
435
                }
436
            }
437
438 31
            return $value;
439 33
        };
440
    }
441
442
    /**
443
     * Return the filters normalizer.
444
     */
445 33
    private function getFiltersNormalizer(): Closure
446
    {
447 33
        return function (Options $options, $data) {
448 31
            $normalizedData = [];
449
450 31
            foreach ($data as $name => $field) {
451 2
                if (\is_string($field)) {
452
                    $field = [
453 1
                        'name' => $field,
454
                        'type' => TextType::class,
455
                        'options' => [],
456
                    ];
457
                } else {
458 1
                    $field['name'] = $name;
459
                }
460
461 2
                $filterConfiguration = new FilterConfiguration();
462 2
                $filterConfiguration->configure($field);
463
464 2
                $normalizedData[$field['name']] = $filterConfiguration->toArray();
465
            }
466
467 31
            return $normalizedData;
468 33
        };
469
    }
470
471 33
    private function getFormNormalizer(): Closure
472
    {
473 33
        return function (Options $options, $value) {
474 31
            if ($value !== null && $value !== false) {
475 3
                return $value;
476
            }
477
478 28
            if ($value === false) {
479 1
                return null;
480
            }
481
            $mapping = [
482 27
                'create' => AdminType::class,
483
                'edit' => AdminType::class,
484
                'delete' => DeleteType::class,
485
            ];
486
487 27
            if (\array_key_exists($options->offsetGet('name'), $mapping)) {
488 7
                $value = $mapping[$options->offsetGet('name')];
489
            }
490
491 27
            return $value;
492 33
        };
493
    }
494
495 33
    private function getRouteParametersNormalizer(): Closure
496
    {
497 33
        return function (Options $options, $value) {
498 32
            if (\count($value) > 0) {
499 1
                return $value;
500
            }
501
502 31
            if ($options->offsetGet('name') === 'edit' || $options->offsetGet('name') === 'delete') {
503 5
                return ['id' => null];
504
            }
505
506 26
            return [];
507 33
        };
508
    }
509
}
510