GenerateTypeScriptInterfaceCommand::toTypeScript()   D
last analyzed

Complexity

Conditions 26
Paths 26

Size

Total Lines 39
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 37
dl 0
loc 39
rs 4.1666
c 0
b 0
f 0
cc 26
nc 26
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace DoctrineRepoHelper\Command;
4
5
6
use Doctrine\ORM\EntityManagerInterface;
7
use Doctrine\ORM\Mapping\ClassMetadata;
8
use Symfony\Component\Console\Command\Command;
9
use Symfony\Component\Console\Input\InputInterface;
10
use Symfony\Component\Console\Input\InputOption;
11
use Symfony\Component\Console\Output\OutputInterface;
12
13
class GenerateTypeScriptInterfaceCommand extends Command
14
{
15
    /** @var EntityManagerInterface */
16
    protected $entityManager;
17
18
    /**
19
     * GenerateTypeScriptInterfaceCommand constructor.
20
     * @param EntityManagerInterface $entityManager
21
     */
22
    public function __construct(EntityManagerInterface $entityManager)
23
    {
24
        $this->entityManager = $entityManager;
25
        parent::__construct();
26
    }
27
28
    protected function configure()
29
    {
30
        parent::configure();
31
32
        $this
33
            ->setName('orm:generate-ts-interfaces')
34
            ->setDescription('Generate typescript interfaces')
35
            ->setHelp('Generates typescript interfaces for use in frontend development')
36
            ->addOption(
37
                'output',
38
                'o',
39
                InputOption::VALUE_OPTIONAL,
40
                'Output path',
41
                getcwd() . '/data/interfaces'
42
            )
43
            ->addOption(
44
                'force',
45
                'f',
46
                InputOption::VALUE_OPTIONAL,
47
                'Overwrite existing interfaces',
48
                true
49
            )
50
            ->addOption(
51
                'filter',
52
                null,
53
                InputOption::VALUE_OPTIONAL,
54
                'filter the list of entities interfaces are created for'
55
            )
56
            ->addOption(
57
                'namespace',
58
                null,
59
                InputOption::VALUE_OPTIONAL,
60
                'Declares the namespace'
61
            );
62
    }
63
64
    protected function execute(InputInterface $input, OutputInterface $output)
65
    {
66
        $destination = $input->getOption('output');
67
        $namespace = $input->getOption('namespace');
68
        $overwrite = $input->hasOption('force');
69
        $metaDataEntries = $this->entityManager->getMetadataFactory()->getAllMetadata();
70
71
        if (!is_dir($destination)) {
0 ignored issues
show
Bug introduced by
It seems like $destination can also be of type string[]; however, parameter $filename of is_dir() 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

71
        if (!is_dir(/** @scrutinizer ignore-type */ $destination)) {
Loading history...
72
            mkdir($destination, 0777, true);
0 ignored issues
show
Bug introduced by
It seems like $destination can also be of type string[]; however, parameter $pathname of mkdir() 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

72
            mkdir(/** @scrutinizer ignore-type */ $destination, 0777, true);
Loading history...
73
        }
74
75
        /** @var ClassMetadata $metaData */
76
        foreach ($metaDataEntries as $metaData) {
77
            if ($filter = $input->getOption('filter')) {
78
                if (strpos($metaData->getName(), $filter) === false) {
79
                    $output->writeln(
80
                        sprintf(
81
                            'Filtering out %s...',
82
                            $metaData->getName()
83
                        ),
84
                        OutputInterface::VERBOSITY_VERY_VERBOSE
85
                    );
86
                    continue;
87
                }
88
            }
89
90
            $fileName = $destination . '/' . $metaData->reflClass->getShortName() . 'Interface.ts';
0 ignored issues
show
Bug introduced by
Accessing reflClass on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
91
            // TODO handle duplicate names
92
93
            $indent = '';
94
            $fileContent = '';
95
96
            if ($namespace) {
97
                $fileContent = sprintf("%snamespace %s {", $indent, $namespace) . PHP_EOL;
98
                $indent = '    ';
99
            }
100
101
            $fileContent .= $indent . sprintf(
102
                "%sinterface %s {" . PHP_EOL,
103
                $namespace ? 'export ': '',
104
                $metaData->reflClass->getShortName()
105
            );
106
            $indent .= '    ';
107
108
            foreach ($metaData->fieldMappings as $fieldMapping) {
0 ignored issues
show
Bug introduced by
Accessing fieldMappings on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
109
                $fileContent .= sprintf(
110
                    '%s%s%s: %s;' . PHP_EOL,
111
                    $indent,
112
                    $fieldMapping['fieldName'],
113
                    $fieldMapping['nullable'] ? '?' : '',
114
                    $this->toTypeScript($fieldMapping['type'])
115
                );
116
            }
117
118
            $indent = substr($indent, 0, -4);
119
            $fileContent .= $indent . '}' . PHP_EOL;
120
121
            if ($namespace) {
122
                $indent = '';
123
                $fileContent .= sprintf("%s}", $indent) . PHP_EOL;
124
            }
125
126
            if (file_exists($fileName) && !$overwrite) {
127
                $output->writeln(
128
                    sprintf(
129
                        '%s already exists. Skipping...',
130
                        $fileName
131
                    ),
132
                    OutputInterface::VERBOSITY_VERBOSE
133
                );
134
135
                continue;
136
            }
137
138
            file_put_contents($fileName, $fileContent);
139
        }
140
141
        $output->writeln(
142
            sprintf(
143
                'TypeScript interfaces written to "%s"',
144
                $destination
0 ignored issues
show
Bug introduced by
It seems like $destination can also be of type string[]; however, parameter $args of sprintf() 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

144
                /** @scrutinizer ignore-type */ $destination
Loading history...
145
            )
146
        );
147
    }
148
149
    /**
150
     * @param string $type
151
     * @return string
152
     */
153
    private function toTypeScript(string $type): string
154
    {
155
        switch ($type) {
156
            case 'smallint':
157
            case 'integer':
158
            case 'decimal':
159
            case 'float':
160
            case 'bigint':
161
                return 'number';
162
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
163
            case 'text':
164
            case 'guid':
165
            case 'binary':
166
            case 'blob':
167
            case 'string':
168
                return 'string';
169
                break;
170
            case 'boolean':
171
                return 'boolean';
172
                break;
173
            case 'date':
174
            case 'date_immutable':
175
            case 'datetime':
176
            case 'datetime_immutable':
177
            case 'datetimetz':
178
            case 'datetimetz_immutable':
179
            case 'time':
180
            case 'time_immutable':
181
                return 'Date';
182
                break;
183
            case 'dateinterval':
184
            case 'array':
185
            case 'simple_array':
186
            case 'json':
187
            case 'json_array':
188
            case 'object':
189
            default:
190
                return 'any';
191
                break;
192
        }
193
    }
194
195
}