From 92acd32410af75934671a408e5e5af0b2c97f607 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Thu, 20 Oct 2011 09:35:41 -0700 Subject: [PATCH 01/83] ProxyFactory creates proxy's parent structure if it doesn't exist --- lib/Doctrine/ORM/Proxy/ProxyFactory.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index 490c3a119..0bba4dabb 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -152,6 +152,11 @@ class ProxyFactory $file = str_replace($placeholders, $replacements, $file); + $parentDirectory = dirname($fileName); + if (! file_exists($parentDirectory)) { + mkdir($parentDirectory, 0, true); + } + file_put_contents($fileName, $file, LOCK_EX); } From fde9d122ccf0fa9fbec1a8e6bccc0189f9beb033 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Thu, 20 Oct 2011 13:33:20 -0700 Subject: [PATCH 02/83] ProxyFactory checks presence of directory with `is_dir` instead of `file_exists` --- lib/Doctrine/ORM/Proxy/ProxyFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index 0bba4dabb..6ab28be3a 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -153,7 +153,7 @@ class ProxyFactory $file = str_replace($placeholders, $replacements, $file); $parentDirectory = dirname($fileName); - if (! file_exists($parentDirectory)) { + if (! is_dir($parentDirectory)) { mkdir($parentDirectory, 0, true); } From 99c1383ef566c6442191981e29e232724cc0cda4 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Mon, 24 Oct 2011 19:32:38 -0700 Subject: [PATCH 03/83] If proxy directory doesn't exist & cannot be created via mkdir, a ProxyException is thrown --- lib/Doctrine/ORM/Proxy/ProxyException.php | 4 ++++ lib/Doctrine/ORM/Proxy/ProxyFactory.php | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/Proxy/ProxyException.php b/lib/Doctrine/ORM/Proxy/ProxyException.php index 892dbebbb..29462780d 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyException.php +++ b/lib/Doctrine/ORM/Proxy/ProxyException.php @@ -36,6 +36,10 @@ class ProxyException extends \Doctrine\ORM\ORMException { return new self("You must configure a proxy directory. See docs for details"); } + public static function proxyDirectoryNotWritable() { + return new self("Your proxy directory must be writable."); + } + public static function proxyNamespaceRequired() { return new self("You must configure a proxy namespace. See docs for details"); } diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index 6ab28be3a..9436bcb71 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -153,8 +153,8 @@ class ProxyFactory $file = str_replace($placeholders, $replacements, $file); $parentDirectory = dirname($fileName); - if (! is_dir($parentDirectory)) { - mkdir($parentDirectory, 0, true); + if (! is_dir($parentDirectory) && ! mkdir($parentDirectory, 0775, true)) { + throw ProxyException::proxyDirectoryNotWritable(); } file_put_contents($fileName, $file, LOCK_EX); From 48bf5022e4f67b0718f7b26ed00f89fec4d1cc17 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Mon, 24 Oct 2011 19:45:23 -0700 Subject: [PATCH 04/83] ProxyFactory always checks if directory is writable first --- lib/Doctrine/ORM/Proxy/ProxyFactory.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index 9436bcb71..aa53a0686 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -153,7 +153,12 @@ class ProxyFactory $file = str_replace($placeholders, $replacements, $file); $parentDirectory = dirname($fileName); - if (! is_dir($parentDirectory) && ! mkdir($parentDirectory, 0775, true)) { + + if (! is_dir($parentDirectory)) { + mkdir($parentDirectory, 0775, true); + } + + if ( ! is_writable($parentDirectory)) { throw ProxyException::proxyDirectoryNotWritable(); } From 5b64dbe1955e643f61957a1937b9710877d67002 Mon Sep 17 00:00:00 2001 From: Eric Clemmons Date: Tue, 25 Oct 2011 11:51:09 -0700 Subject: [PATCH 05/83] Added error suppression to mkdir in ProxyFactory See: Symfony\Component\HttpKernel\Kernel#buildContainre --- lib/Doctrine/ORM/Proxy/ProxyFactory.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index aa53a0686..18202e0f0 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -154,11 +154,11 @@ class ProxyFactory $parentDirectory = dirname($fileName); - if (! is_dir($parentDirectory)) { - mkdir($parentDirectory, 0775, true); - } - - if ( ! is_writable($parentDirectory)) { + if ( ! is_dir($parentDirectory)) { + if (false === @mkdir($parentDirectory, 0775, true)) { + throw ProxyException::proxyDirectoryNotWritable(); + } + } else if ( ! is_writable($parentDirectory)) { throw ProxyException::proxyDirectoryNotWritable(); } From 82a1626e82729740ab0ef8bbb711369f6f12a897 Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Fri, 18 Nov 2011 10:57:27 +0100 Subject: [PATCH 06/83] Better generation of exported xml (valid with xsd) --- .../ORM/Tools/Export/Driver/XmlExporter.php | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php b/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php index 01309475b..8ec4d5cd5 100644 --- a/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php +++ b/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php @@ -95,7 +95,10 @@ class XmlExporter extends AbstractExporter } } - $root->addChild('change-tracking-policy', $this->_getChangeTrackingPolicyString($metadata->changeTrackingPolicy)); + $trackingPolicy = $this->_getChangeTrackingPolicyString($metadata->changeTrackingPolicy); + if ( $trackingPolicy != 'DEFERRED_IMPLICIT') { + $root->addChild('change-tracking-policy', $trackingPolicy); + } if (isset($metadata->table['indexes'])) { $indexesXml = $root->addChild('indexes'); @@ -183,7 +186,17 @@ class XmlExporter extends AbstractExporter } } } - + $orderMap = array( + ClassMetadataInfo::ONE_TO_ONE, + ClassMetadataInfo::ONE_TO_MANY, + ClassMetadataInfo::MANY_TO_ONE, + ClassMetadataInfo::MANY_TO_MANY, + ); + uasort($metadata->associationMappings, function($m1, $m2)use(&$orderMap){ + $a1 = array_search($m1['type'],$orderMap); + $a2 = array_search($m2['type'],$orderMap); + return strcmp($a1, $a2); + }); foreach ($metadata->associationMappings as $name => $associationMapping) { if ($associationMapping['type'] == ClassMetadataInfo::ONE_TO_ONE) { $associationMappingXml = $root->addChild('one-to-one'); @@ -204,7 +217,11 @@ class XmlExporter extends AbstractExporter if (isset($associationMapping['inversedBy'])) { $associationMappingXml->addAttribute('inversed-by', $associationMapping['inversedBy']); } - if (isset($associationMapping['orphanRemoval'])) { + if (isset($associationMapping['indexBy'])) { + $associationMappingXml->addAttribute('index-by', $associationMapping['indexBy']); + } + if (isset($associationMapping['orphanRemoval']) && $associationMapping['orphanRemoval']!==false) { + // false is the xml default $associationMappingXml->addAttribute('orphan-removal', $associationMapping['orphanRemoval']); } if (isset($associationMapping['joinTable']) && $associationMapping['joinTable']) { @@ -287,7 +304,7 @@ class XmlExporter extends AbstractExporter } } - if (isset($metadata->lifecycleCallbacks)) { + if (isset($metadata->lifecycleCallbacks) && count($metadata->lifecycleCallbacks)>0) { $lifecycleCallbacksXml = $root->addChild('lifecycle-callbacks'); foreach ($metadata->lifecycleCallbacks as $name => $methods) { foreach ($methods as $method) { From 24432bd0abf1da96b9c2214efdec81ce2911563d Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Fri, 18 Nov 2011 11:00:20 +0100 Subject: [PATCH 07/83] tabs --- lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php b/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php index 8ec4d5cd5..3e936a487 100644 --- a/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php +++ b/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php @@ -217,7 +217,7 @@ class XmlExporter extends AbstractExporter if (isset($associationMapping['inversedBy'])) { $associationMappingXml->addAttribute('inversed-by', $associationMapping['inversedBy']); } - if (isset($associationMapping['indexBy'])) { + if (isset($associationMapping['indexBy'])) { $associationMappingXml->addAttribute('index-by', $associationMapping['indexBy']); } if (isset($associationMapping['orphanRemoval']) && $associationMapping['orphanRemoval']!==false) { @@ -331,4 +331,4 @@ class XmlExporter extends AbstractExporter $result = $dom->saveXML(); return $result; } -} \ No newline at end of file +} From 289c186de5d6bc6cbf36f960c4f4f78f1d6fd773 Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Fri, 16 Dec 2011 16:16:52 +0100 Subject: [PATCH 08/83] orphanRemoval default is false --- lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php b/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php index 3e936a487..1ca5fb403 100644 --- a/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php +++ b/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php @@ -221,8 +221,7 @@ class XmlExporter extends AbstractExporter $associationMappingXml->addAttribute('index-by', $associationMapping['indexBy']); } if (isset($associationMapping['orphanRemoval']) && $associationMapping['orphanRemoval']!==false) { - // false is the xml default - $associationMappingXml->addAttribute('orphan-removal', $associationMapping['orphanRemoval']); + $associationMappingXml->addAttribute('orphan-removal', 'true'); } if (isset($associationMapping['joinTable']) && $associationMapping['joinTable']) { $joinTableXml = $associationMappingXml->addChild('join-table'); From d8227fcd06bc8277b995c5864506528546f8a3cf Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Tue, 3 Jan 2012 16:59:43 -0200 Subject: [PATCH 09/83] give the FQCN to the naming strategy --- .../ORM/Mapping/ClassMetadataInfo.php | 4 +- .../Tests/ORM/Mapping/ClassMetadataTest.php | 46 +++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 637232ac7..7bc0c0beb 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -771,13 +771,13 @@ class ClassMetadataInfo implements ClassMetadata * Initializes a new ClassMetadata instance that will hold the object-relational mapping * metadata of the class with the given name. * - * @param string $entityName The name of the entity class the new instance is used for. + * @param ReflectionService $reflService The reflection service. */ public function initializeReflection($reflService) { $this->reflClass = $reflService->getClass($this->name); $this->namespace = $reflService->getClassNamespace($this->name); - $this->table['name'] = $this->namingStrategy->classToTableName($reflService->getClassShortName($this->name)); + $this->table['name'] = $this->namingStrategy->classToTableName($this->name); if ($this->reflClass) { $this->name = $this->rootEntityName = $this->reflClass->getName(); diff --git a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php index 9c3ad2850..c16eedba9 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php @@ -595,4 +595,50 @@ class ClassMetadataTest extends \Doctrine\Tests\OrmTestCase $this->setExpectedException("Doctrine\ORM\Mapping\MappingException", "The target-entity Doctrine\Tests\Models\CMS\UnknownClass cannot be found in 'Doctrine\Tests\Models\CMS\CmsUser#address'."); $cm->validateAssocations(); } + + /** + * @group DDC-984 + * @group DDC-559 + * @group DDC-1575 + */ + public function testFullyQualifiedClassNameShouldBeGivenToNamingStrategy() + { + $namingStrategy = new MyNamespacedNamingStrategy(); + $addressMetadata = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsAddress', $namingStrategy); + $articleMetadata = new ClassMetadata('DoctrineGlobal_Article', $namingStrategy); + $routingMetadata = new ClassMetadata('Doctrine\Tests\Models\Routing\RoutingLeg',$namingStrategy); + + $addressMetadata->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); + $articleMetadata->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); + $routingMetadata->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); + + $addressMetadata->mapManyToMany(array( + 'fieldName' => 'user', + 'targetEntity' => 'CmsUser' + )); + + $articleMetadata->mapManyToMany(array( + 'fieldName' => 'author', + 'targetEntity' => 'Doctrine\Tests\Models\CMS\CmsUser' + )); + + $this->assertEquals('routing_routingleg', $routingMetadata->table['name']); + $this->assertEquals('cms_cmsaddress_cms_cmsuser', $addressMetadata->associationMappings['user']['joinTable']['name']); + $this->assertEquals('doctrineglobal_article_cms_cmsuser', $articleMetadata->associationMappings['author']['joinTable']['name']); + } +} + +class MyNamespacedNamingStrategy extends \Doctrine\ORM\Mapping\DefaultNamingStrategy +{ + /** + * {@inheritdoc} + */ + public function classToTableName($className) + { + if (strpos($className, '\\') !== false) { + $className = str_replace('\\', '_', str_replace('Doctrine\Tests\Models\\', '', $className)); + } + + return strtolower($className); + } } From 781a661704c904598afcdad57971fc3b8df8d068 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Tue, 3 Jan 2012 17:58:20 -0200 Subject: [PATCH 10/83] change naming position --- lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 7bc0c0beb..328d7bc35 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -777,11 +777,12 @@ class ClassMetadataInfo implements ClassMetadata { $this->reflClass = $reflService->getClass($this->name); $this->namespace = $reflService->getClassNamespace($this->name); - $this->table['name'] = $this->namingStrategy->classToTableName($this->name); if ($this->reflClass) { $this->name = $this->rootEntityName = $this->reflClass->getName(); } + + $this->table['name'] = $this->namingStrategy->classToTableName($this->name); } /** From 4deeb23af05d43774901ceac6fb5bed7f3871fac Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 3 Jan 2012 21:56:04 +0100 Subject: [PATCH 11/83] Update dependencies --- lib/vendor/doctrine-build-common | 2 +- lib/vendor/doctrine-common | 2 +- lib/vendor/doctrine-dbal | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/vendor/doctrine-build-common b/lib/vendor/doctrine-build-common index 5812b7acd..c9ff83d71 160000 --- a/lib/vendor/doctrine-build-common +++ b/lib/vendor/doctrine-build-common @@ -1 +1 @@ -Subproject commit 5812b7acdc962196140e6b9f7a4758fb6d6f4933 +Subproject commit c9ff83d716d56a82b3c15733ea7d180b8619d9d9 diff --git a/lib/vendor/doctrine-common b/lib/vendor/doctrine-common index cc04744bc..17e774007 160000 --- a/lib/vendor/doctrine-common +++ b/lib/vendor/doctrine-common @@ -1 +1 @@ -Subproject commit cc04744bcf5a4743c46fae0487ac7a093a722856 +Subproject commit 17e774007b98beb2e253e645260e0f9c32f4c936 diff --git a/lib/vendor/doctrine-dbal b/lib/vendor/doctrine-dbal index 29b714b7f..3dc1b22e1 160000 --- a/lib/vendor/doctrine-dbal +++ b/lib/vendor/doctrine-dbal @@ -1 +1 @@ -Subproject commit 29b714b7fe72641d749ae90324a5759853fe09b0 +Subproject commit 3dc1b22e12368bc3bd5aaa72a7d4f75f61273223 From 8d3d604ed308bcda1249d716e9da691f539ee62b Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 3 Jan 2012 22:27:03 +0100 Subject: [PATCH 12/83] Release 2.2.0-BETA2 --- composer.json | 2 +- lib/Doctrine/ORM/Version.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 8f570e200..c3c4d7b05 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "doctrine/orm", - "type": "library", + "type": "library","version":"2.2.0-BETA2", "description": "Object-Relational-Mapper for PHP", "keywords": ["orm", "database"], "homepage": "http://www.doctrine-project.org", diff --git a/lib/Doctrine/ORM/Version.php b/lib/Doctrine/ORM/Version.php index e8124cd45..787cf9fe1 100644 --- a/lib/Doctrine/ORM/Version.php +++ b/lib/Doctrine/ORM/Version.php @@ -36,7 +36,7 @@ class Version /** * Current Doctrine Version */ - const VERSION = '2.2.0-DEV'; + const VERSION = '2.2.0-BETA2'; /** * Compares a Doctrine version with the current one. From d34c39555d356e771452a6f397a4cb1146594304 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Tue, 3 Jan 2012 22:27:03 +0100 Subject: [PATCH 13/83] Bump dev version to 2.2.0 --- composer.json | 2 +- lib/Doctrine/ORM/Version.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index c3c4d7b05..8f570e200 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "doctrine/orm", - "type": "library","version":"2.2.0-BETA2", + "type": "library", "description": "Object-Relational-Mapper for PHP", "keywords": ["orm", "database"], "homepage": "http://www.doctrine-project.org", diff --git a/lib/Doctrine/ORM/Version.php b/lib/Doctrine/ORM/Version.php index 787cf9fe1..e8124cd45 100644 --- a/lib/Doctrine/ORM/Version.php +++ b/lib/Doctrine/ORM/Version.php @@ -36,7 +36,7 @@ class Version /** * Current Doctrine Version */ - const VERSION = '2.2.0-BETA2'; + const VERSION = '2.2.0-DEV'; /** * Compares a Doctrine version with the current one. From facd64ef2f0f131149c10799a2b18cc7700f26c0 Mon Sep 17 00:00:00 2001 From: Marcel Raaijmakers Date: Fri, 6 Jan 2012 16:58:27 +0100 Subject: [PATCH 14/83] enable set visibilty of class fields in EntityGenerator --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 24 ++++++++++++++++--- .../Tests/ORM/Tools/EntityGeneratorTest.php | 3 ++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index 66d79a837..eae008d7f 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -86,6 +86,8 @@ class EntityGenerator /** Whether or not to re-generate entity class if it exists already */ private $_regenerateEntityIfExists = false; + private $_fieldVisibility = 'private'; + private static $_classTemplate = '() { } -'; +'; public function __construct() { @@ -297,6 +299,22 @@ public function () $this->_generateAnnotations = $bool; } + /** + * Set whether or not to generate annotations for the entity + * + * @param bool $bool + * @return void + */ + public function setFieldVisibility($visibility) + { + if($visibility != 'private' && $visibility != 'protected') + { + throw new \InvalidArgumentException('Invalid provided visibilty (only private and protected are allowed): ' . $visibility); + } + + $this->_fieldVisibility = $visibility; + } + /** * Set an annotation prefix. * @@ -699,7 +717,7 @@ public function () } $lines[] = $this->_generateAssociationMappingPropertyDocBlock($associationMapping, $metadata); - $lines[] = $this->_spaces . 'private $' . $associationMapping['fieldName'] + $lines[] = $this->_spaces . $this->_fieldVisibility . ' $' . $associationMapping['fieldName'] . ($associationMapping['type'] == 'manyToMany' ? ' = array()' : null) . ";\n"; } @@ -717,7 +735,7 @@ public function () } $lines[] = $this->_generateFieldMappingPropertyDocBlock($fieldMapping, $metadata); - $lines[] = $this->_spaces . 'private $' . $fieldMapping['fieldName'] + $lines[] = $this->_spaces . $this->_fieldVisibility . ' $' . $fieldMapping['fieldName'] . (isset($fieldMapping['default']) ? ' = ' . var_export($fieldMapping['default'], true) : null) . ";\n"; } diff --git a/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php b/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php index 4b02a94d1..3b6c1a803 100644 --- a/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php @@ -26,6 +26,7 @@ class EntityGeneratorTest extends \Doctrine\Tests\OrmTestCase $this->_generator->setGenerateStubMethods(true); $this->_generator->setRegenerateEntityIfExists(false); $this->_generator->setUpdateEntityIfExists(true); + $this->_generator->setFieldVisibility('protected'); } public function tearDown() @@ -133,7 +134,7 @@ class EntityGeneratorTest extends \Doctrine\Tests\OrmTestCase $this->assertTrue($reflClass->hasProperty('id'), "Regenerating keeps property 'id'."); $this->assertTrue($reflClass->hasProperty('test'), "Check for property test failed."); - $this->assertTrue($reflClass->getProperty('test')->isPrivate(), "Check for private property test failed."); + $this->assertTrue($reflClass->getProperty('test')->isProtected(), "Check for private property test failed."); $this->assertTrue($reflClass->hasMethod('getTest'), "Check for method 'getTest' failed."); $this->assertTrue($reflClass->getMethod('getTest')->isPublic(), "Check for public visibility of method 'getTest' failed."); $this->assertTrue($reflClass->hasMethod('setTest'), "Check for method 'getTest' failed."); From e16803de618ef0b94b1fa0917bf0fefff23e07de Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 9 Jan 2012 08:02:53 +0100 Subject: [PATCH 15/83] [doctrine/common-GH-92] Fixing notice when annotation driver is used in combination with static reflection. --- lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index bcd453508..c8b53fe94 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -134,6 +134,11 @@ class AnnotationDriver implements Driver public function loadMetadataForClass($className, ClassMetadataInfo $metadata) { $class = $metadata->getReflectionClass(); + if (!$class) { + // this happens when running annotation driver in combination with + // static reflection services. This is not the nicest fix + $class = new \ReflectionClass($class->name); + } $classAnnotations = $this->_reader->getClassAnnotations($class); From 0014afe74694fb668898725a67a753c82731ccec Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 9 Jan 2012 08:04:21 +0100 Subject: [PATCH 16/83] Fix Typo --- lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index c8b53fe94..f11331ffa 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -137,7 +137,7 @@ class AnnotationDriver implements Driver if (!$class) { // this happens when running annotation driver in combination with // static reflection services. This is not the nicest fix - $class = new \ReflectionClass($class->name); + $class = new \ReflectionClass($metadata->name); } $classAnnotations = $this->_reader->getClassAnnotations($class); From d0b0b0ce5994ed3dd8a1e8624ada8744100dff91 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 9 Jan 2012 08:05:15 +0100 Subject: [PATCH 17/83] Merge 2.2 --- lib/vendor/doctrine-build-common | 2 +- lib/vendor/doctrine-common | 2 +- lib/vendor/doctrine-dbal | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/vendor/doctrine-build-common b/lib/vendor/doctrine-build-common index c9ff83d71..5812b7acd 160000 --- a/lib/vendor/doctrine-build-common +++ b/lib/vendor/doctrine-build-common @@ -1 +1 @@ -Subproject commit c9ff83d716d56a82b3c15733ea7d180b8619d9d9 +Subproject commit 5812b7acdc962196140e6b9f7a4758fb6d6f4933 diff --git a/lib/vendor/doctrine-common b/lib/vendor/doctrine-common index 17e774007..cc04744bc 160000 --- a/lib/vendor/doctrine-common +++ b/lib/vendor/doctrine-common @@ -1 +1 @@ -Subproject commit 17e774007b98beb2e253e645260e0f9c32f4c936 +Subproject commit cc04744bcf5a4743c46fae0487ac7a093a722856 diff --git a/lib/vendor/doctrine-dbal b/lib/vendor/doctrine-dbal index 3dc1b22e1..29b714b7f 160000 --- a/lib/vendor/doctrine-dbal +++ b/lib/vendor/doctrine-dbal @@ -1 +1 @@ -Subproject commit 3dc1b22e12368bc3bd5aaa72a7d4f75f61273223 +Subproject commit 29b714b7fe72641d749ae90324a5759853fe09b0 From 41ae873048d53ca767acf1623e50d5571a93f7c6 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 9 Jan 2012 08:26:07 +0100 Subject: [PATCH 18/83] DDC-1588 - Improve ResultCache API. The default cache impl is passed to new query cache profiles automatically now. --- lib/Doctrine/ORM/AbstractQuery.php | 4 ++-- tests/Doctrine/Tests/ORM/Query/QueryTest.php | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index 3fe27f93b..866febcca 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -316,7 +316,7 @@ abstract class AbstractQuery $this->_queryCacheProfile = $this->_queryCacheProfile ? $this->_queryCacheProfile->setLifetime($lifetime) - : new QueryCacheProfile($lifetime); + : new QueryCacheProfile($lifetime, null, $this->_em->getConfiguration()->getResultCacheImpl()); return $this; } @@ -615,7 +615,7 @@ abstract class AbstractQuery { $this->_queryCacheProfile = $this->_queryCacheProfile ? $this->_queryCacheProfile->setCacheKey($id) - : new QueryCacheProfile(0, $id); + : new QueryCacheProfile(0, $id, $this->_em->getConfiguration()->getResultCacheImpl()); return $this; } diff --git a/tests/Doctrine/Tests/ORM/Query/QueryTest.php b/tests/Doctrine/Tests/ORM/Query/QueryTest.php index b1d2bee23..4e142e149 100644 --- a/tests/Doctrine/Tests/ORM/Query/QueryTest.php +++ b/tests/Doctrine/Tests/ORM/Query/QueryTest.php @@ -2,7 +2,7 @@ namespace Doctrine\Tests\ORM\Query; -require_once __DIR__ . '/../../TestInit.php'; +use Doctrine\Common\Cache\ArrayCache; class QueryTest extends \Doctrine\Tests\OrmTestCase { @@ -90,4 +90,15 @@ class QueryTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals('baz', $q->getHint('bar')); $this->assertEquals(array('foo' => 'bar', 'bar' => 'baz'), $q->getHints()); } -} \ No newline at end of file + + /** + * @group DDC-1588 + */ + public function testQueryDefaultResultCache() + { + $this->_em->getConfiguration()->setResultCacheImpl(new ArrayCache()); + $q = $this->_em->createQuery("select a from Doctrine\Tests\Models\CMS\CmsArticle a"); + $q->useResultCache(true); + $this->assertSame($this->_em->getConfiguration()->getResultCacheImpl(), $q->getQueryCacheProfile()->getResultCacheDriver()); + } +} From 615e22073f5c99200b57b5d755c3cd99fab18f9b Mon Sep 17 00:00:00 2001 From: jsor Date: Wed, 11 Jan 2012 15:58:57 +0100 Subject: [PATCH 19/83] Pass options attribute in @Column annotation to Schema\Column's customSchemaOptions --- lib/Doctrine/ORM/Tools/SchemaTool.php | 4 +++ .../Tests/ORM/Tools/SchemaToolTest.php | 29 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/lib/Doctrine/ORM/Tools/SchemaTool.php b/lib/Doctrine/ORM/Tools/SchemaTool.php index 2c1236a1f..2e9feb1f5 100644 --- a/lib/Doctrine/ORM/Tools/SchemaTool.php +++ b/lib/Doctrine/ORM/Tools/SchemaTool.php @@ -360,6 +360,10 @@ class SchemaTool $options['columnDefinition'] = $mapping['columnDefinition']; } + if (isset($mapping['options'])) { + $options['customSchemaOptions'] = $mapping['options']; + } + if ($class->isIdGeneratorIdentity() && $class->getIdentifierFieldNames() == array($mapping['fieldName'])) { $options['autoincrement'] = true; } diff --git a/tests/Doctrine/Tests/ORM/Tools/SchemaToolTest.php b/tests/Doctrine/Tests/ORM/Tools/SchemaToolTest.php index f38de01b7..6e4b62efe 100644 --- a/tests/Doctrine/Tests/ORM/Tools/SchemaToolTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/SchemaToolTest.php @@ -32,6 +32,21 @@ class SchemaToolTest extends \Doctrine\Tests\OrmTestCase $this->assertTrue($schema->getTable('cms_users')->columnsAreIndexed(array('username')), "username column should be indexed."); } + public function testColumnAnnotationOptionsAttribute() + { + $em = $this->_getTestEntityManager(); + $schemaTool = new SchemaTool($em); + + $classes = array( + $em->getClassMetadata(__NAMESPACE__ . '\\TestEntityWithColumnAnnotationOptionsAttribute'), + ); + + $schema = $schemaTool->getSchemaFromMetadata($classes); + + $expected = array('foo' => 'bar', 'baz' => array('key' => 'val')); + $this->assertEquals($expected, $schema->getTable('TestEntityWithColumnAnnotationOptionsAttribute')->getColumn('test')->getCustomSchemaOptions(), "options annotation are passed to the columns customSchemaOptions"); + } + /** * @group DDC-200 */ @@ -86,6 +101,20 @@ class SchemaToolTest extends \Doctrine\Tests\OrmTestCase } } +/** + * @Entity + */ +class TestEntityWithColumnAnnotationOptionsAttribute +{ + /** @Id @Column */ + private $id; + + /** + * @Column(type="string", options={"foo": "bar", "baz": {"key": "val"}}) + */ + private $test; +} + class GenerateSchemaEventListener { public $tableCalls = 0; From c1dae35a24036e320f19d1dfd2d9ae8fd9d81598 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Thu, 12 Jan 2012 11:20:49 +0100 Subject: [PATCH 20/83] Fix notice when using regenerate if exists and file is not new. --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index 66d79a837..915675712 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -191,6 +191,8 @@ public function () if ( ! $this->_isNew) { $this->_parseTokensInEntityFile(file_get_contents($path)); + } else { + $this->_staticReflection[$metadata->name] = array('properties' => array(), 'methods' => array()); } if ($this->_backupExisting && file_exists($path)) { @@ -1088,4 +1090,4 @@ public function () throw new \InvalidArgumentException('Invalid provided IdGeneratorType: ' . $type); } } -} \ No newline at end of file +} From bb1021198355871bd78b19c7795900be0020520b Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Thu, 12 Jan 2012 23:58:08 -0500 Subject: [PATCH 21/83] Fixes DDC-1596. Added table alias to discriminator column when using STI. --- lib/Doctrine/ORM/Persisters/SingleTablePersister.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/Doctrine/ORM/Persisters/SingleTablePersister.php b/lib/Doctrine/ORM/Persisters/SingleTablePersister.php index 171264a1d..8644e1d6e 100644 --- a/lib/Doctrine/ORM/Persisters/SingleTablePersister.php +++ b/lib/Doctrine/ORM/Persisters/SingleTablePersister.php @@ -48,12 +48,13 @@ class SingleTablePersister extends AbstractEntityInheritancePersister $columnList = parent::_getSelectColumnListSQL(); - // Append discriminator column - $discrColumn = $this->_class->discriminatorColumn['name']; - $columnList .= ', ' . $discrColumn; - $rootClass = $this->_em->getClassMetadata($this->_class->rootEntityName); $tableAlias = $this->_getSQLTableAlias($rootClass->name); + + // Append discriminator column + $discrColumn = $this->_class->discriminatorColumn['name']; + $columnList .= ', ' . $tableAlias . '.' . $discrColumn; + $resultColumnName = $this->_platform->getSQLResultCasing($discrColumn); $this->_rsm->setDiscriminatorColumn('r', $resultColumnName); From 52ee848bcb1fa0c2c49c355d35518550e29ca889 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Fri, 13 Jan 2012 00:37:59 -0500 Subject: [PATCH 22/83] Added coverage to DDC-1595 and DDC-1596. --- .../ORM/Functional/Ticket/DDC1595Test.php | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1595Test.php diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1595Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1595Test.php new file mode 100644 index 000000000..a45c9f1f2 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1595Test.php @@ -0,0 +1,111 @@ +_em->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\DebugStack); + + $this->_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\\DDC1595BaseInheritance'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\\DDC1595InheritedEntity1'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\\DDC1595InheritedEntity2'), + )); + } + + public function testIssue() + { + $e1 = new DDC1595InheritedEntity1(); + + $this->_em->persist($e1); + $this->_em->flush(); + $this->_em->clear(); + + $sqlLogger = $this->_em->getConnection()->getConfiguration()->getSQLLogger(); + $repository = $this->_em->getRepository(__NAMESPACE__ . '\\DDC1595InheritedEntity1'); + + $entity1 = $repository->find($e1->id); + + // DDC-1596 + $this->assertEquals( + "SELECT t0.id AS id1, t0.type FROM base t0 WHERE t0.id = ? AND t0.type IN ('Entity1')", + $sqlLogger->queries[count($sqlLogger->queries)]['sql'] + ); + + $entities = $entity1->getEntities()->getValues(); + + $this->assertEquals( + "SELECT t0.id AS id1, t0.type FROM base t0 INNER JOIN entity1_entity2 ON t0.id = entity1_entity2.item WHERE entity1_entity2.parent = ? AND t0.type IN ('Entity2')", + $sqlLogger->queries[count($sqlLogger->queries)]['sql'] + ); + + $this->_em->clear(); + + $entity1 = $repository->find($e1->id); + $entities = $entity1->getEntities()->count(); + + $this->assertEquals( + "SELECT COUNT(*) FROM entity1_entity2 t WHERE parent = ?", + $sqlLogger->queries[count($sqlLogger->queries)]['sql'] + ); + } +} + +/** + * @Entity + * @Table(name="base") + * + * @InheritanceType("SINGLE_TABLE") + * @DiscriminatorColumn(name="type", type="string") + * @DiscriminatorMap({ + * "Entity1" = "DDC1595InheritedEntity1", + * "Entity2" = "DDC1595InheritedEntity2" + * }) + */ +abstract class DDC1595BaseInheritance +{ + /** + * @Id @GeneratedValue + * @Column(type="integer") + * + * @var integer + */ + public $id; +} + +/** + * @Entity + * @Table(name="entity1") + */ +class DDC1595InheritedEntity1 extends DDC1595BaseInheritance +{ + /** + * @ManyToMany(targetEntity="DDC1595InheritedEntity2", fetch="EXTRA_LAZY") + * @JoinTable(name="entity1_entity2", + * joinColumns={@JoinColumn(name="parent", referencedColumnName="id")}, + * inverseJoinColumns={@JoinColumn(name="item", referencedColumnName="id")} + * ) + */ + protected $entities; + + public function getEntities() + { + return $this->entities; + } +} + +/** + * @Entity + * @Table(name="entity2") + */ +class DDC1595InheritedEntity2 extends DDC1595BaseInheritance +{ +} \ No newline at end of file From f26d43b3eadfbd89d95012430256b4e2d3253a3a Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 13 Jan 2012 11:24:35 +0100 Subject: [PATCH 23/83] remove whitespace tabs -> spaces added class constants updated phpdoc --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 29 ++++++++++++------- .../Tests/ORM/Tools/EntityGeneratorTest.php | 2 +- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index eae008d7f..a3f2a250c 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -47,6 +47,15 @@ use Doctrine\ORM\Mapping\ClassMetadataInfo, */ class EntityGenerator { + /** + * Specifies class fields should be protected + */ + const FIELD_VISIBLE_PROTECTED = 'protected'; + /** + * Specifies class fields should be private + */ + const FIELD_VISIBLE_PRIVATE = 'private'; + /** * @var bool */ @@ -86,8 +95,8 @@ class EntityGenerator /** Whether or not to re-generate entity class if it exists already */ private $_regenerateEntityIfExists = false; - private $_fieldVisibility = 'private'; - + private $_fieldVisibility = 'private'; + private static $_classTemplate = '() { } -'; +'; public function __construct() { @@ -300,21 +309,21 @@ public function () } /** - * Set whether or not to generate annotations for the entity + * Set the class fields visibility for the entity (can either be private or protected) * * @param bool $bool * @return void */ public function setFieldVisibility($visibility) { - if($visibility != 'private' && $visibility != 'protected') - { - throw new \InvalidArgumentException('Invalid provided visibilty (only private and protected are allowed): ' . $visibility); - } - + if($visibility != 'private' && $visibility != 'protected') + { + throw new \InvalidArgumentException('Invalid provided visibilty (only private and protected are allowed): ' . $visibility); + } + $this->_fieldVisibility = $visibility; } - + /** * Set an annotation prefix. * diff --git a/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php b/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php index 3b6c1a803..f3d84317b 100644 --- a/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php @@ -26,7 +26,7 @@ class EntityGeneratorTest extends \Doctrine\Tests\OrmTestCase $this->_generator->setGenerateStubMethods(true); $this->_generator->setRegenerateEntityIfExists(false); $this->_generator->setUpdateEntityIfExists(true); - $this->_generator->setFieldVisibility('protected'); + $this->_generator->setFieldVisibility(EntityGenerator::FIELD_VISIBLE_PROTECTED); } public function tearDown() From 69f0d70a98874695f6fbda353f0d7cf07ccc4917 Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 13 Jan 2012 14:14:28 +0100 Subject: [PATCH 24/83] fix if coding standard fix typo --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 3 +-- tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index a3f2a250c..f81f63bb3 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -316,8 +316,7 @@ public function () */ public function setFieldVisibility($visibility) { - if($visibility != 'private' && $visibility != 'protected') - { + if ($visibility != 'private' && $visibility != 'protected') { throw new \InvalidArgumentException('Invalid provided visibilty (only private and protected are allowed): ' . $visibility); } diff --git a/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php b/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php index f3d84317b..cdecdf871 100644 --- a/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php @@ -134,7 +134,7 @@ class EntityGeneratorTest extends \Doctrine\Tests\OrmTestCase $this->assertTrue($reflClass->hasProperty('id'), "Regenerating keeps property 'id'."); $this->assertTrue($reflClass->hasProperty('test'), "Check for property test failed."); - $this->assertTrue($reflClass->getProperty('test')->isProtected(), "Check for private property test failed."); + $this->assertTrue($reflClass->getProperty('test')->isProtected(), "Check for protected property test failed."); $this->assertTrue($reflClass->hasMethod('getTest'), "Check for method 'getTest' failed."); $this->assertTrue($reflClass->getMethod('getTest')->isPublic(), "Check for public visibility of method 'getTest' failed."); $this->assertTrue($reflClass->hasMethod('setTest'), "Check for method 'getTest' failed."); From 72d5d0281a9465ec9cccdf7d555ba7a8edb6a72b Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 13 Jan 2012 14:34:34 +0100 Subject: [PATCH 25/83] use !== to check field visibility use class constants --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index f81f63bb3..36ebc52ca 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -316,7 +316,7 @@ public function () */ public function setFieldVisibility($visibility) { - if ($visibility != 'private' && $visibility != 'protected') { + if ($visibility !== EntityGenerator::FIELD_VISIBLE_PRIVATE && $visibility !== EntityGenerator::FIELD_VISIBLE_PROTECTED) { throw new \InvalidArgumentException('Invalid provided visibilty (only private and protected are allowed): ' . $visibility); } From f76d3274136874ec84830006823d7e6fdcbea5df Mon Sep 17 00:00:00 2001 From: Marcel Date: Fri, 13 Jan 2012 14:43:13 +0100 Subject: [PATCH 26/83] use self:: instead of EntityGenerator:: --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index 36ebc52ca..5a7dba0c3 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -316,7 +316,7 @@ public function () */ public function setFieldVisibility($visibility) { - if ($visibility !== EntityGenerator::FIELD_VISIBLE_PRIVATE && $visibility !== EntityGenerator::FIELD_VISIBLE_PROTECTED) { + if ($visibility !== self::FIELD_VISIBLE_PRIVATE && $visibility !== self::FIELD_VISIBLE_PROTECTED) { throw new \InvalidArgumentException('Invalid provided visibilty (only private and protected are allowed): ' . $visibility); } From ea14bcff4a2a78bf774e8847b6645dca112f9757 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Fri, 13 Jan 2012 20:46:59 -0500 Subject: [PATCH 27/83] Fixed DDC-657. Added type conversion to scalar result. --- .../Internal/Hydration/AbstractHydrator.php | 3 + lib/Doctrine/ORM/Query/ResultSetMapping.php | 12 +- lib/Doctrine/ORM/Query/SqlWalker.php | 12 +- .../ORM/Functional/Ticket/DDC657Test.php | 118 ++++++++++++++++++ 4 files changed, 140 insertions(+), 5 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC657Test.php diff --git a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php index b58bfb933..e1d8cd1f2 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php @@ -210,6 +210,7 @@ abstract class AbstractHydrator case (isset($this->_rsm->scalarMappings[$key])): $cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key]; + $cache[$key]['type'] = Type::getType($this->_rsm->typeMappings[$key]); $cache[$key]['isScalar'] = true; break; @@ -232,6 +233,8 @@ abstract class AbstractHydrator } if (isset($cache[$key]['isScalar'])) { + $value = $cache[$key]['type']->convertToPHPValue($value, $this->_platform); + $rowData['scalars'][$cache[$key]['fieldName']] = $value; continue; diff --git a/lib/Doctrine/ORM/Query/ResultSetMapping.php b/lib/Doctrine/ORM/Query/ResultSetMapping.php index b35af474f..32aa298f1 100644 --- a/lib/Doctrine/ORM/Query/ResultSetMapping.php +++ b/lib/Doctrine/ORM/Query/ResultSetMapping.php @@ -71,6 +71,12 @@ class ResultSetMapping */ public $scalarMappings = array(); + /** + * @ignore + * @var array Maps column names in the result set to the alias/field type to use in the mapped result. + */ + public $typeMappings = array(); + /** * @ignore * @var array Maps entities in the result set to the alias name to use in the mapped result. @@ -296,12 +302,16 @@ class ResultSetMapping * * @param string $columnName The name of the column in the SQL result set. * @param string $alias The result alias with which the scalar result should be placed in the result structure. + * @param string $type The column type + * * @return ResultSetMapping This ResultSetMapping instance. + * * @todo Rename: addScalar */ - public function addScalarResult($columnName, $alias) + public function addScalarResult($columnName, $alias, $type = null) { $this->scalarMappings[$columnName] = $alias; + $this->typeMappings[$columnName] = $type ?: 'string'; if ( ! $this->isMixed && $this->fieldMappings) { $this->isMixed = true; diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 47263517c..f1ef94ff1 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -1084,8 +1084,10 @@ class SqlWalker implements TreeWalker $col = $sqlTableAlias . '.' . $columnName; + $fieldType = $class->getTypeOfField($fieldName); + if (isset($class->fieldMappings[$fieldName]['requireSQLConversion'])) { - $type = Type::getType($class->getTypeOfField($fieldName)); + $type = Type::getType($fieldType); $col = $type->convertToPHPValueSQL($col, $this->_conn->getDatabasePlatform()); } @@ -1094,7 +1096,7 @@ class SqlWalker implements TreeWalker $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; if ( ! $hidden) { - $this->_rsm->addScalarResult($columnAlias, $resultAlias); + $this->_rsm->addScalarResult($columnAlias, $resultAlias, $fieldType); $this->_scalarFields[$dqlAlias][$fieldName] = $columnAlias; } break; @@ -1118,7 +1120,8 @@ class SqlWalker implements TreeWalker $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; if ( ! $hidden) { - $this->_rsm->addScalarResult($columnAlias, $resultAlias); + // We cannot resolve field type here; assume 'string'. + $this->_rsm->addScalarResult($columnAlias, $resultAlias, 'string'); } break; @@ -1131,7 +1134,8 @@ class SqlWalker implements TreeWalker $this->_scalarResultAliasMap[$resultAlias] = $columnAlias; if ( ! $hidden) { - $this->_rsm->addScalarResult($columnAlias, $resultAlias); + // We cannot resolve field type here; assume 'string'. + $this->_rsm->addScalarResult($columnAlias, $resultAlias, 'string'); } break; diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC657Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC657Test.php new file mode 100644 index 000000000..a0a10a1b7 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC657Test.php @@ -0,0 +1,118 @@ +useModelSet('generic'); + parent::setUp(); + + $this->loadFixtures(); + } + + public function testEntitySingleResult() + { + $query = $this->_em->createQuery('SELECT d FROM ' . self::NS . '\DateTimeModel d'); + $datetime = $query->setMaxResults(1)->getSingleResult(); + + $this->assertTrue($datetime instanceof DateTimeModel); + + $this->assertTrue($datetime->datetime instanceof \DateTime); + $this->assertTrue($datetime->time instanceof \DateTime); + $this->assertTrue($datetime->date instanceof \DateTime); + } + + public function testScalarResult() + { + $query = $this->_em->createQuery('SELECT d.id, d.time, d.date, d.datetime FROM ' . self::NS . '\DateTimeModel d ORDER BY d.date ASC'); + $result = $query->getScalarResult(); + + $this->assertCount(2,$result); + + $this->assertEquals('11:11:11', $result[0]['time']); + $this->assertEquals('2010-01-01', $result[0]['date']); + $this->assertEquals('2010-01-01 11:11:11', $result[0]['datetime']); + + $this->assertEquals('12:12:12', $result[1]['time']); + $this->assertEquals('2010-02-02', $result[1]['date']); + $this->assertEquals('2010-02-02 12:12:12', $result[1]['datetime']); + } + + public function testaTicketEntityArrayResult() + { + $query = $this->_em->createQuery('SELECT d FROM ' . self::NS . '\DateTimeModel d ORDER BY d.date ASC'); + $result = $query->getArrayResult(); + + $this->assertCount(2,$result); + + $this->assertTrue($result[0]['datetime'] instanceof \DateTime); + $this->assertTrue($result[0]['time'] instanceof \DateTime); + $this->assertTrue($result[0]['date'] instanceof \DateTime); + + $this->assertTrue($result[1]['datetime'] instanceof \DateTime); + $this->assertTrue($result[1]['time'] instanceof \DateTime); + $this->assertTrue($result[1]['date'] instanceof \DateTime); + } + + public function testTicketSingleResult() + { + $query = $this->_em->createQuery('SELECT d.id, d.time, d.date, d.datetime FROM ' . self::NS . '\DateTimeModel d ORDER BY d.date ASC'); + $datetime = $query->setMaxResults(1)->getSingleResult(); + + $this->assertTrue(is_array($datetime)); + + $this->assertTrue($datetime['datetime'] instanceof \DateTime); + $this->assertTrue($datetime['time'] instanceof \DateTime); + $this->assertTrue($datetime['date'] instanceof \DateTime); + } + + public function testTicketResult() + { + $query = $this->_em->createQuery('SELECT d.id, d.time, d.date, d.datetime FROM ' . self::NS . '\DateTimeModel d ORDER BY d.date ASC'); + $result = $query->getResult(); + + $this->assertCount(2,$result); + + $this->assertTrue($result[0]['time'] instanceof \DateTime); + $this->assertTrue($result[0]['date'] instanceof \DateTime); + $this->assertTrue($result[0]['datetime'] instanceof \DateTime); + $this->assertEquals('2010-01-01 11:11:11', $result[0]['datetime']->format('Y-m-d G:i:s')); + + $this->assertTrue($result[1]['time'] instanceof \DateTime); + $this->assertTrue($result[1]['date'] instanceof \DateTime); + $this->assertTrue($result[1]['datetime'] instanceof \DateTime); + $this->assertEquals('2010-02-02 12:12:12', $result[1]['datetime']->format('Y-m-d G:i:s')); + } + + public function loadFixtures() + { + $timezone = new \DateTimeZone('America/Sao_Paulo'); + + $dateTime1 = new DateTimeModel(); + $dateTime2 = new DateTimeModel(); + + $dateTime1->date = new \DateTime('2010-01-01', $timezone); + $dateTime1->time = new \DateTime('2010-01-01 11:11:11', $timezone); + $dateTime1->datetime= new \DateTime('2010-01-01 11:11:11', $timezone); + + $dateTime2->date = new \DateTime('2010-02-02', $timezone); + $dateTime2->time = new \DateTime('2010-02-02 12:12:12', $timezone); + $dateTime2->datetime= new \DateTime('2010-02-02 12:12:12', $timezone); + + $this->_em->persist($dateTime1); + $this->_em->persist($dateTime2); + + $this->_em->flush(); + } +} \ No newline at end of file From 6ffe4d3dda23483c8e57845cd3717ecba99ebdd2 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 15 Jan 2012 11:27:28 +0100 Subject: [PATCH 28/83] [DDC-1601] Fix bugs in SchemaValidator, using all modelsets as testdata for a large test --- lib/Doctrine/ORM/Tools/SchemaValidator.php | 75 ++++++++++--------- .../Tests/Models/Company/CompanyPerson.php | 2 +- .../Tests/Models/DDC117/DDC117Reference.php | 4 +- .../Tests/Models/DDC117/DDC117Translation.php | 4 +- .../Models/Legacy/LegacyUserReference.php | 2 +- .../Models/Navigation/NavPointOfInterest.php | 4 +- .../ORM/Functional/SchemaValidatorTest.php | 50 +++++++++++++ 7 files changed, 99 insertions(+), 42 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/SchemaValidatorTest.php diff --git a/lib/Doctrine/ORM/Tools/SchemaValidator.php b/lib/Doctrine/ORM/Tools/SchemaValidator.php index f6bcadb72..0e1f80cdf 100644 --- a/lib/Doctrine/ORM/Tools/SchemaValidator.php +++ b/lib/Doctrine/ORM/Tools/SchemaValidator.php @@ -95,8 +95,8 @@ class SchemaValidator } foreach ($class->associationMappings AS $fieldName => $assoc) { - if (!$cmf->hasMetadataFor($assoc['targetEntity'])) { - $ce[] = "The target entity '" . $assoc['targetEntity'] . "' specified on " . $class->name . '#' . $fieldName . ' is unknown.'; + if (!class_exists($assoc['targetEntity']) || $cmf->isTransient($assoc['targetEntity'])) { + $ce[] = "The target entity '" . $assoc['targetEntity'] . "' specified on " . $class->name . '#' . $fieldName . ' is unknown or not an entity.'; return $ce; } @@ -119,7 +119,7 @@ class SchemaValidator $ce[] = "The field " . $class->name . "#" . $fieldName . " is on the inverse side of a ". "bi-directional relationship, but the specified mappedBy association on the target-entity ". $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " does not contain the required ". - "'inversedBy' attribute."; + "'inversedBy=".$fieldName."' attribute."; } else if ($targetMetadata->associationMappings[$assoc['mappedBy']]['inversedBy'] != $fieldName) { $ce[] = "The mappings " . $class->name . "#" . $fieldName . " and " . $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " are ". @@ -162,30 +162,21 @@ class SchemaValidator if ($assoc['isOwningSide']) { if ($assoc['type'] == ClassMetadataInfo::MANY_TO_MANY) { + $identifierColumns = $class->getIdentifierColumnNames(); foreach ($assoc['joinTable']['joinColumns'] AS $joinColumn) { - if (!isset($class->fieldNames[$joinColumn['referencedColumnName']])) { - $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' does not " . - "have a corresponding field with this column name on the class '" . $class->name . "'."; - break; - } - - $fieldName = $class->fieldNames[$joinColumn['referencedColumnName']]; - if (!in_array($fieldName, $class->identifier)) { + if (!in_array($joinColumn['referencedColumnName'], $identifierColumns)) { $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " . - "has to be a primary key column."; + "has to be a primary key column on the target entity class '".$class->name."'."; + break; } } - foreach ($assoc['joinTable']['inverseJoinColumns'] AS $inverseJoinColumn) { - if (!isset($targetMetadata->fieldNames[$inverseJoinColumn['referencedColumnName']])) { - $ce[] = "The inverse referenced column name '" . $inverseJoinColumn['referencedColumnName'] . "' does not " . - "have a corresponding field with this column name on the class '" . $targetMetadata->name . "'."; - break; - } - $fieldName = $targetMetadata->fieldNames[$inverseJoinColumn['referencedColumnName']]; - if (!in_array($fieldName, $targetMetadata->identifier)) { - $ce[] = "The referenced column name '" . $inverseJoinColumn['referencedColumnName'] . "' " . - "has to be a primary key column."; + $identifierColumns = $targetMetadata->getIdentifierColumnNames(); + foreach ($assoc['joinTable']['inverseJoinColumns'] AS $inverseJoinColumn) { + if (!in_array($inverseJoinColumn['referencedColumnName'], $identifierColumns)) { + $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " . + "has to be a primary key column on the target entity class '".$targetMetadata->name."'."; + break; } } @@ -204,29 +195,23 @@ class SchemaValidator } } else if ($assoc['type'] & ClassMetadataInfo::TO_ONE) { + $identifierColumns = $targetMetadata->getIdentifierColumnNames(); foreach ($assoc['joinColumns'] AS $joinColumn) { - if (!isset($targetMetadata->fieldNames[$joinColumn['referencedColumnName']])) { - $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' does not " . - "have a corresponding field with this column name on the class '" . $targetMetadata->name . "'."; - break; - } - - $fieldName = $targetMetadata->fieldNames[$joinColumn['referencedColumnName']]; - if (!in_array($fieldName, $targetMetadata->identifier)) { + if (!in_array($joinColumn['referencedColumnName'], $identifierColumns)) { $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " . - "has to be a primary key column."; + "has to be a primary key column on the target entity class '".$targetMetadata->name."'."; } } - if (count($class->getIdentifierColumnNames()) != count($assoc['joinColumns'])) { + if (count($identifierColumns) != count($assoc['joinColumns'])) { $ids = array(); foreach ($assoc['joinColumns'] AS $joinColumn) { $ids[] = $joinColumn['name']; } $ce[] = "The join columns of the association '" . $assoc['fieldName'] . "' " . - "have to match to ALL identifier columns of the source entity '". $class->name . "', " . - "however '" . implode(", ", array_diff($class->getIdentifierColumnNames(), $ids)) . + "have to match to ALL identifier columns of the target entity '". $class->name . "', " . + "however '" . implode(", ", array_diff($targetMetadata->getIdentifierColumnNames(), $ids)) . "' are missing."; } } @@ -260,6 +245,28 @@ class SchemaValidator return $ce; } + /** + * @param string $columnName + * @param ClassMetadataInfo $class + * @return bool + */ + private function columnExistsOnEntity($columnName, $class) + { + if (isset($class->fieldNames[$columnName])) { + return true; + } + foreach ($class->associationMappings as $assoc) { + if ($assoc['isOwningSide']) { + foreach ($assoc['joinColumns'] as $columnMapping) { + if ($columnMapping['name'] == $columnName) { + return true; + } + } + } + } + return false; + } + /** * Check if the Database Schema is in sync with the current metadata state. * diff --git a/tests/Doctrine/Tests/Models/Company/CompanyPerson.php b/tests/Doctrine/Tests/Models/Company/CompanyPerson.php index 712e81c83..0dfe9191c 100644 --- a/tests/Doctrine/Tests/Models/Company/CompanyPerson.php +++ b/tests/Doctrine/Tests/Models/Company/CompanyPerson.php @@ -28,7 +28,7 @@ class CompanyPerson */ private $name; /** - * @OneToOne(targetEntity="CompanyPerson", mappedBy="spouse") + * @OneToOne(targetEntity="CompanyPerson") * @JoinColumn(name="spouse_id", referencedColumnName="id") */ private $spouse; diff --git a/tests/Doctrine/Tests/Models/DDC117/DDC117Reference.php b/tests/Doctrine/Tests/Models/DDC117/DDC117Reference.php index 80cfb3a83..3c9017d19 100644 --- a/tests/Doctrine/Tests/Models/DDC117/DDC117Reference.php +++ b/tests/Doctrine/Tests/Models/DDC117/DDC117Reference.php @@ -16,7 +16,7 @@ class DDC117Reference /** * @Id - * @ManyToOne(targetEntity="DDC117Article", inversedBy="references") + * @ManyToOne(targetEntity="DDC117Article") * @JoinColumn(name="target_id", referencedColumnName="article_id") */ private $target; @@ -61,4 +61,4 @@ class DDC117Reference { return $this->description; } -} \ No newline at end of file +} diff --git a/tests/Doctrine/Tests/Models/DDC117/DDC117Translation.php b/tests/Doctrine/Tests/Models/DDC117/DDC117Translation.php index 1d38710c9..b0fb4375d 100644 --- a/tests/Doctrine/Tests/Models/DDC117/DDC117Translation.php +++ b/tests/Doctrine/Tests/Models/DDC117/DDC117Translation.php @@ -9,7 +9,7 @@ class DDC117Translation { /** * @Id - * @ManyToOne(targetEntity="DDC117Article") + * @ManyToOne(targetEntity="DDC117Article", inversedBy="translations") * @JoinColumn(name="article_id", referencedColumnName="article_id") */ private $article; @@ -62,4 +62,4 @@ class DDC117Translation { return $this->reviewedByEditors; } -} \ No newline at end of file +} diff --git a/tests/Doctrine/Tests/Models/Legacy/LegacyUserReference.php b/tests/Doctrine/Tests/Models/Legacy/LegacyUserReference.php index e666ae196..8dc20db23 100644 --- a/tests/Doctrine/Tests/Models/Legacy/LegacyUserReference.php +++ b/tests/Doctrine/Tests/Models/Legacy/LegacyUserReference.php @@ -17,7 +17,7 @@ class LegacyUserReference /** * @Id - * @ManyToOne(targetEntity="LegacyUser", inversedBy="_references") + * @ManyToOne(targetEntity="LegacyUser") * @JoinColumn(name="iUserIdTarget", referencedColumnName="iUserId") */ private $_target; diff --git a/tests/Doctrine/Tests/Models/Navigation/NavPointOfInterest.php b/tests/Doctrine/Tests/Models/Navigation/NavPointOfInterest.php index f212e68e2..662a57a09 100644 --- a/tests/Doctrine/Tests/Models/Navigation/NavPointOfInterest.php +++ b/tests/Doctrine/Tests/Models/Navigation/NavPointOfInterest.php @@ -26,7 +26,7 @@ class NavPointOfInterest private $name; /** - * @ManyToOne(targetEntity="NavCountry") + * @ManyToOne(targetEntity="NavCountry", inversedBy="pois") */ private $country; @@ -53,4 +53,4 @@ class NavPointOfInterest public function getCountry() { return $this->country; } -} \ No newline at end of file +} diff --git a/tests/Doctrine/Tests/ORM/Functional/SchemaValidatorTest.php b/tests/Doctrine/Tests/ORM/Functional/SchemaValidatorTest.php new file mode 100644 index 000000000..a575db0c0 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/SchemaValidatorTest.php @@ -0,0 +1,50 @@ + $classes) { + if ($modelSet == "customtype") { + continue; + } + $modelSets[] = array($modelSet); + } + return $modelSets; + } + + /** + * @dataProvider dataValidateModelSets + */ + public function testValidateModelSets($modelSet) + { + $validator = new SchemaValidator($this->_em); + + $classes = array(); + foreach (self::$_modelSets[$modelSet] as $className) { + $classes[] = $this->_em->getClassMetadata($className); + } + + foreach ($classes as $class) { + $ce = $validator->validateClass($class); + + foreach ($ce as $key => $error) { + if (strpos($error, "must be private or protected. Public fields may break lazy-loading.") !== false) { + unset($ce[$key]); + } + } + + $this->assertEquals(0, count($ce), "Invalid Modelset: " . $modelSet . " class " . $class->name . ": ". implode("\n", $ce)); + } + } +} From 106f10513fc71bac35249da47f5438cbff13b6bc Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 15 Jan 2012 11:27:28 +0100 Subject: [PATCH 29/83] [DDC-1601] Fix bugs in SchemaValidator, using all modelsets as testdata for a large test --- lib/Doctrine/ORM/Tools/SchemaValidator.php | 75 ++++++++++--------- .../Tests/Models/Company/CompanyPerson.php | 2 +- .../Tests/Models/DDC117/DDC117Reference.php | 4 +- .../Tests/Models/DDC117/DDC117Translation.php | 4 +- .../Models/Legacy/LegacyUserReference.php | 2 +- .../Models/Navigation/NavPointOfInterest.php | 4 +- .../ORM/Functional/SchemaValidatorTest.php | 50 +++++++++++++ 7 files changed, 99 insertions(+), 42 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/SchemaValidatorTest.php diff --git a/lib/Doctrine/ORM/Tools/SchemaValidator.php b/lib/Doctrine/ORM/Tools/SchemaValidator.php index f6bcadb72..0e1f80cdf 100644 --- a/lib/Doctrine/ORM/Tools/SchemaValidator.php +++ b/lib/Doctrine/ORM/Tools/SchemaValidator.php @@ -95,8 +95,8 @@ class SchemaValidator } foreach ($class->associationMappings AS $fieldName => $assoc) { - if (!$cmf->hasMetadataFor($assoc['targetEntity'])) { - $ce[] = "The target entity '" . $assoc['targetEntity'] . "' specified on " . $class->name . '#' . $fieldName . ' is unknown.'; + if (!class_exists($assoc['targetEntity']) || $cmf->isTransient($assoc['targetEntity'])) { + $ce[] = "The target entity '" . $assoc['targetEntity'] . "' specified on " . $class->name . '#' . $fieldName . ' is unknown or not an entity.'; return $ce; } @@ -119,7 +119,7 @@ class SchemaValidator $ce[] = "The field " . $class->name . "#" . $fieldName . " is on the inverse side of a ". "bi-directional relationship, but the specified mappedBy association on the target-entity ". $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " does not contain the required ". - "'inversedBy' attribute."; + "'inversedBy=".$fieldName."' attribute."; } else if ($targetMetadata->associationMappings[$assoc['mappedBy']]['inversedBy'] != $fieldName) { $ce[] = "The mappings " . $class->name . "#" . $fieldName . " and " . $assoc['targetEntity'] . "#" . $assoc['mappedBy'] . " are ". @@ -162,30 +162,21 @@ class SchemaValidator if ($assoc['isOwningSide']) { if ($assoc['type'] == ClassMetadataInfo::MANY_TO_MANY) { + $identifierColumns = $class->getIdentifierColumnNames(); foreach ($assoc['joinTable']['joinColumns'] AS $joinColumn) { - if (!isset($class->fieldNames[$joinColumn['referencedColumnName']])) { - $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' does not " . - "have a corresponding field with this column name on the class '" . $class->name . "'."; - break; - } - - $fieldName = $class->fieldNames[$joinColumn['referencedColumnName']]; - if (!in_array($fieldName, $class->identifier)) { + if (!in_array($joinColumn['referencedColumnName'], $identifierColumns)) { $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " . - "has to be a primary key column."; + "has to be a primary key column on the target entity class '".$class->name."'."; + break; } } - foreach ($assoc['joinTable']['inverseJoinColumns'] AS $inverseJoinColumn) { - if (!isset($targetMetadata->fieldNames[$inverseJoinColumn['referencedColumnName']])) { - $ce[] = "The inverse referenced column name '" . $inverseJoinColumn['referencedColumnName'] . "' does not " . - "have a corresponding field with this column name on the class '" . $targetMetadata->name . "'."; - break; - } - $fieldName = $targetMetadata->fieldNames[$inverseJoinColumn['referencedColumnName']]; - if (!in_array($fieldName, $targetMetadata->identifier)) { - $ce[] = "The referenced column name '" . $inverseJoinColumn['referencedColumnName'] . "' " . - "has to be a primary key column."; + $identifierColumns = $targetMetadata->getIdentifierColumnNames(); + foreach ($assoc['joinTable']['inverseJoinColumns'] AS $inverseJoinColumn) { + if (!in_array($inverseJoinColumn['referencedColumnName'], $identifierColumns)) { + $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " . + "has to be a primary key column on the target entity class '".$targetMetadata->name."'."; + break; } } @@ -204,29 +195,23 @@ class SchemaValidator } } else if ($assoc['type'] & ClassMetadataInfo::TO_ONE) { + $identifierColumns = $targetMetadata->getIdentifierColumnNames(); foreach ($assoc['joinColumns'] AS $joinColumn) { - if (!isset($targetMetadata->fieldNames[$joinColumn['referencedColumnName']])) { - $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' does not " . - "have a corresponding field with this column name on the class '" . $targetMetadata->name . "'."; - break; - } - - $fieldName = $targetMetadata->fieldNames[$joinColumn['referencedColumnName']]; - if (!in_array($fieldName, $targetMetadata->identifier)) { + if (!in_array($joinColumn['referencedColumnName'], $identifierColumns)) { $ce[] = "The referenced column name '" . $joinColumn['referencedColumnName'] . "' " . - "has to be a primary key column."; + "has to be a primary key column on the target entity class '".$targetMetadata->name."'."; } } - if (count($class->getIdentifierColumnNames()) != count($assoc['joinColumns'])) { + if (count($identifierColumns) != count($assoc['joinColumns'])) { $ids = array(); foreach ($assoc['joinColumns'] AS $joinColumn) { $ids[] = $joinColumn['name']; } $ce[] = "The join columns of the association '" . $assoc['fieldName'] . "' " . - "have to match to ALL identifier columns of the source entity '". $class->name . "', " . - "however '" . implode(", ", array_diff($class->getIdentifierColumnNames(), $ids)) . + "have to match to ALL identifier columns of the target entity '". $class->name . "', " . + "however '" . implode(", ", array_diff($targetMetadata->getIdentifierColumnNames(), $ids)) . "' are missing."; } } @@ -260,6 +245,28 @@ class SchemaValidator return $ce; } + /** + * @param string $columnName + * @param ClassMetadataInfo $class + * @return bool + */ + private function columnExistsOnEntity($columnName, $class) + { + if (isset($class->fieldNames[$columnName])) { + return true; + } + foreach ($class->associationMappings as $assoc) { + if ($assoc['isOwningSide']) { + foreach ($assoc['joinColumns'] as $columnMapping) { + if ($columnMapping['name'] == $columnName) { + return true; + } + } + } + } + return false; + } + /** * Check if the Database Schema is in sync with the current metadata state. * diff --git a/tests/Doctrine/Tests/Models/Company/CompanyPerson.php b/tests/Doctrine/Tests/Models/Company/CompanyPerson.php index 712e81c83..0dfe9191c 100644 --- a/tests/Doctrine/Tests/Models/Company/CompanyPerson.php +++ b/tests/Doctrine/Tests/Models/Company/CompanyPerson.php @@ -28,7 +28,7 @@ class CompanyPerson */ private $name; /** - * @OneToOne(targetEntity="CompanyPerson", mappedBy="spouse") + * @OneToOne(targetEntity="CompanyPerson") * @JoinColumn(name="spouse_id", referencedColumnName="id") */ private $spouse; diff --git a/tests/Doctrine/Tests/Models/DDC117/DDC117Reference.php b/tests/Doctrine/Tests/Models/DDC117/DDC117Reference.php index 80cfb3a83..3c9017d19 100644 --- a/tests/Doctrine/Tests/Models/DDC117/DDC117Reference.php +++ b/tests/Doctrine/Tests/Models/DDC117/DDC117Reference.php @@ -16,7 +16,7 @@ class DDC117Reference /** * @Id - * @ManyToOne(targetEntity="DDC117Article", inversedBy="references") + * @ManyToOne(targetEntity="DDC117Article") * @JoinColumn(name="target_id", referencedColumnName="article_id") */ private $target; @@ -61,4 +61,4 @@ class DDC117Reference { return $this->description; } -} \ No newline at end of file +} diff --git a/tests/Doctrine/Tests/Models/DDC117/DDC117Translation.php b/tests/Doctrine/Tests/Models/DDC117/DDC117Translation.php index 1d38710c9..b0fb4375d 100644 --- a/tests/Doctrine/Tests/Models/DDC117/DDC117Translation.php +++ b/tests/Doctrine/Tests/Models/DDC117/DDC117Translation.php @@ -9,7 +9,7 @@ class DDC117Translation { /** * @Id - * @ManyToOne(targetEntity="DDC117Article") + * @ManyToOne(targetEntity="DDC117Article", inversedBy="translations") * @JoinColumn(name="article_id", referencedColumnName="article_id") */ private $article; @@ -62,4 +62,4 @@ class DDC117Translation { return $this->reviewedByEditors; } -} \ No newline at end of file +} diff --git a/tests/Doctrine/Tests/Models/Legacy/LegacyUserReference.php b/tests/Doctrine/Tests/Models/Legacy/LegacyUserReference.php index e666ae196..8dc20db23 100644 --- a/tests/Doctrine/Tests/Models/Legacy/LegacyUserReference.php +++ b/tests/Doctrine/Tests/Models/Legacy/LegacyUserReference.php @@ -17,7 +17,7 @@ class LegacyUserReference /** * @Id - * @ManyToOne(targetEntity="LegacyUser", inversedBy="_references") + * @ManyToOne(targetEntity="LegacyUser") * @JoinColumn(name="iUserIdTarget", referencedColumnName="iUserId") */ private $_target; diff --git a/tests/Doctrine/Tests/Models/Navigation/NavPointOfInterest.php b/tests/Doctrine/Tests/Models/Navigation/NavPointOfInterest.php index f212e68e2..662a57a09 100644 --- a/tests/Doctrine/Tests/Models/Navigation/NavPointOfInterest.php +++ b/tests/Doctrine/Tests/Models/Navigation/NavPointOfInterest.php @@ -26,7 +26,7 @@ class NavPointOfInterest private $name; /** - * @ManyToOne(targetEntity="NavCountry") + * @ManyToOne(targetEntity="NavCountry", inversedBy="pois") */ private $country; @@ -53,4 +53,4 @@ class NavPointOfInterest public function getCountry() { return $this->country; } -} \ No newline at end of file +} diff --git a/tests/Doctrine/Tests/ORM/Functional/SchemaValidatorTest.php b/tests/Doctrine/Tests/ORM/Functional/SchemaValidatorTest.php new file mode 100644 index 000000000..a575db0c0 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/SchemaValidatorTest.php @@ -0,0 +1,50 @@ + $classes) { + if ($modelSet == "customtype") { + continue; + } + $modelSets[] = array($modelSet); + } + return $modelSets; + } + + /** + * @dataProvider dataValidateModelSets + */ + public function testValidateModelSets($modelSet) + { + $validator = new SchemaValidator($this->_em); + + $classes = array(); + foreach (self::$_modelSets[$modelSet] as $className) { + $classes[] = $this->_em->getClassMetadata($className); + } + + foreach ($classes as $class) { + $ce = $validator->validateClass($class); + + foreach ($ce as $key => $error) { + if (strpos($error, "must be private or protected. Public fields may break lazy-loading.") !== false) { + unset($ce[$key]); + } + } + + $this->assertEquals(0, count($ce), "Invalid Modelset: " . $modelSet . " class " . $class->name . ": ". implode("\n", $ce)); + } + } +} From 9950af2f58da8194c9d3762b26cce73fbfbd7709 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 15 Jan 2012 12:12:08 +0100 Subject: [PATCH 30/83] [DDC-1601] Fix failing test and remove unused code --- lib/Doctrine/ORM/Tools/SchemaValidator.php | 22 ------------------- .../Tests/ORM/Tools/SchemaValidatorTest.php | 4 ++-- 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/SchemaValidator.php b/lib/Doctrine/ORM/Tools/SchemaValidator.php index 0e1f80cdf..c8f41bc07 100644 --- a/lib/Doctrine/ORM/Tools/SchemaValidator.php +++ b/lib/Doctrine/ORM/Tools/SchemaValidator.php @@ -245,28 +245,6 @@ class SchemaValidator return $ce; } - /** - * @param string $columnName - * @param ClassMetadataInfo $class - * @return bool - */ - private function columnExistsOnEntity($columnName, $class) - { - if (isset($class->fieldNames[$columnName])) { - return true; - } - foreach ($class->associationMappings as $assoc) { - if ($assoc['isOwningSide']) { - foreach ($assoc['joinColumns'] as $columnMapping) { - if ($columnMapping['name'] == $columnName) { - return true; - } - } - } - } - return false; - } - /** * Check if the Database Schema is in sync with the current metadata state. * diff --git a/tests/Doctrine/Tests/ORM/Tools/SchemaValidatorTest.php b/tests/Doctrine/Tests/ORM/Tools/SchemaValidatorTest.php index 1d68a9bcb..7b77fe507 100644 --- a/tests/Doctrine/Tests/ORM/Tools/SchemaValidatorTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/SchemaValidatorTest.php @@ -103,8 +103,8 @@ class SchemaValidatorTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals( array( - "The referenced column name 'id' does not have a corresponding field with this column name on the class 'Doctrine\Tests\ORM\Tools\InvalidEntity1'.", - "The join columns of the association 'assoc' have to match to ALL identifier columns of the source entity 'Doctrine\Tests\ORM\Tools\InvalidEntity2', however 'key3, key4' are missing." + "The referenced column name 'id' has to be a primary key column on the target entity class 'Doctrine\Tests\ORM\Tools\InvalidEntity1'.", + "The join columns of the association 'assoc' have to match to ALL identifier columns of the target entity 'Doctrine\Tests\ORM\Tools\InvalidEntity2', however 'key1, key2' are missing." ), $ce ); From cd6b584722388fa5536aa398f4d86d8e16e275eb Mon Sep 17 00:00:00 2001 From: Daniel Holmes Date: Sun, 15 Jan 2012 22:31:01 +1100 Subject: [PATCH 31/83] Fix namespace of BasicEntityPersisterTypeValueSqlTest --- .../ORM/Persisters/BasicEntityPersisterTypeValueSqlTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Doctrine/Tests/ORM/Persisters/BasicEntityPersisterTypeValueSqlTest.php b/tests/Doctrine/Tests/ORM/Persisters/BasicEntityPersisterTypeValueSqlTest.php index d1fe9a6b5..f31fb2b2f 100644 --- a/tests/Doctrine/Tests/ORM/Persisters/BasicEntityPersisterTypeValueSqlTest.php +++ b/tests/Doctrine/Tests/ORM/Persisters/BasicEntityPersisterTypeValueSqlTest.php @@ -1,6 +1,6 @@ Date: Sun, 15 Jan 2012 23:25:57 +1100 Subject: [PATCH 32/83] Added fix for collection->contains when many-to-many extra lazy fetchMode --- .../ORM/Persisters/ManyToManyPersister.php | 4 +- .../ManyToManyExtraLazyContainsTest.php | 55 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/ManyToManyExtraLazyContainsTest.php diff --git a/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php b/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php index 33dab21d1..4286264f0 100644 --- a/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php +++ b/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php @@ -254,7 +254,9 @@ class ManyToManyPersister extends AbstractCollectionPersister $uow = $this->_em->getUnitOfWork(); // shortcut for new entities - if ($uow->getEntityState($element, UnitOfWork::STATE_NEW) == UnitOfWork::STATE_NEW) { + $entityState = $uow->getEntityState($element, UnitOfWork::STATE_NEW); + if ($entityState == UnitOfWork::STATE_NEW || + ($entityState == UnitOfWork::STATE_MANAGED && $uow->isScheduledForInsert($element))) { return false; } diff --git a/tests/Doctrine/Tests/ORM/Functional/ManyToManyExtraLazyContainsTest.php b/tests/Doctrine/Tests/ORM/Functional/ManyToManyExtraLazyContainsTest.php new file mode 100644 index 000000000..bbb26fcea --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/ManyToManyExtraLazyContainsTest.php @@ -0,0 +1,55 @@ +useModelSet('company'); + parent::setUp(); + } + + public function testManyToManyExtraLazyContainsAddedPendingInsertEntityIsTrue() + { + $contract = new \Doctrine\Tests\Models\Company\CompanyFlexContract(); + + $this->_em->persist($contract); + $this->_em->flush(); + + $this->_em->clear(); + $contract = $this->_em->find('Doctrine\Tests\Models\Company\CompanyFlexContract', $contract->getId()); + + $pendingInsertManager = new \Doctrine\Tests\Models\Company\CompanyManager(); + $this->_em->persist($pendingInsertManager); + $contract->getManagers()->add($pendingInsertManager); + + $result = $contract->getManagers()->contains($pendingInsertManager); + + $this->assertTrue($result); + } + + public function testManyToManyExtraLazyContainsNonAddedPendingInsertEntityIsFalse() + { + $contract = new \Doctrine\Tests\Models\Company\CompanyFlexContract(); + + $this->_em->persist($contract); + $this->_em->flush(); + + $this->_em->clear(); + $contract = $this->_em->find('Doctrine\Tests\Models\Company\CompanyFlexContract', $contract->getId()); + + $pendingInsertManager = new \Doctrine\Tests\Models\Company\CompanyManager(); + $this->_em->persist($pendingInsertManager); + + $result = $contract->getManagers()->contains($pendingInsertManager); + + $this->assertFalse($result); + } +} \ No newline at end of file From 36ce26691d32b701964e17744f45c6389985e383 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 15 Jan 2012 14:58:56 +0100 Subject: [PATCH 33/83] DDC-1585 - Throw exception if setting target entity of the wrong type to an assocation. --- lib/Doctrine/ORM/UnitOfWork.php | 9 +++++++++ .../ORM/Functional/BasicFunctionalTest.php | 17 +++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 0ebdedcb9..6142ca958 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -707,6 +707,15 @@ class UnitOfWork implements PropertyChangedListener $state = $this->getEntityState($entry, self::STATE_NEW); $oid = spl_object_hash($entry); + if (!($entry instanceof $assoc['targetEntity'])) { + throw new ORMException(sprintf("Found entity of type %s on association %s#%s, but expecting %s", + get_class($entry), + $assoc['sourceEntity'], + $assoc['fieldName'], + $targetClass->name + )); + } + switch ($state) { case self::STATE_NEW: if ( ! $assoc['isCascadePersist']) { diff --git a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php index 9b68098c0..3955387e0 100644 --- a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php @@ -1197,4 +1197,21 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase $user2 = $this->_em->find(get_class($user2), $user2->id); $this->assertEquals('developer', $user2->status); } + + /** + * @group DDC-1585 + */ + public function testWrongAssocationInstance() + { + $user = new CmsUser; + $user->name = 'Dominik'; + $user->username = 'domnikl'; + $user->status = 'developer'; + $user->address = $user; + + $this->_em->persist($user); + + $this->setExpectedException("Doctrine\ORM\ORMException", "Found entity of type Doctrine\Tests\Models\CMS\CmsUser on association Doctrine\Tests\Models\CMS\CmsUser#address, but expecting Doctrine\Tests\Models\CMS\CmsAddress"); + $this->_em->flush(); + } } From 56ea4872cafd173b9dcdf287c9f26d2781bbda8e Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 15 Jan 2012 15:48:44 +0100 Subject: [PATCH 34/83] DDC-1594 - Fix problem with merge and an existing managed proxy instance. --- lib/Doctrine/ORM/UnitOfWork.php | 4 ++ .../ORM/Functional/Ticket/DDC1594Test.php | 45 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1594Test.php diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 6142ca958..49b3fcd34 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1685,6 +1685,10 @@ class UnitOfWork implements PropertyChangedListener $class->setIdentifierValues($managedCopy, $id); $this->persistNew($class, $managedCopy); + } else { + if ($managedCopy instanceof Proxy && ! $managedCopy->__isInitialized__) { + $managedCopy->__load(); + } } } diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1594Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1594Test.php new file mode 100644 index 000000000..b8dec401c --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1594Test.php @@ -0,0 +1,45 @@ +useModelSet('cms'); + parent::setUp(); + } + + public function testIssue() + { + $user = new CmsUser(); + $user->status = 'foo'; + $user->username = 'foo'; + $user->name = 'foo'; + + $this->_em->persist($user); + $this->_em->flush(); + + $this->_em->clear(); + $detachedUser = clone $user; + $detachedUser->name = 'bar'; + $detachedUser->status = 'bar'; + + $newUser = $this->_em->getReference(get_class($user), $user->id); + + $mergedUser = $this->_em->merge($detachedUser); + + $this->assertNotSame($mergedUser, $detachedUser); + $this->assertEquals('bar', $detachedUser->getName()); + $this->assertEquals('bar', $mergedUser->getName()); + } +} From a12e5ac8a719e332ca18e47c34a6832ac265b75b Mon Sep 17 00:00:00 2001 From: Daniel Holmes Date: Mon, 16 Jan 2012 08:12:11 +1100 Subject: [PATCH 35/83] Updated some comparisons to strict equality --- lib/Doctrine/ORM/Persisters/ManyToManyPersister.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php b/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php index 4286264f0..54dc2141d 100644 --- a/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php +++ b/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php @@ -255,8 +255,8 @@ class ManyToManyPersister extends AbstractCollectionPersister // shortcut for new entities $entityState = $uow->getEntityState($element, UnitOfWork::STATE_NEW); - if ($entityState == UnitOfWork::STATE_NEW || - ($entityState == UnitOfWork::STATE_MANAGED && $uow->isScheduledForInsert($element))) { + if ($entityState === UnitOfWork::STATE_NEW || + ($entityState === UnitOfWork::STATE_MANAGED && $uow->isScheduledForInsert($element))) { return false; } @@ -277,7 +277,7 @@ class ManyToManyPersister extends AbstractCollectionPersister $uow = $this->_em->getUnitOfWork(); // shortcut for new entities - if ($uow->getEntityState($element, UnitOfWork::STATE_NEW) == UnitOfWork::STATE_NEW) { + if ($uow->getEntityState($element, UnitOfWork::STATE_NEW) === UnitOfWork::STATE_NEW) { return false; } From 56c49fedd28489b5d6509c7b5e3565948dfdba26 Mon Sep 17 00:00:00 2001 From: armetiz Date: Mon, 16 Jan 2012 10:30:15 +0100 Subject: [PATCH 36/83] Unique key name isn't correctly set - DDC-1603 --- lib/Doctrine/ORM/Tools/SchemaTool.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/SchemaTool.php b/lib/Doctrine/ORM/Tools/SchemaTool.php index 2e9feb1f5..60760e66c 100644 --- a/lib/Doctrine/ORM/Tools/SchemaTool.php +++ b/lib/Doctrine/ORM/Tools/SchemaTool.php @@ -223,7 +223,7 @@ class SchemaTool if (isset($class->table['indexes'])) { foreach ($class->table['indexes'] AS $indexName => $indexData) { - $table->addIndex($indexData['columns'], $indexName); + $table->addIndex($indexData['columns'], is_numeric($indexName) ? null : $indexName); } } From a029b284234065a03dab4241b29f0325975eeac6 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 16 Jan 2012 10:57:29 +0100 Subject: [PATCH 37/83] [DDC-1604] Have ORM Proxy implement new \Doctrine\Common\Persistence\Proxy * Adjust ProxyFactory to generate proxies according to new naming schema. * Change proxy naming and file-name generation to be a bit more consistent than previous approach. [DDC-1598] Additional regexp to check for simple ID methods to make it even more safe. --- lib/Doctrine/ORM/Proxy/Proxy.php | 6 +- lib/Doctrine/ORM/Proxy/ProxyFactory.php | 84 ++++++++++++++----- .../ORM/Functional/ReferenceProxyTest.php | 24 ++++++ .../ORM/Proxy/ProxyClassGeneratorTest.php | 18 ++-- 4 files changed, 101 insertions(+), 31 deletions(-) diff --git a/lib/Doctrine/ORM/Proxy/Proxy.php b/lib/Doctrine/ORM/Proxy/Proxy.php index 5eaff19fe..09e2b33ef 100644 --- a/lib/Doctrine/ORM/Proxy/Proxy.php +++ b/lib/Doctrine/ORM/Proxy/Proxy.php @@ -1,7 +1,5 @@ * @since 2.0 */ -interface Proxy {} \ No newline at end of file +interface Proxy extends BaseProxy {} diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index cf7048549..958a0c7e6 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -21,7 +21,8 @@ namespace Doctrine\ORM\Proxy; use Doctrine\ORM\EntityManager, Doctrine\ORM\Mapping\ClassMetadata, - Doctrine\ORM\Mapping\AssociationMapping; + Doctrine\ORM\Mapping\AssociationMapping, + Doctrine\Common\Util\ClassUtils; /** * This factory is used to create proxy objects for entities at runtime. @@ -41,6 +42,14 @@ class ProxyFactory /** The directory that contains all proxy classes. */ private $_proxyDir; + /** + * Used to match very simple id methods that don't need + * to be proxied since the identifier is known. + * + * @var string + */ + const PATTERN_MATCH_ID_METHOD = '((public\s)?(function\s{1,}%s\s?\(\)\s{1,})\s{0,}{\s{0,}return\s{0,}\$this->%s;\s{0,}})i'; + /** * Initializes a new instance of the ProxyFactory class that is * connected to the given EntityManager. @@ -74,13 +83,12 @@ class ProxyFactory */ public function getProxy($className, $identifier) { - $proxyClassName = str_replace('\\', '', $className) . 'Proxy'; - $fqn = $this->_proxyNamespace . '\\' . $proxyClassName; + $fqn = ClassUtils::generateProxyClassName($className, $this->_proxyNamespace); if (! class_exists($fqn, false)) { - $fileName = $this->_proxyDir . DIRECTORY_SEPARATOR . $proxyClassName . '.php'; + $fileName = $this->getProxyFileName($className); if ($this->_autoGenerate) { - $this->_generateProxyClass($this->_em->getClassMetadata($className), $proxyClassName, $fileName, self::$_proxyClassTemplate); + $this->_generateProxyClass($this->_em->getClassMetadata($className), $fileName, self::$_proxyClassTemplate); } require $fileName; } @@ -94,6 +102,17 @@ class ProxyFactory return new $fqn($entityPersister, $identifier); } + /** + * Generate the Proxy file name + * + * @param string $className + * @return string + */ + private function getProxyFileName($className) + { + return $this->_proxyDir . DIRECTORY_SEPARATOR . '__CG__' . str_replace('\\', '', $className) . '.php'; + } + /** * Generates proxy classes for all given classes. * @@ -112,9 +131,8 @@ class ProxyFactory continue; } - $proxyClassName = str_replace('\\', '', $class->name) . 'Proxy'; - $proxyFileName = $proxyDir . $proxyClassName . '.php'; - $this->_generateProxyClass($class, $proxyClassName, $proxyFileName, self::$_proxyClassTemplate); + $proxyFileName = $this->getProxyFileName($class->name); + $this->_generateProxyClass($class, $proxyFileName, self::$_proxyClassTemplate); } } @@ -122,11 +140,10 @@ class ProxyFactory * Generates a proxy class file. * * @param $class - * @param $originalClassName * @param $proxyClassName * @param $file The path of the file to write to. */ - private function _generateProxyClass($class, $proxyClassName, $fileName, $file) + private function _generateProxyClass($class, $fileName, $file) { $methods = $this->_generateMethods($class); $sleepImpl = $this->_generateSleep($class); @@ -138,16 +155,19 @@ class ProxyFactory '', '', '' ); - if(substr($class->name, 0, 1) == "\\") { - $className = substr($class->name, 1); - } else { - $className = $class->name; - } + $className = ltrim($class->name, '\\'); + $proxyClassName = ClassUtils::generateProxyClassName($class->name, $this->_proxyNamespace); + $parts = explode('\\', strrev($proxyClassName), 2); + $proxyClassNamespace = strrev($parts[1]); + $proxyClassName = strrev($parts[0]); $replacements = array( - $this->_proxyNamespace, - $proxyClassName, $className, - $methods, $sleepImpl, $cloneImpl + $proxyClassNamespace, + $proxyClassName, + $className, + $methods, + $sleepImpl, + $cloneImpl ); $file = str_replace($placeholders, $replacements, $file); @@ -230,6 +250,14 @@ class ProxyFactory } /** + * Check if the method is a short identifier getter. + * + * What does this mean? For proxy objects the identifier is already known, + * however accessing the getter for this identifier usually triggers the + * lazy loading, leading to a query that may not be necessary if only the + * ID is interesting for the userland code (for example in views that + * generate links to the entity, but do not display anything else). + * * @param ReflectionMethod $method * @param ClassMetadata $class * @return bool @@ -237,7 +265,7 @@ class ProxyFactory private function isShortIdentifierGetter($method, $class) { $identifier = lcfirst(substr($method->getName(), 3)); - return ( + $cheapCheck = ( $method->getNumberOfParameters() == 0 && substr($method->getName(), 0, 3) == "get" && in_array($identifier, $class->identifier, true) && @@ -245,6 +273,18 @@ class ProxyFactory (($method->getEndLine() - $method->getStartLine()) <= 4) && in_array($class->fieldMappings[$identifier]['type'], array('integer', 'bigint', 'smallint', 'string')) ); + + if ($cheapCheck) { + $code = file($class->reflClass->getFileName()); + $code = trim(implode(" ", array_slice($code, $method->getStartLine() - 1, $method->getEndLine() - $method->getStartLine() + 1))); + + $pattern = sprintf(self::PATTERN_MATCH_ID_METHOD, $method->getName(), $identifier); + + if (preg_match($pattern, $code)) { + return true; + } + } + return false; } /** @@ -318,6 +358,12 @@ class extends \ implements \Doctrine\ORM\Proxy\Proxy } } + /** @private */ + public function __isInitialized() + { + return $this->__isInitialized__; + } + public function __sleep() diff --git a/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php b/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php index 9cd216066..676d41878 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php @@ -195,4 +195,28 @@ class ReferenceProxyTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals('Doctrine Cookbook', $entity->getName()); $this->assertTrue($entity->__isInitialized__, "Getting something other than the identifier initializes the proxy."); } + + /** + * @group DDC-1604 + */ + public function testCommonPersistenceProxy() + { + $id = $this->createProduct(); + + /* @var $entity Doctrine\Tests\Models\ECommerce\ECommerceProduct */ + $entity = $this->_em->getReference('Doctrine\Tests\Models\ECommerce\ECommerceProduct' , $id); + $className = \Doctrine\Common\Util\ClassUtils::getClass($entity); + + $this->assertInstanceOf('Doctrine\Common\Persistence\Proxy', $entity); + $this->assertFalse($entity->__isInitialized()); + $this->assertEquals('Doctrine\Tests\Models\ECommerce\ECommerceProduct', $className); + + $restName = str_replace($this->_em->getConfiguration()->getProxyNamespace(), "", get_class($entity)); + $restName = substr(get_class($entity), strlen($this->_em->getConfiguration()->getProxyNamespace()) +1); + $proxyFileName = $this->_em->getConfiguration()->getProxyDir() . DIRECTORY_SEPARATOR . str_replace("\\", "", $restName) . ".php"; + $this->assertTrue(file_exists($proxyFileName), "Proxy file name cannot be found generically."); + + $entity->__load(); + $this->assertTrue($entity->__isInitialized()); + } } diff --git a/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php b/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php index a45ca2c1f..ace2bb57b 100644 --- a/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php +++ b/tests/Doctrine/Tests/ORM/Proxy/ProxyClassGeneratorTest.php @@ -52,7 +52,7 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase public function testReferenceProxyDelegatesLoadingToThePersister() { $identifier = array('id' => 42); - $proxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureProxy'; + $proxyClass = 'Proxies\__CG__\Doctrine\Tests\Models\ECommerce\ECommerceFeature'; $persister = $this->_getMockPersister(); $this->_uowMock->setEntityPersister('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $persister); @@ -69,7 +69,7 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase public function testReferenceProxyExecutesLoadingOnlyOnce() { $identifier = array('id' => 42); - $proxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureProxy'; + $proxyClass = 'Proxies\__CG__\Doctrine\Tests\Models\ECommerce\ECommerceFeature'; $persister = $this->_getMockPersister(); $this->_uowMock->setEntityPersister('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $persister); $proxy = $this->_proxyFactory->getProxy('Doctrine\Tests\Models\ECommerce\ECommerceFeature', $identifier); @@ -108,7 +108,7 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase public function testCreatesAssociationProxyAsSubclassOfTheOriginalOne() { - $proxyClass = 'Proxies\DoctrineTestsModelsECommerceECommerceFeatureProxy'; + $proxyClass = 'Proxies\__CG__\Doctrine\Tests\Models\ECommerce\ECommerceFeature'; $this->assertTrue(is_subclass_of($proxyClass, 'Doctrine\Tests\Models\ECommerce\ECommerceFeature')); } @@ -125,7 +125,7 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase require_once dirname(__FILE__)."/fixtures/NonNamespacedProxies.php"; $className = "\DoctrineOrmTestEntity"; - $proxyName = "DoctrineOrmTestEntityProxy"; + $proxyName = "DoctrineOrmTestEntity"; $classMetadata = new \Doctrine\ORM\Mapping\ClassMetadata($className); $classMetadata->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); $classMetadata->mapField(array('fieldName' => 'id', 'type' => 'integer')); @@ -133,16 +133,16 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase $this->_proxyFactory->generateProxyClasses(array($classMetadata)); - $classCode = file_get_contents(dirname(__FILE__)."/generated/".$proxyName.".php"); + $classCode = file_get_contents(dirname(__FILE__)."/generated/__CG__".$proxyName.".php"); - $this->assertNotContains("class DoctrineOrmTestEntityProxy extends \\\\DoctrineOrmTestEntity", $classCode); - $this->assertContains("class DoctrineOrmTestEntityProxy extends \\DoctrineOrmTestEntity", $classCode); + $this->assertNotContains("class DoctrineOrmTestEntity extends \\\\DoctrineOrmTestEntity", $classCode); + $this->assertContains("class DoctrineOrmTestEntity extends \\DoctrineOrmTestEntity", $classCode); } public function testClassWithSleepProxyGeneration() { $className = "\Doctrine\Tests\ORM\Proxy\SleepClass"; - $proxyName = "DoctrineTestsORMProxySleepClassProxy"; + $proxyName = "DoctrineTestsORMProxySleepClass"; $classMetadata = new \Doctrine\ORM\Mapping\ClassMetadata($className); $classMetadata->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); $classMetadata->mapField(array('fieldName' => 'id', 'type' => 'integer')); @@ -150,7 +150,7 @@ class ProxyClassGeneratorTest extends \Doctrine\Tests\OrmTestCase $this->_proxyFactory->generateProxyClasses(array($classMetadata)); - $classCode = file_get_contents(dirname(__FILE__)."/generated/".$proxyName.".php"); + $classCode = file_get_contents(dirname(__FILE__)."/generated/__CG__".$proxyName.".php"); $this->assertEquals(1, substr_count($classCode, 'function __sleep')); } From 21c9be74c93394711366afe436e000816f39ff81 Mon Sep 17 00:00:00 2001 From: armetiz Date: Mon, 16 Jan 2012 13:54:04 +0100 Subject: [PATCH 38/83] Update lib/Doctrine/ORM/Tools/SchemaTool.php --- lib/Doctrine/ORM/Tools/SchemaTool.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/SchemaTool.php b/lib/Doctrine/ORM/Tools/SchemaTool.php index 60760e66c..b0d14eb26 100644 --- a/lib/Doctrine/ORM/Tools/SchemaTool.php +++ b/lib/Doctrine/ORM/Tools/SchemaTool.php @@ -229,7 +229,7 @@ class SchemaTool if (isset($class->table['uniqueConstraints'])) { foreach ($class->table['uniqueConstraints'] AS $indexName => $indexData) { - $table->addUniqueIndex($indexData['columns'], $indexName); + $table->addUniqueIndex($indexData['columns'], is_numeric($indexName) ? null : $indexName); } } From fdb2b9c655220049dd764bbe5b0d553bd0905261 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Mon, 16 Jan 2012 14:26:13 -0500 Subject: [PATCH 39/83] Optimized scalar type mapping support. --- lib/Doctrine/ORM/Query/ResultSetMapping.php | 4 +- .../ORM/Functional/Ticket/DDC657Test.php | 40 ++++++++++--------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/lib/Doctrine/ORM/Query/ResultSetMapping.php b/lib/Doctrine/ORM/Query/ResultSetMapping.php index 32aa298f1..c62d8ac5c 100644 --- a/lib/Doctrine/ORM/Query/ResultSetMapping.php +++ b/lib/Doctrine/ORM/Query/ResultSetMapping.php @@ -308,10 +308,10 @@ class ResultSetMapping * * @todo Rename: addScalar */ - public function addScalarResult($columnName, $alias, $type = null) + public function addScalarResult($columnName, $alias, $type = 'string') { $this->scalarMappings[$columnName] = $alias; - $this->typeMappings[$columnName] = $type ?: 'string'; + $this->typeMappings[$columnName] = $type; if ( ! $this->isMixed && $this->fieldMappings) { $this->isMixed = true; diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC657Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC657Test.php index a0a10a1b7..40d30401a 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC657Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC657Test.php @@ -26,11 +26,11 @@ class DDC657Test extends \Doctrine\Tests\OrmFunctionalTestCase $query = $this->_em->createQuery('SELECT d FROM ' . self::NS . '\DateTimeModel d'); $datetime = $query->setMaxResults(1)->getSingleResult(); - $this->assertTrue($datetime instanceof DateTimeModel); + $this->assertInstanceOf('Doctrine\Tests\Models\Generic\DateTimeModel', $datetime); - $this->assertTrue($datetime->datetime instanceof \DateTime); - $this->assertTrue($datetime->time instanceof \DateTime); - $this->assertTrue($datetime->date instanceof \DateTime); + $this->assertInstanceOf('DateTime', $datetime->datetime); + $this->assertInstanceOf('DateTime', $datetime->time); + $this->assertInstanceOf('DateTime', $datetime->date); } public function testScalarResult() @@ -56,13 +56,13 @@ class DDC657Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertCount(2,$result); - $this->assertTrue($result[0]['datetime'] instanceof \DateTime); - $this->assertTrue($result[0]['time'] instanceof \DateTime); - $this->assertTrue($result[0]['date'] instanceof \DateTime); + $this->assertInstanceOf('DateTime', $result[0]['datetime']); + $this->assertInstanceOf('DateTime', $result[0]['time']); + $this->assertInstanceOf('DateTime', $result[0]['date']); - $this->assertTrue($result[1]['datetime'] instanceof \DateTime); - $this->assertTrue($result[1]['time'] instanceof \DateTime); - $this->assertTrue($result[1]['date'] instanceof \DateTime); + $this->assertInstanceOf('DateTime', $result[1]['datetime']); + $this->assertInstanceOf('DateTime', $result[1]['time']); + $this->assertInstanceOf('DateTime', $result[1]['date']); } public function testTicketSingleResult() @@ -72,9 +72,9 @@ class DDC657Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertTrue(is_array($datetime)); - $this->assertTrue($datetime['datetime'] instanceof \DateTime); - $this->assertTrue($datetime['time'] instanceof \DateTime); - $this->assertTrue($datetime['date'] instanceof \DateTime); + $this->assertInstanceOf('DateTime', $datetime['datetime']); + $this->assertInstanceOf('DateTime', $datetime['time']); + $this->assertInstanceOf('DateTime', $datetime['date']); } public function testTicketResult() @@ -84,14 +84,16 @@ class DDC657Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertCount(2,$result); - $this->assertTrue($result[0]['time'] instanceof \DateTime); - $this->assertTrue($result[0]['date'] instanceof \DateTime); - $this->assertTrue($result[0]['datetime'] instanceof \DateTime); + $this->assertInstanceOf('DateTime', $result[0]['time']); + $this->assertInstanceOf('DateTime', $result[0]['date']); + $this->assertInstanceOf('DateTime', $result[0]['datetime']); + $this->assertEquals('2010-01-01 11:11:11', $result[0]['datetime']->format('Y-m-d G:i:s')); - $this->assertTrue($result[1]['time'] instanceof \DateTime); - $this->assertTrue($result[1]['date'] instanceof \DateTime); - $this->assertTrue($result[1]['datetime'] instanceof \DateTime); + $this->assertInstanceOf('DateTime', $result[1]['time']); + $this->assertInstanceOf('DateTime', $result[1]['date']); + $this->assertInstanceOf('DateTime', $result[1]['datetime']); + $this->assertEquals('2010-02-02 12:12:12', $result[1]['datetime']->format('Y-m-d G:i:s')); } From 0f070448367f90dffb81225c680ef9b3490a3f0a Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Mon, 16 Jan 2012 22:31:14 -0500 Subject: [PATCH 40/83] Added coverage to DDC-1587. --- .../Tests/ORM/Tools/SchemaValidatorTest.php | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/Tools/SchemaValidatorTest.php b/tests/Doctrine/Tests/ORM/Tools/SchemaValidatorTest.php index 7b77fe507..0b9657fe0 100644 --- a/tests/Doctrine/Tests/ORM/Tools/SchemaValidatorTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/SchemaValidatorTest.php @@ -109,6 +109,19 @@ class SchemaValidatorTest extends \Doctrine\Tests\OrmTestCase $ce ); } + + /** + * @group DDC-1587 + */ + public function testValidOneToOneAsIdentifierSchema() + { + $class1 = $this->em->getClassMetadata(__NAMESPACE__ . '\DDC1587ValidEntity2'); + $class2 = $this->em->getClassMetadata(__NAMESPACE__ . '\DDC1587ValidEntity1'); + + $ce = $this->validator->validateClass($class1); + + $this->assertEquals(array(), $ce); + } } /** @@ -154,3 +167,57 @@ class InvalidEntity2 */ protected $assoc; } + +/** + * @Entity(repositoryClass="Entity\Repository\Agent") + * @Table(name="agent") + */ +class DDC1587ValidEntity1 +{ + /** + * @var int + * + * @Id @GeneratedValue + * @Column(name="pk", type="integer") + */ + private $pk; + + /** + * @var string + * + * @Column(name="name", type="string", length=32) + */ + private $name; + + /** + * @var Identifier + * + * @OneToOne(targetEntity="DDC1587ValidEntity2", cascade={"all"}, mappedBy="agent") + * @JoinColumn(name="pk", referencedColumnName="pk_agent") + */ + private $identifier; +} + +/** + * @Entity + * @Table + */ +class DDC1587ValidEntity2 +{ + /** + * @var DDC1587ValidEntity1 + * + * @Id + * @OneToOne(targetEntity="DDC1587ValidEntity1", inversedBy="identifier") + * @JoinColumn(name="pk_agent", referencedColumnName="pk", nullable=false) + */ + private $agent; + + /** + * @var string + * + * @Column(name="num", type="string", length=16, nullable=true) + */ + private $num; +} + From 543c73bc05170fc07210d7545631dfa2788c53c4 Mon Sep 17 00:00:00 2001 From: John Wright Date: Mon, 16 Jan 2012 21:01:44 -0800 Subject: [PATCH 41/83] added optional command array as parameter for run method --- lib/Doctrine/ORM/Tools/Console/ConsoleRunner.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/Console/ConsoleRunner.php b/lib/Doctrine/ORM/Tools/Console/ConsoleRunner.php index 6907dde39..ffb839eee 100644 --- a/lib/Doctrine/ORM/Tools/Console/ConsoleRunner.php +++ b/lib/Doctrine/ORM/Tools/Console/ConsoleRunner.php @@ -28,14 +28,16 @@ class ConsoleRunner * Run console with the given helperset. * * @param \Symfony\Component\Console\Helper\HelperSet $helperSet + * @param \Symfony\Component\Console\Command\Command[] $commands * @return void */ - static public function run(HelperSet $helperSet) + static public function run(HelperSet $helperSet, $commands = array()) { $cli = new Application('Doctrine Command Line Interface', \Doctrine\ORM\Version::VERSION); $cli->setCatchExceptions(true); $cli->setHelperSet($helperSet); self::addCommands($cli); + $cli->addCommands($commands); $cli->run(); } From d39760ba49418f42f235e9befe223896f11de4bd Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Wed, 18 Jan 2012 01:04:25 -0500 Subject: [PATCH 42/83] Fixed DDC-1608. Non-initialized PersistentCollection methods removeElement and contains now deal correctly with managed entities. --- .../ORM/Persisters/ManyToManyPersister.php | 21 ++++- .../ORM/Persisters/OneToManyPersister.php | 21 ++++- .../Functional/ExtraLazyCollectionTest.php | 79 +++++++++++++++++-- .../ManyToManyExtraLazyContainsTest.php | 55 ------------- 4 files changed, 106 insertions(+), 70 deletions(-) delete mode 100644 tests/Doctrine/Tests/ORM/Functional/ManyToManyExtraLazyContainsTest.php diff --git a/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php b/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php index 54dc2141d..daef03774 100644 --- a/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php +++ b/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php @@ -253,10 +253,15 @@ class ManyToManyPersister extends AbstractCollectionPersister { $uow = $this->_em->getUnitOfWork(); - // shortcut for new entities + // Shortcut for new entities $entityState = $uow->getEntityState($element, UnitOfWork::STATE_NEW); - if ($entityState === UnitOfWork::STATE_NEW || - ($entityState === UnitOfWork::STATE_MANAGED && $uow->isScheduledForInsert($element))) { + + if ($entityState === UnitOfWork::STATE_NEW) { + return false; + } + + // Entity is scheduled for inclusion + if ($entityState === UnitOfWork::STATE_MANAGED && $uow->isScheduledForInsert($element)) { return false; } @@ -277,7 +282,15 @@ class ManyToManyPersister extends AbstractCollectionPersister $uow = $this->_em->getUnitOfWork(); // shortcut for new entities - if ($uow->getEntityState($element, UnitOfWork::STATE_NEW) === UnitOfWork::STATE_NEW) { + $entityState = $uow->getEntityState($element, UnitOfWork::STATE_NEW); + + if ($entityState === UnitOfWork::STATE_NEW) { + return false; + } + + // If Entity is scheduled for inclusion, it is not in this collection. + // We can assure that because it would have return true before on array check + if ($entityState === UnitOfWork::STATE_MANAGED && $uow->isScheduledForInsert($element)) { return false; } diff --git a/lib/Doctrine/ORM/Persisters/OneToManyPersister.php b/lib/Doctrine/ORM/Persisters/OneToManyPersister.php index 1a277613d..6f477f08f 100644 --- a/lib/Doctrine/ORM/Persisters/OneToManyPersister.php +++ b/lib/Doctrine/ORM/Persisters/OneToManyPersister.php @@ -159,7 +159,14 @@ class OneToManyPersister extends AbstractCollectionPersister $uow = $this->_em->getUnitOfWork(); // shortcut for new entities - if ($uow->getEntityState($element, UnitOfWork::STATE_NEW) == UnitOfWork::STATE_NEW) { + $entityState = $uow->getEntityState($element, UnitOfWork::STATE_NEW); + + if ($entityState === UnitOfWork::STATE_NEW) { + return false; + } + + // Entity is scheduled for inclusion + if ($entityState === UnitOfWork::STATE_MANAGED && $uow->isScheduledForInsert($element)) { return false; } @@ -168,7 +175,7 @@ class OneToManyPersister extends AbstractCollectionPersister // only works with single id identifier entities. Will throw an // exception in Entity Persisters if that is not the case for the // 'mappedBy' field. - $id = current( $uow->getEntityIdentifier($coll->getOwner()) ); + $id = current( $uow->getEntityIdentifier($coll->getOwner())); return $persister->exists($element, array($mapping['mappedBy'] => $id)); } @@ -183,7 +190,15 @@ class OneToManyPersister extends AbstractCollectionPersister $uow = $this->_em->getUnitOfWork(); // shortcut for new entities - if ($uow->getEntityState($element, UnitOfWork::STATE_NEW) == UnitOfWork::STATE_NEW) { + $entityState = $uow->getEntityState($element, UnitOfWork::STATE_NEW); + + if ($entityState === UnitOfWork::STATE_NEW) { + return false; + } + + // If Entity is scheduled for inclusion, it is not in this collection. + // We can assure that because it would have return true before on array check + if ($entityState === UnitOfWork::STATE_MANAGED && $uow->isScheduledForInsert($element)) { return false; } diff --git a/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php b/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php index 0e3be4d25..f8b41e06a 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php @@ -64,7 +64,7 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase /** * @group DDC-546 */ - public function testCountWhenNewEntitysPresent() + public function testCountWhenNewEntityPresent() { $user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); @@ -223,13 +223,15 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase $user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); $this->assertFalse($user->articles->isInitialized(), "Pre-Condition: Collection is not initialized."); - $article = $this->_em->find('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId); - + // Test One to Many existance retrieved from DB + $article = $this->_em->find('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId); $queryCount = $this->getCurrentQueryCount(); + $this->assertTrue($user->articles->contains($article)); $this->assertFalse($user->articles->isInitialized(), "Post-Condition: Collection is not initialized."); $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + // Test One to Many existance with state new $article = new \Doctrine\Tests\Models\CMS\CmsArticle(); $article->topic = "Testnew"; $article->text = "blub"; @@ -238,12 +240,26 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertFalse($user->articles->contains($article)); $this->assertEquals($queryCount, $this->getCurrentQueryCount(), "Checking for contains of new entity should cause no query to be executed."); + // Test One to Many existance with state clear $this->_em->persist($article); $this->_em->flush(); $queryCount = $this->getCurrentQueryCount(); $this->assertFalse($user->articles->contains($article)); - $this->assertEquals($queryCount+1, $this->getCurrentQueryCount(), "Checking for contains of managed entity should cause one query to be executed."); + $this->assertEquals($queryCount+1, $this->getCurrentQueryCount(), "Checking for contains of persisted entity should cause one query to be executed."); + $this->assertFalse($user->articles->isInitialized(), "Post-Condition: Collection is not initialized."); + + // Test One to Many existance with state managed + $article = new \Doctrine\Tests\Models\CMS\CmsArticle(); + $article->topic = "How to not fail anymore on tests"; + $article->text = "That is simple! Just write more tests!"; + + $this->_em->persist($article); + + $queryCount = $this->getCurrentQueryCount(); + + $this->assertFalse($user->articles->contains($article)); + $this->assertEquals($queryCount, $this->getCurrentQueryCount(), "Checking for contains of managed entity (but not persisted) should cause no query to be executed."); $this->assertFalse($user->articles->isInitialized(), "Post-Condition: Collection is not initialized."); } @@ -255,6 +271,7 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase $user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); $this->assertFalse($user->groups->isInitialized(), "Pre-Condition: Collection is not initialized."); + // Test Many to Many existance retrieved from DB $group = $this->_em->find('Doctrine\Tests\Models\CMS\CmsGroup', $this->groupId); $queryCount = $this->getCurrentQueryCount(); @@ -262,6 +279,7 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Checking for contains of managed entity should cause one query to be executed."); $this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized."); + // Test Many to Many existance with state new $group = new \Doctrine\Tests\Models\CMS\CmsGroup(); $group->name = "A New group!"; @@ -271,13 +289,26 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals($queryCount, $this->getCurrentQueryCount(), "Checking for contains of new entity should cause no query to be executed."); $this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized."); + // Test Many to Many existance with state clear $this->_em->persist($group); $this->_em->flush(); $queryCount = $this->getCurrentQueryCount(); $this->assertFalse($user->groups->contains($group)); - $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Checking for contains of managed entity should cause one query to be executed."); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Checking for contains of persisted entity should cause one query to be executed."); + $this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized."); + + // Test Many to Many existance with state managed + $group = new \Doctrine\Tests\Models\CMS\CmsGroup(); + $group->name = "My managed group"; + + $this->_em->persist($group); + + $queryCount = $this->getCurrentQueryCount(); + + $this->assertFalse($user->groups->contains($group)); + $this->assertEquals($queryCount, $this->getCurrentQueryCount(), "Checking for contains of managed entity (but not persisted) should cause no query to be executed."); $this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized."); } @@ -313,6 +344,7 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase $user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); $this->assertFalse($user->articles->isInitialized(), "Pre-Condition: Collection is not initialized."); + // Test One to Many removal with Entity retrieved from DB $article = $this->_em->find('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId); $queryCount = $this->getCurrentQueryCount(); @@ -321,6 +353,7 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertFalse($user->articles->isInitialized(), "Post-Condition: Collection is not initialized."); $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + // Test One to Many removal with Entity state as new $article = new \Doctrine\Tests\Models\CMS\CmsArticle(); $article->topic = "Testnew"; $article->text = "blub"; @@ -331,6 +364,7 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals($queryCount, $this->getCurrentQueryCount(), "Removing a new entity should cause no query to be executed."); + // Test One to Many removal with Entity state as clean $this->_em->persist($article); $this->_em->flush(); @@ -338,8 +372,21 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase $user->articles->removeElement($article); - $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Removing a managed entity should cause one query to be executed."); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Removing a persisted entity should cause one query to be executed."); $this->assertFalse($user->articles->isInitialized(), "Post-Condition: Collection is not initialized."); + + // Test One to Many removal with Entity state as managed + $article = new \Doctrine\Tests\Models\CMS\CmsArticle(); + $article->topic = "How to not fail anymore on tests"; + $article->text = "That is simple! Just write more tests!"; + + $this->_em->persist($article); + + $queryCount = $this->getCurrentQueryCount(); + + $user->articles->removeElement($article); + + $this->assertEquals($queryCount, $this->getCurrentQueryCount(), "Removing a managed entity should cause no query to be executed."); } /** @@ -350,14 +397,16 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase $user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId); $this->assertFalse($user->groups->isInitialized(), "Pre-Condition: Collection is not initialized."); + // Test Many to Many removal with Entity retrieved from DB $group = $this->_em->find('Doctrine\Tests\Models\CMS\CmsGroup', $this->groupId); $queryCount = $this->getCurrentQueryCount(); $user->groups->removeElement($group); - $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Removing a managed entity should cause one query to be executed."); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Removing a persisted entity should cause one query to be executed."); $this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized."); + // Test Many to Many removal with Entity state as new $group = new \Doctrine\Tests\Models\CMS\CmsGroup(); $group->name = "A New group!"; @@ -368,6 +417,7 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals($queryCount, $this->getCurrentQueryCount(), "Removing new entity should cause no query to be executed."); $this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized."); + // Test Many to Many removal with Entity state as clean $this->_em->persist($group); $this->_em->flush(); @@ -375,7 +425,20 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase $user->groups->removeElement($group); - $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Removing a managed entity should cause one query to be executed."); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Removing a persisted entity should cause one query to be executed."); + $this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized."); + + // Test Many to Many removal with Entity state as managed + $group = new \Doctrine\Tests\Models\CMS\CmsGroup(); + $group->name = "A New group!"; + + $this->_em->persist($group); + + $queryCount = $this->getCurrentQueryCount(); + + $user->groups->removeElement($group); + + $this->assertEquals($queryCount, $this->getCurrentQueryCount(), "Removing a managed entity should cause no query to be executed."); $this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized."); } diff --git a/tests/Doctrine/Tests/ORM/Functional/ManyToManyExtraLazyContainsTest.php b/tests/Doctrine/Tests/ORM/Functional/ManyToManyExtraLazyContainsTest.php deleted file mode 100644 index bbb26fcea..000000000 --- a/tests/Doctrine/Tests/ORM/Functional/ManyToManyExtraLazyContainsTest.php +++ /dev/null @@ -1,55 +0,0 @@ -useModelSet('company'); - parent::setUp(); - } - - public function testManyToManyExtraLazyContainsAddedPendingInsertEntityIsTrue() - { - $contract = new \Doctrine\Tests\Models\Company\CompanyFlexContract(); - - $this->_em->persist($contract); - $this->_em->flush(); - - $this->_em->clear(); - $contract = $this->_em->find('Doctrine\Tests\Models\Company\CompanyFlexContract', $contract->getId()); - - $pendingInsertManager = new \Doctrine\Tests\Models\Company\CompanyManager(); - $this->_em->persist($pendingInsertManager); - $contract->getManagers()->add($pendingInsertManager); - - $result = $contract->getManagers()->contains($pendingInsertManager); - - $this->assertTrue($result); - } - - public function testManyToManyExtraLazyContainsNonAddedPendingInsertEntityIsFalse() - { - $contract = new \Doctrine\Tests\Models\Company\CompanyFlexContract(); - - $this->_em->persist($contract); - $this->_em->flush(); - - $this->_em->clear(); - $contract = $this->_em->find('Doctrine\Tests\Models\Company\CompanyFlexContract', $contract->getId()); - - $pendingInsertManager = new \Doctrine\Tests\Models\Company\CompanyManager(); - $this->_em->persist($pendingInsertManager); - - $result = $contract->getManagers()->contains($pendingInsertManager); - - $this->assertFalse($result); - } -} \ No newline at end of file From e0fc09994c8f0ceb9daf4d5cd602a89b81e3073c Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Wed, 18 Jan 2012 21:31:49 +0100 Subject: [PATCH 43/83] DDC-742 - Flush Memcache, otherwise fail. --- tests/Doctrine/Tests/ORM/Functional/Ticket/DDC742Test.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC742Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC742Test.php index 513904418..7e6e200d9 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC742Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC742Test.php @@ -8,6 +8,9 @@ require_once __DIR__ . '/../../../TestInit.php'; class DDC742Test extends \Doctrine\Tests\OrmFunctionalTestCase { + private $userCm; + private $commentCm; + protected function setUp() { parent::setUp(); @@ -15,6 +18,7 @@ class DDC742Test extends \Doctrine\Tests\OrmFunctionalTestCase if (\extension_loaded('memcache')) { $memcache = new \Memcache(); $memcache->addServer('localhost'); + $memcache->flush(); $cacheDriver = new \Doctrine\Common\Cache\MemcacheCache(); $cacheDriver->setMemcache($memcache); @@ -123,4 +127,4 @@ class DDC742Comment * @var string */ public $content; -} \ No newline at end of file +} From b98280a504e1190479777e5f1d3a17429e8c04c3 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Wed, 18 Jan 2012 23:09:23 -0500 Subject: [PATCH 44/83] Quick optimizations are always good. --- lib/Doctrine/ORM/UnitOfWork.php | 39 ++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 49b3fcd34..09ba8004a 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -705,15 +705,17 @@ class UnitOfWork implements PropertyChangedListener foreach ($unwrappedValue as $key => $entry) { $state = $this->getEntityState($entry, self::STATE_NEW); - $oid = spl_object_hash($entry); - if (!($entry instanceof $assoc['targetEntity'])) { - throw new ORMException(sprintf("Found entity of type %s on association %s#%s, but expecting %s", - get_class($entry), - $assoc['sourceEntity'], - $assoc['fieldName'], - $targetClass->name - )); + if ( ! ($entry instanceof $assoc['targetEntity'])) { + throw new ORMException( + sprintf( + 'Found entity of type %s on association %s#%s, but expecting %s', + get_class($entry), + $assoc['sourceEntity'], + $assoc['fieldName'], + $targetClass->name + ) + ); } switch ($state) { @@ -1010,7 +1012,7 @@ class UnitOfWork implements PropertyChangedListener // are not yet available. $newNodes = array(); - foreach ($entityChangeSet as $oid => $entity) { + foreach ($entityChangeSet as $entity) { $className = get_class($entity); if ($calc->hasClass($className)) { @@ -1656,7 +1658,8 @@ class UnitOfWork implements PropertyChangedListener foreach ($id as $idField => $idValue) { if (isset($class->associationMappings[$idField])) { $targetClassMetadata = $this->em->getClassMetadata($class->associationMappings[$idField]['targetEntity']); - $associatedId = $this->getEntityIdentifier($idValue); + $associatedId = $this->getEntityIdentifier($idValue); + $flatId[$idField] = $associatedId[$targetClassMetadata->identifier[0]]; } } @@ -2418,9 +2421,9 @@ class UnitOfWork implements PropertyChangedListener case (isset($this->identityMap[$targetClass->rootEntityName][$relatedIdHash])): $newValue = $this->identityMap[$targetClass->rootEntityName][$relatedIdHash]; - // if this is an uninitialized proxy, we are deferring eager loads, + // If this is an uninitialized proxy, we are deferring eager loads, // this association is marked as eager fetch, and its an uninitialized proxy (wtf!) - // then we cann append this entity for eager loading! + // then we can append this entity for eager loading! if ($hints['fetchMode'][$class->name][$field] == ClassMetadata::FETCH_EAGER && isset($hints['deferEagerLoad']) && !$targetClass->isIdentifierComposite && @@ -2515,13 +2518,13 @@ class UnitOfWork implements PropertyChangedListener $this->eagerLoadingEntities = array(); foreach ($eagerLoadingEntities as $entityName => $ids) { + if ( ! $ids) continue; + $class = $this->em->getClassMetadata($entityName); - if ($ids) { - $this->getEntityPersister($entityName)->loadAll( - array_combine($class->identifier, array(array_values($ids))) - ); - } + $this->getEntityPersister($entityName)->loadAll( + array_combine($class->identifier, array(array_values($ids))) + ); } } @@ -2901,7 +2904,7 @@ class UnitOfWork implements PropertyChangedListener */ public function isReadOnly($object) { - if ( ! is_object($object) ) { + if ( ! is_object($object)) { throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object); } From 74f3ed7e29baa7a71cdc19a07838cef8167179ec Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Wed, 18 Jan 2012 23:27:28 -0500 Subject: [PATCH 45/83] Fixing CS. --- lib/Doctrine/ORM/UnitOfWork.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 09ba8004a..beeab7a5c 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -2518,7 +2518,9 @@ class UnitOfWork implements PropertyChangedListener $this->eagerLoadingEntities = array(); foreach ($eagerLoadingEntities as $entityName => $ids) { - if ( ! $ids) continue; + if ( ! $ids) { + continue; + } $class = $this->em->getClassMetadata($entityName); From febfe35c2315a20534630aa9e5b9c72a0047f569 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Wed, 18 Jan 2012 23:51:11 -0500 Subject: [PATCH 46/83] Added coverage for DDC-1529. --- .../Tests/ORM/Query/SelectSqlGenerationTest.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index 9fed1d239..a7e96be3e 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -1535,6 +1535,17 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase 'SELECT c0_.id AS id0, -(c0_.customInteger) AS customInteger1 FROM customtype_parents c0_' ); } + + /** + * @group DDC-1529 + */ + public function testMultipleFromAndInheritanceCondition() + { + $this->assertSqlGeneration( + 'SELECT fix, flex FROM Doctrine\Tests\Models\Company\CompanyFixContract fix, Doctrine\Tests\Models\Company\CompanyFlexContract flex', + "SELECT c0_.id AS id0, c0_.completed AS completed1, c0_.fixPrice AS fixPrice2, c1_.id AS id3, c1_.completed AS completed4, c1_.hoursWorked AS hoursWorked5, c1_.pricePerHour AS pricePerHour6, c1_.maxPrice AS maxPrice7, c0_.discr AS discr8, c1_.discr AS discr9 FROM company_contracts c0_, company_contracts c1_ WHERE (c0_.discr IN ('fix') AND c1_.discr IN ('flexible', 'flexultra'))" + ); + } } From 0f3abde413455e8eb3f9688569fbfb949c18e48e Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 21 Jan 2012 11:29:48 +0100 Subject: [PATCH 47/83] [DBAL-204] Filter namespaced assets if Schemas/Emulation is not supported. --- lib/Doctrine/ORM/Tools/SchemaTool.php | 8 +++++- lib/vendor/doctrine-dbal | 2 +- .../SchemaTool/MySqlSchemaToolTest.php | 27 +++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/Tools/SchemaTool.php b/lib/Doctrine/ORM/Tools/SchemaTool.php index b0d14eb26..efe7f7b7c 100644 --- a/lib/Doctrine/ORM/Tools/SchemaTool.php +++ b/lib/Doctrine/ORM/Tools/SchemaTool.php @@ -21,6 +21,8 @@ namespace Doctrine\ORM\Tools; use Doctrine\ORM\ORMException, Doctrine\DBAL\Types\Type, + Doctrine\DBAL\Schema\Schema, + Doctrine\DBAL\Schema\Visitor\RemoveNamespacedAssets, Doctrine\ORM\EntityManager, Doctrine\ORM\Mapping\ClassMetadata, Doctrine\ORM\Internal\CommitOrderCalculator, @@ -127,7 +129,7 @@ class SchemaTool $sm = $this->_em->getConnection()->getSchemaManager(); $metadataSchemaConfig = $sm->createSchemaConfig(); $metadataSchemaConfig->setExplicitForeignKeyIndexes(false); - $schema = new \Doctrine\DBAL\Schema\Schema(array(), array(), $metadataSchemaConfig); + $schema = new Schema(array(), array(), $metadataSchemaConfig); $evm = $this->_em->getEventManager(); @@ -252,6 +254,10 @@ class SchemaTool } } + if ( ! $this->_platform->supportsSchemas() && ! $this->_platform->canEmulateSchemas() ) { + $schema->visit(new RemoveNamespacedAssets()); + } + if ($evm->hasListeners(ToolEvents::postGenerateSchema)) { $evm->dispatchEvent(ToolEvents::postGenerateSchema, new GenerateSchemaEventArgs($this->_em, $schema)); } diff --git a/lib/vendor/doctrine-dbal b/lib/vendor/doctrine-dbal index 29b714b7f..480b127fb 160000 --- a/lib/vendor/doctrine-dbal +++ b/lib/vendor/doctrine-dbal @@ -1 +1 @@ -Subproject commit 29b714b7fe72641d749ae90324a5759853fe09b0 +Subproject commit 480b127fb5c35d6fbd70964a228cd63cfe2b7e14 diff --git a/tests/Doctrine/Tests/ORM/Functional/SchemaTool/MySqlSchemaToolTest.php b/tests/Doctrine/Tests/ORM/Functional/SchemaTool/MySqlSchemaToolTest.php index 74d68ca60..5937d8d6e 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SchemaTool/MySqlSchemaToolTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SchemaTool/MySqlSchemaToolTest.php @@ -64,4 +64,31 @@ class MySqlSchemaToolTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals(1, count($sql)); $this->assertEquals("CREATE TABLE boolean_model (id INT AUTO_INCREMENT NOT NULL, booleanField TINYINT(1) NOT NULL, PRIMARY KEY(id)) ENGINE = InnoDB", $sql[0]); } + + /** + * @group DBAL-204 + */ + public function testGetCreateSchemaSql4() + { + $classes = array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\\MysqlSchemaNamespacedEntity') + ); + + $tool = new SchemaTool($this->_em); + $sql = $tool->getCreateSchemaSql($classes); + + $this->assertEquals(0, count($sql)); + } + } + +/** + * @Entity + * @Table("namespace.entity") + */ +class MysqlSchemaNamespacedEntity +{ + /** @Column(type="integer") @Id @GeneratedValue */ + public $id; +} + From 6c242514521d3e83784bd6595db3dc7a3e893c3d Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 21 Jan 2012 13:06:30 +0100 Subject: [PATCH 48/83] [DDC-1612] Fix bug with EntityManager#flush($entity) on new entities. --- lib/Doctrine/ORM/UnitOfWork.php | 2 +- .../Tests/ORM/Functional/BasicFunctionalTest.php | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index beeab7a5c..fc892b138 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -390,7 +390,7 @@ class UnitOfWork implements PropertyChangedListener */ private function computeSingleEntityChangeSet($entity) { - if ( ! $this->isInIdentityMap($entity) ) { + if ( $this->getEntityState($entity) !== self::STATE_MANAGED) { throw new \InvalidArgumentException("Entity has to be managed for single computation " . self::objToStr($entity)); } diff --git a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php index 3955387e0..695e10c55 100644 --- a/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/BasicFunctionalTest.php @@ -1141,6 +1141,21 @@ class BasicFunctionalTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->flush($user); } + /** + * @group DDC-720 + * @group DDC-1612 + */ + public function testFlushSingleNewEntity() + { + $user = new CmsUser; + $user->name = 'Dominik'; + $user->username = 'domnikl'; + $user->status = 'developer'; + + $this->_em->persist($user); + $this->_em->flush($user); + } + /** * @group DDC-720 */ From faf92883b6341321d74ac750ea815448b2d5b2e5 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 21 Jan 2012 13:58:25 +0100 Subject: [PATCH 49/83] [DDC-1610] Add test and fix wakeup reflection in combination with event listener --- lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php | 2 +- .../Tests/ORM/Mapping/ClassMetadataLoadEventTest.php | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index 9e0f2b38d..815808014 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -291,7 +291,6 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface // Invoke driver try { $this->driver->loadMetadataForClass($className, $class); - $this->wakeupReflection($class, $this->getReflectionService()); } catch (ReflectionException $e) { throw MappingException::reflectionFailure($className, $e); } @@ -333,6 +332,7 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface $eventArgs = new \Doctrine\ORM\Event\LoadClassMetadataEventArgs($class, $this->em); $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs); } + $this->wakeupReflection($class, $this->getReflectionService()); $this->validateRuntimeMetadata($class, $parent); diff --git a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataLoadEventTest.php b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataLoadEventTest.php index 5ef67c3eb..70aae70d4 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataLoadEventTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataLoadEventTest.php @@ -9,6 +9,9 @@ require_once __DIR__ . '/../../TestInit.php'; class ClassMetadataLoadEventTest extends \Doctrine\Tests\OrmTestCase { + /** + * @group DDC-1610 + */ public function testEvent() { $em = $this->_getTestEntityManager(); @@ -17,6 +20,8 @@ class ClassMetadataLoadEventTest extends \Doctrine\Tests\OrmTestCase $evm->addEventListener(Events::loadClassMetadata, $this); $classMetadata = $metadataFactory->getMetadataFor('Doctrine\Tests\ORM\Mapping\LoadEventTestEntity'); $this->assertTrue($classMetadata->hasField('about')); + $this->assertArrayHasKey('about', $classMetadata->reflFields); + $this->assertInstanceOf('ReflectionProperty', $classMetadata->reflFields['about']); } public function loadClassMetadata(\Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs) @@ -48,4 +53,4 @@ class LoadEventTestEntity private $name; private $about; -} \ No newline at end of file +} From 0fce3c8f977878a5187c47db1639a69e6d64834a Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sat, 21 Jan 2012 12:42:46 -0200 Subject: [PATCH 50/83] Fix DDC-1412 --- .../ORM/Mapping/Driver/DriverChain.php | 48 ++++++++++++++++--- .../Tests/ORM/Mapping/DriverChainTest.php | 36 ++++++++++++++ 2 files changed, 78 insertions(+), 6 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/Driver/DriverChain.php b/lib/Doctrine/ORM/Mapping/Driver/DriverChain.php index 321962d2c..da30f177a 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/DriverChain.php +++ b/lib/Doctrine/ORM/Mapping/Driver/DriverChain.php @@ -39,7 +39,34 @@ class DriverChain implements Driver /** * @var array */ - private $_drivers = array(); + private $drivers = array(); + + /** + * The default driver + * + * @var Driver + */ + private $defaultDriver; + + /** + * Get the default driver. + * + * @return Driver + */ + public function getDefaultDriver() + { + return $this->defaultDriver; + } + + /** + * Set the default driver. + * + * @param Driver $driver + */ + public function setDefaultDriver(Driver $driver) + { + $this->defaultDriver = $driver; + } /** * Add a nested driver. @@ -49,7 +76,7 @@ class DriverChain implements Driver */ public function addDriver(Driver $nestedDriver, $namespace) { - $this->_drivers[$namespace] = $nestedDriver; + $this->drivers[$namespace] = $nestedDriver; } /** @@ -59,7 +86,7 @@ class DriverChain implements Driver */ public function getDrivers() { - return $this->_drivers; + return $this->drivers; } /** @@ -70,13 +97,18 @@ class DriverChain implements Driver */ public function loadMetadataForClass($className, ClassMetadataInfo $metadata) { - foreach ($this->_drivers as $namespace => $driver) { + foreach ($this->drivers as $namespace => $driver) { if (strpos($className, $namespace) === 0) { $driver->loadMetadataForClass($className, $metadata); return; } } + if ($this->defaultDriver !== null) { + $this->defaultDriver->loadMetadataForClass($className, $metadata); + return; + } + throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className); } @@ -89,7 +121,7 @@ class DriverChain implements Driver { $classNames = array(); $driverClasses = array(); - foreach ($this->_drivers AS $namespace => $driver) { + foreach ($this->drivers AS $namespace => $driver) { $oid = spl_object_hash($driver); if (!isset($driverClasses[$oid])) { $driverClasses[$oid] = $driver->getAllClassNames(); @@ -114,12 +146,16 @@ class DriverChain implements Driver */ public function isTransient($className) { - foreach ($this->_drivers AS $namespace => $driver) { + foreach ($this->drivers AS $namespace => $driver) { if (strpos($className, $namespace) === 0) { return $driver->isTransient($className); } } + if ($this->defaultDriver !== null) { + return $this->defaultDriver->isTransient($className); + } + // class isTransient, i.e. not an entity or mapped superclass return true; } diff --git a/tests/Doctrine/Tests/ORM/Mapping/DriverChainTest.php b/tests/Doctrine/Tests/ORM/Mapping/DriverChainTest.php index 2383db64c..35560f35a 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/DriverChainTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/DriverChainTest.php @@ -88,6 +88,42 @@ class DriverChainTest extends \Doctrine\Tests\OrmTestCase $this->assertTrue($chain->isTransient('stdClass'), "stdClass isTransient"); $this->assertFalse($chain->isTransient('Doctrine\Tests\Models\CMS\CmsUser'), "CmsUser is not Transient"); } + + /** + * @group DDC-1412 + */ + public function testDefaultDriver() + { + $companyDriver = $this->getMock('Doctrine\ORM\Mapping\Driver\Driver'); + $dafaultDriver = $this->getMock('Doctrine\ORM\Mapping\Driver\Driver'); + $entityClassName = 'Doctrine\Tests\ORM\Mapping\DriverChainEntity'; + $managerClassName = 'Doctrine\Tests\Models\Company\CompanyManager'; + $chain = new DriverChain(); + + $companyDriver->expects($this->never()) + ->method('loadMetadataForClass'); + $companyDriver->expects($this->once()) + ->method('isTransient') + ->with($this->equalTo($managerClassName)) + ->will($this->returnValue(false)); + + $dafaultDriver->expects($this->never()) + ->method('loadMetadataForClass'); + $dafaultDriver->expects($this->once()) + ->method('isTransient') + ->with($this->equalTo($entityClassName)) + ->will($this->returnValue(true)); + + $this->assertNull($chain->getDefaultDriver()); + + $chain->setDefaultDriver($dafaultDriver); + $chain->addDriver($companyDriver, 'Doctrine\Tests\Models\Company'); + + $this->assertSame($dafaultDriver, $chain->getDefaultDriver()); + + $this->assertTrue($chain->isTransient($entityClassName)); + $this->assertFalse($chain->isTransient($managerClassName)); + } } class DriverChainEntity From 9f831f4c98e839066e1235e37898006137af719b Mon Sep 17 00:00:00 2001 From: Alexandr Torosh Date: Sat, 21 Jan 2012 23:38:55 +0200 Subject: [PATCH 51/83] changed submodule doctrine-build-common url https://github.com/doctrine/doctrine-build-common.git to git://github.com/doctrine/doctrine-build-common.git with read-only access --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 21a1fb2fa..2d52e9acd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -12,4 +12,4 @@ url = git://github.com/symfony/Yaml.git [submodule "lib/vendor/doctrine-build-common"] path = lib/vendor/doctrine-build-common - url = https://github.com/doctrine/doctrine-build-common.git + url = git://github.com/doctrine/doctrine-build-common.git From 775071e1ff92b2de4df4b7b4b4355559a29dd829 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 22 Jan 2012 13:35:06 +0100 Subject: [PATCH 52/83] [DDC-1613] Merge KnpLabs/Pagerfanta Pagination into a Doctrine\ORM\Tools\Pagination namespace. Thanks to @hobodave, pablo and the knplabs team for developing and maintaining this code. --- .../ORM/Tools/Pagination/CountWalker.php | 80 ++++++++ .../Tools/Pagination/LimitSubqueryWalker.php | 115 +++++++++++ .../ORM/Tools/Pagination/Paginator.php | 180 ++++++++++++++++++ .../ORM/Tools/Pagination/WhereInWalker.php | 142 ++++++++++++++ .../Tests/ORM/Functional/PaginationTest.php | 105 ++++++++++ .../ORM/Tools/Pagination/CountWalkerTest.php | 78 ++++++++ .../Pagination/LimitSubqueryWalkerTest.php | 36 ++++ .../Tools/Pagination/PaginationTestCase.php | 142 ++++++++++++++ .../Tools/Pagination/WhereInWalkerTest.php | 111 +++++++++++ 9 files changed, 989 insertions(+) create mode 100644 lib/Doctrine/ORM/Tools/Pagination/CountWalker.php create mode 100644 lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryWalker.php create mode 100644 lib/Doctrine/ORM/Tools/Pagination/Paginator.php create mode 100644 lib/Doctrine/ORM/Tools/Pagination/WhereInWalker.php create mode 100644 tests/Doctrine/Tests/ORM/Functional/PaginationTest.php create mode 100644 tests/Doctrine/Tests/ORM/Tools/Pagination/CountWalkerTest.php create mode 100644 tests/Doctrine/Tests/ORM/Tools/Pagination/LimitSubqueryWalkerTest.php create mode 100644 tests/Doctrine/Tests/ORM/Tools/Pagination/PaginationTestCase.php create mode 100644 tests/Doctrine/Tests/ORM/Tools/Pagination/WhereInWalkerTest.php diff --git a/lib/Doctrine/ORM/Tools/Pagination/CountWalker.php b/lib/Doctrine/ORM/Tools/Pagination/CountWalker.php new file mode 100644 index 000000000..10df1c3e1 --- /dev/null +++ b/lib/Doctrine/ORM/Tools/Pagination/CountWalker.php @@ -0,0 +1,80 @@ + + * @copyright Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/) + * @license http://hobodave.com/license.txt New BSD License + */ +class CountWalker extends TreeWalkerAdapter +{ + /** + * Distinct mode hint name + */ + const HINT_DISTINCT = 'doctrine_paginator.distinct'; + + /** + * Walks down a SelectStatement AST node, modifying it to retrieve a COUNT + * + * @param SelectStatement $AST + * @return void + */ + public function walkSelectStatement(SelectStatement $AST) + { + $rootComponents = array(); + foreach ($this->_getQueryComponents() AS $dqlAlias => $qComp) { + $isParent = array_key_exists('parent', $qComp) + && $qComp['parent'] === null + && $qComp['nestingLevel'] == 0 + ; + if ($isParent) { + $rootComponents[] = array($dqlAlias => $qComp); + } + } + if (count($rootComponents) > 1) { + throw new \RuntimeException("Cannot count query which selects two FROM components, cannot make distinction"); + } + $root = reset($rootComponents); + $parentName = key($root); + $parent = current($root); + + $pathExpression = new PathExpression( + PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName, + $parent['metadata']->getSingleIdentifierFieldName() + ); + $pathExpression->type = PathExpression::TYPE_STATE_FIELD; + + $distinct = $this->_getQuery()->getHint(self::HINT_DISTINCT); + $AST->selectClause->selectExpressions = array( + new SelectExpression( + new AggregateExpression('count', $pathExpression, $distinct), null + ) + ); + + // ORDER BY is not needed, only increases query execution through unnecessary sorting. + $AST->orderByClause = null; + } +} + diff --git a/lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryWalker.php b/lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryWalker.php new file mode 100644 index 000000000..97ed5ba81 --- /dev/null +++ b/lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryWalker.php @@ -0,0 +1,115 @@ + + * @copyright Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/) + * @license http://hobodave.com/license.txt New BSD License + */ + +namespace Doctrine\ORM\Tools\Pagination; + +use Doctrine\DBAL\Types\Type, + Doctrine\ORM\Query\TreeWalkerAdapter, + Doctrine\ORM\Query\AST\SelectStatement, + Doctrine\ORM\Query\AST\SelectExpression, + Doctrine\ORM\Query\AST\PathExpression, + Doctrine\ORM\Query\AST\AggregateExpression; + +/** + * Replaces the selectClause of the AST with a SELECT DISTINCT root.id equivalent + * + * @category DoctrineExtensions + * @package DoctrineExtensions\Paginate + * @author David Abdemoulaie + * @copyright Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/) + * @license http://hobodave.com/license.txt New BSD License + */ +class LimitSubqueryWalker extends TreeWalkerAdapter +{ + /** + * ID type hint + */ + const IDENTIFIER_TYPE = 'doctrine_paginator.id.type'; + + /** + * @var int Counter for generating unique order column aliases + */ + private $_aliasCounter = 0; + + /** + * Walks down a SelectStatement AST node, modifying it to retrieve DISTINCT ids + * of the root Entity + * + * @param SelectStatement $AST + * @return void + */ + public function walkSelectStatement(SelectStatement $AST) + { + $parent = null; + $parentName = null; + $selectExpressions = array(); + + foreach ($this->_getQueryComponents() AS $dqlAlias => $qComp) { + // preserve mixed data in query for ordering + if (isset($qComp['resultVariable'])) { + $selectExpressions[] = new SelectExpression($qComp['resultVariable'], $dqlAlias); + continue; + } + + if ($qComp['parent'] === null && $qComp['nestingLevel'] == 0) { + $parent = $qComp; + $parentName = $dqlAlias; + continue; + } + } + + $identifier = $parent['metadata']->getSingleIdentifierFieldName(); + $this->_getQuery()->setHint( + self::IDENTIFIER_TYPE, + Type::getType($parent['metadata']->getTypeOfField($identifier)) + ); + + $pathExpression = new PathExpression( + PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, + $parentName, + $identifier + ); + $pathExpression->type = PathExpression::TYPE_STATE_FIELD; + + array_unshift($selectExpressions, new SelectExpression($pathExpression, '_dctrn_id')); + $AST->selectClause->selectExpressions = $selectExpressions; + + if (isset($AST->orderByClause)) { + foreach ($AST->orderByClause->orderByItems as $item) { + if ($item->expression instanceof PathExpression) { + $pathExpression = new PathExpression( + PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, + $item->expression->identificationVariable, + $item->expression->field + ); + $pathExpression->type = PathExpression::TYPE_STATE_FIELD; + $AST->selectClause->selectExpressions[] = new SelectExpression( + $pathExpression, + '_dctrn_ord' . $this->_aliasCounter++ + ); + } + } + } + + $AST->selectClause->isDistinct = true; + } + +} + + + diff --git a/lib/Doctrine/ORM/Tools/Pagination/Paginator.php b/lib/Doctrine/ORM/Tools/Pagination/Paginator.php new file mode 100644 index 000000000..760d7de13 --- /dev/null +++ b/lib/Doctrine/ORM/Tools/Pagination/Paginator.php @@ -0,0 +1,180 @@ +. + */ + +namespace Doctrine\ORM\Tools\Pagination; + +use Doctrine\ORM\QueryBuilder; +use Doctrine\ORM\Query; +use Doctrine\ORM\NoResultException; +use Doctrine\ORM\Tools\Pagination\WhereInWalker; +use Doctrine\ORM\Tools\Pagination\CountWalker; +use Countable; +use IteratorAggregate; +use ArrayIterator; + +/** + * Paginator + * + * The paginator can handle various complex scenarios with DQL. + * + * @author Pablo Díez + * @author Benjamin Eberlei + * @license New BSD + */ +class Paginator implements \Countable, \IteratorAggregate +{ + /** + * @var Query + */ + private $query; + + /** + * @var bool + */ + private $fetchJoinCollection; + + /** + * @var int + */ + private $count; + + /** + * Constructor. + * + * @param Query|QueryBuilder $query A Doctrine ORM query or query builder. + * @param Boolean $fetchJoinCollection Whether the query joins a collection (true by default). + */ + public function __construct($query, $fetchJoinCollection = true) + { + if ($query instanceof QueryBuilder) { + $query = $query->getQuery(); + } + + $this->query = $query; + $this->fetchJoinCollection = (Boolean) $fetchJoinCollection; + } + + /** + * Returns the query + * + * @return Query + */ + public function getQuery() + { + return $this->query; + } + + /** + * Returns whether the query joins a collection. + * + * @return Boolean Whether the query joins a collection. + */ + public function getFetchJoinCollection() + { + return $this->fetchJoinCollection; + } + + /** + * {@inheritdoc} + */ + public function count() + { + if ($this->count === null) { + /* @var $countQuery Query */ + $countQuery = $this->cloneQuery($this->query); + + if ( ! $countQuery->getHint(CountWalker::HINT_DISTINCT)) { + $countQuery->setHint(CountWalker::HINT_DISTINCT, true); + } + + $countQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\CountWalker')); + $countQuery->setFirstResult(null)->setMaxResults(null); + + try { + $data = $countQuery->getScalarResult(); + $data = array_map('current', $data); + $this->count = array_sum($data); + } catch(NoResultException $e) { + $this->count = 0; + } + } + return $this->count; + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + $offset = $this->query->getFirstResult(); + $length = $this->query->getMaxResults(); + + if ($this->fetchJoinCollection) { + $subQuery = $this->cloneQuery($this->query); + $subQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\LimitSubqueryWalker')) + ->setFirstResult($offset) + ->setMaxResults($length); + + $ids = array_map('current', $subQuery->getScalarResult()); + + $whereInQuery = $this->cloneQuery($this->query); + // don't do this for an empty id array + if (count($ids) > 0) { + $namespace = WhereInWalker::PAGINATOR_ID_ALIAS; + + $whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\WhereInWalker')); + $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, count($ids)); + $whereInQuery->setFirstResult(null)->setMaxResults(null); + foreach ($ids as $i => $id) { + $i++; + $whereInQuery->setParameter("{$namespace}_{$i}", $id); + } + } + + $result = $whereInQuery->getResult($this->query->getHydrationMode()); + } else { + $result = $this->cloneQuery($this->query) + ->setMaxResults($length) + ->setFirstResult($offset) + ->getResult($this->query->getHydrationMode()) + ; + } + return new \ArrayIterator($result); + } + + /** + * Clones a query. + * + * @param Query $query The query. + * + * @return Query The cloned query. + */ + private function cloneQuery(Query $query) + { + /* @var $cloneQuery Query */ + $cloneQuery = clone $query; + $cloneQuery->setParameters($query->getParameters()); + foreach ($query->getHints() as $name => $value) { + $cloneQuery->setHint($name, $value); + } + + return $cloneQuery; + } +} + diff --git a/lib/Doctrine/ORM/Tools/Pagination/WhereInWalker.php b/lib/Doctrine/ORM/Tools/Pagination/WhereInWalker.php new file mode 100644 index 000000000..5400ef247 --- /dev/null +++ b/lib/Doctrine/ORM/Tools/Pagination/WhereInWalker.php @@ -0,0 +1,142 @@ + + * @copyright Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/) + * @license http://hobodave.com/license.txt New BSD License + */ + +namespace Doctrine\ORM\Tools\Pagination; + +use Doctrine\ORM\Query\AST\ArithmeticExpression, + Doctrine\ORM\Query\AST\SimpleArithmeticExpression, + Doctrine\ORM\Query\TreeWalkerAdapter, + Doctrine\ORM\Query\AST\SelectStatement, + Doctrine\ORM\Query\AST\PathExpression, + Doctrine\ORM\Query\AST\InExpression, + Doctrine\ORM\Query\AST\NullComparisonExpression, + Doctrine\ORM\Query\AST\InputParameter, + Doctrine\ORM\Query\AST\ConditionalPrimary, + Doctrine\ORM\Query\AST\ConditionalTerm, + Doctrine\ORM\Query\AST\ConditionalExpression, + Doctrine\ORM\Query\AST\ConditionalFactor, + Doctrine\ORM\Query\AST\WhereClause; + +/** + * Replaces the whereClause of the AST with a WHERE id IN (:foo_1, :foo_2) equivalent + * + * @category DoctrineExtensions + * @package DoctrineExtensions\Paginate + * @author David Abdemoulaie + * @copyright Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/) + * @license http://hobodave.com/license.txt New BSD License + */ +class WhereInWalker extends TreeWalkerAdapter +{ + /** + * ID Count hint name + */ + const HINT_PAGINATOR_ID_COUNT = 'doctrine.id.count'; + + /** + * Primary key alias for query + */ + const PAGINATOR_ID_ALIAS = 'dpid'; + + /** + * Replaces the whereClause in the AST + * + * Generates a clause equivalent to WHERE IN (:dpid_1, :dpid_2, ...) + * + * The parameter namespace (dpid) is defined by + * the PAGINATOR_ID_ALIAS + * + * The total number of parameters is retrieved from + * the HINT_PAGINATOR_ID_COUNT query hint + * + * @param SelectStatement $AST + * @return void + */ + public function walkSelectStatement(SelectStatement $AST) + { + $rootComponents = array(); + foreach ($this->_getQueryComponents() AS $dqlAlias => $qComp) { + $isParent = array_key_exists('parent', $qComp) + && $qComp['parent'] === null + && $qComp['nestingLevel'] == 0 + ; + if ($isParent) { + $rootComponents[] = array($dqlAlias => $qComp); + } + } + if (count($rootComponents) > 1) { + throw new \RuntimeException("Cannot count query which selects two FROM components, cannot make distinction"); + } + $root = reset($rootComponents); + $parentName = key($root); + $parent = current($root); + + $pathExpression = new PathExpression( + PathExpression::TYPE_STATE_FIELD, $parentName, $parent['metadata']->getSingleIdentifierFieldName() + ); + $pathExpression->type = PathExpression::TYPE_STATE_FIELD; + + $count = $this->_getQuery()->getHint(self::HINT_PAGINATOR_ID_COUNT); + + if ($count > 0) { + $arithmeticExpression = new ArithmeticExpression(); + $arithmeticExpression->simpleArithmeticExpression = new SimpleArithmeticExpression( + array($pathExpression) + ); + $expression = new InExpression($arithmeticExpression); + $ns = self::PAGINATOR_ID_ALIAS; + + for ($i = 1; $i <= $count; $i++) { + $expression->literals[] = new InputParameter(":{$ns}_$i"); + } + } else { + $expression = new NullComparisonExpression($pathExpression); + $expression->not = false; + } + + $conditionalPrimary = new ConditionalPrimary; + $conditionalPrimary->simpleConditionalExpression = $expression; + if ($AST->whereClause) { + if ($AST->whereClause->conditionalExpression instanceof ConditionalTerm) { + $AST->whereClause->conditionalExpression->conditionalFactors[] = $conditionalPrimary; + } elseif ($AST->whereClause->conditionalExpression instanceof ConditionalPrimary) { + $AST->whereClause->conditionalExpression = new ConditionalExpression(array( + new ConditionalTerm(array( + $AST->whereClause->conditionalExpression, + $conditionalPrimary + )) + )); + } elseif ($AST->whereClause->conditionalExpression instanceof ConditionalExpression) { + $tmpPrimary = new ConditionalPrimary; + $tmpPrimary->conditionalExpression = $AST->whereClause->conditionalExpression; + $AST->whereClause->conditionalExpression = new ConditionalTerm(array( + $tmpPrimary, + $conditionalPrimary + )); + } + } else { + $AST->whereClause = new WhereClause( + new ConditionalExpression(array( + new ConditionalTerm(array( + $conditionalPrimary + )) + )) + ); + } + } +} + diff --git a/tests/Doctrine/Tests/ORM/Functional/PaginationTest.php b/tests/Doctrine/Tests/ORM/Functional/PaginationTest.php new file mode 100644 index 000000000..990353519 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/PaginationTest.php @@ -0,0 +1,105 @@ +useModelSet('cms'); + parent::setUp(); + $this->populate(); + } + + public function testCountSimpleWithoutJoin() + { + $dql = "SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u"; + $query = $this->_em->createQuery($dql); + + $paginator = new Paginator($query); + $this->assertEquals(3, count($paginator)); + } + + public function testCountWithFetchJoin() + { + $dql = "SELECT u,g FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN u.groups g"; + $query = $this->_em->createQuery($dql); + + $paginator = new Paginator($query); + $this->assertEquals(3, count($paginator)); + } + + public function testIterateSimpleWithoutJoinFetchJoinHandlingOff() + { + $dql = "SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u"; + $query = $this->_em->createQuery($dql); + + $paginator = new Paginator($query, false); + + $data = array(); + foreach ($paginator as $user) { + $data[] = $user; + } + $this->assertEquals(3, count($data)); + } + + public function testIterateSimpleWithoutJoinFetchJoinHandlingOn() + { + $dql = "SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u"; + $query = $this->_em->createQuery($dql); + + $paginator = new Paginator($query, true); + + $data = array(); + foreach ($paginator as $user) { + $data[] = $user; + } + $this->assertEquals(3, count($data)); + } + + public function testIterateWithFetchJoin() + { + $dql = "SELECT u,g FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN u.groups g"; + $query = $this->_em->createQuery($dql); + + $paginator = new Paginator($query, true); + + $data = array(); + foreach ($paginator as $user) { + $data[] = $user; + } + $this->assertEquals(3, count($data)); + } + + public function populate() + { + for ($i = 0; $i < 3; $i++) { + $user = new CmsUser(); + $user->name = "Name$i"; + $user->username = "username$i"; + $user->status = "active"; + $this->_em->persist($user); + + for ($j = 0; $j < 3; $j++) {; + $group = new CmsGroup(); + $group->name = "group$j"; + $user->addGroup($group); + $this->_em->persist($group); + } + } + $this->_em->flush(); + } +} diff --git a/tests/Doctrine/Tests/ORM/Tools/Pagination/CountWalkerTest.php b/tests/Doctrine/Tests/ORM/Tools/Pagination/CountWalkerTest.php new file mode 100644 index 000000000..816339df0 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Tools/Pagination/CountWalkerTest.php @@ -0,0 +1,78 @@ +entityManager->createQuery( + 'SELECT p, c, a FROM Doctrine\Tests\ORM\Tools\Pagination\BlogPost p JOIN p.category c JOIN p.author a'); + $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\CountWalker')); + $query->setHint(CountWalker::HINT_DISTINCT, true); + $query->setFirstResult(null)->setMaxResults(null); + + $this->assertEquals( + "SELECT count(DISTINCT b0_.id) AS sclr0 FROM BlogPost b0_ INNER JOIN Category c1_ ON b0_.category_id = c1_.id INNER JOIN Author a2_ ON b0_.author_id = a2_.id", $query->getSql() + ); + } + + public function testCountQuery_MixedResultsWithName() + { + $query = $this->entityManager->createQuery( + 'SELECT a, sum(a.name) as foo FROM Doctrine\Tests\ORM\Tools\Pagination\Author a'); + $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\CountWalker')); + $query->setHint(CountWalker::HINT_DISTINCT, true); + $query->setFirstResult(null)->setMaxResults(null); + + $this->assertEquals( + "SELECT count(DISTINCT a0_.id) AS sclr0 FROM Author a0_", $query->getSql() + ); + } + + public function testCountQuery_KeepsGroupBy() + { + $query = $this->entityManager->createQuery( + 'SELECT b FROM Doctrine\Tests\ORM\Tools\Pagination\BlogPost b GROUP BY b.id'); + $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\CountWalker')); + $query->setHint(CountWalker::HINT_DISTINCT, true); + $query->setFirstResult(null)->setMaxResults(null); + + $this->assertEquals( + "SELECT count(DISTINCT b0_.id) AS sclr0 FROM BlogPost b0_ GROUP BY b0_.id", $query->getSql() + ); + } + + public function testCountQuery_RemovesOrderBy() + { + $query = $this->entityManager->createQuery( + 'SELECT p, c, a FROM Doctrine\Tests\ORM\Tools\Pagination\BlogPost p JOIN p.category c JOIN p.author a ORDER BY a.name'); + $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\CountWalker')); + $query->setHint(CountWalker::HINT_DISTINCT, true); + $query->setFirstResult(null)->setMaxResults(null); + + $this->assertEquals( + "SELECT count(DISTINCT b0_.id) AS sclr0 FROM BlogPost b0_ INNER JOIN Category c1_ ON b0_.category_id = c1_.id INNER JOIN Author a2_ ON b0_.author_id = a2_.id", $query->getSql() + ); + } + + public function testCountQuery_RemovesLimits() + { + $query = $this->entityManager->createQuery( + 'SELECT p, c, a FROM Doctrine\Tests\ORM\Tools\Pagination\BlogPost p JOIN p.category c JOIN p.author a'); + $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\CountWalker')); + $query->setHint(CountWalker::HINT_DISTINCT, true); + $query->setFirstResult(null)->setMaxResults(null); + + $this->assertEquals( + "SELECT count(DISTINCT b0_.id) AS sclr0 FROM BlogPost b0_ INNER JOIN Category c1_ ON b0_.category_id = c1_.id INNER JOIN Author a2_ ON b0_.author_id = a2_.id", $query->getSql() + ); + } +} + diff --git a/tests/Doctrine/Tests/ORM/Tools/Pagination/LimitSubqueryWalkerTest.php b/tests/Doctrine/Tests/ORM/Tools/Pagination/LimitSubqueryWalkerTest.php new file mode 100644 index 000000000..f166dddb3 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Tools/Pagination/LimitSubqueryWalkerTest.php @@ -0,0 +1,36 @@ +entityManager->createQuery( + 'SELECT p, c, a FROM Doctrine\Tests\ORM\Tools\Pagination\MyBlogPost p JOIN p.category c JOIN p.author a'); + $limitQuery = clone $query; + $limitQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\LimitSubqueryWalker')); + + $this->assertEquals( + "SELECT DISTINCT m0_.id AS id0 FROM MyBlogPost m0_ INNER JOIN Category c1_ ON m0_.category_id = c1_.id INNER JOIN Author a2_ ON m0_.author_id = a2_.id", $limitQuery->getSql() + ); + } + + public function testCountQuery_MixedResultsWithName() + { + $query = $this->entityManager->createQuery( + 'SELECT a, sum(a.name) as foo FROM Doctrine\Tests\ORM\Tools\Pagination\Author a'); + $limitQuery = clone $query; + $limitQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\LimitSubqueryWalker')); + + $this->assertEquals( + "SELECT DISTINCT a0_.id AS id0, sum(a0_.name) AS sclr1 FROM Author a0_", $limitQuery->getSql() + ); + } +} + diff --git a/tests/Doctrine/Tests/ORM/Tools/Pagination/PaginationTestCase.php b/tests/Doctrine/Tests/ORM/Tools/Pagination/PaginationTestCase.php new file mode 100644 index 000000000..a6e4c67be --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Tools/Pagination/PaginationTestCase.php @@ -0,0 +1,142 @@ +entityManager = $this->_getTestEntityManager(); + } +} + + +/** +* @Entity +*/ +class MyBlogPost +{ + + /** @Id @column(type="integer") @generatedValue */ + public $id; + /** + * @ManyToOne(targetEntity="Author") + */ + public $author; + /** + * @ManyToOne(targetEntity="Category") + */ + public $category; +} + +/** + * @Entity + */ +class MyAuthor +{ + + /** @Id @column(type="integer") @generatedValue */ + public $id; + +} + +/** +* @Entity +*/ +class MyCategory +{ + + /** @id @column(type="integer") @generatedValue */ + public $id; + +} + + +/** + * @Entity + */ +class BlogPost +{ + + /** @Id @column(type="integer") @generatedValue */ + public $id; + /** + * @ManyToOne(targetEntity="Author") + */ + public $author; + /** + * @ManyToOne(targetEntity="Category") + */ + public $category; +} + +/** + * @Entity + */ +class Author +{ + + /** @Id @column(type="integer") @generatedValue */ + public $id; + /** @Column(type="string") */ + public $name; + +} + +/** + * @Entity + */ +class Person +{ + + /** @Id @column(type="integer") @generatedValue */ + public $id; + /** @Column(type="string") */ + public $name; + /** @Column(type="string") */ + public $biography; + +} + +/** + * @Entity + */ +class Category +{ + + /** @id @column(type="integer") @generatedValue */ + public $id; + +} + + +/** @Entity @Table(name="groups") */ +class Group +{ + + /** @Id @column(type="integer") @generatedValue */ + public $id; + /** @ManyToMany(targetEntity="User", mappedBy="groups") */ + public $users; +} + +/** @Entity */ +class User +{ + + /** @Id @column(type="integer") @generatedValue */ + public $id; + /** + * @ManyToMany(targetEntity="Group", inversedBy="users") + * @JoinTable( + * name="user_group", + * joinColumns = {@JoinColumn(name="user_id", referencedColumnName="id")}, + * inverseJoinColumns = {@JoinColumn(name="group_id", referencedColumnName="id")} + * ) + */ + public $groups; +} diff --git a/tests/Doctrine/Tests/ORM/Tools/Pagination/WhereInWalkerTest.php b/tests/Doctrine/Tests/ORM/Tools/Pagination/WhereInWalkerTest.php new file mode 100644 index 000000000..d7bcd0e12 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Tools/Pagination/WhereInWalkerTest.php @@ -0,0 +1,111 @@ +entityManager->createQuery( + 'SELECT u, g FROM Doctrine\Tests\ORM\Tools\Pagination\User u JOIN u.groups g' + ); + $whereInQuery = clone $query; + $whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\WhereInWalker')); + $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10); + + $this->assertEquals( + "SELECT u0_.id AS id0, g1_.id AS id1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE u0_.id IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", $whereInQuery->getSql() + ); + } + + public function testCountQuery_MixedResultsWithName() + { + $query = $this->entityManager->createQuery( + 'SELECT a, sum(a.name) as foo FROM Doctrine\Tests\ORM\Tools\Pagination\Author a' + ); + $whereInQuery = clone $query; + $whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\WhereInWalker')); + $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10); + + $this->assertEquals( + "SELECT a0_.id AS id0, a0_.name AS name1, sum(a0_.name) AS sclr2 FROM Author a0_ WHERE a0_.id IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", $whereInQuery->getSql() + ); + } + + public function testWhereInQuery_SingleWhere() + { + $query = $this->entityManager->createQuery( + 'SELECT u, g FROM Doctrine\Tests\ORM\Tools\Pagination\User u JOIN u.groups g WHERE 1 = 1' + ); + $whereInQuery = clone $query; + $whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\WhereInWalker')); + $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10); + + $this->assertEquals( + "SELECT u0_.id AS id0, g1_.id AS id1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE 1 = 1 AND u0_.id IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", $whereInQuery->getSql() + ); + } + + public function testWhereInQuery_MultipleWhereWithAnd() + { + $query = $this->entityManager->createQuery( + 'SELECT u, g FROM Doctrine\Tests\ORM\Tools\Pagination\User u JOIN u.groups g WHERE 1 = 1 AND 2 = 2' + ); + $whereInQuery = clone $query; + $whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\WhereInWalker')); + $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10); + + $this->assertEquals( + "SELECT u0_.id AS id0, g1_.id AS id1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE 1 = 1 AND 2 = 2 AND u0_.id IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", $whereInQuery->getSql() + ); + } + + public function testWhereInQuery_MultipleWhereWithOr() + { + $query = $this->entityManager->createQuery( + 'SELECT u, g FROM Doctrine\Tests\ORM\Tools\Pagination\User u JOIN u.groups g WHERE 1 = 1 OR 2 = 2' + ); + $whereInQuery = clone $query; + $whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\WhereInWalker')); + $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10); + + $this->assertEquals( + "SELECT u0_.id AS id0, g1_.id AS id1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE (1 = 1 OR 2 = 2) AND u0_.id IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", $whereInQuery->getSql() + ); + } + + public function testWhereInQuery_MultipleWhereWithMixed_1() + { + $query = $this->entityManager->createQuery( + 'SELECT u, g FROM Doctrine\Tests\ORM\Tools\Pagination\User u JOIN u.groups g WHERE (1 = 1 OR 2 = 2) AND 3 = 3' + ); + $whereInQuery = clone $query; + $whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\WhereInWalker')); + $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10); + + $this->assertEquals( + "SELECT u0_.id AS id0, g1_.id AS id1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE (1 = 1 OR 2 = 2) AND 3 = 3 AND u0_.id IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", $whereInQuery->getSql() + ); + } + + public function testWhereInQuery_MultipleWhereWithMixed_2() + { + $query = $this->entityManager->createQuery( + 'SELECT u, g FROM Doctrine\Tests\ORM\Tools\Pagination\User u JOIN u.groups g WHERE 1 = 1 AND 2 = 2 OR 3 = 3' + ); + $whereInQuery = clone $query; + $whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\WhereInWalker')); + $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10); + + $this->assertEquals( + "SELECT u0_.id AS id0, g1_.id AS id1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE (1 = 1 AND 2 = 2 OR 3 = 3) AND u0_.id IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", $whereInQuery->getSql() + ); + } +} + From 5dc0081f563f0b05ac3fbae4d23ee7bca5925cfd Mon Sep 17 00:00:00 2001 From: Sander Marechal Date: Mon, 23 Jan 2012 15:42:41 +0100 Subject: [PATCH 53/83] Add support for paginating WHERE NOT ... queries The Pagination tool throws an exception on a DQL query like: SELECT u FROM User u WHERE NOT (u INSTANCE OF Person) This is because Paginate does not know about the Doctrine\ORM\Query\AST\ConditionalFactor which implements the NOT operator. This patch adds support for that. --- .../ORM/Tools/Pagination/WhereInWalker.php | 4 +++- .../ORM/Tools/Pagination/WhereInWalkerTest.php | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/Pagination/WhereInWalker.php b/lib/Doctrine/ORM/Tools/Pagination/WhereInWalker.php index 5400ef247..3bfb7b3fb 100644 --- a/lib/Doctrine/ORM/Tools/Pagination/WhereInWalker.php +++ b/lib/Doctrine/ORM/Tools/Pagination/WhereInWalker.php @@ -120,7 +120,9 @@ class WhereInWalker extends TreeWalkerAdapter $conditionalPrimary )) )); - } elseif ($AST->whereClause->conditionalExpression instanceof ConditionalExpression) { + } elseif ($AST->whereClause->conditionalExpression instanceof ConditionalExpression + || $AST->whereClause->conditionalExpression instanceof ConditionalFactor + ) { $tmpPrimary = new ConditionalPrimary; $tmpPrimary->conditionalExpression = $AST->whereClause->conditionalExpression; $AST->whereClause->conditionalExpression = new ConditionalTerm(array( diff --git a/tests/Doctrine/Tests/ORM/Tools/Pagination/WhereInWalkerTest.php b/tests/Doctrine/Tests/ORM/Tools/Pagination/WhereInWalkerTest.php index d7bcd0e12..0db5d2587 100644 --- a/tests/Doctrine/Tests/ORM/Tools/Pagination/WhereInWalkerTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/Pagination/WhereInWalkerTest.php @@ -107,5 +107,19 @@ class WhereInWalkerTest extends PaginationTestCase "SELECT u0_.id AS id0, g1_.id AS id1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE (1 = 1 AND 2 = 2 OR 3 = 3) AND u0_.id IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", $whereInQuery->getSql() ); } + + public function testWhereInQuery_WhereNot() + { + $query = $this->entityManager->createQuery( + 'SELECT u, g FROM Doctrine\Tests\ORM\Tools\Pagination\User u JOIN u.groups g WHERE NOT 1 = 2' + ); + $whereInQuery = clone $query; + $whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\WhereInWalker')); + $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10); + + $this->assertEquals( + "SELECT u0_.id AS id0, g1_.id AS id1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE (NOT 1 = 2) AND u0_.id IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", $whereInQuery->getSql() + ); + } } From c6c82efe07438c5f38b76251a7ecfb0cc47c5e44 Mon Sep 17 00:00:00 2001 From: Sander Marechal Date: Mon, 23 Jan 2012 16:11:30 +0100 Subject: [PATCH 54/83] Fixed indentation --- .../Doctrine/Tests/ORM/Tools/Pagination/WhereInWalkerTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Tools/Pagination/WhereInWalkerTest.php b/tests/Doctrine/Tests/ORM/Tools/Pagination/WhereInWalkerTest.php index 0db5d2587..330e64ed1 100644 --- a/tests/Doctrine/Tests/ORM/Tools/Pagination/WhereInWalkerTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/Pagination/WhereInWalkerTest.php @@ -111,14 +111,14 @@ class WhereInWalkerTest extends PaginationTestCase public function testWhereInQuery_WhereNot() { $query = $this->entityManager->createQuery( - 'SELECT u, g FROM Doctrine\Tests\ORM\Tools\Pagination\User u JOIN u.groups g WHERE NOT 1 = 2' + 'SELECT u, g FROM Doctrine\Tests\ORM\Tools\Pagination\User u JOIN u.groups g WHERE NOT 1 = 2' ); $whereInQuery = clone $query; $whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\WhereInWalker')); $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, 10); $this->assertEquals( - "SELECT u0_.id AS id0, g1_.id AS id1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE (NOT 1 = 2) AND u0_.id IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", $whereInQuery->getSql() + "SELECT u0_.id AS id0, g1_.id AS id1 FROM User u0_ INNER JOIN user_group u2_ ON u0_.id = u2_.user_id INNER JOIN groups g1_ ON g1_.id = u2_.group_id WHERE (NOT 1 = 2) AND u0_.id IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", $whereInQuery->getSql() ); } } From 1d2f46bda7800fba1c9e85582496b32ec920cb87 Mon Sep 17 00:00:00 2001 From: Benjamin Michotte Date: Tue, 24 Jan 2012 19:08:25 +0100 Subject: [PATCH 55/83] Add fluent code for relations --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index 915675712..0f369c4d0 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -128,10 +128,12 @@ public function ($) * * * @param $ + * @return */ public function ($) { $this->[] = $; +return $this; }'; private static $_lifecycleCallbackMethodTemplate = From d9bb861b1fd8741bce443898f4b1455f4424d398 Mon Sep 17 00:00:00 2001 From: Thomas Rabaix Date: Tue, 24 Jan 2012 22:51:21 +0100 Subject: [PATCH 56/83] Fix DDC-1618 - add more check before throwing an iterateWithFetchJoinNotAllowed exception --- lib/Doctrine/ORM/Query/SqlWalker.php | 11 ++++++--- tests/Doctrine/Tests/ORM/Query/QueryTest.php | 24 ++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index f1ef94ff1..c91eae147 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -590,6 +590,10 @@ class SqlWalker implements TreeWalker $sql = 'SELECT ' . (($selectClause->isDistinct) ? 'DISTINCT ' : ''); $sqlSelectExpressions = array_filter(array_map(array($this, 'walkSelectExpression'), $selectClause->selectExpressions)); + if ($this->_query->getHint(Query::HINT_INTERNAL_ITERATION) == true && $selectClause->isDistinct) { + $this->_query->setHint('doctrine.distinct', true); + } + $addMetaColumns = ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) && $this->_query->getHydrationMode() == Query::HYDRATE_OBJECT || @@ -811,9 +815,10 @@ class SqlWalker implements TreeWalker // Ensure we got the owning side, since it has all mapping info $assoc = ( ! $relation['isOwningSide']) ? $targetClass->associationMappings[$relation['mappedBy']] : $relation; - - if ($this->_query->getHint(Query::HINT_INTERNAL_ITERATION) == true && $relation['type'] & ClassMetadata::TO_MANY) { - throw QueryException::iterateWithFetchJoinNotAllowed($assoc); + if ($this->_query->getHint(Query::HINT_INTERNAL_ITERATION) == true && (!$this->_query->getHint('doctrine.distinct') || isset($this->_selectedClasses[$joinedDqlAlias]))) { + if ($relation['type'] == ClassMetadata::ONE_TO_MANY || $relation['type'] == ClassMetadata::MANY_TO_MANY) { + throw QueryException::iterateWithFetchJoinNotAllowed($assoc); + } } if ($joinVarDecl->indexBy) { diff --git a/tests/Doctrine/Tests/ORM/Query/QueryTest.php b/tests/Doctrine/Tests/ORM/Query/QueryTest.php index 4e142e149..f975af3ec 100644 --- a/tests/Doctrine/Tests/ORM/Query/QueryTest.php +++ b/tests/Doctrine/Tests/ORM/Query/QueryTest.php @@ -101,4 +101,28 @@ class QueryTest extends \Doctrine\Tests\OrmTestCase $q->useResultCache(true); $this->assertSame($this->_em->getConfiguration()->getResultCacheImpl(), $q->getQueryCacheProfile()->getResultCacheDriver()); } + + /** + * @expectedException Doctrine\ORM\Query\QueryException + **/ + public function testIterateWithNoDistinctAndWrongSelectClause() + { + $q = $this->_em->createQuery("select u, a from Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN u.articles a"); + $q->iterate(); + } + + /** + * @expectedException Doctrine\ORM\Query\QueryException + **/ + public function testIterateWithNoDistinctAndWithValidSelectClause() + { + $q = $this->_em->createQuery("select u from Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN u.articles a"); + $q->iterate(); + } + + public function testIterateWithDistinct() + { + $q = $this->_em->createQuery("SELECT DISTINCT u from Doctrine\Tests\Models\CMS\CmsUser u LEFT JOIN u.articles a"); + $q->iterate(); + } } From 8027fca378a3c5992d30a07f606dd0f0fbcee2a1 Mon Sep 17 00:00:00 2001 From: Thomas Rabaix Date: Tue, 24 Jan 2012 23:34:20 +0100 Subject: [PATCH 57/83] Add SqlWalker::HINT_DISTINCT constant --- lib/Doctrine/ORM/Query/SqlWalker.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index c91eae147..13910054c 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -39,6 +39,11 @@ use Doctrine\DBAL\LockMode, */ class SqlWalker implements TreeWalker { + /** + * @var string + */ + const HINT_DISTINCT = 'doctrine.distinct'; + /** * @var ResultSetMapping */ @@ -591,7 +596,7 @@ class SqlWalker implements TreeWalker $sqlSelectExpressions = array_filter(array_map(array($this, 'walkSelectExpression'), $selectClause->selectExpressions)); if ($this->_query->getHint(Query::HINT_INTERNAL_ITERATION) == true && $selectClause->isDistinct) { - $this->_query->setHint('doctrine.distinct', true); + $this->_query->setHint(self::HINT_DISTINCT, true); } $addMetaColumns = ! $this->_query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) && @@ -815,7 +820,7 @@ class SqlWalker implements TreeWalker // Ensure we got the owning side, since it has all mapping info $assoc = ( ! $relation['isOwningSide']) ? $targetClass->associationMappings[$relation['mappedBy']] : $relation; - if ($this->_query->getHint(Query::HINT_INTERNAL_ITERATION) == true && (!$this->_query->getHint('doctrine.distinct') || isset($this->_selectedClasses[$joinedDqlAlias]))) { + if ($this->_query->getHint(Query::HINT_INTERNAL_ITERATION) == true && (!$this->_query->getHint(self::HINT_DISTINCT) || isset($this->_selectedClasses[$joinedDqlAlias]))) { if ($relation['type'] == ClassMetadata::ONE_TO_MANY || $relation['type'] == ClassMetadata::MANY_TO_MANY) { throw QueryException::iterateWithFetchJoinNotAllowed($assoc); } From 7dae89bb02d20bab79961f46188e4d18a2a24586 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Wed, 25 Jan 2012 10:19:01 +0100 Subject: [PATCH 58/83] [DDC-1619] Add QueryBuilder#distinct --- lib/Doctrine/ORM/QueryBuilder.php | 28 +++++++++++++++++-- tests/Doctrine/Tests/ORM/QueryBuilderTest.php | 15 +++++++++- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/lib/Doctrine/ORM/QueryBuilder.php b/lib/Doctrine/ORM/QueryBuilder.php index c67506d98..521263495 100644 --- a/lib/Doctrine/ORM/QueryBuilder.php +++ b/lib/Doctrine/ORM/QueryBuilder.php @@ -50,6 +50,7 @@ class QueryBuilder * @var array The array of DQL parts collected. */ private $_dqlParts = array( + 'distinct' => false, 'select' => array(), 'from' => array(), 'join' => array(), @@ -346,7 +347,7 @@ class QueryBuilder * ->setParameters(array( * 'user_id1' => 1, * 'user_id2' => 2 - * )); + )); * * * @param array $params The query parameters to set. @@ -503,6 +504,25 @@ class QueryBuilder return $this->add('select', new Expr\Select($selects), false); } + /** + * Add a DISTINCT flag to this query. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->distinct() + * ->from('User', 'u'); + * + * + * @param bool + * @return QueryBuilder + */ + public function distinct($flag = true) + { + $this->_dqlParts['distinct'] = (bool) $flag; + return $this; + } + /** * Adds an item that is to be returned in the query result. * @@ -981,7 +1001,9 @@ class QueryBuilder private function _getDQLForSelect() { - $dql = 'SELECT' . $this->_getReducedDQLQueryPart('select', array('pre' => ' ', 'separator' => ', ')); + $dql = 'SELECT' + . ($this->_dqlParts['distinct']===true ? ' DISTINCT' : '') + . $this->_getReducedDQLQueryPart('select', array('pre' => ' ', 'separator' => ', ')); $fromParts = $this->getDQLPart('from'); $joinParts = $this->getDQLPart('join'); @@ -1090,4 +1112,4 @@ class QueryBuilder } } } -} \ No newline at end of file +} diff --git a/tests/Doctrine/Tests/ORM/QueryBuilderTest.php b/tests/Doctrine/Tests/ORM/QueryBuilderTest.php index 93af68ae6..f078b8995 100644 --- a/tests/Doctrine/Tests/ORM/QueryBuilderTest.php +++ b/tests/Doctrine/Tests/ORM/QueryBuilderTest.php @@ -732,4 +732,17 @@ class QueryBuilderTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u', $qb->getDQL()); } -} \ No newline at end of file + + /** + * @group DDC-1619 + */ + public function testAddDistinct() + { + $qb = $this->_em->createQueryBuilder() + ->select('u') + ->distinct() + ->from('Doctrine\Tests\Models\CMS\CmsUser', 'u'); + + $this->assertEquals('SELECT DISTINCT u FROM Doctrine\Tests\Models\CMS\CmsUser u', $qb->getDQL()); + } +} From d68fcd8bd20511f55f9ebdfe24d589db7cfc464b Mon Sep 17 00:00:00 2001 From: Jan Sorgalla Date: Thu, 26 Jan 2012 14:36:56 +0100 Subject: [PATCH 59/83] Implement custom options on table level --- doctrine-mapping.xsd | 18 ++++++++++ .../ORM/Mapping/ClassMetadataInfo.php | 4 +++ .../ORM/Mapping/Driver/AnnotationDriver.php | 4 +++ lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php | 33 +++++++++++++++++++ .../ORM/Mapping/Driver/YamlDriver.php | 4 +++ lib/Doctrine/ORM/Mapping/Table.php | 2 ++ lib/Doctrine/ORM/Tools/SchemaTool.php | 6 ++++ .../ORM/Mapping/AbstractMappingDriverTest.php | 19 ++++++++++- .../php/Doctrine.Tests.ORM.Mapping.User.php | 4 +++ .../Doctrine.Tests.ORM.Mapping.User.dcm.xml | 8 ++++- .../Doctrine.Tests.ORM.Mapping.User.dcm.yml | 4 +++ .../Tests/ORM/Tools/SchemaToolTest.php | 11 ++++--- 12 files changed, 111 insertions(+), 6 deletions(-) diff --git a/doctrine-mapping.xsd b/doctrine-mapping.xsd index e8e21f193..5dd510ff1 100644 --- a/doctrine-mapping.xsd +++ b/doctrine-mapping.xsd @@ -86,6 +86,7 @@ + @@ -110,6 +111,23 @@ + + + + + + + + + + + + + + + + + diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 328d7bc35..253ab6003 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -1705,6 +1705,10 @@ class ClassMetadataInfo implements ClassMetadata if (isset($table['uniqueConstraints'])) { $this->table['uniqueConstraints'] = $table['uniqueConstraints']; } + + if (isset($table['options'])) { + $this->table['options'] = $table['options']; + } } /** diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index f11331ffa..e95d32e94 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -190,6 +190,10 @@ class AnnotationDriver implements Driver } } + if ($tableAnnot->options !== null) { + $primaryTable['options'] = $tableAnnot->options; + } + $metadata->setPrimaryTable($primaryTable); } diff --git a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php index 7308a23e3..e8e2a80a6 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php @@ -161,6 +161,10 @@ class XmlDriver extends AbstractFileDriver } } + if (isset($xmlRoot->options)) { + $metadata->table['options'] = $this->_parseOptions($xmlRoot->options->children()); + } + // Evaluate mappings if (isset($xmlRoot->field)) { foreach ($xmlRoot->field as $fieldMapping) { @@ -454,6 +458,35 @@ class XmlDriver extends AbstractFileDriver } } + /** + * Parses (nested) option elements. + * + * @param $options The XML element. + * @return array The options array. + */ + private function _parseOptions(SimpleXMLElement $options) + { + $array = array(); + + foreach ($options as $option) { + if ($option->count()) { + $value = $this->_parseOptions($option->children()); + } else { + $value = (string) $option; + } + + $attr = $option->attributes(); + + if (isset($attr->name)) { + $array[(string) $attr->name] = $value; + } else { + $array[] = $value; + } + } + + return $array; + } + /** * Constructs a joinColumn mapping array based on the information * found in the given SimpleXMLElement. diff --git a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php index a65b6fb2f..0106f3aba 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php @@ -156,6 +156,10 @@ class YamlDriver extends AbstractFileDriver } } + if (isset($element['options'])) { + $metadata->table['options'] = $element['options']; + } + $associationIds = array(); if (isset($element['id'])) { // Evaluate identifier settings diff --git a/lib/Doctrine/ORM/Mapping/Table.php b/lib/Doctrine/ORM/Mapping/Table.php index 41db294de..25e8e82b2 100644 --- a/lib/Doctrine/ORM/Mapping/Table.php +++ b/lib/Doctrine/ORM/Mapping/Table.php @@ -33,4 +33,6 @@ final class Table implements Annotation public $indexes; /** @var array<\Doctrine\ORM\Mapping\UniqueConstraint> */ public $uniqueConstraints; + /** @var array */ + public $options = array(); } diff --git a/lib/Doctrine/ORM/Tools/SchemaTool.php b/lib/Doctrine/ORM/Tools/SchemaTool.php index efe7f7b7c..85e50b2b0 100644 --- a/lib/Doctrine/ORM/Tools/SchemaTool.php +++ b/lib/Doctrine/ORM/Tools/SchemaTool.php @@ -235,6 +235,12 @@ class SchemaTool } } + if (isset($class->table['options'])) { + foreach ($class->table['options'] AS $key => $val) { + $table->addOption($key, $val); + } + } + $processedClasses[$class->name] = true; if ($class->isIdGeneratorSequence() && $class->name == $class->rootEntityName) { diff --git a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php index 40ff74342..6e440a457 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php @@ -77,6 +77,21 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase * @depends testEntityTableNameAndInheritance * @param ClassMetadata $class */ + public function testEntityOptions($class) + { + $this->assertArrayHasKey('options', $class->table, 'ClassMetadata should have options key in table property.'); + + $this->assertEquals(array( + 'foo' => 'bar', 'baz' => array('key' => 'val') + ), $class->table['options']); + + return $class; + } + + /** + * @depends testEntityOptions + * @param ClassMetadata $class + */ public function testEntitySequence($class) { $this->assertInternalType('array', $class->sequenceGeneratorDefinition, 'No Sequence Definition set on this driver.'); @@ -424,7 +439,8 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase * @Table( * name="cms_users", * uniqueConstraints={@UniqueConstraint(name="search_idx", columns={"name", "user_email"})}, - * indexes={@Index(name="name_idx", columns={"name"}), @Index(name="0", columns={"user_email"})} + * indexes={@Index(name="name_idx", columns={"name"}), @Index(name="0", columns={"user_email"})}, + * options={"foo": "bar", "baz": {"key": "val"}} * ) */ class User @@ -495,6 +511,7 @@ class User $metadata->setInheritanceType(ClassMetadataInfo::INHERITANCE_TYPE_NONE); $metadata->setPrimaryTable(array( 'name' => 'cms_users', + 'options' => array('foo' => 'bar', 'baz' => array('key' => 'val')), )); $metadata->setChangeTrackingPolicy(ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT); $metadata->addLifecycleCallback('doStuffOnPrePersist', 'prePersist'); diff --git a/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.User.php b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.User.php index 33020aa6b..b9ce24ccd 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.User.php +++ b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.User.php @@ -106,6 +106,10 @@ $metadata->mapManyToMany(array( ), 'orderBy' => NULL, )); +$metadata->table['options'] = array( + 'foo' => 'bar', + 'baz' => array('key' => 'val') +); $metadata->table['uniqueConstraints'] = array( 'search_idx' => array('columns' => array('name', 'user_email')), ); diff --git a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.User.dcm.xml b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.User.dcm.xml index f116fb0fe..44521e827 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.User.dcm.xml +++ b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.User.dcm.xml @@ -6,6 +6,12 @@ http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd"> + + + + + @@ -15,7 +21,7 @@ - + diff --git a/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.User.dcm.yml b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.User.dcm.yml index 333474981..7a39a874a 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.User.dcm.yml +++ b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.User.dcm.yml @@ -1,6 +1,10 @@ Doctrine\Tests\ORM\Mapping\User: type: entity table: cms_users + options: + foo: bar + baz: + key: val namedQueries: all: SELECT u FROM __CLASS__ u id: diff --git a/tests/Doctrine/Tests/ORM/Tools/SchemaToolTest.php b/tests/Doctrine/Tests/ORM/Tools/SchemaToolTest.php index 6e4b62efe..66093a38d 100644 --- a/tests/Doctrine/Tests/ORM/Tools/SchemaToolTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/SchemaToolTest.php @@ -32,19 +32,21 @@ class SchemaToolTest extends \Doctrine\Tests\OrmTestCase $this->assertTrue($schema->getTable('cms_users')->columnsAreIndexed(array('username')), "username column should be indexed."); } - public function testColumnAnnotationOptionsAttribute() + public function testAnnotationOptionsAttribute() { $em = $this->_getTestEntityManager(); $schemaTool = new SchemaTool($em); $classes = array( - $em->getClassMetadata(__NAMESPACE__ . '\\TestEntityWithColumnAnnotationOptionsAttribute'), + $em->getClassMetadata(__NAMESPACE__ . '\\TestEntityWithAnnotationOptionsAttribute'), ); $schema = $schemaTool->getSchemaFromMetadata($classes); $expected = array('foo' => 'bar', 'baz' => array('key' => 'val')); - $this->assertEquals($expected, $schema->getTable('TestEntityWithColumnAnnotationOptionsAttribute')->getColumn('test')->getCustomSchemaOptions(), "options annotation are passed to the columns customSchemaOptions"); + + $this->assertEquals($expected, $schema->getTable('TestEntityWithAnnotationOptionsAttribute')->getOptions(), "options annotation are passed to the tables optionss"); + $this->assertEquals($expected, $schema->getTable('TestEntityWithAnnotationOptionsAttribute')->getColumn('test')->getCustomSchemaOptions(), "options annotation are passed to the columns customSchemaOptions"); } /** @@ -103,8 +105,9 @@ class SchemaToolTest extends \Doctrine\Tests\OrmTestCase /** * @Entity + * @Table(options={"foo": "bar", "baz": {"key": "val"}}) */ -class TestEntityWithColumnAnnotationOptionsAttribute +class TestEntityWithAnnotationOptionsAttribute { /** @Id @Column */ private $id; From 88bbee127c8c7ca69bcda26763ab6d3228174ae7 Mon Sep 17 00:00:00 2001 From: Sergio Moya Date: Thu, 26 Jan 2012 17:37:50 +0100 Subject: [PATCH 60/83] No unique join column fields for Single Table inheritance type. --- lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 328d7bc35..e4b2bf4da 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -1217,7 +1217,7 @@ class ClassMetadataInfo implements ClassMetadata $uniqueContraintColumns = array(); foreach ($mapping['joinColumns'] as $key => &$joinColumn) { - if ($mapping['type'] === self::ONE_TO_ONE) { + if ($mapping['type'] === self::ONE_TO_ONE && ! $this->isInheritanceTypeSingleTable()) { if (count($mapping['joinColumns']) == 1) { $joinColumn['unique'] = true; } else { From fac820f0e29d3dbfb185d27bd9816066b0a2dc10 Mon Sep 17 00:00:00 2001 From: jsor Date: Fri, 27 Jan 2012 11:05:47 +0100 Subject: [PATCH 61/83] Complete custom column option implementation - Support for xml driver - Tests --- doctrine-mapping.xsd | 1 + lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php | 8 ++++---- .../Tests/ORM/Mapping/AbstractMappingDriverTest.php | 6 +++++- .../ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.User.php | 1 + .../Mapping/xml/Doctrine.Tests.ORM.Mapping.User.dcm.xml | 9 ++++++++- .../Mapping/yaml/Doctrine.Tests.ORM.Mapping.User.dcm.yml | 4 ++++ 6 files changed, 23 insertions(+), 6 deletions(-) diff --git a/doctrine-mapping.xsd b/doctrine-mapping.xsd index 5dd510ff1..227af4403 100644 --- a/doctrine-mapping.xsd +++ b/doctrine-mapping.xsd @@ -182,6 +182,7 @@ + diff --git a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php index e8e2a80a6..a528346e6 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php @@ -196,10 +196,6 @@ class XmlDriver extends AbstractFileDriver $mapping['unique'] = ((string)$fieldMapping['unique'] == "false") ? false : true; } - if (isset($fieldMapping['options'])) { - $mapping['options'] = (array)$fieldMapping['options']; - } - if (isset($fieldMapping['nullable'])) { $mapping['nullable'] = ((string)$fieldMapping['nullable'] == "false") ? false : true; } @@ -212,6 +208,10 @@ class XmlDriver extends AbstractFileDriver $mapping['columnDefinition'] = (string)$fieldMapping['column-definition']; } + if (isset($fieldMapping->options)) { + $mapping['options'] = $this->_parseOptions($fieldMapping->options->children()); + } + $metadata->mapField($mapping); } } diff --git a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php index 6e440a457..369c8fdeb 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php @@ -144,6 +144,9 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase $this->assertTrue($class->fieldMappings['name']['nullable']); $this->assertTrue($class->fieldMappings['name']['unique']); + $expected = array('foo' => 'bar', 'baz' => array('key' => 'val')); + $this->assertEquals($expected, $class->fieldMappings['name']['options']); + return $class; } @@ -454,7 +457,7 @@ class User public $id; /** - * @Column(length=50, nullable=true, unique=true) + * @Column(length=50, nullable=true, unique=true, options={"foo": "bar", "baz": {"key": "val"}}) */ public $name; @@ -530,6 +533,7 @@ class User 'unique' => true, 'nullable' => true, 'columnName' => 'name', + 'options' => array('foo' => 'bar', 'baz' => array('key' => 'val')), )); $metadata->mapField(array( 'fieldName' => 'email', diff --git a/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.User.php b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.User.php index b9ce24ccd..df4dedda5 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.User.php +++ b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.User.php @@ -27,6 +27,7 @@ $metadata->mapField(array( 'unique' => true, 'nullable' => true, 'columnName' => 'name', + 'options' => array('foo' => 'bar', 'baz' => array('key' => 'val')), )); $metadata->mapField(array( 'fieldName' => 'email', diff --git a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.User.dcm.xml b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.User.dcm.xml index 44521e827..18f4d5819 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.User.dcm.xml +++ b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.User.dcm.xml @@ -37,7 +37,14 @@ - + + + + + + + diff --git a/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.User.dcm.yml b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.User.dcm.yml index 7a39a874a..5c1018560 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.User.dcm.yml +++ b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.User.dcm.yml @@ -22,6 +22,10 @@ Doctrine\Tests\ORM\Mapping\User: length: 50 nullable: true unique: true + options: + foo: bar + baz: + key: val email: type: string column: user_email From 551df4af5210a9c1f083d1851bb90913b1fccf89 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 28 Jan 2012 11:16:36 +0100 Subject: [PATCH 62/83] [DDC-1617] Implement support for Generating Unique Constraints/Indexes in @Table annotation of EntityGenerator. --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 23 +++++++++++++++++++ .../Tests/ORM/Tools/EntityGeneratorTest.php | 2 ++ 2 files changed, 25 insertions(+) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index 915675712..c57ee4f27 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -583,9 +583,32 @@ public function () $table[] = 'name="' . $metadata->table['name'] . '"'; } + if (isset($metadata->table['uniqueConstraints']) && $metadata->table['uniqueConstraints']) { + $constraints = $this->_generateTableConstraints('UniqueConstraint', $metadata->table['uniqueConstraints']); + $table[] = 'uniqueConstraints={' . $constraints . '}'; + } + + if (isset($metadata->table['indexes']) && $metadata->table['indexes']) { + $constraints = $this->_generateTableConstraints('Index', $metadata->table['indexes']); + $table[] = 'indexes={' . $constraints . '}'; + } + return '@' . $this->_annotationsPrefix . 'Table(' . implode(', ', $table) . ')'; } + private function _generateTableConstraints($constraintName, $constraints) + { + $annotations = array(); + foreach ($constraints as $name => $constraint) { + $columns = array(); + foreach ($constraint['columns'] as $column) { + $columns[] = '"' . $column . '"'; + } + $annotations[] = '@' . $this->_annotationsPrefix . $constraintName . '(name="' . $name . '", columns={' . implode(', ', $columns) . '})'; + } + return implode(', ', $annotations); + } + private function _generateInheritanceAnnotation($metadata) { if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) { diff --git a/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php b/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php index 4b02a94d1..f6fdf19c5 100644 --- a/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php @@ -47,6 +47,8 @@ class EntityGeneratorTest extends \Doctrine\Tests\OrmTestCase $metadata->customRepositoryClassName = $this->_namespace . '\EntityGeneratorBookRepository'; $metadata->table['name'] = 'book'; + $metadata->table['uniqueConstraints']['name_uniq'] = array('columns' => array('name')); + $metadata->table['indexes']['status_idx'] = array('columns' => array('status')); $metadata->mapField(array('fieldName' => 'name', 'type' => 'string')); $metadata->mapField(array('fieldName' => 'status', 'type' => 'string', 'default' => 'published')); $metadata->mapField(array('fieldName' => 'id', 'type' => 'integer', 'id' => true)); From 3407620bf80ec5d09475dcab1f0e089aecfcc064 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 28 Jan 2012 12:25:01 +0100 Subject: [PATCH 63/83] [DDC-1526] Collections are not marked as initialized when they are fetch joined but dont contain any results. This only occurs when using LEFT JOINs on the assocations and causes another query to be fired when the empty collection is accessed again. --- .../ORM/Internal/Hydration/ObjectHydrator.php | 7 +- .../ORM/Functional/Ticket/DDC1526Test.php | 67 +++++++++++++++++++ 2 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1526Test.php diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 5595727b0..878c130c2 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -359,7 +359,6 @@ class ObjectHydrator extends AbstractHydrator continue; } - $parentClass = $this->_ce[$this->_rsm->aliasMap[$parentAlias]]; $oid = spl_object_hash($parentObject); $relationField = $this->_rsm->relationMap[$dqlAlias]; @@ -368,6 +367,7 @@ class ObjectHydrator extends AbstractHydrator // Check the type of the relation (many or single-valued) if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) { + $reflFieldValue = $reflField->getValue($parentObject); // PATH A: Collection-valued association if (isset($nonemptyComponents[$dqlAlias])) { $collKey = $oid . $relationField; @@ -408,9 +408,12 @@ class ObjectHydrator extends AbstractHydrator // Update result pointer $this->_resultPointers[$dqlAlias] = $reflFieldValue[$index]; } - } else if ( ! $reflField->getValue($parentObject)) { + } else if ( ! $reflFieldValue) { $reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias); + } else if ($reflFieldValue instanceof PersistentCollection && $reflFieldValue->isInitialized() === false) { + $reflFieldValue->setInitialized(true); } + } else { // PATH B: Single-valued association $reflFieldValue = $reflField->getValue($parentObject); diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1526Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1526Test.php new file mode 100644 index 000000000..27d0a87ba --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1526Test.php @@ -0,0 +1,67 @@ +_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\\DDC1526Menu'), + )); + } + + public function testIssue() + { + $parents = array(); + for ($i = 0; $i < 9; $i++) { + $entity = new DDC1526Menu; + + if (isset ($parents[($i % 3)])) { + $entity->parent = $parents[($i%3)]; + } + + $this->_em->persist($entity); + $parents[$i] = $entity; + } + $this->_em->flush(); + $this->_em->clear(); + + + $dql = "SELECT m, c + FROM " . __NAMESPACE__ . "\DDC1526Menu m + LEFT JOIN m.children c"; + $menus = $this->_em->createQuery($dql)->getResult(); + + // All Children collection now have to be initiailzed + foreach ($menus as $menu) { + $this->assertTrue($menu->children->isInitialized()); + } + } +} + +/** + * @Entity + */ +class DDC1526Menu +{ + /** + * @Column(type="integer") + * @Id + * @GeneratedValue + */ + public $id; + /** + * @ManyToOne(targetEntity="DDC1526Menu", inversedBy="children") + */ + public $parent; + + /** + * @OneToMany(targetEntity="DDC1526Menu", mappedBy="parent") + */ + public $children; +} From e774b1d8c0653098e715efaba788aa644fbf2371 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 29 Jan 2012 15:01:36 +0100 Subject: [PATCH 64/83] Fix test for non-mysql like datetimes. --- .../Tests/ORM/Functional/Ticket/DDC657Test.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC657Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC657Test.php index 40d30401a..2d4279ba3 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC657Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC657Test.php @@ -40,13 +40,13 @@ class DDC657Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertCount(2,$result); - $this->assertEquals('11:11:11', $result[0]['time']); - $this->assertEquals('2010-01-01', $result[0]['date']); - $this->assertEquals('2010-01-01 11:11:11', $result[0]['datetime']); + $this->assertContains('11:11:11', $result[0]['time']); + $this->assertContains('2010-01-01', $result[0]['date']); + $this->assertContains('2010-01-01 11:11:11', $result[0]['datetime']); - $this->assertEquals('12:12:12', $result[1]['time']); - $this->assertEquals('2010-02-02', $result[1]['date']); - $this->assertEquals('2010-02-02 12:12:12', $result[1]['datetime']); + $this->assertContains('12:12:12', $result[1]['time']); + $this->assertContains('2010-02-02', $result[1]['date']); + $this->assertContains('2010-02-02 12:12:12', $result[1]['datetime']); } public function testaTicketEntityArrayResult() @@ -117,4 +117,4 @@ class DDC657Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->flush(); } -} \ No newline at end of file +} From bea78f42e36b0063308626348684885c7c433443 Mon Sep 17 00:00:00 2001 From: Miha Vrhovnik Date: Mon, 30 Jan 2012 11:44:08 +0100 Subject: [PATCH 65/83] Proxy not initialized when parent has get function. Fixes DDC-1625 --- lib/Doctrine/ORM/Proxy/ProxyFactory.php | 2 +- .../ORM/Functional/ReferenceProxyTest.php | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index 75781eaeb..ed5d1560c 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -285,7 +285,7 @@ class ProxyFactory ); if ($cheapCheck) { - $code = file($class->reflClass->getFileName()); + $code = file($method->getDeclaringClass()->getFileName()); $code = trim(implode(" ", array_slice($code, $method->getStartLine() - 1, $method->getEndLine() - $method->getStartLine() + 1))); $pattern = sprintf(self::PATTERN_MATCH_ID_METHOD, $method->getName(), $identifier); diff --git a/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php b/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php index 676d41878..3ce24e739 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ReferenceProxyTest.php @@ -6,6 +6,7 @@ use Doctrine\ORM\Proxy\ProxyFactory; use Doctrine\ORM\Proxy\ProxyClassGenerator; use Doctrine\Tests\Models\ECommerce\ECommerceProduct; use Doctrine\Tests\Models\ECommerce\ECommerceShipping; +use Doctrine\Tests\Models\Company\CompanyAuction; require_once __DIR__ . '/../../TestInit.php'; @@ -39,6 +40,18 @@ class ReferenceProxyTest extends \Doctrine\Tests\OrmFunctionalTestCase return $product->getId(); } + public function createAuction() + { + $event = new CompanyAuction(); + $event->setData('Doctrine Cookbook'); + $this->_em->persist($event); + + $this->_em->flush(); + $this->_em->clear(); + + return $event->getId(); + } + public function testLazyLoadsFieldValuesFromDatabase() { $id = $this->createProduct(); @@ -161,6 +174,21 @@ class ReferenceProxyTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertFalse($entity->__isInitialized__, "Getting the identifier doesn't initialize the proxy."); } + /** + * @group DDC-1625 + */ + public function testDoNotInitializeProxyOnGettingTheIdentifier_DDC_1625() + { + $id = $this->createAuction(); + + /* @var $entity Doctrine\Tests\Models\Company\CompanyAuction */ + $entity = $this->_em->getReference('Doctrine\Tests\Models\Company\CompanyAuction' , $id); + + $this->assertFalse($entity->__isInitialized__, "Pre-Condition: Object is unitialized proxy."); + $this->assertEquals($id, $entity->getId()); + $this->assertFalse($entity->__isInitialized__, "Getting the identifier doesn't initialize the proxy when extending."); + } + public function testDoNotInitializeProxyOnGettingTheIdentifierAndReturnTheRightType() { $product = new ECommerceProduct(); From 299def471247a97fe62eb4f4fa993a9b9c4e4d0a Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 10 Feb 2012 21:38:42 +0100 Subject: [PATCH 66/83] DDC-1641 - Fix test producing failure when skipped. --- tests/Doctrine/Tests/ORM/Tools/SetupTest.php | 28 +++++++++++--------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Tools/SetupTest.php b/tests/Doctrine/Tests/ORM/Tools/SetupTest.php index 45c9e3ea7..50db4b2e1 100644 --- a/tests/Doctrine/Tests/ORM/Tools/SetupTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/SetupTest.php @@ -22,6 +22,21 @@ class SetupTest extends \Doctrine\Tests\OrmTestCase $this->originalIncludePath = get_include_path(); } + public function tearDown() + { + if ( ! $this->originalIncludePath) { + return; + } + + set_include_path($this->originalIncludePath); + $loaders = spl_autoload_functions(); + for ($i = 0; $i < count($loaders); $i++) { + if ($i > $this->originalAutoloaderCount+1) { + spl_autoload_unregister($loaders[$i]); + } + } + } + public function testGitAutoload() { Setup::registerAutoloadGit(__DIR__ . "/../../../../../"); @@ -92,15 +107,4 @@ class SetupTest extends \Doctrine\Tests\OrmTestCase $this->assertSame($cache, $config->getMetadataCacheImpl()); $this->assertSame($cache, $config->getQueryCacheImpl()); } - - public function tearDown() - { - set_include_path($this->originalIncludePath); - $loaders = spl_autoload_functions(); - for ($i = 0; $i < count($loaders); $i++) { - if ($i > $this->originalAutoloaderCount+1) { - spl_autoload_unregister($loaders[$i]); - } - } - } -} \ No newline at end of file +} From 86054eb659a8589ec23f50be55b30837688a1112 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sun, 12 Feb 2012 22:24:03 -0200 Subject: [PATCH 67/83] fix DDC-1642 --- .../ORM/Persisters/BasicEntityPersister.php | 5 ++- .../Models/CustomType/CustomTypeUpperCase.php | 5 +++ .../Tests/ORM/Functional/TypeValueSqlTest.php | 37 +++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index d2f1b4661..629c1fa2a 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -1152,9 +1152,10 @@ class BasicEntityPersister foreach ($columns AS $column) { $placeholder = '?'; - if (isset($this->_columnTypes[$column]) && + if (isset($this->_class->fieldNames[$column]) && + isset($this->_columnTypes[$this->_class->fieldNames[$column]]) && isset($this->_class->fieldMappings[$this->_class->fieldNames[$column]]['requireSQLConversion'])) { - $type = Type::getType($this->_columnTypes[$column]); + $type = Type::getType($this->_columnTypes[$this->_class->fieldNames[$column]]); $placeholder = $type->convertToDatabaseValueSQL('?', $this->_platform); } diff --git a/tests/Doctrine/Tests/Models/CustomType/CustomTypeUpperCase.php b/tests/Doctrine/Tests/Models/CustomType/CustomTypeUpperCase.php index 26e0ec115..e4b46e880 100644 --- a/tests/Doctrine/Tests/Models/CustomType/CustomTypeUpperCase.php +++ b/tests/Doctrine/Tests/Models/CustomType/CustomTypeUpperCase.php @@ -18,4 +18,9 @@ class CustomTypeUpperCase * @Column(type="upper_case_string") */ public $lowerCaseString; + + /** + * @Column(type="upper_case_string", name="named_lower_case_string", nullable = true) + */ + public $namedLowerCaseString; } diff --git a/tests/Doctrine/Tests/ORM/Functional/TypeValueSqlTest.php b/tests/Doctrine/Tests/ORM/Functional/TypeValueSqlTest.php index 1bd5184d0..537f238ca 100644 --- a/tests/Doctrine/Tests/ORM/Functional/TypeValueSqlTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/TypeValueSqlTest.php @@ -47,6 +47,43 @@ class TypeValueSqlTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals('FOO', $this->_em->getConnection()->fetchColumn("select lowerCaseString from customtype_uppercases where id=".$entity->id.""), 'Database holds uppercase string'); } + /** + * @group DDC-1642 + */ + public function testUpperCaseStringTypeWhenColumnNameIsDefined() + { + + $entity = new CustomTypeUpperCase(); + $entity->lowerCaseString = 'Some Value'; + $entity->namedLowerCaseString = 'foo'; + + $this->_em->persist($entity); + $this->_em->flush(); + + $id = $entity->id; + + $this->_em->clear(); + + $entity = $this->_em->find('\Doctrine\Tests\Models\CustomType\CustomTypeUpperCase', $id); + $this->assertEquals('foo', $entity->namedLowerCaseString, 'Entity holds lowercase string'); + $this->assertEquals('FOO', $this->_em->getConnection()->fetchColumn("select named_lower_case_string from customtype_uppercases where id=".$entity->id.""), 'Database holds uppercase string'); + + + $entity->namedLowerCaseString = 'bar'; + + $this->_em->persist($entity); + $this->_em->flush(); + + $id = $entity->id; + + $this->_em->clear(); + + + $entity = $this->_em->find('\Doctrine\Tests\Models\CustomType\CustomTypeUpperCase', $id); + $this->assertEquals('bar', $entity->namedLowerCaseString, 'Entity holds lowercase string'); + $this->assertEquals('BAR', $this->_em->getConnection()->fetchColumn("select named_lower_case_string from customtype_uppercases where id=".$entity->id.""), 'Database holds uppercase string'); + } + public function testTypeValueSqlWithAssociations() { $parent = new CustomTypeParent(); From 5d01123413c9eea68469af1eca37f7ae816ed1cc Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Mon, 13 Feb 2012 23:22:49 -0200 Subject: [PATCH 68/83] Fix DDC-807, DDC-553 --- doctrine-mapping.xsd | 4 +- .../ORM/Mapping/DiscriminatorColumn.php | 2 + .../ORM/Mapping/Driver/AnnotationDriver.php | 3 +- lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php | 7 +-- .../ORM/Mapping/Driver/YamlDriver.php | 7 +-- lib/Doctrine/ORM/Tools/SchemaTool.php | 13 +++-- .../ORM/Mapping/AbstractMappingDriverTest.php | 51 +++++++++++++++++++ ...octrine.Tests.ORM.Mapping.DDC807Entity.php | 15 ++++++ ...ine.Tests.ORM.Mapping.DDC807Entity.dcm.xml | 20 ++++++++ ...ine.Tests.ORM.Mapping.DDC807Entity.dcm.yml | 13 +++++ 10 files changed, 123 insertions(+), 12 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.DDC807Entity.php create mode 100644 tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.DDC807Entity.dcm.xml create mode 100644 tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.DDC807Entity.dcm.yml diff --git a/doctrine-mapping.xsd b/doctrine-mapping.xsd index 227af4403..0d3704a7e 100644 --- a/doctrine-mapping.xsd +++ b/doctrine-mapping.xsd @@ -203,9 +203,10 @@ - + + @@ -278,6 +279,7 @@ + diff --git a/lib/Doctrine/ORM/Mapping/DiscriminatorColumn.php b/lib/Doctrine/ORM/Mapping/DiscriminatorColumn.php index aec011538..0a9d6e9d5 100644 --- a/lib/Doctrine/ORM/Mapping/DiscriminatorColumn.php +++ b/lib/Doctrine/ORM/Mapping/DiscriminatorColumn.php @@ -33,4 +33,6 @@ final class DiscriminatorColumn implements Annotation public $length; /** @var mixed */ public $fieldName; // field name used in non-object hydration (array/scalar) + /** @var string */ + public $columnDefinition; } diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index e95d32e94..93e8473a4 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -228,7 +228,8 @@ class AnnotationDriver implements Driver $metadata->setDiscriminatorColumn(array( 'name' => $discrColumnAnnot->name, 'type' => $discrColumnAnnot->type, - 'length' => $discrColumnAnnot->length + 'length' => $discrColumnAnnot->length, + 'columnDefinition' => $discrColumnAnnot->columnDefinition )); } else { $metadata->setDiscriminatorColumn(array('name' => 'dtype', 'type' => 'string', 'length' => 255)); diff --git a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php index a528346e6..b62df3893 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php @@ -99,9 +99,10 @@ class XmlDriver extends AbstractFileDriver if (isset($xmlRoot->{'discriminator-column'})) { $discrColumn = $xmlRoot->{'discriminator-column'}; $metadata->setDiscriminatorColumn(array( - 'name' => (string)$discrColumn['name'], - 'type' => (string)$discrColumn['type'], - 'length' => (string)$discrColumn['length'] + 'name' => isset($discrColumn['name']) ? (string)$discrColumn['name'] : null, + 'type' => isset($discrColumn['type']) ? (string)$discrColumn['type'] : null, + 'length' => isset($discrColumn['length']) ? (string)$discrColumn['length'] : null, + 'columnDefinition' => isset($discrColumn['column-definition']) ? (string)$discrColumn['column-definition'] : null )); } else { $metadata->setDiscriminatorColumn(array('name' => 'dtype', 'type' => 'string', 'length' => 255)); diff --git a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php index 0106f3aba..ef0eff6ec 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php @@ -96,9 +96,10 @@ class YamlDriver extends AbstractFileDriver if (isset($element['discriminatorColumn'])) { $discrColumn = $element['discriminatorColumn']; $metadata->setDiscriminatorColumn(array( - 'name' => $discrColumn['name'], - 'type' => $discrColumn['type'], - 'length' => $discrColumn['length'] + 'name' => isset($discrColumn['name']) ? (string)$discrColumn['name'] : null, + 'type' => isset($discrColumn['type']) ? (string)$discrColumn['type'] : null, + 'length' => isset($discrColumn['length']) ? (string)$discrColumn['length'] : null, + 'columnDefinition' => isset($discrColumn['columnDefinition']) ? (string)$discrColumn['columnDefinition'] : null )); } else { $metadata->setDiscriminatorColumn(array('name' => 'dtype', 'type' => 'string', 'length' => 255)); diff --git a/lib/Doctrine/ORM/Tools/SchemaTool.php b/lib/Doctrine/ORM/Tools/SchemaTool.php index 85e50b2b0..027285c27 100644 --- a/lib/Doctrine/ORM/Tools/SchemaTool.php +++ b/lib/Doctrine/ORM/Tools/SchemaTool.php @@ -288,11 +288,16 @@ class SchemaTool $discrColumn['length'] = 255; } - $table->addColumn( - $discrColumn['name'], - $discrColumn['type'], - array('length' => $discrColumn['length'], 'notnull' => true) + $options = array( + 'length' => isset($discrColumn['length']) ? $discrColumn['length'] : null, + 'notnull' => true ); + + if (isset($discrColumn['columnDefinition'])) { + $options['columnDefinition'] = $discrColumn['columnDefinition']; + } + + $table->addColumn($discrColumn['name'], $discrColumn['type'], $options); } /** diff --git a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php index 369c8fdeb..4b2da0a70 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php @@ -434,6 +434,21 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals('NAME', $class->columnNames['name']); $this->assertEquals('DDC1476ENTITY_WITH_DEFAULT_FIELD_TYPE', $class->table['name']); } + + /** + * @group DDC-807 + * @group DDC-553 + */ + public function testDiscriminatorColumnDefinition() + { + $class = $this->createClassMetadata(__NAMESPACE__ . '\DDC807Entity'); + + $this->assertArrayHasKey('columnDefinition', $class->discriminatorColumn); + $this->assertArrayHasKey('name', $class->discriminatorColumn); + + $this->assertEquals("ENUM('ONE','TWO')", $class->discriminatorColumn['columnDefinition']); + $this->assertEquals("dtype", $class->discriminatorColumn['name']); + } } /** @@ -724,6 +739,42 @@ class DDC1170Entity } +/** + * @Entity + * @InheritanceType("SINGLE_TABLE") + * @DiscriminatorMap({"ONE" = "DDC807SubClasse1", "TWO" = "DDC807SubClasse2"}) + * @DiscriminatorColumn(name = "dtype", columnDefinition="ENUM('ONE','TWO')") + */ +class DDC807Entity +{ + /** + * @Id + * @Column(type="integer") + * @GeneratedValue(strategy="NONE") + **/ + public $id; + + public static function loadMetadata(ClassMetadataInfo $metadata) + { + $metadata->mapField(array( + 'id' => true, + 'fieldName' => 'id', + )); + + $metadata->setDiscriminatorColumn(array( + 'name' => "dtype", + 'type' => "string", + 'columnDefinition' => "ENUM('ONE','TWO')" + )); + + $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_NONE); + } +} + + +class DDC807SubClasse1 {} +class DDC807SubClasse2 {} + class Address {} class Phonenumber {} class Group {} diff --git a/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.DDC807Entity.php b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.DDC807Entity.php new file mode 100644 index 000000000..1682d7a0d --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.DDC807Entity.php @@ -0,0 +1,15 @@ +mapField(array( + 'id' => true, + 'fieldName' => 'id', +)); + +$metadata->setDiscriminatorColumn(array( + 'name' => "dtype", + 'columnDefinition' => "ENUM('ONE','TWO')" +)); + +$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_NONE); \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.DDC807Entity.dcm.xml b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.DDC807Entity.dcm.xml new file mode 100644 index 000000000..3dc9135c5 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.DDC807Entity.dcm.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.DDC807Entity.dcm.yml b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.DDC807Entity.dcm.yml new file mode 100644 index 000000000..20db3c328 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.DDC807Entity.dcm.yml @@ -0,0 +1,13 @@ +Doctrine\Tests\ORM\Mapping\DDC807Entity: + type: entity + inheritanceType: SINGLE_TABLE + discriminatorMap: + ONE: DDC807SubClasse1 + TWO: DDC807SubClasse2 + discriminatorColumn: + name: dtype + columnDefinition: ENUM('ONE','TWO') + id: + id: + generator: + strategy: NONE \ No newline at end of file From cdde6e8a5c32cf403b84e7898a7c0a8f0230a4c7 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Mon, 13 Feb 2012 23:38:36 -0200 Subject: [PATCH 69/83] fix required discriminator column name --- lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php | 9 ++++++--- lib/Doctrine/ORM/Mapping/MappingException.php | 5 +++++ .../Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php | 11 +++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 253ab6003..725ccfaed 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -1937,19 +1937,22 @@ class ClassMetadataInfo implements ClassMetadata public function setDiscriminatorColumn($columnDef) { if ($columnDef !== null) { + if ( ! isset($columnDef['name'])) { + throw MappingException::nameIsMandatoryForDiscriminatorColumns($this->name, $columnDef); + } + if (isset($this->fieldNames[$columnDef['name']])) { throw MappingException::duplicateColumnName($this->name, $columnDef['name']); } - if ( ! isset($columnDef['name'])) { - throw MappingException::nameIsMandatoryForDiscriminatorColumns($this->name, $columnDef); - } if ( ! isset($columnDef['fieldName'])) { $columnDef['fieldName'] = $columnDef['name']; } + if ( ! isset($columnDef['type'])) { $columnDef['type'] = "string"; } + if (in_array($columnDef['type'], array("boolean", "array", "object", "datetime", "time", "date"))) { throw MappingException::invalidDiscriminatorColumnType($this->name, $columnDef['type']); } diff --git a/lib/Doctrine/ORM/Mapping/MappingException.php b/lib/Doctrine/ORM/Mapping/MappingException.php index c71c2e91c..0eff17cd6 100644 --- a/lib/Doctrine/ORM/Mapping/MappingException.php +++ b/lib/Doctrine/ORM/Mapping/MappingException.php @@ -226,6 +226,11 @@ class MappingException extends \Doctrine\ORM\ORMException return new self("Discriminator column type on entity class '$className' is not allowed to be '$type'. 'string' or 'integer' type variables are suggested!"); } + public static function nameIsMandatoryForDiscriminatorColumns($className) + { + return new self("Discriminator column name on entity class '$className' is not defined."); + } + public static function cannotVersionIdField($className, $fieldName) { return new self("Setting Id field '$fieldName' as versionale in entity class '$className' is not supported."); diff --git a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php index c16eedba9..8f079e789 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php @@ -596,6 +596,17 @@ class ClassMetadataTest extends \Doctrine\Tests\OrmTestCase $cm->validateAssocations(); } + /** + * @expectedException \Doctrine\ORM\Mapping\MappingException + * @expectedExceptionMessage Discriminator column name on entity class 'Doctrine\Tests\Models\CMS\CmsUser' is not defined. + */ + public function testNameIsMandatoryForDiscriminatorColumnsMappingException() + { + $cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); + $cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); + $cm->setDiscriminatorColumn(array()); + } + /** * @group DDC-984 * @group DDC-559 From da9b2e805e5185c47fc4806755406091b46fd601 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Mon, 13 Feb 2012 23:43:19 -0200 Subject: [PATCH 70/83] remove unused parameter --- lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 725ccfaed..0c28ae473 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -1938,7 +1938,7 @@ class ClassMetadataInfo implements ClassMetadata { if ($columnDef !== null) { if ( ! isset($columnDef['name'])) { - throw MappingException::nameIsMandatoryForDiscriminatorColumns($this->name, $columnDef); + throw MappingException::nameIsMandatoryForDiscriminatorColumns($this->name); } if (isset($this->fieldNames[$columnDef['name']])) { From 2e81fbfd64a18ad025b68ebf7498788be3f16e7e Mon Sep 17 00:00:00 2001 From: Andreas Hucks Date: Tue, 14 Feb 2012 19:07:51 +0100 Subject: [PATCH 71/83] added type hint --- lib/Doctrine/ORM/Proxy/ProxyFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index ed5d1560c..7e01ad7c2 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -143,7 +143,7 @@ class ProxyFactory * @param $proxyClassName * @param $file The path of the file to write to. */ - private function _generateProxyClass($class, $fileName, $file) + private function _generateProxyClass(ClassMetadata $class, $fileName, $file) { $methods = $this->_generateMethods($class); $sleepImpl = $this->_generateSleep($class); From 3419c65efee24788e603820deeb6c66aba7e0bc6 Mon Sep 17 00:00:00 2001 From: Andreas Hucks Date: Tue, 14 Feb 2012 19:08:44 +0100 Subject: [PATCH 72/83] fixed docblock --- lib/Doctrine/ORM/Proxy/ProxyFactory.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index 7e01ad7c2..bb8de3d19 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -139,9 +139,9 @@ class ProxyFactory /** * Generates a proxy class file. * - * @param $class - * @param $proxyClassName - * @param $file The path of the file to write to. + * @param ClassMetadata $class Metadata for the original class + * @param string $fileName Filename (full path) for the generated class + * @param string $file The proxy class template data */ private function _generateProxyClass(ClassMetadata $class, $fileName, $file) { From 1d927541e2a2406b3360ec5370b525ec32bfa94f Mon Sep 17 00:00:00 2001 From: Andreas Hucks Date: Tue, 14 Feb 2012 19:12:20 +0100 Subject: [PATCH 73/83] added type hint --- lib/Doctrine/ORM/Proxy/ProxyFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index bb8de3d19..50bf18a7c 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -272,7 +272,7 @@ class ProxyFactory * @param ClassMetadata $class * @return bool */ - private function isShortIdentifierGetter($method, $class) + private function isShortIdentifierGetter($method, ClassMetadata $class) { $identifier = lcfirst(substr($method->getName(), 3)); $cheapCheck = ( From d1b2dabc0fe14de925f558ef10646d5a9e4ddca5 Mon Sep 17 00:00:00 2001 From: Asmir Mustafic Date: Wed, 15 Feb 2012 12:43:55 +0100 Subject: [PATCH 74/83] nullable assoc --- lib/Doctrine/ORM/Tools/EntityGenerator.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index 6a18610ee..3e6258fdb 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -707,6 +707,9 @@ public function () private function _isAssociationIsNullable($associationMapping) { + if (isset($associationMapping['id']) && $associationMapping['id']) { + return false; + } if (isset($associationMapping['joinColumns'])) { $joinColumns = $associationMapping['joinColumns']; } else { From d995c6dbdc0f0b26461473d950eec0dd71678e06 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Fri, 17 Feb 2012 23:26:42 +0100 Subject: [PATCH 75/83] [DDC-1655][DDC-1650][DDC-1556] Fix issues with @postLoad Callback being not fired, or fired multiple times. --- .../ORM/Internal/Hydration/ObjectHydrator.php | 15 +- .../Hydration/SimpleObjectHydrator.php | 13 +- .../ORM/Persisters/BasicEntityPersister.php | 10 -- lib/Doctrine/ORM/UnitOfWork.php | 11 ++ .../ORM/Functional/Ticket/DDC1655Test.php | 144 ++++++++++++++++++ 5 files changed, 157 insertions(+), 36 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1655Test.php diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 878c130c2..1453aab3e 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -234,20 +234,7 @@ class ObjectHydrator extends AbstractHydrator $this->_hints['fetchAlias'] = $dqlAlias; - $entity = $this->_uow->createEntity($className, $data, $this->_hints); - - //TODO: These should be invoked later, after hydration, because associations may not yet be loaded here. - if (isset($this->_ce[$className]->lifecycleCallbacks[Events::postLoad])) { - $this->_ce[$className]->invokeLifecycleCallbacks(Events::postLoad, $entity); - } - - $evm = $this->_em->getEventManager(); - - if ($evm->hasListeners(Events::postLoad)) { - $evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->_em)); - } - - return $entity; + return $this->_uow->createEntity($className, $data, $this->_hints); } private function _getEntityFromIdentityMap($className, array $data) diff --git a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php index 648f64979..09c2c7f3d 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php @@ -130,17 +130,6 @@ class SimpleObjectHydrator extends AbstractHydrator $uow = $this->_em->getUnitOfWork(); $entity = $uow->createEntity($entityName, $data, $this->_hints); - //TODO: These should be invoked later, after hydration, because associations may not yet be loaded here. - if (isset($this->class->lifecycleCallbacks[Events::postLoad])) { - $this->class->invokeLifecycleCallbacks(Events::postLoad, $entity); - } - - $evm = $this->_em->getEventManager(); - - if ($evm->hasListeners(Events::postLoad)) { - $evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->_em)); - } - $result[] = $entity; } @@ -191,4 +180,4 @@ class SimpleObjectHydrator extends AbstractHydrator ); } } -} \ No newline at end of file +} diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 629c1fa2a..673d97496 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -698,16 +698,6 @@ class BasicEntityPersister $hydrator = $this->_em->newHydrator(Query::HYDRATE_OBJECT); $hydrator->hydrateAll($stmt, $this->_rsm, array(Query::HINT_REFRESH => true)); - - if (isset($this->_class->lifecycleCallbacks[Events::postLoad])) { - $this->_class->invokeLifecycleCallbacks(Events::postLoad, $entity); - } - - $evm = $this->_em->getEventManager(); - - if ($evm->hasListeners(Events::postLoad)) { - $evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->_em)); - } } /** diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index fc892b138..2f555e6e1 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -2501,6 +2501,17 @@ class UnitOfWork implements PropertyChangedListener } } + if ($overrideLocalValues) { + if (isset($class->lifecycleCallbacks[Events::postLoad])) { + $class->invokeLifecycleCallbacks(Events::postLoad, $entity); + } + + + if ($this->evm->hasListeners(Events::postLoad)) { + $this->evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->em)); + } + } + return $entity; } diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1655Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1655Test.php new file mode 100644 index 000000000..22b20e722 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1655Test.php @@ -0,0 +1,144 @@ +_schemaTool->createSchema(array( + $this->_em->getClassMetadata(__NAMESPACE__ . '\\DDC1655Foo'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\\DDC1655Bar'), + $this->_em->getClassMetadata(__NAMESPACE__ . '\\DDC1655Baz'), + )); + } catch(\Exception $e) { + + } + } + + public function testPostLoadOneToManyInheritance() + { + $cm = $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1655Foo'); + $this->assertEquals(array("postLoad" => array("postLoad")), $cm->lifecycleCallbacks); + + $cm = $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1655Bar'); + $this->assertEquals(array("postLoad" => array("postLoad", "postSubLoaded")), $cm->lifecycleCallbacks); + + $baz = new DDC1655Baz(); + $foo = new DDC1655Foo(); + $foo->baz = $baz; + $bar = new DDC1655Bar(); + $bar->baz = $baz; + + $this->_em->persist($foo); + $this->_em->persist($bar); + $this->_em->persist($baz); + $this->_em->flush(); + $this->_em->clear(); + + $baz = $this->_em->find(get_class($baz), $baz->id); + foreach ($baz->foos as $foo) { + $this->assertEquals(1, $foo->loaded, "should have loaded callback counter incremented for " . get_class($foo)); + } + } + + /** + * Check that post load is not executed several times when the entity + * is rehydrated again although its already known. + */ + public function testPostLoadInheritanceChild() + { + $bar = new DDC1655Bar(); + + $this->_em->persist($bar); + $this->_em->flush(); + $this->_em->clear(); + + $bar = $this->_em->find(get_class($bar), $bar->id); + $this->assertEquals(1, $bar->loaded); + $this->assertEquals(1, $bar->subLoaded); + + $bar = $this->_em->find(get_class($bar), $bar->id); + $this->assertEquals(1, $bar->loaded); + $this->assertEquals(1, $bar->subLoaded); + + $dql = "SELECT b FROM " . __NAMESPACE__ . "\DDC1655Bar b WHERE b.id = ?1"; + $bar = $this->_em->createQuery($dql)->setParameter(1, $bar->id)->getSingleResult(); + + $this->assertEquals(1, $bar->loaded); + $this->assertEquals(1, $bar->subLoaded); + + $this->_em->refresh($bar); + + $this->assertEquals(2, $bar->loaded); + $this->assertEquals(2, $bar->subLoaded); + } +} + +/** + * @Entity + * @InheritanceType("SINGLE_TABLE") + * @DiscriminatorMap({ + * "foo" = "DDC1655Foo", + * "bar" = "DDC1655Bar" + * }) + * @HasLifecycleCallbacks + */ +class DDC1655Foo +{ + /** @Id @GeneratedValue @Column(type="integer") */ + public $id; + + public $loaded = 0; + + /** + * @ManyToOne(targetEntity="DDC1655Baz", inversedBy="foos") + */ + public $baz; + + /** + * @PostLoad + */ + public function postLoad() + { + $this->loaded++; + } +} + +/** + * @Entity + * @HasLifecycleCallbacks + */ +class DDC1655Bar extends DDC1655Foo +{ + public $subLoaded; + + /** + * @PostLoad + */ + public function postSubLoaded() + { + $this->subLoaded++; + } +} + +/** + * @Entity + */ +class DDC1655Baz +{ + /** @Id @GeneratedValue @Column(type="integer") */ + public $id; + + /** + * @OneToMany(targetEntity="DDC1655Foo", mappedBy="baz") + */ + public $foos = array(); +} From 9fc1d85e8dd9db217d895c923f3d0d3a26ead6f1 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 18 Feb 2012 00:40:42 +0100 Subject: [PATCH 76/83] [DDC-1643] Fix bugs when cloning PersistentCollection and re-using it. --- lib/Doctrine/ORM/PersistentCollection.php | 24 ++++ .../AbstractCollectionPersister.php | 2 +- lib/Doctrine/ORM/UnitOfWork.php | 17 +++ .../ORM/Functional/Ticket/DDC1643Test.php | 121 ++++++++++++++++++ 4 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1643Test.php diff --git a/lib/Doctrine/ORM/PersistentCollection.php b/lib/Doctrine/ORM/PersistentCollection.php index 66dca2bbd..2d8a77ef6 100644 --- a/lib/Doctrine/ORM/PersistentCollection.php +++ b/lib/Doctrine/ORM/PersistentCollection.php @@ -305,6 +305,7 @@ final class PersistentCollection implements Collection if ($this->association !== null && $this->association['isOwningSide'] && $this->association['type'] === ClassMetadata::MANY_TO_MANY && + $this->owner && $this->em->getClassMetadata(get_class($this->owner))->isChangeTrackingNotify()) { $this->em->getUnitOfWork()->scheduleForDirtyCheck($this->owner); } @@ -759,4 +760,27 @@ final class PersistentCollection implements Collection return $this->coll->slice($offset, $length); } + + /** + * Cleanup internal state of cloned persistent collection. + * + * The following problems have to be prevented: + * 1. Added entities are added to old PC + * 2. New collection is not dirty, if reused on other entity nothing + * changes. + * 3. Snapshot leads to invalid diffs being generated. + * 4. Lazy loading grabs entities from old owner object. + * 5. New collection is connected to old owner and leads to duplicate keys. + */ + public function __clone() + { + $this->initialize(); + $this->owner = null; + + if (is_object($this->coll)) { + $this->coll = clone $this->coll; + } + $this->snapshot = array(); + $this->changed(); + } } diff --git a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php index 7f8c40b09..8c129481e 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php @@ -204,4 +204,4 @@ abstract class AbstractCollectionPersister * @param mixed $element */ abstract protected function _getInsertRowSQLParameters(PersistentCollection $coll, $element); -} \ No newline at end of file +} diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 2f555e6e1..7ab4046e6 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -584,6 +584,23 @@ class UnitOfWork implements PropertyChangedListener $assoc = $class->associationMappings[$propName]; + // Persistent collection was exchanged with the "originally" + // created one. This can only mean it was cloned and replaced + // on another entity. + if ($actualValue instanceof PersistentCollection) { + $owner = $actualValue->getOwner(); + if ($owner === null) { // cloned + $actualValue->setOwner($entity, $assoc); + } else if ($owner !== $entity) { // no clone, we have to fix + if (!$actualValue->isInitialized()) { + $actualValue->initialize(); // we have to do this otherwise the cols share state + } + $newValue = clone $actualValue; + $newValue->setOwner($entity, $assoc); + $class->reflFields[$propName]->setValue($entity, $newValue); + } + } + if ($orgValue instanceof PersistentCollection) { // A PersistentCollection was de-referenced, so delete it. $coid = spl_object_hash($orgValue); diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1643Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1643Test.php new file mode 100644 index 000000000..53d6bb6e2 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1643Test.php @@ -0,0 +1,121 @@ +useModelSet('cms'); + parent::setUp(); + + $user1 = new CmsUser(); + $user1->username = "beberlei"; + $user1->name = "Benjamin"; + $user1->status = "active"; + $group1 = new CmsGroup(); + $group1->name = "test"; + $group2 = new CmsGroup(); + $group2->name = "test"; + $user1->addGroup($group1); + $user1->addGroup($group2); + $user2 = new CmsUser(); + $user2->username = "romanb"; + $user2->name = "Roman"; + $user2->status = "active"; + + $this->_em->persist($user1); + $this->_em->persist($user2); + $this->_em->persist($group1); + $this->_em->persist($group2); + $this->_em->flush(); + $this->_em->clear(); + + $this->user1 = $this->_em->find(get_class($user1), $user1->id); + $this->user2 = $this->_em->find(get_class($user1), $user2->id); + } + + public function testClonePersistentCollectionAndReuse() + { + $user1 = $this->user1; + + $user1->groups = clone $user1->groups; + + $this->_em->flush(); + $this->_em->clear(); + + $user1 = $this->_em->find(get_class($user1), $user1->id); + + $this->assertEquals(2, count($user1->groups)); + } + + public function testClonePersistentCollectionAndShare() + { + $user1 = $this->user1; + $user2 = $this->user2; + + $user2->groups = clone $user1->groups; + + $this->_em->flush(); + $this->_em->clear(); + + $user1 = $this->_em->find(get_class($user1), $user1->id); + $user2 = $this->_em->find(get_class($user1), $user2->id); + + $this->assertEquals(2, count($user1->groups)); + $this->assertEquals(2, count($user2->groups)); + } + + public function testCloneThenDirtyPersistentCollection() + { + $user1 = $this->user1; + $user2 = $this->user2; + + $group3 = new CmsGroup(); + $group3->name = "test"; + $user2->groups = clone $user1->groups; + $user2->groups->add($group3); + + $this->_em->persist($group3); + $this->_em->flush(); + $this->_em->clear(); + + $user1 = $this->_em->find(get_class($user1), $user1->id); + $user2 = $this->_em->find(get_class($user1), $user2->id); + + $this->assertEquals(3, count($user2->groups)); + $this->assertEquals(2, count($user1->groups)); + } + + public function testNotCloneAndPassAroundFlush() + { + $user1 = $this->user1; + $user2 = $this->user2; + + $group3 = new CmsGroup(); + $group3->name = "test"; + $user2->groups = $user1->groups; + $user2->groups->add($group3); + + $this->assertEQuals(1, count($user1->groups->getInsertDiff())); + + $this->_em->persist($group3); + $this->_em->flush(); + $this->_em->clear(); + + $user1 = $this->_em->find(get_class($user1), $user1->id); + $user2 = $this->_em->find(get_class($user1), $user2->id); + + $this->assertEquals(3, count($user2->groups)); + $this->assertEquals(3, count($user1->groups)); + } +} + From bd1bc072706d41cdd37082b47e5af480a5102be5 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 18 Feb 2012 16:07:55 +0100 Subject: [PATCH 77/83] [DDC-1651] Convert entities as parameters early in setParameter() to avoid them being part of result cache strings, which causes non-uniqueness. --- lib/Doctrine/ORM/AbstractQuery.php | 48 +++++++++++++++++++ lib/Doctrine/ORM/Query.php | 36 +------------- .../Functional/CompositePrimaryKeyTest.php | 22 ++++++++- .../Tests/ORM/Functional/QueryTest.php | 22 ++++++++- 4 files changed, 92 insertions(+), 36 deletions(-) diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index 5ff47c15a..eb6917b82 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -208,6 +208,7 @@ abstract class AbstractQuery { $key = trim($key, ':'); + $value = $this->processParameterValue($value); if ($type === null) { $type = Query\ParameterTypeInferer::inferType($value); } @@ -218,6 +219,53 @@ abstract class AbstractQuery return $this; } + /** + * Process an individual parameter value + * + * @param mixed $value + * @return array + */ + private function processParameterValue($value) + { + switch (true) { + case is_array($value): + for ($i = 0, $l = count($value); $i < $l; $i++) { + $paramValue = $this->processParameterValue($value[$i]); + $value[$i] = is_array($paramValue) ? $paramValue[key($paramValue)] : $paramValue; + } + + return $value; + + case is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value)): + return $this->convertObjectParameterToScalarValue($value); + + default: + return $value; + } + } + + protected function convertObjectParameterToScalarValue($value) + { + $class = $this->_em->getClassMetadata(get_class($value)); + + if ($class->isIdentifierComposite) { + throw new \InvalidArgumentException("Binding an entity with a composite primary key to a query is not supported. You should split the parameter into the explicit fields and bind them seperately."); + } + + if ($this->_em->getUnitOfWork()->getEntityState($value) === UnitOfWork::STATE_MANAGED) { + $values = $this->_em->getUnitOfWork()->getEntityIdentifier($value); + } else { + $values = $class->getIdentifierValues($value); + } + + $value = $values[$class->getSingleIdentifierFieldName()]; + if (!$value) { + throw new \InvalidArgumentException("Binding entities to query parameters only allowed for entities that have an identifier."); + } + + return $value; + } + /** * Sets a collection of query parameters. * diff --git a/lib/Doctrine/ORM/Query.php b/lib/Doctrine/ORM/Query.php index ff3f8426e..16c6c3803 100644 --- a/lib/Doctrine/ORM/Query.php +++ b/lib/Doctrine/ORM/Query.php @@ -282,7 +282,8 @@ final class Query extends AbstractQuery } $sqlPositions = $paramMappings[$key]; - $value = array_values($this->processParameterValue($value)); + // optimized multi value sql positions away for now, they are not allowed in DQL anyways. + $value = array($value); $countValue = count($value); for ($i = 0, $l = count($sqlPositions); $i < $l; $i++) { @@ -305,39 +306,6 @@ final class Query extends AbstractQuery return array($sqlParams, $types); } - /** - * Process an individual parameter value - * - * @param mixed $value - * @return array - */ - private function processParameterValue($value) - { - switch (true) { - case is_array($value): - for ($i = 0, $l = count($value); $i < $l; $i++) { - $paramValue = $this->processParameterValue($value[$i]); - - // TODO: What about Entities that have composite primary key? - $value[$i] = is_array($paramValue) ? $paramValue[key($paramValue)] : $paramValue; - } - - return array($value); - - case is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value)): - if ($this->_em->getUnitOfWork()->getEntityState($value) === UnitOfWork::STATE_MANAGED) { - return array_values($this->_em->getUnitOfWork()->getEntityIdentifier($value)); - } - - $class = $this->_em->getClassMetadata(get_class($value)); - - return array_values($class->getIdentifierValues($value)); - - default: - return array($value); - } - } - /** * Defines a cache driver to be used for caching queries. * diff --git a/tests/Doctrine/Tests/ORM/Functional/CompositePrimaryKeyTest.php b/tests/Doctrine/Tests/ORM/Functional/CompositePrimaryKeyTest.php index 80e2fa0d6..4d321ad04 100644 --- a/tests/Doctrine/Tests/ORM/Functional/CompositePrimaryKeyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/CompositePrimaryKeyTest.php @@ -4,6 +4,7 @@ namespace Doctrine\Tests\ORM\Functional; use Doctrine\Tests\Models\Navigation\NavCountry; use Doctrine\Tests\Models\Navigation\NavPointOfInterest; use Doctrine\Tests\Models\Navigation\NavTour; +use Doctrine\Tests\Models\Navigation\NavPhotos; require_once __DIR__ . '/../../TestInit.php'; @@ -51,6 +52,25 @@ class CompositePrimaryKeyTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals('Brandenburger Tor', $poi->getName()); } + /** + * @group DDC-1651 + */ + public function testSetParameterCompositeKeyObject() + { + $this->putGermanysBrandenburderTor(); + + $poi = $this->_em->find('Doctrine\Tests\Models\Navigation\NavPointOfInterest', array('lat' => 100, 'long' => 200)); + $photo = new NavPhotos($poi, "asdf"); + $this->_em->persist($photo); + $this->_em->flush(); + $this->_em->clear(); + + $dql = 'SELECT t FROM Doctrine\Tests\Models\Navigation\NavPhotos t WHERE t.poi = ?1'; + + $this->setExpectedException('Doctrine\ORM\Query\QueryException', 'A single-valued association path expression to an entity with a composite primary key is not supported.'); + $sql = $this->_em->createQuery($dql)->getSQL(); + } + public function testManyToManyCompositeRelation() { $this->putGermanysBrandenburderTor(); @@ -98,4 +118,4 @@ class CompositePrimaryKeyTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->setExpectedException('Doctrine\ORM\ORMException', 'The identifier long is missing for a query of Doctrine\Tests\Models\Navigation\NavPointOfInterest'); $poi = $this->_em->find('Doctrine\Tests\Models\Navigation\NavPointOfInterest', array('key1' => 100)); } -} \ No newline at end of file +} diff --git a/tests/Doctrine/Tests/ORM/Functional/QueryTest.php b/tests/Doctrine/Tests/ORM/Functional/QueryTest.php index 557e4689e..b19a8a6d2 100644 --- a/tests/Doctrine/Tests/ORM/Functional/QueryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/QueryTest.php @@ -599,4 +599,24 @@ class QueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals(3, count($users)); $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $users[0]); } -} \ No newline at end of file + + /** + * @group DDC-1651 + */ + public function testSetParameterBindingSingleIdentifierObjectConverted() + { + $userC = new CmsUser; + $userC->name = 'Jonathan'; + $userC->username = 'jwage'; + $userC->status = 'developer'; + $this->_em->persist($userC); + + $this->_em->flush(); + $this->_em->clear(); + + $q = $this->_em->createQuery("SELECT DISTINCT u from Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id = ?1"); + $q->setParameter(1, $userC); + + $this->assertEquals($userC->id, $q->getParameter(1)); + } +} From 35764c24024b45aa2c5536ce2f3b660abae5401d Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 20 Feb 2012 09:36:35 +0100 Subject: [PATCH 78/83] [DDC-1659] Remove read only marker when clearing entities. --- lib/Doctrine/ORM/UnitOfWork.php | 2 + .../Tests/ORM/Functional/ReadOnlyTest.php | 41 +++++++++++++++++-- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 7ab4046e6..146789397 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1395,6 +1395,7 @@ class UnitOfWork implements PropertyChangedListener if (isset($this->identityMap[$className][$idHash])) { unset($this->identityMap[$className][$idHash]); + unset($this->readOnlyObjects[$oid]); //$this->entityStates[$oid] = self::STATE_DETACHED; @@ -2209,6 +2210,7 @@ class UnitOfWork implements PropertyChangedListener $this->collectionDeletions = $this->collectionUpdates = $this->extraUpdates = + $this->readOnlyObjects = $this->orphanRemovals = array(); if ($this->commitOrderCalculator !== null) { diff --git a/tests/Doctrine/Tests/ORM/Functional/ReadOnlyTest.php b/tests/Doctrine/Tests/ORM/Functional/ReadOnlyTest.php index 44832fc76..2519a9c57 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ReadOnlyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ReadOnlyTest.php @@ -15,9 +15,12 @@ class ReadOnlyTest extends \Doctrine\Tests\OrmFunctionalTestCase { parent::setUp(); - $this->_schemaTool->createSchema(array( - $this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\ReadOnlyEntity'), - )); + try { + $this->_schemaTool->createSchema(array( + $this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\ReadOnlyEntity'), + )); + } catch(\Exception $e) { + } } public function testReadOnlyEntityNeverChangeTracked() @@ -36,6 +39,36 @@ class ReadOnlyTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals("Test1", $dbReadOnly->name); $this->assertEquals(1234, $dbReadOnly->numericValue); } + + /** + * @group DDC-1659 + */ + public function testClearReadOnly() + { + $readOnly = new ReadOnlyEntity("Test1", 1234); + $this->_em->persist($readOnly); + $this->_em->flush(); + $this->_em->getUnitOfWork()->markReadOnly($readOnly); + + $this->_em->clear(); + + $this->assertFalse($this->_em->getUnitOfWork()->isReadOnly($readOnly)); + } + + /** + * @group DDC-1659 + */ + public function testClearEntitiesReadOnly() + { + $readOnly = new ReadOnlyEntity("Test1", 1234); + $this->_em->persist($readOnly); + $this->_em->flush(); + $this->_em->getUnitOfWork()->markReadOnly($readOnly); + + $this->_em->clear(get_class($readOnly)); + + $this->assertFalse($this->_em->getUnitOfWork()->isReadOnly($readOnly)); + } } /** @@ -58,4 +91,4 @@ class ReadOnlyEntity $this->name = $name; $this->numericValue = $number; } -} \ No newline at end of file +} From 68436fee757a0b069429d3b496dabb6d9480afe1 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 20 Feb 2012 10:33:16 +0100 Subject: [PATCH 79/83] [DDC-1654] Add support for orphanRemoval on ManyToMany associations. This only makes sense when ManyToMany is used as uni-directional OneToMany association with join table. The join column has a unique constraint on it to enforce this on the DB level, but we dont validate that this actually happens. Foreign Key constraints help prevent issues and notify developers early if they use it wrong. --- doctrine-mapping.xsd | 1 + .../ORM/Mapping/ClassMetadataInfo.php | 4 +- .../ORM/Mapping/Driver/AnnotationDriver.php | 1 + lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php | 4 + .../ORM/Mapping/Driver/YamlDriver.php | 4 + lib/Doctrine/ORM/Mapping/ManyToMany.php | 2 + lib/Doctrine/ORM/PersistentCollection.php | 6 +- .../ORM/Functional/Ticket/DDC1654Test.php | 103 ++++++++++++++++++ .../ORM/Mapping/ClassMetadataBuilderTest.php | 1 + .../Doctrine/Tests/OrmFunctionalTestCase.php | 25 +++++ 10 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1654Test.php diff --git a/doctrine-mapping.xsd b/doctrine-mapping.xsd index 0d3704a7e..96f155a60 100644 --- a/doctrine-mapping.xsd +++ b/doctrine-mapping.xsd @@ -371,6 +371,7 @@ + diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 0c28ae473..69164bd6d 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -1115,7 +1115,7 @@ class ClassMetadataInfo implements ClassMetadata $mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\'); } - if ( ($mapping['type'] & (self::MANY_TO_ONE|self::MANY_TO_MANY)) > 0 && + if ( ($mapping['type'] & self::MANY_TO_ONE) > 0 && isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'] == true) { @@ -1335,6 +1335,8 @@ class ClassMetadataInfo implements ClassMetadata } } + $mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ? (bool) $mapping['orphanRemoval'] : false; + if (isset($mapping['orderBy'])) { if ( ! is_array($mapping['orderBy'])) { throw new \InvalidArgumentException("'orderBy' is expected to be an array, not ".gettype($mapping['orderBy'])); diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index 93e8473a4..0b1ec83cb 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -412,6 +412,7 @@ class AnnotationDriver implements Driver $mapping['inversedBy'] = $manyToManyAnnot->inversedBy; $mapping['cascade'] = $manyToManyAnnot->cascade; $mapping['indexBy'] = $manyToManyAnnot->indexBy; + $mapping['orphanRemoval'] = $manyToManyAnnot->orphanRemoval; $mapping['fetch'] = $this->getFetchMode($className, $manyToManyAnnot->fetch); if ($orderByAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OrderBy')) { diff --git a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php index b62df3893..4a07974d9 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php @@ -402,6 +402,10 @@ class XmlDriver extends AbstractFileDriver $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string)$manyToManyElement['fetch']); } + if (isset($manyToManyElement['orphan-removal'])) { + $mapping['orphanRemoval'] = (bool)$manyToManyElement['orphan-removal']; + } + if (isset($manyToManyElement['mapped-by'])) { $mapping['mappedBy'] = (string)$manyToManyElement['mapped-by']; } else if (isset($manyToManyElement->{'join-table'})) { diff --git a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php index ef0eff6ec..b66f29407 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php @@ -451,6 +451,10 @@ class YamlDriver extends AbstractFileDriver $mapping['indexBy'] = $manyToManyElement['indexBy']; } + if (isset($manyToManyElement['orphanRemoval'])) { + $mapping['orphanRemoval'] = (bool)$manyToManyElement['orphanRemoval']; + } + $metadata->mapManyToMany($mapping); } } diff --git a/lib/Doctrine/ORM/Mapping/ManyToMany.php b/lib/Doctrine/ORM/Mapping/ManyToMany.php index 1e2ae06c0..28f41aaff 100644 --- a/lib/Doctrine/ORM/Mapping/ManyToMany.php +++ b/lib/Doctrine/ORM/Mapping/ManyToMany.php @@ -35,6 +35,8 @@ final class ManyToMany implements Annotation public $cascade; /** @var string */ public $fetch = 'LAZY'; + /** @var boolean */ + public $orphanRemoval = false; /** @var string */ public $indexBy; } diff --git a/lib/Doctrine/ORM/PersistentCollection.php b/lib/Doctrine/ORM/PersistentCollection.php index 2d8a77ef6..3bfb0d1eb 100644 --- a/lib/Doctrine/ORM/PersistentCollection.php +++ b/lib/Doctrine/ORM/PersistentCollection.php @@ -388,7 +388,7 @@ final class PersistentCollection implements Collection $this->changed(); if ($this->association !== null && - $this->association['type'] == ClassMetadata::ONE_TO_MANY && + $this->association['type'] & ClassMetadata::TO_MANY && $this->association['orphanRemoval']) { $this->em->getUnitOfWork()->scheduleOrphanRemoval($removed); } @@ -426,7 +426,7 @@ final class PersistentCollection implements Collection $this->changed(); if ($this->association !== null && - $this->association['type'] === ClassMetadata::ONE_TO_MANY && + $this->association['type'] & ClassMetadata::TO_MANY && $this->association['orphanRemoval']) { $this->em->getUnitOfWork()->scheduleOrphanRemoval($element); } @@ -631,7 +631,7 @@ final class PersistentCollection implements Collection $uow = $this->em->getUnitOfWork(); - if ($this->association['type'] === ClassMetadata::ONE_TO_MANY && $this->association['orphanRemoval']) { + if ($this->association['type'] & ClassMetadata::TO_MANY && $this->association['orphanRemoval']) { // we need to initialize here, as orphan removal acts like implicit cascadeRemove, // hence for event listeners we need the objects in memory. $this->initialize(); diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1654Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1654Test.php new file mode 100644 index 000000000..016961935 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1654Test.php @@ -0,0 +1,103 @@ +setUpEntitySchema(array( + __NAMESPACE__ . '\\DDC1654Post', + __NAMESPACE__ . '\\DDC1654Comment', + )); + } + + public function testManyToManyRemoveFromCollectionOrphanRemoval() + { + $post = new DDC1654Post(); + $post->comments[] = new DDC1654Comment(); + $post->comments[] = new DDC1654Comment(); + + $this->_em->persist($post); + $this->_em->flush(); + + $post->comments->remove(0); + $post->comments->remove(1); + + $this->_em->flush(); + $this->_em->clear(); + + $comments = $this->_em->getRepository(__NAMESPACE__ . '\\DDC1654Comment')->findAll(); + $this->assertEquals(0, count($comments)); + } + + public function testManyToManyRemoveElementFromCollectionOrphanRemoval() + { + $post = new DDC1654Post(); + $post->comments[] = new DDC1654Comment(); + $post->comments[] = new DDC1654Comment(); + + $this->_em->persist($post); + $this->_em->flush(); + + $post->comments->removeElement($post->comments[0]); + $post->comments->removeElement($post->comments[1]); + + $this->_em->flush(); + $this->_em->clear(); + + $comments = $this->_em->getRepository(__NAMESPACE__ . '\\DDC1654Comment')->findAll(); + $this->assertEquals(0, count($comments)); + } + + public function testManyToManyClearCollectionOrphanRemoval() + { + $post = new DDC1654Post(); + $post->comments[] = new DDC1654Comment(); + $post->comments[] = new DDC1654Comment(); + + $this->_em->persist($post); + $this->_em->flush(); + + $post->comments->clear(); + + $this->_em->flush(); + $this->_em->clear(); + + $comments = $this->_em->getRepository(__NAMESPACE__ . '\\DDC1654Comment')->findAll(); + $this->assertEquals(0, count($comments)); + + } +} + +/** + * @Entity + */ +class DDC1654Post +{ + /** + * @Id @Column(type="integer") @GeneratedValue + */ + public $id; + + /** + * @ManyToMany(targetEntity="DDC1654Comment", orphanRemoval=true, + * cascade={"persist"}) + */ + public $comments = array(); +} + +/** + * @Entity + */ +class DDC1654Comment +{ + /** + * @Id @Column(type="integer") @GeneratedValue + */ + public $id; +} diff --git a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataBuilderTest.php b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataBuilderTest.php index 888db0d81..f14b60b3f 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataBuilderTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataBuilderTest.php @@ -377,6 +377,7 @@ class ClassMetadataBuilderTest extends \Doctrine\Tests\OrmTestCase array( 'user_id' => 'id', ), + 'orphanRemoval' => false, ), ), $this->cm->associationMappings); } diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index 641826925..4b92ed5d3 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -38,6 +38,12 @@ abstract class OrmFunctionalTestCase extends OrmTestCase /** Whether the database schema has already been created. */ protected static $_tablesCreated = array(); + /** + * Array of entity class name to their tables that were created. + * @var array + */ + protected static $_entityTablesCreated = array(); + /** List of model sets and their classes. */ protected static $_modelSets = array( 'cms' => array( @@ -235,6 +241,25 @@ abstract class OrmFunctionalTestCase extends OrmTestCase $this->_em->clear(); } + protected function setUpEntitySchema(array $classNames) + { + if ($this->_em === null) { + throw new \RuntimeException("EntityManager not set, you have to call parent::setUp() before invoking this method."); + } + + $classes = array(); + foreach ($classNames as $className) { + if ( ! isset(static::$_entityTablesCreated[$className])) { + static::$_entityTablesCreated[$className] = true; + $classes[] = $this->_em->getClassMetadata($className); + } + } + + if ($classes) { + $this->_schemaTool->createSchema($classes); + } + } + /** * Creates a connection to the test database, if there is none yet, and * creates the necessary tables. From 502585bf40f37233532594a707dbbbf0426a882e Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 20 Feb 2012 15:34:02 +0100 Subject: [PATCH 80/83] [DDC-1649] Add additional check for not allowed mapping of dependent association keys. --- lib/Doctrine/ORM/Tools/SchemaValidator.php | 5 +++ .../Tests/ORM/Tools/SchemaValidatorTest.php | 44 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/lib/Doctrine/ORM/Tools/SchemaValidator.php b/lib/Doctrine/ORM/Tools/SchemaValidator.php index 0e1f80cdf..e98372d4c 100644 --- a/lib/Doctrine/ORM/Tools/SchemaValidator.php +++ b/lib/Doctrine/ORM/Tools/SchemaValidator.php @@ -106,6 +106,11 @@ class SchemaValidator $targetMetadata = $cmf->getMetadataFor($assoc['targetEntity']); + if ($assoc['id'] && $targetMetadata->containsForeignIdentifier) { + $ce[] = "Cannot map association '" . $class->name. "#". $fieldName ." as identifier, because " . + "the target entity '". $targetMetadata->name . "' also maps an association as identifier."; + } + /* @var $assoc AssociationMapping */ if ($assoc['mappedBy']) { if ($targetMetadata->hasField($assoc['mappedBy'])) { diff --git a/tests/Doctrine/Tests/ORM/Tools/SchemaValidatorTest.php b/tests/Doctrine/Tests/ORM/Tools/SchemaValidatorTest.php index 0b9657fe0..6f2e91236 100644 --- a/tests/Doctrine/Tests/ORM/Tools/SchemaValidatorTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/SchemaValidatorTest.php @@ -122,6 +122,20 @@ class SchemaValidatorTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals(array(), $ce); } + + /** + * @group DDC-1649 + */ + public function testInvalidTripleAssociationAsKeyMapping() + { + $classThree = $this->em->getClassMetadata(__NAMESPACE__ . '\DDC1649Three'); + $ce = $this->validator->validateClass($classThree); + + $this->assertEquals(Array( + "Cannot map association 'Doctrine\Tests\ORM\Tools\DDC1649Three#two as identifier, because the target entity 'Doctrine\Tests\ORM\Tools\DDC1649Two' also maps an association as identifier.", + "The referenced column name 'id' has to be a primary key column on the target entity class 'Doctrine\Tests\ORM\Tools\DDC1649Two'." + ), $ce); + } } /** @@ -221,3 +235,33 @@ class DDC1587ValidEntity2 private $num; } +/** + * @Entity + */ +class DDC1649One +{ + /** + * @Id @Column @GeneratedValue + */ + public $id; +} + +/** + * @Entity + */ +class DDC1649Two +{ + /** @Id @ManyToOne(targetEntity="DDC1649One")@JoinColumn(name="id", referencedColumnName="id") */ + public $one; +} + +/** + * @Entity + */ +class DDC1649Three +{ + /** @Id @ManyToOne(targetEntity="DDC1649Two") @JoinColumn(name="id", + * referencedColumnName="id") */ + private $two; +} + From dbd646b2de38c71ed7e35c91104b2a2d8d05ba89 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 20 Feb 2012 15:38:25 +0100 Subject: [PATCH 81/83] [DDC-1649] Fix notice by last commit. --- lib/Doctrine/ORM/Tools/SchemaValidator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Tools/SchemaValidator.php b/lib/Doctrine/ORM/Tools/SchemaValidator.php index e98372d4c..f91bfff25 100644 --- a/lib/Doctrine/ORM/Tools/SchemaValidator.php +++ b/lib/Doctrine/ORM/Tools/SchemaValidator.php @@ -106,7 +106,7 @@ class SchemaValidator $targetMetadata = $cmf->getMetadataFor($assoc['targetEntity']); - if ($assoc['id'] && $targetMetadata->containsForeignIdentifier) { + if (isset($assoc['id']) && $targetMetadata->containsForeignIdentifier) { $ce[] = "Cannot map association '" . $class->name. "#". $fieldName ." as identifier, because " . "the target entity '". $targetMetadata->name . "' also maps an association as identifier."; } From 9b9acd6e9e139ef87257b7f416184c9259396d74 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 20 Feb 2012 16:03:42 +0100 Subject: [PATCH 82/83] Fix composer.json with regards to latest changes. --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 8f570e200..504c304b7 100644 --- a/composer.json +++ b/composer.json @@ -14,8 +14,8 @@ "require": { "php": ">=5.3.2", "ext-pdo": "*", - "doctrine/common": "master-dev", - "doctrine/dbal": "master-dev" + "doctrine/common": "dev-master", + "doctrine/dbal": "dev-master" }, "autoload": { "psr-0": { "Doctrine\\ORM": "lib/" } From 1bbd52b8eed116c2beb3d5483f8ce8881b8450ee Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Mon, 20 Feb 2012 17:48:34 +0100 Subject: [PATCH 83/83] [DDC-1652] Fix SqlWalker to include foreign key identifiers in SQL SELECT statement no matter what the meta column setting is suggesting. --- .../Internal/Hydration/AbstractHydrator.php | 2 +- .../ORM/Internal/Hydration/ArrayHydrator.php | 2 +- lib/Doctrine/ORM/Query/SqlWalker.php | 15 ++++++++-- .../ORM/Functional/Ticket/DDC117Test.php | 28 +++++++++++++++++++ .../ORM/Query/SelectSqlGenerationTest.php | 2 +- 5 files changed, 44 insertions(+), 5 deletions(-) diff --git a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php index e1d8cd1f2..020d48554 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php @@ -234,7 +234,7 @@ abstract class AbstractHydrator if (isset($cache[$key]['isScalar'])) { $value = $cache[$key]['type']->convertToPHPValue($value, $this->_platform); - + $rowData['scalars'][$cache[$key]['fieldName']] = $value; continue; diff --git a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php index 20c2b5785..9a8fcee83 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ArrayHydrator.php @@ -286,4 +286,4 @@ class ArrayHydrator extends AbstractHydrator return $this->_ce[$className]; } -} \ No newline at end of file +} diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index 13910054c..9701f4718 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -636,11 +636,17 @@ class SqlWalker implements TreeWalker } // Add foreign key columns to SQL, if necessary - if ( ! $addMetaColumns) continue; + if ( ! $addMetaColumns && ! $class->containsForeignIdentifier) { + continue; + } // Add foreign key columns of class and also parent classes foreach ($class->associationMappings as $assoc) { - if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) continue; + if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) { + continue; + } else if ( !$addMetaColumns && !isset($assoc['id'])) { + continue; + } $owningClass = (isset($assoc['inherited'])) ? $this->_em->getClassMetadata($assoc['inherited']) : $class; $sqlTableAlias = $this->getSQLTableAlias($owningClass->getTableName(), $dqlAlias); @@ -654,6 +660,11 @@ class SqlWalker implements TreeWalker } } + // Add foreign key columns to SQL, if necessary + if ( ! $addMetaColumns) { + continue; + } + // Add foreign key columns of subclasses foreach ($class->subClasses as $subClassName) { $subClass = $this->_em->getClassMetadata($subClassName); diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC117Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC117Test.php index bee6ef971..6ae4595dc 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC117Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC117Test.php @@ -433,4 +433,32 @@ class DDC117Test extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals($this->article1->id(), $refRep->source()->id()); $this->assertEquals($this->article2->id(), $refRep->target()->id()); } + + /** + * @group DDC-1652 + */ + public function testArrayHydrationWithCompositeKey() + { + $dql = "SELECT r,s,t FROM Doctrine\Tests\Models\DDC117\DDC117Reference r INNER JOIN r.source s INNER JOIN r.target t"; + $before = count($this->_em->createQuery($dql)->getResult()); + + $this->article1 = $this->_em->find("Doctrine\Tests\Models\DDC117\DDC117Article", $this->article1->id()); + $this->article2 = $this->_em->find("Doctrine\Tests\Models\DDC117\DDC117Article", $this->article2->id()); + + $this->reference = new DDC117Reference($this->article2, $this->article1, "Test-Description"); + $this->_em->persist($this->reference); + + $this->reference = new DDC117Reference($this->article1, $this->article1, "Test-Description"); + $this->_em->persist($this->reference); + + $this->reference = new DDC117Reference($this->article2, $this->article2, "Test-Description"); + $this->_em->persist($this->reference); + + $this->_em->flush(); + + $dql = "SELECT r,s,t FROM Doctrine\Tests\Models\DDC117\DDC117Reference r INNER JOIN r.source s INNER JOIN r.target t"; + $data = $this->_em->createQuery($dql)->getArrayResult(); + + $this->assertEquals($before + 3, count($data)); + } } diff --git a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php index a7e96be3e..fccfed03b 100644 --- a/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Doctrine/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -1074,7 +1074,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase { $this->assertSqlGeneration( "SELECT t, s, l FROM Doctrine\Tests\Models\DDC117\DDC117Link l INNER JOIN l.target t INNER JOIN l.source s", - "SELECT d0_.article_id AS article_id0, d0_.title AS title1, d1_.article_id AS article_id2, d1_.title AS title3 FROM DDC117Link d2_ INNER JOIN DDC117Article d0_ ON d2_.target_id = d0_.article_id INNER JOIN DDC117Article d1_ ON d2_.source_id = d1_.article_id" + "SELECT d0_.article_id AS article_id0, d0_.title AS title1, d1_.article_id AS article_id2, d1_.title AS title3, d2_.source_id AS source_id4, d2_.target_id AS target_id5 FROM DDC117Link d2_ INNER JOIN DDC117Article d0_ ON d2_.target_id = d0_.article_id INNER JOIN DDC117Article d1_ ON d2_.source_id = d1_.article_id" ); }