Passed
Push — master ( cf54c3...3ae95d )
by Rafael
07:43
created

PaginationDefinitionExtension::addFilters()   D

Complexity

Conditions 9
Paths 3

Size

Total Lines 40
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 0
Metric Value
dl 0
loc 40
ccs 0
cts 0
cp 0
rs 4.909
c 0
b 0
f 0
cc 9
eloc 28
nc 3
nop 3
crap 90
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\Extension;
12
13
use Doctrine\Common\Annotations\Reader;
14
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
15
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
16
use Ynlo\GraphQLBundle\Definition\ArgumentDefinition;
17
use Ynlo\GraphQLBundle\Definition\DefinitionInterface;
18
use Ynlo\GraphQLBundle\Definition\EnumDefinition;
19
use Ynlo\GraphQLBundle\Definition\ExecutableDefinitionInterface;
20
use Ynlo\GraphQLBundle\Definition\FieldDefinition;
21
use Ynlo\GraphQLBundle\Definition\FieldsAwareDefinitionInterface;
22
use Ynlo\GraphQLBundle\Definition\InputObjectDefinition;
23
use Ynlo\GraphQLBundle\Definition\ObjectDefinition;
24
use Ynlo\GraphQLBundle\Definition\QueryDefinition;
25
use Ynlo\GraphQLBundle\Definition\Registry\Endpoint;
26
use Ynlo\GraphQLBundle\Model\OrderBy;
27
use Ynlo\GraphQLBundle\Query\Node\AllNodesWithPagination;
28
29
/**
30
 * Convert a simple return of nodes into a paginated collection with edges
31
 */
32
class PaginationDefinitionExtension extends AbstractDefinitionExtension
33
{
34
    public const ONE_TO_MANY = 'ONE_TO_MANY';
35
    public const MANY_TO_MANY = 'MANY_TO_MANY';
36
37
    /**
38
     * @var Reader
39
     */
40
    protected $reader;
41
42
    /**
43
     * @var int
44
     */
45
    protected $limit;
46
47
    /**
48
     * PaginationDefinitionExtension constructor.
49
     *
50 21
     * @param Reader $reader
51
     * @param array  $config
52 21
     */
53 21
    public function __construct(Reader $reader, $config = [])
54 21
    {
55
        $this->reader = $reader;
56
        $this->limit = $config['limit'] ?? 100;
57
    }
58
59 21
    /**
60
     * {@inheritDoc}
61
     */
62 21
    public function buildConfig(ArrayNodeDefinition $root)
63 21
    {
64 21
        $config = $root
65
            ->info('Enable pagination in queries or sub-fields')
66
            ->canBeEnabled()
67 21
            ->children();
68 21
69 21
        /** @var NodeBuilder $rootNode */
70 21
        $config->scalarNode('target')
71 21
               ->info('Target node to properly paginate. If is possible will be auto-resolved using naming conventions')
72 21
               ->isRequired();
73 21
        $config->integerNode('limit')->info('Max number of records allowed for first & last')->defaultValue($this->limit);
74 21
        $config->scalarNode('parent_field')
75 21
               ->info('When is used in sub-fields should be the field to filter by parent instance');
76 21
        $config->enumNode('parent_relation')
77 21
               ->info('When is used in sub-fields should be the type of relation with the parent field')
78
               ->defaultValue(self::ONE_TO_MANY)
79
               ->values([self::ONE_TO_MANY, self::MANY_TO_MANY]);
80
    }
81
82 21
    /**
83
     * {@inheritDoc}
84 21
     */
85 21
    public function normalizeConfig(DefinitionInterface $definition, $config): array
86
    {
87
        if (true === $config && $definition instanceof ExecutableDefinitionInterface) {
88 21
            $config = [];
89 21
        }
90
91
        if (is_array($config) && !isset($config['target'])) {
92 21
            $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

92
            /** @scrutinizer ignore-call */ 
93
            $config['target'] = $definition->getType();
Loading history...
93
        }
94
95
        if (false === $config) {
96 21
            $config = [];
97
        }
98
99
        return $config;
100
    }
101
102 21
    /**
103
     * {@inheritdoc}
104 21
     */
105 21
    public function configure(DefinitionInterface $definition, Endpoint $endpoint, array $config)
106
    {
107
        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...
108 21
            return;
109
        }
110
111
        if (!$definition instanceof QueryDefinition && !$definition instanceof FieldDefinition) {
112 21
            return;
113 21
        }
114 21
115 21
        $search = new ArgumentDefinition();
116 21
        $search->setName('search');
117 21
        $search->setType('string');
118
        $search->setNonNull(false);
119 21
        $search->setDescription('Search in current list by given string');
120 21
        $definition->addArgument($search);
121 21
122 21
        $first = new ArgumentDefinition();
123 21
        $first->setName('first');
124 21
        $first->setType('int');
125
        $first->setNonNull(false);
126 21
        $first->setDescription('Returns the first *n* elements from the list.');
127 21
        $definition->addArgument($first);
128 21
129 21
        $last = new ArgumentDefinition();
130 21
        $last->setName('last');
131 21
        $last->setType('int');
132
        $last->setNonNull(false);
133 21
        $last->setDescription('Returns the last *n* elements from the list.');
134 21
        $definition->addArgument($last);
135 21
136 21
        $after = new ArgumentDefinition();
137 21
        $after->setName('after');
138 21
        $after->setType('string');
139
        $after->setNonNull(false);
140 21
        $after->setDescription('Returns the last *n* elements from the list.');
141 21
        $definition->addArgument($after);
142 21
143 21
        $before = new ArgumentDefinition();
144 21
        $before->setName('before');
145 21
        $before->setType('string');
146
        $before->setNonNull(false);
147 21
        $before->setDescription('Returns the last *n* elements from the list.');
148 21
        $definition->addArgument($before);
149 21
150 21
        if (!$definition->hasArgument('orderBy')) {
151 21
            $orderBy = new ArgumentDefinition();
152 21
            $orderBy->setName('orderBy');
153 21
            $orderBy->setType(OrderBy::class);
154 21
            $orderBy->setNonNull(false);
155
            $orderBy->setList(true);
156
            $orderBy->setDescription('Ordering options for this list.');
157
            $definition->addArgument($orderBy);
158
        } else {
159
            //if exist move to the end
160
            $orderBy = $definition->getArgument('orderBy');
161
            $definition->removeArgument('orderBy');
162 21
            $definition->addArgument($orderBy);
163 21
        }
164 21
165
        $target = null;
166
        if ($definition instanceof FieldDefinition) {
167 21
            $target = $definition->getType();
168 21
        }
169 21
170
        $target = $config['target'] ?? $target;
171
        if ($endpoint->hasTypeForClass($target)) {
172 21
            $target = $endpoint->getTypeForClass($target);
173 21
        }
174
175 21
        $connection = new ObjectDefinition();
176 21
        $connection->setName(ucfirst($target).'Connection');
177
178 21
        if (!$endpoint->hasType($connection->getName())) {
179 21
            $endpoint->addType($connection);
180 21
181 21
            $totalCount = new FieldDefinition();
182 21
            $totalCount->setName('totalCount');
183
            $totalCount->setType('Int');
184 21
            $totalCount->setNonNull(true);
185 21
            $connection->addField($totalCount);
186 21
187 21
            $pageInfo = new FieldDefinition();
188 21
            $pageInfo->setName('pageInfo');
189
            $pageInfo->setType('PageInfo');
190 21
            $pageInfo->setNonNull(true);
191 21
            $connection->addField($pageInfo);
192 21
193 21
            $edgeObject = new ObjectDefinition();
194
            $edgeObject->setName(ucfirst($target).'Edge');
195 21
            if (!$endpoint->hasType($edgeObject->getName())) {
196 21
                $endpoint->addType($edgeObject);
197 21
198 21
                $node = new FieldDefinition();
199 21
                $node->setName('node');
200
                $node->setType($target);
201 21
                $node->setNonNull(true);
202 21
                $edgeObject->addField($node);
203 21
204 21
                $cursor = new FieldDefinition();
205 21
                $cursor->setName('cursor');
206
                $cursor->setType('string');
207
                $cursor->setNonNull(true);
208 21
                $edgeObject->addField($cursor);
209 21
            }
210 21
211 21
            $edges = new FieldDefinition();
212 21
            $edges->setName('edges');
213
            $edges->setType($edgeObject->getName());
214
            $edges->setList(true);
215 21
            $connection->addField($edges);
216 21
        }
217 21
218 21
        $definition->setType($connection->getName());
219
        $definition->setList(false);
220 21
        $definition->setMeta('node', $target);
221 21
        $definition->setMeta('pagination', $config);
222
223 21
        if (!$definition->getResolver()) {
224
            $definition->setResolver(AllNodesWithPagination::class);
225
        }
226
227
        //TODO: add some config to customize filter
228
        $this->addFilters($definition, $target, $endpoint);
229
    }
230
231
    /**
232
     * @param ExecutableDefinitionInterface $definition
233
     * @param string                        $targetType
234
     * @param Endpoint                      $endpoint
235
     */
236
    public function addFilters(ExecutableDefinitionInterface $definition, $targetType, Endpoint $endpoint)
237
    {
238
        $filters = new InputObjectDefinition();
239
        $filters->setName(ucfirst($definition->getName()).'Filter');
240
        if ($endpoint->hasType($filters->getName())) {
241
            return;
242
        }
243
244
        $endpoint->add($filters);
245
246
        $object = $endpoint->getType($targetType);
247
        if ($object instanceof FieldsAwareDefinitionInterface) {
248
            foreach ($object->getFields() as $field) {
249
                if ('id' === $field->getName()
250
                    || !$field->getOriginName()
251
                    || \ReflectionProperty::class !== $field->getOriginType()) {
252
                    continue;
253
                }
254
255
                $filter = new FieldDefinition();
256
                $filter->setName($field->getName());
257
                $type = $field->getType();
258
                if ($endpoint->hasType($type)) {
259
                    $typeDefinition = $endpoint->getType($type);
260
                    if (!$typeDefinition instanceof EnumDefinition) {
261
                        $type = 'ID';
262
                    }
263
                    $filter->setList(true);
264
                }
265
                $filter->setType($type);
266
                $filters->addField($filter);
267
            }
268
        }
269
270
        $search = new ArgumentDefinition();
271
        $search->setName('filters');
272
        $search->setType($filters->getName());
273
        $search->setDescription('Filter the list by given filters');
274
        $search->setNonNull(false);
275
        $definition->addArgument($search);
276
    }
277
}
278