Beziehungen
Das ORM übernimmt die Beziehungs-Typen 1:1, 1:n und n:m aus dem Konzept der relationalen Datenbanken. Dabei stehen aber nicht die Tabellen, sondern die Entities in Beziehung zueinander.
Beziehungen definieren
Gehen wir davon aus, dass du einen Blog entwickeln möchtest und dessen Artikel sollen kommentiert werden können. Dazu müssen die Entities BlogArticle
und Comment
miteinander in einer 1:n Beziehung stehen. Ein Artikel kann mehrere Kommentare enthalten und ein Kommentar ist immer nur einem Artikel zugeordnet. Deshalb soll die Entity Comment
auch eine Eigenschaft $blogArticle
vom Typ BlogArticle
enthalten.
Um Beziehungs-Eigenschaften zu markieren, stehen die Annotationen AnnoOneToOne
, AnnoManyToOne
, AnnoOneToMany
und AnnoManyToMany
zur Verfügung.
class Comment extends ObjectAdapter { private static function _annos(AnnoInit $ai) { $ai->p('blogArticle', new AnnoManyToOne(BlogArticle::getClass())); } private $id; private $author; private $text; private $blogArticle; public function getId() { return $this->id; } public function setAuthor($author) { $this->author = $author; } public function getAuthor() { return $this->author; } public function setText($text) { $this->text = $text; } public function getText() { return $this->text; } public function setBlogArticle(BlogArticle $blogArticle) { $this->blogArticle = $blogArticle; } /** * @return BlogArticle */ public function getBlogArticle() { return $this->blogArticle; } }
Da die Eigenschaft $blogArticle
aus der Sicht der Entity Comment
eine n:1 Beziehung zu BlogArticle
realisiert, annotieren wir $blogArticle
mit AnnoManyToOne
.
Soll die Beziehung bidirektional sein, benötigen wir eine weitere Eigenschaft BlogArticle::$comments
vom Typ Comment[]
. Als Collection-Typ für Beziehungs-Eigenschaften wird ausschliesslich ArrayObject
verwendet.
class BlogArticle extends ObjectAdapter { private static function _annos(AnnoInit $ai) { $ai->p('comments', new AnnoOneToMany(Comment::getClass(), 'article')); } private $id; private $title; private $text; private $comments; public function __construct($title = null) { $this->title = $title; } public function getId() { return $this->id; } public function getTitle() { return $this->title; } public function setTitle($title) { $this->title = $title; } public function getText() { return $this->text; } public function setText($text) { $this->text = $text; } public function getComments() { return $this->comments; } public function setComments(\ArrayObject $comments) { $this->comments = $comments; } }
Da die Eigenschaft $comments
aus der Sicht der Entity BlogArticle
eine 1:n Beziehung zu Comment
realisiert, annotieren wir $comments
mit AnnoOneToMany
.
Bei einer bidirektionale Beziehung gilt eine Eigenschaft als "Owner" (Besitzer). Die Gegenseite muss die "Owner"-Eigenschaft referenzieren. Dafür ist der zweite Parameter ($mappedBy
) von AnnoOneToOne
, AnnoOneToMany
und AnnoManyToMany
vorgesehen. AnnoManyToOne
enthält keinen $mappedBy
-Parameter und muss immer der "Owner" sein. Es werden nur die Änderungen von Beziehungs-Eigenschaften der "Owner"-Seite in die Datenbank übernommen.
Im oben stehenden Beispiel ist die Eigenschaft Comment::$blogArticle
der "Owner" dieser Beziehung. Möchtest du einen neuen Kommentar erstellen und diesen einem Artikel zuweisen, musst du die Beziehung über Comment::setBlogArticle()
herstellen.
$blogArticle = $em->find(BlogArticle::getClass(), $id); $newComment = new Comment(); $newComment->setAuthor('Author'); $newComment->setText('Lorem ipsum'); $newComment->setBlogArticle($blogArticle); $em->persist($newComment);
Änderungen an BlogArticle::$comments
werden ignoriert und nicht in die Datenbank übernommen. Die Beziehung käme über folgenden Code also nicht zu Stande:
$blogArticle->getComments()->append($newComment); // does not affect any changes
$mappedBy
zu referenzieren, sind die Beziehungs-Eigenschaften beider Seiten "Owner". Dies führt dazu, dass unabhänig voneinaner zwei Beziehungen hergestellt werden.Für BlogArticle
und Comment
kämen folgende Tabellen in Frage:
CREATE TABLE `blog_article` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `title` varchar(50) NOT NULL, `text` text, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `comment` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `author` varchar(32) DEFAULT NULL, `text` text, `blog_article_id` int(10) unsigned DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
OneToOne (1:1) / ManyToOne (n:1)
Das ORM assoziiert OneToOne- und ManyToOne-Eigenschaften (bei bidirektionalen Beziehungen nur die "Owner"-Eigenschaft) standardmässig mit einer entsprechenden Join-Spalte in der Tabelle der definierenden Entity. Du kannst den Namen der Join-Spalte über n2n\persistence\orm\annotation\AnnoJoinColumn
annotieren. Ansonsten wird der Namen der Join-Spalte anhand des Property-Namens automatisch generiert.
class Comment extends ObjectAdapter { private static function _annos(AnnoInit $ai) { $ai->p('blogArticle', new AnnoManyToOne(BlogArticle::getClass()), new AnnoJoinColumn('article_id')); } private $blogArticle; public function setBlogArticle(BlogArticle $blogArticle) { $this->blogArticle = $blogArticle; } /** * @return BlogArticle */ public function getBlogArticle() { return $this->blogArticle; } // rest of class body }
Die Eigenschaft $blogArticle
wird über die Join-Spalte "article_id" assoziiert. Hättest du diese nicht annotiert, wäre der Name der Join-Spalte standardmässig "blog_article_id".
null
als Wert. Beim Persistieren würde das ORM im oben stehenden Beispiel in diesem Fall auch null
in die Spalte "article_id" schreiben.Über die Annotation n2n\persistence\orm\annotation\AnnoJoinTable
könntest du diese beiden Beziehungs-Typen auch über eine Zwischentabelle realisieren.
OneToMany (1:n) / ManyToMany (n:m)
Für OneToMany- und ManyToMany-Eigenschaften (bei bidirektionalen Beziehungen nur für die "Owner"-Eigenschaft) sucht das ORM standardmässig eine Zwischentabelle. Du kannst die Namen der Zwischentabelle und deren Join-Spalten über n2n\persistence\orm\annotation\AnnoJoinTable
annotieren. Ansonsten werden die Namen automatisch generiert.
Zusätzlich kannst du für diese beiden Eigenschafts-Typen (egal ob "Owner" oder nicht) über die Annotation n2n\persistence\orm\annotation\AnnoOrderBy
die Reihenfolge der Entities in der Collection bestimmen.
class BlogArticle extends ObjectAdapter { private static function _annos(AnnoInit $ai) { $ai->p('categories', new AnnoManyToMany(Category::getClass()), new AnnoOrderBy(array('name' => 'ASC'))); } private $categories; /** * @return Category[] */ public function getCategories() { return $this->categories; } public function setCategories(\ArrayObject $categories) { $this->categories = $categories; } // rest of class body }
In diesem Beispiel realisieren wir für unsere Entity BlogArticle
eine n:m Beziehung zur Entity Category
. Dazu implementieren wir die ManyToMany-Eigenschaft BlogArticle::$blogArticle
. Beim Lesen aus der Datenbank versorgt das ORM diese Eigenschaft mit einem ArrayObject
, das Category
-Entities enthält, die nach Category::$name
sortiert sind.
Die Beziehung soll bidirektional sein, weshalb wir für die Entity Category
eine Eigenschaft $blogArticle
implementieren. Der "Owner" ist BlogArticle::$categories
.
class Category extends ObjectAdapter { private static function _annos(AnnoInit $ai) { $ai->p('blogArticles', new AnnoManyToMany(BlogArticle::getClass(), 'categories')); } private $id; private $name; private $blogArticles; public function __construct($name = null) { $this->name = $name; } public function getId() { return $this->id; } public function getName() { return $this->name; } public function setName($name) { $this->name = $name; } public function getBlogArticles() { return $this->blogArticles; } public function setBlogArticles(\ArrayObject $blogArticles) { $this->blogArticles = $blogArticles; } }
Für das oben stehende Beispiel wäre folgende Tabelle nötig (beachte besonders die Zwischentabelle "blog_article_categories"):
CREATE TABLE `blog_article` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `title` varchar(50) NOT NULL, `text` text, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `blog_article_categories` ( `blog_article_id` int(10) unsigned NOT NULL, `category_id` int(10) unsigned NOT NULL, PRIMARY KEY (`blog_article_id`,`category_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `category` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(32) NOT NULL PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Wie weiter oben beschrieben, kannst du die Namen der Zwischentabelle und deren Join-Spalten auch selbst bestimmen. Dazu annotierst du die "Owner"-Eigenschaft mit AnnoJoinTable
.
private static function _annos(AnnoInit $ai) { $ai->p('categories', new AnnoManyToMany(Category::getClass()), new AnnoJoinTable('junction_table', 'join_column', 'inverse_join_column'), new AnnoOrderBy(array('name' => 'ASC'))); }
OneToMany Join-Spalte
Du kannst für OneToMany-Eigenschaften (bei bidirektionalen Beziehungen nur für die "Owner") über AnnoJoinColumn
auch eine Join-Spalte annotieren. Diese wird in der Tabelle der Gegenseite gesucht. Auf diese Weise lässt sich auch die 1:n Beziehung zwischen BlogArticle
und Comment
realisieren.
class BlogArticle extends ObjectAdapter { private static function _annos(AnnoInit $ai) { $ai->p('comments', new AnnoOneToMany(Comment::getClass()), new AnnoJoinColumn('blog_article_id')); } private $comments; /** * @return Comment[] */ public function getComments() { return $this->comments; } public function setComments(\ArrayObject $comments) { $this->comments = $comments; } // rest of class body }
Im Gegensatz zum Beispiel weiter oben, ist die Eigenschaft BlogArticle::$comments
nun der "Owner".
class Comment extends ObjectAdapter { private static function _annos(AnnoInit $ai) { $ai->p('blogArticle', new AnnoManyToOne(BlogArticle::getClass(), 'comments')); } private $blogArticle; public function setBlogArticle(BlogArticle $blogArticle) { $this->blogArticle = $blogArticle; } /** * @return BlogArticle */ public function getBlogArticle() { return $this->blogArticle; } // rest of class body }
Die Tabellen bleiben dabei identisch:
CREATE TABLE `blog_article` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `title` VARCHAR(50) NOT NULL, `text` TEXT, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `comment` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `author` VARCHAR(32) DEFAULT NULL, `text` TEXT, `blog_article_id` INT(10) UNSIGNED DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Cascade
Über den Parameter $cascadeType
der Annotationen AnnoOneToOne
, AnnoOneToMany
, AnnoManyToOne
und AnnoManyToMany
kannst du definieren, welche [Andreas, Link]Operationen[/Link] an die annotierte Eigenschaft weitergegeben werden sollen. Folgende Typen stehen zur Verfügung:
n2n\persistence\orm\CascadeType::PERSIST
n2n\persistence\orm\CascadeType::MERGE
n2n\persistence\orm\CascadeType::REMOVE
n2n\persistence\orm\CascadeType::REFRESH
n2n\persistence\orm\CascadeType::DETACH
n2n\persistence\orm\CascadeType::ALL
Definierst du zum Beispiel CacscadeType::PERSIST
für die Eigenschaft BlogArticle::$comments
und persistierst eine Entity BlogArticle
mit EntityManager::persist()
, werden auch alle Entities Comment
persistiert, die zu diesem Zeitpunkt der Eigenschaft BlogArticle::$comments
zugewiesen sind.
class BlogArticle extends EntityAdapter { private static function _annos(AnnoInit $ai) { $ai->p('comments', new AnnoOneToMany(Comment::getClass(), 'blogArticle', CascadeType::PERSIST)); } private $comments; /** * @return Comment[] */ public function getComments() { return $this->comments; } public function setComments(\ArrayObject $comments) { $this->comments = $comments; } // rest of class body }
Wie im Artikel Lifecylce beschrieben, wird bei einem Flush-Ereignis auf jede Entity (mit Status MANAGED) eine Persist-Operation angewendet. Dies bedeutet nun, dass wir einem Artikel einen neuen Kommentar hinzufügen können, der beim Commit (ein Commit führt zu einem Flush-Ereignis) automatisch persistiert wird.
public function doNewComment(BlogArticleDao $blogArticleDao, $blogArticleId) { $this->beginTransaction(); $blogArticle = $blogArticleDao->getBlogArticleById($blogArticleId); if ($blogArticle === null) { throw new PageNotFoundException(); } $newComment = new Comment(); $newComment->setAuthor('Author'); $newComment->setText('Lorem ipsum'); $newComment->setBlogArticle($blogArticle); $blogArticle->getComments()->append($newComment); $this->commit(); }
Da wir für BlogArticle::$comments
CascadeType::PERSIST
annotiert haben, wird die neue Entity Comment
beim Commit automatisch persistiert. Da die Eigenschaft BlogArticle::$comments
aber nicht der "Owner" der Beziehung ist, müssen wir der neuen Entity Comment
über Comment::setBlogArticle()
dennoch die entsprechende Entity BlogArticle
zuweisen, damit die Beziehung zu Stande kommt.
Du kannst Cascade-Typen auch kombinieren:
class BlogArticle extends EntityAdapter { private static function _annos(AnnoInit $ai) { $ai->p('comments', new AnnoOneToMany(Comment::getClass(), 'blogArticle', CascadeType::PERSIST|CascadeType::REMOVE)); } // rest of class body }
Würdest du jetzt einen Artikel löschen (EntityManager::remove()
), würden auch alle seine Kommentare gelöscht.
Sollen alle Operationen an eine Eigenschaft weitergegeben werden, kannst du auch CascadeType::ALL
nutzen.
private static function _annos(AnnoInit $ai) { $ai->p('categories', new AnnoManyToMany(Category::getClass(), null, CascadeType::ALL)); }
Fetch Type
Über den Parameter $fetchType
der Annotationen AnnoOneToOne
, AnnoOneToMany
, AnnoManyToOne
und AnnoManyToMany
bestimmst du, wann die Entity / die Entities einer Eigenschaft aus der Datenbank geladen werden soll. Zur Auswahl stehen n2n\persistence\orm\FetchType::LAZY
und n2n\persistence\orm\FetchType::EAGER
. Standard ist bei allen Beziehungs-Typen FetchType::LAZY
.
class Comment extends EntityAdapter { private static function _annos(AnnoInit $ai) { $ai->p('blogArticle', new AnnoManyToOne(BlogArticle::getClass())); } private $blogArticle; public function setBlogArticle(BlogArticle $blogArticle) { $this->blogArticle = $blogArticle; } /** * @return BlogArticle */ public function getBlogArticle() { return $this->blogArticle; } // rest of class body }
In diesem Beispiel gilt für die Eigenschaft Comment::$blogArticle
der FetchType::LAZY
. Dies hat zur Folge, dass das ORM die Eigenschaften von BlogArticle
erst lädt, wenn das erste Mal darauf zugegriffen wird (ein SELECT
-Statement wird abgesetzt).
$comment = $em->find(Comment::getClass(), $commentId); $blogArticle = $comment->getBlogArticle(); var_dump($blogArticle->getTitle()); // BlogArticle properties get loaded here
In diesem Beispiel werden die Eigenschaften von BlogArticle
erst beim Aufruf von $blogArticle->getTitle()
geladen.
FetchType::LAZY
nur für Entities möglich, die keine Eigenschaften mit Sichtbarkeit public
haben. FetchType::LAZY
ist auch nicht für Entities nutzbar, die [Andreas, Link]Inheritance[/Andreas, Link] verwenden und Sub-Entities besitzen.Bei OneToMany- oder ManyToMany-Eigenschaften mit FetchType::LAZY
werden die Entities geladen, sobald das erste Mal auf Felder des Arrays (ArrayObject
) zugegriffen wird (zum Beispiel durch eine foreach
-Schleife).
$blogArticle = $em->find(BlogArticle::getClass(), $id); $categories = $blogArticle->getCategories(); foreach ($categories as $category) { // categories get loaded here test($category->getName()); }
Es werden dabei immer die Entities aller Felder geladen, auch wenn du nur auf ein einzelnes Feld zugreifen solltest (z. B. $comments[0]
).
Wählst du den FetchType::EAGER
, wird die Entity oder die Entities dieser Eigenschaft gleich nach dem Laden der definierenden Entity geladen (zusätzliches SELECT
-Statement wird abgesetzt).
class Comment extends EntityAdapter { private static function _annos(AnnoInit $ai) { $ai->p('blogArticle', new AnnoManyToOne(BlogArticle::getClass(), null, FetchType::EAGER)); } private $blogArticle; public function setBlogArticle(BlogArticle $blogArticle) { $this->blogArticle = $blogArticle; } /** * @return BlogArticle */ public function getBlogArticle() { return $this->blogArticle; } // rest of class body }
Da wir in diesem Beispiel für die Eigenschaft Comment::$blogArticle
den FetchType::EAGER
gewählt haben, werden die Eigenschaften von BlogArticle
gleich nach der Abfrage von Comment
geladen.
$comment = $em->find(Comment::getClass(), $commentId); // BlogArticle properties get loaded here $blogArticle = $comment->getBlogArticle(); var_dump($blogArticle->getTitle());
FetchType::EAGER
kann beim Arbeiten mit Transaktionen nützlich sein.
Fetch Join
Mit einem Fetch Join erzwingst du, dass Eigenschaften gleich bei der Abfrage (SELECT
-Statement wird um einen JOIN
erweitert), und nicht erst später mit einem zusätzlichen SELECT
-Statement, geladen werden. Dies ist auch möglich, wenn für sie FetchType::LAZY
gilt. Einen Fetch Join aktivierst du über den 4. Parameter der Methode Criteria::joinProperty()
.
$criteria = $em->createCriteria(); $criteria->select('c') ->from(Comment::getClass(), 'c') ->joinProperty('c.blogArticle', 'ba', null, true) ->where()->match('c.id', '=', $commentId);
Durch den Fetch Join wird in diesem Beispiel das Laden von Comment::$blogArticle
in die Abfrage integriert.
In NQL erreichst du dieses Verhalten über das Keyword JOIN FETCH
.
$criteria = $em->createNqlCriteria( 'SELECT c FROM Comment c JOIN FETCH c.blogArticle WHERE c.id = :commentId', array('commentId' => $commentId));
Orphan Removal
Über den letzten Parameter von AnnoOneToOne
und AnnoOneToMany
aktivierst du "Orphan Removal". Wird die Beziehung zu Entities aufgehoben, die von dieser Eigenschaft referenziert wurden, werden diese gelöscht. Dies geschieht zum Beispiel, wenn du eine OneToOne-Eigenschaft auf null
setzt oder du Entities aus dem ArrayObject
einer OneToMany-Eigenschaft entfernst.
class BlogArticle extends EntityAdapter { private static function _annos(AnnoInit $ai) { $ai->p('comments', new AnnoOneToMany(Comment::getClass(), 'blogArticle', null, null, true)); } private $comments; /** * @return Comment[] */ public function getComments() { return $this->comments; } public function setComments(\ArrayObject $comments) { $this->comments = $comments; } // rest of class body }
Entfernst du nun einen Kommentar aus einem Artikel, wird dieser Kommentar gelöscht.
public function doRemoveAllComments(BlogArticleDao $blogArticleDao, $blogArticleId) { $this->beginTransaction(); $blogArticle = $blogArticleDao->getBlogArticleById($blogArticleId); if ($blogArticle === null) { throw new PageNotFoundException(); } $blogArticle->getComments()->exchangeArray(array()); $this->commit(); }
In diesem Beispiel werden alle Kommentare eines Artikels gelöscht.
Beziehungen in Abfragen
Beziehungs-Eigenschaften lassen sich perfekt in Abfragen integrieren. Mit dem "."-Operator kannst du zum Beispiel "Joins" über Eigenschaften realisieren.
Beispiel 1 (WHERE)
In diesem Beispiel fragen wir alle Kommentare ab, die einem Artikel mit Titel $title
zugewiesen sind.
Criteria API
$criteria = $em->createSimpleCriteria(Comment::getClass(), array('blogArticle.title' => $title));
NQL
$criteria = $em->createNqlCriteria( 'SELECT c FROM Comment c WHERE c.blogArticle.title = :title', array('title' => $title));
Beispiel 2 (WHERE)
Für Vergleiche mit ToOne-Eigenschaften kannst du die Vergleichsoperatoren =
, !=
, IN
, NOT IN
verwenden. Die Variable $category
in folgendem Beispiel enthält ein Array von Comment
-Entities.
Criteria API
$criteria = $em->createCriteria(); $criteria->select('c') ->from(Comment::getClass(), 'c') ->where()->match('c.blogArticle', 'IN', $blogArticles);
NQL
$criteria = $em->createNqlCriteria( 'SELECT c FROM Comment c WHERE c.blogArticles IN (:blogArticles)', array('blogArticles' => $blogArticles));
Beispiel 3 (WHERE)
Für Vergleiche mit ToMany-Eigenschaften kannst du die Vergleichsoperatoren CONTAINS
und CONTAINS NOT
verwenden. Die Variable $category
in folgendem Beispiel enthält eine Entity Category
.
Criteria API
$criteria = $em->createCriteria(); $criteria->select('a') ->from(BlogArticle::getClass(), 'a') ->where()->match('a.categories', 'CONTAINS', $category);
NQL
$criteria = $em->createNqlCriteria( 'SELECT a FROM BlogArticle a WHERE a.categories CONTAINS :category', array('category' => $category));
Beispiel 4 (SUB SELECT)
Criteria API
$deniedTitle = 'Test'; $criteria = $em->createCriteria(); $criteria->select('c') ->from(Comment::getClass(), 'c') ->where()->match('c.blogArticle', 'NOT IN', $criteria->subCriteria() ->select('ba') ->from(BlogArticle::getClass(), 'ba') ->where()->match('ba.title', '=', $deniedTitle)->endWhere());
NQL
$deniedTitle = 'Test'; $criteria = $em->createNqlCriteria( 'SELECT c FROM Comment c WHERE c.blogArticle NOT IN ( SELECT ba FROM BlogArticle ba WHERE ba.title = :deniedTitle)', array('deniedTitle' => $deniedTitle));
Beziehungen in Embedded Extraklassen und Mapped Superclasses
Über n2n\persistence\orm\annotation\AnnoAttributeOverrides
(Artikel Attribte Overrides) ist es nicht möglich die Namen von Join-Spalten und Zwischentabellen zu überschreiben. Hierfür existiert die Annotation n2n\persistence\orm\annotation\AnnoAssociationOverrides
. Sie funktioniert gleich wie AnnoAttributeOverrides
, erwartet jedoch als ersten Parameter ein array
von AnnoJoinColumn
- und als zweiten Parameter ein array
von AnnoJoinTable
-Annotationen.
class Member extends ObjectAdapter { private static function _annos(AnnoInit $ai) { $ai->p('assignement', new AnnoEmbedded(Assignement::getClass()), new AnnoAssociationOverrides(array('assignementGroup' => new AnnoJoinColumn('group_id')))); } private $id; private $assignement; // rest of class body }
class Assignement extends ObjectAdapter { private static function _annos(AnnoInit $ai) { $ai->p('assignementGroup', new AnnoManyToOne(AssignementGroup::getClass())); } private $name; private $assignementGroup; // rest of class body }
Eine Beziehungs-Eigenschaft kann nur Beziehungen zu Entities (nicht zu Extraklassen) realisieren, auch wenn sich die Owner-Eigenschaft, wie in diesen Beispiel, in einer Extraklasse befindet. Achte im folgenden Code-Ausschnitt auf den Parameter $mappedBy
von AnnoOneToMany
.
class AssignementGroup extends ObjectAdapter { private static function _annos(AnnoInit $ai) { $ai->p('members', new AnnoOneToMany(Member::getClass(), 'assignement.assignementGroup')); } private $id; private $name; private $members; // rest of class body }
Zum oben stehenden Beispiel passen folgende Tabellen:
CREATE TABLE `member` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `name` VARCHAR(50) NOT NULL, `group_id` INT(10) UNSIGNED NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `assignement_group` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `name` VARCHAR(50) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;