Advent of code 2020/16
Ajax Direct

Answer

Part 1 :
Part 2 :
[$rules, $mine, $others] = $input->split("\n\n");

// Parse rules : hashmap of valid numbers for each field
$rules = $rules->lines->mapAssoc(function ($i, $rule) {
    [$name, $ranges] = $rule->split(": ");
    $ranges = $ranges->split(" or ")->map(fn ($r) => $r->range);
    return [(string) $name => (array) $ranges->merge()];
});

// Parse cards
$mine = $mine->lines[1]->numbers;
$others = $others->lines->slice(1)->map(fn ($l) => $l->numbers);

// ==================================================
// > PART 1
// ==================================================
$all_rules = $rules->merge();
$invalid_nums = $invalid_cards = set();
foreach ($others as $i=>$nums) {
    $invalid = $nums->remove($all_rules);
    if ($invalid->count()) {
        $invalid_nums = $invalid_nums->merge($invalid);
        $invalid_cards[] = $i;
    }
}
$solution_1 = $invalid_nums->sum();

// ==================================================
// > PART 2
// ==================================================
$others = $others->removeKeys($invalid_cards)->values();
$fields = array_fill_keys((array) $mine->keys(), (array) $rules->keys());

// Restrict possibilites
foreach ($fields as $i=>$ff) {
    foreach ($ff as $fi=>$f) {
       foreach ($others as $nums) {
            if (!in_array($nums[$i], $rules[$f])) {
                unset($fields[$i][$fi]);
                continue 2;
            }
        }
    }
}

// Select fields by process of elimination
$selection = [];
while (true) {
    foreach ($fields as $i=>$field) {
        if (count($field) == 1) {
            $selection[$i] = $field = array_values($field)[0];
            unset($fields[$i]);
            foreach ($fields as $j=>$f) $fields[$j] = array_diff($fields[$j], [$field]);
            continue 2;
        }
    }
    break;
}

$solution_2 = $mine->keepKeys(
    set($selection)->filter(fn ($f) => substr($f, 0, 9) == "departure")->keys()
)->multiply();