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") |
|
|
|
|
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); |
|
|
|
|
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
|
|
|
|
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.