Skip to content

Commit

Permalink
Add support for entity listeners (#45)
Browse files Browse the repository at this point in the history
* Add support for defining entity listeners

* Apply fixes from StyleCI

[ci skip] [skip ci]
  • Loading branch information
patrickbrouwers authored Jul 2, 2017
1 parent 499147c commit 8c3359f
Show file tree
Hide file tree
Showing 6 changed files with 317 additions and 2 deletions.
12 changes: 12 additions & 0 deletions src/Builders/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,18 @@ public function events(callable $callback = null)
return $events;
}

/**
* {@inheritdoc}
*/
public function listen(callable $callback = null)
{
$events = new EntityListeners($this->builder);

$this->callbackAndQueue($events, $callback);

return $events;
}

/**
* {@inheritdoc}
*/
Expand Down
110 changes: 110 additions & 0 deletions src/Builders/EntityListeners.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

namespace LaravelDoctrine\Fluent\Builders;

use Doctrine\ORM\Events;
use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder;
use InvalidArgumentException;
use LaravelDoctrine\Fluent\Buildable;

/**
* @method EntityListeners preRemove(string $listener, string $method = null)
* @method EntityListeners postRemove(string $listener, string $method = null)
* @method EntityListeners prePersist(string $listener, string $method = null)
* @method EntityListeners postPersist(string $listener, string $method = null)
* @method EntityListeners preUpdate(string $listener, string $method = null)
* @method EntityListeners postUpdate(string $listener, string $method = null)
* @method EntityListeners postLoad(string $listener, string $method = null)
* @method EntityListeners loadClassMetadata(string $listener, string $method = null)
* @method EntityListeners onClassMetadataNotFound(string $listener, string $method = null)
* @method EntityListeners preFlush(string $listener, string $method = null)
* @method EntityListeners onFlush(string $listener, string $method = null)
* @method EntityListeners postFlush(string $listener, string $method = null)
* @method EntityListeners onClear(string $listener, string $method = null)
*/
class EntityListeners implements Buildable
{
/**
* @var ClassMetadataBuilder
*/
private $builder;

/**
* @var array
*/
private $events = [
Events::preRemove => [],
Events::postRemove => [],
Events::prePersist => [],
Events::postPersist => [],
Events::preUpdate => [],
Events::postUpdate => [],
Events::postLoad => [],
Events::loadClassMetadata => [],
Events::onClassMetadataNotFound => [],
Events::preFlush => [],
Events::onFlush => [],
Events::postFlush => [],
Events::onClear => [],
];

/**
* LifecycleEvents constructor.
*
* @param ClassMetadataBuilder $builder
*/
public function __construct(ClassMetadataBuilder $builder)
{
$this->builder = $builder;
}

/**
* Magically call all methods that match an event name.
*
* @param string $event
* @param array $args
*
* @throws InvalidArgumentException
*
* @return LifecycleEvents
*/
public function __call($event, $args)
{
if (array_key_exists($event, $this->events)) {
array_unshift($args, $event);

return call_user_func_array([$this, 'add'], $args);
}

throw new InvalidArgumentException('Fluent builder method ['.$event.'] does not exist');
}

/**
* @param string $event
* @param string $class
* @param string|null $method
*
* @return EntityListeners
*/
private function add($event, $class, $method = null)
{
$this->events[$event][] = [
'class' => $class,
'method' => $method ?: $event,
];

return $this;
}

/**
* Execute the build process.
*/
public function build()
{
foreach ($this->events as $event => $listeners) {
foreach ($listeners as $listener) {
$this->builder->getClassMetadata()->addEntityListener($event, $listener['class'], $listener['method']);
}
}
}
}
7 changes: 7 additions & 0 deletions src/Fluent.php
Original file line number Diff line number Diff line change
Expand Up @@ -460,4 +460,11 @@ public function override($name, callable $callback);
* @return Builders\LifecycleEvents
*/
public function events(callable $callback = null);

/**
* @param callable|null $callback
*
* @return Builders\EntityListeners
*/
public function listen(callable $callback = null);
}
50 changes: 48 additions & 2 deletions tests/Builders/BuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use LaravelDoctrine\Fluent\Buildable;
use LaravelDoctrine\Fluent\Builders\Builder;
use LaravelDoctrine\Fluent\Builders\Embedded;
use LaravelDoctrine\Fluent\Builders\EntityListeners;
use LaravelDoctrine\Fluent\Builders\Field;
use LaravelDoctrine\Fluent\Builders\Index;
use LaravelDoctrine\Fluent\Builders\Inheritance\Inheritance;
Expand All @@ -33,11 +34,12 @@
use LogicException;
use Tests\FakeEntity;
use Tests\Stubs\Embedabbles\StubEmbeddable;
use Tests\Stubs\StubEntityListener;

class BuilderTest extends \PHPUnit_Framework_TestCase
{
use IsMacroable;

/**
* @var ClassMetadataBuilder
*/
Expand Down Expand Up @@ -674,6 +676,50 @@ public function test_events_can_be_configured_through_a_callable()
);
}

public function test_can_add_entity_listener()
{
$entityListeners = $this->fluent->listen();

$this->assertInstanceOf(EntityListeners::class, $entityListeners);
$this->assertContains($entityListeners, $this->fluent->getQueued());
}

public function test_entity_listeners_can_be_configured_through_a_callable()
{
$this->fluent->listen(function (EntityListeners $events) {
$events->onFlush(StubEntityListener::class, 'swipeFloor');
$events->postFlush(StubEntityListener::class, 'cleanToilet');

// defaults to the event name as method
$events->onClear(StubEntityListener::class);
});

foreach ($this->fluent->getQueued() as $buildable) {
$buildable->build();
}

$this->assertEquals([
[
'class' => StubEntityListener::class,
'method' => 'swipeFloor',
]
], $this->fluent->getClassMetadata()->entityListeners['onFlush']);

$this->assertEquals([
[
'class' => StubEntityListener::class,
'method' => 'cleanToilet',
]
], $this->fluent->getClassMetadata()->entityListeners['postFlush']);

$this->assertEquals([
[
'class' => StubEntityListener::class,
'method' => 'onClear',
]
], $this->fluent->getClassMetadata()->entityListeners['onClear']);
}

public function test_can_override_an_attribute()
{
$this->fluent->string('name');
Expand Down Expand Up @@ -825,7 +871,7 @@ public function test_can_guess_an_embeded_field_name()

public function test_buildable_objects_returned_from_macros_get_queued_and_built()
{
Builder::macro('foo', function(){
Builder::macro('foo', function () {
/** @var Buildable|\Mockery\Mock $buildable */
$buildable = \Mockery::mock(Buildable::class);
$buildable->shouldReceive('build')->once();
Expand Down
114 changes: 114 additions & 0 deletions tests/Builders/EntityListenersTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php

namespace Tests\Builders;

use Doctrine\ORM\Events;
use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use LaravelDoctrine\Fluent\Builders\EntityListeners;
use Symfony\Component\VarDumper\Cloner\Stub;
use Tests\Stubs\Entities\StubEntity;
use Tests\Stubs\StubEntityListener;

class EntityListenersTest extends \PHPUnit_Framework_TestCase
{
/**
* @var EntityListeners
*/
protected $builder;

/**
* @var ClassMetadataBuilder
*/
protected $fluent;

protected function setUp()
{
$this->fluent = new ClassMetadataBuilder(
new ClassMetadataInfo(StubEntity::class)
);

$this->builder = new EntityListeners($this->fluent);
}

/**
* @dataProvider eventsProvider
*
* @param string $event
* @param string $listener
* @param string|null $method
* @param string $expectedMethod
*/
public function test_can_add_event_listeners($event, $listener, $method = null, $expectedMethod)
{
$this->builder->{$event}($listener, $method);

$this->builder->build();

$this->assertTrue(
isset($this->fluent->getClassMetadata()->entityListeners[$event])
);

$this->assertCount(
1, $this->fluent->getClassMetadata()->entityListeners[$event]
);

$this->assertEquals([
[
'class' => $listener,
'method' => $expectedMethod
]
], $this->fluent->getClassMetadata()->entityListeners[$event]);
}

public function test_can_add_multiple_entity_listeners_per_event()
{
$this->builder
->onClear(StubEntityListener::class, 'onClear')
->onClear(StubEntityListener::class, 'handle');

$this->builder->build();

$this->assertTrue(
isset($this->fluent->getClassMetadata()->entityListeners['onClear'])
);

$this->assertCount(
2, $this->fluent->getClassMetadata()->entityListeners['onClear']
);

$this->assertEquals([
[
'class' => StubEntityListener::class,
'method' => 'onClear'
],
[
'class' => StubEntityListener::class,
'method' => 'handle'
]
], $this->fluent->getClassMetadata()->entityListeners['onClear']);
}

/**
* @return array
*/
public function eventsProvider()
{
return [
[Events::preRemove, StubEntityListener::class, 'preRemove', 'preRemove'],
[Events::postRemove, StubEntityListener::class, 'handle', 'handle'],
[Events::prePersist, StubEntityListener::class, 'handle', 'handle'],
[Events::postPersist, StubEntityListener::class, 'handle', 'handle'],
[Events::preUpdate, StubEntityListener::class, 'handle', 'handle'],
[Events::postUpdate, StubEntityListener::class, 'handle', 'handle'],
[Events::postLoad, StubEntityListener::class, 'handle', 'handle'],
[Events::loadClassMetadata, StubEntityListener::class, 'handle', 'handle'],
[Events::onClassMetadataNotFound, StubEntityListener::class, 'handle', 'handle'],
[Events::preFlush, StubEntityListener::class, 'handle', 'handle'],
[Events::onFlush, StubEntityListener::class, 'handle', 'handle'],
[Events::postFlush, StubEntityListener::class, 'handle', 'handle'],
[Events::onClear, StubEntityListener::class, 'handle', 'handle'],
[Events::onClear, StubEntityListener::class, null, 'onClear'],
];
}
}
26 changes: 26 additions & 0 deletions tests/Stubs/StubEntityListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Tests\Stubs;

class StubEntityListener
{
public function handle()
{
}

public function swipeFloor()
{
}

public function cleanToilet()
{
}

public function preRemove()
{
}

public function onClear()
{
}
}

0 comments on commit 8c3359f

Please sign in to comment.