Co-founder at MoneyBird, Entrepreneur, Software Engineer.
Begin dit jaar schreef Tri Pham een interessant artikel op Scriptorama over de perfecte webdeveloper. Zijn conclusie was simpel maar doeltreffend:
De perfecte web developer bestaat misschien niet, maar er naar streven doet geen vlieg kwaad.
Een manier om hier naar te streven is voor mij een goede opbouw van een applicatie. De laatste tijd heb ik verschillende verzoeken ontvangen om te adviseren bij het ontwikkelen van een webapplicatie. Omdat het hier niet om kleine websites gaat, is de opbouw van groot belang. In dit artikel ga ik proberen de perfecte web developer uit te hangen en mijn mening geven over de perfecte opbouw van een webapplicatie.
A key thing about patterns is that you can never just apply the solution blindly, which is why pattern tools have been such miserable failures. I like to say that patterns are “half baked,” meaning that you always have to finish them off in the oven of your own project. Every time I use a pattern I tweak it a little here and a little there. You see the same solution many times over, but it’s never exactly the same.
De bovenstaande uitspraak komt uit het boek Patterns of Enterprise Application Architecture van Martin Fowler. In dit artikel zullen veel design patterns aan bod komen, patterns die ik zelf handig vind om te gebruiken. Luister naar Fowler, blijf altijd nadenken over de opbouw van je eigen applicatie!
De opbouw van webapplicaties is meestal in grote lijnen met elkaar te vergelijken. Je hebt te maken met de weergave van de applicatie in een webbrowser. Daarnaast zal er een bepaalde gegevensbron zijn, bijvoorbeeld in de vorm van een database. Het laatste element is de koppeling tussen de weergave en de database. Dit element zal de aanvraag van een client omzetten naar een weergave van gegevens uit de database.
De drie bovenstaande elementen zijn beter bekend als Model, View en Controller. Het MVC pattern is in mijn ogen daarom een goede basis voor een webapplicatie.
Het model van een webapplicatie bestaat uit de koppeling tussen de applicatie en een gegevensbron. In veel gevallen hebben hier te maken met een database. De gegevens in een database bestaan uit verschillende entiteiten die inhoud kunnen bevatten. In verschillende projecten in het verleden heb ik ervoor gekozen om voor elke tabel een klasse aan te maken die er globaal als volgt uit ziet:
<?php
class PageModel extends Model {
public function getPages(){
return $this->query("SELECT * FROM pages");
}
public function getPage($iId){
return $this->query("SELECT * FROM pages WHERE id = ".$iId);
}
public function addPage($sName, $sLocation, $bPublished){
return $this->query("INSERT INTO ...");
}
public function editPage($iId, $sName, $sLocation, $bPublished){
return $this->query("UPDATE pages SET ...");
}
public function deletePage($iId){
return $this->query("DELETE FROM ...");
}
}
?>
De methoden werden meestal aangevuld met controles op de gegeven parameters. De Model klasse bevatte alle functionaliteit voor de koppeling met de database. Vaak gaf de methode ‘query’ al associatieve arrays terug met gegevens zodat het in de applicatie verder niet nodig was om te werken met mysql_* functies. Het voordeel hiervan was ook de uitwisselbaarheid met andere database typen, zoals PostgreSQL.
De bovenstaande opzet van een model klasse werkt, alleen doe je regelmatig dubbel werk. De queries in de verschillende klassen zullen voor het grootste gedeelte gelijk zijn. Daarnaast mis je veel flexibiliteit. Zodra je in een controller of view een andere sortering van de gegevens wilt dien je gelijk aanpassingen te maken in de model klasse. Er is dus nog veel ruimte voor automatisering van een model klasse.
Dat laatste is iets waar ik veel belang aan ben gaan hechten. Dubbel werk moet niet nodig zijn als je een goede opbouw van je applicatie hebt, DRY dus. Helemaal als je gebruik gaat maken van object-georiënteerd programmeren. Versie 5 van PHP biedt de hiervoor voldoende mogelijkheden, al ben ik zelf meer een Ruby aanhanger aan het worden. Ruby is zelf een native OOP taal waardoor er veel meer mogelijk is op dat gebied.
Een goed design pattern voor het model is in mijn ogen ActiveRecord. Binnen verschillende frameworks wordt dit pattern toegepast en mijn ervaring is dat het geweldig werkt. Het biedt alle mogelijkheden om DRY te werken, maar je kunt altijd nog zelf je queries gaan schrijven als het nodig is.
Een simpel voorbeeld:
<?php
class Person extends ActiveRecord {
}
// SELECT * FROM persons WHERE id = 1
$person = Person::find(1);
echo $person->name;
// UPDATE persons SET name = 'Edwin V.' WHERE id = 1
$person->name = "Edwin V.";
$person->save;
?>
Zoals je ziet is het overbodig om queries te schrijven, ActiveRecord stelt zelf de queries samen. Het hangt van de implementatie van ActiveRecord af wat allemaal mogelijk is. De implementatie binnen Ruby on Rails biedt bijvoorbeeld functionaliteit voor associaties en gegevens validatie. Het Zend Framework biedt helaas geen implementatie van ActiveRecord aan, maar wel andere handige database klassen.
Een interessant overzicht van object-relational mapping implementaties in PHP is te vinden op de PHP wiki.
Een net zo belangrijk onderdeel van een applicatie is de weergave richting de gebruiker. Bij een webapplicatie gebeurd dat meestal in een browser, dus de weergave bestaat uit HTML en de bijbehorende afbeeldingen, JavaScripts en stylesheets. Daarnaast is het natuurlijk mogelijk om XML als weergave te hebben, voor bijvoorbeeld gebruik in een andere applicatie of als RSS feed.
Een laatste – redelijk nieuwe – techniek is JavaScript. In dat geval zal als reactie op een Ajax request vanuit de browser een stukje JavaScript terug gezonden worden om uitgevoerd te worden in de browser. Deze manier van werken wordt de laatste tijd meer toegepast door de introductie van RJS templates in Rails. Een korte introductie van RJS is te vinden op de weblog van Cody Fauser.
De belangrijkste taak van de view is dus de gegevens uit de controller omzetten naar een passend formaat. Hiervoor kan je vaak gebruik maken van een template engine zoals Smarty of Yapter. Mijn ervaring is dat template engines meestal overbodig zijn omdat PHP zelf al een perfecte template engine is.
<ul>
[LOOP $items]
<li>[$item]</li>
[ENDLOOP]
</ul>
<ul>
<? foreach($this->items as $item): ?>
<li><?= $item ?></li>
<? end; ?>
</ul>
Het bovenste voorbeeld zou uit een template engine kunnen komen. Het doet precies wat je wilt, maar absoluut niet meer. Het onderste voorbeeld is dezelfde code in PHP. Als nadeel kan je het aantal tekens noemen, maar dat weegt zeker op tegen de voordelen. Je kunt namelijk gewoon alle functionaliteit gebruiken die PHP te bieden heeft.
Een goede implementatie van een View is die van het Zend Framework. Hierbij ken je in de controller waarden toe aan variabelen in het view object. Deze variabelen kan je vervolgens weer gebruiken in je template.
Binnen templates kan het handig zijn om met helpers te werken. Dit principe kennen we onder andere van Rails en zit wederom in het Zend Framework. Helpers bevatten functies die veel gebruikte HTML tags genereren. Ze kunnen bijvoorbeeld helpen bij het samenstellen van een formulier voor een bepaald object.
De taak van de controller klassen is een request van een gebruiker omzetten naar een bepaalde weergave van de staat van het systeem. Een request van een gebruiker bestaat meestal uit een URL met daarin bepaalde informatie over de op te vragen gegevens. Een veel gebruikte opzet bestaat uit:
* Controller: De entiteit die opgevraagd wordt, bijvoorbeeld ‘person’
* Actie: De actie die uitgevoerd wordt op de entiteit, bijvoorbeeld ‘show’
* ID: De unieke identifier naar de entiteit, bijvoorbeeld ’1′.
Een aanvraag kan er dus uit zien als ‘index.php?controller=person&action=show&id=1′, maar vaak wordt gebruik gemaakt van nette URL’s en ziet de URL er zo uit: ‘/person/show/1′. Voor elke entiteit bestaat vervolgens een controller:
<?php
class PersonController extends ApplicationController {
public function showAction(){
// Show Person with given ID
}
}
?>
Een controller bevat voor elke mogelijke actie een methode. In het verleden heb ik wel gewerkt met speciale klassen voor elke actie, maar mijn ervaring is dat dit erg onoverzichtelijk is. Het is aan te raden om voor elke actie een methode te gebruiken.
Het omzetten van de URL naar een aanroep van een methode in een controller gaat meestal via routing. De implementatie van controllers in het Zend Framework bevat een RewriteRouter waarmee dit makkelijk te realiseren is. Zodra het routing gedaan is kan de methode de benodigde acties uitvoeren. Een voorbeeld van een controller met twee acties is:
<?php
class PersonController extends ApplicationController {
public function showAction(){
$view = new View();
$view->person = Person::find($this->params['id']);
return $view->parse('person/show.tpl');
}
}
?>
Zoals uit het artikel blijkt ben ik zelf een grote fan van Ruby on Rails en het Zend Framework. Het Rails framework geeft webdevelopers een kant-en-klare oplossing voor de opbouw van een webapplicatie. Zowel het MVC pattern als ActiveRecords is hierin terug te vinden, beide met een zeer goede implementatie.
Het Zend Framework is helaas nog in een beta status en dwingt niet de opbouw van een applicatie af. Ze geven echter wel een goede basis met verschillende klassen ter ondersteuning van MVC en object-relation mapping. Hoewel ik de laatste tijd veel werk met Ruby, zou ik het framework zeker gebruiken als basis voor een PHP applicatie.
De genoemde opbouw van een applicatie is in mijn ogen een manier om DRY te werken en toch voldoende flexibiliteit te hebben. De methode heeft al meerdere keren goede dienst bewezen bij mij. En om net zo’n pakkende conclusie als Tri Pham te geven:
Een perfecte opbouw van een webapplicatie bestaat niet, maar er naar streven doet geen vlieg kwaad.