Skip to content

Commit bf4549c

Browse files
authored
feat(2.8): dispatch events (#974)
1 parent 8516af1 commit bf4549c

18 files changed

+456
-12
lines changed

.roave-backward-compatibility-check.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@
77
<ignored-regex>#\[BC\] SKIPPED: Roave\\BetterReflection\\Reflection\\ReflectionClass "Psalm\\Plugin\\PluginEntryPointInterface" could not be found in the located source#</ignored-regex>
88
<ignored-regex>#\[BC\] SKIPPED: Roave\\BetterReflection\\Reflection\\ReflectionClass "Psalm\\Plugin\\EventHandler\\AfterMethodCallAnalysisInterface" could not be found in the located source#</ignored-regex>
99
<ignored-regex>#(.*)Zenstruck\\Foundry\\Utils\\Rector(.*)#</ignored-regex>
10+
<ignored-regex>#(.*)initializeInternal(.*)#</ignored-regex>
1011
</baseline>
1112
</roave-bc-check>

composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"symfony/browser-kit": "^6.4|^7.0|^8.0",
4141
"symfony/console": "^6.4|^7.0|^8.0",
4242
"symfony/dotenv": "^6.4|^7.0|^8.0",
43+
"symfony/event-dispatcher": "^6.4|^7.0",
4344
"symfony/framework-bundle": "^6.4|^7.0|^8.0",
4445
"symfony/maker-bundle": "^1.55",
4546
"symfony/phpunit-bridge": "^6.4|^7.0|^8.0",
@@ -83,6 +84,7 @@
8384
},
8485
"conflict": {
8586
"doctrine/persistence": "<2.0",
87+
"symfony/event-dispatcher": "<6.4",
8688
"symfony/framework-bundle": "<6.4"
8789
},
8890
"extra": {

config/services.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
service('.zenstruck_foundry.in_memory.repository_registry'),
4747
service('.foundry.persistence.objects_tracker')->nullOnInvalid(),
4848
param('zenstruck_foundry.enable_auto_refresh_with_lazy_objects'),
49+
service('event_dispatcher')->nullOnInvalid(),
4950
])
5051
->public()
5152
;

docs/index.rst

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,57 @@ You can also add hooks directly in your factory class:
664664

665665
Read `Initialization`_ to learn more about the ``initialize()`` method.
666666

667+
Events
668+
~~~~~~
669+
670+
In addition to hooks, Foundry also leverages `symfony/event-dispatcher` and dispatches events that you can listen to,
671+
allowing to create hooks globally, as Symfony services:
672+
673+
::
674+
675+
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
676+
use Zenstruck\Foundry\Object\Event\AfterInstantiate;
677+
use Zenstruck\Foundry\Object\Event\BeforeInstantiate;
678+
use Zenstruck\Foundry\Persistence\Event\AfterPersist;
679+
680+
final class FoundryEventListener
681+
{
682+
#[AsEventListener]
683+
public function beforeInstantiate(BeforeInstantiate $event): void
684+
{
685+
// do something before the object is instantiated:
686+
// $event->parameters is what will be used to instantiate the object, manipulate as required
687+
// $event->objectClass is the class of the object being instantiated
688+
// $event->factory is the factory instance which creates the object
689+
}
690+
691+
#[AsEventListener]
692+
public function afterInstantiate(AfterInstantiate $event): void
693+
{
694+
// $event->object is the instantiated object
695+
// $event->parameters contains the attributes used to instantiate the object and any extras
696+
// $event->factory is the factory instance which creates the object
697+
}
698+
699+
#[AsEventListener]
700+
public function afterPersist(AfterPersist $event): void
701+
{
702+
// this event is only called if the object was persisted
703+
// $event->object is the persisted Post object
704+
// $event->parameters contains the attributes used to instantiate the object and any extras
705+
// $event->factory is the factory instance which creates the object
706+
}
707+
}
708+
709+
.. versionadded:: 2.8
710+
711+
Those events are triggered since Foundry 2.8.
712+
713+
.. note::
714+
715+
If you want to save data to the database in an ``AfterPersist`` listener, Foundry won't flush automatically, and you
716+
will need to explicitly call ``EntityManager::flush()`` inside the listener.
717+
667718
Initialization
668719
~~~~~~~~~~~~~~
669720

src/Configuration.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Zenstruck\Foundry;
1313

1414
use Faker;
15+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
1516
use Zenstruck\Foundry\Exception\FactoriesTraitNotUsed;
1617
use Zenstruck\Foundry\Exception\FoundryNotBooted;
1718
use Zenstruck\Foundry\Exception\PersistenceDisabled;
@@ -63,6 +64,7 @@ public function __construct(
6364
public readonly ?InMemoryRepositoryRegistry $inMemoryRepositoryRegistry = null,
6465
public readonly ?PersistedObjectsTracker $persistedObjectsTracker = null,
6566
private readonly bool $enableAutoRefreshWithLazyObjects = false,
67+
private readonly ?EventDispatcherInterface $eventDispatcher = null,
6668
) {
6769
if (null === self::$instance) {
6870
$this->faker->seed(self::fakerSeed($forcedFakerSeed));
@@ -106,6 +108,16 @@ public function assertPersistenceEnabled(): void
106108
}
107109
}
108110

111+
public function hasEventDispatcher(): bool
112+
{
113+
return (bool) $this->eventDispatcher;
114+
}
115+
116+
public function eventDispatcher(): EventDispatcherInterface
117+
{
118+
return $this->eventDispatcher ?? throw new \RuntimeException('No event dispatcher configured.');
119+
}
120+
109121
public function inADataProvider(): bool
110122
{
111123
return $this->bootedForDataProvider;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the zenstruck/foundry package.
7+
*
8+
* (c) Kevin Bond <[email protected]>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Zenstruck\Foundry\Object\Event;
15+
16+
use Zenstruck\Foundry\Factory;
17+
use Zenstruck\Foundry\ObjectFactory;
18+
19+
/**
20+
* @author Nicolas PHILIPPE <[email protected]>
21+
*
22+
* @phpstan-import-type Parameters from Factory
23+
*/
24+
final class AfterInstantiate
25+
{
26+
public function __construct(
27+
public readonly object $object,
28+
/** @phpstan-var Parameters */
29+
public readonly array $parameters,
30+
/** @var ObjectFactory<object> */
31+
public readonly ObjectFactory $factory,
32+
) {
33+
}
34+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the zenstruck/foundry package.
7+
*
8+
* (c) Kevin Bond <[email protected]>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Zenstruck\Foundry\Object\Event;
15+
16+
use Zenstruck\Foundry\Factory;
17+
use Zenstruck\Foundry\ObjectFactory;
18+
19+
/**
20+
* @author Nicolas PHILIPPE <[email protected]>
21+
*
22+
* @phpstan-import-type Parameters from Factory
23+
*/
24+
final class BeforeInstantiate
25+
{
26+
public function __construct(
27+
/** @phpstan-var Parameters */
28+
public array $parameters,
29+
/** @var class-string */
30+
public readonly string $objectClass,
31+
/** @var ObjectFactory<object> */
32+
public readonly ObjectFactory $factory,
33+
) {
34+
}
35+
}

src/ObjectFactory.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Zenstruck\Foundry;
1313

14+
use Zenstruck\Foundry\Object\Event\AfterInstantiate;
15+
use Zenstruck\Foundry\Object\Event\BeforeInstantiate;
1416
use Zenstruck\Foundry\Object\Instantiator;
1517
use Zenstruck\Foundry\Persistence\ProxyGenerator;
1618

@@ -205,6 +207,33 @@ final protected function normalizeReusedAttributes(): array
205207
return $attributes;
206208
}
207209

210+
/**
211+
* @internal
212+
*/
213+
protected function initializeInternal(): static
214+
{
215+
if (!Configuration::isBooted() || !Configuration::instance()->hasEventDispatcher()) {
216+
return $this;
217+
}
218+
219+
return $this->beforeInstantiate(
220+
static function(array $parameters, string $objectClass, self $usedFactory): array {
221+
Configuration::instance()->eventDispatcher()->dispatch(
222+
$hook = new BeforeInstantiate($parameters, $objectClass, $usedFactory)
223+
);
224+
225+
return $hook->parameters;
226+
}
227+
)
228+
->afterInstantiate(
229+
static function(object $object, array $parameters, self $usedFactory): void {
230+
Configuration::instance()->eventDispatcher()->dispatch(
231+
new AfterInstantiate($object, $parameters, $usedFactory)
232+
);
233+
}
234+
);
235+
}
236+
208237
/**
209238
* @return list<object>
210239
* @internal
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the zenstruck/foundry package.
7+
*
8+
* (c) Kevin Bond <[email protected]>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Zenstruck\Foundry\Persistence\Event;
15+
16+
use Zenstruck\Foundry\Factory;
17+
use Zenstruck\Foundry\Persistence\PersistentObjectFactory;
18+
19+
/**
20+
* @author Nicolas PHILIPPE <[email protected]>
21+
*
22+
* @phpstan-import-type Parameters from Factory
23+
*/
24+
final class AfterPersist
25+
{
26+
public function __construct(
27+
public readonly object $object,
28+
/** @phpstan-var Parameters */
29+
public readonly array $parameters,
30+
/** @var PersistentObjectFactory<object> */
31+
public readonly PersistentObjectFactory $factory,
32+
) {
33+
}
34+
}

src/Persistence/PersistenceManager.php

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ final class PersistenceManager
3535
private bool $flush = true;
3636
private bool $persist = true;
3737

38-
/** @var list<callable():void> */
38+
/** @var list<callable():bool> */
3939
private array $afterPersistCallbacks = [];
4040

4141
/**
@@ -79,9 +79,9 @@ public function save(object $object): object
7979
$om->persist($object);
8080
$this->flush($om);
8181

82-
$callbacksCalled = $this->callPostPersistCallbacks();
82+
$shouldFlush = $this->callPostPersistCallbacks();
8383

84-
if ($callbacksCalled) {
84+
if ($shouldFlush) {
8585
$this->flush($om);
8686
}
8787

@@ -96,7 +96,7 @@ public function save(object $object): object
9696
* @template T of object
9797
*
9898
* @param T $object
99-
* @param list<callable():void> $afterPersistCallbacks
99+
* @param list<callable():bool> $afterPersistCallbacks
100100
*
101101
* @return T
102102
*/
@@ -445,11 +445,15 @@ private function callPostPersistCallbacks(): bool
445445
$afterPersistCallbacks = $this->afterPersistCallbacks;
446446
$this->afterPersistCallbacks = [];
447447

448+
$shouldFlush = false;
449+
448450
foreach ($afterPersistCallbacks as $afterPersistCallback) {
449-
$afterPersistCallback();
451+
if ($afterPersistCallback()) {
452+
$shouldFlush = true;
453+
}
450454
}
451455

452-
return true;
456+
return $shouldFlush;
453457
}
454458

455459
/**

0 commit comments

Comments
 (0)