diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index b4047da96..03285b2b8 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -139,6 +139,13 @@ class EntityGenerator */ protected $fieldVisibility = 'private'; + /** + * Whether or not to make generated embeddables immutable. + * + * @var boolean. + */ + protected $embeddablesImmutable = false; + /** * Hash-map for handle types. * @@ -299,6 +306,21 @@ public function __construct() { } +'; + + /** + * @var string + */ + protected static $embeddableConstructorMethodTemplate = +'/** + * Constructor + * + * + */ +public function __construct() +{ + +} '; /** @@ -485,6 +507,16 @@ public function __construct() $this->fieldVisibility = $visibility; } + /** + * Sets whether or not to generate immutable embeddables. + * + * @param boolean $embeddablesImmutable + */ + public function setEmbeddablesImmutable($embeddablesImmutable) + { + $this->embeddablesImmutable = (boolean) $embeddablesImmutable; + } + /** * Sets an annotation prefix. * @@ -599,6 +631,7 @@ public function __construct() protected function generateEntityBody(ClassMetadataInfo $metadata) { $fieldMappingProperties = $this->generateEntityFieldMappingProperties($metadata); + $embeddedProperties = $this->generateEntityEmbeddedProperties($metadata); $associationMappingProperties = $this->generateEntityAssociationMappingProperties($metadata); $stubMethods = $this->generateEntityStubMethods ? $this->generateEntityStubMethods($metadata) : null; $lifecycleCallbackMethods = $this->generateEntityLifecycleCallbackMethods($metadata); @@ -609,6 +642,10 @@ public function __construct() $code[] = $fieldMappingProperties; } + if ($embeddedProperties) { + $code[] = $embeddedProperties; + } + if ($associationMappingProperties) { $code[] = $associationMappingProperties; } @@ -637,6 +674,10 @@ public function __construct() return ''; } + if ($metadata->isEmbeddedClass && $this->embeddablesImmutable) { + return $this->generateEmbeddableConstructor($metadata); + } + $collections = array(); foreach ($metadata->associationMappings as $mapping) { @@ -652,6 +693,100 @@ public function __construct() return ''; } + /** + * @param ClassMetadataInfo $metadata + * + * @return string + */ + private function generateEmbeddableConstructor(ClassMetadataInfo $metadata) + { + $paramTypes = array(); + $paramVariables = array(); + $params = array(); + $fields = array(); + + // Resort fields to put optional fields at the end of the method signature. + $requiredFields = array(); + $optionalFields = array(); + + foreach ($metadata->fieldMappings as $fieldMapping) { + if (empty($fieldMapping['nullable'])) { + $requiredFields[] = $fieldMapping; + + continue; + } + + $optionalFields[] = $fieldMapping; + } + + $fieldMappings = array_merge($requiredFields, $optionalFields); + + foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) { + $paramType = '\\' . ltrim($embeddedClass['class'], '\\'); + $paramVariable = '$' . $fieldName; + + $paramTypes[] = $paramType; + $paramVariables[] = $paramVariable; + $params[] = $paramType . ' ' . $paramVariable; + $fields[] = '$this->' . $fieldName . ' = ' . $paramVariable . ';'; + } + + foreach ($fieldMappings as $fieldMapping) { + if (isset($fieldMapping['declaredField']) && + isset($metadata->embeddedClasses[$fieldMapping['declaredField']]) + ) { + continue; + } + + $paramTypes[] = $this->getType($fieldMapping['type']) . (!empty($fieldMapping['nullable']) ? '|null' : ''); + $param = '$' . $fieldMapping['fieldName']; + $paramVariables[] = $param; + + if ($fieldMapping['type'] === 'datetime') { + $param = $this->getType($fieldMapping['type']) . ' ' . $param; + } + + if (!empty($fieldMapping['nullable'])) { + $param .= ' = null'; + } + + $params[] = $param; + + $fields[] = '$this->' . $fieldMapping['fieldName'] . ' = $' . $fieldMapping['fieldName'] . ';'; + } + + $maxParamTypeLength = max(array_map('strlen', $paramTypes)); + $paramTags = array_map( + function ($type, $variable) use ($maxParamTypeLength) { + return '@param ' . $type . str_repeat(' ', $maxParamTypeLength - strlen($type) + 1) . $variable; + }, + $paramTypes, + $paramVariables + ); + + // Generate multi line constructor if the signature exceeds 120 characters. + if (array_sum(array_map('strlen', $params)) + count($params) * 2 + 29 > 120) { + $delimiter = "\n" . $this->spaces; + $params = $delimiter . implode(',' . $delimiter, $params) . "\n"; + } else { + $params = implode(', ', $params); + } + + $replacements = array( + '' => implode("\n * ", $paramTags), + '' => $params, + '' => implode("\n" . $this->spaces, $fields), + ); + + $constructor = str_replace( + array_keys($replacements), + array_values($replacements), + static::$embeddableConstructorMethodTemplate + ); + + return $this->prefixCodeWithSpaces($constructor); + } + /** * @todo this won't work if there is a namespace in brackets and a class outside of it. * @@ -866,7 +1001,8 @@ public function __construct() 'generateTableAnnotation', 'generateInheritanceAnnotation', 'generateDiscriminatorColumnAnnotation', - 'generateDiscriminatorMapAnnotation' + 'generateDiscriminatorMapAnnotation', + 'generateEntityAnnotation', ); foreach ($methods as $method) { @@ -875,14 +1011,6 @@ public function __construct() } } - $customRepository = $metadata->customRepositoryClassName - ? '(repositoryClass="' . $metadata->customRepositoryClassName . '")' - : ''; - - $lines[] = ' * @' . $this->annotationsPrefix - . ($metadata->isMappedSuperclass ? 'MappedSuperclass' : 'Entity') - . $customRepository; - if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) { $lines[] = ' * @' . $this->annotationsPrefix . 'HasLifecycleCallbacks'; } @@ -893,6 +1021,26 @@ public function __construct() return implode("\n", $lines); } + /** + * @param ClassMetadataInfo $metadata + * + * @return string + */ + protected function generateEntityAnnotation(ClassMetadataInfo $metadata) + { + $prefix = '@' . $this->annotationsPrefix; + + if ($metadata->isEmbeddedClass) { + return $prefix . 'Embeddable'; + } + + $customRepository = $metadata->customRepositoryClassName + ? '(repositoryClass="' . $metadata->customRepositoryClassName . '")' + : ''; + + return $prefix . ($metadata->isMappedSuperclass ? 'MappedSuperclass' : 'Entity') . $customRepository; + } + /** * @param ClassMetadataInfo $metadata * @@ -900,6 +1048,10 @@ public function __construct() */ protected function generateTableAnnotation($metadata) { + if ($metadata->isEmbeddedClass) { + return ''; + } + $table = array(); if (isset($metadata->table['schema'])) { @@ -1003,7 +1155,17 @@ public function __construct() $methods = array(); foreach ($metadata->fieldMappings as $fieldMapping) { - if ( ! isset($fieldMapping['id']) || ! $fieldMapping['id'] || $metadata->generatorType == ClassMetadataInfo::GENERATOR_TYPE_NONE) { + if (isset($fieldMapping['declaredField']) && + isset($metadata->embeddedClasses[$fieldMapping['declaredField']]) + ) { + continue; + } + + if (( ! isset($fieldMapping['id']) || + ! $fieldMapping['id'] || + $metadata->generatorType == ClassMetadataInfo::GENERATOR_TYPE_NONE + ) && (! $metadata->isEmbeddedClass || ! $this->embeddablesImmutable) + ) { if ($code = $this->generateEntityStubMethod($metadata, 'set', $fieldMapping['fieldName'], $fieldMapping['type'])) { $methods[] = $code; } @@ -1014,6 +1176,22 @@ public function __construct() } } + foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) { + if (isset($embeddedClass['declaredField'])) { + continue; + } + + if ( ! $metadata->isEmbeddedClass || ! $this->embeddablesImmutable) { + if ($code = $this->generateEntityStubMethod($metadata, 'set', $fieldName, $embeddedClass['class'])) { + $methods[] = $code; + } + } + + if ($code = $this->generateEntityStubMethod($metadata, 'get', $fieldName, $embeddedClass['class'])) { + $methods[] = $code; + } + } + foreach ($metadata->associationMappings as $associationMapping) { if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) { $nullable = $this->isAssociationIsNullable($associationMapping) ? 'null' : null; @@ -1123,7 +1301,12 @@ public function __construct() foreach ($metadata->fieldMappings as $fieldMapping) { if ($this->hasProperty($fieldMapping['fieldName'], $metadata) || - $metadata->isInheritedField($fieldMapping['fieldName'])) { + $metadata->isInheritedField($fieldMapping['fieldName']) || + ( + isset($fieldMapping['declaredField']) && + isset($metadata->embeddedClasses[$fieldMapping['declaredField']]) + ) + ) { continue; } @@ -1135,6 +1318,27 @@ public function __construct() return implode("\n", $lines); } + /** + * @param ClassMetadataInfo $metadata + * + * @return string + */ + protected function generateEntityEmbeddedProperties(ClassMetadataInfo $metadata) + { + $lines = array(); + + foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) { + if (isset($embeddedClass['declaredField']) || $this->hasProperty($fieldName, $metadata)) { + continue; + } + + $lines[] = $this->generateEmbeddedPropertyDocBlock($embeddedClass); + $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldName . ";\n"; + } + + return implode("\n", $lines); + } + /** * @param ClassMetadataInfo $metadata * @param string $type @@ -1496,6 +1700,35 @@ public function __construct() return implode("\n", $lines); } + /** + * @param array $embeddedClass + * + * @return string + */ + protected function generateEmbeddedPropertyDocBlock(array $embeddedClass) + { + $lines = array(); + $lines[] = $this->spaces . '/**'; + $lines[] = $this->spaces . ' * @var \\' . ltrim($embeddedClass['class'], '\\'); + + if ($this->generateAnnotations) { + $lines[] = $this->spaces . ' *'; + + $embedded = array('class="' . $embeddedClass['class'] . '"'); + + if (isset($fieldMapping['columnPrefix'])) { + $embedded[] = 'columnPrefix=' . var_export($embeddedClass['columnPrefix'], true); + } + + $lines[] = $this->spaces . ' * @' . + $this->annotationsPrefix . 'Embedded(' . implode(', ', $embedded) . ')'; + } + + $lines[] = $this->spaces . ' */'; + + return implode("\n", $lines); + } + /** * @param string $code * @param int $num diff --git a/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php b/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php index 172213dab..d23d543ca 100644 --- a/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/EntityGeneratorTest.php @@ -49,7 +49,12 @@ class EntityGeneratorTest extends OrmTestCase rmdir($this->_tmpDir . '/' . $this->_namespace); } - public function generateBookEntityFixture() + /** + * @param ClassMetadataInfo[] $embeddedClasses + * + * @return ClassMetadataInfo + */ + public function generateBookEntityFixture(array $embeddedClasses = array()) { $metadata = new ClassMetadataInfo($this->_namespace . '\EntityGeneratorBook'); $metadata->namespace = $this->_namespace; @@ -79,6 +84,11 @@ class EntityGeneratorTest extends OrmTestCase $metadata->addLifecycleCallback('willBeRemoved', 'preRemove'); $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO); + foreach ($embeddedClasses as $fieldName => $embeddedClass) { + $this->mapNestedEmbedded($fieldName, $metadata, $embeddedClass); + $this->mapEmbedded($fieldName, $metadata, $embeddedClass); + } + $this->_generator->writeEntityClass($metadata, $this->_tmpDir); return $metadata; @@ -102,22 +112,117 @@ class EntityGeneratorTest extends OrmTestCase return $metadata; } + /** + * @return ClassMetadataInfo + */ + private function generateIsbnEmbeddableFixture(array $embeddedClasses = array()) + { + $metadata = new ClassMetadataInfo($this->_namespace . '\EntityGeneratorIsbn'); + $metadata->namespace = $this->_namespace; + $metadata->isEmbeddedClass = true; + $metadata->mapField(array('fieldName' => 'prefix', 'type' => 'integer')); + $metadata->mapField(array('fieldName' => 'groupNumber', 'type' => 'integer')); + $metadata->mapField(array('fieldName' => 'publisherNumber', 'type' => 'integer')); + $metadata->mapField(array('fieldName' => 'titleNumber', 'type' => 'integer')); + $metadata->mapField(array('fieldName' => 'checkDigit', 'type' => 'integer')); + + foreach ($embeddedClasses as $fieldName => $embeddedClass) { + $this->mapEmbedded($fieldName, $metadata, $embeddedClass); + } + + $this->_generator->writeEntityClass($metadata, $this->_tmpDir); + + return $metadata; + } + + /** + * @return ClassMetadataInfo + */ + private function generateTestEmbeddableFixture() + { + $metadata = new ClassMetadataInfo($this->_namespace . '\EntityGeneratorTestEmbeddable'); + $metadata->namespace = $this->_namespace; + $metadata->isEmbeddedClass = true; + $metadata->mapField(array('fieldName' => 'field1', 'type' => 'integer')); + $metadata->mapField(array('fieldName' => 'field2', 'type' => 'integer', 'nullable' => true)); + $metadata->mapField(array('fieldName' => 'field3', 'type' => 'datetime')); + $metadata->mapField(array('fieldName' => 'field4', 'type' => 'datetime', 'nullable' => true)); + + $this->_generator->writeEntityClass($metadata, $this->_tmpDir); + + return $metadata; + } + + /** + * @param string $fieldName + * @param ClassMetadataInfo $classMetadata + * @param ClassMetadataInfo $embeddableMetadata + * @param string|null $columnPrefix + */ + private function mapEmbedded( + $fieldName, + ClassMetadataInfo $classMetadata, + ClassMetadataInfo $embeddableMetadata, + $columnPrefix = false + ) { + $classMetadata->mapEmbedded( + array('fieldName' => $fieldName, 'class' => $embeddableMetadata->name, 'columnPrefix' => $columnPrefix) + ); + } + + /** + * @param string $fieldName + * @param ClassMetadataInfo $classMetadata + * @param ClassMetadataInfo $embeddableMetadata + */ + private function mapNestedEmbedded( + $fieldName, + ClassMetadataInfo $classMetadata, + ClassMetadataInfo $embeddableMetadata + ) { + foreach ($embeddableMetadata->embeddedClasses as $property => $embeddableClass) { + $classMetadata->mapEmbedded(array( + 'fieldName' => $fieldName . '.' . $property, + 'class' => $embeddableClass['class'], + 'columnPrefix' => $embeddableClass['columnPrefix'], + 'declaredField' => $embeddableClass['declaredField'] + ? $fieldName . '.' . $embeddableClass['declaredField'] + : $fieldName, + 'originalField' => $embeddableClass['originalField'] ?: $property, + )); + } + } + + /** + * @param ClassMetadataInfo $metadata + */ + private function loadEntityClass(ClassMetadataInfo $metadata) + { + $className = basename(str_replace('\\', '/', $metadata->name)); + $path = $this->_tmpDir . '/' . $this->_namespace . '/' . $className . '.php'; + + $this->assertFileExists($path); + + require_once $path; + } + /** * @param ClassMetadataInfo $metadata - * @return EntityGeneratorBook + * + * @return mixed An instance of the given metadata's class. */ public function newInstance($metadata) { - $path = $this->_tmpDir . '/'. $this->_namespace . '/EntityGeneratorBook.php'; - $this->assertFileExists($path); - require_once $path; + $this->loadEntityClass($metadata); return new $metadata->name; } public function testGeneratedEntityClass() { - $metadata = $this->generateBookEntityFixture(); + $testMetadata = $this->generateTestEmbeddableFixture(); + $isbnMetadata = $this->generateIsbnEmbeddableFixture(array('test' => $testMetadata)); + $metadata = $this->generateBookEntityFixture(array('isbn' => $isbnMetadata)); $book = $this->newInstance($metadata); $this->assertTrue(class_exists($metadata->name), "Class does not exist."); @@ -125,11 +230,20 @@ class EntityGeneratorTest extends OrmTestCase $this->assertTrue(method_exists($metadata->namespace . '\EntityGeneratorBook', 'getId'), "EntityGeneratorBook::getId() missing."); $this->assertTrue(method_exists($metadata->namespace . '\EntityGeneratorBook', 'setName'), "EntityGeneratorBook::setName() missing."); $this->assertTrue(method_exists($metadata->namespace . '\EntityGeneratorBook', 'getName'), "EntityGeneratorBook::getName() missing."); + $this->assertTrue(method_exists($metadata->namespace . '\EntityGeneratorBook', 'setStatus'), "EntityGeneratorBook::setStatus() missing."); + $this->assertTrue(method_exists($metadata->namespace . '\EntityGeneratorBook', 'getStatus'), "EntityGeneratorBook::getStatus() missing."); $this->assertTrue(method_exists($metadata->namespace . '\EntityGeneratorBook', 'setAuthor'), "EntityGeneratorBook::setAuthor() missing."); $this->assertTrue(method_exists($metadata->namespace . '\EntityGeneratorBook', 'getAuthor'), "EntityGeneratorBook::getAuthor() missing."); $this->assertTrue(method_exists($metadata->namespace . '\EntityGeneratorBook', 'getComments'), "EntityGeneratorBook::getComments() missing."); $this->assertTrue(method_exists($metadata->namespace . '\EntityGeneratorBook', 'addComment'), "EntityGeneratorBook::addComment() missing."); $this->assertTrue(method_exists($metadata->namespace . '\EntityGeneratorBook', 'removeComment'), "EntityGeneratorBook::removeComment() missing."); + $this->assertTrue(method_exists($metadata->namespace . '\EntityGeneratorBook', 'setIsbn'), "EntityGeneratorBook::setIsbn() missing."); + $this->assertTrue(method_exists($metadata->namespace . '\EntityGeneratorBook', 'getIsbn'), "EntityGeneratorBook::getIsbn() missing."); + + $reflClass = new \ReflectionClass($metadata->name); + + $this->assertCount(6, $reflClass->getProperties()); + $this->assertCount(15, $reflClass->getMethods()); $this->assertEquals('published', $book->getStatus()); @@ -154,13 +268,27 @@ class EntityGeneratorTest extends OrmTestCase $this->assertEquals(new \Doctrine\Common\Collections\ArrayCollection(array($comment)), $book->getComments()); $book->removeComment($comment); $this->assertEquals(new \Doctrine\Common\Collections\ArrayCollection(array()), $book->getComments()); + + $this->newInstance($isbnMetadata); + $isbn = new $isbnMetadata->name(); + + $book->setIsbn($isbn); + $this->assertSame($isbn, $book->getIsbn()); + + $reflMethod = new \ReflectionMethod($metadata->name, 'setIsbn'); + $reflParameters = $reflMethod->getParameters(); + $this->assertEquals($isbnMetadata->name, $reflParameters[0]->getClass()->name); } public function testEntityUpdatingWorks() { - $metadata = $this->generateBookEntityFixture(); + $metadata = $this->generateBookEntityFixture(array('isbn' => $this->generateIsbnEmbeddableFixture())); + $metadata->mapField(array('fieldName' => 'test', 'type' => 'string')); + $testEmbeddableMetadata = $this->generateTestEmbeddableFixture(); + $this->mapEmbedded('testEmbedded', $metadata, $testEmbeddableMetadata); + $this->_generator->writeEntityClass($metadata, $this->_tmpDir); $this->assertFileExists($this->_tmpDir . "/" . $this->_namespace . "/EntityGeneratorBook.php~"); @@ -171,13 +299,26 @@ class EntityGeneratorTest extends OrmTestCase $this->assertTrue($reflClass->hasProperty('name'), "Regenerating keeps property 'name'."); $this->assertTrue($reflClass->hasProperty('status'), "Regenerating keeps property 'status'."); $this->assertTrue($reflClass->hasProperty('id'), "Regenerating keeps property 'id'."); + $this->assertTrue($reflClass->hasProperty('isbn'), "Regenerating keeps property 'isbn'."); $this->assertTrue($reflClass->hasProperty('test'), "Check for property test failed."); $this->assertTrue($reflClass->getProperty('test')->isProtected(), "Check for protected property test failed."); + $this->assertTrue($reflClass->hasProperty('testEmbedded'), "Check for property testEmbedded failed."); + $this->assertTrue($reflClass->getProperty('testEmbedded')->isProtected(), "Check for protected property testEmbedded 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."); - $this->assertTrue($reflClass->getMethod('getTest')->isPublic(), "Check for public visibility of method 'getTest' failed."); + $this->assertTrue($reflClass->hasMethod('setTest'), "Check for method 'setTest' failed."); + $this->assertTrue($reflClass->getMethod('setTest')->isPublic(), "Check for public visibility of method 'setTest' failed."); + $this->assertTrue($reflClass->hasMethod('getTestEmbedded'), "Check for method 'getTestEmbedded' failed."); + $this->assertTrue( + $reflClass->getMethod('getTestEmbedded')->isPublic(), + "Check for public visibility of method 'getTestEmbedded' failed." + ); + $this->assertTrue($reflClass->hasMethod('setTestEmbedded'), "Check for method 'setTestEmbedded' failed."); + $this->assertTrue( + $reflClass->getMethod('setTestEmbedded')->isPublic(), + "Check for public visibility of method 'setTestEmbedded' failed." + ); } /** @@ -185,10 +326,12 @@ class EntityGeneratorTest extends OrmTestCase */ public function testDoesNotRegenerateExistingMethodsWithDifferentCase() { - $metadata = $this->generateBookEntityFixture(); + $metadata = $this->generateBookEntityFixture(array('isbn' => $this->generateIsbnEmbeddableFixture())); // Workaround to change existing fields case (just to simulate the use case) $metadata->fieldMappings['status']['fieldName'] = 'STATUS'; + $metadata->embeddedClasses['ISBN'] = $metadata->embeddedClasses['isbn']; + unset($metadata->embeddedClasses['isbn']); // Should not throw a PHP fatal error $this->_generator->writeEntityClass($metadata, $this->_tmpDir); @@ -200,8 +343,12 @@ class EntityGeneratorTest extends OrmTestCase $this->assertTrue($reflClass->hasProperty('status')); $this->assertTrue($reflClass->hasProperty('STATUS')); + $this->assertTrue($reflClass->hasProperty('isbn')); + $this->assertTrue($reflClass->hasProperty('ISBN')); $this->assertTrue($reflClass->hasMethod('getStatus')); $this->assertTrue($reflClass->hasMethod('setStatus')); + $this->assertTrue($reflClass->hasMethod('getIsbn')); + $this->assertTrue($reflClass->hasMethod('setIsbn')); } /** @@ -209,8 +356,9 @@ class EntityGeneratorTest extends OrmTestCase */ public function testMethodDocBlockShouldStartWithBackSlash() { - $metadata = $this->generateBookEntityFixture(); - $book = $this->newInstance($metadata); + $embeddedMetadata = $this->generateIsbnEmbeddableFixture(); + $metadata = $this->generateBookEntityFixture(array('isbn' => $embeddedMetadata)); + $book = $this->newInstance($metadata); $this->assertPhpDocVarType('\Doctrine\Common\Collections\Collection', new \ReflectionProperty($book, 'comments')); $this->assertPhpDocReturnType('\Doctrine\Common\Collections\Collection', new \ReflectionMethod($book, 'getComments')); @@ -220,6 +368,11 @@ class EntityGeneratorTest extends OrmTestCase $this->assertPhpDocVarType('\Doctrine\Tests\ORM\Tools\EntityGeneratorAuthor', new \ReflectionProperty($book, 'author')); $this->assertPhpDocReturnType('\Doctrine\Tests\ORM\Tools\EntityGeneratorAuthor', new \ReflectionMethod($book, 'getAuthor')); $this->assertPhpDocParamType('\Doctrine\Tests\ORM\Tools\EntityGeneratorAuthor', new \ReflectionMethod($book, 'setAuthor')); + + $expectedClassName = '\\' . $embeddedMetadata->name; + $this->assertPhpDocVarType($expectedClassName, new \ReflectionProperty($book, 'isbn')); + $this->assertPhpDocReturnType($expectedClassName, new \ReflectionMethod($book, 'getIsbn')); + $this->assertPhpDocParamType($expectedClassName, new \ReflectionMethod($book, 'setIsbn')); } public function testEntityExtendsStdClass() @@ -229,6 +382,10 @@ class EntityGeneratorTest extends OrmTestCase $book = $this->newInstance($metadata); $this->assertInstanceOf('stdClass', $book); + + $metadata = $this->generateIsbnEmbeddableFixture(); + $isbn = $this->newInstance($metadata); + $this->assertInstanceOf('stdClass', $isbn); } public function testLifecycleCallbacks() @@ -244,12 +401,15 @@ class EntityGeneratorTest extends OrmTestCase public function testLoadMetadata() { - $metadata = $this->generateBookEntityFixture(); + $embeddedMetadata = $this->generateIsbnEmbeddableFixture(); + $metadata = $this->generateBookEntityFixture(array('isbn' => $embeddedMetadata)); $book = $this->newInstance($metadata); + $reflectionService = new RuntimeReflectionService(); + $cm = new ClassMetadata($metadata->name); - $cm->initializeReflection(new RuntimeReflectionService); + $cm->initializeReflection($reflectionService); $driver = $this->createAnnotationDriver(); $driver->loadMetadataForClass($cm->name, $cm); @@ -260,22 +420,38 @@ class EntityGeneratorTest extends OrmTestCase $this->assertEquals($cm->identifier, $metadata->identifier); $this->assertEquals($cm->idGenerator, $metadata->idGenerator); $this->assertEquals($cm->customRepositoryClassName, $metadata->customRepositoryClassName); + $this->assertEquals($cm->embeddedClasses, $metadata->embeddedClasses); + $this->assertEquals($cm->isEmbeddedClass, $metadata->isEmbeddedClass); $this->assertEquals(ClassMetadataInfo::FETCH_EXTRA_LAZY, $cm->associationMappings['comments']['fetch']); + + $isbn = $this->newInstance($embeddedMetadata); + + $cm = new ClassMetadata($embeddedMetadata->name); + $cm->initializeReflection($reflectionService); + + $driver->loadMetadataForClass($cm->name, $cm); + + $this->assertEquals($cm->columnNames, $embeddedMetadata->columnNames); + $this->assertEquals($cm->embeddedClasses, $embeddedMetadata->embeddedClasses); + $this->assertEquals($cm->isEmbeddedClass, $embeddedMetadata->isEmbeddedClass); } public function testLoadPrefixedMetadata() { $this->_generator->setAnnotationPrefix('ORM\\'); - $metadata = $this->generateBookEntityFixture(); + $embeddedMetadata = $this->generateIsbnEmbeddableFixture(); + $metadata = $this->generateBookEntityFixture(array('isbn' => $embeddedMetadata)); $reader = new AnnotationReader(); $driver = new AnnotationDriver($reader, array()); $book = $this->newInstance($metadata); + $reflectionService = new RuntimeReflectionService(); + $cm = new ClassMetadata($metadata->name); - $cm->initializeReflection(new RuntimeReflectionService); + $cm->initializeReflection($reflectionService); $driver->loadMetadataForClass($cm->name, $cm); @@ -285,6 +461,17 @@ class EntityGeneratorTest extends OrmTestCase $this->assertEquals($cm->identifier, $metadata->identifier); $this->assertEquals($cm->idGenerator, $metadata->idGenerator); $this->assertEquals($cm->customRepositoryClassName, $metadata->customRepositoryClassName); + + $isbn = $this->newInstance($embeddedMetadata); + + $cm = new ClassMetadata($embeddedMetadata->name); + $cm->initializeReflection($reflectionService); + + $driver->loadMetadataForClass($cm->name, $cm); + + $this->assertEquals($cm->columnNames, $embeddedMetadata->columnNames); + $this->assertEquals($cm->embeddedClasses, $embeddedMetadata->embeddedClasses); + $this->assertEquals($cm->isEmbeddedClass, $embeddedMetadata->isEmbeddedClass); } /** @@ -644,7 +831,120 @@ class EntityGeneratorTest extends OrmTestCase $this->assertFalse($rc3->hasMethod('getCreatedAt')); $this->assertFalse($rc3->hasMethod('setCreatedAt')); } - + + /** + * @group DDC-3304 + */ + public function testGeneratedMutableEmbeddablesClass() + { + $embeddedMetadata = $this->generateTestEmbeddableFixture(); + $metadata = $this->generateIsbnEmbeddableFixture(array('test' => $embeddedMetadata)); + + $isbn = $this->newInstance($metadata); + + $this->assertTrue(class_exists($metadata->name), "Class does not exist."); + $this->assertFalse(method_exists($metadata->name, '__construct'), "EntityGeneratorIsbn::__construct present."); + $this->assertTrue(method_exists($metadata->name, 'getPrefix'), "EntityGeneratorIsbn::getPrefix() missing."); + $this->assertTrue(method_exists($metadata->name, 'setPrefix'), "EntityGeneratorIsbn::setPrefix() missing."); + $this->assertTrue(method_exists($metadata->name, 'getGroupNumber'), "EntityGeneratorIsbn::getGroupNumber() missing."); + $this->assertTrue(method_exists($metadata->name, 'setGroupNumber'), "EntityGeneratorIsbn::setGroupNumber() missing."); + $this->assertTrue(method_exists($metadata->name, 'getPublisherNumber'), "EntityGeneratorIsbn::getPublisherNumber() missing."); + $this->assertTrue(method_exists($metadata->name, 'setPublisherNumber'), "EntityGeneratorIsbn::setPublisherNumber() missing."); + $this->assertTrue(method_exists($metadata->name, 'getTitleNumber'), "EntityGeneratorIsbn::getTitleNumber() missing."); + $this->assertTrue(method_exists($metadata->name, 'setTitleNumber'), "EntityGeneratorIsbn::setTitleNumber() missing."); + $this->assertTrue(method_exists($metadata->name, 'getCheckDigit'), "EntityGeneratorIsbn::getCheckDigit() missing."); + $this->assertTrue(method_exists($metadata->name, 'setCheckDigit'), "EntityGeneratorIsbn::setCheckDigit() missing."); + $this->assertTrue(method_exists($metadata->name, 'getTest'), "EntityGeneratorIsbn::getTest() missing."); + $this->assertTrue(method_exists($metadata->name, 'setTest'), "EntityGeneratorIsbn::setTest() missing."); + + $isbn->setPrefix(978); + $this->assertSame(978, $isbn->getPrefix()); + + $this->newInstance($embeddedMetadata); + $test = new $embeddedMetadata->name(); + + $isbn->setTest($test); + $this->assertSame($test, $isbn->getTest()); + + $reflMethod = new \ReflectionMethod($metadata->name, 'setTest'); + $reflParameters = $reflMethod->getParameters(); + $this->assertEquals($embeddedMetadata->name, $reflParameters[0]->getClass()->name); + } + + /** + * @group DDC-3304 + */ + public function testGeneratedImmutableEmbeddablesClass() + { + $this->_generator->setEmbeddablesImmutable(true); + $embeddedMetadata = $this->generateTestEmbeddableFixture(); + $metadata = $this->generateIsbnEmbeddableFixture(array('test' => $embeddedMetadata)); + + $this->loadEntityClass($embeddedMetadata); + $this->loadEntityClass($metadata); + + $this->assertTrue(class_exists($metadata->name), "Class does not exist."); + $this->assertTrue(method_exists($metadata->name, '__construct'), "EntityGeneratorIsbn::__construct missing."); + $this->assertTrue(method_exists($metadata->name, 'getPrefix'), "EntityGeneratorIsbn::getPrefix() missing."); + $this->assertFalse(method_exists($metadata->name, 'setPrefix'), "EntityGeneratorIsbn::setPrefix() present."); + $this->assertTrue(method_exists($metadata->name, 'getGroupNumber'), "EntityGeneratorIsbn::getGroupNumber() missing."); + $this->assertFalse(method_exists($metadata->name, 'setGroupNumber'), "EntityGeneratorIsbn::setGroupNumber() present."); + $this->assertTrue(method_exists($metadata->name, 'getPublisherNumber'), "EntityGeneratorIsbn::getPublisherNumber() missing."); + $this->assertFalse(method_exists($metadata->name, 'setPublisherNumber'), "EntityGeneratorIsbn::setPublisherNumber() present."); + $this->assertTrue(method_exists($metadata->name, 'getTitleNumber'), "EntityGeneratorIsbn::getTitleNumber() missing."); + $this->assertFalse(method_exists($metadata->name, 'setTitleNumber'), "EntityGeneratorIsbn::setTitleNumber() present."); + $this->assertTrue(method_exists($metadata->name, 'getCheckDigit'), "EntityGeneratorIsbn::getCheckDigit() missing."); + $this->assertFalse(method_exists($metadata->name, 'setCheckDigit'), "EntityGeneratorIsbn::setCheckDigit() present."); + $this->assertTrue(method_exists($metadata->name, 'getTest'), "EntityGeneratorIsbn::getTest() missing."); + $this->assertFalse(method_exists($metadata->name, 'setTest'), "EntityGeneratorIsbn::setTest() present."); + + $test = new $embeddedMetadata->name(1, new \DateTime()); + $isbn = new $metadata->name($test, 978, 3, 12, 732320, 83); + + $reflMethod = new \ReflectionMethod($isbn, '__construct'); + $reflParameters = $reflMethod->getParameters(); + + $this->assertCount(6, $reflParameters); + + $this->assertSame($embeddedMetadata->name, $reflParameters[0]->getClass()->name); + $this->assertSame('test', $reflParameters[0]->getName()); + $this->assertFalse($reflParameters[0]->isOptional()); + + $this->assertSame('prefix', $reflParameters[1]->getName()); + $this->assertFalse($reflParameters[1]->isOptional()); + + $this->assertSame('groupNumber', $reflParameters[2]->getName()); + $this->assertFalse($reflParameters[2]->isOptional()); + + $this->assertSame('publisherNumber', $reflParameters[3]->getName()); + $this->assertFalse($reflParameters[3]->isOptional()); + + $this->assertSame('titleNumber', $reflParameters[4]->getName()); + $this->assertFalse($reflParameters[4]->isOptional()); + + $this->assertSame('checkDigit', $reflParameters[5]->getName()); + $this->assertFalse($reflParameters[5]->isOptional()); + + $reflMethod = new \ReflectionMethod($test, '__construct'); + $reflParameters = $reflMethod->getParameters(); + + $this->assertCount(4, $reflParameters); + + $this->assertSame('field1', $reflParameters[0]->getName()); + $this->assertFalse($reflParameters[0]->isOptional()); + + $this->assertSame('DateTime', $reflParameters[1]->getClass()->name); + $this->assertSame('field3', $reflParameters[1]->getName()); + $this->assertFalse($reflParameters[1]->isOptional()); + + $this->assertSame('field2', $reflParameters[2]->getName()); + $this->assertTrue($reflParameters[2]->isOptional()); + + $this->assertSame('DateTime', $reflParameters[3]->getClass()->name); + $this->assertSame('field4', $reflParameters[3]->getName()); + $this->assertTrue($reflParameters[3]->isOptional()); + } + /** * @return array */