Passed
Branch main (5746ae)
by Sammy
02:23
created

Database::introspect()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 43
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 4
eloc 22
nc 5
nop 0
dl 0
loc 43
rs 9.568
c 3
b 0
f 0
1
<?php
2
3
namespace HexMakina\Crudites;
4
5
use HexMakina\Crudites\Queries\Select;
6
use HexMakina\Crudites\Queries\Describe;
7
use HexMakina\Crudites\Table\Manipulation;
8
use HexMakina\Crudites\Table\Column;
9
use HexMakina\BlackBox\Database\ConnectionInterface;
10
use HexMakina\BlackBox\Database\DatabaseInterface;
11
use HexMakina\BlackBox\Database\TableManipulationInterface;
12
13
class Database implements DatabaseInterface
14
{
15
    private $connection = null;
16
    private $table_cache = [];
17
    private $fk_by_table = [];
18
    private $unique_by_table = [];
19
20
    public function __construct(ConnectionInterface $connection)
21
    {
22
        $this->connection = $connection;
23
        $this->introspect();
24
    }
25
26
    public function name()
27
    {
28
        return $this->connection()->databaseName();
29
    }
30
31
    public function connection(): ConnectionInterface
32
    {
33
        return $this->connection;
34
    }
35
36
    public function introspect()
37
    {
38
        $fields = [
39
          'TABLE_NAME',
40
          'CONSTRAINT_NAME',
41
          'ORDINAL_POSITION',
42
          'COLUMN_NAME',
43
          'POSITION_IN_UNIQUE_CONSTRAINT',
44
          'REFERENCED_TABLE_NAME',
45
          'REFERENCED_COLUMN_NAME'
46
        ];
47
48
        $statement = 'SELECT ' . implode(', ', $fields)
49
        . ' FROM KEY_COLUMN_USAGE'
50
        . ' WHERE TABLE_SCHEMA = "%s"'
51
        . ' ORDER BY TABLE_NAME, CONSTRAINT_NAME, ORDINAL_POSITION';
52
53
        $this->connection->useDatabase('INFORMATION_SCHEMA');
54
        $res = $this->connection->query(sprintf($statement, $this->name()))->fetchAll();
55
        $this->connection->useDatabase($this->name());
56
57
        foreach ($res as $key_usage) {
58
            $table_name = $key_usage['TABLE_NAME'];
59
60
            if (isset($key_usage['REFERENCED_TABLE_NAME'])) { // FOREIGN KEYS
61
                $this->addForeignKeyByTable($table_name, $key_usage);
62
63
                // $this->fk_by_table[$table_name] = $this->fk_by_table[$table_name] ?? [];
64
                // $this->fk_by_table[$table_name][$key_usage['COLUMN_NAME']] = [$key_usage['REFERENCED_TABLE_NAME'], $key_usage['REFERENCED_COLUMN_NAME']];
65
            }
66
67
            if (!isset($key_usage['POSITION_IN_UNIQUE_CONSTRAINT'])) { // PRIMARY & UNIQUES
68
                $this->addUniqueKeyByTable($table_name, $key_usage);
69
                // $constraint_name = $key_usage['CONSTRAINT_NAME'];
70
                // $column_name = $key_usage['COLUMN_NAME'];
71
                //
72
                // $this->unique_by_table[$table_name] = $this->unique_by_table[$table_name] ?? [];
73
                // $this->unique_by_table[$table_name][$constraint_name] = $this->unique_by_table[$table_name][$constraint_name] ?? [];
74
                // $this->unique_by_table[$table_name][$constraint_name][$key_usage['ORDINAL_POSITION']] = $column_name;
75
            }
76
        }
77
78
        $this->refactorConstraintNameIndex();
79
    }
80
81
    // vague memory that it makes later operation easier. written on the spot.. testing will reveal it's true nature
82
    private function refactorConstraintNameIndex()
83
    {
84
        foreach ($this->unique_by_table as $table_name => $uniques) {
85
            foreach ($uniques as $constraint_name => $columns) {
86
                foreach ($columns as $column_name) {
87
                    $this->unique_by_table[$table_name][$column_name] = [0 => $constraint_name] + $columns;
88
                }
89
                unset($this->unique_by_table[$table_name][$constraint_name]);
90
            }
91
        }
92
    }
93
94
    private function addUniqueKeyByTable($table_name, $key_usage)
95
    {
96
        $constraint_name = $key_usage['CONSTRAINT_NAME'];
97
        $column_name = $key_usage['COLUMN_NAME'];
98
99
        $this->unique_by_table[$table_name] = $this->unique_by_table[$table_name] ?? [];
100
        $this->unique_by_table[$table_name][$constraint_name] = $this->unique_by_table[$table_name][$constraint_name] ?? [];
101
        $this->unique_by_table[$table_name][$constraint_name][$key_usage['ORDINAL_POSITION']] = $column_name;
102
    }
103
104
    private function addForeignKeyByTable($table_name, $key_usage)
105
    {
106
        $this->fk_by_table[$table_name] = $this->fk_by_table[$table_name] ?? [];
107
        $this->fk_by_table[$table_name][$key_usage['COLUMN_NAME']] = [$key_usage['REFERENCED_TABLE_NAME'], $key_usage['REFERENCED_COLUMN_NAME']];
108
    }
109
110
    public function inspect($table_name): TableManipulationInterface
111
    {
112
        if (isset($this->table_cache[$table_name])) {
113
            return $this->table_cache[$table_name];
114
        }
115
116
117
        $describe = (new Describe($table_name));
118
        $describe->connection($this->connection());
119
        $description = $describe->ret();
120
121
      // TODO test this when all is back to normal 2021.03.09
122
        if ($description === false) {
123
            throw new \PDOException("Unable to describe $table_name");
124
        }
125
126
        $table = new Manipulation($table_name, $this->connection());
127
128
        foreach ($description as $column_name => $specs) {
129
            $column = new Column($table, $column_name, $specs);
130
131
          // handling usage constraints
132
            if (isset($this->unique_by_table[$table_name][$column_name])) {
133
                $unique_name = $this->unique_by_table[$table_name][$column_name][0];
134
135
                switch (count($this->unique_by_table[$table_name][$column_name])) {
136
                    case 2: // constraint name + column
137
                        $column->uniqueName($unique_name);
138
                        $table->addUniqueKey($unique_name, $column_name);
139
                        break;
140
141
                    default:
142
                        $column->uniqueGroupName($unique_name);
143
                        unset($this->unique_by_table[$table_name][$column_name][0]);
144
                        $table->addUniqueKey($unique_name, $this->unique_by_table[$table_name][$column_name]);
145
                        break;
146
                }
147
            }
148
            // handling usage foreign keys
149
            if (($reference = $this->getForeignKey($table_name, $column_name)) !== false) {
150
                $column->isForeign(true);
151
                $column->setForeignTableName($reference[0]);
152
                $column->setForeignColumnName($reference[1]);
153
154
                $table->addForeignKey($column);
155
            }
156
            $table->addColumn($column);
157
        }
158
      // ddt($table);
159
        $this->table_cache[$table_name] = $table;
160
161
        return $this->table_cache[$table_name];
162
    }
163
164
    public function getForeignKey($table_name, $column_name)
165
    {
166
        return $this->fk_by_table[$table_name][$column_name] ?? false;
167
    }
168
}
169