diff --git a/lib/Doctrine/DBAL/Connection.php b/lib/Doctrine/DBAL/Connection.php index 2e6630e47..574617291 100644 --- a/lib/Doctrine/DBAL/Connection.php +++ b/lib/Doctrine/DBAL/Connection.php @@ -777,4 +777,4 @@ class Connection } return $this->_schemaManager; } -} \ No newline at end of file +} diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index 9685b053f..b17fc687c 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -572,7 +572,7 @@ class EntityManager * * @return ProxyFactory */ - public function getProxyGenerator() + public function getProxyFactory() { return $this->_proxyFactory; } diff --git a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php index 956de0425..4afd36beb 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php @@ -38,6 +38,8 @@ use Doctrine\Common\DoctrineException; */ abstract class AbstractHydrator { + const TYPES_JOINCOLUMN = 'join_column'; + /** The ResultSetMapping. */ protected $_rsm; @@ -184,12 +186,16 @@ abstract class AbstractHydrator } else if (isset($this->_rsm->fieldMappings[$key])) { $classMetadata = $this->_em->getClassMetadata($this->_rsm->getOwningClass($key)); $fieldName = $this->_rsm->fieldMappings[$key]; - if ( ! isset($classMetadata->reflFields[$fieldName])) { + if ( ! isset($classMetadata->reflFields[$fieldName]) && ! in_array($fieldName, $classMetadata->joinColumnNames)) { $classMetadata = $this->_lookupDeclaringClass($classMetadata, $fieldName); } $cache[$key]['fieldName'] = $fieldName; $cache[$key]['isScalar'] = false; - $cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']); + if (isset($classMetadata->fieldMappings[$fieldName])) { + $cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']); + } else { + $cache[$key]['type'] = self::TYPES_JOINCOLUMN; + } $cache[$key]['isIdentifier'] = $classMetadata->isIdentifier($fieldName); $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; } else { @@ -217,7 +223,11 @@ abstract class AbstractHydrator $id[$dqlAlias] .= '|' . $value; } - $rowData[$dqlAlias][$cache[$key]['fieldName']] = $cache[$key]['type']->convertToPHPValue($value, $this->_platform); + if ($cache[$key]['type'] == self::TYPES_JOINCOLUMN) { + $rowData[$dqlAlias][$cache[$key]['fieldName']] = $value; + } else { + $rowData[$dqlAlias][$cache[$key]['fieldName']] = $cache[$key]['type']->convertToPHPValue($value, $this->_platform); + } if ( ! isset($nonemptyComponents[$dqlAlias]) && $value !== null) { $nonemptyComponents[$dqlAlias] = true; @@ -302,4 +312,4 @@ abstract class AbstractHydrator throw DoctrineException::updateMe("No owner found for field '$fieldName' during hydration."); } -} \ No newline at end of file +} diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 728c6a852..3c1bf75d7 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -236,6 +236,13 @@ class ObjectHydrator extends AbstractHydrator } $entity = $this->_uow->createEntity($className, $data); + $joinColumnsValues = array(); + foreach ($this->_ce[$className]->joinColumnNames as $name) { + if (isset($data[$name])) { + $joinColumnsValues[$name] = $data[$name]; + } + } + // Properly initialize any unfetched associations, if partial objects are not allowed. if ( ! $this->_allowPartialObjects) { foreach ($this->_ce[$className]->associationMappings as $field => $assoc) { @@ -243,7 +250,7 @@ class ObjectHydrator extends AbstractHydrator if ($assoc->isOneToOne()) { if ($assoc->isLazilyFetched()) { // Inject proxy - $proxy = $this->_em->getProxyGenerator()->getAssociationProxy($entity, $assoc); + $proxy = $this->_em->getProxyFactory()->getAssociationProxy($entity, $assoc, $joinColumnsValues); $this->_ce[$className]->reflFields[$field]->setValue($entity, $proxy); } else { //TODO: Schedule for eager fetching @@ -462,4 +469,4 @@ class ObjectHydrator extends AbstractHydrator { return new \Doctrine\Common\Collections\Collection; } -} \ No newline at end of file +} diff --git a/lib/Doctrine/ORM/Mapping/AssociationMapping.php b/lib/Doctrine/ORM/Mapping/AssociationMapping.php index e6ef4b32b..963ca9ba1 100644 --- a/lib/Doctrine/ORM/Mapping/AssociationMapping.php +++ b/lib/Doctrine/ORM/Mapping/AssociationMapping.php @@ -379,12 +379,13 @@ abstract class AssociationMapping /** * Loads data in $targetEntity domain object using this association. - * The data comes from the association navigated from $owningEntity + * The data comes from the association navigated from $sourceEntity * using $em. * - * @param object $owningEntity + * @param object $sourceEntity * @param object $targetEntity * @param EntityManager $em + * @param array $joinColumnValues */ - abstract public function load($owningEntity, $targetEntity, $em); + abstract public function load($sourceEntity, $targetEntity, $em, array $joinColumnValues); } diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadata.php b/lib/Doctrine/ORM/Mapping/ClassMetadata.php index 7902397ce..812414a3d 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadata.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadata.php @@ -254,6 +254,12 @@ final class ClassMetadata */ public $columnNames = array(); + /** + * Array of all column names that does not map to a field. + * Foreign keys are here. + */ + public $joinColumnNames = array(); + /** * Whether to automatically OUTER JOIN subtypes when a basetype is queried. * @@ -493,6 +499,11 @@ final class ClassMetadata $this->reflFields[$propName] = $property; } + public function addJoinColumn($name) + { + $this->joinColumnNames[] = $name; + } + /** * Gets a ReflectionProperty for a specific field of the mapped class. * @@ -1315,7 +1326,7 @@ final class ClassMetadata /** * Makes some automatic additions to the association mapping to make the life - * easier for the user. + * easier for the user, and store join columns in the metadata. * * @param array $mapping * @todo Pass param by ref? @@ -1326,6 +1337,11 @@ final class ClassMetadata if (isset($mapping['targetEntity']) && strpos($mapping['targetEntity'], '\\') === false) { $mapping['targetEntity'] = $this->namespace . '\\' . $mapping['targetEntity']; } + if (isset($mapping['joinColumns'])) { + foreach ($mapping['joinColumns'] as $columns) { + $this->addJoinColumn($columns['name']); + } + } return $mapping; } @@ -1779,4 +1795,4 @@ final class ClassMetadata { return __CLASS__ . '@' . spl_object_hash($this); } -} \ No newline at end of file +} diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index 82b29c623..fdb70a3b3 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -372,4 +372,4 @@ class ClassMetadataFactory throw new DoctrineException("Unexhaustive match."); } } -} \ No newline at end of file +} diff --git a/lib/Doctrine/ORM/Mapping/ManyToManyMapping.php b/lib/Doctrine/ORM/Mapping/ManyToManyMapping.php index 03df394d3..5a91e3ef1 100644 --- a/lib/Doctrine/ORM/Mapping/ManyToManyMapping.php +++ b/lib/Doctrine/ORM/Mapping/ManyToManyMapping.php @@ -138,7 +138,7 @@ class ManyToManyMapping extends AssociationMapping return $this->targetKeyColumns; } - public function load($owningEntity, $targetEntity, $em) + public function load($owningEntity, $targetEntity, $em, array $joinColumnValues) { throw new Exception('Not yet implemented.'); } diff --git a/lib/Doctrine/ORM/Mapping/OneToManyMapping.php b/lib/Doctrine/ORM/Mapping/OneToManyMapping.php index 4a4442f68..bec1dc2cd 100644 --- a/lib/Doctrine/ORM/Mapping/OneToManyMapping.php +++ b/lib/Doctrine/ORM/Mapping/OneToManyMapping.php @@ -97,7 +97,7 @@ class OneToManyMapping extends AssociationMapping return true; } - public function load($owningEntity, $targetEntity, $em) + public function load($owningEntity, $targetEntity, $em, array $joinColumnValues) { throw new Exception('Not yet implemented.'); } diff --git a/lib/Doctrine/ORM/Mapping/OneToOneMapping.php b/lib/Doctrine/ORM/Mapping/OneToOneMapping.php index 35773888a..dbfe9e646 100644 --- a/lib/Doctrine/ORM/Mapping/OneToOneMapping.php +++ b/lib/Doctrine/ORM/Mapping/OneToOneMapping.php @@ -35,6 +35,7 @@ namespace Doctrine\ORM\Mapping; * * @since 2.0 * @author Roman Borschel + * @author Giorgio Sironi */ class OneToOneMapping extends AssociationMapping { @@ -152,11 +153,13 @@ class OneToOneMapping extends AssociationMapping /** * {@inheritdoc} * - * @param object $owningEntity - * @param object $targetEntity + * @param object $sourceEntity the entity source of this association + * @param object $targetEntity the entity to load data in * @param EntityManager $em + * @param array $joinColumnValues values of the join columns of $sourceEntity. There are no fields + * to store this data in $sourceEntity */ - public function load($owningEntity, $targetEntity, $em) + public function load($sourceEntity, $targetEntity, $em, array $joinColumnValues) { $sourceClass = $em->getClassMetadata($this->sourceEntityName); $targetClass = $em->getClassMetadata($this->targetEntityName); @@ -165,24 +168,38 @@ class OneToOneMapping extends AssociationMapping if ($this->isOwningSide) { foreach ($this->sourceToTargetKeyColumns as $sourceKeyColumn => $targetKeyColumn) { - $conditions[$targetKeyColumn] = $sourceClass->getReflectionProperty( - $sourceClass->getFieldName($sourceKeyColumn))->getValue($owningEntity); + // getting customer_id + if (isset($sourceClass->reflFields[$sourceKeyColumn])) { + $conditions[$targetKeyColumn] = $this->_getPrivateValue($sourceClass, $sourceEntity, $sourceKeyColumn); + } else { + $conditions[$targetKeyColumn] = $joinColumnValues[$sourceKeyColumn]; + } } - if ($targetClass->hasInverseAssociation($this->sourceFieldName)) { + if ($targetClass->hasInverseAssociationMapping($this->sourceFieldName)) { $targetClass->setFieldValue( $targetEntity, - $targetClass->inverseMappings[$this->_sourceFieldName]->getSourceFieldName(), - $owningEntity); + $targetClass->inverseMappings[$this->sourceFieldName]->getSourceFieldName(), + $sourceEntity); } } else { $owningAssoc = $em->getClassMetadata($this->targetEntityName)->getAssociationMapping($this->mappedByFieldName); foreach ($owningAssoc->getTargetToSourceKeyColumns() as $targetKeyColumn => $sourceKeyColumn) { - $conditions[$sourceKeyColumn] = $sourceClass->getReflectionProperty( - $sourceClass->getFieldName($targetKeyColumn))->getValue($owningEntity); + // getting id + if (isset($sourceClass->reflFields[$targetKeyColumn])) { + $conditions[$sourceKeyColumn] = $this->_getPrivateValue($sourceClass, $sourceEntity, $targetKeyColumn); + } else { + $conditions[$sourceKeyColumn] = $joinColumnValues[$targetKeyColumn]; + } } - $targetClass->setFieldValue($targetEntity, $this->mappedByFieldName, $owningEntity); + $targetClass->setFieldValue($targetEntity, $this->mappedByFieldName, $sourceEntity); } $em->getUnitOfWork()->getEntityPersister($this->targetEntityName)->load($conditions, $targetEntity); } + + protected function _getPrivateValue(ClassMetadata $class, $entity, $column) + { + $reflField = $class->getReflectionProperty($class->getFieldName($column)); + return $reflField->getValue($entity); + } } diff --git a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php index 4dc0c3c7d..5dcc96f40 100644 --- a/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/StandardEntityPersister.php @@ -411,10 +411,15 @@ class StandardEntityPersister $stmt = $this->_conn->prepare($this->_getSelectSingleEntitySql($criteria)); $stmt->execute(array_values($criteria)); $data = array(); + $joinColumnValues = array(); foreach ($stmt->fetch(Connection::FETCH_ASSOC) as $column => $value) { - $fieldName = $this->_class->fieldNames[$column]; - $data[$fieldName] = Type::getType($this->_class->getTypeOfField($fieldName)) + if (isset($this->_class->fieldNames[$column])) { + $fieldName = $this->_class->fieldNames[$column]; + $data[$fieldName] = Type::getType($this->_class->getTypeOfField($fieldName)) ->convertToPHPValue($value, $this->_platform); + } else { + $joinColumnValues[$column] = $value; + } } $stmt->closeCursor(); @@ -440,9 +445,9 @@ class StandardEntityPersister // empty collections respectively. foreach ($this->_class->associationMappings as $field => $assoc) { if ($assoc->isOneToOne()) { - if ($assoc->isLazilyFetched) { + if ($assoc->isLazilyFetched()) { // Inject proxy - $proxy = $this->_em->getProxyGenerator()->getAssociationProxy($entity, $assoc); + $proxy = $this->_em->getProxyFactory()->getAssociationProxy($entity, $assoc, $joinColumnValues); $this->_class->reflFields[$field]->setValue($entity, $proxy); } else { //TODO: Eager fetch @@ -472,11 +477,23 @@ class StandardEntityPersister if ($columnList != '') $columnList .= ', '; $columnList .= $this->_conn->quoteIdentifier($column); } + if (!$this->_em->getConfiguration()->getAllowPartialObjects()) { + foreach ($this->_class->joinColumnNames as $column) { + $columnList .= ', ' . $this->_conn->quoteIdentifier($column); + } + } $conditionSql = ''; foreach ($criteria as $field => $value) { if ($conditionSql != '') $conditionSql .= ' AND '; - $conditionSql .= $this->_conn->quoteIdentifier($this->_class->columnNames[$field]) . ' = ?'; + if (isset($this->_class->columnNames[$field])) { + $columnName = $this->_class->columnNames[$field]; + } else if (in_array($field, $this->_class->joinColumnNames)) { + $columnName = $field; + } else { + throw new Exception("Unrecognized field: $field"); + } + $conditionSql .= $this->_conn->quoteIdentifier($columnName) . ' = ?'; } return 'SELECT ' . $columnList . ' FROM ' . $this->_class->getTableName() diff --git a/lib/Doctrine/ORM/Proxy/ProxyClassGenerator.php b/lib/Doctrine/ORM/Proxy/ProxyClassGenerator.php index c93b5f710..ed35d2569 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyClassGenerator.php +++ b/lib/Doctrine/ORM/Proxy/ProxyClassGenerator.php @@ -88,12 +88,13 @@ class ProxyClassGenerator $class = $this->_em->getClassMetadata($originalClassName); $proxyFullyQualifiedClassName = self::$_ns . $proxyClassName; + $class = $this->_em->getClassMetadata($originalClassName); + $this->_em->getMetadataFactory()->setMetadataFor($proxyFullyQualifiedClassName, $class); + if (class_exists($proxyFullyQualifiedClassName, false)) { return $proxyFullyQualifiedClassName; } - $class = $this->_em->getClassMetadata($originalClassName); - $this->_em->getMetadataFactory()->setMetadataFor($proxyFullyQualifiedClassName, $class); $fileName = $this->_cacheDir . $proxyClassName . '.g.php'; if (file_exists($fileName)) { @@ -228,18 +229,21 @@ namespace Doctrine\Generated\Proxies { private $_em; private $_assoc; private $_owner; + private $_joinColumnValues; private $_loaded = false; - public function __construct($em, $assoc, $owner) { + public function __construct($em, $assoc, $owner, array $joinColumnValues) { $this->_em = $em; $this->_assoc = $assoc; $this->_owner = $owner; + $this->_joinColumnValues = $joinColumnValues; } private function _load() { if ( ! $this->_loaded) { - $this->_assoc->load($this->_owner, $this, $this->_em); + $this->_assoc->load($this->_owner, $this, $this->_em, $this->_joinColumnValues); unset($this->_em); unset($this->_owner); unset($this->_assoc); + unset($this->_joinColumnValues); $this->_loaded = true; } } diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index eee4e4fe4..f01fac1f4 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -66,9 +66,9 @@ class ProxyFactory /** * Gets an association proxy instance. */ - public function getAssociationProxy($owner, \Doctrine\ORM\Mapping\AssociationMapping $assoc) + public function getAssociationProxy($owner, \Doctrine\ORM\Mapping\AssociationMapping $assoc, array $joinColumnValues) { $proxyClassName = $this->_generator->generateAssociationProxyClass($assoc->getTargetEntityName()); - return new $proxyClassName($this->_em, $assoc, $owner); + return new $proxyClassName($this->_em, $assoc, $owner, $joinColumnValues); } } diff --git a/lib/Doctrine/ORM/Query/Parser.php b/lib/Doctrine/ORM/Query/Parser.php index 8bf38a1cf..9d84be4d9 100644 --- a/lib/Doctrine/ORM/Query/Parser.php +++ b/lib/Doctrine/ORM/Query/Parser.php @@ -2229,4 +2229,4 @@ class Parser { self::$_DATETIME_FUNCTIONS[$name] = $class; } -} \ No newline at end of file +} diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 7ca38d8ec..049ad9a74 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -470,6 +470,13 @@ class SqlWalker implements TreeWalker $sql .= $sqlTableAlias . '.' . $this->_conn->quoteIdentifier($mapping['columnName']) . ' AS ' . $columnAlias; $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName); } + if (!$this->_em->getConfiguration()->getAllowPartialObjects()) { + foreach ($class->joinColumnNames as $name) { + $columnAlias = $this->getSqlColumnAlias($name); + $sql .= ', ' . $sqlTableAlias . '.' . $this->_conn->quoteIdentifier($name) . ' AS ' . $columnAlias; + $this->_rsm->addFieldResult($dqlAlias, $columnAlias, $name); + } + } } } @@ -1304,4 +1311,4 @@ class SqlWalker implements TreeWalker return $sql; } -} \ No newline at end of file +} diff --git a/lib/Doctrine/ORM/Tools/SchemaTool.php b/lib/Doctrine/ORM/Tools/SchemaTool.php index df29ad859..0f0898042 100644 --- a/lib/Doctrine/ORM/Tools/SchemaTool.php +++ b/lib/Doctrine/ORM/Tools/SchemaTool.php @@ -302,4 +302,4 @@ class SchemaTool { //TODO } -} \ No newline at end of file +} diff --git a/tests/Doctrine/Tests/Mocks/EntityManagerMock.php b/tests/Doctrine/Tests/Mocks/EntityManagerMock.php index d0259bb3a..45b02860e 100644 --- a/tests/Doctrine/Tests/Mocks/EntityManagerMock.php +++ b/tests/Doctrine/Tests/Mocks/EntityManagerMock.php @@ -20,6 +20,7 @@ */ namespace Doctrine\Tests\Mocks; +use Doctrine\ORM\Proxy\ProxyFactory; /** * Special EntityManager mock used for testing purposes. @@ -27,6 +28,7 @@ namespace Doctrine\Tests\Mocks; class EntityManagerMock extends \Doctrine\ORM\EntityManager { private $_uowMock; + private $_proxyFactoryMock; private $_idGenerators = array(); /** @@ -48,6 +50,16 @@ class EntityManagerMock extends \Doctrine\ORM\EntityManager { $this->_uowMock = $uow; } + + public function setProxyFactory($proxyFactory) + { + $this->_proxyFactoryMock = $proxyFactory; + } + + public function getProxyFactory() + { + return isset($this->_proxyFactoryMock) ? $this->_proxyFactoryMock : parent::getProxyFactory(); + } /** * Mock factory method to create an EntityManager. @@ -87,4 +99,4 @@ class EntityManagerMock extends \Doctrine\ORM\EntityManager return parent::getIdGenerator($className); } */ -} \ No newline at end of file +} diff --git a/tests/Doctrine/Tests/ORM/AllTests.php b/tests/Doctrine/Tests/ORM/AllTests.php index ac5238690..540b19358 100644 --- a/tests/Doctrine/Tests/ORM/AllTests.php +++ b/tests/Doctrine/Tests/ORM/AllTests.php @@ -24,7 +24,6 @@ class AllTests $suite->addTestSuite('Doctrine\Tests\ORM\CommitOrderCalculatorTest'); $suite->addTestSuite('Doctrine\Tests\ORM\QueryBuilderTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Proxy\ProxyClassGeneratorTest'); - $suite->addTest(Query\AllTests::suite()); $suite->addTest(Hydration\AllTests::suite()); $suite->addTest(Entity\AllTests::suite()); diff --git a/tests/Doctrine/Tests/ORM/Functional/AllTests.php b/tests/Doctrine/Tests/ORM/Functional/AllTests.php index 304030f98..20db30dd2 100644 --- a/tests/Doctrine/Tests/ORM/Functional/AllTests.php +++ b/tests/Doctrine/Tests/ORM/Functional/AllTests.php @@ -36,6 +36,7 @@ class AllTests $suite->addTestSuite('Doctrine\Tests\ORM\Functional\ManyToManySelfReferentialAssociationTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\ReferenceProxyTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Functional\LifecycleCallbackTest'); + $suite->addTestSuite('Doctrine\Tests\ORM\Functional\StandardEntityPersisterTest'); $suite->addTest(Locking\AllTests::suite()); diff --git a/tests/Doctrine/Tests/ORM/Functional/OneToOneBidirectionalAssociationTest.php b/tests/Doctrine/Tests/ORM/Functional/OneToOneBidirectionalAssociationTest.php index 203f0f23e..d3f157ae0 100644 --- a/tests/Doctrine/Tests/ORM/Functional/OneToOneBidirectionalAssociationTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/OneToOneBidirectionalAssociationTest.php @@ -65,8 +65,21 @@ class OneToOneBidirectionalAssociationTest extends \Doctrine\Tests\OrmFunctional $this->assertEquals('paypal', $customer->getCart()->getPayment()); } - public function testLazyLoad() { - $this->markTestSkipped(); + public function testLazyLoadsObjectsOnTheOwningSide() { + $this->_createFixture(); + $this->_em->getConfiguration()->setAllowPartialObjects(false); + $metadata = $this->_em->getClassMetadata('Doctrine\Tests\Models\ECommerce\ECommerceCart'); + $metadata->getAssociationMapping('customer')->fetchMode = AssociationMapping::FETCH_LAZY; + + $query = $this->_em->createQuery('select c from Doctrine\Tests\Models\ECommerce\ECommerceCart c'); + $result = $query->getResultList(); + $cart = $result[0]; + + $this->assertTrue($cart->getCustomer() instanceof ECommerceCustomer); + $this->assertEquals('Giorgio', $cart->getCustomer()->getName()); + } + + public function testLazyLoadsObjectsOnTheInverseSide() { $this->_createFixture(); $this->_em->getConfiguration()->setAllowPartialObjects(false); $metadata = $this->_em->getClassMetadata('Doctrine\Tests\Models\ECommerce\ECommerceCustomer'); diff --git a/tests/Doctrine/Tests/ORM/Functional/OneToOneUnidirectionalAssociationTest.php b/tests/Doctrine/Tests/ORM/Functional/OneToOneUnidirectionalAssociationTest.php index fc1529d74..2fc57bfd9 100644 --- a/tests/Doctrine/Tests/ORM/Functional/OneToOneUnidirectionalAssociationTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/OneToOneUnidirectionalAssociationTest.php @@ -58,8 +58,7 @@ class OneToOneUnidirectionalAssociationTest extends \Doctrine\Tests\OrmFunctiona $this->assertEquals(1, $product->getShipping()->getDays()); } - public function testLazyLoad() { - $this->markTestSkipped(); + public function testLazyLoadsObjects() { $this->_createFixture(); $this->_em->getConfiguration()->setAllowPartialObjects(false); $metadata = $this->_em->getClassMetadata('Doctrine\Tests\Models\ECommerce\ECommerceProduct'); @@ -73,6 +72,17 @@ class OneToOneUnidirectionalAssociationTest extends \Doctrine\Tests\OrmFunctiona $this->assertEquals(1, $product->getShipping()->getDays()); } + public function testDoesNotLazyLoadObjectsIfConfigurationDoesNotAllowIt() { + $this->_createFixture(); + $this->_em->getConfiguration()->setAllowPartialObjects(true); + + $query = $this->_em->createQuery('select p from Doctrine\Tests\Models\ECommerce\ECommerceProduct p'); + $result = $query->getResultList(); + $product = $result[0]; + + $this->assertNull($product->getShipping()); + } + protected function _createFixture() { $product = new ECommerceProduct; diff --git a/tests/Doctrine/Tests/ORM/Functional/StandardEntityPersisterTest.php b/tests/Doctrine/Tests/ORM/Functional/StandardEntityPersisterTest.php new file mode 100644 index 000000000..11ab6c6e5 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/StandardEntityPersisterTest.php @@ -0,0 +1,39 @@ + + */ +class StandardEntityPersisterTest extends \Doctrine\Tests\OrmFunctionalTestCase +{ + protected function setUp() + { + $this->useModelSet('ecommerce'); + parent::setUp(); + } + + public function testAcceptsForeignKeysAsCriteria() { + $customer = new ECommerceCustomer(); + $customer->setName('John Doe'); + $cart = new ECommerceCart(); + $cart->setPayment('Credit card'); + $customer->setCart($cart); + $this->_em->persist($customer); + $this->_em->flush(); + $this->_em->clear(); + unset($cart); + + $persister = $this->_em->getUnitOfWork()->getEntityPersister('Doctrine\Tests\Models\ECommerce\ECommerceCart'); + $newCart = new ECommerceCart(); + $persister->load(array('customer_id' => $customer->getId()), $newCart); + $this->assertEquals('Credit card', $newCart->getPayment()); + } +} diff --git a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php index d453e708c..91ec7c4eb 100644 --- a/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php +++ b/tests/Doctrine/Tests/ORM/Hydration/ObjectHydratorTest.php @@ -4,6 +4,8 @@ namespace Doctrine\Tests\ORM\Hydration; use Doctrine\Tests\Mocks\HydratorMockStatement; use Doctrine\ORM\Query\ResultSetMapping; +use Doctrine\ORM\Proxy\ProxyFactory; +use Doctrine\ORM\Mapping\AssociationMapping; require_once __DIR__ . '/../../TestInit.php'; @@ -98,6 +100,50 @@ class ObjectHydratorTest extends HydrationTestCase $this->assertEquals('Cool things II.', $result[3]->topic); } + /** + * Select p from \Doctrine\Tests\Models\ECommerce\ECommerceProduct p + */ + public function testCreatesProxyForLazyLoadingWithForeignKeys() + { + $rsm = new ResultSetMapping; + $rsm->addEntityResult('Doctrine\Tests\Models\ECommerce\ECommerceProduct', 'p'); + $rsm->addFieldResult('p', 'p__id', 'id'); + $rsm->addFieldResult('p', 'p__name', 'name'); + $rsm->addFieldResult('p', 'p__shipping_id', 'shipping_id'); + + // Faked result set + $resultSet = array( + array( + 'p__id' => '1', + 'p__name' => 'Doctrine Book', + 'p__shipping_id' => 42 + ) + ); + + // mocking the proxy factory + $proxyFactory = $this->getMock('Doctrine\ORM\Proxy\ProxyFactory', array('getAssociationProxy'), array(), '', false, false, false); + $proxyFactory->expects($this->once()) + ->method('getAssociationProxy') + ->with($this->isInstanceOf('Doctrine\Tests\Models\ECommerce\ECommerceProduct'), + $this->isInstanceOf('Doctrine\ORM\Mapping\OneToOneMapping'), + array('shipping_id' => 42)); + + $this->_em->setProxyFactory($proxyFactory); + + // configuring lazy loading + $this->_em->getConfiguration()->setAllowPartialObjects(false); + $metadata = $this->_em->getClassMetadata('Doctrine\Tests\Models\ECommerce\ECommerceProduct'); + $metadata->getAssociationMapping('shipping')->fetchMode = AssociationMapping::FETCH_LAZY; + + $stmt = new HydratorMockStatement($resultSet); + $hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em); + + $result = $hydrator->hydrateAll($stmt, $rsm); + + $this->assertEquals(1, count($result)); + $this->assertTrue($result[0] instanceof \Doctrine\Tests\Models\ECommerce\ECommerceProduct); + } + /** * select u.id, u.status, p.phonenumber, upper(u.name) nameUpper from User u * join u.phonenumbers p @@ -679,4 +725,4 @@ class ObjectHydratorTest extends HydrationTestCase ++$rowNum; } } -} \ No newline at end of file +} diff --git a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php index bc1219202..357976976 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php @@ -14,7 +14,6 @@ require_once __DIR__ . '/../../TestInit.php'; class ClassMetadataFactoryTest extends \Doctrine\Tests\OrmTestCase { - public function testGetMetadataForSingleClass() { $driverMock = new DriverMock(); @@ -38,6 +37,11 @@ class ClassMetadataFactoryTest extends \Doctrine\Tests\OrmTestCase $cm1->mapField(array('fieldName' => 'id', 'type' => 'integer', 'id' => true)); // and a mapped association $cm1->mapOneToOne(array('fieldName' => 'other', 'targetEntity' => 'Other', 'mappedBy' => 'this')); + // and an association on the owning side + $joinColumns = array( + array('name' => 'other_id', 'referencedColumnName' => 'id') + ); + $cm1->mapOneToOne(array('fieldName' => 'association', 'targetEntity' => 'Other', 'joinColumns' => $joinColumns)); // and an id generator type $cm1->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_AUTO); @@ -49,13 +53,14 @@ class ClassMetadataFactoryTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals(array(), $cm1->parentClasses); $this->assertEquals(ClassMetadata::INHERITANCE_TYPE_NONE, $cm1->inheritanceType); $this->assertTrue($cm1->hasField('name')); - $this->assertEquals(1, count($cm1->associationMappings)); + $this->assertEquals(2, count($cm1->associationMappings)); $this->assertEquals(ClassMetadata::GENERATOR_TYPE_AUTO, $cm1->generatorType); // Go $cm1 = $cmf->getMetadataFor('Doctrine\Tests\ORM\Mapping\TestEntity1'); $this->assertEquals(array(), $cm1->parentClasses); + $this->assertEquals(array('other_id'), $cm1->joinColumnNames); $this->assertTrue($cm1->hasField('name')); $this->assertEquals(ClassMetadata::GENERATOR_TYPE_SEQUENCE, $cm1->generatorType); } @@ -93,4 +98,5 @@ class TestEntity1 private $id; private $name; private $other; -} \ No newline at end of file + private $association; +} diff --git a/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php b/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php index 53e980637..df4740cf0 100644 --- a/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php +++ b/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php @@ -62,11 +62,22 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase public function testAllowsIdempotentCreationOfReferenceProxyClass() { - $proxyClass = $this->_generator->generateReferenceProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); - $theSameProxyClass = $this->_generator->generateReferenceProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); + $originalClassName = 'Doctrine\Tests\Models\ECommerce\ECommerceFeature'; + $proxyClass = $this->_generator->generateReferenceProxyClass($originalClassName); + $theSameProxyClass = $this->_generator->generateReferenceProxyClass($originalClassName); $this->assertEquals($proxyClass, $theSameProxyClass); } + public function testRegeneratesMetadataAfterIdempotentCreation() + { + $originalClassName = 'Doctrine\Tests\Models\ECommerce\ECommerceFeature'; + $metadataFactory = $this->_emMock->getMetadataFactory(); + $proxyClass = $this->_generator->generateReferenceProxyClass($originalClassName); + $metadataFactory->setMetadataFor($proxyClass, null); + $theSameProxyClass = $this->_generator->generateReferenceProxyClass($originalClassName); + $this->assertNotNull($metadataFactory->getMetadataFor($theSameProxyClass)); + } + public function testReferenceProxyRequiresPersisterInTheConstructor() { $proxyClass = $this->_generator->generateReferenceProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); @@ -140,22 +151,23 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase $this->assertNotEquals($referenceProxyClass, $associationProxyClass); } - public function testAssociationProxyRequiresEntityManagerAndAssociationAndOwnerInTheConstructor() + public function testAssociationProxyRequiresEntityManagerAssociationOwnerAndForeignKeysInTheConstructor() { $proxyClass = $this->_generator->generateAssociationProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); $product = new ECommerceProduct; - $proxy = new $proxyClass($this->_emMock, $this->_getAssociationMock(), $product); + $proxy = new $proxyClass($this->_emMock, $this->_getAssociationMock(), $product, array()); } public function testAssociationProxyDelegatesLoadingToTheAssociation() { $proxyClass = $this->_generator->generateAssociationProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); $product = new ECommerceProduct; + $foreignKeys = array('customer_id' => 42); $assoc = $this->_getAssociationMock(); - $proxy = new $proxyClass($this->_emMock, $assoc, $product); + $proxy = new $proxyClass($this->_emMock, $assoc, $product, $foreignKeys); $assoc->expects($this->any()) ->method('load') - ->with($product, $this->isInstanceOf($proxyClass), $this->isInstanceOf('Doctrine\Tests\Mocks\EntityManagerMock')); + ->with($product, $this->isInstanceOf($proxyClass), $this->isInstanceOf('Doctrine\Tests\Mocks\EntityManagerMock'), $foreignKeys); $proxy->getDescription(); } @@ -163,7 +175,7 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase { $proxyClass = $this->_generator->generateAssociationProxyClass('Doctrine\Tests\Models\ECommerce\ECommerceFeature'); $assoc = $this->_getAssociationMock(); - $proxy = new $proxyClass($this->_emMock, $assoc, null); + $proxy = new $proxyClass($this->_emMock, $assoc, null, array()); $assoc->expects($this->once()) ->method('load'); @@ -171,7 +183,6 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase $proxy->getDescription(); } - protected function _getAssociationMock() { $assoc = $this->getMock('Doctrine\ORM\Mapping\AssociationMapping', array('load'), array(), '', false, false, false); diff --git a/tests/Doctrine/Tests/OrmTestCase.php b/tests/Doctrine/Tests/OrmTestCase.php index 4d5f914dd..8886773a3 100644 --- a/tests/Doctrine/Tests/OrmTestCase.php +++ b/tests/Doctrine/Tests/OrmTestCase.php @@ -36,7 +36,10 @@ class OrmTestCase extends DoctrineTestCase 'password' => 'wayne' ); } - return \Doctrine\ORM\EntityManager::create($conn, $config, $eventManager); + if (is_array($conn)) { + $conn = \Doctrine\DBAL\DriverManager::getConnection($conn, $config, $eventManager); + } + return \Doctrine\Tests\Mocks\EntityManagerMock::create($conn, $config, $eventManager); } private static function getSharedMetadataCacheImpl()