Passed
Push — master ( c325a6...db2527 )
by Yonel Ceruto
11:21
created

MutationFormResolverExtension::getName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 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\Extension;
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
/**
37
 * MutationFormResolverExtension
38
 */
39
class MutationFormResolverExtension extends AbstractDefinitionExtension
40
{
41
    /**
42
     * @var FormFactory
43
     */
44
    protected $formFactory;
45
46
    /**
47
     * @param FormFactory $formFactory
48
     */
49 21
    public function __construct(FormFactory $formFactory)
50
    {
51 21
        $this->formFactory = $formFactory;
52 21
    }
53
54
    /**
55
     * {@inheritDoc}
56
     */
57 21
    public function getName(): string
58
    {
59 21
        return 'form';
60
    }
61
62
    /**
63
     * {@inheritDoc}
64
     */
65 21
    public function buildConfig(ArrayNodeDefinition $root)
66
    {
67
        $config = $root
68 21
            ->info('Resolve the form to use as input for mutations')
69 21
            ->addDefaultsIfNotSet()
70 21
            ->canBeDisabled()
71 21
            ->children();
72
73 21
        $config->variableNode('type')
74 21
               ->defaultNull()
75 21
               ->info(
76 21
                   'Specify the form type to use,
77
[string] Name of the form type to use
78
[true|null] The form will be automatically resolved to ...Bundle\Form\Input\{Node}\{MutationName}Input.
79
[true] Throw a exception if the form can`t be located
80
[false] The form is not required and should not be resolved'
81
               );
82 21
        $config->variableNode('options')->defaultValue([])->info('Form options');
83 21
        $config->variableNode('argument')
84 21
               ->defaultValue('input')
85 21
               ->info('Name of the argument to use as input');
86
87 21
        $config->booleanNode('client_mutation_id')
88 21
               ->defaultTrue()
89 21
               ->info('Automatically add a field called clientMutationId');
90 21
    }
91
92
    /**
93
     * {@inheritDoc}
94
     */
95 21
    public function configure(DefinitionInterface $definition, Endpoint $endpoint, array $config)
96
    {
97 21
        if (!$config || !$config['enabled'] || !$definition instanceof MutationDefinition) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $config of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
98 21
            return;
99
        }
100
101 21
        $formType = $config['type'] ?? null;
102
103
        //the related class is used to match a form using naming conventions
104 21
        $relatedClass = null;
105 21
        if ($definition instanceof NodeAwareDefinitionInterface && $definition->getNode()) {
106 21
            $relatedClass = $definition->getNode();
107 21
            if ($class = $endpoint->getClassForType($relatedClass)) {
108 21
                $relatedClass = $class;
109
            }
110
        } elseif ($definition->getResolver()) {
111
            $relatedClass = $definition->getResolver();
112
        }
113
114
        //try find the form using a related class
115 21
        if ($relatedClass && (!$formType || true === $formType)) {
116 21
            $bundleNamespace = ClassUtils::relatedBundleNamespace($relatedClass);
117 21
            $formClass = ClassUtils::applyNamingConvention(
118 21
                $bundleNamespace,
119 21
                'Form\Input',
120 21
                $definition->getNode(),
121 21
                ucfirst($definition->getName()),
122 21
                'Input'
123
            );
124 21
            if (class_exists($formClass)) {
125 21
                $formType = $formClass;
126
            } elseif (true === $formType) {
127
                $error = sprintf(
128
                    'Can`t find a valid input form type to use in "%s".
129
                         Create the form "%s" or specify a custom form',
130
                    $definition->getName(),
131
                    $formClass
132
                );
133
                throw new \Exception($error);
134
            }
135
        }
136
137 21
        if ($formType) {
138 21
            $config['type'] = $formType;
139
140 21
            $form = $this->formFactory->create($formType, null, $config['options'] ?? []);
141 21
            $inputObject = $this->createFormInputObject($endpoint, $form, ucfirst($definition->getName()));
142 21
            $endpoint->addType($inputObject);
143
144 21
            $input = new ArgumentDefinition();
145 21
            $input->setName($config['argument']);
146 21
            $input->setType($inputObject->getName());
147
148 21
            if ($config['client_mutation_id']) {
149 21
                $clientMutationId = new FieldDefinition();
150 21
                $clientMutationId->setName('clientMutationId');
151 21
                $clientMutationId->setType(Types::STRING);
152 21
                $clientMutationId->setDescription('A unique identifier for the client performing the mutation.');
153 21
                $inputObject->prependField($clientMutationId);
154
            }
155
156 21
            $definition->addArgument($input);
157 21
            $definition->setMeta('form', $config);
158
        }
159 21
    }
160
161
    /**
162
     * @param Endpoint      $endpoint
163
     * @param FormInterface $form
164
     * @param string        $name
165
     *
166
     * @return InputObjectDefinition
167
     */
168 21
    public function createFormInputObject(Endpoint $endpoint, FormInterface $form, $name)
169
    {
170 21
        $inputObject = new InputObjectDefinition();
171 21
        $inputObject->setName($name.'Input');
172
173 21
        foreach ($form->all() as $formField) {
174 21
            $field = new FieldDefinition();
175 21
            $field->setName($formField->getConfig()->getOption('label') ?? $formField->getName());
176 21
            $field->setNonNull($formField->isRequired());
177 21
            $field->setOriginName($formField->getName());
178
179 21
            if ($formField->all()) {
180 21
                $childName = $name.ucfirst($formField->getName());
181 21
                $child = $this->createFormInputObject($endpoint, $formField, $childName);
182 21
                $endpoint->addType($child);
183 21
                $field->setType($child->getName());
184 21
            } elseif (is_a($formField->getConfig()->getType()->getInnerType(), CollectionType::class)) {
185
                $childName = $name.ucfirst($formField->getName());
186
                $childFormType = $formField->getConfig()->getOptions()['entry_type'];
187
                $childFormOptions = $formField->getConfig()->getOptions()['entry_options'];
188
                $childForm = $this->formFactory->create($childFormType, null, $childFormOptions ?? []);
189
                $childForm->setParent($form);
190
                try {
191
                    //resolve type if is a valid scalar type or predefined type
192
                    $this->resolveFormFieldDefinition($field, $childForm);
193
                    $field->setList(true);
194
                } catch (\InvalidArgumentException $exception) {
195
                    //on exception, try build a child form for this collection
196
                    $child = $this->createFormInputObject($endpoint, $childForm, $childName);
197
                    $field->setType($child->getName());
198
                    $field->setList(true);
199
                    $endpoint->add($child);
200
                }
201
            } else {
202 21
                $this->resolveFormFieldDefinition($field, $formField);
203
            }
204
205 21
            $inputObject->addField($field);
206
        }
207
208 21
        return $inputObject;
209
    }
210
211
    /**
212
     * @param FieldDefinition $field
213
     * @param FormInterface   $form
214
     */
215 21
    public function resolveFormFieldDefinition(FieldDefinition $field, FormInterface $form)
216
    {
217 21
        $type = null;
218 21
        $resolver = $form->getConfig()->getType()->getOptionsResolver();
219 21
        if ($resolver->hasDefault('graphql_type')) {
220
            $type = $resolver->resolve([])['graphql_type'];
221
            $field->setList(TypeUtil::isTypeList($type));
222
            $type = TypeUtil::normalize($type);
223
        }
224
225 21
        if (is_a($form->getConfig()->getType()->getInnerType(), GraphQLType::class, true)) {
226 21
            $type = $form->getConfig()->getOptions()['graphql_type'];
227 21
            $field->setList(TypeUtil::isTypeList($type));
228 21
            $type = TypeUtil::normalize($type);
229
        }
230
231 21
        if (is_a($form->getConfig()->getType()->getInnerType(), IDType::class, true)) {
232 21
            if ($form->getConfig()->hasOption('multiple') && $form->getConfig()->getOption('multiple')) {
233 21
                $field->setList(true);
234
            }
235 21
            $type = Types::ID;
236
        }
237
238 21
        if (is_a($form->getConfig()->getType()->getInnerType(), TextType::class, true)) {
239 21
            $type = Types::STRING;
240
        }
241
242 21
        if (is_a($form->getConfig()->getType()->getInnerType(), TextareaType::class, true)) {
243
            $type = Types::STRING;
244
        }
245
246 21
        if (is_a($form->getConfig()->getType()->getInnerType(), EmailType::class, true)) {
247 21
            $type = Types::STRING;
248
        }
249
250 21
        if (is_a($form->getConfig()->getType()->getInnerType(), CheckboxType::class, true)) {
251 21
            $type = Types::BOOLEAN;
252
        }
253
254 21
        if (is_a($form->getConfig()->getType()->getInnerType(), IntegerType::class, true)) {
255
            $type = Types::INT;
256
        }
257
258 21
        if (is_a($form->getConfig()->getType()->getInnerType(), NumberType::class, true)) {
259
            $type = Types::FLOAT;
260
        }
261
262 21
        if (!$type) {
263
            $error = sprintf(
264
                'The field "%s" in the parent form "%s" does not have a valid type. 
265
                If your are using a custom type, must define a option called "graphql_type" to resolve the form to a valid GraphQL type',
266
                $form->getName(),
267
                $form->getParent()->getName()
268
            );
269
            throw new \InvalidArgumentException($error);
270
        }
271
272 21
        $field->setType($type);
273 21
    }
274
}
275