Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
68.97% covered (warning)
68.97%
40 / 58
70.00% covered (warning)
70.00%
7 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
SchemaUtils
68.97% covered (warning)
68.97%
40 / 58
70.00% covered (warning)
70.00%
7 / 10
77.92
0.00% covered (danger)
0.00%
0 / 1
 getTables
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getTableFields
88.00% covered (warning)
88.00%
22 / 25
0.00% covered (danger)
0.00%
0 / 1
11.21
 firstHumanReadableField
62.50% covered (warning)
62.50%
5 / 8
0.00% covered (danger)
0.00%
0 / 1
9.58
 getTableFieldsWithIds
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUserIdField
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 hasTable
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasTimestamps
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 hasSoftDelete
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 tableExists
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRealTableName
14.29% covered (danger)
14.29%
2 / 14
0.00% covered (danger)
0.00%
0 / 1
37.86
1<?php
2
3namespace San\Crud\Utils;
4
5use Illuminate\Support\Facades\DB;
6
7class SchemaUtils {
8    public static function getTables(string|array $exclude) {
9        $tables = DB::connection()->getDoctrineSchemaManager()->listTableNames();
10
11        return array_values(array_filter($tables, fn($table) => !in_array($table, (array) $exclude)));
12    }
13
14    public static function getTableFields(string $tableName, array $excludedColumns = [], array $alwaysIgnoredColumns = ['id', 'created_at', 'updated_at', 'deleted_at']) {
15        // ugly enum hack as doctrine does not support enum types
16        // https://www.doctrine-project.org/projects/doctrine-orm/en/latest/cookbook/mysql-enums.html#solution-1-mapping-to-varchars
17
18        $tableName = self::getRealTableName($tableName);
19        DB::connection()->getDoctrineSchemaManager()->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'guid');
20
21        $columns = DB::getDoctrineSchemaManager()->listTableColumns($tableName);
22        $ignoredColumns = array_merge((array) $excludedColumns, (array) $alwaysIgnoredColumns);
23        $indexes = DB::getDoctrineSchemaManager()->listTableIndexes($tableName);
24        $uniqueColumns = [];
25
26        foreach ($indexes as $index) {
27            if ($index->isUnique() && count($index->getColumns()) === 1) {
28                $uniqueColumns = array_merge($uniqueColumns, $index->getColumns());
29            }
30        }
31
32        foreach ($columns as $column) {
33            if (in_array($column->getName(), $ignoredColumns)) continue;
34
35            $field = ['id' => $column->getName(), 'type' => $column->getType()->getName(), 'name' => \Str::title(str_replace('_', ' ', $column->getName())), 'nullable' => !$column->getNotnull()];
36
37            if ($field['type'] == 'guid') {
38                try {
39                    $enums = DB::select("SHOW COLUMNS FROM $tableName WHERE Field = '$field[name]'");
40                    $field['values'] = explode(',', str_replace("'", '', substr($enums[0]->Type, 5, -1)));
41                } catch (\Throwable $e) {
42                }
43            }
44
45            if (preg_match('/^(.*?)_id$/', $field['id'], $matches)) {
46                $relatedTable = \Str::plural($matches[1]);
47                if (self::tableExists($relatedTable)) {
48                    $field['relation'] = $matches[1];
49                    $field['related_table'] = $relatedTable;
50                }
51            }
52
53            //check if column is unique index
54            if (in_array($field['id'], $uniqueColumns)) {
55                $field['unique'] = TRUE;
56            }
57
58            $fields[] = $field;
59        }
60
61        return $fields ?? [];
62    }
63
64    public static function firstHumanReadableField(string $table, string $key = NULL) {
65        $all = self::getTableFields($table);
66        if (empty($all)) return NULL;
67
68        foreach ($all as $f) {
69            if (preg_match('/_id$/', $f['id'])) continue;
70            if (preg_match('/(string|text)/', $f['type'])) return $key ? $f[$key] : $f;
71            $last = $f['id'];
72        }
73
74        $result = $last ?? $all[0];
75        return $key ? $result[$key] : $result;
76    }
77
78    public static function getTableFieldsWithIds(string $table, array $excludedColumns = []) {
79        return array_values(array_filter(self::getTableFields($table, $excludedColumns), fn($f) => !empty($f['relation'])));
80    }
81
82    public static function getUserIdField(string $tableName, $userIdField = 'user_id') {
83        if (!self::tableExists($tableName)) return NULL;
84        return \Schema::hasColumn(self::getRealTableName($tableName), $userIdField) ? $userIdField : NULL;
85    }
86
87    public static function hasTable(string $tableName) {
88        return self::tableExists($tableName);
89    }
90
91    public static function hasTimestamps(string $tableName) {
92        if (!self::tableExists($tableName)) return FALSE;
93        return \Schema::hasColumn(self::getRealTableName($tableName), 'created_at') && \Schema::hasColumn(self::getRealTableName($tableName), 'updated_at');
94    }
95
96    public static function hasSoftDelete(string $tableName) {
97        if (!self::tableExists($tableName)) return FALSE;
98        return \Schema::hasColumn(self::getRealTableName($tableName), 'deleted_at');
99    }
100
101    public static function tableExists(string $tableName) {
102        return !!self::getRealTableName($tableName);
103    }
104
105    public static function getRealTableName(string $tableName) {
106        if (\Schema::hasTable($tableName)) return $tableName;
107        //return false if it's not a mysql connection
108        if (DB::connection()->getDriverName() != 'mysql') return FALSE;
109
110        if (empty($GLOBALS['view_tables'])) $GLOBALS['view_tables'] = [];
111        if (!empty($GLOBALS['view_tables'][$tableName])) return $GLOBALS['view_tables'][$tableName];
112
113        //check if there is a view
114        $views = DB::select('SHOW FULL TABLES WHERE Table_type = \'VIEW\'');
115        foreach ($views as $view) {
116            $key = 'Tables_in_' . DB::connection()->getDatabaseName();
117            if ($view->$key == $tableName) {
118                //get the original table name
119                $results = DB::select("SHOW CREATE VIEW $tableName");
120                $result = $results[0]->{'Create View'};
121                preg_match('/ FROM `(.*?)` /i', $result, $matches);
122                $GLOBALS['view_tables'][$tableName] = $matches[1];
123                return $matches[1];
124            }
125        }
126
127        return FALSE;
128    }
129}