Skip to content

Commit 6136ed7

Browse files
committed
Import with a foreign key
1 parent a783277 commit 6136ed7

File tree

5 files changed

+58
-11
lines changed

5 files changed

+58
-11
lines changed

src/Domain/Subscription/Model/Dto/ImportSubscriberDto.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ class ImportSubscriberDto
1111
#[Assert\NotBlank]
1212
public string $email;
1313

14+
/**
15+
* Optional external identifier used for matching existing subscribers during import.
16+
*/
17+
#[Assert\Length(max: 191)]
18+
public ?string $foreignKey = null;
19+
1420
#[Assert\Type('bool')]
1521
public bool $confirmed;
1622

@@ -37,7 +43,8 @@ public function __construct(
3743
bool $htmlEmail,
3844
bool $disabled,
3945
?string $extraData = null,
40-
array $extraAttributes = []
46+
array $extraAttributes = [],
47+
?string $foreignKey = null,
4148
) {
4249
$this->email = $email;
4350
$this->confirmed = $confirmed;
@@ -47,5 +54,6 @@ public function __construct(
4754
$this->disabled = $disabled;
4855
$this->extraData = $extraData;
4956
$this->extraAttributes = $extraAttributes;
57+
$this->foreignKey = $foreignKey;
5058
}
5159
}

src/Domain/Subscription/Repository/SubscriberRepository.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*
1717
* @author Oliver Klee <[email protected]>
1818
* @author Tatevik Grigoryan <[email protected]>
19+
* @SuppressWarnings(PHPMD.TooManyPublicMethods)
1920
*/
2021
class SubscriberRepository extends AbstractRepository implements PaginatableRepositoryInterface
2122
{
@@ -41,6 +42,11 @@ public function findOneByUniqueId(string $uniqueId): ?Subscriber
4142
return $this->findOneBy(['uniqueId' => $uniqueId]);
4243
}
4344

45+
public function findOneByForeignKey(string $foreignKey): ?Subscriber
46+
{
47+
return $this->findOneBy(['foreignKey' => $foreignKey]);
48+
}
49+
4450
public function findSubscribersBySubscribedList(int $listId): ?Subscriber
4551
{
4652
return $this->createQueryBuilder('s')

src/Domain/Subscription/Service/CsvRowToDtoMapper.php

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,38 @@
88

99
class CsvRowToDtoMapper
1010
{
11+
private const FK_HEADER = 'foreignkey';
1112
private const KNOWN_HEADERS = [
12-
'email', 'confirmed', 'blacklisted', 'html_email', 'disabled', 'extra_data',
13+
'email', 'confirmed', 'blacklisted', 'html_email', 'disabled', 'extra_data', 'foreignkey',
1314
];
1415

1516
public function map(array $row): ImportSubscriberDto
1617
{
17-
$extraAttributes = array_filter($row, function ($key) {
18+
// Normalize keys to lower-case for header matching safety (CSV library keeps original headers)
19+
$normalizedRow = [];
20+
foreach ($row as $key => $value) {
21+
$normalizedRow[strtolower((string)$key)] = is_string($value) ? trim($value) : $value;
22+
}
23+
24+
$email = strtolower(trim((string)($normalizedRow['email'] ?? '')));
25+
26+
if (array_key_exists(self::FK_HEADER, $normalizedRow) && $normalizedRow[self::FK_HEADER] !== '') {
27+
$foreignKey = (string)$normalizedRow[self::FK_HEADER];
28+
}
29+
30+
$extraAttributes = array_filter($normalizedRow, function ($key) {
1831
return !in_array($key, self::KNOWN_HEADERS, true);
1932
}, ARRAY_FILTER_USE_KEY);
2033

2134
return new ImportSubscriberDto(
22-
email: trim($row['email'] ?? ''),
23-
confirmed: filter_var($row['confirmed'] ?? false, FILTER_VALIDATE_BOOLEAN),
24-
blacklisted: filter_var($row['blacklisted'] ?? false, FILTER_VALIDATE_BOOLEAN),
25-
htmlEmail: filter_var($row['html_email'] ?? false, FILTER_VALIDATE_BOOLEAN),
26-
disabled: filter_var($row['disabled'] ?? false, FILTER_VALIDATE_BOOLEAN),
27-
extraData: $row['extra_data'] ?? null,
28-
extraAttributes: $extraAttributes
35+
email: $email,
36+
confirmed: filter_var($normalizedRow['confirmed'] ?? false, FILTER_VALIDATE_BOOLEAN),
37+
blacklisted: filter_var($normalizedRow['blacklisted'] ?? false, FILTER_VALIDATE_BOOLEAN),
38+
htmlEmail: filter_var($normalizedRow['html_email'] ?? false, FILTER_VALIDATE_BOOLEAN),
39+
disabled: filter_var($normalizedRow['disabled'] ?? false, FILTER_VALIDATE_BOOLEAN),
40+
extraData: $normalizedRow['extra_data'] ?? null,
41+
extraAttributes: $extraAttributes,
42+
foreignKey: $foreignKey ?? null,
2943
);
3044
}
3145
}

src/Domain/Subscription/Service/Manager/SubscriberManager.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ public function createFromImport(ImportSubscriberDto $subscriberDto): Subscriber
114114
$subscriber->setHtmlEmail($subscriberDto->htmlEmail);
115115
$subscriber->setDisabled($subscriberDto->disabled);
116116
$subscriber->setExtraData($subscriberDto->extraData ?? '');
117+
if ($subscriberDto->foreignKey !== null) {
118+
$subscriber->setForeignKey($subscriberDto->foreignKey);
119+
}
117120

118121
$this->entityManager->persist($subscriber);
119122

@@ -129,6 +132,9 @@ public function updateFromImport(Subscriber $existingSubscriber, ImportSubscribe
129132
$existingSubscriber->setHtmlEmail($subscriberDto->htmlEmail);
130133
$existingSubscriber->setDisabled($subscriberDto->disabled);
131134
$existingSubscriber->setExtraData($subscriberDto->extraData);
135+
if ($subscriberDto->foreignKey !== null) {
136+
$existingSubscriber->setForeignKey($subscriberDto->foreignKey);
137+
}
132138

133139
$uow = $this->entityManager->getUnitOfWork();
134140
$meta = $this->entityManager->getClassMetadata(Subscriber::class);

src/Domain/Subscription/Service/SubscriberCsvImporter.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,20 @@ private function processRow(
182182
return null;
183183
}
184184

185-
$subscriber = $this->subscriberRepository->findOneByEmail($dto->email);
185+
$subscriberByEmail = $this->subscriberRepository->findOneByEmail($dto->email);
186+
$subscriberByFk = null;
187+
if ($dto->foreignKey !== null) {
188+
$subscriberByFk = $this->subscriberRepository->findOneByForeignKey($dto->foreignKey);
189+
}
190+
191+
if ($subscriberByEmail && $subscriberByFk && $subscriberByEmail->getId() !== $subscriberByFk->getId()) {
192+
$stats['skipped']++;
193+
$stats['errors'][] = $this->translator->trans('Conflict: email and foreign key refer to different subscribers.');
194+
return null;
195+
}
196+
197+
$subscriber = $subscriberByFk ?? $subscriberByEmail;
198+
186199
if ($this->handleSkipCase($subscriber, $options, $stats)) {
187200
return null;
188201
}

0 commit comments

Comments
 (0)