ugrás a tartalomhoz

DAO - Adatbáziskezelés picit másképp

Hodicska Gergely · 2004. Dec. 22. (Sze), 06.06
DAO - Adatbáziskezelés picit másképp
Ezúttal egy az adatbáziskezeléssel kapcsolatos témáról lesz szó. A cikk első felében egy módszert mutatok be, melynek alkalmazásával jól elkülöníthetőek az adatok mentésével, módosításával kapcsolatos kódok programunk egyéb részétől. A második részben arról lesz szó, hogy hogyan tudjuk az adatbáziskezelés során fellépő hibákat minél egyszerűbben kezelni. Már itt jelezni szeretném, hogy ennek a résznek a második fele némiképp egy gondolatkísérlet, vitaindítónak szánom. Ezt a megközelítést még nem nagyon láttam máshol leírva (lehet nem véletlenül), így kíváncsian várom majd a véleményeket vele kapcsolatban.

Kapcsolódás

Mielőtt bármit is tennénk, az első lépés, hogy kapcsolódunk az adatbázishoz. Itt jegyezném meg, hogy a példákban az ADODB adatbáziskezelő réteget fogjuk használni. Általában célszerű, ha egy ilyen jellegű eszközt használunk. A fő érv emellett többnyire, hogy jól jöhet, ha szeretnénk alkalmazásunk alatt lecserélni az adatbáziskezelőt (DBMS). Erre azért viszonylag ritkán van szükség, számomra sokkal fontosabb, hogy a különböző típusú adatbázisokat egyazon API-n keresztül érhetem el. Ráadásul az ADODB nagyon sok hasznos funkcióval rendelkezik, mindenkinek ajánlom, hogy legalább egyszer nézze meg, próbálja ki. De aki nem szereti, más a kedvence, az se riadjon el, mert a bemutatott elvek bármilyen réteg vagy akár a natív függvények használata esetén is alkalmazhatóak.

A kapcsolódásnál tartottunk. Én erre a következő kis osztályt ajánlom.
<?php
    class AdoDBFactory {
        function getConnectionParam($name)
        {
            switch ($name) {
                case 'USER' :
                case 'SESSION' :
                case 'DEFAULT' :
                    return array(
                        'type'  => DEFAULT_DB_TYPE,
                        'host'  => DEFAULT_DB_HOST,
                        'user'  => DEFAULT_DB_USER,
                        'pass'  => DEFAULT_DB_PASS,
                        'db'    => DEFAULT_DB_DB,
                        'debug' => DEFAULT_DB_DEBUG
                    );
                case 'IMPORT' :
                    return array(…);
                case 'TEST' :
                    return array(…);
                default:
                    trigger_error('AdoDBFactory -> Undefinied connection:'.$name.' !', E_USER_ERROR);
            }
        }

        function &getConnection($name = 'DEFAULT')
        {
            static $connections;
            if (!isset($connections)) {
                $connections = array();

                include_once(DIR_ADODB.'adodb.inc.php');
                $GLOBAL[‘ADODB_FETCH_MODE’] = ADODB_FETCH_ASSOC;
            }

            $param = AdoDBFactory::getConnectionParam(strtoupper($name));
            $id = md5($param['type'].$param['user'].$param['pass'].$param['db']);
            if (!array_key_exists($id, $connections)) {
                $connections[$id] =& ADONewConnection($param['type']);
                $connections[$id]->Connect($param['host'], $param['user'], $param['pass'], $param['db']);
                $connections[$id]->debug = $param['debug'];
            }

            return $connections[$id];
        }
    }
?>
Mielőtt magyarázatra kerülne sor, lássuk, hogy hogyan is használhatjuk.
<?php
    include_once('AdoDBFactory.php');
    
    $conn =& AdoDBFactory::getConnection();
    $rs =& $conn->Execute('...');
?>
Bárhol, ahol szükségünk van adatbáziskezelésre, ott a fenti módon csatlakozhatunk. A módszer előnyei:
  • Az adatbáziskezelő kódok csak akkor kerülnek betöltésre, amikor ténylegesen szükség van rájuk. Például ha a kérésben szereplő adatok elbuknak az ellenőrzésen, akkor felesleges időveszteség lenne ezen kódok parse-olása.
  • A konkrét kapcsolati paraméterek megadása helyett bejön egy plusz réteg. Alkalmazásunk minden egyes modulja például a saját nevén kérheti el a kapcsolatot. Ezután ha valamiért mégis másik adatbáziskezelőre volna szüksége, akkor ezen az egy központi helyen beállítom, hogy például a "SESSION" kapcsolat esetén nem az alapértelmezett, hanem egyéb paraméterekkel történjen a kapcsolódás.
  • Nincs szükség egy globális változóban tárolni a kapcsolatot, így programunk szervezése is szebb lesz. A tényleges kapcsolódás az első kérés esetén történik, a kapcsolatot megtestesítő ADODB objektumot egy statikus tömbben tároljuk, majd ezt követően, ha ugyanezen paraméterekkel rendelkező kapcsolatot kérünk, akkor ennek referenciáját kapjuk vissza. Így a többlet munka elenyésző, a cserébe kapott rugalmasság sokkal többet ér.

Függetlenség az adatbáziskezelőtől

Fentebb említettük, hogy az adatbáziskezelő rétegek használatának egyik oka, hogy szeretnénk alkalmazásunkat függetlenné tenni az éppen alkalmazott adatbázistól. Ez azonban csak az első lépés. Ha alkalmazásunkban mindig közvetlenül adunk ki SQL utasításokat a szükséges helyeken, akkor ezek a kódok szanaszét átszövik programjainkat. Az egyes SQL nyelvezetekben azonban lehetnek eltérések, illetve egyéb funkcionális különbségek (pl. Oracle esetén limit), ha váltani szeretnénk, akkor elkerülhetetlen, utasításainkon apró módosításokat kelljen eszközölnünk. Viszont a fenti kódszervezés esetén óriási munka lehet felkutatni az összes SQL utasítást, és ellenőrizni azok megfelelőségét. Ennek elkerülése érdekében érdemes az adatbázis kezeléssel kapcsolatos kódokat elválasztani az alkalmazás többi részétől. A következő részben arról lesz szó, hogy ezt hogyan érdemes megtenni.

Data Access Object - DAO

Az adatbáziskezelő kódokat logikailag összetartozó csoportokba válogatjuk, minden egyes ilyen csoport egy úgynevezett DAO osztályt fog alkotni. Például az article táblánkhoz tartozik egy ArticleDao osztály, melyen keresztül tudunk cikkeket hozzáadni, módosítani, törölni az adatbázisban. Nem véletlenül logikai csoportosításról van szó, nem fog feltétlenül minden egyes táblához egy ilyen osztály tartozni. Ha például van egy táblánk, melyben tároljuk egy cikk esetén a hozzá kapcsolódó cikkeket, akkor ennek kezelését szintén az ArticleDao osztályunk fogja elvégezni. Még lesz szó arról, hogy mit is érdemes egy ilyen DAO osztályba betenni, de előbb lássunk egy egyszerű infrastruktúrát ezen osztályok kényelmes használatára.

Először is legyen egy alap DAO osztályunk, melyből származtathatjuk a többi ilyen jellegű osztályt. Ez kezdetben nagyon egyszerű lesz: konstruktor, illetve az ADODB hibaüzeneteinek "kivezetése".
<?php
    include_once('AdoDBFactory.php');

    class Dao
    {
        var $db = null;
        var $query = null;

        function Dao($connectionName = 'DEFAULT')
        {
            $this->db =& AdoDBFactory::getConnection($connectionName);
        }

        function getErrorNo()
        {
            return $this->db->ErrorNo();
        }

        function getErrorMsg()
        {
            return $this->db->ErrorMsg();
        }

        function getError()
        {
            return 'SQL error (code: '.$this->db->ErrorNo().'): '.$this->db->ErrorMsg();
        }
    }
?> 
Egy példa egy konkrét DAO objektumra.
<?php
    include_once('Dao.php');

    class UserDao {
        function add($row)
        {
            $id = $this->db->GenID('seq_user');
            $rs = $this->db->Execute('
                INSERT INTO
                    user
                    ( user_id
                      name,
                      email,
                      login,
                      password,
                      created )
                VALUES
                    ( '.$id.',
                      '.$this->db->qstr($row['name']).',
                      '.$this->db->qstr($row['email']).',
                      '.$this->db->qstr($row['login']).',
                      '.$this->db->qstr(md5($row['password'])).',
                      now())
            ');
            if ($rs === false) {
                return false;
            }
            return true;
        }
    }
?>
A kényelmes használathoz még meg kell oldjuk, hogy bármikor könnyen hozzáférjünk ezekhez az objektumokhoz. Ehhez ismét a factory tervezési mintát hívjuk segítségül.
<?php
    class DaoFactory {
        function &singleton($name, $connectionName = 'DEFAULT')
        {
            static $obj;
            if (!isset($obj)) {
                $obj = array();
            }

            if (!isset($obj[$name])) {
                if (is_readable(DIR_INCLUDE_DAO.$name.'Dao.php')) {
                    include_once(DIR_INCLUDE_DAO.$name.'Dao.php');
                    $obj[$name] = $name.'Dao';
                    // Nem szép, de megúszunk egy plusz változót ;)
                    $obj[$name] = new $obj[$name]($connectionName);
                } else {
                    trigger_error('DaoFactory -> The '.DIR_INCLUDE_DAO.$name.'Dao.php'.' file does not exist or is not readable!', E_USER_ERROR);
                }
            }

            return $obj[$name];
        }
    }
?> 
A fenti kód azt feltételezi, hogy a DAO osztályainkat a DIR_INCLUDE_DAO konstans által meghatározott könyvtárban tartjuk, illetve, hogy a "User" DAO-t a UserDao.php fájlban definiált UserDao nevű osztály valósítja meg. Ha mindezzel megvagyunk, akkor a használat már pofon egyszerű.
<?php
    include_once('DaoFactory.php');

    if (true === ($row = validateInput())) {
        $dao =& DaoFactory::singleton('User');
        $dao->add($row);
    }
?>
Már csak szemre is tetszetős, nem? ;) Ez is egy nagy előny, hogy az adatbázis kezelő kódok kivonásával programunk olvashatósága is megnő, jobban követhető annak logikája. Ezenkívül egy plusz előny, hogy DAO osztályaink felépítése, alap funkcionalitása (add(), delete(), update(), get(), getList()) hasonló lesz, így egy már kész osztályból gyorsan létre tudjunk hozni egy újat viszonylag kevés módosítással. Illetve itt jegyezném meg, hogy lehetőségünk van ezen DAO objektumok adatbázis séma alapján történő automatikus generálására (PEAR::DB_DataObject, Propel, Metastorage, DaoGen), de ez nem témája jelen cikkünknek.

Felelősség

Fontos, hogy kellő figyelmet fordítsunk arra, hogy DAO objektumainkban csak olyan funkciókat valósítsunk meg, melyek ténylegesen az adatbázis eléréshez kötődnek. Könnyen kísértésbe eshetünk, hogy olyan funkciókat is ide tegyünk, melyek az üzleti logikához kötődnek. Például tegyük fel, hogy van egy olyan függvényünk mely visszaadja az adott tábla rekordjainak azonosító és név mezőjét. Erre például egy select lista generálásakor lehet szükségünk. Egyből adódhat az ötlet, hogy a függvény már egyből az option listát adja vissza, így könnyebb használni is. Ez azonban nem túl szerencsés (tervezési hiba), a függvény adja csak vissza egy tömbben a szükséges adatokat, és legyen egy külön függvényünk/osztályunk, mely ezen tömb alapján legenerálja a select listát.

Előfordulhat azonban olyan szituáció is, amikor egy DAO osztály nem csak az adatbázissal foglalkozik. Ilyen helyzet lehet például, ha az adatbázisban tárolt adatokhoz fájlrendszerben tárolt állományok is kapcsolódnak. Ilyenkor ezek kezelése a DAO objektum felelőssége, fontos, hogy a fájlok és az eltárolt információk konzisztensek maradjanak.

Hibakezelés 1

Mint minden más esetben a programozás során fontos a hibák ellenőrzése, megfelelő kezelése. Hiába ellenőrizzük bejövő adatainkat alaposan, adódhat olyan helyzet, amikor az adatok szintaktikailag jónak tűnnek, de a belőlük összeállított SQL utasításunk hibát fog generálni, mert valamilyen megszorítás (egyediség, hivatkozási épség, stb.) nem teljesül. A hibakezelésre egy lehetséges megoldás lehet az alábbi, de ehhez szükséges, hogy az általunk használt adatbázis kezelő megfelelő hibaüzenetet adjon vissza. Ez például MySQL esetén nem teljesül, de sajnos nem ez a legnagyobb hibája, ami miatt nem érdemes használni.
<?php
    include_once('Dao.php');

    define('USERDAO_LOGIN_ALREADY_EXISTS', -1);
    define('USERDAO_EMAIL_ALREADY_EXISTS', -2);

    class UserDao {
        function add($row)
        {
            $id = $this->db->GenID('seq_user');
            $rs = $this->db->Execute('
                INSERT INTO
                    user
                    ( user_id
                      name,
                      email,
                      login,
                      password,
                      created )
                VALUES
                    ( '.$id.',
                      '.$this->db->qstr($row['name']).',
                      '.$this->db->qstr($row['email']).',
                      '.$this->db->qstr($row['login']).',

                      '.$this->db->qstr(md5($row['password'])).',
                      now())
            ');
            if ($rs === false) {
                $error = $this->db->ErrorMsg();
                if (strpos($error, 'users_login_uk') !== false) {
                    return USER_LOGIN_ALREADY_EXISTS;
                }
                if (strpos($error, 'users_email_uk') !== false) {
                    return USER_EMAIL_ALREADY_EXISTS;
                }
                return false;
            }
            return true;
        }
    }
?>
A megoldás lényege, hogy a hibaüzenetben rákeresünk a hiba okára utaló szavakra. Ez így kicsit bizonytalannak tűnhet, de gyakorlatban jól bevált módszer. Persze némi odafigyelést igényel. Cserébe viszont nem kell "kézzel" megnéznünk, hogy a beszúrandó loginnév, email használva van-e már, egyszerűen megpróbáljuk beszúrni az adatokat, és a megfelelő oszlopokon elhelyezett egyediséget biztosító megszorítások elvégzik a piszkos munkát. Ha nem sikerül a hibát felismerni, akkor false-t adunk vissza. Ez általában csak akkor történhet meg, ha valamilyen hiba van a programunkban, vagy a lekérdezés hibás. A lehetséges hibák jelzésére konstansokat hozunk létre, melynek célja szintén a kód olvashatóságának növelése. A hívó fél oldaláról ez így néz ki.
 <?php
    $dao =& DaoFactory::singleton('User');
    $ret = $dao->add($row);
    if ($ret === false || $ret < 0) {
        if ($ret == USER_LOGIN_ALREADY_EXISTS) {
            echo 'A felhasználó hozzáadása sikertelen: ez a felhasználónév már foglalt!';
        } elseif ($ret == USER_EMAIL_ALREADY_EXISTS) {
            echo 'A felhasználó hozzáadása sikertelen: ez az email cím már regisztrált!';
        } else {
            echo 'A felhasználó hozzáadása sikertelen: adatbázis hiba!';
        }
    } else {
        echo 'A felhasználó hozzáadása sikeres!';
    }
?>

Hibakezelés 2

Most következik az a rész, melyről a bevezetőben írtam, hogy vitaindítónak szánom. Ez mostanában kristályosodott ki bennem, jól használható dolognak tűnik, de még nem egy agyonhasznált, többféle környezetben kipróbált módszer.

Ha megnézzük, akkor a fent bemutatott módon kezelt hibák egy jó része az alkalmazás elkészültével normális használat mellett nem fordulhat elő. Jöjjön egy példa egyből, ami az előző mondatot érthetőbbe teszi. Például ahogy fent is említettem a return false ágak nem fordulhatnak elő, csak ha konkrét programozási hiba van. Elegendő lenne, ha ezek a hibák jelzésre kerülnének, de nem szükséges a kezelésükre külön figyelmet fordítani, hiszen ezek a hibák hamar kiderülnek (már minimális tesztelés esetén is), a végső rendszerben biztosan nem lesznek jelen. Egy másik ide sorolható hiba például a hivatkozási függőség nem teljesülése. Általában a kapcsolódó dolgokat egy select listából választva tud a felhasználó egy másik dologhoz rendelni. Ha én ezt jól generálom, és a felhasználó ebből választ, akkor nem lehet gond. Ha felteszem, hogy jól generálom, illetve ellenőrzöm, hogy választott-e, akkor csak egy esetben adódhat ebből a szempontból hibás lekérdezés, ha a felhasználó megpróbál a felületet megkerülve adatokat eljuttatni a rendszerhez.

Szóval a lényeg, hogy az alkalmazásban olyan hiba ágakat kezelek le, amelyekbe normál működés mellett nem kerülhet az alkalmazás. Erre találtam ki, hogy a hiba okának kiderítése helyett ezekben az esetekben a trigger_error függvény segítségével PHP errort generálok, úgy, hogy a hibaüzenet egy speciális karaktersorozattal kezdődik, így a saját hibakezelő függvényem ezeket meg tudja különböztetni az egyéb hibáktól. Ennek támogatásához némileg módosítsuk az alap DAO osztályunkat:
 <?php
    include_once('AdoDBFactory.php');

    define('DAO_SQLERROR_USER', '_SQLERROR_USER_');
    define('DAO_SQLERROR_DEVELOPER', '_SQLERROR_DEV_');

    class Dao
    {
        var $db = null;
        var $query = null;

        function Dao($connectionName = 'DEFAULT')
        {
            $this->db =& AdoDBFactory::getConnection($connectionName);
        }

        function getErrorNo()
        {
            return $this->db->ErrorNo();
        }

        function getErrorMsg()
        {
            return $this->db->ErrorMsg();
        }

        function getError()
        {
            return 'SQL error (code: '.$this->db->ErrorNo().'): '.$this->db->ErrorMsg();
        }

        function getDevError($str = null)
        {
            if(!empty($str))
            {
                $str = ' {HINT}'.$str.'{/HINT}';
            }

            return DAO_SQLERROR_DEVELOPER.$str.$this->getError().' {QUERY}'.$this->query.'{/QUERY}';
        }

        function getUserError($str)
        {
            if(!empty($str))
            {
                $str = ' {HINT}'.$str.'{/HINT}';
            }
            return $msg = DAO_SQLERROR_USER.$str.$this->getError().' {QUERY}'.$this->query.'{/QUERY}';
        }
    }
?>
És a magyarázat előtt jöjjön egyből egy példa is a használatára.
 <?php
    include_once('Dao.php');

    class DocumentDao extends Dao {
        function update($id, $row)
        {
            $this->query = '
                UPDATE
                    document
                SET
                    file_id = '.$this->db->qstr($row['FILE_ID']).',
                    keywords = '.$this->db->qstr($row['KEYWORDS']).',
                    document_cat_id = '.$this->db->qstr($row['DOCUMENT_CAT_ID']).',
                    date_activate = TO_DATE('.$this->db->qstr($row['DATE_ACTIVATE']).', \''.DATE_FORMAT_ORACLE.'\'),
                    date_modified = SYSDATE,
                    modifier_id = '.$this->db->qstr($row['MODIFIER_ID']).'
                WHERE
                    document_id = '.$id.'
                    AND is_deleted = 0
            ';

            $rs =& $this->db->Execute($this->query);
            if ($rs)
                if($this->db->Affected_Rows() == 1) {
                    return true;
                } else {
                    trigger_error($this->getUserError('The given id was wrong!'), E_USER_ERROR);
                }
            }

            trigger_error($this->getDevError(), E_USER_ERROR);
        }
    }
?>
Mint látható kétféle "extra" hibát különböztetek meg. Az egyik kategória a biztosan fejlesztői hibából származó hiba. A neki megfelelő prefix-szel ellátott hibaüzenetet a getDevError() függvény adja vissza. Például minden DAO metódus végén dobok egy ilyen üzenetet. Ide viszont csak akkor kerülhet a program futása, ha a lekérdezés hibás volt. Fejlesztési időben ez nagyon hasznos, egyből, erőteljesen látom, ha rosszul van a lekérdezés összeállítva. A másik kategória az, amikor csak akkor lehet hiba, ha "hackelés" történik. Normál működés esetén nem fordulhat elő, hogy a módosításkor egy nem létező azonosítót kapok vissza.

Ezután már csak annyit kell tennem, hogy a hibakezelő függvényemben figyelem a hibaüzeneteket, és ha az említett prefixek egyike megtalálható benne, akkor a hiba típusától függően reagálok azokra. Például "fejlesztői hiba" eseten mehet egy egységes képernyő a felhasználónak, melyben étesítem, hogy a szolgáltatás jelenleg nem elérhető, és persze megy a levél a fejlesztőnek, hogy valami nem kerek, "felhasználói hiba" esetén pedig szintén lehet egy egységes képernyő, melyben finoman közöljük, hogy a felhasználó nem rendeltetés szerűen használta az oldalt (plusz szintén levél a fejlesztőnek). Ha valaki saját hibakezelőt szeretne írni, előtte mindenféleképpen ajánlom, hogy nézze meg a Papp "Chemotox" Győző által készített Errorhandlert.

Ez a módszer egész jól működni látszik, tranzakciókezelés során sincs gond, ugyanis ilyenkor a script leállása automatikusan Rollbacket von maga után. Illetve a MySQL-lel már megint baj van, ugyanis pconnect() használata esetén ez nem történik meg, beragad a tranzakció, és az esetleges lockolások is, melyek csak az adatbázis újraindításával hozhatók rendbe. Ha mégis pconnectre van szükség, akkor esetleg egy shutdown függvény segítségével orvosolható a probléma, ugyanis ezek a függvények hiba esetén is lefutnak a script leállása előtt, így ebben mondhatunk egy Rollbacket ha épp tranzakcióban voltunk.

Nagyjából ennyit szerettem volna mára, kíváncsian várom a véleményeteket.
 
1

Szerintem szebb megoldas lenn

pp · 2004. Dec. 22. (Sze), 08.25
Szerintem szebb megoldas lenne egy $_ERROR valtozohoz hozzarendelni egy olyan (sajat) error objektumot, ami minden fontos adatot tartalmaz. Ezt azutan a hibakezelodben is felhasznalhatod, es a fuggveny visszateresi ertekenek is megadhatod, mivel nem mindig szukseges/lehetseges error-t nyomni. ([aki nem tudna]lehet notice-t vagy warning-ot is annak ellenere, hogy a fuggveny neve trigger_error;), ilyenkor a program futasa nem szakad felbe[/aki nem tudna])

A dev-user error szetvalasztasa ilyen modon szerintem ertelmetlen (->felesleges, rossz, stb.;)) ket okbol is.
1. a peldanal maradva mi van akkor, ha egy masik juzer (vagy ugyan ez csak ket bongeszoablakot futtat) eppen a szerkesztett documentumot torli? A program egy user error-t dob, holott ez egy fejlesztoi tervezesi hiba, hisz ezt nem szabadna megengedni. (es most nem a kodot fikazom, hanem a hiba lehetosegere vilagitok ra, hisz nincs hibatlan program.)
2. Nem biztos, hogy egyedul fejleszted a programot, ilyenkor a legtobb "user" hiba egy masik "developer"-tol ered.

pp
2

Szia! [i]mivel nem mindig

Hodicska Gergely · 2004. Dec. 22. (Sze), 10.10
Szia!

mivel nem mindig szukseges/lehetseges error-t nyomni.
Jelen esetben ezek olyan szintu hibak, amire illik E_USER_ERROR-t dobni.

Konkurencia
Tudtam, hogy vegen kifelejtek valamit. Errol direkt szerettem volna irni, hogy azokban az esetekben, ahol fenall a konkurens hasznalat lehetosege, ott erre kulon figyelni kell. En minden esetben figyelek erre mar a db kialakitasa soran is. Igaz, hogy ilyen esetben keves helyen johet jol ez a megvalositas.

Nem biztos, hogy egyedul fejleszted a programot, ilyenkor a legtobb "user" hiba egy masik "developer"-tol ered.
Azt hittem, hogy az egyertelmu lesz, hogy a megkulonboztetesnek csak az eles rendszeren van jelentosege. Fejlesztes alatt ugyanaz lehet a ket hiba kezelese. Nekem amugy az tetszik, hogy eliras, vagy hasonlo huncutsagok eseten eleg radikalisan latszik, hogy hiba van.

Felho
4

Konkurrencia

gerzson · 2004. Dec. 22. (Sze), 11.31
... ahol fenall a konkurens hasznalat lehetosege, ott erre kulon figyelni kell.

Erre jó(?) megoldás lehet, ha a leküldött oldalban elhelyezünk egy ún. verziószámot. Az adatok visszaküldésével ez is visszakerül a szerverre, ami összeveti a módosítandó DB rekord aktuális verziószámát a visszaküldött értékkel, és ha ez annál régebbi, megtagadja a módosítást. Ezzel elkerülhető, hogy ketten ugyanazt a rekordot ugyanabban az állapotában kétszer módosítsák. (Erről beszéltünk is Felhővel, de valahogy mégis kimaradt.)
Ezt a módszert egészen odáig lehet fejleszteni, hogy csak azokat műveleteket tagadja meg a program, amelyek tényleg fel nem oldható konfliktusban állnak egymással, tehát pl. két különböző mező (NEV és CIM) megváltoztatása ugyanazon a verziószámú rekordon, nem ütközik egymással, így mindegyik egymás után végrehajtható. A rekord törlése természetesen mindig konfliktust eredményez.

Ez elméleti szinten megegyezik a verziókezelők (CVS, SVN) által kiosztott verziószámok figyelésével, mert mindkét esetben az aszinkron kliens-szerver modellből fakadó adatintegritási (adatelévülés, stb.) problémáról van szó. Biztonsági megfontolások miatt ennek kódolt formában kell "utazni", hogy ne lehessen könnyen átejteni a programunkat.
3

trigger_error

gerzson · 2004. Dec. 22. (Sze), 11.03
Ha már megszólítattam ebben a cikkben, ehhez a mondathoz fűznék némi kommentárt:

([aki nem tudna]lehet notice-t vagy warning-ot is annak ellenere, hogy a fuggveny neve trigger_error;), ilyenkor a program futasa nem szakad felbe[/aki nem tudna])

A kezdők számára nagyon fontos hangsúlyozni, hogy a trigger_error() csak hibát generál, és ennek a hibának a kezelése másra tartozik!
Legutolsó ismereteim szerint, ha saját hibakezelő fv-t írunk, akkor teljesen ránk van bízva, hogy mikor álljon le az alkalmazásunk -- függetlenül a hiba szintjétől. (Ad absurdum úgy is meg lehet írni, hogy minden 10-re álljon csak le.) Amire PP hivatkozott, az természetesen csak a beépített hibakezelőre vonatkozik.

A hibakezelő fv-k lehetőségeiről várhatóan a PHP Chemotox IV. részében lesz szó (ez egy másik weblaboros cikk).
5

dev-user szétválasztás

gerzson · 2004. Dec. 22. (Sze), 12.13
... szebb megoldas lenne egy $_ERROR valtozohoz hozzarendelni egy ... error objektumot ...
A dev-user error szetvalasztasa ilyen modon szerintem ertelmetlen ...

Többnyire én is hajlok erre a véleményre, de a cikk alapvető ötlete az, hogy hogyan is szabaduljunk meg sok if-ezéstől. A hibaobjektumok bevezetésével azonban a megoldás vékonyságát veszítenénk el, mert ezzel a PHP 5 kivéltelkezelését imitálnánk/szimulálnánk: feladunk minden információt a hibáról, h. majd a felső szint eldöntse, mit kezd vele. Valami ilyesmi történik a PEAR-ben is, nem? (Más kérdés, hogy a pattern-ek alkalmazásakor van-e értelme 'light-weight' megoldásról beszélnem.)

A fejlesztői hibákat talán jobb lenne assert() segítségével jobban elkülöníteni/kiemelni, amivel azt is nyernénk, hogy a fejlesztői ellenőrzéseket ki/be lehetne kapcsolni anélkül, hogy sok más hasznos funkcióról le kellene mondani. ASSERT_WARNING opcióval hibát is generálhatunk az assert-ekből, ami így végső soron a hibakezelőhöz jut. Ezt a megoldást azonban több szempontból is mérlegelni kell.
Valószínűleg nem lett volna szerencsés egy újabb koncepció belekeverésével elvonni a figyelmet a lényegről. Ha a szkript sikeressége "elfér egy biten" (goal - no goal), és többre nem vagyunk kiváncsiak, akkor ez a megközelítés rendkívül kényelmes. A szkriptben szinte nincs is hibakezelés, de legalább egy központi helyen megtörténik ez, ami messze jobb, mint ha egyáltalán nem írnánk hibakezelést a teljes alkalmazásba.
6

$GLOBAL helyett $GLOBALS

Anonymous · 2005. Jan. 3. (H), 21.30
Csak egy apró hiba(AdoDBFactory::getConnection()-ban), de gondoltam szólok :)
A cikk nagyon tetszik, remélem mihamarabb lesz folytatása.

üdv,
kalmi
7

class UserDao extends Dao

Anonymous · 2005. Jan. 4. (K), 18.51
mintha lemaradt volna az extends!
Egyébként nagyon tetszik a cikk. Még sok ilyet!
Üdv,
Bongyi
8

Tudom, nagyon régi a cikk,

H.Z. v2 · 2011. Júl. 12. (K), 12.31
Tudom, nagyon régi a cikk, de... Ugye nem tévedek nagyot, ha úgy gondolom, hogy az itt DAO-ként említett technika manapság ActiveRecord néven forog a köztudatban?