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));
Où $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",
[...]
},
[...]
}
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
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
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é:
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();