2012-11-12 12 views
26

Dla projektu Symfony2 musiałem stworzyć relację między postem na blogu a tzw. Platformami. Platforma definiuje określony filtr na podstawie domeny używanej do przeglądania witryny. Na przykład: jeśli dołączysz do witryny przez adres url first-example.com, witryna będzie udostępniać tylko posty na blogu, które są połączone z tą konkretną platformą.Używanie EntityRepository :: findBy() z wieloma relacjami prowadzi do E_NOTICE w Doctrine

W tym celu utworzyłem dwie jednostki: Post i Platform. Potem zmapowałem ich razem z relacją wiele do wielu. Próbuję pobrać dane za pośrednictwem relacji Wiele do wielu z wbudowanej funkcji findBy() w Doctrines 'EntityRepository.

// every one of these methods will throw the same error 
$posts = $postRepo->findBy(array('platforms' => array($platform))); 
$posts = $postRepo->findByPlatforms($platform); 
$posts = $postRepo->findByPlatforms(array($platform)); 

$postRepo Gdzie jest poprawne repozytorium dla podmiotu Post i $platform istniejący Platform obiektu.
Tak czy inaczej: skończę otrzymuję następujący błąd:

ErrorException: Notice: Undefined index: joinColumns in [...]/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php line 1495 

[...]/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php:1495 
[...]/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php:1452 
[...]/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php:1525 
[...]/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php:1018 
[...]/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php:842 
[...]/vendor/doctrine/orm/lib/Doctrine/ORM/EntityRepository.php:157 
[...]/src/Foobar/BlogBundle/Tests/ORM/PostTest.php:102 

Czy to w ogóle możliwe, aby odzyskać związanych z udziałów w jednostkach w wiele do wielu relacji ten sposób, czy jestem zmuszony pisać te funkcje przez siebie? Dziwne jest to, że: Doctrine nie rzuci żadnego błędu w stylu: "To niemożliwe", ale wewnętrznego: E_NOTICE. To dlatego myślę, że to powinno być możliwe, ale brakuje mi tutaj pewnych punktów.

Pozbawieni interesujących części, dwie Jednostki wyglądają tak.

<?php 

namespace Foobar\CommunityBundle\Entity; 

use Doctrine\Common\Collections\ArrayCollection; 
use Doctrine\ORM\Mapping as ORM; 

// [...] other namespace stuff 

/** 
* @ORM\Entity(repositoryClass="Foobar\CommunityBundle\Entity\Repository\PlatformRepository") 
* @ORM\Table(name="platforms") 
*/ 
class Platform 
{ 
    /** 
    * @ORM\Id 
    * @ORM\Column(type="integer") 
    * @ORM\GeneratedValue(strategy="AUTO") 
    */ 
    protected $id; 

    // [...] other field stuff 
} 
<?php 

namespace Foobar\BlogBundle\Entity; 

use Doctrine\Common\Collections\ArrayCollection; 
use Doctrine\ORM\Mapping as ORM; 

// [...] other namespace stuff 

/** 
* @ORM\Entity(repositoryClass="Foobar\BlogBundle\Entity\Repository\PostRepository") 
* @ORM\Table(name="posts") 
*/ 
class Post implements Likeable, Commentable, Taggable, PlatformAware 
{ 
    /** 
    * @ORM\Id 
    * @ORM\Column(type="integer") 
    * @ORM\GeneratedValue(strategy="AUTO") 
    */ 
    protected $id; 

    /** 
    * @ORM\ManyToMany(targetEntity="Foobar\CommunityBundle\Entity\Platform", cascade={"persist"}) 
    * @ORM\JoinTable(name="map_post_platform", 
    *  joinColumns={@ORM\JoinColumn(name="post_id", referencedColumnName="id")}, 
    *  inverseJoinColumns={@ORM\JoinColumn(name="platform_id", referencedColumnName="id")} 
    *  ) 
    */ 
    protected $platforms; 

    // [...] other fields 

    /** 
    * Constructor 
    */ 
    public function __construct() 
    { 
     // [...] 
     $this->platforms = new ArrayCollection(); 
    } 
} 

I oczywiście plików composer.json (jak również okrojona do odpowiednich linii)

{ 
    [...] 
    "require": { 
     "php": ">=5.3.3", 
     "symfony/symfony": "2.1.*", 
     "doctrine/orm": ">=2.2.3,<2.4-dev", 
     "doctrine/doctrine-bundle": "1.0.*", 
     "doctrine/doctrine-fixtures-bundle": "dev-master", 
     [...] 

    }, 
    [...] 
} 

Odpowiedz

19

Jest bardzo możliwe, ale Stock Doktryna Repository nie działa w ten sposób, .

Masz dwie opcje, w zależności od kontekstu:

napisać metodę niestandardowego w repozytorium.

class PostRepository extends EntityRepository 
{ 
    public function getPosts($id) 
    { 
    $qb = $this->createQueryBuilder('p'); 
    $qb->join('p.platform', 'f') 
     ->where($qb->expr()->eq('f.id', $id)); 
    return $qb; 
    } 
} 

Lub użyj domyślnych metod gettera w obiekcie platformy.

$posts = $platform->getPosts(); 

You „okrojona do interesujących części”, więc nie jest to oczywiste, jeśli masz tę metodę, ale jest zwykle wykonana na

app/console doctrine:generate:entities 
+0

Dziękuję za odpowiedź. Tylko mały dodatek. Twoja pierwsza metoda polega na pisaniu własnej funkcji w niestandardowym repozytorium. Być może wyraziłem się niejasno, ale próbowałem pobrać powiązane obiekty za pomocą wbudowanej funkcji 'find *()' Doctrine. Druga metoda nie działa, ponieważ mam jednokierunkowe powiązanie. W związku z tym nie ma właściwości '$ posts' na' Platformie', a co za tym idzie, żadnych programów pobierających i ustawiających. Niemniej twoja odpowiedź bardzo mi pomaga, ponieważ teraz jestem bardziej pewny, że nie można po prostu użyć wbudowanych sposobów, aby uzyskać filtrowane powiązania wielu do wielu. – devsheeep

1

na to pytanie wydaje się być problem z relacji ManyToMany którego chcę BIDROFICJALNĄ (i jest teraz UNIDIRECTRIONAL). Użyj MappedBy stworzyć dwukierunkowości:

http://doctrine-orm.readthedocs.org/en/latest/reference/association-mapping.html#many-to-many-bidirectional

Praktyczna:

Jeden z podmiotów jest POSIADAJĄCY boku, drugi INVERSE SIDE. W Twojej przykładowej jednostce o nazwie Post jest po stronie, a jednostka o nazwie Platforma jest stroną odwrotną.

POSIADAJĄCY konfiguracji BOCZNA:

Class Post { 
    ...  
    /** 
    * @ManyToMany(targetEntity="Platform") 
    * @JoinTable(name="map_post_platform", 
    *  joinColumns={@JoinColumn(name="post_id", referencedColumnName="id")}, 
    *  inverseJoinColumns={@JoinColumn(name="platform_id", referencedColumnName="id", unique=true)}) 
    **/ 
    protected $platforms; 
    ... 
    public function Post() { 
     $this->platforms= new ArrayCollection(); 
    } 
    ... 
    public function assignToPlatform($platform) { 
     $this->platforms[] = $platform; 
    } 
    ... 
    public function getPlatforms() { 
     return $this->platforms; 
    } 
} 

ODWRACANIE BOCZNA Ustawienia:

Class Platform { 
    ... 
    /** 
    * @ManyToMany(targetEntity="Post", mappedBy="platforms") 
    **/ 
    protected $posts; 
    ... 
    public function Platform() { 
     $this->posts= new ArrayCollection(); 
    } 
    ... 
    public function getPosts() 
    { 
     return $this->posts; 
    } 
} 

Przykład pobierania szereg jednostek, począwszy od jednego z boków:

$post->getPlatforms(); 
$platform->getPosts(); 
+0

to jest właściwe rozwiązanie do tego celu. –

28

inny sposób, może trochę OO/cleaner bez używania ID:

public function getPosts(Platform $platform) 
{ 
    $qb = $this->createQueryBuilder("p") 
     ->where(':platform MEMBER OF p.platforms') 
     ->setParameters(array('platform' => $platform)) 
    ; 
    return $qb->getQuery()->getResult(); 
} 

Lepszym nazwa metody byłoby findPostsByPlatform

+1

Awesome, nie widziałem tego w Doctrine docs. A co z wydajnością? – Nevertheless

+2

Jeśli zależy Ci na wydajności, nie używaj QueryBuilder ani ORM. – jhvaras

+1

Zgadzam się, że twoje imię jest bardziej opisowe, ale wolę unikać "szukania" w niestandardowych nazwach z powodu magicznych funkcji Doctrine, które zaczynają się od "find". getPostsByPlatform(). – Lighthart

Powiązane problemy