Passed
Push — develop ( b8535a...b19a66 )
by Mathieu
11:29
created

addFormProperty()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 10
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 3
1
<?php
2
3
namespace Neimheadh\SonataAnnotationBundle\Reader;
4
5
use Doctrine\Common\Annotations\Reader;
6
use InvalidArgumentException;
7
use Neimheadh\SonataAnnotationBundle\Annotation\ActionAnnotationInterface;
8
use Neimheadh\SonataAnnotationBundle\Annotation\PositionAnnotationInterface;
9
use Neimheadh\SonataAnnotationBundle\Annotation\Sonata\Admin;
10
use ReflectionClass;
11
use Sonata\AdminBundle\Datagrid\DatagridMapper;
12
use Sonata\AdminBundle\Datagrid\ListMapper;
13
use Sonata\AdminBundle\Form\FormMapper;
14
use Sonata\AdminBundle\Mapper\MapperInterface;
15
use Sonata\AdminBundle\Show\ShowMapper;
16
17
/**
18
 * Field configuration reader.
19
 */
20
abstract class AbstractFieldConfigurationReader extends AbstractReader
21
{
22
23
    /**
24
     * Associated annotation class.
25
     *
26
     * @var string|null
27
     */
28
    protected ?string $annotationClass;
29
30
    /**
31
     * @param Reader  $annotationReader Doctrine annotation reader.
32
     * @param ?string $annotationClass  Associated annotation class.
33
     */
34
    public function __construct(
35
        Reader $annotationReader,
36
        ?string $annotationClass = null
37
    ) {
38
        parent::__construct($annotationReader);
39
        $this->annotationClass = $annotationClass;
40
    }
41
42
    /**
43
     * Build list field configurations.
44
     *
45
     * @param ReflectionClass $class  Entity class.
46
     * @param MapperInterface $mapper Admin mapper.
47
     *
48
     * @return void
49
     */
50
    public function configureFields(
51
        ReflectionClass $class,
52
        MapperInterface $mapper
53
    ): void {
54
        $this->configureReaderFields(
55
            $class,
56
            $mapper,
57
            $this->annotationClass
58
        );
59
    }
60
61
    /**
62
     * Configure fields with positions.
63
     *
64
     * @param ReflectionClass $class           Read entity class.
65
     * @param MapperInterface $mapper          Admin mapper.
66
     * @param string|null     $annotationClass Filtered annotation class.
67
     * @param string|null     $action          Current action.
68
     *
69
     * @return void
70
     */
71
    protected function configureReaderFields(
72
        ReflectionClass $class,
73
        MapperInterface $mapper,
74
        ?string $annotationClass = null,
75
        ?string $action = null
76
    ): void {
77
        $fields = $this->loadPositionAnnotationFields(
78
            $class,
79
            $annotationClass,
80
            $action
81
        );
82
83
        if (empty($fields) && $annotationClass !== null) {
84
            $fields = $this->loadDefaultFields($class, $annotationClass);
85
        }
86
87
        array_walk(
88
            $fields,
89
            fn(array $field) => $this->addMapperProperty($mapper, $field)
90
        );
91
    }
92
93
    /**
94
     * Get fields from the class Admin annotation.
95
     *
96
     * @param Admin   $annotation Admin annotation.
97
     * @param ?string $action     Current action.
98
     *
99
     * @return array
100
     */
101
    abstract protected function getAdminAnnotationFields(
102
        Admin $annotation,
103
        ?string $action
104
    ): array;
105
106
    /**
107
     * Check if given annotation is supported.
108
     *
109
     * @param object      $annotation Annotation.
110
     * @param string|null $action     Current action.
111
     *
112
     * @return bool
113
     */
114
    protected function isSupported(object $annotation, ?string $action): bool
115
    {
116
        return !$annotation instanceof ActionAnnotationInterface
117
            || $action === null
118
            || !isset($annotation->action)
119
            || $annotation->action === $action;
120
    }
121
122
    /**
123
     * Load default fields.
124
     *
125
     * @param ReflectionClass $class           Entity class.
126
     * @param string|null     $annotationClass Reader annotation class.
127
     *
128
     * @return array
129
     */
130
    protected function loadDefaultFields(
131
        ReflectionClass $class,
132
        string $annotationClass
133
    ): array {
134
        $properties = [];
135
136
        foreach ($class->getProperties() as $property) {
137
            $properties[] = [
138
                'name' => $property->getName(),
139
                'annotation' => new $annotationClass(),
140
            ];
141
        }
142
143
        return $properties;
144
    }
145
146
    /**
147
     * Load class properties with position annotation.
148
     *
149
     * @param ReflectionClass $class           Entity class.
150
     * @param string|null     $annotationClass Reader annotation class.
151
     * @param string|null     $action          Current action.
152
     *
153
     * @return array
154
     */
155
    protected function loadPositionAnnotationFields(
156
        ReflectionClass $class,
157
        ?string $annotationClass,
158
        ?string $action
159
    ): array {
160
        $propertiesAndMethods = array_merge(
161
            $this->getClassPropertiesAnnotations($class, $annotationClass),
162
            $this->getClassMethodsAnnotations($class, $annotationClass)
163
        );
164
165
        $propertiesWithPosition = [];
166
        $propertiesWithoutPosition = [];
167
168
        $this->fillAdminProperties(
169
            $propertiesWithoutPosition,
170
            $propertiesWithPosition,
171
            $class,
172
            $action
173
        );
174
175
        foreach ($propertiesAndMethods as $name => $annotations) {
176
            /** @var PositionAnnotationInterface $annotation */
177
            foreach ($annotations as $annotation) {
178
                if (!$this->isSupported($annotation, $action)) {
179
                    continue;
180
                }
181
182
                $name = $this->getAnnotationFieldName($name, $annotation);
183
184
                $this->stackProperty(
185
                    $name,
186
                    $annotation,
187
                    $propertiesWithPosition,
188
                    $propertiesWithoutPosition
189
                );
190
            }
191
        }
192
193
        ksort($propertiesWithPosition);
194
195
        return array_merge(
196
            $propertiesWithPosition,
197
            $propertiesWithoutPosition
198
        );
199
    }
200
201
    /**
202
     * Stack given annotation as a property with or without position.
203
     *
204
     * @param string $name                      Property name.
205
     * @param object $annotation                Annotation.
206
     * @param array  $propertiesWithPosition    Properties with position stack.
207
     * @param array  $propertiesWithoutPosition Properties without position
208
     *                                          stack.
209
     *
210
     * @return void
211
     */
212
    protected function stackProperty(
213
        string $name,
214
        object $annotation,
215
        array &$propertiesWithPosition,
216
        array &$propertiesWithoutPosition
217
    ): void {
218
        if (!isset($annotation->position)) {
219
            $propertiesWithoutPosition[] = [
220
                'name' => $name,
221
                'annotation' => $annotation,
222
            ];
223
224
            return;
225
        }
226
227
        if (array_key_exists(
228
            $annotation->position,
229
            $propertiesWithPosition
230
        )) {
231
            throw new InvalidArgumentException(
232
                sprintf(
233
                    'Position "%s" is already in use by "%s", try setting a different position for "%s".',
234
                    $annotation->position,
235
                    $propertiesWithPosition[$annotation->position]['name'],
236
                    $name
237
                )
238
            );
239
        }
240
241
        $propertiesWithPosition[$annotation->position] = [
242
            'name' => $name,
243
            'annotation' => $annotation,
244
        ];
245
    }
246
247
    /**
248
     * Add property to datagrid mapper.
249
     *
250
     * @param DatagridMapper $mapper   Datagrid mapper.
251
     * @param string         $name     Property name.
252
     * @param array          $settings Annotation settings.
253
     *
254
     * @return void
255
     */
256
    private function addDatagridProperty(
257
        DatagridMapper $mapper,
258
        string $name,
259
        array $settings
260
    ): void {
261
        $mapper->add(
262
            $name,
263
            $settings[0] ?? null,
264
            $settings[1] ?? [],
265
            $settings[2] ?? [],
266
        );
267
    }
268
269
    /**
270
     * Add property to form mapper.
271
     *
272
     * @param FormMapper $mapper   Datagrid mapper.
273
     * @param string     $name     Property name.
274
     * @param array      $settings Annotation settings.
275
     *
276
     * @return void
277
     */
278
    private function addFormProperty(
279
        FormMapper $mapper,
280
        string $name,
281
        array $settings
282
    ): void {
283
        $mapper->add(
284
            $name,
285
            $settings[0] ?? null,
286
            $settings[1] ?? [],
287
            $settings[2] ?? [],
288
        );
289
    }
290
291
    /**
292
     * Add property to list mapper.
293
     *
294
     * @param ListMapper $mapper   List mapper.
295
     * @param string     $name     Property name.
296
     * @param array      $settings Annotation settings.
297
     *
298
     * @return void
299
     */
300
    private function addListProperty(
301
        ListMapper $mapper,
302
        string $name,
303
        array $settings
304
    ): void {
305
        $mapper->add(
306
            $name,
307
            $settings[0] ?? null,
308
            $settings[1] ?? [],
309
        );
310
    }
311
312
    /**
313
     * Add a property to the given mapper.
314
     *
315
     * @param MapperInterface $mapper   Admin mapper.
316
     * @param array           $property Property information.
317
     *
318
     * @return void
319
     */
320
    private function addMapperProperty(
321
        MapperInterface $mapper,
322
        array $property
323
    ): void {
324
        $settings = $property['annotation']->getSettings();
325
326
        if ($mapper instanceof DatagridMapper) {
327
            $this->addDatagridProperty($mapper, $property['name'], $settings);
328
        } elseif ($mapper instanceof FormMapper) {
329
            $this->addFormProperty($mapper, $property['name'], $settings);
330
        } elseif ($mapper instanceof ListMapper) {
331
            $this->addListProperty($mapper, $property['name'], $settings);
332
        } elseif ($mapper instanceof ShowMapper) {
333
            $this->addShowProperty($mapper, $property['name'], $settings);
334
        }
335
    }
336
337
    /**
338
     * Add property to show mapper.
339
     *
340
     * @param ShowMapper $mapper   Show mapper.
341
     * @param string     $name     Property name.
342
     * @param array      $settings Annotation settings.
343
     *
344
     * @return void
345
     */
346
    private function addShowProperty(
347
        ShowMapper $mapper,
348
        string $name,
349
        array $settings
350
    ): void {
351
        $mapper->add(
352
            $name,
353
            $settings[0] ?? null,
354
            $settings[1] ?? [],
355
        );
356
    }
357
358
    /**
359
     * Fill properties arrays with Admin class annotation properties.
360
     *
361
     * @param array           $withoutPosition Properties without position.
362
     * @param array           $withPosition    Properties with position.
363
     * @param ReflectionClass $class           Administrated class.
364
     * @param string|null     $action          Action name.
365
     *
366
     * @return void
367
     */
368
    private function fillAdminProperties(
369
        array &$withoutPosition,
370
        array &$withPosition,
371
        ReflectionClass $class,
372
        ?string $action
373
    ): void {
374
        $admin = $this->annotationReader->getClassAnnotation(
375
            $class,
376
            Admin::class
377
        );
378
379
        if ($admin === null) {
380
            return;
381
        }
382
383
        $adminFields = $this->getAdminAnnotationFields($admin, $action);
384
385
        /** @var PositionAnnotationInterface $field */
386
        foreach ($adminFields as $name => $field) {
387
            if (!$this->isSupported($field, $action)) {
388
                continue;
389
            }
390
391
            $name = $this->getAnnotationFieldName($name, $field);
392
393
            $this->stackProperty(
394
                $name,
395
                $field,
396
                $withPosition,
397
                $withoutPosition
398
            );
399
        }
400
    }
401
402
}