Skip to content

Commit 44b328a

Browse files
committed
ISSUE-345: update tests
1 parent 7180d0a commit 44b328a

File tree

8 files changed

+467
-329
lines changed

8 files changed

+467
-329
lines changed

config/services/managers.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,12 @@ services:
5252
autowire: true
5353
autoconfigure: true
5454

55-
PhpList\Core\Domain\Subscription\Service\SubscriberCsvManager:
55+
PhpList\Core\Domain\Subscription\Service\SubscriberCsvExportManager:
56+
autowire: true
57+
autoconfigure: true
58+
public: true
59+
60+
PhpList\Core\Domain\Subscription\Service\SubscriberCsvImportManager:
5661
autowire: true
5762
autoconfigure: true
5863
public: true
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\Core\Domain\Subscription\Model\Dto;
6+
7+
class SubscriberImportOptions
8+
{
9+
/**
10+
* @SuppressWarnings("BooleanArgumentFlag")
11+
*/
12+
public function __construct(
13+
public readonly bool $updateExisting = false,
14+
) {
15+
}
16+
}
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\Core\Domain\Subscription\Service;
6+
7+
use PhpList\Core\Domain\Subscription\Model\Filter\SubscriberFilter;
8+
use PhpList\Core\Domain\Subscription\Model\Subscriber;
9+
use PhpList\Core\Domain\Subscription\Repository\SubscriberAttributeDefinitionRepository;
10+
use PhpList\Core\Domain\Subscription\Repository\SubscriberRepository;
11+
use Symfony\Component\HttpFoundation\Response;
12+
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
13+
use Symfony\Component\HttpFoundation\StreamedResponse;
14+
15+
/**
16+
* Service for importing and exporting subscribers from/to CSV files.
17+
*/
18+
class SubscriberCsvExportManager
19+
{
20+
private SubscriberAttributeManager $attributeManager;
21+
private SubscriberRepository $subscriberRepository;
22+
private SubscriberAttributeDefinitionRepository $definitionRepository;
23+
24+
public function __construct(
25+
SubscriberAttributeManager $attributeManager,
26+
SubscriberRepository $subscriberRepository,
27+
SubscriberAttributeDefinitionRepository $definitionRepository
28+
) {
29+
$this->attributeManager = $attributeManager;
30+
$this->subscriberRepository = $subscriberRepository;
31+
$this->definitionRepository = $definitionRepository;
32+
}
33+
34+
/**
35+
* Export subscribers to a CSV file.
36+
*
37+
* @param SubscriberFilter|null $filter Optional filter to apply
38+
* @param int $batchSize Number of subscribers to process in each batch
39+
* @return Response A streamed response with the CSV file
40+
*/
41+
public function exportToCsv(?SubscriberFilter $filter = null, int $batchSize = 1000): Response
42+
{
43+
if ($filter === null) {
44+
$filter = new SubscriberFilter();
45+
}
46+
47+
$response = new StreamedResponse(function () use ($filter, $batchSize) {
48+
$this->generateCsvContent($filter, $batchSize);
49+
});
50+
51+
return $this->configureResponse($response);
52+
}
53+
54+
/**
55+
* Generate CSV content for the export.
56+
*
57+
* @param SubscriberFilter $filter Filter to apply
58+
* @param int $batchSize Batch size for processing
59+
*/
60+
private function generateCsvContent(SubscriberFilter $filter, int $batchSize): void
61+
{
62+
$handle = fopen('php://output', 'w');
63+
$attributeDefinitions = $this->definitionRepository->findAll();
64+
65+
$headers = $this->getExportHeaders($attributeDefinitions);
66+
fputcsv($handle, $headers);
67+
68+
$this->exportSubscribers($handle, $filter, $batchSize, $attributeDefinitions);
69+
70+
fclose($handle);
71+
}
72+
73+
/**
74+
* Get headers for the export CSV.
75+
*
76+
* @param array $attributeDefinitions Attribute definitions
77+
* @return array Headers
78+
*/
79+
private function getExportHeaders(array $attributeDefinitions): array
80+
{
81+
$headers = [
82+
'email',
83+
'confirmed',
84+
'blacklisted',
85+
'html_email',
86+
'disabled',
87+
'extra_data',
88+
];
89+
90+
foreach ($attributeDefinitions as $definition) {
91+
$headers[] = $definition->getName();
92+
}
93+
94+
return $headers;
95+
}
96+
97+
/**
98+
* Export subscribers in batches.
99+
*
100+
* @param resource $handle File handle
101+
* @param SubscriberFilter $filter Filter to apply
102+
* @param int $batchSize Batch size
103+
* @param array $attributeDefinitions Attribute definitions
104+
*/
105+
private function exportSubscribers(
106+
$handle,
107+
SubscriberFilter $filter,
108+
int $batchSize,
109+
array $attributeDefinitions
110+
): void {
111+
$lastId = 0;
112+
113+
do {
114+
$subscribers = $this->subscriberRepository->getFilteredAfterId(
115+
lastId: $lastId,
116+
limit: $batchSize,
117+
filter: $filter
118+
);
119+
120+
foreach ($subscribers as $subscriber) {
121+
$row = $this->getSubscriberRow($subscriber, $attributeDefinitions);
122+
fputcsv($handle, $row);
123+
$lastId = $subscriber->getId();
124+
}
125+
126+
$subscriberCount = count($subscribers);
127+
} while ($subscriberCount === $batchSize);
128+
}
129+
130+
/**
131+
* Get a row of data for a subscriber.
132+
*
133+
* @param Subscriber $subscriber The subscriber
134+
* @param array $attributeDefinitions Attribute definitions
135+
* @return array Row data
136+
*/
137+
private function getSubscriberRow(Subscriber $subscriber, array $attributeDefinitions): array
138+
{
139+
$row = [
140+
$subscriber->getEmail(),
141+
$subscriber->isConfirmed() ? '1' : '0',
142+
$subscriber->isBlacklisted() ? '1' : '0',
143+
$subscriber->hasHtmlEmail() ? '1' : '0',
144+
$subscriber->isDisabled() ? '1' : '0',
145+
$subscriber->getExtraData(),
146+
];
147+
148+
foreach ($attributeDefinitions as $definition) {
149+
$attributeValue = $this->attributeManager->getSubscriberAttribute(
150+
subscriberId: $subscriber->getId(),
151+
attributeDefinitionId: $definition->getId()
152+
);
153+
$row[] = $attributeValue ? $attributeValue->getValue() : '';
154+
}
155+
156+
return $row;
157+
}
158+
159+
/**
160+
* Configure the response for CSV download.
161+
*
162+
* @param StreamedResponse $response The response
163+
* @return Response The configured response
164+
*/
165+
private function configureResponse(StreamedResponse $response): Response
166+
{
167+
$response->headers->set('Content-Type', 'text/csv; charset=utf-8');
168+
$disposition = $response->headers->makeDisposition(
169+
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
170+
'subscribers_export_' . date('Y-m-d') . '.csv'
171+
);
172+
$response->headers->set('Content-Disposition', $disposition);
173+
174+
return $response;
175+
}
176+
}

0 commit comments

Comments
 (0)