|
Jul
24
|
Achtung:
Dieses Turorial bezieht sich auf eine veraltete Version von Doctrine und funktioniert vermutlich mit den neueren 1.x und späteren 2.x Releasen nicht mehr.
Heute starte ich meine Serie zum Thema ORM in PHP mit Doctrine.
Durch ORM ermöglicht man dem Programmierer den vollständigen objektorientierten Zugriff auf die Daten in der Datenbank und macht ihn somit unabhängig von der Speichern und verhindert somit auch die Verwendung von SQL. Das hat den Vorteil, dass man bei einem Wechsel des DBMS keine Querys anpassen muss (so denn das DBMS von dem ORM-Layer untersützt wird.
Doctrine ist eine in PHP5 programmierte Anwendung und ca. seit einem Jahr in Entwicklung. Da Doctrine dieses Jahr als Projekt bei Google Summer of Code antreten durfte, tut sich im Moment recht viel. Ich habe Doctrine seit der Revision 7xx verwendet und muss sagen, dass sich bis zur heutigen 2066 recht viel getan hat. Der erste Release-Candidate ist für Ende August angekündigt worden. Der Layer funktioniert großteils reibungslos, jedoch muss man bei Problemen auch hin und wieder mal in den Source schauen um zu verstehen, wieso etwas (noch) nicht funktioniert. Die Dokumentation ist zwar ausführlich, jedoch nicht so perfekt, als dass sie helfen könnte.
Heute machen wir einen kleinen Schnelleinstieg. Wir beginnen mit dem Aufbau der Db-Verbindung, Anlegen einer Doctrine-Db-Klasse, Einfügen von Datensätzen, Auslesen per Finder, Updaten, Auslesen per Query und Löschen. Die aktuellen Beispiele basieren auf der Revision 2066. Diese kann aus dem SVN-Repository von Doctrine ausgecheckt werden. Als Client verwende ich Tortoise SVN.
Steigen wir nun in unser Beispiel ein. Wir wollen in einer Datenbanktabelle Produkte speichern. In unserem Falle noch recht simpel in einer Tabelle. Als Verzeichnisstruktur legen wir folgendes an:
lib/external/Doctrine [Checkout-Verzeichnis für Doctrine]
lib/Data/Db [Ordner für die Db-Klassen]
Beginnen wir nun in unserer /index.php und binden dort zunächst Doctrine per Require ein. Danach muss der Autoloader von Doctrine registriert werden, damit die benötigten Unterklassen von Doctrine automatisch nachgeladen werden können. Danach können wir direkt eine Verbindnug zur Datenbank öffnen.
1 2 3 4 | require_once ( './lib/Doctrine/lib/Doctrine.php' ); spl_autoload_register ( array( 'Doctrine' , 'autoload') ); $Connection = Doctrine_Manager::connection ('mysql://root:@localhost/doctrine'); |
Lassen wir uns diese nun per echo ausgeben, erhalten wir die Bestätigung, dass die Datenbankverbindung geöffnet wurde:
Doctrine_Connection object
State : open
Open Transactions : 0
Table in memory :
Driver name : mysql
Doctrine ist übrigens auch im Fehlerhandling komplett objektorientiert. Also werden keine Fehlermeldungen geworfen, sondern Exceptions. Diese müssen mittels try-catch-Block aufgefangen werden.
Jetzt haben wir Doctrine startklar gemacht, jedoch haben wir noch keine Datenbanktabelle, die wir verwenden können. Also legen wir uns mittels SQL folgende Tabelle an:
1 2 3 4 5 6 | CREATE TABLE `products` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY , `name` VARCHAR( 255 ) NOT NULL , `description` TINYTEXT NOT NULL , `date_created` DATETIME NOT NULL ) ENGINE = MYISAM ; |
Als nächstes legen wir die Doctrine-Db-Klasse an, mit der wir Doctrine sagen, wie die Tabelle aussehen soll, auf die wir zugreifen wollen. Eine Datenbankrelation entspricht also einer Doctrine-Klasse. Mittels hasColumn definieren wir die Spalten. Hier werden Name, Typ, Länge und Attribute angegeben. Die Klasse muss von der Doctrine-Klasse Doctrine_Record abgeleitet werden und sieht in unserem Falle so aus: (lib/Db/Product.php)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | class Data_Db_Products extends Doctrine_Record { public function setTableDefinition ( ) { $this->setTableName('products'); $this->hasColumn('id', 'integer', 4, array('notnull' => true, 'primary' => true, 'unsigned' > true, 'autoincrement' => true)); $this->hasColumn('name' , 'string' , 255 , array ('notnull' => true)); $this->hasColumn('description' , 'string' , 3000 , array ('notnull' => true)); $this->hasColumn('date_created' , 'timestamp' , null); } } |
Jetzt haben wir alle Werte gesetzt, um auf die Datenbank zugreifen zu können. Nun wollen wir mal ein Produkt anlegen und es direkt mittels der save()-Methode in die Datenbank speichern:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | try { $Product = new Data_Db_Products; $Product [ 'name' ] = 'My First Product'; $Product [ 'description' ] = 'Product Product Product'; $Product [ 'date_created' ] = date('Y-m-d H:i:s'); $Product->save(); } catch ( Exception $e ) { var_dump($e->getMessage()); } |
Geht bei dieser Operation etwas schief, so werden wir die Meldung von Doctrine im Catch-Block ausgegeben bekommen. Wir sehen, wir können ein Objekt vom Typ Data_Db_Products behandeln wie ein Array, da für diese Klassen SPL::ArrayAccess implementiert ist. Wir können natürlich anstatt der Array-Klammern auch die Objektpfeile benutzen $Product->name.
Nun können wir im PHPMyAdmin bereits sehen, dass ein Datensatz in der Datenbank angelegt worden ist. Fügen wir nun hinter dem Save folgendes ein:
1 | echo $Product["id"]; |
wird uns die Id des gerade eingegebenen Produktes angezeigt. Dies können wir nun natürlich auch direkt auslesen. Für die Suche nach Primärschlüsselwerten bietet Doctrine eine Finder-Methode, auf die wie folgt zugegriffen werden kann:
1 2 | $Product = $Connection->getTable('Data_Db_Products')->find ( 1 ); echo $Product["name"]; |
Das Ergebnis spricht für sich, es hat funktioniert:
My First Product
Jetzt wollen wir den gerade ermittelten Datensatz aktualisieren. Dazu reicht es, wenn wir die Werte, die wir ändern möchten via Array-Zugriff ändern und dann speichern. Das Mappen des Datensatzes übernimmt Doctrine. Jedoch dürfen wir nicht schreibend auf die Id zugreifen, das schützt uns vor ungewollten Aktionen:
1 2 | $Product['name'] = 'My Second Product'; $Product->save() |
Lesen wir dieses Produkt dann nochmal per finder neu ein, können wir sehen, dass die Änderung nicht nur in der Variable, sondern auch nach dem erneuten auslesen persistent gespeichert wurde:
My Second Product
Nun verschaffen wir uns mit folgendem Script mal ein paar Testdaten:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | for ( $i = 0; $i < 100; $i++ ) { try { $Product = new Data_Db_Products; $Product [ 'name' ] = 'Product ' . sprintf ( "%03d", $i ); $Product [ 'description' ] = 'Product Product Product'; $Product [ 'date_created' ] = date('Y-m-d H:i:s'); $Product->save(); } catch ( Exception $e ) { var_dump($e->getMessage()); } } |
Jetzt möchten wir alle Produkte auslesen, welche eine Id zwischen 10 und 20 haben. Dazu verwenden wir einen Doctrine-Query, welcher dann eine Doctrine-Collection zurück gibt. Diese könnten wir dann mit einer Foreach-Schleife durchlaufen. Wir wollen aber zunächst nur auslesen und die Anzahl der Datensätze ausgeben:
1 2 3 4 5 | $Query = new Doctrine_Query; $Query->from ( 'Data_Db_Products p' ); $Query->where ( 'p.id < 20 AND p.id > 9' ); $Result = $Query->execute(); echo count($Result); |
Das Ergebnis zeigt, dass wir Erfolg hatten:
10
Möchten wir anstatt einer Doctrine-Collection direkt ein Array zurück bekommen, um den Overhead, den die Objekte mit sich bringen, ein wenig einzuschränken tauschen wir den execute() Aufruf aus und erhalten folglich nur noch ein Array:
1 | $Result = $Query->execute(array(1),Doctrine::FETCH_ARRAY); |
Wir bleiben allerdings jetzt bei unserer Collection. Wir haben diese Objekte selektiert um sie zu löschen. Dies ist einfacher als man denkt, wie folgendes Beispiel zeigt:
1 2 3 4 5 6 7 8 9 10 11 12 13 | $Query = new Doctrine_Query; $Query->from ( 'Data_Db_Products p' ); $Query->where ( 'p.id < 20 AND p.id > 9' ); $Result = $Query->execute(); echo "<br />PRE-DELETE: "; echo count($Result); $Result->delete(); $Result = $Query->execute(); echo "<br />POST-DELETE: "; echo count($Result); |
Wir führen den Such-Query nach dem Löschen nochmal aus und sehen, dass wir nun 0 Datensätze haben die in unser Suchmuster passen. Das Löschen war also erfolgreich:
PRE-DELETE: 10
POST-DELETE: 0
Das soll es für heute auch gewesen sein. Ich werde die Reihe in unregelmäßigen Abständen fortsetzen. Es gibt noch viele spannende Themen wie 1:n- n:m-Beziehungen, Events … Ich freue mich über Kommentare und Meinungen und hoffe, dass ich mit diesen Beiträgen noch mehr Leute zur objektorientierten Programmierung und Doctrine bringen kann.
Juli 30th, 2007 at 20:46
Nach dem wir im letzten Beitrag die Grundlagen zu Doctrine gelernt haben, wollen wir heute ein wenig weiter in die Tiefe gehen.
Wir nehmen wieder unser Beispiel und nehmen an wir haben ein Produkt z.B. Coca Cola. Hierzu gibt es viele Varianten wie z.B. Z
August 23rd, 2007 at 21:56
Hallo Alexander!
Danke für den ausführlichen Artikel! Ich kenn mich mit ORM noch überhaupt nicht aus, habe versucht Doctrine zu verwenden und hatte ständig irgendeinen Fehler – ich hoffe mit deinen Anweisungen gehts besser!
jetzt hoffe ich nur noch, den captcha code zu entschlüsseln…
Gruß,
Günter
August 23rd, 2007 at 21:59
Hallo Günter,
vielen Dank für Deinen Kommentar!
Wenn Du weitergehende Fragen hast, kannst Du mir auch gerne eine Mail schreiben, ich helfe gerne weiter. Doctrine ist ein sehr spannendes Thema, jedoch durch die Doku und den Entwicklungsstatus etwas schwierig bei Fehlern einzuschätzen.
Wenn was ist, oder im Beitrag nicht mehr funktioniert, einfach kurz per Mail melden.
Grüße,
Alex
Februar 15th, 2008 at 17:37
Can someone translate to English?
Februar 21st, 2009 at 21:05
Hallo Alexander,
ich wollte mich nur für diesen Artikel bedanken. Es ist jetzt zwar über 1,5 Jahre her seit dem dieser Artikel verfasst wurde, jedoch wird es mir auch Stand heute noch sehr helfen mich in die Thematik Doctrine einzuarbeiten! Es ist genau in dem Stil geschrieben, wie ich es selbst auch geschrieben hätte.
Schön das dieser Artikel und somit der ganze Blog noch existiert! Weiter so!
Viele Grüße
Human
März 7th, 2010 at 12:26
Nach ca. zweieinhalb Jahren funktionieren alle Beispiele auch noch einwandfrei. Danke für dieses Tutorial, bin schon auf die weiteren Artikel gespannt. Muss mich mal durcharbeiten
März 8th, 2010 at 07:25
Wow, da bin ich aber froh
. Sobald Doctrine 2.0 draußen ist, folgen sicher noch ein paar Artikel dieser Art
.