OOP Beginnershandleiding (PHP5)
- Inleiding
- Object geörienteerd denken
- Foute denkwijze
- Object georiënteerd programmeren
- Visibility
- Naamgeving
- Constructor __construct()
- Voorbeeld: HTML tabel
- Inheritance
- Voorbeeld: HTML tabel 2 (inheritance)
- Static methods en properties
- Abstract classes en Interfaces
- Magic methods
- Slotwoord en referenties
- Reacties op deze tutorial
Magic methods
Binnen OO PHP kennen we een aantal zogenaamde magic methods. Dit zijn methods die binnen elke class werken en een speciale functionaliteit hebben. Eerder in deze handleiding zijn we al zo'n method tegen gekomen, namelijk __construct(), die de constructor van een class definieert.Magic methods zijn herkenbaar aan de dubbele underscore voorafgaand aan de naam van de method. In dit hoofdstuk zal ik de andere magic methods bespreken en het gebruik ervan toelichten.
__set() en __get()
PHP is standaard een 'loosely typed language' en als gevolg daarvan is het niet noodzakelijk om variabelen te declareren alvorens ze te gebruiken. Ditzelfde geldt voor de properties van een class:
Code
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
<?php
class User {
public $username;
}
$user = new User();
$user->username = 'jan';
$user->email = 'jan@email-example.nl';
echo $user->email;
?>
class User {
public $username;
}
$user = new User();
$user->username = 'jan';
$user->email = 'jan@email-example.nl';
echo $user->email;
?>
Code: output
1
jan@email-example.nl
Dit voorbeeld zal geen foutmeldingen opleveren ondanks dat de property $email niet bestaat. PHP zal deze property gewoon aanmaken met een public visibility en de opgegeven waarde eraan toekennen.
In eerdere hoofdstukken van deze handleiding zou dit gedrag van PHP een reden zijn om nooit deze notatie te gebruiken om properties van een object te manipuleren. Daar zouden we liever gekozen hebben voor een alternatieve oplossing via een set- of get-method. PHP biedt echter een oplossing waarmee we dit gedrag kunnen beïnvloeden, de __set() en __get() magic methods.
Met behulp van __set() kun je beïnvloeden hoe informatie opgeslagen wordt binnen je object. De __get() method bepaalt logischerwijs de manier hoe informatie uit je object opgehaald kan worden:
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
class User {
public $username;
private $_data;
public function __construct($username) {
$this->username = $username;
$this->_data = array();
}
public function __set($var, $value) {
$this->_data[$var] = $value;
}
public function __get($var) {
if(isset($this->_data[$var])) {
return $this->_data[$var];
}
}
}
$jan = new User('jan');
$jan->email = 'jan@email-example.nl';
echo $jan->email;
?>
class User {
public $username;
private $_data;
public function __construct($username) {
$this->username = $username;
$this->_data = array();
}
public function __set($var, $value) {
$this->_data[$var] = $value;
}
public function __get($var) {
if(isset($this->_data[$var])) {
return $this->_data[$var];
}
}
}
$jan = new User('jan');
$jan->email = 'jan@email-example.nl';
echo $jan->email;
?>
Code: output
1
jan@email-example.nl
Dit voorbeeld verschilt qua output niets van het voorbeeld daarvoor maar achter de schermen gebeurt er wel degelijk iets anders. PHP herkent dat de $email property niet bestaat binnen de class. Er wordt gekeken of er een __set() method bestaat die dit af kan handelen voordat de property als public gedeclareerd wordt. In ons geval is dat zo en wordt de waarde weggeschreven naar de array $_data.
De __get() method werkt op eenzelfde manier. Als er een property opgevraagd wordt die niet bestaat, zal in plaats van een foutmelding eerst __get() aangeroepen worden. Aangezien wij in __set() de waarden weggeschreven hebben naar de $_data array, zullen we in __get() controleren of de opgevraagde variabele daarin voorkomt. Als dat het geval is, wordt de waarde van bijbehorende variabele geretourneerd.
De __set() method is ook te gebruiken om het loosely typed gedrag van PHP in te perken. Het enige dat we dan hoeven te doen, is vanuit de __set() method een foutmelding geven en op die manier voorkomen dat er geen properties gebruikt kunnen worden die vooraf niet gedeclareerd zijn. Dit zou er als volgt uit kunnen zien.
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class User {
private $_username;
public function __construct($username) {
$this->_username = $username;
}
public function __set($var, $value) {
echo 'Kan geen waarde toekennen aan onbestaande property $'.$var.'.';
}
}
$jan = new User('jan');
$jan->email = 'jan@email-example.nl';
?>
class User {
private $_username;
public function __construct($username) {
$this->_username = $username;
}
public function __set($var, $value) {
echo 'Kan geen waarde toekennen aan onbestaande property $'.$var.'.';
}
}
$jan = new User('jan');
$jan->email = 'jan@email-example.nl';
?>
Code: output
1
Kan geen waarde toekennen aan onbestaande property $email.
Deze methode sluit goed aan bij de gedachte dat elk object zijn eigen eigenschappen bepaalt en de enige is die daar verantwoordelijk voor is.
Opmerking: het is niet gebruikelijk om op deze manier foutmeldingen te geven vanuit een class. Dit doen we liever met behulp van de Exception class waar ik in het hoofdstuk over foutafhandeling op in zal gaan.
__isset() en __unset()
De __isset() en __unset() methods kunnen we gebruiken om enerzijds te controleren of niet vooraf gedeclareerde properties bestaan (dus met behulp van __set() aangemaakt zijn) en anderzijds bestaande niet vooraf gedeclareerde properties unsetten. Dit kan er als volgt uit zien:
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
class User {
public $username;
private $_data;
public function __construct($username) {
$this->username = $username;
$this->_data = array();
}
public function __set($var, $value) {
$this->_data[$var] = $value;
}
public function __get($var) {
if(isset($this->_data[$var])) {
return $this->_data[$var];
}
}
public function __isset($var) {
return isset($this->_data[$var]);
}
public function __unset($var) {
unset($this->_data[$var]);
}
}
$jan = new User('jan');
$jan->email = 'jan@email-example.nl';
var_dump(isset($jan->email));
unset($jan->email);
var_dump(isset($jan->email));
?>
class User {
public $username;
private $_data;
public function __construct($username) {
$this->username = $username;
$this->_data = array();
}
public function __set($var, $value) {
$this->_data[$var] = $value;
}
public function __get($var) {
if(isset($this->_data[$var])) {
return $this->_data[$var];
}
}
public function __isset($var) {
return isset($this->_data[$var]);
}
public function __unset($var) {
unset($this->_data[$var]);
}
}
$jan = new User('jan');
$jan->email = 'jan@email-example.nl';
var_dump(isset($jan->email));
unset($jan->email);
var_dump(isset($jan->email));
?>
Code: output
1
bool(true) bool(false)
Dit voorbeeld spreekt denk ik redelijk voor zich. Zodra isset() aangeroepen wordt voor een properties die in eerste instantie niet gedclareerd is in de class, wordt automatisch de __isset() method aangeroepen. Hierin is vervolgens de functionaliteit geprogrammeerd dat bekeken wordt of de betreffende variabele in de $_data array voorkomt. Hetzelfde geldt voor het aanroepen van de unset() functie op niet vooraf gedeclareerde properties.
Let wel goed op met het gebruik van deze functionaliteit en dan vooral bij het gebruik van unset(). Het is namelijk ook mogelijk om public gedeclareerde properties te unsetten, hetgeen in veel gevallen niet gewenst is. Ik zou hier aanraden om deze functionaliteit in de class zelf te programmeren in plaats van deze 'handige' __unset() method te gebruiken.
__call()
De __call() magic method doet voor methodes hetzelfde als __set() en __get() voor properties doen. Als een niet bestaande method aangeroepen wordt, wordt deze automatisch doorgegeven aan de __call() magic method.
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class User {
public $username;
public function __construct($username) {
$this->username = $username;
}
public function __call($method, $args) {
echo 'De method User::'.$method.' is niet gedeclareerd';
}
}
$jan = new User('jan');
$jan->setEmail('jan@email-example.nl');
?>
class User {
public $username;
public function __construct($username) {
$this->username = $username;
}
public function __call($method, $args) {
echo 'De method User::'.$method.' is niet gedeclareerd';
}
}
$jan = new User('jan');
$jan->setEmail('jan@email-example.nl');
?>
Code: output
1
De method User::setEmail is niet gedeclareerd
Dit voorbeeld laat zien dat __call() inderdaad de aanroep van een niet bestaande method opvangt. Het is natuurlijk niet nuttig om deze foutmelding te echoën, sterker nog dan kun je beter PHP de foutmelding laten genereren door geen __call() te gebruiken.
Waar deze method wel van pas komt is bijvoorbeeld bij het gebruik van meerdere classes die van elkaar afhankelijk zijn. Ik zal hier geen voorbeeld van geven maar er zijn situaties te bedenken waarin je een method probeert aan te roepen op het ene object en er intern met behulp van __call() de method van een ander object uitgevoerd wordt. Deze toepassingen gaan echter te ver om hier in deze handleiding te bespreken.
__toString()
De laatste magic method die ik in deze handleiding zal behandelen, is __toString(). Deze method wordt automatisch aangeroepen wanneer een object omgezet wordt in een string, bijvoorbeeld bij het echoën van een object:
Code
1
2
3
4
2
3
4
<?php
$table = new Table();
echo $table;
?>
$table = new Table();
echo $table;
?>
We zien hier al een referentie naar de eerdere HTML tabel voorbeelden, en dat is ook precies waar ik hier gebruik van wil maken. We zouden de verschillende draw() methods in de Table, Row en Cell classes namelijk kunnen vervangen door deze __toString() magic method:
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
<?php
class Table {
private $_rows;
public function __construct() {
$this->_rows = array();
}
public function append($row) {
$this->_rows[] = $row;
}
public function __toString() {
$output = '<table border="1">'.PHP_EOL;
foreach($this->_rows as $row) {
$output .= $row;
}
$output .= '</table>'.PHP_EOL;
return $output;
}
}
class Row {
private $_cells;
public function __construct() {
$this->_cells = array();
}
public function append($cell) {
$this->_cells[] = $cell;
}
public function __toString() {
$output = '<tr>'.PHP_EOL;
foreach($this->_cells as $cell) {
$output .= $cell;
}
$output .= '</tr>'.PHP_EOL;
return $output;
}
}
class Cell {
protected $_content;
public function __construct($content) {
$this->_content = $content;
}
public function __toString() {
return '<td>'.$this->_content.'</td>'.PHP_EOL;
}
}
/* Procedurele code */
$cellA1 = new Cell('Dit is cel A1');
$cellA2 = new Cell('Dit is cel A2');
$rowA = new Row();
$rowA->append($cellA1);
$rowA->append($cellA2);
$rowA->append(new Cell('Dit is cel A3')); // Zo kan het ook!
$table = new Table();
$table->append($rowA);
// Weergave gaat nu met behulp van echo!
echo $table;
?>
class Table {
private $_rows;
public function __construct() {
$this->_rows = array();
}
public function append($row) {
$this->_rows[] = $row;
}
public function __toString() {
$output = '<table border="1">'.PHP_EOL;
foreach($this->_rows as $row) {
$output .= $row;
}
$output .= '</table>'.PHP_EOL;
return $output;
}
}
class Row {
private $_cells;
public function __construct() {
$this->_cells = array();
}
public function append($cell) {
$this->_cells[] = $cell;
}
public function __toString() {
$output = '<tr>'.PHP_EOL;
foreach($this->_cells as $cell) {
$output .= $cell;
}
$output .= '</tr>'.PHP_EOL;
return $output;
}
}
class Cell {
protected $_content;
public function __construct($content) {
$this->_content = $content;
}
public function __toString() {
return '<td>'.$this->_content.'</td>'.PHP_EOL;
}
}
/* Procedurele code */
$cellA1 = new Cell('Dit is cel A1');
$cellA2 = new Cell('Dit is cel A2');
$rowA = new Row();
$rowA->append($cellA1);
$rowA->append($cellA2);
$rowA->append(new Cell('Dit is cel A3')); // Zo kan het ook!
$table = new Table();
$table->append($rowA);
// Weergave gaat nu met behulp van echo!
echo $table;
?>
Dit script geeft dezelfde output als het eerste voorbeeld van de HTML tabel, alleen wordt deze nu op een andere manier gegenereerd. Zodra een object geëchoed wordt, wordt de __toString() method aangeroepen. In de code zien we dat de echo's vervangen zijn en dat de objecten nu daadwerkelijk een string retourneren in plaats van zelf voor de output te zorgen. Precies zoals het hoort.
Hiermee wil ik dit hoofdstuk over magic methods afsluiten. Naast de methods die ik hier beschreven heb, zijn er nog een aantal die ik niet genoemd heb. De functionaliteit daarvan overstijgt het niveau van deze handleiding, maar voor de volledigheid zal ik ze toch noemen:
- __sleep() en __wakeup() - te gebruiken bij het serialiseren en unserialiseren van objecten
- __autoload() - te gebruiken om classes automatisch te laten (voorbeeld volgt in deze handleiding)
- __clone() - te gebruiken om een kopie van een object te maken