CreateDateDimensionEntitiesCommand   A
last analyzed

Complexity

Total Complexity 16

Size/Duplication

Total Lines 170
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 16
eloc 53
dl 0
loc 170
rs 10
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A validatorYearStart() 0 11 3
A createEntities() 0 23 3
A getProgressBar() 0 16 1
A getYearEnd() 0 6 1
A process() 0 16 1
A __construct() 0 4 1
A execute() 0 17 1
A validatorYearEnd() 0 12 4
A getYearStart() 0 3 1
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