Podczas pisania pracy inżynierskiej natknąłem się na kilka ciekawych miejsc w kodzie, „produkujących” nieznane mi do tej pory błędy. Być może miałem świadomość ich istnienia, ale nie udało mi się jeszcze ich „popełnić”. W dzisiejszym wpisie chciałbym przedstawić Wam kolejny problem, tym razem dotyczący pewnych starszych mechanizmów języka PHP. Zapraszam do lektury.
Wstęp: „obiektowość” PHP4.
Zarówno wpis z wtorku, traktujący problemach związanych głównie z zagapieniem się programisty, jak i dzisiejszy wyjaśniający nieco dziwne podejście PHP do wprowadzonego przez niego w wersji piątej modelu obiektowości to konsekwencja całkiem sporej bazy kodu, jaki powstał podczas implementacji mojego frameworka. Okazało się wtedy, że sięgając do absolutnych podstaw biblioteki standardowej i próbując wymusić wymyśloną wcześniej strukturę kodu nie zawsze wszystko działa tak, jakbyśmy oczekiwali.
Opisywany dzisiaj problem w pewnym sensie dotyczy, wydawałoby się dawno porzuconej (a przynajmniej nieużywanej w nowych projektach), czwartej wersji interpretera PHP. Zastosowany wtedy sposób deklaracji klas zakładał, podobnie jak w C++ i Javie, konstruktory o nazwach takich, jak nazwa klasy. Przykładowa klasa PHP4 mogła wyglądać następująco:
<?php
class Foo
{
private $str = '';
public function Foo()
{
$this->str = 'foo';
}
}
Funkcja __construct nie miała wtedy żadnego znaczenia – jest to nowość wprowadzona dopiero w PHP5. Przyznam, że całkiem przydatna, ponieważ przy zmianie nazwy klasy nie trzeba się martwić „dezaktywacją” istniejącego konstruktora, aczkolwiek biorąc pod uwagę fakt, że w PHP nie ma przeciążania funkcji / metod, a więc możemy utworzyć jedynie jeden konstruktor, nie wygląda to już tak dobrze.
Wróćmy jednak do naszego problemu. Cóż takiego zrobiłem, że pojawił się błąd? Otóż, zdefiniowałem w jednej z klas oprócz standardowego, „nowego” konstruktora, metodę o nazwie takiej, jak nazwa klasy.
Problem: podwójna obiektowość PHP5.
Rozważmy więc przykładowy kod:
<?php
class Foo
{
private $str = '';
public function __construct()
{
echo __METHOD__;
}
public function Foo()
{
echo __METHOD__;
}
}
$foo = new Foo();
$foo->Foo();
Widzimy, że klasa ma „dwa konstruktory”, a więc powyższe dwa listingi wyprodukują… nie, teraz jeszcze błędu nie będzie, spokojnie. ;] Tym razem otrzymamy całkowicie poprawny wynik:
Foo::__construct Foo::Foo
Problem pojawia się, kiedy rozdzielimy na dwa pliki implementację klasy i wykorzystującego ją kodu. Dzieje się tak w większości przypadków – w każdym razie wszystkie frameworki i większe biblioteki stosują się do zasady „jeden plik – jedna klasa”, także możemy przyjąć, że pod tym względem jest to prawie pewnik. Zostało to opisane w błędzie #52160 i „podobno poprawione” w wersji 5.2.14 – piszę „podobno”, ponieważ otrzymałem ten błąd w wersji 5.3.1.
Zobaczmy więc ten sam kod rozbity na dwa pliki:
// class.php
<?php
class Foo
{
private $str = '';
public function __construct()
{
echo __METHOD__;
}
public function Foo()
{
echo __METHOD__;
}
}
// code.php
<?php
error_reporting(E_ALL | E_STRICT);
ini_set('display_errors', true);
require_once('redefining_ctor_class.php');
$foo = new Foo();
$foo->Foo();
Jaki będzie wynik? Tym razem otrzymamy:
Strict Standards: Redefining already defined constructor for class Foo in [path]/class.php on line [line] Foo::__construct Foo::Foo
Jest to kolejny mankament „obiektowości” języka PHP, o którym należy pamiętać. Miejmy nadzieję, że pojawi się w przyszłości wersja interpretera zrywająca wsteczną kompatybilność i rozwiązująca obiektowość w zupełnie nowy sposób, podobny do języków takich jak np. C++. Nie mówię tutaj oczywiście o bezpośredniej kopii, ale o samych możliwościach, które są bardzo przydatne podczas projektowania zaawansowanych struktur kodu.
Dlaczego umieściłem w nagłówku słowo „podwójna”? Ponieważ w momencie, kiedy interpreter PHP5 nie znajdzie w klasie konstruktora nowego typu [funkcji __construct()]… szuka konstruktora starego typu. Wszystko w imię kompatybilności wstecznej, która niestety jest chyba największym hamulcem rozwoju współczesnego oprogramowania. Trudno, jakoś trzeba z tym żyć. ;]
Należy tutaj także zauważyć, że jest to błąd typu Strict Standards, który to typ jest aktywowany poprzez umieszczenie w zmiennej error_reporting [za pomocą funkcji o tej samej nazwie bądź funkcji ini_set()] flagi E_STRICT. Stała ta została wprowadzona właśnie w PHP5 dla większej kontroli błędów występujących podczas wykonania skryptu.
Mam nadzieję, że dzięki temu wpisowi pojawienie się ostrzeżenia o nadpisywaniu konstruktora nie przeszkodzi Wam w pisaniu kodu i szybko naprawicie rzeczony błąd. Do zobaczenia w kolejnym wpisie!
Warto przeczytać.
Trwa ładowanie…