Beziehungen

ORM

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.

  1. Beziehungen definieren
  2. Cascade
  3. Fetch Type
  4. Orphan Removal
  5. Beziehungen in Abfragen
  6. Beziehungen in Embedded Extraklassen und Mapped Superclasses

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
Vergisst du bei der Gegenseite einer bidirektionalen Beziehung die "Owner"-Eigenschaft über $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;
Für das automatische Generieren von Tabellen- und Spaltennamen wird die aktuell geltende Naming Strategy berücksichtigt. Sind die benötigten Tabellen nicht in gültiger Form vorhanden, wird eine ausführliche Exception geworfen. Auf Grund dieser Exception lassen sich Fehler sehr einfach erkennen, weshalb es meistens einfacher ist, die Tabellen erst beim Auftreten dieser Exception zu erstellen.

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

OneToOne- oder ManyToOne-Eigeschaften erlauben auch 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.

Bei OneToOne- und ManyToOne-Eigenschaften ist der 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;
« Transaktionen Erweiterte Abfragen »

Kommentare

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

Fragen