Skip to content

Commit 8c3359f

Browse files
Add support for entity listeners (#45)
* Add support for defining entity listeners * Apply fixes from StyleCI [ci skip] [skip ci]
1 parent 499147c commit 8c3359f

File tree

6 files changed

+317
-2
lines changed

6 files changed

+317
-2
lines changed

src/Builders/Builder.php

+12
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,18 @@ public function events(callable $callback = null)
125125
return $events;
126126
}
127127

128+
/**
129+
* {@inheritdoc}
130+
*/
131+
public function listen(callable $callback = null)
132+
{
133+
$events = new EntityListeners($this->builder);
134+
135+
$this->callbackAndQueue($events, $callback);
136+
137+
return $events;
138+
}
139+
128140
/**
129141
* {@inheritdoc}
130142
*/

src/Builders/EntityListeners.php

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
3+
namespace LaravelDoctrine\Fluent\Builders;
4+
5+
use Doctrine\ORM\Events;
6+
use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder;
7+
use InvalidArgumentException;
8+
use LaravelDoctrine\Fluent\Buildable;
9+
10+
/**
11+
* @method EntityListeners preRemove(string $listener, string $method = null)
12+
* @method EntityListeners postRemove(string $listener, string $method = null)
13+
* @method EntityListeners prePersist(string $listener, string $method = null)
14+
* @method EntityListeners postPersist(string $listener, string $method = null)
15+
* @method EntityListeners preUpdate(string $listener, string $method = null)
16+
* @method EntityListeners postUpdate(string $listener, string $method = null)
17+
* @method EntityListeners postLoad(string $listener, string $method = null)
18+
* @method EntityListeners loadClassMetadata(string $listener, string $method = null)
19+
* @method EntityListeners onClassMetadataNotFound(string $listener, string $method = null)
20+
* @method EntityListeners preFlush(string $listener, string $method = null)
21+
* @method EntityListeners onFlush(string $listener, string $method = null)
22+
* @method EntityListeners postFlush(string $listener, string $method = null)
23+
* @method EntityListeners onClear(string $listener, string $method = null)
24+
*/
25+
class EntityListeners implements Buildable
26+
{
27+
/**
28+
* @var ClassMetadataBuilder
29+
*/
30+
private $builder;
31+
32+
/**
33+
* @var array
34+
*/
35+
private $events = [
36+
Events::preRemove => [],
37+
Events::postRemove => [],
38+
Events::prePersist => [],
39+
Events::postPersist => [],
40+
Events::preUpdate => [],
41+
Events::postUpdate => [],
42+
Events::postLoad => [],
43+
Events::loadClassMetadata => [],
44+
Events::onClassMetadataNotFound => [],
45+
Events::preFlush => [],
46+
Events::onFlush => [],
47+
Events::postFlush => [],
48+
Events::onClear => [],
49+
];
50+
51+
/**
52+
* LifecycleEvents constructor.
53+
*
54+
* @param ClassMetadataBuilder $builder
55+
*/
56+
public function __construct(ClassMetadataBuilder $builder)
57+
{
58+
$this->builder = $builder;
59+
}
60+
61+
/**
62+
* Magically call all methods that match an event name.
63+
*
64+
* @param string $event
65+
* @param array $args
66+
*
67+
* @throws InvalidArgumentException
68+
*
69+
* @return LifecycleEvents
70+
*/
71+
public function __call($event, $args)
72+
{
73+
if (array_key_exists($event, $this->events)) {
74+
array_unshift($args, $event);
75+
76+
return call_user_func_array([$this, 'add'], $args);
77+
}
78+
79+
throw new InvalidArgumentException('Fluent builder method ['.$event.'] does not exist');
80+
}
81+
82+
/**
83+
* @param string $event
84+
* @param string $class
85+
* @param string|null $method
86+
*
87+
* @return EntityListeners
88+
*/
89+
private function add($event, $class, $method = null)
90+
{
91+
$this->events[$event][] = [
92+
'class' => $class,
93+
'method' => $method ?: $event,
94+
];
95+
96+
return $this;
97+
}
98+
99+
/**
100+
* Execute the build process.
101+
*/
102+
public function build()
103+
{
104+
foreach ($this->events as $event => $listeners) {
105+
foreach ($listeners as $listener) {
106+
$this->builder->getClassMetadata()->addEntityListener($event, $listener['class'], $listener['method']);
107+
}
108+
}
109+
}
110+
}

src/Fluent.php

+7
Original file line numberDiff line numberDiff line change
@@ -460,4 +460,11 @@ public function override($name, callable $callback);
460460
* @return Builders\LifecycleEvents
461461
*/
462462
public function events(callable $callback = null);
463+
464+
/**
465+
* @param callable|null $callback
466+
*
467+
* @return Builders\EntityListeners
468+
*/
469+
public function listen(callable $callback = null);
463470
}

tests/Builders/BuilderTest.php

+48-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use LaravelDoctrine\Fluent\Buildable;
1717
use LaravelDoctrine\Fluent\Builders\Builder;
1818
use LaravelDoctrine\Fluent\Builders\Embedded;
19+
use LaravelDoctrine\Fluent\Builders\EntityListeners;
1920
use LaravelDoctrine\Fluent\Builders\Field;
2021
use LaravelDoctrine\Fluent\Builders\Index;
2122
use LaravelDoctrine\Fluent\Builders\Inheritance\Inheritance;
@@ -33,11 +34,12 @@
3334
use LogicException;
3435
use Tests\FakeEntity;
3536
use Tests\Stubs\Embedabbles\StubEmbeddable;
37+
use Tests\Stubs\StubEntityListener;
3638

3739
class BuilderTest extends \PHPUnit_Framework_TestCase
3840
{
3941
use IsMacroable;
40-
42+
4143
/**
4244
* @var ClassMetadataBuilder
4345
*/
@@ -674,6 +676,50 @@ public function test_events_can_be_configured_through_a_callable()
674676
);
675677
}
676678

679+
public function test_can_add_entity_listener()
680+
{
681+
$entityListeners = $this->fluent->listen();
682+
683+
$this->assertInstanceOf(EntityListeners::class, $entityListeners);
684+
$this->assertContains($entityListeners, $this->fluent->getQueued());
685+
}
686+
687+
public function test_entity_listeners_can_be_configured_through_a_callable()
688+
{
689+
$this->fluent->listen(function (EntityListeners $events) {
690+
$events->onFlush(StubEntityListener::class, 'swipeFloor');
691+
$events->postFlush(StubEntityListener::class, 'cleanToilet');
692+
693+
// defaults to the event name as method
694+
$events->onClear(StubEntityListener::class);
695+
});
696+
697+
foreach ($this->fluent->getQueued() as $buildable) {
698+
$buildable->build();
699+
}
700+
701+
$this->assertEquals([
702+
[
703+
'class' => StubEntityListener::class,
704+
'method' => 'swipeFloor',
705+
]
706+
], $this->fluent->getClassMetadata()->entityListeners['onFlush']);
707+
708+
$this->assertEquals([
709+
[
710+
'class' => StubEntityListener::class,
711+
'method' => 'cleanToilet',
712+
]
713+
], $this->fluent->getClassMetadata()->entityListeners['postFlush']);
714+
715+
$this->assertEquals([
716+
[
717+
'class' => StubEntityListener::class,
718+
'method' => 'onClear',
719+
]
720+
], $this->fluent->getClassMetadata()->entityListeners['onClear']);
721+
}
722+
677723
public function test_can_override_an_attribute()
678724
{
679725
$this->fluent->string('name');
@@ -825,7 +871,7 @@ public function test_can_guess_an_embeded_field_name()
825871

826872
public function test_buildable_objects_returned_from_macros_get_queued_and_built()
827873
{
828-
Builder::macro('foo', function(){
874+
Builder::macro('foo', function () {
829875
/** @var Buildable|\Mockery\Mock $buildable */
830876
$buildable = \Mockery::mock(Buildable::class);
831877
$buildable->shouldReceive('build')->once();
+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<?php
2+
3+
namespace Tests\Builders;
4+
5+
use Doctrine\ORM\Events;
6+
use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder;
7+
use Doctrine\ORM\Mapping\ClassMetadataInfo;
8+
use LaravelDoctrine\Fluent\Builders\EntityListeners;
9+
use Symfony\Component\VarDumper\Cloner\Stub;
10+
use Tests\Stubs\Entities\StubEntity;
11+
use Tests\Stubs\StubEntityListener;
12+
13+
class EntityListenersTest extends \PHPUnit_Framework_TestCase
14+
{
15+
/**
16+
* @var EntityListeners
17+
*/
18+
protected $builder;
19+
20+
/**
21+
* @var ClassMetadataBuilder
22+
*/
23+
protected $fluent;
24+
25+
protected function setUp()
26+
{
27+
$this->fluent = new ClassMetadataBuilder(
28+
new ClassMetadataInfo(StubEntity::class)
29+
);
30+
31+
$this->builder = new EntityListeners($this->fluent);
32+
}
33+
34+
/**
35+
* @dataProvider eventsProvider
36+
*
37+
* @param string $event
38+
* @param string $listener
39+
* @param string|null $method
40+
* @param string $expectedMethod
41+
*/
42+
public function test_can_add_event_listeners($event, $listener, $method = null, $expectedMethod)
43+
{
44+
$this->builder->{$event}($listener, $method);
45+
46+
$this->builder->build();
47+
48+
$this->assertTrue(
49+
isset($this->fluent->getClassMetadata()->entityListeners[$event])
50+
);
51+
52+
$this->assertCount(
53+
1, $this->fluent->getClassMetadata()->entityListeners[$event]
54+
);
55+
56+
$this->assertEquals([
57+
[
58+
'class' => $listener,
59+
'method' => $expectedMethod
60+
]
61+
], $this->fluent->getClassMetadata()->entityListeners[$event]);
62+
}
63+
64+
public function test_can_add_multiple_entity_listeners_per_event()
65+
{
66+
$this->builder
67+
->onClear(StubEntityListener::class, 'onClear')
68+
->onClear(StubEntityListener::class, 'handle');
69+
70+
$this->builder->build();
71+
72+
$this->assertTrue(
73+
isset($this->fluent->getClassMetadata()->entityListeners['onClear'])
74+
);
75+
76+
$this->assertCount(
77+
2, $this->fluent->getClassMetadata()->entityListeners['onClear']
78+
);
79+
80+
$this->assertEquals([
81+
[
82+
'class' => StubEntityListener::class,
83+
'method' => 'onClear'
84+
],
85+
[
86+
'class' => StubEntityListener::class,
87+
'method' => 'handle'
88+
]
89+
], $this->fluent->getClassMetadata()->entityListeners['onClear']);
90+
}
91+
92+
/**
93+
* @return array
94+
*/
95+
public function eventsProvider()
96+
{
97+
return [
98+
[Events::preRemove, StubEntityListener::class, 'preRemove', 'preRemove'],
99+
[Events::postRemove, StubEntityListener::class, 'handle', 'handle'],
100+
[Events::prePersist, StubEntityListener::class, 'handle', 'handle'],
101+
[Events::postPersist, StubEntityListener::class, 'handle', 'handle'],
102+
[Events::preUpdate, StubEntityListener::class, 'handle', 'handle'],
103+
[Events::postUpdate, StubEntityListener::class, 'handle', 'handle'],
104+
[Events::postLoad, StubEntityListener::class, 'handle', 'handle'],
105+
[Events::loadClassMetadata, StubEntityListener::class, 'handle', 'handle'],
106+
[Events::onClassMetadataNotFound, StubEntityListener::class, 'handle', 'handle'],
107+
[Events::preFlush, StubEntityListener::class, 'handle', 'handle'],
108+
[Events::onFlush, StubEntityListener::class, 'handle', 'handle'],
109+
[Events::postFlush, StubEntityListener::class, 'handle', 'handle'],
110+
[Events::onClear, StubEntityListener::class, 'handle', 'handle'],
111+
[Events::onClear, StubEntityListener::class, null, 'onClear'],
112+
];
113+
}
114+
}

tests/Stubs/StubEntityListener.php

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace Tests\Stubs;
4+
5+
class StubEntityListener
6+
{
7+
public function handle()
8+
{
9+
}
10+
11+
public function swipeFloor()
12+
{
13+
}
14+
15+
public function cleanToilet()
16+
{
17+
}
18+
19+
public function preRemove()
20+
{
21+
}
22+
23+
public function onClear()
24+
{
25+
}
26+
}

0 commit comments

Comments
 (0)