niedziela, 24 maja 2015

[PHP] Namespaces - przestrzenie nazw w PHP

TRUE
2394048099834356901
Zapewne spotkaliśmy się już z dziwnie wyglądającymi nazwami klas w PHP, np. podczas pracy z trochę bardziej rozbudowanymi webaplikacjami. Przykładowa nazwa jakiejś klasy mogła wyglądać np. tak: Bundle\HelloBundle\Controller\HelloController. Nie ma tu jednak żadnej pokręconej magii, a jedynie przestrzeń nazw. Klasa w tym przypadku ma jak najbardziej normalną nazwę HelloController, a wszystko to co znajduje się przed jej nazwą to przestrzeń nazw w jakiej się ona zawiera.
Korzystanie z przestrzeni nazw znacznie usprawnia pracę z kodem, a także eliminuje problem kolizji, gdzie dwie klasy mogą mieć identyczne nazwy. Przestrzenie nazw pozwalają również na stworzenie bardzo logicznej i przejrzystej hierarchi naszego kodu. W tym krótkim tutorialu dowiemy się czym są i jak  poprawnie używać przestrzeni nazw w PHP.

Funkcjonalność ta niczym nie różni się od tego samego rozwiązania stosowanego od zarania dziejów w językach takich jak C, czy Java. Przestrzenie nazw w PHP zostały dodane dość późno, bo dopiero w wersji 5.3.

Przestrzeń globalna

Jest główną przestrzenią nazw. Zauważmy, że bardzo często piszemy sobie jakieś klasy, czy też funkcje, bądź też korzystamy z tych wbudowanych w silnik PHP. Korzystamy np. z funkcji header() służacej do ustawienia nagłówka, funkcji strip_tags() służącej do obcięcia tagów HTML i wielu innych. Nazewnictwo tych funkcji jest proste i składa się z jednego członu określającego nazwę funkcji. Funkcje takie istnieją w domyślnej przestrzeni nazw i są dostępne wszędzie w naszym kodzie.

Podobnie jest z klasami, ich metodami i właściwościami. Nie wymagają poprzedzania ich nazwy nazwą przestrzeni w jakiej występują, gdyż globalna przestrzeń jest tą domyślną, bazową. Wyobraźmy sobie teraz, że tworzymy własną klasę, np. niech to będzie klasa o nazwie:
[code]MyClass {}[/code]
Utworzymy sobie plik z klasą, który będzie zawierał definicję jednej klasy, jednej funkcji i jednej stałej, niech będzie to np. plik /classes/MyClass.php. W pliku tym stworzymy sobie następującą definicję dla klasy MyClass:
[code]
<?php
// /classes/MyClass.php

const MYCONST = 'classes/MyClass.php';

function MyFunction()
{
  return __FUNCTION__;
}


class MyClass {

  public function MyMethodName()
  {
    return __METHOD__;
  }

  public function MyClassName()
  {
    return __CLASS__;
  }

  public static function SayHello()
  {
    return 'Hello from ' . __CLASS__ . '::' . __METHOD__;
  }
}
?>
[/code]
Plik z klasą dołączymy do naszego głównego pliku, niech będzie to plik index.php.
Następnie utworzymy nowy obiekt tejże klasy i wywołamy metodę GetMethodName(), która zwróci nam nazwę naszej metody w klasie MyClass:
[code]
<?php
// index.php
require_once(__DIR__.'/classes/MyClass.php');

echo MYCONST;  // WYNIK: classes/MyClass.php
echo 'Function name:' . MyFunction();  // WYNIK: MyFunction

$c = new MyClass;
echo 'Method name:' . $c->MyMethodName();  // WYNIK: Method name: GetMethodName
echo 'Class name:' . $c->MyClassName();  // WYNIK: Class name: MyClass
echo MyClass::SayHello(); // WYNIK: Hello from MyClass::SayHello
?>
[/code]
Jak widzimy, na początku wyświetlamy stałą, potem nazwę funkcji, następnie klasę - jest to prościutka klasa, z trzema metodami zwracającymi odpowiednio:

- nazwę wywoływanej metody
- nazwę wywoływanej klasy
- tekst przywitania z przedstawieniem się

W tym prostym przypadku nie zdeklarowaliśmy żadnej przestrzeni nazw, nasza klasa istnieje zatem w przestrzeni globalnej.
Możemy utworzyć jej obiety w dowolnym miejscu w kodzie. A co jeśli teraz stworzymy kolejną klasę o takiej samej nazwie? Stwórzmy plik /other_classes/MyClass.php zawierający identyczną definicję klasy co /classes/MyClass.php i dołączmy do naszego index.php:
[code]
<?php
// index.php
require_once(__DIR__.'/classes/MyClass.php');
require_once(__DIR__.'/other_classes/MyClass.php');

echo MYCONST;
echo 'Function name:' . MyFunction();

$c = new MyClass;
echo 'Method name:' . $c->MyMethodName();
echo 'Class name:' . $c->MyClassName();
echo MyClass::SayHello();

// WYNIK: Fatal error: Cannot redeclare class Users
?>
[/code]
Działanie takie spowoduje błąd, gdyż staramy się zdefiniować dwie takie same klasy w jednej i tej samej przestrzeni nazw (tutaj - w globalnej). Do rozwiązania tego problemu wykorzystamy właśnie oddzielne przestrzenie nazw.

namespace

Przestrzeń nazw definiujemy za pomocą wyrażenia:
[code]namespace NAZWA_PRZESTRZENI;[/code]
Deklaracja taka musi się znaleźć zaraz na początku pliku - należy o tym pamiętać.
Blok kodu jaki wystąpi po deklaracji przestrzeni nazw może należeć jednocześnie jedynie do jednej przestrzeni, jednakże możemy w jednym pliku zawrzeć kilka przestrzeni i bloków kodu:
[code]
<?php
namespace MyApp1;
// blok kodu dla MyApp1

namespace MyApp2;
// blok kodu MyApp2

namespace MyApp3;
// blok kodu dla MyApp3
?>
[/code]
Nie polecam jednak takiego rozwiazania - my będziemy stosować po jednej przestrzeni na jeden plik.

Wyobraźmy sobie, że nasza aplikacja webowa ma nazwę MyApp i jest podzielona na jakieś elementy. Jednym z elementów moze być obsługa kont użytkowników, innym dostęp do bazy danych, jeszcze innym moduł do wyświetlania artykułów.
Kod takich elementów zapewne rozmieścilibyśmy sobie w aplikacji mniej więcej w taki sposób:

  • /classes/Users.php
  • /classes/DB.php
  • /classes/Articles.php
  • itd...

Idąc takim pomysłem, stwórzmy sobie nową klasę, o nazwie np. Users i zapiszmy w pliku /classes/Users.php, ale tym razem dołączmy do niej przestrzeń nazw o nazwie np. MyApp\Users.
Stwórzmy naszą klasę:
[code]
<?php
// /classes/Users.php

namespace MyApp\Users;

class Users {

  public function MyMethodName()
  {
    return __METHOD__;
  }

  public function MyClassName()
  {
    return __CLASS__;
  }

  public static function SayHello()
  {
    return 'Hi, I\'m users class: ' . __CLASS__;
  }
}
?>
[/code]
Na samym początku stworzyliśmy tutaj przestrzeń nazw o nazwie MyApp\Users.
Zauważmy, że na początku podaliśmy MyApp, następnie po backslashu (\) podaliśmy Users.
Taka właśnie jest konwencja nazewnictwa w przestrzeniach nazw, nic nie stoi na przeszkodzie, abyśmy następne przestrzenie nazywali jeszcze bardziej hierarchicznie, jak np.:
MyApp\Users\Actions\AddUsers.

Załączmy teraz utworzoną klasę do naszego index.php i spróbujmy wywołać metodę Users::SayHello():
[code]
<?php
// index.php
require_once(__DIR__.'/classes/Users.php'); // dołączamy klasę Users
echo Users::SayHello(); // WYNIK: Fatal error: Class 'Users' not found!
?>
[/code]
Wywołanie pliku index.php wyświetliło nam błąd informujący o tym, że klasa Users nie istnieje!
Ale zaraz, zaraz - przecież właśnie ja stworzyliśmy i załączyliśmy do pliku index.php. W czym więc problem? Otóż problem w tym, że nasza klasa istnieje, ale w przestrzeni nazw o nazwie MyApp\Users. Nie istnieje natomiast w przestrzeni globalnej naszego kodu.
Zmodyfikujmy teraz lekko nasz index.php i zmienmy jego kod na:
[code]
<?php
// index.php
require_once(__DIR__.'/classes/Users.php'); // dołączamy klasę Users
echo \MyApp\Users\Users::SayHello(); // WYNIK: Hi, I'm users class: MyApp\Users\Users
?>
[/code]
Voila! Zadziałało, mamy dostęp do klasy Users.
Czego więc tutaj naprawdę dokonaliśmy? Otóż poprzedziliśmy nazwę wywoływanej klasy jej przestrzenią nazw.
Zauważmy więc, że nasza klasa Users istnieje TYLKO I WYŁĄCZNIE w swojej przestrzeni i tylko w taki sposób możemy się do niej odwołać.

Wyjaśnienia wymaga:
[code]\MyApp\Users\[/code]
Otóż tym zapisem podaliśmy prefix naszej klasy, czyli jej przestrzeń nazw, dopiero po tym prefixie właściwą nazwę klasy. Zapis ten rozpoczęliśmy jednak od backslasha (\) - co to oznacza? Oznacza to to, że pierwszy backslash zawsze oznacza przestrzeń główną, bazową, tą położoną najwyżej w hierarchi. Jej nazwa jest pusta, więc podajemy ją jedynie za pomocą jednego backslasha na początku.

Wykonajmy teraz podobny manewr co wcześniej i stwórzmy drugą, identyczną klasę o nazwie Users, ale w innej przestrzeni nazw. Stwórzmy np. plik /other_classes/Users.php i wpiszmy do niego to samo co w poprzedniej klasie Users, zmieńmy jednak jego przestrzeń nazw:
[code]
<?php
// /other_classes/Users.php

namespace MyApp\OtherUsers;

class Users {

  public function MyMethodName()
  {
    return __METHOD__;
  }

  public function MyClassName()
  {
    return __CLASS__;
  }

  public static function SayHello()
  {
    return 'Hi, I\'m users class: ' . __CLASS__;
  }
}
?>
[/code]
Załączmy definicję klasy do naszego index.php i zobaczmy co się stanie:
[code]
<?php
// index.php
require_once(__DIR__.'/classes/Users.php'); // dołączamy klasę Users (z przestrzeni MyApp/Users)
require_once(__DIR__.'/other_classes/Users.php'); // dołączamy klasę Users (z przestrzeni MyApp/OtherUsers)
echo \MyApp\Users\Users::SayHello(); // WYNIK: Hi, I'm users class: MyApp\Users\Users
echo '<br />';
echo \MyApp\OtherUsers\Users::SayHello(); // WYNIK: Hi, I'm users class: MyApp\OtherUsers\Users
?>
[/code]
Jak widać - tym razem nie spowodowaliśmy błędu załączając dwie identyczne klasy o takich samych nazwach. Stało się to dlatego, iż obie klasy Users istnieją w oddzielnych przestrzeniach nazw.
Dostęp do każdej z nich natomiast uzyskujemy za pomocą podania ich przestrzeni nazw przed właściwą nazwą.

use

Dostęp do klasy lub funkcji z danej przestrzeni nazw można uzyskać również w formie prostrzej, wykorzystując do tego celu dyrektywę use. Na czym to polega? Otóż dyrektywa use podana w kodzie informuje PHP o tym, z jakiej przestrzeni nazw obecnie chcemy korzystamy. Prześledźmy to na przykładzie, modyfikując nasz plik index.php:
[code]
<?php
// index.php
use MyApp\Users\Users; // informujemy o używaniu przestrzeni \MyApp\Users
require_once(__DIR__.'/classes/Users.php'); // dołączamy klasę Users (z przestrzeni MyApp/Users)
require_once(__DIR__.'/other_classes/Users.php'); // dołączamy klasę Users (z przestrzeni MyApp/OtherUsers)

echo Users::SayHello(); // WYNIK: Hi, I'm users class: MyApp\Users\Users
?>
[/code]
Jak widać powyżej, poinformowaliśmy PHP o tym, że korzystamy obecnie z przestrzeni \MyApp\Users i dzięki temu w odwołaniu do klasy Users nie musimy już podawać jej przestrzeni nazw.
Dyrektywa use zaczyna obowiązywać od miejsca jej użycia w kodzie.
Co za tym idzie, zapis:
[code]
<?php
// index.php
require_once(__DIR__.'/classes/Users.php'); // dołączamy klasę Users (z przestrzeni MyApp/Users)
require_once(__DIR__.'/other_classes/Users.php'); // dołączamy klasę Users (z przestrzeni MyApp/OtherUsers)

echo Users::SayHello();
use MyApp\Users\Users;
?>
[/code]
będzie niepoprawny i spowoduje błąd.

Uwaga: po słowie use nie podajemy backslasha na początku nazwy.
Możemy jednocześnie korzystać z kilku przestrzeni nazw, podając je po przecinku, wg zasady:
[code]use namespace1, namespace2, namespace3.....;[/code]

Aliasy nazw

Istnieje jeszcze jedno ciekawe usprawnienie związane z przestrzeniami nazw, mianowicie aliasy nazw. Wyobraźmy sobie, że stworzyliśmy sobie długą, hierarchiczną przestrzeń nazw:
[code]MyApp\UsersAccounts\Actions\AddUsers[/code]
a w niej dopiero klasę o nazwie Users.
Wywołanie tej klasy w naszym kodzie będzie dość długie, np:
[code]$user = new \MyApp\UsersAccounts\Actions\AddUsers\Users;[/code]
Aby nie pisać tak długich konstrukcji (a przecież mogą być jeszcze dłuższe) wykorzystamy aliasy, czyli chwilowe zastąpienie nazwy naszej przestrzeni za pomocą skróconej nazwy pod jaką będzie ona dostępna w kodzie. Użycie aliasu wygląda następująco:
[code]use NAZWA_PRZESTRZENI as ALIAS[/code]
w naszym przypadku zróbmy więc:
[code]use MyApp\UsersAccounts\Actions\AddUsers as U;[/code]
Od tej chwili do przestrzeni MyApp\UsersAccounts\Actions\AddUsers możemy odwoływać się stosując formę skróconą U. Prześledźmy w praktyce (zakładając, że dołączamy klasę z przestrzeni MyApp\UsersAccounts\Actions\AddUsers):
[code]
<?php
// index.php
use MyApp\UsersAccounts\Actions\AddUsers as U; // informujemy o używaniu przestrzeni MyApp\UsersAccounts\Actions\AddUsers pod aliasem U
require_once(__DIR__.'/classes/Users.php'); // dołączamy klasę Users (z przestrzeni nazw MyApp\Users\Actions\AddUsers)

echo U\Users::SayHello(); // WYNIK: Hi, I'm users class: MyApp\UsersAccounts\Actions\AddUsers\Users
?>
[/code]

Możemy też stworzyć alias bezpośrednio dla klasy (pamiętajmy, że sposób działa tylko dla klas, dla funkcji i zmiennych nie zadziała!):
[code]
<?php
// index.php

use MyApp\UsersAccounts\Actions\AddUsers\Users as Obj; // tworzymy alias do klasy
require_once('classes/Users.php'); // dołączamy klasę Users (z przestrzeni nazw MyApp\UsersAccounts\Actions\AddUsers)

echo Obj::SayHello(); // WYNIK: Hi, I'm users class: MyApp\UsersAccounts\Actions\AddUsers\Users
?>
[/code]

__autoload

Jak zapewne już zauważyliśmy, hierarchiczne nazewnictwo przestrzeni nazw przypomina coś co znamy na codzień - strukturę folderów. Spostrzeżenie jak najbardziej poprawne, gdyż właśnie w ten sposób jest to wykorzystywane. Standardem w obecnych frameworkach jest rozmieszczenie plików z klasami w folderach odpowiadającym ich hierarchicznej strukturze. Przykładowo, klasę Users zdeklarowaną w przestrzeni nazw MyApp\UsersAccounts\Actions\AddUsers zapisujemy' w folderze /classes/MyApp/UsersAccounts/Actions/AddUsers/:

classes
|__MyApp
|  |__UsersAccounts
|  |  |__Actions
|  |     |__AddUsers
|  |        |__Users.php

Jest to bardzo wygodne, proste i efektywne rozwiązanie - pozwala zachować strukturę umiejscowienia naszych plików z klasami odpowiadającą ich nazewnictwu w przestrzeni nazw.
Ponadto jest to pomocne również dla samego programisty, który "z automatu" już wie, gdzie należy szukać definicji danej klasy. Rozwiązanie takie stosuje każda współczesna aplikacja napisana w PHP.

Wykorzystajmy więc teraz funkcję __autoload() do zautomatyzowania sobie procesu dołączania naszych klas. Na początek prześledżmy jak wygląda taki standardowy __autoload() dla zwykłych klas:
[code]
<?php
function __autoload($class_name)
{
    require_once(__DIR__.'/classes/' . $class_name. '.php');
}
?>
[/code]
Usprawnijmy więc jego działanie, tak aby podążał za hierarchią naszej przestrzeni nazw w folderach:
[code]
<?php
function __autoload($class_name)
{
 $path_to_class = __DIR__.'/classes/' . str_replace('\\', DIRECTORY_SEPARATOR, $class_name) . '.php';
  require_once($path_to_class);
}
?>
[/code]
Jak widzimy, wszystkie backslashe są zamieniane na slashe rozdzielające nam ścieżkę do pliku z klasą. I tym sposobem np. wywołanie klasy Users z przestrzeni nazw:
[code]MyApp\UsersAccounts\Actions\AddUsers[/code]
spowoduje, że funkcja __autoload() załaduje nam definicję klasy z pliku:
[code]/classes/MyApp/UsersAccounts/Actions/AddUsers/Users.php[/code]
Jak widać rozwiązanie jest proste i zarazem bardzo efektywne.
Ze swojej strony gorąco polecam stosowanie przestrzeni nazw nawet w mniejszych projektach, aby wyrobić sobie nawyk ich używania, gdyż przy większych projektach korzystanie z przestrzeni nazw jest wręcz konieczne.

Poza tym, w roku 2009 została po raz pierwszy obuplikowana konwencja o nazwie PSR-0, która jasno określa sposoby nazewnictwa przestrzeni nazw i odpowiedniego rozmieszczenia klas w folderach projektu. Konwencja ta ustala pewne reguły, dzięki którym nie występuje kolizja pomiędzy poszczególnymi aplikacjami, szczególnie tyczy to się funkcji __autoload(). Ciekawe rozwiązanie na autoload ma także Composer - polecam się zapoznać.
Więcej o konwencji przeczytać możecie np. tutaj: http://phpedia.pl/wiki/Konwencja_nazewnictwa_PSR-0

No i to tyle, mam nadzieję, że opisałem cały mechanizm prosto i zrozumiale, zachęcam do własnoręcznego eksperymentowania.

13 komentarzy:

  1. Nareszcie kompletny i porządny opis tego problemu. Wielkie dzięki.

    OdpowiedzUsuń
    Odpowiedzi
    1. Cieszę się, że się przydało i pozdrawiam :)

      Usuń
  2. Dzięki, bardzo dobry opis! Wreszcie zrozumiałem

    OdpowiedzUsuń
  3. :) ¡ɯɐıʍɐɹpzod ,ǝuoıusɐɾʎʍ ǝıuɾɐɟ ,ozpɹɐq ıʞǝızp

    OdpowiedzUsuń
  4. Przydało się, opisane jak dla chłopa od łopaty. Zrozumiałe i działa :-)

    OdpowiedzUsuń
  5. ice code bro follow me www.gajabwap.blogspot.in

    OdpowiedzUsuń
  6. Często takie proste wytłumaczenie pozwala bardzo dobrze zrozumieć podstawy programowania i w tym przypadku języka PHP. Wiem, że dla osób pracujących w firmie tj. https://craftware.pl czyli zajmującej się profesjonalnymi aplikacjami, taki poziom jest zapewne podstawowy. Jednak dla osób początkujących z pewnością się przyda i pozwoli wdrożyć się w języki programowania.

    OdpowiedzUsuń
  7. jest super! ale scisnalbym np kod troche

    OdpowiedzUsuń
  8. W 4 bloku kodu (powyżej opisu namespace) powinno być chyba "// WYNIK: Fatal error: Cannot redeclare class MyClass" zamiast "// WYNIK: Fatal error: Cannot redeclare class Users". Tak czy inaczej super wytłumaczone, świetna robota ;)

    OdpowiedzUsuń
  9. Wreście jasno i przejrzyście opisane, dziękuję że CI się chciało :)

    OdpowiedzUsuń
  10. Php Majster: [Php] Namespaces - Przestrzenie Nazw W Php >>>>> Download Now

    >>>>> Download Full

    Php Majster: [Php] Namespaces - Przestrzenie Nazw W Php >>>>> Download LINK

    >>>>> Download Now

    Php Majster: [Php] Namespaces - Przestrzenie Nazw W Php >>>>> Download Full

    >>>>> Download LINK

    OdpowiedzUsuń

Masz sugestię? Znalazłeś błąd? Napisz komentarz! :)

webmaester.pl - profesjonalne projektowanie WWW i webaplikacji
webmaester.pl - profesjonalne projektowanie WWW i webaplikacji