Advent of code 2021/16
Ajax Direct

Answer

Part 1 :
Part 2 :
/**
 * Direct value of a bin
 *
 * @param string $bin
 * @return array [value, remaining bits]
 */
function direct_value($bin) {
    $num = "";
    for ($i = 0;; $i += 5) {
        $num .= substr($bin, $i + 1, 4);
        if (!$bin[$i]) break;
    }
    return [bindec($num), substr($bin, $i + 5)];
}

/**
 * Decode a bin that has subpackets
 *
 * @param string $bin
 * @return array [version total, values, remaining bits]
 */
function sub_decode($bin) {
    $v = 0;
    $values = [];
    [$i, $bin] = [$bin[0], substr($bin, 1)];

    $l = bindec(substr($bin, 0, $i ? 11 : 15));
    $bin = substr($bin, $i ? 11 : 15);

    while (true) {
        [$sv, $sn, $rest] = decode($bin);
        $v += $sv;
        $values[] = $sn;
        $l -= $i ? 1 : strlen($bin) - strlen($rest);
        $bin = $rest;
        if (!$l) break;
    }

    return [$v, $values, $rest];
}

/**
 * Decode a bin
 *
 * @param string $bin
 * @return array [version total, value, remaining bits]
 */
function decode($bin) {
    [$v, $t, $bin] = [bindec(substr($bin, 0, 3)), bindec(substr($bin, 3, 3)), substr($bin, 6)];

    // Direct value : return has is
    if ($t == 4) return [$v, ...direct_value($bin)];

    // Operation : get values of all subpackets and then apply operation
    [$sv, $values, $bin] = sub_decode($bin);
    $v += $sv;

    $value = match($t) {
        0 => array_sum($values),
        1 => array_product($values),
        2 => min($values),
        3 => max($values),
        5 => (int) ($values[0] > $values[1]),
        6 => (int) ($values[0] < $values[1]),
        7 => (int) ($values[0] == $values[1]),
        default => 0
    };

    return [$v, $value, $bin];
}

// ==================================================
// > SOLVE
// ==================================================
$bin = $input->chars()->map(fn ($c) =>
    str_pad(base_convert($c, 16, 2), 4, "0", STR_PAD_LEFT)
)->join();

[$solution_1, $solution_2] = decode($bin);