diff --git a/lib/Doctrine/Common/Annotations/AnnotationReader.php b/lib/Doctrine/Common/Annotations/AnnotationReader.php index f2b53b5b5..e44b11b00 100644 --- a/lib/Doctrine/Common/Annotations/AnnotationReader.php +++ b/lib/Doctrine/Common/Annotations/AnnotationReader.php @@ -72,7 +72,7 @@ class AnnotationReader $this->_parser = new Parser; $this->_cache = $cache ?: new Doctrine\Common\Cache\ArrayCache; } - + /** * Sets the default namespace that the AnnotationReader should assume for annotations * with not fully qualified names. @@ -83,7 +83,7 @@ class AnnotationReader { $this->_parser->setDefaultAnnotationNamespace($defaultNamespace); } - + /** * Sets an alias for an annotation namespace. * @@ -94,7 +94,7 @@ class AnnotationReader { $this->_parser->setAnnotationNamespaceAlias($namespace, $alias); } - + /** * Gets the annotations applied to a class. * diff --git a/lib/Doctrine/Common/Annotations/Parser.php b/lib/Doctrine/Common/Annotations/Parser.php index 4b91a8ab9..7b28dc34e 100644 --- a/lib/Doctrine/Common/Annotations/Parser.php +++ b/lib/Doctrine/Common/Annotations/Parser.php @@ -249,7 +249,7 @@ class Parser $name = implode('\\', $nameParts); } - // If it really an annotation class? + // Is it really an annotation class? if ( (! $this->_isNestedAnnotation && $this->_lexer->lookahead != null && ! $this->_lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS) && diff --git a/lib/Doctrine/ORM/Event/LifecycleEventArgs.php b/lib/Doctrine/ORM/Event/LifecycleEventArgs.php index 88334e351..3e7cdee21 100644 --- a/lib/Doctrine/ORM/Event/LifecycleEventArgs.php +++ b/lib/Doctrine/ORM/Event/LifecycleEventArgs.php @@ -22,7 +22,8 @@ namespace Doctrine\ORM\Event; /** - * Lifecycle Events are triggered by the UnitOfWork + * Lifecycle Events are triggered by the UnitOfWork during lifecycle transitions + * of entities. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.doctrine-project.com diff --git a/lib/Doctrine/ORM/Event/OnFlushEventArgs.php b/lib/Doctrine/ORM/Event/OnFlushEventArgs.php new file mode 100644 index 000000000..1b4cb9ba8 --- /dev/null +++ b/lib/Doctrine/ORM/Event/OnFlushEventArgs.php @@ -0,0 +1,78 @@ +. +*/ + +namespace Doctrine\ORM\Event; + +/** + * Provides event arguments for the preFlush event. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.com + * @since 2.0 + * @version $Revision$ + * @author Roman Borschel + * @author Benjamin Eberlei + */ +class OnFlushEventArgs extends \Doctrine\Common\EventArgs +{ + /** + * @var EntityManager + */ + private $_em; + + //private $_entitiesToPersist = array(); + //private $_entitiesToRemove = array(); + + public function __construct($em) + { + $this->_em = $em; + } + + /** + * @return EntityManager + */ + public function getEntityManager() + { + return $this->_em; + } + + /* + public function addEntityToPersist($entity) + { + + } + + public function addEntityToRemove($entity) + { + + } + + public function addEntityToUpdate($entity) + { + + } + + public function getEntitiesToPersist() + { + return $this->_entitiesToPersist; + } + */ +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Events.php b/lib/Doctrine/ORM/Events.php index a7142c196..e25de658a 100644 --- a/lib/Doctrine/ORM/Events.php +++ b/lib/Doctrine/ORM/Events.php @@ -108,4 +108,15 @@ final class Events * @var string */ const loadClassMetadata = 'loadClassMetadata'; + + /** + * The onFlush event occurs when the EntityManager#flush() operation is invoked, + * after any changes to managed entities have been determined but before any + * actual database operations are executed. The event is only raised if there is + * actually something to do for the underlying UnitOfWork. If nothing needs to be done, + * the onFlush event is not raised. + * + * @var string + */ + const onFlush = 'onFlush'; } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php index 50c7b5760..6065eabe9 100644 --- a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php @@ -339,10 +339,10 @@ class StandardEntityPersister if (isset($this->_class->associationMappings[$field])) { $assocMapping = $this->_class->associationMappings[$field]; // Only owning side of x-1 associations can have a FK column. - if ( ! $assocMapping->isOneToOne() || ! $assocMapping->isOwningSide) { + if ( ! $assocMapping->isOwningSide || ! $assocMapping->isOneToOne()) { continue; } - + if ($newVal !== null) { $oid = spl_object_hash($newVal); if (isset($this->_queuedInserts[$oid]) || $uow->isScheduledForInsert($newVal)) { diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index bd52c46e9..911ebaa69 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -271,6 +271,11 @@ class UnitOfWork implements PropertyChangedListener } } + // Raise onFlush + if ($this->_evm->hasListeners(Events::onFlush)) { + $this->_evm->dispatchEvent(Events::onFlush, new Event\OnFlushEventArgs($this->_em)); + } + // Now we need a commit order to maintain referential integrity $commitOrder = $this->_getCommitOrder(); @@ -945,7 +950,8 @@ class UnitOfWork implements PropertyChangedListener } /** - * Registers a deleted entity. + * INTERNAL: + * Schedules an entity for deletion. * * @param object $entity */ @@ -2064,4 +2070,34 @@ class UnitOfWork implements PropertyChangedListener $this->_entityUpdates[$oid] = $entity; } } + + /** + * Gets the currently scheduled entity insertions in this UnitOfWork. + * + * @return array + */ + public function getScheduledEntityInsertions() + { + return $this->_entityInsertions; + } + + /** + * Gets the currently scheduled entity updates in this UnitOfWork. + * + * @return array + */ + public function getScheduledEntityUpdates() + { + return $this->_entityUpdates; + } + + /** + * Gets the currently scheduled entity deletions in this UnitOfWork. + * + * @return array + */ + public function getScheduledEntityDeletions() + { + return $this->_entityDeletions; + } } diff --git a/tests/Doctrine/Tests/Common/Annotations/ParserTest.php b/tests/Doctrine/Tests/Common/Annotations/ParserTest.php index c9709ba07..bb7d5a7b9 100644 --- a/tests/Doctrine/Tests/Common/Annotations/ParserTest.php +++ b/tests/Doctrine/Tests/Common/Annotations/ParserTest.php @@ -106,7 +106,7 @@ DOCBLOCK; */ public function testAnnotationNamespaceAlias() { - $parser = new Parser; + $parser = $this->createTestParser(); $parser->setAnnotationNamespaceAlias('Doctrine\Tests\Common\Annotations\\', 'alias'); $docblock = <<useModelSet('cms'); + parent::setUp(); + } + + public function testPersistNewEntitiesOnPreFlush() + { + //$this->_em->getConnection()->getConfiguration()->setSqlLogger(new \Doctrine\DBAL\Logging\EchoSqlLogger); + $this->_em->getEventManager()->addEventListener(Events::onFlush, new OnFlushListener); + + $user = new CmsUser; + $user->username = 'romanb'; + $user->name = 'Roman'; + $user->status = 'Dev'; + + $this->_em->persist($user); + + $this->assertEquals(0, $user->phonenumbers->count()); + + $this->_em->flush(); + + $this->assertEquals(1, $user->phonenumbers->count()); + $this->assertTrue($this->_em->contains($user->phonenumbers->get(0))); + $this->assertTrue($user->phonenumbers->get(0)->getUser() === $user); + + $this->assertFalse($user->phonenumbers->isDirty()); + + // Can be used together with SQL Logging to check that a subsequent flush has + // nothing to do. This proofs the correctness of the changes that happened in onFlush. + //echo "SECOND FLUSH"; + //$this->_em->flush(); + } +} + +class OnFlushListener +{ + public function onFlush(OnFlushEventArgs $args) + { + //echo "---preFlush".PHP_EOL; + + $em = $args->getEntityManager(); + $uow = $em->getUnitOfWork(); + + foreach ($uow->getScheduledEntityInsertions() as $entity) { + + if ($entity instanceof CmsUser) { + // Adds a phonenumber to every newly persisted CmsUser ... + + $phone = new CmsPhonenumber; + $phone->phonenumber = 12345; + // Update object model + $entity->addPhonenumber($phone); + // Invoke regular persist call + $em->persist($phone); + // Explicitly calculate the changeset since onFlush is raised + // after changeset calculation! + $uow->computeChangeSet($em->getClassMetadata(get_class($phone)), $phone); + + // Take a snapshot because the UoW wont do this for us, because + // the UoW did not visit this collection. + // Alternatively we could provide an ->addVisitedCollection() method + // on the UoW. + $entity->getPhonenumbers()->takeSnapshot(); + } + + /*foreach ($uow->getEntityChangeSet($entity) as $field => $change) { + list ($old, $new) = $change; + + var_dump($old); + }*/ + + } + } +} + +