Importer::assertHeaders()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 10
c 0
b 0
f 0
ccs 8
cts 8
cp 1
rs 10
cc 3
nc 3
nop 1
crap 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Application\Service;
6
7
use Application\Enum\Precision;
8
use Application\Enum\Site;
9
use Application\Handler\TemplateHandler;
10
use Application\Model\AbstractModel;
11
use Application\Model\Card;
12
use Application\Model\Collection;
13
use Application\Model\Country;
14
use Application\Model\DocumentType;
15
use Application\Model\Domain;
16
use Application\Model\Material;
17
use Application\Model\Period;
18
use Application\Repository\CountryRepository;
19
use Application\Repository\DocumentTypeRepository;
20
use Application\Repository\DomainRepository;
21
use Application\Repository\MaterialRepository;
22
use Application\Repository\PeriodRepository;
23
use Ecodev\Felix\Api\Exception;
24
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
25
use PhpOffice\PhpSpreadsheet\IOFactory;
26
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
27
use Psr\Http\Message\UploadedFileInterface;
28
use Throwable;
29
30
class Importer
31
{
32
    /**
33
     * @var string[]
34
     */
35
    private readonly array $domains;
36
37
    /**
38
     * @var string[]
39
     */
40
    private readonly array $documentTypes;
41
42
    /**
43
     * @var string[]
44
     */
45
    private readonly array $countries;
46
47
    /**
48
     * @var string[]
49
     */
50
    private readonly array $materials;
51
52
    /**
53
     * @var string[]
54
     */
55
    private readonly array $periods;
56
57
    private ?Collection $collection = null;
58
59 5
    public function __construct(
60
        private readonly Site $site,
61
    ) {
62
        /** @var DomainRepository $domainRepository */
63 5
        $domainRepository = _em()->getRepository(Domain::class);
64 5
        $this->domains = $domainRepository->getFullNames($this->site);
0 ignored issues
show
Bug introduced by
The property domains is declared read-only in Application\Service\Importer.
Loading history...
65
66
        /** @var PeriodRepository $periodRepository */
67 5
        $periodRepository = _em()->getRepository(Period::class);
68 5
        $this->periods = $periodRepository->getFullNames($this->site);
0 ignored issues
show
Bug introduced by
The property periods is declared read-only in Application\Service\Importer.
Loading history...
69
70
        /** @var MaterialRepository $materialRepository */
71 5
        $materialRepository = _em()->getRepository(Material::class);
72 5
        $this->materials = $materialRepository->getFullNames($this->site);
0 ignored issues
show
Bug introduced by
The property materials is declared read-only in Application\Service\Importer.
Loading history...
73
74
        /** @var CountryRepository $countryRepository */
75 5
        $countryRepository = _em()->getRepository(Country::class);
76 5
        $this->countries = $countryRepository->getNames();
0 ignored issues
show
Bug introduced by
The property countries is declared read-only in Application\Service\Importer.
Loading history...
77
78
        /** @var DocumentTypeRepository $documentTypeRepository */
79 5
        $documentTypeRepository = _em()->getRepository(DocumentType::class);
80 5
        $this->documentTypes = $documentTypeRepository->getNames($this->site);
0 ignored issues
show
Bug introduced by
The property documentTypes is declared read-only in Application\Service\Importer.
Loading history...
81
    }
82
83 5
    public function import(UploadedFileInterface $file, array $files, ?Collection $collection): array
84
    {
85 5
        $this->collection = $collection;
86 5
        $tempFile = tempnam('data/tmp/', 'import');
87 5
        $file->moveTo($tempFile);
88 5
        $spreadsheet = IOFactory::load($tempFile);
89 5
        $sheet = $spreadsheet->getSheet(0);
90
91 5
        $this->assertHeaders($sheet);
92 4
        $cards = $this->importSheet($sheet, $files);
93 1
        unlink($tempFile);
94
95 1
        return $cards;
96
    }
97
98 5
    private function assertHeaders(Worksheet $sheet): void
99
    {
100 5
        $col = 1;
101 5
        $row = 1;
102 5
        foreach (TemplateHandler::HEADERS as $header) {
103 5
            $actual = $sheet->getCell([$col, $row])->getValue();
104 5
            if ($actual !== $header) {
105 1
                $this->throwException($col, $row, 'S\'attend à "' . $header . '", mais a vu "' . $actual . '"');
106
            }
107 4
            ++$col;
108
        }
109
    }
110
111 4
    private function importSheet(Worksheet $sheet, array $images): array
112
    {
113 4
        $imagesToImport = $this->indexByName($images);
114
115 4
        $col = 1;
116 4
        $row = 2;
117
118 4
        $cards = [];
119 4
        while ($imageName = (string) $sheet->getCell([$col, $row])->getValue()) {
120 3
            $imageNameWithoutExtension = pathinfo($imageName, PATHINFO_FILENAME);
121 3
            if (!array_key_exists($imageNameWithoutExtension, $imagesToImport)) {
0 ignored issues
show
Bug introduced by
It seems like $imageNameWithoutExtension can also be of type array; however, parameter $key of array_key_exists() does only seem to accept integer|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

121
            if (!array_key_exists(/** @scrutinizer ignore-type */ $imageNameWithoutExtension, $imagesToImport)) {
Loading history...
122 1
                $this->throwException($col, $row, 'Image présente dans le fichier Excel, mais pas retrouvée dans les images uploadées: ' . $imageName);
123
            }
124
125 2
            $image = $imagesToImport[$imageNameWithoutExtension];
126 2
            $cards[] = $this->importOne($sheet, $row, $image);
127
128 1
            unset($imagesToImport[$imageNameWithoutExtension]);
129
130 1
            ++$row;
131
        }
132
133 2
        if (count($imagesToImport) > 1) {
134 1
            $message = count($imagesToImport) . ' images ont été uploadé pour lesquelles aucune information ont été trouvée dans le fichier Excel: ' . implode(', ', array_keys($imagesToImport));
135 1
            $this->throwException($col, $row, $message);
136
        }
137
138 1
        return $cards;
139
    }
140
141 4
    private function throwException(int $column, int $row, string $message, ?Throwable $previousException = null): never
142
    {
143 4
        $cell = Coordinate::stringFromColumnIndex($column) . $row;
144 4
        $message = 'Erreur dans la cellule ' . $cell . ': ' . $message;
145
146 4
        throw new Exception($message, 0, $previousException);
147
    }
148
149 4
    private function indexByName(array $images): array
150
    {
151 4
        $imagesToImport = [];
152 4
        foreach ($images as $image) {
153 3
            $filename = pathinfo($image->getClientFilename(), PATHINFO_FILENAME);
154 3
            $imagesToImport[$filename] = $image;
155
        }
156
157 4
        return $imagesToImport;
158
    }
159
160 2
    private function importOne(Worksheet $sheet, int $row, UploadedFileInterface $image): Card
161
    {
162 2
        $card = new Card();
163 2
        $card->setSite($this->site);
164 2
        if ($this->collection) {
165 1
            $card->addCollection($this->collection);
166
        }
167
168 2
        _em()->persist($card);
169 2
        $col = 2;
170
171 2
        $card->setName($this->readString($sheet, $col++, $row));
172 2
        $card->setExpandedName($this->readString($sheet, $col++, $row));
173 2
        $domain = $this->readDomain($sheet, $col++, $row);
174 1
        if ($domain) {
0 ignored issues
show
introduced by
$domain is of type Application\Model\Domain, thus it always evaluated to true.
Loading history...
175 1
            $card->addDomain($domain);
176
        }
177
178 1
        $material = $this->readMaterial($sheet, $col++, $row);
179 1
        if ($material) {
0 ignored issues
show
introduced by
$material is of type Application\Model\Material, thus it always evaluated to true.
Loading history...
180 1
            $card->addMaterial($material);
181
        }
182
183 1
        $period = $this->readPeriod($sheet, $col++, $row);
184 1
        if ($period) {
0 ignored issues
show
introduced by
$period is of type Application\Model\Period, thus it always evaluated to true.
Loading history...
185 1
            $card->addPeriod($period);
186
        }
187
188 1
        $card->setFrom($this->readInt($sheet, $col++, $row));
189 1
        $card->setTo($this->readInt($sheet, $col++, $row));
190 1
        $card->setCountry($this->readCountry($sheet, $col++, $row));
191 1
        $card->setLocality($this->readString($sheet, $col++, $row));
192 1
        $card->setProductionPlace($this->readString($sheet, $col++, $row));
193 1
        $card->setObjectReference($this->readString($sheet, $col++, $row));
194 1
        $card->setDocumentType($this->readDocumentType($sheet, $col++, $row));
195 1
        $card->setTechniqueAuthor($this->readString($sheet, $col++, $row));
196 1
        $card->setTechniqueDate($this->readString($sheet, $col++, $row));
197 1
        $card->setLatitude($this->readFloat($sheet, $col++, $row));
198 1
        $card->setLongitude($this->readFloat($sheet, $col++, $row));
199 1
        $card->setPrecision($this->readPrecision($sheet, $col++, $row));
200
201
        try {
202 1
            $card->setFile($image);
203
        } catch (Throwable $e) {
204
            $this->throwException($col, 1, 'Erreur avec l\'image', $e);
205
        }
206
207 1
        return $card;
208
    }
209
210 2
    private function readString(Worksheet $sheet, int $col, int $row): string
211
    {
212 2
        return mb_trim((string) $sheet->getCell([$col, $row])->getValue());
213
    }
214
215 2
    private function readDomain(Worksheet $sheet, int $col, int $row): ?Domain
216
    {
217
        /** @var null|Domain $result */
218 2
        $result = $this->read($sheet, $col, $row, Domain::class, $this->domains, 'Domaine');
219
220 1
        return $result;
221
    }
222
223 1
    private function readMaterial(Worksheet $sheet, int $col, int $row): ?Material
224
    {
225
        /** @var null|Material $result */
226 1
        $result = $this->read($sheet, $col, $row, Material::class, $this->materials, 'Materiel');
227
228 1
        return $result;
229
    }
230
231 1
    private function readPeriod(Worksheet $sheet, int $col, int $row): ?Period
232
    {
233
        /** @var null|Period $result */
234 1
        $result = $this->read($sheet, $col, $row, Period::class, $this->periods, 'Période');
235
236 1
        return $result;
237
    }
238
239 1
    private function readCountry(Worksheet $sheet, int $col, int $row): ?Country
240
    {
241
        /** @var null|Country $result */
242 1
        $result = $this->read($sheet, $col, $row, Country::class, $this->countries, 'Pays');
243
244 1
        return $result;
245
    }
246
247 1
    private function readDocumentType(Worksheet $sheet, int $col, int $row): ?DocumentType
248
    {
249
        /** @var null|DocumentType $result */
250 1
        $result = $this->read($sheet, $col, $row, DocumentType::class, $this->documentTypes, 'Type de document');
251
252 1
        return $result;
253
    }
254
255 1
    private function readPrecision(Worksheet $sheet, int $col, int $row): ?Precision
256
    {
257 1
        $value = $sheet->getCell([$col, $row])->getValue();
258 1
        if (!$value) {
259 1
            return null;
260
        }
261
262 1
        $value = Precision::tryFrom($value);
263 1
        if ($value) {
264 1
            return $value;
265
        }
266
267
        $this->throwException($col, $row, 'Précision introuvable: ' . $value);
268
    }
269
270 2
    private function read(Worksheet $sheet, int $col, int $row, string $class, array $values, string $name): ?AbstractModel
271
    {
272 2
        $value = $sheet->getCell([$col, $row])->getValue();
273 2
        if (!$value) {
274 1
            return null;
275
        }
276
277 2
        if (array_key_exists($value, $values)) {
278 1
            return _em()->getRepository($class)->find($values[$value]);
279
        }
280
281 1
        $this->throwException($col, $row, $name . ' introuvable: ' . $value);
282
    }
283
284 1
    private function readInt(Worksheet $sheet, int $col, int $row): ?int
285
    {
286 1
        $value = $sheet->getCell([$col, $row])->getValue();
287 1
        if (!$value) {
288 1
            return null;
289
        }
290
291 1
        if (is_numeric($value)) {
292 1
            return (int) $value;
293
        }
294
295
        $this->throwException($col, $row, 'N\'est pas un nombre entier: ' . $value);
296
    }
297
298 1
    private function readFloat(Worksheet $sheet, int $col, int $row): ?float
299
    {
300 1
        $value = $sheet->getCell([$col, $row])->getValue();
301 1
        if (!$value) {
302 1
            return null;
303
        }
304
305 1
        if (is_numeric($value)) {
306 1
            return (float) $value;
307
        }
308
309
        $this->throwException($col, $row, 'N\'est pas un nombre à virgule: ' . $value);
310
    }
311
}
312