You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
107 lines
3.3 KiB
107 lines
3.3 KiB
<?php |
|
|
|
namespace App\Services; |
|
|
|
use App\Contracts\DatabaseListerInterface; |
|
use Illuminate\Support\Facades\DB; |
|
use InvalidArgumentException; |
|
use Throwable; |
|
|
|
class MySqlDatabaseService implements DatabaseListerInterface |
|
{ |
|
private const PROTECTED = ['mysql', 'information_schema', 'performance_schema', 'sys']; |
|
|
|
private const QUERY = <<<'SQL' |
|
SELECT |
|
s.SCHEMA_NAME AS db_name, |
|
'—' AS created, |
|
0 AS active_days, |
|
'ONLINE' AS state, |
|
'—' AS last_index_activity, |
|
'—' AS last_backup |
|
FROM information_schema.SCHEMATA s |
|
WHERE s.SCHEMA_NAME NOT IN ('information_schema', 'performance_schema', 'sys', 'mysql') |
|
SQL; |
|
|
|
private string $connection = 'mysql'; |
|
|
|
public function using(string $connection): self |
|
{ |
|
$clone = clone $this; |
|
$clone->connection = $connection; |
|
|
|
return $clone; |
|
} |
|
|
|
public function listDatabases(string $sort, string $dir, string $search = ''): array |
|
{ |
|
try { |
|
$rows = collect(DB::connection($this->connection)->select(self::QUERY)) |
|
->map(fn ($row) => (array) $row); |
|
|
|
if ($search !== '') { |
|
$needle = mb_strtolower($search); |
|
$rows = $rows->filter(fn (array $row) => str_contains(mb_strtolower($row['db_name'] ?? ''), $needle)); |
|
} |
|
|
|
$rows = $rows->sortBy(fn (array $row) => $row[$sort] ?? '', SORT_REGULAR, $dir === 'desc'); |
|
|
|
return [ |
|
'rows' => $rows->values()->all(), |
|
'meta' => [ |
|
'total' => $rows->count(), |
|
'server' => config('database.connections.' . $this->connection . '.host'), |
|
'driver' => 'mysql', |
|
'sql_started' => null, |
|
'uptime_days' => null, |
|
], |
|
'error' => null, |
|
]; |
|
} catch (Throwable) { |
|
return [ |
|
'rows' => [], |
|
'meta' => [ |
|
'total' => 0, |
|
'server' => config('database.connections.' . $this->connection . '.host'), |
|
'driver' => 'mysql', |
|
'sql_started' => null, |
|
'uptime_days' => null, |
|
], |
|
'error' => 'Unable to load MySQL/MariaDB databases. Check connectivity and credentials.', |
|
]; |
|
} |
|
} |
|
|
|
public function makeOffline(string $databaseName): void |
|
{ |
|
$databaseName = $this->validateName($databaseName); |
|
|
|
if ($this->isProtected($databaseName)) { |
|
throw new InvalidArgumentException('System databases cannot be restricted.'); |
|
} |
|
|
|
$quoted = '`' . str_replace('`', '``', $databaseName) . '`'; |
|
DB::connection($this->connection)->statement("ALTER DATABASE {$quoted} READ ONLY = 1"); |
|
} |
|
|
|
public function isProtected(string $databaseName): bool |
|
{ |
|
return in_array(mb_strtolower($databaseName), self::PROTECTED, true); |
|
} |
|
|
|
public function supportsOffline(): bool |
|
{ |
|
return true; |
|
} |
|
|
|
private function validateName(string $databaseName): string |
|
{ |
|
$databaseName = trim($databaseName); |
|
|
|
if (! preg_match('/^[a-zA-Z0-9_\-]+$/', $databaseName)) { |
|
throw new InvalidArgumentException('Invalid database name.'); |
|
} |
|
|
|
return $databaseName; |
|
} |
|
}
|
|
|