OOP model in class
Als ik het zo zie, dan lijkt het nog steeds alsof er geen aparte User class is, maar enkel een mapper.
Een data mapper "mapt" eigenschappen van een object naar kolommen in een database(tabel) en vice versa. Het object zelf weet niet waar of hoe het wordt opgeslagen: dat is de verantwoordelijkheid van de mapper.
Dat schaalt flexibel omdat je veel gemakkelijker databases kunt vervangen door andere databases of bijvoorbeeld door een API. Dat gaat allemaal voorbij aan het User-object en alle andere functionaliteit die User-objecten gebruikt. Als die logica echter allemaal "in" het User-object zelf zit, heb je een enorme berg werk wanneer je bijvoorbeeld MySQL-queries moet herschrijven naar API-calls.
Een voorbeeld van een UserMapper die een User-class afhandelt:
https://github.com/domnikl/DesignPatternsPHP/blob/master/Structural/DataMapper/UserMapper.php
Als je het nog eenvoudiger en directer wilt doen, kan dat overigens met het active record pattern. Daarbij komt het User(Model) overeen met één record in een databasetabel:
Code (php)
1
2
3
4
5
6
2
3
4
5
6
<?php
$user = new User();
$user->name = 'Jan';
$user->email = '[email protected]';
$user->save();
?>
$user = new User();
$user->name = 'Jan';
$user->email = '[email protected]';
$user->save();
?>
SELECT * FROM users WHERE id = 1
Dat kan en is het eenvoudigst, maar aangezien de UserMapper het enige object is dat weet hoe zowel het User-model enerzijds als de User-tabel anderzijds eruitzien, kan de data mapper ook gerichter te werk gaan dan met alleen een SELECT *. Dat is bijvoorbeeld al gauw het geval wanneer je meer dan één tabel gebruikt voor data die wel gerelateerd zijn aan de gebruiker maar daarmee geen directe één-op-één-relatie hebben.
Uhm, wát kan?
Mijn vraag was waar de queries worden ingesteld. Iemand zei eerder dat een model/mapper niet weet of hij met een database of bijv. Xml-bestand te maken heeft. Maar ergens moeten toch de queries worden ingesteld.
De data mapper voert de queries uit. Of roept een API aan. Of maakt een XML-bestand. Het model zelf niet, omdat het niet weet waar en hoe het wordt opgeslagen. En eigenlijk weet het model niet eens dát het wordt opgeslagen.
Waarom zou je de queries niet in een model zetten, en als je vervolgens je data ergens anders vandaan wilt halen een ander model toepassen om de data op te halen?
Even heel simplistisch:
Code (php)
De verwarring ontstaat mede door jouw eigen naamgeving: je hebt een "UserModel", dat duidelijk een model is, maar daarnaast een "User" waarvan niet in een oogopslag duidelijk is wat het dan is. Geen model kennelijk, maar ook niet expliciet iets anders.
Om onduidelijke redenen vind je dat je het UserModel in een User moet injecteren. Kun je eens wat code van die User laten zien, zodat we die redenen begrijpen?
Even heel simpel ... hieronder een stukje van de User class, nu even met slechts 1 method om de naam op te halen. Via die User class kun je dus dit doen "echo $user->getName();".
Volgens mij is het bovenstaande wel duidelijk. Het model haalt nu vervolgens de data op uit de database, maar stel dat ik data zou willen ophalen op een andere manier, dan maak ik een nieuw model en dat geef ik dan in plaats van het huidige model mee aan de constructor van de User class. De User class weet dus niet waar de gegevens vandaan komen. Die spreekt gewoon telkens het model aan, en het model haalt de data op.
Wil je op een gegeven moment je data ergens anders vandaan halen, dan kan overal in je website de User class gewoon gebruikt blijven worden. Echter, bij het initiëren van het object wijzig je het model.
Code (php)
Is het nu duidelijker wat ik bedoel?
Aan de herhaling van de methode getName() zie je al dat dit niet DRY is. Sterker nog, het is een letterlijke herhaling: User->getName() doet niets meer of minder dan UserModel->getName().
Met een User extends UserModel bereik je precies hetzelfde, maar belangrijker is de vraag of je niet gewoon één van die twee klassen moet weggooien.
Als ik een class weggooi, gaat het mis als ik wil wisselen van de manier waarop ik m'n data ophaal.
Stel, overal in je applicatie gebruik ik bijv. $user->getName();
Maar ... gebruikers kunnen zijn opgeslagen in de interne database, of ze komen van (ik noem maar wat) Facebook. Dan moet in het ene geval de naam uit de database worden opgehaald, en in het andere geval via een API van Facebook. Ik het dus 2 aparte modellen nodig ... denk ik :-s
Als je een goede methode weet om dit op te lossen dan hoor ik dat heel graag.
Behandel de User die je nu hebt als het model, met getters en setters voor de eigenschappen, maar delegeer het opslaan en weer opvragen van het model naar een andere klasse, bijvoorbeeld de UserMapper.
Kun je misschien een heeeeeeel simplistisch voorbeeld geven van hoe jij het dan bedoelt? Bijv. aan de hand van de method getUsername()? Dat zou heel fijn zijn. Als ik het principe doorheb, kan ik daar op voortborduren :-)
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
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
<?php
class UserMapper()
{
protected $Database;
public function __construct(\PDO $database)
{
$this->Database = $database;
}
public function fetchUserById($user_id)
{
$stmt = $this->Database->prepare('SELECT * FROM users WHERE user_id = :user_id');
$stmt->bindValue(':user_id', $user_id, \PDO::PARAM_INT);
$stmt->execute();
$result = $stmt->fetchAll(\PDO::FETCH_ASSOC);
$user_data = $result[0];
$user = new User();
$user->setUsername($user_data['username']);
return $user;
}
}
class User
{
protected $Username;
public function getUsername()
{
return $this->Username;
}
public function setUsername($username)
{
$this->Username = $username;
}
}
// Gebruik:
$db = new PDO('mysql:dbname=test;host=localhost', 'root', '');
$user_mapper = new UserMapper($db);
$user = $user_mapper->fetchUserById($_SESSION['user_id']);
echo $user->getUsername();
?>
class UserMapper()
{
protected $Database;
public function __construct(\PDO $database)
{
$this->Database = $database;
}
public function fetchUserById($user_id)
{
$stmt = $this->Database->prepare('SELECT * FROM users WHERE user_id = :user_id');
$stmt->bindValue(':user_id', $user_id, \PDO::PARAM_INT);
$stmt->execute();
$result = $stmt->fetchAll(\PDO::FETCH_ASSOC);
$user_data = $result[0];
$user = new User();
$user->setUsername($user_data['username']);
return $user;
}
}
class User
{
protected $Username;
public function getUsername()
{
return $this->Username;
}
public function setUsername($username)
{
$this->Username = $username;
}
}
// Gebruik:
$db = new PDO('mysql:dbname=test;host=localhost', 'root', '');
$user_mapper = new UserMapper($db);
$user = $user_mapper->fetchUserById($_SESSION['user_id']);
echo $user->getUsername();
?>
Alleen eens over nadenken hoe je dat in de praktijk toepast, want je hebt in jouw opzet dus altijd 2 objecten, de User en de Mapper. Stel ik laad/fetch de User in "het begin" van de applicatie en wil deze verderop in de applicatie opslaan, dan moet ik dus ook die $user_mapper bij de hand hebben ... of een nieuw object initialiseren. Hmmm, iets om over na te denken ...
1. App A vraagt user X op.
2. App B vraagt user X op.
3. App A wijzigt user X.
Wat gebeurt er nu met user X in app B?
Geen idee? Is het een raadsel? :-D