Mirin webspace

Nejbohatší život má ten, kdo žije s minimem nároků

24. 11. 2007 - Komentáře (5) PHP

Jmenné prostory (namespaces) v PHP 5.3

Vývojáři PHP se po dlouhých diskuzích nakonec rozhodli do pětkové verze PHP dát poměrně důležité věci, které se původně plánovaly až do PHP 6. Vzhledem k tomu, že jde o poměrně zásadní změny a verze PHP 5.3, která je bude obsahovat, bude vypuštěna někdy začátkem roku 2008, je pomalu na čase se s nimy seznámit. Mezi největší změny patří podpora jmenných prostorů (namespaces), late static binding a nové mysql rozhraní mysqlnd. V tomto a několika dalších článcích se pokusím popsat v čem tyto změny spočívají.

Z dnešního pohledu je článek zastaralý, a nezohledňuje implementaci jmenných prostorů v aktuální verzi PHP, byl vydán jako přehled v době, kdy se implementace jmenných prostorů teprve připravovala.

Namespaces - základy

Po podpoře jmenných prostorů (dále jen namespaces, je to kratší 8-)) se v PHP komunitě dlouho volalo. Hlavní motivací k jejich zavedení je separace symbolů a tím i jednodušší použití a vytváření knihoven, zejména knihoven objektového kódu. V současnosti se to obchází různými berličkami v podobě podtržítek apod. (viz. třeba v Zend Frameworku) vznikají pak třídy jako Zend_Search_Lucene_Analysis_Analyzer _Common_Text_CaseInsensitive. Jejich používáním se pak kód dost znepřehledňuje. Použití namespaces nám umožní psát přehledný objektový kód bez obav před kolizemi v názvech. Jedná se tedy o podobnou motivaci jako v C++, Javě a jiných jazycích, které namespaces používají.

Základní vlastnost namespaces je tedy v tom, že rozdílné namespaces mohou obsahovat stejné názvy tříd, metod, funkcí a konstant. Definice namespace je jednoduchá a měla by se uvádět na začátku souboru.


<?php
/** classes/my/foo/MyClass.php */

namespace my::foo;

//definice tridy
class MyClass {}

//Definice funkci a konstant uvnitr namespace mimo tridu je take mozna
function myFunc() { }
const MY_CONST = 'foo';
?>

Následující kód ukazuje některé možnosti použití věcí definovaných v předchozím namespace.


<?php
/** test.php */
include('classes/my/foo/MyClass.php');

//Pouziti tridy pomoci uplne definice.
$foo = new my::foo::MyClass();

//pomoci klicoveho slova use imporuje definice z namespace my::foo.
use my::foo;
//to nam umozni pouziti kratsich zapisu - misto my::foo jen foo.
$foo = new foo::MyClass();

//muzeme si importovat jen nazev jedne tridy a pak jej pouzivat samostatne.
use my::foo::MyClass;
$foo = new MyClass;

//Muzeme vytvaret aliasy.
use my::foo as MyFoo;
use my::foo::MyClass as MyFooClass;
$foo = new MyFoo::MyClass();
$foo = new MyFooClass();

//Nasledujici vyrazy jsou si ekvivalentni
use my::foo;
use my::foo as foo;

//pristupovat k funcim a konstantam pak muzeme obema zpusoby
my::foo::myFunc();
myFoo::myFunc();
my::foo::MY_CONST;
myFoo::MY_CONST;
?>

Jen namespaces a třídy mohou být importovány pomocí use. Není možné importovat třeba konstanty nebo funkce. Use má platnost od své deklarace až do konce souboru a můžeme ho použít kdekoliv v globálním namespace. Můžeme použít stejný namespace ve více souborech, definice namespace by se však měla v jednom souboru nacházet jen jednou. Je možné, že se na výše popsaném chování ještě něco změní, např. místo klíčového slova use se mělo původně používat import, ke změně došlo nedávno.

Globální namespace - ::

Použitím :: před symbolem říkáme, že se jedná o globálně definovaný symbol nezávislý od aktuálního namespace importu. Používat se bude zejména při práci uvnitř namespace.

Přechod na použití namespaces - změny ve stávajícím kódu.

Pokud budete chtít začít namespaces používat ve svých projektech a knihovnách, je třeba si dát pozor na některé věci, které použití namespaces přináší. Jde zejména o autoloading, pravidla pro dohledání symbolů uvnitř namespace a pojmenování tříd uvniř namespace.

Jména tříd nesmí kolidovat s globálními klíčovými slovy PHP, pokud tedy dříve bylo


/** classes/my/form/element/static.php */
class MyFormElementStatic {}

nově s namespaces není možné napsat


/** classes/my/form/element/static.php */
namespace my::form::element;
class Static {}

Static je v PHP klíčové slovo rezervované pro interní použití.

Autoloading

V PHP 5.3 bude funkce __autoload dostávat jméno třídy plně kvalifikované včetně namespace. Budete si muset tedy své class loadery trochu modifikovat.


/** test.php */
function __autoload($className)
{
    require 'classes/'.str_replace('::', DIRECTORY_SEPARATOR, $className).'.php';
}
$foo = new my::foo::MyClass();

Stejně tak pokud využíváte pro autoloading SPL


/** classes/my/core/classloader.php */
namespace my::core;

function classLoader($className)
{
    require 'classes/'.str_replace('::', DIRECTORY_SEPARATOR, $className).'.php';
}

spl_autoload_register('my::core::classLoader');


/** test.php */
require 'classes/my/core/classLoader.php';
$foo = new my::foo::MyClass();

Funkce get_class(), get_parent_class() budou vracet plně kvalifikovaná jména včetně namespace.

Reflection API

Podpora namespaces se přidá i do reflection API. Zatím to vypadá asi takto:

  • nová třída ReflectionNamespace s metodami getName(), getClasses(), getFunctions(), getFiles()
  • třídy ReflectionClass and ReflectionFunction se rozšíří o metodu getNamespace()

Přidá se nová konstanta __NAMESPACE__, která bude obsahovat jméno aktuálního namespace.

Pro rozhodování, jak se který symbol odkud použije, budou platit docela složitá pravidla popsaná zatím v PHP namespaces README. Nejdůležitější je si pamatovat, že PHP se při rozhodování, který symbol použije, podívá nejdříve do aktuálního namespace.
Příklad:


namespace my::foo;
...
bar();
...
::bar();

Při volání bar(), se nejdříve bude hledat funkce bar() v namespace my::foo. Pak se PHP pokusí zavolat interní (POZOR nikoli definovanou někde globálně ve skriptu) funkci bar(). To znamená, že tímto způsobem nelze volat globálně definované funkce, to lze pak pomocí ::bar(), kde se PHP pokusí zavolat globálně definovanou funkci bar(). A aby toho nebylo málo, tak pro:


namespace my::foo;

core::bar();

::core::bar();

Při core::bar(), se nejdříve zavolá v funkce namespace my::foo::core(), pokud ta neexistuje, PHP se pokusí zavolat metodu bar() interní PHP třídy core. Při ::core::bar(), se PHP pokusí zavolat funkci bar() z namespace core, pokud ta neexistuje tak metodu bar() globálně definované třídy core.

Podpora namespaces je určitě dobrým krokem, přinese standardní prostředek pro psaní a práci s kódem objektových knihoven. Otázkou je, jak rychle a jestli vůbec dojde k adopci namespaces ve stávajících frameworcích, zejména Zend Frameworku, uvidíme. Osobně jsem docela pesimista, jsou ale už i výjimky, projekt Phing, o kterém jsem se zmínil nedávno v článku o deploymentu PHP aplikací už na namespaces přešel. Stejně tak nová verze balíčkovacího systému PEAR bude namespaces používat.


Komentáře (5)

  1. Jakub Podhorský - 25. 11. 2007 11:10

    v ZF se podle mě namespace určitě nevyskytnou kvůli zpětný kompatibilitě

    osobně se mi ale moc namespaces, tak jak jsou teď moc nelíbí

    už jenom kvůli tomu, že nemůžu definovat v jednom souboru víc namespaců(nechápu proč není rozsah namespace v blocích pomocí {})

    navíc když udělám:
    namespace my::foo;
    class MyClass {}

    a pak v dalším souboru:
    use my::foo;
    $foo = new foo::MyClass();

    nechápu proč musím znovu spát foo když už ho mám jednou v use

    sice je tohle lepší jak drátem do oka ale nevidím v tom zatím moc výhod :-/

    podle mě se měli třeba inspirovat C#

  2. koubel - 25. 11. 2007 16:09

    Taky myslím, že v ZF to v nejbližší době nebude, při změně major verze (ZF 2.0) by se ale přechod na namespace mohl uskutečnit.

    S tím
    use my::foo;
    $foo = new foo::MyClass();

    je to pravda, asi to má nějakou souvislost s tím rozhodováním, co se odkud použije.

  3. error414 - 28. 11. 2007 10:41

    se vsadim ze to zmasti.

  4. whysper - 10. 6. 2010 14:06

    Ahoj nic proti ale namespace se dá také definovat s {} takže třeba takhle:
    namespace Namespace1
    {
    class Trida 1
    {}.........
    }
    namespace Namespace 2
    {
    ........
    }

    Ale více namespaců jde používat také bez {} takže se napíše:
    namespace Namespace1;
    class Trida1
    {
    }
    .
    .
    .
    .
    namespace Namespace2;
    .
    .
    .
    .
    .
    namespace Namespace3;
    .
    .
    .
    .
    Takže nevím kde jste to vzali, že nelze použít více namespaces.
    No a využití namespace spočívá hlavně v tom, že když používáte nějaké frameworky tak mohou mít stejné třídy, jaké používáte vy ve svém projektu takže vy si dáte ke svému projektu nějaké namespace (já teď dělám třeba "wapi") a už se vám nebudou třídy a metody kolidovat s třídami z onoho frameworku protože je budete volat pomocí wapi::Trida1

  5. koubel - 25. 7. 2010 15:39

    [6] - jasně, tenhle článek je už zastaralý, je z roku 2007, kdy se teprve řešilo, jak na to, někam to tam napíšu

Komentáře jsou uzavřeny.