Passed
Pull Request — master (#116)
by Arnaud
03:38
created

FieldFactory::resolveConfiguration()   A

Complexity

Conditions 6
Paths 4

Size

Total Lines 38
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 18
nc 4
nop 2
dl 0
loc 38
rs 9.0444
c 0
b 0
f 0
1
<?php
2
3
namespace LAG\AdminBundle\Factory;
4
5
use LAG\AdminBundle\Configuration\ActionConfiguration;
6
use LAG\AdminBundle\Configuration\ApplicationConfiguration;
7
use LAG\AdminBundle\Configuration\ApplicationConfigurationStorage;
8
use LAG\AdminBundle\Configuration\FieldConfiguration;
9
use LAG\AdminBundle\Event\Events;
10
use LAG\AdminBundle\Event\Events\FieldEvent;
11
use LAG\AdminBundle\Exception\Exception;
12
use LAG\AdminBundle\Exception\Field\FieldTypeNotFoundException;
13
use LAG\AdminBundle\Field\FieldInterface;
14
use LAG\AdminBundle\Field\TwigAwareFieldInterface;
15
use LAG\AdminBundle\Field\TranslatorAwareFieldInterface;
16
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
17
use Symfony\Component\OptionsResolver\OptionsResolver;
18
use Symfony\Contracts\Translation\TranslatorInterface;
19
use Twig\Environment;
20
21
/**
22
 * Field factory. Instances fields.
23
 */
24
class FieldFactory
25
{
26
    /**
27
     * Application configuration.
28
     *
29
     * @var ApplicationConfiguration
30
     */
31
    protected $configuration;
32
33
    /**
34
     * Field class mapping array, indexed by field type.
35
     *
36
     * @var array
37
     */
38
    protected $fieldsMapping = [];
39
40
    /**
41
     * Translator for field values.
42
     *
43
     * @var TranslatorInterface
44
     */
45
    protected $translator;
46
47
    /**
48
     * Twig engine.
49
     *
50
     * @var Environment
51
     */
52
    protected $twig;
53
54
    /**
55
     * @var ConfigurationFactory
56
     */
57
    protected $configurationFactory;
58
59
    /**
60
     * @var EventDispatcherInterface
61
     */
62
    protected $eventDispatcher;
63
64
    /**
65
     * FieldFactory constructor.
66
     *
67
     * @param ApplicationConfigurationStorage $applicationConfigurationStorage
68
     * @param ConfigurationFactory            $configurationFactory
69
     * @param TranslatorInterface             $translator
70
     * @param EventDispatcherInterface        $eventDispatcher
71
     * @param Environment                     $twig
72
     */
73
    public function __construct(
74
        ApplicationConfigurationStorage $applicationConfigurationStorage,
75
        ConfigurationFactory $configurationFactory,
76
        TranslatorInterface $translator,
77
        EventDispatcherInterface $eventDispatcher,
78
        Environment $twig
79
    ) {
80
        $this->configuration = $applicationConfigurationStorage->getConfiguration();
81
        $this->fieldsMapping = $this
82
            ->configuration
83
            ->getParameter('fields_mapping'); // shortcut to the fields mapping array
84
        $this->translator = $translator;
85
        $this->twig = $twig;
86
        $this->configurationFactory = $configurationFactory;
87
        $this->eventDispatcher = $eventDispatcher;
88
    }
89
90
    /**
91
     * @param ActionConfiguration $configuration
92
     *
93
     * @return array
94
     */
95
    public function createFields(ActionConfiguration $configuration): array
96
    {
97
        $fields = [];
98
99
        foreach ($configuration->getParameter('fields') as $field => $fieldConfiguration) {
100
            $fields[] = $this->create($field, $fieldConfiguration, $configuration);
101
        }
102
103
        return $fields;
104
    }
105
106
    /**
107
     * Create a new field instance according to the given configuration.
108
     *
109
     * @param string              $name
110
     * @param array               $configuration
111
     * @param ActionConfiguration $actionConfiguration
112
     *
113
     * @return FieldInterface
114
     *
115
     * @throws Exception
116
     */
117
    public function create(string $name, array $configuration, ActionConfiguration $actionConfiguration): FieldInterface
118
    {
119
        $resolver = new OptionsResolver();
120
        $configuration = $this->resolveConfiguration($configuration, $actionConfiguration);
121
122
        // Dispatch an event to allow dynamic changes on the form type
123
        $event = new FieldEvent(
124
            $actionConfiguration->getAdminName(),
125
            $actionConfiguration->getActionName(),
126
            $name,
127
            $actionConfiguration->getAdminConfiguration()->get('entity'),
128
            $configuration['type']
129
        );
130
        $this->eventDispatcher->dispatch(Events::FIELD_PRE_CREATE, $event);
131
132
        if (null === $event->getType()) {
133
            throw new FieldTypeNotFoundException($event->getAdminName(), $event->getActionName(), $name);
134
        }
135
        $type = $event->getType();
136
        $options = array_merge($configuration['options'], $event->getOptions());
137
138
        if (!key_exists($type, $this->fieldsMapping)) {
139
            $type = 'auto';
140
        }
141
        $field = $this->instanciateField($name, $type);
142
        $field->configureOptions($resolver, $actionConfiguration);
143
144
        try {
145
            $field->setOptions($resolver->resolve($options));
146
        } catch (\Exception $exception) {
147
            throw new Exception(
148
                'An error has occurred when resolving the options for the field "'.$name.'": '.$exception->getMessage(),
149
                $exception->getCode(),
150
                $exception
151
            );
152
        }
153
        $event = new FieldEvent(
154
            $actionConfiguration->getAdminName(),
155
            $actionConfiguration->getActionName(),
156
            $name,
157
            $actionConfiguration->getAdminConfiguration()->get('entity'),
158
            $type,
159
            $field
160
        );
161
        $this->eventDispatcher->dispatch(Events::FIELD_POST_CREATE, $event);
162
163
        return $field;
164
    }
165
166
    /**
167
     * Return field class according to the field type. If the type is not present in the field mapping array, an
168
     * exception will be thrown.
169
     *
170
     * @param string $type
171
     *
172
     * @return string
173
     *
174
     * @throws Exception
175
     */
176
    private function getFieldClass(string $type): string
177
    {
178
        if (!array_key_exists($type, $this->fieldsMapping)) {
179
            throw new Exception("Field type \"{$type}\" not found in field mapping. Allowed fields are \"".implode('", "', $this->fieldsMapping).'"');
180
        }
181
182
        return $this->fieldsMapping[$type];
183
    }
184
185
    /**
186
     * @param string $name
187
     * @param string $type
188
     *
189
     * @return FieldInterface
190
     *
191
     * @throws Exception
192
     */
193
    private function instanciateField(string $name, string $type): FieldInterface
194
    {
195
        $fieldClass = $this->getFieldClass($type);
196
        $field = new $fieldClass($name);
197
198
        if (!$field instanceof FieldInterface) {
199
            throw new Exception("Field class \"{$fieldClass}\" must implements ".FieldInterface::class);
200
        }
201
202
        if ($field instanceof TranslatorAwareFieldInterface) {
203
            $field->setTranslator($this->translator);
204
        }
205
        if ($field instanceof TwigAwareFieldInterface) {
206
            $field->setTwig($this->twig);
207
        }
208
209
        return $field;
210
    }
211
212
    /**
213
     * @param array               $configuration
214
     * @param ActionConfiguration $actionConfiguration
215
     *
216
     * @return array
217
     *
218
     * @throws Exception
219
     */
220
    private function resolveConfiguration(array $configuration, ActionConfiguration $actionConfiguration)
221
    {
222
        $resolver = new OptionsResolver();
223
        $fieldConfiguration = new FieldConfiguration(array_keys($this->fieldsMapping));
224
        $fieldConfiguration->configureOptions($resolver);
225
        $fieldConfiguration->setParameters($resolver->resolve($configuration));
226
        $configuration = $fieldConfiguration->all();
227
228
        // For collection of fields, we resolve the configuration of each item
229
        if ('collection' == $fieldConfiguration->getType()) {
230
            $items = [];
231
232
            foreach ($fieldConfiguration->getOptions() as $itemFieldName => $itemFieldConfiguration) {
233
                // The configuration should be an array
234
                if (!$itemFieldConfiguration) {
235
                    $itemFieldConfiguration = [];
236
                }
237
238
                // The type should be defined
239
                if (!array_key_exists('type', $itemFieldConfiguration)) {
240
                    throw new Exception("Missing type configuration for field {$itemFieldName}");
241
                }
242
243
                // The field options are optional
244
                if (!array_key_exists('options', $itemFieldConfiguration)) {
245
                    $itemFieldConfiguration['options'] = [];
246
                }
247
248
                // create collection item
249
                $items[] = $this->create($itemFieldName, $itemFieldConfiguration, $actionConfiguration);
250
            }
251
            // add created item to the field options
252
            $configuration['options'] = [
253
                'fields' => $items,
254
            ];
255
        }
256
257
        return $configuration;
258
    }
259
}
260