Aug 14

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.

Nachdem wir uns im letzten Beitrag nochmal mit dem Beziehungen der Datenbanktabellen und Klassen beschäftigt haben, wollen uns heute einem weiteren Kapitel widmen, den Event-Listenern.

Sie sind ein einfaches Instrument, die auch bei vielen DBMS durch Trigger abgebildet sind. Ein praktisches Beispiel schauen wir uns nun einmal an. Wir haben ja eine Produkt-Tabelle und Klasse, hier wollen wir nun eine User-Id speichern, damit wir wissen, welcher User ein Produkt angelegt hat. Zusätzlich möchten wir einen aggregierten Counter für jeden User haben, damit wir nicht bei jedem Request seines Profils alle Produkte summieren müssen, um herrauszufinden, wie viele Produkte ein User angelegt hat. Dies soll natürlich nicht explizit geschehen müssen. Wir möchten, dass einem User automatisch ein Produkt “gutgeschrieben” wird, sobald er es anlegt, und es abgezogen wird, wenn er das Produkt löscht.

Hierzu legen wir ein Feld “product_count” an, welches beim User selbst abgelegt wird. Beginnen wir nun mit dem Anlegen der User-Klasse und Datenbank-Tabelle:

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
class
	Data_Db_Users
extends
	Doctrine_Record
{
 
	public function setTableDefinition
	(
	)
	{
		$this->setTableName('users');
 
        $this->hasColumn('id', 'integer', 4, array('notnull' => true,
                                                   'primary' => true,
                                                   'unsigned' > true,
                                                   'autoincrement' => true));
		$this->hasColumn('username' , 'string' , 255 , array ('notnull' => true));
		$this->hasColumn('firstname' , 'string' , 255 , array ('notnull' => true));
		$this->hasColumn('lastname' , 'string' , 255 , array ('notnull' => true));
		$this->hasColumn('password' , 'string' , 255 , array ('notnull' => true));
		$this->hasColumn('product_count' , 'integer' , 8 , array ('default' => 0));
	}
 
	public function setUp
	(
	)
	{
        $this->hasMany('Data_Db_Products', array('local' => 'id',
                                    'foreign' => 'users_id'));
	}
 
}

Die Tabelle Verknüpfen wir direkt über die users.id über die users_id mit der Produkt-Tabelle. Bevor wir uns aber genauer der Produktkklasse widmen, müssen wir noch die Datenbanktabelle mittels SQL in der Datenbank anlegen:

1
2
3
4
5
6
7
8
9
CREATE TABLE `users` (
  `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
  `username` VARCHAR(255) COLLATE latin1_general_ci NOT NULL,
  `firstname` VARCHAR(255) COLLATE latin1_general_ci NOT NULL,
  `lastname` VARCHAR(255) COLLATE latin1_general_ci NOT NULL,
  `password` VARCHAR(255) COLLATE latin1_general_ci NOT NULL,
  `product_count` BIGINT(20) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM

Ist dies nun gemacht, legen wir eine Rückverknüpfung für die Produkt-Tabelle an. Hierzu fügen wir zunächst das Feld “users_id” in die Klasse Data_Db_Products ein. Dazu muss die folgende Zeile in die Table-Definition eingefügt werden:

1
$this->hasColumn('users_id' , 'integer' , 8 , array ('notnull' => true));

Desweiteren wollen wir die Verknüpfung zur User-Tabelle realisieren, dies tun wir mit folgender Definition in der allerseits bekannten setUp-Methode:

1
2
        $this->hasOne('Data_Db_Products', array('local' => 'users_id',
                                    'foreign' => 'id'));

Nun sollte noch das Feld in die Tabelle ausgeführt werden, dazu reicht ein Alter-Table-Befehl:

1
ALTER TABLE `products` ADD `users_id` BIGINT NOT NULL AFTER `id` ;

Wenden wir uns nun den Event-Listenern zu. Doctrine unterstützt eine Reihe von Listenern. Eine vollständige Liste findet man im Manual. Ich beschränke mich in meinem Beispiel auf zwei Record-Hooks. Es gibt noch diverse andere Möglichkeiten, die im Manual beschrieben sind. leider mußte ich feststellen, dass zwischen einigen Revisions die separaten Listener-Klassen nicht mehr funktionierten, vielleicht ist dies ja mitlerweile wieder der Fall. Wir beschränken uns auf einen postInsert und einen postDelete-Hook. Im Manual findet sich auch ein Hinweis, dass dieser Bereich gerade under heavy Construction ist, es empfielt sich also öfters mal ins Manual zu sehen.

Erstellen wir nun zunächst einen neuen User und speichern diesen persistent in die Datenbank. Hierbei kommen bisher keine Techniken zum Einsatz, die in vorherigen Einträgen nicht schon beschrieben worden wären. Der Vollständigkeit halber, hier aber nochmal ein vollständiges Beispiel:

1
2
3
4
5
6
$Users = new Data_Db_Users;
$Users["username"] = 'User01';
$Users["firstname"] = 'John';
$Users["lastname"] = 'Doe';
$Users["password"] = md5('test12345');
$Users->save();

Jetzt legen wir einen postInsert Listener an, der reagiert, NACHDEM ein Eintrag neu eingefügt wurde, wir erhöhen damit den Counter des Produkts und speichern es wieder. Das Gegenstück dazu heißt postDelete und wird nach dem Löschen eines Produkts ausgeführt und macht genau das Gegenstück von dem, was postInsert macht. Fügen wir nun diese beiden Methoden in die Produkt-Datenbank-Klasse ein:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
	public function postInsert
	(
	)
	{
		$Users = $GLOBALS ["Connection"]->getTable('Data_Db_Users')->find($this["users_id"]);
		$Users["product_count"] = $Users["product_count"]+1;
		$Users->save(); 
	}
 
	public function postDelete
	(
	)
	{
		$Users = $GLOBALS ["Connection"]->getTable('Data_Db_Users')->find($this["users_id"]);
		$Users["product_count"] = $Users["product_count"]-1;
		$Users->save(); 
	}

Dies testen wir nun mit dem folgenden Code:

1
2
3
4
5
6
$Products = new Data_Db_Products;
$Products["name"] = 'MyProduct01';
$Products["users_id"] = $Users["id"];
$Products->save();
 
$Products->delete();

Wir fügen nun ein Produkt ein und löschen es danach wieder. Das bedeutet, dass der Counter “product_count” weiterhin bei 0 stehen bleibt. Kommentieren wir nun das Delete aus, wird der Counter hochgezählt. Wir haben also alles richtig gemacht :-) .
Weitere Einsatzzwecke für die Listener sind weit gesteckt. Von History-Generierung über Datumsaktualisierung kann man einiges machen, da bleibt der eigenen Phantasie und Logik keine Grenze gesetzt, viel Spaß beim Ausprobieren.

Ich kann jetzt noch nicht konkret sagen, zu welchem Doctrine-Thema die Reihe fortgesetzt wird, aber sie wird fortgesetzt :-) .

Post to Twitter Post to Delicious Post to Digg Post to Facebook

written by Alexander \\ tags: , , , , ,


One Response to “Enterprise: ORM in PHP mit Doctrine #4”

  1. 1. Alexander Stelter Says:

    Nachdem wir uns in den letzten Beiträgen die generelle Funktionsweise von Doctrine angesehen haben, wollen wir uns nun die weiterführenden Features ansehen. Heute beginnen wir mit dem NestedSet. Mit diesem ist es möglich eine Baumstruktur datenbankseitig

Leave a Reply

i3Theme sponsored by Top 10 Web Hosting and Hosting in Colombia