diff --git a/src/Phaseolies/Console/Commands/Migrations/MigrateTemporalCommand.php b/src/Phaseolies/Console/Commands/Migrations/MigrateTemporalCommand.php index 90da28ca..376126a6 100644 --- a/src/Phaseolies/Console/Commands/Migrations/MigrateTemporalCommand.php +++ b/src/Phaseolies/Console/Commands/Migrations/MigrateTemporalCommand.php @@ -41,7 +41,8 @@ public function handle(): int $connectionLabel = $connection ?: 'per-model'; - $this->line("๐Ÿ• Temporal Migration โ€” connection: {$connectionLabel}"); + $this->newLine(); + $this->line(" Temporal migration connection: {$connectionLabel}" . ($dryRun ? ' ยท dry run' : '') . ''); $this->newLine(); $classes = $this->discoverTemporalModels($modelsPath); @@ -51,15 +52,32 @@ public function handle(): int return Command::SUCCESS; } + $entries = []; + + foreach ($classes as $class) { + $model = new $class(); + $baseTable = $model->getTable(); + + $entries[] = [ + 'class' => $class, + 'base' => $baseTable, + 'history' => TemporalManager::historyTable($baseTable, $class), + 'connection' => $this->resolveConnectionForModel($model, $connection), + ]; + } + + $classWidth = max(array_map(fn ($e) => strlen($e['class']), $entries)); + $connectionWidth = max(array_map(fn ($e) => strlen($e['connection']), $entries)); + $tableWidth = max(array_map(fn ($e) => strlen($e['base'] . ' โ†’ ' . $e['history']), $entries)); + $created = 0; $skipped = 0; - foreach ($classes as $class) { - $attr = TemporalManager::getAttribute($class); - $model = new $class(); - $baseTable = $model->getTable(); - $historyTable = TemporalManager::historyTable($baseTable, $class); - $modelConnection = $this->resolveConnectionForModel($model, $connection); + foreach ($entries as $entry) { + $class = $entry['class']; + $historyTable = $entry['history']; + $modelConnection = $entry['connection']; + $attr = TemporalManager::getAttribute($class); try { $pdo = Database::getPdoInstance($modelConnection); @@ -71,23 +89,22 @@ public function handle(): int return Command::FAILURE; } - $this->line(" Model : {$class}"); - $this->line(" Connection : {$modelConnection}"); - $this->line(" Base table : {$baseTable}"); - $this->line(" History table : {$historyTable}"); + $row = fn (string $icon, string $color, string $status) => $this->line( + " {$icon} " . str_pad($class, $classWidth) . ' ' + . '' . str_pad($entry['base'] . ' โ†’ ' . $historyTable, $tableWidth) . ' ' + . '' . str_pad($modelConnection, $connectionWidth) . " {$status}" + ); if (!$dryRun && $this->tableExists($pdo, $driver, $historyTable)) { // Table exists โ€” check if we need to add the actor column if ($attr?->trackActor && !$this->columnExists($pdo, $driver, $historyTable, 'actor')) { - $sql = $this->addActorColumnSql($driver, $historyTable); - $pdo->exec($sql); - $this->line(' โ†’ Added missing `actor` column.'); + $pdo->exec($this->addActorColumnSql($driver, $historyTable)); + $row('โœ“', 'green', 'actor column added'); $created++; } else { - $this->line(' โ†’ Already exists, skipped.'); + $row('โ€ข', 'yellow', 'already exists'); $skipped++; } - $this->newLine(); continue; } @@ -98,20 +115,21 @@ public function handle(): int ); if ($dryRun) { + $row('โ€บ', 'yellow', 'would create'); foreach ($statements as $sql) { - $this->line(' SQL: ' . preg_replace('/\s+/', ' ', trim($sql))); + $this->line(' ' . preg_replace('/\s+/', ' ', trim($sql)) . ''); } } else { foreach ($statements as $sql) { $pdo->exec($sql); } - $this->line(' โ†’ Created successfully.'); + $row('โœ“', 'green', 'created'); $created++; } - - $this->newLine(); } + $this->newLine(); + if (!$dryRun) { $this->displaySuccess( "Done. Created: {$created}" . ($skipped ? ", Skipped (already exist): {$skipped}" : '') diff --git a/src/Phaseolies/Database/Drivers/MySQLDriver.php b/src/Phaseolies/Database/Drivers/MySQLDriver.php index 7b67fa96..5d4b4aa6 100644 --- a/src/Phaseolies/Database/Drivers/MySQLDriver.php +++ b/src/Phaseolies/Database/Drivers/MySQLDriver.php @@ -31,41 +31,26 @@ public function connect(array $config): PDO PDO::ATTR_STRINGIFY_FETCHES => false, ]; - $sslCa = $this->mysqlAttr('ATTR_SSL_CA', 'MYSQL_ATTR_SSL_CA'); - - if ($sslCa !== null && !empty($config['options'][$sslCa])) { - $options[$sslCa] = $config['options'][$sslCa]; - - $verify = $this->mysqlAttr( - 'ATTR_SSL_VERIFY_SERVER_CERT', - 'MYSQL_ATTR_SSL_VERIFY_SERVER_CERT' - ); - - if ($verify !== null) { - $options[$verify] = false; - } - - foreach ( - [ - ['ATTR_SSL_CERT', 'MYSQL_ATTR_SSL_CERT'], - ['ATTR_SSL_KEY', 'MYSQL_ATTR_SSL_KEY'], - ['ATTR_SSL_CAPATH', 'MYSQL_ATTR_SSL_CAPATH'], - ['ATTR_SSL_CIPHER', 'MYSQL_ATTR_SSL_CIPHER'], - ] as [$new, $old] - ) { - - $attr = $this->mysqlAttr($new, $old); - - if ($attr !== null && isset($config['options'][$attr])) { - $options[$attr] = $config['options'][$attr]; + $configured = $config['options'] ?? []; + + if (!empty($configured[Mysql::ATTR_SSL_CA])) { + $options[Mysql::ATTR_SSL_CA] = $configured[Mysql::ATTR_SSL_CA]; + $options[Mysql::ATTR_SSL_VERIFY_SERVER_CERT] = false; + + foreach ([ + Mysql::ATTR_SSL_CERT, + Mysql::ATTR_SSL_KEY, + Mysql::ATTR_SSL_CAPATH, + Mysql::ATTR_SSL_CIPHER, + ] as $attr) { + if (isset($configured[$attr])) { + $options[$attr] = $configured[$attr]; } } } - $attr = $this->mysqlAttr('ATTR_INIT_COMMAND', 'MYSQL_ATTR_INIT_COMMAND'); - - if ($attr !== null && isset($config['options'][$attr])) { - $options[$attr] = $config['options'][$attr]; + if (isset($configured[Mysql::ATTR_INIT_COMMAND])) { + $options[Mysql::ATTR_INIT_COMMAND] = $configured[Mysql::ATTR_INIT_COMMAND]; } $pdo = new PDO( @@ -254,34 +239,4 @@ public function deleteAll(PDO $pdo, string $table): int { return (int) $pdo->exec("DELETE FROM {$table}"); } - - /** - * Resolve a MySQL PDO attribute constant in a version-compatible way - * - * PHP 8.5 moved MySQL-specific PDO constants from PDO::MYSQL_ATTR_* - * to the namespaced Pdo\Mysql::ATTR_* class and deprecated the old ones. - * - * This helper safely resolves the correct constant for: - * - PHP 8.5+ (Pdo\Mysql::ATTR_*) - * - PHP 8.3 / 8.4 (PDO::MYSQL_ATTR_*) - * - * If neither constant exists (e.g., extension not available), - * null is returned. - * - * @param string $new The new constant name (e.g. 'ATTR_SSL_CA') - * @param string $old The legacy constant name (e.g. 'MYSQL_ATTR_SSL_CA') - * @return int|null - */ - private function mysqlAttr(string $new, string $old): ?int - { - if (class_exists(Mysql::class) && defined("Pdo\\Mysql::$new")) { - return constant("Pdo\\Mysql::$new"); - } - - if (defined("PDO::$old")) { - return constant("PDO::$old"); - } - - return null; - } } diff --git a/src/Phaseolies/Database/Entity/Query/InteractsWithBigDataProcessing.php b/src/Phaseolies/Database/Entity/Query/InteractsWithBigDataProcessing.php index f4d4b287..0010e4b9 100644 --- a/src/Phaseolies/Database/Entity/Query/InteractsWithBigDataProcessing.php +++ b/src/Phaseolies/Database/Entity/Query/InteractsWithBigDataProcessing.php @@ -207,11 +207,9 @@ private function pages(int $chunkSize): Generator private function openCursor(bool $unbuffered): Generator { $restore = null; - $attribute = match (true) { - defined('Pdo\Mysql::ATTR_USE_BUFFERED_QUERY') => constant('Pdo\Mysql::ATTR_USE_BUFFERED_QUERY'), - defined('PDO::MYSQL_ATTR_USE_BUFFERED_QUERY') => constant('PDO::MYSQL_ATTR_USE_BUFFERED_QUERY'), - default => null, - }; + $attribute = defined('Pdo\Mysql::ATTR_USE_BUFFERED_QUERY') + ? constant('Pdo\Mysql::ATTR_USE_BUFFERED_QUERY') + : null; try { if (