"PHP Database Bridge" - (pdb)

Hei,

Jeg har hatt en liten kodesnutt som jeg har pusset på over en lengre periode. Nå er den ikke liten lengre, men begynner å bli ganske stor (uten at det er et problem). Jeg tror uansett det kanskje kan være andre som ønsker å bruke denne, jeg velger defor å legge ut litt info her, så jeg kan få litt tilbakemeldinger først...

Hva er pdb (Php Database Bridge), vel det er et lag mellom dine klasser og en database. Vel, det har du sikkert hørt om før, så hva er det som gjør denne noe enklere/bedre å bruke?

Jeg mener denne er genial til det meste, men dog ikke til alt.. :)

Hovedpunktene:
- Du trenger ikke skrive noe SQL
- Den kan brukes både mot sqlite samt mysql (kan enkelt lage støtte for andre databaser)
- Bruker objekter for å søke etter andre objekter
- Automatisk ta seg av oppdateringer av dato/timestamper
- Lett oppdatere databasen

Når skal du ikke bruke pdb:
- Når du skal hente ut gigantiske rekords ( > 10000)

pdb bruker en XML fil for å vite hvordan dataene skal "mappes" mellom databasen og dine klasser. Denne XML filen kan lages manuelt, eller du kan generere den automatisk, bassert på kommetar felter i dine klasser. (Se eksempel under)

EDIT 12. Sep:
Har startet jobben med å skrive en litt mer detaljert dokumentasjon her:
http://trac.europlan.no/trac/wiki/GettingStarted

TODO:
- Flere kommentarer
- Opprydding i kode

Eksempel:
For å vise hvordan den fungerer, legger jeg med et eksempel her:

<?php
// Vi begynner med å ha en klasse, denne kunne like gjerne vært i en egen fil.

/**
* @pdb.class
* Denne brukes for å fortelle det automatiske scriptet at dette er en pdb klasse
*/
class User {


       
/**
         * @pdb.field type="int" size="12" index="true" autoincrement="true"
         * pdb.field sier at dette feltet skal eksistere i databasen, du angir også hva slags type data det er, samt index og autoincrement
         */    
       
private $id;
       
       
/**
         * @pdb.field type="varchar" size="255" null="false"
         * Samme som over, men har bruker vi varchar som type
         */
       
private $name;

       
/**
         * @pdb.field type="datetime" null="true"
         */
       
private $date;


        public function
__construct($id = null) {
               
$this->id = $id;
        }
               

        public function
getId() {
                return
$this->id;
        }
       
        public function
getName() {
                return
$this->name;
        }

        public function
setName($name) {
               
$this->name = $name;
        }
       
        public function
getDate() {
                return
$this->date;
        }

        public function
setDate($date) {
               
$this->date = $date;
        }

}


/*
Da skal vi generere vår XML configurasjon.
Normalt vil XML configurasjonen legge seg nivået over der du befinner deg og i en config katalog når du generer den. Dette kan overstyres
ved å definere en konstant.
*/

// Vi definerer her at vår konfigurasjons fil, skal ligge i samme katalog som vi befinner oss i.
define("PDB_CONFIG", "pdb.xml");

// Vi inkulderer her pdb
include_once("pdb.class.php");



// Nå skal vi lage konfigurasjons filen
PdbBroker::createConfiguration("mysql://<brukernavn>:<passord>@<server>/<databasenavn>", "./");

/*
createConfiguration() skal ha en URI hvor du levere informasjon om hvilken server, brukernavn, passord og datababase den skal bruke.
Den siste variabelen angir hvor pdb skal søke etter våre klasse filer.
*/



/*
Om du hadde skrive rettigheter til filen, skal nå konfigurasjonsfilen være opprettet.
Vi skal nå generere databasen automatisk.
*/

// Vi henter nå vår pdb broker, ved å bruke getInstance (singelton).
$broker = PdbBroker::getInstance();

// Så kjører vi createDatabase() for å generere databasen.
$broker->createDatabase();



/*
Om databasen ble opprette, skal vi nå forsøke oss med å dytte inn data i databasen.
*/

// Vi oppretter en bruker, samt gir den noe data
$user = new User();
$user->setName("Glenn Larsen");
$user->setDate(date("Y-m-d"));


// Så bruker vi broker til å lagre dette objektet
if ($broker->store($user)) {
    echo
"Objektet ble lagret...";
} else {
    echo
"Objektet ble IKKE lagret...";
}



/*
Vi skal nå hente ut objektet..
*/

// Siden vi har en constructor som gir oss muligheten til å definere en id, bruker vi denne
// til å fortelle vår broker at vi ønsker ett objekt som ligner på dette objektet.
$user1 = $broker->getObjectByObject(new User(1));
var_dump($user1);


// Vi kunne også satt andre verdier, som her hvor vi setter navnet for så å fortelle broker at vi leter etter dette..
$searchUser = new User();
$searchUser->setName("Glenn Larsen");
$user2 = $broker->getObjectByObject($searchUser);
var_dump($user2);


// Evt, kan vi hente alle User objektene..
$users = $broker->getObjectsByObject(new User());
var_dump($users);

/*
Legg merke til at broker støtter både: getObjectByObject samt getObjectsByObject.
*/



/*
Skal du oppdatere en bruker, er det bare å bruker broker til å hente objektet, sette verdiene du ønsker å forandre,
samt bruker store() for å lagre objektet igjen. pdb vil da automatisk bruke update for å oppdatere databasen.
*/


/*
Virker det ikke, kan du prøve å sette:
PdbBroker::$debug = 1;
før du bruker genInstance(), for å printe SQL kommandoene ut på skjermen..
*/
?>

Du kan laste ned pdb.class.php her http://teamsite.no/pdb.class.txt

Feedback...

Kommentarer

Stilig

Ser du bruker PDO i PdbSQLite og mysql i PdbMysql. Vurdert å bruke PDO over hele linja?

Syns det var litt vanskelig å få oversikten over biblioteket når alt lå i en fil. Hva med å sette opp et Subversion repository, dele opp i enkeltfiler og skrive unit tester med PHPUnit?

Ser umiddelbart for meg:

src/
  PdbBroker.php
  PdbClass.php
  PdbClassModifier.php
  PdbAbstractDatabase.php
  PdbDatabaseManager.php
  PdbResult.php
  PdbObject.php
  PdbObjectManager.php
  PdbObjectProperty.php
  PdbObjectScanner.php
  PdbObjectType.php
  PdbAbstractQuery.php
  PdbCriteria.php
  PdbCriterion.php
  PdbSubCriteriaHolder.php
  queries/
    PdbQueryByCriteria.php
    PdbQueryBySQL.php
  databases/
    PdbMysql.php
    PdbSQLite.php
  interfaces/
    PdbDatabase.php
    PdbQuery.php
tests/
  AllTests.php
  PdbBrokerTest.php
  PdbClassTest.php
  PdbObjectManagerTest.php
  etc...
build.xml - automatisering med Phing (doc, lint, test, dist)

Kunne vært en litt mer hyggelig/oversiktlig måte å utvikle pdb videre på. Du kan bruke Phing til å kombinere alle filene i src/ når du lager en release f.eks.

Uansett, stilig løsning :)

Knut Urdalen
urdalen.com

Re: Stilig

Tenkte på PDO hele veien, men ønsket egentlig å bruke drivere som var nærmest MYSQL. Jeg veit ikke om det er noe ekstra mellomlegg i PDO, men ble til at jeg valgte mysql_*.

Når det gjelder oversiktet skjønner jeg frustrasjonen din. MEN det er ikke meningen man skulle ha oversikt på denne enkelt filen. Da jeg bruker pdb til "alle" mine oppgaver, så er det veldig behagelig å ha det i en fil. Spesielt ved nye endringer.

Når jeg utvikler pdb, er dette selvsagt ikke rette formatet å jobbe med. Hele pdb ligger allerede i svn (du kan se det her http://trac.europlan.no/trac/browser/trunk/src). Dessverre bruker jeg ikke PHPUnit, men laget noe eget for å teste kode. Ideen er å bruke PHPUnit en dag.

Phing hadde også vært en ide, denne har jeg ikke rukket å teste enda, så også noe jeg må se på. Laget mitt eget build.php script, for å bygge til en fil.

Mitt hoved problem ved å legge ut kode, tror jeg må være dokumentasjon. Skulle vært litt flinkere med å dokumentere koden med kommentarer, slik at phpdocen ble penere.

Finnes jo mye annen som er ganske kjekt, som ikke er i eksempelet over. F.eks. relasjoner, event (onupdate, onselecet), virtuelle koloner osv osv..

Uansett, takk for tilbakemeldingen..

--
Glenn O Larsen

Er det mulig å få anonym

Er det mulig å få anonym tilgang til Subversion for å sjekke ut kode?

Knut Urdalen
urdalen.com

Utsjekking av kode...

Har åpnet for anonym utsjekking:
svn co svn://europlan.no/pdb

--
Glenn O Larsen

Brukte et par timer på å

Brukte et par timer på å sette opp Phing og PHPUnit for PDB. Jeg portet den første unittesten din over som et eksempel. Se oppdatert README.txt for alle detaljer. Oppsettet bundles med en frisk eksport av PHPUnit 3.2 (ikke sluppet ennå) for å ta i bruk DBUnit funksjonalitet.

Last ned pakka her:
http://www.urdalen.no/php/pdb-with-phing-and-phpunit.tar.gz

PHP Database Bridge (pdb)
=========================

Directory structure
-------------------
o build.xml - Phing build file
o src/ - all PHP source files
o tests/ - all unit tests (using bundled PHPUnit 3.2 and the Database Extension)
o examples/ - example files
o build/ - build directory for automatically generated files (such as documentation, one-file-release and tarball release)

Build targets
-------------
Phing (http://www.phing.info) is used as the build system. The following
targets are used:

$ phing lint - check syntax of all source files
$ phing doc - generate API documentation (will report any errors and put the files into build/doc)
$ phing test - will run the whole test suite
$ phing coverage - generate code coverage report (in build/coverage)
$ phing dist - generate a release file for pdb (merge all classes into one file)
$ phing all - running all targets
$ phing clean - delete the build directory

Ellers så jeg du bruker die() istedet for exceptions. Det er vel litt hardt å bruke die() i bibliotekkode ;) (måtte også gjøre en liten fix med realpath() for å få absolutt path til å fungere). Eksempel på forslag til bruk av Exceptions (inkl. bugfix):

+class PdbObjectManagerException extends Exception {
+
+}
+
/**
  * PdbObjectManager
  *
@@ -28,7 +32,9 @@
        static public function createConfiguration($uri, $path = "./") {

                // Always add the relative path to the users request
-               $path = dirname($_SERVER["SCRIPT_FILENAME"]) . "/" . $path;
+               if(!$path = realpath($path)) {
+                       throw new PdbObjectManagerException("'$path' do not exist");
+               }

Jeg er også tilhenger av å bruke exceptionklassene fra SPL. I dette tilfelle hadde det vel vært mest naturlig med InvalidArgumentException.

Knut Urdalen
urdalen.com

Sjekket inn koden i trunk

Sjekket inn koden i trunk (http://trac.europlan.no/trac/browser/trunk/)

Knut Urdalen
urdalen.com

Dokumentasjon

Har startet jobben med å skrive en litt mer detaljert dokumentasjon her:
http://trac.europlan.no/trac/wiki/GettingStarted

Den er ikke ferdig enda, men er kanskje litt mer forklarende enn teksten over.

--
Glenn O Larsen

Junction PHP

Ser det er noen andre også som holder på med nettopp dette her:

http://junctionphp.com
http://devzone.zend.com/article/2594-Junction----a-new-persistance-layer...

Knut Urdalen
urdalen.com

Propel

Propel er et annet eksempel.

http://propel.phpdb.org/trac/wiki/Users/Introduction

Propel ser mer modent ut enn Junction med støtte for joins og mange-mange relasjoner.

Er det noen som har erfaring med disse? Jeg holder på og ser på hvordan jeg skal løse objekt persistering i applikasjonen min. så langt ser propel mest lovende ut, men ikke testet noe særlig. Trenger en løsning som er meget fleksibel og vil helst ha lav (ingen) coupling mot domenelaget.

Edit: Ser nå at Propel har en håpløs løsning for n-m relasjoner som fører til n+1 SQL spørringer.

Doctrine

Ta en titt på Doctrine som løser det meste. Den er også raskere enn Propel.

Knut Urdalen
urdalen.com

Doctrine

Je, ser ut som den dekker det meste. Men den gir en veldig tight coupling mellom domeneobjektene og datalaget som jeg ikke er så begeistret for.

Edit:
Havnet til slutt på en manuell implementasjon av DataMapper patternet. Litt (ok, mye) mer manuell skriving, men full kontroll og muligheter for optimalisering på mye brukte domeneobjekter. Men med mange DataAccess_DataMapper_x klasser nå så gleder jeg meg virkelig til namespaces i 5.3!!!

Interessant Ketil :) Kanskje

Interessant Ketil :) Kanskje du kunne fortelle litt om dine erfaringer ifm med implementasjonen din? Jeg skrev en blogg-post om dette tidligere i høst "ORM the manual way" som bare beskriver konseptet. Hadde vært interessant å høre litt mer om hvordan andre har gått frem.

Knut Urdalen
urdalen.com