Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ public function handle(): int

$connectionLabel = $connection ?: 'per-model';

$this->line("<fg=yellow>🕐 Temporal Migration — connection: {$connectionLabel}</>");
$this->newLine();
$this->line(" <fg=yellow;options=bold>Temporal migration</> <fg=gray>connection: {$connectionLabel}" . ($dryRun ? ' · dry run' : '') . '</>');
$this->newLine();

$classes = $this->discoverTemporalModels($modelsPath);
Expand All @@ -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);
Expand All @@ -71,23 +89,22 @@ public function handle(): int
return Command::FAILURE;
}

$this->line(" <fg=cyan>Model</> : <fg=white>{$class}</>");
$this->line(" <fg=cyan>Connection</> : <fg=white>{$modelConnection}</>");
$this->line(" <fg=cyan>Base table</> : <fg=white>{$baseTable}</>");
$this->line(" <fg=cyan>History table</> : <fg=white>{$historyTable}</>");
$row = fn (string $icon, string $color, string $status) => $this->line(
" <fg={$color}>{$icon}</> <fg=white>" . str_pad($class, $classWidth) . '</> '
. '<fg=gray>' . str_pad($entry['base'] . ' → ' . $historyTable, $tableWidth) . '</> '
. '<fg=cyan>' . str_pad($modelConnection, $connectionWidth) . "</> <fg={$color}>{$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(' <fg=green>→ Added missing `actor` column.</>');
$pdo->exec($this->addActorColumnSql($driver, $historyTable));
$row('✓', 'green', 'actor column added');
$created++;
} else {
$this->line(' <fg=yellow>→ Already exists, skipped.</>');
$row('•', 'yellow', 'already exists');
$skipped++;
}
$this->newLine();
continue;
}

Expand All @@ -98,20 +115,21 @@ public function handle(): int
);

if ($dryRun) {
$row('›', 'yellow', 'would create');
foreach ($statements as $sql) {
$this->line(' <fg=yellow>SQL:</> ' . preg_replace('/\s+/', ' ', trim($sql)));
$this->line(' <fg=gray>' . preg_replace('/\s+/', ' ', trim($sql)) . '</>');
}
} else {
foreach ($statements as $sql) {
$pdo->exec($sql);
}
$this->line(' <fg=green>→ Created successfully.</>');
$row('✓', 'green', 'created');
$created++;
}

$this->newLine();
}

$this->newLine();

if (!$dryRun) {
$this->displaySuccess(
"Done. Created: {$created}" . ($skipped ? ", Skipped (already exist): {$skipped}" : '')
Expand Down
77 changes: 16 additions & 61 deletions src/Phaseolies/Database/Drivers/MySQLDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
Loading