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!