ABIS Infor - 2012-04

PHP: meer dan een webinterface

Peter Vanroose (ABIS) - februari 2012

Samenvatting

PHP kennen we als "de" server-side programmeertaal voor webservers. Maar eigenlijk heeft PHP veel meer in z'n mars! In deze korte bijdrage worden enkele concrete voorbeelden uitgewerkt, telkens met voldoende uitleg voor de PHP-leek, van wat PHP ons ook buiten de context van een webserver kan bieden.

PHP in de context van een webinterface

PHP is oorspronkelijk (rond 1995) ontwikkeld door Rasmus Lerdorf als een "Personal Home Page" preprocessor-tool om HTML te genereren voor z'n persoonlijke webpagina; maar zeer snel daarna werd het ontdekt door anderen, en vandaag is PHP de de facto "server side scripting"-taal die gebruikt wordt op webserver-pagina's, ingebed in de HTML van die pagina om dynamisch HTML te genereren, of om acties op de server uit te voeren zoals communiceren met een relationele database-server.

Eigenlijk wordt PHP omzeggens uitsluitend gebruikt als de programmeer-omgeving van een webserver (dikwijls Apache); het lijkt een goed bewaard geheim dat PHP-programma's ook kunnen geschreven en gebruikt worden buiten een HTML-context en zelfs buiten een webserver-context. Laten we dus eens inzoomen op dat aspect van deze programmeertaal.

PHP als "gewone" programmeertaal

Zoals bij elke andere programmeertaal, kan een PHP-programma gecompileerd en zelfs uitgevoerd worden door een compiler. Elke PHP-installatie heeft een programma "php": dit is de commandolijn-interface voor de PHP-compiler. Geef hem uw programmabestand (of zorg ervoor dat dit programma in MS-Windows standaard wordt opgeroepen voor de extensie php) en u kunt meteen aan de slag!

Formeel moeten PHP-programs wel altijd in HTML ingebed zijn, dus een .php-bronbestand is altijd van de volgende vorm:

  <?php
   ...
  ?>

waarbij de "..." natuurlijk zullen vervangen worden door het eigenlijke PHP-programma.

De PHP-programmeertaal is zeer gelijkaardig -syntactisch gezien- aan Java, of eigenlijk zelfs meer aan Perl. Als u één van deze talen kent, wees u dan wel bewust van enkele subtiele maar belangrijke verschillen... De naar mijn mening belangrijkste redenen om PHP te overwegen voor uw applicatie-ontwikkeling (in plaats van de typischer alternatieven zoals Java, C#, C++, C, Perl, Python, Ruby, ...) zijn waarschijnlijk (1) zijn "rechttoe-rechtaan"-syntax en wat nog belangrijker is (2) zijn uitgebreide set aan uitbreidingsbibliotheken zoals b.v. ODBC, CURL, LDAP, of XML (maar dat is ook waar voor de meeste andere talen), en misschien wel het allerbelangrijkst (3) de krachtige ingebouwde tekstmanipulatie-mogelijkheden, inclusief verschillende functies voor patroonherkenning m.b.v. reguliere expressies. Zo dadelijk een voorbeeld hiervan.

PHP is object-georiënteerd, maar het OO-aspect wordt niet opgedrongen aan de programmeur, en de meeste PHP-programma's (vooral de kleinere) worden inderdaad niet in OO-stijl geschreven. Al mijn voorbeelden zijn niet-OO.

Buiten de context van een webserver heeft PHP geen GUI-component. Daarom zijn stand-alone PHP-programma's het nuttigst in de context van een commando-lijn (dus tekstgebaseerd). Dit is een voor de hand liggende interface voor Unix-gebruikers maar kan voor Windows-gebruikers misschien ongemakkelijk aanvoelen. Toch hoeft dit geen bezwaar te zijn: klik gewoon op dat .php-bestand en een zwart venster wordt automatisch geopend om met u te communiceren - weliswaar met I/O die tekstgebaseerd en dus niet grafisch is, maar zolang het programma functioneel doet wat u nodig hebt...

In dit geval is het wellicht een goed idee om de volgende instructie toe te voegen op het einde van uw PHP-programma:

	readline("Druk ENTER om dit programma te beëindigen. ");

PHP-programma's kunnen inderdaad zeer krachtig en niet te kloppen zijn wanneer het aankomt op tekstverwerking, wiskunde, gegevensanalyse of het herformatteren van gegevens zoals data, namen, adressen, of wat dan ook. Laten we enkele voorbeelden bekijken.

Tekstmanipulatie

Onderstel dat u naar datums wil zoeken in één of ander tekstdocument. Stel dat we enkel willen weten of er een datum voorkomt in het bestand, dus een tekst van de vorm "twee cijfers schuine streep twee cijfers schuine streep vier cijfers".

Eerst zal dat bestand moeten geopend worden, z'n inhoud ingelezen, en dan deze inhoud doorzocht naar het datum-"patroon" zoals hierboven beschreven. De inhoud van een bestand inlezen in een variabele is eenvoudig in PHP:

	$content = file_get_contents("myfile.txt");

waarbij "myfile.txt" de bestandsnaam is (in de huidige directory), en $content de variabele zal zijn waarin de bestandsinhoud zal opgeslagen zijn na de oproep van de functie "file_get_contents". Bemerk dat variabelen altijd een dollar-sigil als prefix moeten hebben. Variabelen hoeven niet gedeclareerd te worden: ze "ontstaan" bij eerste gebruik.

Nu moeten we de inhoud van de variabele $content doorzoeken naar iets van de vorm "dd/dd/dddd", waar "d" staat voor een cijfer, dus een teken met een waarde tussen "0" en "9". De functie die exact hiervoor bedoeld is, is preg_match: deze functie verwacht twee argumenten, een zoekpatroon ("wat" wordt gezocht) en de doeltekst ("waarin" wordt gezocht); de functie geeft hetzij "true" (indien gevonden) hetzij "false" terug.

	preg_match("!\d\d/\d\d/\d\d\d\d!", $content);

Het eerste argument van preg_match is een zogenaamde reguliere expressie die het patroon beschrijft. Reguliere expressies moeten tussen twee delimiters geplaatst worden (hier de twee uitroepingstekens, maar elk ander teken is toegelaten). Verder wordt elk teken waar letterlijk naar gezocht wordt (hier de twee slashes) ook letterlijk geschreven; sommige tekens hebben echter een speciale betekenis; b.v. de combinatie "\d" betekent "een cijfer" ("digit" in het Engels).

Een programma met enkel de twee bovenstaande instructies doet nog niet veel nuttigs, vermits we niets zien, laat staan dat we een verschil zien tussen het geval "iets gevonden" of "niets gevonden". Wel, voeg gewoon een (voorwaardelijke) print-instructie toe die iets naar het scherm zal schrijven, b.v.:

	if (preg_match("!\d\d/\d\d/\d\d\d\d!", $content))
	{  print "Er was een datum in myfile.txt\n";  }
	else
	{  print "Er was geen datum in myfile.txt\n";  }

Bemerk de "\n" aan het eind van de af te printen tekst; dit stuurt een "regel-einde"-teken naar het scherm, zodat een eventuele volgende print-instructie z'n output op een nieuwe regel op het scherm zal plaatsen.

Stel nu dat we niet alleen willen weten of er een datum aanwezig is in een bestand, maar ook alle datums willen "normaliseren", b.v., omzetten van het dag/maand/jaar-formaat naar het ISO-formaat: jaar-maand-dag. En de rest van de bestandsinhoud ongewijzigd laten. Hiervoor is de functie preg_replace ideaal geschikt:

	$content = preg_replace("!(\d\d)/(\d\d)/(\d\d\d\d)!", "$3-$2-$1", $content);

preg_replace geeft de gewijzigde inhoud van $content terug, en we overschrijven de variabele ermee. Bemerk de notaties $1, $2 en $3 in de vervangtekst (het tweede argument van preg_replace): deze variabelen bevatten de inhoud van de drie haakjesgroepen in de reguliere expressie.

Als we ten slotte de inhoud van $content naar een bestand willen schrijven, hetzij om bestand myfile.txt te overschrijven of om een nieuw bestand myfile2.txt aan te maken, voegen we volgende instructie toe aan het PHP-programma:

	file_put_contents("myfile2.txt", $content);

Bemerk dat alle datums hiermee geconverteerd zijn, vermits de actie van preg_replace altijd globaal is.

Er zijn nog twee nuttige functies in de context van tekstuele patroon-matching: preg_split en preg_grep. De eerste creëert een lijst van tekstfragmenten uit een gegeven tekst, gebaseerd op een "separator"-patroon, terwijl de tweede een gegeven lijst "filtert" gebaseerd op een zoekpatroon, en dus een kortere lijst produceert met enkel de entries die voldoen aan het patroon.

Laten we bijvoorbeeld zoeken naar alle drieletterwoorden in een bestand. Splits daartoe eerste de tekst in "woorden":

	$wordlist = preg_split("!\W+!", file_get_contents("myfile.txt"));

De reguliere expressie "\W" betekent "elk niet-woord-teken", dus spaties, leestekens, ...; de "+" betekent "één of meer van deze", dus elke groep niet-letters wordt gezien als één separator. Het gevolg hiervan is dat $wordlist zal bestaan uit alle woorden van de tekst, noch min noch meer. Filter daarna deze lijst door er alle woorden uit te gooien die niet uit precies drie letters bestaan:

	$wordlist = preg_grep("!^...$!", $wordlist);

We overschijven dus opnieuw de bestaande variabele (in dit geval een lijst-variabele) met z'n nieuwe inhoud. De reguliere expressie bevat nu drie ingrediënten: "^" betekent "begin van de tekst", "." staat voor "één letterteken", en "$" duidt op het einde van de tekst. preg_grep itereert zelf door de lijst, dus "tekst" slaat hier op elk van de afzonderlijke woorden.

Print ten slotte $wordlist uit op het scherm, één woord per regel:

	foreach ($wordlist as $w) { print "$w\n"; }

Of indien men de woorden liever in alfabetische volgorde in een bestand wegschrijft, kan $wordlist eerst gesorteerd worden om vervolgens de woorden in de tekstvariabele $words te plaatsen in ten slotte file_put_contents op te roepen zoals we in het eerste voorbeeld reeds deden.

	sort($wordlist); foreach ($wordlist as $w) { $words .= "$w\n"; }

Enkele nuttige bibliotheken

De basisfunctionaliteit van PHP is ingebouwd in de compiler. Maar de meeste functies, inclusief de preg-functies die we hierboven gebruikt hebben, worden via "plug-in-bibliotheken" aangeboden; op een bepaald systeem kunnen deze libraries al dan niet aanwezig zijn.

Laat me twee interessante uitbreidingsbibliotheken van PHP vermelden: de XML-library en de MySQL-library. Hierin zitten functies voor enerzijds het parsen en schrijven van XML-documenten, en anderzijds voor het communiceren met een MySQL-database-server.

Gewoon om een idee te geven van de mogelijkheden heb ik hieronder een PHP-programma uitgeschreven dat het XML-bestand "courses.xml" zal lezen en parsen, met daarin cursusinformatie van de vorm

  <Course><name>Basiscursus programmeren PHP</name><date>15.06.2012</date></Course>

Het programma zal die informatie dan wegschrijven naar de MySQL-tabel "courseinfo" die uit twee kolommen bestaat.

  $xml = DOMDocument::load("courses.xml");
  mysql_connect("localhost", "username", "password");
  mysql_set_charset("utf8");

  $rows = $xml->getElementsByTagName('Course');
  foreach ($rows as $row)
  {
    $name = $row->getElementsByTagName('name')->item(0)->nodeValue;
    $date = $row->getElementsByTagName('date')->item(0)->nodeValue;
    mysql_query("INSERT INTO courses.courseinfo VALUES('$name','$date')");
  }

Wat is er nog meer?

Hieronder nog enkele (al dan niet nuttige) kleine PHP-programma's (one-liners zelfs) als antwoord op concrete vragen:

  • Wat is er vandaag op televisie? print file_get_contents("http://www.een.be/tv-gids","r");
  • Wat is de datum van Pasen in 2013? print date("d/m/Y",easter_date(2013));
  • Om hoe laat gaat de zon morgenochtend op in Leuven?
    print date_sunrise(time()+86400,1,50.88,4.71,90,1); (tel er een uur bij indien zomertijd)
  • Geef inhoudelijke informatie (dus het bestandstype) van alle bestanden in de huidige folder:
    $f=finfo_open();foreach(glob("*") as $n){print $n."\t".finfo_file($f,$n)."\n";}
  • Wat is de kans om minstens één keer een zes te gooien met 10 dobbelstenen? print 1-pow(5/6,10);
  • Hoe dikwijls komt het woord "ABIS" voor in het bestand waarvan de naam op de commandolijn gegeven is?
    $f=file_get_contents($argv[1]); $l=preg_split('/ABIS/i',$f); print count($l)-1;

Meer info?

Eerst en vooral is er de officiële website van PHP: www.php.net, met o.a. een zeer uitgebreide online-documentatie. En hoeft het gezegd, uiteraard kunt u ook een basiscursus "PHP" volgen bij ABIS! Voor meer info verwijzen we u naar onze website: www.abis.be/html/nl1521.html.