Symfony2 form collection - symfony

I have this entity (Registro):
<?php
namespace Gitek\RegistroBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Gitek\RegistroBundle\Entity\Registro
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Gitek\RegistroBundle\Entity\RegistroRepository"))
*/
class Registro
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var datetime $fecha
*
* #ORM\Column(name="fecha", type="datetime")
*/
private $fecha;
/**
* #var smallint $comenzado
*
* #ORM\Column(name="comenzado", type="smallint", nullable=true)
*/
private $comenzado;
/**
* #var smallint $completado
*
* #ORM\Column(name="completado", type="smallint", nullable=true)
*/
private $completado;
/**
* #var datetime $created_at
*
* #ORM\Column(name="created_at", type="datetime")
*/
private $created_at;
/**
* #var datetime $updated_at
*
* #ORM\Column(name="updated_at", type="datetime")
*/
private $updated_at;
/** #ORM\ManyToOne(targetEntity="Gitek\UsuarioBundle\Entity\Usuario") */
protected $usuario;
/** #ORM\ManyToOne(targetEntity="Gitek\HotelBundle\Entity\Tipotarea") */
protected $tipotarea;
/** #ORM\ManyToOne(targetEntity="Gitek\HotelBundle\Entity\Habitacion") */
protected $habitacion;
/** #ORM\ManyToOne(targetEntity="Gitek\RegistroBundle\Entity\Master") */
protected $master;
/**
* #ORM\ManyToMany(targetEntity="Gitek\HotelBundle\Entity\Incidencia", inversedBy="registros")
* #ORM\JoinTable(name="incidencia_registro",
* joinColumns={#ORM\JoinColumn(name="registro_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="incidencia_id", referencedColumnName="id")}
* )
*/
protected $incidencias;
public function __construct()
{
$this->created_at = new \DateTime();
$this->updated_at = new \DateTime();
}
// public function __toString()
// {
// return $this->getNombre();
// }
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set fecha
*
* #param datetime $fecha
*/
public function setFecha($fecha)
{
$this->fecha = $fecha;
}
/**
* Get fecha
*
* #return datetime
*/
public function getFecha()
{
return $this->fecha;
}
/**
* Set comenzado
*
* #param smallint $comenzado
*/
public function setComenzado($comenzado)
{
$this->comenzado = $comenzado;
}
/**
* Get comenzado
*
* #return smallint
*/
public function getComenzado()
{
return $this->comenzado;
}
/**
* Set completado
*
* #param smallint $completado
*/
public function setCompletado($completado)
{
$this->completado = $completado;
}
/**
* Get completado
*
* #return smallint
*/
public function getCompletado()
{
return $this->completado;
}
/**
* Set created_at
*
* #param datetime $createdAt
*/
public function setCreatedAt($createdAt)
{
$this->created_at = $createdAt;
}
/**
* Get created_at
*
* #return datetime
*/
public function getCreatedAt()
{
return $this->created_at;
}
/**
* Set updated_at
*
* #param datetime $updatedAt
*/
public function setUpdatedAt($updatedAt)
{
$this->updated_at = $updatedAt;
}
/**
* Get updated_at
*
* #return datetime
*/
public function getUpdatedAt()
{
return $this->updated_at;
}
/**
* Set usuario
*
* #param Gitek\UsuarioBundle\Entity\Usuario $usuario
*/
public function setUsuario(\Gitek\UsuarioBundle\Entity\Usuario $usuario)
{
$this->usuario = $usuario;
}
/**
* Get usuario
*
* #return Gitek\UsuarioBundle\Entity\Usuario
*/
public function getUsuario()
{
return $this->usuario;
}
/**
* Set tipotarea
*
* #param Gitek\HotelBundle\Entity\Tipotarea $tipotarea
*/
public function setTipotarea(\Gitek\HotelBundle\Entity\Tipotarea $tipotarea)
{
$this->tipotarea = $tipotarea;
}
/**
* Get tipotarea
*
* #return Gitek\HotelBundle\Entity\Tipotarea
*/
public function getTipotarea()
{
return $this->tipotarea;
}
/**
* Set habitacion
*
* #param Gitek\HotelBundle\Entity\Habitacion $habitacion
*/
public function setHabitacion(\Gitek\HotelBundle\Entity\Habitacion $habitacion)
{
$this->habitacion = $habitacion;
}
/**
* Get habitacion
*
* #return Gitek\HotelBundle\Entity\Habitacion
*/
public function getHabitacion()
{
return $this->habitacion;
}
/**
* Add incidencias
*
* #param Gitek\HotelBundle\Entity\Incidencia $incidencias
*/
public function addIncidencia(\Gitek\HotelBundle\Entity\Incidencia $incidencias)
{
$this->incidencias[] = $incidencias;
}
/**
* Get incidencias
*
* #return Doctrine\Common\Collections\Collection
*/
public function getIncidencias()
{
return $this->incidencias;
}
}
I wanted to save multiple data on a row, so I created a new Entity with only an array propery like this (Master):
<?php
namespace Gitek\RegistroBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
class Master
{
/** #ORM\OneToMany(targetEntity="Gitek\HotelBundle\Entity\Habitacion", mappedBy="master") */
protected $registros;
public function __construct()
{
$this->registros = new ArrayCollection();
}
public function getRegistros()
{
return $this->registros;
}
public function setRegistros(ArrayCollection $registros)
{
$this->registros = $registros;
}
}
I created my MasterType like this:
<?php
namespace Gitek\RegistroBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use Doctrine\ORM\EntityRepository;
class MasterType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('registros', 'collection', array(
'type' => new RegistroType(),
'allow_add' => true,
'by_reference' => true,
));
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'Gitek\RegistroBundle\Entity\Master'
);
}
public function getName()
{
return 'master';
}
}
And this is my controller:
public function asignarAction()
{
$master = new Master();
$request = $this->getRequest();
$form = $this->createForm(new MasterType(), $master);
if ('POST' === $request->getMethod()) {
$form->bindRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($master);
$em->flush();
return $this->redirect($this->generateUrl('recepcion_asignar'));
}else {
print_r("ezez");
print_r($form->getErrors());
}
} else {
}
return $this->render('RegistroBundle:Recepcion:asignar.html.twig', array(
'registro' => $master,
'form' => $form->createView()
));
}
The form works ok, and I see the data is submited correctly but it is not persisted, I´m getting this error all the time:
Class Gitek\RegistroBundle\Entity\Master is not a valid entity or mapped super class.
I think that the problem is within de Master entity.
Any help or clue?

You missed the #Entity annotation on your Master class, you will also need a master table on your database for this to work.
If you don't want to create the master table, then you can skip the #Entity annotation, but you can not persist master. Instead you would have to iterate through the collection and persist only the entities in the array.

Related

Symfony relation OneToMany

I'm stuck with this problem. I try to upload one or more picture for one entity with a collection but when I submit my form I've got this error :
Symfony\Component\Validator\ConstraintViolation
Object(Symfony\Component\Form\Form).data.imagearticle[0].Article = null
It's because in my entity ImageArticle I put this :
namespace AD\PlatformBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints as Assert;
use AD\PlatformBundle\Entity\Article;
/**
* ImageArticle
*
* #ORM\Table()
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks
*/
class ImageArticle
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="url", type="string", length=255)
*/
private $url;
/**
* #var string
*
* #ORM\Column(name="alt", type="string", length=255)
*/
private $alt;
/**
* #var File
*
* #Assert\File(
* maxSize = "1M",
* mimeTypes = {
* "image/jpeg",
* "image/gif",
* "image/png",
* },
* maxSizeMessage = "La taille maximum du fichier doit etre inférieur ou égale à 1MB. Pour reduire sa taille vous pouvez utiliser le site : compressjpeg.com",
* mimeTypesMessage = "Seulement les fichiers .jpeg / .gif /.png sont acceptés"
* )
*/
private $file;
private $tempFileName;
/**
* #var Article
* #ORM\ManyToOne(targetEntity="AD\PlatformBundle\Entity\Article", inversedBy="imagearticle")
* #ORM\JoinColumn(nullable=false)
* #Assert\NotNull()
*/
private $Article;
public function getFile()
{
return $this->file;
}
public function setFile(UploadedFile $file)
{
$this->file = $file;
if (null !== $this->url)
{
$this->tempFileName = $this->url;
$this->url=null;
$this->alt=null;
}
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set url
*
* #param string $url
*
* #return ImageArticle
*/
public function setUrl($url)
{
$this->url = $url;
return $this;
}
/**
* Get url
*
* #return string
*/
public function getUrl()
{
return $this->url;
}
/**
* Set alt
*
* #param string $alt
*
* #return ImageArticle
*/
public function setAlt($alt)
{
$this->alt = $alt;
return $this;
}
/**
* Get alt
*
* #return string
*/
public function getAlt()
{
return $this->alt;
}
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
if (null === $this->file)
{
return;
}
//On add un extension pour le fichier.
$this->url = $this->file->guessExtension();
//Le alt est le nom du fichier du client.
$this->alt= $this->file->getClientOriginalName();
}
/**
*
* #ORM\PostPersist()
* #ORM\PostUpdate()
*
*/
public function upload()
{
if(null=== $this->file)
{
return;
}
//Si ancien fichier on supprime
if(null !== $this->tempFileName)
{
$oldFile = $this->getUploadRootDir().'/'.$this->id.'.'.$this->tempFileName;
if (file_exists($oldFile))
{
unlink($oldFile);
}
}
//On deplace
$this->file->move
(
$this->getUploadRootDir(),
$this->id.'.'.$this->url
);
// chmod($this->getUploadRootDir().'/'.$this->id.'.'.$this->url,644);
}
/**
*#ORM\PreRemove()
*/
public function preRemoveUpload()
{
$this->tempFileName = $this->getUploadRootDir().'/'.$this->id.'.'.$this->url;
}
/**
*
* #ORM\PostRemove()
*/
public function removeUpload()
{
if(file_exists($this->tempFileName))
{
unlink($this->tempFileName);
}
}
public function getUploadDir()
{
return 'upload/img/blog/';
}
protected function getUploadRootDir()
{
return __DIR__.'/../../../../web/'.$this->getUploadDir();
}
public function __toString()
{
return $this->getUploadDir().$this->id.'.'.$this->getUrl();
}
/**
* Set source
*
* #param string $source
*
* #return Image
*/
/**
* Set article
*
* #param \AD\PlatformBundle\Entity\Article $article
*
* #return ImageArticle
*/
public function setArticle(\AD\PlatformBundle\Entity\Article $article = null)
{
$this->article = $article;
return $this;
}
/**
* Get article
*
* #return \AD\PlatformBundle\Entity\Article
*/
public function getArticle()
{
dump($this->article);
return $this->article;
}
But when I remove this Assert\NotNull my error is "Column article_id cannot be null"
This is my Article entity :
<?php
namespace AD\PlatformBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Article
*
* #ORM\Table()
* #ORM\Entity
*/
class Article
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="titre1", type="string", length=255)
*/
private $titre1;
/**
* #var string
*
* #ORM\Column(name="titre2", type="string", length=255)
*/
private $titre2;
/**
* #var string
*
* #ORM\Column(name="description", type="text")
*/
private $description;
/**
* #ORM\ManyToMany(targetEntity="AD\UserBundle\Entity\User")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
/**
*
* #ORM\OneToMany(targetEntity="AD\PlatformBundle\Entity\ImageArticle", mappedBy="article", cascade="all", orphanRemoval=true)
* #Assert\Valid()
* #ORM\OrderBy({"position" = "ASC"})
*
*/
private $imagearticle;
/**
* #Gedmo\Slug(fields={"titre1"})
* #ORM\Column(length=128, unique=true)
*/
private $slugurl;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set titre1
*
* #param string $titre1
*
* #return Article
*/
public function setTitre1($titre1)
{
$this->titre1 = $titre1;
return $this;
}
/**
* Get titre1
*
* #return string
*/
public function getTitre1()
{
return $this->titre1;
}
/**
* Set titre2
*
* #param string $titre2
*
* #return Article
*/
public function setTitre2($titre2)
{
$this->titre2 = $titre2;
return $this;
}
/**
* Get titre2
*
* #return string
*/
public function getTitre2()
{
return $this->titre2;
}
/**
* Set description
*
* #param string $description
*
* #return Article
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Set user
*
* #param \AD\UserBundle\Entity\User $user
*
* #return Article
*/
public function setUser(\AD\UserBundle\Entity\User $user)
{
$this->user = $user;
return $this;
}
/**
* Get user
*
* #return \AD\UserBundle\Entity\User
*/
public function getUser()
{
return $this->user;
}
/**
* Set imagearticle
*
* #param \AD\PlatformBundle\Entity\ImageArticle $imagearticle
*
* #return Article
*/
public function setImagearticle(\AD\PlatformBundle\Entity\ImageArticle $imagearticle = null)
{
$this->imagearticle = $imagearticle;
return $this;
}
/**
* Get imagearticle
*
* #return \AD\PlatformBundle\Entity\ImageArticle
*/
public function getImagearticle()
{
return $this->imagearticle;
}
/**
* Constructor
*/
public function __construct()
{
$this->user = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Set slugurl
*
* #param string $slugurl
*
* #return Article
*/
public function setSlugurl($slugurl)
{
$this->slugurl = $slugurl;
return $this;
}
/**
* Get slugurl
*
* #return string
*/
public function getSlugurl()
{
return $this->slugurl;
}
/**
* Add user
*
* #param \AD\UserBundle\Entity\User $user
*
* #return Article
*/
public function addUser(\AD\UserBundle\Entity\User $user)
{
$this->user[] = $user;
return $this;
}
/**
* Remove user
*
* #param \AD\UserBundle\Entity\User $user
*/
public function removeUser(\AD\UserBundle\Entity\User $user)
{
$this->user->removeElement($user);
}
/**
* Add imagearticle
*
* #param \AD\PlatformBundle\Entity\ImageArticle $imagearticle
*
* #return Article
*/
public function addImagearticle(\AD\PlatformBundle\Entity\ImageArticle $imagearticle)
{
$this->imagearticle[] = $imagearticle;
return $this;
}
/**
* Remove imagearticle
*
* #param \AD\PlatformBundle\Entity\ImageArticle $imagearticle
*/
public function removeImagearticle(\AD\PlatformBundle\Entity\ImageArticle $imagearticle)
{
$this->imagearticle->removeElement($imagearticle);
}
}
Thx for your help!
My controller :
public function newArticleAction(Request $request)
{
$article= new Article();
$form = $this->get('form.factory')->create(new \AD\PlatformBundle\Form\ArticleType(), $article);
if($form->handleRequest($request)->isValid())
{
$em = $this->getDoctrine()->getManager();
$em->persist($article);
$em->flush();
$request->getSession()->getFlashBag()->add('notice', 'Annonce enregistrée ! :)');
return $this->redirect($this->generateUrl('va_platform_blog'));
}
return $this->render('ADPlatformBundle:Cars:newarticle.html.twig', array(
'form' =>$form->createView(),
));
}
And my form ArticleType :
class ArticleType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('titre1')
->add('titre2')
->add('description', 'textarea', array('required' => false))
->add('imagearticle', 'collection', array(
'type' => new ImageArticleType(),
'allow_add' => true,
'allow_delete' => true,
'required' => true,
'by_reference' => false,
))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AD\PlatformBundle\Entity\Article'
));
}
/**
* #return string
*/
public function getName()
{
return 'ad_platformbundle_article';
}
}
ImageArticleType
namespace AD\PlatformBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class ImageArticleType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('file', 'file', array('label' => 'Choisir mon images'))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AD\PlatformBundle\Entity\ImageArticle'
));
}
/**
* #return string
*/
public function getName()
{
return 'ad_platformbundle_imagearticle';
}
Here is the solution.
In ImageArticle.php:
/**
* #var Article
* #ORM\ManyToOne(targetEntity="AD\PlatformBundle\Entity\Article", inversedBy="imagearticle")
* #ORM\JoinColumn(nullable=false)
* #Assert\NotNull()
*/
private $article; //previously $Article
In Article.php:
/**
*
* #ORM\OneToMany(targetEntity="AD\PlatformBundle\Entity\ImageArticle", mappedBy="article", cascade="all", orphanRemoval=true)
* #Assert\Valid()
*
*/
private $imagearticle; // remove #ORM\OrderBy({"position" = "ASC"}) since there is no position property
public function __construct()
{
$this->user = new \Doctrine\Common\Collections\ArrayCollection();
$this->imagearticle = new \Doctrine\Common\Collections\ArrayCollection(); //instanciate as an ArrayCollection when a new instance of Article is created
}
/**
* Add imagearticle
*
* #param \AD\PlatformBundle\Entity\ImageArticle $imagearticle
*
* #return Article
*/
public function addImagearticle(\AD\PlatformBundle\Entity\ImageArticle $imagearticle)
{
$imagearticle->setArticle($this); //since doctrine only checks owning side you need to do this to keep data in sync
$this->imagearticle[] = $imagearticle;
return $this;
}
/**
* Remove imagearticle
*
* #param \AD\PlatformBundle\Entity\ImageArticle $imagearticle
*/
public function removeImagearticle(\AD\PlatformBundle\Entity\ImageArticle $imagearticle)
{
$imagearticle->setArticle(); //same reason than above
$this->imagearticle->removeElement($imagearticle);
return $this;
}
I also recommend you to remove method setImagearticle() since image article is a collection and not an object, you should not have to use it.
Use camelCase to name your variable and method (so it should be $imageArticle and not imagearticle) this is a good practice to make your code more readable by others.
Finally use plural when you are working with collections (i.e. users instead of user) so you immediatly know what you are working with (another good practice).

Warning: spl_object_hash() expects parameter 1 to be object, string given

I have this issues "Warning: spl_object_hash() expects parameter 1 to be object, string given" when I try to save the onetomay entity side of my relationship, the code below may be give you some ideas:
<?php
namespace BackendBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* BulletinSalaire
*
* #ORM\Table(name="bulletin_salaire")
* #ORM\Entity(repositoryClass="BackendBundle\Repository\BulletinSalaireRepository")
*/
class BulletinSalaire
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var \DateTime
*
* #ORM\Column(name="periode", type="date")
*/
private $periode;
/**
* #var string
*
* #ORM\Column(name="mode_reglement", type="string", length=255)
*/
private $modeReglement;
/**
*
* #ORM\ManyToOne(targetEntity="Employee", inversedBy="employees", cascade={"persist"})
* #ORM\JoinColumn(name="id_employee", referencedColumnName="id")
*/
private $employee;
/**
*
* #ORM\OneToMany(targetEntity="LibelleBulletin", mappedBy="bulletinsalaire", cascade={"persist"})
* #ORM\JoinColumn(name="id_libelleBulltin", referencedColumnName="id")
*/
private $libelleBulletins;
public function __construct()
{
$this->libelleBulletins= new ArrayCollection();
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set libelle
*
* #param string $libelle
*
* #return BulletinSalaire
*/
public function setLibelle($libelle)
{
$this->libelle = $libelle;
return $this;
}
/**
* Get libelle
*
* #return string
*/
public function getLibelle()
{
return $this->libelle;
}
/**
* Set base
*
* #param integer $base
*
* #return BulletinSalaire
*/
public function setBase($base)
{
$this->base = $base;
return $this;
}
/**
* Get base
*
* #return int
*/
public function getBase()
{
return $this->base;
}
/**
* Set retenues
*
* #param float $retenues
*
* #return BulletinSalaire
*/
public function setRetenues($retenues)
{
$this->retenues = $retenues;
return $this;
}
/**
* Get retenues
*
* #return float
*/
public function getRetenues()
{
return $this->retenues;
}
/**
* Set periode
*
* #param \DateTime $periode
*
* #return BulletinSalaire
*/
public function setPeriode($periode)
{
$this->periode = $periode;
return $this;
}
/**
* Get periode
*
* #return \DateTime
*/
public function getPeriode()
{
return $this->periode;
}
/**
* Set modeReglement
*
* #param string $modeReglement
*
* #return BulletinSalaire
*/
public function setModeReglement($modeReglement)
{
$this->modeReglement = $modeReglement;
return $this;
}
/**
* Get modeReglement
*
* #return string
*/
public function getModeReglement()
{
return $this->modeReglement;
}
/**
* #return mixed
*/
public function getEmployee()
{
return $this->employee;
}
/**
* #param mixed $employee
*/
public function setEmployee($employee)
{
$this->employee = $employee;
}
/**
* #param LibelleBulletin $libellebulletin
* #return $this
*/
public function addLibelleBulletin(LibelleBulletin $libellebulletin)
{
$this->libelleBulletins[] = $libellebulletin;
$libellebulletin->setBulletinSalaire($this);
return $this;
}
/**
* #param LibelleBulletin $libellebulletin
*/
public function removeLibelleBulletin(LibelleBulletin $libellebulletin)
{
$this->libelleBulletins->removeElement($libellebulletin);
}
public function getLibelleBulletins()
{
return $this->libelleBulletins;
}
}
/---------------------Entity Many libelle to one bulletin de salaire ----------------/
<?php
/**
* Created by PhpStorm.
* User: achouri
* Date: 17/11/2016
* Time: 4:05 PM
*/
namespace BackendBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* BulletinSalaire
*
* #ORM\Table(name="libelle_bulletins")
* #ORM\Entity()
*/
class LibelleBulletin
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="BulletinSalaire", inversedBy="libellebulletins")
* #ORM\JoinColumn(nullable=false)
*/
private $bulletinSalaire;
/**
* #var string
*
* #ORM\Column(name="libelle", type="string", length=255)
*
*/
private $libelle;
/**
* #var int
*
* #ORM\Column(name="base", type="integer")
*/
private $base;
/**
* #var float
*
* #ORM\Column(name="retenues", type="float")
*/
private $retenues;
/**
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* #param int $id
*/
public function setId($id)
{
$this->id = $id;
}
/**
* #return string
*/
public function getLibelle()
{
return $this->libelle;
}
/**
* #param string $libelle
*/
public function setLibelle($libelle)
{
$this->libelle = $libelle;
}
/**
* #return int
*/
public function getBase()
{
return $this->base;
}
/**
* #param int $base
*/
public function setBase($base)
{
$this->base = $base;
}
/**
* #return float
*/
public function getRetenues()
{
return $this->retenues;
}
/**
* #param float $retenues
*/
public function setRetenues($retenues)
{
$this->retenues = $retenues;
}
/**
* #return mixed
*/
public function getBulletinSalaire()
{
return $this->bulletinSalaire;
}
/**
* #param mixed $bulletinSalaire
*/
public function setBulletinSalaire($bulletinSalaire)
{
$this->bulletinSalaire = $bulletinSalaire;
}
}
/---------------------bulletinsallaireType---------------------------/
<?php
namespace BackendBundle\Form;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use BackendBundle\Form\LibelleBulletinType;
class BulletinSalaireType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('libelleBulletins',LibelleBulletinType::class,array('label' => '','data_class' => null))
->add('periode')->add('modeReglement')
->add('employee',EntityType::class,array('attr' => array(
'class' => 'form-control'),
'class' => 'BackendBundle:Employee',
'choice_label' => function ($employee) {
return $employee->getNom()." ".$employee->getPrenom();
}));
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'BackendBundle\Entity\BulletinSalaire'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'backendbundle_bulletinsalaire';
}
}
/------------------------libelleBultinType-----------------------/
<?php
namespace BackendBundle\Form;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use BackendBundle\Form\EmployeeType;
class LibelleBulletinType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('libelle')->add('base')->add('retenues');
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'BackendBundle\Entity\LibelleBulletin'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'backendbundle_libellebulletin';
}
}
/----------------------Action du save-----------------/
/**
* Creates a new bulletinSalaire entity.
*
* #Route("/new", name="bulletinsalaire_new")
* #Method({"GET", "POST"})
*/
public function newAction(Request $request)
{
$bulletinSalaire = new Bulletinsalaire();
$libellebulletin= new LibelleBulletin();
$form = $this->createForm('BackendBundle\Form\BulletinSalaireType', $bulletinSalaire);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$bulletinSalaire->addLibelleBulletin($libellebulletin);
$em->persist($bulletinSalaire);
$em->flush($bulletinSalaire);
return $this->redirectToRoute('bulletinsalaire_show', array('id' => $bulletinSalaire->getId()));
}
return $this->render('BackendBundle:bulletinsalaire:new.html.twig', array(
'bulletinSalaire' => $bulletinSalaire,
'form' => $form->createView(),
));
}
As #xabbuh pointed, we need to see how do you create the object. Looks like you are trying to set $employee or $libelleBulletins with a string instead of an object.
This is wrong.
$bulletin->setEmployee('some value');
This is right.
$employee = $dm->getRepository('BackendBundle:Employee')->findOneById('employeeId');
$bulletin->setEmployee($employee);

Expected value of type "SourcingBundle\Entity\RequestForEstimate" for association field

I get the following error:
Expected value of type "SourcingBundle\Entity\RequestForEstimate" for
association field
"SourcingBundle\Entity\RequestForEstimateDetail#$detail", got
"Doctrine\Common\Collections\ArrayCollection" instead.
I'm trying to create a structure where there is a RequestForEstimate entity that may have multiple RequestForEstimateDetails nodes. Simply each row in the RequestForEstimateDetails will have a request for a different product and its details when RequestForEstimate only the common information. I'm new to Symfony and I struggle at the point where I want to save the parent element together with the children in the DB. I have been following this Symfony tutorial: How to Embed a Collection of Forms
Here is my code:
SourcingBundle\Entity\RequestForEstimate:
<?php
namespace SourcingBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* RequestForEstimate
*
* #ORM\Table(name="request_for_estimate")
* #ORM\Entity(repositoryClass="SourcingBundle\Repository\RequestForEstimateRepository")
*/
class RequestForEstimate
{
/**
* #var int
*
* #ORM\Column(name="request_id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var int
*
* #ORM\Column(name="status", type="integer")
*/
private $status;
/**
* #var \DateTime
*
* #ORM\Column(name="create_time", type="datetime")
*/
private $createTime;
/**
* #var \DateTime
*
* #ORM\Column(name="update_time", type="datetime")
*/
private $updateTime;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="RequestForEstimateDetail", mappedBy="detail", cascade={"persist", "remove"}, orphanRemoval=true)
*/
private $details;
/**
* Get id
*
* #return int
*/
/**
* Constructor
*/
public function __construct()
{
$this->details = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set status
*
* #param integer $status
*
* #return RequestForEstimate
*/
public function setStatus($status)
{
$this->status = $status;
return $this;
}
/**
* Get status
*
* #return integer
*/
public function getStatus()
{
return $this->status;
}
/**
* Set createTime
*
* #param \DateTime $createTime
*
* #return RequestForEstimate
*/
public function setCreateTime($createTime)
{
$this->createTime = $createTime;
return $this;
}
/**
* Get createTime
*
* #return \DateTime
*/
public function getCreateTime()
{
return $this->createTime;
}
/**
* Set updateTime
*
* #param \DateTime $updateTime
*
* #return RequestForEstimate
*/
public function setUpdateTime($updateTime)
{
$this->updateTime = $updateTime;
return $this;
}
/**
* Get updateTime
*
* #return \DateTime
*/
public function getUpdateTime()
{
return $this->updateTime;
}
/**
* Set name
*
* #param string $name
*
* #return RequestForEstimate
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Add detail
*
* #param \SourcingBundle\Entity\RequestForEstimateDetail $detail
*
* #return RequestForEstimate
*/
public function addDetail(\SourcingBundle\Entity\RequestForEstimateDetail $detail)
{
$this->details[] = $detail;
return $this;
}
/**
* Remove detail
*
* #param \SourcingBundle\Entity\RequestForEstimateDetail $detail
*/
public function removeDetail(\SourcingBundle\Entity\RequestForEstimateDetail $detail)
{
$this->details->removeElement($detail);
}
/**
* Get details
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getDetails()
{
return $this->details;
}
}
SourcingBundle\Entity\RequestForEstimateDetail:
<?php
namespace SourcingBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* RequestForEstimateDetail
*
* #ORM\Table(name="request_for_estimate_detail")
* #ORM\Entity(repositoryClass="SourcingBundle\Repository\RequestForEstimateDetailRepository")
*/
class RequestForEstimateDetail
{
/**
* #var int
*
* #ORM\Column(name="request_detail_id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="RequestForEstimate", inversedBy="details")
* #ORM\JoinColumn(name="request_id", referencedColumnName="request_id")
*/
private $detail;
/**
* #var int
*
* #ORM\Column(name="product_id", type="integer")
*/
private $productId;
/**
* #var int
*
* #ORM\Column(name="quantity", type="integer")
*/
private $quantity;
/**
* #var string
*
* #ORM\Column(name="price_per_unit", type="decimal", precision=2, scale=0)
*/
private $pricePerUnit;
/**
* #var string
*
* #ORM\Column(name="shipping_cost", type="decimal", precision=2, scale=0)
*/
private $shippingCost;
/**
* #var string
*
* #ORM\Column(name="other_fees", type="decimal", precision=2, scale=0)
*/
private $otherFees;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set product
*
* #param integer $product
*
* #return RequestForEstimateDetail
*/
public function setProduct($product)
{
$this->product = $product;
return $this;
}
/**
* Get product
*
* #return int
*/
public function getProduct()
{
return $this->product;
}
/**
* Set quantity
*
* #param integer $quantity
*
* #return RequestForEstimateDetail
*/
public function setQuantity($quantity)
{
$this->quantity = $quantity;
return $this;
}
/**
* Get quantity
*
* #return int
*/
public function getQuantity()
{
return $this->quantity;
}
/**
* Set pricePerUnit
*
* #param string $pricePerUnit
*
* #return RequestForEstimateDetail
*/
public function setPricePerUnit($pricePerUnit)
{
$this->pricePerUnit = $pricePerUnit;
return $this;
}
/**
* Get pricePerUnit
*
* #return string
*/
public function getPricePerUnit()
{
return $this->pricePerUnit;
}
/**
* Set shippingCost
*
* #param string $shippingCost
*
* #return RequestForEstimateDetail
*/
public function setShippingCost($shippingCost)
{
$this->shippingCost = $shippingCost;
return $this;
}
/**
* Get shippingCost
*
* #return string
*/
public function getShippingCost()
{
return $this->shippingCost;
}
/**
* Set otherFees
*
* #param string $otherFees
*
* #return RequestForEstimateDetail
*/
public function setOtherFees($otherFees)
{
$this->otherFees = $otherFees;
return $this;
}
/**
* Get otherFees
*
* #return string
*/
public function getOtherFees()
{
return $this->otherFees;
}
/**
* Set requestId
*
* #param \SourcingBundle\Entity\RequestForEstimate $requestId
*
* #return RequestForEstimateDetail
*/
public function setRequestId(\SourcingBundle\Entity\RequestForEstimate $requestId = null)
{
$this->requestId = $requestId;
return $this;
}
/**
* Get requestId
*
* #return \SourcingBundle\Entity\RequestForEstimate
*/
public function getRequestId()
{
return $this->requestId;
}
/**
* Set productId
*
* #param \SourcingBundle\Entity\Product $productId
*
* #return RequestForEstimateDetail
*/
public function setProductId(\SourcingBundle\Entity\Product $productId = null)
{
$this->productId = $productId;
return $this;
}
/**
* Get productId
*
* #return \SourcingBundle\Entity\Product
*/
public function getProductId()
{
return $this->productId;
}
/**
* Set detail
*
* #param \SourcingBundle\Entity\RequestForEstimate $detail
*
* #return RequestForEstimateDetail
*/
public function setDetail(\SourcingBundle\Entity\RequestForEstimate $detail = null)
{
$this->detail = $detail;
return $this;
}
/**
* Get detail
*
* #return \SourcingBundle\Entity\RequestForEstimate
*/
public function getDetail()
{
return $this->detail;
}
/**
* Constructor
*/
public function __construct()
{
$this->detail = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add detail
*
* #param \SourcingBundle\Entity\RequestForEstimate $detail
*
* #return RequestForEstimateDetail
*/
public function addDetail(\SourcingBundle\Entity\RequestForEstimate $detail)
{
$this->detail[] = $detail;
return $this;
}
/**
* Remove detail
*
* #param \SourcingBundle\Entity\RequestForEstimate $detail
*/
public function removeDetail(\SourcingBundle\Entity\RequestForEstimate $detail)
{
$this->detail->removeElement($detail);
}
}
My controller:
<?php
namespace SourcingBundle\Controller;
use SourcingBundle\Entity\RequestForEstimate;
use SourcingBundle\Entity\RequestForEstimateDetail;
use SourcingBundle\Form\Type\RequestForEstimateType;
use SourcingBundle\Form\Type\RequestForEstimateDetailType;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class RequestForEstimateController extends Controller
{
/**
* #Route("sourcing/request-for-estimate", name="request_for_estimate")
*/
public function addRequest(Request $request)
{
$RequestForEstimate = new RequestForEstimate();
$form = $this->createForm(RequestForEstimateType::class, $RequestForEstimate);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$request = $form->getData();
foreach($form->get('details')->getData() as $detail)
{
$request->addDetail($detail);
}
print_r($form->get('details')->getData());
$request->setStatus(0);
$request->setupdateTime(new \DateTime());
$request->setcreateTime(new \DateTime());
$em = $this->getDoctrine()->getManager();
$em->persist($request);
$em->flush();
return $this->redirectToRoute('RequestsForEstimate');
}
return $this->render('sourcing/requestforestimate/create.html.twig', array(
'form' => $form->createView(),
));
}
}
My forms:
<?php
namespace SourcingBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
class RequestForEstimateType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name');
$builder->add('details', CollectionType::class, array(
'entry_type' => RequestForEstimateDetailType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
));
$builder->add('save', SubmitType::class, array('label' => 'Create Request'));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'SourcingBundle\Entity\RequestForEstimate',
));
}
}
<?php
namespace SourcingBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class RequestForEstimateDetailType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('quantity');
$builder->add('pricePerUnit');
$builder->add('shippingCost');
$builder->add('otherFees');
// $builder->add('product');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'SourcingBundle\Entity\RequestForEstimateDetail',
));
}
}
In the RequestForEstimateDetail the getter/setter method of the field detail are for manage the collection itself (the add/remove methods are for the single element of the collection), so modify the getter/setter method to handle an ArrayCollection object.
As Example:
RequestForEstimateDetail
/**
* Set detail
*
* #param \Doctrine\Common\Collections\ArrayCollection an array of $detail
*
* #return RequestForEstimateDetail
*/
public function setDetail(\Doctrine\Common\Collections\ArrayCollection $detail = null)
{
$this->detail = $detail;
return $this;
}
/**
* Get detail
*
* #return \Doctrine\Common\Collections\ArrayCollection
*/
public function getDetail()
{
return $this->detail;
}
Hope this help
Well, the existing addDetail method in the RequestForEstimate entity did the trick:
public function addDetail(\SourcingBundle\Entity\RequestForEstimateDetail $detail)
{
$this->details->add($detail);
$detail->setDetail($this);
return $this;
}

Symfony 2 Form Validation: missing name of property in validation message

I don't understand why some constraints does not insert name of property in error message after validation. I have this entity class:
<?php
namespace AC\OperaBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use AC\UserBundle\Entity\Utente;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Class Episodio
* #package AC\OperaBundle\Entity
* #ORM\Entity(repositoryClass="AC\OperaBundle\Repository\EpisodioRepository")
* * #ORM\Table(
* name="ac_Episodio",
* uniqueConstraints={#ORM\UniqueConstraint(name="unique_idx", columns={"opera", "numero_episodio", "extra"})},
* indexes={ #ORM\Index(name="opera_idx", columns={"opera"}),
* #ORM\Index(name="numero_episodio_idx", columns={"numero_episodio"}),
* #ORM\Index(name="extra_idx", columns={"extra"}),
* #ORM\Index(name="attivo_idx", columns={"attivo"}) })
*/
class Episodio {
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var Opera
*
* #ORM\ManyToOne(targetEntity="AC\OperaBundle\Entity\Opera", inversedBy="episodi")
* #ORM\JoinColumn(name="opera", referencedColumnName="id", nullable=false)
*/
protected $opera;
/**
* #var string
*
* #ORM\Column(name="numero_episodio", type="string", length=5, nullable=false)
*/
protected $numero_episodio;
/**
* #var string
*
* #ORM\Column(name="extra", type="string", length=30, nullable=false)
*/
protected $extra;
/**
* #var string
*
* #ORM\Column(name="titolo_italiano", type="string", length=150, nullable=false)
* #Assert\NotBlank()
*/
protected $titolo_italiano;
/**
* #var string
*
* #ORM\Column(name="titolo_originale", type="string", length=150, nullable=false)
* #Assert\NotBlank()
*/
protected $titolo_originale;
/**
* #var \DateTime
*
* #ORM\Column(name="data_ita", type="date", nullable=true)
* #Assert\Date()
*/
protected $data_ita;
/**
* #var \DateTime
*
* #ORM\Column(name="data_jap", type="date", nullable=true)
* #Assert\Date()
*/
protected $data_jap;
/**
* #var int
*
* #ORM\Column(name="durata", type="smallint", nullable=false, options={"default" = 0})
*/
protected $durata;
/**
* #var string
*
* #ORM\Column(name="trama", type="text", nullable=false)
*/
protected $trama;
/**
* #var int
*
* #ORM\Column(name="ordine", type="smallint", nullable=false, options={"default" = 0})
*/
protected $ordine;
/**
* #var int
*
* #ORM\Column(name="promosso", type="integer", nullable=false, options={"default" = 0})
*/
protected $promosso;
/**
* #var int
*
* #ORM\Column(name="rimandato", type="integer", nullable=false, options={"default" = 0})
*/
protected $rimandato;
/**
* #var int
*
* #ORM\Column(name="bocciato", type="integer", nullable=false, options={"default" = 0})
*/
protected $bocciato;
/**
* #var boolean
*
* #ORM\Column(name="fansub", type="boolean", nullable=false)
*/
protected $fansub;
/**
* #var boolean
*
* #ORM\Column(name="attivo", type="boolean", nullable=false)
*/
protected $attivo;
/**
* #var \DateTime
*
* #ORM\Column(name="data_aggiornamento", type="date")
*/
protected $data_aggiornamento;
/**
* #var Utente
*
* #ORM\ManyToOne(targetEntity="AC\UserBundle\Entity\Utente")
* #ORM\JoinColumn(name="utente_aggiornamento", referencedColumnName="id", nullable=true)
*/
protected $utente_aggiornamento;
/**
* #param boolean $attivo
*/
public function setAttivo($attivo)
{
$this->attivo = $attivo;
}
/**
* #return boolean
*/
public function getAttivo()
{
return $this->attivo;
}
/**
* #param int $bocciato
*/
public function setBocciato($bocciato)
{
$this->bocciato = $bocciato;
}
/**
* #return int
*/
public function getBocciato()
{
return $this->bocciato;
}
/**
* #param \DateTime $data_aggiornamento
*/
public function setDataAggiornamento($data_aggiornamento)
{
$this->data_aggiornamento = $data_aggiornamento;
}
/**
* #return \DateTime
*/
public function getDataAggiornamento()
{
return $this->data_aggiornamento;
}
/**
* #param \DateTime $data_ita
*/
public function setDataIta($data_ita)
{
$this->data_ita = $data_ita;
}
/**
* #return \DateTime
*/
public function getDataIta()
{
return $this->data_ita;
}
/**
* #param \DateTime $data_jap
*/
public function setDataJap($data_jap)
{
$this->data_jap = $data_jap;
}
/**
* #return \DateTime
*/
public function getDataJap()
{
return $this->data_jap;
}
/**
* #param int $durata
*/
public function setDurata($durata)
{
$this->durata = $durata;
}
/**
* #return int
*/
public function getDurata()
{
return $this->durata;
}
/**
* #param string $extra
*/
public function setExtra($extra)
{
$this->extra = $extra;
}
/**
* #return string
*/
public function getExtra()
{
return $this->extra;
}
/**
* #param boolean $fansub
*/
public function setFansub($fansub)
{
$this->fansub = $fansub;
}
/**
* #return boolean
*/
public function getFansub()
{
return $this->fansub;
}
/**
* #param int $id
*/
public function setId($id)
{
$this->id = $id;
}
/**
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* #param string $numero_episodio
*/
public function setNumeroEpisodio($numero_episodio)
{
$this->numero_episodio = $numero_episodio;
}
/**
* #return string
*/
public function getNumeroEpisodio()
{
return $this->numero_episodio;
}
/**
* #param \AC\OperaBundle\Entity\Opera $opera
*/
public function setOpera($opera)
{
$this->opera = $opera;
}
/**
* #return \AC\OperaBundle\Entity\Opera
*/
public function getOpera()
{
return $this->opera;
}
/**
* #param int $ordine
*/
public function setOrdine($ordine)
{
$this->ordine = $ordine;
}
/**
* #return int
*/
public function getOrdine()
{
return $this->ordine;
}
/**
* #param int $promosso
*/
public function setPromosso($promosso)
{
$this->promosso = $promosso;
}
/**
* #return int
*/
public function getPromosso()
{
return $this->promosso;
}
/**
* #param int $rimandato
*/
public function setRimandato($rimandato)
{
$this->rimandato = $rimandato;
}
/**
* #return int
*/
public function getRimandato()
{
return $this->rimandato;
}
/**
* #param string $titolo_italiano
*/
public function setTitoloItaliano($titolo_italiano)
{
$this->titolo_italiano = $titolo_italiano;
}
/**
* #return string
*/
public function getTitoloItaliano()
{
return $this->titolo_italiano;
}
/**
* #param string $titolo_originale
*/
public function setTitoloOriginale($titolo_originale)
{
$this->titolo_originale = $titolo_originale;
}
/**
* #return string
*/
public function getTitoloOriginale()
{
return $this->titolo_originale;
}
/**
* #param string $trama
*/
public function setTrama($trama)
{
$this->trama = $trama;
}
/**
* #return string
*/
public function getTrama()
{
return $this->trama;
}
/**
* #param \AC\UserBundle\Entity\Utente $utente_aggiornamento
*/
public function setUtenteAggiornamento($utente_aggiornamento)
{
$this->utente_aggiornamento = $utente_aggiornamento;
}
/**
* #return \AC\UserBundle\Entity\Utente
*/
public function getUtenteAggiornamento()
{
return $this->utente_aggiornamento;
}
}
In the controller perform the classi call at the $form->isValid() method to check validation. If there are errors i call $form->getErrorsAsString() and this is the result:
ERROR: This value should not be blank.
ERROR: This value should not be blank.
numeroEpisodio:
No errors
titoloItaliano:
No errors
titoloOriginale:
No errors
dataIta:
ERROR: This value is not valid.
dataJap:
ERROR: This value is not valid.
durata:
No errors
trama:
No errors
extra:
No errors
fansub:
No errors
The property that use #Assert\NotBlank() dose not put property name in error message! And for this reason i have the first two error line with e generic:
ERROR: This value should not be blank.
ERROR: This value should not be blank.
I i don't know who is the property that failed the validation. I look in the source code of Symfony Component and i see that for not blank constraint:
/**
* #author Bernhard Schussek <bschussek#gmail.com>
*
* #api
*/
class NotBlankValidator extends ConstraintValidator
{
/**
* {#inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof NotBlank) {
throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\NotBlank');
}
if (false === $value || (empty($value) && '0' != $value)) {
$this->context->addViolation($constraint->message);
}
}
}
And for data constraint:
/**
* #author Bernhard Schussek <bschussek#gmail.com>
*
* #api
*/
class DateValidator extends ConstraintValidator
{
const PATTERN = '/^(\d{4})-(\d{2})-(\d{2})$/';
/**
* {#inheritdoc}
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof Date) {
throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Date');
}
if (null === $value || '' === $value || $value instanceof \DateTime) {
return;
}
if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString'))) {
throw new UnexpectedTypeException($value, 'string');
}
$value = (string) $value;
if (!preg_match(static::PATTERN, $value, $matches) || !checkdate($matches[2], $matches[3], $matches[1])) {
$this->context->addViolation($constraint->message, array('{{ value }}' => $value));
}
}
}
The important difference is $this->context->addViolation($constraint->message); VS $this->context->addViolation($constraint->message, array('{{ value }}' => $value));
Why?
This is core part of controller
if ($request->getMethod() == 'POST') {
try {
$form->handleRequest($request);
if ($form->isValid()) {
/* #var $item Episodio */
$item = $form->getData();
$em->persist($item);
$em->flush();
$msg = new Message(true, Message::OK_MESSAGE);
} else {
$msg = new Message(false, Message::KO_MESSAGE);
$errors = Utility::getErrorMessages($form);
$msg->setData($errors);
}
} catch (\Exception $ex) {
$msg = new Message(false, $ex->getMessage());
}
return new Response($this->get('jms_serializer')->serialize($msg, 'json'));
}
This is utility class that fetch error from form
class Utility {
static function getErrorMessages(\Symfony\Component\Form\Form $form) {
$errors = array();
foreach ($form->getErrors() as $key => $error) {
$template = $error->getMessageTemplate();
$parameters = $error->getMessageParameters();
foreach($parameters as $var => $value){
$template = str_replace($var, $value, $template);
}
$errors[$key] = $template;
}
//if ($form->hasChildren()) {
foreach ($form->all() as $child) {
if (!$child->isValid()) {
$errors[$child->getName()] = Utility::getErrorMessages($child);
}
}
//}
return $errors;
}
}
Form Class
class EpisodioForm extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('numeroEpisodio', 'text');
$builder->add('titoloItaliano', 'text',array());
$builder->add('titoloOriginale', 'text');
$builder->add('dataIta', 'date', array('widget' => 'single_text', 'format' => 'dd/MM/yyyy'));
$builder->add('dataJap', 'date', array('widget' => 'single_text', 'format' => 'dd/MM/yyyy'));
$builder->add('durata', 'text');
$builder->add('trama', 'textarea');
$builder->add('extra', 'text');
$builder->add('fansub', 'checkbox');
}
/**
* Returns the name of this type.
*
* #return string The name of this type
*/
public function getName()
{
return "episodio_form";
}
}
The version of framework is Symfony 2.4
Why $this->context->addViolation($constraint->message); VS $this->context->addViolation($constraint->message, array('{{ value }}' => $value));?
This is because the date constraint validator shows the value which failed to validate in the error message, e.g.
45.04.2014 is not a valid date.
whereas the NotBlank constraint validator doesn't need to, since the value causing the error is always empty.
Putting the property name in the error message
This could be achieved:
by changing the annotations for the constraints in your entity to something like:
class Episodio {
/**
* #Assert\NotBlank(message="'titolo_italiano' should not be blank.")
*/
protected $titolo_italiano;
/**
* #Assert\NotBlank(message="'titolo_originale' should not be blank.")
*/
protected $titolo_originale;
}
I left out the column definitions and the other fields in the entity for the sake of readability.
OR by defining a custom validator which passes the name of the property to the error message:
The constraint:
class MyCustomNotBlank extends NotBlank
{
public $message = "'{{ propertyName }}' should not be blank.";
}
The validator:
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class MyCustomNotBlankValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
if (!$value) {
$this->context->addViolation(
$constraint->message,
array('{{ propertyName}}' => $this->context->getPropertyPath())
);
}
}
}
This shows you how to define a custom validation constraint:
http://symfony.com/doc/current/cookbook/validation/custom_constraint.html
Obtaining a key-value array with property name and error message
Afaik, there is no function in Symfony2's form component which does that, so it has to be implemented.
See https://stackoverflow.com/a/13763053/277106 . You can add that function to a service for reusability. In your (AJAX) controller you can then do:
public function someAction(Request $request)
{
$errors = array();
$form = $this->createForm(new MyType);
if (!$form->bindRequest($request)->isValid()) {
$errors = $this->myFormService->getErrorMessages($form);
}
else {
// save the entity
}
return JsonResponse($errors);
}

Symfony2/Doctrine2 OneToMany related entities do not have ID of the inverse side

I have 2 related entities, Invoice & InvoiceRow, and I have a form to let people create new invoices.
I also have some fixtures of test data that I load to seed the database with test data and no matter how I persist the entities to the database the invoice_id in InvoiceRow is always null
In the fixtures I've tried persisting the Invoice
InvoiceRow.php:
namespace Settleup\InvoiceBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
Class InvoiceRow {
/**
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #ORM\Column(type="string", length=100)
*/
protected $description;
/**
* The invoice row amount, stored in "bottle tops", ie £1.00 is 10000 bottle tops
*
* #ORM\Column(type="integer")
*/
protected $amount;
/**
* The invoice this row belongs to.
*
* #ORM\ManyToOne(targetEntity="Settleup\InvoiceBundle\Entity\Invoice", inversedBy="rows")
* #var integer
**/
protected $invoice;
public function setDescription($description) {
$this->description = $description;
}
public function getDescription() {
return $this->description;
}
public function setAmount($amount) {
$this->amount = $amount * 10000;
}
public function getAmount() {
return $this->amount / 10000;
}
/**
* Returns the invoice this row is attached to.
*
* #return Settleup\InvoiceBundle\Entity\Invoice The invoice.
*/
public function getInvoice() {
return $this->invoice;
}
/**
* Set the invoice this row belongs to.
*
* #param Settleup\InvoiceBundle\Entity\Invoice $newinvoice The invoice.
*/
public function setInvoice($invoice) {
$this->invoice = $invoice;
return $this;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
}
Invoice.php
<?php
namespace Settleup\InvoiceBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Sylius\Bundle\AddressingBundle\Model\AddressInterface;
use Settleup\InvoiceBundle\Exception\MerchantUpdateException;
/**
* #ORM\Entity
*/
Class Invoice {
/**
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #ORM\Column(type="string", length=100)
*/
protected $reference;
/**
* #ORM\Column(type="datetime")
*/
protected $issueDate;
/**
* #ORM\OneToMany(targetEntity="Settleup\InvoiceBundle\Entity\InvoiceRow", mappedBy="invoice", cascade="persist")
*/
protected $rows;
/**
* #ORM\ManyToOne(targetEntity="Settleup\UserBundle\Entity\User")
*/
protected $merchant;
/**
* #ORM\ManyToOne(targetEntity="Settleup\InvoiceBundle\Entity\InvoiceAddress", cascade={"persist"})
*/
protected $invoiceAddress;
/**
* undocumented function
*
* #return void
* #author
**/
public function __construct()
{
$this->rows = new ArrayCollection();
$this->issueDate = new \DateTime();
}
/**
* undocumented function
*
* #return void
* #author
**/
public function getId()
{
return $this->id;
}
/**
* Returns all the rows on this invoice.
*
* #return ArrayCollection
* #author
**/
public function getRows()
{
return $this->rows;
}
/**
* Set the reference for this invoice.
*
* #return void
* #author
**/
public function setReference($reference)
{
$this->reference = $reference;
}
/**
* Returnt he invoice reference.
*
* #return string
* #author
**/
public function getReference()
{
return $this->reference;
}
/**
* Sets the date this invoice was issued.
*
* #param \DateTime $issueDate The date the invoice was issued.
* #return void
* #author
**/
public function setIssueDate(\DateTime $issueDate)
{
$this->issueDate = $issueDate;
}
/**
* Return the date the invoice was raised.
*
* #return \DateTime
* #author
**/
public function getIssueDate()
{
return $this->issueDate;
}
/**
* Sets the collections of rows that make up this invoice.
*
* #return void
* #author
**/
public function setRows(ArrayCollection $rows)
{
$this->rows = $rows;
}
/**
* Return the merchant for this invoice.
*
* #return Settleup\UserBundle\Entity\User
* #author
**/
public function getMerchant()
{
return $this->merchant;
}
/**
* Returns an instance of SyliusAddress, the address this invoice is for.
*
* #return Settleup\InvoiceBundle\Entity\InvoiceAddress
* #author
**/
public function getInvoiceAddress()
{
return $this->invoiceAddress;
}
/**
* Sets the address for this invoice
*
* #param Settleup\InvoiceBundle\Entity\InvoiceAddress invoiceAddress The address this invoice is for.
*
* #return void
* #author
**/
public function setInvoiceAddress(AddressInterface $invoiceAddress)
{
$this->invoiceAddress = $invoiceAddress;
}
/**
* Add rows
*
* #param \Settleup\InvoiceBundle\Entity\InvoiceRow $rows
* #return Invoice
*/
public function addRow(\Settleup\InvoiceBundle\Entity\InvoiceRow $rows)
{
$this->rows[] = $rows;
return $this;
}
/**
* Remove rows
*
* #param \Settleup\InvoiceBundle\Entity\InvoiceRow $rows
*/
public function removeRow(\Settleup\InvoiceBundle\Entity\InvoiceRow $rows)
{
$this->rows->removeElement($rows);
}
/**
* Set merchant
*
* #param \Settleup\UserBundle\Entity\User $merchant
* #return Invoice
*/
public function setMerchant(\Settleup\UserBundle\Entity\User $merchant = null)
{
if ($this->id == null) {
$this->merchant = $merchant;
} else {
throw new MerchantUpdateException("Unable to update the merchant for this invoice, you can only assign a merchant when creating the invoice.");
}
return $this;
}
/**
* #TODO Find out why this method is needed.
*
* #return void
* #author
**/
public function getAddresses()
{
}
public function setAddresses() {}
}
I've got a form that I use to populate rows in both tables:
class InvoiceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('reference')
->add('rows', 'collection', array(
'type' => 'invoice_row',
'allow_add' => true,
'by_reference' => false
))
->add('invoiceAddress', 'sylius_address');
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function(FormEvent $event) {
$form = $event->getForm();
$form->add('merchant', 'hidden');
}
);
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Settleup\InvoiceBundle\Entity\Invoice',
'cascade_validation' => true
));
}
public function getName()
{
return 'settleup_invoicebundle_invoicetype';
}
}
When that form is saved using an action on the controller:
public function createAction(Request $request)
{
$entity = new Invoice();
$form = $this->createForm(new InvoiceType(), $entity);
$form->bind($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$entity->setMerchant($this->getUser());
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('invoice_show', array('id' => $entity->getId())));
} else {
}
return array(
'entity' => $entity,
'form' => $form->createView(),
);
}
both rows have all the correct data except InvoiceRow is missing the invoice_id.
It's a bit late but may be useful for others
1) Try persisting the owning side (the one with FK) i.e. invoiceRow
2) I had issues persisting a related entity because the id field didn't follow the defaults. Adding this made it work:
* #ORM\ManyToOne(targetEntity="Personas", fetch="EAGER", inversedBy="cuentasCorriente",
* cascade={"persist", "refresh"})
* #ORM\JoinColumn(name="persona_id", referencedColumnName="ID", nullable=false)
referencedColumnName="ID" see "ID" != "id"
as I added Doctrine to a legacy system, had no choice.

Resources