[oop] mapper
Kan iemand me uitleggen waartoe een mapper dient? Mijn gedachte (maar ik weet dus niet of dit 100% klopt) is dat een mapper een tussenlaag is tussen een class en een opslagmedium (meestal een database). In plaats van dat je in een class een "save" method hebt die rechtstreeks de database aanspreekt en een query uitvoert, spreek je de mapper aan. Klopt dit?
In een ander topic kwam toevallig dit stukje code voorbij:
Hier zie ik dus dat er een UserMapper wordt aangemaakt en dat er via die UserMapper een User object wordt teruggegeven.
Zou het niet handiger zijn om gewoon een nieuwe User aan te maken, en dat de User class intern een UserMapper gebruikt om de juiste databasegegevens te laden? Of kan dat niet?
En als je dan een User wil opslaan, moet je dat dan ook weer via die UserMapper doen?
Kortom, mijn vraag is eigenlijk hoe zo'n mapper werkt. Misschien kan iemand een heel simpel voorbeeldje geven van hoe je met een mapper een User moet laden en opslaan?
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
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
/**
* Valideren van velden is weg gelaten!
*/
class User {
private $id;
private $username;
/**
* Alleen de id opslaan als dit ook een echte id is, want de mapper zal
* dit aanroepen en dan zal de id niet correct opgeslagen worden.
*/
public function __construct($id = 0) {
(int) $id != 0 ? $this->setId($id) : false;
}
public function setId($id) {
$this->id = $id;
}
public function setUsername($username) {
$this->username = $username;
}
public function getId() {
return $this->id;
}
public function getUsername() {
return $this->username;
}
}
class UserMapper extends DataMapper {
/**
* @param object $user
*/
public function getById(User $user) {
$sth = $this->pdo->prepare('SELECT {velden} FROM users WHERE id = :id');
$sth->execute(array(':id' => $user->getId());
return $sth->fetchObject('User');
}
/**
* @param object $user
*/
public function save(User $user) {
$sth = $this->pdo->prepare('UPDATE users SET username = :username WHERE id = :id');
return $sth->execute(array(':id' => $user->getId(), ':username' => $user->getUsername()));
}
}
/**
* De user mapper openen.
*/
$userMapper = new UserMapper();
/**
* De gebruiker met id 12 laden.
*/
print_r($userMapper->getById(new User(12));
/**
* De gebruiker credentials toevoegen.
*/
$user = new User(12);
$user->setUsername('Aaron');
$userMapper->save($user);
?>
/**
* Valideren van velden is weg gelaten!
*/
class User {
private $id;
private $username;
/**
* Alleen de id opslaan als dit ook een echte id is, want de mapper zal
* dit aanroepen en dan zal de id niet correct opgeslagen worden.
*/
public function __construct($id = 0) {
(int) $id != 0 ? $this->setId($id) : false;
}
public function setId($id) {
$this->id = $id;
}
public function setUsername($username) {
$this->username = $username;
}
public function getId() {
return $this->id;
}
public function getUsername() {
return $this->username;
}
}
class UserMapper extends DataMapper {
/**
* @param object $user
*/
public function getById(User $user) {
$sth = $this->pdo->prepare('SELECT {velden} FROM users WHERE id = :id');
$sth->execute(array(':id' => $user->getId());
return $sth->fetchObject('User');
}
/**
* @param object $user
*/
public function save(User $user) {
$sth = $this->pdo->prepare('UPDATE users SET username = :username WHERE id = :id');
return $sth->execute(array(':id' => $user->getId(), ':username' => $user->getUsername()));
}
}
/**
* De user mapper openen.
*/
$userMapper = new UserMapper();
/**
* De gebruiker met id 12 laden.
*/
print_r($userMapper->getById(new User(12));
/**
* De gebruiker credentials toevoegen.
*/
$user = new User(12);
$user->setUsername('Aaron');
$userMapper->save($user);
?>
Het idee erond is dat een User object gebruikt wordt voor het vasthouden van de velden en de mapper dan weer de velden gaat afhandelen in de database.
Zo hebben de verschillende classes maar één taak.
Het is ook gemakkelijker als je de mappers moet updaten om de een of de andere reden.
Ik snap alleen nog niet helemaal hoe het werkt. Want als ik een user wil ophalen dan doe ik dit??
Ik geef dan het User object mee aan de mapper? Correct? Alleen in jouw voorbeeld wordt de ID niet via de constructor geset. Of mis ik iets?
En wat gebeurt hier precies?
return $sth->fetchObject('User');
En hoe komt de data uit de database dan weer in die User class terecht?
Ik hoop dat je het nog een beetje kan toelichten!
Code (php)
1
2
3
4
5
6
7
2
3
4
5
6
7
<?php
function getUsername() {
return preg_replace('![^a-zA-Z]!', '', $this->username);
}
?>
function getUsername() {
return preg_replace('![^a-zA-Z]!', '', $this->username);
}
?>
Met fetch object laat je het resultaat dus fetchen in een object. Wat heel gemakkelijk is omdat je dan weeral niets fout kan doen (zie getUsername()). Als je meerdere resultaten hebt gebruik je dan fetchAll(PDO::FETCH_CLASS, 'User').
fetchObject en fetchAll hebben toegang tot de private values dacht ik. Dit wordt tevens afgehandeld door PDO. In andere gevallen zou je gewoon een array terug geven.
Construct is nu toegevoegd.
Zou je als laatste nog kunnen uitleggen hoe dat fetchObject werkt. Als ik hier kijk http://php.net/manual/en/pdostatement.fetchobject.php dan lijkt het alsof je nog een array met argumenten moet toevoegen, die vervolgens in de constructor wordt geinjecteerd.
En waarom geef je in de mapper het User object mee, terwijl je via fetchObject weer een User obejct teruggeeft?
Sorry voor de vele vragen, maar het is me (zoals je merkt) nog niet helemaal duidelijk wat er nou precies gebeurt.
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
/**
* mixed PDOStatement::fetchObject ([ string $class_name = "stdClass" [, array $ctor_args ]] )
*/
$user = $sth->fetchObject('User');
/**
* Dan zul je gewoon het volgende kunnen doen als er natuurlijk een
* gebruiker is.
*
* De argumenten die je aan fetchObject kun meegeven heb ik nog nooit gebruikt. Maar
* je moet er wel voor zorgen als je een constructor gebruikt dat je ervoor zorgt dat je
* geen argument toelaat die niet valid zijn omdat anders fetchObject de constructor aanroept en
* zo bijvoorbeeld id 'Aaron' wordt.
*/
echo $user->getUsername();
?>
/**
* mixed PDOStatement::fetchObject ([ string $class_name = "stdClass" [, array $ctor_args ]] )
*/
$user = $sth->fetchObject('User');
/**
* Dan zul je gewoon het volgende kunnen doen als er natuurlijk een
* gebruiker is.
*
* De argumenten die je aan fetchObject kun meegeven heb ik nog nooit gebruikt. Maar
* je moet er wel voor zorgen als je een constructor gebruikt dat je ervoor zorgt dat je
* geen argument toelaat die niet valid zijn omdat anders fetchObject de constructor aanroept en
* zo bijvoorbeeld id 'Aaron' wordt.
*/
echo $user->getUsername();
?>
Waarom ik aan de mapper een object mee geef.
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
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
<?php
/**
* $_GET['id'] = 'Een String?';
*
* Laten we veronderstellen dat $_GET['id'] bestaat. Hier
* zullen we $_GET['id'] dus nog moeten valideren. Daarom dat ik dus
* user gebruik.
*/
$userMapper = new UserMapper();
$user = $userMapper->getById($_GET['id']);
/**
* Laten we nu maar eens User gebruiken. Hier hoef je
* niet te doen omdat $user->getId() ervoor zou moeten zorgen
* dat je een id terug krijg of toch minstens een nummer (al dan niet correct).
*
* Ook naar de insert en save toe is het gewoon veel veiliger omdat alle input
* dan gevalideerd is...
*
* De validatie is weg gelaten.
*/
$userMapper = new UserMapper();
$user = $userMapper->getById(new User($_GET['id']));
/**
* In het eerste geval zal de query dan de volgende zijn.
* SELECT {velden} FROM users WHERE id = 'Een String?'
*/
/**
* In het tweede geval.
* SELECT {velden} FROM users WHERE id = '0' of SELECT {velden} FROM users WHERE id = ''
*
* Die of is gewoon te zien hoe je de input valideert. Ik gebruik voor een int typecasting en dan
* als het 0 is niets returnen.
*/
?>
/**
* $_GET['id'] = 'Een String?';
*
* Laten we veronderstellen dat $_GET['id'] bestaat. Hier
* zullen we $_GET['id'] dus nog moeten valideren. Daarom dat ik dus
* user gebruik.
*/
$userMapper = new UserMapper();
$user = $userMapper->getById($_GET['id']);
/**
* Laten we nu maar eens User gebruiken. Hier hoef je
* niet te doen omdat $user->getId() ervoor zou moeten zorgen
* dat je een id terug krijg of toch minstens een nummer (al dan niet correct).
*
* Ook naar de insert en save toe is het gewoon veel veiliger omdat alle input
* dan gevalideerd is...
*
* De validatie is weg gelaten.
*/
$userMapper = new UserMapper();
$user = $userMapper->getById(new User($_GET['id']));
/**
* In het eerste geval zal de query dan de volgende zijn.
* SELECT {velden} FROM users WHERE id = 'Een String?'
*/
/**
* In het tweede geval.
* SELECT {velden} FROM users WHERE id = '0' of SELECT {velden} FROM users WHERE id = ''
*
* Die of is gewoon te zien hoe je de input valideert. Ik gebruik voor een int typecasting en dan
* als het 0 is niets returnen.
*/
?>
En waarom ik een object terug geef is net dezelfde reden zoals hierboven. Dan wordt alle input nogmaals gecontroleerd, want neem nu aan dat er toch iets verkeerd in de database is opgeslagen... zal het nog altijd min of meer correct op het scherm komen (toch niet dat het gevaarlijk is voor de klant).
Je zou ook gewoon een errormelding kunnen geven als $_GET['id'] geen getal is? Ik vind het vreemd dat je dan een User object meegeeft alleen om een getalletje te controleren.
Anyhow... dat fetchObject snap ik nog steeds niet. Jij doet dit:
$user = $sth->fetchObject('User');
Maar hoe komen de gegevens dan in het object terecht?
Hoe de gegevens er terug komen weet ik niet want dit wordt door PDO geregeld (anders zou je ook nooit aan private values kunnen en dat kan hier wel). Alleen moeten de kolom namen gelijk zijn aan die in het object. Dus als je een kolom id hebt, moet je ook ergens $this->id hebben.
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
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
<?php
/**
* In de veronderstelling dat alle POST en GET values bestaan!
*/
$user = new User($_GET['id']);
$userMapper = new UserMapper();
if($userMapper->getById($user) == false) {
# Afhandelen
}
/**
* Form submit.
*/
if($_SERVER['REQUEST_METHOD'] === 'POST') {
$user->setUsername($_POST['username']);
# Hier tussen nog alles valideren :-) Met een try catch block.
$userMapper->save($user);
}
/**
* Formulier tonen dat ook $user->get() gebruikt.
*/
?>
/**
* In de veronderstelling dat alle POST en GET values bestaan!
*/
$user = new User($_GET['id']);
$userMapper = new UserMapper();
if($userMapper->getById($user) == false) {
# Afhandelen
}
/**
* Form submit.
*/
if($_SERVER['REQUEST_METHOD'] === 'POST') {
$user->setUsername($_POST['username']);
# Hier tussen nog alles valideren :-) Met een try catch block.
$userMapper->save($user);
}
/**
* Formulier tonen dat ook $user->get() gebruikt.
*/
?>
Zoals je kunt zien heb je dan toch al een object met alle veilige values!
Mochten er anderen zijn die ook gebruik maken van mappers, dan ben ik benieuwd naar hoe zij dat doen.
Alle reacties zijn van harte welkom!
http://www.phphulp.nl/php/forum/topic/databse-in-een-class-hoe/82977/ (wat zou het toch makkelijk zijn als je al je eigen topics kan terugvinden...)
Hmm... Ik gebruik alleen geen fetchObject, wat ik wel eens doe is mappers in mappers gebruiken:
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
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
<?php
class menuMapper{
public function fetchById($id){
$smtp = $this->db->prepare('SELECT title, id FROM menus WHERE id=:id');
$smtp->execute(array(
':id' => $id
));
$result = $smtp->fetch(\PDO::FETCH_ASSOC);
$menu = new \Menu;
$menu->setId();
$menu->setTitle();
$itemMapper = new \menuItemMapper;
$items = $itemMapper->fetchByMenuId($result['id']);
$item = new \menuItem;
foreach($items as $item){
$item->setId();
$item->setTitle();
$menu->addItem(serialize($item));
}
}
}
$menuMapper = new menuMapper;
$menu = $menuMapper->fetchById(1);
print_r($menu->getItemsInArray());
?>
class menuMapper{
public function fetchById($id){
$smtp = $this->db->prepare('SELECT title, id FROM menus WHERE id=:id');
$smtp->execute(array(
':id' => $id
));
$result = $smtp->fetch(\PDO::FETCH_ASSOC);
$menu = new \Menu;
$menu->setId();
$menu->setTitle();
$itemMapper = new \menuItemMapper;
$items = $itemMapper->fetchByMenuId($result['id']);
$item = new \menuItem;
foreach($items as $item){
$item->setId();
$item->setTitle();
$menu->addItem(serialize($item));
}
}
}
$menuMapper = new menuMapper;
$menu = $menuMapper->fetchById(1);
print_r($menu->getItemsInArray());
?>
Gewijzigd op 05/05/2013 20:15:04 door Tim S
Wouter... dat zou inderdaad bijzonder makkelijk zijn. Hoe heb jij dat topic gevonden?
Op de een of andere manier kan ik regelmatig mijn topics niet meer terugvinden.
Ah oke... ik zoek me altijd rot en kan dan vaak niks meer terugvinden. En dan ga ik zelfs twijfelen of ik er uberhaupt al wel eens een vraag over heb gesteld. Jammer dat je via het forum zelf niet makkelijk iets kunt terugvinden.