Passed
Push — master ( 37c5f2...a4e384 )
by Divine Niiquaye
03:06
created

DatabaseTableCommand::execute()   A

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 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 16
c 1
b 0
f 0
nc 5
nop 2
dl 0
loc 30
ccs 0
cts 13
cp 0
crap 20
rs 9.7333
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of DivineNii opensource projects.
7
 *
8
 * PHP version 7.4 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2019 DivineNii (https://divinenii.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 Rade\Commands\CycleORM;
19
20
use Cycle\Database\DatabaseInterface;
0 ignored issues
show
Bug introduced by
The type Cycle\Database\DatabaseInterface 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...
21
use Cycle\Database\DatabaseProviderInterface;
0 ignored issues
show
Bug introduced by
The type Cycle\Database\DatabaseProviderInterface 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\Driver\DriverInterface;
0 ignored issues
show
Bug introduced by
The type Cycle\Database\Driver\DriverInterface 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...
23
use Cycle\Database\Exception\DBALException;
0 ignored issues
show
Bug introduced by
The type Cycle\Database\Exception\DBALException 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...
24
use Cycle\Database\Injection\FragmentInterface;
0 ignored issues
show
Bug introduced by
The type Cycle\Database\Injection\FragmentInterface 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...
25
use Cycle\Database\Query\QueryParameters;
0 ignored issues
show
Bug introduced by
The type Cycle\Database\Query\QueryParameters 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...
26
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...
27
use Cycle\Database\Schema\AbstractForeignKey;
0 ignored issues
show
Bug introduced by
The type Cycle\Database\Schema\AbstractForeignKey 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...
28
use Cycle\Database\Schema\AbstractIndex;
0 ignored issues
show
Bug introduced by
The type Cycle\Database\Schema\AbstractIndex 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...
29
use Cycle\Database\Schema\AbstractTable;
0 ignored issues
show
Bug introduced by
The type Cycle\Database\Schema\AbstractTable 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...
30
use Symfony\Component\Console\Command\Command;
31
use Symfony\Component\Console\Helper\Table;
32
use Symfony\Component\Console\Input\InputArgument;
33
use Symfony\Component\Console\Input\InputInterface;
34
use Symfony\Component\Console\Input\InputOption;
35
use Symfony\Component\Console\Output\OutputInterface;
36
use Symfony\Component\Console\Style\SymfonyStyle;
37
38
/**
39
 * Describes table schema for a database.
40
 *
41
 * @author Divine Niiquaye Ibok <[email protected]>
42
 */
43
class DatabaseTableCommand extends Command
44
{
45
    protected static $defaultName = 'cycle:database:table';
46
47
    private SymfonyStyle $io;
48
49
    private Table $table;
50
51
    public function __construct(private DatabaseProviderInterface $provider)
52
    {
53
        parent::__construct();
54
    }
55
56
    /**
57
     * {@inheritdoc}
58
     */
59
    protected function configure(): void
60
    {
61
        $this
62
            ->setDefinition([
63
                new InputArgument('table', InputArgument::REQUIRED, 'Table name'),
64
                new InputOption('database', 'd', InputOption::VALUE_OPTIONAL, 'Source database', 'default'),
65
            ])
66
            ->setDescription('Describe table schema of specific database')
67
        ;
68
    }
69
70
    /**
71
     * This optional method is the first one executed for a command after configure()
72
     * and is useful to initialize properties based on the input arguments and options.
73
     */
74
    protected function initialize(InputInterface $input, OutputInterface $output): void
75
    {
76
        // SymfonyStyle is an optional feature that Symfony provides so you can
77
        // apply a consistent look to the commands of your application.
78
        // See https://symfony.com/doc/current/console/style.html
79
        $this->io = new SymfonyStyle($input, $output);
80
        $this->table = new Table($output);
81
    }
82
83
    /**
84
     * {@inheritdoc}
85
     */
86
    protected function execute(InputInterface $input, OutputInterface $output): int
87
    {
88
        $database = $this->provider->database($input->getOption('database'));
89
        $schema = $database->table($input->getArgument('table'))->getSchema();
90
91
        if (!$schema->exists()) {
92
            throw new DBALException("Table {$database->getName()}.{$input->getArgument('table')} does not exists.");
93
        }
94
95
        $output->writeln(
96
            \sprintf(
97
                "\n<fg=cyan>Columns of </fg=cyan><comment>%s.%s</comment>:\n",
98
                $database->getName(),
99
                $input->getArgument('table')
100
            )
101
        );
102
103
        $this->describeColumns($schema);
104
105
        if (!empty($indexes = $schema->getIndexes())) {
106
            $this->describeIndexes($database, $indexes, $input);
107
        }
108
109
        if (!empty($foreignKeys = $schema->getForeignKeys())) {
110
            $this->describeForeignKeys($database, $foreignKeys, $input);
111
        }
112
113
        $output->write("\n");
114
115
        return self::SUCCESS;
116
    }
117
118
    protected function describeColumns(AbstractTable $schema): void
119
    {
120
        $columnsTable = $this->table->setHeaders(
121
            [
122
                'Column:',
123
                'Database Type:',
124
                'Abstract Type:',
125
                'PHP Type:',
126
                'Default Value:',
127
            ]
128
        );
129
130
        foreach ($schema->getColumns() as $column) {
131
            $name = $column->getName();
132
133
            if (\in_array($column->getName(), $schema->getPrimaryKeys(), true)) {
134
                $name = "<fg=magenta>{$name}</fg=magenta>";
135
            }
136
137
            $defaultValue = $this->describeDefaultValue($column, $schema->getDriver());
138
139
            $columnsTable->addRow(
140
                [
141
                    $name,
142
                    $this->describeType($column),
143
                    $this->describeAbstractType($column),
144
                    $column->getType(),
145
                    $defaultValue ?? '<comment>---</comment>',
146
                ]
147
            );
148
        }
149
150
        $columnsTable->render();
151
    }
152
153
    /**
154
     * @param array<int,AbstractIndex> $indexes
155
     */
156
    protected function describeIndexes(DatabaseInterface $database, array $indexes, InputInterface $input): void
157
    {
158
        $this->sprintf(
159
            "\n<fg=cyan>Indexes of </fg=cyan><comment>%s.%s</comment>:\n",
160
            $database->getName(),
161
            $input->getArgument('table')
162
        );
163
164
        $indexesTable = $this->table->setHeaders(['Name:', 'Type:', 'Columns:']);
165
166
        foreach ($indexes as $index) {
167
            $indexesTable->addRow(
168
                [
169
                    $index->getName(),
170
                    $index->isUnique() ? 'UNIQUE INDEX' : 'INDEX',
171
                    \implode(', ', $index->getColumns()),
172
                ]
173
            );
174
        }
175
176
        $indexesTable->render();
177
    }
178
179
    /**
180
     * @param array<int,AbstractForeignKey> $foreignKeys
181
     */
182
    protected function describeForeignKeys(DatabaseInterface $database, array $foreignKeys, InputInterface $input): void
183
    {
184
        $this->sprintf(
185
            "\n<fg=cyan>Foreign Keys of </fg=cyan><comment>%s.%s</comment>:\n",
186
            $database->getName(),
187
            $input->getArgument('table')
188
        );
189
        $foreignTable = $this->table->setHeaders(
190
            [
191
                'Name:',
192
                'Column:',
193
                'Foreign Table:',
194
                'Foreign Column:',
195
                'On Delete:',
196
                'On Update:',
197
            ]
198
        );
199
200
        foreach ($foreignKeys as $reference) {
201
            $foreignTable->addRow(
202
                [
203
                    $reference->getName(),
204
                    \implode(', ', $reference->getColumns()),
205
                    $reference->getForeignTable(),
206
                    \implode(', ', $reference->getForeignKeys()),
207
                    $reference->getDeleteRule(),
208
                    $reference->getUpdateRule(),
209
                ]
210
            );
211
        }
212
213
        $foreignTable->render();
214
    }
215
216
    /**
217
     * @return mixed
218
     */
219
    protected function describeDefaultValue(AbstractColumn $column, DriverInterface $driver)
220
    {
221
        $defaultValue = $column->getDefaultValue();
222
223
        if ($defaultValue instanceof FragmentInterface) {
224
            $value = $driver->getQueryCompiler()->compile(new QueryParameters(), '', $defaultValue);
225
226
            return "<info>{$value}</info>";
227
        }
228
229
        if ($defaultValue instanceof \DateTimeInterface) {
230
            $defaultValue = $defaultValue->format('c');
231
        }
232
233
        return $defaultValue;
234
    }
235
236
    /**
237
     * Identical to write function but provides ability to format message. Does not add new line.
238
     *
239
     * @param array ...$args
240
     */
241
    protected function sprintf(string $format, ...$args)
242
    {
243
        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...
244
    }
245
246
    private function describeType(AbstractColumn $column): string
247
    {
248
        $type = $column->getType();
249
250
        $abstractType = $column->getAbstractType();
251
252
        if ($column->getSize()) {
253
            $type .= " ({$column->getSize()})";
254
        }
255
256
        if ('decimal' === $abstractType) {
257
            $type .= " ({$column->getPrecision()}, {$column->getScale()})";
258
        }
259
260
        return $type;
261
    }
262
263
    private function describeAbstractType(AbstractColumn $column): string
264
    {
265
        $abstractType = $column->getAbstractType();
266
267
        if (\in_array($abstractType, ['primary', 'bigPrimary'])) {
268
            $abstractType = "<fg=magenta>{$abstractType}</fg=magenta>";
269
        }
270
271
        return $abstractType;
272
    }
273
}
274