Skip to content

Commit 9f0f8e1

Browse files
committed
add events
1 parent 02a010a commit 9f0f8e1

File tree

8 files changed

+182
-11
lines changed

8 files changed

+182
-11
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace JG\BatchEntityImportBundle\Event;
6+
7+
class RecordImportedSuccessfullyEvent
8+
{
9+
public function __construct(readonly public string $class, readonly public string $id)
10+
{
11+
}
12+
}

src/Model/Configuration/AbstractImportConfiguration.php

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Doctrine\DBAL\Exception;
88
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
99
use Doctrine\ORM\EntityManagerInterface;
10+
use JG\BatchEntityImportBundle\Event\RecordImportedSuccessfullyEvent;
1011
use JG\BatchEntityImportBundle\Exception\DatabaseException;
1112
use JG\BatchEntityImportBundle\Exception\DatabaseNotUniqueDataException;
1213
use JG\BatchEntityImportBundle\Exception\MatrixRecordInvalidDataTypeException;
@@ -16,11 +17,14 @@
1617
use JG\BatchEntityImportBundle\Model\Matrix\MatrixRecord;
1718
use JG\BatchEntityImportBundle\Utils\ColumnNameHelper;
1819
use Knp\DoctrineBehaviors\Contract\Entity\TranslatableInterface;
20+
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
1921
use TypeError;
2022

2123
abstract class AbstractImportConfiguration implements ImportConfigurationInterface
2224
{
23-
public function __construct(private readonly EntityManagerInterface $em)
25+
protected array $updatedEntities = [];
26+
27+
public function __construct(private readonly EntityManagerInterface $em, private readonly EventDispatcherInterface $eventDispatcher)
2428
{
2529
}
2630

@@ -96,19 +100,33 @@ protected function prepareRecord(MatrixRecord $record, array $headerInfo): void
96100
if (\interface_exists(TranslatableInterface::class) && $entity instanceof TranslatableInterface) {
97101
$entity->mergeNewTranslations();
98102
}
103+
104+
$this->updatedEntities[] = $entity;
99105
}
100106

101107
protected function save(): void
102108
{
103109
try {
104110
$this->em->flush();
111+
$this->dispatchEvents();
105112
} catch (UniqueConstraintViolationException) {
106113
throw new DatabaseNotUniqueDataException();
107114
} catch (Exception) {
108115
throw new DatabaseException();
109116
}
110117
}
111118

119+
protected function dispatchEvents(): void
120+
{
121+
foreach ($this->updatedEntities as $entity) {
122+
$identifierValues = $this->em->getUnitOfWork()->getEntityIdentifier($entity);
123+
124+
$this->eventDispatcher->dispatch(
125+
new RecordImportedSuccessfullyEvent($this->getEntityClassName(), (string) \reset($identifierValues)),
126+
);
127+
}
128+
}
129+
112130
protected function getEntity(MatrixRecord $record): object
113131
{
114132
return $record->getEntity() ?: $this->getNewEntity($record);

tests/Controller/ImportControllerTraitTest.php

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66

77
use Doctrine\ORM\EntityRepository;
88
use Generator;
9+
use JG\BatchEntityImportBundle\Event\RecordImportedSuccessfullyEvent;
910
use JG\BatchEntityImportBundle\Tests\DatabaseLoader;
1011
use JG\BatchEntityImportBundle\Tests\Fixtures\Entity\TestEntity;
12+
use JG\BatchEntityImportBundle\Tests\Fixtures\Event\TestableEventDispatcher;
1113
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
1214
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
1315
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
@@ -17,7 +19,8 @@ class ImportControllerTraitTest extends WebTestCase
1719
private const DEFAULT_RECORDS_NUMBER = 20;
1820
private const NEW_RECORDS_NUMBER = 30;
1921
private const URL = '/jg_batch_entity_import_bundle/import';
20-
protected KernelBrowser $client;
22+
private KernelBrowser $client;
23+
private readonly TestableEventDispatcher $eventDispatcher;
2124

2225
protected function setUp(): void
2326
{
@@ -26,6 +29,10 @@ protected function setUp(): void
2629
$databaseLoader = self::$kernel->getContainer()->get(DatabaseLoader::class);
2730
$databaseLoader->reload();
2831
$databaseLoader->loadFixtures();
32+
33+
$this->eventDispatcher = self::$kernel->getContainer()->get(TestableEventDispatcher::class);
34+
$this->assertInstanceOf(TestableEventDispatcher::class, $this->eventDispatcher);
35+
$this->eventDispatcher->resetDispatchedEvents();
2936
}
3037

3138
public function testInsertNewData(): void
@@ -51,6 +58,8 @@ public function testInsertNewData(): void
5158
$this->assertEntityValues(['test2', 'lorem ipsum 2', 'qwerty2', ['arr_val_1', 'arr_val_2', 'arr_val_3']], $newEntityId2);
5259
$this->assertEntityValues(['test3', 'lorem ipsum 3', 'qwerty3', []], $newEntityId3);
5360
$this->assertEntityValues(['test4', 'lorem ipsum 4', 'qwerty4', ['arr_val_1', '']], $newEntityId4);
61+
62+
$this->checkDispatchedEvents(self::NEW_RECORDS_NUMBER);
5463
}
5564

5665
/**
@@ -82,6 +91,8 @@ public function testUpdateExistingRecord(
8291

8392
$this->checkData(0);
8493
$this->assertEntityValues($expectedValuesAfterChange, $updatedEntityId);
94+
95+
$this->checkDispatchedEvents(1);
8596
}
8697

8798
public static function updateRecordDataProvider(): Generator
@@ -116,8 +127,10 @@ public function testDuplicationFoundInDatabase(): void
116127

117128
$this->checkData();
118129
$this->assertEntityValues(['test2', 'lorem ipsum 2', 'qwerty2', ['arr_val_1', 'arr_val_2', 'arr_val_3']], $updatedEntityId);
130+
$this->checkDispatchedEvents(self::NEW_RECORDS_NUMBER);
119131

120132
// update existing data
133+
$this->eventDispatcher->resetDispatchedEvents();
121134
$this->submitSelectFileForm(__DIR__ . '/../Fixtures/Resources/test_updated_data.csv');
122135

123136
$this->client->submitForm('btn-submit', [
@@ -142,6 +155,8 @@ public function testDuplicationFoundInDatabase(): void
142155
);
143156
self::assertStringContainsString('Such entity already exists for the same values of fields: test-private-property2.', $response->getContent());
144157
self::assertCount(self::DEFAULT_RECORDS_NUMBER + self::NEW_RECORDS_NUMBER, $this->getRepository()->findAll());
158+
159+
$this->checkDispatchedEvents(0);
145160
}
146161

147162
public function testImportFileWrongExtension(): void
@@ -151,6 +166,8 @@ public function testImportFileWrongExtension(): void
151166

152167
self::assertStringContainsString('Wrong file extension.', $this->client->getResponse()->getContent());
153168
self::assertStringContainsString('id="file_import_file"', $this->client->getResponse()->getContent());
169+
170+
$this->checkDispatchedEvents(0);
154171
}
155172

156173
public function testInvalidDataTypeFlashMessage(): void
@@ -160,6 +177,8 @@ public function testInvalidDataTypeFlashMessage(): void
160177

161178
$this->client->submitForm('btn-submit');
162179
self::assertStringContainsString('Invalid type of data. Probably missing validation.', $this->client->getResponse()->getContent());
180+
181+
$this->checkDispatchedEvents(0);
163182
}
164183

165184
public function testImportConfigurationServiceNotFound(): void
@@ -168,6 +187,8 @@ public function testImportConfigurationServiceNotFound(): void
168187
$this->expectException(ServiceNotFoundException::class);
169188
$this->client->request('GET', '/jg_batch_entity_import_bundle/import_no_service');
170189
$this->client->submitForm('btn-submit', ['file_import[file]' => __DIR__ . '/../Fixtures/Resources/test.csv']);
190+
191+
$this->checkDispatchedEvents(0);
171192
}
172193

173194
private function submitSelectFileForm(string $uploadedFile): void
@@ -206,4 +227,15 @@ private function getRepository(): EntityRepository
206227
{
207228
return self::$kernel->getContainer()->get('doctrine.orm.entity_manager')->getRepository(TestEntity::class);
208229
}
230+
231+
private function checkDispatchedEvents(int $expectedNumber): void
232+
{
233+
$dispatchedEvents = $this->eventDispatcher->getEventsFor(RecordImportedSuccessfullyEvent::class);
234+
self::assertCount($expectedNumber, $dispatchedEvents);
235+
foreach ($dispatchedEvents as $event) {
236+
self::assertInstanceOf(RecordImportedSuccessfullyEvent::class, $event);
237+
self::assertSame(TestEntity::class, $event->class);
238+
self::assertNotEmpty($event->id);
239+
}
240+
}
209241
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace JG\BatchEntityImportBundle\Tests\Fixtures\Event;
6+
7+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
8+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
9+
10+
class TestableEventDispatcher implements EventDispatcherInterface
11+
{
12+
private EventDispatcherInterface $decorated;
13+
private static $dispatchedEvents = [];
14+
15+
public function __construct(EventDispatcherInterface $decorated)
16+
{
17+
$this->decorated = $decorated;
18+
}
19+
20+
public function dispatch(object $event, ?string $eventName = null): object
21+
{
22+
$eventName = $eventName ?? get_class($event);
23+
self::$dispatchedEvents[$eventName][] = $event;
24+
25+
return $this->decorated->dispatch($event, $eventName);
26+
}
27+
28+
public function getDispatchedEvents(): array
29+
{
30+
return self::$dispatchedEvents;
31+
}
32+
33+
public function hasEvent(string $eventName): bool
34+
{
35+
return array_key_exists($eventName, self::$dispatchedEvents);
36+
}
37+
38+
public function getEventsFor(string $eventName): array
39+
{
40+
return self::$dispatchedEvents[$eventName] ?? [];
41+
}
42+
43+
public function resetDispatchedEvents(): void
44+
{
45+
self::$dispatchedEvents = [];
46+
}
47+
48+
public function addListener(string $eventName, callable|array $listener, int $priority = 0): void
49+
{
50+
$this->decorated->addListener($eventName, $listener, $priority);
51+
}
52+
53+
public function addSubscriber(EventSubscriberInterface $subscriber): void
54+
{
55+
$this->decorated->addSubscriber($subscriber);
56+
}
57+
58+
public function removeListener(string $eventName, callable $listener): void
59+
{
60+
$this->decorated->removeListener($eventName, $listener);
61+
}
62+
63+
public function removeSubscriber(EventSubscriberInterface $subscriber): void
64+
{
65+
$this->decorated->removeSubscriber($subscriber);
66+
}
67+
68+
public function getListeners(?string $eventName = null): array
69+
{
70+
return $this->decorated->getListeners($eventName);
71+
}
72+
73+
public function getListenerPriority(string $eventName, callable $listener): ?int
74+
{
75+
return $this->decorated->getListenerPriority($eventName, $listener);
76+
}
77+
78+
public function hasListeners(?string $eventName = null): bool
79+
{
80+
return $this->decorated->hasListeners($eventName);
81+
}
82+
}

tests/Form/Type/MatrixRecordTypeTest.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use JG\BatchEntityImportBundle\Tests\Fixtures\Configuration\BaseConfiguration;
1212
use JG\BatchEntityImportBundle\Tests\Fixtures\Configuration\FieldsTypeConfiguration;
1313
use JG\BatchEntityImportBundle\Tests\Fixtures\Entity\TestEntity;
14+
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
1415
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
1516
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
1617
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
@@ -32,7 +33,7 @@ protected function setUp(): void
3233
public function testValidFormWithBaseConfig(): void
3334
{
3435
$data = $this->getRecordData();
35-
$configuration = new BaseConfiguration($this->createMock(EntityManagerInterface::class));
36+
$configuration = new BaseConfiguration($this->createMock(EntityManagerInterface::class), $this->createMock(EventDispatcherInterface::class));
3637
$matrixRecord = new MatrixRecord($data);
3738

3839
$form = $this->factory->create(MatrixRecordType::class, $matrixRecord, ['configuration' => $configuration]);
@@ -50,7 +51,7 @@ public function testValidFormWithBaseConfig(): void
5051
public function testValidFormWithFieldsConfig(): void
5152
{
5253
$data = $this->getRecordData();
53-
$configuration = new FieldsTypeConfiguration($this->createMock(EntityManagerInterface::class));
54+
$configuration = new FieldsTypeConfiguration($this->createMock(EntityManagerInterface::class), $this->createMock(EventDispatcherInterface::class));
5455
$matrixRecord = new MatrixRecord($data);
5556

5657
$form = $this->factory->create(MatrixRecordType::class, $matrixRecord, ['configuration' => $configuration]);

tests/Form/Type/MatrixTypeTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use JG\BatchEntityImportBundle\Model\Configuration\ImportConfigurationInterface;
1010
use JG\BatchEntityImportBundle\Model\Matrix\Matrix;
1111
use JG\BatchEntityImportBundle\Tests\Fixtures\Configuration\BaseConfiguration;
12+
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
1213
use Symfony\Component\Form\Test\TypeTestCase;
1314
use Symfony\Component\OptionsResolver\Exception\MissingOptionsException;
1415

@@ -20,7 +21,7 @@ protected function setUp(): void
2021
{
2122
parent::setUp();
2223

23-
$this->baseConfig = new BaseConfiguration($this->createMock(EntityManagerInterface::class));
24+
$this->baseConfig = new BaseConfiguration($this->createMock(EntityManagerInterface::class), $this->createMock(EventDispatcherInterface::class));
2425
}
2526

2627
public function testValidForm(): void

tests/Model/Configuration/ImportConfigurationTest.php

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,30 @@
66

77
use Doctrine\ORM\EntityManagerInterface;
88
use Generator;
9+
use JG\BatchEntityImportBundle\Event\RecordImportedSuccessfullyEvent;
910
use JG\BatchEntityImportBundle\Exception\DatabaseNotUniqueDataException;
1011
use JG\BatchEntityImportBundle\Exception\MatrixRecordInvalidDataTypeException;
1112
use JG\BatchEntityImportBundle\Model\Configuration\AbstractImportConfiguration;
1213
use JG\BatchEntityImportBundle\Model\Matrix\Matrix;
1314
use JG\BatchEntityImportBundle\Tests\DatabaseLoader;
1415
use JG\BatchEntityImportBundle\Tests\Fixtures\Configuration\BaseConfiguration;
1516
use JG\BatchEntityImportBundle\Tests\Fixtures\Entity\TestEntity;
17+
use JG\BatchEntityImportBundle\Tests\Fixtures\Event\TestableEventDispatcher;
1618
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
1719

1820
class ImportConfigurationTest extends WebTestCase
1921
{
2022
protected ?EntityManagerInterface $entityManager;
23+
protected TestableEventDispatcher $eventDispatcher;
2124

2225
protected function setUp(): void
2326
{
2427
self::bootKernel();
2528

2629
$this->entityManager = self::$kernel->getContainer()->get('doctrine.orm.entity_manager');
30+
$this->eventDispatcher = self::$kernel->getContainer()->get(TestableEventDispatcher::class);
31+
$this->eventDispatcher->resetDispatchedEvents();
32+
2733
$databaseLoader = self::$kernel->getContainer()->get(DatabaseLoader::class);
2834
$databaseLoader->reload();
2935
}
@@ -51,7 +57,7 @@ public function testItemImportedSuccessfully(array $header, array $records): voi
5157

5258
$matrix = new Matrix($header, $records);
5359

54-
$config = new BaseConfiguration($this->entityManager);
60+
$config = new BaseConfiguration($this->entityManager, $this->eventDispatcher);
5561
$config->import($matrix);
5662

5763
self::assertCount(2, $repository->findAll());
@@ -69,6 +75,17 @@ public function testItemImportedSuccessfully(array $header, array $records): voi
6975
self::assertNotEmpty($item);
7076
self::assertSame('value_4', $item->getTestPrivateProperty());
7177
self::assertSame('public_value_2', $item->testPublicProperty);
78+
79+
$dispatchedEvents = $this->eventDispatcher->getEventsFor(RecordImportedSuccessfullyEvent::class);
80+
self::assertCount(2, $dispatchedEvents);
81+
82+
self::assertInstanceOf(RecordImportedSuccessfullyEvent::class, $dispatchedEvents[0]);
83+
self::assertSame(TestEntity::class, $dispatchedEvents[0]->class);
84+
self::assertSame('1', $dispatchedEvents[0]->id);
85+
86+
self::assertInstanceOf(RecordImportedSuccessfullyEvent::class, $dispatchedEvents[1]);
87+
self::assertSame(TestEntity::class, $dispatchedEvents[1]->class);
88+
self::assertSame('2', $dispatchedEvents[1]->id);
7289
}
7390

7491
public static function matrixDataProvider(): Generator
@@ -180,17 +197,22 @@ public static function matrixDataProvider(): Generator
180197
/**
181198
* @dataProvider exceptionCheckProvider
182199
*/
183-
public function testExceptionsDuringImport(string $exceptionClass, array $data): void
200+
public function testExceptionsDuringImport(string $expectedExceptionClass, array $data): void
184201
{
185-
$this->expectException($exceptionClass);
186-
187202
$repository = $this->entityManager->getRepository(TestEntity::class);
188203
self::assertEmpty($repository->findAll());
189204

190205
$matrix = new Matrix(['test_private_property'], $data);
191206

192-
$config = new BaseConfiguration($this->entityManager);
193-
$config->import($matrix);
207+
$config = new BaseConfiguration($this->entityManager, $this->eventDispatcher);
208+
209+
$exception = null;
210+
try {
211+
$config->import($matrix);
212+
} catch (\Throwable $exception) {
213+
}
214+
self::assertInstanceOf($expectedExceptionClass, $exception);
215+
self::assertFalse($this->eventDispatcher->hasEvent(RecordImportedSuccessfullyEvent::class));
194216
}
195217

196218
public static function exceptionCheckProvider(): Generator

0 commit comments

Comments
 (0)