Leitfaden zur PHP-Versionsmigration (baserCMS)
Eine Rezeptsammlung für veraltete und breaking Changes, die bei PHP-Version-Upgrades auftreten können, sortiert nach Version. Probleme, die direkt aus dem CakePHP-Framework stammen, werden in der separaten Skill cakephp-migration behandelt.
Enthält: PHP 8.2 / 8.4 / 8.5. Bei der Unterstützung einer neuen Version wird ein neuer Abschnitt „## PHP X.Y“ hinzugefügt. Hinweis: PHP 8.5 ist die letzte Version vor 9.0. Alle in 9.0 als veraltet markierten Funktionen werden entfernt. Daher sollten die in 8.5 auftretenden Deprecation-Warnungen frühzeitig beseitigt werden.
Grundprinzipien
- Unterscheiden Sie zwischen „Fatal Error“ und „Deprecation Warning“. Im Debug-Modus werden Deprecation-Warnungen unten auf dem Bildschirm angezeigt und sehen aus wie Fehler, aber Einträge mit
debug:inlogs/debug.logsind nur Warnungen (der Code läuft weiter). Fatal/Exception sind echte Fehler. - Regression von bestehenden Fehlern trennen. Prüfen Sie mit
git diff HEAD -- <file>, ob die betreffende Datei zu Ihren Änderungen gehört. - Wenn Sie die Signatur einer Elternklasse ändern, passen Sie auch Kindklassen und Interfaces an.
- Zur vollständigen Erkennung aller Deprecations sind statische Analysetools wie Rector effektiv.
Informationen zur Ausführung von Unit-Tests, Syntaxüberprüfungen und Fehleranalyse finden Sie in der Skill
basercms-unittest. Beachten Sie, dassphp -l(Syntaxprüfung) auch Warnungen zu impliziten Nullable-Typen usw. anzeigt.
PHP 8.2
Dynamische Eigenschaften (deprecated)
Die Zuweisung an nicht deklarierte Eigenschaften ist veraltet. Es erscheint die Meldung Creation of dynamic property X::$prop is deprecated.
- Lösung A (empfohlen): Deklarieren Sie die Eigenschaft in der Klasse.
Typischerweise betrifft dies Zuweisungen wie/** @var ThemeFoldersServiceInterface */ protected $ThemeFoldersService; protected $_tempalteTypes = [];$this->Xxx = $this->getService(...)im Konstruktor. - Lösung B: Für Basisklassen, von denen viele Kindklassen betroffen sind (z. B. die Testbasis
BcTestCasemit über 400 Unterklassen), fügen Sie#[\AllowDynamicProperties]hinzu. Das Attribut wird an Kindklassen vererbt, daher reicht eine Stelle.#[\AllowDynamicProperties] class BcTestCase extends TestCase { ... }
PHP 8.4
1. Implizite nullable Parameter (deprecated)
Die Angabe von Typ $x = null ist als impliziter nullable-Typ veraltet. Verwenden Sie explizit ?Typ.
// Vorher
function foo(int $contentId = null, BcAbstractDetector $agent = null)
// Nachher
function foo(?int $contentId = null, ?BcAbstractDetector $agent = null)
- Erkennung (Regex): Findet Parameter, die kein
?vor dem Typ haben, aber= nullenthalten. Beispiel:([A-Za-z_\\][A-Za-z0-9_\\]*)\s+(\$[A-Za-z0-9_]+)\s*=\s*null(wobei kein?davor steht). - Wenn Sie die Signatur einer Elternklasse ändern, passen Sie auch Kindklassen/Interfaces an.
- Warnung:
Implicitly marking parameter $x as nullable is deprecated, the explicit nullable type must be used instead.
2. $escape-Parameter von fgetcsv() (deprecated)
Die Angabe des $escape-Parameters ist nun erforderlich, das Weglassen ist veraltet.
// Vorher
$head = fgetcsv($fp, 10240);
// Nachher
$head = fgetcsv($fp, 10240, ',', '"', '\\');
- Betrifft nur Stellen, die tatsächlich
fgetcsv()aufrufen. Eigene Implementierungen (z. B.fgets+ Regex zum Parsen von CSV) sind nicht betroffen.
3. Einzelargument-Aufruf von ReflectionProperty::setValue() (deprecated)
Bei nicht-statischen Eigenschaften muss das Zielobjekt übergeben werden.
// Vorher
$property->setValue($value);
// Nachher
$property->setValue($object, $value);
- Warnung:
Calling ReflectionProperty::setValue() with a single argument is deprecated. - Tritt häufig in Test-Hilfsfunktionen auf (z. B. zum Setzen privater Eigenschaften).
- Für statische Eigenschaften übergeben Sie
nullals erstes Argument:setValue(null, $value). Dies funktioniert in allen Versionen 8.1–8.5 (bricht die Kompatibilität zu 8.1 nicht).
4. Übergabe von null an string-Parameter von Kernfunktionen (deprecated, eigentlich ab 8.1+)
Die Übergabe von null an Parameter, die nicht als ?string deklariert sind (z. B. parse_url(null), version_compare($a, null), str_replace(..., null)), löst die Warnung Passing null to parameter #N ($x) of type string is deprecated aus. Verwenden Sie einen expliziten (string)-Cast.
// Vorher → Nachher
parse_url($url); → parse_url((string)$url);
version_compare($current, $required); → version_compare((string)$current, (string)$required);
- Diese Deprecation wurde in PHP 8.1 eingeführt, tritt aber bei Migrationsarbeiten (insbesondere zu 8.4/8.5) gehäuft auf, weshalb sie hier aufgeführt ist.
- Da die Warnung nur erscheint, wenn der Wert zur Laufzeit
nullist, ist sie durch statisches Greppen nicht vollständig erfassbar. Zählen Sie alle Vorkommen vonPassing null to parameterin den Testlogs, um die Quellen zu identifizieren.
PHP 8.5 (Release: 2025-11-20)
Die letzte Minor-Version vor 9.0. Die meisten der folgenden Punkte lösen nur Warnungen aus, der Code läuft weiter. Da sie jedoch in 9.0 entfernt werden, sollten sie behoben werden. Die Liste ist nach absteigender Wahrscheinlichkeit von Auswirkungen auf die Anwendung/das Framework sortiert. ※ Nur Punkt „0.“ ist ein fataler Fehler (breaking Change) und hat höchste Priorität.
⚠️ Hinweis zur Aufrechterhaltung der Kompatibilität mit PHP 8.1 (wenn
composer.json"php": ">=8.1"erfordert). Das Beheben einiger Deprecations in PHP 8.5 mit der „empfohlenen“ Alternative kann dazu führen, dass das Gleiche in PHP 8.1 nicht existiert und ein fataler Fehler auftritt. Die unten mit ❌ gekennzeichneten Punkte sollten daher zurückgestellt werden, bis die Unterstützung für 8.1 endet (die Warnung in 8.5 ist akzeptabel). Das Ausführen unter 8.5 selbst ist möglich, ohne 8.1 zu beschädigen (nur Warnungen).
- ❌
str_increment()(8.3+) → unter 8.1 bleibt es bei$s++- ❌
http_get_last_response_headers()(8.4+) → unter 8.1 bleibt es bei$http_response_header- ❌ Namensraum-PDO (z. B.
Pdo\Mysql::ATTR_*, 8.4+) → unter 8.1 bleibt es beiPDO::MYSQL_ATTR_*Alle anderen Punkte (nicht-normative Casts,
null-Offset →''-, Entfernung von Auto-Free-Funktionen, Backticks →shell_exec,case ;,__serialize) können sicher auch unter 8.1 angewendet werden.
0. [Breaking Change, Fatal] Verwendung reservierter Klassennamen (z. B. Array) mit use führt zu Fatal
Ab PHP 8.5 sind Namen wie Array als spezielle Klassennamen reserviert. Die Verwendung von use ...\Array; oder class Array führt zu einem fatalen Fehler (keine Warnung). Wenn dies bereits in der Autoloading-Phase der Tests passiert, kann die gesamte Testsuite nicht gestartet werden. Daher hat dies höchste Priorität.
Fatal error: Cannot use BcMail\Test\TestCase\Model\Array as Array because 'Array' is a special class name
- Beispiel aus baserCMS: In
MailMessagesTableTest.phpfanden sich am Anfanguse ...\Array;unduse ...\ClassRegistry;(beides tote Importe von nicht existierenden Klassen aus der CakePHP2-Ära). Unter PHP 8.4 waren sie harmlos, unter 8.5 verursachen sie einen Fatal. Löschen Sie die betreffendenuse-Zeilen (die tatsächlichen Referenzen waren bereits durchmarkTestIncompletedeaktiviert). - Erkennung:
grep -rnE "use [A-Za-z0-9_\\\\]+\\\\(Array|Object|Resource|Enum|Mixed|Never|Void|Null|False|True|Iterable|Numeric)\s*;" --include="*.php". - Hinweis: Da es sich um einen fatalen Fehler handelt, kann er mit
php -lfür einzelne Dateien gefunden werden. Ein vorheriges Durchsuchen mit grep verhindert jedoch, dass die Testsuite bereits in der Ladephase abstürzt.
1. Nicht-normative Cast-Namen (deprecated, hohe Auswirkung)
Die folgenden Casts sind veraltet und sollten durch die regulären Namen ersetzt werden: (boolean), (integer), (double), (binary).
// Vorher → Nachher
(boolean) → (bool)
(integer) → (int)
(double) → (float)
(binary) → (string)
- Erkennung:
grep -rnE "\\((boolean|integer|double|binary)\\)" --include="*.php". - Findet sich oft in älterem Code. Kann mechanisch ersetzt werden.
2. Verwendung von null als Array-Offset (deprecated)
Die Verwendung von null als Schlüssel in Array-Zugriffen (z. B. $arr[null], array_key_exists(null, $arr)) ist veraltet. Es wurde implizit in "" umgewandelt. Verwenden Sie stattdessen explizit einen leeren String "".
// Vorher
$arr[null] = $v; array_key_exists(null, $arr);
// Nachher
$arr[''] = $v; array_key_exists('', $arr);
- Achten Sie auf Stellen, an denen eine Variable als Schlüssel verwendet wird (
$arr[$key]) und$keynullsein kann. - Schwer mit statischem Greppen zu erkennen (tritt nur auf, wenn der Wert zur Laufzeit
nullist). Führen Sie alle Tests unter PHP 8.5 aus und sammeln Sie die Quellen mitgrep "null as an array offset" phpunit.log(die Ausgabe enthälton line N of <file>). - Korrekturstrategie: ① Ersetzen Sie den Schlüssel durch
$key ?? ''(entspricht der alten impliziten Konvertierung, achten Sie darauf, dass Set und Get denselben Schlüssel verwenden). ② Fügen Sie vorisset()/Zugriff eine Prüfung$key !== null &&ein. Alternativ kann der Rückgabewert vongetData('batch')usw. mit?? ''abgefangen werden. - Beispiele aus baserCMS (insgesamt 308 Fälle):
BcFileUploader(Set/Get vonuploadingFiles[$bcUploadId]mit?? ''harmonisiert),BcUploadBehavior(oldEntity[$alias][$entity->_bc_upload_id]mit?? ''),MailMessagesTable($dists[$mailField->group_valid]),SearchIndexes/Contents/PermissionsController(getData('batch') ?? ''),BcTextHelper::arrayValue/BcUtil::decodeContent($key !== null && isset(...)).
3. Ressourcen-Freigabefunktionen für Objekte (deprecated)
Die entsprechenden Ressourcen sind nun Objekte, die durch den Garbage Collector automatisch freigegeben werden. Daher ist der explizite Aufruf von close/free-Funktionen veraltet. Entfernen Sie den Aufruf.
curl_close()/curl_share_close()(CurlHandle / CurlShareHandle)finfo_close()(finfo)imagedestroy()(GdImage) ← Häufig in Bildverarbeitungs-Hilfsfunktionenxml_parser_free()(XMLParser)- Erkennung:
grep -rnE "\b(curl_close|curl_share_close|finfo_close|imagedestroy|xml_parser_free)\s*\\(" --include="*.php". - Beispiel aus baserCMS:
Imageresizer.phpundBcFileUploader.phpenthielten 4imagedestroy()-Aufrufe. Sie können entweder einfach gelöscht oder durchunset($img)ersetzt werden, um die sofortige Speicherfreigabe innerhalb von Schleifen zu erhalten (GdImage ist ein Objekt, das der GC beim Lösen der Referenz freigibt. Sicher ab PHP 8.1).
3-2. ReflectionProperty/Method::setAccessible() (deprecated, häufigster Fall)
setAccessible() ist seit PHP 8.1 ein No-Op (private/protected sind mit getValue/setValue/invoke direkt zugänglich). In PHP 8.5 wird es als deprecated markiert. Entfernen Sie die Aufrufzeile (sicher ab >=8.1).
// Vorher
$property = new ReflectionProperty($obj, 'foo');
$property->setAccessible(true); // ← Diese Zeile entfernen
$property->setValue($obj, $v);
- Beispiel aus baserCMS: 33 Vorkommen in src/ und Tests (u. a.
BcUtil,BcTestCase,BaserCorePlugin,PreviewController,BcDatabaseService). Da sie sich an Hot-Paths (z. B. Test-Basisklassen) befinden, ist die Anzahl der Laufzeit-Vorkommen um Größenordnungen höher (ca. 6.300 in der gesamten Testsuite ≈ der Großteil der 8.5-Deprecations). Wenn Sie diese zuerst beheben, reduziert sich das Log drastisch. - Massenlöschung (nur für einzelne Anweisungen):
sed -i -E "/^[[:space:]]*\$[A-Za-z0-9_]+->setAccessible\(true\);[[:space:]]*$/d" <file>. - Hinweis zur Erkennung: Wenn das grep-Muster mit
->setAccessiblebeginnt, könnte-als Option interpretiert werden. Verwenden Siegrep -rn "setAccessible"(vermeiden Sie den führenden Bindestrich) odergrep -e.
4. Inkrementieren von nicht-numerischen Strings (deprecated)
$str++ (für nicht-numerische Strings) ist veraltet. Verwenden Sie str_increment(). ※ str_increment() ist erst ab PHP 8.3 verfügbar. Solange die Kompatibilität mit 8.1 gewahrt werden muss, bleibt es bei $str++.
// Vorher
$code++; // Angenommen $code ist z. B. 'AZ'
// Nachher
$code = str_increment($code);
5. __sleep() / __wakeup() (soft deprecated)
Verwenden Sie __serialize() / __unserialize() (wenn keine Kompatibilität mit PHP 7 mehr benötigt wird).
6. $http_response_header (deprecated)
Die Superglobale $http_response_header ist veraltet. Verwenden Sie http_get_last_response_headers(). ※ http_get_last_response_headers() ist erst ab PHP 8.4 verfügbar. Solange die Kompatibilität mit 8.1 gewahrt werden muss, bleibt es bei $http_response_header.
7. Backtick-Operator (deprecated)
Der Backtick-Operator `command` (ein Alias für shell_exec()) ist veraltet. Verwenden Sie shell_exec() direkt.
8. Beenden eines case-Statements mit ; (deprecated)
case 1; ist veraltet. Verwenden Sie case 1:.
- Erkennung:
grep -rnE "case\s+.+;\s*$" --include="*.php"(Achtung: Kann Fehlalarme liefern, daher visuelle Überprüfung empfohlen).
8-2. array_key_exists(null, ...) (deprecated, oft durch ungültige Serialisierung ausgelöst)
Die Übergabe von null als erstes Argument an array_key_exists() ist veraltet (verwenden Sie einen leeren String). Wenn dies im Framework-Core (CakePHP JsonView::_dataToSerialize) auftritt, liegt die Ursache in einer ungültigen Form der serialize-Option in Ihrem eigenen Code. Wenn Sie setOption('serialize', ['foo', 'message' => $message]) verwenden, also 'key' => Wert in die Liste der Variablennamen mischen, wird $message möglicherweise null auftreten, was zu array_key_exists(null, ...) führt.
- Korrektur: Die
serialize-Option sollte eine Liste von View-Variablennamen sein.['foo', 'message' => $message]→['foo', 'message'](die Werte werden mit$this->set()gesetzt). Beispiel aus baserCMS:MailMessagesController(index/view-Aktionen). - Identifikation der Quelle, wenn sie im Framework liegt: Patchen Sie das Vendor-Verzeichnis temporär (z. B.
if ($key === null) file_put_contents(..., var_export($serialize,true).getTraceAsString())). Führen Sie dann die Tests des betreffenden Plugins aus, identifizieren Sie den eigenen Controller im Stacktrace und stellen Sie das Vendor-Verzeichnis wieder her.
8-3. Übergabe eines Objekts als backing-Array an ArrayObject (deprecated)
Die Übergabe eines Objekts (z. B. stdClass von json_decode()) an new ArrayObject($obj) ist veraltet. Casten Sie es in ein Array.
- Korrektur:
new ArrayObject(json_decode($body)->devices)→new ArrayObject((array)json_decode($body)->devices). Beispiel aus baserCMS:SitesControllerTest.
9. Sonstiges (falls zutreffend)
- PDO-treiberspezifische Konstanten/Methoden werden in den Namensraum verschoben (z. B.
PDO::MYSQL_ATTR_*→Pdo\Mysql::ATTR_*,PDO::sqliteCreateFunction()→Pdo\Sqlite::createFunction()). Betrifft meist den Framework-Core, es sei denn, Sie verwenden solche Konstanten selbst. Das"uri:"-DSN-Schema von PDO ist ebenfalls veraltet. ※ Namensraum-PDO-Klassen sind erst ab PHP 8.4 verfügbar. Solange die Kompatibilität mit 8.1 gewahrt werden muss, bleibt es beiPDO::MYSQL_ATTR_*. - Der
$key_length-Parameter vonopenssl_pkey_derive()und der$context-Parameter vonfinfo_buffer()sind veraltet (werden ignoriert). - Die INI-Einstellung
register_argc_argvist in Nicht-CLI-SAPIs veraltet (verwenden Sie$_GET/$_SERVER['QUERY_STRING']). - Einige Closure-Bindings (z. B. an statische Closures) sind veraltet.
- Geänderte Fehlermeldung für
clone null(Testauswirkungen): Bis PHP 8.4 hieß es__clone method called on non-object, ab PHP 8.5clone(): Argument #1 ($object) must be of type object, null given. Tests, die String-Vergleiche für solche Fehlerfälle durchführen, müssen die erwartete Meldung aktualisieren (Beispiel baserCMS:UploaderCategoriesControllerTestin Admin/Apitest_copy).
⚠️ Fehlerketten in vollständigen Testläufen: In dieser Codebasis kann es selten vorkommen, dass ein vorheriger Test einen globalen Zustand (z. B. Plugin-Konfigurationen in Configure) verschmutzt. Dies führt dazu, dass fast alle nachfolgenden Tests im Application-Bootstrap mit Fehlern wie
PluginCollection::create(): $config ... null givenfehlschlagen. Wenn eine einzelne Plugin-Suite (z. B.plugins/baser-core/tests/TestCase) erfolgreich läuft und ein erneuter Durchlauf sauber ist, liegt es nicht am Code, sondern an Flakyness. Überprüfen Sie dies immer durch Isolation und Wiederholung.
Vorgehen bei der Untersuchung (Checkliste)
- Verwenden Sie
php -l(Syntaxprüfung) oderlogs/debug.log, um die Datei und Zeile der Deprecation-Warnung zu identifizieren. - Klassifizieren Sie das Muster im entsprechenden Versions-Abschnitt und korrigieren Sie es. Für mechanisch ersetzbare Muster (z. B. nicht-normative Casts) können Sie grep zur Massenidentifikation verwenden.
- Führen Sie
php -lerneut aus, um zu prüfen, ob die Warnung verschwunden ist. - Führen Sie den entsprechenden Test isoliert mit
--filteraus. Führen Sie abschließend alle Tests erneut aus.
Verwandt: Probleme, die im Framework (Rückgabewerte von Events, doppelte Associations, ResultSet, FormProtection usw.) liegen, werden in der Skill cakephp-migration behandelt.