EntityManager.php 17.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<?php
/*
 *  $Id$
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the LGPL. For more information, see
19
 * <http://www.doctrine-project.org>.
20 21
 */

22
namespace Doctrine\ORM;
23

24 25 26 27
use Doctrine\Common\EventManager,
    Doctrine\DBAL\Connection,
    Doctrine\ORM\Mapping\ClassMetadata,
    Doctrine\ORM\Mapping\ClassMetadataFactory,
28
    Doctrine\ORM\Proxy\ProxyFactory;
29

30
/**
31
 * The EntityManager is the central access point to ORM functionality.
32
 *
33 34 35 36 37
 * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
 * @link www.doctrine-project.org
 * @since 2.0
 * @version $Revision$
 * @author Roman Borschel <roman@code-factory.org>
38 39
 * @todo Remove flush modes. They dont seem to be of much use. Manual flushing should
 *       be enough.
40
 */
41
class EntityManager
42
{
43 44 45
    /**
     * The used Configuration.
     *
46
     * @var Doctrine\ORM\Configuration
47
     */
48
    private $_config;
49

50 51 52
    /**
     * The database connection used by the EntityManager.
     *
53
     * @var Doctrine\DBAL\Connection
54 55
     */
    private $_conn;
56

57
    /**
58
     * The metadata factory, used to retrieve the ORM metadata of entity classes.
59
     *
60
     * @var Doctrine\ORM\Mapping\ClassMetadataFactory
61 62
     */
    private $_metadataFactory;
63

64 65 66 67 68 69
    /**
     * The EntityRepository instances.
     *
     * @var array
     */
    private $_repositories = array();
70

71
    /**
72
     * The UnitOfWork used to coordinate object-level transactions.
73
     *
74
     * @var Doctrine\ORM\UnitOfWork
75 76
     */
    private $_unitOfWork;
77

78 79 80
    /**
     * The event manager that is the central point of the event system.
     *
81
     * @var Doctrine\Common\EventManager
82 83
     */
    private $_eventManager;
romanb's avatar
romanb committed
84

85 86 87 88 89 90 91
    /**
     * The maintained (cached) hydrators. One instance per type.
     *
     * @var array
     */
    private $_hydrators = array();

92
    /**
romanb's avatar
romanb committed
93
     * The proxy factory used to create dynamic proxies.
94
     *
romanb's avatar
romanb committed
95
     * @var Doctrine\ORM\Proxy\ProxyFactory
96
     */
97
    private $_proxyFactory;
98

99 100 101 102
    /**
     * @var ExpressionBuilder The expression builder instance used to generate query expressions.
     */
    private $_expressionBuilder;
103

104 105 106
    /**
     * Whether the EntityManager is closed or not.
     */
romanb's avatar
romanb committed
107
    private $_closed = false;
108

109
    /**
romanb's avatar
romanb committed
110 111
     * Creates a new EntityManager that operates on the given database connection
     * and uses the given Configuration and EventManager implementations.
112
     *
romanb's avatar
romanb committed
113
     * @param Doctrine\DBAL\Connection $conn
114 115
     * @param Doctrine\ORM\Configuration $config
     * @param Doctrine\Common\EventManager $eventManager
116
     */
117
    protected function __construct(Connection $conn, Configuration $config, EventManager $eventManager)
118 119
    {
        $this->_conn = $conn;
romanb's avatar
romanb committed
120 121
        $this->_config = $config;
        $this->_eventManager = $eventManager;
122
        $this->_metadataFactory = new ClassMetadataFactory($this);
123
        $this->_metadataFactory->setCacheDriver($this->_config->getMetadataCacheImpl());
124
        $this->_unitOfWork = new UnitOfWork($this);
125 126 127 128
        $this->_proxyFactory = new ProxyFactory($this,
                $config->getProxyDir(),
                $config->getProxyNamespace(),
                $config->getAutoGenerateProxyClasses());
129
    }
130

131 132 133
    /**
     * Gets the database connection object used by the EntityManager.
     *
134
     * @return Doctrine\DBAL\Connection
135 136 137 138 139
     */
    public function getConnection()
    {
        return $this->_conn;
    }
140

romanb's avatar
romanb committed
141
    /**
142
     * Gets the metadata factory used to gather the metadata of classes.
143 144
     *
     * @return Doctrine\ORM\Mapping\ClassMetadataFactory
romanb's avatar
romanb committed
145
     */
146
    public function getMetadataFactory()
romanb's avatar
romanb committed
147
    {
148
        return $this->_metadataFactory;
romanb's avatar
romanb committed
149
    }
150

151 152
    /**
     * Gets an ExpressionBuilder used for object-oriented construction of query expressions.
153
     *
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
     * Example:
     *
     *     [php]
     *     $qb = $em->createQueryBuilder();
     *     $expr = $em->getExpressionBuilder();
     *     $qb->select('u')->from('User', 'u')
     *         ->where($expr->orX($expr->eq('u.id', 1), $expr->eq('u.id', 2)));
     *
     * @return ExpressionBuilder
     */
    public function getExpressionBuilder()
    {
        if ($this->_expressionBuilder === null) {
            $this->_expressionBuilder = new Query\Expr;
        }
        return $this->_expressionBuilder;
    }
171

172
    /**
173
     * Starts a transaction on the underlying database connection.
174 175 176
     */
    public function beginTransaction()
    {
177
        $this->_conn->beginTransaction();
178
    }
179

180
    /**
181
     * Commits a transaction on the underlying database connection.
182
     */
183
    public function commit()
184
    {
185
        $this->_conn->commit();
186
    }
187

188 189 190 191 192 193
    /**
     * Performs a rollback on the underlying database connection and closes the
     * EntityManager as it may now be in a corrupted state.
     */
    public function rollback()
    {
romanb's avatar
romanb committed
194 195
        $this->_conn->rollback();
        $this->close();
196
    }
197 198 199 200

    /**
     * Returns the metadata for a class.
     *
201
     * @return Doctrine\ORM\Mapping\ClassMetadata
202
     * @internal Performance-sensitive method.
203 204
     */
    public function getClassMetadata($className)
205
    {
206 207
        return $this->_metadataFactory->getMetadataFor($className);
    }
208

209
    /**
210
     * Creates a new Query object.
211
     *
212
     * @param string  The DQL string.
213
     * @return Doctrine\ORM\Query
214 215 216
     */
    public function createQuery($dql = "")
    {
217
        $query = new Query($this);
218
        if ( ! empty($dql)) {
219
            $query->setDql($dql);
220 221 222
        }
        return $query;
    }
223

224
    /**
225
     * Creates a Query from a named query.
226
     *
227
     * @param string $name
228
     * @return Doctrine\ORM\Query
229 230 231
     */
    public function createNamedQuery($name)
    {
232
        return $this->createQuery($this->_config->getNamedQuery($name));
233
    }
234

235
    /**
236 237 238
     * Creates a native SQL query.
     *
     * @param string $sql
239
     * @param ResultSetMapping $rsm The ResultSetMapping to use.
240
     * @return NativeQuery
241
     */
242
    public function createNativeQuery($sql, \Doctrine\ORM\Query\ResultSetMapping $rsm)
243
    {
244 245 246 247
        $query = new NativeQuery($this);
        $query->setSql($sql);
        $query->setResultSetMapping($rsm);
        return $query;
248
    }
249

250
    /**
251
     * Creates a NativeQuery from a named native query.
252
     *
253
     * @param string $name
254
     * @return Doctrine\ORM\NativeQuery
255 256 257
     */
    public function createNamedNativeQuery($name)
    {
258 259
        list($sql, $rsm) = $this->_config->getNamedNativeQuery($name);
        return $this->createNativeQuery($sql, $rsm);
260
    }
261

262
    /**
263 264 265
     * Create a QueryBuilder instance
     *
     * @return QueryBuilder $qb
266
     */
267
    public function createQueryBuilder()
268
    {
269
        return new QueryBuilder($this);
270
    }
271

272 273
    /**
     * Flushes all changes to objects that have been queued up to now to the database.
274 275
     * This effectively synchronizes the in-memory state of managed objects with the
     * database.
276 277 278
     */
    public function flush()
    {
279
        $this->_errorIfClosed();
280
        $this->_unitOfWork->commit();
281
    }
282

283
    /**
284
     * Finds an Entity by its identifier.
285
     *
romanb's avatar
romanb committed
286
     * This is just a convenient shortcut for getRepository($entityName)->find($id).
287
     *
288 289
     * @param string $entityName
     * @param mixed $identifier
290
     * @return object
291 292 293 294 295
     */
    public function find($entityName, $identifier)
    {
        return $this->getRepository($entityName)->find($identifier);
    }
296 297 298

    /**
     * Gets a reference to the entity identified by the given type and identifier
299
     * without actually loading it.
300
     *
301 302 303
     * If partial objects are allowed, this method will return a partial object that only
     * has its identifier populated. Otherwise a proxy is returned that automatically
     * loads itself on first access.
304 305 306 307 308
     *
     * @return object The entity reference.
     */
    public function getReference($entityName, $identifier)
    {
309
        $class = $this->_metadataFactory->getMetadataFor($entityName);
310

romanb's avatar
romanb committed
311
        // Check identity map first, if its already in there just return it.
312
        if ($entity = $this->_unitOfWork->tryGetById($identifier, $class->rootEntityName)) {
romanb's avatar
romanb committed
313 314
            return $entity;
        }
315 316
        if ( ! is_array($identifier)) {
            $identifier = array($class->identifier[0] => $identifier);
romanb's avatar
romanb committed
317
        }
318
        $entity = $this->_proxyFactory->getProxy($entityName, $identifier);
319
        $this->_unitOfWork->registerManaged($entity, $identifier, array());
romanb's avatar
romanb committed
320

321 322
        return $entity;
    }
323

324
    /**
325 326 327 328
     * Clears the EntityManager. All entities that are currently managed
     * by this EntityManager become detached.
     *
     * @param string $entityName
329 330 331 332
     */
    public function clear($entityName = null)
    {
        if ($entityName === null) {
333
            $this->_unitOfWork->clear();
334
        } else {
romanb's avatar
romanb committed
335
            //TODO
romanb's avatar
romanb committed
336
            throw new ORMException("EntityManager#clear(\$entityName) not yet implemented.");
337 338
        }
    }
339

340
    /**
341 342 343
     * Closes the EntityManager. All entities that are currently managed
     * by this EntityManager become detached. The EntityManager may no longer
     * be used after it is closed.
344 345 346
     */
    public function close()
    {
347
        $this->clear();
romanb's avatar
romanb committed
348
        $this->_closed = true;
349
    }
350

351
    /**
romanb's avatar
romanb committed
352
     * Tells the EntityManager to make an instance managed and persistent.
353
     *
romanb's avatar
romanb committed
354 355
     * The entity will be entered into the database at or before transaction
     * commit or as a result of the flush operation.
356
     *
romanb's avatar
romanb committed
357
     * @param object $object The instance to make managed and persistent.
358
     */
359
    public function persist($entity)
360
    {
361 362 363
        if ( ! is_object($entity)) {
            throw new \InvalidArgumentException(gettype($entity));
        }
364
        $this->_errorIfClosed();
365
        $this->_unitOfWork->persist($entity);
366
    }
367

368
    /**
romanb's avatar
romanb committed
369
     * Removes an entity instance.
370
     *
romanb's avatar
romanb committed
371
     * A removed entity will be removed from the database at or before transaction commit
372 373
     * or as a result of the flush operation.
     *
romanb's avatar
romanb committed
374
     * @param object $entity The entity instance to remove.
375
     */
romanb's avatar
romanb committed
376
    public function remove($entity)
377
    {
378 379 380
        if ( ! is_object($entity)) {
            throw new \InvalidArgumentException(gettype($entity));
        }
381
        $this->_errorIfClosed();
romanb's avatar
romanb committed
382
        $this->_unitOfWork->remove($entity);
383
    }
384

romanb's avatar
romanb committed
385
    /**
romanb's avatar
romanb committed
386
     * Refreshes the persistent state of an entity from the database,
romanb's avatar
romanb committed
387
     * overriding any local changes that have not yet been persisted.
romanb's avatar
romanb committed
388
     *
romanb's avatar
romanb committed
389
     * @param object $entity The entity to refresh.
romanb's avatar
romanb committed
390
     */
391
    public function refresh($entity)
romanb's avatar
romanb committed
392
    {
393 394 395
        if ( ! is_object($entity)) {
            throw new \InvalidArgumentException(gettype($entity));
        }
romanb's avatar
romanb committed
396
        $this->_errorIfClosed();
romanb's avatar
romanb committed
397
        $this->_unitOfWork->refresh($entity);
romanb's avatar
romanb committed
398
    }
399 400

    /**
401 402 403
     * Detaches an entity from the EntityManager, causing a managed entity to
     * become detached.  Unflushed changes made to the entity if any
     * (including removal of the entity), will not be synchronized to the database.
404
     * Entities which previously referenced the detached entity will continue to
405
     * reference it.
406 407 408 409 410
     *
     * @param object $entity The entity to detach.
     */
    public function detach($entity)
    {
411 412 413
        if ( ! is_object($entity)) {
            throw new \InvalidArgumentException(gettype($entity));
        }
414
        $this->_unitOfWork->detach($entity);
415 416 417 418
    }

    /**
     * Merges the state of a detached entity into the persistence context
419 420
     * of this EntityManager and returns the managed copy of the entity.
     * The entity passed to merge will not become associated/managed with this EntityManager.
421
     *
422
     * @param object $entity The detached entity to merge into the persistence context.
423 424 425 426
     * @return object The managed copy of the entity.
     */
    public function merge($entity)
    {
427 428 429
        if ( ! is_object($entity)) {
            throw new \InvalidArgumentException(gettype($entity));
        }
430
        $this->_errorIfClosed();
431 432
        return $this->_unitOfWork->merge($entity);
    }
433

romanb's avatar
romanb committed
434 435 436
    /**
     * Creates a copy of the given entity. Can create a shallow or a deep copy.
     *
437 438
     * @param object $entity  The entity to copy.
     * @return object  The new entity.
439
     * @todo Implementation or remove.
romanb's avatar
romanb committed
440
     */
441
    public function copy($entity, $deep = false)
romanb's avatar
romanb committed
442
    {
443
        throw new \BadMethodCallException("Not implemented.");
romanb's avatar
romanb committed
444
    }
445

446
    /**
447
     * Gets the repository for an entity class.
448
     *
449
     * @param string $entityName  The name of the Entity.
450
     * @return EntityRepository  The repository.
451 452 453 454 455 456 457 458 459 460
     */
    public function getRepository($entityName)
    {
        if (isset($this->_repositories[$entityName])) {
            return $this->_repositories[$entityName];
        }

        $metadata = $this->getClassMetadata($entityName);
        $customRepositoryClassName = $metadata->getCustomRepositoryClass();
        if ($customRepositoryClassName !== null) {
461
            $repository = new $customRepositoryClassName($this, $metadata);
462
        } else {
463
            $repository = new EntityRepository($this, $metadata);
464 465 466 467 468
        }
        $this->_repositories[$entityName] = $repository;

        return $repository;
    }
469

470
    /**
romanb's avatar
romanb committed
471
     * Determines whether an entity instance is managed in this EntityManager.
472
     *
473
     * @param object $entity
474
     * @return boolean TRUE if this EntityManager currently manages the given entity, FALSE otherwise.
475
     */
476
    public function contains($entity)
477
    {
478 479
        return $this->_unitOfWork->isScheduledForInsert($entity) ||
                $this->_unitOfWork->isInIdentityMap($entity) &&
romanb's avatar
romanb committed
480
                ! $this->_unitOfWork->isScheduledForDelete($entity);
481
    }
482

483 484 485
    /**
     * Gets the EventManager used by the EntityManager.
     *
486
     * @return Doctrine\Common\EventManager
487 488 489 490 491
     */
    public function getEventManager()
    {
        return $this->_eventManager;
    }
492

493
    /**
romanb's avatar
romanb committed
494
     * Gets the Configuration used by the EntityManager.
495
     *
496
     * @return Doctrine\ORM\Configuration
497
     */
romanb's avatar
romanb committed
498
    public function getConfiguration()
499
    {
romanb's avatar
romanb committed
500 501
        return $this->_config;
    }
502

503 504
    /**
     * Throws an exception if the EntityManager is closed or currently not active.
505
     *
romanb's avatar
romanb committed
506
     * @throws ORMException If the EntityManager is closed.
507
     */
508
    private function _errorIfClosed()
romanb's avatar
romanb committed
509
    {
510
        if ($this->_closed) {
romanb's avatar
romanb committed
511
            throw ORMException::entityManagerClosed();
romanb's avatar
romanb committed
512
        }
513
    }
514

515
    /**
romanb's avatar
romanb committed
516
     * Gets the UnitOfWork used by the EntityManager to coordinate operations.
517
     *
518
     * @return Doctrine\ORM\UnitOfWork
519
     */
romanb's avatar
romanb committed
520
    public function getUnitOfWork()
521
    {
romanb's avatar
romanb committed
522
        return $this->_unitOfWork;
523
    }
524

525 526 527
    /**
     * Gets a hydrator for the given hydration mode.
     *
528 529 530 531 532
     * This method caches the hydrator instances which is used for all queries that don't
     * selectively iterate over the result.
     *
     * @param int $hydrationMode
     * @return Doctrine\ORM\Internal\Hydration\AbstractHydrator
533 534 535 536
     */
    public function getHydrator($hydrationMode)
    {
        if ( ! isset($this->_hydrators[$hydrationMode])) {
537
            $this->_hydrators[$hydrationMode] = $this->newHydrator($hydrationMode);
538
        }
539 540 541
        return $this->_hydrators[$hydrationMode];
    }

542 543
    /**
     * Create a new instance for the given hydration mode.
544
     *
545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568
     * @param  int $hydrationMode
     * @return Doctrine\ORM\Internal\Hydration\AbstractHydrator
     */
    public function newHydrator($hydrationMode)
    {
        switch ($hydrationMode) {
            case Query::HYDRATE_OBJECT:
                $hydrator = new Internal\Hydration\ObjectHydrator($this);
                break;
            case Query::HYDRATE_ARRAY:
                $hydrator = new Internal\Hydration\ArrayHydrator($this);
                break;
            case Query::HYDRATE_SCALAR:
                $hydrator = new Internal\Hydration\ScalarHydrator($this);
                break;
            case Query::HYDRATE_SINGLE_SCALAR:
                $hydrator = new Internal\Hydration\SingleScalarHydrator($this);
                break;
            default:
                throw ORMException::invalidHydrationMode($hydrationMode);
        }
        return $hydrator;
    }

569
    /**
570
     * Gets the proxy factory used by the EntityManager to create entity proxies.
571
     *
572
     * @return ProxyFactory
573
     */
574
    public function getProxyFactory()
575
    {
576
        return $this->_proxyFactory;
577
    }
578

579
    /**
romanb's avatar
romanb committed
580
     * Factory method to create EntityManager instances.
581
     *
romanb's avatar
romanb committed
582
     * @param mixed $conn An array with the connection parameters or an existing
583 584 585 586
     *      Connection instance.
     * @param Configuration $config The Configuration instance to use.
     * @param EventManager $eventManager The EventManager instance to use.
     * @return EntityManager The created EntityManager.
romanb's avatar
romanb committed
587
     */
588
    public static function create($conn, Configuration $config = null, EventManager $eventManager = null)
romanb's avatar
romanb committed
589
    {
590 591
        $config = $config ?: new Configuration();

romanb's avatar
romanb committed
592
        if (is_array($conn)) {
593 594 595
            $conn = \Doctrine\DBAL\DriverManager::getConnection($conn, $config, ($eventManager ?: new EventManager()));
        } else if ($conn instanceof Connection) {
            if ($eventManager !== null && $conn->getEventManager() !== $eventManager) {
596
                 throw ORMException::mismatchedEventManager();
597 598
            }
        } else {
599
            throw new \InvalidArgumentException("Invalid argument: " . $conn);
romanb's avatar
romanb committed
600
        }
601 602

        return new EntityManager($conn, $config, $conn->getEventManager());
romanb's avatar
romanb committed
603
    }
604
}