Passed
Pull Request — 4.x (#27)
by Loïc
14:13
created

GenerateDtoCommand::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
nc 1
nop 2
dl 0
loc 5
c 1
b 0
f 0
cc 1
ccs 0
cts 2
cp 0
crap 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the DataImporter package.
7
 *
8
 * (c) Loïc Sapone <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace IQ2i\DataImporter\Command;
15
16
use IQ2i\DataImporter\Dto\Generator;
17
use IQ2i\DataImporter\Reader\CsvReader;
18
use IQ2i\DataImporter\Reader\ReaderInterface;
19
use IQ2i\DataImporter\Reader\XmlReader;
20
use Symfony\Component\Console\Command\Command;
21
use Symfony\Component\Console\Input\InputArgument;
22
use Symfony\Component\Console\Input\InputInterface;
23
use Symfony\Component\Console\Input\InputOption;
24
use Symfony\Component\Console\Output\OutputInterface;
25
use Symfony\Component\Console\Style\SymfonyStyle;
26
use Symfony\Component\Filesystem\Filesystem;
27
28
use function Symfony\Component\String\u;
29
30
class GenerateDtoCommand extends Command
31
{
32
    public function __construct(
33
        private ?string $defaultPath = null,
34
        private ?string $defaultNamespace = null,
35
    ) {
36
        parent::__construct();
37
    }
38
39
    protected function configure(): void
40
    {
41
        $this
42
            ->setName('generate')
43
            ->setDescription('Generate DTO from file to import.')
44
            ->addArgument('file', InputArgument::REQUIRED, 'The file from which the DTO should be generated.')
45
            ->addOption('length', null, InputOption::VALUE_OPTIONAL, 'Number of lines to analyze.', 10)
46
            ->addOption('path', null, InputOption::VALUE_REQUIRED, 'Customize the path for generated DTOs')
47
            ->addOption('namespace', null, InputOption::VALUE_REQUIRED, 'Customize the namespace for generated DTOs')
48
        ;
49
    }
50
51
    protected function execute(InputInterface $input, OutputInterface $output): int
52
    {
53
        $filesystem = new Filesystem();
54
        $io = new SymfonyStyle($input, $output);
55
56
        $file = $input->getArgument('file');
57
        if (!\file_exists($file)) {
58
            throw new \InvalidArgumentException(\sprintf('File "%s" does not exists.', $file));
59
        }
60
61
        if (null === $this->defaultPath || $input->hasOption('path')) {
62
            $this->defaultPath = $input->getOption('path') ?? $io->ask("Specify the DTO's path");
63
        }
64
65
        $this->defaultPath = \rtrim((string) $this->defaultPath, '/');
66
67
        if (null === $this->defaultNamespace || $input->hasOption('namespace')) {
68
            $this->defaultNamespace = $input->getOption('namespace') ?? $io->ask("Specify the DTO's namespace");
69
        }
70
71
        $dtoClass = $io->ask('Class name of the entity to create or update');
72
        $dtoFilename = $this->defaultPath.'/'.$dtoClass.'.php';
73
        if ($filesystem->exists($dtoFilename) && !$io->confirm(\sprintf('File %s already exists. Do you want to override it?', $dtoFilename))) {
74
            return Command::SUCCESS;
75
        }
76
77
        $readerClass = 'IQ2i\\DataImporter\\Reader\\'.$io->choice(
78
            'Which reader do you want to use?',
79
            ['CsvReader', 'JsonReader', 'XmlReader']
80
        );
81
82
        $context = match ($readerClass) {
83
            CsvReader::class => [
84
                CsvReader::CONTEXT_DELIMITER => $io->ask('Specify the delimiter', ','),
85
                CsvReader::CONTEXT_ENCLOSURE => $io->ask('Specify the enclosure', '"'),
86
                CsvReader::CONTEXT_ESCAPE_CHAR => $io->ask('Specify the escape character', ''),
87
            ],
88
            XmlReader::class => [
89
                XmlReader::CONTEXT_XPATH => $io->ask('Specify the xpath', ''),
90
            ],
91
            default => [],
92
        };
93
94
        /** @var ReaderInterface $reader */
95
        $reader = new $readerClass($file, null, $context);
96
97
        // init properties array
98
        $properties = [];
99
        foreach ($reader->current() as $key => $value) {
100
            $name = u($key)->camel()->toString();
101
            $properties[$key] = [
102
                'name' => $name,
103
                'serialized_name' => $name !== $key ? $key : null,
104
                'types' => [self::findPropertyType($value)],
105
            ];
106
        }
107
108
        for ($i = 0; $i < $input->getOption('length'); ++$i) {
109
            foreach ($reader->current() as $key => $value) {
110
                $properties[$key]['types'][] = self::findPropertyType($value);
111
            }
112
113
            $reader->next();
114
        }
115
116
        foreach ($properties as $property) {
117
            $property['type'] = 1 === \count(\array_unique($property['types'], \SORT_REGULAR))
118
                ? $property['types'][0]
119
                : 'string';
120
        }
121
122
        $generator = new Generator();
123
        $generatedDto = $generator->generate($dtoClass, [
124
            ['name' => 'author', 'serialized_name' => null, 'type' => 'string'],
125
            ['name' => 'title', 'serialized_name' => null, 'type' => 'string'],
126
            ['name' => 'genre', 'serialized_name' => null, 'type' => 'string'],
127
            ['name' => 'price', 'serialized_name' => null, 'type' => 'float'],
128
            ['name' => 'description', 'serialized_name' => null, 'type' => 'string'],
129
        ], $this->defaultNamespace);
130
131
        if (!$filesystem->exists($this->defaultPath)) {
132
            $filesystem->mkdir($this->defaultPath);
133
        }
134
135
        $filesystem->dumpFile($dtoFilename, $generatedDto);
136
137
        return Command::SUCCESS;
138
    }
139
140
    private static function findPropertyType(string $value): string
141
    {
142
        if (\is_numeric($value) && \str_contains($value, '.')) {
143
            return 'float';
144
        } elseif (\is_numeric($value) && !\in_array($value, ['0', '1'])) {
145
            return 'int';
146
        } elseif (\in_array($value, ['0', '1', 'true', 'false'])) {
147
            return 'bool';
148
        } else {
149
            return 'string';
150
        }
151
    }
152
}
153