Versio

DataObjects

Ik ben lui, en ik denk dat er meer met mij zijn. Ik wil het liefst gewoon een object manipuleren en voor de rest niet omkijken naar inhoud escapen, datatypen controleren enz. enz.

Dus ik 'bedacht' dataobjecten. Deze abstracte klassen.

En om het leven nog wat makkelijker te maken, zijn zowel de DataObjecten als de DataSets zo gebakken dat zij voldoen aan de voorwaarden die de interface Iterator stelt. Hierdoor kan je ze probleemloos in een foreach-lus gebruiken!

Iedere klasse vertegenwoordigt een tabel in je database. Je zal in iedere klasse dus de structuur van de tabel moeten opgeven.

Heel belangrijk: Roep voor het gebruik van de objecten DataSet::buffer() aan, eventueel met het aantal objecten dat je van te voren wilt vullen als parameter. Dit zorgt ervoor dat hij al die objecten in 1 keer voltankt, en niet per object een query uitvoert.

Maar genoeg gepraat, een voorbeeld is vast begrijpbaarder:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
<?php
class Film extends AbstractDataObject {
   public static $genres = Array (
    1 => 'actie',
    2 => 'animatie',
    4 => 'avontuur'
   );

   /* De tabelnaam in de database */
   protected $tableName = 'films';
  
   /*
     * De primaire sleutel. Deze wordt gebruikt bij SELECT
     * en UPDATE en moet dus, heel belangrijk, uniek zijn.
     */

   protected $primaryKey = 'filmId';
  
   /*
    * De tabelstructuur:
    * key: de alias, daarmee roep je hem later aan als $object->keynaam
    * eerste array-entry: de veldnaam in de db
    * de rest in de array: flags. Datatype (gebruikt door
    *  -- AbstractDataObject::convert) en READ_ONLY. Spreekt voor zich.
    */

   protected $tableStructure = array(
      'id'        => array('filmId', AbstractDataObject::INT, AbstractDataObject::READ_ONLY),
      'title'     => array('filmTitle', AbstractDataObject::STRING),
      'round'     => array('filmRound', AbstractDataObject::INT),
      'playtime'  => array('filmPlaytime', AbstractDataObject::INT),
      'IMDBRating'=> array('filmIMDBRating', AbstractDataObject::FLOAT),
      'filmGenres'=> array('filmGenres')
      );


   /*
    * LET OP! Er wordt sowieso een instanse gemaakt zonder paramters
    * door de db-klasse, om de tabelstructuur te achterhalen. let er dus op
    * dat je _niets doet_ zonder parameters. Zonder parameters is is het
    * __een dummy__!
    */

   public function __construct($filmName = null, $databaseInstance = null)
   {

      $this->databaseInstance = $databaseInstance;
      if(is_string($filmName)) {
         if(!$this->populate('filmTitle = :filmName', array('filmName' => $filmName))) {
            throw new Exception('Film '.$filmName.' not found');
         }
      }
elseif(is_int($filmName)) {
         if(!$this->populate('filmId = :filmId', array('filmId' => $filmName))) {
            throw new Exception('Film #'.$filmName.' not found');
         }
      }

      /* zie je, ik onderneem geen actie waneer geen parameter is meegegeven */
   }

   /*
    * Voorbeeld van callback-functie:
    * Waneer je $ditObject->genres vult, wordt er teruggegaan naar
    * deze functie. Deze functie is vervolgens verantwoordelijk voor
    * het manipuleren van de $data-array die hij meekrijgt, dat is
    * de array met alle bijna raw inhoud van de database. Voorzichtig dus!
    *
    * In dit geval wordt dus een array (die van Film::$genres) omgezet in
    * een integer, aangezien ik moeilijk die hele array erin kan stoppen.
    */

   public function setGenres(&$data, $filmGenres)
   {

      $data['filmGenres'] = array_sum($filmGenres);
      return true;
   }


   /*
    * En omdat ik niets heb aan een integer, maar een array met genres wil,
    * levert deze callback-functie mij een array met alle genres die door de
    * integer uit de db vertegenwoordigd worden.
    */

   public function getGenres(&$data)
   {

      $keys = array();
      foreach(array_keys(self::$genres) as $key) {
         if($data['filmGenres'] & $key) {
            $keys[] = self::$genres[$key];
         }
      }

      return $keys;
   }


   /*
    * En nog even een laatste voorbeeld: je callback-functie hoeft niet
    * in de $this->tableStructure voor te komen. Maar $instance->link
    * zal een gestripte $this->title geven.
    */

   public function getLink($page) {
      return str_replace(' ', '-', sprintf($page, $this->title));
   }
}


//En nu het gebruik:
$db = new Database('localhost', 'root', '', 'films');
$resultSet = $db->fetch('Film', 'filmRound = :ronde', array('ronde' => 1));
$resultSet->buffer(); //laad alle regels vast in
foreach($resultSet as $film) {
   echo 'Deze film heet ' . $film->title;
   foreach($film as $sleutel => $waarde) {
      echo '[' . $sleutel . '] => ' . var_dump($waarde, true) . '<br>';
   }
}


// update voorbeeld
$resultSet->rewind(); // even overnieuw
$resultSet->next();
$film = $resultSet->current();
$film->title = str_repeat($film->title, 2);
$film->commit();
// en toen was de naam dubbelop!
?>


Inserten is ook vrij simpel: gewoon je decorator lief invullen
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
<?php
$film
= new Film(null, $db);
$film->title = 'iets';
// enz.
$film->commit();
// en hij zit erin!
?>


Er zullen nog wel wat meer features inzitten, die ik zelf zelfs al vergeten ben. Ik gebruik hem gewoon, en het werkt. En zo hoort het :) Mocht je vragen of problemen hebben, maak een topic in het forum en post hier een link in de reacties naar het topic. Zo houden we de reacties schoon.

Gesponsorde koppelingen

BHosted Hosting al vanaf € 1,- per maand

Controleer nu gratis jouw domeinnaam:

  

PHP script bestanden

  1. dataobjects

 

14 reacties op 'DataObjects'

PHP hulp
PHP hulp
0 seconden vanaf nu
 

Gesponsorde koppelingen
Legolas
Legolas
7 jaar geleden
 
0 +1 -0 -1
Zoiets wou ik nou net gaan maken... Allemaal applaus voor Jelmer! :)
Jonathan -
Jonathan -
7 jaar geleden
 
0 +1 -0 -1
netjes!
Robert Deiman
Robert Deiman
7 jaar geleden
 
0 +1 -0 -1
Mooie tutorial Jelmer :)

Edit:

foutje die ik aangaf is opgelost
Jelmer rrrr
Jelmer rrrr
7 jaar geleden
 
0 +1 -0 -1
Een typo, opgelost, dankje Robert.
Martijn B
Martijn B
7 jaar geleden
 
0 +1 -0 -1
Misschien is het aardig om de uitvoer tijd op te
nemen in de query methode.
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?
public function query($sql)
{

    $iTime = array_sum(explode(' ', microtime()));
    
    if( $result = mysql_query($sql, $this->handle) )
    {

         self::$queries[] = $sql .
                            '<br>time: ' . round(array_sum(explode(' ', microtime())) - $iTime, 4) .
                            '<br>hits: ' . @mysql_num_rows($result) . '/' . @mysql_affected_rows($result);
         return $result;
    }

    
    unset($iTime);
    throw new DatabaseException($this->handle, 'Error while executing query', $sql);
}

?>


Ik zelf heb een attribuut "$this->_bDie_onError" binnen mijn database klasse die er voor zorgt dat het script niet dood gaat als er een error is. Standaard staat dit attribuut op true maar als je bijvoorbeeld bezig met met sessies of gebruikers gegevens dan zet ik deze altijd op false. Uiteraard wordt de error wel geregistreerd.

edit:

Moet je in jou query methode er nu zelf voor zorgen dat je de exception opvangt?

edit2:

Arg weer vergeten...

Strakke code!
Willem Jan Z
Willem Jan Z
7 jaar geleden
 
0 +1 -0 -1
Quote:
Iedere klasse vertegenwoordigt een tabel in je database. Je zal in iedere klasse dus de structuur van de tabel moeten opgeven.

Is dit niet beter op te lossen? Heb niet complete classes door gekeken, maar is het niet makkelijker elk object een tabel te laten zijn? Dus dat je in de constructor de tabelstructuur aanmaakt. Lijkt me niet lastig te realiseren, maar zoals ik zei, heb niet alles grondig nagelopen verder. Ziet er wel top uit verder, lekker lui zoals het hoort :)
Pim Vernooij
Pim Vernooij
7 jaar geleden
 
0 +1 -0 -1
Quote:
Heb niet complete classes door gekeken, maar is het niet makkelijker elk object een tabel te laten zijn? Dus dat je in de constructor de tabelstructuur aanmaakt.
Dat is idd erg handig. Wat ik doe met mijn DbObject class is de tabel structuur ophalen aan de hand van een SELECT query. De kolommen die die query oplevert, worden editbaar door mijn DbObject.

Wat ik ook kan doen is van tevoren een 'model' opgeven, waarin ik specifieke methods aanmaak die weer extra dingen kunnen. In dat model kan je van tevoren ook een tabel structuur opgeven.

Dit model is vergelijkbaar met de class van Jelmer. Het verschil is dat ik niet verplicht een model hoef aan te maken. Mijn models hebben ook een abstracte class als basis. Wat ik mooier vind aan die van jelmer is de mogelijkheid om validatie op object niveau en bijvoorbeeld niet op formulier niveau uit te voeren. Ook de readonly velden zijn erg goed gedaan! Daar ga ik zeker nog mee aan de slag :)

Een wrapper voor meerdere DbObject instanties is DbObjectCollection. Het engie wat deze doet is een array met DbObject instanties genereren. Ook heeft deze een aantal functies waarmee ik bijvoorbeeld alle objecten in de array kan opslaan oid.

via __destruct(); heb ik de mogelijkheid om mijn DbObject'en automatisch op te slaan :) default staat dat uit...

Mijn standaard DbObjectCollection instantie:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
<?php
$oUsers
= new DbObjectCollection( 'gebruiker' );
foreach( $oUsers->getObjects() as $oUser ) {
    echo $oUser->name . "<br/>";
    $oUser->lastViewed = time();
}

$oUsers->saveObjects();
?>

Dat is alles :) geen kolom namen oid. En door mijn DBAL maakt het ook niet uit op welk database platform ik het DbObject initieer. Het werkt allemaal!

En nu een met model (voor het aanmaken van een nieuwe user):
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
<?php
//$data is een resultaat vanuit een formulier.
$oUser = new User( $data );
$oUser->save();
?>

De class User bevat de kolommen die gevuld moeten worden. Deze layout is gelocked waardoor deze altijd hetzelfde blijft. Onder de class User bevindt zich een abstracte class DefaultDbObject. Deze bevat weer methods die het DbObject aansturen.
Jelmer rrrr
Jelmer rrrr
7 jaar geleden
 
0 +1 -0 -1
Omdat ik mijn Select-query bij Database::fetch alleen de ID's laat ophalen (zodat je 500 results kan krijgen, maar hij niet alle data in hoeft te laden als je er toch maar 10 gebruikt. De rest zet je dan bijv. in een sessie) De DataSet klasse kan dan vervolgens alle gegevens aan de hand van het ID zoeken. Gewoon uit efficientie.
Majid Ahddin
Majid Ahddin
7 jaar geleden
 
0 +1 -0 -1
misschien snap ik het niet hoor, maar hoe gebruik ik fetch() als ik alle films wil hebben met ronde > 1 ??
Jelmer rrrr
Jelmer rrrr
7 jaar geleden
 
0 +1 -0 -1
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
<?php
    $dbInstance
->fetch('Film', 'filmRound > :roundNumber', array('roundNumber' => 1));
?>

of simpeler, waneer 1 niet uit userinput komt:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
<?php
    $dbInstance
->fetch('Film', 'filmRound > 1');
?>
DaeDaluz
DaeDaluz
7 jaar geleden
 
0 +1 -0 -1
Abstraction

Any class that contains at least one abstract method must also be abstract.

Maar zover ik nu zie, zijn er geen abstracte methodes in jouw 'abstracte' klasse, ik snap het idee opzich wel van een klasse met wat standaard functies. Maar het gebruik van abstract is hier overbodig.

Abstraction is bedoeld dat je je ervan verzekerd dat als een andere klass de abstracte klasse implementeerd dat die dan de methodes ook bevat.

Dus als je een abstracte klasse Dier hebt met de methodes beweeg() en maakGeluid(). En je hebt een klasse Hond en een klasse Vogel, dan doen beweeg en maakGeluid beiden iets anders maar je wilt wel dat ze allebei geluid kunnen maken en kunnen bewegen.
Jelmer rrrr
Jelmer rrrr
7 jaar geleden
 
0 +1 -0 -1
Hij is abstract omdat hij niet kan leven zonder $tableStructure, $tableName en $primaryKey. Helaas kan ik tot nu toe alleen maar functies verplichten. En ik vond het een beetje zonde om er functies van te maken. Het liefst had ik ze zelfs het liefst 'static' meegegeven. Maar het is mij ook nog niet gelukt om static variabelen van de child door de parent uit te laten lezen. Dan had ik die Reflection-zooi ook niet nodig gehad. Zie ook mijn topic erover toentertijd.

edit: bij de AbstractIterator geldt hetzelfde, dan maar voor de array $stack.
DaeDaluz
DaeDaluz
7 jaar geleden
 
0 +1 -0 -1
Mwah, dat ie afhankelijk is van variabelen maakt hem niet abstract, het is vooral bedoeld voor methodes, daarnaast vind ik het nut van abstracte klasses in php sowieso niet heel groot. Aangezien het vooral handig is met datatypes, die je niet hebt in php.
Jelmer rrrr
Jelmer rrrr
7 jaar geleden
 
0 +1 -0 -1
Quote:
Mwah, dat ie afhankelijk is van variabelen maakt hem niet abstract, het is vooral bedoeld voor methodes

Door wie...
Ik heb dit gemaakt voor wat ik handig vind. En een AbstractDataObject kan gewoon simpelweg niet zelf als instance gebruikt worden. Het is puur een toolkit met functies die ik verder in andere functies (die hier niet vermeld staan, bijv. pagination-script) weer aanroep. Om dit af te dwingen, en om er voor te zorgen dat het onmogelijk is om een AbstractDataObject te instantiƫren zonder een tabelstuctuur, heb ik hem abstract gemaakt.

Om te reageren heb je een account nodig en je moet ingelogd zijn.

  • Labels
  • Geen tags toegevoegd.
Get Adobe Flash player