Passed
Push — 4.x ( f89fc0...558be7 )
by Loïc
03:18
created

GenerateDtoCommand   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 104
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 55
c 1
b 0
f 0
dl 0
loc 104
ccs 0
cts 64
cp 0
rs 10
wmc 17

4 Methods

Rating   Name   Duplication   Size   Complexity  
A configure() 0 9 1
A __construct() 0 5 1
B execute() 0 68 10
A interact() 0 12 5
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\Dto\TypeDetector;
18
use IQ2i\DataImporter\Reader\CsvReader;
19
use IQ2i\DataImporter\Reader\ReaderInterface;
20
use IQ2i\DataImporter\Reader\XmlReader;
21
use Symfony\Component\Console\Command\Command;
22
use Symfony\Component\Console\Input\InputArgument;
23
use Symfony\Component\Console\Input\InputInterface;
24
use Symfony\Component\Console\Input\InputOption;
25
use Symfony\Component\Console\Output\OutputInterface;
26
use Symfony\Component\Console\Style\SymfonyStyle;
27
use Symfony\Component\Filesystem\Filesystem;
28
29
use function Symfony\Component\String\u;
30
31
class GenerateDtoCommand extends Command
32
{
33
    public function __construct(
34
        private ?string $defaultPath = null,
35
        private ?string $defaultNamespace = null,
36
    ) {
37
        parent::__construct();
38
    }
39
40
    protected function configure(): void
41
    {
42
        $this
43
            ->setName('generate')
44
            ->setDescription('Generate DTO from file to import.')
45
            ->addArgument('file', InputArgument::REQUIRED, 'The file from which the DTO should be generated.')
46
            ->addOption('length', null, InputOption::VALUE_OPTIONAL, 'Number of lines to analyze.', 10)
47
            ->addOption('path', null, InputOption::VALUE_REQUIRED, 'Customize the path for generated DTOs')
48
            ->addOption('namespace', null, InputOption::VALUE_REQUIRED, 'Customize the namespace for generated DTOs')
49
        ;
50
    }
51
52
    protected function interact(InputInterface $input, OutputInterface $output): void
53
    {
54
        $io = new SymfonyStyle($input, $output);
55
56
        if (null === $this->defaultPath || $input->hasOption('path')) {
57
            $this->defaultPath = $input->getOption('path') ?? $io->ask("Specify the DTO's path");
58
        }
59
60
        $this->defaultPath = \rtrim((string) $this->defaultPath, '/');
61
62
        if (null === $this->defaultNamespace || $input->hasOption('namespace')) {
63
            $this->defaultNamespace = $input->getOption('namespace') ?? $io->ask("Specify the DTO's namespace");
64
        }
65
    }
66
67
    protected function execute(InputInterface $input, OutputInterface $output): int
68
    {
69
        $filesystem = new Filesystem();
70
        $io = new SymfonyStyle($input, $output);
71
72
        $file = $input->getArgument('file');
73
        if (!\file_exists($file)) {
74
            throw new \InvalidArgumentException(\sprintf('File "%s" does not exists.', $file));
75
        }
76
77
        $dtoClass = $io->ask('Class name of the entity to create or update');
78
        $dtoFilename = $this->defaultPath.'/'.$dtoClass.'.php';
79
        if ($filesystem->exists($dtoFilename) && !$io->confirm(\sprintf('File %s already exists. Do you want to override it?', $dtoFilename))) {
80
            return Command::SUCCESS;
81
        }
82
83
        $readerClass = 'IQ2i\\DataImporter\\Reader\\'.$io->choice(
84
            'Which reader do you want to use?',
85
            ['CsvReader', 'JsonReader', 'XmlReader']
86
        );
87
88
        $context = match ($readerClass) {
89
            CsvReader::class => [
90
                CsvReader::CONTEXT_DELIMITER => $io->ask('Specify the delimiter', ','),
91
                CsvReader::CONTEXT_ENCLOSURE => $io->ask('Specify the enclosure', '"'),
92
                CsvReader::CONTEXT_ESCAPE_CHAR => $io->ask('Specify the escape character', ''),
93
            ],
94
            XmlReader::class => [
95
                XmlReader::CONTEXT_XPATH => $io->ask('Specify the xpath', ''),
96
            ],
97
            default => [],
98
        };
99
100
        /** @var ReaderInterface $reader */
101
        $reader = new $readerClass($file, null, $context);
102
103
        $properties = [];
104
        foreach ($reader->current() as $key => $value) {
105
            $name = u($key)->camel()->toString();
106
            $properties[$key] = [
107
                'name' => $name,
108
                'serialized_name' => $name !== $key ? $key : null,
109
                'types' => [TypeDetector::findType($value)],
110
            ];
111
        }
112
113
        for ($i = 0; $i < $input->getOption('length'); ++$i) {
114
            foreach ($reader->current() as $key => $value) {
115
                $properties[$key]['types'][] = TypeDetector::findType($value);
116
            }
117
118
            $reader->next();
119
        }
120
121
        foreach ($properties as &$property) {
122
            $property['type'] = TypeDetector::resolve($property['types']);
123
            unset($property['types']);
124
        }
125
126
        $generatedDto = (new Generator())->generate($dtoClass, $properties, $this->defaultNamespace);
127
128
        if (!$filesystem->exists($this->defaultPath)) {
0 ignored issues
show
Bug introduced by
It seems like $this->defaultPath can also be of type null; however, parameter $files of Symfony\Component\Filesystem\Filesystem::exists() does only seem to accept iterable|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

128
        if (!$filesystem->exists(/** @scrutinizer ignore-type */ $this->defaultPath)) {
Loading history...
129
            $filesystem->mkdir($this->defaultPath);
0 ignored issues
show
Bug introduced by
It seems like $this->defaultPath can also be of type null; however, parameter $dirs of Symfony\Component\Filesystem\Filesystem::mkdir() does only seem to accept iterable|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

129
            $filesystem->mkdir(/** @scrutinizer ignore-type */ $this->defaultPath);
Loading history...
130
        }
131
132
        $filesystem->dumpFile($dtoFilename, $generatedDto);
133
134
        return Command::SUCCESS;
135
    }
136
}
137