web-dev-qa-db-fra.com

Utiliser EntityRepository :: findBy () avec des relations plusieurs-à-plusieurs conduira à une E_NOTICE dans Doctrine

Pour un projet Symfony2, j'ai dû créer une relation entre un article de blog et des plates-formes dites. Une plateforme définit un filtre spécifique en fonction du domaine que vous utilisez pour afficher le site. Par exemple: si vous rejoignez le site par url first-example.com, le site ne fournira que des articles de blog, qui sont connectés à cette plate-forme spécifique.

Pour ce faire, j'ai créé deux entités Post et Platform. Ensuite, je les ai cartographiés avec une relation plusieurs-à-plusieurs. J'essaie de récupérer des données via cette relation plusieurs-à-plusieurs à partir de la fonction intégrée findBy() dans 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 Est le référentiel correct pour l'entité Post et $platform Un objet Platform existant.
Quoi qu'il en soit: je finis par obtenir l'erreur suivante:

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

Est-il même possible de récupérer de cette façon des entités apparentées dans une relation plusieurs-à-plusieurs, ou suis-je obligé d'écrire ces fonctions par moi-même? La chose étrange est: Doctrine ne lancera aucune erreur comme: "Ce n'est pas possible.", Mais un E_NOTICE Interne. C'est pourquoi je tente de penser que cela devrait être possible, mais il me manque quelques points ici.

Dépouillés des parties intéressantes, les deux entités ressemblent à ceci.

<?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();
    }
}

Et bien sûr le fichier composer.json (aussi bien dépouillé sur les lignes pertinentes)

{
    [...]
    "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",
        [...]

    },
    [...]
}
31
devsheeep

C'est très possible, mais le Stock Doctrine Repository ne fonctionne pas de cette façon.

Vous avez deux options, selon votre contexte:

Écrivez une méthode personnalisée dans le référentiel.

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;
  }
}

Ou utilisez les méthodes getter par défaut dans l'objet plateforme.

$posts = $platform->getPosts();

Vous "vous êtes dépouillé des parties intéressantes" donc ce n'est pas évident si vous avez cette méthode mais elle est normalement faite sur

app/console doctrine:generate:entities
27
Lighthart

Une autre façon, peut-être un peu OO/nettoyant sans utiliser d'ID:

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

Un meilleur nom de méthode serait findPostsByPlatform

34
jhvaras

Cette question semble être un problème avec une relation ManyToMany que vous souhaitez BIDIRECTIONNELLE (et qui est maintenant UNIDIRECTRIONALE). Utilisez MappedBy pour créer la bidirectionnalité:

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

Pratique :

L'une de vos entités est OWNING SIDE, l'autre INVERSE SIDE. Dans votre exemple, l'entité nommée Post est propriétaire et l'entité nommée Platform est inversée.

Configuration du côté propriétaire:

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;
    }
}

Configuration INVERSE SIDE:

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

EXEMPLE de récupération d'un tableau d'entités, à partir de l'un des côtés:

$post->getPlatforms();
$platform->getPosts();
2
Vincent