Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Already on GitHub? Sign in to your account

Support custom id generators #206

Merged
merged 22 commits into from Mar 22, 2012
Commits
Jump to file or symbol
Failed to load files and symbols.
+245 −34
Split
View
@@ -161,6 +161,7 @@
<xs:enumeration value="SEQUENCE"/>
<xs:enumeration value="IDENTITY"/>
<xs:enumeration value="AUTO"/>
+ <xs:enumeration value="CUSTOM" />
</xs:restriction>
</xs:simpleType>
@@ -273,6 +274,7 @@
<xs:sequence>
<xs:element name="generator" type="orm:generator" minOccurs="0" />
<xs:element name="sequence-generator" type="orm:sequence-generator" minOccurs="0" maxOccurs="1" />
+ <xs:element name="custom-id-generator" type="orm:custom-id-generator" minOccurs="0" maxOccurs="1" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
@@ -293,6 +295,13 @@
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
+ <xs:complexType name="custom-id-generator">
+ <xs:sequence>
+ <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
+ </xs:sequence>
+ <xs:attribute name="class" type="xs:NMTOKEN" use="required" />
+ </xs:complexType>
+
<xs:complexType name="inverse-join-columns">
<xs:sequence>
<xs:element name="join-column" type="orm:join-column" minOccurs="1" maxOccurs="unbounded" />
@@ -518,6 +518,14 @@ private function completeIdGeneratorMapping(ClassMetadataInfo $class)
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);
}
@@ -95,6 +95,10 @@ class ClassMetadataInfo implements ClassMetadata
*/
const GENERATOR_TYPE_NONE = 5;
/**
+ * CUSTOM means that customer will use own ID generator that supposedly work
+ */
+ const GENERATOR_TYPE_CUSTOM = 6;
+ /**
* 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
* be done for all entities that are in MANAGED state at commit-time.
@@ -177,6 +181,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:
+ * <code>
+ * array(
+ * 'class' => 'ClassName',
+ * )
+ * </code>
+ *
+ * @var array
+ * @todo Merge with tableGeneratorDefinition into generic generatorDefinition
+ */
+ public $customGeneratorDefinition;
+
+ /**
* The name of the custom repository class used for the entity class.
* (Optional).
*
@@ -2130,6 +2150,15 @@ public function setIdGenerator($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.
*
* The definition must have the following structure:
@@ -0,0 +1,30 @@
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Mapping;
+
+/**
+ * @Annotation
+ * @Target("PROPERTY")
+ */
+final class CustomIdGenerator implements Annotation
+{
+ /** @var string */
+ public $class;
+}
@@ -336,6 +336,10 @@ public function loadMetadataForClass($className, ClassMetadataInfo $metadata)
));
} 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')) {
@@ -39,6 +39,7 @@
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';
@@ -259,6 +259,11 @@ public function loadMetadataForClass($className, ClassMetadataInfo $metadata)
'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);
}
@@ -200,6 +200,11 @@ public function loadMetadataForClass($className, ClassMetadataInfo $metadata)
// 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);
}
@@ -105,6 +105,18 @@ public function testEntitySequence($class)
);
}
+ 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 @@ public static function loadMetadata(ClassMetadataInfo $metadata)
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"));
}
}
@@ -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 @@ public function testGetMetadataForSingleClass()
$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 @@ public function testGetMetadataForSingleClass()
$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 @@ public function testGetMetadataForSingleClass()
$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 @@ protected function _createEntityManager($metadataDriver)
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)
+ {
+ }
+}
@@ -14,17 +14,14 @@ protected function _loadDriver()
{
$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);
@stof

stof Nov 29, 2011

Member

what is the point of replacing some commented code by some other commented code ?

@velovint

velovint Nov 29, 2011

Contributor

I need to generate PHP mapping from existing one and original code didn't work as interface of ClassMetadataExported changed. So I placed a code that can be used to convert mapping

return new PHPDriver($path);
}
Oops, something went wrong.