Advent of code 2020/23
Ajax Direct

Answer 4960ms

Part 1 : 82934675 Part 2 : 474600314018
function moves($cups, $count = 100) {
    // Build circular linked list [item => next item]
    $next = [];
    foreach ($cups as $i=>$cup) {
        $next[$cup] = $cups[$i + 1] ?? $cups[0];
    }

    // Move items by updating their relations
    $current = $cups[0];
    for ($i = 0; $i < $count; $i++) {

        // Remove 3 items, relink current item
        $removed = [];
        $cr = $current;
        for ($r = 0; $r < 3; $r++) {
            $removed[] = $cr = $next[$cr];
        }
        $next[$current] = $next[$cr];

        // Find placing
        $pos = $current - 1;
        while ($pos < 1 || in_array($pos, $removed)) {
            $pos--; $pos = $pos < 1 ? count($cups) : $pos;
        }

        // Place items
        $after = $next[$pos];
        $next[$pos] = $removed[0];
        $next[$removed[2]] = $after;

        // Continue with next one
        $current = $next[$current];
    }

    return $next;
}

// ==================================================
// > PART 1
// ==================================================
$list = moves($input->chars()->map("int"), 100);

$solution_1 = "";
$next = $list[1];
while (true) {
    $solution_1 .= $next;
    $next = $list[$next];
    if ($next == 1) break;
}

// ==================================================
// > PART 2
// ==================================================
$list = moves($input->chars()->map("int")->merge(range(10, 1_000_000)), 10_000_000);
$solution_2 = $list[1] * $list[$list[1]];