9.17a Adatbáziskezelés absztrakciós rétegen keresztül

Adatbáziskezelés másképpen – absztrakciós rétegek, ADODB, ODBC

Fontos kérdés, hogy mit tegyünk akkor, ha előre nem tudjuk, hogy milyen adatbáziskezelőt fogunk használni az oldalunkon, vagy a fejlesztés nem ugyanazon az adatbáziskezelőn folyik, mint ahol a program fog futni?

Az egyes adatbáziskezelők az SQL szintakszis szerint működnek, tehát ha készítünk egy olyan programcsomagot, amely eltakarja előlünk az adatbáziskezelők közti különbségeket, akkor tudunk erre alapozva olyan alkalmazást írni, amelynek mindegy, hogy melyik adatbáziskezelőt használja. Ez az adatbázis absztrakciós réteg alkalmazásának módszere.

Az absztrakciós réteg

A konkrét adatbáziskezelőtől független olyan utility csomag, amely eltakarja az adatbázis-kezelők közötti különbségeket, kiegészíti azokat plusz funkciókkal kényelmesebbé teszi az adatbáziskezeléssel kapcsolatos programozást.

PHP program

Adatbázis típusa, kapcs.azonosító, SQL  ↓                    ↑  A lekérdezés eredménye

Absztrakciós réteg (pl. ADODB)

↓↑

↓↑

↓↑

↓↑

MySQL driver

PQ SQL driver

MSSQL driver

ODBC driver

Felmerül a kérdés, hogy vajon nem, lassítja-e le nagyon a programjaink futását egy ilyen réteg alkalmazása. Megnyugtathatjuk a kedves olvasót, hogy bármiféle absztrakciós réteg lassítja a rendszert, de csak olyan csekély mértékben, hogy a lassulás észrevehetetlen, csak extrém nagy szerver terhelés, mellett észrevehető, ugyanakkor az alkalmazásfejlesztés nagyságrendekkel meggyorsul.

Ilyen egyszerű absztrakciós réteget többet is lehet találni az interneten:

ODBC programcsomag

A Microsoft már korábban megvalósított egy adatbázis-kezelés esetén jól használható absztrakciós eljárást, ez pedig az ODBC driverek csoportja.

Az ODBC driverek olyan meghajtók, amelyek a Windows rendszerre telepített (szinte) tetszőleges adatbáziskezelőt egységes módon érnek el. Ennek megfelelően az ilyen drivert használó alkalmazások a Windows rendszeren az SQL szerver módosításával is működnek, ODBC-n keresztül érik el őket.

Használatuk:

  1. Létre kell hozni az adatbázist valamilyen adatbázis-kezelővel: Egy Windows rendszeren lehet, Access, MSSQL, MySQL, PostgreSQL, stb…, amelynek létezik ODBC drivere.

  2. Létre kell hozni egy ODBC kapcsolatot. Ezt az ODBC kezelővel tehetjük meg.A különböző Windows verziók esetén ez máshol van, de alapvetően a Vezérlőpult környékén kell keresni „ODBC adatforrások” vagy hasonló néven. 

A fenti beállításokkal most egy MySQL adatforrást adtunk meg.

Ezek után az ODBC adatbáziskezelő utasításokat kell használni a PHP-ban. Az ODBC csomag részletes leírását lásd az alábbi helyen: http://www.php.net/manual/hu/ref.odbc.php

Az ADODB réteg (elavult)

Az ADODB eredetileg a Microsoft által kifejlesztett adatbázis-kezelési programcsomag, amely egyesíti az SQL adatkezelés és a kliens oldali adatelérés előnyeit. A lényege, hogy az adatbázis-kezelőtől lekérdezésekkel kapott eredményeket – rekordszeteket – nem csak szekvenciálisan dolgozhatunk fel, hanem véletlen eléréssel bármelyik részét elérhetjük. Segítségével könnyen tudunk gördíthető menüket, listboxokat és ehhez hasonló objektumokat készíteni grafikus rendszerben.

Az php ADODB alkalmazáscsomag ehhez hasonló funkciókat valósít meg. Az ADOdb for PHP-hez legalább PHP 4.0.2 kell. Használatának előnyei:

  • Könnyen használható a Windowson programozóknak, mivel szintaktikája hasonlít a Microsoft féle ADODB-hez.

  • A PHP natív adatbázis-kezelése felé egy réteget von, ami nem lassítja le lényegesen az adatbázis elérését, viszont tetszőleges adatbázison azonossá teszi az adatbázisok kezelését.

  • Nem csak lekérdezésekre, hanem Update és Insert utasításokra is jól használható.

  • Támogatja a PHP4 sessionját.

  • Saját magában definiált adattípusokkal dolgozik – ez is a különböző rendszerek kompatibilitását segíti elő.

Telepítése:

Az ADODB library-t letöltés után be kell tenni az adodb alkönyvtárba, vagy a PHP include library-jai közé. Ez utóbbi esetben a PHP.ini file-t meg kell szerkeszteni egy kicsit:

; Windows: "\path1;\path2"
include_path = ".;i:\phplibs;i:\phplibs\adodb;" 

Ekkor az alábbi formát lehet használnunk, mivel a PHP eléri mindig az ADODB-t.

<?php
    include('adodb.inc.php');
    $db = ADONewConnection('mysql');
    $db->debug = true;
    $db->Connect($server, $user, $password, $database);
    $rs = $db->Execute('select * from some_small_table');
    print_r($rs->GetRows());
?>

Ha nem tudjuk library-be tenni, akkor egy alkönyvtárba tesszük az ADODB programcsomagot.

<?php
    include('adodb/adodb.inc.php');
    $db = &ADONewConnection('mysql');
    $db->debug = true;
    $db->Connect($server, $user, $password, $database);
    $rs = $db->Execute('select * from some_small_table');
    print_r($rs->GetRows());
?>

A scriptben az

include(’adodb/adodb.inc.php’);

utasítással mondjuk meg a scriptnek, hogy ADODB adatbázist fog kezelni a program. Az adatbázis típusát az

$conn = ADONewConnection('mysql') vagy a
$conn = &ADONewConnection(' mysql');

formát használhatjuk a kapcsolat létrehozására. Ennek hatására létrejön a $conn nevű objektum, amely minden olyan paramétert és metódust tartalmaz, ami a későbbi kezeléshez szükséges. Amennyiben egy másik oldalon újra szükségünk van ennek az objektumnak az adataira, az objektumot célszerű lementeni sessionba, majd a következő oldal elején újra betölteni. Feltéve azt, hogy az objektum definició már lefusson, amikor a session elindul, tehát, ha van egy általános session kezelő rutincsomagunk, akkor a helyes includolási sorrend az alábbi:

include(’adodb.inc.php’);
include(’session.php’);

 A továbbiakban nézzünk egy egyszerű példát, amely a Microsoft Acces-hez adott NorthWind adatbázisból egy lekérdezést hajt végre, végigmegy a rekordokon és ha dátum vagy idő típusú mezőt talál, akkor azt megfelelő formátumban írja ki.

<?php
include('adodb.inc.php');
$conn = &ADONewConnection('access');    # Kapcsolat létrehozása
     
$conn->PConnect('northwind');      # MS ACCESS-hez kapcsolódunk, northwind dsn-nel
$recordSet = &$conn->Execute('select CustomerID,OrderDate from Orders');
if (!$recordSet) 
    print $conn->ErrorMsg();       # Hibaüzenet, ha nincsen eredménye a lekérdezésnek
else
while (!$recordSet->EOF) {
    $fld = $recordSet->FetchField(1); # Az első mező beolvasása
    $type = $recordSet->MetaType($fld->type);
    if ( $type == 'D' || $type == 'T') 
          print $recordSet->fields[0].' '.
               $recordSet->UserDate($recordSet->fields[1],'m/d/Y').'<BR>';
    else 
          print $recordSet->fields[0].' '.$recordSet->fields[1].'<BR>';
     
    $recordSet->MoveNext();
}
$recordSet->Close(); # optional
$conn->Close(); # optional
?>

A $conn ADO objektumhoz a

$conn->Pconnect(‘northwind’) 

metódus kapcsolja hozzá a Northwind adatbázist, ami természetesen elérhető a windowsos rendszerben.

Az adatok lekérdezését az

$recordSet = $conn->execute(’select * from Orders’) ;

metódus hajtja végre. Ez a $recordSet változóba visszatér egy ADO rekordszet objektummal. Ha valamilyen ok miatt nem jön létre a rekordszet, akkor azt le lehet kérdezni, és hibakezelést lehet végrehajtani. A rekordszetnek van kurzora. Ez a kurzor jelöli meg az aktuális rekordot. A kurzort

$recordSet->MoveNext()

metódussal tudom tovább mozgatni. A sorok közötti mozgást további parancsok is könnyítik:

$recordSet->Move($n) - az aktuális sorhoz képest n sorral ugrik tovább a kurzor

$recordSet->MoveNext() – következő sorra lép a kurzor

$recordSet->MoveFirst() – első sorra ugrik a kurzor

$recordSet->MoveLast() – utolsó sorra ugrik a kurzor

$recordSet->AbsolutePosition (n) – Egy abszolút módon megadott sorra ugrik

$sorszám = $recordSet->CurrentRow () – Az aktuális sor számát adja vissza

A FetchField függvény teszteli le a mezők típusát. 3 elemű objektumot ad vissza.

  • : oszlop neve
  • : az eredeti mezőtípus
  • : a mező max. hossza. Néha nem ad vissza semmit (pl. MySQL)

A MetaType() lefordítja az eredeti típust általános ADODB típusra. Az alábbi generic típusok léteznek:

  • C: szöveg, a <input type="text"> taggal jeleníthető meg
  • X: TeXt, nagy szöveg, a <textarea> taggal jeleníthető meg
  • B: Blobs, Binary Large Objects. Általában képek.
  • D: Dátum
  • T: Időbélyeg
  • L: Logikai (boolean vagy bit mező)
  • I:  Egész
  • N: Numerikus, illetve autoinkrementális.
  • R: Sorozat típus. Lehet sorozat vagy, autoinkrement egész.

Dátum vagy Időbélyeg esetén a UserDate() függvény konvertálja és kiírja a megfelelő formátumú időt.

További absztrakció

Az adatbázis-kezelés további egyszerűsítéseket igényelhet, különösen oktatási környezetben vagy egyszerűbb alkalmazások esetén. Valójában a legtöbb adatbázis művelet visszavezethető az alábbi adatbázis feladatokra:

  1. Egy összetett lekérdezés, aminek az eredménye két dimenziós tömb
  2. Új sor beszúrása egy meglévő táblába
  3. Egy sor adatainak a módosítása egy táblában
  4. Egy sor adatainak a törlése egy táblában
  5. Egyéb SQL utasítások kiadása

Az 1. feladatkörnek a jellemző tulajdonsága az, hogy két dimenziós tömb az eredménye.

A 2., 3., 4. utasítások eredménye egy szám. 0, ha nem sikerült, 1 vagy több, ha sikerült és a szám azt jelzi, hogy hány sorra hajtódott végre az utasítás.

A fenti feladatokat 2 függvénnyel meg lehet oldani. Az alábbiakra látunk egy példát azzal a kiegészítéssel, hogy az 1-es feladatnak a speciális esete az, hogy csak egy sort kapunk vissza eredményül, továbbá definiáltunk két példát arra, hogy hogyan kell egy tablet és egy tábla egy rekordját megjeleníteni.

<?php
/***************************
* ADODB interface és függvények
* Copyright by Fabian Zoltan
****************************/
require_once("adodb/adodb.inc.php");   //ADODB driver betöltése
     
  $ADO_CON =&ADONewConnection('access');
  $dsn = "Driver={Microsoft Access Driver(*.mdb)};Dbq=D:/wwwroot/_tanitas/_lib/2002c.mdb;Uid=Admin;Pwd=;";
    $ADO_CON->PConnect($dsn);      //Adatbázishoz kapcsolódás
    $ADO_CON->debug = false;          //Az adatbázis lekérdezések debuggolása kikapcsolva
     
/**
 * @return Recordset
 * @param string $qrystr
 * @desc INSERT, UPDATE, DELETE kiadásakor érdemes használni
*/
function OpenRs(&$con,$qrystr){
    $rs = $con->Execute($qrystr);
    return $rs;
}
     
/**
 * @return unknown
 * @param ADOConnection $con
 * @param string $sql
 * @desc Egy lekérdezés teljes 2 dimenziós eredménytömbjét adja vissza
*/
Function ExecuteRSGetAll(&$con,$sql){
    $con->SetFetchMode(ADODB_FETCH_BOTH);
     
    $rs = $con->Execute($sql);
    $rsall = $rs->GetAll();
    if(!isset($rsall)){
          $rsall = array();
    }
    return $rsall;
}
    
/**
 * @return Megadott mezőt vagy a teljes sort adja vissza
 * @param Lekérdezés szövege $qry
 * @param keresett mező $sRet
 * @desc Végrehajt egy lekérdezést és egy sort vagy a sor egy oszlopát adja vissza.
*/
function ExecuteRSRow(&$con,$sql,$sRet=""){
    $row = array();
    $con->SetFetchMode(ADODB_FETCH_BOTH);
    $rs  = $con->Execute($sql);
    $row = $rs->FetchRow();
    $rs->close();
    if(empty($sRet)) return $row;
    else            return $row[$sRet];
}
/*
* Egy tömböt megjelenít táblázatos formában
*  @param $2D tömb
*/
Function 2dtable($t){
    $n = count($t);                     //Sorok száma
    //
    print("<Table border='1'>\n");      //Tábla nyitó tag
    if($n>0){
          print("<tr>\n");                    //egy sor nyitó html tag
          foreach($t[0] AS $j => $r){
               print("<td>".$j."</td>\n");    //Egy cella kiiratása
          }
          print("</tr>\n");              //Egy sor záró tag
    }
     
    for($i = 0; $i<$n;$i++){            //ciklus a sorokon végig
          $row = $t[$i];
          print("<tr>\n");                    //egy sor nyitó html tag
          foreach($row AS $j => $r){
               print("<td>".$r."</td>\n");    //Egy cella kiiratása
          }
          print("</tr>\n");              //Egy sor záró tag
    }
    print("</table>\n");                //tábla záró tag
}
/**
* Egy rekord adatai oszlopos formában, űrlap formájában
* egy sor
* @paraméter 1 rekord tömb alakban
*/
Function egyrekord($r, $target=””, $method=”POST”, $submit=”Elküld”){
 print(”<FROM method=’”.$method.”’ Name=’”.$method.”’ ”.isset($target?$”ACTION=’”.$target.”’”:””).”>”);
 print(”<TABLE>”);
 foreach($r AS $i => $e){
   print(”<TR>”);
   print(”<td>”.$i.”</TD>”);
   print(”<td><input type=’text’ name=’”.$i.”’ value=’”.$i.”’></TD>”);
   print(”</TR>”);
 }
 print(”</TABLE>”);
}
?>  

Idő kezelése PHP-ben és MySQL-ben

Az időpontok kezelése a Unix hagyományokra épül. A Unix „időszámítás” kezdete 1970. január.01, 0 óra 0 perc. Az időt a Unix rendszerek innen számított másodpercekben mérik és tárolják. Ennek megfelelően a PHP és a MySQL is innen indul ki. A UNIX idő elnevezése TIMESTAMP. Az így kapott időpont azonban egy nagy egész szám, amit az adatok bevitelénél és kiíratásánál nemigen lehet használni, ezért léteznek konverziós függvények ilyen célokra.

A PHP esetén az aktuális időpontot a

string Date(formátumstring [,timestamp]) függvény állítja elő

A fromátumstringben az alábbi megjegyzések lehetnek:

Év – y két karakter (01), Y 4 karakter hosszú (2001)

Hónap - m – két karakter, sorszám 01, 02,…, M – a hónap angol neve (January, February, …)

Nap – d –két karakter, a hónapban a nap sorszáma, D – elnevezése angolul (Monday, Thuesday, …)

Óra – h – 12 órás ciklus 00-12, H – 24 órás ciklus két karakter 00 - 23

Perc – i – kt karakter 00-59

Az eredmény tehát string!

Az időpont átalakítása TIMESTAMP formátumra az alábbi függvénnyel lehetséges:

int mktime (int óra, int perc, int másodperc, int hónap, int nap, int év [, int is_dst])

Amennyiben nem akarunk kitölteni bizonyos értékeket, akkor 0-val kell feltölteni őket. Az utolsó paraméter a nappal értékét tartalmazza, általában elhagyható, sőt gyakorlatilag elhagyandó.

A MySQL esetén az időpont tárolása és formátuma hasonlóan kettős. Lehet tárolni időpontot TIMESTAMP formátumban. Ekkor a UNIX timestamp lesz a belső tárolási formátum, de a megjelenítésnél már karaktersorozatot kapunk vissza. Ahhoz, hogy a PHP-nek megfelelő Timestamp legyen egy lekérdezés eredménye, a MySQL TIMESTAMP() függvényét kell használni. Az alábbiakban egy ilyen megoldásra látunk példát.

Function koncertlista($datum)
{
    // A lekérdezés összeállítása
    $qry ="SELECT UNIX_TIMESTAMP(Mikor) AS Ido,Mi,Hol FROM esemeny WHERE Mikor > '".$datum."'";
    $result = @mysql_query($qry, $fc);
    $str ="<UL>";
    while ($a = mysql_fetch_object($result)) {
          $str .= "<LI>".date("Y.M.d, D H:i",$a->Ido).", ".$a->Mi."<BR>";
          $str .= $a->Hol."<BR>Belépő:".$a->Belepo." Ft</LI>";
    }
    $str .="</UL>";
echo $str;
return;
}
     
// Itt hívom meg az aktuális dátummal a függvényt. A formátum azért fontos, mert csak így 
// hasonlítható össze a MySQL Timestamp-jével.
koncertlista(date("YmdHis"));
     
Az eredmény az alábbi lesz:
2002.Jan.12, Sat 09:12, Koncert
    Uj várklub
    Belépõ:500 Ft

Az alkalmazott adattípus az SQL táblában a TIMESTAMP(14)

Sokáig futó programok

A PHP scriptek futásának a maximális ideje korlátozva van a PHP-ben, mert ha egy végtelen ciklust ír az ember, akkor lefagyaszthatja a szervert. A korlát default értéke 30 sec. Ha egy scriptünk mégis tovább futna, mert valamilyen oknál fogva a feldolgozás lassú, vagy esetleg egy adatbázisszervertől vár adatokat, akkor célszerű használnunk az alábbi függvényt a PHP oldalunk elején, mivel ez a kívánt ideig engedi futni a scriptet

Set_time_limit( másodperc)

Célszerűen csak akkora értéket írjunk be, hogy a script biztonságosan lefusson, ugyanakkor idejében ki is lépjen, ha kell. Ha időnek 0-t írunk, akkor soha nem jár le a script, és ha végtelen ciklust teszünk bele, akkor örökhajtós lesz a scriptünk.