diff --git a/doctrine-mapping.xsd b/doctrine-mapping.xsd index cd3c8e751..a25dd56f6 100644 --- a/doctrine-mapping.xsd +++ b/doctrine-mapping.xsd @@ -162,6 +162,7 @@ + @@ -274,6 +275,7 @@ + @@ -294,6 +296,13 @@ + + + + + + + diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index 54ce6ff34..a74ddac8b 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -521,6 +521,14 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface case ClassMetadata::GENERATOR_TYPE_TABLE: throw new ORMException("TableGenerator not yet implemented."); break; + case ClassMetadata::GENERATOR_TYPE_CUSTOM: + $definition = $class->customGeneratorDefinition; + if (!class_exists($definition['class'])) { + throw new ORMException("Can't instantiate custom generator : " . + $definition['class']); + } + $class->setIdGenerator(new $definition['class']); + break; default: throw new ORMException("Unknown generator type: " . $class->generatorType); } diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 826f986c3..c942621f6 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -99,6 +99,10 @@ class ClassMetadataInfo implements ClassMetadata * portability is currently not guaranteed. */ const GENERATOR_TYPE_UUID = 6; + /** + * CUSTOM means that customer will use own ID generator that supposedly work + */ + const GENERATOR_TYPE_CUSTOM = 7; /** * DEFERRED_IMPLICIT means that changes of entities are calculated at commit-time * by doing a property-by-property comparison with the original data. This will @@ -181,6 +185,22 @@ class ClassMetadataInfo implements ClassMetadata */ public $rootEntityName; + /** + * READ-ONLY: The definition of custom generator. Only used for CUSTOM + * generator type + * + * The definition has the following structure: + * + * array( + * 'class' => 'ClassName', + * ) + * + * + * @var array + * @todo Merge with tableGeneratorDefinition into generic generatorDefinition + */ + public $customGeneratorDefinition; + /** * The name of the custom repository class used for the entity class. * (Optional). @@ -2146,6 +2166,15 @@ class ClassMetadataInfo implements ClassMetadata $this->idGenerator = $generator; } + /** + * Sets definition + * @param array $definition + */ + public function setCustomGeneratorDefinition(array $definition) + { + $this->customGeneratorDefinition = $definition; + } + /** * Sets the definition of the sequence ID generator for this class. * diff --git a/lib/Doctrine/ORM/Mapping/CustomIdGenerator.php b/lib/Doctrine/ORM/Mapping/CustomIdGenerator.php new file mode 100644 index 000000000..4739c3c81 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/CustomIdGenerator.php @@ -0,0 +1,30 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * @Annotation + * @Target("PROPERTY") + */ +final class CustomIdGenerator implements Annotation +{ + /** @var string */ + public $class; +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index de6a4939f..94a624c0a 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -343,6 +343,10 @@ class AnnotationDriver implements Driver )); } else if ($tblGeneratorAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\TableGenerator')) { throw MappingException::tableIdGeneratorNotImplemented($className); + } else if ($customGeneratorAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\CustomIdGenerator')) { + $metadata->setCustomGeneratorDefinition(array( + 'class' => $customGeneratorAnnot->class + )); } } else if ($oneToOneAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OneToOne')) { if ($idAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Id')) { diff --git a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php index 290fc6529..bd1632b2c 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php +++ b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php @@ -39,6 +39,7 @@ require_once __DIR__.'/../UniqueConstraint.php'; require_once __DIR__.'/../Index.php'; require_once __DIR__.'/../JoinTable.php'; require_once __DIR__.'/../SequenceGenerator.php'; +require_once __DIR__.'/../CustomIdGenerator.php'; require_once __DIR__.'/../ChangeTrackingPolicy.php'; require_once __DIR__.'/../OrderBy.php'; require_once __DIR__.'/../NamedQueries.php'; diff --git a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php index 4a07974d9..c60a8c5ae 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php @@ -259,6 +259,11 @@ class XmlDriver extends AbstractFileDriver 'allocationSize' => (string)$seqGenerator['allocation-size'], 'initialValue' => (string)$seqGenerator['initial-value'] )); + } else if (isset($idElement->{'custom-id-generator'})) { + $customGenerator = $idElement->{'custom-id-generator'}; + $metadata->setCustomGeneratorDefinition(array( + 'class' => (string) $customGenerator['class'] + )); } else if (isset($idElement->{'table-generator'})) { throw MappingException::tableIdGeneratorNotImplemented($className); } diff --git a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php index b66f29407..7db57dbad 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php @@ -200,6 +200,11 @@ class YamlDriver extends AbstractFileDriver // Check for SequenceGenerator/TableGenerator definition if (isset($idElement['sequenceGenerator'])) { $metadata->setSequenceGeneratorDefinition($idElement['sequenceGenerator']); + } else if (isset($idElement['customIdGenerator'])) { + $customGenerator = $idElement['customIdGenerator']; + $metadata->setCustomGeneratorDefinition(array( + 'class' => (string) $customGenerator['class'] + )); } else if (isset($idElement['tableGenerator'])) { throw MappingException::tableIdGeneratorNotImplemented($className); } diff --git a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php index 4b2da0a70..b15481bdb 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php @@ -105,6 +105,18 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase ); } + public function testEntityCustomGenerator() + { + $class = $this->createClassMetadata('Doctrine\Tests\ORM\Mapping\Animal'); + + $this->assertEquals(ClassMetadata::GENERATOR_TYPE_CUSTOM, + $class->generatorType, "Generator Type"); + $this->assertEquals( + array("class" => "stdClass"), + $class->customGeneratorDefinition, + "Custom Generator Definition"); + } + /** * @depends testEntityTableNameAndInheritance @@ -650,13 +662,15 @@ class User abstract class Animal { /** - * @Id @Column(type="string") @GeneratedValue + * @Id @Column(type="string") @GeneratedValue(strategy="CUSTOM") + * @CustomIdGenerator(class="stdClass") */ public $id; public static function loadMetadata(ClassMetadataInfo $metadata) { - + $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_CUSTOM); + $metadata->setCustomGeneratorDefinition(array("class" => "stdClass")); } } diff --git a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php index cd1e4f3ff..04f413c95 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php @@ -3,7 +3,6 @@ namespace Doctrine\Tests\ORM\Mapping; use Doctrine\Tests\Mocks\MetadataDriverMock; -use Doctrine\Tests\Mocks\DatabasePlatformMock; use Doctrine\Tests\Mocks\EntityManagerMock; use Doctrine\Tests\Mocks\ConnectionMock; use Doctrine\Tests\Mocks\DriverMock; @@ -25,28 +24,12 @@ class ClassMetadataFactoryTest extends \Doctrine\Tests\OrmTestCase $mockPlatform->setPrefersSequences(true); $mockPlatform->setPrefersIdentityColumns(false); - // Self-made metadata - $cm1 = new ClassMetadata('Doctrine\Tests\ORM\Mapping\TestEntity1'); - $cm1->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); - $cm1->setPrimaryTable(array('name' => '`group`')); - // Add a mapped field - $cm1->mapField(array('fieldName' => 'name', 'type' => 'varchar')); - // Add a mapped field - $cm1->mapField(array('fieldName' => 'id', 'type' => 'integer', 'id' => true)); - // and a mapped association - $cm1->mapOneToOne(array('fieldName' => 'other', 'targetEntity' => 'TestEntity1', 'mappedBy' => 'this')); - // and an association on the owning side - $joinColumns = array( - array('name' => 'other_id', 'referencedColumnName' => 'id') - ); - $cm1->mapOneToOne(array('fieldName' => 'association', 'targetEntity' => 'TestEntity1', 'joinColumns' => $joinColumns)); - // and an id generator type - $cm1->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_AUTO); + $cm1 = $this->_createValidClassMetadata(); // SUT $cmf = new \Doctrine\ORM\Mapping\ClassMetadataFactory(); $cmf->setEntityManager($entityManager); - $cmf->setMetadataFor('Doctrine\Tests\ORM\Mapping\TestEntity1', $cm1); + $cmf->setMetadataFor($cm1->name, $cm1); // Prechecks $this->assertEquals(array(), $cm1->parentClasses); @@ -57,7 +40,7 @@ class ClassMetadataFactoryTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals('group', $cm1->table['name']); // Go - $cmMap1 = $cmf->getMetadataFor('Doctrine\Tests\ORM\Mapping\TestEntity1'); + $cmMap1 = $cmf->getMetadataFor($cm1->name); $this->assertSame($cm1, $cmMap1); $this->assertEquals('group', $cmMap1->table['name']); @@ -66,6 +49,46 @@ class ClassMetadataFactoryTest extends \Doctrine\Tests\OrmTestCase $this->assertTrue($cmMap1->hasField('name')); } + public function testGetMetadataFor_ReturnsLoadedCustomIdGenerator() + { + $cm1 = $this->_createValidClassMetadata(); + $cm1->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_CUSTOM); + $cm1->customGeneratorDefinition = array( + "class" => "Doctrine\Tests\ORM\Mapping\CustomIdGenerator"); + $cmf = $this->_createTestFactory(); + $cmf->setMetadataForClass($cm1->name, $cm1); + + $actual = $cmf->getMetadataFor($cm1->name); + + $this->assertEquals(ClassMetadata::GENERATOR_TYPE_CUSTOM, + $actual->generatorType); + $this->assertInstanceOf("Doctrine\Tests\ORM\Mapping\CustomIdGenerator", + $actual->idGenerator); + } + + public function testGetMetadataFor_ThrowsExceptionOnUnknownCustomGeneratorClass() + { + $cm1 = $this->_createValidClassMetadata(); + $cm1->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_CUSTOM); + $cm1->customGeneratorDefinition = array("class" => "NotExistingGenerator"); + $cmf = $this->_createTestFactory(); + $cmf->setMetadataForClass($cm1->name, $cm1); + $this->setExpectedException("Doctrine\ORM\ORMException"); + + $actual = $cmf->getMetadataFor($cm1->name); + } + + public function testGetMetadataFor_ThrowsExceptionOnMissingCustomGeneratorDefinition() + { + $cm1 = $this->_createValidClassMetadata(); + $cm1->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_CUSTOM); + $cmf = $this->_createTestFactory(); + $cmf->setMetadataForClass($cm1->name, $cm1); + $this->setExpectedException("Doctrine\ORM\ORMException"); + + $actual = $cmf->getMetadataFor($cm1->name); + } + public function testHasGetMetadata_NamespaceSeperatorIsNotNormalized() { require_once __DIR__."/../../Models/Global/GlobalNamespaceModel.php"; @@ -143,6 +166,44 @@ class ClassMetadataFactoryTest extends \Doctrine\Tests\OrmTestCase return EntityManagerMock::create($conn, $config, $eventManager); } + + /** + * @return ClassMetadataFactoryTestSubject + */ + protected function _createTestFactory() + { + $mockDriver = new MetadataDriverMock(); + $entityManager = $this->_createEntityManager($mockDriver); + $cmf = new ClassMetadataFactoryTestSubject(); + $cmf->setEntityManager($entityManager); + return $cmf; + } + + /** + * @param string $class + * @return ClassMetadata + */ + protected function _createValidClassMetadata() + { + // Self-made metadata + $cm1 = new ClassMetadata('Doctrine\Tests\ORM\Mapping\TestEntity1'); + $cm1->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); + $cm1->setPrimaryTable(array('name' => '`group`')); + // Add a mapped field + $cm1->mapField(array('fieldName' => 'name', 'type' => 'varchar')); + // Add a mapped field + $cm1->mapField(array('fieldName' => 'id', 'type' => 'integer', 'id' => true)); + // and a mapped association + $cm1->mapOneToOne(array('fieldName' => 'other', 'targetEntity' => 'TestEntity1', 'mappedBy' => 'this')); + // and an association on the owning side + $joinColumns = array( + array('name' => 'other_id', 'referencedColumnName' => 'id') + ); + $cm1->mapOneToOne(array('fieldName' => 'association', 'targetEntity' => 'TestEntity1', 'joinColumns' => $joinColumns)); + // and an id generator type + $cm1->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_AUTO); + return $cm1; + } } /* Test subject class with overriden factory method for mocking purposes */ @@ -179,3 +240,10 @@ class TestEntity1 private $other; private $association; } + +class CustomIdGenerator extends \Doctrine\ORM\Id\AbstractIdGenerator +{ + public function generate(\Doctrine\ORM\EntityManager $em, $entity) + { + } +} diff --git a/tests/Doctrine/Tests/ORM/Mapping/PHPMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/PHPMappingDriverTest.php index b346973cf..e14828e00 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/PHPMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/PHPMappingDriverTest.php @@ -14,17 +14,14 @@ class PHPMappingDriverTest extends AbstractMappingDriverTest { $path = __DIR__ . DIRECTORY_SEPARATOR . 'php'; - /* - // Convert YAML mapping information to PHP - // Uncomment this code if the YAML changes and you want to update the PHP code + // Convert Annotation mapping information to PHP + // Uncomment this code if annotations changed and you want to update the PHP code // for the same mapping information - $cme = new ClassMetadataExporter(); - $cme->addMappingSource(__DIR__ . DIRECTORY_SEPARATOR . 'yaml'); - - $exporter = $cme->getExporter('php', $path); - $exporter->setMetadatas($cme->getMetadatas()); - $exporter->export(); - */ +// $meta = new \Doctrine\ORM\Mapping\ClassMetadataInfo("Doctrine\Tests\ORM\Mapping\Animal"); +// $driver = $this->createAnnotationDriver(); +// $driver->loadMetadataForClass("Doctrine\Tests\ORM\Mapping\Animal", $meta); +// $exporter = $cme->getExporter('php', $path); +// echo $exporter->exportClassMetadata($meta); return new PHPDriver($path); } diff --git a/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.Animal.php b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.Animal.php new file mode 100644 index 000000000..005178eed --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.ORM.Mapping.Animal.php @@ -0,0 +1,30 @@ +setInheritanceType(ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_TABLE); +$metadata->setDiscriminatorColumn(array( + 'name' => 'dtype', + 'type' => 'string', + 'length' => 255, + 'fieldName' => 'dtype', + )); +$metadata->setDiscriminatorMap(array( + 'cat' => 'Doctrine\\Tests\\ORM\\Mapping\\Cat', + 'dog' => 'Doctrine\\Tests\\ORM\\Mapping\\Dog', + )); +$metadata->setChangeTrackingPolicy(ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT); +$metadata->mapField(array( + 'fieldName' => 'id', + 'type' => 'string', + 'length' => NULL, + 'precision' => 0, + 'scale' => 0, + 'nullable' => false, + 'unique' => false, + 'id' => true, + 'columnName' => 'id', + )); +$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_CUSTOM); +$metadata->setCustomGeneratorDefinition(array("class" => "stdClass")); diff --git a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.Animal.dcm.xml b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.Animal.dcm.xml index 6c2a2356f..6981d0ba6 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.Animal.dcm.xml +++ b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.ORM.Mapping.Animal.dcm.xml @@ -8,5 +8,9 @@ + + + + \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.Animal.dcm.yml b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.Animal.dcm.yml index cd6ec292c..8fdfe3076 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.Animal.dcm.yml +++ b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.ORM.Mapping.Animal.dcm.yml @@ -3,4 +3,11 @@ Doctrine\Tests\ORM\Mapping\Animal: inheritanceType: SINGLE_TABLE discriminatorMap: cat: Cat - dog: Dog \ No newline at end of file + dog: Dog + id: + id: + type: integer + generator: + strategy: CUSTOM + customIdGenerator: + class: stdClass \ No newline at end of file diff --git a/tests/Doctrine/Tests/OrmTestCase.php b/tests/Doctrine/Tests/OrmTestCase.php index 23099ac77..b9f7b6be5 100644 --- a/tests/Doctrine/Tests/OrmTestCase.php +++ b/tests/Doctrine/Tests/OrmTestCase.php @@ -17,7 +17,7 @@ abstract class OrmTestCase extends DoctrineTestCase /** * @param array $paths - * @return \Doctrine\Common\Annotations\AnnotationReader + * @return \Doctrine\ORM\Mapping\Driver\AnnotationDriver */ protected function createAnnotationDriver($paths = array(), $alias = null) {