Jedną z najbardziej wyczekiwanych funkcjonalności po wprowadzeniu do PHP obiektowości z prawdziwego zdarzenia, były przestrzenie nazw. Jeszcze przed swoim pojawieniem się budziły kontrowersje za sprawą separatora. Nie dość, że mamy do dyspozycji strzałkę (dla obiektów, ich metod oraz właściwości), podwójny dwukropek (dla statycznych metod i właściwości), to dostaliśmy kolejny symbol – backslash, oddzielający od siebie kolejne przestrzenie nazw. Niezależnie od tego, czy symbol ten się nam podoba, czy też nie, warto wiedzieć co to są przestrzenie nazw i jak z nich korzystać.
Co to są przestrzenie nazw i po co powstały?
Odpowiedź na powyższe pytanie najlepiej będzie przedstawić na przykładzie. Załóżmy, że tworzymy duży projekt, w skład którego będzie wchodzić kilka aplikacji, między innymi blog oraz forum. Tak się składa, że obie aplikacje posiadają klasę o takiej samej nazwie – Entry, będącą obiektową reprezentacją pojedynczego posta/wpisu. Jeśli stworzymy widok prezentujący wpisy na blogu oraz posty na forum pojedynczego użytkownika, staniemy przez problemem doskonale opisanym w poniższym błędzie.
Fatal error: Cannot redeclare class Entry
W tym właśnie celu powstały przestrzenie nazw, które tworzą, jak sama nazwa wskazuje, przestrzeń, w której nazwa klasy musi pozostać unikatowa. Jeśli wykorzystane przez nas aplikacje posiadałyby przestrzenie nazw, np Blog oraz Forum, wówczas wspomniany błąd nie miałby miejsca.
Definiowanie przestrzeni nazw
Przestrzeń nazw musi zostać zdefiniowana w pierwszym wierszu pliku jako pierwsza instrukcja w pliku. W przeciwnym wypadku PHP zgłosi błąd. Przestrzeń nazw będzie miała wpływ klasy, funkcje oraz stałe.
namespace Foo;
class Bar
{
public function baz()
{
$out = 'Przestrzen nazw: ' . __NAMESPACE__ . PHP_EOL
. 'Klasa: ' . __CLASS__ . PHP_EOL
. 'Metoda: ' . __METHOD__ . PHP_EOL;
return $out;
}
}
function funckja()
{
return __FUNCTION__;
}
const STALA = 'abc';
Jeśli uruchomimy powyższy kod
$foo = new Foo\Bar(); echo $foo->baz(); echo Foo\funckja(); echo Foo\STALA;
uzyskamy w efekcie
Przestrzen nazw: Foo Klasa: Foo\Bar Metoda: Foo\Bar::baz Foo\funckja abc
W przypadku bardziej skomplikowanych projektów zachodzi potrzeba zagnieżdżania przestrzeni nazw. Definiowanie zagnieżdżonych przestrzeni nazw nie różni się niczym od “standardowych” przestrzeni. Nazwy kolejnych zagnieżdżeń oddzielamy, a jakże, backslashem.
namespace Foo\Bar\Baz;
Wszystkich, którzy czują się nieswojo bez klamer, ucieszy na pewno wiadomość, iż w przypadku przestrzeni nazw również możemy z nich korzystać.
namespace Foo
{
/* klasy funkcje i stałe */
}
Zabawa z widocznością
Podobnie jak ma to miejsce w przypadku dołączania plików, przestrzenie nazw mogą spowodować zwiększenie ilości WTF na minutę. A to za sprawą sposobu w jaki PHP określa przestrzeń, w której się aktualnie znajdujemy. Jeśli nie straszne dla są zabawy ze ścieżkami plików, z tym również sobie poradzimy.
Manual wymienia trzy sposoby określania przestrzeni nazw wykorzystywanej klasy, funkcji lub stałej. Są to:
- bezpośrednie użycie klasy, funkcji lub stałej
- użycie klasy, funkcji lub stałej poprzedzonych przestrzenią nazw
- użycie klasy, funkcji lub stałej poprzedzonych przestrzenią nazw z przestrzenią globalną
W dwóch pierwszych przypadkach PHP określi całkowitą przestrzeń nazw na podstawie bieżącej przestrzeni oraz “ścieżki” przez nas dostarczonej.
namespace Foo
{
class Bar
{
public function run()
{
$obj = new Baz();
return $obj->test();
}
}
class Baz
{
public function test()
{
return __METHOD__;
}
}
}
Jeśli chcielibyśmy w klasie Bar skorzystać z klasy Baz możemy bez żadnego problemu pominąć przestrzeń nazw, ponieważ PHP za nas doda bieżącą przestrzeń. Dlatego też uruchomienie kodu
$obj = new Foo\Bar(); echo $obj->run();
zaowocuje wyświetleniem
Foo\Baz::test
Jeśli dodamy podprzestrzeń do przestrzeni Foo, nadal będziemy mogli pomijać bieżącą przestrzeń nazw.
namespace Foo\SubFoo
{
class Baz
{
public function test()
{
return __METHOD__;
}
}
}
Jeśli teraz zmienimy w metodzie run klasę, której obiekt tworzymy, na
$obj = new SubFoo\Baz();
uzyskamy w efekcie
Foo\SubFoo\Baz::test
Sprawy nieco się komplikują jeśli chcemy wykorzystać metodę z klasy, która znajduje się w zupełnie innej przestrzeni nazw. W takim przypadku musimy posłużyć się symbolem backslach umieszczonym na początku “ścieżki”. W ten sposób tworzymy “bezwzględną ścieżkę” przestrzeni nazw.
namespace Foo2\SubFoo
{
class Baz
{
public function test()
{
return __METHOD__;
}
}
}
Kolejny raz zmieniamy metodę run
$obj = new \Foo2\SubFoo\Baz();
i uruchamiamy kod. Zgodnie z oczekiwaniami uzyskaliśmy w efekcie
Foo2\SubFoo\Baz::test
Te same zasady dotyczą argumentów przekazywanych do funkcji oraz metod.
Prawdziwa zabawa z widocznością zaczyna się w momencie, gdy w danej przestrzeni nazw nie znajduje się wskazana klasa, funkcja lub stała. W przypadku klasy, PHP zgłosi błąd. Dlatego też jeśli chcemy korzystać z wbudowanych klas wewnątrz przestrzeni nazw, musimy poprzedzić je globalną przestrzenią nazw (\). Sprawa ma się zupełnie inaczej w przypadku funkcji i stałych. Jeśli będziemy chcieli skorzystać z funkcji lub stałej, która nie jest zdefiniowana w bieżącej przestrzeni, wówczas PHP automatycznie przeszuka globalną przestrzeń nazw.
Importowanie przestrzeni nazw i aliasy
W dużych projektach bardzo często będziemy korzystać z mocno zagnieżdżonych przestrzeni nazw (wystarczy spojrzeć w kod Symfony 2 lub Zend Frameworka 2). Korzystanie z pełnych nazw jest co najmniej kłopotliwe (o czym przekonali się użytkownicy pierwszej wersji ZF). W przypadku przestrzeni nazw mamy możliwość importowania przestrzeni nazw oraz tworzenia aliasów.
Importowanie odbywa się przy użyciu operatora use. Dodanie do niego operatora as spowoduje utworzenie aliasu.
use Foo\Bar\Baz as X; $obj = new X\Klasa();
Jeśli nie użyjemy aliasu (w tym przypadku X), wówczas aliasem będzie ostatni element użyty w operatorze use.
use Foo\Bar\Baz; $obj = new Baz\Klasa();
W przypadku klas mamy możliwość w operatorze use wykorzystać nazwę klasy. Wówczas zapis będzie jeszcze prostszy.
use Foo\Bar\Baz\Klasa; $obj = new Klasa();
Na koniec uwaga warta zapamiętania. Importować oraz tworzyć aliasy można tylko i wyłącznie w największym zakresie widoczności. Oznacza to, że operator use użyty np. w klasie lub funkcji spowoduje zgłoszenie błędu.
Tips & trics
- nie korzystajcie z globalnej przestrzeni nazw – ktoś inny może wpaść na podobny pomysł
- w jednym pliku powinna znaleźć się tylko jedna przestrzeń nazw (jedno słowo kluczowe namespace)
- w jednym pliku może znaleźć się kilka klas/interfejsów należących do tej samej przestrzeni nazw, pod warunkiem, że są one ze sobą ściśle powiązane
- w przestrzeni nazw nie będą działać konstruktory, których nazwa jest taka sama jak nazwa klasy
- jeśli przestrzenie nazw odpowiadają hierarchii katalogów, wówczas można skorzystać z funkcji spl_autoload_register. Wywołana bez żadnego parametru spowoduje użycie domyślnego loadera (spl_autoload), który będzie w stanie automatycznie dołączać klasy bez konieczności korzystania z funkcji include/require. Sposób ten będzie działać od PHP 5.3.3.
- koniecznie zapoznajecie się z PSR-0, będącym próbą ustandaryzowania nazwenictwa klas i przestrzeni nazw w PHP. Wszystkie znaczące w świecie PHP projekty trzymają się wytycznych zawartych w tym dokumencie, więc warto znać jego założenia (podziękowania dla Zyxa za link).
A dwa dni czytałem na ten temat w manualu co by sobie uściślić kilka rzeczy – dobrze mieć to po polsku
Z tym, że jedna uwaga – piszesz, że przestrzeń musi być zdefiniowana w pierwszym wierszu pliku. Nie jest to do końca prawda – musi być to pierwsza instrukcja/polecenie w pliku. Poniższy przykład zadziała bez problemu:
<?php
//Hello World!
/**
@author johndoe@example.com
*/
namespace Foo;
class Bar
{
}
@singles
Masz rację. Nieprecyzyjnie się wyraziłem. Oczywiście chodziło o pierwszą instrukcję w pliku. Poprawione.
Z tego co pamiętam to możliwe jest zdefiniowanie kilku przestrzeni w jednym pliku natomiast muszą one być w klamrach…
@sokzzuka – tak jak mówisz, jest to możliwe, aczkolwiek niezalecane – tak samo jak kilka klas w jednym pliku.
Multiple Namespaces at PHP.net.
@batman – chyba coś nie tak z kolorowaniem ze znacznikiem code w komentarzach
@sokzzuka
Tak, jest taka możliwość, ale tak jak napisał @singles nie jest ona zalecana.
@singles
Fakt, komentarze mają czasami problem z kolorowaniem składni. Jak tylko wezmę bloga na kanał, zajmę się tą sprawą.
Błąd we wpisie: `spl_autoload_register()` służy do rejestrowania automatycznych ładowarek, a nie do ładowania klas. Klasy ładuje `spl_autoload()` i w dodatku jest ona na tyle złośliwa, że nie rozróżnia wielkości liter oraz nie zamienia podkreśleń na `DIRECTORY_SEPARATOR`.
Od siebie dodam, że po prawie roku korzystania z przestrzeni nazw nie wiem, o co były te boje i płacze ze znakiem `\`. Znak, jak każdy inny. Ponadto na początku trzeba się przyzwyczaić nieco, ponieważ korzystanie z przestrzeni nazw i automatycznego ładowania może generować dość ciekawe błędy. Jeśli zapomnimy zrobić `use`, PHP weźmie bieżącą przestrzeń nazw, co skończy się najprawdopodobniej rzuceniem błędu, na którego widok po raz pierwszy ja osobiście zgłupiałem i nie wiedziałem, co jest grane. Teraz już wiem, że jak autoloader się rzuca, to w pierwszej kolejności sprawdzam, czy mam wszystko dobrze zaimportowane.
Wypadałoby też napisać, żeby ludzie nie błądzili, jak pijane owce we mgle, o czymś takim jak PSR-0. Definiuje on minimalne wymagania dotyczące nazewnictwa klas oraz ich tłumaczenia na ścieżki do systemu plików. Warto się ich trzymać, ponieważ są one obecnie adaptowane przez niemal wszystkie wiodące projekty PHP i można wtedy używać uniwersalnej ładowarki do wszystkiego.
@Zyx
Dzięki za zwrócenie uwagi na spl_autoload_register. Faktycznie źle napisałem. Jak znajdę chwilę, poprawię całe zdanie, by nie wprowadzało w błąd.
Odnośnie separatora można toczyć długie spory, które i tak skończą się tym, że trzeba się po prostu do niego przyzwyczaić.
Dzięki za linka do grupy. Na pewno pojawi się w Tips & Tricks. Widziałem, że na liście członków znajduje się Matthew Weier O’Phinney. Standard ten ma jakieś oficjalne wsparcie ze strony Zenda, czy na razie jest tylko propozycją?
A czy jest możliwość korzystania z namespaców wraz z Zend Framework 1.*?
Oczywiście. Jeśli na serwerze, na którym znajduje się Zend Framework masz zainstalowane PHP 5.3, nic nie stoi na przeszkodzie, aby korzystać z przestrzeni nazw. Musisz tylko pamiętać, że korzystanie z klas nie znajdujących się w globalnej przestrzeni będzie lekko utrudnione. Będziesz musiał pamiętać o dodawaniu backslasha przed każdą z nich, jeśli będziesz chciał korzystać z nich z wnętrza dodanych przestrzeni nazw.
Batman -> tak, autoloader obsługuje go od wersji ZF 1.10. Ekipa ZF-a była zaangażowana w jego powstawanie jako członek tej grupy.