Skip to content

Commit 82e27ea

Browse files
committedNov 22, 2024
Remove doctrine/dbal dependency
1 parent 173a191 commit 82e27ea

File tree

3 files changed

+78
-84
lines changed

3 files changed

+78
-84
lines changed
 

‎composer.json

+4-5
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,13 @@
99
"console commands"
1010
],
1111
"require": {
12-
"php": "^8.1",
13-
"doctrine/dbal": "^3.7",
14-
"laravel/framework": "^10.2 || ^11.0"
12+
"php": "^8.2",
13+
"laravel/framework": "^11.0"
1514
},
1615
"require-dev": {
1716
"interaction-design-foundation/coding-standard": "0.*",
18-
"orchestra/testbench": "^8.0",
19-
"phpunit/phpunit": "^10.1 || ^11.0"
17+
"orchestra/testbench": "^9.0",
18+
"phpunit/phpunit": "^11.0"
2019
},
2120
"minimum-stability": "dev",
2221
"prefer-stable": true,

‎src/Console/Commands/FindInvalidDatabaseValues.php

+36-43
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@
22

33
namespace InteractionDesignFoundation\LaravelDatabaseToolkit\Console\Commands;
44

5-
use Doctrine\DBAL\Schema\Column;
6-
use Doctrine\DBAL\Schema\Table;
7-
use Doctrine\DBAL\Types\Types;
85
use Illuminate\Database\Connection;
96
use Illuminate\Database\ConnectionResolverInterface;
107
use Illuminate\Database\Console\DatabaseInspectionCommand;
118
use Illuminate\Database\MySqlConnection;
129
use Illuminate\Support\Facades\DB;
10+
use Illuminate\Support\Facades\Schema;
1311
use Symfony\Component\Console\Attribute\AsCommand;
1412

1513
#[AsCommand('database:find-invalid-values')]
@@ -20,34 +18,27 @@ final class FindInvalidDatabaseValues extends DatabaseInspectionCommand
2018
private const CHECK_TYPE_LONG_TEXT = 'long_text';
2119
private const CHECK_TYPE_LONG_STRING = 'long_string';
2220

23-
/**
24-
* @var string The name and signature of the console command.
25-
*/
21+
/** @var string The name and signature of the console command. */
2622
protected $signature = 'database:find-invalid-values {connection=default} {--check=* : Check only specific types of issues. Available types: {null, datetime, long_text, long_string}}';
2723

28-
/**
29-
* @var string The console command description.
30-
*/
24+
/** @var string The console command description. */
3125
protected $description = 'Find invalid data created in non-strict SQL mode.';
3226

3327
private int $valuesWithIssuesFound = 0;
3428

35-
/**
36-
* @throws \Doctrine\DBAL\Exception
37-
*/
3829
public function handle(ConnectionResolverInterface $connections): int
3930
{
4031
$connection = $this->getConnection($connections);
41-
$schema = $connection->getDoctrineSchemaManager();
4232
if (!$connection instanceof MySqlConnection) {
4333
throw new \InvalidArgumentException('Command supports MySQL DBs only.');
4434
}
4535

46-
$this->registerTypeMappings($schema->getDatabasePlatform());
36+
$tables = Schema::getConnection()->getDoctrineSchemaManager()->listTableNames();
4737

48-
foreach ($schema->listTables() as $table) {
49-
foreach ($table->getColumns() as $column) {
50-
$this->processColumn($column, $table, $connection);
38+
foreach ($tables as $tableName) {
39+
$columns = Schema::getConnection()->getDoctrineSchemaManager()->listTableColumns($tableName);
40+
foreach ($columns as $column) {
41+
$this->processColumn($column, $tableName, $connection);
5142
}
5243
}
5344

@@ -60,24 +51,24 @@ public function handle(ConnectionResolverInterface $connections): int
6051
return self::FAILURE;
6152
}
6253

63-
private function processColumn(Column $column, Table $table, Connection $connection): void
54+
private function processColumn(object $column, string $tableName, Connection $connection): void
6455
{
65-
$this->info("{$table->getName()}.{$column->getName()}:\t{$column->getType()->getName()}", 'vvv');
56+
$this->info("{$tableName}.{$column->getName()}:\t{$column->getType()->getName()}", 'vvv');
6657

6758
if ($this->shouldRunCheckType(self::CHECK_TYPE_NULL)) {
68-
$this->checkNullOnNotNullableColumn($column, $connection, $table);
59+
$this->checkNullOnNotNullableColumn($column, $connection, $tableName);
6960
}
7061

7162
if ($this->shouldRunCheckType(self::CHECK_TYPE_DATETIME)) {
72-
$this->checkForInvalidDatetimeValues($column, $connection, $table);
63+
$this->checkForInvalidDatetimeValues($column, $connection, $tableName);
7364
}
7465

7566
if ($this->shouldRunCheckType(self::CHECK_TYPE_LONG_TEXT)) {
76-
$this->checkForTooLongTextTypeValues($column, $connection, $table);
67+
$this->checkForTooLongTextTypeValues($column, $connection, $tableName);
7768
}
7869

7970
if ($this->shouldRunCheckType(self::CHECK_TYPE_LONG_STRING)) {
80-
$this->checkForTooLongStringTypeValues($column, $connection, $table);
71+
$this->checkForTooLongStringTypeValues($column, $connection, $tableName);
8172
}
8273
}
8374

@@ -94,71 +85,73 @@ private function getConnection(ConnectionResolverInterface $connections): Connec
9485
return $connection;
9586
}
9687

97-
private function checkNullOnNotNullableColumn(Column $column, Connection $connection, Table $table): void
88+
private function checkNullOnNotNullableColumn(object $column, Connection $connection, string $tableName): void
9889
{
9990
if ($column->getNotnull()) {
10091
$columnName = $column->getName();
10192

102-
$nullsOnNotNullableColumnCount = DB::selectOne("SELECT COUNT(`{$columnName}`) AS `count` FROM {$connection->getDatabaseName()}.`{$table->getName()}` WHERE `{$columnName}` IS NULL")->count;
93+
$nullsOnNotNullableColumnCount = DB::selectOne("SELECT COUNT(`{$columnName}`) AS `count` FROM {$connection->getDatabaseName()}.`{$tableName}` WHERE `{$columnName}` IS NULL")->count;
10394
if ($nullsOnNotNullableColumnCount > 0) {
104-
$this->error("{$table->getName()}.{$columnName} has {$nullsOnNotNullableColumnCount} NULLs but the column is not nullable.");
95+
$this->error("{$tableName}.{$columnName} has {$nullsOnNotNullableColumnCount} NULLs but the column is not nullable.");
10596
$this->valuesWithIssuesFound += $nullsOnNotNullableColumnCount;
10697
} else {
10798
$this->comment("\t".self::CHECK_TYPE_NULL.': OK', 'vvv');
10899
}
109100
}
110101
}
111102

112-
private function checkForInvalidDatetimeValues(Column $column, Connection $connection, Table $table): void
103+
private function checkForInvalidDatetimeValues(object $column, Connection $connection, string $tableName): void
113104
{
114-
$integerProbablyUsedForTimestamp = in_array($column->getType()->getName(), [Types::INTEGER, Types::BIGINT], true) && (str_contains($column->getName(), 'timestamp') || str_ends_with($column->getName(), '_at'));
115-
if ($integerProbablyUsedForTimestamp
116-
|| in_array($column->getType()->getName(), [Types::DATE_MUTABLE, Types::DATE_IMMUTABLE, Types::DATETIME_MUTABLE, Types::DATETIME_IMMUTABLE, Types::DATETIMETZ_MUTABLE, Types::DATETIMETZ_IMMUTABLE], true)
105+
$columnType = $column->getType()->getName();
106+
$columnName = $column->getName();
107+
108+
$integerProbablyUsedForTimestamp = in_array($columnType, ['integer', 'bigint'], true) && (str_contains($columnName, 'timestamp') || str_ends_with($columnName, '_at'));
109+
if (
110+
$integerProbablyUsedForTimestamp
111+
|| in_array($columnType, ['date', 'datetime', 'timestamp'], true)
117112
) {
118-
$columnName = $column->getName();
119-
120-
$invalidDatetimeRecordsCount = DB::selectOne("SELECT COUNT(`{$columnName}`) AS `count` FROM {$connection->getDatabaseName()}.`{$table->getName()}` WHERE `{$columnName}` <= 1")->count;
113+
$invalidDatetimeRecordsCount = DB::selectOne("SELECT COUNT(`{$columnName}`) AS `count` FROM {$connection->getDatabaseName()}.`{$tableName}` WHERE `{$columnName}` <= 1")->count;
121114
if ($invalidDatetimeRecordsCount > 0) {
122-
$this->error("{$table->getName()}.{$columnName} has {$invalidDatetimeRecordsCount} invalid datetime values.");
115+
$this->error("{$tableName}.{$columnName} has {$invalidDatetimeRecordsCount} invalid datetime values.");
123116
$this->valuesWithIssuesFound += $invalidDatetimeRecordsCount;
124117
} else {
125118
$this->comment("\t".self::CHECK_TYPE_DATETIME.': OK', 'vvv');
126119
}
127120
}
128121
}
129122

130-
private function checkForTooLongTextTypeValues(Column $column, Connection $connection, Table $table): void
123+
private function checkForTooLongTextTypeValues(object $column, Connection $connection, string $tableName): void
131124
{
132-
if ($column->getType()->getName() === Types::TEXT) {
125+
if ($column->getType()->getName() === 'text') {
133126
$columnName = $column->getName();
134127

135-
$tooLongTextValuesCount = DB::selectOne("SELECT COUNT(`{$columnName}`) AS `count` FROM {$connection->getDatabaseName()}.`{$table->getName()}` WHERE LENGTH(`{$columnName}`) > @@max_allowed_packet;")->count;
128+
$tooLongTextValuesCount = DB::selectOne("SELECT COUNT(`{$columnName}`) AS `count` FROM {$connection->getDatabaseName()}.`{$tableName}` WHERE LENGTH(`{$columnName}`) > @@max_allowed_packet;")->count;
136129
if ($tooLongTextValuesCount > 0) {
137-
$this->error("{$table->getName()}.{$columnName} has {$tooLongTextValuesCount} too long text values.");
130+
$this->error("{$tableName}.{$columnName} has {$tooLongTextValuesCount} too long text values.");
138131
$this->valuesWithIssuesFound += $tooLongTextValuesCount;
139132
} else {
140133
$this->comment("\t".self::CHECK_TYPE_LONG_TEXT.': OK', 'vvv');
141134
}
142135
}
143136
}
144137

145-
private function checkForTooLongStringTypeValues(Column $column, Connection $connection, Table $table): void
138+
private function checkForTooLongStringTypeValues(object $column, Connection $connection, string $tableName): void
146139
{
147-
if (in_array($column->getType()->getName(), [Types::STRING, Types::ASCII_STRING], true)) {
140+
if (in_array($column->getType()->getName(), ['string', 'ascii_string'], true)) {
148141
$columnName = $column->getName();
149142

150143
$maxLength = $column->getLength();
151144

152145
if (is_int($maxLength) && $maxLength !== 0) {
153-
$tooLongStringValuesCount = DB::selectOne("SELECT COUNT(`{$columnName}`) AS `count` FROM {$connection->getDatabaseName()}.`{$table->getName()}` WHERE LENGTH(`{$columnName}`) > {$maxLength};")->count;
146+
$tooLongStringValuesCount = DB::selectOne("SELECT COUNT(`{$columnName}`) AS `count` FROM {$connection->getDatabaseName()}.`{$tableName}` WHERE LENGTH(`{$columnName}`) > {$maxLength};")->count;
154147
if ($tooLongStringValuesCount > 0) {
155-
$this->error("{$table->getName()}.{$columnName} has {$tooLongStringValuesCount} too long string values (longer than {$maxLength} chars).");
148+
$this->error("{$tableName}.{$columnName} has {$tooLongStringValuesCount} too long string values (longer than {$maxLength} chars).");
156149
$this->valuesWithIssuesFound += $tooLongStringValuesCount;
157150
} else {
158151
$this->comment("\t".self::CHECK_TYPE_LONG_STRING.': OK', 'vvv');
159152
}
160153
} else {
161-
$this->warn("Could not find max length for {$table->getName()}.{$columnName} column.");
154+
$this->warn("Could not find max length for {$tableName}.{$columnName} column.");
162155
}
163156
}
164157
}

‎src/Console/Commands/FindRiskyDatabaseColumns.php

+38-36
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@
66
use Illuminate\Database\ConnectionResolverInterface;
77
use Illuminate\Database\Console\DatabaseInspectionCommand;
88
use Illuminate\Database\MySqlConnection;
9-
use Illuminate\Support\Arr;
109
use Illuminate\Support\Facades\DB;
1110
use Illuminate\Support\Facades\Schema;
1211
use Symfony\Component\Console\Attribute\AsCommand;
13-
use function Laravel\Prompts\table;
1412

1513
/**
1614
* Inspired by @see https://medium.com/beyn-technology/ill-never-forget-this-number-4294967295-0xffffffff-c9ad4b72f53a
@@ -27,65 +25,59 @@
2725
#[AsCommand('database:find-risky-columns')]
2826
final class FindRiskyDatabaseColumns extends DatabaseInspectionCommand
2927
{
30-
/**
31-
* @var string The name and signature of the console command.
32-
*/
28+
/** @var string The name and signature of the console command. */
3329
protected $signature = 'database:find-risky-columns {connection=default} {--threshold=70 : Percentage occupied rows number on which the command should treat it as an issue}';
3430

35-
/**
36-
* @var string The console command description.
37-
*/
31+
/** @var string The console command description. */
3832
protected $description = 'Find risky auto-incremental columns on databases which values are close to max possible values.';
3933

40-
/**
41-
* @var array<string, array{min: int|float, max: int|float}>
42-
*/
34+
/** @var array<string, array{min: int|float, max: int|float}> */
4335
private array $columnMinsAndMaxs = [
4436
'integer' => [
4537
'min' => -2_147_483_648,
4638
'max' => 2_147_483_647,
4739
],
48-
'int unsigned' => [
40+
'unsigned integer' => [
4941
'min' => 0,
5042
'max' => 4_294_967_295,
5143
],
5244
'bigint' => [
5345
'min' => -9_223_372_036_854_775_808,
5446
'max' => 9_223_372_036_854_775_807,
5547
],
56-
'bigint unsigned' => [
48+
'unsigned bigint' => [
5749
'min' => 0,
5850
'max' => 18_446_744_073_709_551_615,
5951
],
6052
'tinyint' => [
6153
'min' => -128,
6254
'max' => 127,
6355
],
64-
'tinyint unsigned' => [
56+
'unsigned tinyint' => [
6557
'min' => 0,
6658
'max' => 255,
6759
],
6860
'smallint' => [
6961
'min' => -32_768,
7062
'max' => 32_767,
7163
],
72-
'smallint unsigned' => [
64+
'unsigned smallint' => [
7365
'min' => 0,
7466
'max' => 65_535,
7567
],
7668
'mediumint' => [
7769
'min' => -8_388_608,
7870
'max' => 8_388_607,
7971
],
80-
'mediumint unsigned' => [
72+
'unsigned mediumint' => [
8173
'min' => 0,
8274
'max' => 16_777_215,
8375
],
8476
'decimal' => [
8577
'min' => -99999999999999999999999999999.99999999999999999999999999999,
8678
'max' => 99999999999999999999999999999.99999999999999999999999999999,
8779
],
88-
'decimal unsigned' => [
80+
'unsigned decimal' => [
8981
'min' => 0,
9082
'max' => 99999999999999999999999999999.99999999999999999999999999999,
9183
],
@@ -94,15 +86,17 @@ final class FindRiskyDatabaseColumns extends DatabaseInspectionCommand
9486
public function handle(ConnectionResolverInterface $connections): int
9587
{
9688
$thresholdAlarmPercentage = (float) $this->option('threshold');
97-
$connection = Schema::getConnection();
89+
90+
$connection = $this->getConnection($connections);
9891
if (! $connection instanceof MySqlConnection) {
9992
throw new \InvalidArgumentException('Command supports MySQL DBs only.');
10093
}
10194

10295
$outputTable = [];
96+
$tables = Schema::getConnection()->getDoctrineSchemaManager()->listTableNames();
10397

104-
foreach (Schema::getTables() as $table) {
105-
$riskyColumnsInfo = $this->processTable($table, $connection, $thresholdAlarmPercentage);
98+
foreach ($tables as $tableName) {
99+
$riskyColumnsInfo = $this->processTable($tableName, $connection, $thresholdAlarmPercentage);
106100
if (is_array($riskyColumnsInfo)) {
107101
$outputTable = [...$outputTable, ...$riskyColumnsInfo];
108102
}
@@ -123,32 +117,27 @@ public function handle(ConnectionResolverInterface $connections): int
123117
return self::FAILURE;
124118
}
125119

126-
/**
127-
* @return list<array<string, string>>|null
128-
*/
129-
private function processTable(array $table, Connection $connection, float $thresholdAlarmPercentage): ?array
120+
/** @return list<array<string, string>>|null */
121+
private function processTable(string $tableName, Connection $connection, float $thresholdAlarmPercentage): ?array
130122
{
131-
$tableName = Arr::get($table, 'name');
132123
$this->comment("Table {$connection->getDatabaseName()}.{$tableName}: checking...", 'v');
133124

134-
$tableSize = Arr::get($table, 'size');
135-
125+
$tableSize = $this->getTableSize($connection, $tableName);
136126
if ($tableSize === null) {
137127
$tableSize = -1; // not critical info, we can skip this issue
138128
}
139129

140-
/**
141-
* @var \Illuminate\Support\Collection<int, Schema> $getColumns
142-
*/
143-
$columns = collect(Schema::getColumns($tableName))->filter(
144-
static fn($column): bool => Arr::get($column, 'auto_increment') === true
145-
);
130+
$columns = Schema::getConnection()->getDoctrineSchemaManager()->listTableColumns($tableName);
131+
$autoIncrementColumns = array_filter($columns, fn($column) => $column->getAutoincrement());
146132

147133
$riskyColumnsInfo = [];
148134

149-
foreach ($columns as $column) {
150-
$columnName = Arr::get($column, 'name');
151-
$columnType = Arr::get($column, 'type');
135+
foreach ($autoIncrementColumns as $column) {
136+
$columnName = $column->getName();
137+
$columnType = $column->getType()->getName();
138+
if ($column->getUnsigned()) {
139+
$columnType = "unsigned {$columnType}";
140+
}
152141

153142
$this->comment("\t{$columnName} is autoincrement.", 'vvv');
154143

@@ -179,6 +168,19 @@ private function processTable(array $table, Connection $connection, float $thres
179168
: null;
180169
}
181170

171+
private function getConnection(ConnectionResolverInterface $connections): Connection
172+
{
173+
$connectionName = $this->argument('connection');
174+
if ($connectionName === 'default') {
175+
$connectionName = config('database.default');
176+
}
177+
178+
$connection = $connections->connection($connectionName);
179+
assert($connection instanceof Connection);
180+
181+
return $connection;
182+
}
183+
182184
private function getMaxValueForColumn(string $columnType): int | float
183185
{
184186
if (array_key_exists($columnType, $this->columnMinsAndMaxs)) {

0 commit comments

Comments
 (0)