Advent of code 2017/21
Ajax Direct

Answer 818ms

Part 1 : 155 Part 2 : 2449665
// Build rules index with every rotation and flip
$rules = [];
foreach ($input->lines as $line) {
    // Parse rule
    $rule = explode(" => ", $line);
    [$pattern, $expand] = [explode("/", $rule[0]), explode("/", $rule[1])];
    $pattern = array_map("str_split", $pattern);
    // Rotate + flip 4 times
    for ($i = 0; $i < 4; $i++) {
        // Rotate & register
        $pattern = array_map(null, ...$pattern);
        $pattern = array_map("array_reverse", $pattern);
        $rules[implode("/", array_map("implode", $pattern))] = $expand;
        // Flip & register
        $fliped = array_map("array_reverse", $pattern);
        $rules[implode("/", array_map("implode", $fliped))] = $expand;
    }
}

// Enhance square one time
function enhance($square, $rules) {
    $len = strlen($square[0]) % 2 == 0 ? 2 : 3;
    $cnt = count($square) / $len;

    // Split square intro subsquares of size $len
    $squares = array_merge(...array_map(fn ($chunk) =>
        array_map(fn ($square) => implode("/", $square), array_map(null, ...$chunk))
    , array_chunk(array_map(fn ($row) => str_split($row, $len), $square), $len)));

    // Enhance subsquares
    $squares = array_map(fn ($square) => $rules[$square], $squares);

    // Merge subsquares into square
    return $cnt > 1 ? array_merge(...array_map(fn ($chunk) =>
        array_map("implode", array_map(null, ...$chunk))
    , array_chunk($squares, $cnt))) : $squares[0];
}

function solve($count, $rules) {
    $square = explode("/", ".#./..#/###");

    for ($i = 0; $i < $count; $i++) {
        $square = enhance($square, $rules);
    }

    return substr_count(implode("", $square), "#");
}

// ==================================================
// > SOLUTONS
// ==================================================
$solution_1 = solve(5, $rules);
$solution_2 = solve(18, $rules);