Problemy
Nie mówię tu o symbolu oddzielającym nazwy. To detal odwracający uwagę od prawdziwych problemów.
Im dłużej używam przestrzeni nazw PHP 5.3, tym bardziej dochodzę do wniosku, że są wybrakowane i mało użyteczne.
Nie da się importować całych przestrzeni
Instrukcja use
importuje tylko nazwy wymienionych klas lub przestrzeni i nie ma możliwości masowego importowania:
use MójProjekt\Komponent\*; // Nie w PHP
Importowanie całej „cudzej” przestrzeni może być ryzykowne, więc brak takiej funkcjonalności można by wybaczyć, ale PHP nie ma nawet wygodnej składni do importowania wybranych klas:
Niezależnie od tego, w jaki sposób odwołujesz się do klas, nie unikniesz pisania pełnej nazwy z przedrostkiem (fully qualified name) przynajmniej raz. Jeżeli nie potrzebujesz użyć zaimportowanej klasy więcej niż raz, to przestrzenie nazw wydłużają kod.
Trzeba w kółko powtarzać instrukcje use
Nie sugeruję, żeby use
było globalne, bo wtedy oczywiście nie miało by sensu.
instrukcje 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 pozwalają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 przestrzeni — jest niezbyt praktyczna:
- Ładuje klasy natychmiast, przez co nie nadaje się do deklarowania nazw „na wyrost” (np. nie można sobie „emulować”
use Ns\*
bez spowalniania programu). - Tworzy nazwę ograniczoną do jednej, specyficznej przestrzeni. Można sobie dla wygody wrzucić nazwy w przestrzeń globalną, ale to jest dokładnie to, czego przestrzenie nazw miały uniknąć. Wrzucenie nazw we własną przestrzeń nie uwolni od instrukcji
use
, jeżeli używa się podprzestrzeni nazw.
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 instrukcje 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 duplikaty, 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 oddzielanych separatorem \
. Oznacza to, że PHP umożliwia każdej części takiej „ścieżki” kolidowanie z zarezerwowanym słowem kluczowym. Można natknąć się na sytuację, gdy działający kod:
new List_DoublyLinked();
staje się błędny po skonwertowaniu na przestrzenie nazw:
new List\DoublyLinked(); // Kolizja!
Powyższy kod jest błędem składniowym, ponieważ słowo kluczowe list
jest zarezerwowane. 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 technicznie nie mają żadnej specjalnej funkcjonalności. W szczególnoś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 GUI
i GUI\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 przedrostkiem. 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 wyszukiwania 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 przestrzeniami (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żonych maciupeńkich przestrzeni 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 podkreśleniami. Możliwość użycia krótszych nazw bez przedrostków i odporność na kolizje nazw niweczone są przez konieczność używania długich i powtarzają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 kontrolowanych 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 automatycznie 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:
- Nazwy wraz z przedrostkiem powinny być pojedynczym tokenem. To pozwoli uniknąć kolizji nazw z zarezerwowanymi słowami kluczowymi.
- Gramatyka języka powinna pozwalać na używanie nazw z przedrostkiem w deklaracjach klas. Nazwy klas powinny działać wszędzie tak samo, a różne funkcjonalności języka nie powinny sobie wchodzić w drogę.
autoload
powinien być rozszerzony, aby obsługiwał ładowanie funkcji i stałych, jeżeli są używane za pośrednictwem nazw z przedrostkiem (qualified names). Niestety nazwy bez przedrostka muszą pozostać wyjątkiem (inaczej trzeba by pisać\strlen()
zamiaststrlen()
, co by doprowadziło do szaleństwa).
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.