Passed
Pull Request — master (#97)
by Aleksei
03:27
created

SchemaCommand::configure()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 4
ccs 0
cts 3
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Cycle\Command\Schema;
6
7
use Cycle\ORM\Relation;
8
use Cycle\ORM\Schema;
9
use Cycle\ORM\SchemaInterface;
10
use Symfony\Component\Console\Command\Command;
11
use Symfony\Component\Console\Input\InputArgument;
12
use Symfony\Component\Console\Input\InputInterface;
13
use Symfony\Component\Console\Output\OutputInterface;
14
use Yiisoft\Yii\Console\ExitCode;
15
use Yiisoft\Yii\Cycle\Command\CycleDependencyProxy;
16
17
final class SchemaCommand extends Command
18
{
19
    protected static $defaultName = 'cycle/schema';
20
21
    private CycleDependencyProxy $promise;
22
    private const STR_RELATION = [
23
        Relation::HAS_ONE => 'has one',
24
        Relation::HAS_MANY => 'has many',
25
        Relation::BELONGS_TO => 'belongs to',
26
        Relation::REFERS_TO => 'refers to',
27
        Relation::MANY_TO_MANY => 'many to many',
28
        Relation::BELONGS_TO_MORPHED => 'belongs to morphed',
29
        Relation::MORPHED_HAS_ONE => 'morphed has one',
30
        Relation::MORPHED_HAS_MANY => 'morphed has many',
31
    ];
32
    private const STR_PREFETCH_MODE = [
33
        Relation::LOAD_PROMISE => 'promise',
34
        Relation::LOAD_EAGER => 'eager',
35
    ];
36
37
    public function __construct(CycleDependencyProxy $promise)
38
    {
39
        $this->promise = $promise;
40
        parent::__construct();
41
    }
42
43
    public function configure(): void
44
    {
45
        $this->setDescription('Shown current schema');
46
        $this->addArgument('role', InputArgument::OPTIONAL, 'Roles to display (separated by ",").');
47
    }
48
49
    protected function execute(InputInterface $input, OutputInterface $output): int
50
    {
51
        /** @var string|null $roleArgument */
52
        $roleArgument = $input->getArgument('role');
53
        $result = true;
54
        $schema = $this->promise->getSchema();
55
        $roles = $roleArgument !== null ? explode(',', $roleArgument) : $schema->getRoles();
56
57
        foreach ($roles as $role) {
58
            $result = $this->displaySchema($schema, $role, $output) && $result;
59
        }
60
61
        return $result ? ExitCode::OK : ExitCode::UNSPECIFIED_ERROR;
62
    }
63
64
    /**
65
     * Write a role schema in the output
66
     *
67
     * @param SchemaInterface $schema Data schema
68
     * @param string $role Role to display
69
     * @param OutputInterface $output Output console
70
     *
71
     * @return bool
72
     */
73
    private function displaySchema(SchemaInterface $schema, string $role, OutputInterface $output): bool
74
    {
75
        if (!$schema->defines($role)) {
76
            $output->writeln(sprintf("<fg=red>The role</> %s <fg=red>is not defined!</>", $this->wrapRole($role)));
77
            return false;
78
        }
79
80
        $output->write('[' . $this->wrapRole($role));
81
        $alias = $schema->resolveAlias($role);
82
        // alias
83
        if ($alias !== null && $alias !== $role) {
84
            $output->write(' => ' . $this->wrapRole($alias));
85
        }
86
        $output->write(']');
87
88
        // database and table
89
        $database = $schema->define($role, Schema::DATABASE);
90
        $table = $schema->define($role, Schema::TABLE);
91
        if ($database !== null) {
92
            $output->write(sprintf(' :: %s.%s', $this->wrapDB($database), $this->wrapDB($table)));
93
        }
94
        $output->writeln('');
95
96
        // Entity
97
        $entity = $schema->define($role, Schema::ENTITY);
98
        $output->write('   Entity     : ');
99
        $output->writeln($entity === null ? 'no entity' : $this->wrapPhp($entity));
100
        // Mapper
101
        $mapper = $schema->define($role, Schema::MAPPER);
102
        $output->write('   Mapper     : ');
103
        $output->writeln($mapper === null ? 'no mapper' : $this->wrapPhp($mapper));
104
        // Constrain
105
        $constrain = $schema->define($role, Schema::CONSTRAIN);
106
        $output->write('   Constrain  : ');
107
        $output->writeln($constrain === null ? 'no constrain' : $this->wrapPhp($constrain));
108
        // Repository
109
        $repository = $schema->define($role, Schema::REPOSITORY);
110
        $output->write('   Repository : ');
111
        $output->writeln($repository === null ? 'no repository' : $this->wrapPhp($repository));
112
        // PK
113
        $pk = $schema->define($role, Schema::PRIMARY_KEY);
114
        $output->write('   Primary key: ');
115
        $output->writeln($pk === null ? 'no primary key' : $this->wrapProperty($pk));
116
        // Fields
117
        $columns = $schema->define($role, Schema::COLUMNS);
118
        $output->write('   Fields     :');
119
        $output->writeln(sprintf(
120
            ' (%s -> %s -> %s)',
121
            $this->wrapProperty('property'),
122
            $this->wrapDB('db.field'),
123
            $this->wrapPhp('typecast')
124
        ));
125
        $types = $schema->define($role, Schema::TYPECAST);
126
        foreach ($columns as $property => $field) {
127
            $typecast = $types[$property] ?? $types[$field] ?? null;
128
            $output->write(sprintf('     %s -> %s', $this->wrapProperty($property), $this->wrapDB($field)));
129
            if ($typecast !== null) {
130
                $output->write(sprintf(' -> %s', $this->wrapPhp(implode('::', (array)$typecast))));
131
            }
132
            $output->writeln('');
133
        }
134
135
        // Relations
136
        $relations = $schema->define($role, Schema::RELATIONS);
137
        if (count($relations) > 0) {
138
            $output->writeln('   Relations  :');
139
            foreach ($relations as $field => $relation) {
140
                $type = self::STR_RELATION[$relation[Relation::TYPE] ?? ''] ?? '?';
141
                $target = $relation[Relation::TARGET] ?? '?';
142
                $loading = self::STR_PREFETCH_MODE[$relation[Relation::LOAD] ?? ''] ?? '?';
143
                $relSchema = $relation[Relation::SCHEMA];
144
                $innerKey = $relSchema[Relation::INNER_KEY] ?? '?';
145
                $outerKey = $relSchema[Relation::OUTER_KEY] ?? '?';
146
                $where = $relSchema[Relation::WHERE] ?? [];
147
                $orderBy = $relSchema[Relation::ORDER_BY] ?? [];
148
                $cascade = $relSchema[Relation::CASCADE] ?? null;
149
                $cascadeStr = $cascade ? 'cascaded' : 'not cascaded';
150
                $nullable = $relSchema[Relation::NULLABLE] ?? null;
151
                $nullableStr = $nullable ? 'nullable' : ($nullable === false ? 'not null' : 'n/a');
152
                $morphKey = $relSchema[Relation::MORPH_KEY] ?? null;
153
                // Many-To-Many relation(s) options
154
                $mmInnerKey = $relSchema[Relation::THROUGH_INNER_KEY] ?? '?';
155
                $mmOuterKey = $relSchema[Relation::THROUGH_OUTER_KEY] ?? '?';
156
                $mmEntity = $relSchema[Relation::THROUGH_ENTITY] ?? null;
157
                $mmWhere = $relSchema[Relation::THROUGH_WHERE] ?? [];
158
                // print
159
                $output->write(
160
                    sprintf(
161
                        '     %s->%s %s %s %s load',
162
                        $this->wrapRole($role),
163
                        $this->wrapProperty($field),
164
                        $type,
165
                        $this->wrapRole($target),
166
                        $loading
167
                    )
168
                );
169
                if ($morphKey !== null) {
170
                    $output->writeln('       Morphed key: ' . $this->wrapProperty($morphKey));
171
                }
172
                $output->writeln(" <fg=yellow>{$cascadeStr}</>");
173
                $output->write(
174
                    sprintf('       %s %s.%s <=', $nullableStr, $this->wrapRole($role), $this->wrapProperty($innerKey))
175
                );
176
                if ($mmEntity !== null) {
177
                    $output->write(sprintf(' %s.%s', $this->wrapRole($mmEntity), $this->wrapProperty($mmInnerKey)));
178
                    $output->write('|');
179
                    $output->write(sprintf('%s.%s ', $this->wrapRole($mmEntity), $this->wrapProperty($mmOuterKey)));
180
                }
181
                $output->writeln(sprintf('=> %s.%s ', $this->wrapRole($target), $this->wrapProperty($outerKey)));
182
                if (count($where)) {
183
                    $output->write('       Where:');
184
                    $output->writeln($this->printValue($where, '         '));
185
                }
186
                if (count($orderBy)) {
187
                    $output->writeln('       Order by:');
188
                    $output->writeln($this->printValue($orderBy, '         '));
189
                }
190
                if (count($mmWhere)) {
191
                    $output->write('       Through where:');
192
                    $output->writeln($this->printValue($mmWhere, '         '));
193
                }
194
            }
195
        } else {
196
            $output->writeln('   No relations');
197
        }
198
        return true;
199
    }
200
201
    /**
202
     * @param mixed $value
203
     */
204
    private function printValue($value, string $prefix = ''): string
205
    {
206
        if (!is_iterable($value)) {
207
            return (string)$value;
208
        }
209
        $result = [];
210
        foreach ($value as $key => $val) {
211
            $result[] = "$prefix$key" . (is_iterable($val) ? ":\n" : ' => ') . $this->printValue($val, $prefix . '  ');
212
        }
213
        return implode("\n", $result);
214
    }
215
216
    private function wrapRole(string $role): string
217
    {
218
        return "<fg=magenta>{$role}</>";
219
    }
220
221
    private function wrapDB(string $entity): string
222
    {
223
        return "<fg=green>{$entity}</>";
224
    }
225
226
    private function wrapPhp(string $entity): string
227
    {
228
        return "<fg=blue>{$entity}</>";
229
    }
230
231
    private function wrapProperty(string $name): string
232
    {
233
        return "<fg=cyan>{$name}</>";
234
    }
235
}
236