Scriptorama.nl

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

PHP 5.3: Wat doet de intl extensie ?

Buiten de uitbreidingen op de taal PHP, komt PHP 5.3 ook gewoon met een paar nieuwe extensies die extra functionaliteit brengen. Zo is er de intl extensie, een extensie die de internationalization-gerelateerde uit de Unicode library die PHP6 gebruikt aanbiedt voor gebruik in PHP 5.3.

Om iets concreter te zijn, de extensie geeft je de mogelijkheid om met regio-specifieke instellingen om te gaan zoals: Hoe noteer je een datum in Frankrijk, gebruik je een punt of een komma, of misschien wel een spatie om duizendtallen te scheiden, hoe sorteer je de speciale letters uit het Frans, enzovoorts. De extensie biedt haar functionaliteit aan via 6 verschillende klassen: Locale, MessageFormatter, IntlDateFormatter, NumberFormatter, Collator en Normalizer. In dit artikel kijken we naar de eerste 5.

Installatie

De Intl extensie wordt wel standaard meegeleverd, maar niet standaard meegecompileerd met PHP. Hiervoor zul je ofwel de Intl extensie moeten meecompileren:

CODE:
  1. ./configure --enable-intl

Anders, als de module al beschikbaar is, maar nog niet geladen, zul je deze moeten laden in php.ini:

CODE:
  1. extension=intl.so

Op naar de code!

Locale klasse

Een 'locale' is een collectie gegevens die het hoe en wat van een regio en van een taal omschrijft. De Locale class biedt een interface om enkele van deze gegevens op te vragen. Denk hierbij aan de naam van het betreffende land, het schrift dat ze gebruiken maar ook de taal die gesproken wordt.

Een greep uit de beschikbare functies:

PHP:
  1. // Haal de standaard locale op:
  2. echo Locale::getDefault(); // bij mij: en_US_POSIX
  3.  
  4. // Wijzig de standaard Locale voor dit hele request
  5. Locale::setDefault('nl_NL');
  6.  
  7. // Toon de "Regio" voor nl_NL in het Fins:
  8. echo Locale::getDisplayRegion('nl_NL', 'fi_FI'); // Alankomaat
  9.  
  10. // Parse de Accept-Language header voor de taal instellingen van de gebruiker
  11. echo Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']);

Collator

Verschillende talen hebben verschillende regels wat betreft het sorteren van bepaalde speciale tekens. De Fransen hebben andere opvattingen dan wij over hoe bijv. ô en é gesorteerd moeten worden, en hoe sorteer je eigenlijk iets in het Fins?

De Collator klasse neemt je al deze details voor alle verschillende talen uit handen en biedt een simpele interface om daarop te kunnen sorteren:

PHP:
  1. $sortTest = array('côte', 'coté');
  2.  
  3. $collatorNL = new Collator('nl_NL');
  4. $collatorFR = new Collator('fr_FR');
  5.  
  6. $collatorFR->sort($sortTest);
  7. var_dump($sortTest);
  8.  
  9. /*
  10. array(2) {
  11.   [0]=>
  12.   string(5) "côte"
  13.   [1]=>
  14.   string(5) "coté"
  15. }
  16. */
  17.  
  18. $collatorNL->sort($sortTest);
  19. var_dump($sortTest);
  20.  
  21. /*
  22. array(2) {
  23.   [0]=>
  24.   string(5) "coté"
  25.   [1]=>
  26.   string(5) "côte"
  27. }
  28. */

MessageFormatter

De MessageFormatter kun je het beste vergelijken met een locale-aware versie van sprintf() maar met net even iets meer functionaliteit. Om de MessageFormatter te gebruiken, creeër je een MessageFormatter object via de statische methode MessageFormatter::create():

PHP:
  1. $msgfmt = MessageFormatter::create(
  2.     'nl_NL',
  3.     'Scriptorama heeft maandelijks bijna {0,number,integer} bezoekers'
  4. );

Deze aanroep creert een nieuwe MessageFormatter met de Nederlandse instellingen voor de weergave van nummers, datums, etc en met een string waarin ik een formatter tag heb geplaatst. Het formaat van deze tag is als volgt:

CODE:
  1. { identifier, [formatter, formatter opties]}
  2. // De formatter is optioneel (default: string) en dus zijn ook de formatter opties optioneel.

In dit geval heb ik dus een formatter tag met identifier 0, we gebruiken de number formatter en geven als optie mee dat het opgegeven nummer moet worden behandeld als integer.

Vervolgens gebruik je de MessageFormatter::format() methode om de string met een waarde vorm te geven. De waarden voor de formatter geef je door als array, waarbij de array index correspondeert met de identifier uit je format string. Oftewel element 0 uit de array wordt gebruikt voor {0,number,integer}, element 1 voor {1,string}, enzovoorts.

PHP:
  1. echo $msgfmt->format(array(7500) );
  2. // 7,500

Natuurlijk is number niet de enige formatter die beschikbaar is. Je kunt de volgende formatters gebruiken:

string
Heeft geen speciale functionaliteit en kan theoretisch ook weggelaten worden.
number
Formatteer de opgegeven waarde als nummer. Zonder formatter opties wordt het nummer mét decimalen geformatteerd. Formatter opties:

  • currency: formatteer nummer als bedrag (incl. munteenheid symbool)
  • percent: formatteer nummer als percentage (incl. percentage symbool)
  • integer: formatteer nummer als heel nummer
date
Formatteer de opgegeven waarde als datum. Formatter opties:

  • full: bijv. zaterdag 16 augustus, 2008
  • long: bijv. 16 augustus 2008
  • medium: bijv. 16 aug 2008
  • short: bijv. 16-08-2008
time
Formatteer de opgegeven waarde als tijd.

  • full: bijv. 11:40:27 GMT+02:00
  • long: bijv. 11:40:27 GMT+02:00
  • medium: bijv. 11:40:27
  • short: bijv. 11:40
choice
Dit is een aparte formatter die afhankelijk van de opgegeven waarde verschillende teksten kan tonen:

PHP:
  1. $msgfmt = MessageFormatter::create(
  2.     'nl_NL',
  3.     "Scriptorama heeft vandaag {0,choice,0#geen bezoekers|1#één hele bezoeker|1<{0,number,integer} bezoekers|500#echt te veel bezoekers}"
  4. );
  5.  
  6. echo $msgfmt->format(array(0)), PHP_EOL;
  7. echo $msgfmt->format(array(1)), PHP_EOL;
  8. echo $msgfmt->format(array(15)), PHP_EOL;
  9. echo $msgfmt->format(array(501)), PHP_EOL;
  10.  
  11. /*
  12. Scriptorama heeft vandaag geen bezoekers
  13. Scriptorama heeft vandaag één hele bezoeker
  14. Scriptorama heeft vandaag 15 bezoekers
  15. Scriptorama heeft vandaag echt te veel bezoekers
  16. */

Laten we alleen even de formatter er bij pakken en iets leesbaarder maken:

CODE:
  1. {
  2.     0,
  3.     choice,
  4.         0#geen bezoekers|
  5.         1#één hele bezoeker|
  6.         1<{0,number,integer} bezoekers|
  7.         500#echt te veel bezoekers
  8. }

De formatter optie voor de choice formatter bestaat uit een lijst van mogelijkheden gescheiden door een pipe (|) teken. In elk element van de lijst geef je aan wat de maximale waarde is, of deze waarde inclusief (het gebruik van #) of exclusief is (het gebruik van <) en de tekst die vervolgens getoond moet worden. In deze tekst kun je ook weer een formatter gebruiken.

Inlezen van messages

Hoewel de MessageFormatter heel goed is in het weergeven van berichten, maakt de kennis die de klasse heeft over de Locale, de MessageFormatter ook interessant voor het omgekeerde: het inlezen van gegevens. Hiervoor beschikt de MessageFormatter over de methode MessageFormatter::parse():

PHP:
  1. $str = "Scriptorama bestaat sinds 15-01-2006 en heeft sindsdien ongeveer 180.000 bezoekers gehad!";
  2.  
  3. $msgfmt = MessageFormatter::create(
  4.         "nl_NL",
  5.         "Scriptorama bestaat sinds {0,date,short} en heeft sindsdien ongeveer {1,number} bezoekers gehad!"
  6. );
  7.  
  8. var_dump($msgfmt->parse($str));
  9.  
  10. /*
  11. array(2) {
  12.   [0]=>
  13.   int(1137279600)
  14.   [1]=>
  15.   int(180000)
  16. }
  17. */

Zoals je ziet heeft de MessageFormatter de twee waarden er uit geplukt en terug gegeven als array.

IntlDateFormatter

De IntlDateFormatter, met een speciale naam om verdere Date-klasse problemen te voorkomen, biedt de mogelijkheid om datums in een locale-specifiek formaat weer te geven.

PHP:
  1. $fmt = new IntlDateFormatter(
  2.     'nl_NL',
  3.     IntlDateFormatter::FULL,
  4.     IntlDateFormatter::SHORT
  5. );
  6.  
  7. echo 'Vandaag is: ', $fmt->format(time());

Het 2e en 3e argument geven aan op welke manier de datum en tijd (respectievelijk) getoond moeten worden. De beschikbare constants daarvoor zijn:

  • IntlDateFormatter::FULL: bijv. zaterdag 16 augustus, 2008 of 11:40:27 GMT+02:00
  • IntlDateFormatter::LONG: bijv. 16 augustus 2008 of 11:40:27 GMT+02:00
  • IntlDateFormatter::MEDIUM: bijv. 16 aug 2008 of 11:40:27
  • IntlDateFormatter::SHORT: bijv. 16-08-2008 of 11:40
  • IntlDateFormatter::NONE: Toon dit element niet

Ook de IntlDateFormatter is in staat om geformatteerde teksten te lezen ipv. alleen weer te geven. Dit doe je met de IntlDateFormatter::parse() methode, welke ofwel een Unix timestamp terug geeft, of FALSE als het parsen niet gelukt is.

PHP:
  1. $str = "zaterdag 16 augustus 2008 12:22";
  2.  
  3. $dateFmt = new IntlDateFormatter("nl_NL", IntlDateFormatter::FULL, IntlDateFormatter::SHORT);
  4. $result = $dateFmt->parse($str);
  5.  
  6. echo date('d-m-Y H:i', $result);
  7. // 16-08-2008 12:22

NumberFormatter

De NumberFormatter is de laatste formatter die de Intl extensie biedt, en, je raad het al, deze klasse biedt de mogelijkheid om cijfers in een locale-specifiek formaat te tonen en in te lezen:

PHP:
  1. $nf = NumberFormatter::create('en_US', NumberFormatter::DECIMAL);
  2. echo $nf->format(10000), PHP_EOL; // 10,000
  3.  
  4. $nf = NumberFormatter::create('nl_NL', NumberFormatter::SPELLOUT);
  5. echo $nf->format(1500000), PHP_EOL; // een miljoen vijf honderd duizend

De NumberFormatter kan ook met geldbedragen omgaan, maar om een munteenheid weer te geven die anders is dan die van de opgegeven locale zul je de speciale methode NumberFormat::formatCurrency() moeten gebruiken:

PHP:
  1. $nf = NumberFormatter::create('nl_NL', NumberFormatter::CURRENCY);
  2. echo $nf->format(150); // € 150,00
  3.  
  4. $nf = NumberFormatter::create('nl_NL', NumberFormatter::CURRENCY);
  5. echo $nf->formatCurrency(150,'CAD'); // Can$ 150,00

De volgende formatters zijn beschikbaar voor de NumberFormatter:

  • NumberFormatter::DECIMAL: bijv. 10,000
  • NumberFormatter::CURRENCY: bijv. € 10,000
  • NumberFormatter::PERCENT: bijv. 10%, geef waarde op als decimaal: 0=0%, 0.5=50%, 1 = 100%
  • NumberFormatter::SCIENTIFIC: bijv. 1.5E6
  • NumberFormatter::SPELLOUT: bijv. tien duizend, dit lijkt voornamelijk te werken bij westerse talen
  • NumberFormatter::DURATION: bijv. 14:00, de opgegeven waarde wordt gelezen als minuten. 60 levert dus '1:00' op.

Het inlezen gaat net als bij de IntlDateFormatter met de parse() methode, al is er voor het parsen van geldbedragen een aparte parseCurrency methode:

PHP:
  1. $fmt = new NumberFormatter( 'nl_NL', NumberFormatter::CURRENCY );
  2. $num = 'Can$ 150,00';
  3.  
  4. echo "Dit is ".$fmt->parseCurrency($num, $curr)." in $curr\n";
  5. // Dit is 150 in CAD

Conclusie

Dit zijn natuurlijk niet alle mogelijkheden die de verschillende klassen uit de Intl extensie bieden, zo kun je bijvoorbeeld ook eigen formaten opgeven bij de verschillende formatters, maar waarschijnlijk wel degene die je eventueel het meeste zou gebruiken.

Ik denk dat met name de Collator, NumberFormatter en de MessageFormatter klassen voor veel mensen nuttig zullen zijn. Met dat gezegd hoop ik dat de extensie nog wat gepolijst wordt voordat PHP 5.3 uitgebracht wordt: momenteel is helaas de documentatie niet bepaald duidelijk, de extensie integreert nog niet echt met relevante onderdelen van PHP en is de functionaliteit is niet overal even compleet.

Reageer ook!

De datum functies zijn vooral interessant :)
Je hebt wel Zend_Local en zelf heb ik iets anders geschreven, maar Native C is toch meestal sneller.

Zeker een handige extensie!

Met de updates die allemaal komen in PHP 5.3 ziet het er allemaal wel goed uit. Kan het overigens niet meer dan eens zijn met de feature requests van jou, Mathieu.

Ben zelf totaal geen voorstander van UNIX timestamps en als ik dit systeem zou willen integreren met DateTime (wat ik zelf nu al gebruik in mijn applicaties) dan zou ik dingen door moeten gaan geven via die timestamps. Not done in mijn ogen.

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>