Файл: database/upgrades/2026_06_28_000038_change_users_dates_to_datetime.php
Строк: 154
<?php
use IlluminateDatabaseMigrationsMigration;
use IlluminateDatabaseSchemaBlueprint;
use IlluminateSupportFacadesDate;
use IlluminateSupportFacadesDB;
use IlluminateSupportFacadesSchema;
return new class extends Migration {
/**
* Чанковая конверсия с транзакцией на чанк: createFromTimestamp/parse сохраняют
* историческую таймзону (старый DST), а батч-коммит убирает fsync-на-строку.
* whereNull по $resumeCol позволяет продолжить с места падения (таймаут на шареде).
*/
private function convert(string $table, string $resumeCol, array $cols, callable $map): void
{
DB::table($table)->select(array_merge(['id'], $cols))
->whereNull($resumeCol)
->orderBy('id')
->chunkById(5000, function ($rows) use ($table, $map) {
DB::transaction(function () use ($rows, $table, $map) {
foreach ($rows as $row) {
DB::table($table)->where('id', $row->id)->update($map($row));
}
});
});
}
/**
* Создаёт только отсутствующие временные колонки: упавший upgrade мог
* оставить их с прошлого запуска, повторный запуск не должен падать.
*/
private function addTempColumns(string $table, string $type, array $cols): void
{
$missing = array_filter($cols, static fn ($col) => ! Schema::hasColumn($table, $col));
if ($missing) {
Schema::table($table, static function (Blueprint $blueprint) use ($type, $missing) {
foreach ($missing as $col) {
$blueprint->{$type}($col)->nullable();
}
});
}
}
public function up(): void
{
// Свежая схема уже создаёт колонки как datetime — конверсия не нужна
if (Schema::getColumnType('users', 'created_at') === 'datetime') {
return;
}
// 0 в timebonus = «бонус никогда не брали» → null
$toDt = static fn ($v) => $v ? Date::createFromTimestamp($v, config('app.timezone'))->format('Y-m-d H:i:s') : null;
$this->addTempColumns('users', 'dateTime', ['timeban_dt', 'timebonus_dt', 'created_at_dt', 'updated_at_dt']);
$this->convert('users', 'created_at_dt', ['created_at', 'updated_at', 'timeban', 'timebonus'], static fn ($r) => [
'created_at_dt' => $toDt($r->created_at),
'updated_at_dt' => $toDt($r->updated_at),
'timeban_dt' => $toDt($r->timeban),
'timebonus_dt' => $toDt($r->timebonus),
]);
Schema::table('users', function (Blueprint $table) {
$table->dropIndex(['created_at']);
$table->dropColumn(['created_at', 'updated_at', 'timeban', 'timebonus']);
});
Schema::table('users', function (Blueprint $table) {
$table->renameColumn('timeban_dt', 'timeban');
$table->renameColumn('timebonus_dt', 'timebonus');
$table->renameColumn('created_at_dt', 'created_at');
$table->renameColumn('updated_at_dt', 'updated_at');
});
Schema::table('users', fn (Blueprint $table) => $table->index('created_at'));
}
public function down(): void
{
// Колонки уже int — откатывать нечего
if (Schema::getColumnType('users', 'created_at') !== 'datetime') {
return;
}
$toInt = static fn ($v) => $v ? Date::parse($v, config('app.timezone'))->getTimestamp() : null;
$this->addTempColumns('users', 'integer', ['timeban_int', 'timebonus_int', 'created_at_int', 'updated_at_int']);
$this->convert('users', 'created_at_int', ['created_at', 'updated_at', 'timeban', 'timebonus'], static fn ($r) => [
'created_at_int' => $toInt($r->created_at),
'updated_at_int' => $toInt($r->updated_at),
'timeban_int' => $toInt($r->timeban),
'timebonus_int' => $toInt($r->timebonus),
]);
Schema::table('users', function (Blueprint $table) {
$table->dropIndex(['created_at']);
$table->dropColumn(['created_at', 'updated_at', 'timeban', 'timebonus']);
});
Schema::table('users', function (Blueprint $table) {
$table->renameColumn('timeban_int', 'timeban');
$table->renameColumn('timebonus_int', 'timebonus');
$table->renameColumn('created_at_int', 'created_at');
$table->renameColumn('updated_at_int', 'updated_at');
});
Schema::table('users', fn (Blueprint $table) => $table->index('created_at'));
}
};