Technieken, tricks en tips
1. Inleiding
2. Fluent Interfaces
3. Serializeren, __sleep() en __wakeup()
4. Singelton
5. Recursie
5.1. Inleiding
5.2. Gewone recursieve functies
5.3. Wederkerig recursieve functies
5.4. Nut?
6. Template method pattern
7. Core klasse
top
1. Inleiding
Dit artikel zal bestaan uit verschillende kleine artikeltjes die tricks en tips weggeven over PHP (waarbij ik me zoveel mogelijk ga toespitsen op 'object oriëntatie').
Op het moment van schrijven is er slechts nog maar 1 artikeltje maar hopelijk zal ik dit in de toekomst kunnen aanvullen met meerdere.
top
2. Fluent Interfaces
Ik was laatst weer eens aan het zoeken naar nieuwe technieken in het oop gebeuren en kwam op fluent interfaces uit: http://martinfowler.com/bliki/FluentInterface.html.
Op het eerste zicht zag het er vrij raar en complex uit maar toen ik de
techniek door had zag ik hoe eenvoudig het allemaal wel niet is.
In dit korte artikeltje zal ik bespreken hoe dit in zijn werk gaat.
Als voorbeeld zullen we werken met een fictieve Input klasse (waar ik
later een code van zal geven). Een Input klasse is alleenstaand een
beetje nutteloos maar het wordt enkel gebruikt om een voorbeeld te
schetsen.
Deze Input klasse zal een HTML 'input element' terugsturen via de methode 'get()'.
Laten we aannemen dat het 'input element' slechts drie attributen heeft
(hoewel het er in het echt veel meer zijn): 'type', 'name' en 'value'.
We kunnen deze attributen setten door 3 methodes aan te roepen: 'setType(type)', 'setName(name)' en 'setValue(value)'.
Dit zou dan volgende code opleveren:
<?php
$input = new Input();
// we zouden de waarden ook hier kunnen meegeven
// maar dat gaan we niet doen, anders valt het hele idee weg
$input->setType('text');
$input->setName('voornaam');
$input->setValue('Koen');
// setten
echo $input->get();
// weergeven
?>
Via fluent interfaces kan bovenstaande code een hoop worden verkort.
Er kunnen drie regels worden weggelaten.
<?php
$input = new Input();
echo $input->setType('text')->setName('voornaam')->setValue('Koen')->get();
?>
Hoe komt het nu dat dit zou werken vraag je jezelf misschien af.
Het antwoord is eenvoudig, elke methode (buiten de laatste) moet $this terugsturen.
Een voorbeeldclass zou er dan als volgt uitzien:
<?php
class Input {
var $type;
var $name;
var $value;
function Input() {} /* kan nog iets komen */
function setType($type) {
$this->type = $type;
return $this;
}
function setName($name) {
$this->name = $name;
return $this;
}
function setValue($value) {
$this->value = $value;
return $this;
}
function get() {
return '<input type="' . $this->type . '" name="' . $this->name . '" value="' . $this->value . '" />';
}
}
?>
De grote vraag die we ons moeten stellen is: is dit bevorderlijk qua leesbaarheid. Dit kan van persoon tot persoon afhangen.
Als je aan een project in groep begint moet je dus de vraag stellen 'gaan we dit gebruiken'.
Ook ga je moeten oppassen en onthouden welke methodes nu juist '$this' terugsturen en welke niet.
Het is een experimentele techniek die soms handig kan zijn en in andere gevallen niet.
Tri Pham deelde in zijn blog mee dat dit bij een DAL (Database Abstraction Layer) mooi zou kunnen staan.
<?php
$dal->select('naam_kolom, adres_kolom')->from('naw')->where('leeftijd=15');
// source: http://www.scriptorama.nl/algemeen/fluent-interfaces
?>
top
3. Serializeren, __sleep() en __wakeup()
In dit kleine artikeltje zal ik het serializeren van een object bespreken.
Dit is een oude forumpost van mij waarvan ik denk dat het nog nut kan hebben.
__sleep en __wakeup heb je nodig
wanneer je je object serialiseerd. Dit betekend de waardes onthouden en
deze in string formaat opslaan. Zo kan je dus een heel object in 1 databaserow
opslaan. Het probleem is dat serialize nogal wat problemen heeft met
results. Deze waarden kunnen niet geserializeerd worden. Je kan PHP de opdracht geven deze variabelen te laten 'slapen'.
Bij de aanroep van __wakeup() ga je deze variabelen weer 'setten'...
Denk er aan dat een instantie van een class (het object) deserializeren alleen kan op een pagina waar de class definitie voorkomt.
Een voorbeeld hiervan vind je hieronder
<?php
class Test {
public function __construct($var, $foo, $bar) {
list($this->tvar, $this->foo, $this->bar) = array($var, $foo, $bar);
}
function __sleep() {
// deze vars zullen mee geserializeerd worden
return array('tvar', 'foo');
}
function __wakeup() {
$this->bar = "blaat";
}
public function run() {
echo $this->tvar . $this->foo . $this->bar;
}
private $tvar;
private $foo;
private $bar;
}
$test = new Test('blaat', 'blaat2', 'blaat3');
$test = serialize($test); // __sleep wordt aangeroepen
$test = unserialize($test); // __wakeup wordt aangeroepen
$test->run(); // 'blaatblaat2blaat'
?>
top
4. Singelton
Het singleton pattroon is een "design pattern" dat er voor zorgt dat je een beperkt aantal instances van een class kan maken.
Het is mogelijk om het te beperken tot 1 of een klein aantal.
De eerste vraag die je moet beantwoorden voor je hieraan begint is 'waarom zou ik dat willen?'.
Het antwoord is heel simpel: 'in sommige gevallen is het nutteloos om 2 instances te maken'.
Om een voorbeeld te geven:
- een MySQL-klasse, je gaat bij een standaard applicatie niet direct
met meerdere db's verbinden of proberen te verbinden op 1 pagina
- een Core-klasse, je gaat maar één klasse gebruiken om andere klasses in te laten. Denk maar aan het "ZEND FRAMEWORK"
- een Settings-klasse, je hebt maar 1 paar instellingen. Dit hangt er
natuurlijk af van wat voor settings je gebruikt. Als het om db-settings
gaat heb je maar 1 paar, gaat het om eender wat kan je er meerdere
hebben.
- en zo kan je nog duizend en één voorbeelden geven
- ...
En nu aan de slag:
hoe gaan we dit verwezenlijken.
Eerst en vooral gaan we het creëeren van een instantie vanaf buiten de klasse onmogelijk maken.
Dit lijkt moeilijker dan het is, volgende code zorgt hiervoor:
<?php
class Singleton {
private function __construct() {}
private function Singleton() {}
// zowel php5 als php4 constructor blokkeren
}
?>
Het is nu praktisch onmogelijk om volgende code uit te voeren:
<?php
$eenObject = new Singleton();
?>
Omdat er dan een private methode wordt aangeroepen.
Nu schiet waarschijnlijk de vraag teboven: hoe kan ik dan wel een instantie maken.
Hiervoor moeten we eerst de volgende vraag antwoorden: hoeveel instanties wil ik hebben.
Laten we aannemen dat we er één willen.
Volgende code zal dan deze instantie verlenen:
<?php
class Singleton {
static private $instance = NULL;
// we zouden hier een teller kunnen bijhouden met het aantal instanties zodat we het kunnen verhogen tot 2 of 3
private function __construct() {}
private function Singleton() {}
// zowel php5 als php4 constructor blokkeren
static public function createInstance() {
if(self::$instance == NULL) {
self::$instance = new self;
}
return self::$instance;
}
public function aFunction() {
echo "Ik wordt aangeroepen";
}
}
?>
Volgende code laat ons toe om een instantie te maken. Deze wordt op volgende manier aangeroepen.
<?php
$eenObject = Singleton::createInstance();
$eenObject->aFunction();
?>
Je moet natuurlijk niet overal Singleton gaan gebruiken omdat je vindt
dat het mooi staat. Gebruik het wanneer je het nodig hebt!
top
5. Recursie
5.1. Inleiding
Recursie is een interessante techniek met vele voordelen, Fenrir heeft ze ooit eens besproken maar dat artikel is verdwenen.
Daarom probeer ik het opnieuw te documenteren zodat mensen dit kunnen ontdekken en gebruiken.
Recursie komt niet alleen voor bij programmeertalen maar ook bij taal en wiskunde. Recursie betekend 'zelf-verwijzing'.
PHP is een recursief acroniem: een afkorting die naar zichzelf verwijst.
PHP staat voor: 'PHP Hypertext Preprocessor', het woord PHP komt voor in de afkorting ervan.
Een ander voorbeeld is GNU: 'GNU's Not Unix'.
Een ander voorbeeld is bijvoorbeeld wanneer je met een webcamera een filmpje maakt en dit rechtstreeks afspeelt op de computer.
Als je dan met de camera naar het computerscherm kijkt dan blijft het beeld zich oneindig diep herhalen.
Een voordeel van recursie bij programmeertalen is dat we het kunnen beïnvloeden en dus op tijd laten stoppen.
Een alternatief voor recursie is iteratie, denk maar aan de 'for'- en 'while'-loop.
Toch kan je met recursie net iets meer, je kan veel dieper verwijzen.
Er zijn twee vormen van recursieve functies binnen PHP:
- Gewone recursieve functies
- Wederkerig recursieve functies
We beginnen met de gewone te bespreken, of in ieder geval: het maken ervan.
top
5.2. Gewone recursieve functies
Om te beginnen ga ik eerst een klein voorbeeldje van iteratie geven. We gaan een tellertje maken dat van 5 naar 0 telt.
<?php
for($i = 5; $i >= 0; $i--) {
echo $i . "<br />";
}
?>
|
Bovenstaand voorbeeld is een basisvoorbeeld van herhaling, om hetzelfde resultaat te bekomen met recursie moet je een functie maken
die zichzelf aanroept. In de functie moet een voorwaardelijke constructie (if) zitten zodat je geen oneindige loop krijgt.
<?php
function countDown($teller = 5) {
if($teller >= 0) {
echo $teller . "<br />";
countDown(--$teller);
}
}
?>
|
We zullen dit stukje code stap voor stap analyseren.
We maken een functie aan genaamd 'countDown' en geven deze een standaardargument mee namelijk '$teller'.
Als deze niet is 'geset' heeft deze de waarde 5, anders de gesette waarde.
Vervolgens gaan we kijken of teller groter of gelijk is aan 0, zoja dan gaan we verder, zo nee: dan stopt het programma.
We gaan nu het getal op het scherm weergeven en als laatste roept de functie zichzelf aan met de de waarde van $teller min 1.
top
5.3. Wederkerig recursieve functies
Volgend voorbeeld demonstreert wat wederkerig recursieve functies zijn en doen.
<?php
function recursief_1($arg) {
if($arg > 0) {
echo $arg . "<br />";
recursief_2($arg - 2);
}
}
function recursief_2($arg) {
if($arg > 0) {
echo $arg . "<br />";
recursief_1(++$arg);
}
}
recursief_1(10);
?>
|
Wat gebeurt er nu?
Eerst wordt de functie 'recursief_1' aangeroepen met het argument 10. Dit wordt weergegeven en de functie 'recursief_2' wordt aangeroepen met de waarde van $arg (10) min 2.
De functie 'recursief_2' ontvangt dan het argument 2. Om het als een stappenplan op te schrijven:
10 wordt meegegeven als argument aan de functie 'recursief_1'.
8 wordt meegegeven als argument aan de functie 'recursief_2'.
9 wordt meegegeven als argument aan de functie 'recursief_1'.
7 wordt meegegeven als argument aan de functie 'recursief_2'.
8 wordt meegegeven als argument aan de functie 'recursief_1'.
6 wordt meegegeven als argument aan de functie 'recursief_2'.
7 wordt meegegeven als argument aan de functie 'recursief_1'.
5 wordt meegegeven als argument aan de functie 'recursief_2'.
6 wordt meegegeven als argument aan de functie 'recursief_1'.
4 wordt meegegeven als argument aan de functie 'recursief_2'.
5 wordt meegegeven als argument aan de functie 'recursief_1'.
3 wordt meegegeven als argument aan de functie 'recursief_2'.
4 wordt meegegeven als argument aan de functie 'recursief_1'.
2 wordt meegegeven als argument aan de functie 'recursief_2'.
3 wordt meegegeven als argument aan de functie 'recursief_1'.
1 wordt meegegeven als argument aan de functie 'recursief_2'.
2 wordt meegegeven als argument aan de functie 'recursief_1'.
|
top
5.4. Nut?
Wat is nu het nut van recursieve functies denk je nu misschien. Ten eerste: je kan oneindig diep gaan zoeken in een array, natuurlijk is daarvoor de functie 'array_map()' die een functie kan uitvoeren op een gehele array.
Je kan bijvoorbeeld ook een gehele directory uitlezen, je kan stoppen wanneer je wil, je kan een bestand unzippen en ga maar door.
Je zou bijvoorbeeld een array tot drie lagen diep kunnen doorzoeken en dan stoppen.
Waar het ook voor gebruikt wordt is 'brute force'. Je kan alle mogelijke combinaties kunnen afgaan en blijven afgaan tot je het hebt. Met recursie heb kan je met variabele lengtes werken.
Een voorbeeldje van hoe je een array kan uitlezen met recursie (zonder gebruik van 'array_map') vind je hieronder.
<?php
function lees_array($array) {
echo "<ul>";
foreach($array as $k => $v) {
echo "<li>";
if(is_array($v)) {
echo $k . ":";
lees_array($v);
} else {
echo $k . ": " . $v;
}
echo "</li>";
}
echo "</ul>";
}
$array = array(
1 => "Dit staat als eerste",
2 => "Dit als tweede",
3 => array(
"3.1" => "Dit staat in een onderverdeling",
"3.2" => "Dit ook"
)
);
lees_array($array);
?>
|
De output zal er dan zo uitzien.
- 1: Dit staat als eerste
- 2: Dit als tweede
- 3:
- 3.1: Dit staat in een onderverdeling
- 3.2: Dit ook
|
Hopelijk zal recursie voortaan het scripten voor jou versnellen, ik vind het in ieder geval een van de handigste dingen technieken het programmeren.
Je kan er zoveel mee zonder veel te moeten doen.
top
6. Template method pattern
Het template method pattern is een patroon dat je in staat stelt in grote lijnen te omschrijven hoe je code gaat verlopen.
Je gaat bijvoorbeeld van een Mens een klasse maken.
Deze klasse wil je later onderverdelen onder culturen.
Dus dat je mensen hebt uit verschillende culturen.
Over het algemeen kan je stellen dat elke Mens veel gelijkenissen vertoond qua acties:
- slapen
- opstaan
- eten
- bezigheid van de dag
- eten
- slapen
Tussen deze culturen verschillen de stappen.
Zo gaat bijvoorbeeld iemand van de westerse wereld werken terwijl
Indianen (laten we even stellen dat ze nog leven zoals vroeger) gaan
jagen.
De armeren onder ons gaan eten zoeken.
Wij slapen in een huis, Indianen in een wigwam, ...
Wat je wel kan stellen is dat voor elke mens deze stappen worden ondernomen (en dan gaan we niet op uitzonderingen zoeken).
Zo kunnen we dus een algemene mens klasse maken
<?php
abstract class Mens {
abstract function slapen();
abstract function opstaan();
abstract function eten();
abstract function bezigheid();
final function dagVerloop() {
$this->slapen();
$this->opstaan();
$this->eten();
$this->bezigheid();
$this->eten();
$this->slapen();
}
}
?>
We declareren de laatste methode final omdat deze niet meer mag veranderen.
Hier kan bijvoorbeeld ook een ingewikkeld algoritme staan om dingen te
berekenen die in sommige klassen op exact dezelfde manier berekend
moeten worden maar dan met andere waardes.
Via bovenstaande template kunnen we nu de culturen gaan inbouwen.
Een voorbeeldcode vind je hieronder:
<?php
abstract class Mens {
abstract function slapen();
abstract function opstaan();
abstract function eten();
abstract function bezigheid();
final function dagVerloop() {
$this->slapen();
$this->opstaan();
$this->eten();
$this->bezigheid();
$this->eten();
$this->slapen();
}
}
class Westers extends Mens {
function slapen() {
echo "Ik lig in een bed<br />";
}
function opstaan() {
echo "Ik sta op en kleed mij aan<br />";
}
function eten() {
echo "Ik doe de koelkast open en pak eten<br />";
}
function bezigheid() {
echo "Ik stap in de auto. Ik rijd naar het werk. Ik doe mijn werk. Ik ga middageten. ...<br />";
}
}
class Indiaan extends Mens {
function slapen() {
echo "Ik lig onder een matje in mijn WigWam<br />";
}
function opstaan() {
echo "Ik sta op<br />";
}
function eten() {
echo "Gelukkig ben ik gisteren gaan jagen, nu kan ik eten<br />";
}
function bezigheid() {
echo "Ik pak mijn pijl en boog en ga jagen<br />";
}
}
$jan = new Westers;
$jan->dagVerloop();
echo "<hr />";
$rodeBeer = new Indiaan;
$rodeBeer->dagVerloop();
?>
Het wordt allemaal misschien een beetje grof voorgesteld maar het gaat om de techniek, niet om de inhoud.
top
7. Core klasse
Een techniek die ik heb leren appreciëren door het ZEND Framework te
bestuderen is het 'inladen van andere klasses doormiddel van een core
klasse'.
Hierdoor kan je bepaalde klassen inladen doormiddel van 1 grote klasse deze te opdracht te geven.
Stel je library bestaat uit 20 klassen (een form klasse, een upload klasse, een validate klasse, enzovoort).
Je kan deze dan 1 maal in config.php inladen en dit bestand overal
'includen' maar hierdoor zullen onnodige klasse definities voorkomen.
Daarom is er een techniek ontworpen om ze alleen maar in te laden wanneer je ze nodig hebt, met een zo eenvoudig mogelijke code.
Deze core klasse zal volgens het singleton patroon worden opgebouwd.
<?php
class Core {
private function __construct() {}
private function Core() {}
}
?>
Hierboven kan je het begin hiervan zien.
Ik ga deze Core klasse uitbreiden tot een deftige basis, niet zoiets complex en geavanceerd als de Zend klasse.
We beginnen met een 'inlaadmodule'. Het is de bedoeling dat we
uiteindelijk alleen maar op volgende manier een klasse moeten laten.
<?php
$myInput = Core::loadModule("Core_Form_Input");
// dit zou dan synoniem moeten zijn voor $myForm = new Core_Form_Input;
?>
Zend Framework heeft een leuk ideetje gevonden om te kunnen weten waar
deze klasse zich bevind, namelijk door de _ te vervangen door /.
Zo kan het eenvoudig de directory uitlezen.
De Input klasse bevindt zich dan in de map Core/Form/ en het bestand heet Input.php.
We zullen eens kijken naar de methode 'loadModule'.
<?php
class Core {
private function __construct() {}
private function Core() {}
// Singleton
static public function loadModule($theClass) {
$the_file = str_replace("_", "/", $theClass) . ".php"; // nu krijgen we Core/Form/Input.php
if(self::isReadable($the_file)) {
//bestaat deze file wel
require_once $the_file; // we hoeven hem maar 1maal in te laden,
// zo kan de klasse meerdere keren worden aangeroepen
// maar moet het maar 1maal worden ingeladen
return new $theClass;
} else {
return false;
}
}
/**
* functie gekopieerd van ZEND FRAMEWORK
* Map: /
* File: Zend.php
* Class: Zend
* Original author: Zend
**/
static public function isReadable($filename)
{
$f = @fopen($filename, 'r', true);
$readable = is_resource($f);
if ($readable) {
fclose($f);
}
return $readable;
}
}
$input = Core::loadModule("Core_Form_Input");
?>
De Zend Core bevat veel meer dan deze functie, het kan ook interfaces
inladen, het kan op een uitgebreide manier bestanden inladen, enzovoort.
Dit is slechts om een basis idee te geven voor een Core, niemand houdt je tegen om verder te bouwen.
top
|