Vererbung

ORM

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".

  1. Vererbungs-Strategien
  2. class-Eigenschaft

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
Die Vererbungs-Strategie "Single Table" ist in den meisten Fällen die performanteste. Solange es nicht nötigt ist, die Daten auf mehrere Tabellen aufzuteilen, sollte sie die erste Wahl sein.

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.

In Tabellen der Sub-Entities dürfen die Id-Werte nicht automatisch von der Datenbank generiert werden.

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.

« Eigenschafts-Typen Annotationen »

Kommentare

Du musst eingeloggt sein, damit du Beiträge erstellen kannst.

Fragen