diff --git a/lib/Doctrine/ORM/Tools/EntityGenerator.php b/lib/Doctrine/ORM/Tools/EntityGenerator.php index fafa51f3d..391732dd0 100644 --- a/lib/Doctrine/ORM/Tools/EntityGenerator.php +++ b/lib/Doctrine/ORM/Tools/EntityGenerator.php @@ -604,18 +604,23 @@ public function __construct() */ protected function generateEntityNamespace(ClassMetadataInfo $metadata) { - if ($this->hasNamespace($metadata)) { - return 'namespace ' . $this->getNamespace($metadata) .';'; + if (! $this->hasNamespace($metadata)) { + return ''; } + + return 'namespace ' . $this->getNamespace($metadata) .';'; } + /** + * @return string + */ protected function generateEntityUse() { - if ($this->generateAnnotations) { - return "\n".'use Doctrine\ORM\Mapping as ORM;'."\n"; - } else { - return ""; + if (! $this->generateAnnotations) { + return ''; } + + return "\n".'use Doctrine\ORM\Mapping as ORM;'."\n"; } /** @@ -738,9 +743,7 @@ public function __construct() } foreach ($fieldMappings as $fieldMapping) { - if (isset($fieldMapping['declaredField']) && - isset($metadata->embeddedClasses[$fieldMapping['declaredField']]) - ) { + if (isset($fieldMapping['declaredField'], $metadata->embeddedClasses[$fieldMapping['declaredField']])) { continue; } @@ -911,6 +914,8 @@ public function __construct() * @param ClassMetadataInfo $metadata * * @return array + * + * @throws \ReflectionException */ protected function getTraits(ClassMetadataInfo $metadata) { @@ -918,9 +923,7 @@ public function __construct() return []; } - $reflClass = $metadata->reflClass === null - ? new \ReflectionClass($metadata->name) - : $metadata->reflClass; + $reflClass = $metadata->reflClass ?? new \ReflectionClass($metadata->name); $traits = []; @@ -1010,6 +1013,7 @@ public function __construct() 'generateDiscriminatorColumnAnnotation', 'generateDiscriminatorMapAnnotation', 'generateEntityAnnotation', + 'generateEntityListenerAnnotation', ]; foreach ($methods as $method) { @@ -1113,9 +1117,11 @@ public function __construct() */ protected function generateInheritanceAnnotation(ClassMetadataInfo $metadata) { - if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) { - return '@' . $this->annotationsPrefix . 'InheritanceType("'.$this->getInheritanceTypeString($metadata->inheritanceType).'")'; + if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_NONE) { + return ''; } + + return '@' . $this->annotationsPrefix . 'InheritanceType("'.$this->getInheritanceTypeString($metadata->inheritanceType).'")'; } /** @@ -1125,14 +1131,16 @@ public function __construct() */ protected function generateDiscriminatorColumnAnnotation(ClassMetadataInfo $metadata) { - if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) { - $discrColumn = $metadata->discriminatorColumn; - $columnDefinition = 'name="' . $discrColumn['name'] - . '", type="' . $discrColumn['type'] - . '", length=' . $discrColumn['length']; - - return '@' . $this->annotationsPrefix . 'DiscriminatorColumn(' . $columnDefinition . ')'; + if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_NONE) { + return ''; } + + $discrColumn = $metadata->discriminatorColumn; + $columnDefinition = 'name="' . $discrColumn['name'] + . '", type="' . $discrColumn['type'] + . '", length=' . $discrColumn['length']; + + return '@' . $this->annotationsPrefix . 'DiscriminatorColumn(' . $columnDefinition . ')'; } /** @@ -1142,15 +1150,17 @@ public function __construct() */ protected function generateDiscriminatorMapAnnotation(ClassMetadataInfo $metadata) { - if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) { - $inheritanceClassMap = []; - - foreach ($metadata->discriminatorMap as $type => $class) { - $inheritanceClassMap[] .= '"' . $type . '" = "' . $class . '"'; - } - - return '@' . $this->annotationsPrefix . 'DiscriminatorMap({' . implode(', ', $inheritanceClassMap) . '})'; + if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_NONE) { + return null; } + + $inheritanceClassMap = []; + + foreach ($metadata->discriminatorMap as $type => $class) { + $inheritanceClassMap[] .= '"' . $type . '" = "' . $class . '"'; + } + + return '@' . $this->annotationsPrefix . 'DiscriminatorMap({' . implode(', ', $inheritanceClassMap) . '})'; } /** @@ -1163,18 +1173,14 @@ public function __construct() $methods = []; foreach ($metadata->fieldMappings as $fieldMapping) { - if (isset($fieldMapping['declaredField']) && - isset($metadata->embeddedClasses[$fieldMapping['declaredField']]) - ) { + if (isset($fieldMapping['declaredField'], $metadata->embeddedClasses[$fieldMapping['declaredField']])) { continue; } $nullableField = $this->nullableFieldExpression($fieldMapping); - if (( ! isset($fieldMapping['id']) || - ! $fieldMapping['id'] || - $metadata->generatorType == ClassMetadataInfo::GENERATOR_TYPE_NONE - ) && (! $metadata->isEmbeddedClass || ! $this->embeddablesImmutable) + if ((!$metadata->isEmbeddedClass || !$this->embeddablesImmutable) + && (!isset($fieldMapping['id']) || ! $fieldMapping['id'] || $metadata->generatorType === ClassMetadataInfo::GENERATOR_TYPE_NONE) && $code = $this->generateEntityStubMethod($metadata, 'set', $fieldMapping['fieldName'], $fieldMapping['type'], $nullableField) ) { $methods[] = $code; @@ -1307,12 +1313,9 @@ public function __construct() $lines = []; foreach ($metadata->fieldMappings as $fieldMapping) { - if ($this->hasProperty($fieldMapping['fieldName'], $metadata) || - $metadata->isInheritedField($fieldMapping['fieldName']) || - ( - isset($fieldMapping['declaredField']) && - isset($metadata->embeddedClasses[$fieldMapping['declaredField']]) - ) + if (isset($fieldMapping['declaredField'], $metadata->embeddedClasses[$fieldMapping['declaredField']]) || + $this->hasProperty($fieldMapping['fieldName'], $metadata) || + $metadata->isInheritedField($fieldMapping['fieldName']) ) { continue; } @@ -1413,6 +1416,7 @@ public function __construct() if ($this->hasMethod($methodName, $metadata)) { return ''; } + $this->staticReflection[$metadata->name]['methods'][] = $methodName; $replacements = [ @@ -1748,6 +1752,27 @@ public function __construct() return implode("\n", $lines); } + private function generateEntityListenerAnnotation(ClassMetadataInfo $metadata): string + { + if (0 === \count($metadata->entityListeners)) { + return ''; + } + + $processedClasses = []; + foreach ($metadata->entityListeners as $event => $eventListeners) { + foreach ($eventListeners as $eventListener) { + $processedClasses[] = '"' . $eventListener['class'] . '"'; + } + } + + return \sprintf( + '%s%s({%s})', + '@' . $this->annotationsPrefix, + 'EntityListeners', + \implode(',', \array_unique($processedClasses)) + ); + } + /** * @param string $code * @param int $num diff --git a/lib/Doctrine/ORM/Tools/Export/Driver/AnnotationExporter.php b/lib/Doctrine/ORM/Tools/Export/Driver/AnnotationExporter.php index 044a1da53..171ac8728 100644 --- a/lib/Doctrine/ORM/Tools/Export/Driver/AnnotationExporter.php +++ b/lib/Doctrine/ORM/Tools/Export/Driver/AnnotationExporter.php @@ -59,7 +59,7 @@ class AnnotationExporter extends AbstractExporter } /** - * @param \Doctrine\ORM\Mapping\ClassMetadataInfo $metadata + * @param ClassMetadataInfo $metadata * * @return string */ @@ -69,7 +69,7 @@ class AnnotationExporter extends AbstractExporter } /** - * @param \Doctrine\ORM\Tools\EntityGenerator $entityGenerator + * @param EntityGenerator $entityGenerator * * @return void */ diff --git a/lib/Doctrine/ORM/Tools/Export/Driver/PhpExporter.php b/lib/Doctrine/ORM/Tools/Export/Driver/PhpExporter.php index 69db59f78..c918c71dd 100644 --- a/lib/Doctrine/ORM/Tools/Export/Driver/PhpExporter.php +++ b/lib/Doctrine/ORM/Tools/Export/Driver/PhpExporter.php @@ -82,6 +82,8 @@ class PhpExporter extends AbstractExporter } } + $lines = array_merge($lines, $this->processEntityListeners($metadata)); + foreach ($metadata->fieldMappings as $fieldMapping) { $lines[] = '$metadata->mapField(' . $this->_varExport($fieldMapping) . ');'; } @@ -177,4 +179,22 @@ class PhpExporter extends AbstractExporter return $export; } + + private function processEntityListeners(ClassMetadataInfo $metadata) : array + { + $lines = []; + + foreach ($metadata->entityListeners as $event => $entityListenerConfig) { + foreach ($entityListenerConfig as $entityListener) { + $lines[] = \sprintf( + '$metadata->addEntityListener(%s, %s, %s);', + \var_export($event, true), + \var_export($entityListener['class'], true), + \var_export($entityListener['method'], true) + ); + } + } + + return $lines; + } } diff --git a/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php b/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php index c4eb8f637..2d6bf1a73 100644 --- a/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php +++ b/lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php @@ -20,6 +20,7 @@ namespace Doctrine\ORM\Tools\Export\Driver; use Doctrine\ORM\Mapping\ClassMetadataInfo; +use SimpleXMLElement; /** * ClassMetadata exporter for Doctrine XML mapping files. @@ -40,10 +41,10 @@ class XmlExporter extends AbstractExporter */ public function exportClassMetadata(ClassMetadataInfo $metadata) { - $xml = new \SimpleXmlElement(""); + $xml = new SimpleXmlElement(''); if ($metadata->isMappedSuperclass) { $root = $xml->addChild('mapped-superclass'); @@ -390,16 +391,18 @@ class XmlExporter extends AbstractExporter } } + $this->processEntityListeners($metadata, $root); + return $this->_asXml($xml); } /** * Exports (nested) option elements. * - * @param \SimpleXMLElement $parentXml - * @param array $options + * @param SimpleXMLElement $parentXml + * @param array $options */ - private function exportTableOptions(\SimpleXMLElement $parentXml, array $options) + private function exportTableOptions(SimpleXMLElement $parentXml, array $options) : void { foreach ($options as $name => $option) { $isArray = is_array($option); @@ -418,12 +421,12 @@ class XmlExporter extends AbstractExporter /** * Export sequence information (if available/configured) into the current identifier XML node * - * @param \SimpleXMLElement $identifierXmlNode + * @param SimpleXMLElement $identifierXmlNode * @param ClassMetadataInfo $metadata * * @return void */ - private function exportSequenceInformation(\SimpleXMLElement $identifierXmlNode, ClassMetadataInfo $metadata) + private function exportSequenceInformation(SimpleXMLElement $identifierXmlNode, ClassMetadataInfo $metadata) : void { $sequenceDefinition = $metadata->sequenceGeneratorDefinition; @@ -438,12 +441,7 @@ class XmlExporter extends AbstractExporter $sequenceGeneratorXml->addAttribute('initial-value', $sequenceDefinition['initialValue']); } - /** - * @param \SimpleXMLElement $simpleXml - * - * @return string $xml - */ - private function _asXml($simpleXml) + private function _asXml(SimpleXMLElement $simpleXml) : string { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->loadXML($simpleXml->asXML()); @@ -451,4 +449,46 @@ class XmlExporter extends AbstractExporter return $dom->saveXML(); } + + private function processEntityListeners(ClassMetadataInfo $metadata, SimpleXMLElement $root): void + { + if (0 === \count($metadata->entityListeners)) { + return; + } + + $entityListenersXml = $root->addChild('entity-listeners'); + $entityListenersXmlMap = []; + + $this->generateEntityListenerXml($metadata, $entityListenersXmlMap, $entityListenersXml); + } + + private function generateEntityListenerXml(ClassMetadataInfo $metadata, array $entityListenersXmlMap, SimpleXMLElement $entityListenersXml): void + { + foreach ($metadata->entityListeners as $event => $entityListenerConfig) { + foreach ($entityListenerConfig as $entityListener) { + $entityListenerXml = $this->addClassToMapIfExists( + $entityListenersXmlMap, + $entityListener, + $entityListenersXml + ); + + $entityListenerCallbackXml = $entityListenerXml->addChild('lifecycle-callback'); + $entityListenerCallbackXml->addAttribute('type', $event); + $entityListenerCallbackXml->addAttribute('method', $entityListener['method']); + } + } + } + + private function addClassToMapIfExists(array $entityListenersXmlMap, array $entityListener, SimpleXMLElement $entityListenersXml): SimpleXMLElement + { + if (isset($entityListenersXmlMap[$entityListener['class']])) { + return $entityListenersXmlMap[$entityListener['class']]; + } + + $entityListenerXml = $entityListenersXml->addChild('entity-listener'); + $entityListenerXml->addAttribute('class', $entityListener['class']); + $entityListenersXmlMap[$entityListener['class']] = $entityListenerXml; + + return $entityListenerXml; + } } diff --git a/lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php b/lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php index b9a38f904..5da790954 100644 --- a/lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php +++ b/lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php @@ -214,6 +214,8 @@ class YamlExporter extends AbstractExporter $array['lifecycleCallbacks'] = $metadata->lifecycleCallbacks; } + $array = $this->processEntityListeners($metadata, $array); + return $this->yamlDump([$metadata->name => $array], 10); } @@ -232,4 +234,32 @@ class YamlExporter extends AbstractExporter { return Yaml::dump($array, $inline); } + + private function processEntityListeners(ClassMetadataInfo $metadata, array $array) : array + { + if (0 === \count($metadata->entityListeners)) { + return $array; + } + + $array['entityListeners'] = []; + + foreach ($metadata->entityListeners as $event => $entityListenerConfig) { + $array = $this->processEntityListenerConfig($array, $entityListenerConfig, $event); + } + + return $array; + } + + private function processEntityListenerConfig(array $array, array $entityListenerConfig, string $event) : array + { + foreach ($entityListenerConfig as $entityListener) { + if (! isset($array['entityListeners'][$entityListener['class']])) { + $array['entityListeners'][$entityListener['class']] = []; + } + + $array['entityListeners'][$entityListener['class']][$event] = [$entityListener['method']]; + } + + return $array; + } } diff --git a/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php b/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php index e9523b5b1..a74af72a1 100644 --- a/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php +++ b/tests/Doctrine/Tests/ORM/Tools/Export/AbstractClassMetadataExporterTest.php @@ -5,6 +5,8 @@ namespace Doctrine\Tests\ORM\Tools\Export; use Doctrine\Common\EventManager; use Doctrine\Common\Persistence\Mapping\Driver\PHPDriver; use Doctrine\ORM\Configuration; +use Doctrine\ORM\Events; +use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadataFactory; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Mapping\Driver\AnnotationDriver; @@ -344,7 +346,7 @@ abstract class AbstractClassMetadataExporterTest extends OrmTestCase { $this->assertEquals('user', $class->associationMappings['address']['inversedBy']); } - /** + /** * @depends testExportDirectoryAndFilesAreCreated */ public function testCascadeAllCollapsed() @@ -371,6 +373,26 @@ abstract class AbstractClassMetadataExporterTest extends OrmTestCase } } + /** + * @depends testExportedMetadataCanBeReadBackIn + * + * @param ClassMetadata $class + */ + public function testEntityListenersAreExported($class) + { + $this->assertNotEmpty($class->entityListeners); + $this->assertCount(2, $class->entityListeners[Events::prePersist]); + $this->assertCount(2, $class->entityListeners[Events::postPersist]); + $this->assertEquals(UserListener::class, $class->entityListeners[Events::prePersist][0]['class']); + $this->assertEquals('customPrePersist', $class->entityListeners[Events::prePersist][0]['method']); + $this->assertEquals(GroupListener::class, $class->entityListeners[Events::prePersist][1]['class']); + $this->assertEquals('prePersist', $class->entityListeners[Events::prePersist][1]['method']); + $this->assertEquals(UserListener::class, $class->entityListeners[Events::postPersist][0]['class']); + $this->assertEquals('customPostPersist', $class->entityListeners[Events::postPersist][0]['method']); + $this->assertEquals(AddressListener::class, $class->entityListeners[Events::postPersist][1]['class']); + $this->assertEquals('customPostPersist', $class->entityListeners[Events::postPersist][1]['method']); + } + public function __destruct() { # $this->_deleteDirectory(__DIR__ . '/export/'.$this->_getType()); @@ -406,3 +428,28 @@ class Group { } +class UserListener +{ + /** + * @\Doctrine\ORM\Mapping\PrePersist + */ + public function customPrePersist(): void {} + /** + * @\Doctrine\ORM\Mapping\PostPersist + */ + public function customPostPersist(): void {} +} +class GroupListener +{ + /** + * @\Doctrine\ORM\Mapping\PrePersist + */ + public function prePersist(): void {} +} +class AddressListener +{ + /** + * @\Doctrine\ORM\Mapping\PostPersist + */ + public function customPostPersist(): void {} +} diff --git a/tests/Doctrine/Tests/ORM/Tools/Export/annotation/Doctrine.Tests.ORM.Tools.Export.User.php b/tests/Doctrine/Tests/ORM/Tools/Export/annotation/Doctrine.Tests.ORM.Tools.Export.User.php index 344708059..c6a066258 100644 --- a/tests/Doctrine/Tests/ORM/Tools/Export/annotation/Doctrine.Tests.ORM.Tools.Export.User.php +++ b/tests/Doctrine/Tests/ORM/Tools/Export/annotation/Doctrine.Tests.ORM.Tools.Export.User.php @@ -5,6 +5,11 @@ namespace Doctrine\Tests\ORM\Tools\Export; /** * @Entity * @HasLifecycleCallbacks + * @EntityListeners({ + * Doctrine\Tests\ORM\Tools\Export\UserListener::class, + * Doctrine\Tests\ORM\Tools\Export\GroupListener::class, + * Doctrine\Tests\ORM\Tools\Export\AddressListener::class + * }) * @Table(name="cms_users",options={"engine"="MyISAM","foo"={"bar"="baz"}}) */ class User @@ -57,21 +62,21 @@ class User /** * @PrePersist */ - public function doStuffOnPrePersist() + public function doStuffOnPrePersist(): void { } /** * @PrePersist */ - public function doOtherStuffOnPrePersistToo() + public function doOtherStuffOnPrePersistToo(): void { } /** * @PostPersist */ - public function doStuffOnPostPersist() + public function doStuffOnPostPersist(): void { } } diff --git a/tests/Doctrine/Tests/ORM/Tools/Export/php/Doctrine.Tests.ORM.Tools.Export.User.php b/tests/Doctrine/Tests/ORM/Tools/Export/php/Doctrine.Tests.ORM.Tools.Export.User.php index 5c8a0c013..5e54c0b2e 100644 --- a/tests/Doctrine/Tests/ORM/Tools/Export/php/Doctrine.Tests.ORM.Tools.Export.User.php +++ b/tests/Doctrine/Tests/ORM/Tools/Export/php/Doctrine.Tests.ORM.Tools.Export.User.php @@ -1,7 +1,11 @@ setInheritanceType(ClassMetadataInfo::INHERITANCE_TYPE_NONE); $metadata->setPrimaryTable( @@ -11,9 +15,9 @@ $metadata->setPrimaryTable( ] ); $metadata->setChangeTrackingPolicy(ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT); -$metadata->addLifecycleCallback('doStuffOnPrePersist', 'prePersist'); -$metadata->addLifecycleCallback('doOtherStuffOnPrePersistToo', 'prePersist'); -$metadata->addLifecycleCallback('doStuffOnPostPersist', 'postPersist'); +$metadata->addLifecycleCallback('doStuffOnPrePersist', Events::prePersist); +$metadata->addLifecycleCallback('doOtherStuffOnPrePersistToo', Events::prePersist); +$metadata->addLifecycleCallback('doStuffOnPostPersist', Events::postPersist); $metadata->mapField( [ 'id' => true, @@ -149,3 +153,7 @@ $metadata->mapManyToMany( 'orderBy' => NULL, ] ); +$metadata->addEntityListener(Events::prePersist, UserListener::class, 'customPrePersist'); +$metadata->addEntityListener(Events::postPersist, UserListener::class, 'customPostPersist'); +$metadata->addEntityListener(Events::prePersist, GroupListener::class, 'prePersist'); +$metadata->addEntityListener(Events::postPersist, AddressListener::class, 'customPostPersist'); diff --git a/tests/Doctrine/Tests/ORM/Tools/Export/xml/Doctrine.Tests.ORM.Tools.Export.User.dcm.xml b/tests/Doctrine/Tests/ORM/Tools/Export/xml/Doctrine.Tests.ORM.Tools.Export.User.dcm.xml index 8d0c5d217..78bac4f5f 100644 --- a/tests/Doctrine/Tests/ORM/Tools/Export/xml/Doctrine.Tests.ORM.Tools.Export.User.dcm.xml +++ b/tests/Doctrine/Tests/ORM/Tools/Export/xml/Doctrine.Tests.ORM.Tools.Export.User.dcm.xml @@ -77,6 +77,18 @@ + + + + + + + + + + + + diff --git a/tests/Doctrine/Tests/ORM/Tools/Export/yaml/Doctrine.Tests.ORM.Tools.Export.User.dcm.yml b/tests/Doctrine/Tests/ORM/Tools/Export/yaml/Doctrine.Tests.ORM.Tools.Export.User.dcm.yml index d52e94601..df2dae1c5 100644 --- a/tests/Doctrine/Tests/ORM/Tools/Export/yaml/Doctrine.Tests.ORM.Tools.Export.User.dcm.yml +++ b/tests/Doctrine/Tests/ORM/Tools/Export/yaml/Doctrine.Tests.ORM.Tools.Export.User.dcm.yml @@ -75,3 +75,11 @@ Doctrine\Tests\ORM\Tools\Export\User: lifecycleCallbacks: prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ] postPersist: [ doStuffOnPostPersist ] + entityListeners: + Doctrine\Tests\ORM\Tools\Export\UserListener: + prePersist: [customPrePersist] + postPersist: [customPostPersist] + Doctrine\Tests\ORM\Tools\Export\GroupListener: + prePersist: [prePersist] + Doctrine\Tests\ORM\Tools\Export\AddressListener: + postPersist: [customPostPersist]