EntityManager.php 18.1 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 28
use Doctrine\Common\EventManager,
    Doctrine\Common\DoctrineException,
    Doctrine\DBAL\Connection,
    Doctrine\ORM\Mapping\ClassMetadata,
    Doctrine\ORM\Mapping\ClassMetadataFactory,
29
    Doctrine\ORM\Proxy\ProxyFactory;
30

31
/**
32
 * The EntityManager is the central access point to ORM functionality.
33
 *
34 35 36 37 38
 * @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>
39
 */
40
class EntityManager
41
{
romanb's avatar
romanb committed
42 43 44 45
    /**
     * IMMEDIATE: Flush occurs automatically after each operation that issues database
     * queries. No operations are queued.
     */ 
46
    const FLUSHMODE_IMMEDIATE = 1;
romanb's avatar
romanb committed
47 48 49 50 51
    /**
     * AUTO: Flush occurs automatically in the following situations:
     * - Before any query executions (to prevent getting stale data)
     * - On EntityManager#commit()
     */
52
    const FLUSHMODE_AUTO = 2;
romanb's avatar
romanb committed
53 54 55
    /**
     * COMMIT: Flush occurs automatically only on EntityManager#commit().
     */
56
    const FLUSHMODE_COMMIT = 3;
romanb's avatar
romanb committed
57 58 59 60
    /**
     * MANUAL: Flush occurs never automatically. The only way to flush is
     * through EntityManager#flush().
     */
61
    const FLUSHMODE_MANUAL = 4;
62
    
63 64 65
    /**
     * The used Configuration.
     *
66
     * @var Doctrine\ORM\Configuration
67
     */
68
    private $_config;
69
    
70 71 72
    /**
     * The database connection used by the EntityManager.
     *
73
     * @var Doctrine\DBAL\Connection
74 75 76 77
     */
    private $_conn;
    
    /**
78
     * The metadata factory, used to retrieve the ORM metadata of entity classes.
79
     *
80
     * @var Doctrine\ORM\Mapping\ClassMetadataFactory
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
     */
    private $_metadataFactory;
    
    /**
     * The EntityRepository instances.
     *
     * @var array
     */
    private $_repositories = array();
    
    /**
     * The currently used flush mode. Defaults to 'commit'.
     *
     * @var string
     */
96
    private $_flushMode = self::FLUSHMODE_COMMIT;
97 98
    
    /**
99
     * The UnitOfWork used to coordinate object-level transactions.
100
     *
101
     * @var Doctrine\ORM\UnitOfWork
102 103 104
     */
    private $_unitOfWork;
    
105 106 107
    /**
     * The event manager that is the central point of the event system.
     *
108
     * @var Doctrine\Common\EventManager
109 110
     */
    private $_eventManager;
romanb's avatar
romanb committed
111

112 113 114 115 116 117 118
    /**
     * The maintained (cached) hydrators. One instance per type.
     *
     * @var array
     */
    private $_hydrators = array();

119
    /**
120
     * The proxy factory which creates association or reference proxies.
121
     *
122
     * @var ProxyFactory
123
     */
124
    private $_proxyFactory;
125

126 127 128
    /**
     * Whether the EntityManager is closed or not.
     */
romanb's avatar
romanb committed
129 130
    private $_closed = false;
    
131
    /**
romanb's avatar
romanb committed
132 133
     * Creates a new EntityManager that operates on the given database connection
     * and uses the given Configuration and EventManager implementations.
134
     *
romanb's avatar
romanb committed
135
     * @param Doctrine\DBAL\Connection $conn
136
     * @param string $name
137 138
     * @param Doctrine\ORM\Configuration $config
     * @param Doctrine\Common\EventManager $eventManager
139
     */
140
    protected function __construct(Connection $conn, Configuration $config, EventManager $eventManager)
141 142
    {
        $this->_conn = $conn;
romanb's avatar
romanb committed
143 144
        $this->_config = $config;
        $this->_eventManager = $eventManager;
145
        $this->_metadataFactory = new ClassMetadataFactory($this);
146
        $this->_metadataFactory->setCacheDriver($this->_config->getMetadataCacheImpl());
147
        $this->_unitOfWork = new UnitOfWork($this);
148 149 150 151
        $this->_proxyFactory = new ProxyFactory($this,
                $config->getProxyDir(),
                $config->getProxyNamespace(),
                $config->getAutoGenerateProxyClasses());
152 153 154 155 156
    }
    
    /**
     * Gets the database connection object used by the EntityManager.
     *
157
     * @return Doctrine\DBAL\Connection
158 159 160 161 162
     */
    public function getConnection()
    {
        return $this->_conn;
    }
163

romanb's avatar
romanb committed
164
    /**
165
     * Gets the metadata factory used to gather the metadata of classes.
166 167
     *
     * @return Doctrine\ORM\Mapping\ClassMetadataFactory
romanb's avatar
romanb committed
168
     */
169
    public function getMetadataFactory()
romanb's avatar
romanb committed
170
    {
171
        return $this->_metadataFactory;
romanb's avatar
romanb committed
172 173
    }
    
174
    /**
175
     * Starts a transaction on the underlying database connection.
176 177 178
     */
    public function beginTransaction()
    {
179
        $this->_conn->beginTransaction();
180 181 182
    }
    
    /**
183
     * Commits a transaction on the underlying database connection.
184
     * 
185 186
     * This causes a flush() of the EntityManager if the flush mode is set to
     * AUTO or COMMIT.
187
     */
188
    public function commit()
189
    {
190 191 192
        if ($this->_flushMode == self::FLUSHMODE_AUTO || $this->_flushMode == self::FLUSHMODE_COMMIT) {
            $this->flush();
        }
193
        $this->_conn->commit();
194 195 196 197 198 199 200 201
    }
    
    /**
     * 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
202 203
        $this->_conn->rollback();
        $this->close();
204
    }
205 206 207 208

    /**
     * Returns the metadata for a class.
     *
209
     * @return Doctrine\ORM\Mapping\ClassMetadata
210
     * @internal Performance-sensitive method.
211 212 213 214 215 216 217
     */
    public function getClassMetadata($className)
    {        
        return $this->_metadataFactory->getMetadataFor($className);
    }
    
    /**
218
     * Creates a new Query object.
219
     * 
220
     * @param string  The DQL string.
221
     * @return Doctrine\ORM\Query
222 223 224
     */
    public function createQuery($dql = "")
    {
225
        $query = new Query($this);
226
        if ( ! empty($dql)) {
227
            $query->setDql($dql);
228 229 230
        }
        return $query;
    }
231

232
    /**
233
     * Creates a Query from a named query.
234
     *
235
     * @param string $name
236
     * @return Doctrine\ORM\Query
237 238 239
     */
    public function createNamedQuery($name)
    {
240
        return $this->createQuery($this->_config->getNamedQuery($name));
241 242 243
    }
    
    /**
244 245 246
     * Creates a native SQL query.
     *
     * @param string $sql
247
     * @param ResultSetMapping $rsm The ResultSetMapping to use.
248
     * @return Query
249
     */
250
    public function createNativeQuery($sql, \Doctrine\ORM\Query\ResultSetMapping $rsm)
251
    {
252 253 254 255
        $query = new NativeQuery($this);
        $query->setSql($sql);
        $query->setResultSetMapping($rsm);
        return $query;
256 257 258
    }
    
    /**
259 260 261
     * Creates a NativeQuery from a named native query.
     * 
     * @param string $name
262
     * @return Doctrine\ORM\NativeQuery
263 264 265
     */
    public function createNamedNativeQuery($name)
    {
266 267
        list($sql, $rsm) = $this->_config->getNamedNativeQuery($name);
        return $this->createNativeQuery($sql, $rsm);
268 269 270
    }
    
    /**
271 272 273
     * Create a QueryBuilder instance
     *
     * @return QueryBuilder $qb
274
     */
275
    public function createQueryBuilder()
276
    {
277
        return new QueryBuilder($this);
278 279 280 281
    }
    
    /**
     * Flushes all changes to objects that have been queued up to now to the database.
282 283
     * This effectively synchronizes the in-memory state of managed objects with the
     * database.
284 285 286
     */
    public function flush()
    {
287
        $this->_errorIfClosed();
288
        $this->_unitOfWork->commit();
289 290
    }
    
291
    /**
292
     * Finds an Entity by its identifier.
293
     *
romanb's avatar
romanb committed
294
     * This is just a convenient shortcut for getRepository($entityName)->find($id).
295
     *
296 297
     * @param string $entityName
     * @param mixed $identifier
298
     * @return object
299 300 301 302 303
     */
    public function find($entityName, $identifier)
    {
        return $this->getRepository($entityName)->find($identifier);
    }
304 305 306

    /**
     * Gets a reference to the entity identified by the given type and identifier
307 308 309 310 311
     * without actually loading it.
     * 
     * 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.
312 313 314 315 316
     *
     * @return object The entity reference.
     */
    public function getReference($entityName, $identifier)
    {
317 318
        $class = $this->_metadataFactory->getMetadataFor($entityName);
        
romanb's avatar
romanb committed
319
        // Check identity map first, if its already in there just return it.
320
        if ($entity = $this->_unitOfWork->tryGetById($identifier, $class->rootEntityName)) {
romanb's avatar
romanb committed
321 322
            return $entity;
        }
323 324
        if ( ! is_array($identifier)) {
            $identifier = array($class->identifier[0] => $identifier);
romanb's avatar
romanb committed
325
        }
326
        $entity = $this->_proxyFactory->getProxy($entityName, $identifier);
327
        $this->_unitOfWork->registerManaged($entity, $identifier, array());
romanb's avatar
romanb committed
328

329 330
        return $entity;
    }
331
    
332
    /**
333
     * Sets the flush mode to use.
334 335 336 337 338
     *
     * @param string $flushMode
     */
    public function setFlushMode($flushMode)
    {
romanb's avatar
romanb committed
339
        if ( ! ($flushMode >= 1 && $flushMode <= 4)) {
340
            throw EntityManagerException::invalidFlushMode();
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
        }
        $this->_flushMode = $flushMode;
    }
    
    /**
     * Gets the currently used flush mode.
     *
     * @return string
     */
    public function getFlushMode()
    {
        return $this->_flushMode;
    }
    
    /**
356 357 358 359
     * Clears the EntityManager. All entities that are currently managed
     * by this EntityManager become detached.
     *
     * @param string $entityName
360 361 362 363
     */
    public function clear($entityName = null)
    {
        if ($entityName === null) {
364
            $this->_unitOfWork->clear();
365
        } else {
romanb's avatar
romanb committed
366
            //TODO
367
            throw DoctrineException::notImplemented(__FUNCTION__, __CLASS__);
368 369 370 371
        }
    }
    
    /**
372 373 374
     * 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.
375 376 377
     */
    public function close()
    {
378
        $this->clear();
romanb's avatar
romanb committed
379
        $this->_closed = true;
380 381 382
    }
    
    /**
romanb's avatar
romanb committed
383
     * Tells the EntityManager to make an instance managed and persistent.
romanb's avatar
romanb committed
384
     * 
romanb's avatar
romanb committed
385 386 387 388
     * The entity will be entered into the database at or before transaction
     * commit or as a result of the flush operation.
     * 
     * @param object $object The instance to make managed and persistent.
389
     */
romanb's avatar
romanb committed
390
    public function persist($object)
391
    {
392
        $this->_errorIfClosed();
romanb's avatar
romanb committed
393
        $this->_unitOfWork->persist($object);
romanb's avatar
romanb committed
394
        if ($this->_flushMode == self::FLUSHMODE_IMMEDIATE) {
395 396
            $this->flush();
        }
397 398 399
    }
    
    /**
romanb's avatar
romanb committed
400
     * Removes an entity instance.
romanb's avatar
romanb committed
401
     * 
romanb's avatar
romanb committed
402 403 404 405
     * A removed entity will be removed from the database at or before transaction commit
     * or as a result of the flush operation. 
     * 
     * @param object $entity The entity instance to remove.
406
     */
romanb's avatar
romanb committed
407
    public function remove($entity)
408
    {
409
        $this->_errorIfClosed();
romanb's avatar
romanb committed
410
        $this->_unitOfWork->remove($entity);
romanb's avatar
romanb committed
411 412 413
        if ($this->_flushMode == self::FLUSHMODE_IMMEDIATE) {
            $this->flush();
        }
414 415
    }
    
romanb's avatar
romanb committed
416
    /**
romanb's avatar
romanb committed
417
     * Refreshes the persistent state of an entity from the database,
romanb's avatar
romanb committed
418
     * overriding any local changes that have not yet been persisted.
romanb's avatar
romanb committed
419
     *
romanb's avatar
romanb committed
420
     * @param object $entity The entity to refresh.
romanb's avatar
romanb committed
421
     */
422
    public function refresh($entity)
romanb's avatar
romanb committed
423
    {
romanb's avatar
romanb committed
424
        $this->_errorIfClosed();
romanb's avatar
romanb committed
425
        $this->_unitOfWork->refresh($entity);
romanb's avatar
romanb committed
426
    }
427 428

    /**
429 430 431 432 433
     * 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.
     * Entities which previously referenced the detached entity will continue to 
     * reference it.
434 435 436 437 438
     *
     * @param object $entity The entity to detach.
     */
    public function detach($entity)
    {
439
        $this->_unitOfWork->detach($entity);
440 441 442 443
    }

    /**
     * Merges the state of a detached entity into the persistence context
444 445
     * of this EntityManager and returns the managed copy of the entity.
     * The entity passed to merge will not become associated/managed with this EntityManager.
446
     *
447
     * @param object $entity The detached entity to merge into the persistence context.
448 449 450 451
     * @return object The managed copy of the entity.
     */
    public function merge($entity)
    {
452
        $this->_errorIfClosed();
453 454
        return $this->_unitOfWork->merge($entity);
    }
romanb's avatar
romanb committed
455 456 457 458
    
    /**
     * Creates a copy of the given entity. Can create a shallow or a deep copy.
     *
459 460
     * @param object $entity  The entity to copy.
     * @return object  The new entity.
461
     * @todo Implementation or remove.
romanb's avatar
romanb committed
462
     */
463
    public function copy($entity, $deep = false)
romanb's avatar
romanb committed
464
    {
465
        $this->_errorIfClosed();
466
        throw DoctrineException::notImplemented(__FUNCTION__, __CLASS__);
romanb's avatar
romanb committed
467
    }
468

469
    /**
470
     * Gets the repository for an entity class.
471
     *
472
     * @param string $entityName  The name of the Entity.
473
     * @return EntityRepository  The repository.
474 475 476 477 478 479 480 481 482 483
     */
    public function getRepository($entityName)
    {
        if (isset($this->_repositories[$entityName])) {
            return $this->_repositories[$entityName];
        }

        $metadata = $this->getClassMetadata($entityName);
        $customRepositoryClassName = $metadata->getCustomRepositoryClass();
        if ($customRepositoryClassName !== null) {
484
            $repository = new $customRepositoryClassName($this, $metadata);
485
        } else {
486
            $repository = new EntityRepository($this, $metadata);
487 488 489 490 491 492
        }
        $this->_repositories[$entityName] = $repository;

        return $repository;
    }
    
493
    /**
romanb's avatar
romanb committed
494
     * Determines whether an entity instance is managed in this EntityManager.
495
     * 
496
     * @param object $entity
497
     * @return boolean TRUE if this EntityManager currently manages the given entity, FALSE otherwise.
498
     */
499
    public function contains($entity)
500
    {
501 502
        return $this->_unitOfWork->isScheduledForInsert($entity) ||
                $this->_unitOfWork->isInIdentityMap($entity) &&
romanb's avatar
romanb committed
503
                ! $this->_unitOfWork->isScheduledForDelete($entity);
504 505
    }
    
506 507 508
    /**
     * Gets the EventManager used by the EntityManager.
     *
509
     * @return Doctrine\Common\EventManager
510 511 512 513 514
     */
    public function getEventManager()
    {
        return $this->_eventManager;
    }
515 516
    
    /**
romanb's avatar
romanb committed
517
     * Gets the Configuration used by the EntityManager.
518
     *
519
     * @return Doctrine\ORM\Configuration
520
     */
romanb's avatar
romanb committed
521
    public function getConfiguration()
522
    {
romanb's avatar
romanb committed
523 524 525
        return $this->_config;
    }
    
526 527
    /**
     * Throws an exception if the EntityManager is closed or currently not active.
528
     *
529
     * @throws EntityManagerException If the EntityManager is closed.
530
     */
531
    private function _errorIfClosed()
romanb's avatar
romanb committed
532
    {
533
        if ($this->_closed) {
534
            throw EntityManagerException::closed();
romanb's avatar
romanb committed
535
        }
536 537 538
    }
    
    /**
romanb's avatar
romanb committed
539
     * Gets the UnitOfWork used by the EntityManager to coordinate operations.
540
     *
541
     * @return Doctrine\ORM\UnitOfWork
542
     */
romanb's avatar
romanb committed
543
    public function getUnitOfWork()
544
    {
romanb's avatar
romanb committed
545
        return $this->_unitOfWork;
546 547
    }
    
548 549 550 551 552 553 554 555 556
    /**
     * Gets a hydrator for the given hydration mode.
     *
     * @param  $hydrationMode
     */
    public function getHydrator($hydrationMode)
    {
        if ( ! isset($this->_hydrators[$hydrationMode])) {
            switch ($hydrationMode) {
557
                case Query::HYDRATE_OBJECT:
558
                    $this->_hydrators[$hydrationMode] = new Internal\Hydration\ObjectHydrator($this);
559
                    break;
560
                case Query::HYDRATE_ARRAY:
561
                    $this->_hydrators[$hydrationMode] = new Internal\Hydration\ArrayHydrator($this);
562
                    break;
563
                case Query::HYDRATE_SCALAR:
564
                    $this->_hydrators[$hydrationMode] = new Internal\Hydration\ScalarHydrator($this);
565
                    break;
566
                case Query::HYDRATE_SINGLE_SCALAR:
567
                    $this->_hydrators[$hydrationMode] = new Internal\Hydration\SingleScalarHydrator($this);
568
                    break;
569
                default:
570
                    throw DoctrineException::invalidHydrationMode($hydrationMode);
571
            }
572
        }
573 574 575 576
        return $this->_hydrators[$hydrationMode];
    }

    /**
577
     * Gets the proxy factory used by the EntityManager to create entity proxies.
578
     * 
579
     * @return ProxyFactory
580
     */
581
    public function getProxyFactory()
582
    {
583
        return $this->_proxyFactory;
584
    }
585
    
586
    /**
romanb's avatar
romanb committed
587
     * Factory method to create EntityManager instances.
588
     *
romanb's avatar
romanb committed
589
     * @param mixed $conn An array with the connection parameters or an existing
590 591 592 593 594
     *      Connection instance.
     * @param string $name The name of the EntityManager.
     * @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
595
     */
596
    public static function create($conn, Configuration $config = null, EventManager $eventManager = null)
romanb's avatar
romanb committed
597
    {
598 599
        $config = $config ?: new Configuration();

romanb's avatar
romanb committed
600
        if (is_array($conn)) {
601 602 603
            $conn = \Doctrine\DBAL\DriverManager::getConnection($conn, $config, ($eventManager ?: new EventManager()));
        } else if ($conn instanceof Connection) {
            if ($eventManager !== null && $conn->getEventManager() !== $eventManager) {
604
                 throw DoctrineException::invalidEventManager('Cannot use different EventManagers for EntityManager and Connection.');
605 606
            }
        } else {
607
            throw DoctrineException::invalidParameter($conn);
romanb's avatar
romanb committed
608
        }
609 610

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