whereNull('email_verified_at') ->where('created_at', '<', now()->subDays(self::AFTER_DAYS)) // Not a join: the two tables are linked by user_id OR by address // (see Customer::emailTaken — a row can predate or outlive the // other), and either one means somebody is a customer. ->whereNotExists(fn ($q) => $q->selectRaw('1')->from('customers') ->whereColumn('customers.user_id', 'users.id')) ->whereNotExists(fn ($q) => $q->selectRaw('1')->from('customers') ->whereColumn('customers.email', 'users.email')) ->get(); if ($stale->isEmpty()) { $this->info('Nothing to remove.'); return self::SUCCESS; } if ($this->option('dry-run')) { foreach ($stale as $user) { $this->line('would remove: '.$user->email.' (registered '.$user->created_at->toDateString().')'); } return self::SUCCESS; } foreach ($stale as $user) { // Logged individually rather than as a count: an address that // disappears is the kind of thing somebody asks about later, and a // number in a log answers nothing. Log::info('Removing an unconfirmed registration', [ 'email' => $user->email, 'registered_at' => $user->created_at?->toIso8601String(), ]); $user->delete(); } $this->info($stale->count().' unconfirmed registration(s) removed.'); return self::SUCCESS; } }