Skip to content

Commit 766887d

Browse files
committed
feat: dispatch events
1 parent 5681bb9 commit 766887d

14 files changed

+390
-22
lines changed

composer.json

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"doctrine/persistence": "^2.0|^3.0",
2121
"fakerphp/faker": "^1.23",
2222
"symfony/deprecation-contracts": "^2.2|^3.0",
23+
"symfony/event-dispatcher": "^6.4|^7.0",
2324
"symfony/framework-bundle": "^6.4|^7.0",
2425
"symfony/property-access": "^6.4|^7.0",
2526
"symfony/property-info": "^6.4|^7.0",

config/services.php

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
service('.zenstruck_foundry.instantiator'),
3333
service('.zenstruck_foundry.story_registry'),
3434
service('.zenstruck_foundry.persistence_manager')->nullOnInvalid(),
35+
service('event_dispatcher'),
3536
])
3637
->public()
3738
;

docs/index.rst

+58-12
Original file line numberDiff line numberDiff line change
@@ -552,10 +552,10 @@ random data for your factories:
552552
faker:
553553
service: my_faker # service id for your own instance of Faker\Generator
554554
555-
Events / Hooks
556-
~~~~~~~~~~~~~~
555+
Hooks
556+
~~~~~
557557

558-
The following events can be added to factories. Multiple event callbacks can be added, they are run in the order
558+
The following events can be added to factories. Multiple hooks callbacks can be added, they are run in the order
559559
they were added.
560560

561561
::
@@ -564,28 +564,28 @@ they were added.
564564
use Zenstruck\Foundry\Proxy;
565565

566566
PostFactory::new()
567-
->beforeInstantiate(function(array $attributes, string $class, static $factory): array {
568-
// $attributes is what will be used to instantiate the object, manipulate as required
567+
->beforeInstantiate(function(array $parameters, string $class, static $factory): array {
568+
// $parameters is what will be used to instantiate the object, manipulate as required
569569
// $class is the class of the object being instantiated
570570
// $factory is the factory instance which creates the object
571-
$attributes['title'] = 'Different title';
571+
$parameters['title'] = 'Different title';
572572

573-
return $attributes; // must return the final $attributes
573+
return $parameters; // must return the final $parameters
574574
})
575-
->afterInstantiate(function(Post $object, array $attributes, static $factory): void {
575+
->afterInstantiate(function(Post $object, array $parameters, static $factory): void {
576576
// $object is the instantiated object
577-
// $attributes contains the attributes used to instantiate the object and any extras
577+
// $parameters contains the attributes used to instantiate the object and any extras
578578
// $factory is the factory instance which creates the object
579579
})
580-
->afterPersist(function(Post $object, array $attributes, static $factory) {
580+
->afterPersist(function(Post $object, array $parameters, static $factory) {
581581
// this event is only called if the object was persisted
582582
// $object is the persisted Post object
583-
// $attributes contains the attributes used to instantiate the object and any extras
583+
// $parameters contains the attributes used to instantiate the object and any extras
584584
// $factory is the factory instance which creates the object
585585
})
586586

587587
// multiple events are allowed
588-
->beforeInstantiate(function($attributes) { return $attributes; })
588+
->beforeInstantiate(function($parameters) { return $parameters; })
589589
->afterInstantiate(function() {})
590590
->afterPersist(function() {})
591591
;
@@ -603,6 +603,52 @@ You can also add hooks directly in your factory class:
603603

604604
Read `Initialization`_ to learn more about the ``initialize()`` method.
605605

606+
Events
607+
~~~~~~
608+
609+
In addition to hooks, Foundry also leverages `symfony/event-dispatcher` and dispatches events that you can listen to,
610+
allowing to create hooks globally, as Symfony services:
611+
612+
::
613+
614+
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
615+
use Zenstruck\Foundry\Object\Event\AfterInstantiate;
616+
use Zenstruck\Foundry\Object\Event\BeforeInstantiate;
617+
use Zenstruck\Foundry\Persistence\Event\AfterPersist;
618+
619+
final class FoundryEventListener
620+
{
621+
#[AsEventListener]
622+
public function beforeInstantiate(BeforeInstantiate $event): void
623+
{
624+
// do something before the object is instantiated:
625+
// $event->parameters is what will be used to instantiate the object, manipulate as required
626+
// $event->objectClass is the class of the object being instantiated
627+
// $event->factory is the factory instance which creates the object
628+
}
629+
630+
#[AsEventListener]
631+
public function afterInstantiate(AfterInstantiate $event): void
632+
{
633+
// $event->object is the instantiated object
634+
// $event->parameters contains the attributes used to instantiate the object and any extras
635+
// $event->factory is the factory instance which creates the object
636+
}
637+
638+
#[AsEventListener]
639+
public function afterPersist(AfterPersist $event): void
640+
{
641+
// this event is only called if the object was persisted
642+
// $event->object is the persisted Post object
643+
// $event->parameters contains the attributes used to instantiate the object and any extras
644+
// $event->factory is the factory instance which creates the object
645+
}
646+
}
647+
648+
.. versionadded:: 2.4
649+
650+
Those events are triggered since Foundry 2.4.
651+
606652
Initialization
607653
~~~~~~~~~~~~~~
608654

src/Configuration.php

+12
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;
@@ -51,6 +52,7 @@ public function __construct(
5152
callable $instantiator,
5253
public readonly StoryRegistry $stories,
5354
private readonly ?PersistenceManager $persistence = null,
55+
private readonly ?EventDispatcherInterface $eventDispatcher = null,
5456
) {
5557
$this->instantiator = $instantiator;
5658
}
@@ -80,6 +82,16 @@ public function assertPersistenceEnabled(): void
8082
}
8183
}
8284

85+
public function hasEventDispatcher(): bool
86+
{
87+
return (bool) $this->eventDispatcher;
88+
}
89+
90+
public function eventDispatcher(): EventDispatcherInterface
91+
{
92+
return $this->eventDispatcher ?? throw new \RuntimeException('No event dispatcher configured.');
93+
}
94+
8395
public function inADataProvider(): bool
8496
{
8597
return $this->bootedForDataProvider;

src/Object/Event/AfterInstantiate.php

+34
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+
}
+35
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

+29
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

1618
/**
@@ -102,4 +104,31 @@ public function afterInstantiate(callable $callback): static
102104

103105
return $clone;
104106
}
107+
108+
/**
109+
* @internal
110+
*/
111+
protected function initializeInternal(): static
112+
{
113+
if (!Configuration::instance()->hasEventDispatcher()) {
114+
return $this;
115+
}
116+
117+
return $this->beforeInstantiate(
118+
static function(array $parameters, string $objectClass, self $usedFactory): array {
119+
Configuration::instance()->eventDispatcher()->dispatch(
120+
$hook = new BeforeInstantiate($parameters, $objectClass, $usedFactory)
121+
);
122+
123+
return $hook->parameters;
124+
}
125+
)
126+
->afterInstantiate(
127+
static function(object $object, array $parameters, self $usedFactory): void {
128+
Configuration::instance()->eventDispatcher()->dispatch(
129+
new AfterInstantiate($object, $parameters, $usedFactory)
130+
);
131+
}
132+
);
133+
}
105134
}
+34
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/PersistentObjectFactory.php

+22-10
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Zenstruck\Foundry\Factory;
2020
use Zenstruck\Foundry\FactoryCollection;
2121
use Zenstruck\Foundry\ObjectFactory;
22+
use Zenstruck\Foundry\Persistence\Event\AfterPersist;
2223
use Zenstruck\Foundry\Persistence\Exception\NotEnoughObjects;
2324
use Zenstruck\Foundry\Persistence\Exception\RefreshObjectFailed;
2425

@@ -406,19 +407,30 @@ final protected function isPersisting(): bool
406407
return $this->persistMode()->isPersisting();
407408
}
408409

409-
/**
410-
* Schedule any new object for insert right after instantiation.
411-
* @internal
412-
*/
413410
final protected function initializeInternal(): static
414411
{
415-
return $this->afterInstantiate(
416-
static function(object $object, array $parameters, PersistentObjectFactory $factory): void {
417-
if (!$factory->isPersisting()) {
418-
return;
412+
// Schedule any new object for insert right after instantiation
413+
$factory = parent::initializeInternal()
414+
->afterInstantiate(
415+
static function(object $object, array $parameters, PersistentObjectFactory $factoryUsed): void {
416+
if (!$factoryUsed->isPersisting()) {
417+
return;
418+
}
419+
420+
Configuration::instance()->persistence()->scheduleForInsert($object);
419421
}
420-
421-
Configuration::instance()->persistence()->scheduleForInsert($object);
422+
);
423+
424+
if (!Configuration::instance()->hasEventDispatcher()) {
425+
return $factory;
426+
};
427+
428+
// Dispatch event after persist
429+
return $factory->afterPersist(
430+
static function(object $object, array $parameters, self $factoryUsed): void {
431+
Configuration::instance()->eventDispatcher()->dispatch(
432+
new AfterPersist($object, $parameters, $factoryUsed)
433+
);
422434
}
423435
);
424436
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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\Tests\Fixture\Entity;
15+
16+
use Doctrine\ORM\Mapping as ORM;
17+
use Zenstruck\Foundry\Tests\Fixture\Model\Base;
18+
19+
#[ORM\Entity]
20+
class EntityForEventListeners extends Base
21+
{
22+
public function __construct(
23+
#[ORM\Column()]
24+
public string $name,
25+
) {
26+
}
27+
}

0 commit comments

Comments
 (0)