Advent of code 2020/11
Ajax Direct

Answer 2870ms

Part 1 : 2273 Part 2 : 2064
$grid = array_map("str_split", explode("\n", ((string) $input)));

function solve($grid, $get_neighbors = false, $limit = 4) {
    [$w, $h] = [count($grid[0]), count($grid)];
    $seen = [];
    $neighbors = [];

    while (true) {
        $ngrid = $grid;
        for ($x = 0; $x < $w; $x++) for ($y = 0; $y < $h; $y++) {

            if ($grid[$y][$x] == ".") continue;

            $taken = 0;
            $neighbors[$y][$x] = $neighbors[$y][$x] ?? $get_neighbors([$x, $y], $grid);
            foreach ($neighbors[$y][$x] as [$nx, $ny]) {
                if (($grid[$ny][$nx] ?? ".") == "#") $taken++;
            }

            if ($taken == 0 && $grid[$y][$x] == "L") $ngrid[$y][$x] = "#";
            if ($taken >= $limit && $grid[$y][$x] == "#") $ngrid[$y][$x] = "L";
        }

        $grid = $ngrid;
        $state = implode("", array_merge(...$grid));
        if (isset($seen[$state])) break;
        $seen[$state] = true;
    }

    return set(str_split($state))->occurrences()["#"];
}

// ==================================================
// > PART 1
// ==================================================
$solution_1 = solve($grid, function ($pos, $grid) {
    return neighbors($pos, true);
}, 4);

// ==================================================
// > PART 2
// ==================================================
$dir = neighbors([0, 0], true);
$solution_2 = solve($grid, function ($pos, $grid) use ($dir) {
    $n = [];

    foreach ($dir as $d) {
        $p = $pos;

        do {
            $p = [$p[0] + $d[0], $p[1] + $d[1]];
        } while (isset($grid[$p[1]][$p[0]]) && $grid[$p[1]][$p[0]] == ".");

        if (isset($grid[$p[1]][$p[0]])) {
            $n[] = $p;
        }
    }
    return $n;
}, 5);