Praktijk (geupdate)
Hoe je dit in de praktijk gaat toepassen moet je zelf uitvogelen. Ik kan wel weer een voorbeeld geven met Mens-Dier-Organisme of met auto's ofzo, maar dat is niet leerzaam. Dan ga je namelijk op het voorbeeld af. Deze tutorial draait puur om besef. Bij elke class die je maakt of juist niet maakt moet je bedenken: waarom maak ik deze (niet)? Is het wel een object? Wat zijn de afhankelijkheden van dit object?
Hier een stukje van hoe je code er uit zou kunnen zien als je alles wel netjes met objecten doet:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Onderstaand stukje zit ook in een class, bijvoorbeeld in een Controller (Google Model View Controller)
$requestData = new RequestFilter($_POST);
$_POST = $requestData->filter();
$validator = new Validator($_POST);
$validator->setValidator('name', new Validator_Not_Empty());
$validator->setValidator('email', new Validator_Email());
if (!$validator->validate()) {
throw new ValidationException($validator->getErrors());
}
$newUser = new User();
$newUser->setName($_POST['name']);
$newUser->setEmail($_POST['email']);
$newUser->insert();
$stat = new RegistrationStat();
$stat->setUser($newUser); // hier wordt een object meegegeven
$stat->setConfirmed($_POST['confirmed']);
$stat->insert();
$smarty = new Smarty();
$smarty->assign('contents', 'Bedankt voor het registreren');
$smarty->assign('last_user', $user->getId());
$smarty->assign('reg_date', $stat->getDate());
$smarty->display('thankyou.tpl');
?>
Probeer maar eens bovenstaand stukje na te maken. Bijvoorbeeld het validatiegedeelte.
Update 7 aug 2008
Ik heb veel vragen gekregen over hoe je dan zo'n validatieopzet zou kunnen maken. Hier een voorbeeld van hoe je dat zou kunnen doen.
Je maakt een interface van hoe je wil dat alle validator-adapters (zoals Validator_Email, etc) er uit moeten zien. Hier is de essentie gewoon dat elke adapter een bepaalde waarde kan valideren. Een simpele interface dus:
Je hoofdklasse Validator gaat uiteindelijk alle adapters aanroepen en verzorgt het hele validatieproces. Maar de Validator laat het valideren zelf aan de adapters over. Vanwege de interface kunnen we met setValidator() geen foute adapters toevoegen aan de Validator. Kijk maar goed:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Validator
{
...
...
// Type hinting: ValidationInterface
// Zo kun je geen object meegeven dat niet voldoet aan de ValidationInterface
public function setValidator($sKey, ValidationInterface $oValidator) {
...
}
public function validate() {
// $this->aData is dus een kopie van $_POST in bovenstaand geval
foreach ($this->aData as $sKey => $sValue) {
// Als er voor deze key geen Validator is geset (met setValidator(key, validator))
if (!$this->hasValidator($sField)) {
continue;
}
// Verkrijg het object dat tijdelijk in een array is gestopt (dus bijv. Validator_Email-object)
$oValidator = $this->getValidator($sKey);
// Kijk of valideren lukt
try {
// Hier geef je dus de daadwerkelijke data mee aan het lege validatieobject dat je bij setValidator() al hebt meegegeven
$oValidator->validate($sValue);
} catch (ValidationException $e) {
$this->aErrorlist[$sKey] = $e->getMessage();
}
}
return empty($this->aErrorlist);
}
}
?>
De adapters moeten een validate()-method hebben vanwege de interface. Verder kun je er mee doen wat je wil.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Deze class is een adapter
class Validator_Not_Empty implements ValidationInterface
{
private $bTrim = false;
// Bij true wordt tijdelijk de whitespace weggehaald zodat een spatie ook empty is
public function __construct($bTrim = false) {
$this->bTrim = $bTrim;
}
public function validate($sData) {
if ($this->bTrim) {
$sData = trim($sData);
}
if (empty($sData)) {
throw new ValidationException($sData, ValidationException::EMPTY); // kan misschien mooier, dit is een voorbeeld
}
return true;
}
}
?>
En hier een simpele:
2
3
4
5
6
7
8
9
10
11
12
13
14
// Deze class is een adapter
class Validator_Email implements ValidationInterface
{
public function validate($sData) {
if (!preg_match('/^[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\.)+(?:[A-Z]{2,6})$/i', $sData)) {
throw new ValidationException($sData, ValidationException::INCORRECT_EMAIL);
}
return true;
}
}
?>
Het zou kunnen dat je het praktischer vindt gewoon 'false' te returnen in plaats van een exception te gooien. Dit is een keuze en hangt een beetje van je programmeerwijze af. Ik vind het voordeel van hier een exception gooien dat je meer informatie mee zou kunnen geven als je dat wilt.
Voor de volledigheid ook nog even de opzet van je Exception-class:
Zo zie je maar weer dat we voor een simpele opzet voor validatie alweer meerdere classes en een interface schrijven. Als je nou nog 10 manieren van validatie wil toevoegen dan krijg je dus ook weer 10 classes erbij. Mooie voorbeelden zouden zijn:
- Validator_In_Range()
- Validator_In_Array()
- Validator_Is_Int()
- Validator_Is_Date()
- Validator_Is_Product() // in een webwinkel bijvoorbeeld
- ...
Naamgevingen
Wat betreft naamgevingen, bekijk bijvoorbeeld de Zend standaard eens. Het is naar mijn mening het meest logisch om bij de namen van methods te denken aan wat het doet. Gebiedende wijs dus. Stuur! Maak! Lig! Update! etc. Verder is Engels meestal gewoon een internationale standaard, maar ook een beetje eigen smaak.
Tips
Als indicatie: voor een niet al te groot webwinkelsysteem met CMSje heb je meestal wel een stuk of 50 objecten, maar het kunnen er even goed 500 zijn. Je kunt in principe elke array met data in een object zetten vanwege flexibiliteit.
Ik zou graag nog even willen voorstellen dat je kijkt naar het Zend Framework, puur omdat het heel goed OOP in elkaar zit. Of je het gebruikt moet je zelf weten.
Daarnaast heb je bij een volledig object geörienteerd systeem al je code binnen classes staan. Op de zogenaamde 'bootstrap' (= één klein index.php'tje) na waar je de hoofdclass(es) aanroept.
Kijk af
Goed OOP leren is net als leren programmeren. Kijk af van anderen en ga dingen aanpassen. Kijk waar flexibiliteit ligt en waar niet. Vergeet nooit het besef dat je moet houden bij elke stap die je zet.
Google eens het Model View Controller design pattern. Dit is een wijze van hoe je je systeem in elkaar zet en wordt bij professioneel webdevelopment eigenlijk bijna overal toegepast. In Java is dit eigenlijk ook de standaard om alles te ontwikkelen (feitelijk kun je er amper omheen bij Java).
CakePHP is bijvoorbeeld een MVC-framework waar vrij makkelijk mee te beginnen is (vooral t.o.v. Zend Framework).
Succes!