Completed
Pull Request — master (#256)
by Tomáš
02:31
created

EntityRepository::select()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 10
rs 9.2
cc 4
eloc 6
nc 2
nop 2
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 Doctrine;
14
use Doctrine\ORM\AbstractQuery;
15
use Doctrine\ORM\NoResultException;
16
use Doctrine\ORM\NonUniqueResultException;
17
use Kdyby;
18
use Kdyby\Persistence;
19
use Nette;
20
use Nette\Utils\ObjectMixin;
21
22
23
24
/**
25
 * This class is an extension to EntityRepository and should help you with prototyping.
26
 * The first and only rule with EntityRepository is not to ever inherit them, ever.
27
 *
28
 * The only valid reason to inherit EntityRepository is to add more common methods to all EntityRepositories in application,
29
 * when you're creating your own framework (but do we really need to go any deeper than this?).
30
 *
31
 * @author Filip Procházka <[email protected]>
32
 */
33
class EntityRepository extends Doctrine\ORM\EntityRepository implements Persistence\QueryExecutor, Persistence\Queryable //, Persistence\ObjectFactory
34
{
35
36
	public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
37
	{
38
		if ($this->criteriaRequiresDql($criteria) === FALSE && $this->criteriaRequiresDql((array) $orderBy) === FALSE) {
39
			return parent::findBy($criteria, $orderBy, $limit, $offset);
40
		}
41
42
		$qb = $this->createQueryBuilder('e')
43
			->whereCriteria($criteria)
44
			->autoJoinOrderBy((array) $orderBy);
45
46
		return $qb->getQuery()
47
			->setMaxResults($limit)
48
			->setFirstResult($offset)
49
			->getResult();
50
	}
51
52
53
54
	public function findOneBy(array $criteria, array $orderBy = null)
55
	{
56
		if ($this->criteriaRequiresDql($criteria) === FALSE && $this->criteriaRequiresDql((array) $orderBy) === FALSE) {
57
			return parent::findOneBy($criteria, $orderBy);
58
		}
59
60
		$qb = $this->createQueryBuilder('e')
61
			->whereCriteria($criteria)
62
			->autoJoinOrderBy((array) $orderBy);
63
64
		try {
65
			return $qb->setMaxResults(1)
66
				->getQuery()->getSingleResult();
67
68
		} catch (NoResultException $e) {
69
			return NULL;
70
		}
71
	}
72
73
74
75
	/**
76
	 * @param array $criteria
77
	 * @return int
78
	 */
79
	public function countBy(array $criteria = [])
80
	{
81
		return (int) $this->createQueryBuilder('e')
82
			->whereCriteria($criteria)
83
			->select('COUNT(e)')
84
			->getQuery()->getSingleScalarResult();
85
	}
86
87
88
89
	/**
90
	 * @param array $criteria
91
	 * @return bool
92
	 */
93
	private function criteriaRequiresDql(array $criteria)
94
	{
95
		foreach ($criteria as $key => $val) {
96
			if (preg_match('~[\\?\\s\\.]~', $key)) {
97
				return TRUE;
98
			}
99
		}
100
101
		return FALSE;
102
	}
103
104
105
106
	/**
107
	 * Fetches all records like $key => $value pairs
108
	 *
109
	 * @param array $criteria parameter can be skipped
110
	 * @param string $value mandatory
111
	 * @param array $orderBy parameter can be skipped
112
	 * @param string $key optional
113
	 *
114
	 * @throws QueryException
115
	 * @return array
116
	 */
117
	public function findPairs($criteria, $value = NULL, $orderBy = [], $key = NULL)
118
	{
119
		if (!is_array($criteria)) {
120
			$key = $orderBy;
121
			$orderBy = $value;
122
			$value = $criteria;
123
			$criteria = [];
124
		}
125
126
		if (!is_array($orderBy)) {
127
			$key = $orderBy;
128
			$orderBy = [];
129
		}
130
131
		if (empty($key)) {
132
			$key = $this->getClassMetadata()->getSingleIdentifierFieldName();
133
		}
134
135
		$query = $this->createQueryBuilder('e')
136
			->whereCriteria($criteria)
137
			->select("e.$value", "e.$key")
0 ignored issues
show
Unused Code introduced by
The call to QueryBuilder::select() has too many arguments starting with "e.{$key}".

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
138
			->resetDQLPart('from')->from($this->getEntityName(), 'e', 'e.' . $key)
139
			->autoJoinOrderBy((array) $orderBy)
140
			->getQuery();
141
142
		try {
143
			return array_map(function ($row) {
144
				return reset($row);
145
			}, $query->getResult(AbstractQuery::HYDRATE_ARRAY));
146
147
		} catch (\Exception $e) {
148
			throw $this->handleException($e, $query);
149
		}
150
	}
151
152
153
154
	/**
155
	 * Fetches all records and returns an associative array indexed by key
156
	 *
157
	 * @param array $criteria
158
	 * @param string $key
159
	 *
160
	 * @throws \Exception|QueryException
161
	 * @return array
162
	 */
163
	public function findAssoc($criteria, $key = NULL)
164
	{
165
		if (!is_array($criteria)) {
166
			$key = $criteria;
167
			$criteria = [];
168
		}
169
170
		$query = $this->createQueryBuilder('e')
171
			->whereCriteria($criteria)
172
			->resetDQLPart('from')->from($this->getEntityName(), 'e', 'e.' . $key)
173
			->getQuery();
174
175
		try {
176
			return $query->getResult();
177
178
		} catch (\Exception $e) {
179
			throw $this->handleException($e, $query);
180
		}
181
	}
182
183
184
185
	/**
186
	 * @param string $sql
187
	 * @param Doctrine\ORM\Query\ResultSetMapping $rsm
188
	 * @return Doctrine\ORM\NativeQuery
189
	 */
190
	public function createNativeQuery($sql, Doctrine\ORM\Query\ResultSetMapping $rsm)
191
	{
192
		return $this->getEntityManager()->createNativeQuery($sql, $rsm);
193
	}
194
195
196
197
	/**
198
	 * @param string $alias
199
	 * @param string $indexBy The index for the from.
200
	 * @return \Kdyby\Doctrine\QueryBuilder
201
	 */
202
	public function createQueryBuilder($alias = NULL, $indexBy = NULL)
203
	{
204
		$qb = $this->getEntityManager()->createQueryBuilder();
205
206
		if ($alias !== NULL) {
207
			$qb->select($alias)->from($this->getEntityName(), $alias, $indexBy);
208
		}
209
210
		return $qb;
211
	}
212
213
214
215
	/**
216
	 * @param string $dql
217
	 *
218
	 * @return \Doctrine\ORM\Query
219
	 */
220
	public function createQuery($dql = NULL)
221
	{
222
		$dql = implode(' ', func_get_args());
223
224
		return $this->getEntityManager()->createQuery($dql);
225
	}
226
227
228
229
	/**
230
	 * @param \Kdyby\Persistence\Query|\Kdyby\Doctrine\QueryObject $queryObject
231
	 * @param int $hydrationMode
232
	 * @throws QueryException
233
	 * @return array|\Kdyby\Doctrine\ResultSet
234
	 */
235
	public function fetch(Persistence\Query $queryObject, $hydrationMode = AbstractQuery::HYDRATE_OBJECT)
236
	{
237
		try {
238
			return $queryObject->fetch($this, $hydrationMode);
0 ignored issues
show
Unused Code introduced by
The call to Query::fetch() has too many arguments starting with $hydrationMode.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
239
240
		} catch (\Exception $e) {
241
			throw $this->handleQueryException($e, $queryObject);
242
		}
243
	}
244
245
246
247
	/**
248
	 * @param \Kdyby\Persistence\Query|\Kdyby\Doctrine\QueryObject $queryObject
249
	 *
250
	 * @throws InvalidStateException
251
	 * @throws QueryException
252
	 * @return object
253
	 */
254
	public function fetchOne(Persistence\Query $queryObject)
255
	{
256
		try {
257
			return $queryObject->fetchOne($this);
258
259
		} catch (NoResultException $e) {
260
			return NULL;
261
262
		} catch (NonUniqueResultException $e) { // this should never happen!
263
			throw new InvalidStateException("You have to setup your query calling ->setMaxResult(1).", 0, $e);
264
265
		} catch (\Exception $e) {
266
			throw $this->handleQueryException($e, $queryObject);
267
		}
268
	}
269
270
271
272
	/**
273
	 * @param integer|array $id
274
	 * @return \Doctrine\ORM\Proxy\Proxy
275
	 */
276
	public function getReference($id)
277
	{
278
		return $this->getEntityManager()->getReference($this->_entityName, $id);
279
	}
280
281
282
283
	/**
284
	 * @param \Exception $e
285
	 * @param \Kdyby\Persistence\Query $queryObject
286
	 *
287
	 * @throws \Exception
288
	 */
289
	private function handleQueryException(\Exception $e, Persistence\Query $queryObject)
290
	{
291
		$lastQuery = $queryObject instanceof QueryObject ? $queryObject->getLastQuery() : NULL;
292
293
		return new QueryException($e, $lastQuery, '[' . get_class($queryObject) . '] ' . $e->getMessage());
294
	}
295
296
297
298
	/**
299
	 * @param \Exception $e
300
	 * @param \Doctrine\ORM\Query $query
301
	 * @param string $message
302
	 */
303
	private function handleException(\Exception $e, Doctrine\ORM\Query $query = NULL, $message = NULL)
304
	{
305
		if ($e instanceof Doctrine\ORM\Query\QueryException) {
306
			return new QueryException($e, $query, $message);
307
		}
308
309
		return $e;
310
	}
311
312
313
314
	/**
315
	 * @return Mapping\ClassMetadata
316
	 */
317
	public function getClassMetadata()
318
	{
319
		return parent::getClassMetadata();
320
	}
321
322
323
324
	/**
325
	 * @return EntityManager
326
	 */
327
	public function getEntityManager()
328
	{
329
		return parent::getEntityManager();
330
	}
331
332
333
334
	/**
335
	 * @param string $relation
336
	 * @return EntityRepository
337
	 */
338
	public function related($relation)
339
	{
340
		$meta = $this->getClassMetadata();
341
		$targetClass = $meta->getAssociationTargetClass($relation);
342
343
		return $this->getEntityManager()->getRepository($targetClass);
344
	}
345
346
347
348
	/*************************** Nette\Object ***************************/
349
350
351
352
	/**
353
	 * Access to reflection.
354
	 *
355
	 * @return \Nette\Reflection\ClassType
356
	 */
357
	public static function getReflection()
358
	{
359
		return new Nette\Reflection\ClassType(get_called_class());
360
	}
361
362
363
364
	/**
365
	 * Call to undefined method.
366
	 *
367
	 * @param string $name
368
	 * @param array $args
369
	 *
370
	 * @throws \Nette\MemberAccessException
371
	 * @return mixed
372
	 */
373
	public function __call($name, $args)
374
	{
375
		if (strpos($name, 'findBy') === 0 || strpos($name, 'findOneBy') === 0) {
376
			return parent::__call($name, $args);
377
		}
378
379
		return ObjectMixin::call($this, $name, $args);
380
	}
381
382
383
384
	/**
385
	 * Call to undefined static method.
386
	 *
387
	 * @param string $name
388
	 * @param array $args
389
	 *
390
	 * @throws \Nette\MemberAccessException
391
	 * @return mixed
392
	 */
393
	public static function __callStatic($name, $args)
394
	{
395
		return ObjectMixin::callStatic(get_called_class(), $name, $args);
396
	}
397
398
399
400
	/**
401
	 * Adding method to class.
402
	 *
403
	 * @param $name
404
	 * @param null $callback
405
	 *
406
	 * @throws \Nette\MemberAccessException
407
	 * @return callable|null
408
	 */
409
	public static function extensionMethod($name, $callback = NULL)
410
	{
411
		if (strpos($name, '::') === FALSE) {
412
			$class = get_called_class();
413
		} else {
414
			list($class, $name) = explode('::', $name);
415
		}
416
		if ($callback === NULL) {
417
			return ObjectMixin::getExtensionMethod($class, $name);
418
		} else {
419
			ObjectMixin::setExtensionMethod($class, $name, $callback);
420
		}
421
	}
422
423
424
425
	/**
426
	 * Returns property value. Do not call directly.
427
	 *
428
	 * @param string $name
429
	 *
430
	 * @throws \Nette\MemberAccessException
431
	 * @return mixed
432
	 */
433
	public function &__get($name)
434
	{
435
		return ObjectMixin::get($this, $name);
436
	}
437
438
439
440
	/**
441
	 * Sets value of a property. Do not call directly.
442
	 *
443
	 * @param string $name
444
	 * @param mixed $value
445
	 *
446
	 * @throws \Nette\MemberAccessException
447
	 * @return void
448
	 */
449
	public function __set($name, $value)
450
	{
451
		ObjectMixin::set($this, $name, $value);
452
	}
453
454
455
456
	/**
457
	 * Is property defined?
458
	 *
459
	 * @param string $name
460
	 *
461
	 * @return bool
462
	 */
463
	public function __isset($name)
464
	{
465
		return ObjectMixin::has($this, $name);
466
	}
467
468
469
470
	/**
471
	 * Access to undeclared property.
472
	 *
473
	 * @param string $name
474
	 *
475
	 * @throws \Nette\MemberAccessException
476
	 * @return void
477
	 */
478
	public function __unset($name)
479
	{
480
		ObjectMixin::remove($this, $name);
481
	}
482
483
}
484