DBI: DataBase Interface object class

Overzicht Reageren

Sponsored by: Vacatures door Monsterboard

Toby hinloopen

toby hinloopen

08/04/2009 01:10:00
Quote Anchor link
Ik ben bezig met een aardig uitgebreide SQL interface classe. Hiermee moet het mogelijk zijn objecten aan te maken en deze op te halen uit de database. De classe gaat in de toekomst ook SQL tabellen kunnen aanmaken maar kan ook bestaande tabellen gebruiken.

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)
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
<?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:
Afbeelding

dit is de bijbehoren DBI classe:
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
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;
    }
    
}


?>


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
 
PHP hulp

PHP hulp

19/04/2024 22:42:40
 
Jurgen assaasas

Jurgen assaasas

08/04/2009 09:23:00
Quote Anchor link
Om je wat op ideeen te brengen is het misschien handig om eens naar Linq2SQL te kijken dit is een database module in VisualStudio (C#). Deze doet in principe het zelfde als jou klasse doet.

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#.
 
Frank -

Frank -

08/04/2009 18:04:00
Quote Anchor link
Klinkt als ORM en dat is meestal geen feest. Performance is vrijwel altijd om te huilen en dat alleen maar omdat een programmeur niet goed thuis is in SQL of er geen aandacht aan wil geven. Hibernate doet het nog redelijk (Java), maar ook daarbij haal je nauwelijks 80% van de performance die je met pure SQL zou kunnen halen. Dynamische SQL door een stuk programmatuur opgesteld, doet wat het moet doen, maar kan nooit de meest optimale queries samenstellen. Daarvoor ontbreekt de kennis en ervaring van de database en zijn data.

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.
 



Overzicht Reageren

 
 

Om de gebruiksvriendelijkheid van onze website en diensten te optimaliseren maken wij gebruik van cookies. Deze cookies gebruiken wij voor functionaliteiten, analytische gegevens en marketing doeleinden. U vindt meer informatie in onze privacy statement.