paginering OOP
paginering vind ik lastig. vooral met OOP. eerlijk gezegd snap ik er geen zak van.
er wordt altijd vertelt: laat het paginate-object de paginering afhandelen, om te zorgen voor flexibiliteit. maar paginering kan je toch eigenlijk niet zien als object? voor mij niets tastbaars. en de gegevens die je altijd moet invoeren, daardoor gaat al die flexibiliteit toch helemaal verloren?
als ik die van Roel hier zie, lijkt die me best netjes. maar het database gebeuren zit in die class, alle links, de hele shit. die flexibiliteit is dan toch weg, en is het meer een verzameling functies?
dus, hoe hoort een paginering in OOP eruit te zien?
er wordt altijd vertelt: laat het paginate-object de paginering afhandelen, om te zorgen voor flexibiliteit. maar paginering kan je toch eigenlijk niet zien als object? voor mij niets tastbaars. en de gegevens die je altijd moet invoeren, daardoor gaat al die flexibiliteit toch helemaal verloren?
als ik die van Roel hier zie, lijkt die me best netjes. maar het database gebeuren zit in die class, alle links, de hele shit. die flexibiliteit is dan toch weg, en is het meer een verzameling functies?
dus, hoe hoort een paginering in OOP eruit te zien?
Stel we hebben het over een forum met pagination. Dan hebben we 1 object die we Topic noemen die 1 topic vasthoudt. Vervolgens hebben we een TopicMapper klasse die de afhandeling tussen DB en Topic object regelt.
Dan hebben we nog een Pagination klasse die voor de pagination zorgt.
Dat is in elk geval hoe ik het zou doen. In je Topic klasse kun je alles kwijt van berichten totaan users... Met de DataMapper kun je alles makkelijk ophalen en met de Pagination kun je de pagination afhandelen, hoeveel er op 1 pagina mag enz.
Niet alle klassen die je in scripten gebruikt zijn objecten. Je hebt soms ook klassen nodig om je script mooi en flexibel te maken. Als ik zo'n bericht schrijf probeer ik daar altijd verschil in te maken, let maar eens op. Klassen die een echt tastbaar object zijn noem ik altijd object en andere noem ik gewoon klassen.
Een praktisch voorbeeldje hoe ik dit dan zou maken:
Dan hebben we nog een Pagination klasse die voor de pagination zorgt.
Dat is in elk geval hoe ik het zou doen. In je Topic klasse kun je alles kwijt van berichten totaan users... Met de DataMapper kun je alles makkelijk ophalen en met de Pagination kun je de pagination afhandelen, hoeveel er op 1 pagina mag enz.
Niet alle klassen die je in scripten gebruikt zijn objecten. Je hebt soms ook klassen nodig om je script mooi en flexibel te maken. Als ik zo'n bericht schrijf probeer ik daar altijd verschil in te maken, let maar eens op. Klassen die een echt tastbaar object zijn noem ik altijd object en andere noem ik gewoon klassen.
Een praktisch voorbeeldje hoe ik dit dan zou maken:
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
$db = new PDO(...);
$topicMapper = new TopicMapper($db);
// deze functie geeft een array met Topic objecten terug
$topics = $topicMapper->getAllByCategory('oop');
// Maak een pagination object
$pagination = new Pagination();
// een paar instellingen
$pagination->setPageCount(5);
// krijg de eerste pagina (in dit geval met 5 topics)
echo $pagination->getPage(1);
?>
$db = new PDO(...);
$topicMapper = new TopicMapper($db);
// deze functie geeft een array met Topic objecten terug
$topics = $topicMapper->getAllByCategory('oop');
// Maak een pagination object
$pagination = new Pagination();
// een paar instellingen
$pagination->setPageCount(5);
// krijg de eerste pagina (in dit geval met 5 topics)
echo $pagination->getPage(1);
?>
Je kan het best mooi modulair maken hoor!
Denk aan:
"Quick'n'Dirty", maar ik hoop dat je het idee een beetje snapt.
Groet,
Pim
Denk aan:
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
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
<?php
interface PaginatorItemInterface
{
pubilc funciton setData(array $data);
public function render();
}
interface PaginatorBackendInterface
{
/**
* @return array[PaginatorItemInterface]
*/
public function getItems($page);
}
interface PaginatorButtonFormInterface
{
public function render($page);
}
class Paginator
{
public function __construct(PaginatorBackendInterface $backend,
PaginatorButtonFormInterface $buttonForm)
{
$this->backend = $backend;
$this->buttonForm = $buttonForm;
}
public function render($page)
{
$items = $this->backend->getItems($page);
$result = '<div>'; // Hier kan je ook nog wel wat mooiers van maken
foreach($items as $item) {
$result .= $item->render();
}
$result .= $this->buttonForm->render($page);
$result .= '</div>';
return $result;
}
}
abstract class AbstractPaginatorPDOBackend implements PaginatorBackendInterface
{
protected $pdo;
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}
}
class PaginatorLinkItem implements PaginatorItemInterface
{
proteced $name, $link;
public function setData(array $data)
{
$this->name = $data['name'];
$this->link = $data['link'];
}
public function render()
{
return '<a href="'.$this->link.'">'.$this->name.'</a>';
}
}
class PaginatorPDOLinkBackend extends AbstractPaginatorPDOBackend
{
public function getItems($page)
{
$stmt = $this->pdo->prepare($SQL);
$stmt->execute(array(':page'=>$page));
$items = array();
foreach($stmt->fetch() as $row) {
$item = new PaginatorLinkItem();
$item->setData($row);
$items[] = $item;
}
return $items;
}
}
?>
interface PaginatorItemInterface
{
pubilc funciton setData(array $data);
public function render();
}
interface PaginatorBackendInterface
{
/**
* @return array[PaginatorItemInterface]
*/
public function getItems($page);
}
interface PaginatorButtonFormInterface
{
public function render($page);
}
class Paginator
{
public function __construct(PaginatorBackendInterface $backend,
PaginatorButtonFormInterface $buttonForm)
{
$this->backend = $backend;
$this->buttonForm = $buttonForm;
}
public function render($page)
{
$items = $this->backend->getItems($page);
$result = '<div>'; // Hier kan je ook nog wel wat mooiers van maken
foreach($items as $item) {
$result .= $item->render();
}
$result .= $this->buttonForm->render($page);
$result .= '</div>';
return $result;
}
}
abstract class AbstractPaginatorPDOBackend implements PaginatorBackendInterface
{
protected $pdo;
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}
}
class PaginatorLinkItem implements PaginatorItemInterface
{
proteced $name, $link;
public function setData(array $data)
{
$this->name = $data['name'];
$this->link = $data['link'];
}
public function render()
{
return '<a href="'.$this->link.'">'.$this->name.'</a>';
}
}
class PaginatorPDOLinkBackend extends AbstractPaginatorPDOBackend
{
public function getItems($page)
{
$stmt = $this->pdo->prepare($SQL);
$stmt->execute(array(':page'=>$page));
$items = array();
foreach($stmt->fetch() as $row) {
$item = new PaginatorLinkItem();
$item->setData($row);
$items[] = $item;
}
return $items;
}
}
?>
"Quick'n'Dirty", maar ik hoop dat je het idee een beetje snapt.
Groet,
Pim
Ok, thanks voor de reacties!
Wouter, ik snap waar je heen wilt, maar method getPage(), zou die een integer met de start terug? En dan gewoon een loopje met $topics[$i]?
Pim: eerlijk gezegd snap ik er niet zo veel van. Wat wordt bedoeld met interface? En implements?
Wouter, ik snap waar je heen wilt, maar method getPage(), zou die een integer met de start terug? En dan gewoon een loopje met $topics[$i]?
Pim: eerlijk gezegd snap ik er niet zo veel van. Wat wordt bedoeld met interface? En implements?
Een interface is een perfect middel om klassen te groeperen. Je kan er ook nog mooi vaststellen welke functies er verplicht in moeten.
Met implements kun je een klasse aan de interface koppelen.
Bijv:
Meer informatie: http://phptuts.nl/view/45/12/
Pim, mooie code. Een goed voorbeeld van het Strategy pattern!
Met implements kun je een klasse aan de interface koppelen.
Bijv:
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
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
<?php
/**
* Interface om storage klassen te groeperen
*/
interface Storage
{
/**
* Functie om een value op te slaan
*
* @param string $key De key naar de value
* @param mixed $value De value
*/
public function set($key, $value);
/**
* Functie om een value te krijgen
*
* @param string $key De key naar de value
*
* @return mixed The value
*/
public function get($key);
}
/**
* Class om session storage af te handelen
*/
class SessionStorage implements Storage
{
public function __construct()
{
session_start();
}
/**
* {@inheritdoc}
*/
public function set($key, $value)
{
$_SESSION[$key] = $value;
}
/**
* {@inheritdoc}
*/
public function get($key)
{
if (!isset($_SESSION[$key])) {
throw new InvalidArgumentException($key.' does not exists in SessionStorage');
}
return $_SESSION[$key];
}
}
class CookieStorage implements Storage
{
// ...
}
?>
/**
* Interface om storage klassen te groeperen
*/
interface Storage
{
/**
* Functie om een value op te slaan
*
* @param string $key De key naar de value
* @param mixed $value De value
*/
public function set($key, $value);
/**
* Functie om een value te krijgen
*
* @param string $key De key naar de value
*
* @return mixed The value
*/
public function get($key);
}
/**
* Class om session storage af te handelen
*/
class SessionStorage implements Storage
{
public function __construct()
{
session_start();
}
/**
* {@inheritdoc}
*/
public function set($key, $value)
{
$_SESSION[$key] = $value;
}
/**
* {@inheritdoc}
*/
public function get($key)
{
if (!isset($_SESSION[$key])) {
throw new InvalidArgumentException($key.' does not exists in SessionStorage');
}
return $_SESSION[$key];
}
}
class CookieStorage implements Storage
{
// ...
}
?>
Meer informatie: http://phptuts.nl/view/45/12/
Pim, mooie code. Een goed voorbeeld van het Strategy pattern!
Gewijzigd op 28/05/2012 20:00:02 door Wouter J
Aah op die manier. Nu ik die ie lijkt het niet meer een manier van commentaar geven, maar in grotere projecten lijkt het me wel handig ja...
Ik heb even tijd nodig om pims script te analyseren.
Ik heb even tijd nodig om pims script te analyseren.
Jeroen, het kan ook zonder het commentaar. Het voordeel hiervan is dat je makkelijk iets kunt controleren. Bijv. in mijn storage interface. Ik heb een User klasse en die slaat in de session op of een User ingelogd is dan kun je nu makkelijk controleren:
@Jeroen,
Kom je er een beetje uit?
Kom je er een beetje uit?
ja opzich wel, al ontgaat de logica mij een beetje. regel 74, waar komt $SQL vandaan?
ook uit de render functies kom ik niet zo uit. er zijn er twee, en de tweede returned niets, maar lijkt me van wel?
ook uit de render functies kom ik niet zo uit. er zijn er twee, en de tweede returned niets, maar lijkt me van wel?
>> regel 74, waar komt $SQL vandaan?
Pim heeft denk ik dit even snel getypt voor het idee, maar niet om zo te gebruiken. $SQL moet je zelf invullen.
Her en der staan ook wat typos.
Verder zie ik geen 1 render functie die iets niks retourneert?
Pim heeft denk ik dit even snel getypt voor het idee, maar niet om zo te gebruiken. $SQL moet je zelf invullen.
Her en der staan ook wat typos.
Verder zie ik geen 1 render functie die iets niks retourneert?
sorry, ik bedoelde ook, ik zie twee reder() methods, maar uit elkaar houden lukt me niet.
verder heeft de setData() method geen return
verder heeft de setData() method geen return
Jeroen, niet elke method moet een return hebben. Een setter (zoals setData) set vaak een value aan een property, deze hoeft -eigenlijk mag- niks te retourneren.
Verder is de opbouw van de logica een beetje als volgt:
Je hebt een PaginatorItemInterface. Deze zorgt ervoor hoe het Item eruit ziet. Dus bijv. of het in een div gaat, of in een lijst.
De method render van deze klasse retourneert 1 enkel item.
Met de method setData vullen we wat gegevens van het item in.
Dan heb je een PaginatorButtonFormInterface. Deze zorgt voor de buttons << < 1 | 2 | 3 | > >> enz. Hoe dat eruit ziet plaats je in de render method van de PaginatorButtonFormInterface.
Vervolgens heb je nog een PaginatorBackendInterface. Deze zorgt voor het ophalen van de gegevens.
De method getItems retourneert een array van PaginatorItemInterfaces met alle items. Deze haalt die bijv. uit de database.
Als laatst heb je een Paginator object die alles bij elkaar voegt en via de render method een mooie code teruggeeft met items en buttons.
Verder is de opbouw van de logica een beetje als volgt:
Je hebt een PaginatorItemInterface. Deze zorgt ervoor hoe het Item eruit ziet. Dus bijv. of het in een div gaat, of in een lijst.
De method render van deze klasse retourneert 1 enkel item.
Met de method setData vullen we wat gegevens van het item in.
Dan heb je een PaginatorButtonFormInterface. Deze zorgt voor de buttons << < 1 | 2 | 3 | > >> enz. Hoe dat eruit ziet plaats je in de render method van de PaginatorButtonFormInterface.
Vervolgens heb je nog een PaginatorBackendInterface. Deze zorgt voor het ophalen van de gegevens.
De method getItems retourneert een array van PaginatorItemInterfaces met alle items. Deze haalt die bijv. uit de database.
Als laatst heb je een Paginator object die alles bij elkaar voegt en via de render method een mooie code teruggeeft met items en buttons.
@Pim,
Waarom maak je niet gebruik van het Adapter pattern? Kan je dat toelichten. Wanneer ik een paginator maak, maak ik meestal gebruik van het Adapter pattern vandaar ;-)
Niels
Waarom maak je niet gebruik van het Adapter pattern? Kan je dat toelichten. Wanneer ik een paginator maak, maak ik meestal gebruik van het Adapter pattern vandaar ;-)
Niels
Dit is toch een adapter?
3 maal zelfs:
Je kan zelf een backend injecteren om de items op te halen, PaginatorPDOLinkBackend is een voorbeeldimplementatie.
De backend kan dan een implementatie van PaginatorItemInterface teruggeven. PaginatorLinkItem is het voorbeeld.
En tot slot kan je zelfs de knopjes zelf aanpassen met PaginatorButtonFormInterface, al had ik geen zin daar een voorbeeld voor te maken/.
Best wel een adapter-pattern toch?
Toevoeging op 02/06/2012 00:42:51:
En @Wouter:
Thanks :)
3 maal zelfs:
Je kan zelf een backend injecteren om de items op te halen, PaginatorPDOLinkBackend is een voorbeeldimplementatie.
De backend kan dan een implementatie van PaginatorItemInterface teruggeven. PaginatorLinkItem is het voorbeeld.
En tot slot kan je zelfs de knopjes zelf aanpassen met PaginatorButtonFormInterface, al had ik geen zin daar een voorbeeld voor te maken/.
Best wel een adapter-pattern toch?
Toevoeging op 02/06/2012 00:42:51:
En @Wouter:
Thanks :)
Hoi Pim,
Je hebt gelijk, excuses ik las dit:
Nu ben ik niet echt een pattern goeroe, maar volgens mij is het Strategy pattern niet helemaal hetzelfde als het Adapter pattern.
Je hebt gelijk, excuses ik las dit:
Quote:
Pim, mooie code. Een goed voorbeeld van het Strategy pattern!
Nu ben ik niet echt een pattern goeroe, maar volgens mij is het Strategy pattern niet helemaal hetzelfde als het Adapter pattern.
Ja ik begin het te begrijpen, alleen AbstractPaginatorPDOBackend wordt geextend in PaginatorPDOLinkBackend. Waarom? Die kan je toch ook prima samenvoegen?
Niels, nee ik had de code eerst verkeerd begrepen. Ik dacht dat het iets was met BehaviorInterfaces, maar dat was helemaal verkeerd...
@Jeroen,
Ja, nu natuurlijk wel, maar het is goed mogelijk dat je meerdere backends voor verschillende items wil maken, die alle op PDO gebaseerd zijn en die ook allemaal een aantal dezelfde helper-functies nodig hebben. Dan wordt de AbstractPaginatorPDOBackend opeens nuttig.
Het is natuurlijk helemaal niet noodzakelijk om zó modulair te werken, maar ik heb een beetje overdreven, omdat jij graag flexibiliteit wou zien.
Ja, nu natuurlijk wel, maar het is goed mogelijk dat je meerdere backends voor verschillende items wil maken, die alle op PDO gebaseerd zijn en die ook allemaal een aantal dezelfde helper-functies nodig hebben. Dan wordt de AbstractPaginatorPDOBackend opeens nuttig.
Het is natuurlijk helemaal niet noodzakelijk om zó modulair te werken, maar ik heb een beetje overdreven, omdat jij graag flexibiliteit wou zien.
nou, in ieder geval bedankt voor de hele uitgebreide reacties op dit vraagstuk. het is mij een heel stuk duidelijker, maar zal nog veel moeten oefenen (op heel OO gebied) om dit goed toe te kunnen toepassen.




