Doctrine 中的多对多关系
Many To Many relationship in Doctrine
我是 Symfony 的新手,我正在一个电影网站上工作,您可以在其中制作电影和 link 令人兴奋的电影演员。我已经声明了与 Doctrine 的关系,并且我拥有所有制作的电影,但我找不到显示与电影相关的演员的方法。学说已经让我成为了支点 table movie_actor
我的电影实体
<?php
namespace App\Entity;
use App\Repository\MovieRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use JetBrains\PhpStorm\Pure;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity(repositoryClass: MovieRepository::class)]
class Movie
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private $id;
#[ORM\Column(type: 'string', length: 255)]
private $title;
#[ORM\Column(type: 'integer')]
private $releaseYear;
#[ORM\Column(type:'string', length:255, nullable: true)]
/**
* @Assert\NotBlank
*/
private $description;
#[ORM\Column(type: 'string', length: 255)]
private $imagePath;
#[ORM\ManyToMany(targetEntity: Actor::class, inversedBy: 'movies')]
private $actors;
public function __construct()
{
$this->actors = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(?string $title): self
{
$this->title = $title;
return $this;
}
public function getReleaseYear(): ?int
{
return $this->releaseYear;
}
public function setReleaseYear(?int $releaseYear): self
{
$this->releaseYear = $releaseYear;
return $this;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(?string $description): self
{
$this->description = $description;
return $this;
}
public function getImagePath(): ?string
{
return $this->imagePath;
}
public function setImagePath(?string $imagePath): self
{
$this->imagePath = $imagePath;
return $this;
}
/**
* @return Collection<int, Actor>
*/
public function getActors(): Collection
{
return $this->actors;
}
public function addActor(Actor $actor): self
{
if (!$this->actors->contains($actor)) {
$this->actors[] = $actor;
}
return $this;
}
public function removeActor(Actor $actor): self
{
$this->actors->removeElement($actor);
return $this;
}
}
我的演员实体
<?php
namespace App\Entity;
use App\Repository\ActorRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: ActorRepository::class)]
class Actor
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private $id;
#[ORM\Column(type: 'string', length: 255)]
private $name;
#[ORM\ManyToMany(targetEntity: Movie::class, mappedBy: 'actors')]
private $movies;
public function __construct()
{
$this->movies = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
/**
* @return Collection<int, Movie>
*/
public function getMovies(): Collection
{
return $this->movies;
}
public function addMovie(Movie $movie): self
{
if (!$this->movies->contains($movie)) {
$this->movies[] = $movie;
$movie->addActor($this);
}
return $this;
}
public function removeMovie(Movie $movie): self
{
if ($this->movies->removeElement($movie)) {
$movie->removeActor($this);
}
return $this;
}
}
电影控制器
<?php
namespace App\Controller;
use App\Entity\Actor;
use App\Entity\Movie;
use App\Form\MovieFormType;
use App\Repository\MovieRepository;
use App\Repository\ActorRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class MoviesController extends AbstractController
{
private $em;
private $movieRepository;
public function __construct(EntityManagerInterface $em, MovieRepository $movieRepository,ActorRepository $actorRepository )
{
$this->em = $em;
$this->movieRepository = $movieRepository;
$this->actorRepository = $actorRepository;
}
#[Route('/movies', name: 'movies')]
public function index(): Response
{
$movies = $this->movieRepository->findAll();
$actors = $this->actorRepository->findAll();
return $this->render('movies/index.html.twig', [
'movies' => $movies,
'actors'=> $actors
]);
}
#[Route('/movies/create', name: 'create_movie')]
public function create(Request $request): Response
{
$movie = new Movie();
$form = $this->createForm(MovieFormType::class, $movie);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$newMovie = $form->getData();
$imagePath = $form->get('imagePath')->getData();
if($imagePath){
$newFileName = uniqid() . '.' . $imagePath->guessExtension();
try{
$imagePath->move(
$this->getParameter('kernel.project_dir') . '/public/uploads',
$newFileName
);
} catch(FileException $e){
return new Response($e->getMessage());
}
$newMovie->setImagePath('/uploads/' . $newFileName);
}
$this->em->persist($newMovie);
$this->em->flush();
return $this->redirectToRoute('movies');
}
return $this->render('movies/create.html.twig', [
'form' => $form->createView()
]);
}
#[Route('/movies/edit/{id}', name: 'edit_movie')]
public function edit($id, Request $request): Response
{
$this->checkLoggedInUser($id);
$movie = $this->movieRepository->find($id);
$form = $this->createForm(MovieFormType::class, $movie);
$form->handleRequest($request);
$imagePath = $form->get('imagePath')->getData();
if ($form->isSubmitted() && $form->isValid()) {
if ($imagePath) {
if ($movie->getImagePath() !== null) {
if (file_exists(
$this->getParameter('kernel.project_dir') . $movie->getImagePath()
)) {
$this->GetParameter('kernel.project_dir') . $movie->getImagePath();
}
$newFileName = uniqid() . '.' . $imagePath->guessExtension();
try {
$imagePath->move(
$this->getParameter('kernel.project_dir') . '/public/uploads/',
$newFileName
);
} catch (FileException $e) {
return new Response($e->getMessage());
}
$movie->setImagePath('/uploads/' . $newFileName);
$this->em->flush();
return $this->redirectToRoute('movies');
}
} else {
$movie->setTitle($form->get('title')->getData());
$movie->setReleaseYear($form->get('releaseYear')->getData());
$movie->setDescription($form->get('description')->getData());
$this->em->flush();
return $this->redirectToRoute('movies');
}
}
return $this->render('movies/edit.html.twig', [
'movie' => $movie,
'form' => $form->createView()
]);
}
#[Route('/movies/delete/{id}', name: 'delete_movie', methods: ['GET', 'DELETE'])]
public function delete($id): Response
{
$this->checkLoggedInUser($id);
$movie = $this->movieRepository->find($id);
$this->em->remove($movie);
$this->em->flush();
return $this->redirectToRoute('movies');
}
#[Route('/movies/{id}', name: 'show_movie', methods: ['GET'])]
public function show($id): Response
{
$movie = $this->movieRepository->find($id);
$actor = $this->actorRepository->find($id);
// $movie = Movie::find(6);
// dd($movie->actors);
// $actor = Actor::find(6);
// $actor->movies()->sync([5]);
// dd($actor->movies);
return $this->render('movies/show.html.twig', [
'movie' => $movie
]);
}
private function checkLoggedInUser($movieId) {
if($this->getUser() == null || $this->getUser()->getId() !== $movieId) {
return $this->redirectToRoute('movies');
}
}
}
如有任何帮助,我们将不胜感激
您可以使用 createQueryBuilder
在你的控制器上,你可以添加这个:
$qb = $entity_manager->createQueryBuilder();
$qb
->select('movie')
->from(Movie::class, 'movie')
->leftJoin('movie.actors', 'actors')
->addSelect('actors')
;
$movies = $qb->getQuery()->getResult();
并在您的模板上传递 $movies
。
查询中的 addSelect
方法允许 doctrine 识别 Actor 和 Movie 之间的多对多关系。
在您的模板上,您可以这样显示结果:
<ul>
{% for movie in movies %}
<li>{{ movie.title }}</li>
<ol>
{% for actor in movie.actors %}
<li>{{ actor.name }}</li>
{% endfor %}
</ol>
{% endfor %}
</ul>
我使用 movieRepostery 解决了我的问题,方法是将此代码添加到我的 public 函数显示中,我搜索的位置只是为了通过其 $id
显示一部电影
$movies = $this->movieRepository->find($id);
return $this->render('movies/show.html.twig', [
'movies' => $movies
]);
然后我在我的 show.html.twig 中使用了两个 for 循环,即:
{% for actor in movies.actors %}
<div class="xl:w-1/3 md:w-1/2 p-4 m-auto">
<a href="/actors/{{ actor.id }}">
<div class="bg-white p-6 rounded-lg">
<img class="lg:h-60 xl:h-56 md:h-64 sm:h-72 xs:h-72 h-72 rounded w-full object-cover object-center mb-6" src="{{ actor.imagePath }}">
<h3 class="tracking-widest text-indigo-500 text-lg font-medium title-font">{{ actor.name }}</h3>
<h2 class="text-lg text-gray-900 font-medium title-font mb-4">Description</h2>
<p class="leading-relaxed text-base truncate ...">{{ actor.description }}</p>
</div>
</a>
</div>
{% endfor %}
我使用了一个循环,因为一部电影有多个演员
我是 Symfony 的新手,我正在一个电影网站上工作,您可以在其中制作电影和 link 令人兴奋的电影演员。我已经声明了与 Doctrine 的关系,并且我拥有所有制作的电影,但我找不到显示与电影相关的演员的方法。学说已经让我成为了支点 table movie_actor
我的电影实体
<?php
namespace App\Entity;
use App\Repository\MovieRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use JetBrains\PhpStorm\Pure;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity(repositoryClass: MovieRepository::class)]
class Movie
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private $id;
#[ORM\Column(type: 'string', length: 255)]
private $title;
#[ORM\Column(type: 'integer')]
private $releaseYear;
#[ORM\Column(type:'string', length:255, nullable: true)]
/**
* @Assert\NotBlank
*/
private $description;
#[ORM\Column(type: 'string', length: 255)]
private $imagePath;
#[ORM\ManyToMany(targetEntity: Actor::class, inversedBy: 'movies')]
private $actors;
public function __construct()
{
$this->actors = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(?string $title): self
{
$this->title = $title;
return $this;
}
public function getReleaseYear(): ?int
{
return $this->releaseYear;
}
public function setReleaseYear(?int $releaseYear): self
{
$this->releaseYear = $releaseYear;
return $this;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(?string $description): self
{
$this->description = $description;
return $this;
}
public function getImagePath(): ?string
{
return $this->imagePath;
}
public function setImagePath(?string $imagePath): self
{
$this->imagePath = $imagePath;
return $this;
}
/**
* @return Collection<int, Actor>
*/
public function getActors(): Collection
{
return $this->actors;
}
public function addActor(Actor $actor): self
{
if (!$this->actors->contains($actor)) {
$this->actors[] = $actor;
}
return $this;
}
public function removeActor(Actor $actor): self
{
$this->actors->removeElement($actor);
return $this;
}
}
我的演员实体
<?php
namespace App\Entity;
use App\Repository\ActorRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: ActorRepository::class)]
class Actor
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private $id;
#[ORM\Column(type: 'string', length: 255)]
private $name;
#[ORM\ManyToMany(targetEntity: Movie::class, mappedBy: 'actors')]
private $movies;
public function __construct()
{
$this->movies = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
/**
* @return Collection<int, Movie>
*/
public function getMovies(): Collection
{
return $this->movies;
}
public function addMovie(Movie $movie): self
{
if (!$this->movies->contains($movie)) {
$this->movies[] = $movie;
$movie->addActor($this);
}
return $this;
}
public function removeMovie(Movie $movie): self
{
if ($this->movies->removeElement($movie)) {
$movie->removeActor($this);
}
return $this;
}
}
电影控制器
<?php
namespace App\Controller;
use App\Entity\Actor;
use App\Entity\Movie;
use App\Form\MovieFormType;
use App\Repository\MovieRepository;
use App\Repository\ActorRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class MoviesController extends AbstractController
{
private $em;
private $movieRepository;
public function __construct(EntityManagerInterface $em, MovieRepository $movieRepository,ActorRepository $actorRepository )
{
$this->em = $em;
$this->movieRepository = $movieRepository;
$this->actorRepository = $actorRepository;
}
#[Route('/movies', name: 'movies')]
public function index(): Response
{
$movies = $this->movieRepository->findAll();
$actors = $this->actorRepository->findAll();
return $this->render('movies/index.html.twig', [
'movies' => $movies,
'actors'=> $actors
]);
}
#[Route('/movies/create', name: 'create_movie')]
public function create(Request $request): Response
{
$movie = new Movie();
$form = $this->createForm(MovieFormType::class, $movie);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$newMovie = $form->getData();
$imagePath = $form->get('imagePath')->getData();
if($imagePath){
$newFileName = uniqid() . '.' . $imagePath->guessExtension();
try{
$imagePath->move(
$this->getParameter('kernel.project_dir') . '/public/uploads',
$newFileName
);
} catch(FileException $e){
return new Response($e->getMessage());
}
$newMovie->setImagePath('/uploads/' . $newFileName);
}
$this->em->persist($newMovie);
$this->em->flush();
return $this->redirectToRoute('movies');
}
return $this->render('movies/create.html.twig', [
'form' => $form->createView()
]);
}
#[Route('/movies/edit/{id}', name: 'edit_movie')]
public function edit($id, Request $request): Response
{
$this->checkLoggedInUser($id);
$movie = $this->movieRepository->find($id);
$form = $this->createForm(MovieFormType::class, $movie);
$form->handleRequest($request);
$imagePath = $form->get('imagePath')->getData();
if ($form->isSubmitted() && $form->isValid()) {
if ($imagePath) {
if ($movie->getImagePath() !== null) {
if (file_exists(
$this->getParameter('kernel.project_dir') . $movie->getImagePath()
)) {
$this->GetParameter('kernel.project_dir') . $movie->getImagePath();
}
$newFileName = uniqid() . '.' . $imagePath->guessExtension();
try {
$imagePath->move(
$this->getParameter('kernel.project_dir') . '/public/uploads/',
$newFileName
);
} catch (FileException $e) {
return new Response($e->getMessage());
}
$movie->setImagePath('/uploads/' . $newFileName);
$this->em->flush();
return $this->redirectToRoute('movies');
}
} else {
$movie->setTitle($form->get('title')->getData());
$movie->setReleaseYear($form->get('releaseYear')->getData());
$movie->setDescription($form->get('description')->getData());
$this->em->flush();
return $this->redirectToRoute('movies');
}
}
return $this->render('movies/edit.html.twig', [
'movie' => $movie,
'form' => $form->createView()
]);
}
#[Route('/movies/delete/{id}', name: 'delete_movie', methods: ['GET', 'DELETE'])]
public function delete($id): Response
{
$this->checkLoggedInUser($id);
$movie = $this->movieRepository->find($id);
$this->em->remove($movie);
$this->em->flush();
return $this->redirectToRoute('movies');
}
#[Route('/movies/{id}', name: 'show_movie', methods: ['GET'])]
public function show($id): Response
{
$movie = $this->movieRepository->find($id);
$actor = $this->actorRepository->find($id);
// $movie = Movie::find(6);
// dd($movie->actors);
// $actor = Actor::find(6);
// $actor->movies()->sync([5]);
// dd($actor->movies);
return $this->render('movies/show.html.twig', [
'movie' => $movie
]);
}
private function checkLoggedInUser($movieId) {
if($this->getUser() == null || $this->getUser()->getId() !== $movieId) {
return $this->redirectToRoute('movies');
}
}
}
如有任何帮助,我们将不胜感激
您可以使用 createQueryBuilder
在你的控制器上,你可以添加这个:
$qb = $entity_manager->createQueryBuilder();
$qb
->select('movie')
->from(Movie::class, 'movie')
->leftJoin('movie.actors', 'actors')
->addSelect('actors')
;
$movies = $qb->getQuery()->getResult();
并在您的模板上传递 $movies
。
查询中的 addSelect
方法允许 doctrine 识别 Actor 和 Movie 之间的多对多关系。
在您的模板上,您可以这样显示结果:
<ul>
{% for movie in movies %}
<li>{{ movie.title }}</li>
<ol>
{% for actor in movie.actors %}
<li>{{ actor.name }}</li>
{% endfor %}
</ol>
{% endfor %}
</ul>
我使用 movieRepostery 解决了我的问题,方法是将此代码添加到我的 public 函数显示中,我搜索的位置只是为了通过其 $id
显示一部电影 $movies = $this->movieRepository->find($id);
return $this->render('movies/show.html.twig', [
'movies' => $movies
]);
然后我在我的 show.html.twig 中使用了两个 for 循环,即:
{% for actor in movies.actors %}
<div class="xl:w-1/3 md:w-1/2 p-4 m-auto">
<a href="/actors/{{ actor.id }}">
<div class="bg-white p-6 rounded-lg">
<img class="lg:h-60 xl:h-56 md:h-64 sm:h-72 xs:h-72 h-72 rounded w-full object-cover object-center mb-6" src="{{ actor.imagePath }}">
<h3 class="tracking-widest text-indigo-500 text-lg font-medium title-font">{{ actor.name }}</h3>
<h2 class="text-lg text-gray-900 font-medium title-font mb-4">Description</h2>
<p class="leading-relaxed text-base truncate ...">{{ actor.description }}</p>
</div>
</a>
</div>
{% endfor %}
我使用了一个循环,因为一部电影有多个演员