Completed
Push — master ( 300eda...15c856 )
by Rafael
09:26
created

PaginationDefinitionPlugin::addFilters()   C

Complexity

Conditions 13
Paths 5

Size

Total Lines 57
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 57
rs 6.5962
c 0
b 0
f 0
cc 13
eloc 36
nc 5
nop 3

How to fix   Long Method    Complexity   

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
 *  This file is part of the GraphQL Bundle package.
4
 *
5
 *  (c) YnloUltratech <[email protected]>
6
 *
7
 *  For the full copyright and license information, please view the LICENSE
8
 *  file that was distributed with this source code.
9
 ******************************************************************************/
10
11
namespace Ynlo\GraphQLBundle\Definition\Plugin;
12
13
use Doctrine\Common\Annotations\Reader;
14
use GraphQL\Type\Definition\ObjectType;
15
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
16
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
17
use Ynlo\GraphQLBundle\Definition\ArgumentDefinition;
18
use Ynlo\GraphQLBundle\Definition\DefinitionInterface;
19
use Ynlo\GraphQLBundle\Definition\EnumDefinition;
20
use Ynlo\GraphQLBundle\Definition\ExecutableDefinitionInterface;
21
use Ynlo\GraphQLBundle\Definition\FieldDefinition;
22
use Ynlo\GraphQLBundle\Definition\FieldsAwareDefinitionInterface;
23
use Ynlo\GraphQLBundle\Definition\InputObjectDefinition;
24
use Ynlo\GraphQLBundle\Definition\ObjectDefinition;
25
use Ynlo\GraphQLBundle\Definition\QueryDefinition;
26
use Ynlo\GraphQLBundle\Definition\Registry\Endpoint;
27
use Ynlo\GraphQLBundle\Model\OrderBy;
28
use Ynlo\GraphQLBundle\Query\Node\AllNodesWithPagination;
29
use Ynlo\GraphQLBundle\Type\Registry\TypeRegistry;
30
31
/**
32
 * Convert a simple return of nodes into a paginated collection with edges
33
 */
34
class PaginationDefinitionPlugin extends AbstractDefinitionPlugin
35
{
36
    public const ONE_TO_MANY = 'ONE_TO_MANY';
37
    public const MANY_TO_MANY = 'MANY_TO_MANY';
38
39
    /**
40
     * @var Reader
41
     */
42
    protected $reader;
43
44
    /**
45
     * @var int
46
     */
47
    protected $limit;
48
49
    public function __construct(Reader $reader, array $config = [])
50
    {
51
        $this->reader = $reader;
52
        $this->limit = $config['limit'] ?? 100;
53
    }
54
55
    /**
56
     * {@inheritDoc}
57
     */
58
    public function buildConfig(ArrayNodeDefinition $root): void
59
    {
60
        $config = $root
61
            ->info('Enable pagination in queries or sub-fields')
62
            ->canBeEnabled()
63
            ->children();
64
65
        /** @var NodeBuilder $rootNode */
66
        $config->scalarNode('target')
67
               ->info('Target node to properly paginate. If is possible will be auto-resolved using naming conventions')
68
               ->isRequired();
69
        $config->integerNode('limit')->info('Max number of records allowed for first & last')->defaultValue($this->limit);
70
        $config->scalarNode('parent_field')
71
               ->info('When is used in sub-fields should be the field to filter by parent instance');
72
        $config->enumNode('parent_relation')
73
               ->info('When is used in sub-fields should be the type of relation with the parent field')
74
               ->defaultValue(self::ONE_TO_MANY)
75
               ->values([self::ONE_TO_MANY, self::MANY_TO_MANY]);
76
    }
77
78
    /**
79
     * {@inheritDoc}
80
     */
81
    public function normalizeConfig(DefinitionInterface $definition, $config): array
82
    {
83
        if (true === $config && $definition instanceof ExecutableDefinitionInterface) {
84
            $config = [];
85
        }
86
87
        if (\is_array($config) && !isset($config['target'])) {
88
            $config['target'] = $definition->getType();
0 ignored issues
show
Bug introduced by
The method getType() does not exist on Ynlo\GraphQLBundle\Definition\DefinitionInterface. It seems like you code against a sub-type of Ynlo\GraphQLBundle\Definition\DefinitionInterface such as Ynlo\GraphQLBundle\Definition\ArgumentDefinition or Ynlo\GraphQLBundle\Defin...ableDefinitionInterface. ( Ignorable by Annotation )

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

88
            /** @scrutinizer ignore-call */ 
89
            $config['target'] = $definition->getType();
Loading history...
89
        }
90
91
        if (false === $config) {
92
            $config = [];
93
        }
94
95
        return $config;
96
    }
97
98
    /**
99
     * {@inheritdoc}
100
     */
101
    public function configure(DefinitionInterface $definition, Endpoint $endpoint, array $config): void
102
    {
103
        if (!$config) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $config of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
104
            return;
105
        }
106
107
        if (!$definition instanceof QueryDefinition && !$definition instanceof FieldDefinition) {
108
            return;
109
        }
110
111
        $search = new ArgumentDefinition();
112
        $search->setName('search');
113
        $search->setType('string');
114
        $search->setNonNull(false);
115
        $search->setDescription('Search in current list by given string');
116
        $definition->addArgument($search);
117
118
        $first = new ArgumentDefinition();
119
        $first->setName('first');
120
        $first->setType('int');
121
        $first->setNonNull(false);
122
        $first->setDescription('Returns the first *n* elements from the list.');
123
        $definition->addArgument($first);
124
125
        $last = new ArgumentDefinition();
126
        $last->setName('last');
127
        $last->setType('int');
128
        $last->setNonNull(false);
129
        $last->setDescription('Returns the last *n* elements from the list.');
130
        $definition->addArgument($last);
131
132
        $after = new ArgumentDefinition();
133
        $after->setName('after');
134
        $after->setType('string');
135
        $after->setNonNull(false);
136
        $after->setDescription('Returns the last *n* elements from the list.');
137
        $definition->addArgument($after);
138
139
        $before = new ArgumentDefinition();
140
        $before->setName('before');
141
        $before->setType('string');
142
        $before->setNonNull(false);
143
        $before->setDescription('Returns the last *n* elements from the list.');
144
        $definition->addArgument($before);
145
146
        if (!$definition->hasArgument('orderBy')) {
147
            $orderBy = new ArgumentDefinition();
148
            $orderBy->setName('orderBy');
149
            $orderBy->setType(OrderBy::class);
150
            $orderBy->setNonNull(false);
151
            $orderBy->setList(true);
152
            $orderBy->setDescription('Ordering options for this list.');
153
            $definition->addArgument($orderBy);
154
        } else {
155
            //if exist move to the end
156
            $orderBy = $definition->getArgument('orderBy');
157
            $definition->removeArgument('orderBy');
158
            $definition->addArgument($orderBy);
159
        }
160
161
        $target = null;
162
        if ($definition instanceof FieldDefinition) {
163
            $target = $definition->getType();
164
        }
165
166
        $target = $config['target'] ?? $target;
167
        if ($endpoint->hasTypeForClass($target)) {
0 ignored issues
show
Bug introduced by
It seems like $target can also be of type null; however, parameter $class of Ynlo\GraphQLBundle\Defin...oint::hasTypeForClass() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

167
        if ($endpoint->hasTypeForClass(/** @scrutinizer ignore-type */ $target)) {
Loading history...
168
            $target = $endpoint->getTypeForClass($target);
0 ignored issues
show
Bug introduced by
It seems like $target can also be of type null; however, parameter $class of Ynlo\GraphQLBundle\Defin...oint::getTypeForClass() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

168
            $target = $endpoint->getTypeForClass(/** @scrutinizer ignore-type */ $target);
Loading history...
169
        }
170
171
        $connection = new ObjectDefinition();
172
        $connection->setName(ucfirst($target).'Connection');
173
174
        if (!$endpoint->hasType($connection->getName())) {
175
            $endpoint->addType($connection);
176
177
            $totalCount = new FieldDefinition();
178
            $totalCount->setName('totalCount');
179
            $totalCount->setType('Int');
180
            $totalCount->setNonNull(true);
181
            $connection->addField($totalCount);
182
183
            $pageInfo = new FieldDefinition();
184
            $pageInfo->setName('pageInfo');
185
            $pageInfo->setType('PageInfo');
186
            $pageInfo->setNonNull(true);
187
            $connection->addField($pageInfo);
188
189
            $edgeObject = new ObjectDefinition();
190
            $edgeObject->setName(ucfirst($target).'Edge');
191
            if (!$endpoint->hasType($edgeObject->getName())) {
192
                $endpoint->addType($edgeObject);
193
194
                $node = new FieldDefinition();
195
                $node->setName('node');
196
                $node->setType($target);
197
                $node->setNonNull(true);
198
                $edgeObject->addField($node);
199
200
                $cursor = new FieldDefinition();
201
                $cursor->setName('cursor');
202
                $cursor->setType('string');
203
                $cursor->setNonNull(true);
204
                $edgeObject->addField($cursor);
205
            }
206
207
            $edges = new FieldDefinition();
208
            $edges->setName('edges');
209
            $edges->setType($edgeObject->getName());
210
            $edges->setList(true);
211
            $connection->addField($edges);
212
        }
213
214
        $definition->setType($connection->getName());
215
        $definition->setList(false);
216
        $definition->setMeta('node', $target);
217
        $definition->setMeta('pagination', $config);
218
219
        if (!$definition->getResolver()) {
220
            $definition->setResolver(AllNodesWithPagination::class);
221
        }
222
223
        //TODO: add some config to customize filter
224
        $this->addFilters($definition, $target, $endpoint);
225
    }
226
227
    public function addFilters(ExecutableDefinitionInterface $definition, string $targetType, Endpoint $endpoint)
228
    {
229
        $filters = new InputObjectDefinition();
230
        $filters->setName(ucfirst($definition->getName()).'Filter');
231
        if ($endpoint->hasType($filters->getName())) {
232
            return;
233
        }
234
235
        $endpoint->add($filters);
236
237
        $object = $endpoint->getType($targetType);
238
        if ($object instanceof FieldsAwareDefinitionInterface) {
239
            foreach ($object->getFields() as $field) {
240
                if ('id' === $field->getName()
241
                    || !$field->getOriginName()
242
                    || \ReflectionProperty::class !== $field->getOriginType()) {
243
                    continue;
244
                }
245
246
                $filter = new FieldDefinition();
247
                $filter->setName($field->getName());
248
                $type = $field->getType();
249
                if ($endpoint->hasType($type)) {
250
                    $typeDefinition = $endpoint->getType($type);
251
                    if (!$typeDefinition instanceof EnumDefinition) {
252
                        $type = 'ID';
253
                    }
254
                    $filter->setList(true);
255
                }
256
257
                // fields using custom object as type
258
                // are not available for filters
259
                if (TypeRegistry::getTypeMapp()) {
260
                    if (isset(TypeRegistry::getTypeMapp()[$type])) {
261
                        $class = TypeRegistry::getTypeMapp()[$type];
262
                        $ref = new \ReflectionClass($class);
263
                        if ($ref->isSubclassOf(ObjectType::class)) {
264
                            continue;
265
                        }
266
                    }
267
                }
268
269
                $filter->setType($type);
270
                $filters->addField($filter);
271
            }
272
        }
273
274
        if (!$filters->getFields()) {
275
            return;
276
        }
277
278
        $search = new ArgumentDefinition();
279
        $search->setName('filters');
280
        $search->setType($filters->getName());
281
        $search->setDescription('Filter the list by given filters');
282
        $search->setNonNull(false);
283
        $definition->addArgument($search);
284
    }
285
}
286