1
0
mirror of synced 2024-12-13 22:56:04 +03:00

DDC-1006, DDC-953 - Fix EntityGenerator creating empty classes

This commit is contained in:
Benjamin Eberlei 2011-02-02 23:21:42 +01:00
parent 277e0aee8c
commit f9c1464879
2 changed files with 222 additions and 103 deletions

View File

@ -47,14 +47,18 @@ use Doctrine\ORM\Mapping\ClassMetadataInfo,
*/
class EntityGenerator
{
/**
* @var bool
*/
private $_backupExisting = true;
/** The extension to use for written php files */
private $_extension = '.php';
/** Whether or not the current ClassMetadataInfo instance is new or old */
private $_isNew = true;
/** If isNew is false then this variable contains instance of ReflectionClass for current entity */
private $_reflection;
private $_staticReflection = array();
/** Number of spaces to use for indention in generated code */
private $_numSpaces = 4;
@ -68,6 +72,11 @@ class EntityGenerator
/** Whether or not to generation annotations */
private $_generateAnnotations = false;
/**
* @var string
*/
private $_annotationsPrefix = '';
/** Whether or not to generated sub methods */
private $_generateEntityStubMethods = false;
@ -160,16 +169,21 @@ public function <methodName>()
mkdir($dir, 0777, true);
}
$this->_isNew = ! file_exists($path);
$this->_isNew = !file_exists($path) || (file_exists($path) && $this->_regenerateEntityIfExists);
if ( ! $this->_isNew) {
require_once $path;
$this->_parseTokensInEntityFile($path);
}
$this->_reflection = new \ReflectionClass($metadata->name);
if ($this->_backupExisting && file_exists($path)) {
$backupPath = dirname($path) . DIRECTORY_SEPARATOR . "~" . basename($path);
if (!copy($path, $backupPath)) {
throw new \RuntimeException("Attempt to backup overwritten entitiy file but copy operation failed.");
}
}
// If entity doesn't exist or we're re-generating the entities entirely
if ($this->_isNew || ( ! $this->_isNew && $this->_regenerateEntityIfExists)) {
if ($this->_isNew) {
file_put_contents($path, $this->generateEntityClass($metadata));
// If entity exists and we're allowed to update the entity class
} else if ( ! $this->_isNew && $this->_updateEntityIfExists) {
@ -265,6 +279,16 @@ public function <methodName>()
$this->_generateAnnotations = $bool;
}
/**
* Set an annotation prefix.
*
* @param string $prefix
*/
public function setAnnotationPrefix($prefix)
{
$this->_annotationsPrefix = $prefix;
}
/**
* Set whether or not to try and update the entity if it already exists
*
@ -298,6 +322,14 @@ public function <methodName>()
$this->_generateEntityStubMethods = $bool;
}
/**
* Should an existing entity be backed up if it already exists?
*/
public function setBackupExisting($bool)
{
$this->_backupExisting = $bool;
}
private function _generateEntityNamespace(ClassMetadataInfo $metadata)
{
if ($this->_hasNamespace($metadata)) {
@ -339,14 +371,50 @@ public function <methodName>()
return implode("\n", $code);
}
/**
* @todo this won't work if there is a namespace in brackets and a class outside of it.
* @param string $path
*/
private function _parseTokensInEntityFile($path)
{
$tokens = token_get_all(file_get_contents($path));
$lastSeenNamespace = "";
$lastSeenClass = false;
for ($i = 0; $i < count($tokens); $i++) {
$token = $tokens[$i];
if ($token[0] == T_NAMESPACE) {
$lastSeenNamespace = $tokens[$i+2][1] . "\\";
} else if ($token[0] == T_CLASS) {
$lastSeenClass = $lastSeenNamespace . $tokens[$i+2][1];
$this->_staticReflection[$lastSeenClass]['properties'] = array();
$this->_staticReflection[$lastSeenClass]['methods'] = array();
} else if ($token[0] == T_FUNCTION) {
if ($tokens[$i+2][0] == T_STRING) {
$this->_staticReflection[$lastSeenClass]['methods'][] = $tokens[$i+2][1];
} else if ($tokens[$i+2][0] == T_AMPERSAND && $tokens[$i+3][0] == T_STRING) {
$this->_staticReflection[$lastSeenClass]['methods'][] = $tokens[$i+3][1];
}
} else if (in_array($token[0], array(T_VAR, T_PUBLIC, T_PRIVATE, T_PROTECTED)) && $tokens[$i+2][0] != T_FUNCTION) {
$this->_staticReflection[$lastSeenClass]['properties'][] = substr($tokens[$i+2][1], 1);
}
}
}
private function _hasProperty($property, ClassMetadataInfo $metadata)
{
return ($this->_isNew) ? false : $this->_reflection->hasProperty($property);
return (
isset($this->_staticReflection[$metadata->name]) &&
in_array($property, $this->_staticReflection[$metadata->name]['properties'])
);
}
private function _hasMethod($method, ClassMetadataInfo $metadata)
{
return ($this->_isNew) ? false : $this->_reflection->hasMethod($method);
return (
isset($this->_staticReflection[$metadata->name]) &&
in_array($method, $this->_staticReflection[$metadata->name]['methods'])
);
}
private function _hasNamespace(ClassMetadataInfo $metadata)
@ -405,9 +473,9 @@ public function <methodName>()
}
if ($metadata->isMappedSuperclass) {
$lines[] = ' * @MappedSupperClass';
$lines[] = ' * @' . $this->_annotationsPrefix . 'MappedSupperClass';
} else {
$lines[] = ' * @Entity';
$lines[] = ' * @' . $this->_annotationsPrefix . 'Entity';
}
if ($metadata->customRepositoryClassName) {
@ -415,7 +483,7 @@ public function <methodName>()
}
if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) {
$lines[] = ' * @HasLifecycleCallbacks';
$lines[] = ' * @' . $this->_annotationsPrefix . 'HasLifecycleCallbacks';
}
}
@ -431,17 +499,13 @@ public function <methodName>()
$table[] = 'name="' . $metadata->table['name'] . '"';
}
if (isset($metadata->table['schema'])) {
$table[] = 'schema="' . $metadata->table['schema'] . '"';
}
return '@Table(' . implode(', ', $table) . ')';
return '@' . $this->_annotationsPrefix . 'Table(' . implode(', ', $table) . ')';
}
private function _generateInheritanceAnnotation($metadata)
{
if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
return '@InheritanceType("'.$this->_getInheritanceTypeString($metadata->inheritanceType).'")';
return '@' . $this->_annotationsPrefix . 'InheritanceType("'.$this->_getInheritanceTypeString($metadata->inheritanceType).'")';
}
}
@ -453,7 +517,7 @@ public function <methodName>()
. '", type="' . $discrColumn['type']
. '", length=' . $discrColumn['length'];
return '@DiscriminatorColumn(' . $columnDefinition . ')';
return '@' . $this->_annotationsPrefix . 'DiscriminatorColumn(' . $columnDefinition . ')';
}
}
@ -466,7 +530,7 @@ public function <methodName>()
$inheritanceClassMap[] .= '"' . $type . '" = "' . $class . '"';
}
return '@DiscriminatorMap({' . implode(', ', $inheritanceClassMap) . '})';
return '@' . $this->_annotationsPrefix . 'DiscriminatorMap({' . implode(', ', $inheritanceClassMap) . '})';
}
}
@ -494,23 +558,7 @@ public function <methodName>()
if ($code = $this->_generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
$methods[] = $code;
}
} else if ($associationMapping['type'] == ClassMetadataInfo::ONE_TO_MANY) {
if ($associationMapping['isOwningSide']) {
if ($code = $this->_generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
$methods[] = $code;
}
if ($code = $this->_generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
$methods[] = $code;
}
} else {
if ($code = $this->_generateEntityStubMethod($metadata, 'add', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
$methods[] = $code;
}
if ($code = $this->_generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], 'Doctrine\Common\Collections\Collection')) {
$methods[] = $code;
}
}
} else if ($associationMapping['type'] == ClassMetadataInfo::MANY_TO_MANY) {
} else if ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) {
if ($code = $this->_generateEntityStubMethod($metadata, 'add', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
$methods[] = $code;
}
@ -536,8 +584,10 @@ public function <methodName>()
}
}
return implode('', $methods);
return implode("\n\n", $methods);
}
return "";
}
private function _generateEntityAssociationMappingProperties(ClassMetadataInfo $metadata)
@ -615,7 +665,7 @@ public function <methodName>()
}
$replacements = array(
'<name>' => $name,
'<name>' => $this->_annotationsPrefix . $name,
'<methodName>' => $methodName,
);
@ -660,7 +710,7 @@ public function <methodName>()
$joinColumnAnnot[] = 'columnDefinition="' . $joinColumn['columnDefinition'] . '"';
}
return '@JoinColumn(' . implode(', ', $joinColumnAnnot) . ')';
return '@' . $this->_annotationsPrefix . 'JoinColumn(' . implode(', ', $joinColumnAnnot) . ')';
}
private function _generateAssociationMappingPropertyDocBlock(array $associationMapping, ClassMetadataInfo $metadata)
@ -717,10 +767,10 @@ public function <methodName>()
$typeOptions[] = 'orphanRemoval=' . ($associationMapping['orphanRemoval'] ? 'true' : 'false');
}
$lines[] = $this->_spaces . ' * @' . $type . '(' . implode(', ', $typeOptions) . ')';
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . '' . $type . '(' . implode(', ', $typeOptions) . ')';
if (isset($associationMapping['joinColumns']) && $associationMapping['joinColumns']) {
$lines[] = $this->_spaces . ' * @JoinColumns({';
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'JoinColumns({';
$joinColumnsLines = array();
@ -742,7 +792,7 @@ public function <methodName>()
$joinTable[] = 'schema="' . $associationMapping['joinTable']['schema'] . '"';
}
$lines[] = $this->_spaces . ' * @JoinTable(' . implode(', ', $joinTable) . ',';
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'JoinTable(' . implode(', ', $joinTable) . ',';
$lines[] = $this->_spaces . ' * joinColumns={';
foreach ($associationMapping['joinTable']['joinColumns'] as $joinColumn) {
@ -761,7 +811,7 @@ public function <methodName>()
}
if (isset($associationMapping['orderBy'])) {
$lines[] = $this->_spaces . ' * @OrderBy({';
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'OrderBy({';
foreach ($associationMapping['orderBy'] as $name => $direction) {
$lines[] = $this->_spaces . ' * "' . $name . '"="' . $direction . '",';
@ -815,31 +865,17 @@ public function <methodName>()
$column[] = 'columnDefinition="' . $fieldMapping['columnDefinition'] . '"';
}
if (isset($fieldMapping['options'])) {
$options = array();
foreach ($fieldMapping['options'] as $key => $value) {
$value = var_export($value, true);
$value = str_replace("'", '"', $value);
$options[] = ! is_numeric($key) ? $key . '=' . $value:$value;
}
if ($options) {
$column[] = 'options={' . implode(', ', $options) . '}';
}
}
if (isset($fieldMapping['unique'])) {
$column[] = 'unique=' . var_export($fieldMapping['unique'], true);
}
$lines[] = $this->_spaces . ' * @Column(' . implode(', ', $column) . ')';
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'Column(' . implode(', ', $column) . ')';
if (isset($fieldMapping['id']) && $fieldMapping['id']) {
$lines[] = $this->_spaces . ' * @Id';
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'Id';
if ($generatorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) {
$lines[] = $this->_spaces.' * @GeneratedValue(strategy="' . $generatorType . '")';
$lines[] = $this->_spaces.' * @' . $this->_annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")';
}
if ($metadata->sequenceGeneratorDefinition) {
@ -857,12 +893,12 @@ public function <methodName>()
$sequenceGenerator[] = 'initialValue="' . $metadata->sequenceGeneratorDefinition['initialValue'] . '"';
}
$lines[] = $this->_spaces . ' * @SequenceGenerator(' . implode(', ', $sequenceGenerator) . ')';
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'SequenceGenerator(' . implode(', ', $sequenceGenerator) . ')';
}
}
if (isset($fieldMapping['version']) && $fieldMapping['version']) {
$lines[] = $this->_spaces . ' * @Version';
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'Version';
}
}

View File

@ -12,20 +12,39 @@ require_once __DIR__ . '/../../TestInit.php';
class EntityGeneratorTest extends \Doctrine\Tests\OrmTestCase
{
private $_generator;
private $_tmpDir;
private $_namespace;
public function setUp()
{
$this->_namespace = uniqid("doctrine_");
$this->_tmpDir = \sys_get_temp_dir();
\mkdir($this->_tmpDir . \DIRECTORY_SEPARATOR . $this->_namespace);
$this->_generator = new EntityGenerator();
$this->_generator->setGenerateAnnotations(true);
$this->_generator->setGenerateStubMethods(true);
$this->_generator->setRegenerateEntityIfExists(false);
$this->_generator->setUpdateEntityIfExists(true);
$this->_generator->setClassToExtend('stdClass');
}
public function testWriteEntityClass()
public function tearDown()
{
$metadata = new ClassMetadataInfo('EntityGeneratorBook');
$ri = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->_tmpDir . '/' . $this->_namespace));
foreach ($ri AS $file) {
/* @var $file \SplFileInfo */
if ($file->isFile()) {
\unlink($file->getPathname());
}
}
rmdir($this->_tmpDir . '/' . $this->_namespace);
}
public function generateBookEntityFixture()
{
$metadata = new ClassMetadataInfo($this->_namespace . '\EntityGeneratorBook');
$metadata->namespace = $this->_namespace;
$metadata->customRepositoryClassName = $this->_namespace . '\EntityGeneratorBookRepository';
$metadata->table['name'] = 'book';
$metadata->mapField(array('fieldName' => 'name', 'type' => 'string'));
$metadata->mapField(array('fieldName' => 'status', 'type' => 'string', 'default' => 'published'));
@ -43,32 +62,43 @@ class EntityGeneratorTest extends \Doctrine\Tests\OrmTestCase
'inverseJoinColumns' => array(array('name' => 'comment_id', 'referencedColumnName' => 'id')),
),
));
$metadata->addLifecycleCallback('loading', 'postLoad');
$metadata->addLifecycleCallback('willBeRemoved', 'preRemove');
$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO);
$this->_generator->writeEntityClass($metadata, __DIR__);
$path = __DIR__ . '/EntityGeneratorBook.php';
$this->assertTrue(file_exists($path));
require_once $path;
$this->_generator->writeEntityClass($metadata, $this->_tmpDir);
return $metadata;
}
/**
* @depends testWriteEntityClass
* @param ClassMetadata $metadata
* @param ClassMetadataInfo $metadata
* @return EntityGeneratorBook
*/
public function testGeneratedEntityClass($metadata)
public function newInstance($metadata)
{
$this->assertTrue(method_exists('\EntityGeneratorBook', 'getId'));
$this->assertTrue(method_exists('\EntityGeneratorBook', 'setName'));
$this->assertTrue(method_exists('\EntityGeneratorBook', 'getName'));
$this->assertTrue(method_exists('\EntityGeneratorBook', 'setAuthor'));
$this->assertTrue(method_exists('\EntityGeneratorBook', 'getAuthor'));
$this->assertTrue(method_exists('\EntityGeneratorBook', 'getComments'));
$this->assertTrue(method_exists('\EntityGeneratorBook', 'addComments'));
$path = $this->_tmpDir . '/'. $this->_namespace . '/EntityGeneratorBook.php';
$this->assertFileExists($path);
require_once $path;
return new $metadata->name;
}
public function testGeneratedEntityClass()
{
$metadata = $this->generateBookEntityFixture();
$book = $this->newInstance($metadata);
$this->assertTrue(class_exists($metadata->name), "Class does not exist.");
$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', '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', 'addComments'), "EntityGeneratorBook::addComments() missing.");
$book = new \EntityGeneratorBook();
$this->assertEquals('published', $book->getStatus());
$book->setName('Jonathan H. Wage');
@ -81,39 +111,92 @@ class EntityGeneratorTest extends \Doctrine\Tests\OrmTestCase
$comment = new EntityGeneratorComment();
$book->addComments($comment);
$this->assertEquals(array($comment), $book->getComments());
return $metadata;
}
/**
* @depends testGeneratedEntityClass
* @param ClassMetadata $metadata
*/
public function testEntityUpdatingWorks($metadata)
public function testEntityUpdatingWorks()
{
$metadata = $this->generateBookEntityFixture();
$metadata->mapField(array('fieldName' => 'test', 'type' => 'string'));
$this->_generator->writeEntityClass($metadata, __DIR__);
$code = file_get_contents(__DIR__ . '/EntityGeneratorBook.php');
$this->_generator->writeEntityClass($metadata, $this->_tmpDir);
$this->assertTrue(strstr($code, 'private $test;') !== false);
$this->assertTrue(strstr($code, 'private $test;') !== false);
$this->assertTrue(strstr($code, 'public function getTest(') !== false);
$this->assertTrue(strstr($code, 'public function setTest(') !== false);
$this->assertFileExists($this->_tmpDir . "/" . $this->_namespace . "/~EntityGeneratorBook.php");
return $metadata;
$book = $this->newInstance($metadata);
$reflClass = new \ReflectionClass($metadata->name);
$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('test'), "Check for property test failed.");
$this->assertTrue($reflClass->getProperty('test')->isPrivate(), "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.");
$this->assertTrue($reflClass->getMethod('getTest')->isPublic(), "Check for public visibility of method 'getTest' failed.");
}
/**
* @depends testEntityUpdatingWorks
* @param ClassMetadata $metadata
*/
public function testEntityExtendsStdClass($metadata)
public function testEntityExtendsStdClass()
{
$book = new \EntityGeneratorBook();
$this->assertType('stdClass', $book);
$this->_generator->setClassToExtend('stdClass');
$metadata = $this->generateBookEntityFixture();
unlink(__DIR__ . '/EntityGeneratorBook.php');
$book = $this->newInstance($metadata);
$this->assertInstanceOf('stdClass', $book);
}
public function testLifecycleCallbacks()
{
$metadata = $this->generateBookEntityFixture();
$book = $this->newInstance($metadata);
$reflClass = new \ReflectionClass($metadata->name);
$this->assertTrue($reflClass->hasMethod('loading'), "Check for postLoad lifecycle callback.");
$this->assertTrue($reflClass->hasMethod('willBeRemoved'), "Check for preRemove lifecycle callback.");
}
public function testLoadMetadata()
{
$metadata = $this->generateBookEntityFixture();
$book = $this->newInstance($metadata);
$cm = new \Doctrine\ORM\Mapping\ClassMetadata($metadata->name);
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
$reader->setDefaultAnnotationNamespace("Doctrine\\ORM\\Mapping\\");
$driver = new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($reader);
$driver->loadMetadataForClass($cm->name, $cm);
$this->assertEquals($cm->columnNames, $metadata->columnNames);
$this->assertEquals($cm->getTableName(), $metadata->getTableName());
$this->assertEquals($cm->lifecycleCallbacks, $metadata->lifecycleCallbacks);
$this->assertEquals($cm->identifier, $metadata->identifier);
$this->assertEquals($cm->idGenerator, $metadata->idGenerator);
$this->assertEquals($cm->customRepositoryClassName, $metadata->customRepositoryClassName);
}
public function testLoadPrefixedMetadata()
{
$this->_generator->setAnnotationPrefix('orm:');
$metadata = $this->generateBookEntityFixture();
$book = $this->newInstance($metadata);
$cm = new \Doctrine\ORM\Mapping\ClassMetadata($metadata->name);
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
$reader->setAnnotationNamespaceAlias("Doctrine\\ORM\\Mapping\\", "orm");
$driver = new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($reader);
$driver->loadMetadataForClass($cm->name, $cm);
$this->assertEquals($cm->columnNames, $metadata->columnNames);
$this->assertEquals($cm->getTableName(), $metadata->getTableName());
$this->assertEquals($cm->lifecycleCallbacks, $metadata->lifecycleCallbacks);
$this->assertEquals($cm->identifier, $metadata->identifier);
$this->assertEquals($cm->idGenerator, $metadata->idGenerator);
$this->assertEquals($cm->customRepositoryClassName, $metadata->customRepositoryClassName);
}
}