Tutorials
Object georiënteerd denken
geupdate! OOP begint bij je denkwijze. Het heeft weinig met de techniek zelf te maken. Hier probeer ik je te laten beseffen hoe OOP werkt, in plaats van het je aan te leren.
Pagina 1
Besef wat objecten zijn
Besef
Deze tutorial is puur bedoeld om mensen inzicht en vooral besef te laten krijgen. Als je dit document doorleest zal er niet letterlijk staan hoe OOP werkt en misschien zijn bepaalde dingen nog wel discutabel ook, maar het gaat er om dat mensen besef krijgen van wat een object is en wat ie doet.
Denkwijze
OOP staat voor Object Oriented Programming; object geörienteerd programmeren. Het is een manier van programmeren en heeft voornamelijk met denkwijze te maken. PHP programmeurs denken vaak onterecht dat als ze een class gebruiken dat ze object geörienteerd bezig zijn.
Leven
Kijk eens naar het leven. Alles bestaat uit 'dingen', zoals mensen, computers, bomen, water, de lucht, dieren, etc. Die 'dingen' zou je ook objecten kunnen noemen. Ik als mens ben een onafhankelijk object, maar kan me wel binnen andere objecten begeven (kantoor) en ik kan andere objecten manipuleren (toetsenbord, computer). Zo manipuleer ik jouw hersenen terwijl je dit leest.
Deze tutorial is puur bedoeld om mensen inzicht en vooral besef te laten krijgen. Als je dit document doorleest zal er niet letterlijk staan hoe OOP werkt en misschien zijn bepaalde dingen nog wel discutabel ook, maar het gaat er om dat mensen besef krijgen van wat een object is en wat ie doet.
Denkwijze
OOP staat voor Object Oriented Programming; object geörienteerd programmeren. Het is een manier van programmeren en heeft voornamelijk met denkwijze te maken. PHP programmeurs denken vaak onterecht dat als ze een class gebruiken dat ze object geörienteerd bezig zijn.
Leven
Kijk eens naar het leven. Alles bestaat uit 'dingen', zoals mensen, computers, bomen, water, de lucht, dieren, etc. Die 'dingen' zou je ook objecten kunnen noemen. Ik als mens ben een onafhankelijk object, maar kan me wel binnen andere objecten begeven (kantoor) en ik kan andere objecten manipuleren (toetsenbord, computer). Zo manipuleer ik jouw hersenen terwijl je dit leest.
Pagina 2
Objecten herkennen
Objecten zijn dus losstaande dingen. Objecten hebben bepaalde eigenschappen. Zo heb ik blauwe ogen, ben ik 1.83m lang en houd ik van bier. Als ik nu mijn kleur ogen wil veranderen dan zal mijn lichaam dat moeten doen. Een object van buitenaf zou hooguit mijn ogen kunnen maskeren, maar niet daadwerkelijk de kleur veranderen. Misschien dat ik met een bepaalde injectie (=object) wel mijn kleur ogen kan veranderen; dan wordt er dus een object van buitenaf in mij geplaatst waardoor mijn lichaam zelf de kleuren van mijn ogen aanpast. Het spul uit de injectie komt in mijn lichaam dus behoort tot mijn lichaam.
Kort gezegd: objecten hebben bepaalde eigenschappen en die eigenschappen kunnen alleen door het object zelf worden gemanipuleerd.
- Objecten definieer je in een class
- Eigenschappen definieer je als variabelen bovenaan je class
- Gedrag/veranderingen/manipulatie van eigenschappen definieer je als methods in je class
Als je objecten uit m'n verhaaltje hierboven gaat vissen dan kom je tot iets van: ik, oog, bier, lichaam, kleur, injectie, injectiespul. Wat je hier ziet is dat "blauw" een eigenschap van "kleur" is, maar dat "kleur" weer een eigenschap van "oog" is en "oog" weer een eigenschap van "lichaam" en "lichaam" een eigenschap van "ik". Dus een eigenschap van een object kan op zichzelf ook weer een object zijn. Dat is zelfs heel gebruikelijk, zoals je ziet.
Klinkt ingewikkeld, maar zo is het leven ook. Ik hoop dat je nu in ieder geval een beetje inziet wat een object nou is. Want het is allemaal veel simpeler dan je denkt. Het is niets technisch.
Datum? Of ook Dag, Maand, Jaar? :S
Het is logisch dat je voor een datum een apart object maakt binnen je systeem, omdat een datum een vrij specifiek 'iets' is. Maar moet je nu ook een Dag, Maand en Jaar object maken? Dit is de vraag waar men bij de reacties van deze tutorial mee zat, dus vandaar deze toevoeging.
Je kunt bedenken: heeft een dag, maand of jaar zelf nog specifieke eigenschappen? Op zich wel, want een dag valt binnen de range(1,31), een maand binnen de range(1,12) en elke maand heeft een unieke naam (januari, februari, etc). Maar het zijn wel constante waardes waar verder niet veel spannends mee gebeurt. En de date()-functie van PHP kan al erg veel.
In dit geval raad ik aan gewoon het Datum-object eens te maken en dan te kijken of je code flexibeler wordt van aparte dag/maand/jaar-objecten of dat het juist een rotzooitje wordt.
Je moet gewoon kijken of het nut heeft een laag dieper te gaan, of het nut heeft om nog meer objecten te maken voor sub-onderdelen. Als je in je Datum-object allemaal variabelen moet gaan setten die specifiek voor een Dag gelden, of voor een Maand, dan ben je dus te veel binnen één object bezig.
Dit soort dingen is gewoon een beetje logisch nadenken en kijken of je niet meerdere objecten aan het definiëren bent binnen één object. Als je meerdere functies krijgt als formatDay(), forwardDay($days), resetDay(), etc, dan zal je wel een apart object moeten gebruiken. Want dan zijn de methods niet meer het Datum-object aan het manipuleren, maar het (niet-bestaande & te creëren) Dag-object.
Strict gezien moet je wel aparte Dag-, Maand en Jaarobjecten maken omdat het op zichzelf 'dingen' zijn.
Kort gezegd: objecten hebben bepaalde eigenschappen en die eigenschappen kunnen alleen door het object zelf worden gemanipuleerd.
- Objecten definieer je in een class
- Eigenschappen definieer je als variabelen bovenaan je class
- Gedrag/veranderingen/manipulatie van eigenschappen definieer je als methods in je class
Als je objecten uit m'n verhaaltje hierboven gaat vissen dan kom je tot iets van: ik, oog, bier, lichaam, kleur, injectie, injectiespul. Wat je hier ziet is dat "blauw" een eigenschap van "kleur" is, maar dat "kleur" weer een eigenschap van "oog" is en "oog" weer een eigenschap van "lichaam" en "lichaam" een eigenschap van "ik". Dus een eigenschap van een object kan op zichzelf ook weer een object zijn. Dat is zelfs heel gebruikelijk, zoals je ziet.
Klinkt ingewikkeld, maar zo is het leven ook. Ik hoop dat je nu in ieder geval een beetje inziet wat een object nou is. Want het is allemaal veel simpeler dan je denkt. Het is niets technisch.
Datum? Of ook Dag, Maand, Jaar? :S
Het is logisch dat je voor een datum een apart object maakt binnen je systeem, omdat een datum een vrij specifiek 'iets' is. Maar moet je nu ook een Dag, Maand en Jaar object maken? Dit is de vraag waar men bij de reacties van deze tutorial mee zat, dus vandaar deze toevoeging.
Je kunt bedenken: heeft een dag, maand of jaar zelf nog specifieke eigenschappen? Op zich wel, want een dag valt binnen de range(1,31), een maand binnen de range(1,12) en elke maand heeft een unieke naam (januari, februari, etc). Maar het zijn wel constante waardes waar verder niet veel spannends mee gebeurt. En de date()-functie van PHP kan al erg veel.
In dit geval raad ik aan gewoon het Datum-object eens te maken en dan te kijken of je code flexibeler wordt van aparte dag/maand/jaar-objecten of dat het juist een rotzooitje wordt.
Je moet gewoon kijken of het nut heeft een laag dieper te gaan, of het nut heeft om nog meer objecten te maken voor sub-onderdelen. Als je in je Datum-object allemaal variabelen moet gaan setten die specifiek voor een Dag gelden, of voor een Maand, dan ben je dus te veel binnen één object bezig.
Dit soort dingen is gewoon een beetje logisch nadenken en kijken of je niet meerdere objecten aan het definiëren bent binnen één object. Als je meerdere functies krijgt als formatDay(), forwardDay($days), resetDay(), etc, dan zal je wel een apart object moeten gebruiken. Want dan zijn de methods niet meer het Datum-object aan het manipuleren, maar het (niet-bestaande & te creëren) Dag-object.
Strict gezien moet je wel aparte Dag-, Maand en Jaarobjecten maken omdat het op zichzelf 'dingen' zijn.
Pagina 3
Foute denkwijze
Men denkt dus vaak: class = OOP. Voor de volledigheid, een class is een blauwdruk van een object. Dus het beschrijft hoe een object gemaakt gaat worden, maar het is op zichzelf geen object. Daarom maak je dus instanties van een class, die instanties noem je objecten.
Voorbeeld van géén OOP:
<?php
class Gastenboek
{
public function __construct() {
}
public function getReacties() {
}
public function insertReactie() {
}
public function printReacties() {
}
public function getReactie($id) {
}
public function getUsers() {
}
public function nextPage() {
}
}
?>
Nu denk je misschien: hoe kun je nou zeggen dat dit geen OOP is zonder te zien wat het doet? Simpel. Je ziet aan de namen van de methods al direct dat ze:
- reacties manipuleren
- één reactie kunnen ophalen
- users kunnen ophalen
- iets met een volgende pagina kunnen doen
Je zou hier dus sowieso al te maken krijgen met de objecten Reactie en User. Je zou zeggen dat je ook het object Page krijgt, maar de pagina is slechts een eigenschap van het Gastenboek of zelfs gewoon een tijdelijk iets.
Aan de functienamen kun je dus meestal herkennen dat het geen goed object is. Daarnaast kun je het vaak herkennen aan het aantal regels code. De meeste objecten bestaan echt maar uit maximaal 100 regels, meestal een stuk minder. Dit komt omdat elk object slechts z'n eigen functionaliteit definieert en verder alles aan de andere objecten overlaat. Je zult ook zien dat je dan heel weinig if's en else's krijgt en weinig loops en dergelijke.
Elk zelfstandig naamwoord is een object. Fiets. Plant. Gastenboek. Reactie. Formulier. Gebruiker. SMS. Connection. Filter. Validator. Etc. Meestal zie je aan de naam van een class al of het kans van slagen heeft. Als je een class Reacties hebt dan hoor je al aan de naam dat dit niet één object is. Dus geen object. Nu worden dit soort constructies in de praktijk wel gebruikt om meerdere Reactie-objecten in te bewaren (die bijv. uit de database komen), maar dat moet je pas gaan gebruiken als je zeker weet dat je 100% snapt wat je doet.
Voorbeeld van géén OOP:
<?php
class Gastenboek
{
public function __construct() {
}
public function getReacties() {
}
public function insertReactie() {
}
public function printReacties() {
}
public function getReactie($id) {
}
public function getUsers() {
}
public function nextPage() {
}
}
?>
Nu denk je misschien: hoe kun je nou zeggen dat dit geen OOP is zonder te zien wat het doet? Simpel. Je ziet aan de namen van de methods al direct dat ze:
- reacties manipuleren
- één reactie kunnen ophalen
- users kunnen ophalen
- iets met een volgende pagina kunnen doen
Je zou hier dus sowieso al te maken krijgen met de objecten Reactie en User. Je zou zeggen dat je ook het object Page krijgt, maar de pagina is slechts een eigenschap van het Gastenboek of zelfs gewoon een tijdelijk iets.
Aan de functienamen kun je dus meestal herkennen dat het geen goed object is. Daarnaast kun je het vaak herkennen aan het aantal regels code. De meeste objecten bestaan echt maar uit maximaal 100 regels, meestal een stuk minder. Dit komt omdat elk object slechts z'n eigen functionaliteit definieert en verder alles aan de andere objecten overlaat. Je zult ook zien dat je dan heel weinig if's en else's krijgt en weinig loops en dergelijke.
Elk zelfstandig naamwoord is een object. Fiets. Plant. Gastenboek. Reactie. Formulier. Gebruiker. SMS. Connection. Filter. Validator. Etc. Meestal zie je aan de naam van een class al of het kans van slagen heeft. Als je een class Reacties hebt dan hoor je al aan de naam dat dit niet één object is. Dus geen object. Nu worden dit soort constructies in de praktijk wel gebruikt om meerdere Reactie-objecten in te bewaren (die bijv. uit de database komen), maar dat moet je pas gaan gebruiken als je zeker weet dat je 100% snapt wat je doet.
Pagina 4
Inheritance
Inheritance (overerving) is het hele "x extends y"-verhaal. Je hebt parents en children. De vraag nu dus is hoe je dat nou goed hanteert. Dat leg ik hier uit.
Vaak is een object ook een ander object. Zo is het object PHPerik ook een object Mens. Mens is ook een Dier en Dier is ook een Organisme. In PHP zeg je dan eigenlijk: PHPerik extends Mens ... Mens extends Dier ... Dier extends Organisme.
Nu is de PHPerikregel: je mag alleen extenden "als X ook een Y is". Dus als je dat letterlijk kan zeggen, dan mag het, en anders niet.
Vb:
- MySQL extends Database -> mag, want MySQL is ook een Database
- MySQLQuery extends MySQL -> mag niet, want MySQLQuery is geen MySQL. Je mag wel een MySQLQuery-object maken, maar hij mag MySQL niet extenden. Als je dan gezeik krijgt met variabelen (zoals databaseconnection) die je moet hebben in MySQLQuery, dan zit er ergens een fout in je ontwerp. Connecties kun je gewoon doorgeven aan een class, want een connectie is op zichzelf alweer een object.
- Formulier extends DOMElement -> dit hangt er heel erg van af of je hier puur het HTML-element <form> bedoelt of dat je de technische representatie van een formulier bedoeld (zoals de data of functionaliteit).
- Konijn extends Dier -> mag
- Dier extends Organisme -> mag
- Konijn extends Organisme -> mag ook
- Dier extends Konijn -> mag niet
Dag extends Maand?
Een dag kan nooooit een maand extenden. Zie de PHPerikregel: "Is een dag ook een maand?" -> nee!. Dus je kan 'm absoluut niet extenden. Stel dat je een class Maand maakt dan bevat die class wel een array met allemaal Dag-objecten. Want een maand bevat wèl meerdere dagen. Maar een dag is geen maand.
Wat wel zou kunnen is: "Januari extends Maand". Januari is namelijk wèl een maand.
Voorbeeldje hoe je Dag-objecten in je Maand-object zouden kunnen zitten:
<?php
class Month
{
const JANUARY = 1;
// etc
const AUGUST = 8;
const $monthdays = array(1 => 31, 2 => 28, etc); // date() kan ook
protected $days = array(); // array met Day-objecten, voor elke dag één
public function __construct($month)
{
// kijk hoeveel dagen $month (in dit geval augustus)
// heeft en maak (in dit geval 31) Day-objecten
$this->makeDays( $this->monthdays[$month] );
}
protected function makeDays($amount)
{
for ($i = 1; $i <= $amount; $i++) {
$day = new Day($i);
$this->days[$i] = $day;
}
}
}
$month = new Month(Month::AUGUST); // of gewoon new Month(8)
?>
Vaak is een object ook een ander object. Zo is het object PHPerik ook een object Mens. Mens is ook een Dier en Dier is ook een Organisme. In PHP zeg je dan eigenlijk: PHPerik extends Mens ... Mens extends Dier ... Dier extends Organisme.
Nu is de PHPerikregel: je mag alleen extenden "als X ook een Y is". Dus als je dat letterlijk kan zeggen, dan mag het, en anders niet.
Vb:
- MySQL extends Database -> mag, want MySQL is ook een Database
- MySQLQuery extends MySQL -> mag niet, want MySQLQuery is geen MySQL. Je mag wel een MySQLQuery-object maken, maar hij mag MySQL niet extenden. Als je dan gezeik krijgt met variabelen (zoals databaseconnection) die je moet hebben in MySQLQuery, dan zit er ergens een fout in je ontwerp. Connecties kun je gewoon doorgeven aan een class, want een connectie is op zichzelf alweer een object.
- Formulier extends DOMElement -> dit hangt er heel erg van af of je hier puur het HTML-element <form> bedoelt of dat je de technische representatie van een formulier bedoeld (zoals de data of functionaliteit).
- Konijn extends Dier -> mag
- Dier extends Organisme -> mag
- Konijn extends Organisme -> mag ook
- Dier extends Konijn -> mag niet
Dag extends Maand?
Een dag kan nooooit een maand extenden. Zie de PHPerikregel: "Is een dag ook een maand?" -> nee!. Dus je kan 'm absoluut niet extenden. Stel dat je een class Maand maakt dan bevat die class wel een array met allemaal Dag-objecten. Want een maand bevat wèl meerdere dagen. Maar een dag is geen maand.
Wat wel zou kunnen is: "Januari extends Maand". Januari is namelijk wèl een maand.
Voorbeeldje hoe je Dag-objecten in je Maand-object zouden kunnen zitten:
<?php
class Month
{
const JANUARY = 1;
// etc
const AUGUST = 8;
const $monthdays = array(1 => 31, 2 => 28, etc); // date() kan ook
protected $days = array(); // array met Day-objecten, voor elke dag één
public function __construct($month)
{
// kijk hoeveel dagen $month (in dit geval augustus)
// heeft en maak (in dit geval 31) Day-objecten
$this->makeDays( $this->monthdays[$month] );
}
protected function makeDays($amount)
{
for ($i = 1; $i <= $amount; $i++) {
$day = new Day($i);
$this->days[$i] = $day;
}
}
}
$month = new Month(Month::AUGUST); // of gewoon new Month(8)
?>
Pagina 5
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:
<?php
// 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:
<?php
interface ValidationInterface {
public function validate($sData);
}
?>
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:
<?php
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.
<?php
// 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:
<?php
// 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:
<?php
// PHPerik-regel: "Is ValidationException ook een Exception?" -> ja
class ValidationException extends Exception
{
// In de praktijk kun je soms deze constantes beter elders definiëren
const EMPTY = 'Element is empty';
const INCORRECT_EMAIL = 'This is not an e-mail address';
}
?>
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!
Hier een stukje van hoe je code er uit zou kunnen zien als je alles wel netjes met objecten doet:
<?php
// 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:
<?php
interface ValidationInterface {
public function validate($sData);
}
?>
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:
<?php
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.
<?php
// 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:
<?php
// 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:
<?php
// PHPerik-regel: "Is ValidationException ook een Exception?" -> ja
class ValidationException extends Exception
{
// In de praktijk kun je soms deze constantes beter elders definiëren
const EMPTY = 'Element is empty';
const INCORRECT_EMAIL = 'This is not an e-mail address';
}
?>
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!
Reacties
0