InputFactory::addOptionalFields()   A
last analyzed

Complexity

Conditions 6
Paths 4

Size

Total Lines 23
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 9
c 1
b 0
f 0
nc 4
nop 3
dl 0
loc 23
rs 9.2222
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ApiSkeletons\Doctrine\GraphQL\Input;
6
7
use ApiSkeletons\Doctrine\GraphQL\AbstractContainer;
8
use ApiSkeletons\Doctrine\GraphQL\Config;
9
use ApiSkeletons\Doctrine\GraphQL\Type\Entity;
10
use ApiSkeletons\Doctrine\GraphQL\Type\TypeManager;
11
use Doctrine\ORM\EntityManager;
12
use Exception;
13
use GraphQL\Error\Error;
14
use GraphQL\Type\Definition\InputObjectField;
15
use GraphQL\Type\Definition\InputObjectType;
16
use GraphQL\Type\Definition\Type;
17
18
use function count;
19
use function in_array;
20
use function uniqid;
21
22
class InputFactory extends AbstractContainer
23
{
24
    public function __construct(
25
        protected Config $config,
26
        protected EntityManager $entityManager,
27
        protected TypeManager $typeManager,
28
    ) {
29
    }
30
31
    /**
32
     * @param string[] $requiredFields An optional list of just the required fields you want for the mutation.
33
     *                                 This allows specific fields per mutation.
34
     * @param string[] $optionalFields An optional list of optional fields you want for the mutation.
35
     *                                 This allows specific fields per mutation.
36
     *
37
     * @throws Error
38
     */
39
    public function get(string $id, array $requiredFields = [], array $optionalFields = []): InputObjectType
40
    {
41
        $fields       = [];
42
        $targetEntity = $this->typeManager->build(Entity::class, $id);
43
44
        if (! count($requiredFields) && ! count($optionalFields)) {
45
            $this->addAllFieldsAsRequired($targetEntity, $fields);
46
        } else {
47
            $this->addRequiredFields($targetEntity, $requiredFields, $fields);
48
            $this->addOptionalFields($targetEntity, $optionalFields, $fields);
49
        }
50
51
        return new InputObjectType([
52
            'name' => $targetEntity->getTypeName() . '_Input_' . uniqid(),
53
            'description' => $targetEntity->getDescription(),
54
            'fields' => static fn () => $fields,
55
        ]);
56
    }
57
58
    /**
59
     * @param string[]                     $optionalFields
60
     * @param array<int, InputObjectField> $fields
61
     */
62
    protected function addOptionalFields(
63
        mixed $targetEntity,
64
        array $optionalFields,
65
        array &$fields,
66
    ): void {
67
        foreach ($this->entityManager->getClassMetadata($targetEntity->getEntityClass())->getFieldNames() as $fieldName) {
68
            if (! in_array($fieldName, $optionalFields) && $optionalFields !== ['*']) {
69
                continue;
70
            }
71
72
            /**
73
             * Do not include identifiers as input.  In the majority of cases there will be
74
             * no reason to set or update an identifier.  For the case where an identifier
75
             * should be set or updated, this factory is not the correct solution.
76
             */
77
            if ($optionalFields === ['*'] && $this->entityManager->getClassMetadata($targetEntity->getEntityClass())->isIdentifier($fieldName)) {
78
                continue;
79
            }
80
81
            $fields[$fieldName] = new InputObjectField([
82
                'name' => $fieldName,
83
                'description' => (string) $targetEntity->getMetadata()['fields'][$fieldName]['description'],
84
                'type' => $this->typeManager->get($targetEntity->getMetadata()['fields'][$fieldName]['type']),
85
            ]);
86
        }
87
    }
88
89
    /**
90
     * @param string[]                     $requiredFields
91
     * @param array<int, InputObjectField> $fields
92
     */
93
    protected function addRequiredFields(
94
        mixed $targetEntity,
95
        array $requiredFields,
96
        array &$fields,
97
    ): void {
98
        foreach ($this->entityManager->getClassMetadata($targetEntity->getEntityClass())->getFieldNames() as $fieldName) {
99
            if (! in_array($fieldName, $requiredFields) && $requiredFields !== ['*']) {
100
                continue;
101
            }
102
103
            /**
104
             * Do not include identifiers as input.  In the majority of cases there will be
105
             * no reason to set or update an identifier.  For the case where an identifier
106
             * should be set or updated, this factory is not the correct solution.
107
             */
108
            if (
109
                $requiredFields === ['*']
110
                && $this->entityManager->getClassMetadata($targetEntity->getEntityClass())->isIdentifier($fieldName)
111
            ) {
112
                continue;
113
            }
114
115
            if ($this->entityManager->getClassMetadata($targetEntity->getEntityClass())->isIdentifier($fieldName)) {
116
                throw new Exception('Identifier ' . $fieldName . ' is an invalid input.');
117
            }
118
119
            $fields[$fieldName] = new InputObjectField([
120
                'name' => $fieldName,
121
                'description' => (string) $targetEntity->getMetadata()['fields'][$fieldName]['description'],
122
                'type' => Type::nonNull($this->typeManager->get(
123
                    $targetEntity->getMetadata()['fields'][$fieldName]['type'],
124
                )),
125
            ]);
126
        }
127
    }
128
129
    /** @param array<int, InputObjectField> $fields */
130
    protected function addAllFieldsAsRequired(mixed $targetEntity, array &$fields): void
131
    {
132
        foreach ($this->entityManager->getClassMetadata($targetEntity->getEntityClass())->getFieldNames() as $fieldName) {
133
            /**
134
             * Do not include identifiers as input.  In the majority of cases there will be
135
             * no reason to set or update an identifier.  For the case where an identifier
136
             * should be set or updated, this factory is not the correct solution.
137
             */
138
            if ($this->entityManager->getClassMetadata($targetEntity->getEntityClass())->isIdentifier($fieldName)) {
139
                continue;
140
            }
141
142
            $fields[$fieldName] = new InputObjectField([
143
                'name' => $fieldName,
144
                'description' => (string) $targetEntity->getMetadata()['fields'][$fieldName]['description'],
145
                'type' => Type::nonNull($this->typeManager->get($targetEntity->getMetadata()['fields'][$fieldName]['type'])),
146
            ]);
147
        }
148
    }
149
}
150