isset() liefert false, obwohl der Wert da ist – die __isset-Falle in TYPO3 14

In TYPO3 14 sind etliche bisher öffentliche Properties protected geworden und nur noch über __get() lesbar. isset() und der ??-Operator nehmen aber einen anderen Weg – sie fragen __isset(), und das antwortet für diese Properties immer mit false. Die Bedingung ist dann stumm falsch: kein Fehler, kein Logeintrag, keine Deprecation-Meldung.

Ein ViewHelper prüfte jahrelang zuverlässig, ob das gerade gerenderte Inhaltselement über ein Shortcut eingebunden wurde. Unter TYPO3 14 tat er es plötzlich nicht mehr – ohne dass irgendetwas darauf hingewiesen hätte.

Sieht richtig aus, ist es aber nicht

return $cObj->parentRecord !== []
    && isset($cObj->parentRecord['currentRecord'], $cObj->parentRecord['data']['CType'])
    && str_starts_with($cObj->parentRecord['currentRecord'], 'tt_content:');

Der Grund: ContentObjectRenderer::$parentRecord ist seit TYPO3 14 protected. Lesen lässt sich die Property trotzdem noch, weil die Klasse den PublicPropertyDeprecationTrait einbindet. Der bringt aber nicht nur __get() mit, sondern auch __isset() – und die beiden verhalten sich unterschiedlich.

vendor/typo3/cms-core/Classes/Compatibility/PublicPropertyDeprecationTrait.php

public function __isset(string $propertyName)
{
    if (isset($this->deprecatedPublicProperties[$propertyName])) {
        trigger_error($this->deprecatedPublicProperties[$propertyName], E_USER_DEPRECATED);
        return isset($this->$propertyName);
    }
    return false;                    // fuer alles Nicht-Gelistete
}

public function __get(string $propertyName)
{
    if (isset($this->deprecatedPublicProperties[$propertyName])) {
        trigger_error($this->deprecatedPublicProperties[$propertyName], E_USER_DEPRECATED);
    }
    return property_exists($this, $propertyName) ? $this->$propertyName : null;
}

__get() liefert den echten Wert. __isset() dagegen gibt für jede Property, die nicht ausdrücklich in $deprecatedPublicProperties steht, immer false zurück – auch wenn die Daten vollständig vorhanden sind. Und isset($obj->prop['key']) geht über __isset(). Dasselbe gilt für den Null-Koaleszenz-Operator: $obj->prop['key'] ?? null ist ebenfalls immer null.

Die Lösung: erst zuweisen, dann prüfen

/** @var array<string, mixed> $parentRecord */
$parentRecord = $cObj->parentRecord;      // einmal ueber __get()

return isset($parentRecord['currentRecord'], $parentRecord['data']['CType'])
    && str_starts_with($parentRecord['currentRecord'], 'tt_content:');

Sobald der Wert in einer gewöhnlichen Variablen liegt, verhält sich isset() wieder normal. Beim Suchen hilft es, im eigenen Code nach isset($cObj-> und ?? in Verbindung mit fremden Objekten zu greppen. Besonders tückisch ist, dass die Falle beim Debuggen ein zweites Mal zuschnappt: Wer den Fehler mit $obj->prop['x'] ?? null untersuchen will, bekommt wieder null und hält die Property fälschlich für leer.


Erstellt am: