QueryObject   A
last analyzed

Complexity

Total Complexity 27

Size/Duplication

Total Lines 207
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 0
Metric Value
wmc 27
c 0
b 0
f 0
lcom 1
cbo 10
dl 0
loc 207
rs 10

10 Methods

Rating   Name   Duplication   Size   Complexity  
doCreateQuery() 0 1 ?
A __construct() 0 4 1
A getQuery() 0 15 5
A doCreateCountQuery() 0 4 1
B count() 0 24 7
A fetch() 0 10 2
A fetchOne() 0 18 2
A postFetch() 0 4 1
A getLastQuery() 0 4 1
B toQuery() 0 24 7
1
<?php
2
3
/**
4
 * This file is part of the Kdyby (http://www.kdyby.org)
5
 *
6
 * Copyright (c) 2008 Filip Procházka ([email protected])
7
 *
8
 * For the full copyright and license information, please view the file license.txt that was distributed with this source code.
9
 */
10
11
namespace Kdyby\Doctrine;
12
13
use ArrayIterator;
14
use Doctrine;
15
use Doctrine\ORM\AbstractQuery;
16
use Doctrine\ORM\Tools\Pagination\Paginator;
17
use Kdyby;
18
use Kdyby\Persistence\Queryable;
19
use Nette;
20
21
22
23
/**
24
 * Purpose of this class is to be inherited and have implemented doCreateQuery() method,
25
 * which constructs DQL from your constraints and filters.
26
 *
27
 * QueryObject inheritors are great when you're printing a data to the user,
28
 * they may be used in service layer but that's not really suggested.
29
 *
30
 * Don't be afraid to use them in presenters
31
 *
32
 * <code>
33
 * $this->template->articles = $this->articlesRepository->fetch(new ArticlesQuery());
34
 * </code>
35
 *
36
 * or in more complex ways
37
 *
38
 * <code>
39
 * $productsQuery = new ProductsQuery();
40
 * $productsQuery
41
 *    ->setColor('green')
42
 *    ->setMaxDeliveryPrice(100)
43
 *    ->setMaxDeliveryMinutes(75);
44
 *
45
 * $productsQuery->size = 'big';
46
 *
47
 * $this->template->products = $this->productsRepository->fetch($productsQuery);
48
 * </code>
49
 *
50
 * @method onPostFetch(QueryObject $self, Queryable $repository, \Iterator $iterator)
51
 *
52
 * @author Filip Procházka <[email protected]>
53
 */
54
abstract class QueryObject implements Kdyby\Persistence\Query
55
{
56
57
	use Nette\SmartObject;
58
59
	/**
60
	 * @var array
61
	 */
62
	public $onPostFetch = [];
63
64
	/**
65
	 * @var \Doctrine\ORM\Query|NativeQueryWrapper|null
66
	 */
67
	private $lastQuery;
68
69
	/**
70
	 * @var \Kdyby\Doctrine\ResultSet
71
	 */
72
	private $lastResult;
73
74
75
76
	/**
77
	 */
78
	public function __construct()
79
	{
80
81
	}
82
83
84
85
	/**
86
	 * @param \Kdyby\Persistence\Queryable $repository
87
	 * @return \Doctrine\ORM\Query|\Doctrine\ORM\QueryBuilder
88
	 */
89
	protected abstract function doCreateQuery(Kdyby\Persistence\Queryable $repository);
90
91
92
93
	/**
94
	 * @param \Kdyby\Persistence\Queryable $repository
95
	 *
96
	 * @throws UnexpectedValueException
97
	 * @return \Doctrine\ORM\Query|NativeQueryWrapper
98
	 */
99
	protected function getQuery(Queryable $repository)
100
	{
101
		$query = $this->toQuery($this->doCreateQuery($repository));
102
103
		if ($this->lastQuery instanceof Doctrine\ORM\Query && $query instanceof Doctrine\ORM\Query &&
104
			$this->lastQuery->getDQL() === $query->getDQL()) {
105
			$query = $this->lastQuery;
106
		}
107
108
		if ($this->lastQuery !== $query) {
109
			$this->lastResult = new ResultSet($query, $this, $repository);
110
		}
111
112
		return $this->lastQuery = $query;
113
	}
114
115
116
117
	/**
118
	 * @param Queryable $repository
119
	 * @return \Doctrine\ORM\Query|\Doctrine\ORM\QueryBuilder
120
	 */
121
	protected function doCreateCountQuery(Queryable $repository)
0 ignored issues
show
Unused Code introduced by
The parameter $repository is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
122
	{
123
124
	}
125
126
127
128
	/**
129
	 * @param \Kdyby\Persistence\Queryable $repository
130
	 * @param ResultSet $resultSet
131
	 * @param \Doctrine\ORM\Tools\Pagination\Paginator $paginatedQuery
132
	 * @return integer
133
	 */
134
	public function count(Queryable $repository, ResultSet $resultSet = NULL, Paginator $paginatedQuery = NULL)
135
	{
136
		if ($query = $this->doCreateCountQuery($repository)) {
137
			return (int)$this->toQuery($query)->getSingleScalarResult();
138
		}
139
140
		if ($this->lastQuery && $this->lastQuery instanceof NativeQueryWrapper) {
141
			$class = get_called_class();
142
			throw new NotSupportedException("You must implement your own count query in $class::doCreateCountQuery(), Paginator from Doctrine doesn't support NativeQueries.");
143
		}
144
145
		if ($paginatedQuery !== NULL) {
146
			return $paginatedQuery->count();
147
		}
148
149
		$query = $this->getQuery($repository)
150
			->setFirstResult(NULL)
151
			->setMaxResults(NULL);
152
153
		$paginatedQuery = new Paginator($query, ($resultSet !== NULL) ? $resultSet->getFetchJoinCollection() : TRUE);
154
		$paginatedQuery->setUseOutputWalkers(($resultSet !== NULL) ? $resultSet->getUseOutputWalkers() : NULL);
155
156
		return $paginatedQuery->count();
157
	}
158
159
160
161
	/**
162
	 * @param \Kdyby\Persistence\Queryable $repository
163
	 * @param int $hydrationMode
164
	 *
165
	 * @return \Kdyby\Doctrine\ResultSet|array
166
	 */
167
	public function fetch(Queryable $repository, $hydrationMode = AbstractQuery::HYDRATE_OBJECT)
168
	{
169
		$query = $this->getQuery($repository)
170
			->setFirstResult(NULL)
171
			->setMaxResults(NULL);
172
173
		return $hydrationMode !== AbstractQuery::HYDRATE_OBJECT
174
			? $query->execute(NULL, $hydrationMode)
175
			: $this->lastResult;
176
	}
177
178
179
180
	/**
181
	 * If You encounter a problem with the LIMIT 1 here,
182
	 * you should instead of fetching toMany relations just use postFetch.
183
	 *
184
	 * And if you really really need to hack it, just override this method and remove the limit.
185
	 *
186
	 * @param \Kdyby\Persistence\Queryable $repository
187
	 * @return object
188
	 */
189
	public function fetchOne(Queryable $repository)
190
	{
191
		$query = $this->getQuery($repository)
192
			->setFirstResult(NULL)
193
			->setMaxResults(1);
194
195
		// getResult has to be called to have consistent result for the postFetch
196
		// this is the only way to main the INDEX BY value
197
		$singleResult = $query->getResult();
198
199
		if (!$singleResult) {
200
			throw new Doctrine\ORM\NoResultException(); // simulate getSingleResult()
201
		}
202
203
		$this->postFetch($repository, new ArrayIterator($singleResult));
204
205
		return array_shift($singleResult);
206
	}
207
208
209
210
	/**
211
	 * @param \Kdyby\Persistence\Queryable $repository
212
	 * @param \Iterator $iterator
213
	 * @return void
214
	 */
215
	public function postFetch(Queryable $repository, \Iterator $iterator)
216
	{
217
		$this->onPostFetch($this, $repository, $iterator);
218
	}
219
220
221
222
	/**
223
	 * @internal For Debugging purposes only!
224
	 * @return \Doctrine\ORM\Query|NativeQueryWrapper|null
225
	 */
226
	public function getLastQuery()
227
	{
228
		return $this->lastQuery;
229
	}
230
231
	/**
232
	 * @param \Doctrine\ORM\QueryBuilder|AbstractQuery|NativeQueryBuilder $query
233
	 * @return Doctrine\ORM\Query|NativeQueryWrapper
234
	 */
235
	private function toQuery($query)
236
	{
237
		if ($query instanceof Doctrine\ORM\QueryBuilder) {
238
			$query = $query->getQuery();
239
240
		} elseif ($query instanceof Doctrine\ORM\NativeQuery) {
241
			$query = new NativeQueryWrapper($query);
242
243
		} elseif ($query instanceof NativeQueryBuilder) {
244
			$query = $query->getQuery();
245
		}
246
247
		if (!$query instanceof Doctrine\ORM\Query && !$query instanceof NativeQueryWrapper) {
248
			throw new UnexpectedValueException(sprintf(
249
				"Method " . get_called_class() . "::doCreateQuery must return " .
250
				"instanceof %s or %s, " .
251
				(is_object($query) ? 'instance of ' . get_class($query) : gettype($query)) . " given.",
252
				\Doctrine\ORM\Query::class,
253
				\Kdyby\Doctrine\QueryBuilder::class
254
			));
255
		}
256
257
		return $query;
258
	}
259
260
}
261