Advent of code 2023/20
Ajax Direct

Answer 96ms

Part 1 : 743871576 Part 2 : 244151741342687
// Parse modules relationships
foreach ($input->lines as $line) {
    [$_, $type, $name, $dest] = $line->match("/(&|%)?([a-z]+) -> (.+)$/");
    $modules[$name] = ["dest" => explode(", ", $dest), "type" => $type];
    if ($type === "%") $modules[$name]["flip"] = 0;
    if ($type === "&") $modules[$name]["conj"] = [];
    foreach (explode(", ", $dest) as $d) $sources[$d][] = $name;
}

// Solve
$counts  = [0, 0];
$gates = [$sources[$sources["rx"][0]]][0];

for ($i = 1;; $i++) {
    $queue = [["broadcaster", "button", 0]];
    while ($queue) {
        [$dest, $source, $freq] = array_shift($queue);
        $counts[$freq]++;
        if (!isset($modules[$dest])) continue;

        // Register first time each gate is turned on
        if (in_array($dest, $gates) && !$freq) {
            $gates_on[$dest] = $i;
            if (count($gates_on) == count($gates)) break 2;
        }

        // Send signals
        if ($modules[$dest]["type"] == "&") $modules[$dest]["conj"][$source] = $freq;
        $send = match ($modules[$dest]["type"]) {
            "%" => (!$freq ? ($modules[$dest]["flip"] = (int) !$modules[$dest]["flip"]) : null),
            "&" => array_sum($modules[$dest]["conj"]) == count($sources[$dest]) ? 0 : 1,
            default => $freq
        };
        if ($send !== null) foreach ($modules[$dest]["dest"] as $d) $queue[] = [$d, $dest, $send];
    }

    if ($i == 1000) $solution_1 = array_product($counts);
}

$solution_2 = array_product($gates_on);