Completed
Push — master ( 300eda...15c856 )
by Rafael
09:26
created

MutationFormResolverPlugin::getName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
1
<?php
2
/*******************************************************************************
3
 *  This file is part of the GraphQL Bundle package.
4
 *
5
 *  (c) YnloUltratech <[email protected]>
6
 *
7
 *  For the full copyright and license information, please view the LICENSE
8
 *  file that was distributed with this source code.
9
 ******************************************************************************/
10
11
namespace Ynlo\GraphQLBundle\Definition\Plugin;
12
13
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
14
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
15
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
16
use Symfony\Component\Form\Extension\Core\Type\EmailType;
17
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
18
use Symfony\Component\Form\Extension\Core\Type\NumberType;
19
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
20
use Symfony\Component\Form\Extension\Core\Type\TextType;
21
use Symfony\Component\Form\FormFactory;
22
use Symfony\Component\Form\FormInterface;
23
use Ynlo\GraphQLBundle\Definition\ArgumentDefinition;
24
use Ynlo\GraphQLBundle\Definition\DefinitionInterface;
25
use Ynlo\GraphQLBundle\Definition\FieldDefinition;
26
use Ynlo\GraphQLBundle\Definition\InputObjectDefinition;
27
use Ynlo\GraphQLBundle\Definition\MutationDefinition;
28
use Ynlo\GraphQLBundle\Definition\NodeAwareDefinitionInterface;
29
use Ynlo\GraphQLBundle\Definition\Registry\Endpoint;
30
use Ynlo\GraphQLBundle\Form\Type\GraphQLType;
31
use Ynlo\GraphQLBundle\Form\Type\IDType;
32
use Ynlo\GraphQLBundle\Type\Types;
33
use Ynlo\GraphQLBundle\Util\ClassUtils;
34
use Ynlo\GraphQLBundle\Util\TypeUtil;
35
36
class MutationFormResolverPlugin extends AbstractDefinitionPlugin
37
{
38
    protected $formFactory;
39
40
    public function __construct(FormFactory $formFactory)
41
    {
42
        $this->formFactory = $formFactory;
43
    }
44
45
    /**
46
     * {@inheritDoc}
47
     */
48
    public function getName(): string
49
    {
50
        return 'form';
51
    }
52
53
    /**
54
     * {@inheritDoc}
55
     */
56
    public function buildConfig(ArrayNodeDefinition $root): void
57
    {
58
        $config = $root
59
            ->info('Resolve the form to use as input for mutations')
60
            ->addDefaultsIfNotSet()
61
            ->canBeDisabled()
62
            ->children();
63
64
        $config->variableNode('type')
65
               ->defaultNull()
66
               ->info(
67
                   'Specify the form type to use,
68
[string] Name of the form type to use
69
[true|null] The form will be automatically resolved to ...Bundle\Form\Input\{Node}\{MutationName}Input.
70
[true] Throw a exception if the form can`t be located
71
[false] The form is not required and should not be resolved'
72
               );
73
        $config->variableNode('options')->defaultValue([])->info('Form options');
74
        $config->variableNode('argument')
75
               ->defaultValue('input')
76
               ->info('Name of the argument to use as input');
77
78
        $config->booleanNode('client_mutation_id')
79
               ->defaultTrue()
80
               ->info('Automatically add a field called clientMutationId');
81
    }
82
83
    /**
84
     * {@inheritDoc}
85
     */
86
    public function configure(DefinitionInterface $definition, Endpoint $endpoint, array $config): void
87
    {
88
        if (!$definition instanceof MutationDefinition || !isset($config['enabled'])) {
89
            return;
90
        }
91
92
        $formType = $config['type'] ?? null;
93
94
        //the related class is used to match a form using naming conventions
95
        $relatedClass = null;
96
        if ($definition instanceof NodeAwareDefinitionInterface && $definition->getNode()) {
97
            $relatedClass = $definition->getNode();
98
            if ($class = $endpoint->getClassForType($relatedClass)) {
99
                $relatedClass = $class;
100
            }
101
        } elseif ($definition->getResolver()) {
102
            $relatedClass = $definition->getResolver();
103
        }
104
105
        //try find the form using a related class
106
        if ($relatedClass && (!$formType || true === $formType)) {
107
            $bundleNamespace = ClassUtils::relatedBundleNamespace($relatedClass);
108
            $formClass = ClassUtils::applyNamingConvention(
109
                $bundleNamespace,
110
                'Form\Input',
111
                $definition->getNode(),
112
                ucfirst($definition->getName()),
113
                'Input'
114
            );
115
            if (class_exists($formClass)) {
116
                $formType = $formClass;
117
            } elseif (true === $formType) {
118
                $error = sprintf(
119
                    'Can`t find a valid input form type to use in "%s".
120
                         Create the form "%s" or specify a custom form',
121
                    $definition->getName(),
122
                    $formClass
123
                );
124
                throw new \RuntimeException($error);
125
            }
126
        }
127
128
        if ($formType) {
129
            $config['type'] = $formType;
130
131
            $form = $this->formFactory->create($formType, null, $config['options'] ?? []);
132
            $inputObject = $this->createFormInputObject($endpoint, $form, ucfirst($definition->getName()));
133
            $endpoint->addType($inputObject);
134
135
            $input = new ArgumentDefinition();
136
            $input->setName($config['argument']);
137
            $input->setType($inputObject->getName());
138
139
            if ($config['client_mutation_id']) {
140
                $clientMutationId = new FieldDefinition();
141
                $clientMutationId->setName('clientMutationId');
142
                $clientMutationId->setType(Types::STRING);
143
                $clientMutationId->setDescription('A unique identifier for the client performing the mutation.');
144
                $inputObject->prependField($clientMutationId);
145
            }
146
147
            $definition->addArgument($input);
148
            $definition->setMeta('form', $config);
149
        }
150
    }
151
152
    public function createFormInputObject(Endpoint $endpoint, FormInterface $form, string $name): InputObjectDefinition
153
    {
154
        $inputObject = new InputObjectDefinition();
155
        $inputObject->setName($name.'Input');
156
157
        foreach ($form->all() as $formField) {
158
            $field = new FieldDefinition();
159
            $label = $formField->getConfig()->getOption('label');
160
            $field->setName(!empty($label) ? $label : $formField->getName());
161
            $field->setDescription($formField->getConfig()->getOption('graphql_description') ?? null);
162
            $field->setDeprecationReason($formField->getConfig()->getOption('graphql_deprecation_reason') ?? null);
163
            $field->setNonNull($formField->isRequired());
164
            $field->setOriginName($formField->getName());
165
166
            if ($formField->all()) {
167
                $childName = $name.ucfirst($formField->getName());
168
                $child = $this->createFormInputObject($endpoint, $formField, $childName);
169
                $endpoint->addType($child);
170
                $field->setType($child->getName());
171
            } elseif (is_a($formField->getConfig()->getType()->getInnerType(), CollectionType::class)) {
172
                $childName = $name.ucfirst($formField->getName());
173
                $childFormType = $formField->getConfig()->getOptions()['entry_type'];
174
                $childFormOptions = $formField->getConfig()->getOptions()['entry_options'];
175
                $childForm = $this->formFactory->create($childFormType, null, $childFormOptions ?? []);
176
                $childForm->setParent($form);
177
                try {
178
                    //resolve type if is a valid scalar type or predefined type
179
                    $this->resolveFormFieldDefinition($field, $childForm);
180
                    $field->setList(true);
181
                } catch (\InvalidArgumentException $exception) {
182
                    //on exception, try build a child form for this collection
183
                    $child = $this->createFormInputObject($endpoint, $childForm, $childName);
184
                    $field->setType($child->getName());
185
                    $field->setList(true);
186
                    $endpoint->add($child);
187
                }
188
            } else {
189
                $this->resolveFormFieldDefinition($field, $formField);
190
            }
191
192
            $inputObject->addField($field);
193
        }
194
195
        return $inputObject;
196
    }
197
198
    public function resolveFormFieldDefinition(FieldDefinition $field, FormInterface $form)
199
    {
200
        $type = null;
201
        $resolver = $form->getConfig()->getType()->getOptionsResolver();
202
        if ($resolver->hasDefault('graphql_type')) {
203
            $type = $resolver->resolve([])['graphql_type'];
204
            if (!$type) {
205
                $type = $form->getConfig()->getOptions()['graphql_type'];
206
            }
207
            $field->setList(TypeUtil::isTypeList($type));
208
            $type = TypeUtil::normalize($type);
209
        }
210
211
        if (is_a($form->getConfig()->getType()->getInnerType(), GraphQLType::class, true)) {
212
            $type = $form->getConfig()->getOptions()['graphql_type'];
213
            $field->setList(TypeUtil::isTypeList($type));
214
            $type = TypeUtil::normalize($type);
215
        }
216
217
        if (!$type && is_a($form->getConfig()->getType()->getInnerType(), IDType::class, true)) {
218
            if ($form->getConfig()->hasOption('multiple') && $form->getConfig()->getOption('multiple')) {
219
                $field->setList(true);
220
            }
221
            $type = Types::ID;
222
        }
223
224
        if (!$type && is_a($form->getConfig()->getType()->getInnerType(), TextType::class, true)) {
225
            $type = Types::STRING;
226
        }
227
228
        if (!$type && is_a($form->getConfig()->getType()->getInnerType(), TextareaType::class, true)) {
229
            $type = Types::STRING;
230
        }
231
232
        if (!$type && is_a($form->getConfig()->getType()->getInnerType(), EmailType::class, true)) {
233
            $type = Types::STRING;
234
        }
235
236
        if (!$type && is_a($form->getConfig()->getType()->getInnerType(), CheckboxType::class, true)) {
237
            $type = Types::BOOLEAN;
238
        }
239
240
        if (!$type && is_a($form->getConfig()->getType()->getInnerType(), IntegerType::class, true)) {
241
            $type = Types::INT;
242
        }
243
244
        if (!$type && is_a($form->getConfig()->getType()->getInnerType(), NumberType::class, true)) {
245
            $type = Types::FLOAT;
246
        }
247
248
        if (!$type) {
249
            $error = sprintf(
250
                'The field "%s" in the parent form "%s" does not have a valid type. 
251
                If your are using a custom type, must define a option called "graphql_type" to resolve the form to a valid GraphQL type',
252
                $form->getName(),
253
                $form->getParent()->getName()
254
            );
255
            throw new \InvalidArgumentException($error);
256
        }
257
258
        $field->setType($type);
259
    }
260
}
261