From 8c3359feec4b8d72780849b9251fecc2a61771d9 Mon Sep 17 00:00:00 2001 From: Patrick Brouwers Date: Sun, 2 Jul 2017 08:47:43 +0200 Subject: [PATCH] Add support for entity listeners (#45) * Add support for defining entity listeners * Apply fixes from StyleCI [ci skip] [skip ci] --- src/Builders/Builder.php | 12 +++ src/Builders/EntityListeners.php | 110 ++++++++++++++++++++++++ src/Fluent.php | 7 ++ tests/Builders/BuilderTest.php | 50 ++++++++++- tests/Builders/EntityListenersTest.php | 114 +++++++++++++++++++++++++ tests/Stubs/StubEntityListener.php | 26 ++++++ 6 files changed, 317 insertions(+), 2 deletions(-) create mode 100644 src/Builders/EntityListeners.php create mode 100644 tests/Builders/EntityListenersTest.php create mode 100644 tests/Stubs/StubEntityListener.php diff --git a/src/Builders/Builder.php b/src/Builders/Builder.php index f803b99..b7494b3 100644 --- a/src/Builders/Builder.php +++ b/src/Builders/Builder.php @@ -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} */ diff --git a/src/Builders/EntityListeners.php b/src/Builders/EntityListeners.php new file mode 100644 index 0000000..e7c0903 --- /dev/null +++ b/src/Builders/EntityListeners.php @@ -0,0 +1,110 @@ + [], + 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']); + } + } + } +} diff --git a/src/Fluent.php b/src/Fluent.php index ead358a..f7f0b47 100644 --- a/src/Fluent.php +++ b/src/Fluent.php @@ -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); } diff --git a/tests/Builders/BuilderTest.php b/tests/Builders/BuilderTest.php index 70e780f..408582e 100644 --- a/tests/Builders/BuilderTest.php +++ b/tests/Builders/BuilderTest.php @@ -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; @@ -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 */ @@ -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'); @@ -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(); diff --git a/tests/Builders/EntityListenersTest.php b/tests/Builders/EntityListenersTest.php new file mode 100644 index 0000000..59aec7f --- /dev/null +++ b/tests/Builders/EntityListenersTest.php @@ -0,0 +1,114 @@ +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'], + ]; + } +} diff --git a/tests/Stubs/StubEntityListener.php b/tests/Stubs/StubEntityListener.php new file mode 100644 index 0000000..8f6b83d --- /dev/null +++ b/tests/Stubs/StubEntityListener.php @@ -0,0 +1,26 @@ +