Bonjour amis développeurs !
Je suis grand débutant sur symfony. Je suis un tutoriel d'OpenClassroom pour apprendre tout en me guidant dans mon projet, et cela fait plusieurs jours que je tourne en rond autour de mon soucis. J'ai eu beau chercher sur beaucoup de forums, mon problème est assez pointus et il m'a été difficile de trouver des topics pouvant m'aider (d'où ce titre de sujet assez peu explicite...). Je suis sûr que la nature de mon problème est bénigne ou tout simplement dûe à une mauvaise compréhension/utilisation du framework, mais toute aide et la bienvenue.
Voici mon problème en tâchant d'être le plus clair possible:
Contexte:
Mon projet est de créer un site pour un groupe d'amis qui proposent des tests et émissions de jeux vidéos sur youtube.
L'erreur:
An exception occurred while executing 'INSERT INTO image (name, extension, videoGame_id) VALUES (?, ?, ?)' with params ["burnoutparadise.jpg", "jpeg", null]:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'videoGame_id' cannot be null
Localisé dans le l'action createAction de mon controlleur VideoGameController:
/**
* Creates a new VideoGame entity.
*
* @Route("/", name="videogame_create")
* @Method("POST")
* @Template("NSTestsBundle:VideoGame:new.html.twig")
*/
public function createAction(Request $request)
{
$entity = new VideoGame();
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
if ($form->isValid()){
$em = $this->getDoctrine()->getManager();
var_dump($entity->getImages());
// ******* ICI **********
$em->persist($entity);
var_dump($entity->getImages()); die();
$em->flush();
return $this->redirect($this->generateUrl('videogame_show', array('id' => $entity->getId())));
}
return array(
'entity' => $entity,
'form' => $form->createView(),
);
}
Avant la persistance de l'objet image, var_dump($entity->getImages()); me donne:
object(Doctrine\Common\Collections\ArrayCollection)[301]
private '_elements' =>
array (size=1)
0 =>
object(NS\HomeBundle\Entity\Image)[1051]
private 'videoGame' => null
private 'id' => null
private 'name' => string 'burnout' (length=7)
private 'extension' => null
private 'file' =>
object(Symfony\Component\HttpFoundation\File\UploadedFile)[13]
...
private 'tempFilename' => null
Après persistance:
object(Doctrine\Common\Collections\ArrayCollection)[301]
private '_elements' =>
array (size=1)
0 =>
object(NS\HomeBundle\Entity\Image)[1051]
private 'videoGame' => null
private 'id' => null
private 'name' => string 'burnoutparadise.jpg' (length=19)
private 'extension' => string 'jpeg' (length=4)
private 'file' =>
object(Symfony\Component\HttpFoundation\File\UploadedFile)[13]
...
private 'tempFilename' => null
Pour résumer:
Mon objet Image imbriqué dans l'objet Videogame ne récupère pas l'ID de VideoGame du coup j'ai une erreur sql lors de la persistance...
But de ma manoeuvre:
Persister une entité VideoGame possédant plusieurs jointures, en particulier une jointure OneToMany avec plusieurs entités Image.
L'entité VideoGame:
<?php
namespace NS\TestsBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* VideoGame
*
* @ORM\Table()
* @ORM\Entity(repositoryClass="NS\TestsBundle\Entity\VideoGameRepository")
*/
class VideoGame
{
/**
* @ORM\OneToMany(targetEntity="NS\TestsBundle\Entity\Test", mappedBy="videoGame", cascade={"persist"})
*/
private $tests;
/**
* @var integer
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var \DateTime
*
* @ORM\Column(name="date_release", type="datetime")
*/
private $dateRelease;
/**
* @var \DateTime
*
* @ORM\Column(name="date_creation", type="datetime")
*/
private $dateCreation;
/**
* @var \DateTime
*
* @ORM\Column(name="date_edit", type="datetime", nullable=true)
*/
private $dateEdit;
/**
* @var string
*
* @ORM\Column(name="name", type="string", length=255, unique=true)
*/
private $name;
/**
* @ORM\ManyToMany(targetEntity="NS\TestsBundle\Entity\Genre", inversedBy="videoGames", cascade={"persist"})
* @ORM\JoinColumn(nullable=false)
*/
private $genres;
/**
* @ORM\ManyToOne(targetEntity="NS\TestsBundle\Entity\Engine", inversedBy="videoGames", cascade={"persist"})
* @ORM\JoinColumn(nullable=false)
*/
private $engine;
/**
* @ORM\ManyToMany(targetEntity="NS\TestsBundle\Entity\Platform", inversedBy="videoGames", cascade={"persist"})
* @ORM\JoinColumn(nullable=false)
*/
private $platforms;
/**
* @ORM\ManyToMany(targetEntity="NS\TestsBundle\Entity\Serie", inversedBy="videoGames", cascade={"persist"})
* @ORM\JoinColumn(nullable=false)
*/
private $series;
/**
* @ORM\OneToMany(targetEntity="NS\HomeBundle\Entity\Image", mappedBy="videoGame", cascade={"persist", "remove"})
* @ORM\JoinColumn(nullable=false)
*/
private $images;
/**
* @ORM\ManyToMany(targetEntity="NS\TestsBundle\Entity\Studio", inversedBy="videoGames", cascade={"persist"})
* @ORM\JoinColumn(nullable=false)
*/
private $studios;
/**
* @ORM\ManyToOne(targetEntity="NS\TestsBundle\Entity\Publisher", inversedBy="videoGames", cascade={"persist"})
* @ORM\JoinColumn(nullable=false)
*/
private $publisher;
public function __construct()
{
$this->dateRelease = new \Datetime();
$this->dateCreation = new \Datetime();
$this->dateEdit = NULL;
$this->genres = new ArrayCollection();
$this->platforms = new ArrayCollection();
$this->series = new ArrayCollection();
$this->images = new ArrayCollection();
$this->studios = new ArrayCollection();
}
public function __toString()
{
return $this->name;
}
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set dateRelease
*
* @param \DateTime $dateRelease
* @return \DateTime
*/
public function setDateRelease($dateRelease)
{
$this->dateRelease = $dateRelease;
return $this;
}
/**
* Get dateRelease
*
* @return \DateTime
*/
public function getDateRelease()
{
return $this->dateRelease;
}
/**
* Set dateCreation
*
* @param \DateTime $dateCreation
* @return Test
*/
public function setDateCreation($dateCreation)
{
$this->dateCreation = $dateCreation;
return $this;
}
/**
* Get dateCreation
*
* @return \DateTime
*/
public function getDateCreation()
{
return $this->dateCreation;
}
/**
* Set dateEdit
*
* @param \DateTime $dateEdit
* @return Test
*/
public function setDateEdit($dateEdit)
{
$this->dateEdit = $dateEdit;
return $this;
}
/**
* Get dateEdit
*
* @return \DateTime
*/
public function getDateEdit()
{
return $this->dateEdit;
}
/**
* Set name
*
* @param string $name
* @return string
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* @return string
*/
public function getName()
{
return $this->name;
}
public function addGenre(Genre $genre)
{
$this->genres[] = $genre;
$genre->addVideoGame($this);
return $this;
}
public function removeGenre(Genre $genre)
{
$this->genres->removeElement($genre);
}
public function getGenres()
{
return $this->genres;
}
/**
* Set engine
*
* @param Engine $engine
* @return Engine
*/
public function setEngine(Engine $engine)
{
$this->engine = $engine;
return $this;
}
/**
* Get engine
*
* @return Engine
*/
public function getEngine()
{
return $this->engine;
}
/**
* Set publisher
*
* @param Publisher $publisher
* @return Publisher
*/
public function setPublisher(Publisher $publisher)
{
$this->publisher = $publisher;
return $this;
}
/**
* Get publisher
*
* @return Publisher
*/
public function getPublisher()
{
return $this->publisher;
}
public function addPlatform(Platform $platform)
{
$this->platforms[] = $platform;
$platform->addVideoGame($this);
return $this;
}
public function removePlatform(Platform $platform)
{
$this->platforms->removeElement($platform);
}
public function getPlatforms()
{
return $this->platforms;
}
public function addSerie(Serie $serie)
{
$this->series[] = $serie;
$serie->addVideoGame($this);
return $this;
}
public function removeSerie(Serie $serie)
{
$this->series->removeElement($serie);
}
public function getSeries()
{
return $this->series;
}
public function addImage(Image $image)
{
$image->setVideoGame($this);
$this->images[] = $image;
return $this;
}
public function removeImage(Image $image)
{
$this->images->removeElement($image);
}
public function getImages()
{
return $this->images;
}
public function addStudio(Studio $studio)
{
$this->studios[] = $studio;
$studio->addVideoGame($this);
return $this;
}
public function removeStudio(Studio $studio)
{
$this->studios->removeElement($studio);
}
public function getStudios()
{
return $this->studios;
}
}
L'entité Image:
<?php
namespace NS\HomeBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* Image
*
* @ORM\Table(name="image")
* @ORM\Entity
* @ORM\HasLifecycleCallbacks
*/
class Image
{
/**
* @ORM\ManyToOne(targetEntity="NS\TestsBundle\Entity\VideoGame", inversedBy="images")
* @ORM\JoinColumn(nullable=false)
*/
private $videoGame;
/**
* @var integer
*
* @ORM\Column(name="id", type="integer", nullable=false)
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="name", type="string", length=50, nullable=false)
*/
private $name;
/**
* @var string
*
* @ORM\Column(name="extension", type="string", length=255, nullable=false)
*/
private $extension;
private $file;
private $tempFilename;
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* @param string $name
* @return Country
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Set extension
*
* @param string $extension
* @return Country
*/
public function setExtension($extension)
{
$this->extension = $extension;
return $this;
}
/**
* Get extension
*
* @return string
*/
public function getExtension()
{
return $this->extension;
}
public function getFile()
{
return $this->file;
}
// On ajoute cet attribut pour y stocker le nom du fichier temporairement
// On modifie le setter de File, pour prendre en compte l'upload d'un fichier lorsqu'il en existe déjà un autre
public function setFile(UploadedFile $file)
{
$this->file = $file;
// On vérifie si on avait déjà un fichier pour cette entité
if (null !== $this->extension) {
// On sauvegarde l'extension du fichier pour le supprimer plus tard
$this->tempFilename = $this->extension;
// On réinitialise les valeurs des attributs extension et name
$this->extension = null;
$this->name = null;
}
}
/**
* @ORM\PrePersist()
* @ORM\PreUpdate()
*/
public function preUpload()
{
// Si jamais il n'y a pas de fichier (champ facultatif)
if (null === $this->file) {
return;
}
$this->extension = $this->file->guessExtension();
$this->name = $this->file->getClientOriginalName();
}
/**
* @ORM\PostPersist()
* @ORM\PostUpdate()
*/
public function upload()
{
// Si jamais il n'y a pas de fichier (champ facultatif)
if (null === $this->file) {
return;
}
// Si on avait un ancien fichier, on le supprime
if (null !== $this->tempFilename) {
$oldFile = $this->getUploadRootDir().'/'.$this->id.'.'.$this->tempFilename;
if (file_exists($oldFile)) {
unlink($oldFile);
}
}
// On déplace le fichier envoyé dans le répertoire de notre choix
$this->file->move(
$this->getUploadRootDir(), // Le répertoire de destination
$this->id.'.'.$this->extension // Le nom du fichier à créer, ici « id.extension »
);
}
/**
* @ORM\PreRemove()
*/
public function preRemoveUpload()
{
// On sauvegarde temporairement le nom du fichier, car il dépend de l'id
$this->tempFilename = $this->getUploadRootDir().'/'.$this->id.'.'.$this->extension;
}
/**
* @ORM\PostRemove()
*/
public function removeUpload()
{
// En PostRemove, on n'a pas accès à l'id, on utilise notre nom sauvegardé
if (file_exists($this->tempFilename)) {
// On supprime le fichier
unlink($this->tempFilename);
}
}
public function getUploadDir()
{
// On retourne le chemin relatif vers l'image pour un navigateur
return 'uploads/img/videogame';
}
protected function getUploadRootDir()
{
// On retourne le chemin relatif vers l'image pour notre code PHP
return __DIR__.'/../../../../web/'.$this->getUploadDir();
}
/**
* Set videoGame
*
* @param VideoGame $videoGame
* @return Image
*/
public function setVideoGame(VideoGame $videoGame)
{
$this->videoGame = $videoGame;
return $this;
}
/**
* Get videoGame
*
* @return VideoGame
*/
public function getVideoGame()
{
return $this->videoGame;
}
}
Méthodes utilisées:
Je construis mes formulaires à l'aide de doctrine. J'ai construis mes vues et mon controlleur avec la génération de CRUD doctrine. Comme je souhaite pouvoir joindre plusieurs images à un jeu vidéo j'utilise l'option collection et add_allow dans mon VideoGameType.php . Et je génère des inputs de type file par javascript.
<?php
namespace NS\TestsBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use NS\HomeBundle\Form\ImageType;
class VideoGameType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('dateRelease', 'date', array(
'format' => 'ddMMyyyy',
'years' => range(intval(date('Y', strtotime('now +10 years'))), 1970)
))
->add('name', 'text')
->add('genres', 'entity', array(
'class' => 'NSTestsBundle:Genre',
'property' => 'name',
'multiple' => true
))
->add('engine', 'entity', array(
'class' => 'NSTestsBundle:Engine',
'property' => 'name'
))
->add('publisher', 'entity', array(
'class' => 'NSTestsBundle:Publisher',
'property' => 'name'
))
->add('studios', 'entity', array(
'class' => 'NSTestsBundle:Studio',
'property' => 'name',
'multiple' => true
))
->add('platforms', 'entity', array(
'class' => 'NSTestsBundle:Platform',
'property' => 'name',
'multiple' => true
))
->add('series', 'entity', array(
'class' => 'NSTestsBundle:Serie',
'property' => 'name',
'multiple' => true
))
->add('images', 'collection', array(
'type' => new ImageType(),
'allow_add' => true,
'allow_delete' => true
))
;
}
/**
* @param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'NS\TestsBundle\Entity\VideoGame'
));
}
/**
* @return string
*/
public function getName()
{
return 'ns_testsbundle_videogame';
}
}
Merci d'avance pour votre aide et votre temps.