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\DBAL\DriverManager; |
15
|
|
|
use Doctrine\ORM\AbstractQuery; |
16
|
|
|
use Doctrine\ORM\ORMException; |
17
|
|
|
use Doctrine\ORM\Query; |
18
|
|
|
use Kdyby; |
19
|
|
|
use Kdyby\Doctrine\Diagnostics\EntityManagerUnitOfWorkSnapshotPanel; |
20
|
|
|
use Kdyby\Doctrine\QueryObject; |
21
|
|
|
use Kdyby\Doctrine\Tools\NonLockingUniqueInserter; |
22
|
|
|
use Kdyby\Persistence; |
23
|
|
|
use Nette; |
24
|
|
|
|
25
|
|
|
|
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* @author Filip Procházka <[email protected]> |
29
|
|
|
* |
30
|
|
|
* @method \Kdyby\Doctrine\Connection getConnection() |
31
|
|
|
* @method \Kdyby\Doctrine\Configuration getConfiguration() |
32
|
|
|
* @method \Kdyby\Doctrine\EntityRepository getRepository($entityName) |
33
|
|
|
*/ |
34
|
|
|
class EntityManager extends Doctrine\ORM\EntityManager implements Persistence\QueryExecutor, Persistence\Queryable |
35
|
|
|
{ |
36
|
|
|
|
37
|
|
|
use \Kdyby\StrictObjects\Scream; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @var NonLockingUniqueInserter |
41
|
|
|
*/ |
42
|
|
|
private $nonLockingUniqueInserter; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* @var \Kdyby\Doctrine\Diagnostics\EntityManagerUnitOfWorkSnapshotPanel |
46
|
|
|
*/ |
47
|
|
|
private $panel; |
48
|
|
|
|
49
|
|
|
|
50
|
|
|
|
51
|
|
|
protected function __construct(Doctrine\DBAL\Connection $conn, Doctrine\ORM\Configuration $config, Doctrine\Common\EventManager $eventManager) |
52
|
|
|
{ |
53
|
|
|
parent::__construct($conn, $config, $eventManager); |
54
|
|
|
|
55
|
|
|
if ($conn instanceof Kdyby\Doctrine\Connection) { |
56
|
|
|
$conn->bindEntityManager($this); |
57
|
|
|
} |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
|
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* @internal |
64
|
|
|
* @param EntityManagerUnitOfWorkSnapshotPanel $panel |
65
|
|
|
*/ |
66
|
|
|
public function bindTracyPanel(EntityManagerUnitOfWorkSnapshotPanel $panel) |
67
|
|
|
{ |
68
|
|
|
$this->panel = $panel; |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
|
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* @throws NotSupportedException |
75
|
|
|
* @return \Kdyby\Doctrine\QueryBuilder |
76
|
|
|
*/ |
77
|
|
|
public function createQueryBuilder($alias = NULL, $indexBy = NULL) |
78
|
|
|
{ |
79
|
|
|
if ($alias !== NULL || $indexBy !== NULL) { |
80
|
|
|
throw new NotSupportedException('Use EntityRepository for $alias and $indexBy arguments to work.'); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
$config = $this->getConfiguration(); |
84
|
|
|
if ($config instanceof Configuration) { |
85
|
|
|
$class = $config->getQueryBuilderClassName(); |
86
|
|
|
return new $class($this); |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
return new QueryBuilder($this); |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
|
93
|
|
|
|
94
|
|
|
/** |
95
|
|
|
* @param string|array|null $entityName if given, only entities of this type will get detached |
96
|
|
|
* @return EntityManager |
97
|
|
|
*/ |
98
|
|
|
public function clear($entityName = null) |
99
|
|
|
{ |
100
|
|
|
foreach (is_array($entityName) ? $entityName : (func_get_args() + [0 => NULL]) as $item) { |
101
|
|
|
parent::clear($item); |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
return $this; |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
|
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* @param object|array $entity |
111
|
|
|
* @return EntityManager |
112
|
|
|
*/ |
113
|
|
|
public function remove($entity) |
114
|
|
|
{ |
115
|
|
|
foreach (is_array($entity) ? $entity : func_get_args() as $item) { |
116
|
|
|
parent::remove($item); |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
return $this; |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
|
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* @param object|array $entity |
126
|
|
|
* @return EntityManager |
127
|
|
|
*/ |
128
|
|
|
public function persist($entity) |
129
|
|
|
{ |
130
|
|
|
foreach (is_array($entity) ? $entity : func_get_args() as $item) { |
131
|
|
|
parent::persist($item); |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
return $this; |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
|
138
|
|
|
|
139
|
|
|
/** |
140
|
|
|
* @param object|array|NULL $entity |
141
|
|
|
* @return EntityManager |
142
|
|
|
*/ |
143
|
|
|
public function flush($entity = null) |
144
|
|
|
{ |
145
|
|
|
try { |
146
|
|
|
parent::flush($entity); |
147
|
|
|
|
148
|
|
|
} catch (\Exception $e) { |
149
|
|
|
if ($this->panel) { |
150
|
|
|
$this->panel->markExceptionOwner($this, $e); |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
throw $e; |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
return $this; |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
|
160
|
|
|
|
161
|
|
|
public function close() |
162
|
|
|
{ |
163
|
|
|
if ($this->panel) { |
164
|
|
|
$this->panel->snapshotUnitOfWork($this); |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
parent::close(); |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
|
171
|
|
|
|
172
|
|
|
/** |
173
|
|
|
* Tries to persist the given entity and returns FALSE if an unique |
174
|
|
|
* constaint was violated. |
175
|
|
|
* |
176
|
|
|
* Warning: On success you must NOT use the passed entity further |
177
|
|
|
* in your application. Use the returned one instead! |
178
|
|
|
* |
179
|
|
|
* @param $entity |
180
|
|
|
* @throws \Doctrine\DBAL\DBALException |
181
|
|
|
* @throws \Exception |
182
|
|
|
* @return bool|object |
183
|
|
|
*/ |
184
|
|
|
public function safePersist($entity) |
185
|
|
|
{ |
186
|
|
|
if ($this->nonLockingUniqueInserter === NULL) { |
187
|
|
|
$this->nonLockingUniqueInserter = new NonLockingUniqueInserter($this); |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
return $this->nonLockingUniqueInserter->persist($entity); |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
|
194
|
|
|
|
195
|
|
|
/** |
196
|
|
|
* @param int $hydrationMode |
197
|
|
|
* @return Doctrine\ORM\Internal\Hydration\AbstractHydrator |
198
|
|
|
* @throws \Doctrine\ORM\ORMException |
199
|
|
|
*/ |
200
|
|
|
public function newHydrator($hydrationMode) |
201
|
|
|
{ |
202
|
|
|
switch ($hydrationMode) { |
203
|
|
|
case Hydration\HashHydrator::NAME: |
204
|
|
|
return new Hydration\HashHydrator($this); |
205
|
|
|
|
206
|
|
|
default: |
207
|
|
|
return parent::newHydrator($hydrationMode); |
208
|
|
|
} |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
|
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* Factory method to create EntityManager instances. |
215
|
|
|
* |
216
|
|
|
* @param \Doctrine\DBAL\Connection|array $conn |
217
|
|
|
* @param \Doctrine\ORM\Configuration $config |
218
|
|
|
* @param \Doctrine\Common\EventManager $eventManager |
219
|
|
|
* @throws \Doctrine\ORM\ORMException |
220
|
|
|
* @throws \InvalidArgumentException |
221
|
|
|
* @throws \Doctrine\ORM\ORMException |
222
|
|
|
* @return EntityManager |
223
|
|
|
*/ |
224
|
|
|
public static function create($conn, Doctrine\ORM\Configuration $config, Doctrine\Common\EventManager $eventManager = NULL) |
225
|
|
|
{ |
226
|
|
|
if (!$config->getMetadataDriverImpl()) { |
227
|
|
|
throw ORMException::missingMappingDriverImpl(); |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
switch (TRUE) { |
231
|
|
|
case (is_array($conn)): |
232
|
|
|
$conn = DriverManager::getConnection( |
233
|
|
|
$conn, $config, ($eventManager ? : new Doctrine\Common\EventManager()) |
234
|
|
|
); |
235
|
|
|
break; |
236
|
|
|
|
237
|
|
|
case ($conn instanceof Doctrine\DBAL\Connection): |
238
|
|
|
if ($eventManager !== NULL && $conn->getEventManager() !== $eventManager) { |
239
|
|
|
throw ORMException::mismatchedEventManager(); |
240
|
|
|
} |
241
|
|
|
break; |
242
|
|
|
|
243
|
|
|
default: |
244
|
|
|
throw new \InvalidArgumentException("Invalid connection"); |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
return new EntityManager($conn, $config, $conn->getEventManager()); |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
|
251
|
|
|
|
252
|
|
|
/****************** Kdyby\Persistence\QueryExecutor *****************/ |
253
|
|
|
|
254
|
|
|
|
255
|
|
|
|
256
|
|
|
/** |
257
|
|
|
* @param \Kdyby\Persistence\Query|\Kdyby\Doctrine\QueryObject $queryObject |
258
|
|
|
* @param int $hydrationMode |
259
|
|
|
* @throws QueryException |
260
|
|
|
* @return array|\Kdyby\Doctrine\ResultSet |
261
|
|
|
*/ |
262
|
|
|
public function fetch(Persistence\Query $queryObject, $hydrationMode = AbstractQuery::HYDRATE_OBJECT) |
263
|
|
|
{ |
264
|
|
|
try { |
265
|
|
|
return $queryObject->fetch($this, $hydrationMode); |
|
|
|
|
266
|
|
|
|
267
|
|
|
} catch (\Exception $e) { |
268
|
|
|
throw $this->handleQueryException($e, $queryObject); |
269
|
|
|
} |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
|
273
|
|
|
|
274
|
|
|
/** |
275
|
|
|
* @param \Kdyby\Persistence\Query|\Kdyby\Doctrine\QueryObject $queryObject |
276
|
|
|
* |
277
|
|
|
* @throws InvalidStateException |
278
|
|
|
* @throws QueryException |
279
|
|
|
* @return object|NULL |
280
|
|
|
*/ |
281
|
|
|
public function fetchOne(Persistence\Query $queryObject) |
282
|
|
|
{ |
283
|
|
|
try { |
284
|
|
|
return $queryObject->fetchOne($this); |
285
|
|
|
|
286
|
|
|
} catch (Doctrine\ORM\NoResultException $e) { |
287
|
|
|
return NULL; |
288
|
|
|
|
289
|
|
|
} catch (Doctrine\ORM\NonUniqueResultException $e) { // this should never happen! |
290
|
|
|
throw new InvalidStateException("You have to setup your query calling ->setMaxResult(1).", 0, $e); |
291
|
|
|
|
292
|
|
|
} catch (\Exception $e) { |
293
|
|
|
throw $this->handleQueryException($e, $queryObject); |
294
|
|
|
} |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
|
298
|
|
|
|
299
|
|
|
/** |
300
|
|
|
* @param \Exception $e |
301
|
|
|
* @param \Kdyby\Persistence\Query $queryObject |
302
|
|
|
* |
303
|
|
|
* @throws \Exception |
304
|
|
|
*/ |
305
|
|
|
private function handleQueryException(\Exception $e, Persistence\Query $queryObject) |
306
|
|
|
{ |
307
|
|
|
$lastQuery = $queryObject instanceof QueryObject ? $queryObject->getLastQuery() : NULL; |
308
|
|
|
|
309
|
|
|
return new QueryException($e, $lastQuery, '['.get_class($queryObject).'] '.$e->getMessage()); |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
} |
313
|
|
|
|
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.