Entity konfigurieren
Id
Ist nichts weiter definiert, wird angenommen, dass die Eigenschaft mit Namen "id" die Id (Primary Key) repräsentiert und das der Wert der zugehörigen Spalte von der Datenbank automatisch generiert wird (in MySQL AUTO_INCREMENT
). Du kannst auch eine andere Eigenschaft als Id definieren, indem du sie mit n2n\persistence\orm\annotation\AnnoId
annotierst.
class User extends EntityAdapter { private static function _annos(AnnoInit $as) { $as->p('name', new AnnoId()); } private $name; private $password; // getters / setters }
Über die Parameter von AnnoId
kannst du dem ORM ausserdem mitteilen, ob der Wert von der Datenbank oder der Applikation generiert wird und ob eine Sequenz zum Generieren dieses Wertes benötigt wird (z. B. in Oracle).
Tabellen- und Spaltennamen
Wird nichts weiter konfiguriert, werden standardmässig Klassen- und Eigenschaftsnamen "hyphenated" und diese als Tabellen- und Spaltennamen verwendet. Für eine Entity mit Klassennamen BlogArticle
wird also eine Tabelle blog_article
und für eine Eigenschaft $orderIndex
eine Spalte oder_index
gesucht. Du kannst die Tabellen- und Spaltennamen aber auch direkt in der Entity über die Annotationen n2n\persistence\orm\annotation\AnnoTable
und n2n\persistence\orm\annotation\AnnoColumn
festlegen.
class BlogUser extends EntityAdapter { private static function _annos(AnnoInit $ai) { $ai->c(new AnnoTable('bloguser')); $ai->p('firstName', new AnnoColumn('firstname')); } private $id; private $firstName; private $lastName; // getters / setters }
Für die Entity User
im oben stehenden Beispiel wäre also folgende Tabelle passend:
CREATE TABLE `bloguser` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `firstname` VARCHAR(50) NULL DEFAULT NULL, `last_name` VARCHAR(50) NULL DEFAULT NULL, PRIMARY KEY (`id`) ) COLLATE='utf8_general_ci' ENGINE=InnoDB;
Naming Strategy
Du kannst den Algorithmus zum Generieren von Tabellen- und Spaltennamen überschreiben, indem du eine eigene "Naming Strategy" definierst. Hierzu musst du eine Klasse erstellen, die n2n\persistence\orm\model\NamingStrategy
implementiert und diese in der app.ini unter der Gruppe [orm]
registrieren.
[orm] naming_strategy = "atusch\config\CustomNamingStrategy"
Du kannst auch eine der Implementationen verwenden, die n2n zur Verfügung stellt:
n2n\persistence\orm\model\HyphenatedNamingStrategy
(Standard)n2n\persistence\orm\model\IdenticalNamingStrategy
n2n\persistence\orm\model\LowercasedNamingStrategy
Über die Annotation n2n\persistence\orm\annotation\AnnoNamingStrategy
kannst du die "Naming Strategy" auch für einzelne Entities überschreiben.
class User extends EntityAdapter { private static function _anno(AnnoInit $ai) { $ai->c(new AnnoNamingStrategy(new LowercasedNamingStrategy())); } // class body }
app.ini
, beeinflusst dies die Entities aller installierten Module. Dies kann dazu führen, dass Module, die zum Beispiel über den Store installiert wurden, ihre Tabellen und Spalten nicht mehr finden. Es ist also zu empfehlen die "Naming Strategy" nur für eigene Entities zu überschreiben (über AnnoNamingStrategy
).Mapped Supperclass
Du kannst Eigenschaften in sogenannte "Mapped Superclasses" auslagern, die von Entities erweitert werden können. Die Eigenschaften werden dabei in der Tabelle der Entity gespeichert, als wären sie in ihr selbst definiert. "Mapped Superclasses" müssen mit n2n\persistence\orm\annotation\AnnoMappedSuperclass
annotiert werden. Es ist nicht nötig sie im app.ini
zu registrieren.
class Teaser extends ObjectAdapter { private static function _annos(AnnoInit $ai) { $ai->c(new AnnoMappedSuperclass()); } private $title; private $description; public function getTitle() { return $this->title; } public function setTitle($title) { $this->title = $title; } public function getDescription() { return $this->description; } public function setDescription($description) { $this->description = $description; } }
Teaser
kann nun von beliebigen Entities erweitert werden.
class News extends Teaser { private $id; private $text; public function getId() { return $this->id; } public function setId($id) { $this->id = $id; } public function getText() { return $this->text; } public function setText($text) { $this->text = $text; } }
Zur Entity News
passt folgende Tabelle:
CREATE TABLE IF NOT EXISTS `news` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `title` VARCHAR(50), `description` VARCHAR(255), `text` text, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Embedded
Du kannst Eigenschaften auch in eine Extraklasse auslagern und deren Objekte per Eigenschaft referenzieren. Solche Eigenschaften müssen mit n2n\persistence\orm\annotation\AnnoEmbedded
annotiert werden.
Gehen wir davon aus, dass wir eine Entity Ordner
benötigen, um Bestellungen eines Shops zu erfassen. Die Bestellung soll eine Rechnungs- und wenn gewünscht eine separate Lieferadresse enthalten. Dies bedeutet, dass wir Eigenschaften wie $forename
und $surname
jeweils für die Rechnungs- sowie Lieferadresse benötigen. Anstatt diese Adress-Eigenschaften doppelt zu deklarieren, ist es einfacher diese in eine Extraklasse Address
auszulagern und in der Entity Order
zwei "embedded" Eigenschaften vom Typ Address
zu deklarieren. Address
könnte später auch von anderen Entities genutzt werden, um Adress-Eigenschaften auszulagern. Extraklassen müssen nicht im app.ini
registriert werden.
class Order extends ObjectAdapter { private static function _annos(AnnoInit $ai) { $ai->p('address', new AnnoEmbedded(Address::getClass())); $ai->p('deliveryAddress', new AnnoEmbedded(Address::getClass(), 'delivery_')); } private $id; private $productName; private $address; private $deliveryAddress; public function getProductName() { return $this->productName; } public function setProductName($productName) { $this->productName = $productName; } /** * @return Address */ public function getAddress() { return $this->address; } public function setAddress(Address $address) { $this->address = $address; } public function hasDeliveryAddress() { return $this->deliveryAddress !== null; } /** * @return Address */ public function getDeliveryAddress() { return $this->deliveryAddress; } public function setDeliveryAddress(Address $deliveryAddress = null) { $this->deliveryAddress = $deliveryAddress; } }
Damit die Eigenschaften der Extraklasse Address
für die Rechnungs- und Lieferadresse nicht die selben Spalten in der Tabelle "order" verwenden, bietet AnnoEmbedded
über den zweiten und dritter Parameter die Möglichkeit einen Prefix sowie Suffix für die Spalten der jeweiligen Extraklasse zu definieren.
AnnoEmbedded
erwartet als ersten Parameter die ReflectionClass
der jeweiligen Extraklasse, darum ist es auch hier sinnvoll, dass die Extraklasse n2n\reflection\ObjectAdapter
erweitert.
class Address extends ObjectAdapter { private $forename; private $surname; // more properties public function __construct($forename = null, $surname = null) { $this->forename = $forename; $this->surname = $surname; } public function getForename() { return $this->forename; } public function setForename($forename) { $this->forename = $forename; } public function getSurname() { return $this->surname; } public function setSurname($surname) { $this->surname = $surname; } }
Zur Entity Order
passt folgende Tabelle:
CREATE TABLE `order` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `product_name` varchar(50) NOT NULL, `forename` varchar(50) NOT NULL, `surname` varchar(50) NOT NULL, `delivery_forename` varchar(50) DEFAULT NULL, `delivery_surname` varchar(50) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Ist Order::$deliveryAddress
beim speichern null
, so werden in die beiden Felder "delivery_forename" und "delivery_surname" NULL geschrieben. Genauso wird Order::$deliveryAddress
beim Lesen null
enthalten, wenn die beiden Felder "delivery_forename" und "delivery_surname" NULL sind.
In folgendem Beispiel erstellen wir eine Bestellung mit und eine ohne Lieferadresse:
public function doOrm(EntityManager $em) { $this->beginTransaction(); $order = new Order(); $order->setProductName('Broccoli'); $order->setAddress(new Address('Peter', 'Awesome')); $order->setDeliveryAddress(new Address('Amanda', 'Pretty')); $em->persist($order); $order = new Order(); $order->setProductName('ZSC Playoff Tickets'); $order->setAddress(new Address('Peter', 'Awesome')); $em->persist($order); $this->commit(); }
Das oben stehende Beispiel schreibt folgende zwei Zeilen in die Tabelle "order":
id | product_name | forename | surname | delivery_forename | delivery_surname |
---|---|---|---|---|---|
1 | Broccoli | Peter | Awesome | Amanda | Pretty |
2 | ZSC Playoff Tickets | Peter | Awesome | NULL | NULL |
Abfragen
Folgende Beispiele zeigen, wie du "embedded" Eigenschaften in Abfragen verwenden kannst.
Beispiel 1
Criteria API
$criteria = $em->createSimpleCriteria(Order::getClass(), array('address.forename' => 'Peter'));
NQL
$criteria = $em->createNqlCriteria( 'SELECT o FROM "Order" o WHERE o.address.forename = :forename', array('forename' => 'Peter'));
Beispiel 2
Criteria API
$address = new Address('Peter', 'Awesome'); $criteria = $em->createSimpleCriteria(Order::getClass(), array('address' => $address));
NQL
$address = new Address('Peter', 'Awesome'); $criteria = $em->createNqlCriteria( 'SELECT o FROM "Order" o WHERE o.address = :address', array('address' => $address));
Beispiel 3
Criteria API
$criteria = $em->createSimpleCriteria(Order::getClass(), array('deliveryAddress' => null));
NQL
$address = new Address('Peter', 'Awesome'); $criteria = $em->createNqlCriteria( 'SELECT o FROM "Order" o WHERE o.deliveryAddress = :deliveryAddress', array('deliveryAddress' => null));
Attribute Overrides
Über die Annotation n2n\persistence\orm\annotation\AnnoAttributeOverrides
kannst du für alle Eigenschaften einer Entity die assoziierten Spaltennamen überschreiben. AnnoAttributeOverrides
ist somit eine Alternative zu n2n\persistence\orm\annotation\AnnoColumn
. AnnoAttributeOverrides
erlaubt es dir auch die assoziierten Spaltennamen der Eigenschaften einer Mapped Supperclass zu überschreiben.
class News extends Teaser { private static function _annos(AnnoInit $ai) { $ai->c(new AnnoAttributeOverrides(array('id' => 'news_id', 'title' => 'news_title', 'description' => 'news_description', 'text' => 'news_text'))); } private $id; private $text; // getters / setters }
In diesem Beispiel haben wir das Beispiel aus dem Abschnitt Mapped Supperclass angepasst und über AnnoAttributeOverrides
alle Spaltennamen der Entity News
und ihrer Mapped Supperclass Teaser
überschrieben.
Die Tabelle von News
sieht neu folgendermassen aus:
CREATE TABLE `news` ( `news_id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `news_title` VARCHAR(50), `news_description` VARCHAR(255), `news_text` TEXT, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Du kannst AnnoAttributeOverrides
auch auf Extraklassen anwenden, indem du die entsprechende Embedded-Eigenschaften annotierst.
class Order extends ObjectAdapter { private static function _annos(AnnoInit $ai) { $ai->p('address', new AnnoEmbedded(Address::getClass()), new AnnoAttributeOverrides(array('forename' => 'bill_forename', 'surname' => 'bill_surname'))); $ai->p('deliveryAddress', new AnnoEmbedded(Address::getClass(), 'delivery_')); } private $id; private $productName; private $address; private $deliveryAddress; // getter / setter }
Hier haben wir das Beispiel aus dem Abschnitt Embedded angepasst und über AnnoAttributeOverrides
die Spaltennamen der Extraklasse Address
überschrieben.
Die Tabelle von Order
sieht neu folgendermassen aus:
CREATE TABLE `order` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `product_name` VARCHAR(50) NOT NULL, `bill_forename` VARCHAR(50) NOT NULL, `bill_surname` VARCHAR(50) NOT NULL, `delivery_forename` VARCHAR(50) DEFAULT NULL, `delivery_surname` VARCHAR(50) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;