ProcessFormatUseCase::isIgnored()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 1
nc 2
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
/*
3
 * This file is part of FlexPHP.
4
 *
5
 * (c) Freddie Gar <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace FlexPHP\Generator\Domain\UseCases;
11
12
use Box\Spout\Reader\Common\Creator\ReaderEntityFactory;
13
use Box\Spout\Reader\ReaderInterface;
14
use Box\Spout\Reader\SheetInterface;
15
use Exception;
16
use FlexPHP\Generator\Domain\Builders\Inflector;
17
use FlexPHP\Generator\Domain\Exceptions\FormatNotSupportedException;
18
use FlexPHP\Generator\Domain\Exceptions\FormatPathNotValidException;
19
use FlexPHP\Generator\Domain\Messages\Requests\CreatePrototypeRequest;
20
use FlexPHP\Generator\Domain\Messages\Requests\ProcessFormatRequest;
21
use FlexPHP\Generator\Domain\Messages\Responses\CreatePrototypeResponse;
22
use FlexPHP\Generator\Domain\Messages\Responses\ProcessFormatResponse;
23
use FlexPHP\Generator\Domain\Validations\FieldSyntaxValidation;
24
use FlexPHP\Generator\Domain\Validations\HeaderSyntaxValidation;
25
use FlexPHP\Generator\Domain\Writers\YamlWriter;
26
use FlexPHP\Schema\Constants\Keyword;
27
use RecursiveDirectoryIterator;
28 12
use RecursiveIteratorIterator;
29
use ZipArchive;
30 12
31
final class ProcessFormatUseCase
32 11
{
33 11
    private const COLUMN_A = 0;
34 11
35
    private const COLUMN_B = 1;
36 11
37 7
    private ?int $rowHeaders = null;
38
39
    private \FlexPHP\Generator\Domain\Builders\Inflector $inflector;
40
41 4
    public function __construct()
42
    {
43 2
        $this->inflector = new Inflector();
44
    }
45 2
46 2
    /**
47
     * @throws FormatPathNotValidException
48 2
     * @throws FormatNotSupportedException
49 2
     */
50 2
    public function execute(ProcessFormatRequest $request): ProcessFormatResponse
51
    {
52
        $path = $request->path;
53 2
54
        if (!\is_file($path)) {
55 2
            throw new FormatPathNotValidException();
56 2
        }
57
58 2
        $yamls = [];
59 2
        $sheetNames = [];
60 2
        $outputDir = \sprintf('%1$s/../../../src/tmp', __DIR__);
61
        $outputTmp = \sprintf('%1$s/../../../src/tmp/skeleton', __DIR__);
62 2
        $name = \str_ireplace('.' . $request->extension, '', $request->filename);
63 2
64 2
        $reader = $this->getReader($request->extension);
65
        $reader->open($path);
66
67 2
        foreach ($reader->getSheetIterator() as $sheet) {
68 2
            if ($this->isIgnored($sheet)) {
69
                continue;
70 2
            }
71
72
            $sheetName = $this->inflector->sheetName($sheet->getName());
73 2
            $conf = $this->getConf($sheetName, $sheet);
74
            $headers = $this->getHeaders($sheet);
75 2
            $attributes = $this->getAttributes($sheet, $headers);
76 2
            $sheetNames[$sheetName] = \count($attributes);
77
            $yamls[$sheetName] = $this->createYaml($sheetName, $conf, $attributes, $outputTmp . '/yamls');
78
        }
79 2
80 2
        $this->createPrototype($this->inflector->prototypeName($name), $yamls, $outputTmp);
0 ignored issues
show
Bug introduced by
It seems like $name can also be of type array; however, parameter $string of FlexPHP\Generator\Domain...lector::prototypeName() does only seem to accept 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

80
        $this->createPrototype($this->inflector->prototypeName(/** @scrutinizer ignore-type */ $name), $yamls, $outputTmp);
Loading history...
81
82 2
        $this->createZip($name, $outputTmp, $outputDir);
0 ignored issues
show
Bug introduced by
It seems like $name can also be of type array; however, parameter $name of FlexPHP\Generator\Domain...matUseCase::createZip() does only seem to accept 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

82
        $this->createZip(/** @scrutinizer ignore-type */ $name, $outputTmp, $outputDir);
Loading history...
83 2
84 2
        return new ProcessFormatResponse($sheetNames);
85
    }
86
87 2
    private function isIgnored(SheetInterface $sheet): bool
88
    {
89 2
        return !$sheet->isVisible() || \substr($sheet->getName(), 0, 1) === '_';
90 2
    }
91
92 2
    private function getReader(?string $extension): ReaderInterface
93
    {
94 2
        switch ($extension) {
95
            case 'xlsx': // MS Excel >= 2007
96 2
                return ReaderEntityFactory::createXLSXReader();
97
            // case 'ods': // Open Format
98 2
            //     return ReaderEntityFactory::createODSReader();
99
            default:
100 2
                throw new FormatNotSupportedException();
101
        }
102
    }
103 2
104
    private function getConf(string $sheetName, SheetInterface $sheet): array
105
    {
106
        $this->rowHeaders = 1;
107
108
        $conf = [
109
            Keyword::TITLE => $sheetName,
110
            Keyword::ICON => '',
111
        ];
112
113
        foreach ($sheet->getRowIterator() as $rowNumber => $row) {
114
            $this->rowHeaders = $rowNumber;
115
116
            if ($row->getCellAtIndex(self::COLUMN_A)->getValue() === Keyword::NAME) {
117
                break;
118
            }
119
120
            $conf[$row->getCellAtIndex(self::COLUMN_A)->getValue()] = $row->getCellAtIndex(self::COLUMN_B)->getValue();
121
        }
122
123
        return $conf;
124
    }
125
126
    private function getHeaders(SheetInterface $sheet): array
127
    {
128
        $headers = [];
129
130
        foreach ($sheet->getRowIterator() as $rowNumber => $row) {
131
            if ($rowNumber < $this->rowHeaders) {
132
                continue;
133
            }
134
135
            $cols = $row->getCells();
136
137
            foreach ($cols as $colNumber => $col) {
138
                $header = \trim($col->getValue());
139
140
                if (empty($header)) {
141
                    continue;
142
                }
143
144
                $headers[$colNumber] = $header;
145
            }
146
147
            try {
148
                (new HeaderSyntaxValidation($headers))->validate();
149
            } catch (Exception $exception) {
150
                throw new Exception(\sprintf('Sheet [%s]: %s', $sheet->getName(), $exception->getMessage()));
151
            }
152
153
            break;
154
        }
155
156
        return $headers;
157
    }
158
159
    private function getAttributes(SheetInterface $sheet, array $headers): array
160
    {
161
        $attributes = [];
162
163
        foreach ($sheet->getRowIterator() as $rowNumber => $row) {
164
            if ($rowNumber <= $this->rowHeaders) {
165
                continue;
166
            }
167
168
            $cols = $row->getCells();
169
170
            $properties = $this->getProperties($cols, $headers);
171
172
            $colHeaderName = $headers[\array_search(Keyword::NAME, $headers)];
173
            $name = $this->inflector->camelProperty($properties[$colHeaderName]);
174
            $attributes[$name] = $properties;
175
        }
176
177
        return $attributes;
178
    }
179
180
    private function getProperties(array $cols, array $headers): array
181
    {
182
        $attributes = [];
183
184
        foreach ($cols as $colNumber => $col) {
185
            $value = \trim($col->getValue());
186
187
            if (empty($value)) {
188
                continue;
189
            }
190
191
            $attributes[$headers[$colNumber]] = $value;
192
        }
193
194
        (new FieldSyntaxValidation($attributes))->validate();
195
196
        return $attributes;
197
    }
198
199
    private function createYaml(string $sheetName, array $conf, array $attributes, string $output): string
200
    {
201
        $writer = new YamlWriter([
202
            $sheetName => $conf + [Keyword::ATTRIBUTES => $attributes],
203
        ], \strtolower($sheetName), $output);
204
205
        return $writer->save();
206
    }
207
208
    private function createPrototype(string $name, array $sheets, string $output): CreatePrototypeResponse
209
    {
210
        return (new CreatePrototypeUseCase())->execute(new CreatePrototypeRequest($name, $sheets, $output));
211
    }
212
213
    private function createZip(string $name, string $outputTmp, string $outputDir): string
214
    {
215
        $outputTmp = (string)\realpath($outputTmp);
216
        $outputDir = (string)\realpath($outputDir);
217
218
        $src = $outputTmp . \DIRECTORY_SEPARATOR . $name . '.zip';
219
        $dst = $outputDir . \DIRECTORY_SEPARATOR . $name . '.zip';
220
221
        $this->getZip($src, $outputTmp);
222
223
        \rename($src, $dst);
224
225
        $this->deleteFolder($outputTmp);
226
227
        return $dst;
228
    }
229
230
    private function getZip(string $name, string $path): void
231
    {
232
        $zip = new ZipArchive();
233
        $zip->open($name, ZipArchive::CREATE | ZipArchive::OVERWRITE);
234
235
        $files = new RecursiveIteratorIterator(
236
            new RecursiveDirectoryIterator($path),
237
            RecursiveIteratorIterator::LEAVES_ONLY
238
        );
239
240
        foreach ($files as $file) {
241
            if (!$file->isDir()) {
242
                $realPath = $file->getRealPath();
243
                $relativePath = \substr($realPath, \strlen($path) + 1);
244
245
                $zip->addFile($realPath, $relativePath);
246
            }
247
        }
248
249
        $zip->close();
250
    }
251
252
    private function deleteFolder(string $dir): void
253
    {
254
        if (\is_dir($dir)) {
255
            $objects = \array_diff(\scandir($dir), ['.', '..']); // @phpstan-ignore-line
256
257
            foreach ($objects as $object) {
258
                $path = $dir . '/' . $object;
259
260
                if (\is_dir($path) && !\is_link($path)) {
261
                    $this->deleteFolder($path);
262
                } else {
263
                    \unlink($path);
264
                }
265
            }
266
267
            \closedir(\opendir($dir)); // @phpstan-ignore-line
268
            \rmdir($dir);
269
        }
270
    }
271
}
272