Completed
Push — develop ( 8dd5aa...fee724 )
by Freddie
14:52
created

ProcessFormatUseCase::getConf()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 10
c 0
b 0
f 0
nc 3
nop 2
dl 0
loc 20
rs 9.9332
ccs 0
cts 0
cp 0
crap 12
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
    /**
38
     * @var int
39
     */
40
    private $rowHeaders;
41 4
42
    /**
43 2
     * @var Inflector
44
     */
45 2
    private $inflector;
46 2
47
    public function __construct()
48 2
    {
49 2
        $this->inflector = new Inflector();
50 2
    }
51
52
    /**
53 2
     * @throws FormatPathNotValidException
54
     * @throws FormatNotSupportedException
55 2
     */
56 2
    public function execute(ProcessFormatRequest $request): ProcessFormatResponse
57
    {
58 2
        $path = $request->path;
59 2
60 2
        if (!\is_file($path)) {
61
            throw new FormatPathNotValidException();
62 2
        }
63 2
64 2
        $yamls = [];
65
        $sheetNames = [];
66
        $outputDir = \sprintf('%1$s/../../../src/tmp', __DIR__);
67 2
        $outputTmp = \sprintf('%1$s/../../../src/tmp/skeleton', __DIR__);
68 2
        $name = \str_ireplace('.' . $request->extension, '', $request->filename);
69
70 2
        $reader = $this->getReader($request->extension);
71
        $reader->open($path);
72
73 2
        foreach ($reader->getSheetIterator() as $sheet) {
74
            if ($this->isIgnored($sheet)) {
75 2
                continue;
76 2
            }
77
78
            $sheetName = $this->inflector->sheetName($sheet->getName());
79 2
            $conf = $this->getConf($sheetName, $sheet);
80 2
            $headers = $this->getHeaders($sheet);
81
            $attributes = $this->getAttributes($sheet, $headers);
82 2
            $sheetNames[$sheetName] = \count($attributes);
83 2
            $yamls[$sheetName] = $this->createYaml($sheetName, $conf, $attributes, $outputTmp . '/yamls');
84 2
        }
85
86
        $this->createPrototype($this->inflector->prototypeName($name), $yamls, $outputTmp);
87 2
88
        $this->createZip($name, $outputTmp, $outputDir);
89 2
90 2
        return new ProcessFormatResponse($sheetNames);
91
    }
92 2
93
    private function isIgnored(SheetInterface $sheet): bool
94 2
    {
95
        return !$sheet->isVisible() || \substr($sheet->getName(), 0, 1) === '_';
96 2
    }
97
98 2
    private function getReader(?string $extension): ReaderInterface
99
    {
100 2
        switch ($extension) {
101
            case 'xlsx': // MS Excel >= 2007
102
                return ReaderEntityFactory::createXLSXReader();
103 2
            // case 'ods': // Open Format
104
            //     return ReaderEntityFactory::createODSReader();
105
            default:
106
                throw new FormatNotSupportedException();
107
        }
108
    }
109
110
    private function getConf(string $sheetName, SheetInterface $sheet): array
111
    {
112
        $this->rowHeaders = 1;
113
114
        $conf = [
115
            Keyword::TITLE => $sheetName,
116
            Keyword::ICON => '',
117
        ];
118
119
        foreach ($sheet->getRowIterator() as $rowNumber => $row) {
120
            $this->rowHeaders = $rowNumber;
121
122
            if ($row->getCellAtIndex(self::COLUMN_A)->getValue() === Keyword::NAME) {
123
                break;
124
            }
125
126
            $conf[$row->getCellAtIndex(self::COLUMN_A)->getValue()] = $row->getCellAtIndex(self::COLUMN_B)->getValue();
127
        }
128
129
        return $conf;
130
    }
131
132
    private function getHeaders(SheetInterface $sheet): array
133
    {
134
        $headers = [];
135
136
        foreach ($sheet->getRowIterator() as $rowNumber => $row) {
137
            if ($rowNumber < $this->rowHeaders) {
138
                continue;
139
            }
140
141
            $cols = $row->getCells();
142
143
            foreach ($cols as $colNumber => $col) {
144
                $header = \trim($col->getValue());
145
146
                if (empty($header)) {
147
                    continue;
148
                }
149
150
                $headers[$colNumber] = $header;
151
            }
152
153
            try {
154
                (new HeaderSyntaxValidation($headers))->validate();
155
            } catch (Exception $e) {
156
                throw new Exception(\sprintf('Sheet [%s]: %s', $sheet->getName(), $e->getMessage()));
157
            }
158
159
            break;
160
        }
161
162
        return $headers;
163
    }
164
165
    private function getAttributes(SheetInterface $sheet, array $headers): array
166
    {
167
        $attributes = [];
168
169
        foreach ($sheet->getRowIterator() as $rowNumber => $row) {
170
            if ($rowNumber <= $this->rowHeaders) {
171
                continue;
172
            }
173
174
            $cols = $row->getCells();
175
176
            $properties = $this->getProperties($cols, $headers);
177
178
            $colHeaderName = $headers[\array_search(Keyword::NAME, $headers)];
179
            $name = $this->inflector->camelProperty($properties[$colHeaderName]);
180
            $attributes[$name] = $properties;
181
        }
182
183
        return $attributes;
184
    }
185
186
    private function getProperties(array $cols, array $headers): array
187
    {
188
        $attributes = [];
189
190
        foreach ($cols as $colNumber => $col) {
191
            $value = \trim($col->getValue());
192
193
            if (empty($value)) {
194
                continue;
195
            }
196
197
            $attributes[$headers[$colNumber]] = $value;
198
        }
199
200
        (new FieldSyntaxValidation($attributes))->validate();
201
202
        return $attributes;
203
    }
204
205
    private function createYaml(string $sheetName, array $conf, array $attributes, string $output): string
206
    {
207
        $writer = new YamlWriter([
208
            $sheetName => $conf + [Keyword::ATTRIBUTES => $attributes],
209
        ], \strtolower($sheetName), $output);
210
211
        return $writer->save();
212
    }
213
214
    private function createPrototype(string $name, array $sheets, string $output): CreatePrototypeResponse
215
    {
216
        return (new CreatePrototypeUseCase())->execute(new CreatePrototypeRequest($name, $sheets, $output));
217
    }
218
219
    private function createZip(string $name, string $outputTmp, string $outputDir): string
220
    {
221
        $outputTmp = \realpath($outputTmp);
222
        $outputDir = \realpath($outputDir);
223
224
        $src = $outputTmp . \DIRECTORY_SEPARATOR . $name . '.zip';
225
        $dst = $outputDir . \DIRECTORY_SEPARATOR . $name . '.zip';
226
227
        $this->getZip($src, $outputTmp);
228
229
        \rename($src, $dst);
230
231
        $this->deleteFolder($outputTmp);
232
233
        return $dst;
234
    }
235
236
    private function getZip(string $name, string $path): void
237
    {
238
        $zip = new ZipArchive();
239
        $zip->open($name, ZipArchive::CREATE | ZipArchive::OVERWRITE);
240
241
        $files = new RecursiveIteratorIterator(
242
            new RecursiveDirectoryIterator($path),
243
            RecursiveIteratorIterator::LEAVES_ONLY
244
        );
245
246
        foreach ($files as $name => $file) {
247
            if (!$file->isDir()) {
248
                $realPath = $file->getRealPath();
249
                $relativePath = \substr($realPath, \strlen($path) + 1);
250
251
                $zip->addFile($realPath, $relativePath);
252
            }
253
        }
254
255
        $zip->close();
256
    }
257
258
    private function deleteFolder(string $dir): void
259
    {
260
        if (\is_dir($dir)) {
261
            $objects = \array_diff(\scandir($dir), ['.', '..']);
0 ignored issues
show
Bug introduced by
It seems like scandir($dir) can also be of type false; however, parameter $array1 of array_diff() does only seem to accept array, 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

261
            $objects = \array_diff(/** @scrutinizer ignore-type */ \scandir($dir), ['.', '..']);
Loading history...
262
263
            foreach ($objects as $object) {
264
                if (\is_dir($dir . '/' . $object) && !\is_link($dir . '/' . $object)) {
265
                    $this->deleteFolder($dir . '/' . $object);
266
                } else {
267
                    \unlink($dir . '/' . $object);
268
                }
269
            }
270
271
            \closedir(\opendir($dir));
0 ignored issues
show
Bug introduced by
It seems like opendir($dir) can also be of type false; however, parameter $dir_handle of closedir() does only seem to accept resource, 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

271
            \closedir(/** @scrutinizer ignore-type */ \opendir($dir));
Loading history...
272
            \rmdir($dir);
273
        }
274
    }
275
}
276