InputFactory   A
last analyzed

Complexity

Total Complexity 15

Size/Duplication

Total Lines 126
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 15
eloc 40
c 1
b 0
f 0
dl 0
loc 126
rs 10

5 Methods

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