et Zend Framework bestaat volledig uit objectgeoriënteerde PHP-code. Om het Zend Framework goed te kunnen begrijpen en te kunnen gebruiken, moet u minimaal de basis van objectgeoriënteerd programmeren (OOP) beheersen. Omdat dit niet direct is gerelateerd aan Zend Framework, hebben we besloten deze stof in deze appendix op te nemen. Indien u het gevoel hebt dat u nog niet volledig thuis bent in de wondere wereld van OOP en ontwerppatronen, raden wij u aan dit hoofdstuk te lezen voordat u zich stort op Zend Framework.
Deze appendix hoort bij Leer jezelf PROFESSIONEEL Werken met het Zend Framework, geschreven door Wouter Tengeler en Matthijs van den Bos, uitgegeven door Van Duuren Media (2010), ISBN 978-90-5940-446-5. © Copyright 2010 Van Duuren Media
Deza appendix behandelt de basisbegrippen van objectgeoriënteerd programmeren. U leert wat een klasse is en wat objecten zijn, hoe u de toegang tot een klasse kunt beperken om beter onderhoudbare en uitbreidbare klassen te schrijven. U leert wat abstracte klassen zijn en hoe u interfaces kunt gebruiken. Verder gaan we in op een aantal ontwerppatronen (design patterns) die van belang zijn voor het goed begrijpen van de werking Zend Framework.
2
Appendix A Objectgeoriënteerd programmeren
Wat objectgeoriënteerd programmeren (OOP) is De term objectgeoriënteerd programmeren is eigenlijk fout. Een betere term zou eigenlijk klassengeörienteerd programmeren (class oriented programming of COP) zijn. We programmeren namelijk helemaal geen objecten maar we programmeren PHP-code die we door het keyword class vooraf laten gaan. Klassen zijn de bestanden die we in onze PHP-scripts includen en waar PHP objecten van kan maken. Objecten zijn de ‘voorwerpen’ die in het geheugen van de computer staan tijdens het uitvoeren van een script. Het idee om grotere problemen op te delen in kleinere stukken die ieder voor zich eenvoudiger zijn op te lossen, is natuurlijk niet nieuw. Al sinds het begin van de moderne computer rond 1960 besefte men dat het niet mogelijk is om als mens in één keer grote problemen te doorzien. Een probleem is in dit geval een vraagstuk dat moet worden opgelost. Bijvoorbeeld: hoe bouw ik een weblogapplicatie? Door de weblogapplicatie op te delen in hapklare blokken en deze blokken los van elkaar te ontwikkelen en op een later tijdstip met elkaar te integreren, is het een stuk eenvoudiger geworden. Ieder blokje doet zijn kleine deel van de applicatie. Door de blokjes onderling met elkaar te laten communiceren, ontstaat één systeem. Een groot voordeel van het ontwikkelen van kleine blokjes functionaliteit die volledig op zichzelf staan, is dat deze blokjes in andere projecten weer opnieuw gebruikt kunnen worden. Dit hergebruik van klassen is één van de drie speerpunten van objectgeoriënteerd programmeren. De twee andere speerpunten zijn onderhoudbaarheid en leesbaarheid. Een klasse bestaat uit twee onderdelen: gegevens en functionaliteit. De gegevens (of data) wordt bewaard in speciale variabelen
Leer jezelf PROFESSIONEEL Werken met het Zend Framework
genaamd membervariabelen. Dit zijn variabelen die door alle functies in de klasse kunnen worden gebruikt. De functionaliteit van een klasse manipuleert de gegevens zodat het informatie wordt. Om dit te verduidelijken geven we het volgende voorbeeld: Het getal 25 zegt in principe niets. We weten dat het een getal is en dat we het kunnen opslaan in een variabele van het type integer, maar verder hebben we geen idee wat het kan betekenen. Als dit getal het resultaat is van een functie berekenLeeftijd in de klasse Persoon dan krijgen we al een idee wat het getal 25 zou kunnen betekenen. De klasse persoon heeft misschien een variabele geboortedatum. De functie berekenLeeftijd kan de huidige datum opvragen en samen met de geboortedatum de leeftijd van een persoon berekenen. De variabelen geboortedatum en huidigedatum zijn gegevens, de leeftijd is informatie. In feite is informatie niets anders dan het in context plaatsen van gegevens. Door gegevens en functionaliteit op logische wijze bij elkaar te plaatsen in een klasse, programmeren we objectgeoriënteerd.
De basis van objectgeoriënteerd programmeren Klassen en objecten Klasse Een klasse is de blauwdruk van een object. Deze beschrijft de gegevens die in het object worden bijgehouden en de functionaliteit van het object.
In de klasse beschrijven we de werking van een object. De klasse wordt door PHP ingelezen en vertaald (parsed). U kunt zich een klasse voorstellen als een mal in een fabriek waarmee voorwerpen (bijvoorbeeld een fiets of een vaas) worden geperst. De mal bepaalt de vorm van het voorwerp, maar pas als het voorwerp is gemaakt kunt u het gebruiken. Het voorwerp dat uit een klasse wordt gemaakt noemen we in OO-termen een instantie (instance).
3
4
Appendix A Objectgeoriënteerd programmeren
Net als in een fabriek kunnen er meer objecten uit dezelfde klasse (mal) worden gemaakt. Al deze objecten hebben dan dezelfde eigenschappen en functionaliteit. Een vaas heeft bijvoorbeeld als eigenschappen een kleur en een materiaal. Pas als er een vaas uit de mal komt krijgen de eigenschappen een definitieve waarde. Het materiaal is dan bijvoorbeeld glas en de kleur is blauw. Een andere vaas (een andere instantie) heeft als materiaal aardewerk en als kleur wit terwijl deze uit dezelfde mal komt. Sommige eigenschappen van een object zijn tijdens de levensduur van het object te wijzigen. U kunt bijvoorbeeld besluiten de vaas een andere kleur te geven door hem te verfen. Andere eigenschappen zijn constant. U kunt de glazen vaas niet plotseling van plastic laten zijn. Eigenschappen van een object noemen we properties.
Membervariabelen Vanaf het moment dat er een object in het geheugen aanwezig is, zijn de properties beschikbaar in de vorm van membervariabelen. Die variabelen horen specifiek bij het object dat zojuist is gemaakt. We kunnen de membervariabelen van het object benaderen door de volgende constructie: $referentievar->membervar. Als voorbeeld nemen we een vereenvoudigde versie van de BlogPost-klasse die we elders in het boek gebruiken. class BlogPost { public $title; public $header; public $body; /** * constructor * @return BlogPost */ public function __construct() { $this->title = ‘’;
Object en klasse Een object is een instantie van een klasse. Het is alleen in gebruik tijdens het uitvoeren van de PHP-code. Van een klasse kunnen meerdere instanties tegelijk in gebruik zijn. Ieder object heeft een unieke identificatie waarmee het van de andere objecten van hetzelfde type kan worden onderscheiden.
Leer jezelf PROFESSIONEEL Werken met het Zend Framework
$this->header = ‘’; $this->body = ‘’; }
public function save() { // doe database acties om de gegevens // van een blogpost op te slaan } }
Membervariabele Een membervariabele is een variabele die één op één is gekoppeld aan een object. Een membervariabele is te benaderen door $objectVar->membervariabele.
In deze code hebben we een klasse gedefinieerd met daarin drie membervariabelen en twee functies. Het is gebruikelijk (en zelfs zeer aan te raden) om de membervariabelen te definiëren direct na het begin van de klasse maar voor de eerste functie. U kunt membervariabelen herkennen aan het keyword public, protected of private voor de naam van de variabele. Een membervariabele die wordt gedefinieerd zoals in bovenstaande code krijgt nog geen type (zoals alle variabelen in PHP pas een type krijgen als aan deze variabele een waarde wordt toegekend). Het is verstandig de variabelen zo snel mogelijk een waarde van het juiste type te geven. In dit geval gebeurt dat in de functie __construct. U ziet dat we een membervariabele altijd benaderen door deze vooraf te laten gaan door de speciale variabele $this met daarachter een pijltje (-> een minteken met direct daarachter een groterdanteken). Omdat een klasse eigenlijk een type is net zoals integer of string, kunnen we meerdere variabelen van hetzelfde type maken. $post1 = new BlogPost(); $post2 = new BlogPost();
$post1 en $post2 zijn nu twee verschillende variabelen van hetzelfde type. Iedere variabele bevat zijn eigen object (instantie). Iedere variabele heeft dus zijn eigen membervariabelen $title, $header en $body. Omdat de namen van de membervariabelen gelijk zijn moeten we tegen PHP vertellen welke $title we willen gebruiken. Daarom is het nodig dat
5
6
Appendix A Objectgeoriënteerd programmeren
we de membervariabele vooraf laten gaan door het object waarvan we de variabele willen gebruiken. $post1->title of $post2->title.
Methoden We hebben gezien dat een klasse (en dus een object) membervariabelen heeft. In deze variabelen wordt de data van het object opgeslagen. Naast data heeft een object ook nog functionaliteit. De functionaliteit is nodig om de data van het object te manipuleren. We hebben de mogelijkheid om binnen klassen functies te definiëren. Deze functies zijn dan niet globaal zoals gewone functies, maar zijn gekoppeld aan de klasse waar de functie in is geplaatst. Het is de bedoeling dat een functie binnen een klasse alleen data van die klasse manipuleert, daarbij eventueel gebruik makend van lokale variabelen. Een functie binnen een klasse heeft in OO-termen de speciale naam ‘methode’. Het verschil tussen een ‘gewone’ functie en een methode is dus dat hij is gekoppeld aan een klasse terwijl een ‘gewone’ functie overal vandaan gebruikt kan worden. We kunnen in PHP meerdere methoden met dezelfde naam hebben mits deze in verschillende klassen zijn gedefiniëerd. De functie __construct en save van het bovenstaande voorbeeld noemen we dus methoden van de klasse BlogPost. Omdat een methode is gedefinieerd binnen een klasse krijgt deze toegang tot de membervariabelen van die klasse via de $this variabele.
Constructor U ziet dat we, nadat een object hebben gemaakt, de membervariabelen een waarde kunnen geven. PHP geeft alle variabelen een standaard waarde (unset). Het nadeel van de waarde unset is dat deze geen type heeft. We willen eigenlijk dat onze objecten altijd gevuld zijn met geldige waarden. Een mem-
Methode Een methode is een functie die binnen een klasse is gedefiniëerd en die toegang heeft tot de data van het object waar de methode bij hoort.
Leer jezelf PROFESSIONEEL Werken met het Zend Framework
bervariabele waar een getal in hoort te staan willen we met een getal initialiseren en een membervariabele die een string hoort te bevatten willen we met een string initialiseren. Het voordeel hiervan is dat we allerlei controles van het type dan niet meer iedere keer hoeven te doen zoals we later zullen zien.
Constructor De constructor heeft in PHP 5 altijd de naam __construct en wordt automatisch aangeroepen op het moment dat het commando new wordt uitgevoerd. In de constructor kunt u membervariabelen initialiseren en eventueel acties uitvoeren die altijd moeten gebeuren.
PHP kent een speciale methode die automatisch wordt aangeroepen als een object wordt gemaakt. Deze methode wordt de constructor genoemd. In PHP 4 heeft deze methode de naam van de klasse, maar in PHP 5 heeft Zend gemeend de naam te moeten veranderen in __construct(). Dit is een zogenaamde magische functie. De constructor is bedoeld om initialisatie van het object uit te voeren. In ons voorbeeld hebben we een constructor opgenomen waarin we alle membervariabelen een beginwaarde geven. Het is ook mogelijk andere acties uit te voeren binnen de constructor. Let op dat de constructorfunctie maar één keer wordt uitgevoerd (namelijk bij het instantiëren van het object) en daarna nooit meer. In de constructor kunt u dus alleen acties doen die éénmalig op het object uitgevoerd moeten worden.
Destructor De destructor is een magische functie met de naam __destruct. Deze wordt automatisch aangeroepen vlak voordat het object uit het geheugen wordt verwijderd. In de destructor kunt u afsluitende acties uitvoeren.
PHP 5 kent nog een magische functie die automatisch wordt aangeroepen vlak voordat het object uit het geheugen wordt verwijderd. Deze magische functie heet __ destruct(). In deze zogenaamde destructor kunt u de nodige opruimacties doen. U kunt bijvoorbeeld gewijzigde data nog snel in de database zetten of een array met veel data netjes opschonen. Het is in PHP mogelijk de constructor en/of de destructor weg te laten. PHP zal dan een standaard constructor of destructor uitvoeren. Deze hebben geen merkbare functionaliteit. Net als bij andere functies is het mogelijk om de constructor parameters mee te geven.
7
8
Appendix A Objectgeoriënteerd programmeren
Deze parameters kunnen dan worden gebruikt bij het initialiseren van het object. class BlogPost { [...] public function __construct($p_title, $p_header, $p_body) { $this->title = $p_title; $this->header = $p_header; $this->body = $p_body; } [...] }
U kunt deze parameters meegeven in de regel waar u het object maakt: $post1 = new BlogPost(‘Een blogpost’, ‘Dit is de header’, ‘Dit is de bodytekst’); $post2 = new BlogPost(“Tweede blogpost”, ‘Nog een header’, “bla bla”);
Op deze manier kunt u het object in één keer initialiseren zonder eerst alle membervariabelen expliciet te moeten zetten.
Encapsulation Een belangrijk begrip binnen objectgeoriënteerd programmeren is encapsulation ofwel afscherming. Om goede herbruikbare code te schrijven moeten we proberen een object zo onafhankelijk mogelijk te laten zijn. We bedoelen hiermee onafhankelijk van code die buiten de klasse staat. Als een klasse niet gebruik maakt van code buiten de klasse, kunnen we het bestand (iedere klasse krijgt zijn eigen bestand) eenvoudig kopiëren naar een volgend project. We hoeven dan niet bang te zijn dat de code niet meer werkt, want het staat volledig op zichzelf. Dit concept noemen we encapsulation.
Leer jezelf PROFESSIONEEL Werken met het Zend Framework
Toegang tot variabelen In PHP 4 was het nog niet mogelijk variabelen in een object werkelijk af te schermen van andere code buiten het object. Het werd aan de discipline van de programmeurs overgelaten geen interne variabelen van een object te gebruiken. Met de komst van het nieuwe objectmodel in PHP 5 is het mogelijk variabelen werkelijk fysiek af te schermen. Hiermee hebben we een sterk middel in handen om beter te programmeren volgens de OO-methodiek. Net als veel andere object georiënteerde programmeertalen kent PHP 5 drie verschillende vormen van variabelenafscherming: I Private Een variabele die is gedefinieerd met het keyword private kan alleen worden gebruikt binnen de klasse waarin deze variabele is gedefinieerd. Als code buiten deze klasse probeert de variabele direct te benaderen, dan volgt een foutmelding. I Protected Een variabele kan protected worden gedefinieerd om aan te geven dat deze alleen binnen de klasse zelf gebruikt mag worden. Het verschil met private ligt in het feit dat de variabele ook in eventuele afgeleide klassen (zie Overerving) gebruikt mag worden. I Public Een variabele die public is gedefinieerd (wat de standaard is als er niets wordt opgegeven), is overal te gebruiken. Dus ook buiten de klasse. class BlogPost { public $title; private $header; protected $body; […] }
9
10
Appendix A Objectgeoriënteerd programmeren
Als we onze BlogPost-klasse aanpassen en de membervariabelen andere toegangsoort (access level) geven, dan kunnen we bekijken wat de gevolgen van deze verandering zijn voor code van buiten de klasse. $post1 = new BlogPost(); $post1->title = ‘Dit is de titel’;
De code werkt nog steeds, omdat de membervariabele title publiek is gedefinieerd. $post1->header = ‘Dit een kopje’; $post1->body = ‘Dit is een bodytekst’;
Deze regels geven een PHP-foutmelding. Beide membervariabelen is hun toegang vanaf code buiten de klasse ontzegd. Het s nu dus niet meer mogelijk de membervariabelen direct te benaderen.
Setters en getters We hebben gezien hoe we de toegang tot membervariabelen van een object kunnen weigeren voor code buiten het object zelf. Dit is natuurlijk erg handig, maar wat als we de waarde van de membervariabele nu toch willen aanpassen? Het is in de OO-methodiek gebruikelijk om voor alle membervariabelen die van buitenaf een (andere) waarde moeten kunnen krijgen, een speciale methode te schrijven. Normaal gesproken geven we deze methode de naam set met de naam van de property die we willen zetten. Dus bijvoorbeeld setTitle, setHeader en setBody. We noemen deze speciale methoden setters. Setters zijn zeer eenvoudige functies met maar één doel: Het van buitenaf kunnen aanpassen van de waarde van de bijbehorende membervariabele. public function setHeader($header) { $this->_header = $header; }
Leer jezelf PROFESSIONEEL Werken met het Zend Framework
_ voor variabelenaam Het is u waarschijnlijk opgevallen dat we de variabele ‘header’ vooraf hebben laten gaan door een underscore: _. Dit is geen typefout. Het is volgens de Zend Framework-codeerstandaard gebruikelijk om private en protected variabelen te laten beginnen met een underscore. Hiermee wordt duidelijk gemaakt dat deze variabele niet extern toegankelijk is.
Een membervariabele die met private (of protected) is gedefinieerd kan helemaal niet worden benaderd, ook niet om de waarde uit te lezen. Als we dus de waarde van een property willen opvragen hebben we ook een methode nodig die de waarde van de property teruggeeft. Deze methode noemen we een getter. Getter-methoden zien er als volgt uit: public function getHeader() { return $this->_header; }
Toegang tot methoden Analoog aan de afscherming van membervariabelen is het ook mogelijk methoden af te schermen in PHP 5. Het is belangrijk alleen die methoden beschikbaar te maken voor code buiten het object die ook werkelijk beschikbaar moeten zijn. In PHP 4 waren alle functies (en methoden) automatisch public. U kunt zich voorstellen dat het soms gewenst is interne methoden af te schermen.
class BlogPost { ... /** * sla gegevens van deze blogpost op in de database * @return void */ public function save() { if ($this->isNew()) { $this->insert(); } else { $this->update(); } } /** * geeft true terug als dit een nieuwe blogpost is, * false als deze al bestaat in de database * @return boolean */
11
12
Appendix A Objectgeoriënteerd programmeren
private function isNew() { return $this->_new; } /** * voegt een nieuw record toe in de database * @return void */ private function insert() { // doe database insert } /** * update een bestaand record in de database * @return void */ private function update() { // doe database update } }
We bekijken de code. We willen de gegevens van een blogpost uiteindelijk in een database opslaan. Het BlogPost-object moet een bepaalde mate van intelligentie bevatten zodat de code die dit object gebruikt eenvoudiger wordt. In dit voorbeeld hebben we een publieke methode save gemaakt. We willen dat de code die een BlogPost-object aanroept niet hoeft te weten of het om een nieuwe blogpost gaat of dat het een bestaande blogpost is. Deze kennis nemen we op in het object zelf. We gebruiken in de code gewoon: $post->save();
Het object zoekt zelf uit of er een insert of een update in de database moet worden gedaan. De code die nodig is om dit te doen is voor de buitenwereld niet van belang. We hebben drie private methoden toegevoegd: isNew, insert en update. Deze methoden worden alleen intern gebruikt en worden dus private gedefinieerd. Zouden we deze
Leer jezelf PROFESSIONEEL Werken met het Zend Framework
methoden aanroepen van buiten het object, dan krijgen we een PHP-foutmelding. Fatal error: Call to private method BlogPost::insert() from context ‘’
Overeving (inheritance) Een belangrijk idee achter objectgeoriënteerd programmeren is dat code onderhoudbaar en uitbreidbaar moet zijn. Wat betekent het nu eigenlijk als iets uitbreidbaar is? In principe is alles namelijk uitbreidbaar. In het ergste geval moet u de code opnieuw schrijven om nieuwe functionaliteit toe te voegen aan een bestaande applicatie. Wat we willen bereiken is dat we de structuur van onze code zodanig opzetten dat we nieuwe functionaliteit kunnen inbouwen zonder dat we daarvoor de al bestaande code moeten aanpassen. Nu hangt het natuurlijk van het soort uitbreiding af of dat in de praktijk ook echt lukt, maar met goed gekozen klassen kunnen we de aanpassingen minimaliseren. Als we minder in bestaande code moeten aanpassen, is er ook minder kans dat we nieuwe fouten introduceren. Dat betekent dat we minder code hoeven te debuggen. Dit alles zorgt ervoor dat we in minder tijd de nieuwe functionaliteit kunnen inbouwen waardoor de ontwikkelkostprijs lager kan zijn. Bijna alle programmeertalen die objectgeoriënteerd programmeren ondersteunen zijn uitgerust met een speciaal mechanisme dat we overerving of in het Engels inheritance noemen. Overerving betekent dat we een bestaande klasse kunnen uitbreiden en nieuwe functionaliteit kunnen toevoegen aan de klasse zonder dat we de bestaande klasse moeten aanpassen. We kunnen een klasse uitbreiden (in het Engels extenden) met het keyword extends. De klasse waarvan de uitgebreide klasse is afgeleid noemen we in OO-termen de basisklasse (ook wel parentclass of superclass). De
13
14
Appendix A Objectgeoriënteerd programmeren
nieuwe klasse wordt afgeleide klasse, subklasse of childclass genoemd. In code ziet het er als volgt uit: /** * Basisklasse */ class Basisklasse { public $naam; public function __construct() { $this->naam = “Basis”; } } /** * Subklasse */ class Subklasse extends Basisklasse { public $extra; public function __construct() { parent::__construct(); $this->extra = “Sub”; } } // main code $object = new Subklasse(); echo ($oObject->naam . “ - ”. $oObject->extra);
U ziet dat we te maken hebben met twee klassen, maar in de hoofdcode wordt maar één object gemaakt. We kunnen de membervariabele naam, die uit de Basisklasse komt, gebruiken in de Subklasse. We zeggen dat de membervariabele naam wordt geërfd van de Basisklasse. U ziet dat we in beide klassen een constructor hebben ingebouwd. De constructor in Subklasse overschrijft de constructor van Basisklasse. Daardoor wordt bij het maken van het object alleen de constructor van Subklasse aangeroepen. Hierdoor zou de geërfde membervariabele naam geen initialisatiewaarde krijgen. We moeten in PHP altijd
Leer jezelf PROFESSIONEEL Werken met het Zend Framework
parent:: We kunnen een methode van een basisklasse aanroepen door voor de naam van de methode het keyword parent op te nemen en daar de speciale operator :: tussen te zetten. Deze operator wordt officieel de scope resolution operator of ook wel scope modifier genoemd.
expliciet nog de constructor van de basisklasse aanroepen. Dat kunnen we doen door de speciale constructie parent::__construct(). U hebt waarschijnlijk gezien dat we de membervariabele naam van de basisklasse public hebben gedefinieerd terwijl we in de vorige paragrafen juist gepromoot hebben dat alle membervariabelen private moeten zijn vanwege de encapsulation. Laten we eens kijken wat er gebeurt als we de membervariabelen private maken: class Basisklasse { private $_naam; [...] } class Subklasse extends Basisklasse { private $_extra; [...] }
We voeren het script weer uit: Notice: Undefined property: Subklasse::$_naam
Protected Het keyword protected zorgt ervoor dat een membervariabele of methode wel wordt geërft van de basisklasse, maar dat code buiten de basisklasse en de subklasse toch niet direct bij de membervariabele of methode kan.
We krijgen een PHP-notice. PHP vertelt ons dat de membervariabele _naam niet is gedefinieerd in de klasse Subklasse. Dit komt doordat het keyword private ervoor zorgt dat een membervariabele of methode niet wordt geërfd. We proberen nu dus een niet bestaande membervariabele te benaderen. Hoe kunnen we dan ervoor zorgen dat we én de encapsulation in orde houden én dat we gebruik kunnen maken van overerving van members? Dit kan door gebruik te maken van het keyword protected in plaats van private. class Basisklasse { protected $_naam; [...] } class Subklasse extends Basisklasse { protected $_extra;
15
16
Appendix A Objectgeoriënteerd programmeren
[...] }
We voeren het script opnieuw uit: Fatal error: Cannot access protected property Subklasse::$_naam
Deze foutmelding is een bekende, want die vertelt ons dat we proberen een membervariabele van buitenaf te benaderen terwijl deze is afgeschermd. Deze foutmelding kunnen we oplossen door het gebruik van getters zoals we eerder hebben gezien. Het principe van overerving werkt alleen niet op één niveau van klassen, maar dit kan ook op meerdere niveaus worden gebruikt: /** * SubSubklasse */ class SubSubklasse extends Subklasse { protected $_extraExtra; public function __construct() { parent::__construct(); $this->_extraExtra = “SubSub”; } }
Voegen we deze klasse toe aan de klassen die we al hebben, dan hebben we in de klassenhierarchie een nieuwe subklasse gemaakt die is afgeleid van Subklasse, maar omdat Subklasse van Basisklasse is afgeleid, is SubSubklasse indirect ook van Basisklasse afgeleid. SubSubklasse erft dus alle public en protected members van Basisklasse én van Subklasse.
Leer jezelf PROFESSIONEEL Werken met het Zend Framework
Abstracte klassen We willen voorkomen dat een programmeur per ongeluk een instantie maakt van de klasse die eigenlijk niet bedoeld is om te instantiëren, maar puur als basisklasse voor andere klassen. Sinds PHP versie 5 hebben we hier de mogelijkheid toe gekregen. We kunnen een klasse abstract definiëren. We maken een klasse abstract door het keyword abstract voor de klassedefinitie te plaatsen. Als we een klasse abstract maken, krijgen we een PHP-foutmelding als we proberen toch een instantie van de klasse te maken. abstract class Basisklasse { [...] }
Abstract Het keyword abstract voor een klasse geeft aan dat deze klasse niet geïnstantieerd kan worden. Het maken van een object van deze klasse geeft een foutmelding. Eventuele subklassen van de klasse kunnen wel geïnstantieerd worden.
Fatal error: Cannot instantiate abstract class ...
We hebben hiermee een krachtig middel in handen om programmeurs in het projectteam te wijzen op een fout in het gebruik van onze klasse. In PHP 4 hadden we deze mogelijkheid niet en moesten we maar hopen dat onze klasse correct gebruikt werd. Er is nog een reden om een klasse abstract te definiëren. Soms willen we dat alle afgeleide klassen (subklassen) van een klasse een bepaalde methode bevatten. Alleen hebben alle afgeleide klassen een totaal andere invulling van die methode. We kunnen in de basisklasse geen algemene invulling aan die methode geven omdat er geen algemene invulling is. We willen verplichten dat iedere afgeleide klasse zelf zijn eigen versie van die methode maakt. Dit kunnen we doen door in de basisklasse een methode op te nemen en deze abstract te definiëren.
17
18
Appendix A Objectgeoriënteerd programmeren
Abstracte methoden We kunnen een methode van een klasse ook vooraf laten gaan door het keyword abstract. Het wil dan zeggen dat deze methode wel aanwezig is, maar geen implementatie heeft. abstract class Basisklasse { protected $_naam; public function __construct() { $this->_naam = ‘’; } public abstract function basismethode(); }
Merk op dat er direct na het laatste haakje een puntkomma staat. Het hele blok met code tussen accolades {} is weggelaten. Hiermee vertellen we PHP dat er wel een methode met de naam basismethode is, maar dat de implementatie van deze methode wordt overgelaten aan een subklasse van deze klasse. class Subklasse extends Basisklasse { protected $_extra; public function __construct() { parent::__construct(); $this->_extra = ‘’; } }
Deze code toont een nieuwe klasse afgeleid van Basisklasse. In deze klasse zijn we ‘vergeten’ de methode basismethode op te nemen. In de volgende regels wordt een instantie gemaakt van de nieuwe klasse. Voeren we deze code uit, dan krijgen we de volgende foutmelding:
Abstracte methode Als u een methode abstract definieert, bent u verplicht de implementatie (de code tussen de accolades) volledig weg te laten.
Leer jezelf PROFESSIONEEL Werken met het Zend Framework
require_once(‘Basisklasse.php’); require_once(‘Subklasse.php’); $object = new Subklasse(); $object->basismethode(); Fatal error: Class Subklasse contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Basisklasse::basismethode)
Deze foutmelding kan soms wat verwarring opleveren. Vooral het deel ‘... and must therefore be declared abstract ...’ is onduidelijk. In ons geval is de tekst ‘...implement the remaining methods (Basisklasse::basismethode) ...’ van belang. Deze tekst geeft aan dat we vergeten zijn een methode die in de basisklasse abstract is gedefinieerd, te implementeren. Dat is precies de melding die we verwachtten. Wat betekent dan het eerste deel? Dit is eigenlijk heel eenvoudig als u bedenkt dat een klasse pas geïnstantieerd kan worden als alle methoden die zijn gedefinieerd een invulling hebben. Een abstracte methode heeft geen invulling (er staat geen code tussen {}). Een klasse met een abstracte methode kan dus nooit geïnstantieerd worden. We hebben gezien dat de enige manier om te forceren dat een klasse niet geïnstantieerd kan worden, is door de klasse zelf abstract te definiëren. De foutmelding geeft ons dus twee mogelijkheden om de fout op te lossen: I Maak een methode met de naam basismethode en implementeer deze. I Definieer deze nieuwe klasse zelf als abstract, zodat het niet geïnstantieerd kan worden. Let op dat we het probleem in oplossing twee eigenlijk verleggen naar eventuele subklassen van de klasse. De correcte code van de Subklasse zou als volgt kunnen zijn: class Subklasse extends Basisklasse {
19
20
Appendix A Objectgeoriënteerd programmeren
protected $_extra; public function __construct() { parent::__construct(); $this->_extra = ‘’; }
Abstracte klasse Als een klasse minimaal één abstracte methode bevat, moet u de klasse zelf abstract definiëren. Dit is om te voorkomen dat u een klasse instantieert waarin nog methoden bestaan zonder implementatie.
public function basismethode() { echo (__METHOD__); } }
Interfaces In de vorige paragraaf hebben we geleerd dat we abstracte klassen kunnen gebruiken om te zorgen dat alle programmeurs in het ontwikkelteam het object op de juiste manier gebruiken. We maken de afspraak dat een klasse zelf niet gebruikt wordt als object, maar alleen om als basis voor andere klassen te dienen. Door het keyword abstract krijgt de programmeur die deze afspraak per ongeluk vergeet vanzelf een foutmelding. Dit bevordert de robuustheid van onze code waardoor we achteraf minder tijd kwijt zijn met het zoeken naar lastige bugs. De term interface zal u misschien iets zeggen. Meestal wordt deze geassocieerd met de gebruikersinterface (User interface). Eigenlijk wil de term interface niets anders zeggen dan de mogelijkheid om met een voorwerp (of persoon) te communiceren. Een gebruikersinterface beschrijft hoe u als mens met een computerprogramma kunt communiceren. Op dezelfde wijze communiceren objecten in uw PHP-code ook onderling. Deze vorm van communiceren verloopt door het aanroepen van methoden van een object. Als een object een methode van een ander object aanroept, dan communiceren deze twee objecten met elkaar. Een interface kunnen we beschrijven als de methoden die de objecten van elkaar kunnen aanroepen.
Klasse-interface De interface van een klasse is de verzameling van publiek gedefinieerde membervariabelen en methoden van die klasse.
Leer jezelf PROFESSIONEEL Werken met het Zend Framework
Waarom is een interface zo belangrijk?
Interface en klassen aanpassen Als u de interface van een klasse wijzigt, moeten andere klassen die gebruik maken van de gewijzigde klasse ook aangepast worden. Dit moet u zo veel mogelijk voorkomen.
Als u klassen ontwerpt, dan bedenkt u hoe deze klassen door andere klassen gebruikt kunnen worden. U bedenkt de namen van de methoden en welke parameters deze methoden moeten krijgen. U bedenkt ook wat voor waarde een methode kan retourneren. Als goed OO-ontwerper weet u dat u membervariabelen niet publiek moet maken, maar dat u in plaats daarvan setters en getters definieert. U gebruikt misschien de magische methoden __set() en __get() om properties te definiëren. Deze properties zijn ook onderdeel van de interface omdat ze van buitenaf benaderd kunnen worden. Als u een methodenaam of propertynaam tijdens de implementatie van de applicatie wilt veranderen, heeft dat grote gevolgen. Andere klassen maken immers gebruik van deze namen en de programmeurs die deze klassen hebben geschreven zullen het u niet in dank afnemen als u besluit de namen aan te passen. Zij moeten dan namelijk ook hun code wijzigen zodat de namen weer overeenkomen. Om te voorkomen dat u vaak de interface van een klasse moet wijzigen, moet u vooraf goed over deze interface nadenken. Dat doet u door de software gedegen te ontwerpen. Hebt u eenmaal een interface bedacht dan communiceert u de interface met de andere ontwikkelaars in het team. Deze kunnen dan uw klasse gebruiken zonder dat uw klasse volledig geïmplementeerd hoeft te zijn. Hierdoor kunt u de applicatie parallel ontwikkelen met meer programmeurs tegelijk. Veel objectgeoriënteerde programmeertalen kennen de interface ook als speciale klasse. Ook PHP versie 5 heeft het keyword interface geïntroduceerd. Door een interfaceklasse maakt u de afspraak dat alle klassen die de interface implementeren, zich houden aan de namen van de methoden die in de interface gedefinieerd staan.
21
22
Appendix A Objectgeoriënteerd programmeren
Om dit te verduidelijken nemen we een voorbeeld uit onze webshop. De klasse BlogList bevat een lijst met BlogPost objecten. Welke blogposts er in de lijst staan, hangt af van de query die we op de database hebben gedaan. Het is voor de code die gebruik maakt van het BlogList-object niet interessant om te weten hoe de lijst tot stand komt, maar wel dat de lijst BlogPost objecten bevat. Een BlogList gebruiken we meestal om gegevens uit de lijst te halen en van iedere BlogPost in de lijst bijvoorbeeld iets af te drukken. Stel we willen de BlogList gebruiken om een HTML-tabel te maken met de titels van alle blogposts in de lijst. Normaal gesproken zouden we een array gebruiken met een foreach-statement om de HTML-tabel te genereren. Maar de BlogList is een object en deze kunnen we niet zomaar gebruiken in een foreach-statement. Om onze code schoon en leesbaar te houden zouden we de volgende code willen gebruiken: $list = new BlogList(); // vul de lijst met posts $list->populate(); // genereer een HTML-tabel echo(‘
’); foreach ($list as $post) { echo(‘’.$post->getTitle().’ |
’); } echo(‘
’);
In deze code instantiëren we een BlogListobject die we vullen (met voorlopig dummy post objecten). We maken een foreach-lus waarin we het BlogList-object gebruiken alsof het een array is. Als we deze code uitvoeren krijgen we geen foutmelding. Dit komt omdat PHP toestaat dat we foreach op een object uitvoeren, maar er gebeurt ook niets. Het BlogList-object is immers geen echte array. Het foreach-statement werkt op een zogenoemde Iterator. Een Iterator is een ontwerppatroon dat het mogelijk maakt om elemen-
Leer jezelf PROFESSIONEEL Werken met het Zend Framework
ten van een lijst langs te lopen. De PHP-array is daar een goed voorbeeld van. PHP biedt de mogelijkheid om zelf objecten te definiëren die zich gedragen als een Iterator. Om een object een Iterator te maken moet deze zich houden aan bepaalde afspraken. Het moet alle methoden die nodig zijn om door een lijst te lopen implementeren. Dit is precies waar een interface voor is bedoeld. Het definieert de methoden die een object verplicht moet bevatten. De Iterator interface in de Standard PHP Library (SPL) is als volgt gedefinieerd: interface Iterator { /* Methods */ public function public function public function public function public function }
Interface Een interface is een volledig abstracte klasse. Dat wil zeggen dat een interface een klasse is met alle methoden abstract gedefinieerd. Een interface kan alleen publieke methoden bevatten en geen membervariabelen.
current(); key(); next(); rewind(); valid();
U ziet dat een interface niets anders is dan een opsomming van de methoden die verplicht aanwezig moeten zijn. Omdat een interface een afspraak is over hoe een object door andere objecten benaderd kan worden, zijn alle methoden van een interface altijd publiek gedefinieerd. Het keyword public mag dan ook weggelaten worden. Als we een klasse een Iterator willen laten zijn, dan moet die klasse aangeven dat het zich houdt aan de afspraken die voor die interface zijn gemaakt. We doen dat door het keyword implements. In onze BlogList zou dat als volgt zijn: class BlogList implements Iterator { [...] }
Net als bij abstracte klassen moet een klasse die een interface implementeert, alle gedefinieerde methoden een invulling geven
23
24
Appendix A Objectgeoriënteerd programmeren
(implementeren). Als u dat niet doet, krijgt u een PHP-foutmelding: Fatal error: Class BlogList contains 5 abstract methods and must therefore be declared abstract or implement the remaining methods (Iterator::current, Iterator::next, Iterator::key, ...)
Als we de BlogList werkend willen maken moeten we de een invulling geven aan de vijf methoden. Dit kan bijvoorbeeld als volgt: class BlogList implements Iterator { private $_list; public function __construct() { $this->_list = array(); } /** * voeg een post toe aan de lijst * @param BlogPost $oPost * @return void */ public function addPost(BlogPost $post) { $this->_list[] = $post; } /** * vul de lijst met objecten * @return void */ public function populate() { // vul de lijst met dummy posts $this->addPost(new BlogPost(‘Eerste post’, ‘’, ‘’)); $this->addPost(new BlogPost(‘Tweede post’, ‘’, ‘’)); $this->addPost(new BlogPost(‘Derde post’, ‘’, ‘’)); $this->addPost(new BlogPost(‘Vierde post’, ‘’, ‘’)); $this->addPost(new BlogPost(‘Vijfde post’, ‘’, ‘’)); $this->addPost(new BlogPost(‘Zesde post’, ‘’, ‘’)); } /** * geef het huidige element uit de lijst * @return mixed */
Leer jezelf PROFESSIONEEL Werken met het Zend Framework
public function current() { return current($this->_list); } /** * geef de key van het huige element uit de lijst * @return mixed */ public function key() { return key($this->_list); } /** * geef het volgende element uit de lijst * @return mixed */ public function next() { return next($this->_list); } /** * zet de interne teller aan het begin van de lijst * @return void */ public function rewind() { reset($this->_list); } /** * geeft true als het huidige element in de lijst geldig is * @return boolean */ public function valid() { return key($this->_list) !== null; } }
In dit voorbeeld hebben we eenvoudigweg gebruikgemaakt van de mogelijkheden van een PHP-array. U ziet dat we in de BlogList een array hebben opgenomen waar de werkelijke BlogPost-objecten in zitten. PHP kent een aantal arrayfuncties die hetzelfde resultaat geven als de functies die we voor onze Iterator nodig hebben. We maken daar dankbaar gebruik van. Alleen de methoden rewind en valid kunnen we niet direct overnemen. De reset-functie geeft hetzelfde
25
26
Appendix A Objectgeoriënteerd programmeren
resultaat als de rewind-methode die we willen implementeren. De valid-methode bestaat niet voor een array, dus deze moeten we zelf bedenken. Nadat we de BlogListklasse hebben uitgebreid, kunnen we deze gebruiken alsof het een PHP-array is en kunnen we het foreach-statement gebruiken zoals onze oorspronkelijke wens was. Het resultaat van de foreach is als volgt:
Eerste post |
Tweede post |
Derde post |
Vierde post |
Vijfde post |
Zesde post |
Meer interfaces implementeren In tegenstelling tot overerving is het mogelijk om meer interfaces tegelijk te implementeren. In ons voorbeeld hebben we een klasse geschreven die zich als een gewoon object gedraagt. We kunnen methoden aanroepen als populate en addPost, maar we kunnen het object ook gebruiken alsof het een array is. Nu heeft een gewone array nog een belangrijke eigenschap die onze BlogList niet heeft. We kunnen het aantal elementen van een array opvragen door de functie count() aan te roepen. Om onze BlogList-klasse zich volledig als een array te laten gedragen willen we ook het aantal posts in de lijst kunnen opvragen door middel van de count() functie die PHP ons biedt. Om dit mogelijk te maken is er binnen PHP een andere interface gedefinieerd genaamd Countable. Deze interface is als volgt: interface Countable { public function count(); }
Leer jezelf PROFESSIONEEL Werken met het Zend Framework
Design by contract We kunnen een interface dus gebruiken om afspraken te maken over welke methoden een klasse moet implementeren. We leggen daarmee in een contract vast welke interface-namen we gaan gebruiken. Dit wordt ook wel design by contract genoemd.
U ziet dat de interface maar één methode definieert. Als we de BlogList-klasse Countable maken voegen we de interfacenaam toe aan de implements lijst in de klasse-definitie. Als we de methode count hebben geïmplementeerd hebben we ons aan de afspraak van de interface gehouden en kunnen we de PHP-functie count gebruiken om het aantal elementen in de lijst op te vragen. class BlogList implements Iterator, Countable { [...] /** * geeft het aantal elementen in de lijst * @return int */ public function count() { return count($this->_list); } }
De code die de HTML-tabel genereert, kunnen we als volgt wijzigen: $list = new BlogList(); // vul de lijst met posts $list->populate(); // genereer een HTML-tabel echo(‘
’); foreach ($list as $post) { echo(‘’.$post->getTitle().’ |
’); } echo(‘’.count($list).’ |
’); echo(‘
’);
Als we deze code uitvoeren, krijgen we netjes een gevulde HTML-tabel met als laatste regel het aantal elementen in de lijst. U ziet dit in de afbeelding.
I
Afbeelding A.1
Resultaat van de code
27
28
Appendix A Objectgeoriënteerd programmeren
Final Met het keyword abstract kunnen we dus voorkomen dat een klasse, die niet bedoeld is om direct te instantiëren, door een onnadenkende programmeur in het team toch per ongeluk wordt geïnstantieerd. We dwingen daarmee af dat de programmeur alleen gebruik maakt van een subklasse van de abstracte klasse. Maar wat als we nu het omgekeerde willen bereiken? Stel we hebben een klasse gemaakt die hele specifieke functionaliteit heeft en waarvan we niet willen dat een andere programmeur die functionaliteit kan overschrijven door een eigen klasse te maken die wordt afgeleid van onze klasse. Om dit te bereiken hebben we in PHP 5 het keyword final tot onze beschikking gekregen. We nemen een eenvoudig voorbeeld: final class Finalklasse { private $_data; public function __construct() { $this->_data = ‘’; } public function doeIets() { // doe iets } } class SubFinal extends Finalklasse { } require_once(‘Finalklasse.php’); require_once(‘SubFinal.php’); $object = new SubFinal();
Als we bovenstaande code proberen uit te voeren, krijgen we een PHP-foutmelding: Fatal error: Class SubFinal may not inherit from final class (Finalklasse)
Leer jezelf PROFESSIONEEL Werken met het Zend Framework
Final Door een klasse final te declareren, voorkomen we dat deze klasse afgeleid kan worden.
We hebben het dus onmogelijk gemaakt dat deze klasse wordt afgeleid en daarmee belangrijke functionaliteit kan worden overschreven.
Final-methoden Het is misschien een beetje overdreven dat de hele klasse niet kan worden overerfd, terwijl misschien alleen een paar methoden zodanig belangrijk zijn dat we niet willen dat iemand een andere implementatie daarvan maakt. In plaats dat we de hele klasse als final definiëren, kunnen we ook alleen een methode het keyword final meegeven. Dat betekent dat alleen die methode niet mag worden overschreven in de subklasse. We veranderen het voorbeeld: class Finalklasse { private $_data; public function __construct() { $this->_data = ‘’; } public function doeIets() { // doe iets } final function speciaal() { // deze methode mag niet worden // overschreven in een subklasse } } class SubFinal extends Finalklasse { public function speciaal() { } }
29
30
Appendix A Objectgeoriënteerd programmeren
Als we nu de code opnieuw uitvoeren krijgen we een andere foutmelding: Fatal error: Cannot override final method Finalklasse::speciaal()
De methode speciaal is nu afgeschermd zodat een afgeleide klasse geen eigen versie van die methode mag hebben. Het nut hiervan ligt in de onderhoudbaarheid. Stel dat de basisklasse een complexe berekening uitvoert die erg cruciaal is (bijvoorbeeld de berekening van een factuurbedrag). We willen zeker weten dat een andere programmeur deze berekening niet per ongeluk fout doet. Door de methode final te maken weten we zeker dat er altijd gebruik gemaakt wordt van onze eigen versie van de methode.
Klassenvariabelen We hebben gezien dat we in klassen variabelen kunnen maken. Deze membervariabelen horen specifiek bij het object dat uit die klasse wordt geïnstantieerd. Hebben we meer objecten van een klasse, dan hebben we ook meer versies van de membervariabelen. Als een membervariabele protected of private is gedefiniëerd, dan is er geen mogelijkheid om vanuit het ene object bij een membervariabele van een ander object te kunnen komen. Soms is het handig om een membervariabele toegankelijk te maken voor alle objecten van dezelfde klasse. We willen dan niet per instantie van een object een aparte variabele hebben, maar juist één variabele waar alle objecten bij kunnen. We noemen dit een klassenvariabele (in tegenstelling tot een membervariabele). We nemen als voorbeeld de klasse BlogPost. We weten dat het totaal aantal blogposts in onze weblog best groot kan worden. Om te meten hoeveel BlogPost-objecten we tegelijkertijd geïnstantieerd hebben willen we een teller bijhouden. Zouden we dit in een
Final-methode Door een methode in een klasse final te declareren, kunnen we verbieden dat een afgeleide klasse deze methode overschrijft.
Abstract en final gaan niet samen Het is misschien overbodig op te merken dat het keyword abstract en final niet samengaan. Als we een klasse of methode abstract declareren, verplichten we dat deze wordt afgeleid in een subklasse. Als we een klasse of methode final declareren, verbieden we dat deze wordt afgeleid.
Leer jezelf PROFESSIONEEL Werken met het Zend Framework
membervariabele doen, dan hebben we per object een aparte variabele. class BlogPost { /** * Totaal aantal BlogPost-objecten in het geheugen * @var int */ private $_nrPostsInUse = 0; [...] public function __construct($p_title, $p_header, $p_body) { $this->_nrPostsInUse++; [...] } [...] }
Deze code levert niet het goede resultaat. We hebben nu voor ieder object van het type BlogPost een eigen membervariabele _ nrPostsInUse. Deze hebben allemaal de waarde 1. Om dit mogelijk te maken hebben we een nieuw keyword static nodig. Met static geven we aan dat de variabele bij de klasse hoort en niet bij het object. Er wordt één variabele gemaakt die door alle objecten wordt gedeeld. class BlogPost { /** * Totaal aantal BlogPost-objecten in het geheugen * @var int */ private static $_nrPostsInUse = 0; [...] public function __construct($p_title, $p_header, $p_body) { self::$_nrPostsInUse++; [...] } [...] }
31
32
Appendix A Objectgeoriënteerd programmeren
De code geeft een voorbeeld van de declaratie van een klassenvariabele _nrPostsinUse. Door in de constructor deze variabele op te hogen, houden we een teller bij van het aantal Artikelobjecten dat op een bepaald moment in gebruik (lees in het geheugen) is. Let op dat een klassenvariabele niet benaderd wordt met de ingebouwde variabele $this, maar met self. In dit geval dus self::$_nrPostsInUse. De operator :: (de scope modifier) hebben we al eerder gezien bij het aanroepen van parentmethoden.
Self Het keyword self wordt gebruikt om klassenvariabelen vanuit de klasse zelf te benaderen. Dit is vergelijkbaar met het keyword parent.
Een (statische variabele) klassenvariabele kan public, private of protected zijn. Dit heeft geen invloed op de werking van de variabele. Dit heeft natuurlijk wel invloed op de toegankelijkheid van de variabele. Willen we een klassenvariabele benaderen van buiten de klasse, dan moeten we de naam van de klasse voor de variabele plaatsen. In ons voorbeeld BlogPost::$_ nrPostsInUse. Dit werkt natuurlijk alleen indien de variabele public is gedefinieerd. In het bovenstaande voorbeeld hogen we wel de teller op als er een nieuw BlogPost-object wordt aangemaakt, maar als een object niet meer wordt gebruikt (hij wordt verwijderd of null gemaakt), dan moeten we de teller ook weer aflagen. De volgende code zorgt daarvoor: class BlogPost { [...] public function __destruct() { self::$_nrPostsInUse—; } [...] }
Dollarteken Een klassenvariabele krijgt een dollarteken voor de variabelenaam, maar achter de scope modifier (::). Dus self::$klassenvariabele of Klasse::$klassenvariabele.
Leer jezelf PROFESSIONEEL Werken met het Zend Framework
Een klassenvariabele wordt geërfd door een subklasse net als een gewone membervariabele. Dit geldt alleen als deze niet private is gedefiniëerd.
Statische methoden Soms is het handig een methode in een klasse te implementeren die logisch gezien wel in die klasse thuishoort, maar geen membervariabelen van die klasse gebruikt. Membervariabelen worden pas actief als er van de klasse een instantie wordt gemaakt en er een object bestaat. Een methode die geen membervariabelen gebruikt kan dus ook worden aangeroepen als er helemaal geen instantie van de klasse is. Normaal gesproken is het niet mogelijk een methode van een klasse aan te roepen zonder eerst een object te maken, maar met een speciale constructie is het mogelijk een methode aan te roepen direct op de klasse zelf. We noemen zo’n methode een statische methode, omdat hij aan de klasse is gekoppeld in plaats van aan een object. Het principe werkt op exact dezelfde manier als bij klassenvariabelen. We gebruiken ook hier het keyword static. Nu plaatsen we deze in de declaratie van de methode. public static function genereerGetal() { return mt_rand(); }
De methode genereert een willekeurig getal. We kunnen deze methode statisch declareren omdat het geen membervariabelen gebruikt en dus geen objectinstantie nodig heeft. Het gebruik van een statische methode komt sterk overeen met het gebruik van een klassenvariabele. Voor een aanroep van de methode van buiten de klasse gebruiken we: Klassenaam::genereerGetal();
33
34
Appendix A Objectgeoriënteerd programmeren
En voor de aanroep van de methode vanuit de klasse zelf gebruiken we: self::genereerGetal();
Zoals gezegd kunnen we in statische methoden geen membervariabelen gebruiken omdat er geen object van de klasse is geïnstantieerd. Het is wel mogelijk om een klassenvariabele te gebruiken in een statische methode. Een klassenvariabele is immers ook beschikbaar als er geen object is geïnstantieerd. Als we ons voorbeeld van de BlogPost weer nemen, kunnen we een statische methode definiëren die het aantal in gebruik zijnde objecten teruggeeft. Dit is in feite een getter voor een klassenvariabele. class BlogPost { [...] public static function getPostsInUse() { return self::$_nrPostsInUse; } [...] }
De volgende code maakt het gebruik van de methode duidelijk: require_once(‘models/BlogPost.php’); echo(BlogPost::getPostsInUse().’
’); $post1 = new BlogPost(‘Eerste post’, ‘’, ‘’); $post2 = new BlogPost(‘Tweede post’, ‘’, ‘’); $post3 = new BlogPost(‘Derde post’, ‘’, ‘’); $post4 = new BlogPost(‘Vierde post’, ‘’, ‘’); $post5 = new BlogPost(‘Vijfde post’, ‘’, ‘’); echo(BlogPost::getPostsInUse().’
’); $post2 = null; echo(BlogPost::getPostsInUse().’
’); unset($post4); echo(BlogPost::getPostsInUse().’
’);
De uitvoer van de code is: 0 5 4 3
Variabelen in statische methoden Het gebruik van membervariabelen in een statische methode is niet mogelijk. Het gebruik van klassenvariabelen binnen een statische methode daarentegen wel. Een klassenvariabele is immers gekoppeld aan de klasse en niet aan een object.
Leer jezelf PROFESSIONEEL Werken met het Zend Framework
Een ander voorbeeld van het gebruik van statische methoden komen we tegen bij het uitwerken van het ontwerppatroon Singleton in de volgende paragraaf.
Klassenconstanten u kent waarschijnlijk de PHP-methode define om een constante te definiëren. Indien we een bepaalde waarde vaker willen gebruiken, kunnen we deze waarde een naam geven. We kunnen deze naam overal in de code gebruiken waar we de waarde willen hebben. De volgende code is een voorbeeld van het gebruik van een constante: define(‘MAX_LENGTE’, 100); define(‘MAX_REGELS’, 100); function checkString($p_sString) { if (is_string($p_sString) && (strlen($p_sString) <= MAX_LENGTE)) { $aLines = explode(“\n”, $p_sString); if (count($aLines) <= MAX_REGELS) { return true; } else { return false; } } else { return false; } } if (checkString(“regel 1\nregel 2\nRegel 3")) { echo (‘String is ok’); }
Het voordeel van het gebruik van constanten is ten eerste de leesbaarheid van de code. In plaats van overal het getal 100 te gebruiken, gebruiken we de naam (in dit geval MAX_ LENGTE). Hebben we meer constanten die toevallig dezelfde waarde hebben, dan kunnen we nu eenvoudig onderscheid maken tussen de waarde. Een tweede voordeel is dat we de waarde eenvoudig centraal kun-
35
36
Appendix A Objectgeoriënteerd programmeren
nen aanpassen. Overal waar in de code de naam is gebruikt wordt automatisch de waarde ook aangepast. Iets dat met een globale zoek-en-vervang niet mogelijk is. In het voorbeeld zouden immers beide getallen 100 worden vervangen terwijl alleen MAX_ LENGTE aangepast moet worden. De getoonde methode van define heeft echter ook een aantal nadelen. Ten eerste is de define()-structuur in PHP een functie die wordt aangeroepen tijdens het uitvoeren van de code. Deze functie staat niet bekend om zijn snelheid. Het gebruik van grote aantallen defines in de code kan tot vertragingen leiden. Over het algemeen zijn dit geen onoverkomelijke vertragingen. Een veel belangrijkere tekortkoming van define is dat het gaat om een globale functie. U kunt de naam overal in uw code gebruiken. Dit lijkt op het eerste gezicht prettig, maar wat als u twee verschillende maximale lengtes wilt gebruiken. U heeft bijvoorbeeld een MAX_LENGTE van een string en een andere MAX_LENGTE van een persoon. U kunt maar één keer MAX_LENGTE definiëren. U moet dus unieke namen bedenken. U kiest bijvoorbeeld voor STRING_MAX_LENGTE en PERSOON_MAX_ LENGTE. PHP 5 biedt ons een elegantere oplossing, de klassenconstante. Zoals de naam al doet vermoeden is dit een constante die direct is gekoppeld aan een klasse. We kunnen daarmee dus gebruik maken van het ingebouwde scope mechanisme van PHP. Omdat een klassenconstante is gekoppeld aan een klasse, kunnen er meerdere klassen bestaan die dezelfde constante gebruiken. We kunnen een klassenconstante gebruiken op dezelfde wijze als een klassenvariabele, namelijk door de naam van de constante vooraf te laten gaan door de naam van de klasse of vanuit de klasse zelf door het keyword self. Het volgende voorbeeld is een aangepaste versie van onze BlogList uit de weblog. We
Leer jezelf PROFESSIONEEL Werken met het Zend Framework
hebben voor dit voorbeeld eenvoudige paginering toegevoegd. We willen niet altijd een volledige lijst van blogposts hebben, maar per pagina een maximaal aantal posts tonen. class BlogList implements Iterator, Countable { const NR_POSTS_PER_PAGE = 10; [...] /** * geeft een lijst met gepagineerde blogposts * @param int $p_page Het paginanummer (start met 0) * @return array Lijst met posts van de gegeven pagina */ public function getPostsOfPage($p_page) { $start = $p_page * self::NR_POSTS_PER_PAGE; $end = $start + self::NR_POSTS_PER_PAGE; $result = array(); if (count($this->_list) > $start) { for ($i = $start; $i < $end; $i++ ) { if (isset($this->_list[$i])) { $result[] = $this->_list[$i]; } else { break; } } } return $result; } }
Notatie van constanten Het is gangbaar de namen van constanten uit hoofdletters te laten bestaan. Hierdoor vallen ze in de code meer op.
U ziet dat klassenconstanten worden gedefinieerd in de sectie waar ook de membervariabelen en de klassenvariabelen staan. Een constante wordt voorafgegaan door het keyword const. We geven de constante direct een waarde (in tegenstelling tot een membervariabele, die in de constructor pas een waarde wordt gegeven). De methode getPostsOfPage geeft een array terug waarin alleen de blogpost-objecten zitten die op een bepaalde gegeven pagina staan. Het aantal posts op een pagina is gedefinieerd in de klassenconstante NR_POST_PER_PAGE. Binnen de methode getPostsOfPage kan de
37
38
Appendix A Objectgeoriënteerd programmeren
constante worden benaderd door self::NR_ POSTS_PER_PAGE.
Geen $ voor contstanten Let op dat u geen $-teken voor de naam van de constante zet. U zou hiermee aan een klassenvariabele refereren. Dit levert een foutmelding op waarvan de oplossing soms lastig is te vinden.
Het gebruik van de methode getPostsOfPage kunnen we als volgt demonstreren: require_once(‘models/BlogList.php’); require_once(‘models/BlogPost.php’); $list = new BlogList(); // vul de lijst met posts $list->populate(); $page = 0; $start = $page * BlogList::NR_POSTS_PER_PAGE; $end = $end = $start + BlogList::NR_POSTS_PER_PAGE; $posts = $list->getPostsOfPage($page);
// genereer een HTML-tabel echo(‘
’); echo(‘post ‘.$start.’ - ‘.$end.’ van ‘.count($list).’ |
’); foreach ($posts as $post) { echo(‘’.$post->getTitle().’ |
’); } echo(‘
’);
We genereren nu een HTML-tabel met daarin een maximum aantal posts. Door de variabele $page de waarde van de gewenste pagina te geven, krijgen we de juiste lijst met BlogPost-objecten. Net als bij de klassenvariabele en de statische methode gebruiken we de klassenaam om aan te geven welke constante uit welke klasse we willen gebruiken. In dit geval BlogList::NR_POST_PER_PAGE. Het resultaat van de code is te zien in de afbeeldingen. We kunnen nu zeer eenvoudig het aantal blogposts per pagina aanpassen door in de klasse BlogList de constante een andere waarde te geven. Dit draagt bij aan de onderhoudbaarheid van onze applicatie.
I Afbeelding A.2 Links: Lijst van pagina 0 ($page = 0). Rechts: Lijst van pagina 1 ($page = 1)
Leer jezelf PROFESSIONEEL Werken met het Zend Framework
Ontwerppatronen (design patterns) Wat design patterns zijn De term design patterns is ontleend aan de architectuur, waarbij al sinds de middeleeuwen gebruik wordt gemaakt van standaardontwerpideeën die hun nut in de loop der tijd hebben bewezen. In 1994 kwam het boek Design Patterns: Elements of Reusable Object-Oriented Software van Erich Gamma, Richard Helm, Ralph Johnson en John Vlissides uit. Dit was het eerste boek waarin heel duidelijk werd beschreven hoe je gebruik kunt maken van standaardontwerpmodules in de softwarearchitectuur. Hoewel deze ‘Gang of Four’ niet de eerste persoenen waren die dit concept gebruikten, zijn zij wel veruit de bekendste. Sinds die tijd is het een must voor iedere softwareontwerper kennis te hebben van, in ieder geval, de meest voorkomende design patterns. Een design pattern is een beschrijving van een veel voorkomend probleem en van de structuur van klassen waarmee dit probleem opgelost kan worden. Sommige design patterns worden direct uitgewerkt in code van een (willekeurige) programmeertaal, maar vaker wordt alleen een werkwijze beschreven. Zo’n werkwijze geeft alleen aan wat de beste methode van de oplossing is, maar laat de programmeur vrij om de precieze implementatie zelf te kiezen. Design patterns worden tegenwoordig ingedeeld in verschillende groepen: I Fundamenteel (fundamental) Fundamentele patronen beschrijven oplossingen voor het vereenvoudigen van complexe klassenstructuren. Door extra klassen te definiëren die taken overnemen van andere complexe klassen, wordt de complexiteit opgesplitst in kleinere meer handelbare delen. Voorbeelden van
39
40
Appendix A Objectgeoriënteerd programmeren
Fundamentele patronen zijn: Delegation, Proxy en Facade. I Structureel (structural) Structurele patronen beschrijven oplossingen voor problemen met klassen die zeer complexe relaties hebben. Stel dat u een klasse hebt die gebruikmaakt van vele andere klassen. Hierdoor kan uitbreidbaarheid een probleem worden puur doordat het te complex wordt. Meestal worden nieuwe klassen toegevoegd die delen van functionaliteit overnemen om de complexe relaties tussen de klassen te vereenvoudigen. Voorbeelden van structurele patronen zijn: Adapter, Compositie, Proxy en Facade. U ziet dat sommige patronen in meerdere groepen ingedeeld kunnen worden. I Creërend (creational) Creërende patronen handelen het creëren van objecten af. Vaak is het instantiëren van objecten een complexe bezigheid. Als dit op veel plaatsen in uw code voorkomt, betekent dit dat veel handelingen steeds opnieuw uitgevoerd moeten worden. Door het instantiëren van het object over te laten aan een klasse die daar speciaal voor is geschreven, wordt op veel plaatsen uw code overzichtelijker en daardoor beter onderhoudbaar. Voorbeelden van creërende patronen zijn: Singleton, Factory en Builder. I Gedrag (Behavioral) Gedragpatronen beschrijven methoden om communicatie tussen objecten te vereenvoudigen. Vaak heeft uw code impliciet de volgorde van aanroepen van methoden vastgelegd. Dit kan soms een probleem zijn als u code wilt aanpassen. Het is dan slecht zichtbaar hoe de communicatie precies verloopt en de kans is groot dat u iets stuk maakt dat werkte. Gedragpatronen beschrijven oplossingen hoe u de communicatie expliciet kunt maken zodat het
Leer jezelf PROFESSIONEEL Werken met het Zend Framework
duidelijker is hoe de communicatie tussen uw objecten verloopt. Voorbeelden van gedragpatronen zijn: Command, Iterator, Observer en Visitor. I Gelijktijdigheid (concurrency) Gelijktijdigheidpatronen houden zich bezig met problemen die ontstaan doordat twee (of meer) stukken parallel worden uitgevoerd. Het gaat hierbij meestal om het oplossen van timingproblemen. PHP kent geen mogelijkheid om binnen één script meerdere delen van de code gelijktijdig te doorlopen (multithreading). Voorbeelden van gelijktijdigheidpatronen zijn: Monitor, Scheduler, Threadpool object en Read-Write lock. In deze appendix beperken we de beschrijving van de design patterns tot de meest gebruikte patronen en vooral die patronen die door het Zend Framework worden gebruikt. De ontwerppatronen die al in het boek zelf worden behandeld, behandelen we niet nogmaals in deze appendix.
Singleton Het eerste design pattern dat we behandelen is het Singleton-object. Dit design pattern valt onder de categorie van creërende patronen. Het is een vrij eenvoudig patroon waarmee geforceerd wordt dat er altijd maar maximaal één object van een bepaald type geïnstantieerd kan zijn. U kunt zich voorstellen dat het regelmatig voorkomt dat we in een applicatie te maken hebben met maximaal één object van een bepaald type. Als we gebruikmaken van de Zend-MVC-structuur, dan weten we dat alle requests worden afgehandeld door het Zend_Controller_Front-object Dit is een object dat het routing- en dispatchingmechanisme in werking zet. Omdat ieder request apart wordt afgehandeld, is er altijd maar één Frontcontroller-object nodig. Dit object bevat
41
42
Appendix A Objectgeoriënteerd programmeren
gegevens die we op veel plaatsen in onze applicatie willen gebruiken. We weten inmiddels dat het gebruik van globale variabelen een zeer slechte oplossing is. Een globale variabele zorgt immers voor een afhankelijkheid van een klasse met de applicatie. Nu is op zich de afhankelijkheid niet zo’n probleem, maar wel het feit dat deze afhankelijkheid niet te controleren is vanuit de klasse. De applicatie kan immers op ieder moment de variabele aanpassen zonder dat de klasse daar weet van heeft. Zo kan de werking van de klasse afhankelijk worden van onbekende factoren. Het begrip encapsulation zegt dat we dit niet willen. Als een globale variabele geen goede oplossing is, wat dan wel? We willen de gegevens dus overal vandaan op een eenvoudige manier kunnen benaderen. We kunnen hiervoor gebruik maken van klassenvariabelen en statische methoden. Deze zijn immers beschikbaar zonder dat een instantie van een object gemaakt hoeft te worden. We kijken naar (een deel van ) de Zend_Controller_ Front-klasse: class Zend_Controller_Front { protected static $_instance = null; ... public static function getInstance() { if (null === self::$_instance) { self::$_instance = new self(); } return self::$_instance; } protected function __construct() { $this->_plugins = new Zend_Controller_Plugin_Broker(); } ... }
Leer jezelf PROFESSIONEEL Werken met het Zend Framework
U ziet dat er een statische variabele $_instance wordt gedefinieerd. Deze variabele heeft in het begin nog geen waarde (null), waarmee wordt aangegeven dat er nog geen object is geïnstantieerd. De methode getInstance is ook statisch. Deze kunnen we dus aanroepen zonder een instantie van de klasse te hebben. Deze methode kijkt of er al een instantie van deze klasse is gemaakt. Is dit niet het geval, dan wordt er een nieuw object gecreëerd (self refereert in dit geval aan Zend_Controller_Front). Dit object wordt opgeslagen in de statische variabele $_ instance. Als er al een object in die variabele stond, wordt het al bestaande object geretourneerd. getInstance levert dus altijd een object terug. Hetzij een nieuw object, hetzij een eerder aangemaakt object. De code die de Frontcontroller wil gebruiken zou er als volgt uit kunnen zien: $controller = Zend_Controller_Front::getInstance(); $controller->setControllerDirectory(‘/pad/naar/controllers/’);
Ergens anders in de applicatie kunnen we de gegevens weer opvragen: $controller = Zend_Controller_Front::getInstance(); $dir = $controller->getControllerDirectory();
Zouden we het commando new gebruikt hebben, dan wordt op beide plaatsen een nieuw object gemaakt en gaan de gegevens , die we in de eerste twee regels in het object zetten, verloren. GetInstance zorgt ervoor dat we op beide plaatsen met hetzelfde object kunnen werken zonder dat we op een lastige manier het object via parameters moeten doorgeven. Om te voorkomen dat een programmeur per ongeluk toch het keyword new gebruikt in plaats van getInstance, wordt de constructor protected gemaakt. Zouden we nu new Zend_Controller_Front() gebruiken, dan krijgen we een nette foutmelding.
43
44
Appendix A Objectgeoriënteerd programmeren
Voorbeelden van het Singleton design pattern binnen Zend Framework zijn: I De Frontcontroller - Zend_Controller_ Front I Authenticatie - Zend_Auth I Registry - Zend_Registry
Iterator Een Iterator is een ontwerppatroon dat het mogelijk maakt om een object te behandelen als een array. Als een object een lijst bevat, willen vaak deze lijst langslopen en per element van de lijst iets doen. Een Iterator biedt een aantal mehoden om door de lijst te lopen. Meestal zijn dat methoden als next, current, rewind, valid enzovoort. Het ontwerppatroon Iterator legt niet precies vast welke methoden er moeten zijn, maar wel dat deze methoden toegang tot de elementen in een lijst bieden. Binnen PHP is de Iterator gedefinieerd als een interface (zie de paragraaf Interfaces hiervoor). interface Iterator { public function public function public function public function public function }
current(); key(); next(); rewind(); valid();
Een object dat de interface Iterator implementeert, mag worden gebruikt in de foreach-constructie van PHP. foreach($iteratorobject as $element) { echo ($element->getName()); }
Een Iterator hoeft niet per definitie vooruit door de lijst te lopen. We kunnen eenvoudig een Iterator maken die in omgekeerde richting door een lijst loopt. Een ander veelvoor-
Leer jezelf PROFESSIONEEL Werken met het Zend Framework
komend voorbeeld is een Iterator die niet alle elementen van een lijst toont, maar een gefilterd deel daarvan. Binnen PHP zijn in de Standaard PHP Library (SPL) vele iterators al voor ons gedefinieerd. Iedere iterator heeft zijn eigen eigenschappen. Hieronder volgt een kleine greep uit alle bestaande Iterators: I ArrayIterator pen.
Een ArrayObject doorlo-
I DirectoryIterator De bestanden van een directory doorlopen. I RecursiveDirectoryIterator De bestanden van een directory en alle subdirectories doorlopen. I SimpleXMLIterator De elementen van een XML-document doorlopen.
Factory Het design pattern Factory is een veelgebruikt patroon. Er zijn verschillende varianten op dit patroon. In dit boek behandelen we de Factory-methode variant. Zoals de naam al doet vermoeden, worden door het Factory-patroon objecten gegenereerd zoals een fabriek ook objecten fabriceert. In veel gevallen is het instantiëren van objecten niet alleen maar het aanroepen van een commando new. Vaak moet het object ook geconfigureerd worden, door na het maken nog een aantal methoden aan te roepen. Als er verschillende soorten objecten gemaakt moeten worden afhankelijk van de huidige toestand van het systeem, kunnen we kiezen een speciale klasse te maken die zich alleen bezighoudt met het genereren van objecten van een bepaald type. Vaak zijn de objecten die worden gegenereerd aan elkaar verwant doordat het klassen zijn die afgeleid zijn van een gezamenlijke basisklasse. Een HTML-formulier in onze website heeft waarschijnlijk meerdere elementen. Bijvoorbeeld twee tekstvelden, een checkboxveld,
45
46
Appendix A Objectgeoriënteerd programmeren
een radiobuttonveld en een submitknop. Al deze elementen hebben als gezamenlijke eigenschap dat het onderdelen van een formulier zijn. Zend_Form biedt ons een eenvoudige mogelijkheid om een webformulier met elementen te creëren. Omdat het maken van een formelement complexer is dan alleen een new Zend_Form_Element, biedt Zend Framework ons een factory-methode om een elementobject te creëren zonder dat we daarvoor vijf regels code nodig hebben: $elmNumber = new Zend_Form_Element_Text(‘frmNumber’); $elmNumber->setLabel(‘Number’); $elmNumber->addValidator(‘Digits’)->setRequired(true); $form->addElement($elmNumber);
Deze code maakt een inputveld van het type tekst aan, zet de titel op Number en voegt een validator toe. Als al deze gegevens van het element zijn gezet, wordt het elementobject toegevoegd aan het formulierobject. $form->addElement(‘text’, ‘frmNumber’, array(‘label’ => ‘Number’, ‘required’ => true, ‘validators’ => array (‘digits’) ) );
Deze code heeft exact hetzelfde resultaat, maar maakt gebruik van de Factory-methode addElement. U ziet dat addElement dus op twee manieren gebruikt kan worden: om een eerder geïnstantieerd object met te geven of als factory. In dat geval geven we alle gegevens, die nodig zijn om het Zend_Form_ Element automatisch te maken, mee in de vorm van arrays. Welke van de twee vormen u gebruikt is totaal aan u. Beide hebben voor- en nadelen.
Leer jezelf PROFESSIONEEL Werken met het Zend Framework
Voorbeelden van het Factory-method patroon binnen Zend Framework zijn: I Form elementen: Zend_Form::addElement(). I Database connectie: Zend_Db::factory(). I Navigatie door pagina’s van de website: Zend_Navigation_Page::factory().
Adapter Het design pattern Adapter wordt op verschillende plaatsen in Zend Framework gebruikt. Om het Adapter-patroon beter te kunnen begrijpen, moeten we eerst het probleem schetsen waarvoor het Adapterpatroon de oplossing biedt. Het gebeurt vaak dat u een klasse hebt geschreven die op een later tijdstip aangepast moet worden. U heeft zich netjes aan de OOP-regels gehouden en maakt goed gebruik van encapsulation in uw klasse. Toch moet om de een of andere reden de interface (de publieke methoden) van de klasse worden aangepast. Dit betekent dat in alle andere code die gebruikmaakt van deze klasse, gecontroleerd moet worden of de veranderde methoden nog correct worden aangeroepen. Zo niet, dan moet de code worden aangepast. Op zich lijkt dit niet zo erg, maar wat als de klasse een zeer veelgebruikte klasse is die in verschillende projecten wordt gebruikt? Het is ondoenlijk om alle bestaande projecten aan te passen alleen vanwege de ene wijziging in uw klasse. U wilt eigenlijk twee verschillende interfaces naar de nieuwe klasse. Alle ‘oude’ methoden moeten nog worden ondersteund, maar ook alle nieuwe methoden wilt u beschikbaar maken. Het Adapter-ontwerppatroon biedt een andere interface naar een bestaande klasse. Het is meestal een aparte klasse die om de aan te passen klasse heen wordt geschreven.
47
48
Appendix A Objectgeoriënteerd programmeren
De methoden van de Adapterklasse bieden indirect toegang tot de onderliggende klasse. Hoe wordt de Adapter in de regel gebruikt? Meestal wordt een Adapter gebruikt om twee (of meer) verschillende klassen, die veel gemeen hebben, maar te weinig om afgeleide klassen te zijn, dezelfde interface te geven. Een mooi voorbeeld hiervan is een databaseconnectie. Er bestaan vele verschillende databases (MySQL, MS-SQL, Oracle, Progress, Ingress enzovoort). Al deze databases bieden ongeveer dezelfde functionaliteit, maar ze verschillen op de details wel degelijk van elkaar. U kunt vanuit PHP al deze databases benaderen met zogenoemde drivers. Deze drivers zorgen ervoor dat u de connectie met de database kunt opzetten. Binnen PHP heeft u de beschikking over objecten die u toegang tot de drivers bieden. U wilt zich in uw code helemaal niet bezighouden met de details van ieder type database. Het meest ideale zou zijn als u alle databases op exact dezelfde manier zou kunnen benaderen. Het Zend Framework gebruikt het Adapterpatroon om dit voor elkaar te krijgen. U instantieert een Adapter voor een bepaalde database. Als parameters geeft u eenmalig de juiste gegevens mee voor het type database dat u wilt gebruiken. Is dat eenmaal gebeurd, dan hoeft u daarna geen rekening meer te houden met het type database dat u gebruikt. De Adapter-interface is voor alle ondersteunde databases gelijk. $db = new Zend_Db_Adapter_Pdo_Mysql(); $db = new Zend_Db_Adapter_Oracle(); $db = new Zend_Db_Adapter_Db2();
De Adapter-klassen moeten dusdanig zijn geschreven dat ze de verschillen tussen de interfaces van de databases zelf oplossen en voor u één interface bieden. Binnen Zend Framework is het zelfs nog eenvoudiger gemaakt om Adapters aan te maken. Het Zend_Db-object biedt een factory-methode om automatisch de juiste Adapter te creëren.
Leer jezelf PROFESSIONEEL Werken met het Zend Framework
$db = Zend_Db::factory(‘Pdo_Mysql’, array( ‘host’ => ‘127.0.0.1’, ‘username’ => ‘webuser’, ‘password’ => ‘xxxxxxxx’, ‘dbname’ => ‘test’ ));
Het resulterende object is in dit geval een object van het type Zend_Db_Adapter_Pdo_ Mysql. Voorbeelden van het Adapter-patroon binnen Zend Framework zijn: I Zend_Db_Adapter_XXX databaseconnecties.
adapter voor
I Zend_Cache_Backend_XXX adapter voor wegschrijven van cache gegevens. I Zend_Auth_Adapter_XXX adapter voor opslag van authenticatiegegevens. I Zend_Http_Client_Adapter_XXX adapter voor het opzetten van de connectie met de clientcomputer.
Registry De werking van het design pattern Registry heeft veel weg van het design pattern Singleton. Een letterlijke vertaling van het woord registry is archief. Het Registry-patroon wordt dan ook gebruikt om objecten op te slaan voor later gebruik. In feite is een Registryklasse een vergaarbak voor objecten. We kunnen objecten in de Registry plaatsen onder een bepaalde naam. Op een later tijdstip kunnen we deze objecten met die naam weer opvragen en gebruiken. Het is niet de bedoeling zo maar ieder object in de Registry te plaatsen. We kiezen zorgvuldig welke objecten we bewaren. Zend Framework maakt uitgebreid gebruik van het design pattern Registry. Er is zelfs een klasse Zend_Registry die een volledige implementatie van het ontwerppatroon is. We bekijken (delen van) de Zend_Registry-klasse:
49
50
Appendix A Objectgeoriënteerd programmeren
class Zend_Registry extends ArrayObject { [...] public static function getInstance() { if (self::$_registry === null) { self::init(); } return self::$_registry; } public static function get($index) { $instance = self::getInstance(); if (!$instance->offsetExists($index)) { require_once ‘Zend/Exception.php’; throw new Zend_Exception(“No entry is registered for key ‘$index’”); } return $instance->offsetGet($index); } public static function set($index, $value) { $instance = self::getInstance(); $instance->offsetSet($index, $value); } public static function isRegistered($index) { if (self::$_registry === null) { return false; } return self::$_registry->offsetExists($index); } ... }
De Zend_Registry-klasse bestaat in essentie uit een array van objecten. Een object kan worden toegevoegd met de methode set() en kan worden opgevraagd met de methode get(). Om op te vragen of een object met een bepaalde naam bestaat, gebruiken we de methode isRegistered(). Wat opvalt is dat
Leer jezelf PROFESSIONEEL Werken met het Zend Framework
Zend_Registry zelf weer gebruik maakt van het Singleton-ontwerppatroon. Er is namelijk normaal gesproken maar één Registry-object in gebruik. Het volgende voorbeeld geeft het gebruik van Zend_Registry weer. Deze code wordt bijvoorbeeld in de Bootstrap geplaatst die altijd wordt uitgevoerd aan het begin van een nieuw request. Er wordt een Zend_Logobject gecreëerd wat debug-logging naar een bestand mogelijk maakt. Dit object wordt in de Registry gezet voor later gebruik. $logger = new Zend_Log(new Zend_Log_Writer_ Stream(‘/log/log.txt’)); Zend_Registry::set(‘logger’, $logger);
In een ander deel van de applicatie kunnen we een debuglog regel wegschrijven met behulp van de volgende code: if (Zend_Registry::isRegistered(‘logger’)) { Zend_Registry::get(‘logger’)->info(‘Dit is een debugregel’); }
Model View Controller Voor de volledigheid hebben we het MVContwerppatroon opgenomen in deze appendix. Omdat het Zend Framework volledig is opgehangen aan het MVC-patroon, wordt dit in hoofdstuk 2 uitgebreid behandeld.
Table Data Gateway Het ontwerppatroon Table Data Gateway wordt uitgebreid behandeld in hoofdstuk 7 van het boek.
Datamapper Het ontwerppatroon Datamapper wordt uitgebreid behandeld in hoofdstuk 7 van het boek.
51