Advent of code 2018/17
Ajax Direct

Answer 3560ms

[VISUALISATION] Part 1 : 27206 Part 2 : 21787
// Build map
$grid = [];
foreach ($input->lines as $line) {
    $nums = $line->numbers();
    foreach (range($nums[1], $nums[2]) as $num) {
        $line[0] == "x"
            ? ($grid[$num][$nums[0]] = "#")
            : ($grid[$nums[0]][$num] = "#");
    }
}

// Simulate waterfall
$ys      = array_keys($grid); sort($ys);
$floor   = max($ys);
$ceiling = $ys[0];

$drops   = [[500, 0]];
$sources = [];
$d = 0;

while ($drops) {
    $drop = array_shift($drops);
    $log[] = $drop;
    $d++;

    [$x, $y] = $drop;
    $grid[$y][$x] = 0;

    if (!empty($sources[$y][$x])) continue;
    $sources[$y][$x] = true;

    // Go down as far as possible
    while ((empty($grid[++$y][$x])) && $y <= $floor) $grid[$y][$x] = 0;
    if ($y > $floor) continue;
    $y--;

    // Go left and right as far as possible
    [$l, $r] = [$x, $x];
    $grid[$y][$x] = 0;
    while ((empty($grid[$y][--$l])) && !empty($grid[$y+1][$l])) $grid[$y][$l] = 0;
    while ((empty($grid[$y][++$r])) && !empty($grid[$y+1][$r])) $grid[$y][$r] = 0;

    // Add drop if we reached a border
    if (empty($grid[$y][$l])) $drops[] = [$l, $y];
    if (empty($grid[$y][$r])) $drops[] = [$r, $y];

    // Add drop above if water is traped, and change water state
    if (!empty($grid[$y][$l]) && !empty($grid[$y][$r])) {
        $sources[$y-1][$x] = false;
        $drops[] = [$x, $y-1];
        foreach (range($l + 1, $r - 1) as $x) {
            $grid[$y][$x] = "~";
        }
    }
}

$tiles = set($grid)->merge();

// ==================================================
// > PART 1
// ==================================================
$solution_1 = $tiles->remove("#")->count() - $ceiling;

// ==================================================
// > PART 2
// ==================================================
$solution_2 = $tiles->keep("~")->count();