[OOP] Een CMS, de user classes
Afgelopen week weer moed kunnen verzamelen om met mijn OOP project verder te gaan. Een CMS.
Ik ben begonnen met het schrijven van een user classe, interface en mapper. Maar voor dat ik alle functies in mijn mapper ga maken ben ik erg benieuwd naar jullie mening. Ben ik zo op de goede weg of hebben jullie een voorkeur op iets anders.
Ik gebruik nu nog MySQLi, omdat de weg naar OOP mij al lastig genoeg gaat, later overschakelen op PDO moet geen probleem zijn.
Ik heb de overbodige functies even verwijderd, ben nu echt alleen nieuwsgierig of dit goed gaat. Bedoel het werkt wel, maar is dit de manier of zit ik fout?
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
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
<?php
class UserMapper implements MapperInterface {
protected $id;
protected $fields;
function read()
{
/*
* Query opbouwen, uitvoeren
*/
$stmt = new mysqli( HOST, USER, PASS, DATB );
$query = "SELECT id, username, password, name, email, phone FROM user";
if( !$result = $stmt->query( $query ) )
{
return $stmt->connect_error();
}
else
{
while( $record = $result->fetch_array() )
{
$User[] = new User( $record['id'], $record['username'], $record['password'], $record['name'], $record['email'], $record['phone'] );
}
}
return $User;
}
}
?>
class UserMapper implements MapperInterface {
protected $id;
protected $fields;
function read()
{
/*
* Query opbouwen, uitvoeren
*/
$stmt = new mysqli( HOST, USER, PASS, DATB );
$query = "SELECT id, username, password, name, email, phone FROM user";
if( !$result = $stmt->query( $query ) )
{
return $stmt->connect_error();
}
else
{
while( $record = $result->fetch_array() )
{
$User[] = new User( $record['id'], $record['username'], $record['password'], $record['name'], $record['email'], $record['phone'] );
}
}
return $User;
}
}
?>
1) geen protected properties gebruiken, altijd private. Protected properties kunnen betekenen dat je op later moment geen mogelijkheid meer hebt om zaken aan te passen, zonder ook allerlei afgeleide klassen te moeten aanpassen.
2) je mapper maakt nu zelf connectie met de database en voert een query uit. Dat betekent dat als je zometeen 5 mappers hebt bijvoorbeeld, dat je 5 klassen hebt die direct met de database werken. Wil je dan omschakelen naar PDO dan wordt het wel een probleem, je moet namelijk 5 klassen aanpassen. Er zou er maar een moeten zijn en deze mapper zou dis klas moeten gebruiken om de query uit te voeren. Deze mapper moet verder geen weet hebben van hoe dat allemaal gebeurt.
3) deze mapper geeft nu een array van User objecten terug. Dat betekent dat deze mapper bepaalt hoe de user gegevens gebruikt worden. Wil je zo een nieuwe applicatie bouwen waarin je wel de mapper kunt gebruiken, maar niet het User object, dan moet je dus ook de mapper aanpassen, dat wil je niet. De mapper zou alleen de rauwe data moeten teruggeven, een ander object mag dan uitvogelen in wat voor object die data gestopt wordt.
Gewoon een simpele array met de data zou dus voldoende zijn? Niet direct in een object stoppen?
Gewijzigd op 15/04/2013 18:56:10 door Milo S
Hoe het database object het allemaal regelt is voor deze mapper niet van belang. Je kan die connectie dus al leggen in de constructor van die database klas, of je maakt de connectie de eerste keer dat de connectie nodig is.
Wil je later dan van MySQLi naar PDO overstappen dan hoef je alleen een nieuwe database klas te bouwen en in je applicatie de ene klas door de andere te vervangen en je bent klaar.
Verder zou ik inderdaad alleen de array met alle gegevens teruggeven. Wil je dan later niet User klas gebruiken om user objecten van te maken, maar UserMetHond klas, dan kan je deze mapper nog gewoon gebruiken.
Dan mag jij concluderen wat beter is.
Nog wat andere tips:
- Gebruik voor foutafhandeling exceptions en geef de fout niet terug
- gebruik betere methodnamen. Read, wat betekend dat? Betekend dat dat ik alle users terug krijg of alleen 1 user?
- In tegenstelling tot Erwin vind ik het niet erg dat de mapper al een user maakt. Het is immers een UserMapper. DataMappers hebben 1 doel: Het contact tussen database en object verzorgen. Het object weet dus niet hoe hij gemaakt wordt en de database weet niet hoe zijn gegevens gebruikt worden. Het is juist de mapper zijn taak om deze 2 te verbinden en dus moet hij zowel de database kant als de object kant weten.
Wat ik wel zou doen is een method maken (populate) deze moet dan de raw data omzetten in een object. Hierdoor kun je hem telkens hergebruiken.
- waarom zou een UserMapper een $id property hebben?
En ook heel belangrijk: visibility op je methods!
Gewoon PDO gebruiken voor de rest. Ik lees nu de tutorial van blanche door en dat moet toch wel redelijk gaan lukken denk ik.
Om alle methodes zo logisch mogelijk een naam te geven kom ik op het volgende uit; createUser, getUser, getUserById, deleteUser en updateUser. Hierbij komt dan wel mijn MapperInterface te vervallen. Ik kan moeilijk een interface methodes als updateUser meegeven als de interface ook gebruikt gaat worden voor pagina's. De conclusie is dus, ik haal de klasnaam User eruit en hou het volgende over; create, getAll, getById, delete en update. Lijkt mij beter?
Voor Wouter zijn punt over de UserMapper valt wat te zeggen, al klinkt dat van Erwin wat vrijer in gebruik?
De id is een soort van "left-over" aan de hand van al mijn probeersels. Ik heb het niet eens opgemerkt...
-------------------------------
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
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
<?php
class UserMapper
{
public function getAll( $db )
{
# Query
$sql =
"
SELECT
id,
username,
password,
name,
email,
phone
FROM
user
";
# Query Interpreteren en Controleren door Database
$stmt = $db->prepare( $sql );
# Query Uitvoeren
$stmt->execute();
# Result Opvangen en Retouneren d.m.v populate function
while( $res = $stmt->fetch( PDO::FETCH_ASSOC ) )
{
return $this->populate( $res );
}
}
public function getById( $db, $id )
{
# Query
$sql =
"
SELECT
id,
username,
password,
name,
email,
phone
FROM
user
WHERE
id = '".$id."'
";
# Query Interpreteren en Controleren door Database
$stmt = $db->prepare( $sql );
# Query Uitvoeren
$stmt->execute( array( (int) $id ) );
# Result Opvangen
$res = $stmt->fetch( PDO::FETCH_ASSOC );
# Result Retouneren d.m.v populate function
return $this->populate( $res );
}
public function populate( array $data )
{
$User[] = new User( $data['id'], $data['username'], $data['name'], $data['email'], $data['phone'] );
return $User;
}
}
?>
class UserMapper
{
public function getAll( $db )
{
# Query
$sql =
"
SELECT
id,
username,
password,
name,
email,
phone
FROM
user
";
# Query Interpreteren en Controleren door Database
$stmt = $db->prepare( $sql );
# Query Uitvoeren
$stmt->execute();
# Result Opvangen en Retouneren d.m.v populate function
while( $res = $stmt->fetch( PDO::FETCH_ASSOC ) )
{
return $this->populate( $res );
}
}
public function getById( $db, $id )
{
# Query
$sql =
"
SELECT
id,
username,
password,
name,
email,
phone
FROM
user
WHERE
id = '".$id."'
";
# Query Interpreteren en Controleren door Database
$stmt = $db->prepare( $sql );
# Query Uitvoeren
$stmt->execute( array( (int) $id ) );
# Result Opvangen
$res = $stmt->fetch( PDO::FETCH_ASSOC );
# Result Retouneren d.m.v populate function
return $this->populate( $res );
}
public function populate( array $data )
{
$User[] = new User( $data['id'], $data['username'], $data['name'], $data['email'], $data['phone'] );
return $User;
}
}
?>
Het gebruik is al volgt:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
Ik vind het dan nu alleen lastig een connectie op te zetten en het daadwerkelijk te gebruiken. Hier ga ik morgen weer even mee stoeien!
Bovenstaande code werkt als het gaat om alleen ophalen met het ID, alles ophalen wil hij maar niet doen. Ik ben nog bezig, maar het gaat niet zonder tegenslagen. Jullie advies is altijd welkom.
Gewijzigd op 16/04/2013 09:17:52 door Milo S
Geef de $db niet bij elke method mee. Injecteer hem via de constructor in de klasse waardoor je hem dan altijd kunt gebruiken.
Ik vraag mij sowieso af of ik niet een db handler moet gebruiken? Zodat ik makkelijk kan veranderen van database..
PSR standaarden.
Probeer je ook even te houden aan de Gewijzigd op 16/04/2013 23:49:32 door - Raoul -
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
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
<?php
public $db;
public function __constructor($db)
{
$this->db = $db;
}
public function getAll()
{
# Query
$sql = "SELECT id, username, password, name, email, phone FROM user";
# Query Interpreteren en Controleren door Database
$stmt = $this->db->prepare($sql);
# Query Uitvoeren
$stmt->execute();
# Result Opvangen en Retouneren d.m.v populate function
while ($res = $stmt->fetch(PDO::FETCH_ASSOC))
{
$User_lib[] = $this->populate($res);
}
return $User_lib;
}
?>
public $db;
public function __constructor($db)
{
$this->db = $db;
}
public function getAll()
{
# Query
$sql = "SELECT id, username, password, name, email, phone FROM user";
# Query Interpreteren en Controleren door Database
$stmt = $this->db->prepare($sql);
# Query Uitvoeren
$stmt->execute();
# Result Opvangen en Retouneren d.m.v populate function
while ($res = $stmt->fetch(PDO::FETCH_ASSOC))
{
$User_lib[] = $this->populate($res);
}
return $User_lib;
}
?>
Wat er nu theoretisch gezien moet gebeuren is het volgende;
Database connectie wordt aangemaakt en de UserMapper wordt aangeroepen. Usermapper zijn constructor pakt de $db variabele op uit de connectie en maakt hem bruikbaar voor in de class. Maar kennelijk ontbreekt er iets. Op het net zag ik dingen als global maken, maar dat werd toch ook weer behoorlijk afgezeken?
----------------------
Ik heb ook was topic hier nog gelezen en aan de hand van de kennis die ik nu heb verzameld een UML gemaakt, liever gezegd aangepast.
Het UML
Met bovenstaande plan kan ik aan de gang gaan met coderen. Ik gebruik PDO als communicatie naar de database. Hierdoor zou ik bewijs van spreken alleen in de connectie hoeven zeggen dat ik een andere database zou willen hebben in plaats van MySql.
Ik kan alles doen wat betrekking heeft tot mijn nieuws, pagina's, gebruikersrollen en gebruikers zelf. Dingen bij voegen is geen probleem, slechts de benodigde classes aanmaken en koppelen aan al bestaande classes. Hiervoor hoef ik zo min mogelijk in mijn huidige classes te knoeien.
Ik had over een database storage gelezen in het topic van Jasper. Nu dacht ik dat te gaan gebruiken op de volgende manier: http://www.youtube.com/watch?v=uF-L7ympvfw
Alleen dan met PDO. Dit scheelt heel wat keer die Query tikken in al mijn mappers? Daarbij kan ik ook cookies, sessies etc. Door middel van zo'n storage gaan behandelen.
Zien jullie ergens nog haken en ogen aan zitten?
Gewijzigd op 17/04/2013 09:05:27 door Milo S
Moet het niet __construct($db) zijn in plaats van __constructor($db)? Mogelijk is dat de reden dat je code niet werkt.
(Als je error reporting aanzet krijg je dit soort fouten ook te zien.)
Doe je in getAll() niet veel te veel? Je maakt een kopie van de databasetabel user inclusief persoonsgegevens en wachtwoorden en stopt die in een public array...
Uiteraard moet wachtwoord daar niet meer bij, maar derest kan toch gewoon publiek zijn?
Als je niet meer dan 1 database connectie binnen je applicatie gaat gebruiken (wat in 99% van de gevallen zo is), kun je er ook over denken om de connectie via een static variable mee te geven. Je levert wat flexibiliteit in op het gebied van testen maar je code wordt veel overzichtelijker
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
/**
* Waarom gebruik je populate? PDO kan dit toch voor jou doen? Je moet
* er dan alleen voor zorgen dat de velden in de database ook bestaan voor de gebruiker.
*
* Eventueel zou je ook gebruik kunnen maken van __set(string $name, mixed $value), maar normaal
* gezien hoeft dat niet tenzei je je velden zou opslaan in een variabele $values of iets dergelijks.
*
* Ik heb even de connectie weggelaten.
*/
$db = '{pdoDatabaseConnection}';
$sth = $db->prepare('SELECT {velden} FROM users');
$sth->execute();
return $sth->fetchAll(PDO::FETCH_CLASS, 'User');
?>
/**
* Waarom gebruik je populate? PDO kan dit toch voor jou doen? Je moet
* er dan alleen voor zorgen dat de velden in de database ook bestaan voor de gebruiker.
*
* Eventueel zou je ook gebruik kunnen maken van __set(string $name, mixed $value), maar normaal
* gezien hoeft dat niet tenzei je je velden zou opslaan in een variabele $values of iets dergelijks.
*
* Ik heb even de connectie weggelaten.
*/
$db = '{pdoDatabaseConnection}';
$sth = $db->prepare('SELECT {velden} FROM users');
$sth->execute();
return $sth->fetchAll(PDO::FETCH_CLASS, 'User');
?>
Toevoeging
Zelf gebruik ik een database manager met alle connecties voor de applicatie omdat er meestal meerdere zijn. Deze kan ik dan gewoon van de database manager krijgen en daarmee aan de slag gaan.
Eventueel zou je een data mapper kunnen maken die een constructor heeft om de database in $db te laden.
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
/**
* DataMapper class. Dit zorgt ervoor dat je de database
* kunt aanroepen.
*/
class DataMapper {
private $db;
/**
* De constructor. Hier wordt de database geladen.
*
* @param string $db
*/
public function __construct($db = 'default') {
/**
* Roep de database manager aan om de database in handen te
* krijgen.
*/
$this->db = DatabaseManager::get($db);
}
}
/**
* Dan heb je nog de user mapper die de database aanvult.
*/
class UserMapper extends DataMapper {
/**
* Hier kun je nu gewoon $this->db gebruiken.
*/
}
?>
/**
* DataMapper class. Dit zorgt ervoor dat je de database
* kunt aanroepen.
*/
class DataMapper {
private $db;
/**
* De constructor. Hier wordt de database geladen.
*
* @param string $db
*/
public function __construct($db = 'default') {
/**
* Roep de database manager aan om de database in handen te
* krijgen.
*/
$this->db = DatabaseManager::get($db);
}
}
/**
* Dan heb je nog de user mapper die de database aanvult.
*/
class UserMapper extends DataMapper {
/**
* Hier kun je nu gewoon $this->db gebruiken.
*/
}
?>
Anders kun je zoals Wouter als zei voor elke mapper de database via de constructor injecteren.
Nu vind ik jou voorbeeld wel heel interessant... Ik ben dus nog lang niet klaar met het uitpluizen van mijn uiteindelijke UML. Zo veel dingen waar je rekening mee moet houden en waar je de mogelijkheid nog niet eens van weet. Ik ben er dan ook van overtuigt dat mijn eerste OO applicatie niet 100% is wat het moet zijn. Ik doe hard me best!
Bij mij zou het dan gaan om een connectie tussen de DatabaseStorage en de DatabaseManager.
Wat betreft je eerste voorbeeld, dat bedoel ik nou dingen waarvan je de mogelijkheid nog niet eens wist. Ik denk alleen wel dat je dan beperkter wordt in je doen en laten binnen de applicatie. Zo slaat hij altijd alles op in een User object, of zie ik dit fout? Ik ga er in iedergeval naar kijken, of ik het echt ga gebruiken durf ik nog niet te zeggen.
Momenteel ben ik bezig met een ErrorHandler om zou alle fouten netjes op te vangen.
Jullie horen van mij! In ieder geval bedankt voor alle hulp tot dus ver, jullie hebben enorm geholpen!!
Gewijzigd op 19/04/2013 08:30:55 door Milo S