Vererbung
Das ORM unterstützt Vererbungen. Das bedeutet, dass Entities andere Entities erweitern können. Versuche Vererbung ("Inheritance") aber zu vermeiden und bevorzuge stattdessen Aggregationen / Kompositionen und greife auf das Decorator-Pattern zurück. Moderne Architekturen basieren auf Interfaces und versuchen Vererbungen gundsätzlich zu vermeiden. Die Architektur wird dadurch flexibler.
Natürlich kann es immer noch sinnvoll sein, abstrakte Super-Klassen ausschliesslich zum Zweck, Eigenschaften oder Methoden wiederverwenden zu können, zu implementieren (z. B. Adapter, die Methoden eines Interfaces implementieren, die fast immer gleich sind). Hierfür eignen sich aber auch "Mapped Superclasses".
Vererbungs-Strategien
Die Vererbungs-Strategie bestimmt die Tabellen-Struktur, die zur Realisierung der jeweiligen Vererbungen verwendet werden soll. Eine Vererbungs-Strategie muss für alle Entities definiert werden, die in der Vererbungs-Hierarchie zuoberst stehen. Hierzu steht die Annotation n2n\persistence\orm\annotation\AnnoInheritance
zur Verfügung. Die Id-Eigenschaft muss jeweils in dieser Entity definiert werden.
Single Table
Die Strategie "Single Table" vereint die Eigenschaften der Entity und aller Sub-Entities in eine Tabelle. Um jede Zeile einer Entity zuordnen zu können, benötigt das ORM eine Diskriminatorspalte. Der Name der Diskriminatorspalte kannst du über die Annotation n2n\persistence\orm\annotation\AnnoDiscriminatorColumn
bestimmen. Andernfalls wird angenommen, dass die Diskriminatorspalte "discr" heisst.
class Item extends ObjectAdapter { private static function _annos(AnnoInit $ai) { $ai->c(new AnnoTable('st_item'), new AnnoInheritance(InheritanceType::SINGLE_TABLE), new AnnoDiscriminatorColumn('type'), new AnnoDiscriminatorValue('item')); } private $id; private $name; public function getId() { return $this->id; } public function getName() { return $this->name; } public function setName($name) { $this->name = $name; } }
class LinkItem extends Item { private static function _annos(AnnoInit $ai) { $ai->c(new AnnoDiscriminatorValue('linkItem')); } private $url; public function getUrl() { return $this->url; } public function setUrl($url) { $this->url = $url; } }
class TextItem extends Item { private static function _annos(AnnoInit $ai) { $ai->c(new AnnoDiscriminatorValue('textItem')); } private $text; public function getText() { return $this->text; } public function setText($text) { $this->text = $text; } }
Für alle nicht abstrakten Entities musst du über die Annotation n2n\persistence\orm\annotation\AnnoDiscriminatorValue
den Diskriminator-Wert bestimmen.
Für das oben stehende Beispiel ist folgende Tabelle gültig:
CREATE TABLE `st_item` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `type` ENUM('item','linkItem','textItem') NOT NULL, `name` VARCHAR(50) NOT NULL, `url` VARCHAR(255) DEFAULT NULL, `text` TEXT DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Für alle nicht verwendeten Felder wird jeweils der Wert NULL
eingefügt.
Beispiel
public function doOrmSt(EntityManager $em) { $this->beginTransaction(); $item = new Item(); $item->setName('foo'); $em->persist($item); $linkItem = new LinkItem(); $linkItem->setName('n2n Support'); $linkItem->setUrl('http://support.n2n.ch'); $em->persist($linkItem); $textItem = new TextItem(); $textItem->setName('bar'); $textItem->setText('lorem ipsum'); $em->persist($textItem); $this->commit(); }
Das oben stehende Beispiel schreibt folgende Zeilen in die Tabelle "st_item":
id | type | name | url | text |
---|---|---|---|---|
1 | item | foo | NULL | NULL |
2 | linkItem | n2n Support | http://support.n2n.ch | NULL |
3 | textItem | bar | NULL | lorem ipsum |
Joined
Die Strategie "Joined" erwartet für die Entity und alle Sub-Entities eine separate Tabelle (wie bei normalen Entities).
class Item extends ObjectAdapter { private static function _annos(AnnoInit $ai) { $ai->c(new AnnoTable('jt_item'), new AnnoInheritance(InheritanceType::JOINED)); } private $id; private $name; public function getId() { return $this->id; } public function getName() { return $this->name; } public function setName($name) { $this->name = $name; } }
class TextItem extends Item { private static function _annos(AnnoInit $ai) { $ai->c(new AnnoTable('jt_text_item')); } private $text; public function getText() { return $this->text; } public function setText($text) { $this->text = $text; } }
class LinkItem extends Item { private static function _annos(AnnoInit $ai) { $ai->c(new AnnoTable('jt_link_item')); } private $url; public function getUrl() { return $this->url; } public function setUrl($url) { $this->url = $url; } }
Schreibst du ein Objekt der Entity LinkItem
in die Datenbank, so wird eine Zeile in der Tabelle "jt_item" sowie "jt_link_item" eingefügt. Damit diese zwei Zeilen miteinander in Verbindung gebracht werden können, wird die Id-Spalte in allen Tabellen der Sub-Entities übernommen.
Für das oben stehende Beispiel sind folgende Tabellen gültig:
CREATE TABLE `jt_item` ( `id` INT(10) unsigned NOT NULL AUTO_INCREMENT, `name` VARCHAR(50) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `jt_link_item` ( `id` INT(10) UNSIGNED NOT NULL, `url` VARCHAR(255) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `jt_text_item` ( `id` INT(10) UNSIGNED NOT NULL, `text` TEXT NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Beispiel
public function doOrmJt(EntityManager $em) { $this->beginTransaction(); $item = new Item(); $item->setName('foo'); $em->persist($item); $linkItem = new LinkItem(); $linkItem->setName('n2n Support'); $linkItem->setUrl('http://support.n2n.ch'); $em->persist($linkItem); $textItem = new TextItem(); $textItem->setName('bar'); $textItem->setText('lorem ipsum'); $em->persist($textItem); $this->commit(); }
Das oben stehende Beispiel schreibt folgende Zeilen in die Tabellen:
jt_item
id | name |
---|---|
1 | foo |
2 | n2n Support |
3 | bar |
jt_link_item
id | url |
---|---|
2 | http://support.n2n.ch |
jt_text_item
id | text |
---|---|
3 | lorem ipsum |
Table Per Class
Die Strategie "Table Per Class" erstellt eine Tabelle für jede Entity. Die Tabelle einer Entity enthält dabei auch alle Spalten der Eigenschaften ihrer Super-Entities. Bei dieser Vererbungs-Strategie ist es nicht möglich, die Id von der Datenbank generieren zu lassen.
class Item extends ObjectAdapter { private static function _annos(AnnoInit $ai) { $ai->c(new AnnoTable('tbc_item'), new AnnoInheritance(InheritanceType::TABLE_PER_CLASS)); } private $id; private $name; public function getId() { return $this->id; } public function setId($id) { $this->id = $id; } public function getName() { return $this->name; } public function setName($name) { $this->name = $name; } }
class LinkItem extends Item { private static function _annos(AnnoInit $ai) { $ai->c(new AnnoTable('tbc_link_item')); } private $url; public function getUrl() { return $this->url; } public function setUrl($url) { $this->url = $url; } }
class TextItem extends Item { private static function _annos(AnnoInit $ai) { $ai->c(new AnnoTable('tbc_text_item')); } private $text; public function getText() { return $this->text; } public function setText($text) { $this->text = $text; } }
Für das oben stehende Beispiel sind folgende Tabellen gültig:
CREATE TABLE `tbc_item` ( `id` INT(10) UNSIGNED NOT NULL, `name` VARCHAR(50) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `tbc_link_item` ( `id` INT(10) UNSIGNED NOT NULL, `name` VARCHAR(50) NOT NULL, `url` VARCHAR(255) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `tbc_text_item` ( `id` INT(10) UNSIGNED NOT NULL, `name` VARCHAR(50) NOT NULL, `text` TEXT NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Beispiel
public function doOrmTbc(EntityManager $em) { $this->beginTransaction(); $item = new Item(); $item->setId(1); $item->setName('foo'); $em->persist($item); $linkItem = new LinkItem(); $linkItem->setId(2); $linkItem->setName('n2n Support'); $linkItem->setUrl('http://support.n2n.ch'); $em->persist($linkItem); $textItem = new TextItem(); $textItem->setId(3); $textItem->setName('bar'); $textItem->setText('lorem ipsum'); $em->persist($textItem); $this->commit(); }
Das oben stehende Beispiel schreibt folgende Zeilen in die Tabellen:
tbc_item
id | name |
---|---|
1 | foo |
tbc_link_item
id | name | url |
---|---|---|
2 | n2n Support | http://support.n2n.ch |
tbc_text_item
id | name | text |
---|---|---|
3 | bar | lorem ipsum |
class-Eigenschaft
In Abfragen kannst du die Pseudo-Eigenschaft class
verwenden. Diese entspricht der ReflectionClass
der jeweiligen Entity.
Criteria API
$criteria = $em->createCriteria(); $criteria->select('i')->from(Item::getClass(), 'i')->where() ->match('i.class', 'IN', array(LinkItem::getClass(), TextItem::getClass())); foreach ($criteria->toQuery()->fetchArray() as $item) { var_dump($item); }
NQL
$criteria = $em->createNqlCriteria('SELECT i FROM Item i WHERE i.class IN :classes', array('classes' => array(LinkItem::getClass(), TextItem::getClass()))); foreach ($criteria->toQuery()->fetchArray() as $item) { test($item); }
In diesem Beispiel fragen wir alle Entity-Objekte ab, die vom Typ LinkItem
oder TextItem
sind.