Tomasz Tomczyk - Programista PHP, Kraków - Strona główna > php, programowanie > PHP 5.3 – część pierwsza: przestrzenie nazw (namespace)

PHP 5.3 – część pierwsza: przestrzenie nazw (namespace)

Czerwiec 1st, 2011

Wprawdzie php 5.3 nie jest już nowością to jednak napiszę o paru nowościach znajdujących się w tym wydaniu. Na pierwszy ogień pójdą przestrzenie nazw. Jest to chyba największa nowość wprowadzona w php 5.3. Dzięki ich zastosowaniu możemy się cieszyć bardziej przejrzystym, zhermetyzowanym i „czyszczym” kodem. Niestety mimo że php 5.3 żyje już od dłuższego czasu to jednak z moich doświadczeń wynika, że bardzo mało osób korzysta z przestrzeni nazw.

Warto zauważyć, że w tej chwili przestrzeni nazw możemy używać tylko w połączeniu z klasami, funkcjami i stałymi.

Przedsmak

Przykładowy kod bez użycia „namespaces”

  function Blog_getpost() {

  }
  class Blog_User {

  }
  define('BLOG_CONST_VAR', '');

  Blog_getpost();
  new Blog_User();
  BLOG_CONST_VAR;

Z użyciem „namespaces”

  namespace Blog;

  function getpost() {

  }
  class User {

  }
  const CONST_VAR = '';

  use Blog;
  getpost();
  new User();
  CONST_VAR;

Prawda, że wyglada lepiej :) ?
Od razu widać że pozbywamy się zbędnych prefixów do stałych, funkcji, klas.

Musimy jednak uważać gdzie definiujemy przestrzeń nazw.

  $a = 1;
  namespace ns;

Powyższy kod wygeneruje nam błąd. Problem tkwi w fakcie, że nie użyliśmy słowa kluczowego namespace na samym początku pliku. Jeśli chcemy zdefiniować w jakimś pliku przestrzeń nazw to jej definicja musi się znajdować na początku pliku. Pozostały kod umieszczamy poniżej definicji.

Prawidłowy kod.

  namespace ns;
  $a = 1;

Deklaracja kilku przestrzeni nazw w jednym pliku

Możemy tego dokonać na dwa różne sposoby.

Pierwszy (deklaracja blokowa):

namespace LIB;
        const CONST_ONE = 1;
        function funcOne() {}
        class ClassOne {}
        $b = new ClassOne();

namespace LIB_OUTER;
        const CONST_ONE = 2;
        function funcOne() {}
        class ClassOne {}
        $a = new ClassOne();

        // LIB\ClassOne
        echo get_class($b);
        // LIB_OUTER\ClassTwo
        echo get_class($a);

Drugi rekomendowany sposób (deklaracja klamrowa):

  namespace LIB {
    const CONST_ONE = 1;
    function funcOne() {}
    class ClassOne {}
  }

  namespace LIB_OUTER {
    const CONST_ONE = 1;
    function funcOne() {}
    class ClassOne {}
  }

Istnieje możliwość połączenia kodu umieszczonego przestrzeni globalnej z kodem umieszczonym w zdefiniowanej przestrzeni nazw. W tym przypadku można użyć tylko notacji klamrowej.

  declare(encoding='UTF-8');
  namespace LIB {
    const CONST_ONE = 1;
    function funcOne() {
      echo 'lib';
    }
    class ClassOne {}
  }

  namespace {
    // global
    function funcOne() {
      echo 'global';
    }
    // użycie funkcji z przstrzeni globalnej
    funcOne();
    // użycie funkcji z przestrzenie LIB
    LIB\funcOne();
  }

Cały kod php’a musi być zamknięty pomiędzy klamrami, jedyny wyjątek (patrz linia 1) to użycie declare

Trzeba zaznaczyć, że definiowanie wielu przestrzeni nazw w jednym pliku jest mocno niezalecane i raczej nieczytelne. Głównie przydaje się w momencie gdy chcemy połączyć wiele skryptów php w jeden plik w celu przyśpieszenie działania serwisu.

Podprzestrzenie nazw (sub-namespaces)

Istnieje możliwość definiowania wielu pod poziomów przestrzeni nazw w danej przestrzeni. Może i brzmi to skomplikowanie ale prosty przykład rozjaśni sprawę.

  namespace Project\Lib\Level {
    const CONST_ONE = 1;
    function funcOne() {
      echo 'lib';
    }
    class ClassOne {}
  }

  namespace {
    // utworzenie obiektu ClassOne z przstrzenie Project\Lib\Level w przestrzeni globalnej
    $oClassOne = new Project\Lib\Level\ClassOne();

    // Project\Lib\Level\ClassOne Object ( )
    print_r($oClassOne);
  }

Hierarchia przestrzeni nazw

Przykład pierwszy – prosty.

  namespace foo;
    function strlen($string) {
      // wywołanie funkcji z przestrzeni globalnej
      return \strlen($string) * 2;
    }

  // 8 - nadal jesteśmy w przestrzenie foo, więc wywołujemy funkcje z przestrzeni foo
  echo strlen("test");

  // 8 - alternatywny sposób odwołania się do funkcji z przestrzeni foo
  echo \foo\strlen("test");

  // 4 - wywołanie funkcji z przestrzeni globalnej
  echo \strlen("test");

Przykład drugi.

Zanim przejdziemy do kodu musimy zaznajomić się z paroma ważnymi definicjami z manuala.
Do przestrzeni nazw możemy odwoływać sie przy pomocy „identyfikatorów” (piszę w cudzysłowiu ponieważ brakuje mi dobrego polskiego odpowiednika). Poniżej to co znajduje się w manualu, zostawiam w wersji oryginalnej ponieważ wydaje mi się ona bardziej zrozumiała, niż ewentualnie przetłumaczona na język polski.

Unqualified name
    This is an identifier without a namespace separator, such as Foo

Qualified name
    This is an identifier with a namespace separator, such as Foo\Bar

Fully qualified name
    This is an identifier with a namespace separator that begins with a namespace separator,
    such as \Foo\Bar. namespace\Foo is also a fully qualified name.

Można się w tym wszystkim dopatrzeć pewnej analogii ze sposobami inkludowania plików do skryptu, więc przyjąłem dla potrzeb tego wpisu trochę uproszczone nazewnictwo.
Unqualified name – odwołanie do bieżącej przestrzeni nazw
Qualified name – odwołanie relatywne do przestrzeni nazw względem bieżącej przestrzeni nazw
Fully qualified name – odwołanie absolutne do przestrzeni nazw przy pomocy jej pełnej ścieżki

Pamiętajcie, że to nie jest oficjalne nazewnictwo :)
Poniżej przykłady.

Plik: subnamespace.php

  namespace Project\Lib\subnamespace;

    const CONST_ONE = 1;
    function funcOne() {
      echo 'subnamespace';
    }
    class ClassOne {
      static function staticMethod() {}
    }

Plik: namespace.php

  namespace Project\Lib;
    include 'subnamespace.php';

    const CONST_ONE = 2;
    function funcOne() {
      echo 'subnamespace';
    }
    class ClassOne {
      static function staticMethod() {}
    }

    /* Sposoby odwołania się do danych przestrzeni nazw */

    // poprzez nazwę funkcji/klasy/stałej - w kontekscie aktualnej przrestrzeni nazw

    funcOne();                   // wywołanie funkcji Project\Lib\funcOne
    ClassOne::staticMethod();           // wywołanie statycznej metody staticMethod z klasy Project\Lib\ClassOne
    echo CONST_ONE;               // użycie stałej Project\Lib\CONST_ONE

    // relatywnie względem przstrzeni w której się aktualnie znajdujemy
    subnamespace\funcOne();           // wywołanie funkcji Project\Lib\subnamespace\funcOne
    subnamespace\ClassOne::staticMethod();     // wywołanie statycznej metody staticMethod z klasy Project\Lib\subnamespace\ClassOne,
    echo subnamespace\CONST_ONE;         // użycie stałej Project\Lib\subnamespace\CONST_ONE

    // absolutne względem globalnej przestrzeni nazw
    \Project\Lib\funcOne();           // wywołanie funkcji Project\Lib\funcOne
    \Project\Lib\ClassOne::staticMethod();     // wywołanie statycznej metody staticMethod z klasy Project\Lib\ClassOne
    echo \Project\Lib\CONST_ONE;         // użycie stałej Project\Lib\CONST_ONE

    // analogicznie
    \Project\Lib\subnamespace\funcOne();           // wywołanie funkcji Project\Lib\subnamespace\funcOne
    \Project\Lib\subnamespace\ClassOne::staticMethod();   // wywołanie statycznej metody staticMethod z klasy Project\Lib\subnamespace\ClassOne,
    \Project\Lib\echo subnamespace\CONST_ONE;         // użycie stałej Project\Lib\subnamespace\CONST_ONE

Czyli podsumowując najpierw dana funkcja/klasa/stała jest wyszukiwana w przestrzeni do której się w danej chwili odwołujemy. Jeśli nie uda się jej znaleźć to przeszukana zostaje przestrzeń globalna.

Nadpisanie działania natywnej funkcji php raczej nie jest dobrym pomysłem, z czasem może to prowadzić do komplikacji. Funkcję strlen zastosowałem tylko w celach przykładowych.

Przestrzenie nazw i autoloading

Musimy uważać w przypadku gdy ładowanie klas chcemy zrealizować za pomocą funkcji __autoload(), ponieważ w naszym kodzie mogą istnieć klasy o tych samych nazwach, zdefiniowane w różnych przestrzeniach nazw. W rozwiązaniu tego problemu pomaga nam fakt, że do funkcji __autoload() zostaje przekazana pełna informacja o klasie, czyli jej nazwa razem z informacją o przestrzeni w której klasa istnieje.

  function __autoload($var) {
    var_dump($var);       // LIB/ClassOne - dla poniższego przypadku
  }

  /*
  Kod znajdujący się w jakimś innym pliku

  namespace LIB;
    class ClassOne {}
    new ClassOne();
  */

Warto wiedzieć również o poniższych faktach:
__autoload zostanie wywołany tylko wtedy gdy klasa nie istnieje w zadanej przestrzeni nazw oraz w przestrzeni globalnej (global scope)
__autoload zadeklarowane wewnątrz jakiejkolwiek przestrzeni nazw nie zostanie wywołany

Importowanie przestrzeni i aliasy

Aliasy można przypisywać do:

  • nazwy klasy (class name)
  • nazwy interfejsu (interface name)
  • nazwy przestrzeni (namespace name)

Nie są obsługiwane aliasy do nazw funkcji oraz stałych

Do tworzenia aliasów używamy słowa kluczowego use

Przykład.
Zakładamy że gdzieś już mamy podefiniowane przestrzenie nazw:
- Project\Lib\ns1
w przestrzeni istnieje klasa ClassOne
- Project\Lib\ns2

  namespace nsNew;

  use Project\Lib\ns1 as ns1Alias;        // zaimportowanie przestrzeni nazw oraz utworzenie aliasu o nazwie ns1Alias
  use Project\Lib\ns2;              // działa tak samo jak: use Project\Lib\ns2 as ns2

  use Project\Lib\ns1\ClassOne as Ns1_ClassOne;  // utworzenie aliasu do klasy ClassOne znajdującej sie w przestrzeni Project\Lib\ns1
  use ArrayObject;                // zaimportowanie globalnej klasy, działa tak samo jak: use ArrayObject as ArrayObject

  $obj = new Ns1_ClassOne();            // utworzenie obiektu klasy Project\Lib\ns1\ClassOne
  $oArrayObject = new ArrayObject(array(1));    // utworzenie obiektu globalnej klasy ArrayObject, jesli wczesniej nie użylibyśmy 'use ArrayObject'
                          // to utworzylibyśmy obiekt klasy nsNew\ArrayObject

Przy importowaniu przestrzeni musimy używać jej pełnej (absolutnej) nazwy. Użycie nazwy poprzez relatywne odwołanie nie zadziała.

Możemy również w jednym poleceniu importować kilka przestrzeni nazw

    use Project\Lib\ns1\ClassOne as Ns1_ClassOne, Project\Lib\ns2;

  $obj = new Ns1_ClassOne();    // utworzenie obiektu klasy Project\Lib\ns1\ClassOne
  ns2\subns\funcOne();       // wywołanie funkcji Project\Lib\ns2\subns\funcOne

Warto zaznaczyć, że importowanie odbywa się w trakcie kompilacji, tak więc nie wpływa to na dynamiczne ładowanie klas, funkcji, stałych.

Dopuszczalne jest użycie słowa ‘use’ w przestrzeni globalnej jak również wewnątrz innych przestrzeni, ale użycie go wewnątrz przestrzeni nazw danej klasy czy funkcji jest już zabronione.

  namespace DB;

  class MyDb {
    use DB\MySQL;    // takie użycie jest zabronione
    ...
  }

  function executeQuery() {
    use DB\MySQL;    // takie również
    ...
  }

Ciekawostki

Pobranie nazwy aktualnej przestrzeni: __NAMESPACE__


echo __NAMESPACE__;     // zwróci pustego stringa

namespace Project\Lib;
echo __NAMESPACE__;     // Project\Lib

Przestrzenie nazw nie przeszkadzają w dynamicznym tworzeniu obiektów.

  $className = 'ClassOne';
  $oClassName = new $className;
  echo get_class($oClassName);   // 'ClassOne'

  $className = 'Lib\ClassOne';
  $oClassName = new $className;
  echo get_class($oClassName);   // 'Lib\ClassOne'

Reguły

Analizując powyższy tekst możemy zauważyć, że z czasem można się pogubić w mnogości odwołań do funkcji/klas/stałych w zależności od tego gdzie się aktualnie znajdujemy. Poniższe reguły powinny pomóc nam zrozumiec jak działają wywołania przestrzeni nazw.

Reguła 1
Wywołanie absolutnej (pełnej) scieżki funkcji, klasy lub stałej jest wiązane w czasie kompilacji.
Przykładowo wywołanie „new \A\B” zostanie powiązane z klasą „A\B”

Reguła 2
Wszystkie pozostałe nazwy przestrzeni są zamieniane na absolutne nazwy przestrzeni podczas komplacji z uwzględnieniem regół importów
Przykładowo, jeśli przestrzeń A\B\C jest importowana pod nazwą C, to wywołanie C\D\e() jest zamieniane na A\B\C\D\e()

Reguła 3
Wewnątrz przestrzeni, wszystkie relatywne nazy przestrzeni, są wiązane zgodnie z regułami importu, ale dodatkowo zostają poprzedzone nazwą bierzącej przestrzni
Przykładowo, jeśli wywołujemy C\D\e(), które jest wykonywane w przestrzni A\B, to zostanie to powiązane z A\B\C\D\e()

Reguła 4
Wszystkie nazwy przestrzeni nie zawierające separatora przestrzeni „\” podczas kompilacji są wiązane z bierzącą regułą importu (jest to przypadek gdy tworzymy krótki alias dla przestrzeni)
Przykładowo, jeśli przestrzeń A\B\C jest zaimportowana pod nazwą C, new C() jest wiązane do postaci A\B\C()

Reguła 5
Wewnątrz przestrzeni (przykładowo A\B), wywołujemy funkcje (unqualified functions) to jest ona wiązana w czasie rzeczywistym (run-time).
Sposoby wiązania.
1. szukana jest funkcja w bierzącej przestrzeni A\B\foo()
2. próbuje znaleźć i wywołać globalną funkcje foo()

Reguła 6
Wewnątrz przestrzeni (przykładowo A\B), wywołanie klasy poprzez nazwę (unqualified class) lub relatywną nazwę (qualified name) jest wiązane w czasie rzeczywisnym.
Poniżej mamy przykład jak wywołanie klasy new C() lub new D\E() jest wiązane.
Dla new C():
1. wyszukujemy klasy w bierzącej przestrzeni nazw A\B\C
2. prubujemy załadować klase A\B\C
Dla wywołania D\E():
1. wyszukujemy klasy poprzedzonej nazwą bierzącej przestrzeni: A\B\D\E
2. prubujemy załadować klasę A\B\D\E

Reguła 7
Aby odwołać sie do klasy znajdujacej się w przestrzeni globalnej np C, musimy użyć pełnej ścieżki czyli new \C()

Poniższa lista kombinacji. Na pewno nie wysyca ona wszystkich możliwości, ale myślę że będzie pomocna.


namespace A;
    use B\D, C\E as F;

    /**** WYWOŁANIE FUNKCJI ****/

// najpierw próbuje wywołać funkcje "foo" zdefiniowaną w przestrzeni "A"
//jeśli jęj nie znajdzie to próbuje wywołac funkcje "foo" z przestrzenie globalnej
    foo();      

// wywołanie funkcji "foo" z przestrzenie globalnej
    \foo();     

// wywołanie funkcji "foo" zdefinowanej w przestrzeni "A\my"
    my\foo();   

// najpier próbuje wywołać "F" zdefinowaną w przestrzeni "A"
// a następnie wywołać funkcje "F" z globalnej przestrzeni
    F();        

    /**** KLASY ****/

// utworzenie obiektu klasy "B" zdefiniowanej w przestrzeni "A",
// jeśli klasa nie została znaleziona próbuje załadować klasę "A\B"
    new B();  

// dzięki importowi tworzy obiekt klasy "D" zdefiniowany w przestrzeni "B",
// jeśli klasa nie została znaleziona próbuje załadować klasę "B\D"
    new D();  

// dzięki importowi tworzy obiekt klasy "E" zdefiniowany w przestrzeni "C",
// jeśli klasa nie została znaleziona próbuje załadować klasę "C\E"
    new F();    

// tworzy obiekt klasy "B" zdefiniowany w globalnej przestrzeni,
// jeśli klasa nie została znaleziona próbuje załadować klasę "B"
    new \B();   

// tworzy obiekt klasy "D" zdefiniowany w globalnej przestrzeni,
// jeśli klasa nie została znaleziona próbuje załadować klasę "D"
    new \D();   

// tworzy obiekt klasy "F" zdefiniowany w globalnej przestrzeni,
// jeśli klasa nie została znaleziona próbuje załadować klasę "F"
    new \F();   

    /**** METODY STATYCZNE, PRZESTRZENIE NAZW, FUNKCJE Z INNYCH PRZESTRZENI NAZW ****/

// wywołanie funkcji "foo" z przestrzeni "A\B"
    B\foo();    

// wywołanie motody "foo" klasy "B" zdefiniowanej w przestrzeni "A",
// jeśli klasa "A\B" nie została znaleziona próbuje załadować klasę "A\B"
    B::foo();   

// dzięki importowi wywołuje metodę "foo" klasy "D" zdefiniowanej w przestrzeni "B",
// jeśli klasa "B\D" nie została znaleziona próbuje załadować klasę "B\D"
    D::foo();   

 // wywołąnie funkcji "foo" z przestrzeni "B"
    \B\foo();  

// wywołąnie metody calls "foo" klasy "B" z globalnej przestrzeni,
// jeśli klasa "B" nie została znaleziona próbuje załadować klasę "B"
    \B::foo();  

    /**** METODY STATYCZNIE, PRZESTRZENIE, FUNKCJE Z BIERZĄCEJ PRZESTRZENI ****/

// wywołanie metody "foo" klasy "B" z przestrzeni "A\A",
// jeśli klasa "A\A\B" nie została znaleziona próbuje załadować klasę "A\A\B"
    A\B::foo(); 

// wywołanie metody "foo" klasy "B" z przestrzeni "A",
// jeśli klasa "A\B" nie została znaleziona próbuje załadować klasę "A\B"
    \A\B::foo();

I to by było wszystko z mojej strony wszystko chodzi o namespace w php 5.3 :)
Miłej lektury.

  1. kubaw
    Czerwiec 1st, 2011 at 12:00 | #1

    Brawo :) A juz myslalem ze sie nigdy nei doczekamy :)

  2. Czerwiec 1st, 2011 at 13:10 | #2

    Świetny artykuł. Rzeczowy i praktyczny.
    No i oczywiście proszę o więcej :)

  3. kubaw
    Sierpień 5th, 2011 at 16:26 | #3

    Kiedy możemy się spodziewać kolejnych części? Może o nowościach z php 5.4?

  4. Sierpień 11th, 2011 at 08:05 | #4

    Hmm … jak napiszę, że już niedługo to nawet mi wydaje się to śmieszne ;)
    Tematem będzie zapewne ciąg dalszy PHP 5.3.
    Jeśli chodzi o PHP 5.4 to tak naprawdę jest jeszcze dużo niewiadomych co finalnie znajdzie się w tym wydaniu. Część propozycji wydaje się interesująca, część już można samemu przetestować jak chociażby wbudowany serwer. Pomysł tematu fajny, dodam do TODO. Może wyrobię się przed wydaniem PHP 5.8 :P

  5. astosenue
    Grudzień 8th, 2011 at 17:35 | #5

    Dzieki za ciekawe informacje

Komentarze są zamknięte