Skip to content

Commit d01bd45

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

13 files changed

+332
-10
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
;

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/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\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+
}

src/Event/AfterPersist.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\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/Event/BeforeInstantiate.php

+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\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\Event\AfterInstantiate;
15+
use Zenstruck\Foundry\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
}

src/Persistence/PersistentObjectFactory.php

+22-10
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Zenstruck\Foundry\Exception\PersistenceNotAvailable;
1919
use Zenstruck\Foundry\Factory;
2020
use Zenstruck\Foundry\FactoryCollection;
21+
use Zenstruck\Foundry\Event\AfterPersist;
2122
use Zenstruck\Foundry\ObjectFactory;
2223
use Zenstruck\Foundry\Persistence\Exception\NotEnoughObjects;
2324
use Zenstruck\Foundry\Persistence\Exception\RefreshObjectFailed;
@@ -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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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\Events;
15+
16+
use Zenstruck\Foundry\Persistence\PersistentObjectFactory;
17+
use Zenstruck\Foundry\Tests\Fixture\Entity\EntityForEventListeners;
18+
19+
/**
20+
* @extends PersistentObjectFactory<EntityForEventListeners>
21+
*/
22+
final class FactoryWithEventListeners extends PersistentObjectFactory
23+
{
24+
public static function class(): string
25+
{
26+
return EntityForEventListeners::class;
27+
}
28+
29+
/**
30+
* @return array<string, mixed>
31+
*/
32+
protected function defaults(): array
33+
{
34+
return [
35+
'name' => self::faker()->sentence(),
36+
];
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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\Events;
15+
16+
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
17+
use Zenstruck\Foundry\Event\AfterInstantiate;
18+
use Zenstruck\Foundry\Event\AfterPersist;
19+
use Zenstruck\Foundry\Event\BeforeInstantiate;
20+
use Zenstruck\Foundry\Tests\Fixture\Entity\EntityForEventListeners;
21+
22+
final class FoundryEventListener
23+
{
24+
#[AsEventListener]
25+
public function beforeInstantiate(BeforeInstantiate $event): void
26+
{
27+
if (EntityForEventListeners::class !== $event->objectClass) {
28+
return;
29+
}
30+
31+
$event->parameters['name'] = "{$event->parameters['name']}\nBeforeInstantiate";
32+
}
33+
34+
#[AsEventListener]
35+
public function afterInstantiate(AfterInstantiate $event): void
36+
{
37+
if (!$event->object instanceof EntityForEventListeners) {
38+
return;
39+
}
40+
41+
$event->object->name = "{$event->object->name}\nAfterInstantiate";
42+
}
43+
44+
#[AsEventListener]
45+
public function afterPersist(AfterPersist $event): void
46+
{
47+
if (!$event->object instanceof EntityForEventListeners) {
48+
return;
49+
}
50+
51+
$event->object->name = "{$event->object->name}\nAfterPersist";
52+
}
53+
}

tests/Fixture/TestKernel.php

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Zenstruck\Foundry\ORM\ResetDatabase\ResetDatabaseMode;
1818
use Zenstruck\Foundry\Tests\Fixture\Factories\ArrayFactory;
1919
use Zenstruck\Foundry\Tests\Fixture\Factories\Object1Factory;
20+
use Zenstruck\Foundry\Tests\Fixture\Events\FoundryEventListener;
2021
use Zenstruck\Foundry\Tests\Fixture\Stories\ServiceStory;
2122

2223
/**
@@ -50,5 +51,7 @@ protected function configureContainer(ContainerBuilder $c, LoaderInterface $load
5051
$c->register(ArrayFactory::class)->setAutowired(true)->setAutoconfigured(true);
5152
$c->register(Object1Factory::class)->setAutowired(true)->setAutoconfigured(true);
5253
$c->register(ServiceStory::class)->setAutowired(true)->setAutoconfigured(true);
54+
55+
$c->register(FoundryEventListener::class)->setAutowired(true)->setAutoconfigured(true);
5356
}
5457
}

0 commit comments

Comments
 (0)