OOP gedachtengang
hier gaan we weer! Ik zou mijn OOP gedachtegang nog eens willen laten controleren door jullie. De situatie is als volgt:
Quote:
Er staat een vast aantal gebruikers in de database. Deze moeten kunnen inloggen en nadien een rapport kunnen aanmaken, raadplegen of verzenden. Een gebruiker kan voor een vast aantal bedrijven rapporten invullen.
Bij het aanmaken van een nieuw rapport selecteert de gebruiker dus eerst een bedrijf waarvoor hij het rapport moet invullen waarna alle klanten van het bedrijf in een lijst word gezet en de klant kan zoeken in die lijst. Heeft hij desbetreffende klant gevonden word er een formulier aangemaakt met enkele basis gegevens verschillend per klant die de gebruiker verder moet aanvullen en nadien kan opslaan tot later of verzenden.
Bij het aanmaken van een nieuw rapport selecteert de gebruiker dus eerst een bedrijf waarvoor hij het rapport moet invullen waarna alle klanten van het bedrijf in een lijst word gezet en de klant kan zoeken in die lijst. Heeft hij desbetreffende klant gevonden word er een formulier aangemaakt met enkele basis gegevens verschillend per klant die de gebruiker verder moet aanvullen en nadien kan opslaan tot later of verzenden.
Uit deze situatie lijd ik 4 objecten (=classes) af.
- Gebruiker
- Rapport
- Klant
- Bedrijf
Mijn gedachtegang:
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
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
<?php
class Gebruiker
{
/*
** Mijn gebruiker moet natuurlijk kunnen inloggen!
** Daarvoor heeft hij zijn GEBRUIKERSNAAM en WACHTWOORD nodig
*/
function inloggen($gebruikersnaam, $wachtwoord)
{
}
/*
** Mijn gebruiker is ingelogd en ik wil zijn naam ophalen
*/
function getName()
{
}
}
class Rapport
{
/*
** RAPPORT ZIT IN DB EN BEVAT:
** - id
** - gebruiker
** - bedrijf
** - datum
** - ...
**/
/*
** Ik wil rapporten kunnen ophalen met filter!
** Filter kan op één bepaald lid of op een datum of op meerdere zaken tegelijk
*/
function getRapporten()
{
}
/*
** Ik wil ook meer info kunnen hebben over één specifiek rapport
*/
function getRapport($id)
{
}
/*
** Mijn lid moet natuurlijk ook een rapport kunnen aanmaken en bewaren
*/
function setRapport()
{
}
/*
** Mijn rapport(en) moet(en) per mail vertuurd kunnen worden. (1 of meerdere per mail)
*/
function sendRapport
{
}
}
class Bedrijf
{
/*
** Ik moet een lijst kunnen weergeven van de bedrijven binnen de applicatie
*/
function getBedrijven()
{
}
}
class Klanten
{
/*
** Ik moet een lijst kunnen geven van alle klanten waaruit een gebruiker kan kiezen voor een rapport in te vullen a.d.h.v. een bedrijf
*/
function getKlanten()
{
}
}
?>
class Gebruiker
{
/*
** Mijn gebruiker moet natuurlijk kunnen inloggen!
** Daarvoor heeft hij zijn GEBRUIKERSNAAM en WACHTWOORD nodig
*/
function inloggen($gebruikersnaam, $wachtwoord)
{
}
/*
** Mijn gebruiker is ingelogd en ik wil zijn naam ophalen
*/
function getName()
{
}
}
class Rapport
{
/*
** RAPPORT ZIT IN DB EN BEVAT:
** - id
** - gebruiker
** - bedrijf
** - datum
** - ...
**/
/*
** Ik wil rapporten kunnen ophalen met filter!
** Filter kan op één bepaald lid of op een datum of op meerdere zaken tegelijk
*/
function getRapporten()
{
}
/*
** Ik wil ook meer info kunnen hebben over één specifiek rapport
*/
function getRapport($id)
{
}
/*
** Mijn lid moet natuurlijk ook een rapport kunnen aanmaken en bewaren
*/
function setRapport()
{
}
/*
** Mijn rapport(en) moet(en) per mail vertuurd kunnen worden. (1 of meerdere per mail)
*/
function sendRapport
{
}
}
class Bedrijf
{
/*
** Ik moet een lijst kunnen weergeven van de bedrijven binnen de applicatie
*/
function getBedrijven()
{
}
}
class Klanten
{
/*
** Ik moet een lijst kunnen geven van alle klanten waaruit een gebruiker kan kiezen voor een rapport in te vullen a.d.h.v. een bedrijf
*/
function getKlanten()
{
}
}
?>
Gewijzigd op 05/06/2012 21:17:46 door Jasper DS
Ga of scripten in het engels of in het nederlands, maar getKlanten oid kan echt niet...
Je objecten heb je nu wel goed. Al is Klant en Gebruiker natuurlijk allebei een mens. Je zou dus een Person object kunnen hebben en de Customer en User kunnen die Person object dan extenden met wat extra waardes (als hun RANG).
Tevens moet je je nu gaan verdiepen in een design pattern voor het organiseren tussen de DB en het object. Ik gebruik hiervoor altijd een DataMapper, dus verdiep je daar eens in.
Je hebt hier meerdere datamappers nodig. Zo heb je hier een UserMapper (of PersonMapper), die kan inloggen/uitloggen; nieuwe users aanmaken; users verwijderen en je hebt een RapportMapper die rapporten ophaalt (met filters of via getById oid); rapporten aanmaakt; rapporten verwijderd en je hebt nog een CompanyMapper voor het ophalen van bedrijven.
De objecten die jij hier hebt zouden hier eigenlijk alleen maar waardes mogen vast houden, personen en bedrijven krijgen een naam; een persoon krijgt een rang; een user een wachtwoord; een rapport meerdere gebruikers; enz.
Kortom: Je objecten gedachtegang is nu goed, alleen nu nog hoe je dat object naar een flexibele en makkelijk te beheren script maakt.
Je moet eigenlijk zo denken dat geen 1 class of method meerdere functionaliteiten mag(lees: zou moeten) hebben. Als je class de communicatie met de DB regelt (het inloggen bijv.) dan mag hij niet ook nog de gegevens vasthouden.
Gewijzigd op 05/06/2012 21:30:45 door Wouter J
Wouter J op 05/06/2012 21:28:47:
Offtopic:
Ga of scripten in het engels of in het nederlands, maar getKlanten oid kan echt niet...
Ga of scripten in het engels of in het nederlands, maar getKlanten oid kan echt niet...
Haha oke doe ik!
Wouter J op 05/06/2012 21:28:47:
Je objecten heb je nu wel goed. Al is Klant en Gebruiker natuurlijk allebei een mens. Je zou dus een Person object kunnen hebben en de Customer en User kunnen die Person object dan extenden met wat extra waardes (als hun RANG).
De gebruiker is inderdaad een persoon, de klant daarentegen is een bedrijf. Zo kan gebruiker x voor bedrijf y (=zijn werkgever) van bedrijf z (= klant van bedrijf x) een rapport invullen.
Wouter J op 05/06/2012 21:28:47:
Tevens moet je je nu gaan verdiepen in een design pattern voor het organiseren tussen de DB en het object. Ik gebruik hiervoor altijd een DataMapper, dus verdiep je daar eens in.
Je hebt hier meerdere datamappers nodig. Zo heb je hier een UserMapper (of PersonMapper), die kan inloggen/uitloggen; nieuwe users aanmaken; users verwijderen en je hebt een RapportMapper die rapporten ophaalt (met filters of via getById oid); rapporten aanmaakt; rapporten verwijderd en je hebt nog een CompanyMapper voor het ophalen van bedrijven.
Je hebt hier meerdere datamappers nodig. Zo heb je hier een UserMapper (of PersonMapper), die kan inloggen/uitloggen; nieuwe users aanmaken; users verwijderen en je hebt een RapportMapper die rapporten ophaalt (met filters of via getById oid); rapporten aanmaakt; rapporten verwijderd en je hebt nog een CompanyMapper voor het ophalen van bedrijven.
En hoe leer ik dit het best of op welke manier bekijk ik zo'n mapper?
Wouter J op 05/06/2012 21:28:47:
De objecten die jij hier hebt zouden hier eigenlijk alleen maar waardes mogen vast houden, personen en bedrijven krijgen een naam; een persoon krijgt een rang; een user een wachtwoord; een rapport meerdere gebruikers; enz.
Met dit deeltje van OOP ben ik dus nog niet mee. Ik heb al mijn objecten een class gegeven en nu moet ik een class toevoegen die de classes met elkaar verbind?
Alvast bedankt voor de verhelderende reactie wouter!
Jasper
Edit:
Ik stootte net op datamapper. Ik neem aan dat dit eigenlijk een soort mini-framework is? Kan ik zoiets gebruiken?
Ik stootte net op datamapper. Ik neem aan dat dit eigenlijk een soort mini-framework is? Kan ik zoiets gebruiken?
Gewijzigd op 05/06/2012 21:41:11 door Jasper DS
Quote:
Ik heb al mijn objecten een class gegeven en nu moet ik een class toevoegen die de classes met elkaar verbind?
Nee. Je hebt nu 2 dingen:
De User object ziet er waarschijnlijk zoiets uit (zonder rangen en person object):
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
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
<?php
class User
{
protected $id;
protected $name;
protected $password;
protected $email;
public function __construct($name, $password, $email)
{
$this->name = (string) $name;
$this->password = (string) $password;
$this->email = (string) $email;
}
public function setName($name)
{
$this->name = (string) $name;
}
public function setPassword($password)
{
$this->password = (string) $password;
}
public function setEmail($email)
{
$this->email = (string) $email;
}
public function setId($id)
{
$this->id = (int) $id;
}
public function getName()
{
return $name;
}
public function getPassword()
{
return $password;
}
public function getEmail()
{
return $email;
}
public function getId()
{
return $id;
}
}
?>
class User
{
protected $id;
protected $name;
protected $password;
protected $email;
public function __construct($name, $password, $email)
{
$this->name = (string) $name;
$this->password = (string) $password;
$this->email = (string) $email;
}
public function setName($name)
{
$this->name = (string) $name;
}
public function setPassword($password)
{
$this->password = (string) $password;
}
public function setEmail($email)
{
$this->email = (string) $email;
}
public function setId($id)
{
$this->id = (int) $id;
}
public function getName()
{
return $name;
}
public function getPassword()
{
return $password;
}
public function getEmail()
{
return $email;
}
public function getId()
{
return $id;
}
}
?>
Dit is een object dat alleen data kan vasthouden. Je gegevens invullen, en bewerken, en ophalen.
De DB is gevuld als een tabel zoals dit:
Code (php)
1
2
3
4
5
6
7
2
3
4
5
6
7
+----+--------+----------+-------------------+-----------+
| id | name | password | email | logged_in |
+----+--------+----------+-------------------+-----------+
| 1 | Wouter | 13hg35f6 | [email protected] | 1 |
| 2 | Jasper | h356gh21 | [email protected] | 0 |
| 3 | ... | ... | ... | |
+----+--------+----------+-------------------+-----------+
| id | name | password | email | logged_in |
+----+--------+----------+-------------------+-----------+
| 1 | Wouter | 13hg35f6 | [email protected] | 1 |
| 2 | Jasper | h356gh21 | [email protected] | 0 |
| 3 | ... | ... | ... | |
+----+--------+----------+-------------------+-----------+
Als we deze connectie tussen User en Db in het User object stoppen komen we in de problemen: Het is niet flexibel en de class heeft meerdere taken. Stel we veranderen van MySQLi in PDO dan zou ik de hele User klasse moeten veranderen, als we een apart object hebben kunnen we die User gewoon met rust laten en alleen dat speciale object aanpassen, we kunnen zelfs meerdere DB objecten maken zodat je een MySQLi Mapper en een PDO mapper hebt, een Adapter pattern.
We hebben dus een extra object nodig: De DataMapper
Code (php)
1
2
3
4
5
6
7
2
3
4
5
6
7
_
(_) ____
| de datamapper |____|
\|/ ============= |____|
| |____|
/ \
De User (object) De DB
(_) ____
| de datamapper |____|
\|/ ============= |____|
| |____|
/ \
De User (object) De DB
Deze DataMapper noemen we UserMapper.
Deze UserMapper zorgt voor de communicatie tussen DB en object en omgekeerd. Een voorbeeldje:
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
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
<?php
class UserMapper
{
protected $db; // db abstractielaag
public function __construct(PDO $db)
{
$this->db = $db;
}
public function getById($id)
{
$stmt = $this->db->prepare("SELECT name, password, email, logged_in FROM users WHERE id = ?");
$stmt->execute(array((int) $id));
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$result['id'] = (int) $id;
return $this->populate($result);
}
/**
* Maak van een arrray met data een User object
*/
public function populate(array $data)
{
$user = new User($data['name'], $data['password'], $data['email']);
$user->setId($data['id']);
return $user;
}
public function save(User $user)
{
$stmt = $this->db->prepare("UPDATE users SET name = :name AND password = :password AND email = :email WHERE id = :id");
$stmt->execute(array(
':name' => $user->getName(),
':password' => $user->getPassword(),
':email' => $user->getEmail(),
':id' => $user>getId(),
));
}
public function create(User $user)
{
$stmt = $this->db->prepare("INSERT INTO users(name, password, email) VALUES (:name, :password, :email)");
$stmt->execute(array(
':name' => $user->getName(),
':password' => $user->getPassword(),
':email' => $user->getEmail(),
));
$user->setId($this->db->lastInsertId);
return $user;
}
public function logIn(User $user)
{
// stel logged_in = 1 in bij de $user->getId()
}
// ...
}
?>
class UserMapper
{
protected $db; // db abstractielaag
public function __construct(PDO $db)
{
$this->db = $db;
}
public function getById($id)
{
$stmt = $this->db->prepare("SELECT name, password, email, logged_in FROM users WHERE id = ?");
$stmt->execute(array((int) $id));
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$result['id'] = (int) $id;
return $this->populate($result);
}
/**
* Maak van een arrray met data een User object
*/
public function populate(array $data)
{
$user = new User($data['name'], $data['password'], $data['email']);
$user->setId($data['id']);
return $user;
}
public function save(User $user)
{
$stmt = $this->db->prepare("UPDATE users SET name = :name AND password = :password AND email = :email WHERE id = :id");
$stmt->execute(array(
':name' => $user->getName(),
':password' => $user->getPassword(),
':email' => $user->getEmail(),
':id' => $user>getId(),
));
}
public function create(User $user)
{
$stmt = $this->db->prepare("INSERT INTO users(name, password, email) VALUES (:name, :password, :email)");
$stmt->execute(array(
':name' => $user->getName(),
':password' => $user->getPassword(),
':email' => $user->getEmail(),
));
$user->setId($this->db->lastInsertId);
return $user;
}
public function logIn(User $user)
{
// stel logged_in = 1 in bij de $user->getId()
}
// ...
}
?>
Nu heb je een mooi object gemaakt voor de communicatie tussen db en object. Het gebruik wordt dan zoiets:
Code (php)
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
$user = new User('koen', 'somepass', '[email protected]');
$userMapper = new UserMapper($PDO); // maak $PDO
$user = $userMapper->create($user);
echo 'Hello '.$user->getName().' welcome!';
// andere pagina
$user = $userMapper->getById(2); // haal Jasper op
$userMapper->logIn($user); // log de user in
$user->setName('Jasper PHP');
$userMapper->save($user); // verander user
$userMapper->delete($user); // delete user
?>
$user = new User('koen', 'somepass', '[email protected]');
$userMapper = new UserMapper($PDO); // maak $PDO
$user = $userMapper->create($user);
echo 'Hello '.$user->getName().' welcome!';
// andere pagina
$user = $userMapper->getById(2); // haal Jasper op
$userMapper->logIn($user); // log de user in
$user->setName('Jasper PHP');
$userMapper->save($user); // verander user
$userMapper->delete($user); // delete user
?>
Quote:
En hoe leer ik dit het best of op welke manier bekijk ik zo'n mapper?
Hier op het forum alle OO topics lezen en zoeken op google op bijv. php oo design patterns getting started of dat getting started weggelaten.
Wat reacties met design patterns:
- Singleton Pattern (niet echt voorstander van)
- Factory Pattern (en ActiveRecord een andere methode voor communiceren met de DB)
- Datamapper Pattern (nog een voorbeeldje, gemaakt voor jou, van Pim)
- Dependency Injection Pattern
- Adapter Pattern (of een ingewikkeldere van pim: http://www.phphulp.nl/php/forum/topic/paginering-oop/84869/#605327)
Gewijzigd op 05/06/2012 22:14:51 door Wouter J
Maar als ik dan kijk naar de procedurele code dan zie ik dat de User class slechts éénmaal word aangeroepen en we die verder niet meer gebruiken?
Gewijzigd op 05/06/2012 22:35:26 door Jasper DS
De User is nu gewoon een klasse geworden die alles bevat wat wij weten over die user, wanneer je dus iets van die user nodig hebt hoef je slechts $user->getWhatYouNeed() aan te roepen.
Code (php)
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
<?php
$PDO = new PDO(...);
$userMapper = new UserMapper($PDO); // maak $PDO
$user = $userMapper->getById($_SESSION['user_id']);
$usermapper->populate($user);
$user->getName();
?>
$PDO = new PDO(...);
$userMapper = new UserMapper($PDO); // maak $PDO
$user = $userMapper->getById($_SESSION['user_id']);
$usermapper->populate($user);
$user->getName();
?>
Gewijzigd op 05/06/2012 22:42:20 door Jasper DS
Alleen Regel 6 hoort er niet, wat denk jij dat de populate method doet?
Ik dacht dat die de data die ik verzamel op lijn 5 in mijn user object zette?
Nee, dat doet de getById method al, kijk maar eens goed
Edit:
kan de getById() ook zo?
Code (php)
ps: hoe zit het dan met mysql injectie?
Gewijzigd op 05/06/2012 22:57:23 door Jasper DS
Toevoeging op 05/06/2012 23:01:44:
PHP Jasper; MySQL injectie bestaat niet meer in PDO, dat wordt automatisch geregeld.
Handig :-)
Bedankt Roel daar was ik nog niet van op de hoogte. En bedankt Wouter, voor mij is dit onderwerp weer een heel stuk duidelijker. :)
Jasper, ja je zou hem ook zo kunnen maken. Maar ik ben meer voorstander van prepared statements, vandaar dat ik dat altijd gebruik.
Roel van de Water op 05/06/2012 23:00:43:
PHP Jasper; MySQL injectie bestaat niet meer in PDO, dat wordt automatisch geregeld.
Handig :-)
Handig :-)
Ho, stop, dat is natuurlijk niet waar. Dit is alleen het geval als je prepared statements gebruikt. Als je gewoon direct de door een gebruiker ingevoerde waarde in een SQL statement plakt dan kan je nog net zo nat gaan als bij de mysql_ functies.
Dat is inderdaad ook een mogelijkheid. Ergo, je moet er dus wel zelf iets voor doen, het gaat niet automatisch.
Code (php)
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
<?php
public function __construct($id, $name, $password, $email)
{
$this->id = (int) $id;
$this->name = (string) $name;
$this->password = (string) $password;
$this->email = (string) $email;
}
?>
public function __construct($id, $name, $password, $email)
{
$this->id = (int) $id;
$this->name = (string) $name;
$this->password = (string) $password;
$this->email = (string) $email;
}
?>
En moet er telkens aangeduid worden welk type? Ik dacht dat dat in php niet hoefde in tegenstelling tot andere programmeertalen zoals java en C.
Gewijzigd op 06/06/2012 14:47:03 door Jasper DS
Code (php)
1
2
3
4
2
3
4
<?php
$var = (int) '5'; //wordt dus ook echt een int, ipv een string
$var = (string) 5; //wordt nu echt een string, ipv het standaard int
?>
$var = (int) '5'; //wordt dus ook echt een int, ipv een string
$var = (string) 5; //wordt nu echt een string, ipv het standaard int
?>
het kan weleens handig zijn.
diende in de getById function.
deze informatie-objecten (wat is de echte term voor dit?), zoals de user, waar meestal niets meer instaat dan een beetje informatie, en geen echte opdrachten uitvoert.
bij deze objecten heb ik altijd: zoveel mogelijk met getters en setters, en kun je meer doen (je wilt bv alleen een naam doorgeven, maar zou je daar voor een id ophalen?) zonder teveel te doen. beetje vaag uitgelegd, maar ik hoop dat een OOP-guru het beter kan uitleggen (of juist een andere mening heeft)
Toevoeging op 06/06/2012 15:17:03:
PHP Jasper op 06/06/2012 15:15:17:
de array wordt gevuld met het id, en door het typecasten wordt ervoor gezorgd dat het ook echt een int is
Toevoeging op 06/06/2012 15:18:29:
btw, die array is al gevuld, het is het resultaat (niet goed gelezen). het moet andersom: