PHP implementation of isset() will only call offsetExists and will never check offsetGet for null.
This is not 100% clear from the docs.
Relevant bug:
https://bugs.php.net/bug.php?id=41727(PHP 5, PHP 7, PHP 8)
ArrayAccess::offsetExists — Prüft, ob ein Offset-Punkt existiert
Prüft, ob ein Offset-Punkt existiert oder nicht.
Diese Methode wird aufgerufen, wenn die Funktionen isset() oder empty() auf Objekte angewendet werden, die ArrayAccess implementieren.
Hinweis:
Wenn empty() verwendet wird, wird die Funktion ArrayAccess::offsetGet() nur dann aufgerufen und untersucht, ob noch kein Wert zugewiesen wurde, wenn ArrayAccess::offsetExists()
truezurückgibt.
offsetDer zu untersuchende Offset-Punkt.
Gibt bei Erfolg true zurück. Bei einem Fehler wird false zurückgegeben.
Hinweis:
Wenn der Rückgabewert kein Boolean ist, wird er in bool umgewandelt.
Beispiel #1 ArrayAccess::offsetExists()-Beispiel
<?php
class obj implements ArrayAccess {
public function offsetSet($offset, $wert): void {
var_dump(__METHOD__);
}
public function offsetExists($var): bool {
var_dump(__METHOD__);
if ($var == "foobar") {
return true;
}
return false;
}
public function offsetUnset($var): void {
var_dump(__METHOD__);
}
#[\ReturnTypeWillChange]
public function offsetGet($var) {
var_dump(__METHOD__);
return "Wert";
}
}
$obj = new obj;
echo "Ausführung obj::offsetExists()\n";
var_dump(isset($obj["foobar"]));
echo "\nAusführung obj::offsetExists() und obj::offsetGet()\n";
var_dump(empty($obj["foobar"]));
echo "\nAusführung obj::offsetExists(), "
."obj:offsetGet() wird *nicht* ausgeführt, "
."wenn nichts zurückgegeben werden kann\n";
var_dump(empty($obj["foobaz"]));
?>Das oben gezeigte Beispiel erzeugt eine ähnliche Ausgabe wie:
Ausführung obj::offsetExists() string(17) "obj::offsetExists" bool(true) Ausführung obj::offsetExists() und obj::offsetGet() string(17) "obj::offsetExists" string(14) "obj::offsetGet" bool(false) Ausführung obj::offsetExists(), obj:offsetGet() wird *nicht* ausgeführt, wenn nichts zurückgegeben werden kann string(17) "obj::offsetExists" bool(true)
PHP implementation of isset() will only call offsetExists and will never check offsetGet for null.
This is not 100% clear from the docs.
Relevant bug:
https://bugs.php.net/bug.php?id=41727Please note something:
The docs explain clearly that this method is called when "isset()" or "empty()" are called on the object's key.
This means that there is a huge difference in your custom implementation when you have an internal array on which you choose to call either "isset()" or "array_key_exists()".
Even though the method says "offsetExists", it is *not* supposed to be used only when the offset exists, because this is not at all the behavior of neither "isset" nor "empty" internally.
This means you can have issues like this (more explanations below):
<?php
class Value {
public function __construct(
public string $value,
) {
}
}
class MyArray implements ArrayAccess {
private array $internal = [];
public function offsetExists(mixed $offset): bool
{
return array_key_exists($offset, $this->internal);
}
// ... rest of the implementation
public function offsetGet(mixed $offset): mixed
{
return $this->offsetExists($offset) ? $this->internal[$offset] : null;
}
public function offsetSet(mixed $offset, mixed $value): void
{
if (is_null($offset)) {
$this->internal[] = $value;
} else {
$this->internal[$offset] = $value;
}
}
public function offsetUnset(mixed $offset): void
{
unset($this->internal[$offset]);
}
}
$object = new MyArray();
$object['key'] = null;
// This is where the error occurs:
// PHP Fatal error: Uncaught TypeError: Value::__construct(): Argument #1 ($value) must be of type string, null given
$otherValue = isset($object['key']) ? new Value($object['key']) : null;
?>
The thing here is that we have some code that cannot use the "??" operator because we need the output of the "isset" call to return true, and only then we want to use.
With a real array, this should be fairly common because we know how "isset" works.
However, since the "offsetExists" method has a lot of different implementations in PHP libaries, you should *not* trust the output in "isset" with objects implementing ArrayAccess.
A workaround is to create an intermediate variable and run "isset()" on it:
<?php
// Before
$otherValue = isset($arrayObject['key']) ? new Value($arrayObject['key']) : null;
// After
$rawValue = $arrayObject['key'] ?? null;
$otherValue = isset($rawValue) ? new Value($rawValue) : null;
?>