Skip to content

Commit

Permalink
Merge pull request #26 from hboomsma/hotfix/cascaded-persist
Browse files Browse the repository at this point in the history
Fire EntityChangedEvent on cascaded persist adding a new entity
  • Loading branch information
nicoschoenmaker authored Aug 9, 2016
2 parents 1b680f4 + 404dc78 commit 0f06076
Show file tree
Hide file tree
Showing 8 changed files with 425 additions and 220 deletions.
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
language: php

services:
- mysql

matrix:
include:
- php: 5.6
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ $event = new UpdatedAtListener(new DateTime());

// register the events
$event_manager->addEventListener('preFlush', $entity_changed_listener);
$event_manager->addEventListener('prePersist', $entity_changed_listener);
$event_manager->addEventListener('entityChanged', $event);

```
Expand Down
30 changes: 16 additions & 14 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,36 +1,38 @@
{
"name": "hostnet/entity-tracker-component",
"description": "Provides an event when a Tracked entity changes",
"license": "MIT",
"name": "hostnet/entity-tracker-component",
"description": "Provides an event when a Tracked entity changes",
"license": "MIT",
"require": {
"php" : "5.*,>=5.6||7.*",
"doctrine/common" : "2.*,>=2.4.0",
"doctrine/annotations" : "1.*,>=1.2.0",
"doctrine/orm" : "2.*,>=2.4.0",
"psr/log" : "^1.0.0"
"php": "5.*,>=5.6||7.*",
"doctrine/annotations": "1.*,>=1.2.0",
"doctrine/common": "2.*,>=2.4.0",
"doctrine/orm": "2.*,>=2.4.0",
"psr/log": "^1.0.0"
},
"require-dev": {
"hostnet/phpcs-tool": "^4.0.8",
"phpunit/phpunit" : "^5.5.0"
"hostnet/database-test-lib": "^1.0",
"hostnet/phpcs-tool": "^4.0.8",
"phpunit/phpunit": "^5.5.0",
"symfony/var-dumper": "^3.1"
},
"minimum-stability": "stable",
"autoload": {
"psr-4": {
"Hostnet\\Component\\EntityTracker\\" : "src/"
"Hostnet\\Component\\EntityTracker\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Hostnet\\Component\\EntityTracker\\" : "test/"
"Hostnet\\Component\\EntityTracker\\": "test/"
}
},
"authors": [
{
"name": "Iltar van der Berg",
"name": "Iltar van der Berg",
"email": "[email protected]"
},
{
"name": "Yannick de Lange",
"name": "Yannick de Lange",
"email": "[email protected]"
}
]
Expand Down
44 changes: 42 additions & 2 deletions src/Listener/EntityChangedListener.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php
namespace Hostnet\Component\EntityTracker\Listener;

use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PreFlushEventArgs;
use Doctrine\ORM\Proxy\Proxy;
use Hostnet\Component\EntityTracker\Event\EntityChangedEvent;
Expand Down Expand Up @@ -87,12 +88,17 @@ public function preFlush(PreFlushEventArgs $event)
continue;
}

$original = $this->meta_mutation_provider->createOriginalEntity($em, $entity);
$original = $this->meta_mutation_provider->createOriginalEntity($em, $entity);

// New entities are handled in the pre-persist event.
if (!$original) {
continue;
}
$mutated_fields = $this->meta_mutation_provider->getMutatedFields($em, $entity, $original);

if (!empty($mutated_fields)) {
$this->logger->info(
'Going to notify a change to {entity_class}, which has {mutated_fields}',
'Going to notify a change (preFlush) to {entity_class}, which has {mutated_fields}',
[
'entity_class' => get_class($entity),
'mutated_fields' => $mutated_fields
Expand All @@ -106,4 +112,38 @@ public function preFlush(PreFlushEventArgs $event)
}
}
}

/**
* Pre Persist event callback
*
* Checks if the entity contains an @Tracked (or derived)
* annotation. If so, it will dispatch 'Events::ENTITY_CHANGED'
* with the new entity states.
*
* @param LifecycleEventArgs $event
*/
public function prePersist(LifecycleEventArgs $event)
{
$em = $event->getEntityManager();
$entity = $event->getEntity();

if (false === $this->meta_annotation_provider->isTracked($em, $entity)) {
return;
}

$mutated_fields = $this->meta_mutation_provider->getMutatedFields($em, $entity, null);

$this->logger->info(
'Going to notify a change (prePersist) to {entity_class}, which has {mutated_fields}',
[
'entity_class' => get_class($entity),
'mutated_fields' => $mutated_fields
]
);

$em->getEventManager()->dispatchEvent(
Events::ENTITY_CHANGED,
new EntityChangedEvent($em, $entity, null, $mutated_fields)
);
}
}
42 changes: 42 additions & 0 deletions test/Functional/Entity/Author.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php
namespace Hostnet\Component\EntityTracker\Functional\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
*/
class Author
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @var int
*/
private $id;

/**
* @ORM\Column
* @var string
*/
public $name;

/**
* @ORM\ManyToMany(
* targetEntity="Book",
* mappedBy="authors",
* cascade="persist"
* )
* @var Book[]
*/
public $books;

/**
* @param string $name
*/
public function __construct($name)
{
$this->name = $name;
}
}
43 changes: 43 additions & 0 deletions test/Functional/Entity/Book.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php
namespace Hostnet\Component\EntityTracker\Functional\Entity;

use Doctrine\ORM\Mapping as ORM;
use Hostnet\Component\EntityTracker\Annotation\Tracked;

/**
* @ORM\Entity
* @Tracked
*/
class Book
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @var int
*/
private $id;

/**
* @ORM\Column
* @var string
*/
public $title;

/**
* @ORM\ManyToMany(
* targetEntity="Author",
* inversedBy="books"
* )
* @var Author[]
*/
public $authors;

/**
* @param string $title
*/
public function __construct($title)
{
$this->title = $title;
}
}
156 changes: 156 additions & 0 deletions test/Functional/EventListenerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<?php
namespace Hostnet\Component\EntityTracker\Functional;

use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\ORM\Tools\Setup;
use Hostnet\Component\DatabaseTest\MysqlPersistentConnection;
use Hostnet\Component\EntityTracker\Event\EntityChangedEvent;
use Hostnet\Component\EntityTracker\Functional\Entity\Author;
use Hostnet\Component\EntityTracker\Functional\Entity\Book;
use Hostnet\Component\EntityTracker\Listener\EntityChangedListener;
use Hostnet\Component\EntityTracker\Provider\EntityAnnotationMetadataProvider;
use Hostnet\Component\EntityTracker\Provider\EntityMutationMetadataProvider;
use Symfony\Component\Console\Logger\ConsoleLogger;
use Symfony\Component\Console\Output\ConsoleOutput;

/**
* @coversNothing
*/
class EventListenerTest extends \PHPUnit_Framework_TestCase
{
/**
* @var EntityChangedEvent[]
*/
private $events;

/**
* @var EntityManagerInterface
*/
private $em;

/**
* @var MysqlPersistentConnection;
*/
private $connection;

/**
* {@inheritdoc}
*/
protected function setUp()
{
$this->connection = new MysqlPersistentConnection();
$params = $this->connection->getConnectionParams();

$config = Setup::createAnnotationMetadataConfiguration([__DIR__ . '/Entity'], true, null, null, false);
$this->em = EntityManager::create($params, $config);

$event_manager = $this->em->getEventManager();

// create tables in the database
$metadata = $this->em->getMetadataFactory()->getAllMetadata();
$schema_tool = new SchemaTool($this->em);
$schema_tool->createSchema($metadata);

// default doctrine annotation reader
$annotation_reader = new AnnotationReader();

// setup required providers
$mutation_metadata_provider = new EntityMutationMetadataProvider($annotation_reader);
$annotation_metadata_provider = new EntityAnnotationMetadataProvider($annotation_reader);

// pre flush event listener that uses the @Tracked annotation
$entity_changed_listener = new EntityChangedListener(
$annotation_metadata_provider,
$mutation_metadata_provider
);

$event_manager->addEventListener('preFlush', $entity_changed_listener);
$event_manager->addEventListener('prePersist', $entity_changed_listener);
$event_manager->addEventListener('entityChanged', $this);

$this->events = [];
}

public function entityChanged(EntityChangedEvent $event)
{
$this->events[] = $event;
}

public function testNewAuthorNewBook()
{
$tolkien = new Author('J. R. R. Tolkien');
$tolkien->books[] = new Book('The Fellowship of the Ring');

$this->em->persist($tolkien);
$this->em->flush();

self::assertCount(1, $this->events);
self::assertSame($tolkien->books[0], $this->events[0]->getCurrentEntity());
}

public function testNewBookPersistAuthor()
{
$tolkien = new Author('J. R. R. Tolkien');
$this->em->persist($tolkien);
$this->em->flush();
$this->events = [];

$tolkien->books[] = new Book('The Two Towers');

$this->em->persist($tolkien);
$this->em->flush();

self::assertCount(1, $this->events);
self::assertSame($tolkien->books[0], $this->events[0]->getCurrentEntity());
}

public function testNewBook()
{
$tolkien = new Author('J. R. R. Tolkien');
$this->em->persist($tolkien);
$this->em->flush();
$this->events = [];

$tolkien->books[] = new Book('The Return of the King');
$this->em->flush();

self::assertCount(1, $this->events);
self::assertSame($tolkien->books[0], $this->events[0]->getCurrentEntity());
}

public function testNewBookPersistAuthorNewBook()
{
$tolkien = new Author('J. R. R. Tolkien');
$this->em->persist($tolkien);
$this->em->flush();
$this->events = [];

$tolkien->books[] = new Book('The Hobbit');
$this->em->persist($tolkien);
$tolkien->books[] = new Book('The Silmarillion');
$this->em->flush();

self::assertCount(2, $this->events);
self::assertSame($tolkien->books[0], $this->events[0]->getCurrentEntity());
self::assertSame($tolkien->books[1], $this->events[1]->getCurrentEntity());
}

public function testNewBookPersistAuthorEditBook()
{
$tolkien = new Author('J. R. R. Tolkien');
$tolkien->books[] = new Book('Silmarillion');
$this->em->persist($tolkien);
$this->em->flush();
$this->events = [];

$tolkien->books[0]->title = 'The Silmarillion';
$this->em->flush();

self::assertCount(1, $this->events);
self::assertSame('The Silmarillion', $this->events[0]->getCurrentEntity()->title);
self::assertSame('Silmarillion', $this->events[0]->getOriginalEntity()->title);
}
}
Loading

0 comments on commit 0f06076

Please sign in to comment.