English version
Brak dzwonka. Potrząśnij butelką z kamieniami No co? Działa!

Problemy

Nie mówię tu o symbolu oddzie­la­jącym nazwy. To detal odwra­cający uwagę od prawdziwych problemów.

Im dłużej używam przestrzeni nazw PHP 5.3, tym bardziej dochodzę do wniosku, że są wy­bra­ko­wane i mało użyteczne.

Nie da się importować całych przestrzeni

Instrukcja use importuje tylko nazwy wymie­nio­nych klas lub przestrzeni i nie ma możli­wości masowego importowania:

use MójProjekt\Komponent\*; // Nie w PHP

Importowanie całej „cudzej” przestrzeni może być ryzykowne, więc brak takiej funkcjo­nal­ności można by wybaczyć, ale PHP nie ma nawet wygodnej składni do impor­towania wybranych klas:

Niezależnie od tego, w jaki sposób odwołujesz się do klas, nie unikniesz pisania pełnej nazwy z przed­rostkiem (fully qualified name) przynaj­mniej raz. Jeżeli nie potrze­bujesz użyć zaimporto­wanej klasy więcej niż raz, to przestrzenie nazw wydłużają kod.

Trzeba w kółko powtarzać instruk­cje use

Nie sugeruję, żeby use było globalne, bo wtedy oczywiście nie miało by sensu.

instruk­cje use działają tylko w obrębie jednej przestrzeni w jednym pliku. Klepanie ich od nowa w każdym pliku jest upierdliwe, więc przydałby się jakiś mechanizm pozwala­jący napisać je raz i potem np. odwoływać się do wspólnego pliku. Niestety nic z tego — trzeba używać metody Kopiego N. Pasta.

use <zestaw_importów_powtarzających_się_w_całym_projekcie.php> // Fantazja

Teoretycznie funkcja class_alias() ma być obejściem problemu, ale — jak cała reszta prze­strzeni — jest niezbyt praktyczna:

Zapomniane importy/aliasy

Wstawianie instrukcji use dla każdej klasy w każdym pliku jest szalone. W praktyce konieczne jest wybranie minimalnego zestawu niezbędnych nazw dla każdego pliku z osobna. Siłą rzeczy różne pliki będą importowały różne nazwy.

W efekcie, żeby uniknąć błędów, trzeba by zaglądać na początek pliku za każdym razem, gdy używa się nazwy klasy bez przedrostka (przesada? Pamiętaj o prawie Murphy'ego). Zapomniane instruk­cje use dadzą o sobie znać dopiero w momencie użycia niezadeklarowanej nazwy.

Miszmasz deklaracji w plikach utrudnia przenoszenie kodu między plikami. Trzeba pamiętać o skopiowaniu instrukcji use, ale trzeba też uważać na dup­li­katy, bo redeklaracja nazwy jest błędem krytycznym.

Przestrzenie nazw mogą powodować kolizje nazw

Składnia PHP nie traktuje nazwy z przedrostkiem jako pojedynczego tokenu, tylko jako serię nazw oddziela­nych separa­torem \. Oznacza to, że PHP umożliwia każdej części takiej „ścieżki” kolido­wanie z zare­zerwo­wanym słowem klu­czo­wym. Można natknąć się na sytuację, gdy działający kod:

new List_DoublyLinked();

staje się błędny po skonwertowaniu na prze­strzenie nazw:

new List\DoublyLinked(); // Kolizja!

Powyższy kod jest błędem składniowym, ponieważ słowo kluczowe list jest zare­zer­wowane. Nie da się tego uniknąć instrukcją use, ponieważ ona też będzie „błędna”.

use List as NieKoliduj; // i tak będzie kolizja!

Zagnieżdżanie przestrzeni nazw jest niedorobione

Podprzestrzenie nazw w PHP są tylko prymitywną sztuczką zrobioną na poziomie składni i tech­nicz­nie nie mają żadnej specjal­nej funkcjonal­ności. W szczegól­ności nie wpływają na sposób interpretacji nazw bez przedrostka.

Podprzestrzenie nie są „osadzone” wewnątrz ich nadrzędnych przestrzeni: zachowują się jak zupełnie osobne, niezależne przestrzenie nazw, które po prostu mają podobne nazwy:

namespace GUI;
class View {}

namespace GUI\Widgets;
class PushButton extends View {} // PHP nie znajdzie klasy View

Powyższy kod nie działa, ponieważ PHP traktuje GUIGUI\Widgets jako dwie zupełnie niezależne od siebie przestrzenie. Nie można nawet zagnieżdżać instrukcji namespace {}.

Wyszukiwanie nazw jest dziwaczne i niespójne

Nie trudno zauważyć, że brak automatycznego dostępu do klas z nadrzędnych przestrzeni powoduje mnożenie się instrukcji use i/lub nazw z przed­rostkiem. PHP unika tego problemu tylko dla funkcji i stałych, i tylko dla globalnej przestrzeni nazw:

namespace Foo\Bar;

\time(); // działa, ale kole w oczy
time(); // PHP jest na tyle sprytny, żeby znaleźć sobie globalną funkcję

new \Date(); // brzydkie i wymagane
new Date(); // PHP już nie jest taki sprytny

Powyższy kod nawali, jeżeli PHP nie znajdzie klasy Foo\Bar\Date. PHP nie będzie próbował znaleźć Foo\Date ani Date.

Przestrzeń nazw Foo zostanie zignorowana przy wyszukiwaniu funkcji, więc nawet jeżeli istnieje Foo\time(), zostanie użyta globalna funkcja time().

Nazwy z przedrostkiem nie działają w deklaracjach klas

Nie da się:

class Foo\Bar\Someclass {…}

Jedyna opcja to:

namespace Foo\Bar;
class Someclass {…}

Przez to ograniczenie nie można sobie wybrać najwygodniejszej przestrzeni nazw niezależnej od przestrzeni, w której będzie klasa. Na przykład przydatne byłoby używanie tej samej przestrzeni w całym projekcie (aby mieć takie same, łatwe do wy­szu­ki­wania i kopiowania nazwy klas) albo można by wybrać przestrzeń, która wymaga najmniej instrukcji use.

To ograniczenie jest szczególnie dokuczliwe, kiedy konwertuje się kod używający podkreśleń w nazwach na natywne przestrzenie nazw. Nie można po prostu zmienić class Foo_Bar na class Foo\Bar.

Popularne projekty w PHP nadużywają podprzestrzeni nazw

Niestety, traktowanie katalogów jako przestrzeni nazw stało się standardem. Czasem nawet nazwy w CamelCase są rozbijane na kawałki przes­trze­niami (FooController staje się Controller\Foo) i wyjątki dostają własną przestrzeń (np.: Framework\Component\­Form\Exception\UnexpectedTypeException).

Ograniczenia PHP w importowaniu i wyszukiwaniu nazw stają się szczególnie bolesne, kiedy projekty używają setek głęboko zagnież­dżo­nych maciupeń­kich prze­strzeni nazw (czasem tylko z jedną lub dwoma klasami na przestrzeń!)

Przestrzenie nie mogą być ładowane automatycznie

„Oszukane” przestrzenie nazw robione za pomocą statycznych metod i stałych należących do klas miały przydatny efekt uboczny — były automatycznie ładowane przez autoload.

Klasa::STAŁA; // Działa zawsze, jeżeli używa się autoload
Przestrzeń\STAŁA; // Nie zostanie załadowana i może działać tylko przypadkiem

Przerzucenie się na prawdziwe przestrzenie nazw tworzy nowy problem, któremu trudno zapobiec: funkcje i stałe, które są dostępne w testach jednostkowych (unit tests) (ponieważ testy ładują najróżniejsze klasy i ten stan nie jest izolowany między testami), mogą być niedostępne podczas normalnego działania strony, kiedy wykonywana jest tylko jedna specyficzna ścieżka programu, która nie wywołuje autoload odpowiednio wcześnie.

new Lib\Obj(Lib\FOO_MODE); // działa
$mode = Lib\FOO_MODE; // nagle nie działa!
new Lib\Obj($mode);

Podsumowanie

Przestrzenie nazw w PHP 5.3 nie są wielkim usprawnieniem w porównaniu do kodu bez przestrzeni nazw albo używającego nazw z pod­kreśleniami. Możliwość użycia krótszych nazw bez przed­rostków i odporność na kolizje nazw niweczone są przez konieczność używania długich i powtarza­jących się instrukcji use i ryzyko błędów, które wcześniej nie były możliwe.

Dla bardzo dużych bibliotek/frameworków jakiekolwiek przestrzenie nazw mogą być niezbędne, ale dla mniejszych i bardziej kontro­lo­wanych projektów użycie przestrzeni nazw (w ich obecnej formie) może się nie opłacać.

Sugerowane rozwiązania

Nie wydaje mi się, żeby właściwym rozwiązaniem było poleganie na ciężkiego kalibru edytorze/IDE, który analizuje wszystkie pliki w całym projekcie i auto­matycznie generuje tony kodu potrzebnego dla przestrzeni nazw. Mam nadzieję, że da się poprawić sam język, aby mógł stać o własnych nogach.

Przestrzenie nazw to skomplikowana sprawa w tak dynamicznym i niezbyt składnym języku jak PHP. Nie twierdzę, że mam idealne rozwiązania, tylko rzucam pomysłami:

Póki co, wolę unikać używania przestrzeni nazw w swoim kodzie. Sporadyczna zmiana nazwy w przypadku kolizji wymaga znacznie mniej wysiłku, niż normalne używanie przestrzeni nazw.