„Deprecated: Creation of dynamic property … is deprecated“ – co zrobić po aktualizacji do PHP 8.2?
Po przejściu na PHP 8.2 log jest pełen komunikatów „Creation of dynamic property“? Co oznacza to ostrzeżenie i jak je porządnie usunąć – z przykładami kodu.
W skrócie: Komunikat „Deprecated: Creation of dynamic property … is deprecated“ oznacza, że kod przypisuje obiektowi właściwość, której klasa nie deklaruje. Od PHP 8.2 jest to oznaczone jako przestarzałe. Strona dalej działa, ale w jednej z przyszłych głównych wersji PHP to zachowanie ma zniknąć. Prawidłowe rozwiązanie to zadeklarowanie właściwości w klasie. Na okres przejściowy pomaga atrybut #[\AllowDynamicProperties].
Ten komunikat prawie zawsze pojawia się zaraz po aktualizacji PHP. Hosting przełącza wersję z 8.1 na 8.2 lub nowszą – i nagle log błędów jest pełny, a czasem komunikaty widać nawet na samej stronie. Pełna treść wygląda tak:
Deprecated: Creation of dynamic property Rechnung::$kunde is deprecated
in /var/www/html/classes/Rechnung.php on line 14
Komunikat podaje więc klasę (Rechnung), właściwość ($kunde), plik i numer linii. To wystarczy, żeby zacząć.
Czym jest „dynamiczna właściwość“?
Przez wiele lat PHP pozwalało po prostu przypisać obiektowi nową właściwość, nawet jeśli klasa jej nie znała. PHP po cichu ją tworzyło. Typowy przykład ze starszego kodu:
class Rechnung
{
public $nummer;
public function __construct($nummer, $kunde)
{
$this->nummer = $nummer;
$this->kunde = $kunde; // $kunde nie jest zadeklarowane
}
}
$nummer jest zadeklarowane na początku klasy, $kunde – nie. Do PHP 8.1 działało to bez żadnego komunikatu. Od PHP 8.2 właśnie ta linia generuje ostrzeżenie Deprecated.
Dlaczego PHP to zmieniło? Dynamiczne właściwości ukrywają literówki. Jeśli ktoś napisze $this->kudne zamiast $this->kunde, PHP po prostu utworzy nową właściwość, a błąd wyjdzie dużo później i w zupełnie innym miejscu. Przy zadeklarowanych właściwościach widać takie pomyłki od razu.
Problem nie dotyczy obiektów stdClass (np. wyniku json_decode() bez drugiego parametru) ani klas, które używają magicznych metod __get i __set.
Czy to jest groźne?
Dzisiaj bezpośrednio nie. „Deprecated“ to ostrzeżenie z wyprzedzeniem, a nie błąd. Pod PHP 8.2, 8.3 i 8.4 kod robi dokładnie to samo, co wcześniej.
To ostrzeżenie ma jednak swój cel. Twórcy PHP planują, że w jednej z przyszłych głównych wersji dynamiczne właściwości nie będą już dozwolone. Kiedy dokładnie i w jakiej formie – tego dziś nie ustalono ostatecznie. Pewny jest tylko kierunek. Gdy to nastąpi, ostrzeżenie zamieni się w prawdziwy błąd, a strony, których to dotyczy, przestaną się ładować.
Już dziś są dwa problemy:
- Widoczne komunikaty: jeśli na serwerze włączone jest
display_errors, ostrzeżenia pojawiają się w przeglądarce. Wygląda to nieprofesjonalnie i zdradza ścieżki na serwerze. - Przepełnione logi: jeśli klasa jest używana przy każdym wyświetleniu strony, log błędów szybko rośnie. Prawdziwe błędy giną w tym szumie.
Rozwiązanie 1: zadeklarować właściwość
To jest właściwa droga i zwykle także najprostsza. Brakującą właściwość dopisują Państwo na początku klasy:
class Rechnung
{
public $nummer;
public $kunde;
public function __construct($nummer, $kunde)
{
$this->nummer = $nummer;
$this->kunde = $kunde;
}
}
Gotowe. Widoczność (public, protected, private) zależy od tego, kto korzysta z właściwości. Jeśli inny kod odczytuje z zewnątrz $rechnung->kunde, musi ona pozostać public.
Uwaga z praktyki: jeśli przy okazji dodają Państwo typy, np. public string $kunde;, właściwość nie ma już wartości początkowej. Odczyt przed przypisaniem kończy się błędem („must not be accessed before initialization“). Dlatego w starym kodzie najpierw dodaję samą deklarację bez typu albo z wartością domyślną, np. public ?string $kunde = null;.
Rozwiązanie 2: #[\AllowDynamicProperties] na przejściowo
Czasem klasa ma dziesiątki takich miejsc albo właściwości powstają z kolumn bazy danych i nie są znane z góry. Wtedy można jawnie zezwolić klasie na dynamiczne właściwości:
#[\AllowDynamicProperties]
class Rechnung
{
public $nummer;
// ...
}
Komunikat znika dla tej klasy i wszystkich klas, które po niej dziedziczą. W PHP 7.x ta linia jest traktowana jak komentarz, więc kod nadal tam działa.
Ja używam tego atrybutu wyłącznie jako rozwiązania tymczasowego. Wycisza ostrzeżenie, ale nie usuwa przyczyny. Jeśli go Państwo stosują, warto zapisać sobie te miejsca i zaplanować właściwą poprawkę.
Rozwiązanie 3: __get i __set dla naprawdę dynamicznych danych
Niektóre klasy celowo mają przyjmować dowolne wartości – na przykład ustawienia albo rekordy z bazy. Tu czyściej jest trzymać dane w tablicy i obsłużyć dostęp przez metody magiczne:
class Einstellungen
{
private array $daten = [];
public function __get(string $name): mixed
{
return $this->daten[$name] ?? null;
}
public function __set(string $name, mixed $value): void
{
$this->daten[$name] = $value;
}
public function __isset(string $name): bool
{
return isset($this->daten[$name]);
}
public function __unset(string $name): void
{
unset($this->daten[$name]);
}
}
Reszta kodu może dalej pisać $einstellungen->farbe = 'blau';, ale nie powstaje już dynamiczna właściwość. Proszę nie zapominać o __isset – bez niego isset() i empty() zwracają błędne wyniki. Często wystarczy też zwykła tablica albo obiekt stdClass.
Jak znaleźć wszystkie miejsca
Nie warto przeklikiwać każdego komunikatu w przeglądarce. Szybciej idzie przez log błędów. W konsoli to polecenie zbiera wszystkie klasy i właściwości w jedną listę:
grep -o "Creation of dynamic property [^ ]*" error.log | sort | uniq -c | sort -rn
Wynik wygląda np. tak: 312 Creation of dynamic property Rechnung::$kunde – razem z liczbą wystąpień. Od razu widać, od których klas zacząć. Ważne: log pokazuje tylko miejsca, które faktycznie zostały wywołane. Rzadko używane funkcje, jak zamknięcie roku czy eksport danych, pojawią się dopiero wtedy, gdy ktoś ich użyje. Dlatego na kopii testowej warto raz przejść wszystkie ważne procesy.
Dlaczego nie warto po prostu ukryć komunikatu
Najszybsza „poprawka“, którą często można znaleźć w internecie, to ta linia:
error_reporting(E_ALL & ~E_DEPRECATED);
Potem jest cisza. Ale problem nie znika – po prostu go Państwo nie widzą. I tracą Państwo dokładnie to ostrzeżenie, które ma dać czas na zmiany. Do tego wyciszają Państwo wszystkie inne komunikaty Deprecated – również te, które przy następnej aktualizacji PHP byłyby bardzo potrzebne.
Trzeba rozróżnić dwie rzeczy:
- Wyświetlanie w przeglądarce: na działającej stronie
display_errorspowinno być zawsze wyłączone. To słuszne i nie ma związku z tym komunikatem. - Logowanie:
log_errorspowinno zostać włączone, a komunikaty Deprecated powinny trafiać do logu. Tylko wtedy wiadomo, co przestanie działać przy kolejnej aktualizacji.
Typowe błędy przy poprawkach
- Poprawianie cudzego kodu bezpośrednio: jeśli komunikat pochodzi z wtyczki, rozszerzenia albo z katalogu
vendor/, zmiana zostanie nadpisana przy następnej aktualizacji. Najpierw proszę sprawdzić, czy jest nowsza wersja. Jeśli nie – zgłosić problem producentowi. - Atrybut wszędzie: dopisanie
#[\AllowDynamicProperties]do każdej klasy wycisza komunikaty, ale tylko odsuwa problem w czasie. - Zła widoczność: zadeklarowanie właściwości jako
private, gdy inny kod korzysta z niej z zewnątrz, powoduje prawdziwy błąd. - Praca na żywej stronie: jedna literówka w centralnej klasie potrafi wyłączyć całą stronę. Zmiany najlepiej robić i testować na kopii.
Kiedy potrzebna jest pomoc
Kilka komunikatów we własnym kodzie da się poprawić samodzielnie według tej instrukcji. Inaczej jest, gdy log pokazuje setki różnych klas, gdy kod napisał programista, z którym nie ma już kontaktu, albo gdy obok ostrzeżeń Deprecated pojawiają się też prawdziwe błędy. Wtedy warto podejść do tego w uporządkowany sposób.
Przeglądam log błędów, układam komunikaty według pilności i poprawiam je na kopii testowej, zanim cokolwiek trafi na żywą stronę. Jeśli chodzi o pojedyncze błędy, przebieg i ceny znajdą Państwo na stronie naprawa błędów PHP (analiza 150 €, naprawa od 290 €). Jeśli i tak czeka Państwa przejście na nowszą wersję PHP, lepszym rozwiązaniem będzie aktualizacja PHP (od 690 €) – wtedy kod jest raz porządnie przygotowany na nową wersję.