Completed
Push — master ( e74f9e...e14524 )
by Adrien
04:36
created

Standard::buildRelationMutation()   B

Complexity

Conditions 6
Paths 2

Size

Total Lines 71
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 10.5

Importance

Changes 0
Metric Value
cc 6
eloc 46
nc 2
nop 3
dl 0
loc 71
ccs 21
cts 42
cp 0.5
crap 10.5
rs 8.5559
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\Exception;
8
use Application\Api\Helper;
9
use Application\Api\Input\PaginationInputType;
10
use Application\Model\AbstractModel;
11
use Doctrine\ORM\Mapping\ClassMetadata;
12
use GraphQL\Type\Definition\Type;
13
use ReflectionClass;
14
15
/**
16
 * Provide easy way to build standard fields to query and mutate objects
17
 */
18
abstract class Standard
19
{
20
    /**
21
     * Returns standard fields to query the object
22
     *
23
     * @param string $class
24
     *
25
     * @return array
26
     */
27 2
    public static function buildQuery(string $class): array
28
    {
29 1
        $metadata = _em()->getClassMetadata($class);
30 1
        $reflect = $metadata->getReflectionClass();
31 1
        $name = lcfirst($reflect->getShortName());
32 1
        $shortName = $reflect->getShortName();
33 1
        $plural = self::makePlural($name);
34
35 1
        $listArgs = self::getListArguments($metadata, $class, $name);
36 1
        $singleArgs = self::getSingleArguments($class);
37
38
        return [
39
            [
40 1
                'name' => $plural,
41 1
                'type' => _types()->get($shortName . 'Pagination'),
42 1
                'args' => $listArgs,
43
                'resolve' => function ($root, array $args) use ($class): array {
44 1
                    if (($args['filters'] ?? false) && ($args['filter'] ?? false)) {
45
                        throw new Exception('Cannot use `filter` and `filters` at the same time');
46
                    }
47 1
                    if ($args['filters'] ?? false) {
48
                        $queryArgs = [$args['filters'] ?? []];
49
                        $queryArgs[] = $args['sorting'];
50
51
                        $qb = _em()->getRepository($class)->getFindAllQuery(...$queryArgs);
52
                    } else {
53 1
                        $qb = _types()->createFilteredQueryBuilder($class, $args['filter'] ?? [], $args['sorting'] ?? []);
54
                    }
55
56 1
                    $result = Helper::paginate($args['pagination'], $qb);
57
58 1
                    return $result;
59 1
                },
60
            ],
61
            [
62 1
                'name' => $name,
63 1
                'type' => _types()->getOutput($class),
64 1
                'args' => $singleArgs,
65
                'resolve' => function ($root, array $args): ?AbstractModel {
66 2
                    $object = $args['id']->getEntity();
67
68 2
                    Helper::throwIfDenied($object, 'read');
69
70 2
                    return $object;
71 1
                },
72
            ],
73
        ];
74
    }
75
76
    /**
77
     * Returns standard fields to mutate the object
78
     *
79
     * @param string $class
80
     *
81
     * @return array
82
     */
83 2
    public static function buildMutation(string $class): array
84
    {
85 1
        $reflect = new ReflectionClass($class);
86 1
        $name = $reflect->getShortName();
87 1
        $plural = self::makePlural($name);
88 1
        $lowerName = lcfirst($name);
89
90
        return [
91
            [
92 1
                'name' => 'create' . $name,
93 1
                'type' => Type::nonNull(_types()->getOutput($class)),
94 1
                'description' => 'Create a new ' . $name,
95
                'args' => [
96 1
                    'input' => Type::nonNull(_types()->getInput($class)),
97
                ],
98
                '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...
99
                    // Check ACL
100
                    $object = new $class();
101
                    Helper::throwIfDenied($object, 'create');
102
103
                    // Do it
104
                    $input = $args['input'];
105
                    Helper::hydrate($object, $input);
106
                    _em()->persist($object);
107
                    _em()->flush();
108
109
                    return $object;
110 1
                },
111
            ],
112
            [
113 1
                'name' => 'update' . $name,
114 1
                'type' => Type::nonNull(_types()->getOutput($class)),
115 1
                'description' => 'Update an existing ' . $name,
116
                'args' => [
117 1
                    'id' => Type::nonNull(_types()->getId($class)),
118 1
                    'input' => Type::nonNull(_types()->getPartialInput($class)),
119
                ],
120
                '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...
121 2
                    $object = $args['id']->getEntity();
122
123
                    // Check ACL
124 2
                    Helper::throwIfDenied($object, 'update');
125
126
                    // Do it
127 1
                    $input = $args['input'];
128 1
                    Helper::hydrate($object, $input);
129
130 1
                    _em()->flush();
131
132 1
                    return $object;
133 1
                },
134
            ],
135
            [
136 1
                'name' => 'delete' . $plural,
137 1
                'type' => Type::nonNull(Type::boolean()),
138 1
                'description' => 'Delete one or several existing ' . $name,
139
                'args' => [
140 1
                    'ids' => Type::nonNull(Type::listOf(Type::nonNull(_types()->getId($class)))),
141
                ],
142
                '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...
143
                    foreach ($args['ids'] as $id) {
144
                        $object = $id->getEntity();
145
146
                        // Check ACL
147
                        Helper::throwIfDenied($object, 'update');
148
149
                        // Do it
150
                        _em()->remove($object);
151
                    }
152
153
                    _em()->flush();
154
155
                    return true;
156 1
                },
157
            ],
158
        ];
159
    }
160
161
    /**
162
     * Returns standard mutations to manage many-to-many relations between two given class
163
     *
164
     * @param string $ownerClass The class owning the relation
165
     * @param string $otherClass The other class, not-owning the relation
166
     * @param bool $byName if true, the name of $other will define the relation instead of its ID
167
     *
168
     * @return array
169
     */
170 1
    public static function buildRelationMutation(string $ownerClass, string $otherClass, bool $byName = false): array
171
    {
172 1
        $ownerReflect = new ReflectionClass($ownerClass);
173 1
        $ownerName = $ownerReflect->getShortName();
174 1
        $lowerOwnerName = lcfirst($ownerName);
175
176 1
        $otherReflect = new ReflectionClass($otherClass);
177 1
        $otherName = $otherReflect->getShortName();
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($byName ? Type::string() : _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 ' . $otherName . '.' . 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, $byName): AbstractModel {
198
                    $owner = $args[$lowerOwnerName]->getEntity();
199
                    if ($byName) {
200
                        $other = self::getByName($otherClass, $args[$lowerOtherName], true);
201
                    } else {
202
                        $other = $args[$lowerOtherName]->getEntity();
203
                    }
204
205
                    // Check ACL
206
                    Helper::throwIfDenied($owner, 'update');
207
208
                    // Do it
209
                    $method = 'add' . $otherName;
210
                    $owner->$method($other);
211
                    _em()->flush();
212
213
                    return $owner;
214 1
                },
215
            ],
216
            [
217 1
                'name' => 'unlink' . $ownerName . $otherName,
218 1
                'type' => Type::nonNull(_types()->getOutput($ownerClass)),
219 1
                'description' => 'Delete a relation between ' . $ownerName . ' and ' . $otherName . '.' . PHP_EOL . PHP_EOL .
220 1
                    'If the relation does not exist, it will have no effect.',
221 1
                'args' => $args,
222
                'resolve' => function ($root, array $args) use ($lowerOwnerName, $lowerOtherName, $otherName, $otherClass, $byName): AbstractModel {
223
                    $owner = $args[$lowerOwnerName]->getEntity();
224
                    if ($byName) {
225
                        $other = self::getByName($otherClass, $args[$lowerOtherName], false);
226
                    } else {
227
                        $other = $args[$lowerOtherName]->getEntity();
228
                    }
229
230
                    // Check ACL
231
                    Helper::throwIfDenied($owner, 'update');
232
233
                    // Do it
234
                    if ($other) {
235
                        $method = 'remove' . $otherName;
236
                        $owner->$method($other);
237
                        _em()->flush();
238
                    }
239
240
                    return $owner;
241 1
                },
242
            ],
243
        ];
244
    }
245
246
    /**
247
     * Load object from DB and optionally create new one if not found
248
     *
249
     * @param string $class
250
     * @param string $name
251
     * @param bool $createIfNotFound
252
     *
253
     * @return null|AbstractModel
254
     */
255
    private static function getByName(string $class, string $name, bool $createIfNotFound): ?AbstractModel
256
    {
257
        $name = trim($name);
258
        $other = _em()->getRepository($class)->findOneByName($name);
0 ignored issues
show
Bug introduced by
The method findOneByName() does not exist on Doctrine\Common\Persistence\ObjectRepository. Did you maybe mean findOneBy()? ( Ignorable by Annotation )

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

258
        $other = _em()->getRepository($class)->/** @scrutinizer ignore-call */ findOneByName($name);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
259
260
        if (!$other && $createIfNotFound) {
261
            $other = new $class();
262
            $other->setName($name);
263
            _em()->persist($other);
264
        }
265
266
        return $other;
267
    }
268
269
    /**
270
     * Returns the plural form of the given name
271
     *
272
     * @param string $name
273
     *
274
     * @return string
275
     */
276 1
    private static function makePlural(string $name): string
277
    {
278 1
        $plural = $name . 's';
279 1
        $plural = preg_replace('/ys$/', 'ies', $plural);
280 1
        $plural = preg_replace('/ss$/', 'ses', $plural);
281
282 1
        return $plural;
283
    }
284
285
    /**
286
     * Return arguments used for the list
287
     *
288
     * @param string $class
289
     *
290
     * @return array
291
     */
292 1
    private static function getListArguments(ClassMetadata $class, string $classs, string $name): array
293
    {
294
        $listArgs = [
295
            [
296 1
                'name' => 'filter',
297 1
                'type' => _types()->getFilter($class->getName()),
298
            ],
299
            [
300 1
                'name' => 'sorting',
301 1
                'type' => _types()->getSorting($class->getName()),
302 1
                'defaultValue' => self::getDefaultSorting($class),
303
            ],
304
        ];
305
306 1
        $filterTypeClass = 'Old' . $class->getReflectionClass()->getShortName() . 'Filter';
307 1
        if (_types()->has($filterTypeClass)) {
308
            $listArgs[] = [
309
                'name' => 'filters',
310
                'type' => _types()->get($filterTypeClass),
311
            ];
312
        }
313
314 1
        $listArgs[] = PaginationInputType::build();
315
316 1
        return $listArgs;
317
    }
318
319
    /**
320
     * Return arguments used for single item
321
     *
322
     * @param string $class
323
     *
324
     * @return array
325
     */
326 1
    private static function getSingleArguments(string $class): array
327
    {
328
        $args = [
329 1
            'id' => Type::nonNull(_types()->getId($class)),
330
        ];
331
332 1
        return $args;
333
    }
334
335
    /**
336
     * Get default sorting values with some fallback for some special cases
337
     *
338
     * @param ClassMetadata $class
339
     *
340
     * @return array
341
     */
342 1
    private static function getDefaultSorting(ClassMetadata $class): array
343
    {
344 1
        $defaultSorting = [];
345
346 1
        $defaultSorting[] = [
347
            'field' => 'id',
348
            'order' => 'ASC',
349
        ];
350
351 1
        return $defaultSorting;
352
    }
353
}
354