Passed
Push — master ( 3ba489...3505e6 )
by Adrien
11: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 27
CRAP Score 4.923

Importance

Changes 0
Metric Value
cc 4
eloc 45
nc 4
nop 3
dl 0
loc 69
ccs 27
cts 44
cp 0.6136
crap 4.923
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\Model\AbstractModel;
9
use Application\Model\Account;
10
use Application\Model\TransactionLine;
11
use Doctrine\ORM\Mapping\ClassMetadata;
12
use Ecodev\Felix\Api\Input\PaginationInputType;
13
use GraphQL\Type\Definition\Type;
14
use Money\Money;
15
use ReflectionClass;
16
use ReflectionException;
17
18
/**
19
 * Provide easy way to build standard fields to query and mutate objects
20
 */
21
abstract class Standard
22
{
23
    /**
24
     * Returns standard fields to query the object
25
     */
26 6
    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')),
41 1
                'args' => $listArgs,
42 6
                'resolve' => function ($root, array $args) use ($class, $metadata): array {
43 5
                    $filters = self::customTypesToScalar($args['filter'] ?? []);
44
45
                    // If null or empty list is provided by client, fallback on default sorting
46 5
                    $sorting = $args['sorting'] ?? [];
47 5
                    if (!$sorting) {
48 4
                        $sorting = self::getDefaultSorting($metadata);
49
                    }
50
51
                    // And **always** sort by ID
52 5
                    $sorting[] = [
53
                        'field' => 'id',
54
                        'order' => 'ASC',
55
                    ];
56
57 5
                    $qb = _types()->createFilteredQueryBuilder($class, $filters, $sorting);
58
59 5
                    $items = Helper::paginate($args['pagination'], $qb);
60 5
                    $exportExcelField = Helper::excelExportField($class, $qb);
61 5
                    $aggregatedFields = Helper::aggregatedFields($class, $qb);
62 5
                    $result = array_merge($aggregatedFields, $exportExcelField, $items);
63
64 5
                    return $result;
65 1
                },
66
            ],
67
            [
68 1
                'name' => $name,
69 1
                'type' => Type::nonNull(_types()->getOutput($class)),
70 1
                'args' => $singleArgs,
71 1
                'resolve' => function ($root, array $args): ?AbstractModel {
72 3
                    $object = $args['id']->getEntity();
73
74 2
                    Helper::throwIfDenied($object, 'read');
75
76 2
                    return $object;
77 1
                },
78
            ],
79
        ];
80
    }
81
82
    /**
83
     * Returns standard fields to mutate the object
84
     */
85 5
    public static function buildMutation(string $class): array
86
    {
87 1
        $reflect = new ReflectionClass($class);
88 1
        $name = $reflect->getShortName();
89 1
        $plural = self::makePlural($name);
90
91
        return [
92
            [
93 1
                'name' => 'create' . $name,
94 1
                'type' => Type::nonNull(_types()->getOutput($class)),
95 1
                'description' => 'Create a new ' . $name,
96
                'args' => [
97 1
                    'input' => Type::nonNull(_types()->getInput($class)),
98
                ],
99 5
                'resolve' => function ($root, array $args) use ($class): AbstractModel {
100
                    // Do it
101 4
                    $object = new $class();
102 4
                    $input = $args['input'];
103 4
                    Helper::hydrate($object, $input);
104
105
                    // Check ACL
106 4
                    Helper::throwIfDenied($object, 'create');
107
108 3
                    _em()->persist($object);
109 3
                    _em()->flush();
110
111 3
                    return $object;
112 1
                },
113
            ],
114
            [
115 1
                'name' => 'update' . $name,
116 1
                'type' => Type::nonNull(_types()->getOutput($class)),
117 1
                'description' => 'Update an existing ' . $name,
118
                'args' => [
119 1
                    'id' => Type::nonNull(_types()->getId($class)),
120 1
                    'input' => Type::nonNull(_types()->getPartialInput($class)),
121
                ],
122 1
                'resolve' => function ($root, array $args): AbstractModel {
123 3
                    $object = $args['id']->getEntity();
124
125
                    // Check ACL
126 3
                    Helper::throwIfDenied($object, 'update');
127
128
                    // Do it
129 2
                    $input = $args['input'];
130 2
                    Helper::hydrate($object, $input);
131
132 2
                    _em()->flush();
133
134 2
                    return $object;
135 1
                },
136
            ],
137
            [
138 1
                'name' => 'delete' . $plural,
139 1
                'type' => Type::nonNull(Type::boolean()),
140 1
                'description' => 'Delete one or several existing ' . $name,
141
                'args' => [
142 1
                    'ids' => Type::nonNull(Type::listOf(Type::nonNull(_types()->getId($class)))),
143
                ],
144 1
                'resolve' => function ($root, array $args): bool {
145 1
                    foreach ($args['ids'] as $id) {
146 1
                        $object = $id->getEntity();
147
148
                        // Check ACL
149 1
                        Helper::throwIfDenied($object, 'delete');
150
151
                        // Do it
152
                        _em()->remove($object);
153
                    }
154
155
                    _em()->flush();
156
157
                    return true;
158 1
                },
159
            ],
160
        ];
161
    }
162
163
    /**
164
     * Returns standard mutations to manage many-to-many relations between two given class
165
     *
166
     * @param string $ownerClass The class owning the relation
167
     * @param string $otherClass The other class, not-owning the relation
168
     * @param null|string $otherName a specific semantic, if needed, to be use as adder. If `$otherName = 'Parent'`, then we will call `addParent()`
169
     */
170 1
    public static function buildRelationMutation(string $ownerClass, string $otherClass, ?string $otherName = null): 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
        $otherClassName = $otherReflect->getShortName();
178 1
        if ($otherName) {
179 1
            $semantic = ' as ' . $otherName;
180
        } else {
181 1
            $semantic = '';
182 1
            $otherName = $otherClassName;
183
        }
184 1
        $lowerOtherName = lcfirst($otherName);
185
186 1
        if ($lowerOwnerName === $lowerOtherName) {
187
            $lowerOwnerName .= 1;
188
            $lowerOtherName .= 2;
189
        }
190
191
        $args = [
192 1
            $lowerOwnerName => Type::nonNull(_types()->getId($ownerClass)),
193 1
            $lowerOtherName => Type::nonNull(_types()->getId($otherClass)),
194
        ];
195
196
        return [
197
            [
198 1
                'name' => 'link' . $ownerName . $otherName,
199 1
                'type' => Type::nonNull(_types()->getOutput($ownerClass)),
200 1
                'description' => 'Create a relation between ' . $ownerName . ' and ' . $otherClassName . $semantic . '.' . PHP_EOL . PHP_EOL .
201 1
                    'If the relation already exists, it will have no effect.',
202 1
                'args' => $args,
203 1
                'resolve' => function ($root, array $args) use ($lowerOwnerName, $lowerOtherName, $otherName): AbstractModel {
204
                    $owner = $args[$lowerOwnerName]->getEntity();
205
                    $other = $args[$lowerOtherName]->getEntity();
206
207
                    // Check ACL
208
                    Helper::throwIfDenied($owner, 'update');
209
210
                    // Do it
211
                    $method = 'add' . $otherName;
212
                    $owner->$method($other);
213
                    _em()->flush();
214
215
                    return $owner;
216 1
                },
217
            ],
218
            [
219 1
                'name' => 'unlink' . $ownerName . $otherName,
220 1
                'type' => Type::nonNull(_types()->getOutput($ownerClass)),
221 1
                'description' => 'Delete a relation between ' . $ownerName . ' and ' . $otherClassName . $semantic . '.' . PHP_EOL . PHP_EOL .
222 1
                    'If the relation does not exist, it will have no effect.',
223 1
                'args' => $args,
224 1
                'resolve' => function ($root, array $args) use ($lowerOwnerName, $lowerOtherName, $otherName): AbstractModel {
225
                    $owner = $args[$lowerOwnerName]->getEntity();
226
                    $other = $args[$lowerOtherName]->getEntity();
227
228
                    // Check ACL
229
                    Helper::throwIfDenied($owner, 'update');
230
231
                    // Do it
232
                    if ($other) {
233
                        $method = 'remove' . $otherName;
234
                        $owner->$method($other);
235
                        _em()->flush();
236
                    }
237
238
                    return $owner;
239 1
                },
240
            ],
241
        ];
242
    }
243
244
    /**
245
     * Returns the plural form of the given name
246
     */
247 1
    private static function makePlural(string $name): string
248
    {
249 1
        $plural = $name . 's';
250 1
        $plural = preg_replace('/ys$/', 'ies', $plural);
251 1
        $plural = preg_replace('/ss$/', 'ses', $plural);
252
253 1
        return $plural;
254
    }
255
256
    /**
257
     * Return arguments used for the list
258
     */
259 1
    private static function getListArguments(ClassMetadata $class): array
260
    {
261
        $listArgs = [
262
            [
263 1
                'name' => 'filter',
264 1
                'type' => _types()->getFilter($class->getName()),
265
            ],
266
            [
267 1
                'name' => 'sorting',
268 1
                'type' => _types()->getSorting($class->getName()),
269 1
                'defaultValue' => self::getDefaultSorting($class),
270
            ],
271
        ];
272
273 1
        $listArgs[] = PaginationInputType::build(_types());
274
275 1
        return $listArgs;
276
    }
277
278
    /**
279
     * Return arguments used for single item
280
     */
281 1
    private static function getSingleArguments(string $class): array
282
    {
283
        $args = [
284 1
            'id' => Type::nonNull(_types()->getId($class)),
285
        ];
286
287 1
        return $args;
288
    }
289
290
    /**
291
     * Get default sorting values with some fallback for some special cases
292
     */
293 5
    private static function getDefaultSorting(ClassMetadata $metadata): array
294
    {
295 5
        $defaultSorting = [];
296
297 5
        $class = $metadata->getName();
298 5
        if ($class === Account::class) {
299 1
            $defaultSorting[] = [
300 1
                'field' => 'code',
301
                'order' => 'ASC',
302
            ];
303 5
        } elseif ($class === TransactionLine::class) {
304 1
            $defaultSorting[] = [
305
                'field' => 'transactionDate',
306
                'order' => 'DESC',
307
            ];
308
        }
309
310 5
        return $defaultSorting;
311
    }
312
313
    /**
314
     * Recursively convert custom scalars that don't implement __toString() to their scalar
315
     * representation to injected back into DQL/SQL
316
     */
317 5
    private static function customTypesToScalar(array $args): array
318
    {
319 5
        foreach ($args as &$p) {
320
            if (is_array($p)) {
321
                $p = self::customTypesToScalar($p);
322
            } elseif ($p instanceof Money) {
323
                $p = $p->getAmount();
324
            }
325
        }
326
327 5
        return $args;
328
    }
329
}
330