Advent of code 2020/4
Ajax Direct

Answer

Part 1 :
Part 2 :
$required = set(["ecl", "pid", "eyr", "hcl", "byr", "iyr", "hgt"]);

$passports = $input->split("\n\n")->map(function ($passport) use ($required) {
    preg_match_all("/([a-z]{3}):([^\n ]+)/", $passport, $matches);
    return $required->remove($matches[1])->count()
        ? false
        : array_combine($matches[1], $matches[2]);
})->filter();

// ==================================================
// > PART 1
// ==================================================
$solution_1 = $passports->count();

// ==================================================
// > PART 2
// ==================================================
$solution_2 = $passports->filter(function ($passport) {

    // byr (Birth Year) - four digits; at least 1920 and at most 2002.
    if (!in_array((int) $passport["byr"], range(1920, 2002))) return false;

    // iyr (Issue Year) - four digits; at least 2010 and at most 2020.
    if (!in_array((int) $passport["iyr"], range(2010, 2020))) return false;

    // eyr (Expiration Year) - four digits; at least 2020 and at most 2030.
    if (!in_array((int) $passport["eyr"], range(2020, 2030))) return false;

    // hgt (Height) - a number followed by either cm or in:
    if (!preg_match("/^(\d+)(cm|in)$/", $passport["hgt"], $matches)) return false;
    // If cm, the number must be at least 150 and at most 193.
    if ($matches[2] == "cm" && !in_array((int) $matches[1], range(150, 193))) return false;
    // If in, the number must be at least 59 and at most 76.
    if ($matches[2] == "in" && !in_array((int) $matches[1], range(59, 76))) return false;

    // hcl (Hair Color) - a # followed by exactly six characters 0-9 or a-f.
    if (!preg_match("/^#[0-9a-f]{6}$/", $passport["hcl"])) return false;

    // ecl (Eye Color) - exactly one of: amb blu brn gry grn hzl oth.
    if (!in_array($passport["ecl"], ["amb", "blu", "brn", "gry", "grn", "hzl", "oth"])) return false;

    // pid (Passport ID) - a nine-digit number, including leading zeroes.
    if (!preg_match("/^\d{9}$/", $passport["pid"])) return false;

    // cid (Country ID) - ignored, missing or not.
    return true;

})->count();