Advent of code 2020/19
Ajax Direct

Answer

Part 1 :
Part 2 :
function get_regex($num, $rules) {
    if ($rules[$num][0] == "\"") {
        $regex[$num] = str_replace("\"", "", $rules[$num]);
        return $regex[$num];
    }

    $rule = str_replace(" ", "", preg_replace_callback("/[0-9]+/", function ($m) use ($num, $rules) {
        if ($m[0] == $num) return "@"; // Self
        return get_regex($m[0], $rules);
    }, $rules[$num]));

    // If contains self...
    if (strpos($rule, "@") !== false) {
        // Get rid of the non-recursive part, as it is redundant
        $rule = substr($rule, strlen($rule) / 2);
        // Update using uniquely named recursive backreference
        $name = "br".uniqid();
        $rule = "(?<".$name.">" . str_replace("@", "(?&".$name.")?", $rule) . ")";
    }

    return "(" . $rule . ")";
}

function solve($messages, $rules) {
    $regex = get_regex(0, $rules);

    return $messages
        ->filter(fn ($m) => preg_match("/^".$regex."$/", $m))
        ->count();
}

// ==================================================
// > SOLUTION
// ==================================================

// Parsing
[$rules, $messages] = $input->split("\n\n");
$rules = $rules->lines->map(fn ($l) => $l->split(": "))->mapAssoc(fn ($i, $r) => [(string) $r[0] => (string) $r[1]]);
$messages = set(explode("\n", (string) $messages));

// Part 1
$solution_1 = solve($messages, $rules);

// Part 2
$rules[8] = "42 | 42 8";
$rules[11] = "42 31 | 42 11 31";
$solution_2 = solve($messages, $rules);