Completed
Pull Request — master (#240)
by Tomáš
06:38
created

Doctrine   B

Complexity

Total Complexity 39

Size/Duplication

Total Lines 263
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 65.66%

Importance

Changes 7
Bugs 0 Features 1
Metric Value
wmc 39
c 7
b 0
f 1
lcom 1
cbo 9
dl 0
loc 263
ccs 65
cts 99
cp 0.6566
rs 8.2857

15 Methods

Rating   Name   Duplication   Size   Complexity  
A getQuery() 0 4 1
A getQb() 0 4 1
A getFilterMapping() 0 4 1
A getSortMapping() 0 4 1
A __construct() 0 10 3
A setUseOutputWalkers() 0 5 1
A setFetchJoinCollection() 0 5 1
A getRand() 0 9 2
A getCount() 0 7 1
B getData() 0 18 5
A filter() 0 6 2
A limit() 0 5 1
A sort() 0 10 3
C makeWhere() 0 37 8
C suggest() 0 35 8
1
<?php
2
3
/**
4
 * This file is part of the Grido (http://grido.bugyik.cz)
5
 *
6
 * Copyright (c) 2011 Petr Bugyík (http://petr.bugyik.cz)
7
 *
8
 * For the full copyright and license information, please view
9
 * the file LICENSE.md that was distributed with this source code.
10
 */
11
12
namespace Grido\DataSources;
13
14
use Grido\Exception;
15
use Grido\Components\Filters\Condition;
16
17
use Nette\Utils\Strings;
18
use Nette\Utils\Random;
19
use Doctrine\ORM\Tools\Pagination\Paginator;
20
21
/**
22
 * Doctrine data source.
23
 *
24
 * @package     Grido
25
 * @subpackage  DataSources
26
 * @author      Martin Jantosovic <[email protected]>
27
 * @author      Petr Bugyík
28
 *
29
 * @property-read \Doctrine\ORM\QueryBuilder $qb
30
 * @property-read array $filterMapping
31
 * @property-read array $sortMapping
32
 * @property-read int $count
33
 * @property-read array $data
34
 */
35 1
class Doctrine extends \Nette\Object implements IDataSource
36
{
37
    /** @var \Doctrine\ORM\QueryBuilder */
38
    protected $qb;
39
40
    /** @var array Map column to the query builder */
41
    protected $filterMapping;
42
43
    /** @var array Map column to the query builder */
44
    protected $sortMapping;
45
46
    /** @var bool use OutputWalker in Doctrine Paginator */
47
    protected $useOutputWalkers;
48
49
    /** @var bool fetch join collection in Doctrine Paginator */
50
    protected $fetchJoinCollection = TRUE;
51
52
    /** @var array */
53
    protected $rand;
54
55
    /**
56
     * If $sortMapping is not set and $filterMapping is set,
57
     * $filterMapping will be used also as $sortMapping.
58
     * @param \Doctrine\ORM\QueryBuilder $qb
59
     * @param array $filterMapping Maps columns to the DQL columns
60
     * @param array $sortMapping Maps columns to the DQL columns
61
     */
62
    public function __construct(\Doctrine\ORM\QueryBuilder $qb, array $filterMapping = NULL, array $sortMapping = NULL)
63
    {
64 1
        $this->qb = $qb;
65 1
        $this->filterMapping = $filterMapping;
0 ignored issues
show
Documentation Bug introduced by
It seems like $filterMapping can be null. However, the property $filterMapping is declared as array. Maybe change the type of the property to array|null or add a type check?

Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.

To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.

function aContainsB(array $needle = null, array  $haystack) {
    if (!$needle) {
        return false;
    }

    return array_intersect($haystack, $needle) == $haystack;
}

The function can be called with either null or an array for the parameter $needle but will only accept an array as $haystack.

Loading history...
66 1
        $this->sortMapping = $sortMapping;
0 ignored issues
show
Documentation Bug introduced by
It seems like $sortMapping can be null. However, the property $sortMapping is declared as array. Maybe change the type of the property to array|null or add a type check?

Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.

To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.

function aContainsB(array $needle = null, array  $haystack) {
    if (!$needle) {
        return false;
    }

    return array_intersect($haystack, $needle) == $haystack;
}

The function can be called with either null or an array for the parameter $needle but will only accept an array as $haystack.

Loading history...
67
68 1
        if (!$this->sortMapping && $this->filterMapping) {
69 1
            $this->sortMapping = $this->filterMapping;
70 1
        }
71 1
    }
72
73
    /**
74
     * @param bool $useOutputWalkers
75
     * @return \Grido\DataSources\Doctrine
76
     */
77
    public function setUseOutputWalkers($useOutputWalkers)
78
    {
79
        $this->useOutputWalkers = $useOutputWalkers;
80
        return $this;
81
    }
82
83
    /**
84
     * @param bool $fetchJoinCollection
85
     * @return \Grido\DataSources\Doctrine
86
     */
87
    public function setFetchJoinCollection($fetchJoinCollection)
88
    {
89
        $this->fetchJoinCollection = $fetchJoinCollection;
90
        return $this;
91
    }
92
93
    /**
94
     * @return \Doctrine\ORM\Query
95
     */
96
    public function getQuery()
97
    {
98 1
        return $this->qb->getQuery();
99
    }
100
101
    /**
102
     * @return \Doctrine\ORM\QueryBuilder
103
     */
104
    public function getQb()
105
    {
106
        return $this->qb;
107
    }
108
109
    /**
110
     * @return array|NULL
111
     */
112
    public function getFilterMapping()
113
    {
114
        return $this->filterMapping;
115
    }
116
117
    /**
118
     * @return array|NULL
119
     */
120
    public function getSortMapping()
121
    {
122
        return $this->sortMapping;
123
    }
124
125
    /**
126
     * @param Condition $condition
127
     * @param \Doctrine\ORM\QueryBuilder $qb
128
     */
129
    protected function makeWhere(Condition $condition, \Doctrine\ORM\QueryBuilder $qb = NULL)
130
    {
131
        $qb = $qb === NULL
132 1
            ? $this->qb
133 1
            : $qb;
134
135 1
        if ($condition->callback) {
136
            return call_user_func_array($condition->callback, [$condition->value, $qb]);
137
        }
138
139 1
        $columns = $condition->column;
140 1
        foreach ($columns as $key => $column) {
141 1
            if (!Condition::isOperator($column)) {
142 1
                $columns[$key] = (isset($this->filterMapping[$column])
143 1
                    ? $this->filterMapping[$column]
144 1
                    : (Strings::contains($column, ".")
145 1
                        ? $column
146 1
                        : current($this->qb->getRootAliases()) . '.' . $column));
147 1
            }
148 1
        }
149
150 1
        $condition->setColumn($columns);
151 1
        list($where) = $condition->__toArray(NULL, NULL, FALSE);
152
153 1
        $rand = $this->getRand();
154 1
        $where = preg_replace_callback('/\?/', function() use ($rand) {
155 1
            static $i = -1;
156 1
            $i++;
157 1
            return ":$rand{$i}";
158 1
        }, $where);
159
160 1
        $qb->andWhere($where);
161
162 1
        foreach ($condition->getValueForColumn() as $i => $val) {
163 1
            $qb->setParameter("$rand{$i}", $val);
164 1
        }
165 1
    }
166
167
    /**
168
     * @return string
169
     */
170
    protected function getRand()
171
    {
172
        do {
173 1
            $rand = Random::generate(4, 'a-z');
174 1
        } while (isset($this->rand[$rand]));
175
176 1
        $this->rand[$rand] = $rand;
177 1
        return $rand;
178
    }
179
180
    /*********************************** interface IDataSource ************************************/
181
182
    /**
183
     * @return int
184
     */
185
    public function getCount()
186
    {
187 1
        $paginator = new Paginator($this->getQuery(), $this->fetchJoinCollection);
188 1
        $paginator->setUseOutputWalkers($this->useOutputWalkers);
189
190 1
        return $paginator->count();
191
    }
192
193
    /**
194
     * It is possible to use query builder with additional columns.
195
     * In this case, only item at index [0] is returned, because
196
     * it should be an entity object.
197
     * @return array
198
     */
199
    public function getData()
200
    {
201 1
        $data = [];
202
203
        // Paginator is better if the query uses ManyToMany associations
204 1
        $result = $this->qb->getMaxResults() !== NULL || $this->qb->getFirstResult() !== NULL
205 1
            ? new Paginator($this->getQuery())
206 1
            : $this->qb->getQuery()->getResult();
207
208 1
        foreach ($result as $item) {
209
            // Return only entity itself
210 1
            $data[] = is_array($item)
211 1
                ? $item[0]
212
                : $item;
213 1
        }
214
215 1
        return $data;
216
    }
217
218
    /**
219
     * Sets filter.
220
     * @param array $conditions
221
     */
222
    public function filter(array $conditions)
223
    {
224 1
        foreach ($conditions as $condition) {
225 1
            $this->makeWhere($condition);
226 1
        }
227 1
    }
228
229
    /**
230
     * Sets offset and limit.
231
     * @param int $offset
232
     * @param int $limit
233
     */
234
    public function limit($offset, $limit)
235
    {
236 1
        $this->qb->setFirstResult($offset)
237 1
            ->setMaxResults($limit);
238 1
    }
239
240
    /**
241
     * Sets sorting.
242
     * @param array $sorting
243
     */
244
    public function sort(array $sorting)
245
    {
246 1
        foreach ($sorting as $key => $value) {
247 1
            $column = isset($this->sortMapping[$key])
248 1
                ? $this->sortMapping[$key]
249 1
                : current($this->qb->getRootAliases()) . '.' . $key;
250
251 1
            $this->qb->addOrderBy($column, $value);
252 1
        }
253 1
    }
254
255
    /**
256
     * @param mixed $column
257
     * @param array $conditions
258
     * @param int $limit
259
     * @return array
260
     * @throws Exception
261
     */
262
    public function suggest($column, array $conditions, $limit)
263
    {
264
        $qb = clone $this->qb;
265
        $qb->setMaxResults($limit);
266
267
        if (is_string($column)) {
268
            $mapping = isset($this->filterMapping[$column])
269
                ? $this->filterMapping[$column]
270
                : current($qb->getRootAliases()) . '.' . $column;
271
272
            $qb->select($mapping)->distinct()->orderBy($mapping);
273
        }
274
275
        foreach ($conditions as $condition) {
276
            $this->makeWhere($condition, $qb);
277
        }
278
279
        $items = [];
280
        $data = $qb->getQuery()->getScalarResult();
281
        foreach ($data as $row) {
282
            if (is_string($column)) {
283
                $value = (string) current($row);
284
            } elseif (is_callable($column)) {
285
                $value = (string) $column($row);
286
            } else {
287
                $type = gettype($column);
288
                throw new Exception("Column of suggestion must be string or callback, $type given.");
289
            }
290
291
            $items[$value] = \Latte\Runtime\Filters::escapeHtml($value);
292
        }
293
294
        is_callable($column) && sort($items);
295
        return array_values($items);
296
    }
297
}
298