Failed Conditions
Push — master ( b1d4df...5a9cf3 )
by David
04:05
created

Importer   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 279
Duplicated Lines 0 %

Test Coverage

Coverage 96.32%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 36
eloc 127
c 1
b 0
f 0
dl 0
loc 279
ccs 131
cts 136
cp 0.9632
rs 9.52

17 Methods

Rating   Name   Duplication   Size   Complexity  
A readMaterial() 0 6 1
A readCountry() 0 6 1
A assertHeaders() 0 10 3
A readFloat() 0 12 3
A __construct() 0 21 1
A import() 0 13 1
A read() 0 12 3
B importOne() 0 48 6
A readPeriod() 0 6 1
A readDomain() 0 6 1
A readString() 0 3 1
A indexByName() 0 9 2
A throwException() 0 6 1
A readDocumentType() 0 6 1
A readPrecision() 0 13 3
A importSheet() 0 28 4
A readInt() 0 12 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(private readonly Site $site)
60
    {
61
        /** @var DomainRepository $domainRepository */
62 5
        $domainRepository = _em()->getRepository(Domain::class);
63 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...
64
65
        /** @var PeriodRepository $periodRepository */
66 5
        $periodRepository = _em()->getRepository(Period::class);
67 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...
68
69
        /** @var MaterialRepository $materialRepository */
70 5
        $materialRepository = _em()->getRepository(Material::class);
71 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...
72
73
        /** @var CountryRepository $countryRepository */
74 5
        $countryRepository = _em()->getRepository(Country::class);
75 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...
76
77
        /** @var DocumentTypeRepository $documentTypeRepository */
78 5
        $documentTypeRepository = _em()->getRepository(DocumentType::class);
79 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...
80
    }
81
82 5
    public function import(UploadedFileInterface $file, array $files, ?Collection $collection): array
83
    {
84 5
        $this->collection = $collection;
85 5
        $tempFile = tempnam('data/tmp/', 'import');
86 5
        $file->moveTo($tempFile);
87 5
        $spreadsheet = IOFactory::load($tempFile);
88 5
        $sheet = $spreadsheet->getSheet(0);
89
90 5
        $this->assertHeaders($sheet);
91 4
        $cards = $this->importSheet($sheet, $files);
92 1
        unlink($tempFile);
93
94 1
        return $cards;
95
    }
96
97 5
    private function assertHeaders(Worksheet $sheet): void
98
    {
99 5
        $col = 1;
100 5
        $row = 1;
101 5
        foreach (TemplateHandler::HEADERS as $header) {
102 5
            $actual = $sheet->getCell([$col, $row])->getValue();
103 5
            if ($actual !== $header) {
104 1
                $this->throwException($col, $row, 'S\'attend à "' . $header . '", mais a vu "' . $actual . '"');
105
            }
106 4
            ++$col;
107
        }
108
    }
109
110 4
    private function importSheet(Worksheet $sheet, array $images): array
111
    {
112 4
        $imagesToImport = $this->indexByName($images);
113
114 4
        $col = 1;
115 4
        $row = 2;
116
117 4
        $cards = [];
118 4
        while ($imageName = (string) $sheet->getCell([$col, $row])->getValue()) {
119 3
            $imageNameWithoutExtension = pathinfo($imageName, PATHINFO_FILENAME);
120 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

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