De classe kan data ophalen zonder queries en cache't de resultaten voor het geval dat dezelfde request nog een keer nodig is in het script om zo te voorkomen dat de database dubbel wordt aangesproken voor dezelfde data.
Na een week scripten heb ik het volgende resultaat:
Merk op dat de tabel "boards" recursief gebruikt wordt: de "parent-board" wordt automatisch opgehaald.
http://www.levensweg.net/livphp4_classes/
met de volgende broncode:
<?php
include('classes/classes.index.php');
nocache();
echo '<body style="background-color:#000;"><pre style="background-color:#222;"><span style="background-color:#111;color:#2F2">';
$sql = new sql;
$forum = new dbi;
$forum->new_table('users','users',array(//maak een users-tabel in de DBI.
'id' => 'PRIMARY INT this.id',//id wordt "users.id" uit de users-tabel uit de database.
'username' => 'UNIQUE STRING this.username',// ''
'status' => 'ENUM({"unactivated":0,"deprecated":1,"banned":2,"user":3,"vip":4,"moderator":5,"global":6,"admin":7}) this.generalstatus'
//haal users.generalstatus op en zet deze om in een getal.
));
$forum->new_table('boards','forum_boards',array(
'id' => 'PRIMARY INT this.id',//"id" wordt "liveforumtopics.id" uit de database.
//GET [table] [column to search in] [value to search for]
'title' => 'STRING this.title',//title wordt liveforumtopics.title uit de database.
'parent' => 'GET boards this.parent_board_id'
));
$forum->new_table('topics','forum_topics',array(
'id' => 'PRIMARY INT this.id',//"id" wordt "liveforumtopics.id" uit de database.
//GET [table] [column to search in] [value to search for]
'user' => 'GET users this.user_id',//Haal de gebruiker uit de DBI users tabel op basis van "user_id" uit de "liveforumtopics" database tabel.
'title' => 'STRING this.title',//title wordt liveforumtopics.title uit de database.
'board' => 'GET boards this.board_id'
));
echo '1:<br/>';
print_r($forum->get('topics',1));//haal topic id=1 op met bijbehoren data.
echo '2:<br/>';
print_r($forum->get('topics',2));//haal topic id=2 op met bijbehoren data.
echo '3:<br/>';
print_r($forum->get('topics',3));//haal topic id=3 op met bijbehoren data.
echo '4:<br/>';
print_r($forum->get('topics',4));//haal topic id=4 op met bijbehoren data.
echo '5:<br/>';
print_r($forum->get('topics',5));//haal topic id=5 op met bijbehoren data.
echo '6:<br/>';
print_r($forum->get('topics',6));//haal topic id=6 op met bijbehoren data.
echo '7:<br/>';
print_r($forum->get('topics',7));//haal topic id=7 op met bijbehoren data.
echo '8:<br/>';
print_r($forum->get('topics',8));//haal topic id=8 op met bijbehoren data.
echo '<br/>---------------------------------------------------------------------------------------<br/>';
print_r(sql::$queries);
echo '<br/>---------------------------------------------------------------------------------------<br/>';
print_r($forum);
//print alle queries (debug)
echo '<br/>---------------------------------------------------------------------------------------<br/>';
echo '</span></pre></body>';
?>PMA screens:

dit is de bijbehoren DBI classe:
<?php
/**
* DataBase Interface object class.
* 2009-03-28
* Toby Hinloopen
*/
define('SJP_CONSTANT',1);
define('SJP_VAR',2);
class dbi {
private $tables = array();
private $default_sql_instance;
public static $default_instance;
private $cache = array();
private $last_error_id;
private $last_error_str;
//object constructor.
public function __construct($sqlinstance = false,$force_set_as_default = false) {
if($force_set_as_default
|| !isset(self::$default_instance)) {//if default_instance should be force-overwritten OR it is not set...
$this->as_default();
}
if(is_resource($sqlinstance)) {
$this->default_sql_instance = $sqlinstance;
} else {
$this->default_sql_instance = sql::$default_instance;
}
}
//static constructor
public static function init($sqlinstance = false) {
return self::$default_instance = new self($sqlinstance,true);
}
public function as_default() {
if(!isset($this)) {
error(7,'Function dbi::as_default cannot be used in static context.');
return false;
}
self::$default_instance = $this;
return true;
}
public function new_table($name,$SQLtable,$layout,$sqlinstance = false) {
if(!isset($this)) {
return self::$default_instance->new_table($name,$SQLtable,$layout,$sqlinstance);
}
$this->tables[$name]['sqltable'] = $SQLtable;
$this->tables[$name]['sqlinstance'] = null;
$this->tables[$name]['sqlcolumns'] = array();
$this->tables[$name]['primary'] = null;
$this->tables[$name]['primarytype'] = 'STRING';
if(is_resource($sqlinstance)) {
$this->tables[$name]['sqlinstance'] = $sqlinstance;
} else {
$this->tables[$name]['sqlinstance'] = $this->default_sql_instance;
}
$results = array();
foreach($layout as $key=>$value) {
$layout[$key] = array(
'unique'=>false,
'rtype'=>null,
'function'=>array('name'=>null)
);
$value = explode(' ',$value);
for($x=0;$x<count($value);$x++) {
if($value[$x] == 'PRIMARY'
|| $value[$x] == 'UNIQUE') {
$layout[$key]['unique'] = true;
if($value[$x] == 'PRIMARY') {
$this->tables[$name]['primary'] = $key;
}
} elseif($value[$x] == 'INT'
|| $value[$x] == 'STRING') {
$layout[$key]['rtype'] = $value[$x];
} elseif($value[$x] == 'GET') {
$layout[$key]['function'] = array('name'=>$value[$x]);
} else {
if(preg_match('/([a-z_][a-z0-9_]+)\\((.*)\\)/i',$value[$x],$matches)) {
foreach($matches as $matchkey => $matchval) {
if($matchkey > 1) {
if(preg_match('/\\A{.+}\\Z/',$matchval)) {
$matches[$matchkey] = stdClassObject2array(json_decode($matchval));
}
}
}
$layout[$key]['rtype'] = array($matches[1],$matches[2]);
} elseif(preg_match('/\\A[a-z_][a-z0-9_]*\\.[a-z_][a-z0-9_]*(?:\\.[a-z_][a-z0-9_]*)*\\Z/i',$value[$x])) {
$value[$x] = explode('.',$value[$x]);
if($value[$x][0] == 'this') {
array_shift($value[$x]);
}
$layout[$key]['function'][] = array(SJP_VAR,$value[$x]);
$this->tables[$name]['sqlcolumns'][] = $value[$x][0];
} else {
if(preg_match('/\\A{.+}\\Z/', $value[$x])) {
$layout[$key]['function'][] = array(SJP_CONSTANT,stdClassObject2array(json_decode($value[$x])));
} else {
$layout[$key]['function'][] = array(SJP_CONSTANT,$value[$x]);
}
}
}
}
}
$this->tables[$name]['layout'] = $layout;
if($this->tables[$name]['layout'][$this->tables[$name]['primary']]['rtype'] == 'INT') {
$this->tables[$name]['primarytype'] = 'INT';
}
$this->cache[$name] = array();
}
//haal een enkele rij op waarbij de primaire sleutel gelijk staat aan "value".
public function get($tablename,$value) {
if(!isset($this)) {
return self::$default_instance->get($tablename,$value);
}
$table = $this->tables[$tablename];
if($table['primarytype']=='STRING') {
$value = (string)$value;
} else {
$value = (int)$value;
}
if(isset($this->cache[$tablename][$value])) {
return $this->cache[$tablename][$value];
}
$result = $table['sqlinstance']->query_result_assoc('SELECT `'.implode('`,`',$table['sqlcolumns']).'` FROM `'.$table['sqltable'].'` WHERE `'.$table['primary'].'` = '.($table['primarytype']=='STRING'?'\''.$table['sqlinstance']->escape($value).'\'':(int)$value).' LIMIT 1');
if($result === false) {
$this->error(6,'SQL query failed. SQL error: ['.$table['sqlinstance']->error_id().']: '.$table['sqlinstance']->error_str());
return false;
}
if(count($result)==0) {
$this->cache[$tablename][$value] = false;
return false;
}
$result = $this->format($table,$result);
$this->cache[$tablename][$value] = $result;
return $result;
}
public function format($table,$result) {
$output = array();
foreach($table['layout'] as $key=>$action) {
$val = null;
foreach($action['function'] as $varkey => $variable) {//converteer alle variablen en constante waarden naar hun "echte" waarden.
if(is_int($varkey)) {
$newvar = null;
if($variable[0]==SJP_CONSTANT) {
$newvar = $variable[1];
} elseif($variable[0]==SJP_VAR) {
$newvar = $result;
foreach($variable[1] as $rkey) {
$newvar = $newvar[$rkey];
}
}
$action['function'][$varkey]=$newvar;
}
}
//argumenten zitten nu opgeslagen in $action['function'][#id]
//$val is de waarde die meegegeven wordt aan $output[$key].
if($action['function']['name']==null) {
$val = $action['function'][0];
} elseif($action['function']['name']=='GET') {
$val = $this->get($action['function'][0],$action['function'][1]);
} elseif($action['function']['name']=='ENUM') {
$val = $action['function'][1][$action['function'][0]];
}
if($action['rtype'] == 'INT') {
$output[$key] = (int)$val;
} elseif($action['rtype'] == 'STRING') {
$output[$key] = (string)$val;
} elseif(is_array($action['rtype'])) {
if($action['rtype'][0]=='ENUM') {
$output[$key] = $action['rtype'][1][$val];
}
} else {
$output[$key] = $val;
}
}
return $output;
}
private function error($id,$str) {
if(!isset($this)) {
return self::$default_instance->error($id,$str);
}
$this->last_error_id = $id;
$this->last_error_str = $str;
classerror($this);
return true;
}
public function error_id() {
if(!isset($this)) {
return self::$default_instance->error_id();
}
return $this->last_error_id;
}
public function error_str() {
if(!isset($this)) {
return self::$default_instance->error_str();
}
return $this->last_error_str;
}
}
?>Natuurlijk ga ik dit nog heel erg uitbreiden: voor nu heb ik alleen 1 functie erin zitten die de data ophaalt: get. Deze functie haalt 1 rij op, op basis van de gegeven "value". Er komen andere functies zoals "get_multiple(1,2,3,4,5,6,7,8)", "get_range(1,8)" en "get_relative_range(0,9)" die gewoon de 1e 10 resultaten ophaalt.
Ook komen er zogeheten regels die je kan instellen. Zo kan je extra standaard regels toevoegen die standaard in een querie toegevoegd moeten worden (achter WHERE). Wanneer bijv. een bepaalde groep gebruikers geen berichten in board_id = 2 mag zien, kan je een regel toevoegen met een functie "exclude('board_id',2)".
Ik weet nog niet precies hoe ik dat allemaal ga indelen maar het is mijn bedoeling een complete website erop te kunnen draaien zonder ook maar 1 querie te hoeven starten en ook nog eens efficient en veiligheid met zich mee te brengen.
De rede dat ik dit hier post is omdat feedback (opmerkingen, gezeik etc) wel handig is bij zulke dingen.