Advent of code 2021/18
Ajax Direct

Answer 638ms

Part 1 : 4184 Part 2 : 4731
function reduce($num) {
    // 1 - Explode
    preg_match_all("/\[[^\]\[]+\]/", $num, $matches, PREG_OFFSET_CAPTURE);
    foreach ($matches[0]??[] as [$match, $offset]) {

        $deep = 0;
        foreach (str_split(substr($num, 0, $offset)) as $c)
        $deep += $c == "[" ? 1 : ($c == "]" ? -1 : 0);
        if ($deep < 4) continue;

        $num = substr_replace($num, "0", $offset, strlen($match));
        [$a, $b] = explode(",", trim($match, "[]"));

        if (preg_match_all("/(\d+)\D*$/", substr($num, 0, $offset), $before, PREG_OFFSET_CAPTURE)) {
            $sum = int($before[1][0][0]) + $a;
            $num = substr_replace($num, $sum, $before[1][0][1], strlen($before[1][0][0]));
            $offset += strlen($sum) - 1;
        }

        if (preg_match_all("/^\D*(\d+)/", substr($num, $offset + 1), $after, PREG_OFFSET_CAPTURE)) {
            $sum = int($after[1][0][0]) + $b;
            $num = substr_replace($num, $sum, $offset + 1 + $after[1][0][1], strlen($after[1][0][0]));
        }

        return reduce($num);
    }

    // 2 - Split
    preg_match("/[0-9]{2}/", $num, $matches, PREG_OFFSET_CAPTURE);
    if ($matches) {
        $rep = "[" . floor(int($matches[0][0]) / 2) . "," . ceil(int($matches[0][0]) / 2) . "]";
        $num = substr_replace($num, $rep, $matches[0][1], 2);
        return reduce($num);
    }

    return $num;
}

function magnitude($sum) {
    while (preg_match("/\[(\d+),(\d+)\]/", $sum, $match)) {
        $sum = str_replace($match[0], $match[1] * 3 + $match[2] * 2, $sum);
    }
    return int($sum);
}

// ==================================================
// > PART 1
// ==================================================
$sum = false;
foreach ($input->lines as $line) {
    if (!$sum) $sum = $line;
    else $sum = reduce("[$sum,$line]");
}
$solution_1 = magnitude($sum);

// ==================================================
// > PART 2
// ==================================================
$solution_2 = 0;
$lines = $input->lines;

// Only consider the 10 largest numbers
$m = $lines->mapAssoc(fn ($k, $v) => [(string) $v => magnitude($v)]);
$lines = $m->sort()->reverse()->keys()->slice(0, 10);

for ($a = 0; $a < count($lines); $a++) {
    for ($b = 0; $b < count($lines); $b++) {
        if ($a == $b) continue;
        $la = $lines[$a]; $lb = $lines[$b];
        $solution_2 = max($solution_2, magnitude(reduce("[$la,$lb]")));
        $solution_2 = max($solution_2, magnitude(reduce("[$lb,$la]")));
    }
}