paginering OOP
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?
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);
?>
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
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?
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
Ik heb even tijd nodig om pims script te analyseren.
Kom je er een beetje uit?
ook uit de render functies kom ik niet zo uit. er zijn er twee, en de tweede returned niets, maar lijkt me van wel?
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?
verder heeft de setData() method geen return
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.
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
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 :)
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...
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.