Zend Framework nader bekeken

Dit is het vierde deel van een serie over de bouw van een nieuwe website op basis van Zend Framework. Kijk in de zijbalk voor de linkjes naar de eerste delen.

Nu we de applicatie hebben opgezet, is het handig om eens van dichtbij al die mappen en bestanden te bekijken die Zend_Tool voor ons heeft klaargemaakt.

Application

Dit is de centrale map waarin je veruit het grootste deel van je ontwikkelwerk zult doen. Application zelf is weer onderverdeeld in

1. Configs – hierin worden de configuratiebestanden van je applicatie opgezet.

2. Controllers – de verzamelplaats waar de requests worden opgevangen die door de browser van de bezoeker naar je applicatie worden verstuurd, en waar de views worden aangestuurd die de bezoeker als gevolg van zijn request uiteindelijk te zien krijgt

3. Models – hierin wordt de logica van je applicatie ondergebracht

4. Views – de scripts waarin de html terecht komt die je uiteindelijk moet produceren om de pagina’s te tonen

Een van de belangrijkste besluiten die je neemt als je een applicatie bouwt met ZF, is de beslissing om een strikte scheiding aan te brengen tussen de logica die je applicatie nodig heeft om goed te functioneren, de pagina’s die getoond moeten worden wil de bezoeker kaas kunnen maken van deze logica, en de verbinding tussen logica en pagina’s.

Deze scheiding is nuttig: als je die altijd hanteert, betekent dat dat je je eigen code aan een strikte discipline onderwerpt. Je weet dat je nooit in je html hoeft te zaken naar databasefunctionaliteit of login-code, want die heeft keurig zijn eigen plek in de applicatiestructuur. Dit ontwerppatroon (design pattern) heeft MVC: Model-View-Controller. En laat die benaming nou net overeenkomen met de submappen onder application!

We weten nu dus dat we in de map Views terecht moeten voor de weergave van onze pagina’s, en dat we in Models waarschijnlijk code kunnen terugvinden die te maken heeft met de data waarop Elesio drijft: de recensies, de producten, de boeken, de webservices van Google en Amazon, etc. Wat doen die Controllers dan precies?

Die vormen de tussenlaag tussen de Models en de Views. Als je bezoeker een pagina opvraagt, moet zijn verzoek (een request) ergens in de applicatie worden opgevangen. Daarvoor zijn de controllers verantwoordelijk. Een controller bekijkt welke pagina wordt opgevraagd, en of er misschien nog bijzondere parameters worden meegegeven: is er een formulier gepost, een zoekvraag gedaan, of een andere bijzondere actie verwacht. Binnen de controller wordt er ook voor gezorgd dat de bezoeker geen kwaad kan uitrichten. Zijn input wordt gecheckt op pogingen om in te breken.

Als het request eenmaal is geanalyseerd, wordt het juiste model aangesproken. Het model is verantwoordelijk voor het ophalen en de manipulatie van de gegevens die de applicatie bevat. Dat kunnen gegevens zijn uit een database, een RSS-feed, een tekstbestand, of andere bronnen. Die gegevens worden, al dan niet na bewerking, terug gegeven aan de controller, die ze vervolgens weer doorstuurt naar de view. De view is uiteindelijk verantwoordelijk voor de correcte weergave ervan.

Bin

Deze map hebben we al kort besproken – hij bevat de scripts die nodig zijn om Zend_Tool aan te roepen. Met Zend_Tool kun je niet alleen nieuwe projecten, maar ook nieuwe controllers, actions, modules, pagina’s, formulieren en nog een keur aan andere onderdelen maken. Later in deze handleiding komen we terug op het gebruik van Zend_Tool.

In de regel zul je alleen het bestand zend.php nodig hebben; er zijn ook twee shortcuts beschikbaar (zf.bat voor Windows en zf.sh voor *nix en Mac), maar die zijn niet altijd even stabiel.

Mocht je in je applicatie behoefte hebben aan andere scripts die je vanaf de command line aanroept (bijvoorbeeld cron jobs of migratiescripts) is dit de plek om ze neer te zetten.

Docs

Deze map bevat, de naam zegt het al, je documentatie. Nog zo goed als leeg op dit moment, zoals je ziet, maar het is zeer aan te bevelen om deze map goed te vullen. Het is de plek voor de technische handleiding, niet voor de eindgebruikers, maar voor de programmeurs en designers die aan de applicatie gaan werken. Zelfs als je zeker weet dat je de enige programmeur bent, dan nog is het handig om een minimum aan documentatie in deze map te plaatsen; je kunt er namelijk donder op zeggen dat je alle subtiliteiten, waar je zo trots op was toen je ze bedacht, bent vergeten als je over een half jaar nog eens in je code moet kijken.

De beste manier om documentatie samen te stellen is door alle klassen en methodes die je zelf ontwerpt, te voorzien van commentaar volgens de richtlijnen van PHPDoc . PHPDoc biedt een script om op elk gewenst moment je commentaar, dat inline staat, om te zetten in een gemakkelijk leesbaar, navigeerbaar en doorklikbaar formaat. Als je je klassen en methoden goed documenteert, ben je er zeker van dat je documentatie up-to-date en compleet is.

Library

Deze map bevat op dit moment alleen het Zend Framework; maar niets staat je in de weg om je eigen bibliotheek aan eerder geschreven scripts en tools aan deze map toe te voegen. Let er wel op hoe je dat doet.

Als je door de map Zend bladert, zal je opvallen dat deze verregaand is gestructureerd. ZF is onderverdeeld in een flink aantal componenten die elk een eigen hoofdmap hebben. Die hoofdmap is op zijn beurt onderverdeeld in submappen waarin de bestanden (klassen) zitten die het ZF uiteindelijk opmaken.

Laten we bij wijze van voorbeeld Zend_Form nemen. Binnen de hoofdmap Zend vind je een bestand Form.php. Als je dat opent, zul je zien dat dit bestand de klassedefinitie bevat van een klasse die Zend_Form wordt genoemd. Als je een formulier wilt aanmaken dat is gebaseerd op Zend_Form, roep je feitelijk deze klasse aan.

Maar er is ook een map Form. Binnen die map vind je twee submappen: Decorator en Element. Dus het bestand Zend/Form/Element/Text.php vind je een enkele klasse Zend_Form_Element_Text dat verantwoordelijk is voor het weergeven van een tekstveld in het formulier. Er is dus een rechtstreekse relatie tussen de namen van klassen en bestanden enerzijds, en de paden in het bestandssysteem anderzijds.

Als je je eigen bibliotheken wilt invoeren in de library-map, is het aan te bevelen om dezelfde conventie aan te houden. Mijn bibliotheek staat in een submap GJB (mijn initialen). Binnen GJB heb ik bijvoorbeeld een submap Utils, en daarbinnen vind ik een bestand Password.php. Dit enkele bestand bevat een klasse die GJB_Utils_Password heet, en die een enkele (statische) methode bevat: een methode die een willekeurig wachtwoord genereert. Dit is een functie die ik in veel applicaties elke keer opnieuw nodig heb, en die ik dus probleemloos van de ene naar de andere applicatie kan kopiëren. Zo hoef ik niet elke keer opnieuw het wiel uit te vinden.

Zo heb ik binnen Utils een bestand MailManager.php (klassenaam GJB_Utils_MailManager), en heb ik naast Utils een map Form met een bestand Standard.php (klassenaam GJB_Form_Standard). Enfin, je begrijpt het idee. Binnen de map library zet je die bestanden die je zelf (of anderen) hebt gemaakt en die voldoende generiek zijn om van applicatie naar applicatie gekopieerd te kunnen worden. Er staat dus geen applicatiespecifieke code in Library.

Public

Dit is de enige map die onder de doc-root van je webserver terecht komt; anders gezegd, dit is de enige map die vanaf het web benaderbaar is. Dat is een belangrijke verdediging tegen hackers: gevoelige gegevens zoals wachtwoorden en ssl-sleutels staan dus niet op een vanaf het web benaderbare plaats.

De map public bevat alleen die bestanden en mappen die ook daadwerkelijk opgevraagd kunnen worden: in de regel zijn dat statische bestanden zoals afbeeldingen, javascript en stylesheets.

Maar dat is niet alles: je map public bevat ook de enige ingang naar je applicatie. Als je het bestand .htaccess opent, zul je deze code tegenkomen.

[codesyntax lang=’apache’ title=’.htaccess’ lines=’normal’]

RewriteEngine On

RewriteCond %{REQUEST_FILENAME} -s [OR]

RewriteCond %{REQUEST_FILENAME} -l [OR]

RewriteCond %{REQUEST_FILENAME} –d

RewriteRule ^.*$ – [NC,L]

RewriteRule ^.*$ index.php [NC,L]

[/codesyntax]

Feitelijk gebeurt hier het volgende: elke request van de bezoeker die niet om een feitelijk bestaand bestand vraagt in de doc-root van de webserver, moet worden doorgestuurd naar index.php. Dit betekent dat index.php de enige pagina is die daadwerkelijk opgevraagd gaat worden. Vanuit dit bestand wordt je applicatie bij elke request opgestart.

Binnen index.php worden enkele omgevingsvariabelen vastgesteld: het absolute pad op de webserver naar de application-map, en ook de variabele die bepaalt in welke fase van het ontwikkelingstraject we zitten: in ontwikkeling, test, acceptatie of productie.

Het include-path wordt aangepast zodat het Zend Framework gevonden en andere library-bestanden gevonden kunnen worden; en de applicatie wordt gestart.

[codesyntax lang=’php’ title=’index.php’ lines=”fancy”]

<?php

// Define path to application directory

defined(‘APPLICATION_PATH’)

|| define(‘APPLICATION_PATH’, realpath(dirname(__FILE__) . ‘/../application’));

// Define application environment

defined(‘APPLICATION_ENV’)

|| define(‘APPLICATION_ENV’, (getenv(‘APPLICATION_ENV’) ? getenv(‘APPLICATION_ENV’) : ‘production’));

// Ensure library/ is on include_path

set_include_path(implode(PATH_SEPARATOR, array(

realpath(APPLICATION_PATH . ‘/../library’),

get_include_path(),

)));

/** Zend_Application */

require_once ‘Zend/Application.php’;

// Create application, bootstrap, and run

$application = new Zend_Application(

APPLICATION_ENV,

APPLICATION_PATH . ‘/configs/application.ini’

);

$application->bootstrap()

->run();

[/codesyntax]

Ga je gang, en maak in de map public vier nieuwe submappen aan die we nodig gaan hebben: js (voor javascript-bestanden), css (voor stylesheets), graphics (voor images) en uploads (voor het geval gebruikers of beheerders bestanden moeten uploaden). Werk je op een *nix systeem, dan moet je de map uploads ook voorzien van de juiste permissies zodat de webserver toegang heeft om er geuploade bestanden in te plaatsen.

Tests

Zend Framework voldoet aan de hoogste industriële standaarden voor software-ontwikkeling. Dat betekent in de praktijk dat elke functie, elke methode, elke bestand en elk component volledig en rigoureus wordt getest voordat het in productie wordt genomen. Het zou goed zijn om in de ontwikkeling van je applicatie een zelfde discipline in acht te nemen. Zend Framework gebruikt Unit Tests om componenten te testen; je eigen testen plaats je in de map tests.

Een template maken

Weer bijna een volledig hoofdstuk afgesloten zonder te coderen… sorry, maar het is soms beter eerst wat theoretische basiskennis te hebben voordat je in het wilde weg code gaat kloppen. Maar nu gaan we een beginnetje maken.

Als je in je browser naar http://dev.elesio.local gaat en de bron bekijkt, zul je zien dat er geen correcte html is gevormd. Bovenaan in de bron zie je een paar stijldeclaraties staan, waarna er een div komt; geen spoor te vinden van html-, head- of bodytags. Daarin gaan we verandering brengen.

Open application/configs/application.ini. Dit configuratiebestand wordt automatisch ingelezen bij de start van onze applicatie. Dat gebeurt onderaan in het bestand public/index.php:

[codesyntax lang=’php’ title=’public/index.php’ lines=’fancy’]

<?php
$application = new Zend_Application(

APPLICATION_ENV,

APPLICATION_PATH . ‘/configs/application.ini’

);

[/codesyntax]

In deze regel wordt een nieuwe instantie van onze applicatie aangemaakt, met twee argumenten in de constructor: de omgevingsvariabele, en het configuratiebestand dat moet worden uitgelezen. Zoals je ziet is application.ini onderverdeeld in een aantal secties; welke sectie van toepassing is, hangt af van de variabele APPLICATION_ENV. In de productie-omgeving wordt de production-sectie uitgelezen, staging (acceptatie, zeggen we in het Nederlands) erft alle variabelen van productie maar staat ook toe dat specifieke variabelen voor deze omgeving worden gedefinieerd; hetzelfde geldt voor de testing en development variabelen. In de virtual host heb je je webserver al meegegeven dat we in de ‘development’ omgeving zitten; en dat betekent dat de development sectie van application.ini wordt uitgelezen. Deze sectie erft van testing, die erft van staging, die erft van production.

De meeste variabelen die in production worden gedefinieerd, worden zo overgenomen via een ouder-kind relatie door development. Nu gaan we in production een aantal nieuwe instellingen definiëren die de applicatie vertellen welke sjablonen worden gebruikt, wat de codering is van de webpagina’s, en nog een aantal andere instellingen.

Voeg deze regels toe aan de production sectie in application.ini:

[codesyntax lang=’bash’ title=’application/configs/application.ini’ lines=’fancy’]

;layout and view

resources.layout.layout = main

resources.layout.layoutPath = APPLICATION_PATH “/layouts”

resources.view.doctype = XHTML1_STRICT

resources.view.encoding = utf-8

resources.view.contentType = text/html;charset=utf-8

[/codesyntax]

Zou je nu je pagina dev.elesio.local verversen, dan zou je alleen een grote foutmelding krijgen. Maar feitelijk geeft die melding ons behoorlijk wat informatie: ZF klaagt dat het niet het juiste pad en bestand kan vinden, want het zoekt naar een bestand main.phtml in de submap layouts van de applicatie-map. Gemakkelijk op te lossen: maak een folder layouts in application, en plaats een bestand met de naam main.phtml in layouts (de extensie phtml wordt door Zend gebruikt om view scripts aan te duiden; dit kun je veranderen, als je wilt, maar waarom zou je?). Als je dat hebt gedaan, verdwijnt de foutmelding; maar kijk je nog eens in de bron, dan zie je dat er niets is veranderd. Logisch ook, want het bestand main.phtml is nog leeg.

Daarin gaan we nu verandering brengen. Plaats deze code in main.phtml:

[codesyntax lang=’php’ title=’application/layouts/main.phtml’ lines=”normal”]

<?php

echo $this->doctype() . PHP_EOL;

?>

<html xmlns=”http://www.w3.org/1999/xhtml”>

<head>

<?php

echo $this->headMeta(). PHP_EOL;

echo $this->headStyle(). PHP_EOL;

echo $this->headScript(). PHP_EOL;

echo $this->headLink(). PHP_EOL;

echo $this->headTitle(). PHP_EOL;

?>

</head>

<body>

<?php

echo $this->layout()->content;

?>

</body>

</html>

[/codesyntax]

Zou je nu de pagina opnieuw verversen, dan zie je dat er een doctype aanwezig is, de html, head en body tags worden keurig weergegeven, en er is een meta tag aanwezig voor de encoding. Maar daarmee zijn we er nog niet.

Open het bestand application/bootstrap.php, en plaats daarin deze code:

[codesyntax lang=”php” lines=”normal” title=”application/bootstrap.php”]

<?php

class Bootstrap extends Zend_Application_Bootstrap_Bootstrap

{

protected function _initMyView()

{

$this->bootstrap(‘view’);

$view = $this->getResource(‘view’);

$view->headTitle(‘Welkom bij Elesio – alles over e-boeken’);

$view->headMeta(‘Description’, ‘Op deze site vind je informatie en recensies over e-readers en e-boeken’);

$view->headMeta(‘Keywords’, ‘e-boeken, eboeken, e-readers, ereaders, recensies, gadgets, literatuur, fictie, non-fictie’);

}

}

[/codesyntax]

Je hebt een nieuwe methode toegevoegd aan de Bootstrap klasse; save dit bestand. Open ten slotte het bestand application/views/scripts/index/index.phtml, verwijder alle bestaande code uit dit bestand, en voeg er dit aan toe:

[codesyntax lang=”html4strict” lines=”normal” title=”application/views/scripts/index/index.phtml”]

<h1>Welkom bij Elesio!</h1>

<p>Dit is mijn eerste Elesio pagina</p>

[/codesyntax]

Bestand opslaan, en pagina verversen. Et voila: je hebt een nieuwe voorpagina die geheel conform de XHTML W3C standaarden is opgebouwd.

Wat is er nu gebeurd?

Application.ini

Zoals gezegd, wordt application.ini geladen op het moment dat de applicatie van start gaat. Het bevat configuratie-instellingen, zoals php-instellingen (al dan niet tonen van fouten bijvoorbeeld), alsmede de instellingen voor bronnen (‘resources’) die de applicatie gaat gebruiken. De database bijvoorbeeld, die we later zullen tegenkomen, kan in application.ini worden geinitialiseerd, maar ook de viewscripts.

De view is een van de bronnen die door ZF wordt gebruikt. De syntax die we in application.ini gebruiken, is als volgt: elke regel die een resource aanduidt, begint met resources. Resources.views bevatten die bronnen die met de view te maken hebben; resources.layout de bronnen die met de sjablonen te maken hebben. In application.ini geven we aan welk doctype onze view bevat, wat de encoding is, en waar het layoutbestand te vinden is.

Bootstrap.php

Als je naar de laatste regel van index.php gaat, zie je dat daar een methode bootstrap wordt aangeroepen van het applicatie-object. Deze bootstrap-methode levert het bootstrap object op, waarop weer een methode ‘run’ kan worden toegepast. En het is deze methode ‘run’ die uiteindelijk het startschot geeft voor de hele applicatie.

Dat startschot gaat als het ware dwars door het bootstrap object, zoals weergegeven in de bootstrap klasse (bootstrap.php) heen. Dit betekent concreet dat bij de initialisatie van de applicatie elke aanwezige methode in de Bootstrap wordt uitgevoerd. Dat is wel aan een voorwaarde gebonden: de methode moet protected of public zijn en beginnen met _init.

Zodoende wordt automatisch de methode _initMyView uitgevoerd die we zojuist in de bootstrap hebben gedefinieerd. In het begin van de methode wordt het view object opgehaald dat door de applicatie heen wordt uitgevoerd. Vervolgens worden een paar view helpers aangeroepen. View helpers zijn methodes die het view object beschikbaar stelt om vaak voorkomende taken op de view uit te voeren. De helpermethode headTitle stelt de titel van de applicatie in; en met headMeta kun je metatags toevoegen aan je html. Hier gebruiken we deze tags om keywords en description toe te voegen.

Er zijn meer helpermethoden die we later nog zullen tegenkomen; de belangrijkste zijn headLink() voor bijvoorbeeld het linken van stylesheets en favicons, headScript() voor het tonen van javascript, en url() voor het produceren van juiste url’s.

Main.phtml

Dit is het eigenlijke sjabloon bestand van de applicatie. In principe wordt elk viewscript dat later in de applicatie wordt gedefinieerd, ingevoegd op de plek waar in dit bestand $this->layout()->content wordt aangeroepen. Het feit dat je in een viewscript zoals main.phtml ook naar het view object $this kunt verwijzen, betekent al dat elk viewscript de beschikking heeft over alle variabelen en methoden van het view object. Vandaar dat $this->doctype() het juiste doctype oplevert, en $this->headTitle() de juiste titel weergeeft.

Nog eens extra wil ik hier benadrukken dat $this->layout()->content dus rechtstreeks informatie ophaalt en weergeeft die in de afzonderlijke viewscripts wordt gedefinieerd. Dat zie je in:

index.phtml

Want hierin hebben we slechts twee regels html code geplaatst, die rechtstreeks terugkomen op de plaats van $this->layout()->content.

Tot besluit

We zijn behoorlijk veel verder gekomen in de doorgronding van het Zend Framework. Maar we hebben nog maar een klein tipje van de sluier opgelicht; echter al wel genoeg om in te zien hoe gemakkelijk het werken met views en templates kan worden. In de volgende aflevering gaan we het verband onderzoeken tussen de controllers enerzijds en de models en de views anderzijds.