1
|
|
|
<?php |
2
|
|
|
declare(strict_types = 1); |
3
|
|
|
/** |
4
|
|
|
* /src/Command/Utils/CreateDateDimensionEntitiesCommand.php |
5
|
|
|
* |
6
|
|
|
* @author TLe, Tarmo Leppänen <[email protected]> |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
namespace App\Command\Utils; |
10
|
|
|
|
11
|
|
|
use App\Entity\DateDimension; |
12
|
|
|
use App\Repository\DateDimensionRepository; |
13
|
|
|
use Closure; |
14
|
|
|
use DateInterval; |
15
|
|
|
use DateTime; |
16
|
|
|
use DateTimeImmutable; |
17
|
|
|
use DateTimeZone; |
18
|
|
|
use InvalidArgumentException; |
19
|
|
|
use Symfony\Component\Console\Attribute\AsCommand; |
20
|
|
|
use Symfony\Component\Console\Command\Command; |
21
|
|
|
use Symfony\Component\Console\Helper\ProgressBar; |
22
|
|
|
use Symfony\Component\Console\Input\InputInterface; |
23
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
24
|
|
|
use Symfony\Component\Console\Style\SymfonyStyle; |
25
|
|
|
use Throwable; |
26
|
|
|
use function sprintf; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* Class CreateDateDimensionEntitiesCommand |
30
|
|
|
* |
31
|
|
|
* @package App\Command\Utils |
32
|
|
|
* @author TLe, Tarmo Leppänen <[email protected]> |
33
|
|
|
*/ |
34
|
|
|
#[AsCommand( |
35
|
|
|
name: 'utils:create-date-dimension-entities', |
36
|
|
|
description: 'Console command to create `DateDimension` entities.', |
37
|
|
|
)] |
38
|
|
|
class CreateDateDimensionEntitiesCommand extends Command |
39
|
|
|
{ |
40
|
|
|
private const YEAR_MIN = 1970; |
41
|
|
|
private const YEAR_MAX = 2047; // This should be the year when I'm officially retired |
42
|
|
|
|
43
|
|
|
public function __construct( |
44
|
|
|
private readonly DateDimensionRepository $dateDimensionRepository, |
45
|
|
|
) { |
46
|
|
|
parent::__construct(); |
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* @noinspection PhpMissingParentCallCommonInspection |
51
|
|
|
* |
52
|
|
|
* @throws Throwable |
53
|
|
|
*/ |
54
|
|
|
protected function execute(InputInterface $input, OutputInterface $output): int |
55
|
|
|
{ |
56
|
|
|
// Create output decorator helpers for the Symfony Style Guide. |
57
|
|
|
$io = new SymfonyStyle($input, $output); |
58
|
|
|
|
59
|
|
|
$io->title($this->getDescription()); |
60
|
|
|
|
61
|
|
|
// Determine start and end years |
62
|
|
|
$yearStart = $this->getYearStart($io); |
63
|
|
|
$yearEnd = $this->getYearEnd($io, $yearStart); |
64
|
|
|
|
65
|
|
|
// Create actual entities |
66
|
|
|
$this->process($io, $yearStart, $yearEnd); |
67
|
|
|
|
68
|
|
|
$io->success('All done - have a nice day!'); |
69
|
|
|
|
70
|
|
|
return 0; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Method to get start year value from user. |
75
|
|
|
* |
76
|
|
|
* @throws InvalidArgumentException |
77
|
|
|
*/ |
78
|
|
|
private function getYearStart(SymfonyStyle $io): int |
79
|
|
|
{ |
80
|
|
|
return (int)$io->ask('Give a year where to start', (string)self::YEAR_MIN, $this->validatorYearStart()); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* Method to get end year value from user. |
85
|
|
|
* |
86
|
|
|
* @throws InvalidArgumentException |
87
|
|
|
*/ |
88
|
|
|
private function getYearEnd(SymfonyStyle $io, int $yearStart): int |
89
|
|
|
{ |
90
|
|
|
return (int)$io->ask( |
91
|
|
|
'Give a year where to end', |
92
|
|
|
(string)self::YEAR_MAX, |
93
|
|
|
$this->validatorYearEnd($yearStart), |
94
|
|
|
); |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* Method to create DateDimension entities to database. |
99
|
|
|
* |
100
|
|
|
* @throws Throwable |
101
|
|
|
*/ |
102
|
|
|
private function process(SymfonyStyle $io, int $yearStart, int $yearEnd): void |
103
|
|
|
{ |
104
|
|
|
$dateStart = new DateTime($yearStart . '-01-01 00:00:00', new DateTimeZone('UTC')); |
105
|
|
|
$dateEnd = new DateTime($yearEnd . '-12-31 23:59:59', new DateTimeZone('UTC')); |
106
|
|
|
|
107
|
|
|
$progress = $this->getProgressBar( |
108
|
|
|
$io, |
109
|
|
|
(int)$dateEnd->diff($dateStart)->format('%a') + 1, |
110
|
|
|
sprintf('Creating DateDimension entities between years %d and %d...', $yearStart, $yearEnd), |
111
|
|
|
); |
112
|
|
|
|
113
|
|
|
// Remove existing entities |
114
|
|
|
$this->dateDimensionRepository->reset(); |
115
|
|
|
|
116
|
|
|
// Create entities to database |
117
|
|
|
$this->createEntities($yearEnd, $dateStart, $progress); |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
/** |
121
|
|
|
* Helper method to get progress bar for console. |
122
|
|
|
*/ |
123
|
|
|
private function getProgressBar(SymfonyStyle $io, int $steps, string $message): ProgressBar |
124
|
|
|
{ |
125
|
|
|
$format = ' |
126
|
|
|
%message% |
127
|
|
|
%current%/%max% [%bar%] %percent:3s%% |
128
|
|
|
Time elapsed: %elapsed:-6s% |
129
|
|
|
Time remaining: %remaining:-6s% |
130
|
|
|
Time estimated: %estimated:-6s% |
131
|
|
|
Memory usage: %memory:-6s% |
132
|
|
|
'; |
133
|
|
|
|
134
|
|
|
$progress = $io->createProgressBar($steps); |
135
|
|
|
$progress->setFormat($format); |
136
|
|
|
$progress->setMessage($message); |
137
|
|
|
|
138
|
|
|
return $progress; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* @throws Throwable |
143
|
|
|
*/ |
144
|
|
|
private function createEntities(int $yearEnd, DateTime $dateStart, ProgressBar $progress): void |
145
|
|
|
{ |
146
|
|
|
// Get entity manager for _fast_ database handling. |
147
|
|
|
$em = $this->dateDimensionRepository->getEntityManager(); |
148
|
|
|
|
149
|
|
|
// You spin me round (like a record... er like a date) |
150
|
|
|
while ((int)$dateStart->format('Y') < $yearEnd + 1) { |
151
|
|
|
$em->persist(new DateDimension(DateTimeImmutable::createFromMutable(clone $dateStart))); |
152
|
|
|
|
153
|
|
|
$dateStart->add(new DateInterval('P1D')); |
154
|
|
|
|
155
|
|
|
// Flush in 1000 batches to database |
156
|
|
|
if ($progress->getProgress() % 1000 === 0) { |
157
|
|
|
$em->flush(); |
158
|
|
|
$em->clear(); |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
$progress->advance(); |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
// Finally flush remaining entities |
165
|
|
|
$em->flush(); |
166
|
|
|
$em->clear(); |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* Getter method for year start validator closure. |
171
|
|
|
* |
172
|
|
|
* @throws InvalidArgumentException |
173
|
|
|
*/ |
174
|
|
|
private function validatorYearStart(): Closure |
175
|
|
|
{ |
176
|
|
|
return static fn (int $year): int => $year < self::YEAR_MIN || $year > self::YEAR_MAX |
177
|
|
|
? throw new InvalidArgumentException( |
178
|
|
|
sprintf( |
179
|
|
|
'Start year must be between %d and %d', |
180
|
|
|
self::YEAR_MIN, |
181
|
|
|
self::YEAR_MAX, |
182
|
|
|
), |
183
|
|
|
) |
184
|
|
|
: $year; |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* Getter method for year end validator closure. |
189
|
|
|
* |
190
|
|
|
* @throws InvalidArgumentException |
191
|
|
|
*/ |
192
|
|
|
private function validatorYearEnd(int $yearStart): Closure |
193
|
|
|
{ |
194
|
|
|
return static fn (int $year): int => $year < self::YEAR_MIN || $year > self::YEAR_MAX || $year < $yearStart |
195
|
|
|
? throw new InvalidArgumentException( |
196
|
|
|
sprintf( |
197
|
|
|
'End year must be between %d and %d and after given start year %d', |
198
|
|
|
self::YEAR_MIN, |
199
|
|
|
self::YEAR_MAX, |
200
|
|
|
$yearStart, |
201
|
|
|
), |
202
|
|
|
) |
203
|
|
|
: $year; |
204
|
|
|
} |
205
|
|
|
} |
206
|
|
|
|