TableCommand   A
last analyzed

Complexity

Total Complexity 24

Size/Duplication

Total Lines 264
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 100
c 1
b 0
f 0
dl 0
loc 264
rs 10
wmc 24

11 Methods

Rating   Name   Duplication   Size   Complexity  
A describeForeignKeys() 0 32 2
A describeDefaultValue() 0 15 3
A configure() 0 8 1
A execute() 0 32 4
A __construct() 0 5 1
A describeColumns() 0 33 3
A sprintf() 0 3 1
A describeIndexes() 0 21 3
A initialize() 0 7 1
A describeAbstractType() 0 9 2
A describeType() 0 15 3
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Biurad opensource projects.
7
 *
8
 * PHP version 7.2 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2019 Biurad Group (https://biurad.com/)
12
 * @license   https://opensource.org/licenses/BSD-3-Clause License
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 */
17
18
namespace Biurad\Cycle\Commands\Database;
19
20
use DateTimeInterface;
21
use Spiral\Database\DatabaseInterface;
22
use Spiral\Database\DatabaseProviderInterface;
23
use Spiral\Database\Driver\DriverInterface;
24
use Spiral\Database\Exception\DBALException;
25
use Spiral\Database\Injection\FragmentInterface;
26
use Spiral\Database\Query\QueryParameters;
27
use Spiral\Database\Schema\AbstractColumn;
28
use Spiral\Database\Schema\AbstractTable;
29
use Symfony\Component\Console\Command\Command;
30
use Symfony\Component\Console\Helper\Table;
31
use Symfony\Component\Console\Input\InputArgument;
32
use Symfony\Component\Console\Input\InputInterface;
33
use Symfony\Component\Console\Input\InputOption;
34
use Symfony\Component\Console\Output\OutputInterface;
35
use Symfony\Component\Console\Style\SymfonyStyle;
36
37
final class TableCommand extends Command
38
{
39
    private const SKIP = '<comment>---</comment>';
40
41
    protected static $defaultName = 'database:table';
42
43
    /** @var DatabaseProviderInterface */
44
    private $factory;
45
46
    /** @var SymfonyStyle */
47
    private $io;
48
49
    /** @var Table */
50
    private $table;
51
52
    public function __construct(DatabaseProviderInterface $dbal)
53
    {
54
        $this->factory = $dbal;
55
56
        parent::__construct();
57
    }
58
59
    /**
60
     * {@inheritdoc}
61
     */
62
    protected function configure(): void
63
    {
64
        $this
65
            ->setDefinition([
66
                new InputArgument('table', InputArgument::REQUIRED, 'Table name'),
67
                new InputOption('database', 'd', InputOption::VALUE_OPTIONAL, 'Source database', 'default'),
68
            ])
69
            ->setDescription('Describe table schema of specific database')
70
        ;
71
    }
72
73
    /**
74
     * This optional method is the first one executed for a command after configure()
75
     * and is useful to initialize properties based on the input arguments and options.
76
     *
77
     * @param InputInterface  $input
78
     * @param OutputInterface $output
79
     */
80
    protected function initialize(InputInterface $input, OutputInterface $output): void
81
    {
82
        // SymfonyStyle is an optional feature that Symfony provides so you can
83
        // apply a consistent look to the commands of your application.
84
        // See https://symfony.com/doc/current/console/style.html
85
        $this->io    = new SymfonyStyle($input, $output);
86
        $this->table = new Table($output);
87
    }
88
89
    /**
90
     * {@inheritdoc}
91
     */
92
    protected function execute(InputInterface $input, OutputInterface $output): int
93
    {
94
        $database = $this->factory->database($input->getOption('database'));
95
        $schema   = $database->table($input->getArgument('table'))->getSchema();
0 ignored issues
show
Bug introduced by
It seems like $input->getArgument('table') can also be of type null and string[]; however, parameter $name of Spiral\Database\DatabaseInterface::table() 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

95
        $schema   = $database->table(/** @scrutinizer ignore-type */ $input->getArgument('table'))->getSchema();
Loading history...
96
97
        if (!$schema->exists()) {
98
            throw new DBALException(
99
                "Table {$database->getName()}.{$input->getArgument('table')} does not exists."
100
            );
101
        }
102
103
        $output->writeln(
104
            \sprintf(
105
                "\n<fg=cyan>Columns of </fg=cyan><comment>%s.%s</comment>:\n",
106
                $database->getName(),
107
                $input->getArgument('table')
0 ignored issues
show
Bug introduced by
It seems like $input->getArgument('table') 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

107
                /** @scrutinizer ignore-type */ $input->getArgument('table')
Loading history...
108
            )
109
        );
110
111
        $this->describeColumns($schema);
112
113
        if (!empty($indexes = $schema->getIndexes())) {
114
            $this->describeIndexes($database, $indexes, $input);
115
        }
116
117
        if (!empty($foreignKeys = $schema->getForeignKeys())) {
118
            $this->describeForeignKeys($database, $foreignKeys, $input);
119
        }
120
121
        $output->write("\n");
122
123
        return 0;
124
    }
125
126
    /**
127
     * @param AbstractTable $schema
128
     */
129
    protected function describeColumns(AbstractTable $schema): void
130
    {
131
        $columnsTable = $this->table->setHeaders(
132
            [
133
                'Column:',
134
                'Database Type:',
135
                'Abstract Type:',
136
                'PHP Type:',
137
                'Default Value:',
138
            ]
139
        );
140
141
        foreach ($schema->getColumns() as $column) {
142
            $name = $column->getName();
143
144
            if (\in_array($column->getName(), $schema->getPrimaryKeys(), true)) {
145
                $name = "<fg=magenta>{$name}</fg=magenta>";
146
            }
147
148
            $defaultValue = $this->describeDefaultValue($column, $schema->getDriver());
149
150
            $columnsTable->addRow(
151
                [
152
                    $name,
153
                    $this->describeType($column),
154
                    $this->describeAbstractType($column),
155
                    $column->getType(),
156
                    $defaultValue ?? self::SKIP,
157
                ]
158
            );
159
        }
160
161
        $columnsTable->render();
162
    }
163
164
    /**
165
     * @param DatabaseInterface $database
166
     * @param array             $indexes
167
     * @param InputInterface    $input
168
     */
169
    protected function describeIndexes(DatabaseInterface $database, array $indexes, InputInterface $input): void
170
    {
171
        $this->sprintf(
172
            "\n<fg=cyan>Indexes of </fg=cyan><comment>%s.%s</comment>:\n",
173
            $database->getName(),
174
            $input->getArgument('table')
175
        );
176
177
        $indexesTable = $this->table->setHeaders(['Name:', 'Type:', 'Columns:']);
178
179
        foreach ($indexes as $index) {
180
            $indexesTable->addRow(
181
                [
182
                    $index->getName(),
183
                    $index->isUnique() ? 'UNIQUE INDEX' : 'INDEX',
184
                    \implode(', ', $index->getColumns()),
185
                ]
186
            );
187
        }
188
189
        $indexesTable->render();
190
    }
191
192
    /**
193
     * @param DatabaseInterface $database
194
     * @param array             $foreignKeys
195
     * @param InputInterface    $input
196
     */
197
    protected function describeForeignKeys(DatabaseInterface $database, array $foreignKeys, InputInterface $input): void
198
    {
199
        $this->sprintf(
200
            "\n<fg=cyan>Foreign Keys of </fg=cyan><comment>%s.%s</comment>:\n",
201
            $database->getName(),
202
            $input->getArgument('table')
203
        );
204
        $foreignTable = $this->table->setHeaders(
205
            [
206
                'Name:',
207
                'Column:',
208
                'Foreign Table:',
209
                'Foreign Column:',
210
                'On Delete:',
211
                'On Update:',
212
            ]
213
        );
214
215
        foreach ($foreignKeys as $reference) {
216
            $foreignTable->addRow(
217
                [
218
                    $reference->getName(),
219
                    \implode(', ', $reference->getColumns()),
220
                    $reference->getForeignTable(),
221
                    \implode(', ', $reference->getForeignKeys()),
222
                    $reference->getDeleteRule(),
223
                    $reference->getUpdateRule(),
224
                ]
225
            );
226
        }
227
228
        $foreignTable->render();
229
    }
230
231
    /**
232
     * @param AbstractColumn  $column
233
     * @param DriverInterface $driver
234
     *
235
     * @return mixed
236
     */
237
    protected function describeDefaultValue(AbstractColumn $column, DriverInterface $driver)
238
    {
239
        $defaultValue = $column->getDefaultValue();
240
241
        if ($defaultValue instanceof FragmentInterface) {
242
            $value = $driver->getQueryCompiler()->compile(new QueryParameters(), '', $defaultValue);
243
244
            return "<info>{$value}</info>";
245
        }
246
247
        if ($defaultValue instanceof DateTimeInterface) {
248
            $defaultValue = $defaultValue->format('c');
249
        }
250
251
        return $defaultValue;
252
    }
253
254
    /**
255
     * Identical to write function but provides ability to format message. Does not add new line.
256
     *
257
     * @param string $format
258
     * @param array  ...$args
259
     */
260
    protected function sprintf(string $format, ...$args)
261
    {
262
        return $this->io->write(\sprintf($format, ...$args), false);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->io->write(sprintf($format, $args), false) targeting Symfony\Component\Consol...e\SymfonyStyle::write() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
263
    }
264
265
    /**
266
     * @param AbstractColumn $column
267
     *
268
     * @return string
269
     */
270
    private function describeType(AbstractColumn $column): string
271
    {
272
        $type = $column->getType();
273
274
        $abstractType = $column->getAbstractType();
275
276
        if ($column->getSize()) {
277
            $type .= " ({$column->getSize()})";
278
        }
279
280
        if ($abstractType === 'decimal') {
281
            $type .= " ({$column->getPrecision()}, {$column->getScale()})";
282
        }
283
284
        return $type;
285
    }
286
287
    /**
288
     * @param AbstractColumn $column
289
     *
290
     * @return string
291
     */
292
    private function describeAbstractType(AbstractColumn $column): string
293
    {
294
        $abstractType = $column->getAbstractType();
295
296
        if (\in_array($abstractType, ['primary', 'bigPrimary'])) {
297
            $abstractType = "<fg=magenta>{$abstractType}</fg=magenta>";
298
        }
299
300
        return $abstractType;
301
    }
302
}
303