From the manual:
A by-value binding means that it is not possible to modify any values from the outer scope
and:
Arrow functions capture variables by value automatically ... When a variable used in the expression is defined in the parent scope it will be implicitly captured by-value
Thus your function cannot modify the value of $stack, and so it returns the same value each time. To do what you want (without a parameter to the function) you need an anonymous function:
$fruit = function () use (&$stack) { return array_shift($stack); };
var_dump($fruit());
var_dump($fruit());
var_dump($fruit());
var_dump($fruit());
Output:
string(6) "orange"
string(6) "banana"
string(5) "apple"
string(9) "raspberry"
Demo on 3v4l.org
You can use an arrow function if you're willing to pass a parameter:
$stack = array("orange", "banana", "apple", "raspberry");
$fruit = fn(&$s) => array_shift($s);
var_dump($fruit($stack));
var_dump($fruit($stack));
var_dump($fruit($stack));
var_dump($fruit($stack));
Output:
string(6) "orange"
string(6) "banana"
string(5) "apple"
string(9) "raspberry"
Demo on 3v4l.org