Completed
Push — master ( 84ea58...932c25 )
by Adrien
07:36
created

Standard::buildRelationMutation()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 69
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 5.0613

Importance

Changes 0
Metric Value
cc 4
eloc 45
nc 4
nop 3
dl 0
loc 69
ccs 25
cts 42
cp 0.5951
crap 5.0613
rs 9.2
c 0
b 0
f 0

How to fix   Long Method   

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
declare(strict_types=1);
4
5
namespace Application\Api\Field;
6
7
use Application\Api\Helper;
8
use Application\Api\Input\PaginationInputType;
9
use Application\Model\AbstractModel;
10
use Doctrine\ORM\Mapping\ClassMetadata;
11
use GraphQL\Type\Definition\Type;
12
use ReflectionClass;
13
14
/**
15
 * Provide easy way to build standard fields to query and mutate objects
16
 */
17
abstract class Standard
18
{
19
    /**
20
     * Returns standard fields to query the object
21
     *
22
     * @param string $class
23
     *
24
     * @return array
25
     */
26 5
    public static function buildQuery(string $class): array
27
    {
28 1
        $metadata = _em()->getClassMetadata($class);
29 1
        $reflect = $metadata->getReflectionClass();
30 1
        $name = lcfirst($reflect->getShortName());
31 1
        $shortName = $reflect->getShortName();
32 1
        $plural = self::makePlural($name);
33
34 1
        $listArgs = self::getListArguments($metadata);
35 1
        $singleArgs = self::getSingleArguments($class);
36
37
        return [
38
            [
39 1
                'name' => $plural,
40 1
                'type' => Type::nonNull(_types()->get($shortName . 'Pagination')),
1 ignored issue
show
Bug introduced by
_types()->get($shortName . 'Pagination') of type GraphQL\Type\Definition\Type is incompatible with the type GraphQL\Type\Definition\NullableType expected by parameter $wrappedType of GraphQL\Type\Definition\Type::nonNull(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

40
                'type' => Type::nonNull(/** @scrutinizer ignore-type */ _types()->get($shortName . 'Pagination')),
Loading history...
41 1
                'args' => $listArgs,
42
                'resolve' => function ($root, array $args) use ($class): array {
43 5
                    $qb = _types()->createFilteredQueryBuilder($class, $args['filter'] ?? [], $args['sorting'] ?? []);
44
45 5
                    $items = Helper::paginate($args['pagination'], $qb);
46 5
                    $aggregatedFields = Helper::aggregatedFields($class, $qb);
47 5
                    $result = array_merge($aggregatedFields, $items);
48
49 5
                    return $result;
50 1
                },
51
            ],
52
            [
53 1
                'name' => $name,
54 1
                'type' => Type::nonNull(_types()->getOutput($class)),
55 1
                'args' => $singleArgs,
56
                'resolve' => function ($root, array $args): ?AbstractModel {
57 3
                    $object = $args['id']->getEntity();
58
59 2
                    Helper::throwIfDenied($object, 'read');
60
61 2
                    return $object;
62 1
                },
63
            ],
64
        ];
65
    }
66
67
    /**
68
     * Returns standard fields to mutate the object
69
     *
70
     * @param string $class
71
     *
72
     * @return array
73
     */
74 4
    public static function buildMutation(string $class): array
75
    {
76 1
        $reflect = new ReflectionClass($class);
77 1
        $name = $reflect->getShortName();
78 1
        $plural = self::makePlural($name);
79 1
        $lowerName = lcfirst($name);
80
81
        return [
82
            [
83 1
                'name' => 'create' . $name,
84 1
                'type' => Type::nonNull(_types()->getOutput($class)),
85 1
                'description' => 'Create a new ' . $name,
86
                'args' => [
87 1
                    'input' => Type::nonNull(_types()->getInput($class)),
88
                ],
89
                'resolve' => function ($root, array $args) use ($class, $lowerName): AbstractModel {
0 ignored issues
show
Unused Code introduced by
The import $lowerName is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
90
                    // Do it
91 3
                    $object = new $class();
92 3
                    $input = $args['input'];
93 3
                    Helper::hydrate($object, $input);
94
95
                    // Check ACL
96 3
                    Helper::throwIfDenied($object, 'create');
97
98 2
                    _em()->persist($object);
99 2
                    _em()->flush();
100
101 2
                    return $object;
102 1
                },
103
            ],
104
            [
105 1
                'name' => 'update' . $name,
106 1
                'type' => Type::nonNull(_types()->getOutput($class)),
107 1
                'description' => 'Update an existing ' . $name,
108
                'args' => [
109 1
                    'id' => Type::nonNull(_types()->getId($class)),
110 1
                    'input' => Type::nonNull(_types()->getPartialInput($class)),
111
                ],
112
                'resolve' => function ($root, array $args) use ($lowerName): AbstractModel {
0 ignored issues
show
Unused Code introduced by
The import $lowerName is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
113 4
                    $object = $args['id']->getEntity();
114
115
                    // Check ACL
116 4
                    Helper::throwIfDenied($object, 'update');
117
118
                    // Do it
119 3
                    $input = $args['input'];
120 3
                    Helper::hydrate($object, $input);
121
122 3
                    _em()->flush();
123
124 3
                    return $object;
125 1
                },
126
            ],
127
            [
128 1
                'name' => 'delete' . $plural,
129 1
                'type' => Type::nonNull(Type::boolean()),
130 1
                'description' => 'Delete one or several existing ' . $name,
131
                'args' => [
132 1
                    'ids' => Type::nonNull(Type::listOf(Type::nonNull(_types()->getId($class)))),
133
                ],
134
                'resolve' => function ($root, array $args) use ($lowerName): bool {
0 ignored issues
show
Unused Code introduced by
The import $lowerName is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
135 1
                    foreach ($args['ids'] as $id) {
136 1
                        $object = $id->getEntity();
137
138
                        // Check ACL
139 1
                        Helper::throwIfDenied($object, 'delete');
140
141
                        // Do it
142
                        _em()->remove($object);
143
                    }
144
145
                    _em()->flush();
146
147
                    return true;
148 1
                },
149
            ],
150
        ];
151
    }
152
153
    /**
154
     * Returns standard mutations to manage many-to-many relations between two given class
155
     *
156
     * @param string $ownerClass The class owning the relation
157
     * @param string $otherClass The other class, not-owning the relation
158
     * @param null|string $otherName a specific semantic, if needed, to be use as adder. If `$otherName = 'Parent'`, then we will call `addParent()`
159
     *
160
     * @throws \ReflectionException
161
     *
162
     * @return array
163
     */
164 1
    public static function buildRelationMutation(string $ownerClass, string $otherClass, ?string $otherName = null): array
165
    {
166 1
        $ownerReflect = new ReflectionClass($ownerClass);
167 1
        $ownerName = $ownerReflect->getShortName();
168 1
        $lowerOwnerName = lcfirst($ownerName);
169
170 1
        $otherReflect = new ReflectionClass($otherClass);
171 1
        $otherClassName = $otherReflect->getShortName();
172 1
        if ($otherName) {
173 1
            $semantic = ' as ' . $otherName;
174
        } else {
175 1
            $semantic = '';
176 1
            $otherName = $otherClassName;
177
        }
178 1
        $lowerOtherName = lcfirst($otherName);
179
180 1
        if ($lowerOwnerName === $lowerOtherName) {
181
            $lowerOwnerName .= 1;
182
            $lowerOtherName .= 2;
183
        }
184
185
        $args = [
186 1
            $lowerOwnerName => Type::nonNull(_types()->getId($ownerClass)),
187 1
            $lowerOtherName => Type::nonNull(_types()->getId($otherClass)),
188
        ];
189
190
        return [
191
            [
192 1
                'name' => 'link' . $ownerName . $otherName,
193 1
                'type' => Type::nonNull(_types()->getOutput($ownerClass)),
194 1
                'description' => 'Create a relation between ' . $ownerName . ' and ' . $otherClassName . $semantic . '.' . PHP_EOL . PHP_EOL .
195 1
                    'If the relation already exists, it will have no effect.',
196 1
                'args' => $args,
197
                'resolve' => function ($root, array $args) use ($lowerOwnerName, $lowerOtherName, $otherName, $otherClass): AbstractModel {
0 ignored issues
show
Unused Code introduced by
The import $otherClass is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
198
                    $owner = $args[$lowerOwnerName]->getEntity();
199
                    $other = $args[$lowerOtherName]->getEntity();
200
201
                    // Check ACL
202
                    Helper::throwIfDenied($owner, 'update');
203
204
                    // Do it
205
                    $method = 'add' . $otherName;
206
                    $owner->$method($other);
207
                    _em()->flush();
208
209
                    return $owner;
210 1
                },
211
            ],
212
            [
213 1
                'name' => 'unlink' . $ownerName . $otherName,
214 1
                'type' => Type::nonNull(_types()->getOutput($ownerClass)),
215 1
                'description' => 'Delete a relation between ' . $ownerName . ' and ' . $otherClassName . $semantic . '.' . PHP_EOL . PHP_EOL .
216 1
                    'If the relation does not exist, it will have no effect.',
217 1
                'args' => $args,
218
                'resolve' => function ($root, array $args) use ($lowerOwnerName, $lowerOtherName, $otherName, $otherClass): AbstractModel {
0 ignored issues
show
Unused Code introduced by
The import $otherClass is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
219
                    $owner = $args[$lowerOwnerName]->getEntity();
220
                    $other = $args[$lowerOtherName]->getEntity();
221
222
                    // Check ACL
223
                    Helper::throwIfDenied($owner, 'update');
224
225
                    // Do it
226
                    if ($other) {
227
                        $method = 'remove' . $otherName;
228
                        $owner->$method($other);
229
                        _em()->flush();
230
                    }
231
232
                    return $owner;
233 1
                },
234
            ],
235
        ];
236
    }
237
238
    /**
239
     * Returns the plural form of the given name
240
     *
241
     * @param string $name
242
     *
243
     * @return string
244
     */
245 1
    private static function makePlural(string $name): string
246
    {
247 1
        $plural = $name . 's';
248 1
        $plural = preg_replace('/ys$/', 'ies', $plural);
249 1
        $plural = preg_replace('/ss$/', 'ses', $plural);
250
251 1
        return $plural;
252
    }
253
254
    /**
255
     * Return arguments used for the list
256
     *
257
     * @param ClassMetadata $class
258
     *
259
     * @return array
260
     */
261 1
    private static function getListArguments(ClassMetadata $class): array
262
    {
263
        $listArgs = [
264
            [
265 1
                'name' => 'filter',
266 1
                'type' => _types()->getFilter($class->getName()),
267
            ],
268
            [
269 1
                'name' => 'sorting',
270 1
                'type' => _types()->getSorting($class->getName()),
271 1
                'defaultValue' => self::getDefaultSorting($class),
272
            ],
273
        ];
274
275 1
        $listArgs[] = PaginationInputType::build();
276
277 1
        return $listArgs;
278
    }
279
280
    /**
281
     * Return arguments used for single item
282
     *
283
     * @param string $class
284
     *
285
     * @return array
286
     */
287 1
    private static function getSingleArguments(string $class): array
288
    {
289
        $args = [
290 1
            'id' => Type::nonNull(_types()->getId($class)),
291
        ];
292
293 1
        return $args;
294
    }
295
296
    /**
297
     * Get default sorting values with some fallback for some special cases
298
     *
299
     * @param ClassMetadata $class
300
     *
301
     * @return array
302
     */
303 1
    private static function getDefaultSorting(ClassMetadata $class): array
304
    {
305 1
        $defaultSorting = [];
306
307 1
        $defaultSorting[] = [
308
            'field' => 'id',
309
            'order' => 'ASC',
310
        ];
311
312 1
        return $defaultSorting;
313
    }
314
}
315