DBI: DataBase Interface object class
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:
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
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
<?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>';
?>
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:
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
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
<?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;
}
}
?>
/**
* 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.
Gewijzigd op 01/01/1970 01:00:00 door Toby hinloopen
Bij Linq2SQL schrijf je als het waren een object voor je query waardoor je dus niet meer rechtstreeks SQL code naar de database stuurt.
Het resultaat kun je vervolgens dan in een resultaat object zetten en deze weer gebruiken in je klassen om bijvoorbeeld gegevens op te halen als getUserById() of iets dergelijks.
Het is nog vrij nieuw dus extreem veel is er niet te vinden maar loop eens een bibliotheek binnen en zoek een modern boek over C#.
Propel is een bekende PHP ORM-toolkit, ik heb daar al regelmatig brakke SQL van voorbij zien komen... Kijk er eens naar en doe er je voordeel mee.
Probleem:
Quote:
De database is langzaam.
Oorzaak:
Quote:
De SQL is zwaar klote, wees blij dat je überhaupt een antwoord krijgt.
Wanneer je een programmeur niet lastig wilt vallen met SQL (wat een goed plan is!), geef hem dan kant-en-klare views en stored procedures die allemaal met een simpele SELECT zijn aan te roepen. De complexe SQL verplaats je daarmee naar de database. Daar kun je de SQL dan ook eenvoudig gaan optimaliseren zonder dat je daar de programmatuur mee lastig hoeft te vallen. Dit maakt bouwen, testen, debuggen en onderhouden een heel stuk eenvoudiger. En dus goedkoper.