option('chunk')); $dryRun = (bool) $this->option('dry-run'); if ($dryRun) { $this->warn('[DRY RUN] No changes will be written.'); } $total = (int) DB::table('users') ->where('username', 'like', 'tmpu%') ->count(); if ($total === 0) { $this->info('No users with temporary tmpu% usernames were found.'); return self::SUCCESS; } $this->info("Found {$total} users with temporary tmpu% usernames."); $processed = 0; $updated = 0; $skipped = 0; DB::table('users') ->select(['id', 'name', 'username']) ->where('username', 'like', 'tmpu%') ->chunkById($chunk, function ($rows) use (&$processed, &$updated, &$skipped, $dryRun) { foreach ($rows as $row) { $processed++; $sourceName = trim((string) ($row->name ?? '')); if ($sourceName === '') { $skipped++; $this->warn("Skipping user id={$row->id}: name is empty."); continue; } $candidate = $this->resolveCandidate($sourceName, (int) $row->id); if ($candidate === null || strcasecmp($candidate, (string) $row->username) === 0) { $skipped++; $this->warn("Skipping user id={$row->id}: unable to resolve a better username from name='{$sourceName}'."); continue; } if ($dryRun) { $this->line("[dry] Would update user id={$row->id} username '{$row->username}' => '{$candidate}'"); $updated++; continue; } $affected = DB::table('users') ->where('id', (int) $row->id) ->where('username', 'like', 'tmpu%') ->update([ 'username' => $candidate, 'username_changed_at' => now(), 'updated_at' => now(), ]); if ($affected > 0) { $updated += $affected; $this->line("[update] user id={$row->id} username '{$row->username}' => '{$candidate}'"); } } }, 'id'); $this->info(sprintf('Finished. processed=%d updated=%d skipped=%d', $processed, $updated, $skipped)); return self::SUCCESS; } private function resolveCandidate(string $sourceName, int $userId): ?string { $base = UsernamePolicy::sanitizeLegacy($sourceName); $min = UsernamePolicy::min(); $max = UsernamePolicy::max(); if ($base === '') { return null; } if (preg_match('/^tmpu\d+$/i', $base) === 1) { $base = 'user' . $userId; } if (strlen($base) < $min) { $base = substr($base . $userId, 0, $max); } if ($base === '' || $base === 'user') { $base = 'user' . $userId; } $candidate = substr($base, 0, $max); $suffix = 1; while ($this->usernameExists($candidate, $userId) || UsernamePolicy::isReserved($candidate)) { $suffixValue = (string) $suffix; $prefixLen = max(1, $max - strlen($suffixValue)); $candidate = substr($base, 0, $prefixLen) . $suffixValue; $suffix++; } return $candidate; } private function usernameExists(string $username, int $ignoreUserId): bool { return DB::table('users') ->whereRaw('LOWER(username) = ?', [strtolower($username)]) ->where('id', '!=', $ignoreUserId) ->exists(); } }