This post comes from the first version of this blog.
Please send me an email if anything needs an update. Thanks!

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?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:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<?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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// class.php
<?php
class Foo
	{
	private $str = '';

	public function __construct()
		{
		echo __METHOD__;
		}

	public function Foo()
		{
		echo __METHOD__;
		}
	}
1
2
3
4
5
6
7
8
9
// 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!