Completed
Pull Request — master (#283)
by Sergey
02:59
created

EntityRepository::__unset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
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
	 * @param array $criteria
76
	 * @return bool
77
	 */
78
	public function exists(array $criteria = [])
79
	{
80
		try {
81
			return (bool) $this->createQueryBuilder('e')
82
				->whereCriteria($criteria)
83
				->select('1')
84
				->setMaxResults(1)
85
				->getQuery()->getSingleScalarResult();
86
87
		} catch (NoResultException $e) {
88
			return false;
89
		}
90
	}
91
	
92
93
	/**
94
	 * @param array $criteria
95
	 * @return int
96
	 */
97
	public function countBy(array $criteria = [])
98
	{
99
		return (int) $this->createQueryBuilder('e')
100
			->whereCriteria($criteria)
101
			->select('COUNT(e)')
102
			->getQuery()->getSingleScalarResult();
103
	}
104
105
106
107
	/**
108
	 * @param array $criteria
109
	 * @return bool
110
	 */
111
	private function criteriaRequiresDql(array $criteria)
112
	{
113
		foreach ($criteria as $key => $val) {
114
			if (preg_match('~[\\?\\s\\.]~', $key)) {
115
				return TRUE;
116
			}
117
		}
118
119
		return FALSE;
120
	}
121
122
123
124
	/**
125
	 * Fetches all records like $key => $value pairs
126
	 *
127
	 * @param array $criteria parameter can be skipped
128
	 * @param string $value mandatory
129
	 * @param array $orderBy parameter can be skipped
130
	 * @param string $key optional
131
	 *
132
	 * @throws QueryException
133
	 * @return array
134
	 */
135
	public function findPairs($criteria, $value = NULL, $orderBy = [], $key = NULL)
136
	{
137
		if (!is_array($criteria)) {
138
			$key = $orderBy;
139
			$orderBy = $value;
140
			$value = $criteria;
141
			$criteria = [];
142
		}
143
144
		if (!is_array($orderBy)) {
145
			$key = $orderBy;
146
			$orderBy = [];
147
		}
148
149
		if (empty($key)) {
150
			$key = $this->getClassMetadata()->getSingleIdentifierFieldName();
151
		}
152
153
		$query = $this->createQueryBuilder('e')
154
			->whereCriteria($criteria)
155
			->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...
156
			->resetDQLPart('from')->from($this->getEntityName(), 'e', 'e.' . $key)
157
			->autoJoinOrderBy((array) $orderBy)
158
			->getQuery();
159
160
		try {
161
			return array_map(function ($row) {
162
				return reset($row);
163
			}, $query->getResult(AbstractQuery::HYDRATE_ARRAY));
164
165
		} catch (\Exception $e) {
166
			throw $this->handleException($e, $query);
167
		}
168
	}
169
170
171
172
	/**
173
	 * Fetches all records and returns an associative array indexed by key
174
	 *
175
	 * @param array $criteria
176
	 * @param string $key
177
	 *
178
	 * @throws \Exception|QueryException
179
	 * @return array
180
	 */
181
	public function findAssoc($criteria, $key = NULL)
182
	{
183
		if (!is_array($criteria)) {
184
			$key = $criteria;
185
			$criteria = [];
186
		}
187
188
		$query = $this->createQueryBuilder('e')
189
			->whereCriteria($criteria)
190
			->resetDQLPart('from')->from($this->getEntityName(), 'e', 'e.' . $key)
191
			->getQuery();
192
193
		try {
194
			return $query->getResult();
195
196
		} catch (\Exception $e) {
197
			throw $this->handleException($e, $query);
198
		}
199
	}
200
201
202
203
	/**
204
	 * @param string $sql
205
	 * @param Doctrine\ORM\Query\ResultSetMapping $rsm
206
	 * @return Doctrine\ORM\NativeQuery
207
	 */
208
	public function createNativeQuery($sql, Doctrine\ORM\Query\ResultSetMapping $rsm)
209
	{
210
		return $this->getEntityManager()->createNativeQuery($sql, $rsm);
211
	}
212
213
214
215
	/**
216
	 * @param string $alias
217
	 * @param string $indexBy The index for the from.
218
	 * @return \Kdyby\Doctrine\QueryBuilder
219
	 */
220
	public function createQueryBuilder($alias = NULL, $indexBy = NULL)
221
	{
222
		$qb = $this->getEntityManager()->createQueryBuilder();
223
224
		if ($alias !== NULL) {
225
			$qb->select($alias)->from($this->getEntityName(), $alias, $indexBy);
226
		}
227
228
		return $qb;
229
	}
230
231
232
233
	/**
234
	 * @param string $dql
235
	 *
236
	 * @return \Doctrine\ORM\Query
237
	 */
238
	public function createQuery($dql = NULL)
239
	{
240
		$dql = implode(' ', func_get_args());
241
242
		return $this->getEntityManager()->createQuery($dql);
243
	}
244
245
246
247
	/**
248
	 * @param \Kdyby\Persistence\Query|\Kdyby\Doctrine\QueryObject $queryObject
249
	 * @param int $hydrationMode
250
	 * @throws QueryException
251
	 * @return array|\Kdyby\Doctrine\ResultSet
252
	 */
253
	public function fetch(Persistence\Query $queryObject, $hydrationMode = AbstractQuery::HYDRATE_OBJECT)
254
	{
255
		try {
256
			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...
257
258
		} catch (\Exception $e) {
259
			throw $this->handleQueryException($e, $queryObject);
260
		}
261
	}
262
263
264
265
	/**
266
	 * @param \Kdyby\Persistence\Query|\Kdyby\Doctrine\QueryObject $queryObject
267
	 *
268
	 * @throws InvalidStateException
269
	 * @throws QueryException
270
	 * @return object
271
	 */
272
	public function fetchOne(Persistence\Query $queryObject)
273
	{
274
		try {
275
			return $queryObject->fetchOne($this);
276
277
		} catch (NoResultException $e) {
278
			return NULL;
279
280
		} catch (NonUniqueResultException $e) { // this should never happen!
281
			throw new InvalidStateException("You have to setup your query calling ->setMaxResult(1).", 0, $e);
282
283
		} catch (\Exception $e) {
284
			throw $this->handleQueryException($e, $queryObject);
285
		}
286
	}
287
288
289
290
	/**
291
	 * @param integer|array $id
292
	 * @return \Doctrine\ORM\Proxy\Proxy
293
	 */
294
	public function getReference($id)
295
	{
296
		return $this->getEntityManager()->getReference($this->_entityName, $id);
297
	}
298
299
300
301
	/**
302
	 * @param \Exception $e
303
	 * @param \Kdyby\Persistence\Query $queryObject
304
	 *
305
	 * @throws \Exception
306
	 */
307
	private function handleQueryException(\Exception $e, Persistence\Query $queryObject)
308
	{
309
		$lastQuery = $queryObject instanceof QueryObject ? $queryObject->getLastQuery() : NULL;
310
311
		return new QueryException($e, $lastQuery, '[' . get_class($queryObject) . '] ' . $e->getMessage());
312
	}
313
314
315
316
	/**
317
	 * @param \Exception $e
318
	 * @param \Doctrine\ORM\Query $query
319
	 * @param string $message
320
	 */
321
	private function handleException(\Exception $e, Doctrine\ORM\Query $query = NULL, $message = NULL)
322
	{
323
		if ($e instanceof Doctrine\ORM\Query\QueryException) {
324
			return new QueryException($e, $query, $message);
325
		}
326
327
		return $e;
328
	}
329
330
331
332
	/**
333
	 * @return Mapping\ClassMetadata
334
	 */
335
	public function getClassMetadata()
336
	{
337
		return parent::getClassMetadata();
338
	}
339
340
341
342
	/**
343
	 * @return EntityManager
344
	 */
345
	public function getEntityManager()
346
	{
347
		return parent::getEntityManager();
348
	}
349
350
351
352
	/**
353
	 * @param string $relation
354
	 * @return EntityRepository
355
	 */
356
	public function related($relation)
357
	{
358
		$meta = $this->getClassMetadata();
359
		$targetClass = $meta->getAssociationTargetClass($relation);
360
361
		return $this->getEntityManager()->getRepository($targetClass);
362
	}
363
364
365
366
	/*************************** Nette\Object ***************************/
367
368
369
370
	/**
371
	 * Access to reflection.
372
	 *
373
	 * @return \Nette\Reflection\ClassType
374
	 */
375
	public static function getReflection()
376
	{
377
		return new Nette\Reflection\ClassType(get_called_class());
378
	}
379
380
381
382
	/**
383
	 * Call to undefined method.
384
	 *
385
	 * @param string $name
386
	 * @param array $args
387
	 *
388
	 * @throws \Nette\MemberAccessException
389
	 * @return mixed
390
	 */
391
	public function __call($name, $args)
392
	{
393
		if (strpos($name, 'findBy') === 0 || strpos($name, 'findOneBy') === 0) {
394
			return parent::__call($name, $args);
395
		}
396
397
		return ObjectMixin::call($this, $name, $args);
398
	}
399
400
401
402
	/**
403
	 * Call to undefined static method.
404
	 *
405
	 * @param string $name
406
	 * @param array $args
407
	 *
408
	 * @throws \Nette\MemberAccessException
409
	 * @return mixed
410
	 */
411
	public static function __callStatic($name, $args)
412
	{
413
		return ObjectMixin::callStatic(get_called_class(), $name, $args);
414
	}
415
416
417
418
	/**
419
	 * Adding method to class.
420
	 *
421
	 * @param $name
422
	 * @param null $callback
423
	 *
424
	 * @throws \Nette\MemberAccessException
425
	 * @return callable|null
426
	 */
427
	public static function extensionMethod($name, $callback = NULL)
428
	{
429
		if (strpos($name, '::') === FALSE) {
430
			$class = get_called_class();
431
		} else {
432
			list($class, $name) = explode('::', $name);
433
		}
434
		if ($callback === NULL) {
435
			return ObjectMixin::getExtensionMethod($class, $name);
436
		} else {
437
			ObjectMixin::setExtensionMethod($class, $name, $callback);
438
		}
439
	}
440
441
442
443
	/**
444
	 * Returns property value. Do not call directly.
445
	 *
446
	 * @param string $name
447
	 *
448
	 * @throws \Nette\MemberAccessException
449
	 * @return mixed
450
	 */
451
	public function &__get($name)
452
	{
453
		return ObjectMixin::get($this, $name);
454
	}
455
456
457
458
	/**
459
	 * Sets value of a property. Do not call directly.
460
	 *
461
	 * @param string $name
462
	 * @param mixed $value
463
	 *
464
	 * @throws \Nette\MemberAccessException
465
	 * @return void
466
	 */
467
	public function __set($name, $value)
468
	{
469
		ObjectMixin::set($this, $name, $value);
470
	}
471
472
473
474
	/**
475
	 * Is property defined?
476
	 *
477
	 * @param string $name
478
	 *
479
	 * @return bool
480
	 */
481
	public function __isset($name)
482
	{
483
		return ObjectMixin::has($this, $name);
484
	}
485
486
487
488
	/**
489
	 * Access to undeclared property.
490
	 *
491
	 * @param string $name
492
	 *
493
	 * @throws \Nette\MemberAccessException
494
	 * @return void
495
	 */
496
	public function __unset($name)
497
	{
498
		ObjectMixin::remove($this, $name);
499
	}
500
501
}
502