Advent of code 2017/18
Ajax Direct

Answer

Part 1 :
Part 2 :
#[AllowDynamicProperties]
class Program
{
    public function __construct($reg, $lines) {
        $this->reg   = $reg;   // Starting register
        $this->lines = $lines; // Parsed lines of the input
        $this->i     = 0;      // Current line
        $this->queue = [];     // Queue of values recieved
        $this->sent  = 0;      // Last sent item OR Number of items sent
        $this->stop  = false;  // Stop flag

        $this->cmds = [
            "set" => fn ($p, $a, $b) => $p->reg[$a] = $p->reg[$b] ?? $b,
            "add" => fn ($p, $a, $b) => $p->reg[$a] += $p->reg[$b] ?? $b,
            "mul" => fn ($p, $a, $b) => $p->reg[$a] *= $p->reg[$b] ?? $b,
            "mod" => fn ($p, $a, $b) => $p->reg[$a] %= $p->reg[$b] ?? $b,
            "jgz" => fn ($p, $a, $b) => $p->i += ($p->reg[$a] ?? $a) > 0 ? ($p->reg[$b] ?? $b) - 1 : 0,
            "snd" => fn ($p, $a) => $p->sent = $p->reg[$a] ?? $a,
            "rcv" => fn ($p, $a) => $p->stop = ($p->reg[$a] ?? $a) != 0,
        ];
    }

    /**
     * Execute the next command
     *
     * @return bool True if we can continue, false if we should wait/stop
     */
    public function next()
    {
        $i_before = $this->i;
        $cmd = $this->lines[$this->i];
        $this->cmds[$cmd[0]]($this, $cmd[1], $cmd[2] ?? null);
        $this->i++;
        return $this->i != $i_before && !$this->stop && $this->i < count($this->lines);
    }
}

// Parsing
$lines = (array) $input->lines->map(fn ($line) => explode(" ", $line->string));
$reg   = array_fill_keys(array_filter(array_unique(array_column($lines, 1)), fn ($l) => in_array($l, range("a", "z"))) , 0);

// ==================================================
// > PART 1
// ==================================================
$p0 = new Program($reg, $lines);
while ($p0->next()); // Run until we stop
$solution_1 = $p0->sent;

// ==================================================
// > PART 2
// ==================================================
$p1 = new Program($reg, $lines);
$p2 = new Program(array_merge($reg, ["p" => 1]), $lines);
$p1->other = $p2; $p2->other = $p1;

// Change instructions
$p1->cmds = $p2->cmds = array_merge($p1->cmds, [
    "snd" => fn ($p, $a) => ++$p->sent && $p->other->queue[] = $p->reg[$a] ?? $a,
    "rcv" => fn ($p, $a) => empty($p->queue) ? $p->i-- : $p->reg[$a] = array_shift($p->queue)
]);

// Run until both programs are waiting or stopped
while ($p1->next() || $p2->next());
$solution_2 = $p2->sent;