Objects instantieren met data al in private properties
Je hebt nu twee mogelijkheden, oftewel roep je de constructor helemaal niet aan... wat ook voor problemen kan zorgen of je roept de constructor wel aan met de argumenten die je zelf toevoegt.
Code (php)
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
<?php
class Instance {
/**
* Create a new instance of the object and add some properties even
* when they are private or protected.
*
* @param string $class,
* @param array $properties,
* @param array | bool $constructor,
* @return bool | object
*/
public static function create($class, array $properties = array(), $constructor = false) {
/**
* Check if the class exists. When the class could not been loaded
* return false.
*/
if(class_exists($class, true) === false) {
return false;
}
/**
* Go on. Make the class and add all the properties the user gave
* in the array.
*/
$reflection = new ReflectionClass($class);
if($constructor === false) {
$instance = $reflection->newInstanceWithoutConstructor();
}
else {
$instance = $reflection->newInstanceArgs((array) $constructor);
}
foreach($properties as $property => $value) {
try {
/**
* Get the property from the $reflection, make it accessible and
* set the value.
*/
$reflectionProperty = $reflection->getProperty($property);
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($instance, $value);
}
catch(ReflectionException $e) {
/**
* Handle the exception or do nothing.
*/
}
}
return $instance;
}
}
class User {
/**
* The identifier for the user.
*
* @param int $userId
*/
private $userId = 'ForNowAString';
/**
* For test purposes only.
*/
private $constructor = false;
public function __construct() {
$this->constructor = 'YouHaveCalledTheConstructor';
}
/**
* Add the user id to this class.
*
* @param int $userId,
* @return void
*/
public function setUserId($userId) {
$this->userId = $userId;
}
/**
* Return the identifier for the user.
*
* @return int $userId
*/
public function getUserId() {
return (int) $this->userId;
}
}
print_r( Instance::create('User', array('userId' => 1), true) );
print_r( Instance::create('User', array('userId' => 1)) );
?>
class Instance {
/**
* Create a new instance of the object and add some properties even
* when they are private or protected.
*
* @param string $class,
* @param array $properties,
* @param array | bool $constructor,
* @return bool | object
*/
public static function create($class, array $properties = array(), $constructor = false) {
/**
* Check if the class exists. When the class could not been loaded
* return false.
*/
if(class_exists($class, true) === false) {
return false;
}
/**
* Go on. Make the class and add all the properties the user gave
* in the array.
*/
$reflection = new ReflectionClass($class);
if($constructor === false) {
$instance = $reflection->newInstanceWithoutConstructor();
}
else {
$instance = $reflection->newInstanceArgs((array) $constructor);
}
foreach($properties as $property => $value) {
try {
/**
* Get the property from the $reflection, make it accessible and
* set the value.
*/
$reflectionProperty = $reflection->getProperty($property);
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($instance, $value);
}
catch(ReflectionException $e) {
/**
* Handle the exception or do nothing.
*/
}
}
return $instance;
}
}
class User {
/**
* The identifier for the user.
*
* @param int $userId
*/
private $userId = 'ForNowAString';
/**
* For test purposes only.
*/
private $constructor = false;
public function __construct() {
$this->constructor = 'YouHaveCalledTheConstructor';
}
/**
* Add the user id to this class.
*
* @param int $userId,
* @return void
*/
public function setUserId($userId) {
$this->userId = $userId;
}
/**
* Return the identifier for the user.
*
* @return int $userId
*/
public function getUserId() {
return (int) $this->userId;
}
}
print_r( Instance::create('User', array('userId' => 1), true) );
print_r( Instance::create('User', array('userId' => 1)) );
?>
En je optie om het aan te roepen zonder constructor is ook geen optie, want dan krijg ik:
Fatal error: Call to undefined method ReflectionClass::newInstanceWithoutConstructor()
(ik heb php 5.3.4 en die method is er pas vanaf php 5.4.0)
Want als je er serialize() op loslaat krijg je de volgende code gepresenteerd.
Code (php)
1
O:5:"Admin":3:{s:12:"*signature";N;s:12:"UseruserId";s:13:"ForNowAString";s:17:"Userconstructor";s:27:"YouHaveCalledTheConstructor";}
Daarnaast is er met mijn code de mogelijkheid om de constructor aan te roepen. Niemand verplicht je hier natuurlijk toe. Maar het kan wel voordelen hebben als je bijvoorbeeld een object aanmaakt in de constructor die nodig is om de klasse te laten werken of om toch data door te spelen.
Dat instanceWithoutConstructor() vanaf versie 5.4 aanwezig is is dus natuurlijk wel een probleem. Maar meestal gebruik ik één van de laatste versies van PHP en dus kreeg ik geen errors.
Je geeft dan een classnaam mee, de database class trekt de data uit de database en stopt dat in een nieuw aan te maken object (van de class die je hebt meegegeven). Het is daarbij dus niet van belang welke class daarbij wordt ge-extend. Een constructor wil je dus ook juist niet aanroepen, want de generieke database class moet zich geen zorgen gaan maken over welke parameters dan allemaal mee moeten. Dit houdt overigens wel in dat je geen classnaam mee zou moeten geven van een class die afhankelijk is van andere objecten die in de constructor meegegeven moeten worden.
Overigens betekent dit laatste dus ook dat de oplossing die ik eerder moest toepassen om jouw methode werkend te krijgen eigenlijk best acceptabel is. In alle gevallen mag de class niet afhankelijk zijn van de constructor, anders werkt het niet meer generiek genoeg.
Code (php)
1
2
3
4
5
6
2
3
4
5
6
<?php
$sth->fetchAll(PDO::FETCH_CLASS, 'User');
$sth->fetchAll(PDO::FETCH_INTO, new User());
?>
$sth->fetchAll(PDO::FETCH_CLASS, 'User');
$sth->fetchAll(PDO::FETCH_INTO, new User());
?>
In het eerste geval kun je niet zien dat de constructor word aangeroepen... maar dit is dus wel het geval en als je gewoon een gebruiker aanmaakt roep je sowieso de constructor aan.
Een klasse mag niet afhankelijk zijn van de constructor. Maar het kan dus wel nuttig zijn om toch de variabelen door te geven aan de constructor als je dat wenst en dat is dus mogelijk maar hoeft niet.
Echter, en daar gaat dit hele verhaal over, stel dat ik over 10 jaar om wat voor reden dan ook wel ineens zou willen/moeten(?) overstappen op een andere database interface die de fetch_class methode niet ondersteunt, dan werkt de code dus niet meer.
Grofweg kan ik nu 2 dingen doen. Ik gebruik gewoon de PDO::FETCH_CLASS methode en mocht ik over een aantal jaren overstappen naar een andere databse interface dan zal ik op dat moment een andere oplossing moeten vinden (bijv. de oplossing van Erwin of die van jou). Maar voor nu gebruik ik dan gewoon FETCH_CLASS en later, ach.. wie dan leeft wie dan zorgt. Of, optie 2, i gebruik die hele FETCH_CLASS methode niet maar ik gebruik direct al de methode van Erwin of jou.
Optie 1 heeft als voordeel dat ik nu niet moeilijk hoef te doen en gewoon PDO::FETCH_CLASS kan gebruiken. Echter, heel misschien betekent dit dat ik ooit in de toekomst als ik overstap naar een andere database interface ik een en ander zou moeten aanpassen.
Optie 2 heeft als voordeel dat ik nooit meer iets hoef aan te passen, maar het nadeel is dat het omslachtiger is (meer code voor hetzelfde resultaat) en dat het significant trager werkt dan optie 1, wat dus nadelig is voor de performance.
Wat is jouw visie hierop?
Ook zou je gebruik kunnen maken van een wrapper die dan de verschillende database interfaces ondersteunt en dan kun je heel gemakkelijk overschakelen van de één naar de ander. Neem nu aan dat de volgende interface geen fetch object heeft kun je de bovenstaande code in no-time implementeren en dan is het probleem opgelost!
De bovenstaande code zou je wel kunnen gebruiken in een File klasse die dus bestanden inleest. Als je dan een object terug wilt heb je de bovenstaande code dus nodig. Anders zou ik er niet voor gaan.
"De bovenstaande code zou je wel kunnen gebruiken in een File klasse die dus bestanden inleest. Als je dan een object terug wilt heb je de bovenstaande code dus nodig. Anders zou ik er niet voor gaan."
Wat bedoel je hiermee? Kun je een voorbeeldje geven?
"Stel nu dat PDO komt te vervallen. Dan zul je sowieso de hele applicatie (of in mijn geval de Mapper klassen) moeten herschrijven."
het zou dan juist mooi zijn als je bijv. alleen de connectie met de database hoeft te wijzigen en dat de rest gewoon blijft werken :)
Maar je hebt wel een punt. Misschien is het zinvoller om voor nu gewoon PDO::FETCH_CLASS te gebruiken en pas op het moment dat het aan de orde is een alternatief te gaan maken. (Hopelijk kan ik dit topic dan nog terugvinden...)
Je zou natuurlijk gewoon nog altijd de setMethod() & getMethod() kunnen gebruiken. Maar daarvoor moest er geen topic aangemaakt worden.
Als je dan toch zou willen veranderen van interface maak je waarschijnlijk beter gebruik van wrappers zodat je in no-time kunt veranderen. Als er dan een nieuwe database interface komt maakt je een nieuwe wrapper en dan is het opgelost!
Maar zeg nu zelf, om de hoeveel tijd zal PHP de database interface drastisch veranderen?
Met het laatste ben ik het overigens totaal niet eens. Een class kan juist wel afhankelijk zijn van de constructor, sterker, het is in veel gevallen volkomen juist om dat te doen. Op die manier leg je namelijk strikte afhankelijkheden tussen classes (wanneer dat nodig is). Het is juist een goed uitgangspunt om ervoor te zorgen dat als een benodigd object ontbreekt je een compile time error krijgt en niet een run time error. Dat forceer je door dit soort objecten mee te geven in de constructor en niet via setters.
(disclaimer: compile time errors in php is een beetje een onjuiste term omdat het compileren run time gebeurd, maar ik hoop dat evengoed duidelijk is wat ik bedoel)
Maar samenvattend:
- welke manier je ook gebruikt, de constructor zou zonder parameters aangeroepen moeten worden, anders krijg je bijna altijd ofwel foutmeldingen, ofwel problemen
- er zijn meerdere manieren om het te doen
- qua performance is het langzamer dan direct instantieren (lijkt me een open deur), maar de twee methodes zijn wat mij betreft niet significant verschillend
Edit: even de quote erbij omdat er inmiddels wat posts tussen waren gekomen
Aaron - op 25/05/2013 15:40:45:
Een klasse mag niet afhankelijk zijn van de constructor. Maar het kan dus wel nuttig zijn om toch de variabelen door te geven aan de constructor als je dat wenst en dat is dus mogelijk maar hoeft niet.
Gewijzigd op 25/05/2013 16:21:38 door Erwin H
Een klasse kan dus afhankelijk zijn. Maar de meeste klassen die FETCH_CLASS gebruiken zijn helemaal nergens afhankelijk. Want de meeste zijn gewoon setMethod() & getMethod() bij mij. Voor de rest gebruik ik mijn Mappers waar de constructor dus wel belangrijk is. Daarom is er dus ook bij mijn oplossing de mogelijkheid om de parameters door te geven aan de constructor, zo wordt die toch aangeroepen.
Het kan altijd nuttig zijn om toch de constructor aan te roepen, want anders had PHP de __construct() & __destruct() functies niet geïmplementeerd.
Maar hoe geef je dan met FETCH_CLASS een argument mee aan de constructor?
-----
Het kan dus wel. Maar je kunt nog altijd geen argumenten van de rij zelf meegeven? Zie: http://stackoverflow.com/questions/9127693/pdo-using-pdofetch-props-late-and-construct-call#answer-9134544
Ik denk dat ik in m'n PDO class gewoon een fetchClass method maak, die dan gebruikmaakt van PDO::FETCH_CLASS en als ik dan ooit (met de nadruk op ALS en OOIT) zou moeten overstappen naar een andere interface, dan maak ik voor die interface wel een nieuwe fetchClass method.
Bedankt allebei voor jullie reacties. Het was weer een interessant topic :-)
Toevoeging op 25/05/2013 16:33:41:
Aaron - op 25/05/2013 16:28:35:
Het kan dus wel. Maar je kunt nog altijd geen argumenten van de rij zelf meegeven? Zie: http://stackoverflow.com/questions/9127693/pdo-using-pdofetch-props-late-and-construct-call#answer-9134544
Oké!