Skip to content
New issue

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

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mapped entity listeners are now processed by metadata exporters #6593

Merged
merged 7 commits into from
Aug 16, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
379 changes: 124 additions & 255 deletions lib/Doctrine/ORM/Tools/EntityGenerator.php

Large diffs are not rendered by default.

16 changes: 3 additions & 13 deletions lib/Doctrine/ORM/Tools/Export/Driver/AnnotationExporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class AnnotationExporter extends AbstractExporter
/**
* {@inheritdoc}
*/
public function exportClassMetadata(ClassMetadataInfo $metadata)
public function exportClassMetadata(ClassMetadataInfo $metadata): string
{
if ( ! $this->_entityGenerator) {
throw new \RuntimeException('For the AnnotationExporter you must set an EntityGenerator instance with the setEntityGenerator() method.');
Expand All @@ -58,22 +58,12 @@ public function exportClassMetadata(ClassMetadataInfo $metadata)
return $this->_entityGenerator->generateEntityClass($metadata);
}

/**
* @param \Doctrine\ORM\Mapping\ClassMetadataInfo $metadata
*
* @return string
*/
protected function _generateOutputPath(ClassMetadataInfo $metadata)
protected function _generateOutputPath(ClassMetadataInfo $metadata): string
{
return $this->_outputDir . '/' . str_replace('\\', '/', $metadata->name) . $this->_extension;
}

/**
* @param \Doctrine\ORM\Tools\EntityGenerator $entityGenerator
*
* @return void
*/
public function setEntityGenerator(EntityGenerator $entityGenerator)
public function setEntityGenerator(EntityGenerator $entityGenerator): void
{
$this->_entityGenerator = $entityGenerator;
}
Expand Down
33 changes: 26 additions & 7 deletions lib/Doctrine/ORM/Tools/Export/Driver/PhpExporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class PhpExporter extends AbstractExporter
/**
* {@inheritdoc}
*/
public function exportClassMetadata(ClassMetadataInfo $metadata)
public function exportClassMetadata(ClassMetadataInfo $metadata): string
{
$lines = [];
$lines[] = '<?php';
Expand Down Expand Up @@ -82,6 +82,8 @@ public function exportClassMetadata(ClassMetadataInfo $metadata)
}
}

$lines = array_merge($lines, $this->processEntityListeners($metadata));

foreach ($metadata->fieldMappings as $fieldMapping) {
$lines[] = '$metadata->mapField(' . $this->_varExport($fieldMapping) . ');';
}
Expand Down Expand Up @@ -159,12 +161,7 @@ public function exportClassMetadata(ClassMetadataInfo $metadata)
return implode("\n", $lines);
}

/**
* @param mixed $var
*
* @return string
*/
protected function _varExport($var)
protected function _varExport($var): string
{
$export = var_export($var, true);
$export = str_replace("\n", PHP_EOL . str_repeat(' ', 8), $export);
Expand All @@ -177,4 +174,26 @@ protected function _varExport($var)

return $export;
}

private function processEntityListeners(ClassMetadataInfo $metadata): array
{
$lines = [];

if (0 === \count($metadata->entityListeners)) {
return $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;
}
}
72 changes: 56 additions & 16 deletions lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
namespace Doctrine\ORM\Tools\Export\Driver;

use Doctrine\ORM\Mapping\ClassMetadataInfo;
use SimpleXMLElement;

/**
* ClassMetadata exporter for Doctrine XML mapping files.
Expand All @@ -38,12 +39,12 @@ class XmlExporter extends AbstractExporter
/**
* {@inheritdoc}
*/
public function exportClassMetadata(ClassMetadataInfo $metadata)
public function exportClassMetadata(ClassMetadataInfo $metadata): string
{
$xml = new \SimpleXmlElement("<?xml version=\"1.0\" encoding=\"utf-8\"?><doctrine-mapping ".
"xmlns=\"http://doctrine-project.org/schemas/orm/doctrine-mapping\" " .
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ".
"xsi:schemaLocation=\"http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd\" />");
$xml = new SimpleXmlElement('<?xml version="1.0" encoding="utf-8"?><doctrine-mapping ' .
'xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" ' .
'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' .
'xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd" />');

if ($metadata->isMappedSuperclass) {
$root = $xml->addChild('mapped-superclass');
Expand Down Expand Up @@ -390,16 +391,18 @@ public function exportClassMetadata(ClassMetadataInfo $metadata)
}
}

$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);
Expand All @@ -418,12 +421,12 @@ private function exportTableOptions(\SimpleXMLElement $parentXml, array $options
/**
* 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;

Expand All @@ -438,17 +441,54 @@ private function exportSequenceInformation(\SimpleXMLElement $identifierXmlNode,
$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());
$dom->formatOutput = true;

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;
}
}
33 changes: 31 additions & 2 deletions lib/Doctrine/ORM/Tools/Export/Driver/YamlExporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class YamlExporter extends AbstractExporter
/**
* {@inheritdoc}
*/
public function exportClassMetadata(ClassMetadataInfo $metadata)
public function exportClassMetadata(ClassMetadataInfo $metadata): string
{
$array = [];

Expand Down Expand Up @@ -214,6 +214,8 @@ public function exportClassMetadata(ClassMetadataInfo $metadata)
$array['lifecycleCallbacks'] = $metadata->lifecycleCallbacks;
}

$array = $this->processEntityListeners($metadata, $array);

return $this->yamlDump([$metadata->name => $array], 10);
}

Expand All @@ -228,8 +230,35 @@ public function exportClassMetadata(ClassMetadataInfo $metadata)
*
* @return string A YAML string representing the original PHP array
*/
protected function yamlDump($array, $inline = 2)
protected function yamlDump($array, $inline = 2): string
{
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
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;
Expand Down Expand Up @@ -344,7 +346,7 @@ public function testInversedByIsExported($class)
{
$this->assertEquals('user', $class->associationMappings['address']['inversedBy']);
}
/**
/**
* @depends testExportDirectoryAndFilesAreCreated
*/
public function testCascadeAllCollapsed()
Expand All @@ -371,6 +373,26 @@ public function testCascadeAllCollapsed()
}
}

/**
* @depends testExportedMetadataCanBeReadBackIn
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you really want to use @depends here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's how all other export tests are structured, so I just adhered to the structure within the test case.

*
* @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']);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, this is the test that I was missing.

$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());
Expand Down Expand Up @@ -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 {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
/**
* @Entity
* @HasLifecycleCallbacks
* @EntityListeners({
* Doctrine\Tests\ORM\Tools\Export\UserListener::class,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should probably be prefixed with \ to avoid import collisions

Copy link
Contributor Author

@tPl0ch tPl0ch Aug 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a weird issue with this, the resulting string is "\Doctrine\Tests\ORM\Tools\Export\UserListener", although ::class notation normally results in class names without root namespace:

<?php
echo \SimpleXMLElement::class;
// SimpleXMLElement

Using the ::class notation that way works as intended.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tPl0ch gotcha, seems weird, but it's probably a doctrine/annotations bug.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* 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
Expand Down Expand Up @@ -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
{
}
}
Loading