ElementAnnotationsListener   F
last analyzed

Complexity

Total Complexity 80

Size/Duplication

Total Lines 385
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 3

Test Coverage

Coverage 97.74%

Importance

Changes 0
Metric Value
wmc 80
lcom 2
cbo 3
dl 0
loc 385
ccs 216
cts 221
cp 0.9774
rs 2
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A attach() 0 39 1
A handleToOne() 0 11 3
A handleToMany() 0 18 3
A handleExcludeAssociation() 0 6 2
A handleExcludeField() 0 8 2
C handleFilterField() 0 31 14
B handleRequiredAssociation() 0 34 10
A handleRequiredField() 0 13 3
C handleTypeField() 0 52 15
C handleValidatorField() 0 47 12
A getFieldMapping() 0 9 3
A getAssociationMapping() 0 9 3
A mergeAssociationOptions() 0 18 2
B prepareEvent() 0 27 6

How to fix   Complexity   

Complex Class

Complex classes like ElementAnnotationsListener 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 ElementAnnotationsListener, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace DoctrineORMModule\Form\Annotation;
6
7
use ArrayObject;
8
use Doctrine\Common\Persistence\ObjectManager;
9
use Doctrine\ORM\Mapping\ClassMetadata;
10
use DoctrineORMModule\Form\Element\EntitySelect;
11
use Laminas\EventManager\AbstractListenerAggregate;
12
use Laminas\EventManager\EventInterface;
13
use Laminas\EventManager\EventManagerInterface;
14
use Laminas\Form\Element as LaminasFormElement;
15
use function array_key_exists;
16
use function array_merge;
17
use function in_array;
18
19
class ElementAnnotationsListener extends AbstractListenerAggregate
20
{
21
    /** @var ObjectManager */
22
    protected $objectManager;
23
24 51
    public function __construct(ObjectManager $objectManager)
25
    {
26 51
        $this->objectManager = $objectManager;
27 51
    }
28
29
    /**
30
     * {@inheritDoc}
31
     */
32 4
    public function attach(EventManagerInterface $events, $priority = 1)
33
    {
34 4
        $this->listeners[] = $events->attach(
35 4
            AnnotationBuilder::EVENT_CONFIGURE_FIELD,
36 4
            [$this, 'handleFilterField']
37
        );
38 4
        $this->listeners[] = $events->attach(
39 4
            AnnotationBuilder::EVENT_CONFIGURE_FIELD,
40 4
            [$this, 'handleTypeField']
41
        );
42 4
        $this->listeners[] = $events->attach(
43 4
            AnnotationBuilder::EVENT_CONFIGURE_FIELD,
44 4
            [$this, 'handleValidatorField']
45
        );
46 4
        $this->listeners[] = $events->attach(
47 4
            AnnotationBuilder::EVENT_CONFIGURE_FIELD,
48 4
            [$this, 'handleRequiredField']
49
        );
50 4
        $this->listeners[] = $events->attach(
51 4
            AnnotationBuilder::EVENT_EXCLUDE_FIELD,
52 4
            [$this, 'handleExcludeField']
53
        );
54 4
        $this->listeners[] = $events->attach(
55 4
            AnnotationBuilder::EVENT_CONFIGURE_ASSOCIATION,
56 4
            [$this, 'handleToOne']
57
        );
58 4
        $this->listeners[] = $events->attach(
59 4
            AnnotationBuilder::EVENT_CONFIGURE_ASSOCIATION,
60 4
            [$this, 'handleToMany']
61
        );
62 4
        $this->listeners[] = $events->attach(
63 4
            AnnotationBuilder::EVENT_CONFIGURE_ASSOCIATION,
64 4
            [$this, 'handleRequiredAssociation']
65
        );
66 4
        $this->listeners[] = $events->attach(
67 4
            AnnotationBuilder::EVENT_EXCLUDE_ASSOCIATION,
68 4
            [$this, 'handleExcludeAssociation']
69
        );
70 4
    }
71
72
    /**
73
     * @internal
74
     */
75 7
    public function handleToOne(EventInterface $event) : void
76
    {
77 7
        $metadata = $event->getParam('metadata');
78 7
        $mapping  = $this->getAssociationMapping($event);
79 7
        if (! $mapping || ! $metadata->isSingleValuedAssociation($event->getParam('name'))) {
80 1
            return;
81
        }
82
83 6
        $this->prepareEvent($event);
84 6
        $this->mergeAssociationOptions($event->getParam('elementSpec'), $mapping['targetEntity']);
85 6
    }
86
87
    /**
88
     * @internal
89
     */
90 7
    public function handleToMany(EventInterface $event) : void
91
    {
92 7
        $metadata = $event->getParam('metadata');
93 7
        $mapping  = $this->getAssociationMapping($event);
94 7
        if (! $mapping || ! $metadata->isCollectionValuedAssociation($event->getParam('name'))) {
95 5
            return;
96
        }
97
98 2
        $this->prepareEvent($event);
99
100 2
        $elementSpec           = $event->getParam('elementSpec');
101 2
        $inputSpec             = $event->getParam('inputSpec');
102 2
        $inputSpec['required'] = false;
103
104 2
        $this->mergeAssociationOptions($elementSpec, $mapping['targetEntity']);
105
106 2
        $elementSpec['spec']['attributes']['multiple'] = true;
107 2
    }
108
109
    /**
110
     * @internal
111
     */
112 5
    public function handleExcludeAssociation(EventInterface $event) : bool
113
    {
114 5
        $metadata = $event->getParam('metadata');
115
116 5
        return $metadata && $metadata->isAssociationInverseSide($event->getParam('name'));
117
    }
118
119
    /**
120
     * @internal
121
     */
122 4
    public function handleExcludeField(EventInterface $event) : bool
123
    {
124 4
        $metadata    = $event->getParam('metadata');
125 4
        $identifiers = $metadata->getIdentifierFieldNames();
126
127 4
        return in_array($event->getParam('name'), $identifiers) &&
128 4
               $metadata->generatorType === ClassMetadata::GENERATOR_TYPE_IDENTITY;
129
    }
130
131
    /**
132
     * @internal
133
     */
134 40
    public function handleFilterField(EventInterface $event) : void
135
    {
136 40
        $metadata = $event->getParam('metadata');
137 40
        if (! $metadata || ! $metadata->hasField($event->getParam('name'))) {
138 36
            return;
139
        }
140
141 15
        $this->prepareEvent($event);
142
143 15
        $inputSpec = $event->getParam('inputSpec');
144
145 15
        switch ($metadata->getTypeOfField($event->getParam('name'))) {
146 15
            case 'bool':
147 14
            case 'boolean':
148 6
                $inputSpec['filters'][] = ['name' => 'Boolean'];
149 6
                break;
150 13
            case 'bigint':
151 12
            case 'integer':
152 11
            case 'smallint':
153 7
                $inputSpec['filters'][] = ['name' => 'Int'];
154 7
                break;
155 10
            case 'datetime':
156 9
            case 'datetimetz':
157 8
            case 'date':
158 7
            case 'time':
159 6
            case 'string':
160 5
            case 'text':
161 10
                $inputSpec['filters'][] = ['name' => 'StringTrim'];
162 10
                break;
163
        }
164 15
    }
165
166
    /**
167
     * @internal
168
     */
169 6
    public function handleRequiredAssociation(EventInterface $event) : void
170
    {
171 6
        $metadata = $event->getParam('metadata');
172 6
        $mapping  = $this->getAssociationMapping($event);
173 6
        if (! $mapping) {
174 1
            return;
175
        }
176
177 6
        $this->prepareEvent($event);
178
179 6
        $inputSpec   = $event->getParam('inputSpec');
180 6
        $elementSpec = $event->getParam('elementSpec');
181
182 6
        if ($metadata->isCollectionValuedAssociation($event->getParam('name'))) {
183 1
            $inputSpec['required'] = false;
184 5
        } elseif (isset($mapping['joinColumns'])) {
185 5
            $required = true;
186 5
            foreach ($mapping['joinColumns'] as $joinColumn) {
187 5
                if (isset($joinColumn['nullable']) && $joinColumn['nullable']) {
188 5
                    $required = false;
189 5
                    if ((isset($elementSpec['spec']['options']) &&
190 5
                         ! array_key_exists('empty_option', $elementSpec['spec']['options'])) ||
191 5
                         ! isset($elementSpec['spec']['options'])
192
                    ) {
193 5
                        $elementSpec['spec']['options']['empty_option'] = 'NULL';
194
                    }
195
196 5
                    break;
197
                }
198
            }
199
200 5
            $inputSpec['required'] = $required;
201
        }
202 6
    }
203
204
    /**
205
     * @internal
206
     */
207 6
    public function handleRequiredField(EventInterface $event) : void
208
    {
209 6
        $this->prepareEvent($event);
210
211 6
        $metadata  = $event->getParam('metadata');
212 6
        $inputSpec = $event->getParam('inputSpec');
213
214 6
        if (! $metadata || ! $metadata->hasField($event->getParam('name'))) {
215 1
            return;
216
        }
217
218 5
        $inputSpec['required'] = ! $metadata->isNullable($event->getParam('name'));
219 5
    }
220
221
    /**
222
     * @internal
223
     */
224 15
    public function handleTypeField(EventInterface $event) : void
225
    {
226 15
        $metadata = $event->getParam('metadata');
227 15
        $mapping  = $this->getFieldMapping($event);
228 15
        if (! $mapping) {
229
            return;
230
        }
231
232 15
        $this->prepareEvent($event);
233
234 15
        $elementSpec = $event->getParam('elementSpec');
235
236 15
        if (isset($elementSpec['spec']['options']['target_class'])) {
237
            $this->mergeAssociationOptions($elementSpec, $elementSpec['spec']['options']['target_class']);
238
239
            return;
240
        }
241
242 15
        if (isset($elementSpec['spec']['type']) || isset($elementSpec['spec']['attributes']['type'])) {
243 4
            return;
244
        }
245
246 15
        switch ($metadata->getTypeOfField($event->getParam('name'))) {
247 15
            case 'bigint':
248 14
            case 'integer':
249 13
            case 'smallint':
250 7
                $type = LaminasFormElement\Number::class;
251 7
                break;
252 12
            case 'bool':
253 11
            case 'boolean':
254 6
                $type = LaminasFormElement\Checkbox::class;
255 6
                break;
256 10
            case 'date':
257 5
                $type = LaminasFormElement\Date::class;
258 5
                break;
259 9
            case 'datetimetz':
260 8
            case 'datetime':
261 6
                $type = LaminasFormElement\DateTime::class;
262 6
                break;
263 7
            case 'time':
264 5
                $type = LaminasFormElement\Time::class;
265 5
                break;
266 6
            case 'text':
267 5
                $type = LaminasFormElement\Textarea::class;
268 5
                break;
269
            default:
270 5
                $type = LaminasFormElement::class;
271 5
                break;
272
        }
273
274 15
        $elementSpec['spec']['type'] = $type;
275 15
    }
276
277
    /**
278
     * @internal
279
     */
280 17
    public function handleValidatorField(EventInterface $event) : void
281
    {
282 17
        $mapping = $this->getFieldMapping($event);
283 17
        if (! $mapping) {
284
            return;
285
        }
286
287 17
        $metadata = $event->getParam('metadata');
288
289 17
        $this->prepareEvent($event);
290
291 17
        $inputSpec = $event->getParam('inputSpec');
292
293 17
        switch ($metadata->getTypeOfField($event->getParam('name'))) {
294 17
            case 'bool':
295 16
            case 'boolean':
296 6
                $inputSpec['validators'][] = [
297
                    'name'    => 'InArray',
298
                    'options' => ['haystack' => ['0', '1']],
299
                ];
300 6
                break;
301 15
            case 'float':
302 5
                $inputSpec['validators'][] = ['name' => 'Float'];
303 5
                break;
304 14
            case 'bigint':
305 13
            case 'integer':
306 12
            case 'smallint':
307 7
                $inputSpec['validators'][] = ['name' => 'Int'];
308 7
                break;
309 11
            case 'string':
310 6
                $elementSpec = $event->getParam('elementSpec');
311 6
                if (isset($elementSpec['spec']['type']) &&
312 6
                    in_array($elementSpec['spec']['type'], ['File', 'Laminas\Form\Element\File'])
313
                ) {
314 4
                    return;
315
                }
316
317 6
                if (isset($mapping['length'])) {
318 5
                    $inputSpec['validators'][] = [
319 5
                        'name'    => 'StringLength',
320 5
                        'options' => ['max' => $mapping['length']],
321
                    ];
322
                }
323
324 6
                break;
325
        }
326 17
    }
327
328
    /**
329
     * @return mixed[]|null
330
     */
331 28
    protected function getFieldMapping(EventInterface $event) : ?array
332
    {
333 28
        $metadata = $event->getParam('metadata');
334 28
        if ($metadata && $metadata->hasField($event->getParam('name'))) {
335 28
            return $metadata->getFieldMapping($event->getParam('name'));
336
        }
337
338
        return null;
339
    }
340
341
    /**
342
     * @return mixed[]|null
343
     */
344 12
    protected function getAssociationMapping(EventInterface $event) : ?array
345
    {
346 12
        $metadata = $event->getParam('metadata');
347 12
        if ($metadata && $metadata->hasAssociation($event->getParam('name'))) {
348 12
            return $metadata->getAssociationMapping($event->getParam('name'));
349
        }
350
351 1
        return null;
352
    }
353
354 8
    protected function mergeAssociationOptions(ArrayObject $elementSpec, string $targetEntity) : void
355
    {
356 8
        $options = $elementSpec['spec']['options'] ?? [];
357 8
        $options = array_merge(
358
            [
359 8
                'object_manager' => $this->objectManager,
360 8
                'target_class'   => $targetEntity,
361
            ],
362 8
            $options
363
        );
364
365 8
        $elementSpec['spec']['options'] = $options;
366 8
        if (isset($elementSpec['spec']['type'])) {
367 4
            return;
368
        }
369
370 8
        $elementSpec['spec']['type'] = EntitySelect::class;
371 8
    }
372
373
    /**
374
     * Normalizes event setting all expected parameters.
375
     */
376 47
    protected function prepareEvent(EventInterface $event) : void
377
    {
378 47
        foreach (['elementSpec', 'inputSpec'] as $type) {
379 47
            if ($event->getParam($type)) {
380 8
                continue;
381
            }
382
383 43
            $event->setParam($type, new ArrayObject());
384
        }
385
386 47
        $elementSpec = $event->getParam('elementSpec');
387 47
        $inputSpec   = $event->getParam('inputSpec');
388
389 47
        if (! isset($elementSpec['spec'])) {
390 41
            $elementSpec['spec'] = [];
391
        }
392
393 47
        if (! isset($inputSpec['filters'])) {
394 47
            $inputSpec['filters'] = [];
395
        }
396
397 47
        if (isset($inputSpec['validators'])) {
398 6
            return;
399
        }
400
401 47
        $inputSpec['validators'] = [];
402 47
    }
403
}
404