Passed
Push — master ( 1bf4f0...5a65ca )
by Rafael
09:05
created

AllNodesWithPagination::createPaginator()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 3
c 1
b 0
f 0
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 1
nc 1
nop 0
crap 1
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\Query\Node;
12
13
use Doctrine\ORM\Query\Expr\Orx;
14
use Doctrine\ORM\QueryBuilder;
15
use GraphQL\Error\Error;
16
use Ynlo\GraphQLBundle\Definition\Extension\PaginationDefinitionExtension;
17
use Ynlo\GraphQLBundle\Model\ConnectionInterface;
18
use Ynlo\GraphQLBundle\Model\NodeConnection;
19
use Ynlo\GraphQLBundle\Model\NodeInterface;
20
use Ynlo\GraphQLBundle\Pagination\DoctrineCursorPaginatorInterface;
21
use Ynlo\GraphQLBundle\Pagination\DoctrineOffsetCursorPaginator;
22
use Ynlo\GraphQLBundle\Pagination\PaginationRequest;
23
24
/**
25
 * Base class to fetch nodes
26
 */
27
class AllNodesWithPagination extends AllNodes
28
{
29
    /**
30
     * @param array[] $args
31
     *
32
     * @return mixed
33
     *
34
     * @throws Error
35
     */
36 10
    public function __invoke($args = [])
37
    {
38 10
        $orderBy = $args['orderBy'] ?? [];
39 10
        $first = $args['first'] ?? null;
40 10
        $last = $args['last'] ?? null;
41 10
        $before = $args['before'] ?? null;
42 10
        $after = $args['after'] ?? null;
43 10
        $search = $args['search'] ?? null;
44
45 10
        $this->initialize();
46
47 10
        $qb = $this->createQuery();
48 10
        $this->applyOrderBy($qb, $orderBy);
49
50 10
        if ($this->getContext()->getRoot()) {
51 3
            $this->applyFilterByParent($qb, $this->getContext()->getRoot());
52
        }
53
54 10
        if ($search) {
55
            $this->search($qb, $search);
56
        }
57
58 10
        $this->configureQuery($qb);
59 10
        foreach ($this->extensions as $extension) {
60 4
            $extension->configureQuery($qb, $this, $this->context);
61
        }
62
63 10
        if (!$first && !$last) {
64
            $error = sprintf('You must provide a `first` or `last` value to properly paginate records in "%s" connection.', $this->queryDefinition->getName());
65
            throw new Error($error);
66
        }
67
68 10
        if ($this->queryDefinition->hasMeta('pagination')) {
69 10
            $limitAllowed = $this->queryDefinition->getMeta('pagination')['limit'];
70
71 10
            if ($first > $limitAllowed || $last > $limitAllowed) {
72
                $current = $first ?? $last;
73
                $where = $first ? 'first' : 'last';
74
                $error = sprintf(
75
                    'Requesting %s records for `%s` exceeds the `%s` limit of %s records for "%s" connection',
76
                    $current,
77
                    $this->queryDefinition->getName(),
78
                    $where,
79
                    $limitAllowed,
80
                    $this->queryDefinition->getName()
81
                );
82
                throw new Error($error);
83
            }
84
        }
85
86 10
        $paginator = $this->createPaginator();
87
88 10
        $connection = $this->createConnection();
89 10
        $paginator->paginate($qb, new PaginationRequest($first, $last, $after, $before), $connection);
90
91 10
        return $connection;
92
    }
93
94
    /**
95
     * @return ConnectionInterface
96
     */
97 10
    protected function createConnection(): ConnectionInterface
98
    {
99 10
        return new NodeConnection();
100
    }
101
102
    /**
103
     * @return DoctrineCursorPaginatorInterface
104
     */
105 10
    protected function createPaginator(): DoctrineCursorPaginatorInterface
106
    {
107 10
        return new DoctrineOffsetCursorPaginator();
108
    }
109
110
    /**
111
     * Filter some columns with simple string.
112
     *
113
     * @param QueryBuilder $qb
114
     * @param string       $search string to search
115
     */
116
    protected function search(QueryBuilder $qb, $search)
117
    {
118
        //search every word separate
119
        $searchArray = explode(' ', $search);
120
121
        $alias = $qb->getRootAliases()[0];
122
123
        //TODO: allow some config to customize search fields
124
        $em = $this->getManager();
125
        $metadata = $em->getClassMetadata($this->entity);
126
        $searchFields = $metadata->getFieldNames();
127
128
        if (count($searchFields) > 0) {
129
            $meta = $qb->getEntityManager()->getClassMetadata($qb->getRootEntities()[0]);
130
            foreach ($searchArray as $q) {
131
                $q = trim(rtrim($q));
132
                $id = md5($q);
133
                $orx = new Orx();
134
                foreach ($searchFields as $field) {
135
                    if (strpos($field, '.') !== false && !isset($meta->embeddedClasses[explode('.', $field)[0]])) {
136
                        $orx->add("$field LIKE :search_$id");
137
                    } else { //append current alias
138
                        $orx->add("$alias.$field LIKE :search_$id");
139
                    }
140
                }
141
                $qb->andWhere($orx);
142
                $qb->setParameter("search_$id", "%$q%");
143
            }
144
        }
145
    }
146
147
    /**
148
     * @param QueryBuilder  $qb
149
     * @param NodeInterface $root
150
     */
151 3
    protected function applyFilterByParent(QueryBuilder $qb, NodeInterface $root)
152
    {
153 3
        $parentField = null;
154 3
        if ($this->queryDefinition->hasMeta('pagination')) {
155 3
            $parentField = $this->queryDefinition->getMeta('pagination')['parent_field'] ?? null;
156
        }
157 3
        if (!$parentField) {
158
            throw new \RuntimeException(
159
                sprintf(
160
                    'Missing parent field to filter "%s" by given parent.
161
             The "parent_field" should be specified.',
162
                    $this->queryDefinition->getName()
163
                )
164
            );
165
        }
166
167 3
        if ($this->objectDefinition->hasField($parentField)) {
168 3
            $parentField = $this->objectDefinition->getField($parentField)->getOriginName();
169
        }
170
171 3
        $paramName = 'root'.mt_rand();
172 3
        if ($this->queryDefinition->getMeta('pagination')['parent_relation'] === PaginationDefinitionExtension::MANY_TO_MANY) {
173 2
            $qb->andWhere(sprintf(':%s MEMBER OF %s.%s', $paramName, $this->queryAlias, $parentField))
174 2
               ->setParameter($paramName, $root);
175
        } else {
176 1
            $qb->andWhere(sprintf('%s.%s = :%s', $this->queryAlias, $parentField, $paramName))
177 1
               ->setParameter($paramName, $root);
178
        }
179 3
    }
180
}
181