Completed
Pull Request — master (#90)
by Arnaud
17:45
created

ActionConfiguration::getFieldsNormalizer()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3.0175

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 17
ccs 7
cts 8
cp 0.875
rs 9.4285
cc 3
eloc 8
nc 1
nop 0
crap 3.0175
1
<?php
2
3
namespace LAG\AdminBundle\Action\Configuration;
4
5
use Closure;
6
use JK\Configuration\Configuration;
7
use LAG\AdminBundle\Admin\AdminInterface;
8
use LAG\AdminBundle\Admin\Behaviors\TranslationKeyTrait;
9
use LAG\AdminBundle\Filter\Configuration\FilterConfiguration;
10
use LAG\AdminBundle\Form\Type\DeleteType;
11
use LAG\AdminBundle\Form\Type\ListType;
12
use LAG\AdminBundle\LAGAdminBundle;
13
use LAG\AdminBundle\Menu\Configuration\MenuConfiguration;
14
use Symfony\Component\DependencyInjection\Container;
15
use Symfony\Component\OptionsResolver\Options;
16
use Symfony\Component\OptionsResolver\OptionsResolver;
17
18
class ActionConfiguration extends Configuration
19
{
20
    use TranslationKeyTrait;
21
    
22
    /**
23
     * Related Action name.
24
     *
25
     * @var string
26
     */
27
    private $actionName;
28
    
29
    /**
30
     * Related Admin (optional)
31
     *
32
     * @var AdminInterface
33
     */
34
    private $admin = null;
35
    
36
    /**
37
     * ActionConfiguration constructor.
38
     *
39
     * @param string              $actionName
40
     * @param AdminInterface|null $admin
41
     */
42 1
    public function __construct($actionName, AdminInterface $admin)
43
    {
44 1
        parent::__construct();
45
        
46 1
        $this->actionName = $actionName;
47 1
        $this->admin = $admin;
48 1
    }
49
    
50
    /**
51
     * Define allowed parameters and values for this configuration, using optionsResolver component.
52
     *
53
     * @param OptionsResolver $resolver
54
     */
55 1
    public function configureOptions(OptionsResolver $resolver)
56
    {
57 1
        $this->configureDefaultOptions($resolver);
58 1
        $this->configureNormalizers($resolver);
59
        
60 1
        $this->setAllowedTypes($resolver);
61 1
        $this->setAllowedValues($resolver);
62 1
    }
63
    
64
    /**
65
     * Configure the default options for an Action.
66
     *
67
     * @param OptionsResolver $resolver
68
     */
69 1
    private function configureDefaultOptions(OptionsResolver $resolver)
70
    {
71
        $resolver
72 1
            ->setDefaults([
73
                // the default action title is a translation key using the translation pattern and the action name
74 1
                'title' => $this->getDefaultTitle(),
75 1
                'service' => $this->getDefaultServiceId(),
76
                'fields' => [
77
                    'id' => [],
78
                ],
79
                // by default, only administrator can access the admin
80
                'permissions' => [
81
                    'ROLE_ADMIN',
82
                ],
83
                // all export are activated
84
                'export' => [
85
                    'json',
86
                    'html',
87
                    'csv',
88
                    'xls',
89
                ],
90
                // no order
91
                'order' => [],
92
                // route will be generated if empty string or null or false is provided
93 1
                'route' => '',
94
                // route parameters will be used when using the route (for links...)
95
                'route_parameters' => [],
96 1
                'route_path' => $this->getDefaultRoutePath(),
97 1
                'route_defaults' => $this->getDefaultRouteDefaults(),
98 1
                'route_requirements' => $this->getDefaultRouteRequirements(),
99
                // icon in the menu
100 1
                'icon' => '',
101
                // entities loading strategy
102
                'load_strategy' => null,
103
                // pager interface, only null or pagerfanta are allowed
104 1
                'pager' => 'pagerfanta',
105 1
                'max_per_page' => $this->admin->getConfiguration()->getParameter('max_per_page'),
106
                // default criteria used to load entities
107
                'criteria' => [],
108
                // filters, should be an array of string (field name => filter options)
109
                'filters' => [],
110
                'menus' => [],
111
                'batch' => false,
112
                // form configuration
113 1
                'form_handler' => $this->getDefaultFormHandler(),
114 1
                'form' => $this->getDefaultForm(),
115 1
                'form_options' => $this->getDefaultFormOptions(),
116
                // twig template
117 1
                'template' => $this->getDefaultTemplate(),
118 1
                'sortable' => $this->getDefaultSortable(),
119
            ]);
120 1
    }
121
    
122
    /**
123
     * Define the allowed values.
124
     *
125
     * @param OptionsResolver $resolver
126
     */
127 1
    private function setAllowedValues(OptionsResolver $resolver)
128
    {
129
        $resolver
130 1
            ->setAllowedValues('load_strategy', [
131 1
                AdminInterface::LOAD_STRATEGY_NONE,
132 1
                AdminInterface::LOAD_STRATEGY_UNIQUE,
133 1
                AdminInterface::LOAD_STRATEGY_MULTIPLE,
134
                null,
135
            ])
136 1
            ->setAllowedValues('pager', [
137 1
                'pagerfanta',
138
                false,
139
            ])
140
        ;
141 1
    }
142
    
143
    /**
144
     * Define the allowed types.
145
     *
146
     * @param OptionsResolver $resolver
147
     */
148 1
    private function setAllowedTypes(OptionsResolver $resolver)
149
    {
150
        $resolver
151 1
            ->setAllowedTypes('title', 'string')
152 1
            ->setAllowedTypes('fields', 'array')
153 1
            ->setAllowedTypes('order', 'array')
154 1
            ->setAllowedTypes('route', 'string')
155 1
            ->setAllowedTypes('route_parameters', 'array')
156 1
            ->setAllowedTypes('icon', 'string')
157 1
            ->setAllowedTypes('criteria', 'array')
158 1
            ->setAllowedTypes('filters', 'array')
159 1
            ->setAllowedTypes('menus', [
160 1
                'array',
161
                'boolean',
162
            ])
163 1
            ->setAllowedTypes('route_path', 'string')
164 1
            ->setAllowedTypes('route_defaults', 'array')
165 1
            ->setAllowedTypes('route_requirements', 'array')
166 1
            ->setAllowedTypes('form', 'string')
167 1
            ->setAllowedTypes('form_options', 'array')
168
        ;
169 1
    }
170
    
171
    /**
172
     * Configure the normalizers.
173
     *
174
     * @param OptionsResolver $resolver
175
     */
176 1
    private function configureNormalizers(OptionsResolver $resolver)
177
    {
178
        $resolver
179 1
            ->setNormalizer('fields', $this->getFieldsNormalizer())
180 1
            ->setNormalizer('order', $this->getOrderNormalizer())
181 1
            ->setNormalizer('route', $this->getRouteNormalizer())
182 1
            ->setNormalizer('load_strategy', $this->getLoadStrategyNormalizer())
183 1
            ->setNormalizer('criteria', $this->getCriteriaNormalizer())
184 1
            ->setNormalizer('menus', $this->getMenuNormalizer())
185 1
            ->setNormalizer('batch', $this->getBatchNormalizer())
186 1
            ->setNormalizer('filters', $this->getFiltersNormalizer())
187
        ;
188 1
    }
189
    
190
    /**
191
     * Return the field normalizer. It will transform null configuration into array to allow field type guessing
192
     * working.
193
     *
194
     * @return Closure
195
     */
196
    private function getFieldsNormalizer()
197
    {
198 1
        return function(Options $options, $fields) {
199 1
            $normalizedFields = [];
200
            
201 1
            foreach ($fields as $name => $field) {
202
                
203 1
                if ($field === null) {
204
                    $field = [];
205
                }
206
                
207 1
                $normalizedFields[$name] = $field;
208
            }
209
            
210 1
            return $normalizedFields;
211 1
        };
212
    }
213
    
214
    /**
215
     * Return the order normalizer. It will check if the order value passed are valid.
216
     *
217
     * @return Closure
218
     */
219
    private function getOrderNormalizer()
220
    {
221 1
        return function(Options $options, $order) {
222 1
            foreach ($order as $field => $sort) {
223
                
224
                if (!is_string($sort) || !is_string($field) || !in_array(strtolower($sort), ['asc', 'desc'])) {
225
                    throw new ConfigurationException(
226
                        'Order value should be an array of string (["field" => $key]), got '.gettype($sort),
227
                        $this->actionName,
228
                        $this->admin
229
                    );
230
                }
231
            }
232
            
233 1
            return $order;
234 1
        };
235
    }
236
    
237
    /**
238
     * Return the route normalizer. If an empty value or null or false, it will generate the route using the Admin.
239
     *
240
     * @return Closure
241
     */
242
    private function getRouteNormalizer()
243
    {
244 1
        return function(Options $options, $value) {
245 1
            if (!$value) {
246
                // generate default route from admin
247
                return $this
248 1
                    ->admin
249 1
                    ->generateRouteName($this->actionName);
250
            }
251
            
252
            return $value;
253 1
        };
254
    }
255
    
256
    /**
257
     * Return the load strategy normalizer. It will set the default strategy according to the action name, if no value
258
     * is provided.
259
     *
260
     * @return Closure
261
     */
262
    private function getLoadStrategyNormalizer()
263
    {
264 1
        return function(Options $options, $value) {
265 1
            if (!$value) {
266 1
                if ($this->actionName == 'create') {
267
                    $value = AdminInterface::LOAD_STRATEGY_NONE;
268 1
                } else if ($this->actionName == 'list') {
269 1
                    $value = AdminInterface::LOAD_STRATEGY_MULTIPLE;
270
                } else {
271
                    $value = AdminInterface::LOAD_STRATEGY_UNIQUE;
272
                }
273
            }
274
            
275 1
            return $value;
276 1
        };
277
    }
278
    
279
    /**
280
     * Return the menu normalizer. It will transform false values into an empty array to allow default menu
281
     * configuration working.
282
     *
283
     * @return Closure
284
     */
285
    private function getMenuNormalizer()
286
    {
287 1
        return function(Options $options, $menus) {
288
            // set default to an array
289 1
            if ($menus === false) {
290
                $menus = [];
291
            }
292
            
293 1
            return $menus;
294 1
        };
295
    }
296
    
297
    /**
298
     * Return the criteria normalizer. It will add the id parameters for the edit and delete actions if no value is
299
     * provided.
300
     *
301
     * @return Closure
302
     */
303
    private function getCriteriaNormalizer()
304
    {
305 1
        return function(Options $options, $value) {
306 1
            if (!$value) {
307
                $idActions = [
308 1
                    'edit',
309
                    'delete',
310
                ];
311
                
312 1
                if (in_array($this->actionName, $idActions)) {
313
                    $value = [
314
                        'id',
315
                    ];
316
                }
317
            }
318
            
319 1
            return $value;
320 1
        };
321
    }
322
    
323
    /**
324
     * Return the batch normalizer. If null value is provided, it will add the delete batch action if the delete
325
     * actions is allowed .
326
     *
327
     * @return Closure
328
     */
329
    private function getBatchNormalizer()
330
    {
331 1
        return function(Options $options, $batch) {
332
            // if batch is not activated, no more checks should be done
333 1
            if ($batch === false) {
334 1
                return $batch;
335
            }
336
            // for list actions, we add a default configuration
337
            if ($batch === null) {
338
                // delete action should be allowed in order to be place in batch actions
339
                $allowedActions = array_keys($this
340
                    ->admin
341
                    ->getConfiguration()
342
                    ->getParameter('actions'));
343
                
344
                if ($this->actionName == 'list' && in_array('delete', $allowedActions)) {
345
                    $pattern = $this
346
                        ->admin
347
                        ->getConfiguration()
348
                        ->getParameter('translation_pattern')
349
                    ;
350
                    
351
                    $batch = [
352
                        'items' => [
353
                            'delete' => [
354
                                'admin' => $this->admin->getName(),
355
                                'action' => 'delete',
356
                                'text' => $this->getTranslationKey($pattern, 'delete', $this->admin->getName()),
357
                            ],
358
                        ],
359
                    ];
360
                } else {
361
                    return $batch;
362
                }
363
            }
364
            $resolver = new OptionsResolver();
365
            $configuration = new MenuConfiguration();
366
            $configuration->configureOptions($resolver);
367
            $batch = $resolver->resolve($batch);
368
            
369
            return $batch;
370 1
        };
371
    }
372
    
373
    /**
374
     * Return the filters normalizer.
375
     *
376
     * @return Closure
377
     */
378
    private function getFiltersNormalizer()
379
    {
380 1
        return function(Options $options, $filters) {
381 1
            if (!is_array($filters)) {
382
                return [];
383
            }
384 1
            $normalizedData = [];
385 1
            $resolver = new OptionsResolver();
386
    
387 1
            foreach ($filters as $filter => $filterOptions) {
388
                // the filter name should be a string
389
                if (!is_string($filter)) {
390
                    throw new ConfigurationException(
391
                        'Invalid filter name "'.$filter.'"',
392
                        $this->actionName,
393
                        $this->admin
394
                    );
395
                }
396
                // normalize string notation
397
                // transform "name" => 'string' into "name" => ['type' => 'string']
0 ignored issues
show
Unused Code Comprehensibility introduced by
46% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
398
                if (is_string($filterOptions)) {
399
                    $filterOptions = [
400
                        'type' => $filterOptions,
401
                    ];
402
                }
403
    
404
                if (null === $filterOptions) {
405
                    $filterOptions = [];
406
                }
407
                $configuration = new FilterConfiguration();
408
                $configuration->configureOptions($resolver);
409
                $filterOptions = $resolver->resolve($filterOptions);
410
                $resolver->clear();
411
    
412
                // set the normalized data
413
                $normalizedData[$filter] = $filterOptions;
414
            }
415
            
416 1
            return $normalizedData;
417 1
        };
418
    }
419
    
420
    /**
421
     * Return the default title using the configured translation pattern.
422
     *
423
     * @return string
424
     */
425 1
    private function getDefaultTitle()
426
    {
427
        $translationPattern = $this
428 1
            ->admin
429 1
            ->getConfiguration()
430 1
            ->getParameter('translation_pattern')
431
        ;
432
        
433 1
        if ($this->admin && $translationPattern) {
434
            // by default, the action title is action name using the configured translation pattern
435
            
436
            $actionTitle = $this->getTranslationKey(
437
                $translationPattern,
438
                $this->actionName,
439
                $this->admin->getName()
440
            );
441
        } else {
442
            // no admin was provided, we camelize the action name
443 1
            $actionTitle = Container::camelize($this->actionName);
444
        }
445
        
446 1
        return $actionTitle;
447
    }
448
    
449
    /**
450
     * Return the default route path according to the action name.
451
     *
452
     * @return string
453
     */
454 1
    private function getDefaultRoutePath()
455
    {
456
        $pattern = $this
457 1
            ->admin
458 1
            ->getConfiguration()
459 1
            ->getParameter('routing_url_pattern')
460
        ;
461
        
462 1
        $path = str_replace('{admin}', $this->admin->getName(), $pattern);
463 1
        $path = str_replace('{action}', $this->actionName, $path);
464
        
465 1
        if (in_array($this->actionName, ['edit', 'delete'])) {
466
            $path .= '/{id}';
467
        }
468
        
469 1
        return $path;
470
    }
471
    
472
    /**
473
     * Return the defaults route parameters according to a mapping based on the action name.
474
     *
475
     * @return array
476
     */
477 1
    private function getDefaultRouteDefaults()
478
    {
479
        $mapping = [
480
            'list' => [
481 1
                '_controller' => LAGAdminBundle::SERVICE_ID_LIST_ACTION,
482 1
                '_admin' => $this->admin->getName(),
483 1
                '_action' => $this->actionName,
484
            ],
485
            'create' => [
486 1
                '_controller' => LAGAdminBundle::SERVICE_ID_CREATE_ACTION,
487 1
                '_admin' => $this->admin->getName(),
488 1
                '_action' => $this->actionName,
489
            ],
490
            'edit' => [
491 1
                '_controller' => LAGAdminBundle::SERVICE_ID_EDIT_ACTION,
492 1
                '_admin' => $this->admin->getName(),
493 1
                '_action' => $this->actionName,
494
            ],
495
            'delete' => [
496 1
                '_controller' => LAGAdminBundle::SERVICE_ID_DELETE_ACTION,
497 1
                '_admin' => $this->admin->getName(),
498 1
                '_action' => $this->actionName,
499
            ],
500
        ];
501 1
        $defaults = [];
502
        
503 1
        if (array_key_exists($this->actionName, $mapping)) {
504 1
            $defaults = $mapping[$this->actionName];
505
        }
506
        
507 1
        return $defaults;
508
    }
509
    
510
    /**
511
     * Return the default route requirements according to the action name.
512
     *
513
     * @return array
514
     */
515 1
    private function getDefaultRouteRequirements()
516
    {
517
        $mapping = [
518 1
            'edit' => [
519
                'id' => '\d+',
520
            ],
521
            'delete' => [
522
                'id' => '\d+',
523
            ],
524
        ];
525 1
        $requirements = [];
526
        
527 1
        if (array_key_exists($this->actionName, $mapping)) {
528
            $requirements = $mapping[$this->actionName];
529
        }
530
        
531 1
        return $requirements;
532
    }
533
    
534
    /**
535
     * Return the default service id according to the action name.
536
     *
537
     * @return string|null
538
     */
539 1
    private function getDefaultServiceId()
540
    {
541 1
        $mapping = LAGAdminBundle::getDefaultActionServiceMapping();
542
        
543 1
        if (!array_key_exists($this->actionName, $mapping)) {
544
            return null;
545
        }
546
        
547 1
        return $mapping[$this->actionName];
548
    }
549
    
550
    /**
551
     * Return the default form according to the action name.
552
     *
553
     * @return string|null
554
     */
555 1
    private function getDefaultForm()
556
    {
557
        $mapping = [
558 1
            'list' => ListType::class,
559
            'delete' => DeleteType::class,
560
        ];
561
        
562 1
        if (!array_key_exists($this->actionName, $mapping)) {
563
            // try to get an admin globally configured form
564
            $adminForm = $this
565
                ->admin
566
                ->getConfiguration()
567
                ->getParameter('form')
568
            ;
569
            
570
            if (null !== $adminForm) {
571
                return $adminForm;
572
            }
573
            
574
            return null;
575
        }
576
        
577 1
        return $mapping[$this->actionName];
578
    }
579
    
580
    /**
581
     * Return a default form handler service id, or null, according to to the action name.
582
     *
583
     * @return mixed|null
584
     */
585 1
    private function getDefaultFormHandler()
586
    {
587
        $mapping = [
588 1
            'edit' => LAGAdminBundle::SERVICE_ID_EDIT_FORM_HANDLER,
589 1
            'list' => LAGAdminBundle::SERVICE_ID_LIST_FORM_HANDLER,
590
        ];
591
        
592 1
        if (!array_key_exists($this->actionName, $mapping)) {
593
            return null;
594
        }
595
        
596 1
        return $mapping[$this->actionName];
597
    }
598
    
599 1
    private function getDefaultFormOptions()
600
    {
601
        $mapping = [
602 1
            'list' => [
603
                'actions' => [
604
                    'lag.admin.delete' => 'delete',
605
                ]
606
            ],
607
        ];
608
    
609 1
        if (!$this->isActionInMapping($mapping)) {
610
            return [];
611
        }
612
    
613 1
        return $mapping[$this->actionName];
614
    }
615
    
616 1
    private function getDefaultTemplate()
617
    {
618
        $mapping = [
619 1
            'list' => '@LAGAdmin/CRUD/list.html.twig',
620
            'edit' => '@LAGAdmin/CRUD/edit.html.twig',
621
            'create' => '@LAGAdmin/CRUD/create.html.twig',
622
            'delete' => '@LAGAdmin/CRUD/delete.html.twig',
623
        ];
624
        
625 1
        if (!$this->isActionInMapping($mapping)) {
626
            return null;
627
        }
628
        
629 1
        return $mapping[$this->actionName];
630
    }
631
    
632 1
    private function isActionInMapping(array $mapping)
633
    {
634 1
        return array_key_exists($this->actionName, $mapping);
635
    }
636
    
637 1
    private function getDefaultSortable()
638
    {
639
        $mapping = [
640 1
            'list' => true,
641
        ];
642
    
643 1
        if (!$this->isActionInMapping($mapping)) {
644
            return false;
645
        }
646
    
647 1
        return $mapping[$this->actionName];
648
    }
649
}
650