phpvms/app/Cron/FiveMinute/PirepPositionExpiration.php
Nabeel Shahzad 7776af88d8
ref(livemap): address review feedback
Drop the tombstone eviction rule; RemoveExpiredLiveFlights already reaps those
PIREPs and the completed rule picks up what it soft-deletes. Simplify evict().
Revert RemoveExpiredLiveFlights to its original body with only the setting name
changed, and revert PirepResource, dropping the scope and resource class added
alongside them.

Also: class_alias by string so PHPStan can analyse the deprecated enum alias,
re-check updated_at at delete time, preserve pirep_id nullability in the
collation ALTER, and cut the comments back.
2026-07-27 10:35:53 -05:00

73 lines
2.6 KiB
PHP

<?php
namespace App\Cron\FiveMinute;
use App\Contracts\Listener;
use App\Enums\PirepPhase;
use App\Enums\PirepState;
use App\Events\CronFiveMinute;
use App\Models\PirepPosition;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
/**
* Decides what leaves the live map. Five-minutely, not hourly, because both of its
* timers are in minutes.
*/
class PirepPositionExpiration extends Listener
{
public function handle(CronFiveMinute $event): void
{
$now = Carbon::now('UTC');
$liveTime = (int) setting('livemap.live_time');
$idleTime = (int) setting('livemap.idle_time');
// Zero disables a timer rather than expiring everything instantly.
if ($liveTime > 0) {
// Finished, whatever phase the client last reported. Soft-deleted
// PIREPs count, since RemoveExpiredLiveFlights leaves them that way.
$this->evict(
$now->copy()->subMinutes($liveTime),
fn ($query) => $query->where(function ($q): void {
$q->where('pireps.state', '<>', PirepState::IN_PROGRESS->value)
->orWhereNotNull('pireps.deleted_at');
})
);
}
if ($idleTime > 0) {
// Paused, or prefiled and never departed. "Never departed" is
// updated_at == created_at, since only a batch moves updated_at.
$this->evict(
$now->copy()->subMinutes($idleTime),
fn ($query) => $query->where('pireps.state', PirepState::IN_PROGRESS->value)
->whereNull('pireps.deleted_at')
->where(function ($q): void {
$q->where('pireps.status', PirepPhase::PAUSED->value)
->orWhereColumn('pirep_positions.updated_at', '=', 'pirep_positions.created_at');
})
);
}
}
/**
* Clocked from pirep_positions.updated_at, not pireps.submitted_at - a pilot
* who lands at 12:00 and files at 15:00 stopped flying at 12:00.
*/
private function evict(Carbon $before, callable $constrain): void
{
$ids = DB::table('pirep_positions')
->join('pireps', 'pireps.id', '=', 'pirep_positions.pirep_id')
->where('pirep_positions.updated_at', '<', $before)
->tap($constrain)
->pluck('pirep_positions.pirep_id');
// Re-checked, not taken on trust: a batch landing between the select and
// the delete has refreshed updated_at and must survive.
PirepPosition::whereIn('pirep_id', $ids)
->where('updated_at', '<', $before)
->delete();
}
}