Scriptorama.nl

Header image showing a keyboard, mouse, laptop and books on design patterns

Namespaces in PHP-6 en PHP-5.3

Goed nieuws voor degene die bij onze vorige posting nog hoopte dat PHP6 uberhaupt namespace ondersteuning zou krijgen. Die kogel is inmiddels door de kerk. Interessanter is dat inmiddels ook is besloten om de volgende versie in de PHP5 serie: PHP 5.3 ook ondersteuning voor Namespaces te geven.

Nadat duidelijk geworden wat ongeveer de verdere plannen zijn voor PHP 5.3, is vorige week een nieuwe ontwikkel branche aangemaakt voor PHP 5.3 en daarop is afgelopen vrijdag ondersteuning voor namespaces aan toegevoegd. In dit artikeltje kijken we wat namespaces zijn en hoe we ze kunnen gebruiken.

Update 1: De implementatie gebruikt nu 'use' in plaats van 'import'. Aangepast.
Update 2: '::' is niet langer de namespace separator, dat is nu '\'. Aangepast.

Waarom namespaces?

Nu PHP5 steeds meer gebruikt wordt, beginnen er allerlei frameworks te ontstaan. Denk bijvoorbeeld aan Zend Framework, Solar, Mojavi of CakePHP. Mensen pakken vaak van verschillende frameworks, maar ook kleinere libraries, een stukje en gebruiken dat in hun website. Dit levert een probleem op. Want het is mogelijk dat bepaalde klassenaam in 2 gebruikte projecten voorkomt. Dit levert een foutmelding op want je kan een klassenaam maar 1x gebruiken.

Bij de release van PHP 5.1 werd PHP zelf geplaagd door dit probleem. In PHP 5.1 werd de nieuwe Date klasse geintroduceerd. Deze klasse bood allerlei nieuwe functionaliteit aan, maar de ontwikkelaars van PHP hadden over het hoofd gezien dat de PEAR library zelf ook een klasse met de naam Date meeleverde. Deze twee klassen hadden echter een compleet andere API en functionaliteit wat natuurlijk de nodige problemen opleverde voor de mensen die de PEAR Date klasse gebruikten.

Fatal error: Cannot redeclare class Date in /home/mathieu/public_html/test.php on line 3

Uiteindelijk is de Date klasse er in PHP 5.1.1 tijdelijk uit PHP gehaald en later in PHP 5.2 weer toegevoegd met de naam DateTime. Over deze klasse hebben we op Scriptorama ook al het nodige geschreven: de DateTime klass in PHP 5.2.

Om naam-botsingen te voorkomen hebben verschillende frameworks (zoals bijvoorbeeld Zend Framework en PEAR) inmiddels gekozen voor "poor man's namespacing" wat zoveel inhoudt dat je je klassen een naam geeft die duidelijk aangeeft bij welk project en welk onderdeel van het project een klasse hoort.

PHP:
  1. class Zend_Search_Lucene_Analysis_Analyzer_Common_TextNum_CaseInsensitive extends Zend_Search_Lucene_Analysis_Analyzer_Common_TextNum
  2. {
  3.     public function __construct()
  4.     {
  5.         $this->addFilter(new Zend_Search_Lucene_Analysis_TokenFilter_LowerCase());
  6.     }
  7. }

Het vervelende daaraan is dat dat, zoals je hierboven ziet, in sommige gevallen erg lange klasse namen kan veroorzaken wat de leesbaarheid van je code weer tegen gaat.

Om ontwikkelaars bij deze problemen te ondersteunen zijn namespaces geintroduceerd.

Namespaces in PHP

Namespaces zijn een manier om onderdelen van je project te kunnen groeperen. De namespace zelf is in feite een extra laag om je klassen en functies heen. Binnen deze laag zijn alle namen weer uniek. Op het moment dat je een namespace definieert heb je dus in feite weer schone lei qua namen. Zo zou ik zowel binnen de namespace Scriptorama::SDK als binnen de namespace Scriptorama::Logging een klasse Database kunnen definiƫren.

Om een namespace te definieren in PHP 5.3 of PHP 6.0 gebruik je het namespace keyword helemaal boven aan je bestand. Het is verplicht om deze als eerste statement in je PHP file te hebben. Hieronder definieer je vervolgens de rest van het bestand, zoals je dat normaal ook al deed:

Scriptorama/SDK/Database.php:

PHP:
  1. <?php
  2.  
  3. namespace Scriptorama\SDK;
  4.  
  5. class Database
  6. {
  7.   function query()
  8.   {
  9.      echo "[Scriptorama-SDK] De aanroeper wilt graag een query uitvoeren.";
  10.   }
  11. }
  12.  
  13. ?>

Scriptorama/Logging/Database.php:

PHP:
  1. <?php
  2.  
  3. namespace Scriptorama\Logging;
  4.  
  5. class Database
  6. {
  7.   function log()
  8.   {
  9.      echo "[Scriptorama Logging] Deze klasse heeft niks met Scriptorama::SDK::Database te maken." . PHP_EOL;
  10.      echo "Ze hebben zelfs een andere interface.";
  11.   }
  12. }
  13.  
  14. ?>

Een namespace is niet beperkt tot 1 bestand. Je kunt dezelfde namespace definieren in verschillende bestanden. De klasse(n) uit de andere bestanden worden dan toegevoegd aan de al bestaande namespace:

Scriptorama/Logging/FileLogger.php:

PHP:
  1. <?php
  2.  
  3. namespace Scriptorama\Logging;
  4.  
  5. class FileLogger
  6. {
  7.     function log($x) {
  8.         echo "[FileLogger] $x";
  9.     }
  10. }
  11. ?>

Om deze klasse vervolgens aan te spreken kun je verschillende dingen doen. Je kunt de klasse volledig met zijn Namespace naam aanspreken:

PHP:
  1. <?php
  2.  
  3. require 'Scriptorama/SDK/Database.php';
  4.  
  5. $dbImpl = new Scriptorama\SDK\Database();
  6. $dbImpl->query("Testing, Testing, 1.. 2.. 3..");
  7.  
  8. ?>

Maar dan ben je natuurlijk niet veel beter af dan met poor man's namespacing. Je kunt daarom ook een willekeurig deel van de namespace 'importeren' in de huidige scope. Dit gebeurt met het use keyword.

PHP:
  1. <?php
  2.  
  3. require 'Scriptorama/Logging/Database.php';
  4. require 'Scriptorama/SDK/Database.php';
  5. require 'Scriptorama/Logging/FileLogger.php';
  6.  
  7. use Scriptorama\SDK as SOR_SDK;
  8.  
  9. use Scriptorama\Logging as SOR_LOG;
  10. use Scriptorama\Logging::Database LogDB;
  11.  
  12. $a = new SOR_SDK\Database();
  13. $b = new SOR_LOG\Database();
  14.  
  15. $c = new SOR_LOG\FileLogger();
  16. $d = new LogDB();
  17.  
  18. var_dump($a, $b, $c, $d);
  19.  
  20. ?>

Zo kun je dus een heel diepe namespace toewijzen aan een kortere naam wat makkelijker te gebruiken is. Zoals je misschien wel door hebt, is het niet daadwerkelijk importeren, zoals bijvoorbeeld in C# gebeurt, maar is het meer een soort aliasing. Overal waar je de klasse of interface wilt aanspreken (bij bijvoorbeeld het implements of extends keyword kun je deze aliasen gebruiken. In de rest van PHP blijft in de klassenaam altijd volledig.

De var_dump() aanroep in het vorige voorbeeld geeft dan ook het volgende resultaat:

CODE:
  1. $ php -c. index.php
  2. object(Scriptorama\SDK\Database)#1 (0) {
  3. }
  4. object(Scriptorama\Logging\Database)#2 (0) {
  5. }
  6. object(Scriptorama\Logging\FileLogger)#3 (0) {
  7. }
  8. object(Scriptorama\Logging\Database)#4 (0) {
  9. }

Namespace gotcha's

Wanneer je over gaat naar PHP 5.3 of PHP 6.0 en namespaces gaat gebruiken, let dan op de volgende dingen:

1. Let op dat functies ook deel uitmaken van een namespace

Hoewel we in het voorbeeld hierboven eigenlijk alleen gebruik maakten van classes is het belangrijk dat je je realiseert dat ook functies deel uit maken van een namespace. Wanneer je dus een functie definieert in een bepaalde namespace, zul je ook de namespace of de alias naar de namespace moeten gebruiken om hem aan te kunnen roepen:

test.php

PHP:
  1. <?php
  2.  
  3. namespace Scriptorama\Functions;
  4.  
  5. function BlogArticleGenerator()
  6. {
  7.     echo "You wish :)";
  8. }
  9.  
  10. ?>

test2.php:

PHP:
  1. <?php
  2.  
  3. require 'test.php';
  4.  
  5. BlogArticleGenerator(); // Geeft E_FATAL error: Undefined function BlogArticleGenerator().
  6.  
  7. use Scriptorama\Functions as SFunc;
  8.  
  9. SFunc\BlogArticleGenerator(); // De juiste aanroep
  10.  
  11. ?>

2. __autoload() moet mogelijk aangepast worden

Als je de __autoload() functionaliteit in PHP5 gebruikt in combinatie met namespaces op Windows, let dan op dat het Windows filesystem geen dubbele punten in de naam toestaat. Je zult deze dus moeten herschrijven:

PHP:
  1. function __autoload($className)
  2. {
  3.     $className = str_replace('\\', DIRECTORY_SEPARATOR, $className) . '.php';
  4.     require $className;
  5. }
  6.  
  7. $db = new Scriptorama\SDK\Database();

Let op: Gotcha #1 is ook van toepassing op de functie __autoload(). Let op dat als je __autoload() gebruikt, dat je deze in de globale scope definieert en dus niet in een namespace, want dan vindt PHP de __autoload functie niet.

Wil je dit toch graag kwijt in een namespace, kun je de autoload functionaliteit uit de SPL gebruiken, welke sowieso aan te raden is, aangezien deze veel flexibeler is dan het standaard mechanisme.

PHP:
  1. <?php
  2.  
  3. namespace Scriptorama\Utils;
  4.  
  5. function AutoLoader($className)
  6. {
  7.     $className = str_replace('\\', DIRECTORY_SEPARATOR, $className) . '.php';
  8.     require $className;
  9. }
  10.  
  11. ?>

PHP:
  1. <?php
  2.  
  3. require 'Scriptorama/Utils/Autoloader.php';
  4.  
  5. spl_autoload_register('Scriptorama\\Utils\\Autoloader');
  6.  
  7. $x = new Scriptorama\SDK\Database();
  8.  
  9. ?>

Conclusie

Namespaces bieden je de mogelijkheid om je code te organizeren en om flexibeler om te gaan met klasse namen.

PHP 5.3 en PHP 6.0 zijn voorlopig nog niet final (PHP 5.3 staat gepland voor het eerste kwartaal van 2008) dus het is mogelijk dat er nog het een en ander veranderd. Als je zelf al wilt spelen met Namespaces kun je je wenden tot snaps.php.net waar je zowel de volledige source code als Windows builds van PHP 5.3 en PHP 6.0 kunt vinden.

Reageer ook!

\o/

Goed nieuws dat het al in 5.3 aan de orde komt :)

Dank voor het overzicht Mathieu.
Misschien overbodig, maar toch: het lijkt erop dat late static binding ook beschikbaar wordt in PHP 5.3. Sinds vorige week is er een patch. Quirky is het blijkbaar wel: http://www.ds-o.com/index.php?url=archives/65-Late-static-binding....sorta.html

Yep, ook late static binding in 5.3: http://schlueters.de/blog/archives/59-PHP-5.3-update.html

Naja, ik vind het dom, ze kunnen het beter bij php 5 houden, ze verwijderen dingen wat vaak word gebruikt, er word wel een paar veiligheids dingen veranderd, maar waarom? willen ze php voor kleine 12 jarige kinderen maken?

als je een beetje php kent dan kan je het zo verhelpen maar ik vind de nieuwe update niks, waneer dit gebeurd gaat 10000000den sites offline

Welke veelgebruikte dingen worden dan weggehaald? Er verdwijnen voornamelijk enkel smerige legacy features welke al jarenlang afgeraden worden (register globals, magic_quotes_gpc etc.)

Namespaces zorgen er niet voor dat je site 'offline gaat'. Sterker nog, zelfs zonder onderhoud van je eigen code is de kans alleen maar groter dat alles blijft werken: Libraries en handige scripts welke je her en der include gebruiken wellicht wel namespaces en botsen zo tenminste nooit meer met je eigen spul. :)

Overigens heb je op een punt deels gelijk: als de overgang net zo traag gaat als de overgang van 4 naar 5.2, kan je het voorlopig nog wel even bij 5.2 houden...

Ik heb een vraag over de namespaces, stel ik doe dit:
use Bibliotheek\Systeem\Start as Start;

Wat er nu gebeurt is dat die "snelkoppeling" enkel werkt in het PHP-bestand waar ik die plaats, bestanden die ik include o.i.d. nemen deze snelkoppeling niet over, klopt dat? Hoe lossen jullie dat op?

[...] info over namespaces in PHP6 kan je hier [...]

Leave a comment
Line and paragraph breaks automatic, e-mail address never displayed, HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>