No co? Działa!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ę.
autoloadpowinien 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.
