Advent of code 2018/4
Ajax Direct

Answer

Part 1 :
Part 2 :
$guards = [];
$guard  = null;

// Parse input
foreach ($input->lines->sort()->values() as $line) {
    $nums = $line->numbers;

    // Switch guard
    if (!empty($nums[5])) {
        $guard = $nums[5];
        continue;
    }

    // Add state switches
    $date = $nums->slice(0, 3)->join("")->string;
    $guards[$guard][$date] ??= [];
    $guards[$guard][$date][] = $nums[4];
}

// Calc ranges
foreach ($guards as $id=>$guard) {
    foreach ($guard as $date=>$events) {
        $guards[$id][$date] = array_merge(
            ...array_map(fn ($range) => range($range[0], $range[1] - 1), array_chunk($events, 2))
        );
    }
}

// ==================================================
// > PART 1
// ==================================================

// Find guard with most minutes asleep
$most_asleep = set(array_map(
    fn ($guard) => count(array_merge(...array_values($guard))),
    $guards
))->sort()->keys()->last();

// Find the most common hour he was asleep
$most_common = set($guards[$most_asleep])->merge()->occurrences()->sort()->keys()->last();

// Part 1
$solution_1 = $most_asleep * $most_common;


// ==================================================
// > PART 2
// ==================================================
$solution_2 = set($guards)->mapAssoc(function ($id, $guard) {
    $hours = set($guard)->merge()->occurrences()->sort();
    return [$hours->keys()->last() * $id => $hours->values()->last()];
})->sort()->keys()->last();