GenerateDtoCommand   A
last analyzed

Complexity

Total Complexity 17

Size/Duplication

Total Lines 107
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 57
c 2
b 0
f 0
dl 0
loc 107
ccs 0
cts 67
cp 0
rs 10
wmc 17

4 Methods

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

132
        if (!$filesystem->exists(/** @scrutinizer ignore-type */ $this->defaultPath)) {
Loading history...
133
            $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

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