I'm new to Symfony and I'm working on a movie website where you can create movies and link exciting actors to the movies. I already declared the relationship with Doctrine and I have all the movies that are created, but I couldn't find a way to show the actor that is related to the movie. Doctrine already made me a pivot table movie_actor
My Movie Entity
<?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;
}
}
My Actor Entity
<?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;
}
}
MovieController
<?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');
}
}
}
Any help would be appreciated
CodePudding user response:
You should create an intermediate Entity (Table) like actorMovie
or movieActor
that contains integer variables referencing the ID from both actor
and movie
(or a whole instance of that Entity).
Those integer or instance variables would be variables in a format similar to this (whole instance):
/**
* ActorMovie
*
* @ORM\Table(name="actor_movie", indexes={@ORM\Index(name="actor_id", columns={"actor_id"}), @ORM\Index(name="movie_id", columns={"movie_id"})
* @ORM\Entity
*/
class ActorMovie {
/**
* @var Actor
*
* @ORM\ManyToOne(targetEntity="App\Entity\Actor")
* @ORM\JoinColumns({
* @ORM\JoinColumn(name="actor_id", referencedColumnName="id")
* })
*/
private Actor $actor;
/**
* @var Movie
*
* @ORM\ManyToOne(targetEntity="App\Entity\Movie")
* @ORM\JoinColumns({
* @ORM\JoinColumn(name="movie_id", referencedColumnName="id")
* })
*/
private Movie $movie;
/* Getters & Setters */
public function getActor(): Actor {
return $this->actor;
}
public function setActor(Actor $actor): void {
$this->actorId = $actor;
}
public function getMovie(): Movie{
return $this->movie;
}
public function setMovie(Movie $movie): void {
$this->movie = $movie;
}
}
I typically pass a whole instance of that Entity rather than just the ID, because then in PHPMyAdmin it appears related and finally it makes it easier to me, but I think you can pass only the ID
(integer).
CodePudding user response:
You can do this with createQueryBuilder
On your controller, you can add this :
$qb = $entity_manager->createQueryBuilder();
$qb
->select('movie')
->from(Movie::class, 'movie')
->leftJoin('movie.actors', 'actors')
->addSelect('actors')
;
$movies = $qb->getQuery()->getResult();
And pass $movies
on your template.
The addSelect
method on the query allows doctrine to identify that is a a many to many relationship between Actor and Movie.
On your template, you can show the result like this :
<ul>
{% for movie in movies %}
<li>{{ movie.title }}</li>
<ol>
{% for actor in movie.actors %}
<li>{{ actor.name }}</li>
{% endfor %}
</ol>
{% endfor %}
</ul>