resolveFormFieldDefinition()   B
last analyzed

Complexity

Conditions 11
Paths 36

Size

Total Lines 47
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 11.0048

Importance

Changes 0
Metric Value
eloc 29
dl 0
loc 47
ccs 28
cts 29
cp 0.9655
rs 7.3166
c 0
b 0
f 0
cc 11
nc 36
nop 2
crap 11.0048

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\CollectionType;
15
use Symfony\Component\Form\FormFactory;
16
use Symfony\Component\Form\FormInterface;
17
use Symfony\Component\Form\Guess\Guess;
18
use Symfony\Component\Form\Guess\TypeGuess;
19
use Ynlo\GraphQLBundle\Definition\ArgumentDefinition;
20
use Ynlo\GraphQLBundle\Definition\DefinitionInterface;
21
use Ynlo\GraphQLBundle\Definition\FieldDefinition;
22
use Ynlo\GraphQLBundle\Definition\InputObjectDefinition;
23
use Ynlo\GraphQLBundle\Definition\MutationDefinition;
24
use Ynlo\GraphQLBundle\Definition\NodeAwareDefinitionInterface;
25
use Ynlo\GraphQLBundle\Definition\Registry\Endpoint;
26
use Ynlo\GraphQLBundle\Form\Input\InputFieldTypeGuesser;
27
use Ynlo\GraphQLBundle\Type\Types;
28
use Ynlo\GraphQLBundle\Util\ClassUtils;
29
use Ynlo\GraphQLBundle\Util\TypeUtil;
30
31
/**
32
 * MutationFormResolverPlugin
33
 */
34
class MutationFormResolverPlugin extends AbstractDefinitionPlugin
35
{
36
    /**
37
     * @var FormFactory
38
     */
39
    protected $formFactory;
40
41
    /**
42
     * @var InputFieldTypeGuesser[]|iterable
43
     */
44
    protected $typeGuessers;
45
46
    /**
47
     * MutationFormResolverPlugin constructor.
48
     *
49
     * @param FormFactory                      $formFactory
50
     * @param iterable|InputFieldTypeGuesser[] $typeGuessers
51
     */
52 1
    public function __construct(FormFactory $formFactory, iterable $typeGuessers = [])
53
    {
54 1
        $this->formFactory = $formFactory;
55 1
        $this->typeGuessers = $typeGuessers;
56 1
    }
57
58
    /**
59
     * {@inheritDoc}
60
     */
61
    public function getName(): string
62
    {
63
        return 'form';
64
    }
65
66
    /**
67
     * {@inheritDoc}
68
     */
69
    public function buildConfig(ArrayNodeDefinition $root): void
70
    {
71
        $config = $root
72
            ->info('Resolve the form to use as input for mutations')
73
            ->addDefaultsIfNotSet()
74
            ->canBeDisabled()
75
            ->children();
76
77
        $config
78
            ->variableNode('type')
79
            ->defaultNull()
80
            ->info(
81
                'Specify the form type to use,
82
[string] Name of the form type to use
83
[true|null] The form will be automatically resolved to ...Bundle\Form\Input\{Node}\{MutationName}Input.'
84
            );
85
        $config->variableNode('options')->defaultValue([])->info('Form options');
86
        $config->variableNode('argument')
87
               ->defaultValue('input')
88
               ->info('Name of the argument to use as input');
89
90
        $config->booleanNode('client_mutation_id')
91
               ->defaultTrue()
92
               ->info('Automatically add a field called clientMutationId');
93
    }
94
95
    /**
96
     * {@inheritDoc}
97
     */
98 1
    public function configure(DefinitionInterface $definition, Endpoint $endpoint, array $config): void
99
    {
100 1
        if (!$definition instanceof MutationDefinition || !isset($config['enabled'])) {
101
            return;
102
        }
103
104 1
        $formType = $config['type'] ?? null;
105
106
        //the related class is used to match a form using naming conventions
107 1
        $relatedClass = null;
108 1
        if ($definition instanceof NodeAwareDefinitionInterface && $definition->getNode()) {
109 1
            $relatedClass = $definition->getNode();
110 1
            if ($class = $endpoint->getClassForType($relatedClass)) {
111 1
                $relatedClass = $class;
112
            }
113
        }
114
115 1
        if (!class_exists($relatedClass) && $definition->getResolver()) {
116
            $relatedClass = $definition->getResolver();
117
        }
118
119
        //try find the form using a related class
120 1
        if ($relatedClass && (!$formType || true === $formType)) {
121 1
            $bundleNamespace = ClassUtils::relatedBundleNamespace($relatedClass);
122 1
            if ($endpoint->hasType($definition->getNode())) {
123 1
                $nodeName = $endpoint->getType($definition->getNode())->getName();
124
            } else {
125
                $nodeName = ClassUtils::getNodeFromClass($relatedClass);
126
            }
127 1
            $formClass = ClassUtils::applyNamingConvention(
128 1
                $bundleNamespace,
129 1
                'Form\Input',
130 1
                $nodeName,
131 1
                ucfirst($definition->getName()),
132 1
                'Input'
133
            );
134 1
            if (class_exists($formClass)) {
135 1
                $formType = $formClass;
136
            } elseif (true === $formType) {
137
                $error = sprintf(
138
                    'Can`t find a valid input form type to use in "%s".
139
                         Create the form "%s" or specify a custom form',
140
                    $definition->getName(),
141
                    $formClass
142
                );
143
                throw new \RuntimeException($error);
144
            }
145
        }
146
147 1
        if ($formType) {
148 1
            $config['type'] = $formType;
149 1
            $options = $config['options'] ?? [];
150 1
            $options['endpoint'] = $endpoint->getName();
151 1
            $form = $this->formFactory->create($formType, null, $options);
152 1
            $inputObject = $this->createFormInputObject($endpoint, $form, ucfirst($definition->getName()));
153 1
            $endpoint->addType($inputObject);
154
155 1
            $input = new ArgumentDefinition();
156 1
            $input->setName($config['argument'] ?? 'input');
157 1
            $input->setType($inputObject->getName());
158
159 1
            if ($config['client_mutation_id'] ?? true) {
160 1
                $clientMutationId = new FieldDefinition();
161 1
                $clientMutationId->setName('clientMutationId');
162 1
                $clientMutationId->setType(Types::STRING);
163 1
                $clientMutationId->setDescription('A unique identifier for the client performing the mutation.');
164 1
                $inputObject->prependField($clientMutationId);
165
            }
166
167 1
            $definition->addArgument($input);
168 1
            $definition->setMeta('form', $config);
169
        }
170 1
    }
171
172
    /**
173
     * @param Endpoint      $endpoint
174
     * @param FormInterface $form
175
     * @param string        $name
176
     *
177
     * @return InputObjectDefinition
178
     */
179 1
    private function createFormInputObject(Endpoint $endpoint, FormInterface $form, string $name): InputObjectDefinition
180
    {
181 1
        $inputObject = new InputObjectDefinition();
182 1
        if ($settledName = $form->getConfig()->getOption('graphql_type')) {
183
            $settledName = preg_replace('/Input$/', null, $settledName);
184
            if (\is_string($settledName) && $settledName) {
185
                $name = $settledName;
186
            }
187
        }
188 1
        $inputObject->setName("{$name}Input");
189 1
        $inputObject->setDescription($form->getConfig()->getOption('graphql_description'));
190
191 1
        foreach ($form->all() as $formField) {
192 1
            $field = new FieldDefinition();
193 1
            $label = $formField->getConfig()->getOption('label');
194 1
            $field->setName(!empty($label) ? $label : $formField->getName());
195 1
            $field->setDescription($formField->getConfig()->getOption('graphql_description') ?? null);
196 1
            $field->setDeprecationReason($formField->getConfig()->getOption('graphql_deprecation_reason') ?? null);
197 1
            $field->setNonNull($formField->isRequired());
198 1
            $field->setOriginName($formField->getName());
199
200 1
            if ($formField->all()) {
201 1
                $childName = $name.ucfirst($formField->getName());
202 1
                $child = $this->createFormInputObject($endpoint, $formField, $childName);
203 1
                $endpoint->addType($child);
204 1
                $field->setType($child->getName());
205 1
            } elseif (is_a($formField->getConfig()->getType()->getInnerType(), CollectionType::class)) {
206 1
                $childName = $name.ucfirst($formField->getName());
207 1
                $childFormType = $formField->getConfig()->getOptions()['entry_type'];
208 1
                $childFormOptions = $formField->getConfig()->getOptions()['entry_options'] ?? [];
209 1
                $childFormOptions['endpoint'] = $endpoint->getName();
210 1
                $childForm = $this->formFactory->create($childFormType, null, $childFormOptions);
211 1
                $childForm->setParent($form);
212
                try {
213
                    //resolve type if is a valid scalar type or predefined type
214 1
                    $this->resolveFormFieldDefinition($field, $childForm);
215 1
                    $field->setList(true);
216 1
                } catch (\InvalidArgumentException $exception) {
217
                    //on exception, try build a child form for this collection
218 1
                    $child = $this->createFormInputObject($endpoint, $childForm, $childName);
219 1
                    $field->setType($child->getName());
220 1
                    $field->setList(true);
221 1
                    $endpoint->add($child);
222
                }
223
            } else {
224 1
                $this->resolveFormFieldDefinition($field, $formField);
225
            }
226
227 1
            $inputObject->addField($field);
228
        }
229
230 1
        return $inputObject;
231
    }
232
233
    /**
234
     * @param FieldDefinition $field
235
     * @param FormInterface   $form
236
     */
237 1
    private function resolveFormFieldDefinition(FieldDefinition $field, FormInterface $form): void
238
    {
239 1
        $type = null;
240 1
        $resolver = $form->getConfig()->getType()->getOptionsResolver();
241 1
        if ($resolver->hasDefault('graphql_type')) {
242 1
            $type = $resolver->resolve([])['graphql_type'];
243 1
            if (!$type) {
244 1
                $type = $form->getConfig()->getOptions()['graphql_type'];
245
            }
246 1
            $field->setList(TypeUtil::isTypeList($type));
247 1
            $type = TypeUtil::normalize($type);
248
        }
249
250 1
        if (!$type) {
251 1
            $guesses = [];
252 1
            foreach ($this->typeGuessers as $guesser) {
253 1
                $formType = \get_class($form->getConfig()->getType()->getInnerType());
254 1
                if ($guess = $guesser->guessType($field, $formType, $form->getConfig()->getOptions())) {
255 1
                    $guesses[] = $guess;
256
                }
257
            }
258
259 1
            $guess = Guess::getBestGuess($guesses);
260 1
            if ($guess && $guess instanceof TypeGuess) {
261 1
                $type = $guess->getType();
262
263 1
                if (isset($guess->getOptions()['required'])) {
264
                    $field->setNonNull($guess->getOptions()['required']);
265
                }
266
267 1
                if (isset($guess->getOptions()['list'])) {
268 1
                    $field->setList($guess->getOptions()['list']);
269
                }
270
            }
271
        }
272
273 1
        if (!$type) {
274 1
            $error = sprintf(
275 1
                'The field "%s" in the parent form "%s" does not have a valid type. 
276
                If your are using a custom type, must define a option called "graphql_type" to resolve the form to a valid GraphQL type',
277 1
                $form->getName(),
278 1
                $form->getParent()->getName()
279
            );
280 1
            throw new \InvalidArgumentException($error);
281
        }
282
283 1
        $field->setType($type);
284 1
    }
285
}
286