DatabaseTableCommand::execute()   A
last analyzed

Complexity

Conditions 4
Paths 5

Size

Total Lines 30
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 16
nc 5
nop 2
dl 0
loc 30
ccs 0
cts 16
cp 0
crap 20
rs 9.7333
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
/*
4
 * This file is part of Biurad opensource projects.
5
 *
6
 * @copyright 2019 Biurad Group (https://biurad.com/)
7
 * @license   https://opensource.org/licenses/BSD-3-Clause License
8
 *
9
 * For the full copyright and license information, please view the LICENSE
10
 * file that was distributed with this source code.
11
 */
12
13
namespace Flange\Commands\CycleORM;
14
15
use Cycle\Database\DatabaseInterface;
16
use Cycle\Database\DatabaseProviderInterface;
17
use Cycle\Database\Driver\DriverInterface;
18
use Cycle\Database\Exception\DBALException;
19
use Cycle\Database\Injection\FragmentInterface;
20
use Cycle\Database\Query\QueryParameters;
21
use Cycle\Database\Schema\AbstractColumn;
0 ignored issues
show
Bug introduced by
The type Cycle\Database\Schema\AbstractColumn was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
22
use Cycle\Database\Schema\AbstractForeignKey;
23
use Cycle\Database\Schema\AbstractIndex;
24
use Cycle\Database\Schema\AbstractTable;
25
use Symfony\Component\Console\Command\Command;
26
use Symfony\Component\Console\Helper\Table;
27
use Symfony\Component\Console\Input\InputArgument;
28
use Symfony\Component\Console\Input\InputInterface;
29
use Symfony\Component\Console\Input\InputOption;
30
use Symfony\Component\Console\Output\OutputInterface;
31
use Symfony\Component\Console\Style\SymfonyStyle;
32
33
/**
34
 * Describes table schema for a database.
35
 *
36
 * @author Divine Niiquaye Ibok <[email protected]>
37
 */
38
class DatabaseTableCommand extends Command
39
{
40
    protected static $defaultName = 'cycle:database:table';
41
42
    private SymfonyStyle $io;
43
44
    private Table $table;
45
46
    public function __construct(private DatabaseProviderInterface $provider)
47
    {
48
        parent::__construct();
49
    }
50
51
    /**
52
     * {@inheritdoc}
53
     */
54
    protected function configure(): void
55
    {
56
        $this
57
            ->setDefinition([
58
                new InputArgument('table', InputArgument::REQUIRED, 'Table name'),
59
                new InputOption('database', 'd', InputOption::VALUE_OPTIONAL, 'Source database', 'default'),
60
            ])
61
            ->setDescription('Describe table schema of specific database')
62
        ;
63
    }
64
65
    /**
66
     * This optional method is the first one executed for a command after configure()
67
     * and is useful to initialize properties based on the input arguments and options.
68
     */
69
    protected function initialize(InputInterface $input, OutputInterface $output): void
70
    {
71
        // SymfonyStyle is an optional feature that Symfony provides so you can
72
        // apply a consistent look to the commands of your application.
73
        // See https://symfony.com/doc/current/console/style.html
74
        $this->io = new SymfonyStyle($input, $output);
75
        $this->table = new Table($output);
76
    }
77
78
    /**
79
     * {@inheritdoc}
80
     */
81
    protected function execute(InputInterface $input, OutputInterface $output): int
82
    {
83
        $database = $this->provider->database($input->getOption('database'));
84
        $schema = $database->table($input->getArgument('table'))->getSchema();
0 ignored issues
show
Bug introduced by
The method getSchema() does not exist on Cycle\Database\TableInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Spiral\Database\TableInterface. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

84
        $schema = $database->table($input->getArgument('table'))->/** @scrutinizer ignore-call */ getSchema();
Loading history...
85
86
        if (!$schema->exists()) {
87
            throw new DBALException("Table {$database->getName()}.{$input->getArgument('table')} does not exists.");
88
        }
89
90
        $output->writeln(
91
            \sprintf(
92
                "\n<fg=cyan>Columns of </fg=cyan><comment>%s.%s</comment>:\n",
93
                $database->getName(),
94
                $input->getArgument('table')
95
            )
96
        );
97
98
        $this->describeColumns($schema);
99
100
        if (!empty($indexes = $schema->getIndexes())) {
101
            $this->describeIndexes($database, $indexes, $input);
102
        }
103
104
        if (!empty($foreignKeys = $schema->getForeignKeys())) {
105
            $this->describeForeignKeys($database, $foreignKeys, $input);
106
        }
107
108
        $output->write("\n");
109
110
        return self::SUCCESS;
111
    }
112
113
    protected function describeColumns(AbstractTable $schema): void
114
    {
115
        $columnsTable = $this->table->setHeaders(
116
            [
117
                'Column:',
118
                'Database Type:',
119
                'Abstract Type:',
120
                'PHP Type:',
121
                'Default Value:',
122
            ]
123
        );
124
125
        foreach ($schema->getColumns() as $column) {
126
            $name = $column->getName();
127
128
            if (\in_array($column->getName(), $schema->getPrimaryKeys(), true)) {
129
                $name = "<fg=magenta>{$name}</fg=magenta>";
130
            }
131
132
            $defaultValue = $this->describeDefaultValue($column, $schema->getDriver());
133
134
            $columnsTable->addRow(
135
                [
136
                    $name,
137
                    $this->describeType($column),
138
                    $this->describeAbstractType($column),
139
                    $column->getType(),
140
                    $defaultValue ?? '<comment>---</comment>',
141
                ]
142
            );
143
        }
144
145
        $columnsTable->render();
146
    }
147
148
    /**
149
     * @param array<int,AbstractIndex> $indexes
150
     */
151
    protected function describeIndexes(DatabaseInterface $database, array $indexes, InputInterface $input): void
152
    {
153
        $this->sprintf(
154
            "\n<fg=cyan>Indexes of </fg=cyan><comment>%s.%s</comment>:\n",
155
            $database->getName(),
156
            $input->getArgument('table')
157
        );
158
159
        $indexesTable = $this->table->setHeaders(['Name:', 'Type:', 'Columns:']);
160
161
        foreach ($indexes as $index) {
162
            $indexesTable->addRow(
163
                [
164
                    $index->getName(),
165
                    $index->isUnique() ? 'UNIQUE INDEX' : 'INDEX',
166
                    \implode(', ', $index->getColumns()),
167
                ]
168
            );
169
        }
170
171
        $indexesTable->render();
172
    }
173
174
    /**
175
     * @param array<int,AbstractForeignKey> $foreignKeys
176
     */
177
    protected function describeForeignKeys(DatabaseInterface $database, array $foreignKeys, InputInterface $input): void
178
    {
179
        $this->sprintf(
180
            "\n<fg=cyan>Foreign Keys of </fg=cyan><comment>%s.%s</comment>:\n",
181
            $database->getName(),
182
            $input->getArgument('table')
183
        );
184
        $foreignTable = $this->table->setHeaders(
185
            [
186
                'Name:',
187
                'Column:',
188
                'Foreign Table:',
189
                'Foreign Column:',
190
                'On Delete:',
191
                'On Update:',
192
            ]
193
        );
194
195
        foreach ($foreignKeys as $reference) {
196
            $foreignTable->addRow(
197
                [
198
                    $reference->getName(),
199
                    \implode(', ', $reference->getColumns()),
200
                    $reference->getForeignTable(),
201
                    \implode(', ', $reference->getForeignKeys()),
202
                    $reference->getDeleteRule(),
203
                    $reference->getUpdateRule(),
204
                ]
205
            );
206
        }
207
208
        $foreignTable->render();
209
    }
210
211
    /**
212
     * @return mixed
213
     */
214
    protected function describeDefaultValue(AbstractColumn $column, DriverInterface $driver)
215
    {
216
        $defaultValue = $column->getDefaultValue();
217
218
        if ($defaultValue instanceof FragmentInterface) {
219
            $value = $driver->getQueryCompiler()->compile(new QueryParameters(), '', $defaultValue);
220
221
            return "<info>{$value}</info>";
222
        }
223
224
        if ($defaultValue instanceof \DateTimeInterface) {
225
            $defaultValue = $defaultValue->format('c');
226
        }
227
228
        return $defaultValue;
229
    }
230
231
    /**
232
     * Identical to write function but provides ability to format message. Does not add new line.
233
     *
234
     * @param array ...$args
235
     */
236
    protected function sprintf(string $format, ...$args)
237
    {
238
        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...
239
    }
240
241
    private function describeType(AbstractColumn $column): string
242
    {
243
        $type = $column->getType();
244
245
        $abstractType = $column->getAbstractType();
246
247
        if ($column->getSize()) {
248
            $type .= " ({$column->getSize()})";
249
        }
250
251
        if ('decimal' === $abstractType) {
252
            $type .= " ({$column->getPrecision()}, {$column->getScale()})";
253
        }
254
255
        return $type;
256
    }
257
258
    private function describeAbstractType(AbstractColumn $column): string
259
    {
260
        $abstractType = $column->getAbstractType();
261
262
        if (\in_array($abstractType, ['primary', 'bigPrimary'], true)) {
263
            $abstractType = "<fg=magenta>{$abstractType}</fg=magenta>";
264
        }
265
266
        return $abstractType;
267
    }
268
}
269